forked from DGNum/gestioCOF
Merge branch 'master' into aureplop/kfet_open
This commit is contained in:
commit
ec59bc2edc
100 changed files with 3401 additions and 2508 deletions
104
bda/admin.py
104
bda/admin.py
|
@ -13,32 +13,76 @@ from bda.models import Spectacle, Salle, Participant, ChoixSpectacle,\
|
|||
Attribution, Tirage, Quote, CategorieSpectacle, SpectacleRevente
|
||||
|
||||
|
||||
class ReadOnlyMixin(object):
|
||||
readonly_fields_update = ()
|
||||
|
||||
def get_readonly_fields(self, request, obj=None):
|
||||
readonly_fields = super().get_readonly_fields(request, obj)
|
||||
if obj is None:
|
||||
return readonly_fields
|
||||
else:
|
||||
return readonly_fields + self.readonly_fields_update
|
||||
|
||||
|
||||
class ChoixSpectacleInline(admin.TabularInline):
|
||||
model = ChoixSpectacle
|
||||
sortable_field_name = "priority"
|
||||
|
||||
|
||||
class AttributionTabularAdminForm(forms.ModelForm):
|
||||
listing = None
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
spectacles = Spectacle.objects.select_related('location')
|
||||
if self.listing is not None:
|
||||
spectacles = spectacles.filter(listing=self.listing)
|
||||
self.fields['spectacle'].queryset = spectacles
|
||||
|
||||
|
||||
class WithoutListingAttributionTabularAdminForm(AttributionTabularAdminForm):
|
||||
listing = False
|
||||
|
||||
|
||||
class WithListingAttributionTabularAdminForm(AttributionTabularAdminForm):
|
||||
listing = True
|
||||
|
||||
|
||||
class AttributionInline(admin.TabularInline):
|
||||
model = Attribution
|
||||
extra = 0
|
||||
listing = None
|
||||
|
||||
def get_queryset(self, request):
|
||||
qs = super(AttributionInline, self).get_queryset(request)
|
||||
return qs.filter(spectacle__listing=False)
|
||||
qs = super().get_queryset(request)
|
||||
if self.listing is not None:
|
||||
qs.filter(spectacle__listing=self.listing)
|
||||
return qs
|
||||
|
||||
|
||||
class AttributionInlineListing(admin.TabularInline):
|
||||
model = Attribution
|
||||
class WithListingAttributionInline(AttributionInline):
|
||||
form = WithListingAttributionTabularAdminForm
|
||||
listing = True
|
||||
|
||||
|
||||
class WithoutListingAttributionInline(AttributionInline):
|
||||
exclude = ('given', )
|
||||
extra = 0
|
||||
|
||||
def get_queryset(self, request):
|
||||
qs = super(AttributionInlineListing, self).get_queryset(request)
|
||||
return qs.filter(spectacle__listing=True)
|
||||
form = WithoutListingAttributionTabularAdminForm
|
||||
listing = False
|
||||
|
||||
|
||||
class ParticipantAdmin(admin.ModelAdmin):
|
||||
inlines = [AttributionInline, AttributionInlineListing]
|
||||
class ParticipantAdminForm(forms.ModelForm):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.fields['choicesrevente'].queryset = (
|
||||
Spectacle.objects
|
||||
.select_related('location')
|
||||
)
|
||||
|
||||
|
||||
class ParticipantAdmin(ReadOnlyMixin, admin.ModelAdmin):
|
||||
inlines = [WithListingAttributionInline, WithoutListingAttributionInline]
|
||||
|
||||
def get_queryset(self, request):
|
||||
return Participant.objects.annotate(nb_places=Count('attributions'),
|
||||
|
@ -65,6 +109,8 @@ class ParticipantAdmin(admin.ModelAdmin):
|
|||
actions_on_bottom = True
|
||||
list_per_page = 400
|
||||
readonly_fields = ("total",)
|
||||
readonly_fields_update = ('user', 'tirage')
|
||||
form = ParticipantAdminForm
|
||||
|
||||
def send_attribs(self, request, queryset):
|
||||
datatuple = []
|
||||
|
@ -94,6 +140,20 @@ class ParticipantAdmin(admin.ModelAdmin):
|
|||
|
||||
|
||||
class AttributionAdminForm(forms.ModelForm):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
if 'spectacle' in self.fields:
|
||||
self.fields['spectacle'].queryset = (
|
||||
Spectacle.objects
|
||||
.select_related('location')
|
||||
)
|
||||
if 'participant' in self.fields:
|
||||
self.fields['participant'].queryset = (
|
||||
Participant.objects
|
||||
.select_related('user', 'tirage')
|
||||
)
|
||||
|
||||
def clean(self):
|
||||
cleaned_data = super(AttributionAdminForm, self).clean()
|
||||
participant = cleaned_data.get("participant")
|
||||
|
@ -106,7 +166,7 @@ class AttributionAdminForm(forms.ModelForm):
|
|||
return cleaned_data
|
||||
|
||||
|
||||
class AttributionAdmin(admin.ModelAdmin):
|
||||
class AttributionAdmin(ReadOnlyMixin, admin.ModelAdmin):
|
||||
def paid(self, obj):
|
||||
return obj.participant.paid
|
||||
paid.short_description = 'A payé'
|
||||
|
@ -116,6 +176,7 @@ class AttributionAdmin(admin.ModelAdmin):
|
|||
'participant__user__first_name',
|
||||
'participant__user__last_name')
|
||||
form = AttributionAdminForm
|
||||
readonly_fields_update = ('spectacle', 'participant')
|
||||
|
||||
|
||||
class ChoixSpectacleAdmin(admin.ModelAdmin):
|
||||
|
@ -160,6 +221,24 @@ class SalleAdmin(admin.ModelAdmin):
|
|||
search_fields = ('name', 'address')
|
||||
|
||||
|
||||
class SpectacleReventeAdminForm(forms.ModelForm):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.fields['answered_mail'].queryset = (
|
||||
Participant.objects
|
||||
.select_related('user', 'tirage')
|
||||
)
|
||||
self.fields['seller'].queryset = (
|
||||
Participant.objects
|
||||
.select_related('user', 'tirage')
|
||||
)
|
||||
self.fields['soldTo'].queryset = (
|
||||
Participant.objects
|
||||
.select_related('user', 'tirage')
|
||||
)
|
||||
|
||||
|
||||
class SpectacleReventeAdmin(admin.ModelAdmin):
|
||||
"""
|
||||
Administration des reventes de spectacles
|
||||
|
@ -182,6 +261,7 @@ class SpectacleReventeAdmin(admin.ModelAdmin):
|
|||
|
||||
actions = ['transfer', 'reinit']
|
||||
actions_on_bottom = True
|
||||
form = SpectacleReventeAdminForm
|
||||
|
||||
def transfer(self, request, queryset):
|
||||
"""
|
||||
|
|
|
@ -22,8 +22,7 @@ class Algorithm(object):
|
|||
show.requests
|
||||
- on crée des tables de demandes pour chaque personne, afin de
|
||||
pouvoir modifier les rankings"""
|
||||
self.max_group = \
|
||||
2 * choices.aggregate(Max('priority'))['priority__max']
|
||||
self.max_group = 2*max(choice.priority for choice in choices)
|
||||
self.shows = []
|
||||
showdict = {}
|
||||
for show in shows:
|
||||
|
|
84
bda/forms.py
84
bda/forms.py
|
@ -1,35 +1,40 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django import forms
|
||||
from django.forms.models import BaseInlineFormSet
|
||||
from django.utils import timezone
|
||||
|
||||
from bda.models import Attribution, Spectacle
|
||||
|
||||
|
||||
class BaseBdaFormSet(BaseInlineFormSet):
|
||||
def clean(self):
|
||||
"""Checks that no two articles have the same title."""
|
||||
super(BaseBdaFormSet, self).clean()
|
||||
if any(self.errors):
|
||||
# Don't bother validating the formset unless each form is valid on
|
||||
# its own
|
||||
return
|
||||
spectacles = []
|
||||
for i in range(0, self.total_form_count()):
|
||||
form = self.forms[i]
|
||||
if not form.cleaned_data:
|
||||
continue
|
||||
spectacle = form.cleaned_data['spectacle']
|
||||
delete = form.cleaned_data['DELETE']
|
||||
if not delete and spectacle in spectacles:
|
||||
raise forms.ValidationError(
|
||||
"Vous ne pouvez pas vous inscrire deux fois pour le "
|
||||
"même spectacle.")
|
||||
spectacles.append(spectacle)
|
||||
class InscriptionInlineFormSet(BaseInlineFormSet):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
# self.instance is a Participant object
|
||||
tirage = self.instance.tirage
|
||||
|
||||
# set once for all "spectacle" field choices
|
||||
# - restrict choices to the spectacles of this tirage
|
||||
# - force_choices avoid many db requests
|
||||
spectacles = tirage.spectacle_set.select_related('location')
|
||||
choices = [(sp.pk, str(sp)) for sp in spectacles]
|
||||
self.force_choices('spectacle', choices)
|
||||
|
||||
def force_choices(self, name, choices):
|
||||
"""Set choices of a field.
|
||||
|
||||
As ModelChoiceIterator (default use to get choices of a
|
||||
ModelChoiceField), it appends an empty selection if requested.
|
||||
|
||||
"""
|
||||
for form in self.forms:
|
||||
field = form.fields[name]
|
||||
if field.empty_label is not None:
|
||||
field.choices = [('', field.empty_label)] + choices
|
||||
else:
|
||||
field.choices = choices
|
||||
|
||||
|
||||
class TokenForm(forms.Form):
|
||||
|
@ -38,7 +43,7 @@ class TokenForm(forms.Form):
|
|||
|
||||
class AttributionModelMultipleChoiceField(forms.ModelMultipleChoiceField):
|
||||
def label_from_instance(self, obj):
|
||||
return "%s" % obj.spectacle
|
||||
return "%s" % str(obj.spectacle)
|
||||
|
||||
|
||||
class ResellForm(forms.Form):
|
||||
|
@ -50,9 +55,13 @@ class ResellForm(forms.Form):
|
|||
|
||||
def __init__(self, participant, *args, **kwargs):
|
||||
super(ResellForm, self).__init__(*args, **kwargs)
|
||||
self.fields['attributions'].queryset = participant.attribution_set\
|
||||
.filter(spectacle__date__gte=timezone.now())\
|
||||
self.fields['attributions'].queryset = (
|
||||
participant.attribution_set
|
||||
.filter(spectacle__date__gte=timezone.now())
|
||||
.exclude(revente__seller=participant)
|
||||
.select_related('spectacle', 'spectacle__location',
|
||||
'participant__user')
|
||||
)
|
||||
|
||||
|
||||
class AnnulForm(forms.Form):
|
||||
|
@ -64,11 +73,15 @@ class AnnulForm(forms.Form):
|
|||
|
||||
def __init__(self, participant, *args, **kwargs):
|
||||
super(AnnulForm, self).__init__(*args, **kwargs)
|
||||
self.fields['attributions'].queryset = participant.attribution_set\
|
||||
self.fields['attributions'].queryset = (
|
||||
participant.attribution_set
|
||||
.filter(spectacle__date__gte=timezone.now(),
|
||||
revente__isnull=False,
|
||||
revente__notif_sent=False,
|
||||
revente__soldTo__isnull=True)
|
||||
.select_related('spectacle', 'spectacle__location',
|
||||
'participant__user')
|
||||
)
|
||||
|
||||
|
||||
class InscriptionReventeForm(forms.Form):
|
||||
|
@ -79,8 +92,11 @@ class InscriptionReventeForm(forms.Form):
|
|||
|
||||
def __init__(self, tirage, *args, **kwargs):
|
||||
super(InscriptionReventeForm, self).__init__(*args, **kwargs)
|
||||
self.fields['spectacles'].queryset = tirage.spectacle_set.filter(
|
||||
date__gte=timezone.now())
|
||||
self.fields['spectacles'].queryset = (
|
||||
tirage.spectacle_set
|
||||
.select_related('location')
|
||||
.filter(date__gte=timezone.now())
|
||||
)
|
||||
|
||||
|
||||
class SoldForm(forms.Form):
|
||||
|
@ -93,7 +109,9 @@ class SoldForm(forms.Form):
|
|||
super(SoldForm, self).__init__(*args, **kwargs)
|
||||
self.fields['attributions'].queryset = (
|
||||
participant.attribution_set
|
||||
.filter(revente__isnull=False,
|
||||
revente__soldTo__isnull=False)
|
||||
.exclude(revente__soldTo=participant)
|
||||
.filter(revente__isnull=False,
|
||||
revente__soldTo__isnull=False)
|
||||
.exclude(revente__soldTo=participant)
|
||||
.select_related('spectacle', 'spectacle__location',
|
||||
'participant__user')
|
||||
)
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
{% block realcontent %}
|
||||
|
||||
<h2>Revente de place</h2>
|
||||
{% with resell_attributions=resellform.attributions annul_attributions=annulform.attributions sold_attributions=soldform.attributions %}
|
||||
|
||||
{% if resellform.attributions %}
|
||||
<h3>Places non revendues</h3>
|
||||
<form class="form-horizontal" action="" method="post">
|
||||
|
@ -15,14 +17,14 @@
|
|||
</form>
|
||||
{% endif %}
|
||||
<br>
|
||||
{% if annulform.attributions or overdue %}
|
||||
{% if annul_attributions or overdue %}
|
||||
<h3>Places en cours de revente</h3>
|
||||
<form action="" method="post">
|
||||
{% csrf_token %}
|
||||
<div class='form-group'>
|
||||
<div class='multiple-checkbox'>
|
||||
<ul>
|
||||
{% for attrib in annulform.attributions %}
|
||||
{% for attrib in annul_attributions %}
|
||||
<li>{{attrib.tag}} {{attrib.choice_label}}</li>
|
||||
{% endfor %}
|
||||
{% for attrib in overdue %}
|
||||
|
@ -31,13 +33,13 @@
|
|||
{{attrib.spectacle}}
|
||||
</li>
|
||||
{% endfor %}
|
||||
{% if annulform.attributions %}
|
||||
{% if annul_attributions %}
|
||||
<input type="submit" class="btn btn-primary" name="annul" value="Annuler les reventes sélectionnées">
|
||||
{% endif %}
|
||||
</form>
|
||||
{% endif %}
|
||||
<br>
|
||||
{% if soldform.attributions %}
|
||||
{% if sold_attributions %}
|
||||
<h3>Places revendues</h3>
|
||||
<form action="" method="post">
|
||||
{% csrf_token %}
|
||||
|
@ -46,8 +48,9 @@
|
|||
<button type="submit" class="btn btn-primary" name="reinit">Réinitialiser</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
{% if not resellform.attributions and not soldform.attributions and not overdue and not annulform.attributions %}
|
||||
{% if not resell_attributions and not annul_attributions and not overdue and not sold_attributions %}
|
||||
<p>Plus de reventes possibles !</p>
|
||||
{% endif %}
|
||||
|
||||
{% endwith %}
|
||||
{% endblock %}
|
||||
|
|
231
bda/views.py
231
bda/views.py
|
@ -1,5 +1,7 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from collections import defaultdict
|
||||
from functools import partial
|
||||
import random
|
||||
import hashlib
|
||||
import time
|
||||
|
@ -11,9 +13,9 @@ from custommail.shortcuts import (
|
|||
from django.shortcuts import render, get_object_or_404
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.contrib import messages
|
||||
from django.db import models, transaction
|
||||
from django.db import transaction
|
||||
from django.core import serializers
|
||||
from django.db.models import Count, Q, Sum
|
||||
from django.db.models import Count, Q, Prefetch
|
||||
from django.forms.models import inlineformset_factory
|
||||
from django.http import (
|
||||
HttpResponseBadRequest, HttpResponseRedirect, JsonResponse
|
||||
|
@ -29,8 +31,8 @@ from bda.models import (
|
|||
)
|
||||
from bda.algorithm import Algorithm
|
||||
from bda.forms import (
|
||||
BaseBdaFormSet, TokenForm, ResellForm, AnnulForm, InscriptionReventeForm,
|
||||
SoldForm
|
||||
TokenForm, ResellForm, AnnulForm, InscriptionReventeForm, SoldForm,
|
||||
InscriptionInlineFormSet,
|
||||
)
|
||||
|
||||
|
||||
|
@ -44,39 +46,44 @@ def etat_places(request, tirage_id):
|
|||
Et le total de toutes les demandes
|
||||
"""
|
||||
tirage = get_object_or_404(Tirage, id=tirage_id)
|
||||
spectacles1 = ChoixSpectacle.objects \
|
||||
.filter(spectacle__tirage=tirage) \
|
||||
.filter(double_choice="1") \
|
||||
.all() \
|
||||
.values('spectacle', 'spectacle__title') \
|
||||
.annotate(total=models.Count('spectacle'))
|
||||
spectacles2 = ChoixSpectacle.objects \
|
||||
.filter(spectacle__tirage=tirage) \
|
||||
.exclude(double_choice="1") \
|
||||
.all() \
|
||||
.values('spectacle', 'spectacle__title') \
|
||||
.annotate(total=models.Count('spectacle'))
|
||||
spectacles = tirage.spectacle_set.all()
|
||||
spectacles_dict = {}
|
||||
total = 0
|
||||
|
||||
spectacles = tirage.spectacle_set.select_related('location')
|
||||
spectacles_dict = {} # index of spectacle by id
|
||||
|
||||
for spectacle in spectacles:
|
||||
spectacle.total = 0
|
||||
spectacle.ratio = 0.0
|
||||
spectacle.total = 0 # init total requests
|
||||
spectacles_dict[spectacle.id] = spectacle
|
||||
for spectacle in spectacles1:
|
||||
spectacles_dict[spectacle["spectacle"]].total += spectacle["total"]
|
||||
spectacles_dict[spectacle["spectacle"]].ratio = \
|
||||
spectacles_dict[spectacle["spectacle"]].total / \
|
||||
spectacles_dict[spectacle["spectacle"]].slots
|
||||
total += spectacle["total"]
|
||||
for spectacle in spectacles2:
|
||||
spectacles_dict[spectacle["spectacle"]].total += 2*spectacle["total"]
|
||||
spectacles_dict[spectacle["spectacle"]].ratio = \
|
||||
spectacles_dict[spectacle["spectacle"]].total / \
|
||||
spectacles_dict[spectacle["spectacle"]].slots
|
||||
total += 2*spectacle["total"]
|
||||
|
||||
choices = (
|
||||
ChoixSpectacle.objects
|
||||
.filter(spectacle__in=spectacles)
|
||||
.values('spectacle')
|
||||
.annotate(total=Count('spectacle'))
|
||||
)
|
||||
|
||||
# choices *by spectacles* whose only 1 place is requested
|
||||
choices1 = choices.filter(double_choice="1")
|
||||
# choices *by spectacles* whose 2 places is requested
|
||||
choices2 = choices.exclude(double_choice="1")
|
||||
|
||||
for spectacle in choices1:
|
||||
pk = spectacle['spectacle']
|
||||
spectacles_dict[pk].total += spectacle['total']
|
||||
for spectacle in choices2:
|
||||
pk = spectacle['spectacle']
|
||||
spectacles_dict[pk].total += 2*spectacle['total']
|
||||
|
||||
# here, each spectacle.total contains the number of requests
|
||||
|
||||
slots = 0 # proposed slots
|
||||
total = 0 # requests
|
||||
for spectacle in spectacles:
|
||||
slots += spectacle.slots
|
||||
total += spectacle.total
|
||||
spectacle.ratio = spectacle.total / spectacle.slots
|
||||
|
||||
context = {
|
||||
"proposed": tirage.spectacle_set.aggregate(Sum('slots'))['slots__sum'],
|
||||
"proposed": slots,
|
||||
"spectacles": spectacles,
|
||||
"total": total,
|
||||
'tirage': tirage
|
||||
|
@ -94,11 +101,16 @@ def _hash_queryset(queryset):
|
|||
@cof_required
|
||||
def places(request, tirage_id):
|
||||
tirage = get_object_or_404(Tirage, id=tirage_id)
|
||||
participant, created = Participant.objects.get_or_create(
|
||||
user=request.user, tirage=tirage)
|
||||
places = participant.attribution_set.order_by(
|
||||
"spectacle__date", "spectacle").all()
|
||||
total = sum([place.spectacle.price for place in places])
|
||||
participant, _ = (
|
||||
Participant.objects
|
||||
.get_or_create(user=request.user, tirage=tirage)
|
||||
)
|
||||
places = (
|
||||
participant.attribution_set
|
||||
.order_by("spectacle__date", "spectacle")
|
||||
.select_related("spectacle", "spectacle__location")
|
||||
)
|
||||
total = sum(place.spectacle.price for place in places)
|
||||
filtered_places = []
|
||||
places_dict = {}
|
||||
spectacles = []
|
||||
|
@ -146,35 +158,31 @@ def inscription(request, tirage_id):
|
|||
messages.error(request, "Le tirage n'est pas encore ouvert : "
|
||||
"ouverture le {:s}".format(opening))
|
||||
return render(request, 'bda/resume-inscription-tirage.html', {})
|
||||
|
||||
participant, _ = (
|
||||
Participant.objects.select_related('tirage')
|
||||
.get_or_create(user=request.user, tirage=tirage)
|
||||
)
|
||||
|
||||
if timezone.now() > tirage.fermeture:
|
||||
# Le tirage est fermé.
|
||||
participant, created = Participant.objects.get_or_create(
|
||||
user=request.user, tirage=tirage)
|
||||
choices = participant.choixspectacle_set.order_by("priority").all()
|
||||
choices = participant.choixspectacle_set.order_by("priority")
|
||||
messages.error(request,
|
||||
" C'est fini : tirage au sort dans la journée !")
|
||||
return render(request, "bda/resume-inscription-tirage.html",
|
||||
{"choices": choices})
|
||||
|
||||
def formfield_callback(f, **kwargs):
|
||||
"""
|
||||
Fonction utilisée par inlineformset_factory ci dessous.
|
||||
Restreint les spectacles proposés aux spectacles du bo tirage.
|
||||
"""
|
||||
if f.name == "spectacle":
|
||||
kwargs['queryset'] = tirage.spectacle_set
|
||||
return f.formfield(**kwargs)
|
||||
BdaFormSet = inlineformset_factory(
|
||||
Participant,
|
||||
ChoixSpectacle,
|
||||
fields=("spectacle", "double_choice", "priority"),
|
||||
formset=BaseBdaFormSet,
|
||||
formfield_callback=formfield_callback)
|
||||
participant, created = Participant.objects.get_or_create(
|
||||
user=request.user, tirage=tirage)
|
||||
formset=InscriptionInlineFormSet,
|
||||
)
|
||||
|
||||
success = False
|
||||
stateerror = False
|
||||
if request.method == "POST":
|
||||
# use *this* queryset
|
||||
dbstate = _hash_queryset(participant.choixspectacle_set.all())
|
||||
if "dbstate" in request.POST and dbstate != request.POST["dbstate"]:
|
||||
stateerror = True
|
||||
|
@ -187,9 +195,14 @@ def inscription(request, tirage_id):
|
|||
formset = BdaFormSet(instance=participant)
|
||||
else:
|
||||
formset = BdaFormSet(instance=participant)
|
||||
# use *this* queryset
|
||||
dbstate = _hash_queryset(participant.choixspectacle_set.all())
|
||||
total_price = 0
|
||||
for choice in participant.choixspectacle_set.all():
|
||||
choices = (
|
||||
participant.choixspectacle_set
|
||||
.select_related('spectacle')
|
||||
)
|
||||
for choice in choices:
|
||||
total_price += choice.spectacle.price
|
||||
if choice.double:
|
||||
total_price += choice.spectacle.price
|
||||
|
@ -218,9 +231,9 @@ def do_tirage(tirage_elt, token):
|
|||
# Initialisation du dictionnaire data qui va contenir les résultats
|
||||
start = time.time()
|
||||
data = {
|
||||
'shows': tirage_elt.spectacle_set.select_related().all(),
|
||||
'shows': tirage_elt.spectacle_set.select_related('location'),
|
||||
'token': token,
|
||||
'members': tirage_elt.participant_set.all(),
|
||||
'members': tirage_elt.participant_set.select_related('user'),
|
||||
'total_slots': 0,
|
||||
'total_losers': 0,
|
||||
'total_sold': 0,
|
||||
|
@ -233,7 +246,7 @@ def do_tirage(tirage_elt, token):
|
|||
ChoixSpectacle.objects
|
||||
.filter(spectacle__tirage=tirage_elt)
|
||||
.order_by('participant', 'priority')
|
||||
.select_related().all()
|
||||
.select_related('participant', 'participant__user', 'spectacle')
|
||||
)
|
||||
results = Algorithm(data['shows'], data['members'], choices)(token)
|
||||
|
||||
|
@ -290,10 +303,30 @@ def do_tirage(tirage_elt, token):
|
|||
])
|
||||
|
||||
# On inscrit à BdA-Revente ceux qui n'ont pas eu les places voulues
|
||||
for (show, _, losers) in results:
|
||||
for (loser, _, _, _) in losers:
|
||||
loser.choicesrevente.add(show)
|
||||
loser.save()
|
||||
ChoixRevente = Participant.choicesrevente.through
|
||||
|
||||
# Suppression des reventes demandées/enregistrées (si le tirage est relancé)
|
||||
(
|
||||
ChoixRevente.objects
|
||||
.filter(spectacle__tirage=tirage_elt)
|
||||
.delete()
|
||||
)
|
||||
(
|
||||
SpectacleRevente.objects
|
||||
.filter(attribution__spectacle__tirage=tirage_elt)
|
||||
.delete()
|
||||
)
|
||||
|
||||
lost_by = defaultdict(set)
|
||||
for show, _, losers in results:
|
||||
for loser, _, _, _ in losers:
|
||||
lost_by[loser].add(show)
|
||||
|
||||
ChoixRevente.objects.bulk_create(
|
||||
ChoixRevente(participant=member, spectacle=show)
|
||||
for member, shows in lost_by.items()
|
||||
for show in shows
|
||||
)
|
||||
|
||||
data["duration"] = time.time() - start
|
||||
data["results"] = results
|
||||
|
@ -458,7 +491,6 @@ def list_revente(request, tirage_id):
|
|||
)
|
||||
if min_resell is not None:
|
||||
min_resell.answered_mail.add(participant)
|
||||
min_resell.save()
|
||||
inscrit_revente.append(spectacle)
|
||||
success = True
|
||||
else:
|
||||
|
@ -496,13 +528,13 @@ def buy_revente(request, spectacle_id):
|
|||
|
||||
# Si l'utilisateur veut racheter une place qu'il est en train de revendre,
|
||||
# on supprime la revente en question.
|
||||
if reventes.filter(seller=participant).exists():
|
||||
revente = reventes.filter(seller=participant)[0]
|
||||
revente.delete()
|
||||
own_reventes = reventes.filter(seller=participant)
|
||||
if len(own_reventes) > 0:
|
||||
own_reventes[0].delete()
|
||||
return HttpResponseRedirect(reverse("bda-shotgun",
|
||||
args=[tirage.id]))
|
||||
|
||||
reventes_shotgun = list(reventes.filter(shotgun=True).all())
|
||||
reventes_shotgun = reventes.filter(shotgun=True)
|
||||
|
||||
if not reventes_shotgun:
|
||||
return render(request, "bda-no-revente.html", {})
|
||||
|
@ -534,16 +566,21 @@ def buy_revente(request, spectacle_id):
|
|||
@login_required
|
||||
def revente_shotgun(request, tirage_id):
|
||||
tirage = get_object_or_404(Tirage, id=tirage_id)
|
||||
spectacles = tirage.spectacle_set.filter(
|
||||
date__gte=timezone.now())
|
||||
shotgun = []
|
||||
for spectacle in spectacles:
|
||||
reventes = SpectacleRevente.objects.filter(
|
||||
attribution__spectacle=spectacle,
|
||||
shotgun=True,
|
||||
soldTo__isnull=True)
|
||||
if reventes.exists():
|
||||
shotgun.append(spectacle)
|
||||
spectacles = (
|
||||
tirage.spectacle_set
|
||||
.filter(date__gte=timezone.now())
|
||||
.select_related('location')
|
||||
.prefetch_related(Prefetch(
|
||||
'attribues',
|
||||
queryset=(
|
||||
Attribution.objects
|
||||
.filter(revente__shotgun=True,
|
||||
revente__soldTo__isnull=True)
|
||||
),
|
||||
to_attr='shotguns',
|
||||
))
|
||||
)
|
||||
shotgun = [sp for sp in spectacles if len(sp.shotguns) > 0]
|
||||
|
||||
return render(request, "bda-shotgun.html",
|
||||
{"shotgun": shotgun})
|
||||
|
@ -553,7 +590,10 @@ def revente_shotgun(request, tirage_id):
|
|||
def spectacle(request, tirage_id, spectacle_id):
|
||||
tirage = get_object_or_404(Tirage, id=tirage_id)
|
||||
spectacle = get_object_or_404(Spectacle, id=spectacle_id, tirage=tirage)
|
||||
attributions = spectacle.attribues.all()
|
||||
attributions = (
|
||||
spectacle.attribues
|
||||
.select_related('participant', 'participant__user')
|
||||
)
|
||||
participants = {}
|
||||
for attrib in attributions:
|
||||
participant = attrib.participant
|
||||
|
@ -582,7 +622,10 @@ class SpectacleListView(ListView):
|
|||
|
||||
def get_queryset(self):
|
||||
self.tirage = get_object_or_404(Tirage, id=self.kwargs['tirage_id'])
|
||||
categories = self.tirage.spectacle_set.all()
|
||||
categories = (
|
||||
self.tirage.spectacle_set
|
||||
.select_related('location')
|
||||
)
|
||||
return categories
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
|
@ -595,9 +638,12 @@ class SpectacleListView(ListView):
|
|||
@buro_required
|
||||
def unpaid(request, tirage_id):
|
||||
tirage = get_object_or_404(Tirage, id=tirage_id)
|
||||
unpaid = tirage.participant_set \
|
||||
.annotate(nb_attributions=Count('attribution')) \
|
||||
.filter(paid=False, nb_attributions__gt=0).all()
|
||||
unpaid = (
|
||||
tirage.participant_set
|
||||
.annotate(nb_attributions=Count('attribution'))
|
||||
.filter(paid=False, nb_attributions__gt=0)
|
||||
.select_related('user')
|
||||
)
|
||||
return render(request, "bda-unpaid.html", {"unpaid": unpaid})
|
||||
|
||||
|
||||
|
@ -632,7 +678,11 @@ def send_rappel(request, spectacle_id):
|
|||
|
||||
def descriptions_spectacles(request, tirage_id):
|
||||
tirage = get_object_or_404(Tirage, id=tirage_id)
|
||||
shows_qs = tirage.spectacle_set
|
||||
shows_qs = (
|
||||
tirage.spectacle_set
|
||||
.select_related('location')
|
||||
.prefetch_related('quote_set')
|
||||
)
|
||||
category_name = request.GET.get('category', '')
|
||||
location_id = request.GET.get('location', '')
|
||||
if category_name:
|
||||
|
@ -643,7 +693,7 @@ def descriptions_spectacles(request, tirage_id):
|
|||
except ValueError:
|
||||
return HttpResponseBadRequest(
|
||||
"La variable GET 'location' doit contenir un entier")
|
||||
return render(request, 'descriptions.html', {'shows': shows_qs.all()})
|
||||
return render(request, 'descriptions.html', {'shows': shows_qs})
|
||||
|
||||
|
||||
def catalogue(request, request_type):
|
||||
|
@ -716,7 +766,11 @@ def catalogue(request, request_type):
|
|||
)
|
||||
tirage = get_object_or_404(Tirage, id=tirage_id)
|
||||
|
||||
shows_qs = tirage.spectacle_set
|
||||
shows_qs = (
|
||||
tirage.spectacle_set
|
||||
.select_related('location')
|
||||
.prefetch_related('quote_set')
|
||||
)
|
||||
if categories_id:
|
||||
shows_qs = shows_qs.filter(category__id__in=categories_id)
|
||||
if locations_id:
|
||||
|
@ -735,14 +789,15 @@ def catalogue(request, request_type):
|
|||
'vips': spectacle.vips,
|
||||
'description': spectacle.description,
|
||||
'slots_description': spectacle.slots_description,
|
||||
'quotes': list(Quote.objects.filter(spectacle=spectacle).values(
|
||||
'author', 'text')),
|
||||
'quotes': [dict(author=quote.author,
|
||||
text=quote.text)
|
||||
for quote in spectacle.quote_set.all()],
|
||||
'image': spectacle.getImgUrl(),
|
||||
'ext_link': spectacle.ext_link,
|
||||
'price': spectacle.price,
|
||||
'slots': spectacle.slots
|
||||
}
|
||||
for spectacle in shows_qs.all()
|
||||
for spectacle in shows_qs
|
||||
]
|
||||
return JsonResponse(data_return, safe=False)
|
||||
# Si la requête n'est pas de la forme attendue, on quitte avec une erreur
|
||||
|
|
1
cof/settings/.gitignore
vendored
Normal file
1
cof/settings/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
secret.py
|
|
@ -1,32 +1,41 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Django settings for cof project.
|
||||
Django common settings for cof project.
|
||||
|
||||
For more information on this file, see
|
||||
https://docs.djangoproject.com/en/1.8/topics/settings/
|
||||
|
||||
For the full list of settings and their values, see
|
||||
https://docs.djangoproject.com/en/1.8/ref/settings/
|
||||
Everything which is supposed to be identical between the production server and
|
||||
the local development server should be here.
|
||||
"""
|
||||
|
||||
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
|
||||
import os
|
||||
|
||||
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
# Database credentials
|
||||
try:
|
||||
from .secret import DBNAME, DBUSER, DBPASSWD
|
||||
except ImportError:
|
||||
# On the local development VM, theses credentials are in the environment
|
||||
DBNAME = os.environ["DBNAME"]
|
||||
DBUSER = os.environ["DBUSER"]
|
||||
DBPASSWD = os.environ["DBPASSWD"]
|
||||
except KeyError:
|
||||
raise RuntimeError("Secrets missing")
|
||||
|
||||
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
|
||||
# Other secrets
|
||||
try:
|
||||
from .secret import (
|
||||
SECRET_KEY, RECAPTCHA_PUBLIC_KEY, RECAPTCHA_PRIVATE_KEY, ADMINS,
|
||||
REDIS_PASSWD, REDIS_DB, REDIS_HOST, REDIS_PORT
|
||||
)
|
||||
except ImportError:
|
||||
raise RuntimeError("Secrets missing")
|
||||
|
||||
# Quick-start development settings - unsuitable for production
|
||||
# See https://docs.djangoproject.com/en/1.8/howto/deployment/checklist/
|
||||
|
||||
# SECURITY WARNING: keep the secret key used in production secret!
|
||||
SECRET_KEY = 'q()(zn4m63i%5cp4)f+ww4-28_w+ly3q9=6imw2ciu&_(5_4ah'
|
||||
BASE_DIR = os.path.dirname(
|
||||
os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
)
|
||||
|
||||
# SECURITY WARNING: don't run with debug turned on in production!
|
||||
DEBUG = True
|
||||
|
||||
# Application definition
|
||||
INSTALLED_APPS = (
|
||||
INSTALLED_APPS = [
|
||||
'gestioncof',
|
||||
'django.contrib.auth',
|
||||
'django.contrib.contenttypes',
|
||||
|
@ -41,16 +50,15 @@ INSTALLED_APPS = (
|
|||
'autocomplete_light',
|
||||
'captcha',
|
||||
'django_cas_ng',
|
||||
'debug_toolbar',
|
||||
'bootstrapform',
|
||||
'kfet',
|
||||
'channels',
|
||||
'widget_tweaks',
|
||||
'custommail',
|
||||
)
|
||||
'djconfig',
|
||||
]
|
||||
|
||||
MIDDLEWARE_CLASSES = (
|
||||
'debug_toolbar.middleware.DebugToolbarMiddleware',
|
||||
MIDDLEWARE_CLASSES = [
|
||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||
'django.middleware.common.CommonMiddleware',
|
||||
'django.middleware.csrf.CsrfViewMiddleware',
|
||||
|
@ -60,7 +68,8 @@ MIDDLEWARE_CLASSES = (
|
|||
'django.contrib.messages.middleware.MessageMiddleware',
|
||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||
'django.middleware.security.SecurityMiddleware',
|
||||
)
|
||||
'djconfig.middleware.DjConfigMiddleware',
|
||||
]
|
||||
|
||||
ROOT_URLCONF = 'cof.urls'
|
||||
|
||||
|
@ -78,25 +87,22 @@ TEMPLATES = [
|
|||
'django.core.context_processors.i18n',
|
||||
'django.core.context_processors.media',
|
||||
'django.core.context_processors.static',
|
||||
'djconfig.context_processors.config',
|
||||
'gestioncof.shared.context_processor',
|
||||
'kfet.context_processors.auth',
|
||||
'kfet.context_processors.kfet_open',
|
||||
'kfet.context_processors.config',
|
||||
],
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
# WSGI_APPLICATION = 'cof.wsgi.application'
|
||||
|
||||
# Database
|
||||
# https://docs.djangoproject.com/en/1.8/ref/settings/#databases
|
||||
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.mysql',
|
||||
'NAME': os.environ['DBNAME'],
|
||||
'USER': os.environ['DBUSER'],
|
||||
'PASSWORD': os.environ['DBPASSWD'],
|
||||
'NAME': DBNAME,
|
||||
'USER': DBUSER,
|
||||
'PASSWORD': DBPASSWD,
|
||||
'HOST': os.environ.get('DBHOST', 'localhost'),
|
||||
}
|
||||
}
|
||||
|
@ -115,19 +121,6 @@ USE_L10N = True
|
|||
|
||||
USE_TZ = True
|
||||
|
||||
|
||||
# Static files (CSS, JavaScript, Images)
|
||||
# https://docs.djangoproject.com/en/1.8/howto/static-files/
|
||||
|
||||
STATIC_URL = '/static/'
|
||||
STATIC_ROOT = '/var/www/static/'
|
||||
|
||||
# Media upload (through ImageField, SiteField)
|
||||
# https://docs.djangoproject.com/en/1.9/ref/models/fields/
|
||||
|
||||
MEDIA_ROOT = os.path.join(BASE_DIR, 'media/')
|
||||
MEDIA_URL = '/media/'
|
||||
|
||||
# Various additional settings
|
||||
SITE_ID = 1
|
||||
|
||||
|
@ -160,12 +153,6 @@ AUTHENTICATION_BACKENDS = (
|
|||
'kfet.backends.GenericTeamBackend',
|
||||
)
|
||||
|
||||
# LDAP_SERVER_URL = 'ldaps://ldap.spi.ens.fr:636'
|
||||
|
||||
# EMAIL_HOST="nef.ens.fr"
|
||||
|
||||
RECAPTCHA_PUBLIC_KEY = "DUMMY"
|
||||
RECAPTCHA_PRIVATE_KEY = "DUMMY"
|
||||
RECAPTCHA_USE_SSL = True
|
||||
|
||||
# Channels settings
|
||||
|
@ -174,28 +161,14 @@ CHANNEL_LAYERS = {
|
|||
"default": {
|
||||
"BACKEND": "asgi_redis.RedisChannelLayer",
|
||||
"CONFIG": {
|
||||
"hosts": [(os.environ.get("REDIS_HOST", "localhost"), 6379)],
|
||||
"hosts": [(
|
||||
"redis://:{passwd}@{host}:{port}/{db}"
|
||||
.format(passwd=REDIS_PASSWD, host=REDIS_HOST,
|
||||
port=REDIS_PORT, db=REDIS_DB)
|
||||
)],
|
||||
},
|
||||
"ROUTING": "cof.routing.channel_routing",
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def show_toolbar(request):
|
||||
"""
|
||||
On ne veut pas la vérification de INTERNAL_IPS faite par la debug-toolbar
|
||||
car cela interfère avec l'utilisation de Vagrant. En effet, l'adresse de la
|
||||
machine physique n'est pas forcément connue, et peut difficilement être
|
||||
mise dans les INTERNAL_IPS.
|
||||
"""
|
||||
if not DEBUG:
|
||||
return False
|
||||
if request.is_ajax():
|
||||
return False
|
||||
return True
|
||||
|
||||
DEBUG_TOOLBAR_CONFIG = {
|
||||
'SHOW_TOOLBAR_CALLBACK': show_toolbar,
|
||||
}
|
||||
|
||||
FORMAT_MODULE_PATH = 'cof.locale'
|
47
cof/settings/dev.py
Normal file
47
cof/settings/dev.py
Normal file
|
@ -0,0 +1,47 @@
|
|||
"""
|
||||
Django development settings for the cof project.
|
||||
The settings that are not listed here are imported from .common
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from .common import *
|
||||
|
||||
|
||||
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
|
||||
|
||||
DEBUG = True
|
||||
|
||||
|
||||
# ---
|
||||
# Apache static/media config
|
||||
# ---
|
||||
|
||||
STATIC_URL = '/static/'
|
||||
STATIC_ROOT = '/var/www/static/'
|
||||
|
||||
MEDIA_ROOT = os.path.join(BASE_DIR, 'media/')
|
||||
MEDIA_URL = '/media/'
|
||||
|
||||
|
||||
# ---
|
||||
# Debug tool bar
|
||||
# ---
|
||||
|
||||
def show_toolbar(request):
|
||||
"""
|
||||
On ne veut pas la vérification de INTERNAL_IPS faite par la debug-toolbar
|
||||
car cela interfère avec l'utilisation de Vagrant. En effet, l'adresse de la
|
||||
machine physique n'est pas forcément connue, et peut difficilement être
|
||||
mise dans les INTERNAL_IPS.
|
||||
"""
|
||||
return DEBUG
|
||||
|
||||
INSTALLED_APPS += ["debug_toolbar", "debug_panel"]
|
||||
MIDDLEWARE_CLASSES = (
|
||||
["debug_panel.middleware.DebugPanelMiddleware"]
|
||||
+ MIDDLEWARE_CLASSES
|
||||
)
|
||||
DEBUG_TOOLBAR_CONFIG = {
|
||||
'SHOW_TOOLBAR_CALLBACK': show_toolbar,
|
||||
}
|
26
cof/settings/prod.py
Normal file
26
cof/settings/prod.py
Normal file
|
@ -0,0 +1,26 @@
|
|||
"""
|
||||
Django development settings for the cof project.
|
||||
The settings that are not listed here are imported from .common
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from .common import *
|
||||
|
||||
|
||||
DEBUG = False
|
||||
|
||||
ALLOWED_HOSTS = [
|
||||
"cof.ens.fr",
|
||||
"www.cof.ens.fr",
|
||||
"dev.cof.ens.fr"
|
||||
]
|
||||
|
||||
STATIC_ROOT = os.path.join(os.path.dirname(BASE_DIR), "static")
|
||||
STATIC_URL = "/gestion/static/"
|
||||
MEDIA_ROOT = os.path.join(os.path.dirname(BASE_DIR), "media")
|
||||
MEDIA_URL = "/gestion/media/"
|
||||
|
||||
LDAP_SERVER_URL = "ldaps://ldap.spi.ens.fr:636"
|
||||
|
||||
EMAIL_HOST = "nef.ens.fr"
|
8
cof/settings/secret_example.py
Normal file
8
cof/settings/secret_example.py
Normal file
|
@ -0,0 +1,8 @@
|
|||
SECRET_KEY = 'q()(zn4m63i%5cp4)f+ww4-28_w+ly3q9=6imw2ciu&_(5_4ah'
|
||||
RECAPTCHA_PUBLIC_KEY = "DUMMY"
|
||||
RECAPTCHA_PRIVATE_KEY = "DUMMY"
|
||||
REDIS_PASSWD = "dummy"
|
||||
REDIS_PORT = 6379
|
||||
REDIS_DB = 0
|
||||
REDIS_HOST = "127.0.0.1"
|
||||
ADMINS = None
|
|
@ -82,6 +82,8 @@ urlpatterns = [
|
|||
url(r'^utile_cof/diff_cof$', gestioncof_views.liste_diffcof),
|
||||
url(r'^utile_bda/bda_revente$', gestioncof_views.liste_bdarevente),
|
||||
url(r'^k-fet/', include('kfet.urls')),
|
||||
# djconfig
|
||||
url(r"^config", gestioncof_views.ConfigUpdate.as_view())
|
||||
]
|
||||
|
||||
if 'debug_toolbar' in settings.INSTALLED_APPS:
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
default_app_config = 'gestioncof.apps.GestioncofConfig'
|
|
@ -1,9 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django import forms
|
||||
from django.contrib import admin
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
@ -18,13 +12,12 @@ from django.contrib.auth.admin import UserAdmin
|
|||
from django.core.urlresolvers import reverse
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.db.models import Q
|
||||
import django.utils.six as six
|
||||
|
||||
import autocomplete_light
|
||||
|
||||
|
||||
def add_link_field(target_model='', field='', link_text=six.text_type,
|
||||
desc_text=six.text_type):
|
||||
def add_link_field(target_model='', field='', link_text=str,
|
||||
desc_text=str):
|
||||
def add_link(cls):
|
||||
reverse_name = target_model or cls.model.__name__.lower()
|
||||
|
||||
|
@ -139,7 +132,6 @@ def ProfileInfo(field, short_description, boolean=False):
|
|||
|
||||
User.profile_login_clipper = FkeyLookup("profile__login_clipper",
|
||||
"Login clipper")
|
||||
User.profile_num = FkeyLookup("profile__num", "Numéro")
|
||||
User.profile_phone = ProfileInfo("phone", "Téléphone")
|
||||
User.profile_occupation = ProfileInfo("occupation", "Occupation")
|
||||
User.profile_departement = ProfileInfo("departement", "Departement")
|
||||
|
@ -166,10 +158,12 @@ class UserProfileAdmin(UserAdmin):
|
|||
is_cof.short_description = 'Membre du COF'
|
||||
is_cof.boolean = True
|
||||
|
||||
list_display = ('profile_num',) + UserAdmin.list_display \
|
||||
list_display = (
|
||||
UserAdmin.list_display
|
||||
+ ('profile_login_clipper', 'profile_phone', 'profile_occupation',
|
||||
'profile_mailing_cof', 'profile_mailing_bda',
|
||||
'profile_mailing_bda_revente', 'is_cof', 'is_buro', )
|
||||
)
|
||||
list_display_links = ('username', 'email', 'first_name', 'last_name')
|
||||
list_filter = UserAdmin.list_filter \
|
||||
+ ('profile__is_cof', 'profile__is_buro', 'profile__mailing_cof',
|
||||
|
@ -215,21 +209,17 @@ class UserProfileAdmin(UserAdmin):
|
|||
|
||||
|
||||
# FIXME: This is absolutely horrible.
|
||||
def user_unicode(self):
|
||||
def user_str(self):
|
||||
if self.first_name and self.last_name:
|
||||
return "%s %s (%s)" % (self.first_name, self.last_name, self.username)
|
||||
return "{} ({})".format(self.get_full_name(), self.username)
|
||||
else:
|
||||
return self.username
|
||||
if six.PY2:
|
||||
User.__unicode__ = user_unicode
|
||||
else:
|
||||
User.__str__ = user_unicode
|
||||
User.__str__ = user_str
|
||||
|
||||
|
||||
class EventRegistrationAdmin(admin.ModelAdmin):
|
||||
form = autocomplete_light.modelform_factory(EventRegistration, exclude=[])
|
||||
list_display = ('__unicode__' if six.PY2 else '__str__', 'event', 'user',
|
||||
'paid')
|
||||
list_display = ('__str__', 'event', 'user', 'paid')
|
||||
list_filter = ('paid',)
|
||||
search_fields = ('user__username', 'user__first_name', 'user__last_name',
|
||||
'user__email', 'event__title')
|
||||
|
|
14
gestioncof/apps.py
Normal file
14
gestioncof/apps.py
Normal file
|
@ -0,0 +1,14 @@
|
|||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class GestioncofConfig(AppConfig):
|
||||
name = 'gestioncof'
|
||||
verbose_name = "Gestion des adhérents du COF"
|
||||
|
||||
def ready(self):
|
||||
self.register_config()
|
||||
|
||||
def register_config(self):
|
||||
import djconfig
|
||||
from .forms import GestioncofConfigForm
|
||||
djconfig.register(GestioncofConfigForm)
|
|
@ -1,21 +1,15 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django import forms
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.contrib.auth.models import User
|
||||
from django.forms.widgets import RadioSelect, CheckboxSelectMultiple
|
||||
from django.forms.formsets import BaseFormSet, formset_factory
|
||||
from django.db.models import Max
|
||||
from django.core.validators import MinLengthValidator
|
||||
|
||||
from djconfig.forms import ConfigForm
|
||||
|
||||
from gestioncof.models import CofProfile, EventCommentValue, \
|
||||
CalendarSubscription, Club
|
||||
from gestioncof.widgets import TriStateCheckbox
|
||||
from gestioncof.shared import lock_table, unlock_table
|
||||
|
||||
from bda.models import Spectacle
|
||||
|
||||
|
@ -243,7 +237,6 @@ class RegistrationProfileForm(forms.ModelForm):
|
|||
self.fields['mailing_cof'].initial = True
|
||||
self.fields['mailing_bda'].initial = True
|
||||
self.fields['mailing_bda_revente'].initial = True
|
||||
self.fields['num'].widget.attrs['readonly'] = True
|
||||
|
||||
self.fields.keyOrder = [
|
||||
'login_clipper',
|
||||
|
@ -251,7 +244,6 @@ class RegistrationProfileForm(forms.ModelForm):
|
|||
'occupation',
|
||||
'departement',
|
||||
'is_cof',
|
||||
'num',
|
||||
'type_cotiz',
|
||||
'mailing_cof',
|
||||
'mailing_bda',
|
||||
|
@ -259,24 +251,9 @@ class RegistrationProfileForm(forms.ModelForm):
|
|||
'comments'
|
||||
]
|
||||
|
||||
def save(self, *args, **kw):
|
||||
instance = super(RegistrationProfileForm, self).save(*args, **kw)
|
||||
if instance.is_cof and not instance.num:
|
||||
# Generate new number
|
||||
try:
|
||||
lock_table(CofProfile)
|
||||
aggregate = CofProfile.objects.aggregate(Max('num'))
|
||||
instance.num = aggregate['num__max'] + 1
|
||||
instance.save()
|
||||
self.cleaned_data['num'] = instance.num
|
||||
self.data['num'] = instance.num
|
||||
finally:
|
||||
unlock_table(CofProfile)
|
||||
return instance
|
||||
|
||||
class Meta:
|
||||
model = CofProfile
|
||||
fields = ("login_clipper", "num", "phone", "occupation",
|
||||
fields = ("login_clipper", "phone", "occupation",
|
||||
"departement", "is_cof", "type_cotiz", "mailing_cof",
|
||||
"mailing_bda", "mailing_bda_revente", "comments")
|
||||
|
||||
|
@ -403,3 +380,16 @@ class ClubsForm(forms.Form):
|
|||
queryset=Club.objects.all(),
|
||||
widget=forms.CheckboxSelectMultiple,
|
||||
required=False)
|
||||
|
||||
|
||||
# ---
|
||||
# Announcements banner
|
||||
# TODO: move this to the `gestion` app once the supportBDS branch is merged
|
||||
# ---
|
||||
|
||||
class GestioncofConfigForm(ConfigForm):
|
||||
gestion_banner = forms.CharField(
|
||||
label=_("Announcements banner"),
|
||||
help_text=_("An empty banner disables annoucements"),
|
||||
max_length=2048
|
||||
)
|
||||
|
|
18
gestioncof/migrations/0011_remove_cofprofile_num.py
Normal file
18
gestioncof/migrations/0011_remove_cofprofile_num.py
Normal file
|
@ -0,0 +1,18 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('gestioncof', '0010_delete_custommail'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='cofprofile',
|
||||
name='num',
|
||||
),
|
||||
]
|
|
@ -1,11 +1,7 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from django.db import models
|
||||
from django.dispatch import receiver
|
||||
from django.contrib.auth.models import User
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.utils.encoding import python_2_unicode_compatible
|
||||
import django.utils.six as six
|
||||
from django.db.models.signals import post_save, post_delete
|
||||
|
||||
from gestioncof.petits_cours_models import choices_length
|
||||
|
@ -35,12 +31,10 @@ TYPE_COMMENT_FIELD = (
|
|||
)
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class CofProfile(models.Model):
|
||||
user = models.OneToOneField(User, related_name="profile")
|
||||
login_clipper = models.CharField("Login clipper", max_length=8, blank=True)
|
||||
is_cof = models.BooleanField("Membre du COF", default=False)
|
||||
num = models.IntegerField("Numéro d'adhérent", blank=True, default=0)
|
||||
phone = models.CharField("Téléphone", max_length=20, blank=True)
|
||||
occupation = models.CharField(_("Occupation"),
|
||||
default="1A",
|
||||
|
@ -72,7 +66,7 @@ class CofProfile(models.Model):
|
|||
verbose_name_plural = "Profils COF"
|
||||
|
||||
def __str__(self):
|
||||
return six.text_type(self.user.username)
|
||||
return self.user.username
|
||||
|
||||
|
||||
@receiver(post_save, sender=User)
|
||||
|
@ -86,7 +80,6 @@ def post_delete_user(sender, instance, *args, **kwargs):
|
|||
instance.user.delete()
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class Club(models.Model):
|
||||
name = models.CharField("Nom", max_length=200, unique=True)
|
||||
description = models.TextField("Description", blank=True)
|
||||
|
@ -98,7 +91,6 @@ class Club(models.Model):
|
|||
return self.name
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class Event(models.Model):
|
||||
title = models.CharField("Titre", max_length=200)
|
||||
location = models.CharField("Lieu", max_length=200)
|
||||
|
@ -115,10 +107,9 @@ class Event(models.Model):
|
|||
verbose_name = "Événement"
|
||||
|
||||
def __str__(self):
|
||||
return six.text_type(self.title)
|
||||
return self.title
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class EventCommentField(models.Model):
|
||||
event = models.ForeignKey(Event, related_name="commentfields")
|
||||
name = models.CharField("Champ", max_length=200)
|
||||
|
@ -130,10 +121,9 @@ class EventCommentField(models.Model):
|
|||
verbose_name = "Champ"
|
||||
|
||||
def __str__(self):
|
||||
return six.text_type(self.name)
|
||||
return self.name
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class EventCommentValue(models.Model):
|
||||
commentfield = models.ForeignKey(EventCommentField, related_name="values")
|
||||
registration = models.ForeignKey("EventRegistration",
|
||||
|
@ -144,7 +134,6 @@ class EventCommentValue(models.Model):
|
|||
return "Commentaire de %s" % self.commentfield
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class EventOption(models.Model):
|
||||
event = models.ForeignKey(Event, related_name="options")
|
||||
name = models.CharField("Option", max_length=200)
|
||||
|
@ -154,10 +143,9 @@ class EventOption(models.Model):
|
|||
verbose_name = "Option"
|
||||
|
||||
def __str__(self):
|
||||
return six.text_type(self.name)
|
||||
return self.name
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class EventOptionChoice(models.Model):
|
||||
event_option = models.ForeignKey(EventOption, related_name="choices")
|
||||
value = models.CharField("Valeur", max_length=200)
|
||||
|
@ -167,10 +155,9 @@ class EventOptionChoice(models.Model):
|
|||
verbose_name_plural = "Choix"
|
||||
|
||||
def __str__(self):
|
||||
return six.text_type(self.value)
|
||||
return self.value
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class EventRegistration(models.Model):
|
||||
user = models.ForeignKey(User)
|
||||
event = models.ForeignKey(Event)
|
||||
|
@ -184,11 +171,9 @@ class EventRegistration(models.Model):
|
|||
unique_together = ("user", "event")
|
||||
|
||||
def __str__(self):
|
||||
return "Inscription de %s à %s" % (six.text_type(self.user),
|
||||
six.text_type(self.event.title))
|
||||
return "Inscription de {} à {}".format(self.user, self.event.title)
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class Survey(models.Model):
|
||||
title = models.CharField("Titre", max_length=200)
|
||||
details = models.TextField("Détails", blank=True)
|
||||
|
@ -199,10 +184,9 @@ class Survey(models.Model):
|
|||
verbose_name = "Sondage"
|
||||
|
||||
def __str__(self):
|
||||
return six.text_type(self.title)
|
||||
return self.title
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class SurveyQuestion(models.Model):
|
||||
survey = models.ForeignKey(Survey, related_name="questions")
|
||||
question = models.CharField("Question", max_length=200)
|
||||
|
@ -212,10 +196,9 @@ class SurveyQuestion(models.Model):
|
|||
verbose_name = "Question"
|
||||
|
||||
def __str__(self):
|
||||
return six.text_type(self.question)
|
||||
return self.question
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class SurveyQuestionAnswer(models.Model):
|
||||
survey_question = models.ForeignKey(SurveyQuestion, related_name="answers")
|
||||
answer = models.CharField("Réponse", max_length=200)
|
||||
|
@ -224,10 +207,9 @@ class SurveyQuestionAnswer(models.Model):
|
|||
verbose_name = "Réponse"
|
||||
|
||||
def __str__(self):
|
||||
return six.text_type(self.answer)
|
||||
return self.answer
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class SurveyAnswer(models.Model):
|
||||
user = models.ForeignKey(User)
|
||||
survey = models.ForeignKey(Survey)
|
||||
|
@ -244,7 +226,6 @@ class SurveyAnswer(models.Model):
|
|||
self.survey.title)
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class CalendarSubscription(models.Model):
|
||||
token = models.UUIDField()
|
||||
user = models.OneToOneField(User)
|
||||
|
|
|
@ -12,28 +12,34 @@ from django.views.decorators.csrf import csrf_exempt
|
|||
from django.conf import settings
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.contrib import messages
|
||||
from django.db import transaction
|
||||
|
||||
from gestioncof.models import CofProfile
|
||||
from gestioncof.petits_cours_models import (
|
||||
PetitCoursDemande, PetitCoursAttribution, PetitCoursAttributionCounter,
|
||||
PetitCoursAbility, PetitCoursSubject
|
||||
PetitCoursAbility
|
||||
)
|
||||
from gestioncof.petits_cours_forms import DemandeForm, MatieresFormSet
|
||||
from gestioncof.decorators import buro_required
|
||||
from gestioncof.shared import lock_table, unlock_tables
|
||||
|
||||
|
||||
class DemandeListView(ListView):
|
||||
model = PetitCoursDemande
|
||||
queryset = (
|
||||
PetitCoursDemande.objects
|
||||
.prefetch_related('matieres')
|
||||
.order_by('traitee', '-id')
|
||||
)
|
||||
template_name = "petits_cours_demandes_list.html"
|
||||
paginate_by = 20
|
||||
|
||||
def get_queryset(self):
|
||||
return PetitCoursDemande.objects.order_by('traitee', '-id').all()
|
||||
|
||||
|
||||
class DemandeDetailView(DetailView):
|
||||
model = PetitCoursDemande
|
||||
queryset = (
|
||||
PetitCoursDemande.objects
|
||||
.prefetch_related('petitcoursattribution_set',
|
||||
'matieres')
|
||||
)
|
||||
template_name = "gestioncof/details_demande_petit_cours.html"
|
||||
context_object_name = "demande"
|
||||
|
||||
|
@ -268,17 +274,17 @@ def _traitement_post(request, demande):
|
|||
headers={'Reply-To': replyto}))
|
||||
connection = mail.get_connection(fail_silently=False)
|
||||
connection.send_messages(mails_to_send)
|
||||
lock_table(PetitCoursAttributionCounter, PetitCoursAttribution, User)
|
||||
for matiere in proposals:
|
||||
for rank, user in enumerate(proposals[matiere]):
|
||||
counter = PetitCoursAttributionCounter.objects.get(user=user,
|
||||
matiere=matiere)
|
||||
counter.count += 1
|
||||
counter.save()
|
||||
attrib = PetitCoursAttribution(user=user, matiere=matiere,
|
||||
demande=demande, rank=rank + 1)
|
||||
attrib.save()
|
||||
unlock_tables()
|
||||
with transaction.atomic():
|
||||
for matiere in proposals:
|
||||
for rank, user in enumerate(proposals[matiere]):
|
||||
counter = PetitCoursAttributionCounter.objects.get(
|
||||
user=user, matiere=matiere
|
||||
)
|
||||
counter.count += 1
|
||||
counter.save()
|
||||
attrib = PetitCoursAttribution(user=user, matiere=matiere,
|
||||
demande=demande, rank=rank + 1)
|
||||
attrib.save()
|
||||
demande.traitee = True
|
||||
demande.traitee_par = request.user
|
||||
demande.processed = datetime.now()
|
||||
|
@ -303,17 +309,15 @@ def inscription(request):
|
|||
profile.petits_cours_accept = "receive_proposals" in request.POST
|
||||
profile.petits_cours_remarques = request.POST["remarques"]
|
||||
profile.save()
|
||||
lock_table(PetitCoursAttributionCounter, PetitCoursAbility, User,
|
||||
PetitCoursSubject)
|
||||
abilities = (
|
||||
PetitCoursAbility.objects.filter(user=request.user).all()
|
||||
)
|
||||
for ability in abilities:
|
||||
PetitCoursAttributionCounter.get_uptodate(
|
||||
ability.user,
|
||||
ability.matiere
|
||||
with transaction.atomic():
|
||||
abilities = (
|
||||
PetitCoursAbility.objects.filter(user=request.user).all()
|
||||
)
|
||||
unlock_tables()
|
||||
for ability in abilities:
|
||||
PetitCoursAttributionCounter.get_uptodate(
|
||||
ability.user,
|
||||
ability.matiere
|
||||
)
|
||||
success = True
|
||||
formset = MatieresFormSet(instance=request.user)
|
||||
else:
|
||||
|
|
|
@ -1,15 +1,8 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.contrib.sites.models import Site
|
||||
from django.conf import settings
|
||||
from django_cas_ng.backends import CASBackend
|
||||
from django_cas_ng.utils import get_cas_client
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.db import connection
|
||||
|
||||
from gestioncof.models import CofProfile
|
||||
|
||||
|
@ -74,25 +67,3 @@ def context_processor(request):
|
|||
"site": Site.objects.get_current(),
|
||||
}
|
||||
return data
|
||||
|
||||
|
||||
def lock_table(*models):
|
||||
query = "LOCK TABLES "
|
||||
for i, model in enumerate(models):
|
||||
table = model._meta.db_table
|
||||
if i > 0:
|
||||
query += ", "
|
||||
query += "%s WRITE" % table
|
||||
cursor = connection.cursor()
|
||||
cursor.execute(query)
|
||||
row = cursor.fetchone()
|
||||
return row
|
||||
|
||||
|
||||
def unlock_tables(*models):
|
||||
cursor = connection.cursor()
|
||||
cursor.execute("UNLOCK TABLES")
|
||||
row = cursor.fetchone()
|
||||
return row
|
||||
|
||||
unlock_table = unlock_tables
|
||||
|
|
|
@ -778,6 +778,17 @@ header .open > .dropdown-toggle.btn-default {
|
|||
border-color: white;
|
||||
}
|
||||
|
||||
/* Announcements banner ------------------ */
|
||||
|
||||
#banner {
|
||||
background-color: #d86b01;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
padding: 10px;
|
||||
color: white;
|
||||
font-size: larger;
|
||||
}
|
||||
|
||||
|
||||
/* FORMS --------------------------------- */
|
||||
|
||||
|
|
|
@ -8,13 +8,13 @@
|
|||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
{# CSS #}
|
||||
{# CSS #}
|
||||
<link type="text/css" rel="stylesheet" href="{% static "css/bootstrap.min.css" %}" />
|
||||
<link type="text/css" rel="stylesheet" href="{% static "css/cof.css" %}" />
|
||||
<link href="https://fonts.googleapis.com/css?family=Dosis|Dosis:700|Raleway|Roboto:300,300i,700" rel="stylesheet">
|
||||
<link rel="stylesheet" href="{% static "font-awesome/css/font-awesome.min.css" %}">
|
||||
|
||||
{# JS #}
|
||||
{# JS #}
|
||||
<script src="https://code.jquery.com/jquery-3.1.0.min.js" integrity="sha256-cCueBR6CsyA4/9szpPfrX3s49M9vUU5BgtiJj06wt/s=" crossorigin="anonymous"></script>
|
||||
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
|
||||
{% block extra_head %}{% endblock %}
|
||||
|
|
23
gestioncof/templates/gestioncof/banner_update.html
Normal file
23
gestioncof/templates/gestioncof/banner_update.html
Normal file
|
@ -0,0 +1,23 @@
|
|||
{% extends "base_title.html" %}
|
||||
{% load bootstrap %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block page_size %}col-sm-8{%endblock%}
|
||||
|
||||
{% block realcontent %}
|
||||
<h2>{% trans "Global configuration" %}</h2>
|
||||
<form id="profile form-horizontal" method="post" action="">
|
||||
<div class="row" style="margin: 0 15%;">
|
||||
{% csrf_token %}
|
||||
<fieldset"center-block">
|
||||
{% for field in form %}
|
||||
{{ field | bootstrap }}
|
||||
{% endfor %}
|
||||
</fieldset>
|
||||
</div>
|
||||
<div class="form-actions">
|
||||
<input type="submit" class="btn btn-primary pull-right"
|
||||
value={% trans "Save" %} />
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
|
@ -16,6 +16,14 @@
|
|||
<h2 class="member-status">{% if user.first_name %}{{ user.first_name }}{% else %}<tt>{{ user.username }}</tt>{% endif %}, {% if user.profile.is_cof %}<tt class="user-is-cof">au COF{% else %}<tt class="user-is-not-cof">non-COF{% endif %}</tt></h2>
|
||||
</div><!-- /.container -->
|
||||
</header>
|
||||
|
||||
{% if config.gestion_banner %}
|
||||
<div id="banner" class="container">
|
||||
<span class="glyphicon glyphicon-bullhorn"></span>
|
||||
<span>{{ config.gestion_banner }}</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if messages %}
|
||||
{% for message in messages %}
|
||||
<div class="messages">
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
import unicodecsv
|
||||
import uuid
|
||||
from datetime import timedelta
|
||||
|
@ -12,9 +10,10 @@ from django.contrib.auth.decorators import login_required
|
|||
from django.contrib.auth.views import login as django_login_view
|
||||
from django.contrib.auth.models import User
|
||||
from django.contrib.sites.models import Site
|
||||
from django.core.urlresolvers import reverse_lazy
|
||||
from django.views.generic import FormView
|
||||
from django.utils import timezone
|
||||
from django.contrib import messages
|
||||
import django.utils.six as six
|
||||
|
||||
from gestioncof.models import Survey, SurveyAnswer, SurveyQuestion, \
|
||||
SurveyQuestionAnswer
|
||||
|
@ -24,10 +23,11 @@ from gestioncof.models import EventCommentField, EventCommentValue, \
|
|||
CalendarSubscription
|
||||
from gestioncof.models import CofProfile, Club
|
||||
from gestioncof.decorators import buro_required, cof_required
|
||||
from gestioncof.forms import UserProfileForm, EventStatusFilterForm, \
|
||||
SurveyForm, SurveyStatusFilterForm, RegistrationUserForm, \
|
||||
RegistrationProfileForm, EventForm, CalendarForm, EventFormset, \
|
||||
RegistrationPassUserForm, ClubsForm
|
||||
from gestioncof.forms import (
|
||||
UserProfileForm, EventStatusFilterForm, SurveyForm, SurveyStatusFilterForm,
|
||||
RegistrationUserForm, RegistrationProfileForm, EventForm, CalendarForm,
|
||||
EventFormset, RegistrationPassUserForm, ClubsForm, GestioncofConfigForm
|
||||
)
|
||||
|
||||
from bda.models import Tirage, Spectacle
|
||||
|
||||
|
@ -94,7 +94,10 @@ def logout(request):
|
|||
|
||||
@login_required
|
||||
def survey(request, survey_id):
|
||||
survey = get_object_or_404(Survey, id=survey_id)
|
||||
survey = get_object_or_404(
|
||||
Survey.objects.prefetch_related('questions', 'questions__answers'),
|
||||
id=survey_id,
|
||||
)
|
||||
if not survey.survey_open or survey.old:
|
||||
raise Http404
|
||||
success = False
|
||||
|
@ -400,12 +403,8 @@ def registration_form2(request, login_clipper=None, username=None,
|
|||
def registration(request):
|
||||
if request.POST:
|
||||
request_dict = request.POST.copy()
|
||||
# num ne peut pas être défini manuellement
|
||||
if "num" in request_dict:
|
||||
del request_dict["num"]
|
||||
member = None
|
||||
login_clipper = None
|
||||
success = False
|
||||
|
||||
# -----
|
||||
# Remplissage des formulaires
|
||||
|
@ -442,7 +441,6 @@ def registration(request):
|
|||
member = user_form.save()
|
||||
profile, _ = CofProfile.objects.get_or_create(user=member)
|
||||
was_cof = profile.is_cof
|
||||
request_dict["num"] = profile.num
|
||||
# Maintenant on remplit le formulaire de profil
|
||||
profile_form = RegistrationProfileForm(request_dict,
|
||||
instance=profile)
|
||||
|
@ -496,16 +494,18 @@ def registration(request):
|
|||
for club in clubs_form.cleaned_data['clubs']:
|
||||
club.membres.add(member)
|
||||
club.save()
|
||||
success = True
|
||||
# Messages
|
||||
if success:
|
||||
msg = ("L'inscription de {:s} (<tt>{:s}</tt>) a été "
|
||||
"enregistrée avec succès"
|
||||
.format(member.get_full_name(), member.email))
|
||||
if member.profile.is_cof:
|
||||
msg += "Il est désormais membre du COF n°{:d} !".format(
|
||||
member.profile.num)
|
||||
messages.success(request, msg, extra_tags='safe')
|
||||
|
||||
# ---
|
||||
# Success
|
||||
# ---
|
||||
|
||||
msg = ("L'inscription de {:s} (<tt>{:s}</tt>) a été "
|
||||
"enregistrée avec succès."
|
||||
.format(member.get_full_name(), member.email))
|
||||
if profile.is_cof:
|
||||
msg += "\nIl est désormais membre du COF n°{:d} !".format(
|
||||
member.profile.id)
|
||||
messages.success(request, msg, extra_tags='safe')
|
||||
return render(request, "gestioncof/registration_post.html",
|
||||
{"user_form": user_form,
|
||||
"profile_form": profile_form,
|
||||
|
@ -569,10 +569,10 @@ def export_members(request):
|
|||
writer = unicodecsv.writer(response)
|
||||
for profile in CofProfile.objects.filter(is_cof=True).all():
|
||||
user = profile.user
|
||||
bits = [profile.num, user.username, user.first_name, user.last_name,
|
||||
bits = [profile.id, user.username, user.first_name, user.last_name,
|
||||
user.email, profile.phone, profile.occupation,
|
||||
profile.departement, profile.type_cotiz]
|
||||
writer.writerow([six.text_type(bit) for bit in bits])
|
||||
writer.writerow([str(bit) for bit in bits])
|
||||
|
||||
return response
|
||||
|
||||
|
@ -588,10 +588,10 @@ def csv_export_mega(filename, qs):
|
|||
comments = "---".join(
|
||||
[comment.content for comment in reg.comments.all()])
|
||||
bits = [user.username, user.first_name, user.last_name, user.email,
|
||||
profile.phone, profile.num,
|
||||
profile.phone, profile.id,
|
||||
profile.comments if profile.comments else "", comments]
|
||||
|
||||
writer.writerow([six.text_type(bit) for bit in bits])
|
||||
writer.writerow([str(bit) for bit in bits])
|
||||
|
||||
return response
|
||||
|
||||
|
@ -610,8 +610,8 @@ def export_mega_remarksonly(request):
|
|||
user = reg.user
|
||||
profile = user.profile
|
||||
bits = [user.username, user.first_name, user.last_name, user.email,
|
||||
profile.phone, profile.num, profile.comments, val.content]
|
||||
writer.writerow([six.text_type(bit) for bit in bits])
|
||||
profile.phone, profile.id, profile.comments, val.content]
|
||||
writer.writerow([str(bit) for bit in bits])
|
||||
|
||||
return response
|
||||
|
||||
|
@ -761,3 +761,18 @@ def calendar_ics(request, token):
|
|||
response = HttpResponse(content=vcal.to_ical())
|
||||
response['Content-Type'] = "text/calendar"
|
||||
return response
|
||||
|
||||
|
||||
class ConfigUpdate(FormView):
|
||||
form_class = GestioncofConfigForm
|
||||
template_name = "gestioncof/banner_update.html"
|
||||
success_url = reverse_lazy("home")
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
if request.user is None or not request.user.is_superuser:
|
||||
raise Http404
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
|
||||
def form_valid(self, form):
|
||||
form.save()
|
||||
return super().form_valid(form)
|
||||
|
|
|
@ -12,3 +12,9 @@ class KFetConfig(AppConfig):
|
|||
|
||||
def ready(self):
|
||||
import kfet.signals
|
||||
self.register_config()
|
||||
|
||||
def register_config(self):
|
||||
import djconfig
|
||||
from kfet.forms import KFetConfigForm
|
||||
djconfig.register(KFetConfigForm)
|
||||
|
|
|
@ -1,15 +1,12 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from __future__ import (absolute_import, division,
|
||||
print_function, unicode_literals)
|
||||
from builtins import *
|
||||
|
||||
import hashlib
|
||||
|
||||
from django.contrib.auth.models import User, Permission
|
||||
from gestioncof.models import CofProfile
|
||||
from kfet.models import Account, GenericTeamToken
|
||||
|
||||
|
||||
class KFetBackend(object):
|
||||
def authenticate(self, request):
|
||||
password = request.POST.get('KFETPASSWORD', '')
|
||||
|
@ -18,13 +15,15 @@ class KFetBackend(object):
|
|||
return None
|
||||
|
||||
try:
|
||||
password_sha256 = hashlib.sha256(password.encode('utf-8')).hexdigest()
|
||||
password_sha256 = (
|
||||
hashlib.sha256(password.encode('utf-8'))
|
||||
.hexdigest()
|
||||
)
|
||||
account = Account.objects.get(password=password_sha256)
|
||||
user = account.cofprofile.user
|
||||
return account.cofprofile.user
|
||||
except Account.DoesNotExist:
|
||||
return None
|
||||
|
||||
return user
|
||||
|
||||
class GenericTeamBackend(object):
|
||||
def authenticate(self, username=None, token=None):
|
||||
|
@ -46,6 +45,10 @@ class GenericTeamBackend(object):
|
|||
|
||||
def get_user(self, user_id):
|
||||
try:
|
||||
return User.objects.get(pk=user_id)
|
||||
return (
|
||||
User.objects
|
||||
.select_related('profile__account_kfet')
|
||||
.get(pk=user_id)
|
||||
)
|
||||
except User.DoesNotExist:
|
||||
return None
|
||||
|
|
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,29 +1,40 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from __future__ import (absolute_import, division,
|
||||
print_function, unicode_literals)
|
||||
from builtins import *
|
||||
from django.core.serializers.json import json, DjangoJSONEncoder
|
||||
|
||||
from channels import Group
|
||||
from channels.generic.websockets import JsonWebsocketConsumer
|
||||
|
||||
class KPsul(JsonWebsocketConsumer):
|
||||
|
||||
# Set to True if you want them, else leave out
|
||||
strict_ordering = False
|
||||
slight_ordering = False
|
||||
class DjangoJsonWebsocketConsumer(JsonWebsocketConsumer):
|
||||
"""Custom Json Websocket Consumer.
|
||||
|
||||
def connection_groups(self, **kwargs):
|
||||
return ['kfet.kpsul']
|
||||
Encode to JSON with DjangoJSONEncoder.
|
||||
|
||||
"""
|
||||
|
||||
@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):
|
||||
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):
|
||||
pass
|
||||
|
||||
class KfetOpen(JsonWebsocketConsumer):
|
||||
def connection_groups(self, **kwargs):
|
||||
|
@ -37,3 +48,8 @@ class KfetOpen(JsonWebsocketConsumer):
|
|||
|
||||
def disconnect(self, message, **kwargs):
|
||||
pass
|
||||
|
||||
|
||||
class KPsul(PermConsumerMixin, DjangoJsonWebsocketConsumer):
|
||||
groups = ['kfet.kpsul']
|
||||
perms_connect = ['kfet.is_team']
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
# -*- 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 .views import KFET_OPEN, KFET_FORCE_CLOSE
|
||||
|
||||
|
||||
from kfet.config import kfet_config
|
||||
|
||||
|
||||
def auth(request):
|
||||
if hasattr(request, 'real_user'):
|
||||
return {
|
||||
|
@ -25,3 +24,7 @@ def kfet_open(request):
|
|||
'kfet_open_date': kfet_open_date.isoformat(),
|
||||
'kfet_force_close': kfet_force_close,
|
||||
}
|
||||
|
||||
|
||||
def config(request):
|
||||
return {'kfet_config': kfet_config}
|
||||
|
|
124
kfet/forms.py
124
kfet/forms.py
|
@ -1,19 +1,25 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from datetime import timedelta
|
||||
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
|
||||
from django.forms import modelformset_factory, widgets
|
||||
from django.utils import timezone
|
||||
|
||||
from djconfig.forms import ConfigForm
|
||||
|
||||
from kfet.models import (
|
||||
Account, Checkout, Article, OperationGroup, Operation,
|
||||
CheckoutStatement, ArticleCategory, Settings, AccountNegative, Transfer,
|
||||
CheckoutStatement, ArticleCategory, AccountNegative, Transfer,
|
||||
TransferGroup, Supplier)
|
||||
from gestioncof.models import CofProfile
|
||||
|
||||
|
||||
# -----
|
||||
# Widgets
|
||||
# -----
|
||||
|
@ -128,6 +134,7 @@ class UserRestrictTeamForm(UserForm):
|
|||
class Meta(UserForm.Meta):
|
||||
fields = ['first_name', 'last_name', 'email']
|
||||
|
||||
|
||||
class UserGroupForm(forms.ModelForm):
|
||||
groups = forms.ModelMultipleChoiceField(
|
||||
Group.objects.filter(name__icontains='K-Fêt'),
|
||||
|
@ -135,20 +142,33 @@ class UserGroupForm(forms.ModelForm):
|
|||
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
|
||||
kfet_groups = self.cleaned_data.get('groups')
|
||||
other_groups = self.instance.groups.exclude(name__icontains='K-Fêt')
|
||||
return list(kfet_groups) + list(other_groups)
|
||||
|
||||
class Meta:
|
||||
model = User
|
||||
model = User
|
||||
fields = ['groups']
|
||||
|
||||
|
||||
class KFetPermissionsField(forms.ModelMultipleChoiceField):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
queryset = Permission.objects.filter(
|
||||
content_type__in=ContentType.objects.filter(app_label="kfet"),
|
||||
)
|
||||
super().__init__(
|
||||
queryset=queryset,
|
||||
widget=widgets.CheckboxSelectMultiple,
|
||||
*args, **kwargs
|
||||
)
|
||||
|
||||
def label_from_instance(self, obj):
|
||||
return obj.name
|
||||
|
||||
|
||||
class GroupForm(forms.ModelForm):
|
||||
permissions = forms.ModelMultipleChoiceField(
|
||||
queryset= Permission.objects.filter(content_type__in=
|
||||
ContentType.objects.filter(app_label='kfet')))
|
||||
permissions = KFetPermissionsField()
|
||||
|
||||
def clean_name(self):
|
||||
name = self.cleaned_data['name']
|
||||
|
@ -322,12 +342,20 @@ class KPsulAccountForm(forms.ModelForm):
|
|||
}),
|
||||
}
|
||||
|
||||
|
||||
class KPsulCheckoutForm(forms.Form):
|
||||
checkout = forms.ModelChoiceField(
|
||||
queryset=Checkout.objects.filter(
|
||||
is_protected=False, valid_from__lte=timezone.now(),
|
||||
valid_to__gte=timezone.now()),
|
||||
widget=forms.Select(attrs={'id':'id_checkout_select'}))
|
||||
queryset=(
|
||||
Checkout.objects
|
||||
.filter(
|
||||
is_protected=False,
|
||||
valid_from__lte=timezone.now(),
|
||||
valid_to__gte=timezone.now(),
|
||||
)
|
||||
),
|
||||
widget=forms.Select(attrs={'id': 'id_checkout_select'}),
|
||||
)
|
||||
|
||||
|
||||
class KPsulOperationForm(forms.ModelForm):
|
||||
article = forms.ModelChoiceField(
|
||||
|
@ -389,40 +417,46 @@ class AddcostForm(forms.Form):
|
|||
self.cleaned_data['amount'] = 0
|
||||
super(AddcostForm, self).clean()
|
||||
|
||||
|
||||
# -----
|
||||
# Settings forms
|
||||
# -----
|
||||
|
||||
class SettingsForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = Settings
|
||||
fields = ['value_decimal', 'value_account', 'value_duration']
|
||||
|
||||
def clean(self):
|
||||
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')
|
||||
class KFetConfigForm(ConfigForm):
|
||||
|
||||
type_decimal = ['SUBVENTION_COF', 'ADDCOST_AMOUNT', 'OVERDRAFT_AMOUNT']
|
||||
type_account = ['ADDCOST_FOR']
|
||||
type_duration = ['OVERDRAFT_DURATION', 'CANCEL_DURATION']
|
||||
kfet_reduction_cof = forms.DecimalField(
|
||||
label='Réduction COF', initial=Decimal('20'),
|
||||
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):
|
||||
checkouts = forms.ModelMultipleChoiceField(queryset = Checkout.objects.all())
|
||||
|
@ -505,11 +539,7 @@ class OrderArticleForm(forms.Form):
|
|||
self.category = kwargs['initial']['category']
|
||||
self.category_name = kwargs['initial']['category__name']
|
||||
self.box_capacity = kwargs['initial']['box_capacity']
|
||||
self.v_s1 = kwargs['initial']['v_s1']
|
||||
self.v_s2 = kwargs['initial']['v_s2']
|
||||
self.v_s3 = kwargs['initial']['v_s3']
|
||||
self.v_s4 = kwargs['initial']['v_s4']
|
||||
self.v_s5 = kwargs['initial']['v_s5']
|
||||
self.v_all = kwargs['initial']['v_all']
|
||||
self.v_moy = kwargs['initial']['v_moy']
|
||||
self.v_et = kwargs['initial']['v_et']
|
||||
self.v_prev = kwargs['initial']['v_prev']
|
||||
|
|
|
@ -14,7 +14,8 @@ from kfet.models import (Account, Article, OperationGroup, Operation,
|
|||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = "Crée des opérations réparties uniformément sur une période de temps"
|
||||
help = ("Crée des opérations réparties uniformément "
|
||||
"sur une période de temps")
|
||||
|
||||
def add_arguments(self, parser):
|
||||
# Nombre d'opérations à créer
|
||||
|
@ -29,7 +30,6 @@ class Command(BaseCommand):
|
|||
parser.add_argument('--transfers', type=int, default=0,
|
||||
help='Number of transfers to create (default 0)')
|
||||
|
||||
|
||||
def handle(self, *args, **options):
|
||||
|
||||
self.stdout.write("Génération d'opérations")
|
||||
|
@ -44,6 +44,7 @@ class Command(BaseCommand):
|
|||
# Convert to seconds
|
||||
time = options['days'] * 24 * 3600
|
||||
|
||||
now = timezone.now()
|
||||
checkout = Checkout.objects.first()
|
||||
articles = Article.objects.all()
|
||||
accounts = Account.objects.exclude(trigramme='LIQ')
|
||||
|
@ -55,6 +56,13 @@ class Command(BaseCommand):
|
|||
except Account.DoesNotExist:
|
||||
con_account = random.choice(accounts)
|
||||
|
||||
# use to fetch OperationGroup pk created by bulk_create
|
||||
at_list = []
|
||||
# use to lazy set OperationGroup pk on Operation objects
|
||||
ope_by_grp = []
|
||||
# OperationGroup objects to bulk_create
|
||||
opegroup_list = []
|
||||
|
||||
for i in range(num_ops):
|
||||
|
||||
# Randomly pick account
|
||||
|
@ -64,8 +72,7 @@ class Command(BaseCommand):
|
|||
account = liq_account
|
||||
|
||||
# Randomly pick time
|
||||
at = timezone.now() - timedelta(
|
||||
seconds=random.randint(0, time))
|
||||
at = now - timedelta(seconds=random.randint(0, time))
|
||||
|
||||
# Majoration sur compte 'concert'
|
||||
if random.random() < 0.2:
|
||||
|
@ -78,13 +85,6 @@ class Command(BaseCommand):
|
|||
# Initialize opegroup amount
|
||||
amount = Decimal('0')
|
||||
|
||||
opegroup = OperationGroup.objects.create(
|
||||
on_acc=account,
|
||||
checkout=checkout,
|
||||
at=at,
|
||||
is_cof=account.cofprofile.is_cof
|
||||
)
|
||||
|
||||
# Generating operations
|
||||
ope_list = []
|
||||
for j in range(random.randint(1, 4)):
|
||||
|
@ -94,25 +94,26 @@ class Command(BaseCommand):
|
|||
# 0.1 probability to have a charge
|
||||
if typevar > 0.9 and account != liq_account:
|
||||
ope = Operation(
|
||||
group=opegroup,
|
||||
type=Operation.DEPOSIT,
|
||||
is_checkout=(random.random() > 0.2),
|
||||
amount=Decimal(random.randint(1, 99)/10)
|
||||
)
|
||||
# 0.1 probability to have a withdrawal
|
||||
# 0.05 probability to have a withdrawal
|
||||
elif typevar > 0.85 and account != liq_account:
|
||||
ope = Operation(
|
||||
type=Operation.WITHDRAW,
|
||||
amount=-Decimal(random.randint(1, 99)/10)
|
||||
)
|
||||
# 0.05 probability to have an edition
|
||||
elif typevar > 0.8 and account != liq_account:
|
||||
ope = Operation(
|
||||
group=opegroup,
|
||||
type=Operation.WITHDRAW,
|
||||
is_checkout=(random.random() > 0.2),
|
||||
amount=-Decimal(random.randint(1, 99)/10)
|
||||
type=Operation.EDIT,
|
||||
amount=Decimal(random.randint(1, 99)/10)
|
||||
)
|
||||
else:
|
||||
article = random.choice(articles)
|
||||
nb = random.randint(1, 5)
|
||||
|
||||
ope = Operation(
|
||||
group=opegroup,
|
||||
type=Operation.PURCHASE,
|
||||
amount=-article.price*nb,
|
||||
article=article,
|
||||
|
@ -129,17 +130,44 @@ class Command(BaseCommand):
|
|||
ope_list.append(ope)
|
||||
amount += ope.amount
|
||||
|
||||
Operation.objects.bulk_create(ope_list)
|
||||
opes_created += len(ope_list)
|
||||
opegroup.amount = amount
|
||||
opegroup.save()
|
||||
opegroup_list.append(OperationGroup(
|
||||
on_acc=account,
|
||||
checkout=checkout,
|
||||
at=at,
|
||||
is_cof=account.cofprofile.is_cof,
|
||||
amount=amount,
|
||||
))
|
||||
at_list.append(at)
|
||||
ope_by_grp.append((at, ope_list, ))
|
||||
|
||||
OperationGroup.objects.bulk_create(opegroup_list)
|
||||
|
||||
# Fetch created OperationGroup objects pk by at
|
||||
opegroups = (OperationGroup.objects
|
||||
.filter(at__in=at_list)
|
||||
.values('id', 'at'))
|
||||
opegroups_by = {grp['at']: grp['id'] for grp in opegroups}
|
||||
|
||||
all_ope = []
|
||||
for _ in range(num_ops):
|
||||
at, ope_list = ope_by_grp.pop()
|
||||
for ope in ope_list:
|
||||
ope.group_id = opegroups_by[at]
|
||||
all_ope.append(ope)
|
||||
|
||||
Operation.objects.bulk_create(all_ope)
|
||||
opes_created = len(all_ope)
|
||||
|
||||
# Transfer generation
|
||||
|
||||
transfer_by_grp = []
|
||||
transfergroup_list = []
|
||||
at_list = []
|
||||
|
||||
for i in range(num_transfers):
|
||||
|
||||
# Randomly pick time
|
||||
at = timezone.now() - timedelta(
|
||||
seconds=random.randint(0, time))
|
||||
at = now - timedelta(seconds=random.randint(0, time))
|
||||
|
||||
# Choose whether to have a comment
|
||||
if random.random() > 0.5:
|
||||
|
@ -147,24 +175,40 @@ class Command(BaseCommand):
|
|||
else:
|
||||
comment = ""
|
||||
|
||||
transfergroup = TransferGroup.objects.create(
|
||||
transfergroup_list.append(TransferGroup(
|
||||
at=at,
|
||||
comment=comment,
|
||||
valid_by=random.choice(accounts)
|
||||
)
|
||||
valid_by=random.choice(accounts),
|
||||
))
|
||||
at_list.append(at)
|
||||
|
||||
# Randomly generate transfer
|
||||
transfer_list = []
|
||||
for i in range(random.randint(1, 4)):
|
||||
transfer_list.append(Transfer(
|
||||
group=transfergroup,
|
||||
from_acc=random.choice(accounts),
|
||||
to_acc=random.choice(accounts),
|
||||
amount=Decimal(random.randint(1, 99)/10)
|
||||
))
|
||||
|
||||
Transfer.objects.bulk_create(transfer_list)
|
||||
transfers += len(transfer_list)
|
||||
transfer_by_grp.append((at, transfer_list, ))
|
||||
|
||||
TransferGroup.objects.bulk_create(transfergroup_list)
|
||||
|
||||
transfergroups = (TransferGroup.objects
|
||||
.filter(at__in=at_list)
|
||||
.values('id', 'at'))
|
||||
transfergroups_by = {grp['at']: grp['id'] for grp in transfergroups}
|
||||
|
||||
all_transfer = []
|
||||
for _ in range(num_transfers):
|
||||
at, transfer_list = transfer_by_grp.pop()
|
||||
for transfer in transfer_list:
|
||||
transfer.group_id = transfergroups_by[at]
|
||||
all_transfer.append(transfer)
|
||||
|
||||
Transfer.objects.bulk_create(all_transfer)
|
||||
transfers += len(all_transfer)
|
||||
|
||||
self.stdout.write(
|
||||
"- {:d} opérations créées dont {:d} commandes d'articles"
|
||||
|
|
|
@ -1,15 +1,27 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from __future__ import (absolute_import, division,
|
||||
print_function, unicode_literals)
|
||||
from builtins import *
|
||||
from django.contrib.auth.models import User
|
||||
|
||||
from django.http import HttpResponseForbidden
|
||||
from kfet.backends import KFetBackend
|
||||
from kfet.models import Account
|
||||
|
||||
|
||||
class KFetAuthenticationMiddleware(object):
|
||||
"""Authenticate another user for this request if KFetBackend succeeds.
|
||||
|
||||
By the way, if a user is authenticated, we refresh its from db to add
|
||||
values from CofProfile and Account of this user.
|
||||
|
||||
"""
|
||||
def process_request(self, request):
|
||||
if request.user.is_authenticated():
|
||||
# avoid multiple db accesses in views and templates
|
||||
user_pk = request.user.pk
|
||||
request.user = (
|
||||
User.objects
|
||||
.select_related('profile__account_kfet')
|
||||
.get(pk=user_pk)
|
||||
)
|
||||
|
||||
kfet_backend = KFetBackend()
|
||||
temp_request_user = kfet_backend.authenticate(request)
|
||||
if temp_request_user:
|
||||
|
|
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',
|
||||
),
|
||||
]
|
|
@ -1,18 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('kfet', '0053_created_at'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='globalpermissions',
|
||||
options={'managed': False, 'permissions': (('is_team', 'Is part of the team'), ('perform_deposit', 'Effectuer une charge'), ('perform_negative_operations', 'Enregistrer des commandes en négatif'), ('override_frozen_protection', "Forcer le gel d'un compte"), ('cancel_old_operations', 'Annuler des commandes non récentes'), ('manage_perms', 'Gérer les permissions K-Fêt'), ('manage_addcosts', 'Gérer les majorations'), ('perform_commented_operations', 'Enregistrer des commandes avec commentaires'), ('view_negs', 'Voir la liste des négatifs'), ('order_to_inventory', "Générer un inventaire à partir d'une commande"), ('edit_balance_account', "Modifier la balance d'un compte"), ('change_account_password', "Modifier le mot de passe d'une personne de l'équipe"), ('special_add_account', 'Créer un compte avec une balance initiale'), ('force_close_kfet', 'Fermer manuelement la K-Fêt'))},
|
||||
),
|
||||
]
|
81
kfet/migrations/0055_move_permissions.py
Normal file
81
kfet/migrations/0055_move_permissions.py
Normal file
|
@ -0,0 +1,81 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
def forwards_perms(apps, schema_editor):
|
||||
"""Safely delete content type for old kfet.GlobalPermissions model.
|
||||
|
||||
Any permissions (except defaults) linked to this content type are updated
|
||||
to link at its new content type.
|
||||
Then, delete the content type. This will delete the three defaults
|
||||
permissions which are assumed unused.
|
||||
|
||||
"""
|
||||
ContentType = apps.get_model('contenttypes', 'contenttype')
|
||||
try:
|
||||
ctype_global = ContentType.objects.get(
|
||||
app_label="kfet", model="globalpermissions",
|
||||
)
|
||||
except ContentType.DoesNotExist:
|
||||
# We are not migrating from existing data, nothing to do.
|
||||
return
|
||||
|
||||
perms = {
|
||||
'account': (
|
||||
'is_team', 'manage_perms', 'manage_addcosts',
|
||||
'edit_balance_account', 'change_account_password',
|
||||
'special_add_account',
|
||||
),
|
||||
'accountnegative': ('view_negs',),
|
||||
'inventory': ('order_to_inventory',),
|
||||
'operation': (
|
||||
'perform_deposit', 'perform_negative_operations',
|
||||
'override_frozen_protection', 'cancel_old_operations',
|
||||
'perform_commented_operations',
|
||||
),
|
||||
}
|
||||
|
||||
Permission = apps.get_model('auth', 'permission')
|
||||
global_perms = Permission.objects.filter(content_type=ctype_global)
|
||||
|
||||
for modelname, codenames in perms.items():
|
||||
model = apps.get_model('kfet', modelname)
|
||||
ctype = ContentType.objects.get_for_model(model)
|
||||
(
|
||||
global_perms
|
||||
.filter(codename__in=codenames)
|
||||
.update(content_type=ctype)
|
||||
)
|
||||
|
||||
ctype_global.delete()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('kfet', '0054_delete_settings'),
|
||||
('contenttypes', '__latest__'),
|
||||
('auth', '__latest__'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='account',
|
||||
options={'permissions': (('is_team', 'Is part of the team'), ('manage_perms', 'Gérer les permissions K-Fêt'), ('manage_addcosts', 'Gérer les majorations'), ('edit_balance_account', "Modifier la balance d'un compte"), ('change_account_password', "Modifier le mot de passe d'une personne de l'équipe"), ('special_add_account', 'Créer un compte avec une balance initiale'))},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='accountnegative',
|
||||
options={'permissions': (('view_negs', 'Voir la liste des négatifs'),)},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='inventory',
|
||||
options={'ordering': ['-at'], 'permissions': (('order_to_inventory', "Générer un inventaire à partir d'une commande"),)},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='operation',
|
||||
options={'permissions': (('perform_deposit', 'Effectuer une charge'), ('perform_negative_operations', 'Enregistrer des commandes en négatif'), ('override_frozen_protection', "Forcer le gel d'un compte"), ('cancel_old_operations', 'Annuler des commandes non récentes'), ('perform_commented_operations', 'Enregistrer des commandes avec commentaires'))},
|
||||
),
|
||||
migrations.RunPython(forwards_perms),
|
||||
]
|
257
kfet/models.py
257
kfet/models.py
|
@ -1,12 +1,7 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from __future__ import (absolute_import, division,
|
||||
print_function, unicode_literals)
|
||||
from builtins import *
|
||||
|
||||
from django.db import models
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.core.exceptions import PermissionDenied, ValidationError
|
||||
from django.core.validators import RegexValidator
|
||||
from django.contrib.auth.models import User
|
||||
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.db import transaction
|
||||
from django.db.models import F
|
||||
from django.core.cache import cache
|
||||
from datetime import date, timedelta
|
||||
from datetime import date
|
||||
import re
|
||||
import hashlib
|
||||
|
||||
from kfet.config import kfet_config
|
||||
|
||||
def choices_length(choices):
|
||||
return reduce(lambda m, choice: max(m, len(choice[0])), choices, 0)
|
||||
|
||||
|
@ -27,8 +23,19 @@ def default_promo():
|
|||
now = date.today()
|
||||
return now.month <= 8 and now.year-1 or now.year
|
||||
|
||||
@python_2_unicode_compatible
|
||||
|
||||
class AccountManager(models.Manager):
|
||||
"""Manager for Account Model."""
|
||||
|
||||
def get_queryset(self):
|
||||
"""Always append related data to this Account."""
|
||||
return super().get_queryset().select_related('cofprofile__user',
|
||||
'negative')
|
||||
|
||||
|
||||
class Account(models.Model):
|
||||
objects = AccountManager()
|
||||
|
||||
cofprofile = models.OneToOneField(
|
||||
CofProfile, on_delete = models.PROTECT,
|
||||
related_name = "account_kfet")
|
||||
|
@ -56,6 +63,19 @@ class Account(models.Model):
|
|||
unique = True,
|
||||
blank = True, null = True, default = None)
|
||||
|
||||
class Meta:
|
||||
permissions = (
|
||||
('is_team', 'Is part of the team'),
|
||||
('manage_perms', 'Gérer les permissions K-Fêt'),
|
||||
('manage_addcosts', 'Gérer les majorations'),
|
||||
('edit_balance_account', "Modifier la balance d'un compte"),
|
||||
('change_account_password',
|
||||
"Modifier le mot de passe d'une personne de l'équipe"),
|
||||
('special_add_account',
|
||||
"Créer un compte avec une balance initiale"),
|
||||
('force_close_kfet', "Fermer manuelement la K-Fêt"),
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return '%s (%s)' % (self.trigramme, self.name)
|
||||
|
||||
|
@ -85,7 +105,7 @@ class Account(models.Model):
|
|||
# Propriétés supplémentaires
|
||||
@property
|
||||
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
|
||||
|
||||
|
@ -113,8 +133,8 @@ class Account(models.Model):
|
|||
return data
|
||||
|
||||
def perms_to_perform_operation(self, amount):
|
||||
overdraft_duration_max = Settings.OVERDRAFT_DURATION()
|
||||
overdraft_amount_max = Settings.OVERDRAFT_AMOUNT()
|
||||
overdraft_duration_max = kfet_config.overdraft_duration
|
||||
overdraft_amount_max = kfet_config.overdraft_amount
|
||||
perms = set()
|
||||
stop_ope = False
|
||||
# Checking is cash account
|
||||
|
@ -214,31 +234,75 @@ class Account(models.Model):
|
|||
def delete(self, *args, **kwargs):
|
||||
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):
|
||||
def __init__(self, trigramme):
|
||||
self.trigramme = trigramme
|
||||
|
||||
|
||||
class AccountNegativeManager(models.Manager):
|
||||
"""Manager for AccountNegative model."""
|
||||
|
||||
def get_queryset(self):
|
||||
return (
|
||||
super().get_queryset()
|
||||
.select_related('account__cofprofile__user')
|
||||
)
|
||||
|
||||
|
||||
class AccountNegative(models.Model):
|
||||
objects = AccountNegativeManager()
|
||||
|
||||
account = models.OneToOneField(
|
||||
Account, on_delete = models.PROTECT,
|
||||
related_name = "negative")
|
||||
start = models.DateTimeField(
|
||||
blank = True, null = True, default = None)
|
||||
Account, on_delete=models.PROTECT,
|
||||
related_name="negative",
|
||||
)
|
||||
start = models.DateTimeField(blank=True, null=True, default=None)
|
||||
balance_offset = models.DecimalField(
|
||||
"décalage de balance",
|
||||
help_text="Montant non compris dans l'autorisation de négatif",
|
||||
max_digits = 6, decimal_places = 2,
|
||||
blank = True, null = True, default = None)
|
||||
max_digits=6, decimal_places=2,
|
||||
blank=True, null=True, default=None,
|
||||
)
|
||||
authz_overdraft_amount = models.DecimalField(
|
||||
"négatif autorisé",
|
||||
max_digits = 6, decimal_places = 2,
|
||||
blank = True, null = True, default = None)
|
||||
max_digits=6, decimal_places=2,
|
||||
blank=True, null=True, default=None,
|
||||
)
|
||||
authz_overdraft_until = models.DateTimeField(
|
||||
"expiration du négatif",
|
||||
blank = True, null = True, default = None)
|
||||
comment = models.CharField("commentaire", max_length = 255, blank = True)
|
||||
blank=True, null=True, default=None,
|
||||
)
|
||||
comment = models.CharField("commentaire", max_length=255, blank=True)
|
||||
|
||||
class Meta:
|
||||
permissions = (
|
||||
('view_negs', 'Voir la liste des négatifs'),
|
||||
)
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class Checkout(models.Model):
|
||||
created_by = models.ForeignKey(
|
||||
Account, on_delete = models.PROTECT,
|
||||
|
@ -416,6 +480,10 @@ class Inventory(models.Model):
|
|||
|
||||
class Meta:
|
||||
ordering = ['-at']
|
||||
permissions = (
|
||||
('order_to_inventory', "Générer un inventaire à partir d'une commande"),
|
||||
)
|
||||
|
||||
|
||||
class InventoryArticle(models.Model):
|
||||
inventory = models.ForeignKey(
|
||||
|
@ -592,6 +660,17 @@ class Operation(models.Model):
|
|||
max_digits=6, decimal_places=2,
|
||||
blank=True, null=True, default=None)
|
||||
|
||||
class Meta:
|
||||
permissions = (
|
||||
('perform_deposit', 'Effectuer une charge'),
|
||||
('perform_negative_operations',
|
||||
'Enregistrer des commandes en négatif'),
|
||||
('override_frozen_protection', "Forcer le gel d'un compte"),
|
||||
('cancel_old_operations', 'Annuler des commandes non récentes'),
|
||||
('perform_commented_operations',
|
||||
'Enregistrer des commandes avec commentaires'),
|
||||
)
|
||||
|
||||
@property
|
||||
def is_checkout(self):
|
||||
return (self.type == Operation.DEPOSIT or
|
||||
|
@ -612,139 +691,5 @@ class Operation(models.Model):
|
|||
amount=self.amount)
|
||||
|
||||
|
||||
class GlobalPermissions(models.Model):
|
||||
class Meta:
|
||||
managed = False
|
||||
permissions = (
|
||||
('is_team', 'Is part of the team'),
|
||||
('perform_deposit', 'Effectuer une charge'),
|
||||
('perform_negative_operations',
|
||||
'Enregistrer des commandes en négatif'),
|
||||
('override_frozen_protection', "Forcer le gel d'un compte"),
|
||||
('cancel_old_operations', 'Annuler des commandes non récentes'),
|
||||
('manage_perms', 'Gérer les permissions K-Fêt'),
|
||||
('manage_addcosts', 'Gérer les majorations'),
|
||||
('perform_commented_operations', 'Enregistrer des commandes avec commentaires'),
|
||||
('view_negs', 'Voir la liste des négatifs'),
|
||||
('order_to_inventory', "Générer un inventaire à partir d'une commande"),
|
||||
('edit_balance_account', "Modifier la balance d'un compte"),
|
||||
('change_account_password', "Modifier le mot de passe d'une personne de l'équipe"),
|
||||
('special_add_account', "Créer un compte avec une balance initiale"),
|
||||
('force_close_kfet', "Fermer manuelement la K-Fêt"),
|
||||
)
|
||||
|
||||
|
||||
|
||||
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):
|
||||
token = models.CharField(max_length = 50, unique = True)
|
||||
|
|
|
@ -1,16 +1,18 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from __future__ import (absolute_import, division,
|
||||
print_function, unicode_literals)
|
||||
from builtins import *
|
||||
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.signals import user_logged_in
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.dispatch import receiver
|
||||
|
||||
|
||||
@receiver(user_logged_in)
|
||||
def messages_on_login(sender, request, user, **kwargs):
|
||||
if (not user.username == 'kfet_genericteam'
|
||||
and user.has_perm('kfet.is_team')):
|
||||
messages.info(request, '<a href="%s">Connexion en utilisateur partagé ?</a>' % reverse('kfet.login.genericteam'), extra_tags='safe')
|
||||
if (not user.username == 'kfet_genericteam' and
|
||||
user.has_perm('kfet.is_team') and
|
||||
'k-fet' in request.GET.get('next', '')):
|
||||
messages.info(
|
||||
request,
|
||||
('<a href="{}" target="_blank">Connexion en utilisateur partagé ?</a>'
|
||||
.format(reverse('kfet.login.genericteam'))),
|
||||
extra_tags='safe')
|
||||
|
|
|
@ -9,17 +9,20 @@
|
|||
#history .day {
|
||||
height:40px;
|
||||
line-height:40px;
|
||||
background-color:#c8102e;
|
||||
background-color:rgba(200,16,46,0.9);
|
||||
color:#fff;
|
||||
padding-left:20px;
|
||||
font-size:16px;
|
||||
font-weight:bold;
|
||||
position:sticky;
|
||||
top:50px;
|
||||
z-index:10;
|
||||
}
|
||||
|
||||
#history .opegroup {
|
||||
height:30px;
|
||||
line-height:30px;
|
||||
background-color:rgba(200,16,46,0.85);
|
||||
background-color:rgba(200,16,46,0.75);
|
||||
color:#fff;
|
||||
font-weight:bold;
|
||||
padding-left:20px;
|
||||
|
|
|
@ -33,10 +33,8 @@ textarea {
|
|||
.table {
|
||||
margin-bottom:0;
|
||||
border-bottom:1px solid #ddd;
|
||||
}
|
||||
|
||||
.table {
|
||||
width:100%;
|
||||
background-color: #FFF;
|
||||
}
|
||||
|
||||
.table td {
|
||||
|
@ -70,6 +68,16 @@ textarea {
|
|||
padding:8px 30px;
|
||||
}
|
||||
|
||||
.table-responsive {
|
||||
border: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.btn {
|
||||
transition: background-color, color;
|
||||
transition-duration: 0.15s;
|
||||
}
|
||||
|
||||
.btn, .btn-lg, .btn-group-lg>.btn {
|
||||
border-radius:0;
|
||||
}
|
||||
|
@ -81,8 +89,29 @@ textarea {
|
|||
border:0;
|
||||
}
|
||||
|
||||
.btn-primary:hover, .btn-primary.focus, .btn-primary:focus {
|
||||
background-color:#000;
|
||||
.btn-primary:hover,
|
||||
.btn-primary.focus, .btn-primary:focus,
|
||||
.btn-primary.active.focus, .btn-primary.active:focus, .btn-primary.active:hover,
|
||||
.btn-primary:active.focus, .btn-primary:active:focus, .btn-primary:active:hover,
|
||||
.nav-pills>li.active>a, .nav-pills>li.active>a:focus, .nav-pills>li.active>a:hover {
|
||||
outline: 0;
|
||||
background-color:rgba(200,16,46,1);
|
||||
color:#FFF;
|
||||
}
|
||||
|
||||
.btn-primary[disabled]:hover,
|
||||
.btn-primary[disabled]:focus {
|
||||
background-color: #000;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.nav-pills>li>a {
|
||||
border-radius:0;
|
||||
}
|
||||
|
||||
.nav-pills>li>a:focus, .nav-pills>li>a:hover {
|
||||
outline: 0;
|
||||
background-color:rgba(200,16,46,1);
|
||||
color:#FFF;
|
||||
}
|
||||
|
||||
|
@ -104,26 +133,17 @@ textarea {
|
|||
padding: 0 !important;
|
||||
}
|
||||
|
||||
.panel-md-margin{
|
||||
background-color: white;
|
||||
overflow:hidden;
|
||||
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;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.col-content-left {
|
||||
position: sticky;
|
||||
top:50px;
|
||||
}
|
||||
}
|
||||
|
||||
.content-left-top {
|
||||
background:#fff;
|
||||
padding:10px 30px;
|
||||
|
@ -137,6 +157,14 @@ textarea {
|
|||
display:block;
|
||||
}
|
||||
|
||||
.content-left .buttons ul.nav-pills {
|
||||
margin-bottom:5px;
|
||||
}
|
||||
|
||||
.content-left .buttons ul.nav-pills li {
|
||||
margin:0 0 -5px;
|
||||
}
|
||||
|
||||
.content-left-top.frozen-account {
|
||||
background:#000FBA;
|
||||
color:#fff;
|
||||
|
@ -169,25 +197,22 @@ textarea {
|
|||
text-align:center;
|
||||
}
|
||||
|
||||
.content-right {
|
||||
margin:0 15px;
|
||||
@media (min-width: 768px) {
|
||||
.content-right {
|
||||
margin: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
.content-right-block {
|
||||
padding-bottom:5px;
|
||||
position:relative;
|
||||
}
|
||||
|
||||
.content-right-block:last-child {
|
||||
padding-bottom:15px;
|
||||
.content-right-block > *:not(.buttons-title) {
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.content-right-block > div:not(.buttons-title) {
|
||||
background:#fff;
|
||||
}
|
||||
|
||||
.content-right-block-transparent > div:not(.buttons-title) {
|
||||
background-color: transparent;
|
||||
.content-right-block > h2 {
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
.content-right-block .buttons-title {
|
||||
|
@ -209,9 +234,8 @@ textarea {
|
|||
|
||||
.content-right-block h3 {
|
||||
border-bottom: 1px solid #c8102e;
|
||||
margin: 20px 15px 15px;
|
||||
padding-bottom: 10px;
|
||||
padding-left: 20px;
|
||||
margin: 0px 15px 15px;
|
||||
padding: 20px 20px 10px;
|
||||
font-size:25px;
|
||||
}
|
||||
|
||||
|
@ -219,20 +243,34 @@ textarea {
|
|||
* Pages tableaux seuls
|
||||
*/
|
||||
|
||||
.content-center > div {
|
||||
background:#fff;
|
||||
.content-center > *:not(.content-right-block) {
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
@media (min-width: 992px) {
|
||||
.content-center {
|
||||
margin: 15px 0;
|
||||
}
|
||||
}
|
||||
|
||||
.content-center tbody tr:not(.section) td {
|
||||
padding:0px 5px !important;
|
||||
padding:0px 5px;
|
||||
}
|
||||
|
||||
.content-center .table .form-control {
|
||||
.table .form-control {
|
||||
padding: 1px 12px ;
|
||||
height:28px;
|
||||
margin:3px 0px;
|
||||
}
|
||||
.content-center .auth-form {
|
||||
|
||||
.table-condensed input.form-control {
|
||||
margin: 0 !important;
|
||||
border-top: 0;
|
||||
border-bottom: 0;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.content-center .auth-form {
|
||||
margin:15px;
|
||||
}
|
||||
|
||||
|
@ -240,15 +278,12 @@ textarea {
|
|||
* Pages formulaires seuls
|
||||
*/
|
||||
|
||||
.form-only .content-form {
|
||||
margin:15px;
|
||||
|
||||
background:#fff;
|
||||
|
||||
padding:15px;
|
||||
.content-form {
|
||||
background-color: #fff;
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.form-only .account_create #id_trigramme {
|
||||
.account_create #id_trigramme {
|
||||
display:block;
|
||||
width:200px;
|
||||
height:80px;
|
||||
|
@ -314,6 +349,10 @@ textarea {
|
|||
* Messages
|
||||
*/
|
||||
|
||||
.messages {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.messages .alert {
|
||||
padding:10px 15px;
|
||||
margin:0;
|
||||
|
@ -551,12 +590,12 @@ thead .tooltip {
|
|||
}
|
||||
}
|
||||
|
||||
.help-block {
|
||||
padding-top: 15px;
|
||||
}
|
||||
|
||||
/* Inventaires */
|
||||
|
||||
#inventoryform input[type=number] {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.inventory_modified {
|
||||
background:rgba(236,100,0,0.15);
|
||||
}
|
||||
|
@ -567,5 +606,51 @@ thead .tooltip {
|
|||
}
|
||||
|
||||
.inventory_update {
|
||||
display:none;
|
||||
display: none;
|
||||
width: 50px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
/* Multiple select customizations */
|
||||
|
||||
.ms-choice {
|
||||
height: 34px !important;
|
||||
line-height: 34px !important;
|
||||
border: 1px solid #ccc !important;
|
||||
box-shadow: inset 0 1px 1px rgba(0,0,0,.075) !important;
|
||||
}
|
||||
|
||||
.ms-choice > div {
|
||||
top: 4px !important;
|
||||
}
|
||||
|
||||
/* Checkbox select multiple */
|
||||
|
||||
.checkbox-select-multiple label {
|
||||
font-weight: normal;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
/* Statement creation */
|
||||
|
||||
.statement-create-summary table {
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.statement-create-summary tr td {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.statement-create-summary tr td:first-child {
|
||||
padding-right: 15px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.statement-create-summary tr td:last-child {
|
||||
width: 80px;
|
||||
}
|
||||
|
||||
#detail_taken table td,
|
||||
#detail_balance table td {
|
||||
padding: 0;
|
||||
}
|
||||
|
|
|
@ -18,6 +18,17 @@ input[type=number]::-webkit-outer-spin-button {
|
|||
100% { background: yellow; }
|
||||
}
|
||||
|
||||
/* Announcements banner */
|
||||
|
||||
#banner {
|
||||
background-color: #d86b01;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
padding: 10px;
|
||||
color: white;
|
||||
font-size: larger;
|
||||
}
|
||||
|
||||
/*
|
||||
* Top row
|
||||
*/
|
||||
|
@ -143,7 +154,7 @@ input[type=number]::-webkit-outer-spin-button {
|
|||
height:50px;
|
||||
padding:0 15px;
|
||||
|
||||
background:#c8102e;
|
||||
background:rgba(200,16,46,0.9);
|
||||
color:#fff;
|
||||
|
||||
font-weight:bold;
|
||||
|
@ -232,16 +243,21 @@ input[type=number]::-webkit-outer-spin-button {
|
|||
|
||||
float:left;
|
||||
|
||||
background:#c8102e;
|
||||
background: rgba(200,16,46,0.9);
|
||||
color:#FFF;
|
||||
|
||||
font-size:18px;
|
||||
font-weight:bold;
|
||||
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#special_operations button:focus, #special_operations button:hover {
|
||||
#special_operations button:focus,
|
||||
#special_operations button:hover {
|
||||
outline:none;
|
||||
background:#000;
|
||||
background: rgba(200,16,46,1);
|
||||
color:#fff;
|
||||
}
|
||||
|
||||
|
@ -256,15 +272,14 @@ input[type=number]::-webkit-outer-spin-button {
|
|||
height:100%;
|
||||
float:left;
|
||||
border:0;
|
||||
border-right:1px solid #c8102e;
|
||||
border-bottom:1px solid #c8102e;
|
||||
border-radius:0;
|
||||
border-bottom: 1px solid rgba(200,16,46,0.9);
|
||||
font-size:16px;
|
||||
font-weight:bold;
|
||||
}
|
||||
|
||||
#article_selection input+input #article_selection input+span {
|
||||
border-right:0;
|
||||
#article_selection input:first-child {
|
||||
border-right: 1px dashed rgba(200,16,46,0.9);
|
||||
}
|
||||
|
||||
#article_autocomplete {
|
||||
|
@ -319,7 +334,7 @@ input[type=number]::-webkit-outer-spin-button {
|
|||
|
||||
#articles_data table tr.category {
|
||||
height:35px;
|
||||
background-color:#c8102e;
|
||||
background-color:rgba(200,16,46,0.9);
|
||||
font-size:16px;
|
||||
color:#FFF;
|
||||
font-weight:bold;
|
||||
|
@ -423,3 +438,7 @@ input[type=number]::-webkit-outer-spin-button {
|
|||
.kpsul_middle_right_col {
|
||||
overflow:auto;
|
||||
}
|
||||
|
||||
.kpsul_middle_right_col #history .day {
|
||||
top: 0;
|
||||
}
|
||||
|
|
|
@ -1,47 +1,61 @@
|
|||
nav {
|
||||
background:#000;
|
||||
color:#DDD;
|
||||
font-family:Oswald;
|
||||
.navbar {
|
||||
background: #000;
|
||||
color: #DDD;
|
||||
font-family: Oswald;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.navbar-nav > li > .dropdown-menu {
|
||||
border:0;
|
||||
border-radius:0;
|
||||
.navbar .navbar-brand {
|
||||
padding: 3px 15px 3px 25px;
|
||||
}
|
||||
|
||||
.navbar-fixed-top {
|
||||
border:0;
|
||||
.navbar .navbar-brand img {
|
||||
height: 44px;
|
||||
}
|
||||
|
||||
nav .navbar-brand {
|
||||
padding:3px 15px 3px 25px;
|
||||
}
|
||||
|
||||
nav .navbar-brand img {
|
||||
height:44px;
|
||||
}
|
||||
|
||||
nav .navbar-toggle .icon-bar {
|
||||
background-color:#FFF;
|
||||
}
|
||||
|
||||
nav a {
|
||||
color:#DDD;
|
||||
.navbar .navbar-toggle .icon-bar {
|
||||
background-color: #FFF;
|
||||
}
|
||||
|
||||
.navbar-nav {
|
||||
font-weight:bold;
|
||||
font-size:14px;
|
||||
text-transform:uppercase;
|
||||
font-weight: bold;
|
||||
font-size: 14px;
|
||||
text-transform: uppercase;
|
||||
margin: 0 -15px;
|
||||
}
|
||||
|
||||
.nav>li>a:focus, .nav>li>a:hover {
|
||||
background-color:#C8102E;
|
||||
color:#FFF;
|
||||
@media (min-width: 768px) {
|
||||
.navbar-nav {
|
||||
margin: 0px;
|
||||
}
|
||||
.navbar-right {
|
||||
margin-right: -15px;
|
||||
}
|
||||
}
|
||||
|
||||
.nav .open>a, .nav .open>a:focus, .nav .open>a:hover {
|
||||
background-color:#C8102E;
|
||||
.navbar-nav a {
|
||||
transition: background-color, box-shadow, color;
|
||||
transition-duration: 0.15s;
|
||||
}
|
||||
|
||||
.navbar-nav > li > a {
|
||||
color: #FFF;
|
||||
}
|
||||
|
||||
.navbar-nav > li:hover > a,
|
||||
.navbar-nav > li > a:focus,
|
||||
.nav .open > a:hover,
|
||||
.nav .open > a:focus {
|
||||
background-color: #C8102E;
|
||||
color: #FFF;
|
||||
box-shadow: inset 0 5px 5px -5px #000;
|
||||
}
|
||||
|
||||
.navbar-nav .dropdown .dropdown-menu {
|
||||
padding: 0;
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
background-color: #FFF;
|
||||
}
|
||||
|
||||
#kfet-open {
|
||||
|
@ -62,24 +76,31 @@ nav a {
|
|||
line-height: 10px;
|
||||
}
|
||||
|
||||
.dropdown-menu {
|
||||
padding:0;
|
||||
.navbar-nav .dropdown .dropdown-menu > li > a {
|
||||
padding: 8px 20px;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.dropdown-menu>li>a {
|
||||
padding:8px 20px;
|
||||
.navbar-nav .dropdown .dropdown-menu > li > a:hover,
|
||||
.navbar-nav .dropdown .dropdown-meny > li > a:focus {
|
||||
color: #c8102e;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.dropdown-menu .divider {
|
||||
margin:0;
|
||||
.navbar-nav .dropdown .dropdown-menu .divider {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
.navbar-nav .open .dropdown-menu {
|
||||
background-color:#FFF;
|
||||
@media (min-width: 768px) {
|
||||
.navbar-nav .dropdown .dropdown-menu {
|
||||
display: block;
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
transition: opacity 0.15s;
|
||||
}
|
||||
|
||||
.navbar-nav {
|
||||
margin:0 -15px;
|
||||
.nav .dropdown:hover .dropdown-menu {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,14 +1,4 @@
|
|||
$(document).ready(function() {
|
||||
$(window).scroll(function() {
|
||||
if ($(window).width() >= 768 && $(this).scrollTop() > 72.6) {
|
||||
$('.col-content-left').css({'position':'fixed', 'top':'50px'});
|
||||
$('.col-content-right').addClass('col-sm-offset-4 col-md-offset-3');
|
||||
} else {
|
||||
$('.col-content-left').css({'position':'relative', 'top':'0'});
|
||||
$('.col-content-right').removeClass('col-sm-offset-4 col-md-offset-3');
|
||||
}
|
||||
});
|
||||
|
||||
if (typeof Cookies !== 'undefined') {
|
||||
// Retrieving csrf token
|
||||
csrftoken = Cookies.get('csrftoken');
|
||||
|
|
|
@ -61,7 +61,7 @@
|
|||
var chart = charts[i];
|
||||
|
||||
// format the data
|
||||
var chart_data = is_time_chart ? handleTimeChart(chart.values) : dictToArray(chart.values, 1);
|
||||
var chart_data = is_time_chart ? handleTimeChart(chart.values) : dictToArray(chart.values, 0);
|
||||
|
||||
chart_datasets.push(
|
||||
{
|
||||
|
@ -132,7 +132,7 @@
|
|||
type: 'line',
|
||||
options: chart_options,
|
||||
data: {
|
||||
labels: (data.labels || []).slice(1),
|
||||
labels: data.labels || [],
|
||||
datasets: chart_datasets,
|
||||
}
|
||||
};
|
||||
|
|
|
@ -4,6 +4,7 @@ from datetime import date, datetime, time, timedelta
|
|||
|
||||
from dateutil.relativedelta import relativedelta
|
||||
from dateutil.parser import parse as dateutil_parse
|
||||
import pytz
|
||||
|
||||
from django.utils import timezone
|
||||
from django.db.models import Sum
|
||||
|
@ -13,7 +14,8 @@ KFET_WAKES_UP_AT = time(7, 0)
|
|||
|
||||
def kfet_day(year, month, day, start_at=KFET_WAKES_UP_AT):
|
||||
"""datetime wrapper with time offset."""
|
||||
return datetime.combine(date(year, month, day), start_at)
|
||||
naive = datetime.combine(date(year, month, day), start_at)
|
||||
return pytz.timezone('Europe/Paris').localize(naive, is_dst=None)
|
||||
|
||||
|
||||
def to_kfet_day(dt, start_at=KFET_WAKES_UP_AT):
|
||||
|
@ -32,16 +34,21 @@ class Scale(object):
|
|||
self.std_chunk = std_chunk
|
||||
if last:
|
||||
end = timezone.now()
|
||||
if std_chunk:
|
||||
if begin is not None:
|
||||
begin = self.get_chunk_start(begin)
|
||||
if end is not None:
|
||||
end = self.do_step(self.get_chunk_start(end))
|
||||
|
||||
if begin is not None and n_steps != 0:
|
||||
self.begin = self.get_from(begin)
|
||||
self.begin = begin
|
||||
self.end = self.do_step(self.begin, n_steps=n_steps)
|
||||
elif end is not None and n_steps != 0:
|
||||
self.end = self.get_from(end)
|
||||
self.end = end
|
||||
self.begin = self.do_step(self.end, n_steps=-n_steps)
|
||||
elif begin is not None and end is not None:
|
||||
self.begin = self.get_from(begin)
|
||||
self.end = self.get_from(end)
|
||||
self.begin = begin
|
||||
self.end = end
|
||||
else:
|
||||
raise Exception('Two of these args must be specified: '
|
||||
'n_steps, begin, end; '
|
||||
|
@ -71,7 +78,7 @@ class Scale(object):
|
|||
def get_datetimes(self):
|
||||
datetimes = [self.begin]
|
||||
tmp = self.begin
|
||||
while tmp <= self.end:
|
||||
while tmp < self.end:
|
||||
tmp = self.do_step(tmp)
|
||||
datetimes.append(tmp)
|
||||
return datetimes
|
||||
|
@ -79,7 +86,103 @@ class Scale(object):
|
|||
def get_labels(self, label_fmt=None):
|
||||
if label_fmt is None:
|
||||
label_fmt = self.label_fmt
|
||||
return [begin.strftime(label_fmt) for begin, end in self]
|
||||
return [
|
||||
begin.strftime(label_fmt.format(i=i, rev_i=len(self)-i))
|
||||
for i, (begin, end) in enumerate(self)
|
||||
]
|
||||
|
||||
def chunkify_qs(self, qs, field=None):
|
||||
if field is None:
|
||||
field = 'at'
|
||||
begin_f = '{}__gte'.format(field)
|
||||
end_f = '{}__lte'.format(field)
|
||||
return [
|
||||
qs.filter(**{begin_f: begin, end_f: end})
|
||||
for begin, end in self
|
||||
]
|
||||
|
||||
def get_by_chunks(self, qs, field_callback=None, field_db='at'):
|
||||
"""Objects of queryset ranked according to the scale.
|
||||
|
||||
Returns a generator whose each item, corresponding to a scale chunk,
|
||||
is a generator of objects from qs for this chunk.
|
||||
|
||||
Args:
|
||||
qs: Queryset of source objects, must be ordered *first* on the
|
||||
same field returned by `field_callback`.
|
||||
field_callback: Callable which gives value from an object used
|
||||
to compare against limits of the scale chunks.
|
||||
Default to: lambda obj: getattr(obj, field_db)
|
||||
field_db: Used to filter against `scale` limits.
|
||||
Default to 'at'.
|
||||
|
||||
Examples:
|
||||
If queryset `qs` use `values()`, `field_callback` must be set and
|
||||
could be: `lambda d: d['at']`
|
||||
If `field_db` use foreign attributes (eg with `__`), it should be
|
||||
something like: `lambda obj: obj.group.at`.
|
||||
|
||||
"""
|
||||
if field_callback is None:
|
||||
def field_callback(obj):
|
||||
return getattr(obj, field_db)
|
||||
|
||||
begin_f = '{}__gte'.format(field_db)
|
||||
end_f = '{}__lte'.format(field_db)
|
||||
|
||||
qs = (
|
||||
qs
|
||||
.filter(**{begin_f: self.begin, end_f: self.end})
|
||||
)
|
||||
|
||||
obj_iter = iter(qs)
|
||||
|
||||
last_obj = None
|
||||
|
||||
def _objects_until(obj_iter, field_callback, end):
|
||||
"""Generator of objects until `end`.
|
||||
|
||||
Ends if objects source is empty or when an object not verifying
|
||||
field_callback(obj) <= end is met.
|
||||
|
||||
If this object exists, it is stored in `last_obj` which is found
|
||||
from outer scope.
|
||||
Also, if this same variable is non-empty when the function is
|
||||
called, it first yields its content.
|
||||
|
||||
Args:
|
||||
obj_iter: Source used to get objects.
|
||||
field_callback: Returned value, when it is called on an object
|
||||
will be used to test ordering against `end`.
|
||||
end
|
||||
|
||||
"""
|
||||
nonlocal last_obj
|
||||
|
||||
if last_obj is not None:
|
||||
yield last_obj
|
||||
last_obj = None
|
||||
|
||||
for obj in obj_iter:
|
||||
if field_callback(obj) <= end:
|
||||
yield obj
|
||||
else:
|
||||
last_obj = obj
|
||||
return
|
||||
|
||||
for begin, end in self:
|
||||
# forward last seen object, if it exists, to the right chunk,
|
||||
# and fill with empty generators for intermediate chunks of scale
|
||||
if last_obj is not None:
|
||||
if field_callback(last_obj) > end:
|
||||
yield iter(())
|
||||
continue
|
||||
|
||||
# yields generator for this chunk
|
||||
# this set last_obj to None if obj_iter reach its end, otherwise
|
||||
# it's set to the first met object from obj_iter which doesn't
|
||||
# belong to this chunk
|
||||
yield _objects_until(obj_iter, field_callback, end)
|
||||
|
||||
|
||||
class DayScale(Scale):
|
||||
|
@ -222,13 +325,3 @@ class ScaleMixin(object):
|
|||
|
||||
def get_default_scale(self):
|
||||
return DayScale(n_steps=7, last=True)
|
||||
|
||||
def chunkify_qs(self, qs, scale, field=None):
|
||||
if field is None:
|
||||
field = 'at'
|
||||
begin_f = '{}__gte'.format(field)
|
||||
end_f = '{}__lte'.format(field)
|
||||
return [
|
||||
qs.filter(**{begin_f: begin, end_f: end})
|
||||
for begin, end in scale
|
||||
]
|
||||
|
|
|
@ -1,67 +1,61 @@
|
|||
{% extends "kfet/base.html" %}
|
||||
{% extends "kfet/base_col_2.html" %}
|
||||
|
||||
{% block title %}Liste des comptes{% endblock %}
|
||||
{% block content-header-title %}Comptes{% endblock %}
|
||||
{% block header-title %}Comptes{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% block fixed-content %}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-4 col-md-3 col-content-left">
|
||||
<div class="content-left">
|
||||
<div class="content-left-top">
|
||||
<div class="line line-big">{{ accounts|length|add:-1 }}</div>
|
||||
<div class="line line-bigsub">compte{{ accounts|length|add:-1|pluralize }}</div>
|
||||
</div>
|
||||
<div class="buttons">
|
||||
<a class="btn btn-primary btn-lg" href="{% url 'kfet.account.create' %}">Créer un compte</a>
|
||||
{% if perms.kfet.manage_perms %}
|
||||
<a class="btn btn-primary btn-lg" href="{% url 'kfet.account.group' %}">Permissions</a>
|
||||
{% endif %}
|
||||
{% if perms.kfet.view_negs %}
|
||||
<a class="btn btn-primary btn-lg" href="{% url 'kfet.account.negative' %}">Négatifs</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-8 col-md-9 col-content-right">
|
||||
{% include 'kfet/base_messages.html' %}
|
||||
<div class="content-right">
|
||||
<div class="content-right-block">
|
||||
<h2>Liste des comptes</h2>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-condensed">
|
||||
<thead>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>Trigramme</td>
|
||||
<td>Nom</td>
|
||||
<td>Balance</td>
|
||||
<td>COF</td>
|
||||
<td>Dpt</td>
|
||||
<td>Promo</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for account in accounts %}
|
||||
<tr>
|
||||
<td class="text-center">
|
||||
<a href="{% url 'kfet.account.read' account.trigramme %}">
|
||||
<span class="glyphicon glyphicon-cog"></span>
|
||||
</a>
|
||||
</td>
|
||||
<td>{{ account.trigramme }}</td>
|
||||
<td>{{ account.name }}</td>
|
||||
<td class="text-right">{{ account.balance }}€</td>
|
||||
<td>{{ account.is_cof }}</td>
|
||||
<td>{{ account.departement }}</td>
|
||||
<td>{{ account.promo|default_if_none:'' }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="content-left-top">
|
||||
<div class="line line-big">{{ accounts|length|add:-1 }}</div>
|
||||
<div class="line line-bigsub">compte{{ accounts|length|add:-1|pluralize }}</div>
|
||||
</div>
|
||||
<div class="buttons">
|
||||
<a class="btn btn-primary btn-lg" href="{% url 'kfet.account.create' %}">Créer un compte</a>
|
||||
{% if perms.kfet.manage_perms %}
|
||||
<a class="btn btn-primary btn-lg" href="{% url 'kfet.account.group' %}">Permissions</a>
|
||||
{% endif %}
|
||||
{% if perms.kfet.view_negs %}
|
||||
<a class="btn btn-primary btn-lg" href="{% url 'kfet.account.negative' %}">Négatifs</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block main-content %}
|
||||
|
||||
<div class="content-right-block">
|
||||
<h2>Liste des comptes</h2>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-condensed">
|
||||
<thead>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>Trigramme</td>
|
||||
<td>Nom</td>
|
||||
<td>Balance</td>
|
||||
<td>COF</td>
|
||||
<td>Dpt</td>
|
||||
<td>Promo</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for account in accounts %}
|
||||
<tr>
|
||||
<td class="text-center">
|
||||
<a href="{% url 'kfet.account.read' account.trigramme %}">
|
||||
<span class="glyphicon glyphicon-cog"></span>
|
||||
</a>
|
||||
</td>
|
||||
<td>{{ account.trigramme }}</td>
|
||||
<td>{{ account.name }}</td>
|
||||
<td class="text-right">{{ account.balance }}€</td>
|
||||
<td>{{ account.is_cof|yesno:"Oui,Non" }}</td>
|
||||
<td>{{ account.departement }}</td>
|
||||
<td>{{ account.promo|default_if_none:'' }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -1,45 +1,39 @@
|
|||
{% extends "kfet/base.html" %}
|
||||
{% extends "kfet/base_col_1.html" %}
|
||||
{% load staticfiles %}
|
||||
|
||||
{% block title %}Nouveau compte{% endblock %}
|
||||
{% block header-title %}Création d'un compte{% endblock %}
|
||||
|
||||
{% block extra_head %}
|
||||
<script src="{% static "autocomplete_light/autocomplete.js" %}" type="text/javascript"></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block content-header-title %}Création d'un compte{% endblock %}
|
||||
{% block main-class %}content-form{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% block main-content %}
|
||||
|
||||
{% include 'kfet/base_messages.html' %}
|
||||
|
||||
<div class="row form-only">
|
||||
<div class="col-sm-12 col-md-8 col-md-offset-2">
|
||||
<div class="content-form">
|
||||
<form action="{% url "kfet.account.create" %}" method="post" class="account_create">
|
||||
{% csrf_token %}
|
||||
<div>
|
||||
{{ trigramme_form.trigramme.errors }}
|
||||
{{ trigramme_form.trigramme }}
|
||||
</div>
|
||||
<div id="trigramme_valid"></div>
|
||||
<p class="help-block">Les mots contenant des caractères non alphanumériques seront ignorés</p>
|
||||
<input type="text" name="q" id="search_autocomplete" spellcheck="false" placeholder="Chercher un utilisateur par nom, prénom ou identifiant clipper" class="form-control">
|
||||
<div style="position:relative;">
|
||||
<div id="search_results"></div>
|
||||
</div>
|
||||
<div class="form-horizontal">
|
||||
<div id="form-placeholder">
|
||||
{% include 'kfet/account_create_form.html' %}
|
||||
</div>
|
||||
{% if not perms.kfet.add_account %}
|
||||
{% include 'kfet/form_authentication_snippet.html' %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<form action="" method="post" class="account_create">
|
||||
{% csrf_token %}
|
||||
<div>
|
||||
{{ trigramme_form.trigramme.errors }}
|
||||
{{ trigramme_form.trigramme }}
|
||||
</div>
|
||||
</div>
|
||||
<div id="trigramme_valid"></div>
|
||||
<p class="help-block">Les mots contenant des caractères non alphanumériques seront ignorés</p>
|
||||
<input type="text" name="q" id="search_autocomplete" spellcheck="false" placeholder="Chercher un utilisateur par nom, prénom ou identifiant clipper" class="form-control">
|
||||
<div style="position:relative;">
|
||||
<div id="search_results"></div>
|
||||
</div>
|
||||
<div class="form-horizontal">
|
||||
<div id="form-placeholder">
|
||||
{% include 'kfet/account_create_form.html' %}
|
||||
</div>
|
||||
{% if not perms.kfet.add_account %}
|
||||
{% include 'kfet/form_authentication_snippet.html' %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<script type="text/javascript">
|
||||
$(document).ready(function() {
|
||||
// Affichage des résultats d'autocomplétion
|
||||
|
|
|
@ -2,44 +2,37 @@
|
|||
{% load staticfiles %}
|
||||
|
||||
{% block title %}Nouveau compte{% endblock %}
|
||||
{% block header-title %}Création d'un compte{% endblock %}
|
||||
|
||||
{% block extra_head %}
|
||||
<script src="{% static "autocomplete_light/autocomplete.js" %}" type="text/javascript"></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block content-header-title %}Création d'un compte{% endblock %}
|
||||
{% block main-class %}content-form{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% block main-content %}
|
||||
|
||||
{% include 'kfet/base_messages.html' %}
|
||||
|
||||
<div class="row form-only">
|
||||
<div class="col-sm-12 col-md-8 col-md-offset-2">
|
||||
<div class="content-form">
|
||||
<form action="{% url "kfet.account.create_special" %}" method="post" class="account_create">
|
||||
{% csrf_token %}
|
||||
<div>
|
||||
{{ trigramme_form.trigramme.errors }}
|
||||
{{ trigramme_form.trigramme }}
|
||||
{{ balance_form }}
|
||||
</div>
|
||||
<div id="trigramme_valid"></div>
|
||||
<input type="text" name="q" id="search_autocomplete" spellcheck="false" placeholder="Chercher un utilisateur par nom, prénom ou identifiant clipper" class="form-control">
|
||||
<div style="position:relative;">
|
||||
<div id="search_results"></div>
|
||||
</div>
|
||||
<div class="form-horizontal">
|
||||
<div id="form-placeholder">
|
||||
{% include 'kfet/account_create_form.html' %}
|
||||
</div>
|
||||
{% if not perms.kfet.add_account %}
|
||||
{% include 'kfet/form_authentication_snippet.html' %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<form action="" method="post" class="account_create">
|
||||
{% csrf_token %}
|
||||
<div>
|
||||
{{ trigramme_form.trigramme.errors }}
|
||||
{{ trigramme_form.trigramme }}
|
||||
{{ balance_form }}
|
||||
</div>
|
||||
</div>
|
||||
<div id="trigramme_valid"></div>
|
||||
<input type="text" name="q" id="search_autocomplete" spellcheck="false" placeholder="Chercher un utilisateur par nom, prénom ou identifiant clipper" class="form-control">
|
||||
<div style="position:relative;">
|
||||
<div id="search_results"></div>
|
||||
</div>
|
||||
<div class="form-horizontal">
|
||||
<div id="form-placeholder">
|
||||
{% include 'kfet/account_create_form.html' %}
|
||||
</div>
|
||||
{% if not perms.kfet.add_account %}
|
||||
{% include 'kfet/form_authentication_snippet.html' %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</form>
|
||||
<script type="text/javascript">
|
||||
$(document).ready(function() {
|
||||
// Affichage des résultats d'autocomplétion
|
||||
|
|
|
@ -1,54 +1,51 @@
|
|||
{% extends 'kfet/base.html' %}
|
||||
{% extends "kfet/base_col_2.html" %}
|
||||
|
||||
{% block title %}Groupes de comptes{% endblock %}
|
||||
{% block content-header-title %}Groupes de comptes{% endblock %}
|
||||
{% block header-title %}Groupes de comptes{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% block fixed-content %}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-4 col-md-3 col-content-left">
|
||||
<div class="content-left">
|
||||
<div class="content-left-top text-center">
|
||||
<div class="line"></div>
|
||||
</div>
|
||||
<div class="buttons">
|
||||
<a class="btn btn-primary btn-lg" href="{% url 'kfet.account.group.create' %}">Créer un groupe</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-8 col-md-9 col-content-right">
|
||||
{% include 'kfet/base_messages.html' %}
|
||||
<div class="content-right">
|
||||
{% for group in groups %}
|
||||
<div class="content-right-block">
|
||||
<div class="buttons-title">
|
||||
<a class="btn btn-primary" href="{% url 'kfet.account.group.update' group.pk %}">
|
||||
<span class="glyphicon glyphicon-cog"></span>
|
||||
</a>
|
||||
</div>
|
||||
<h2>{{ group.name }}</h2>
|
||||
<div class="row">
|
||||
<div class="col-sm-6">
|
||||
<h3>Permissions</h3>
|
||||
<ul>
|
||||
{% for perm in group.permissions.all %}
|
||||
<li>{{ perm.name }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
<h3>Comptes</h3>
|
||||
<ul>
|
||||
{% for user in group.user_set.all %}
|
||||
<li>{{ user.profile.account_kfet }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="buttons">
|
||||
<a class="btn btn-primary btn-lg" href="{% url 'kfet.account.group.create' %}">Créer un groupe</a>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block main-content %}
|
||||
|
||||
{% for group in groups %}
|
||||
<div class="content-right-block">
|
||||
<div class="buttons-title">
|
||||
<a class="btn btn-primary" href="{% url 'kfet.account.group.update' group.pk %}">
|
||||
<span class="glyphicon glyphicon-cog"></span>
|
||||
</a>
|
||||
</div>
|
||||
<h2>{{ group.name }}</h2>
|
||||
<div class="row">
|
||||
<div class="col-sm-6">
|
||||
<h3>Permissions</h3>
|
||||
{% regroup group.permissions.all by content_type as grouped_perms %}
|
||||
<ul class="list-unstyled">
|
||||
{% for perms_group in grouped_perms %}
|
||||
<li><b>{{ perms_group.grouper|title }}</b>
|
||||
<ul class="list-unstyled">
|
||||
{% for perm in perms_group.list %}
|
||||
<li style="padding-left: 20px">{{ perm.name }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
<h3>Comptes</h3>
|
||||
<ul class="list-unstyled">
|
||||
{% for user in group.user_set.all %}
|
||||
<li>{{ user.profile.account_kfet }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
{% endblock %}
|
||||
|
|
|
@ -1,37 +1,46 @@
|
|||
{% extends 'kfet/base.html' %}
|
||||
{% extends 'kfet/base_col_1.html' %}
|
||||
{% load staticfiles %}
|
||||
{% load widget_tweaks %}
|
||||
|
||||
{% block extra_head %}
|
||||
<link rel="stylesheet" text="text/css" href="{% static 'kfet/css/multiple-select.css' %}">
|
||||
<script src="{% static 'kfet/js/multiple-select.js' %}"></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% block title %}Permissions - Édition{% endblock %}
|
||||
{% block header-title %}Modification des permissions{% endblock %}
|
||||
|
||||
<form action="" method="post">
|
||||
{% block main-class %}content-form{% endblock %}
|
||||
|
||||
{% block main-content %}
|
||||
|
||||
<form action="" method="post" class="form-horizontal">
|
||||
{% csrf_token %}
|
||||
<div>
|
||||
{{ form.name.errors }}
|
||||
{{ form.name.label_tag }}
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon">K-Fêt</span>
|
||||
{{ form.name }}
|
||||
<div class="form-group">
|
||||
<label for="{{ form.name.id_for_label }}" class="col-sm-2 control-label">{{ form.name.label }}</label>
|
||||
<div class="col-sm-10">
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon">K-Fêt</span>
|
||||
{{ form.name|add_class:"form-control" }}
|
||||
</div>
|
||||
{% if form.name.errors %}<span class="help-block">{{ form.name.errors }}</span>{% endif %}
|
||||
{% if form.name.help_text %}<span class="help-block">{{ form.name.help_text }}</span>{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
{{ form.permissions.errors }}
|
||||
{{ form.permissions.label_tag }}
|
||||
{{ form.permissions }}
|
||||
</div>
|
||||
<input type="submit" value="Enregistrer">
|
||||
{% include "kfet/form_field_snippet.html" with field=form.permissions %}
|
||||
{% if not perms.kfet.manage_perms %}
|
||||
{% include "kfet/form_authentication_snippet.html" %}
|
||||
{% endif %}
|
||||
{% include "kfet/form_submit_snippet.html" with value="Enregistrer" %}
|
||||
</form>
|
||||
|
||||
<script type="text/javascript">
|
||||
$(document).ready(function() {
|
||||
$("select").multipleSelect({
|
||||
width: 500,
|
||||
filter: true,
|
||||
});
|
||||
let $name_input = $("#id_name");
|
||||
let raw_name = $name_input.val();
|
||||
let prefix = "K-Fêt ";
|
||||
if (raw_name.startsWith(prefix))
|
||||
$name_input.val(raw_name.substring(prefix.length));
|
||||
});
|
||||
</script>
|
||||
|
||||
|
|
|
@ -1,79 +1,73 @@
|
|||
{% extends 'kfet/base.html' %}
|
||||
{% extends "kfet/base_col_2.html" %}
|
||||
|
||||
{% block title %}Comptes en négatifs{% endblock %}
|
||||
{% block content-header-title %}Comptes - Négatifs{% endblock %}
|
||||
{% block title %}Comptes - Négatifs{% endblock %}
|
||||
{% block header-title %}Comptes en négatifs{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% block fixed-content %}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-4 col-md-3 col-content-left">
|
||||
<div class="content-left">
|
||||
<div class="content-left-top">
|
||||
<div class="line line-big">{{ negatives|length }}</div>
|
||||
<div class="line line-bigsub">compte{{ negatives|length|pluralize }} en négatif</div>
|
||||
<div class="block">
|
||||
<div class="line"><b>Total:</b> {{ negatives_sum|floatformat:2 }}€</div>
|
||||
</div>
|
||||
<div class="block">
|
||||
<div class="line"><b>Découvert autorisé par défaut</b></div>
|
||||
<div class="line">Montant: {{ settings.overdraft_amount }}€</div>
|
||||
<div class="line">Pendant: {{ settings.overdraft_duration }}</div>
|
||||
</div>
|
||||
</div>
|
||||
{% if perms.kfet.change_settings %}
|
||||
<div class="buttons">
|
||||
<a class="btn btn-primary btn-lg" href="{% url 'kfet.settings' %}">Modifier les valeurs par défaut</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="content-left-top">
|
||||
<div class="line line-big">{{ negatives|length }}</div>
|
||||
<div class="line line-bigsub">compte{{ negatives|length|pluralize }} en négatif</div>
|
||||
<div class="block">
|
||||
<div class="line"><b>Total:</b> {{ negatives_sum|floatformat:2 }}€</div>
|
||||
</div>
|
||||
<div class="col-sm-8 col-md-9 col-content-right">
|
||||
{% include 'kfet/base_messages.html' %}
|
||||
<div class="content-right">
|
||||
<div class="content-right-block">
|
||||
<h2>Liste des comptes en négatifs</h2>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-condensed">
|
||||
<thead>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>Tri</td>
|
||||
<td>Nom</td>
|
||||
<td>Balance</td>
|
||||
<td>Réelle</td>
|
||||
<td>Début</td>
|
||||
<td>Découvert autorisé</td>
|
||||
<td>Jusqu'au</td>
|
||||
<td>Balance offset</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for neg in negatives %}
|
||||
<tr>
|
||||
<td class="text-center">
|
||||
<a href="{% url 'kfet.account.update' neg.account.trigramme %}">
|
||||
<span class="glyphicon glyphicon-cog"></span>
|
||||
</a>
|
||||
</td>
|
||||
<td>{{ neg.account.trigramme }}</td>
|
||||
<td>{{ neg.account.name }}</td>
|
||||
<td class="text-right">{{ neg.account.balance|floatformat:2 }}€</td>
|
||||
<td class="text-right">
|
||||
{% if neg.balance_offset %}
|
||||
{{ neg.account.real_balance|floatformat:2 }}€
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ neg.start|date:'d/m/Y H:i:s'}}</td>
|
||||
<td>{{ neg.authz_overdraft_amount|default_if_none:'' }}</td>
|
||||
<td>{{ neg.authz_overdrafy_until|default_if_none:'' }}</td>
|
||||
<td>{{ neg.balance_offset|default_if_none:'' }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="block">
|
||||
<div class="line"><b>Découvert autorisé par défaut</b></div>
|
||||
<div class="line">Montant: {{ kfet_config.overdraft_amount }}€</div>
|
||||
<div class="line">Pendant: {{ kfet_config.overdraft_duration }}</div>
|
||||
</div>
|
||||
</div>
|
||||
{% if perms.kfet.change_settings %}
|
||||
<div class="buttons">
|
||||
<a class="btn btn-primary btn-lg" href="{% url 'kfet.settings' %}">Modifier les valeurs par défaut</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block main-content %}
|
||||
|
||||
<div class="content-right-block">
|
||||
<h2>Liste des comptes en négatifs</h2>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-condensed">
|
||||
<thead>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>Tri</td>
|
||||
<td>Nom</td>
|
||||
<td>Balance</td>
|
||||
<td>Réelle</td>
|
||||
<td>Début</td>
|
||||
<td>Découvert autorisé</td>
|
||||
<td>Jusqu'au</td>
|
||||
<td>Balance offset</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for neg in negatives %}
|
||||
<tr>
|
||||
<td class="text-center">
|
||||
<a href="{% url 'kfet.account.update' neg.account.trigramme %}">
|
||||
<span class="glyphicon glyphicon-cog"></span>
|
||||
</a>
|
||||
</td>
|
||||
<td>{{ neg.account.trigramme }}</td>
|
||||
<td>{{ neg.account.name }}</td>
|
||||
<td class="text-right">{{ neg.account.balance|floatformat:2 }}€</td>
|
||||
<td class="text-right">
|
||||
{% if neg.balance_offset %}
|
||||
{{ neg.account.real_balance|floatformat:2 }}€
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ neg.start|date:'d/m/Y H:i:s'}}</td>
|
||||
<td>{{ neg.authz_overdraft_amount|default_if_none:'' }}</td>
|
||||
<td>{{ neg.authz_overdrafy_until|default_if_none:'' }}</td>
|
||||
<td>{{ neg.balance_offset|default_if_none:'' }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{% extends "kfet/base.html" %}
|
||||
{% extends "kfet/base_col_2.html" %}
|
||||
{% load staticfiles %}
|
||||
{% load kfet_tags %}
|
||||
{% load l10n %}
|
||||
|
@ -8,7 +8,6 @@
|
|||
<script type="text/javascript" src="{% static 'kfet/js/moment.js' %}"></script>
|
||||
<script type="text/javascript" src="{% static 'kfet/js/moment-fr.js' %}"></script>
|
||||
<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>
|
||||
|
@ -18,26 +17,18 @@
|
|||
$(document).ready(function() {
|
||||
var stat_last = new StatsGroup(
|
||||
"{% url 'kfet.account.stat.operation.list' trigramme=account.trigramme %}",
|
||||
$("#stat_last"),
|
||||
$("#stat_last")
|
||||
);
|
||||
var stat_balance = new StatsGroup(
|
||||
"{% url 'kfet.account.stat.balance.list' trigramme=account.trigramme %}",
|
||||
$("#stat_balance"),
|
||||
$("#stat_balance")
|
||||
);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block title %}
|
||||
{% if account.user == request.user %}
|
||||
Mon compte
|
||||
{% else %}
|
||||
Informations du compte {{ account.trigramme }}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block content-header-title %}
|
||||
{% if account.user == request.user %}
|
||||
Mon compte
|
||||
{% else %}
|
||||
|
@ -45,62 +36,51 @@ $(document).ready(function() {
|
|||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% block header-title %}
|
||||
{% if account.user == request.user %}
|
||||
Mon compte
|
||||
{% else %}
|
||||
Informations du compte {{ account.trigramme }}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-4 col-md-3 col-content-left">
|
||||
<div class="content-left">
|
||||
{% include 'kfet/left_account.html' %}
|
||||
{% block fixed-content %}
|
||||
{% include "kfet/left_account.html" %}
|
||||
{% endblock %}
|
||||
|
||||
{% block main-content %}
|
||||
|
||||
<div class="tab-content">
|
||||
{% if account.user == request.user %}
|
||||
<div class="content-right-block tab-pane fade in active" id="tab_stats">
|
||||
<h2>Statistiques</h2>
|
||||
<div>
|
||||
<h3>Ma balance</h3>
|
||||
<div id="stat_balance"></div>
|
||||
<h3>Ma consommation</h3>
|
||||
<div id="stat_last"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-8 col-md-9 col-content-right">
|
||||
{% include "kfet/base_messages.html" %}
|
||||
<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">
|
||||
<h2>Historique</h2>
|
||||
<div id="history">
|
||||
</div>
|
||||
</div>
|
||||
</div><!-- content-right-block -->
|
||||
{% endif %}
|
||||
<div class="content-right-block tab-pane fade {% if account.user != request.user %}in active{% endif %}" id="tab_history">
|
||||
{% 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>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
<h2>Historique</h2>
|
||||
<div id="history"></div>
|
||||
</div><!-- content-right-block -->
|
||||
</div><!-- tab-content -->
|
||||
|
||||
<script type="text/javascript">
|
||||
$(document).ready(function() {
|
||||
settings = { 'subvention_cof': parseFloat({{ settings.subvention_cof|unlocalize }})}
|
||||
settings = { 'subvention_cof': parseFloat({{ kfet_config.subvention_cof|unlocalize }})}
|
||||
|
||||
khistory = new KHistory({
|
||||
display_trigramme: false,
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
{% extends "kfet/base.html" %}
|
||||
{% load widget_tweaks %}
|
||||
{% load staticfiles %}
|
||||
{% extends "kfet/base_col_1.html" %}
|
||||
|
||||
{% block extra_head %}
|
||||
{{ negative_form.media }}
|
||||
|
@ -14,7 +12,7 @@
|
|||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block content-header-title %}
|
||||
{% block header-title %}
|
||||
{% if account.user == request.user %}
|
||||
Modification de mes informations
|
||||
{% else %}
|
||||
|
@ -22,29 +20,23 @@
|
|||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% block main-class %}content-form{% endblock %}
|
||||
|
||||
{% include "kfet/base_messages.html" %}
|
||||
{% block main-content %}
|
||||
|
||||
<div class="row form-only">
|
||||
<div class="col-sm-12 col-md-8 col-md-offset-2">
|
||||
<div class="content-form">
|
||||
<form action="{% url 'kfet.account.update' account.trigramme %}" method="post" class="form-horizontal">
|
||||
{% csrf_token %}
|
||||
{% include 'kfet/form_snippet.html' with form=user_form %}
|
||||
{% include 'kfet/form_snippet.html' with form=cof_form %}
|
||||
{% include 'kfet/form_snippet.html' with form=account_form %}
|
||||
{% include 'kfet/form_snippet.html' with form=group_form %}
|
||||
{% include 'kfet/form_snippet.html' with form=pwd_form %}
|
||||
{% include 'kfet/form_snippet.html' with form=negative_form %}
|
||||
{% if perms.kfet.is_team %}
|
||||
{% include 'kfet/form_authentication_snippet.html' %}
|
||||
{% endif %}
|
||||
{% include 'kfet/form_submit_snippet.html' with value="Mettre à jour" %}
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<form action="" method="post" class="form-horizontal">
|
||||
{% csrf_token %}
|
||||
{% include 'kfet/form_snippet.html' with form=user_form %}
|
||||
{% include 'kfet/form_snippet.html' with form=cof_form %}
|
||||
{% include 'kfet/form_snippet.html' with form=account_form %}
|
||||
{% include 'kfet/form_snippet.html' with form=group_form %}
|
||||
{% include 'kfet/form_snippet.html' with form=pwd_form %}
|
||||
{% include 'kfet/form_snippet.html' with form=negative_form %}
|
||||
{% if perms.kfet.is_team %}
|
||||
{% include 'kfet/form_authentication_snippet.html' %}
|
||||
{% endif %}
|
||||
{% include 'kfet/form_submit_snippet.html' with value="Mettre à jour" %}
|
||||
</form>
|
||||
|
||||
<script type="text/javascript">
|
||||
|
||||
|
|
|
@ -1,71 +1,64 @@
|
|||
{% extends 'kfet/base.html' %}
|
||||
{% extends 'kfet/base_col_2.html' %}
|
||||
|
||||
{% block title %}Articles{% endblock %}
|
||||
{% block content-header-title %}Articles{% endblock %}
|
||||
{% block header-title %}Articles{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% block fixed-content %}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-4 col-md-3 col-content-left">
|
||||
<div class="content-left">
|
||||
<div class="content-left-top">
|
||||
<div class="line line-big">{{ articles|length }}</div>
|
||||
<div class="line line-bigsub">article{{ articles|length|pluralize }}</div>
|
||||
</div>
|
||||
<div class="buttons">
|
||||
<a class="btn btn-primary btn-lg" href="{% url 'kfet.article.create' %}">
|
||||
Nouvel article
|
||||
</a>
|
||||
<a class="btn btn-primary btn-lg" href="{% url 'kfet.category' %}">
|
||||
Catégories
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-8 col-md-9 col-content-right">
|
||||
{% include 'kfet/base_messages.html' %}
|
||||
<div class="content-right">
|
||||
<div class="content-right-block">
|
||||
<h2>Liste des articles</h2>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-condensed">
|
||||
<thead>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>Nom</td>
|
||||
<td class="text-right">Prix</td>
|
||||
<td class="text-right">Stock</td>
|
||||
<td class="text-right">En vente</td>
|
||||
<td class="text-right">Affiché</td>
|
||||
<td class="text-right">Dernier inventaire</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for article in articles %}
|
||||
{% ifchanged article.category %}
|
||||
<tr class="section">
|
||||
<td colspan="7">{{ article.category.name }}</td>
|
||||
</tr>
|
||||
{% endifchanged %}
|
||||
<tr>
|
||||
<td class="text-center">
|
||||
<a href="{% url 'kfet.article.read' article.pk %}">
|
||||
<span class="glyphicon glyphicon-cog"></span>
|
||||
</a>
|
||||
</td>
|
||||
<td>{{ article.name }}</td>
|
||||
<td class="text-right">{{ article.price }}€</td>
|
||||
<td class="text-right">{{ article.stock }}</td>
|
||||
<td class="text-right">{{ article.is_sold | yesno:"En vente,Non vendu"}}</td>
|
||||
<td class="text-right">{{ article.hidden | yesno:"Caché,Affiché" }}</td>
|
||||
<td class="text-right">{{ article.inventory.0.at }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="content-left-top">
|
||||
<div class="line line-big">{{ articles|length }}</div>
|
||||
<div class="line line-bigsub">article{{ articles|length|pluralize }}</div>
|
||||
</div>
|
||||
<div class="buttons">
|
||||
<a class="btn btn-primary btn-lg" href="{% url 'kfet.article.create' %}">
|
||||
Nouvel article
|
||||
</a>
|
||||
<a class="btn btn-primary btn-lg" href="{% url 'kfet.category' %}">
|
||||
Catégories
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block main-content %}
|
||||
<div class="content-right-block">
|
||||
<h2>Liste des articles</h2>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-condensed">
|
||||
<thead>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>Nom</td>
|
||||
<td class="text-right">Prix</td>
|
||||
<td class="text-right">Stock</td>
|
||||
<td class="text-right">En vente</td>
|
||||
<td class="text-right">Affiché</td>
|
||||
<td class="text-right">Dernier inventaire</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for article in articles %}
|
||||
{% ifchanged article.category %}
|
||||
<tr class="section">
|
||||
<td colspan="7">{{ article.category.name }}</td>
|
||||
</tr>
|
||||
{% endifchanged %}
|
||||
<tr>
|
||||
<td class="text-center">
|
||||
<a href="{% url 'kfet.article.read' article.pk %}">
|
||||
<span class="glyphicon glyphicon-cog"></span>
|
||||
</a>
|
||||
</td>
|
||||
<td>{{ article.name }}</td>
|
||||
<td class="text-right">{{ article.price }}€</td>
|
||||
<td class="text-right">{{ article.stock }}</td>
|
||||
<td class="text-right">{{ article.is_sold | yesno:"En vente,Non vendu"}}</td>
|
||||
<td class="text-right">{{ article.hidden | yesno:"Caché,Affiché" }}</td>
|
||||
<td class="text-right">{{ article.inventory.0.at }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -1,27 +1,12 @@
|
|||
{% extends 'kfet/base.html' %}
|
||||
{% load widget_tweaks %}
|
||||
{% load staticfiles %}
|
||||
{% extends 'kfet/base_col_1.html' %}
|
||||
|
||||
{% block title %}Nouvel article{% endblock %}
|
||||
{% block content-header-title %}Création d'un article{% endblock %}
|
||||
{% block header-title %}Création d'un article{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% block main-class %}content-form{% endblock %}
|
||||
|
||||
{% include "kfet/base_messages.html" %}
|
||||
{% block main-content %}
|
||||
|
||||
<div class="row form-only">
|
||||
<div class="col-sm-12 col-md-8 col-md-offset-2">
|
||||
<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.add_article %}
|
||||
{% include 'kfet/form_authentication_snippet.html' %}
|
||||
{% endif %}
|
||||
{% include 'kfet/form_submit_snippet.html' with value="Enregistrer" %}
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% include "kfet/base_form.html" with authz=perms.kfet.add_article submit_text="Enregistrer" %}
|
||||
|
||||
{% endblock %}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{% extends 'kfet/base.html' %}
|
||||
{% extends 'kfet/base_col_2.html' %}
|
||||
{% load staticfiles %}
|
||||
|
||||
{% block extra_head %}
|
||||
|
@ -6,97 +6,89 @@
|
|||
<script type="text/javascript" src="{% static 'kfet/js/statistic.js' %}"></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block title %}Informations sur l'article {{ article }}{% endblock %}
|
||||
{% block content-header-title %}Article - {{ article.name }}{% endblock %}
|
||||
{% block title %}Article - {{ article.name }}{% endblock %}
|
||||
{% block header-title %}Informations sur l'article {{ article.name }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% block fixed-content %}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-4 col-md-3 col-content-left">
|
||||
<div class="content-left">
|
||||
<div class="content-left-top">
|
||||
<div class="line line-big">{{ article.name }}</div>
|
||||
<div class="line line-bigsub">{{ article.category }}</div>
|
||||
<div class="block">
|
||||
<div class="line">Prix (hors réduc.): {{ article.price }}€</div>
|
||||
<div class="line">Stock: {{ article.stock }}</div>
|
||||
<div class="line">En vente: {{ article.is_sold | yesno:"Oui,Non" }}</div>
|
||||
<div class="line">Affiché: {{ article.hidden | yesno:"Non,Oui" }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="buttons">
|
||||
<a class="btn btn-primary btn-lg" href="{% url 'kfet.article.update' article.pk %}">
|
||||
Modifier
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="content-left-top">
|
||||
<div class="line line-big">{{ article.name }}</div>
|
||||
<div class="line line-bigsub">{{ article.category }}</div>
|
||||
<div class="block">
|
||||
<div class="line">Prix (hors réduc.): {{ article.price }}€</div>
|
||||
<div class="line">Stock: {{ article.stock }}</div>
|
||||
<div class="line">En vente: {{ article.is_sold | yesno:"Oui,Non" }}</div>
|
||||
<div class="line">Affiché: {{ article.hidden | yesno:"Non,Oui" }}</div>
|
||||
</div>
|
||||
<div class="col-sm-8 col-md-9 col-content-right">
|
||||
{% include 'kfet/base_messages.html' %}
|
||||
<div class="content-right">
|
||||
<div class="content-right-block">
|
||||
<h2>Historique</h2>
|
||||
<div class="row">
|
||||
<div class="col-sm-6">
|
||||
<h3>Inventaires</h3>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<td>Date</td>
|
||||
<td>Stock</td>
|
||||
<td>Erreur</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for inventoryart in inventoryarts %}
|
||||
<tr>
|
||||
<td>{{ inventoryart.inventory.at }}</td>
|
||||
<td>{{ inventoryart.stock_new }}</td>
|
||||
<td>{{ inventoryart.stock_error }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
<h3>Prix fournisseurs</h3>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<td>Date</td>
|
||||
<td>Fournisseur</td>
|
||||
<td>HT</td>
|
||||
<td>TVA</td>
|
||||
<td>Droits</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for supplierart in supplierarts %}
|
||||
<tr>
|
||||
<td>{{ supplierart.at }}</td>
|
||||
<td>{{ supplierart.supplier.name }}</td>
|
||||
<td>{{ supplierart.price_HT }}</td>
|
||||
<td>{{ supplierart.TVA }}</td>
|
||||
<td>{{ supplierart.rights }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div><!-- /row-->
|
||||
</div>
|
||||
<div class="content-right-block content-right-block-transparent">
|
||||
<h2>Statistiques</h2>
|
||||
<div class="row">
|
||||
<div class="col-sm-12 nopadding">
|
||||
<div class="panel-md-margin">
|
||||
<h3>Ventes de {{ article.name }}</h3>
|
||||
<div id="stat_last"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div><!-- /row -->
|
||||
</div>
|
||||
<div class="buttons">
|
||||
<a class="btn btn-primary btn-lg" href="{% url 'kfet.article.update' article.pk %}">
|
||||
Modifier
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block main-content %}
|
||||
|
||||
<div class="content-right-block">
|
||||
<h2>Historique</h2>
|
||||
<div class="row" style="padding-bottom: 15px">
|
||||
<div class="col-md-6">
|
||||
<h3>Inventaires</h3>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<td>Date</td>
|
||||
<td>Stock</td>
|
||||
<td>Erreur</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for inventoryart in inventoryarts %}
|
||||
<tr>
|
||||
<td>{{ inventoryart.inventory.at }}</td>
|
||||
<td>{{ inventoryart.stock_new }}</td>
|
||||
<td>{{ inventoryart.stock_error }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<h3>Prix fournisseurs</h3>
|
||||
<div class="table-responsive">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<td>Date</td>
|
||||
<td>Fournisseur</td>
|
||||
<td>HT</td>
|
||||
<td>TVA</td>
|
||||
<td>Droits</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for supplierart in supplierarts %}
|
||||
<tr>
|
||||
<td>{{ supplierart.at }}</td>
|
||||
<td>{{ supplierart.supplier.name }}</td>
|
||||
<td>{{ supplierart.price_HT }}</td>
|
||||
<td>{{ supplierart.TVA }}</td>
|
||||
<td>{{ supplierart.rights }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div><!-- /row-->
|
||||
</div>
|
||||
<div class="content-right-block">
|
||||
<h2>Statistiques</h2>
|
||||
<div>
|
||||
<h3>Ventes</h3>
|
||||
<div id="stat_last"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -104,7 +96,7 @@
|
|||
$(document).ready(function() {
|
||||
var stat_last = new StatsGroup(
|
||||
"{% url 'kfet.article.stat.sales.list' article.id %}",
|
||||
$("#stat_last"),
|
||||
$("#stat_last")
|
||||
);
|
||||
});
|
||||
</script>
|
||||
|
|
|
@ -1,27 +1,12 @@
|
|||
{% extends 'kfet/base.html' %}
|
||||
{% load widget_tweaks %}
|
||||
{% load staticfiles %}
|
||||
{% extends "kfet/base_col_1.html" %}
|
||||
|
||||
{% block title %}Édition de l'article {{ article.name }}{% endblock %}
|
||||
{% block content-header-title %}Article {{ article.name }} - Édition{% endblock %}
|
||||
{% block title %}{{ article.name }} - Édition{% endblock %}
|
||||
{% block header-title %}Édition de l'article {{ article.name }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% block main-class %}content-form{% endblock %}
|
||||
|
||||
{% include "kfet/base_messages.html" %}
|
||||
{% block main-content %}
|
||||
|
||||
<div class="row form-only">
|
||||
<div class="col-sm-12 col-md-8 col-md-offset-2">
|
||||
<div class="content-form">
|
||||
<form action="" method="post" class="form-horizontal">
|
||||
{% csrf_token %}
|
||||
{% include 'kfet/form_snippet.html' with form=form %}
|
||||
{% if not perms.kfet.change_article %}
|
||||
{% include 'kfet/form_authentication_snippet.html' %}
|
||||
{% endif %}
|
||||
{% include 'kfet/form_submit_snippet.html' with value="Mettre à jour" %}
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% include "kfet/base_form.html" with authz=perms.kfet.change_article submit_text="Mettre à jour"%}
|
||||
|
||||
{% endblock %}
|
||||
|
|
|
@ -56,12 +56,12 @@
|
|||
<body>
|
||||
{% include "kfet/base_nav.html" %}
|
||||
<div class="container-fluid">
|
||||
{% block content-header %}
|
||||
<div class="row row-page-header">
|
||||
<div class="col-lg-12">
|
||||
<h1 class="page-header">{% block content-header-title %}{% endblock %}</h1>
|
||||
</div>
|
||||
{% block header %}
|
||||
<div class="row row-page-header">
|
||||
<div class="col-lg-12">
|
||||
<h1 class="page-header">{% block header-title %}{% endblock %}</h1>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% block content %}{% endblock %}
|
||||
{% include "kfet/base_footer.html" %}
|
||||
|
|
14
kfet/templates/kfet/base_col_1.html
Normal file
14
kfet/templates/kfet/base_col_1.html
Normal file
|
@ -0,0 +1,14 @@
|
|||
{% extends "kfet/base.html" %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div class="row">
|
||||
<div class="nopadding {% block main-size %}col-md-8 col-md-offset-2{% endblock %}">
|
||||
{% include "kfet/base_messages.html" %}
|
||||
<div class="content-center {% block main-class %}{% endblock %}">
|
||||
{% block main-content %}{% endblock %}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
|
||||
{% endblock %}
|
19
kfet/templates/kfet/base_col_2.html
Normal file
19
kfet/templates/kfet/base_col_2.html
Normal file
|
@ -0,0 +1,19 @@
|
|||
{% extends "kfet/base.html" %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-content-left {% block fixed-size %}col-sm-4 col-md-3{% endblock %}">
|
||||
<div class="content-left">
|
||||
{% block fixed-content %}{% endblock %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-content-right {% block main-size %}col-sm-8 col-md-9{% endblock %}">
|
||||
{% include "kfet/base_messages.html" %}
|
||||
<div class="content-right">
|
||||
{% block main-content %}{% endblock %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
10
kfet/templates/kfet/base_form.html
Normal file
10
kfet/templates/kfet/base_form.html
Normal file
|
@ -0,0 +1,10 @@
|
|||
{% load kfet_tags %}
|
||||
|
||||
<form action="" method="post" class="form-horizontal">
|
||||
{% csrf_token %}
|
||||
{% include "kfet/form_snippet.html" %}
|
||||
{% if not authz %}
|
||||
{% include "kfet/form_authentication_snippet.html" %}
|
||||
{% endif %}
|
||||
{% include "kfet/form_submit_snippet.html" with value=submit_text %}
|
||||
</form>
|
|
@ -1,8 +1,15 @@
|
|||
{% if config.gestion_banner %}
|
||||
<div id="banner" class="container">
|
||||
<span class="glyphicon glyphicon-bullhorn"></span>
|
||||
<span>{{ config.gestion_banner }}</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if messages %}
|
||||
<div class="row messages">
|
||||
{% for message in messages %}
|
||||
<div class="col-sm-12 item">
|
||||
<div class="alert alert-{{ message.level_tag }} alert-dismissible fade in{% if message.tags %} {{ message.tags }}{% endif %}">
|
||||
<div class="col-sm-12 nopadding">
|
||||
<div class="alert alert-{{ message.level_tag }} alert-dismissible fade in {{ message.tags }}">
|
||||
<button type="button" class="close" data-dismiss="alert"><span aria-hidden="true">×</span></button>
|
||||
{% if 'safe' in message.tags %}
|
||||
{{ message|safe }}
|
||||
|
|
|
@ -9,47 +9,51 @@
|
|||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
</button>
|
||||
<a class="navbar-brand" href="#">
|
||||
<a class="navbar-brand" href="{% url 'kfet.home' %}">
|
||||
<img src="{% static 'kfet/img/logo3.png' %}">
|
||||
</a>
|
||||
<span id="kfet-open-wrapper"><span id="kfet-open"></span></span>
|
||||
</div>
|
||||
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
|
||||
<ul class="nav navbar-nav">
|
||||
<li><a href="{% url 'kfet.home' %}">Home</a></li>
|
||||
<li class="hidden-xs">
|
||||
<a href="{% url 'kfet.home' %}">
|
||||
<span class="glyphicon glyphicon-home"></span>
|
||||
</a>
|
||||
</li>
|
||||
{% if user.profile.account_kfet %}
|
||||
<li>
|
||||
<a href="{% url 'kfet.account.read' user.profile.account_kfet.trigramme %}">Mon compte</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
<ul class="nav navbar-nav navbar-right">
|
||||
{% if user.username == 'kfet_genericteam' %}
|
||||
<li class="navbar-text">Équipe standard</li>
|
||||
{% endif %}
|
||||
{% if user.profile.account_kfet %}
|
||||
<li>
|
||||
<a href="{% url 'kfet.account.read' user.profile.account_kfet.trigramme %}">Mes infos</a>
|
||||
</li>
|
||||
<li class="navbar-text">Équipe standard</li>
|
||||
{% endif %}
|
||||
{% if perms.kfet.is_team %}
|
||||
<li class="dropdown">
|
||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Admin <span class="caret"></span></a>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a href="{% url 'kfet.kpsul' %}">K-Psul</a></li>
|
||||
<li><a href="{% url 'kfet.history' %}">Historique</a></li>
|
||||
<li><a href="{% url 'kfet.transfers' %}">Transferts</a></li>
|
||||
<li class="divider"></li>
|
||||
<li><a href="{% url 'kfet.account' %}">Comptes</a></li>
|
||||
<li><a href="{% url 'kfet.checkout' %}">Caisses</a></li>
|
||||
<li class="divider"></li>
|
||||
<li><a href="{% url 'kfet.article' %}">Articles</a></li>
|
||||
<li><a href="{% url 'kfet.inventory' %}">Inventaires</a></li>
|
||||
<li><a href="{% url 'kfet.order' %}">Commandes</a></li>
|
||||
{% if user.username != 'kfet_genericteam' %}
|
||||
<li class="divider"></li>
|
||||
<li><a href="{% url 'kfet.login.genericteam' %}" target="_blank" id="genericteam">Connexion standard</a></li>
|
||||
{% endif %}
|
||||
{% if perms.kfet.change_settings %}
|
||||
<li><a href="{% url 'kfet.settings' %}">Paramètres</a></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</li>
|
||||
<li><a href="{% url 'kfet.kpsul' %}">K-Psul</a></li>
|
||||
<li><a href="{% url 'kfet.history' %}">Historique</a></li>
|
||||
<li><a href="{% url 'kfet.transfers' %}">Transferts</a></li>
|
||||
<li class="dropdown">
|
||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Admin <span class="caret"></span></a>
|
||||
<ul class="dropdown-menu">
|
||||
<li class="divider"></li>
|
||||
<li><a href="{% url 'kfet.account' %}">Comptes</a></li>
|
||||
<li><a href="{% url 'kfet.checkout' %}">Caisses</a></li>
|
||||
<li class="divider"></li>
|
||||
<li><a href="{% url 'kfet.article' %}">Articles</a></li>
|
||||
<li><a href="{% url 'kfet.inventory' %}">Inventaires</a></li>
|
||||
<li><a href="{% url 'kfet.order' %}">Commandes</a></li>
|
||||
{% if user.username != 'kfet_genericteam' %}
|
||||
<li class="divider"></li>
|
||||
<li><a href="{% url 'kfet.login.genericteam' %}" target="_blank" id="genericteam">Connexion standard</a></li>
|
||||
{% endif %}
|
||||
{% if perms.kfet.change_settings %}
|
||||
<li><a href="{% url 'kfet.settings' %}">Paramètres</a></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if user.is_authenticated %}
|
||||
<li><a href="{% url 'gestioncof.views.logout' %}?next=/k-fet/" title="Déconnexion"><span class="glyphicon glyphicon-log-out"></span></a></li>
|
||||
|
|
|
@ -1,52 +1,46 @@
|
|||
{% extends 'kfet/base.html' %}
|
||||
{% extends "kfet/base_col_2.html" %}
|
||||
|
||||
{% block title %}Categories d'articles{% endblock %}
|
||||
{% block content-header-title %}Categories d'articles{% endblock %}
|
||||
{% block header-title %}Categories d'articles{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% block fixed-content %}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-4 col-md-3 col-content-left">
|
||||
<div class="content-left">
|
||||
<div class="content-left-top">
|
||||
<div class="line line-big">{{ categories|length }}</div>
|
||||
<div class="line line-bigsub">catégorie{{ categories|length|pluralize }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-8 col-md-9 col-content-right">
|
||||
{% include 'kfet/base_messages.html' %}
|
||||
<div class="content-right">
|
||||
<div class="content-right-block">
|
||||
<h2>Liste des catégories</h2>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-condensed">
|
||||
<thead>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>Nom</td>
|
||||
<td class="text-right">Nombre d'articles</td>
|
||||
<td class="text-right">Peut être majorée</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for category in categories %}
|
||||
<tr>
|
||||
<td class="text-center">
|
||||
<a href="{% url 'kfet.category.update' category.pk %}">
|
||||
<span class="glyphicon glyphicon-cog"></span>
|
||||
</a>
|
||||
</td>
|
||||
<td>{{ category.name }}</td>
|
||||
<td class="text-right">{{ category.articles.all|length }}</td>
|
||||
<td class="text-right">{{ category.has_addcost | yesno:"Oui,Non"}}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="content-left-top">
|
||||
<div class="line line-big">{{ categories|length }}</div>
|
||||
<div class="line line-bigsub">catégorie{{ categories|length|pluralize }}</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block main-content %}
|
||||
|
||||
<div class="content-right-block">
|
||||
<h2>Liste des catégories</h2>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-condensed">
|
||||
<thead>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>Nom</td>
|
||||
<td class="text-right">Nombre d'articles</td>
|
||||
<td class="text-right">Peut être majorée</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for category in categories %}
|
||||
<tr>
|
||||
<td class="text-center">
|
||||
<a href="{% url 'kfet.category.update' category.pk %}">
|
||||
<span class="glyphicon glyphicon-cog"></span>
|
||||
</a>
|
||||
</td>
|
||||
<td>{{ category.name }}</td>
|
||||
<td class="text-right">{{ category.articles.all|length }}</td>
|
||||
<td class="text-right">{{ category.has_addcost | yesno:"Oui,Non"}}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -1,25 +1,12 @@
|
|||
{% extends 'kfet/base.html' %}
|
||||
{% extends "kfet/base_col_1.html" %}
|
||||
|
||||
{% block title %}Édition de la catégorie {{ category.name }}{% endblock %}
|
||||
{% block content-header-title %}Catégorie {{ category.name }} - Édition{% endblock %}
|
||||
{% block title %}{{ articlecategory.name }} - Édition{% endblock %}
|
||||
{% block header-title %}Édition de la catégorie {{ articlecategory.name }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% block main-class %}content-form{% endblock %}
|
||||
|
||||
{% include "kfet/base_messages.html" %}
|
||||
{% block main-content %}
|
||||
|
||||
<div class="row form-only">
|
||||
<div class="col-sm-12 col-md-8 col-md-offset-2">
|
||||
<div class="content-form">
|
||||
<form action="" method="post" class="form-horizontal">
|
||||
{% csrf_token %}
|
||||
{% include 'kfet/form_snippet.html' with form=form %}
|
||||
{% if not perms.kfet.edit_articlecategory %}
|
||||
{% include 'kfet/form_authentication_snippet.html' %}
|
||||
{% endif %}
|
||||
{% include 'kfet/form_submit_snippet.html' with value="Enregistrer"%}
|
||||
<form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% include "kfet/base_form.html" with authz=perms.kfet.edit_articlecategory submit_text="Enregistrer"%}
|
||||
|
||||
{% endblock %}
|
||||
|
|
|
@ -1,59 +1,53 @@
|
|||
{% extends "kfet/base.html" %}
|
||||
{% extends "kfet/base_col_2.html" %}
|
||||
|
||||
{% block title %}Liste des caisses{% endblock %}
|
||||
{% block content-header-title %}Caisses{% endblock %}
|
||||
{% block title %}Caisses{% endblock %}
|
||||
{% block header-title %}Caisses{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% block fixed-content %}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-4 col-md-3 col-content-left">
|
||||
<div class="content-left">
|
||||
<div class="content-left-top text-center">
|
||||
<div class="line line-big">{{ checkouts|length }}</div>
|
||||
<div class="line line-bigsub">caisse{{ checkouts|length|pluralize }}</div>
|
||||
</div>
|
||||
<div class="buttons">
|
||||
<a class="btn btn-primary btn-lg" href="{% url 'kfet.checkout.create' %}">Créer une caisse</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-8 col-md-9 col-content-right">
|
||||
{% include 'kfet/base_messages.html' %}
|
||||
<div class="content-right">
|
||||
<div class="content-right-block">
|
||||
<h2>Liste des caisses</h2>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-condensed">
|
||||
<thead>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>Nom</td>
|
||||
<td class="text-right">Balance</td>
|
||||
<td class="text-right">Déb. valid.</td>
|
||||
<td class="text-right">Fin valid.</td>
|
||||
<td class="text-right">Protégée</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for checkout in checkouts %}
|
||||
<tr>
|
||||
<td>
|
||||
<a href="{% url 'kfet.checkout.read' checkout.pk %}">
|
||||
<span class="glyphicon glyphicon-cog"></span>
|
||||
</a>
|
||||
</td>
|
||||
<td>{{ checkout.name }}</td>
|
||||
<td class="text-right">{{ checkout.balance}}€</td>
|
||||
<td class="text-right">{{ checkout.valid_from }}</td>
|
||||
<td class="text-right">{{ checkout.valid_to }}</td>
|
||||
<td class="text-right">{{ checkout.is_protected }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="content-left-top text-center">
|
||||
<div class="line line-big">{{ checkouts|length }}</div>
|
||||
<div class="line line-bigsub">caisse{{ checkouts|length|pluralize }}</div>
|
||||
</div>
|
||||
<div class="buttons">
|
||||
<a class="btn btn-primary btn-lg" href="{% url 'kfet.checkout.create' %}">Créer une caisse</a>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block main-content %}
|
||||
|
||||
<div class="content-right-block">
|
||||
<h2>Liste des caisses</h2>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-condensed">
|
||||
<thead>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>Nom</td>
|
||||
<td class="text-right">Balance</td>
|
||||
<td class="text-right">Déb. valid.</td>
|
||||
<td class="text-right">Fin valid.</td>
|
||||
<td class="text-right">Protégée</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for checkout in checkouts %}
|
||||
<tr>
|
||||
<td>
|
||||
<a href="{% url 'kfet.checkout.read' checkout.pk %}">
|
||||
<span class="glyphicon glyphicon-cog"></span>
|
||||
</a>
|
||||
</td>
|
||||
<td>{{ checkout.name }}</td>
|
||||
<td class="text-right">{{ checkout.balance}}€</td>
|
||||
<td class="text-right">{{ checkout.valid_from }}</td>
|
||||
<td class="text-right">{{ checkout.valid_to }}</td>
|
||||
<td class="text-right">{{ checkout.is_protected }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -1,28 +1,13 @@
|
|||
{% extends "kfet/base.html" %}
|
||||
{% extends "kfet/base_col_1.html" %}
|
||||
{% block extra_head %}{{ form.media }}{% endblock %}
|
||||
|
||||
{% block title %}Nouvelle caisse{% endblock %}
|
||||
{% block content-header-title %}Création d'une caisse{% endblock %}
|
||||
{% block header-title %}Création d'une caisse{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% block main-class %}content-form{% endblock %}
|
||||
{% block main-content %}
|
||||
|
||||
{% include 'kfet/base_messages.html' %}
|
||||
<form action="" method="post">
|
||||
{% csrf_token %}
|
||||
{{ form.non_field_errors}}
|
||||
{% for field in form %}
|
||||
{{ field.errors }}
|
||||
{{ field.label_tag }}
|
||||
<div style="position:relative">{{ field }}</div>
|
||||
{% if field.help_text %}
|
||||
<p class="help">{{ field.help_text|safe }}</p>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% if not perms.kfet.add_checkout %}
|
||||
<input type="password" name="KFETPASSWORD">
|
||||
{% endif %}
|
||||
<input type="submit" value="Enregistrer">
|
||||
</form>
|
||||
{% include "kfet/base_form.html" with authz=perms.kfet.add_checkout submit_text="Enregistrer" %}
|
||||
|
||||
<script type="text/javascript">
|
||||
|
||||
|
|
|
@ -1,50 +1,41 @@
|
|||
{% extends 'kfet/base.html' %}
|
||||
{% extends "kfet/base_col_2.html" %}
|
||||
|
||||
{% block title %}Informations sur la caisse {{ checkout.name }}{% endblock %}
|
||||
{% block content-header-title %}Caisse - {{ checkout.name }}{% endblock %}
|
||||
{% block title %}Caisse - {{ checkout.name }}{% endblock %}
|
||||
{% block header-title %}Informations sur la caisse {{ checkout.name }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% block fixed-content %}
|
||||
{% include 'kfet/left_checkout.html' %}
|
||||
{% endblock %}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-4 col-md-3 col-content-left">
|
||||
<div class="content-left">
|
||||
{% include 'kfet/left_checkout.html' %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-8 col-md-9 col-content-right">
|
||||
{% include "kfet/base_messages.html" %}
|
||||
<div class="content-right">
|
||||
<div class="content-right-block">
|
||||
<h2>Relevés</h2>
|
||||
<div>
|
||||
{% if not statements %}
|
||||
Pas de relevé
|
||||
{% else %}
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>Date/heure</td>
|
||||
<td>Montant pris</td>
|
||||
<td>Montant laissé</td>
|
||||
<td>Erreur</td>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for statement in statements %}
|
||||
<tr>
|
||||
<td><a href="{% url 'kfet.checkoutstatement.update' checkout.pk statement.pk %}"><span class="glyphicon glyphicon-cog"></span></a></td>
|
||||
<td>{{ statement.at }}</td>
|
||||
<td>{{ statement.amount_taken }}</td>
|
||||
<td>{{ statement.balance_new }}</td>
|
||||
<td>{{ statement.amount_error }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% block main-content %}
|
||||
<div class="content-right-block">
|
||||
<h2>Relevés</h2>
|
||||
<div class="table-responsive">
|
||||
{% if not statements %}
|
||||
Pas de relevé
|
||||
{% else %}
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>Date/heure</td>
|
||||
<td>Montant pris</td>
|
||||
<td>Montant laissé</td>
|
||||
<td>Erreur</td>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for statement in statements %}
|
||||
<tr>
|
||||
<td><a href="{% url 'kfet.checkoutstatement.update' checkout.pk statement.pk %}"><span class="glyphicon glyphicon-cog"></span></a></td>
|
||||
<td>{{ statement.at }}</td>
|
||||
<td>{{ statement.amount_taken }}</td>
|
||||
<td>{{ statement.balance_new }}</td>
|
||||
<td>{{ statement.amount_error }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -1,39 +1,18 @@
|
|||
{% extends 'kfet/base.html' %}
|
||||
{% extends "kfet/base_col_2.html" %}
|
||||
{% block extra_head %}{{ form.media }}{% endblock %}
|
||||
|
||||
{% block title %}Édition de la caisse {{ checkout.name }}{% endblock %}
|
||||
{% block content-header-title %}Caisse {{ checkout.name }} - Édition{% endblock %}
|
||||
{% block title %}Caisse {{ checkout.name }} - Édition{% endblock %}
|
||||
{% block header-title %}Édition de la caisse {{ checkout.name }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% block fixed-content %}
|
||||
{% include "kfet/left_checkout.html" %}
|
||||
{% endblock %}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-4 col-md-3 col-content-left">
|
||||
<div class="content-left">
|
||||
{% include 'kfet/left_checkout.html' %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-8 col-md-9 col-content-right">
|
||||
{% include 'kfet/base_messages.html' %}
|
||||
<div class="content-right">
|
||||
<div class="content-right-block">
|
||||
<form action="" method="post">
|
||||
{% csrf_token %}
|
||||
{{ form.non_field_errors }}
|
||||
{% for field in form %}
|
||||
{{ field.errors }}
|
||||
{{ field.label_tag }}
|
||||
<div style="position:relative">{{ field }}</div>
|
||||
{% if field.help_text %}
|
||||
<p class="help">{{ field.help_text|safe }}</p>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% if not perms.kfet.add_checkout %}
|
||||
<input type="password" name="KFETPASSWORD">
|
||||
{% endif %}
|
||||
<input type=submit value="Mettre à jour">
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% block main-content %}
|
||||
|
||||
<div class="content-right-block">
|
||||
<div style="padding: 15px;">
|
||||
{% include "kfet/base_form.html" with authz=perms.kfet.change_checkout submit_text="Mettre à jour" %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -1,139 +1,188 @@
|
|||
{% extends "kfet/base.html" %}
|
||||
{% extends "kfet/base_col_2.html" %}
|
||||
{% load l10n %}
|
||||
{% load widget_tweaks %}
|
||||
|
||||
{% block title %}Nouveau relevé{% endblock %}
|
||||
{% block content-header-title %}Caisse {{ checkout.name }} - Nouveau relevé{% endblock %}
|
||||
{% block title %}Nouveau relevé - {{ checkout.name }}{% endblock %}
|
||||
{% block header-title %}Création d'un relevé pour la caisse {{ checkout.name }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% block fixed-content %}
|
||||
{% include "kfet/left_checkout.html" %}
|
||||
{% endblock %}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-4 col-md-3 col-content-left">
|
||||
<div class="content-left">
|
||||
{% include 'kfet/left_checkout.html' %}
|
||||
{% block main-content %}
|
||||
<form action="" method="post">
|
||||
{% csrf_token %}
|
||||
<div class="content-right-block">
|
||||
<h2>Général</h2>
|
||||
<div class="content-form statement-create-summary">
|
||||
<table>
|
||||
<tr>
|
||||
<td>
|
||||
<label for="{{ form.not_count.id_for_label }}" class="control-label">{{ form.not_count.label }}</label>
|
||||
</td>
|
||||
<td>{{ form.not_count }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Ancienne balance</td>
|
||||
<td>
|
||||
<span id="balance_old">{{ checkout.balance|unlocalize }}</span> €
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Nouvelle balance</td>
|
||||
<td><span id="balance_new">0</span> €</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Pris</td>
|
||||
<td><span id="amount_taken">0</span> €</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Erreur</td>
|
||||
<td><span id="amount_error">0</span> €</td>
|
||||
</tr>
|
||||
</table>
|
||||
{% if not perms.kfet.add_checkoutstatement %}
|
||||
{% include "kfet/form_authentication_snippet.html" %}
|
||||
{% endif %}
|
||||
<br>
|
||||
<div class="row text-center">
|
||||
<input type="submit" value="Enregistrer" class="btn btn-lg btn-primary">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-8 col-md-9 col-content-right">
|
||||
{% include "kfet/base_messages.html" %}
|
||||
<div class="content-right">
|
||||
<form action="" method="post">
|
||||
{% csrf_token %}
|
||||
<div class="content-right-block">
|
||||
<h2>Général</h2>
|
||||
<div>
|
||||
<label>
|
||||
Ne pas compter la caisse
|
||||
{{ form.not_count }}
|
||||
</label><br>
|
||||
Ancienne balance : <span id="balance_old">{{ checkout.balance|unlocalize }}</span> €<br>
|
||||
Nouvelle balance : <span id="balance_new">0</span> €<br>
|
||||
Pris : <span id="amount_taken">0</span> €<br>
|
||||
Erreur : <span id="amount_error">0</span> €<br>
|
||||
{% if not perms.kfet.add_checkoutstatement %}
|
||||
<label for="password">Mot de passe:</label>
|
||||
<input type="password" id="password" name="KFETPASSWORD">
|
||||
{% endif %}
|
||||
<input type="submit" value="Enregistrer">
|
||||
</div>
|
||||
</div>
|
||||
<div class="content-right-block">
|
||||
<h2>Pris</h2>
|
||||
<div id="detail_taken">
|
||||
<table class="table table-bordered">
|
||||
<tr style="font-weight:bold;">
|
||||
<td>5€</td>
|
||||
<td>10€</td>
|
||||
<td>20€</td>
|
||||
<td>50€</td>
|
||||
<td>100€</td>
|
||||
<td>200€</td>
|
||||
<td>500€</td>
|
||||
<tr>
|
||||
<tr>
|
||||
<td><input id="id_taken_5" name="taken_5" data-value="5" min="0" value="0" type="number" class="form-control" required></td>
|
||||
<td><input id="id_taken_10" name="taken_10" data-value="10" min="0" value="0" type="number" class="form-control" required></td>
|
||||
<td><input id="id_taken_20" name="taken_20" data-value="20" min="0" value="0" type="number" class="form-control" required></td>
|
||||
<td><input id="id_taken_50" name="taken_50" data-value="50" min="0" value="0" type="number" class="form-control" required></td>
|
||||
<td><input id="id_taken_100" name="taken_100" data-value="100" min="0" value="0" type="number" class="form-control" required></td>
|
||||
<td><input id="id_taken_200" name="taken_200" data-value="200" min="0" value="0" type="number" class="form-control" required></td>
|
||||
<td><input id="id_taken_500" name="taken_500" data-value="500" min="0" value="0" type="number" class="form-control" required></td>
|
||||
</tr>
|
||||
</table>
|
||||
<table class="table table-bordered">
|
||||
<tr style="font-weight:bold;">
|
||||
<td>2€</td>
|
||||
<td>1€</td>
|
||||
<td>0.50€</td>
|
||||
<td>0.20€</td>
|
||||
<td>0.10€</td>
|
||||
<td>0.05€</td>
|
||||
<td>0.02€</td>
|
||||
<td>0.01€</td>
|
||||
<tr>
|
||||
<tr>
|
||||
<td><input id="id_taken_2" name="taken_2" data-value="2" min="0" value="0" type="number" class="form-control" required></td>
|
||||
<td><input id="id_taken_1" name="taken_1" data-value="1" min="0" value="0" type="number" class="form-control" required></td>
|
||||
<td><input id="id_taken_05" name="taken_05" data-value="0.5" min="0" value="0" type="number" class="form-control" required></td>
|
||||
<td><input id="id_taken_02" name="taken_02" data-value="0.2" min="0" value="0" type="number" class="form-control" required></td>
|
||||
<td><input id="id_taken_01" name="taken_01" data-value="0.1" min="0" value="0" type="number" class="form-control" required></td>
|
||||
<td><input id="id_taken_005" name="taken_005" data-value="0.05" min="0" value="0" type="number" class="form-control" required></td>
|
||||
<td><input id="id_taken_002" name="taken_002" data-value="0.02" min="0" value="0" type="number" class="form-control" required></td>
|
||||
<td><input id="id_taken_001" name="taken_001" data-value="0.01" min="0" value="0" type="number" class="form-control" required></td>
|
||||
</tr>
|
||||
</table>
|
||||
<p style="font-weight:bold"> Chèque:</p> <input id="id_taken_cheque" name="taken_cheque" data-value="1" min="0" step="0.01" value="0" type="number" class="form-control" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="content-right-block">
|
||||
<h2>En caisse</h2>
|
||||
<div id="detail_balance">
|
||||
<table class="table table-bordered">
|
||||
<tr style="font-weight:bold;">
|
||||
<td>5€</td>
|
||||
<td>10€</td>
|
||||
<td>20€</td>
|
||||
<td>50€</td>
|
||||
<td>100€</td>
|
||||
<td>200€</td>
|
||||
<td>500€</td>
|
||||
<tr>
|
||||
<tr>
|
||||
<td><input id="id_balance_5" name="balance_5" data-value="5" min="0" value="0" type="number" class="form-control" required></td>
|
||||
<td><input id="id_balance_10" name="balance_10" data-value="10" min="0" value="0" type="number" class="form-control" required></td>
|
||||
<td><input id="id_balance_20" name="balance_20" data-value="20" min="0" value="0" type="number" class="form-control" required></td>
|
||||
<td><input id="id_balance_50" name="balance_50" data-value="50" min="0" value="0" type="number" class="form-control" required></td>
|
||||
<td><input id="id_balance_100" name="balance_100" data-value="100" min="0" value="0" type="number" class="form-control" required></td>
|
||||
<td><input id="id_balance_200" name="balance_200" data-value="200" min="0" value="0" type="number" class="form-control" required></td>
|
||||
<td><input id="id_balance_500" name="balance_500" data-value="500" min="0" value="0" type="number" class="form-control" required></td>
|
||||
</tr>
|
||||
</table>
|
||||
<table class="table table-bordered">
|
||||
<tr style="font-weight:bold;">
|
||||
<td>2€</td>
|
||||
<td>1€</td>
|
||||
<td>0.50€</td>
|
||||
<td>0.20€</td>
|
||||
<td>0.10€</td>
|
||||
<td>0.05€</td>
|
||||
<td>0.02€</td>
|
||||
<td>0.01€</td>
|
||||
<tr>
|
||||
<tr>
|
||||
<td><input id="id_balance_2" name="balance_2" data-value="2" min="0" value="0" type="number" class="form-control" required></td>
|
||||
<td><input id="id_balance_1" name="balance_1" data-value="1" min="0" value="0" type="number" class="form-control" required></td>
|
||||
<td><input id="id_balance_05" name="balance_05" data-value="0.5" min="0" value="0" type="number" class="form-control" required></td>
|
||||
<td><input id="id_balance_02" name="balance_02" data-value="0.2" min="0" value="0" type="number" class="form-control" required></td>
|
||||
<td><input id="id_balance_01" name="balance_01" data-value="0.1" min="0" value="0" type="number" class="form-control" required></td>
|
||||
<td><input id="id_balance_005" name="balance_005" data-value="0.05" min="0" value="0" type="number" class="form-control" required></td>
|
||||
<td><input id="id_balance_002" name="balance_002" data-value="0.02" min="0" value="0" type="number" class="form-control" required></td>
|
||||
<td><input id="id_balance_001" name="balance_001" data-value="0.01" min="0" value="0" type="number" class="form-control" required></td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<div class="content-right-block">
|
||||
<h2>Pris</h2>
|
||||
<div id="detail_taken">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-condensed">
|
||||
<thead>
|
||||
<tr>
|
||||
<td>5€</td>
|
||||
<td>10€</td>
|
||||
<td>20€</td>
|
||||
<td>50€</td>
|
||||
<td>100€</td>
|
||||
<td>200€</td>
|
||||
<td>500€</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><input id="id_taken_5" name="taken_5" data-value="5" min="0" value="0" type="number" class="form-control" required></td>
|
||||
<td><input id="id_taken_10" name="taken_10" data-value="10" min="0" value="0" type="number" class="form-control" required></td>
|
||||
<td><input id="id_taken_20" name="taken_20" data-value="20" min="0" value="0" type="number" class="form-control" required></td>
|
||||
<td><input id="id_taken_50" name="taken_50" data-value="50" min="0" value="0" type="number" class="form-control" required></td>
|
||||
<td><input id="id_taken_100" name="taken_100" data-value="100" min="0" value="0" type="number" class="form-control" required></td>
|
||||
<td><input id="id_taken_200" name="taken_200" data-value="200" min="0" value="0" type="number" class="form-control" required></td>
|
||||
<td><input id="id_taken_500" name="taken_500" data-value="500" min="0" value="0" type="number" class="form-control" required></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-condensed">
|
||||
<thead>
|
||||
<tr>
|
||||
<td>2€</td>
|
||||
<td>1€</td>
|
||||
<td>0.50€</td>
|
||||
<td>0.20€</td>
|
||||
<td>0.10€</td>
|
||||
<td>0.05€</td>
|
||||
<td>0.02€</td>
|
||||
<td>0.01€</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><input id="id_taken_2" name="taken_2" data-value="2" min="0" value="0" type="number" class="form-control" required></td>
|
||||
<td><input id="id_taken_1" name="taken_1" data-value="1" min="0" value="0" type="number" class="form-control" required></td>
|
||||
<td><input id="id_taken_05" name="taken_05" data-value="0.5" min="0" value="0" type="number" class="form-control" required></td>
|
||||
<td><input id="id_taken_02" name="taken_02" data-value="0.2" min="0" value="0" type="number" class="form-control" required></td>
|
||||
<td><input id="id_taken_01" name="taken_01" data-value="0.1" min="0" value="0" type="number" class="form-control" required></td>
|
||||
<td><input id="id_taken_005" name="taken_005" data-value="0.05" min="0" value="0" type="number" class="form-control" required></td>
|
||||
<td><input id="id_taken_002" name="taken_002" data-value="0.02" min="0" value="0" type="number" class="form-control" required></td>
|
||||
<td><input id="id_taken_001" name="taken_001" data-value="0.01" min="0" value="0" type="number" class="form-control" required></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-condensed">
|
||||
<thead>
|
||||
<tr><td>Chèques</td></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<input id="id_taken_cheque" name="taken_cheque" data-value="1" min="0" step="0.01" value="0" type="number" class="form-control" required>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="content-right-block">
|
||||
<h2>En caisse</h2>
|
||||
<div id="detail_balance">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-condensed">
|
||||
<thead>
|
||||
<tr>
|
||||
<td>5€</td>
|
||||
<td>10€</td>
|
||||
<td>20€</td>
|
||||
<td>50€</td>
|
||||
<td>100€</td>
|
||||
<td>200€</td>
|
||||
<td>500€</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><input id="id_balance_5" name="balance_5" data-value="5" min="0" value="0" type="number" class="form-control" required></td>
|
||||
<td><input id="id_balance_10" name="balance_10" data-value="10" min="0" value="0" type="number" class="form-control" required></td>
|
||||
<td><input id="id_balance_20" name="balance_20" data-value="20" min="0" value="0" type="number" class="form-control" required></td>
|
||||
<td><input id="id_balance_50" name="balance_50" data-value="50" min="0" value="0" type="number" class="form-control" required></td>
|
||||
<td><input id="id_balance_100" name="balance_100" data-value="100" min="0" value="0" type="number" class="form-control" required></td>
|
||||
<td><input id="id_balance_200" name="balance_200" data-value="200" min="0" value="0" type="number" class="form-control" required></td>
|
||||
<td><input id="id_balance_500" name="balance_500" data-value="500" min="0" value="0" type="number" class="form-control" required></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-condensed">
|
||||
<thead>
|
||||
<tr>
|
||||
<td>2€</td>
|
||||
<td>1€</td>
|
||||
<td>0.50€</td>
|
||||
<td>0.20€</td>
|
||||
<td>0.10€</td>
|
||||
<td>0.05€</td>
|
||||
<td>0.02€</td>
|
||||
<td>0.01€</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><input id="id_balance_2" name="balance_2" data-value="2" min="0" value="0" type="number" class="form-control" required></td>
|
||||
<td><input id="id_balance_1" name="balance_1" data-value="1" min="0" value="0" type="number" class="form-control" required></td>
|
||||
<td><input id="id_balance_05" name="balance_05" data-value="0.5" min="0" value="0" type="number" class="form-control" required></td>
|
||||
<td><input id="id_balance_02" name="balance_02" data-value="0.2" min="0" value="0" type="number" class="form-control" required></td>
|
||||
<td><input id="id_balance_01" name="balance_01" data-value="0.1" min="0" value="0" type="number" class="form-control" required></td>
|
||||
<td><input id="id_balance_005" name="balance_005" data-value="0.05" min="0" value="0" type="number" class="form-control" required></td>
|
||||
<td><input id="id_balance_002" name="balance_002" data-value="0.02" min="0" value="0" type="number" class="form-control" required></td>
|
||||
<td><input id="id_balance_001" name="balance_001" data-value="0.01" min="0" value="0" type="number" class="form-control" required></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<script type="text/javascript">
|
||||
$(document).ready(function() {
|
||||
|
|
|
@ -1,33 +1,15 @@
|
|||
{% extends 'kfet/base.html' %}
|
||||
{% extends "kfet/base_col_1.html" %}
|
||||
|
||||
{% block title %}Modification d'un relevé{% endblock %}
|
||||
{% block content-header-title %}
|
||||
Caisse {{ checkout.name }} - Modification relevé {{ checkoutstatement.at }}
|
||||
{% block header-title %}
|
||||
Caisse {{ checkout.name }}<br>
|
||||
Modification du relevé {{ checkoutstatement.at }}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% block main-class %}content-form{% endblock %}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-4 col-md-3 col-content-left">
|
||||
<div class="content-left">
|
||||
{% include 'kfet/left_checkout.html' %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-8 col-md-9 col-content-right">
|
||||
{% include 'kfet/base_messages.html' %}
|
||||
<div class="content-right form-only">
|
||||
<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_checkoutstatement %}
|
||||
{% include 'kfet/form_authentication_snippet.html' %}
|
||||
{% endif %}
|
||||
{% include 'kfet/form_submit_snippet.html' with value="Enregistrer" %}
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% block main-content %}
|
||||
|
||||
{% include "kfet/base_form.html" with authz=perms.kfet.change_checkoutstatement submit_text="Enregistrer"%}
|
||||
|
||||
{% endblock %}
|
||||
|
|
|
@ -3,7 +3,19 @@
|
|||
<div class="form-group">
|
||||
<label for="{{ field.id_for_label }}" class="col-sm-2 control-label">{{ field.label }}</label>
|
||||
<div class="col-sm-10">
|
||||
{{ field|add_class:'form-control' }}
|
||||
{% if field|widget_type == "checkboxselectmultiple" %}
|
||||
<ul class="list-unstyled checkbox-select-multiple">
|
||||
{% for choice in form.permissions %}
|
||||
<li class="col-sm-6 col-lg-4">
|
||||
<label for="{{ choice.id_for_label }}">
|
||||
{{ choice.tag }} {{ choice.choice_label }}
|
||||
</label>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% else %}
|
||||
{{ field|add_class:'form-control' }}
|
||||
{% endif %}
|
||||
{% if field.errors %}
|
||||
<span class="help-block">{{field.errors}}</span>
|
||||
{% endif %}
|
||||
|
|
|
@ -14,7 +14,6 @@
|
|||
<script type="text/javascript" src="{% static 'kfet/js/moment-timezone-with-data-2010-2020.js' %}"></script>
|
||||
<script type="text/javascript" src="{% static 'kfet/js/bootstrap-datetimepicker.min.js' %}"></script>
|
||||
<script type="text/javascript" src="{% static 'kfet/js/multiple-select.js' %}"></script>
|
||||
<script type="text/javascript" src="{% static 'kfet/js/kfet.js' %}"></script>
|
||||
<script type="text/javascript" src="{% static 'kfet/js/history.js' %}"></script>
|
||||
{% endblock %}
|
||||
|
||||
|
@ -30,7 +29,6 @@
|
|||
<div class="line line-big" id="nb_opes"></div>
|
||||
<div class="line line-bigsub">opérations</div>
|
||||
<div class="block">
|
||||
<h2>Filtres</h2>
|
||||
<div class="line" style="position:relative"><b>De</b> <input type="text" id="from_date" class="form-control"></div>
|
||||
<div class="line" style="position:relative"><b>à</b> <input type="text" id="to_date" class="form-control"></div>
|
||||
<div class="line"><b>Caisses</b> {{ filter_form.checkouts }}</div>
|
||||
|
@ -60,7 +58,7 @@
|
|||
|
||||
<script type="text/javascript">
|
||||
$(document).ready(function() {
|
||||
settings = { 'subvention_cof': parseFloat({{ settings.subvention_cof|unlocalize }})}
|
||||
settings = { 'subvention_cof': parseFloat({{ kfet_config.subvention_cof|unlocalize }})}
|
||||
|
||||
khistory = new KHistory();
|
||||
|
||||
|
@ -130,6 +128,9 @@ $(document).ready(function() {
|
|||
$("select").multipleSelect({
|
||||
width: '100%',
|
||||
filter: true,
|
||||
allSelected: " ",
|
||||
selectAllText: "Tout-te-s",
|
||||
countSelected: "# sur %"
|
||||
});
|
||||
|
||||
$("input").on('dp.change change', function() {
|
||||
|
|
|
@ -1,78 +1,76 @@
|
|||
{% extends "kfet/base.html" %}
|
||||
{% extends "kfet/base_col_2.html" %}
|
||||
{% load staticfiles %}
|
||||
{% load kfet_tags %}
|
||||
|
||||
{% block title %}Accueil{% endblock %}
|
||||
{% block content-header-title %}Accueil{% endblock %}
|
||||
{% block header %}{% endblock %}
|
||||
|
||||
{% block extra_head %}
|
||||
<link rel="stylesheet" type="text/css" href="{% static 'kfet/css/home.css' %}">
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% block fixed-size %}col-sm-2{% endblock %}
|
||||
{% block main-size %}col-sm-10{% endblock %}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-4 col-md-3 col-content-left">
|
||||
<div class="content-left">
|
||||
<div class="content-left-top">
|
||||
<div class="line line-bigsub">La K-Fêt est</div>
|
||||
</div>
|
||||
<div class="content-left-top" id="open_status_parent">
|
||||
<div class="line line-big" id="open_status">?????</div>
|
||||
</div>
|
||||
{% if perms.kfet.is_team %}
|
||||
<div class="buttons">
|
||||
<a class="btn btn-primary btn-lg" id="force_close_button">
|
||||
Fermer manuellement
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-8 col-md-9 col-content-right">
|
||||
{% include 'kfet/base_messages.html' %}
|
||||
<div class="content-right">
|
||||
<div class="content-right-block">
|
||||
<h2>Carte</h2>
|
||||
{% block fixed-content %}
|
||||
|
||||
<div class="content-left-top">
|
||||
<div class="line line-bigsub">La K-Fêt est</div>
|
||||
</div>
|
||||
<div class="content-left-top" id="open_status_parent">
|
||||
<div class="line line-big" id="open_status">?????</div>
|
||||
</div>
|
||||
{% if perms.kfet.is_team %}
|
||||
<div class="buttons">
|
||||
<a class="btn btn-primary btn-lg" id="force_close_button">
|
||||
Fermer manuellement
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block main-content %}
|
||||
|
||||
<div class="content-right-block">
|
||||
<h2>Carte</h2>
|
||||
<div class="column-row">
|
||||
<div class="column-sm-1 column-md-2 column-lg-3">
|
||||
<div class="unbreakable carte-inverted">
|
||||
{% if pressions %}
|
||||
<h3>Pressions du moment</h3>
|
||||
<ul class="carte">
|
||||
{% for article in pressions %}
|
||||
<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>
|
||||
{% 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 class="column-sm-1 column-md-2 column-lg-3">
|
||||
<div class="unbreakable carte-inverted">
|
||||
{% if pressions %}
|
||||
<h3>Pressions du moment</h3>
|
||||
<ul class="carte">
|
||||
{% for article in pressions %}
|
||||
<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>
|
||||
{% 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>
|
||||
|
||||
|
|
|
@ -1,63 +1,55 @@
|
|||
{% extends 'kfet/base.html' %}
|
||||
{% extends "kfet/base_col_2.html" %}
|
||||
|
||||
{% block title %}Inventaires{% endblock %}
|
||||
{% block content-header-title %}Inventaires{% endblock %}
|
||||
{% block header-title %}Inventaires{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% block fixed-content %}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-4 col-md-3 col-content-left">
|
||||
<div class="content-left">
|
||||
<div class="content-left-top">
|
||||
</div>
|
||||
<div class="buttons">
|
||||
<a href="{% url 'kfet.inventory.create' %}" class="btn btn-primary btn-lg">
|
||||
Nouveau
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-8 col-md-9 col-content-right">
|
||||
{% include 'kfet/base_messages.html' %}
|
||||
<div class="content-right">
|
||||
<div class="content-right-block">
|
||||
<h2>Liste des inventaires</h2>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-condensed">
|
||||
<thead class="thead-default">
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>Date</td>
|
||||
<td>Par</td>
|
||||
<td>Nb articles</td>
|
||||
<td>Commande</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for inventory in inventories %}
|
||||
<tr>
|
||||
<td class="text-center">
|
||||
<a href="{% url 'kfet.inventory.read' inventory.pk %}">
|
||||
<span class="glyphicon glyphicon-cog"></span>
|
||||
</a>
|
||||
</td>
|
||||
<td>{{ inventory.at }}</td>
|
||||
<td>{{ inventory.by.trigramme }}</td>
|
||||
<td>{{ inventory.nb_articles }}</td>
|
||||
<td>
|
||||
{% if inventory.order %}
|
||||
<a href="{% url 'kfet.order.read' inventory.order.pk %}">
|
||||
#{{ inventory.order.pk }}
|
||||
</a>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="buttons">
|
||||
<a href="{% url 'kfet.inventory.create' %}" class="btn btn-primary btn-lg">
|
||||
Nouveau
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block main-content %}
|
||||
|
||||
<div class="content-right-block">
|
||||
<h2>Liste des inventaires</h2>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-condensed">
|
||||
<thead>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>Date</td>
|
||||
<td>Par</td>
|
||||
<td>Nb articles</td>
|
||||
<td>Commande</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for inventory in inventories %}
|
||||
<tr>
|
||||
<td class="text-center">
|
||||
<a href="{% url 'kfet.inventory.read' inventory.pk %}">
|
||||
<span class="glyphicon glyphicon-cog"></span>
|
||||
</a>
|
||||
</td>
|
||||
<td>{{ inventory.at }}</td>
|
||||
<td>{{ inventory.by.trigramme }}</td>
|
||||
<td>{{ inventory.nb_articles }}</td>
|
||||
<td>
|
||||
{% if inventory.order %}
|
||||
<a href="{% url 'kfet.order.read' inventory.order.pk %}">
|
||||
#{{ inventory.order.pk }}
|
||||
</a>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{% extends 'kfet/base.html' %}
|
||||
{% extends "kfet/base_col_1.html" %}
|
||||
{% load staticfiles %}
|
||||
{% load widget_tweaks %}
|
||||
|
||||
|
@ -8,75 +8,98 @@
|
|||
{% endblock %}
|
||||
|
||||
{% block title %}Nouvel inventaire{% endblock %}
|
||||
{% block content-header-title %}Nouvel inventaire{% endblock %}
|
||||
{% block header-title %}Création d'un inventaire{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% block main-size %}col-md-10 col-md-offset-1 col-lg-8 col-lg-offset-2{% endblock %}
|
||||
|
||||
{% include 'kfet/base_messages.html' %}
|
||||
<div class="content-center">
|
||||
<div>
|
||||
<form id='inventoryform' action="" method="post">
|
||||
<table class="table text-center">
|
||||
<thead>
|
||||
<tr>
|
||||
<td>Article</td>
|
||||
<td>Quantité par caisse</td>
|
||||
<td>Stock Théorique</td>
|
||||
<td>Caisses en réserve</td>
|
||||
<td>Caisses en arrière</td>
|
||||
<td>Vrac</td>
|
||||
<td>Stock total</td>
|
||||
<td>Compte terminé</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for form in formset %}
|
||||
{% ifchanged form.category %}
|
||||
<tr class='section'>
|
||||
<td>{{ form.category_name }}</td>
|
||||
<td colspan="7"></td>
|
||||
</tr>
|
||||
{% endifchanged %}
|
||||
<tr>
|
||||
{{ form.article }}
|
||||
<td class='name'>{{ form.name }}</td>
|
||||
<td class='box_capacity'>{{ form.box_capacity }}</td>
|
||||
<td><span class='current_stock'>{{ form.stock_old }}</span><span class='stock_diff'></span></td>
|
||||
<td class='box_cellar'>
|
||||
<div class='col-md-2'></div>
|
||||
<div class='col-md-8'>
|
||||
<input type='number' class='form-control' step='1'>
|
||||
</div>
|
||||
</td>
|
||||
<td class='box_bar'>
|
||||
<div class='col-md-offset-2 col-md-8'><input type='number' class='form-control' step='1'></div>
|
||||
</td>
|
||||
<td class='misc'>
|
||||
<div class='col-md-offset-2 col-md-8'><input type='number' class='form-control' step='1'></div>
|
||||
</td>
|
||||
<td class='stock_new'>
|
||||
<div class='col-md-offset-2 col-md-8'>{{ form.stock_new | attr:"readonly"| add_class:"form-control" }}</div>
|
||||
<div class='col-md-2 inventory_update'><button type='button' class='btn-sm btn-primary'>MàJ</button></div>
|
||||
</td>
|
||||
<td class='finished'><input type='checkbox' class='form_control'></td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{{ formset.management_form }}
|
||||
{% if not perms.kfet.add_inventory %}
|
||||
<div class='auth-form form-horizontal'>
|
||||
{% include "kfet/form_authentication_snippet.html" %}
|
||||
</div>
|
||||
{% endif %}
|
||||
<input type="submit" value="Enregistrer" class="btn btn-primary btn-lg btn-block">
|
||||
{% csrf_token %}
|
||||
</form>
|
||||
{% block main-content %}
|
||||
|
||||
<form id='inventoryform' action="" method="post">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-condensed text-center">
|
||||
<thead>
|
||||
<tr>
|
||||
<td>Article</td>
|
||||
<td>Quantité par caisse</td>
|
||||
<td>Stock théorique</td>
|
||||
<td>Caisses en réserve</td>
|
||||
<td>Caisses en arrière</td>
|
||||
<td>Vrac</td>
|
||||
<td>Stock total</td>
|
||||
<td>Compte terminé</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for form in formset %}
|
||||
{% ifchanged form.category %}
|
||||
<tr class='section'>
|
||||
<td>{{ form.category_name }}</td>
|
||||
<td colspan="7"></td>
|
||||
</tr>
|
||||
{% endifchanged %}
|
||||
<tr>
|
||||
{{ form.article }}
|
||||
<td class='name'>{{ form.name }}</td>
|
||||
<td class='box_capacity'>{{ form.box_capacity }}</td>
|
||||
<td>
|
||||
<span class='current_stock'>{{ form.stock_old }}</span><span class='stock_diff'></span>
|
||||
</td>
|
||||
<td class='box_cellar nopadding'>
|
||||
<input type='number' class='form-control' step='1'>
|
||||
</td>
|
||||
<td class='box_bar nopadding'>
|
||||
<input type='number' class='form-control' step='1'>
|
||||
</td>
|
||||
<td class='misc nopadding'>
|
||||
<input type='number' class='form-control' step='1'>
|
||||
</td>
|
||||
<td class='stock_new nopadding'>
|
||||
{{ form.stock_new | attr:"readonly"| add_class:"form-control" }}
|
||||
</td>
|
||||
<td class='finished'>
|
||||
<div class="inventory_update">
|
||||
<button type='button' class='btn-sm btn-primary'>MàJ</button>
|
||||
</div>
|
||||
<div class="inventory_done">
|
||||
<input type='checkbox' class='form_control'>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
<tr class="section">
|
||||
<td>Totaux</td>
|
||||
<td colspan="2"></td>
|
||||
<td class="total_box_cellar"></td>
|
||||
<td class="total_box_bar"></td>
|
||||
<td colspan="3"></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{ formset.management_form }}
|
||||
{% if not perms.kfet.add_inventory %}
|
||||
<div class='auth-form form-horizontal'>
|
||||
{% include "kfet/form_authentication_snippet.html" %}
|
||||
</div>
|
||||
{% endif %}
|
||||
<input type="submit" value="Enregistrer" class="btn btn-primary btn-lg btn-block">
|
||||
{% csrf_token %}
|
||||
</form>
|
||||
|
||||
<script type="text/javascript">
|
||||
function init_total(type) {
|
||||
update_total(type);
|
||||
$('.'+type+' input').on('input', () => update_total(type));
|
||||
}
|
||||
|
||||
function update_total(type) {
|
||||
var total = 0;
|
||||
$('.'+type+' input').each(function() {
|
||||
total += +$(this).val();
|
||||
});
|
||||
$('.total_'+type).text(total);
|
||||
}
|
||||
|
||||
$(document).ready(function() {
|
||||
'use strict';
|
||||
|
||||
|
@ -106,6 +129,7 @@ $(document).ready(function() {
|
|||
function update_stock($line, update_count) {
|
||||
$line.removeClass('inventory_modified');
|
||||
$line.find('.inventory_update').hide();
|
||||
$line.find('.inventory_done').show();
|
||||
|
||||
var old_stock = +$line.find('.current_stock').text()
|
||||
var stock_diff = +$line.find('.stock_diff').text();
|
||||
|
@ -132,6 +156,12 @@ $(document).ready(function() {
|
|||
update_stock($line, true);
|
||||
});
|
||||
|
||||
/**
|
||||
* Total row
|
||||
*/
|
||||
|
||||
init_total('box_cellar');
|
||||
init_total('box_bar');
|
||||
|
||||
/**
|
||||
* Websocket
|
||||
|
@ -147,6 +177,7 @@ $(document).ready(function() {
|
|||
|
||||
//Realigning input and displaying update button
|
||||
$line.find('.inventory_update').show();
|
||||
$line.find('.inventory_done').hide();
|
||||
|
||||
//Displaying stock changes
|
||||
var stock = $line.find('.current_stock').text();
|
||||
|
@ -161,6 +192,8 @@ $(document).ready(function() {
|
|||
$('input[type="submit"]').on("click", function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
var content;
|
||||
|
||||
if (conflicts.size) {
|
||||
content = '';
|
||||
content += "Conflits possibles :"
|
||||
|
|
|
@ -1,61 +1,55 @@
|
|||
{% extends 'kfet/base.html' %}
|
||||
{% extends "kfet/base_col_2.html" %}
|
||||
|
||||
{% block title %}Inventaire #{{ inventory.pk }}{% endblock %}
|
||||
{% block content-header-title %}Inventaire #{{ inventory.pk }}{% endblock %}
|
||||
{% block header-title %}Inventaire #{{ inventory.pk }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% block fixed-content %}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-4 col-md-3 col-content-left">
|
||||
<div class="content-left">
|
||||
<div class="content-left-top">
|
||||
<div class="line"><b>Date:</b> {{ inventory.at }}</div>
|
||||
<div class="line"><b>Par:</b> {{ inventory.by.trigramme }}</div>
|
||||
{% if inventory.order %}
|
||||
<div class="line">
|
||||
<b>Commande relative:</b>
|
||||
<a href="{% url 'kfet.order.read' inventory.order.pk %}">
|
||||
#{{ inventory.order.pk }}
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-8 col-md-9 col-content-right">
|
||||
{% include 'kfet/base_messages.html' %}
|
||||
<div class="content-right">
|
||||
<div class="content-right-block">
|
||||
<h2>Détails</h2>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-condensed">
|
||||
<thead>
|
||||
<tr>
|
||||
<td>Article</td>
|
||||
<td>Stock avant</td>
|
||||
<td>Stock après</td>
|
||||
<td>Erreur</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for inventoryart in inventoryarts %}
|
||||
{% ifchanged inventoryart.article.category %}
|
||||
<tr class="section">
|
||||
<td colspan="4">{{ inventoryart.article.category.name }}</td>
|
||||
</tr>
|
||||
{% endifchanged %}
|
||||
<tr>
|
||||
<td>{{ inventoryart.article.name }}</td>
|
||||
<td>{{ inventoryart.stock_old }}</td>
|
||||
<td>{{ inventoryart.stock_new }}</td>
|
||||
<td>{{ inventoryart.stock_error }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="content-left-top">
|
||||
<div class="line"><b>Date:</b> {{ inventory.at }}</div>
|
||||
<div class="line"><b>Par:</b> {{ inventory.by.trigramme }}</div>
|
||||
{% if inventory.order %}
|
||||
<div class="line">
|
||||
<b>Commande relative:</b>
|
||||
<a href="{% url 'kfet.order.read' inventory.order.pk %}">
|
||||
#{{ inventory.order.pk }}
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block main-content %}
|
||||
|
||||
<div class="content-right-block">
|
||||
<h2>Détails</h2>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-condensed">
|
||||
<thead>
|
||||
<tr>
|
||||
<td>Article</td>
|
||||
<td>Stock avant</td>
|
||||
<td>Stock après</td>
|
||||
<td>Erreur</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for inventoryart in inventoryarts %}
|
||||
{% ifchanged inventoryart.article.category %}
|
||||
<tr class="section">
|
||||
<td colspan="4">{{ inventoryart.article.category.name }}</td>
|
||||
</tr>
|
||||
{% endifchanged %}
|
||||
<tr>
|
||||
<td>{{ inventoryart.article.name }}</td>
|
||||
<td>{{ inventoryart.stock_old }}</td>
|
||||
<td>{{ inventoryart.stock_new }}</td>
|
||||
<td>{{ inventoryart.stock_error }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
|
||||
{% block title %}K-Psul{% endblock %}
|
||||
|
||||
{% block content-header %}{% endblock %}
|
||||
{% block header %}{% endblock %}
|
||||
|
||||
{% block help %}
|
||||
|
||||
|
@ -178,7 +178,7 @@ $(document).ready(function() {
|
|||
settings = {}
|
||||
|
||||
function resetSettings() {
|
||||
$.ajax({
|
||||
return $.ajax({
|
||||
dataType: "json",
|
||||
url : "{% url 'kfet.kpsul.get_settings' %}",
|
||||
method : "POST",
|
||||
|
@ -187,7 +187,6 @@ $(document).ready(function() {
|
|||
settings['addcost_for'] = data['addcost_for'];
|
||||
settings['addcost_amount'] = parseFloat(data['addcost_amount']);
|
||||
settings['subvention_cof'] = parseFloat(data['subvention_cof']);
|
||||
displayAddcost();
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -389,6 +388,11 @@ $(document).ready(function() {
|
|||
var last_statement_container = $('#last_statement');
|
||||
var last_statement_html_default = '<b>Dernier relevé: </b><br><span id="checkout-last_statement_balance"></span>€ le <span id="checkout-last_statement_at"></span> par <span id="checkout-last_statement_by_trigramme"></span>';
|
||||
|
||||
// If only one checkout is available, select it
|
||||
var checkout_choices = checkoutInput.find("option[value!='']");
|
||||
if (checkout_choices.length == 1) {
|
||||
$(checkout_choices[0]).prop("selected", true);
|
||||
}
|
||||
|
||||
// Display data
|
||||
function displayCheckoutData() {
|
||||
|
@ -1422,9 +1426,11 @@ $(document).ready(function() {
|
|||
resetArticles();
|
||||
resetPreviousOp();
|
||||
khistory.reset();
|
||||
resetSettings();
|
||||
getArticles();
|
||||
getHistory();
|
||||
resetSettings().done(function (){
|
||||
getArticles();
|
||||
getHistory();
|
||||
displayAddcost();
|
||||
});
|
||||
}
|
||||
|
||||
function resetSelectable() {
|
||||
|
|
|
@ -36,6 +36,12 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="buttons">
|
||||
{% if account.user == request.user %}
|
||||
<ul class='nav nav-pills nav-justified'>
|
||||
<li class="active"><a class="btn btn-primary" data-toggle="pill" href="#tab_stats">Statistiques</a></li>
|
||||
<li><a class="btn btn-primary" 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 %}">
|
||||
Modifier
|
||||
</a>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{% extends 'kfet/base.html' %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
Connexion utilisateur K-Fêt générique réussie
|
||||
|
||||
{% block extra_head %}
|
||||
<script type="text/javascript">
|
||||
close();
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
|
|
@ -1,105 +1,97 @@
|
|||
{% extends 'kfet/base.html' %}
|
||||
{% extends "kfet/base_col_2.html" %}
|
||||
|
||||
{% block title %}Commandes{% endblock %}
|
||||
{% block content-header-title %}Commandes{% endblock %}
|
||||
{% block header-title %}Commandes{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% block fixed-content %}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-4 col-md-3 col-content-left">
|
||||
<div class="content-left">
|
||||
<div class="content-left-top">
|
||||
<div class="line line-big">{{ orders|length }}</div>
|
||||
<div class="line line-bigsub">commande{{ orders|length|pluralize }}</div>
|
||||
</div>
|
||||
<div class="buttons">
|
||||
</div>
|
||||
</div>
|
||||
<div class="content-left-top">
|
||||
<div class="line line-big">{{ orders|length }}</div>
|
||||
<div class="line line-bigsub">commande{{ orders|length|pluralize }}</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block main-content %}
|
||||
|
||||
<div class="content-right-block">
|
||||
<h2>Fournisseurs</h2>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-condensed">
|
||||
<thead>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td>Nom</td>
|
||||
<td>Mail</td>
|
||||
<td>Tél.</td>
|
||||
<td>Adresse</td>
|
||||
<td>Commentaire</td>
|
||||
<tbody>
|
||||
{% for supplier in suppliers %}
|
||||
<tr>
|
||||
<td class="no-padding">
|
||||
<a href="{% url 'kfet.order.new' supplier.pk %}" class="btn btn-primary">
|
||||
Passer une commande
|
||||
</a>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<a href="{% url 'kfet.order.supplier.update' supplier.pk %}">
|
||||
<span class="glyphicon glyphicon-cog"></span>
|
||||
</a>
|
||||
</td>
|
||||
<td>{{ supplier.name }}</td>
|
||||
<td>{{ supplier.email }}</td>
|
||||
<td>{{ supplier.phone }}</td>
|
||||
<td>{{ supplier.address }}</td>
|
||||
<td>{{ supplier.comment }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="col-sm-8 col-md-9 col-content-right">
|
||||
{% include 'kfet/base_messages.html' %}
|
||||
<div class="content-right">
|
||||
<div class="content-right-block">
|
||||
<h2>Fournisseurs</h2>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-condensed">
|
||||
<thead>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td>Nom</td>
|
||||
<td>Mail</td>
|
||||
<td>Tél.</td>
|
||||
<td>Adresse</td>
|
||||
<td>Commentaire</td>
|
||||
<tbody>
|
||||
{% for supplier in suppliers %}
|
||||
<tr>
|
||||
<td class="no-padding">
|
||||
<a href="{% url 'kfet.order.new' supplier.pk %}" class="btn btn-primary">
|
||||
Passer une commande
|
||||
</a>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<a href="{% url 'kfet.order.supplier.update' supplier.pk %}">
|
||||
<span class="glyphicon glyphicon-cog"></span>
|
||||
</a>
|
||||
</td>
|
||||
<td>{{ supplier.name }}</td>
|
||||
<td>{{ supplier.email }}</td>
|
||||
<td>{{ supplier.phone }}</td>
|
||||
<td>{{ supplier.address }}</td>
|
||||
<td>{{ supplier.comment }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="content-right-block">
|
||||
<h2>Liste des commandes</h2>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-condensed">
|
||||
<thead>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td>Date</td>
|
||||
<td>Fournisseur</td>
|
||||
<td>Inventaire</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for order in orders %}
|
||||
<tr>
|
||||
<td class="no-padding">
|
||||
{% if not order.inventory %}
|
||||
<a href="{% url 'kfet.order.to_inventory' order.pk %}" class="btn btn-primary">
|
||||
Générer inventaire
|
||||
</a>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
<a href="{% url 'kfet.order.read' order.pk %}">
|
||||
<span class="glyphicon glyphicon-cog"></span>
|
||||
</a>
|
||||
</td>
|
||||
<td>{{ order.at }}</td>
|
||||
<td>{{ order.supplier }}</td>
|
||||
<td>
|
||||
{% if order.inventory %}
|
||||
<a href="{% url 'kfet.inventory.read' order.inventory.pk %}">
|
||||
#{{ order.inventory.pk }}
|
||||
</a>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="content-right-block">
|
||||
<h2>Liste des commandes</h2>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-condensed">
|
||||
<thead>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td>Date</td>
|
||||
<td>Fournisseur</td>
|
||||
<td>Inventaire</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for order in orders %}
|
||||
<tr>
|
||||
<td class="no-padding">
|
||||
{% if not order.inventory %}
|
||||
<a href="{% url 'kfet.order.to_inventory' order.pk %}" class="btn btn-primary">
|
||||
Générer inventaire
|
||||
</a>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
<a href="{% url 'kfet.order.read' order.pk %}">
|
||||
<span class="glyphicon glyphicon-cog"></span>
|
||||
</a>
|
||||
</td>
|
||||
<td>{{ order.at }}</td>
|
||||
<td>{{ order.supplier }}</td>
|
||||
<td>
|
||||
{% if order.inventory %}
|
||||
<a href="{% url 'kfet.inventory.read' order.inventory.pk %}">
|
||||
#{{ order.inventory.pk }}
|
||||
</a>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -1,71 +1,72 @@
|
|||
{% extends 'kfet/base.html' %}
|
||||
{% extends "kfet/base_col_1.html" %}
|
||||
{% load widget_tweaks %}
|
||||
|
||||
{% block title %}Nouvelle commande{% endblock %}
|
||||
{% block content-header-title %}Nouvelle commande {{ supplier.name }}{% endblock %}
|
||||
{% block header-title %}Création d'une commande {{ supplier.name }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% block main-size %}col-md-10 col-md-offset-1 col-lg-8 col-lg-offset-2{% endblock %}
|
||||
|
||||
{% block main-content %}
|
||||
|
||||
<div class="content-center">
|
||||
<div>
|
||||
<form action="" method="post">
|
||||
{% csrf_token %}
|
||||
<table class="table text-center">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-condensed text-center">
|
||||
<thead>
|
||||
<tr>
|
||||
<td rowspan="2">Article</td>
|
||||
<td colspan="5">Ventes
|
||||
<td colspan="{{ scale|length }}">Ventes
|
||||
<span class='glyphicon glyphicon-question-sign' title="Ventes des 5 dernières semaines" data-placement="bottom"></span>
|
||||
</td>
|
||||
<td rowspan="2">V. moy.
|
||||
<td rowspan="2">V. moy.<br>
|
||||
<span class='glyphicon glyphicon-question-sign' title="Moyenne des ventes" data-placement="bottom"></span>
|
||||
</td>
|
||||
<td rowspan="2">E.T.
|
||||
<td rowspan="2">E.T.<br>
|
||||
<span class='glyphicon glyphicon-question-sign' title="Écart-type des ventes" data-placement="bottom"></span>
|
||||
</td>
|
||||
<td rowspan="2">Prév.
|
||||
<td rowspan="2">Prév.<br>
|
||||
<span class='glyphicon glyphicon-question-sign' title="Prévision de ventes" data-placement="bottom"></span>
|
||||
</td>
|
||||
<td rowspan="2">Stock</td>
|
||||
<td rowspan="2">Rec.
|
||||
<td rowspan="2">Box<br>
|
||||
<span class='glyphicon glyphicon-question-sign' title="Capacité d'une boite" data-placement="bottom"></span>
|
||||
</td>
|
||||
<td rowspan="2">Rec.<br>
|
||||
<span class='glyphicon glyphicon-question-sign' title="Quantité conseillée" data-placement="bottom"></span>
|
||||
</td>
|
||||
<td rowspan="2">Commande</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>S1</td>
|
||||
<td>S2</td>
|
||||
<td>S3</td>
|
||||
<td>S4</td>
|
||||
<td>S5</td>
|
||||
{% for label in scale.get_labels %}
|
||||
<td>{{ label }}</td>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for form in formset %}
|
||||
{% ifchanged form.category %}
|
||||
<tr class='section'>
|
||||
<td> {{ form.category_name }}</td>
|
||||
<td colspan="11"></td>
|
||||
<tr class='section text-left'>
|
||||
<td colspan="{{ scale|length|add:'8' }}">{{ form.category_name }}</td>
|
||||
</tr>
|
||||
{% endifchanged %}
|
||||
<tr>
|
||||
{{ form.article }}
|
||||
<td>{{ form.name }}</td>
|
||||
<td>{{ form.v_s1 }}</td>
|
||||
<td>{{ form.v_s2 }}</td>
|
||||
<td>{{ form.v_s3 }}</td>
|
||||
<td>{{ form.v_s4 }}</td>
|
||||
<td>{{ form.v_s5 }}</td>
|
||||
{% for v_chunk in form.v_all %}
|
||||
<td>{{ v_chunk }}</td>
|
||||
{% endfor %}
|
||||
<td>{{ form.v_moy }}</td>
|
||||
<td>{{ form.v_et }}</td>
|
||||
<td>{{ form.v_prev }}</td>
|
||||
<td>{{ form.stock }}</td>
|
||||
<td>{{ form.box_capacity|default:"" }}</td>
|
||||
<td>{{ form.c_rec }}</td>
|
||||
<td>{{ form.quantity_ordered | add_class:"form-control" }}</td>
|
||||
<td class="nopadding">{{ form.quantity_ordered | add_class:"form-control" }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{{ formset.management_form }}
|
||||
{% if not perms.kfet.add_inventory %}
|
||||
<div class='auth-form form-horizontal'>
|
||||
|
@ -74,8 +75,6 @@
|
|||
{% endif %}
|
||||
<input type="submit" value="Enregistrer" class="btn btn-primary btn-lg btn-block">
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type='text/javascript'>
|
||||
$(document).ready(function () {
|
||||
|
|
|
@ -1,66 +1,67 @@
|
|||
{% extends 'kfet/base.html' %}
|
||||
{% extends "kfet/base_col_2.html" %}
|
||||
|
||||
{% block title %}Commande #{{ order.pk }}{% endblock %}
|
||||
{% block content-header-title %}Commande #{{ order.pk }}{% endblock %}
|
||||
{% block header-title %}Commande #{{ order.pk }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% block fixed-content %}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-4 col-md-3 col-content-left">
|
||||
<div class="content-left">
|
||||
<div class="content-left-top">
|
||||
<div class="line"><b>Créée le:</b> {{ order.at }}</div>
|
||||
<div class="line"><b>Fournisseur:</b> {{ order.supplier.name }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="content-left-top">
|
||||
<div class="line"><b>Créée le:</b> {{ order.at }}</div>
|
||||
<div class="line"><b>Fournisseur:</b> {{ order.supplier.name }}</div>
|
||||
</div>
|
||||
<div class="buttons">
|
||||
{% if not order.inventory %}
|
||||
<a class="btn btn-primary btn-lg" href="{% url 'kfet.order.to_inventory' order.pk %}">
|
||||
Générer inventaire
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block main-content %}
|
||||
|
||||
<div class="content-right-block">
|
||||
<h2>Détails</h2>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-condensed">
|
||||
<thead>
|
||||
<tr>
|
||||
<td>Article</td>
|
||||
<td>Commandé</td>
|
||||
<td>Commandé (#box)</td>
|
||||
<td>Reçu</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for orderart in orderarts %}
|
||||
{% ifchanged orderart.article.category %}
|
||||
<tr class="section">
|
||||
<td colspan="4">{{ orderart.article.category.name }}</td>
|
||||
</tr>
|
||||
{% endifchanged %}
|
||||
<tr>
|
||||
<td>{{ orderart.article.name }}</td>
|
||||
<td>{{ orderart.quantity_ordered }}</td>
|
||||
<td>
|
||||
{% if orderart.article.box_capacity %}
|
||||
{# c'est une division ! #}
|
||||
{% widthratio orderart.quantity_ordered orderart.article.box_capacity 1 %}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{{ orderart.quantity_received|default_if_none:'' }}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="col-sm-8 col-md-9 col-content-right">
|
||||
{% include 'kfet/base_messages.html' %}
|
||||
<div class="content-right">
|
||||
<div class="content-right-block">
|
||||
<h2>Détails</h2>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-condensed">
|
||||
<thead>
|
||||
<tr>
|
||||
<td>Article</td>
|
||||
<td>Commandé</td>
|
||||
<td>Commandé (#box)</td>
|
||||
<td>Reçu</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for orderart in orderarts %}
|
||||
{% ifchanged orderart.article.category %}
|
||||
<tr class="section">
|
||||
<td colspan="4">{{ orderart.article.category.name }}</td>
|
||||
</tr>
|
||||
{% endifchanged %}
|
||||
<tr>
|
||||
<td>{{ orderart.article.name }}</td>
|
||||
<td>{{ orderart.quantity_ordered }}</td>
|
||||
<td>
|
||||
{% if orderart.article.box_capacity %}
|
||||
{# c'est une division ! #}
|
||||
{% widthratio orderart.quantity_ordered orderart.article.box_capacity 1 %}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{{ orderart.quantity_received|default_if_none:'' }}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="content-right-block">
|
||||
<h2>Mail</h2>
|
||||
<div>
|
||||
<textarea class="form-control" style="height:500px;">{{ mail }}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="content-right-block">
|
||||
<h2>Mail</h2>
|
||||
<div>
|
||||
<textarea class="form-control" style="height:500px;">{{ mail }}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -1,18 +1,17 @@
|
|||
{% extends 'kfet/base.html' %}
|
||||
{% extends "kfet/base_col_1.html" %}
|
||||
{% load widget_tweaks %}
|
||||
|
||||
{% block title %}{% endblock %}
|
||||
{% block content-header-title %}{% endblock %}
|
||||
{% block title %}Nouvel inventaire{% endblock %}
|
||||
{% block header-title %}Création d'inventaire depuis la commande #{{ order.pk }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% block main-size %}col-md-10 col-md-offset-1 col-lg-8 col-lg-offset-2{% endblock %}
|
||||
|
||||
{% include 'kfet/base_messages.html' %}
|
||||
{% block main-content %}
|
||||
|
||||
<div class="content-center">
|
||||
<div>
|
||||
<form action="" method="post">
|
||||
{% csrf_token %}
|
||||
<table class='table text-center'>
|
||||
<div class="table-responsive">
|
||||
<table class='table table-condensed text-center'>
|
||||
<thead>
|
||||
<tr>
|
||||
<td style="width:25%">Article</td>
|
||||
|
@ -25,23 +24,24 @@
|
|||
</thead>
|
||||
<tbody>
|
||||
{% for form in formset %}
|
||||
{% ifchanged form.category %}
|
||||
<tr class='section'>
|
||||
<td>{{ form.category_name }}</td>
|
||||
<td colspan="5"></td>
|
||||
</tr>
|
||||
{% endifchanged %}
|
||||
<tr>
|
||||
{{ form.article }}
|
||||
<td>{{ form.name }}</td>
|
||||
<td>{{ form.price_HT | add_class:"form-control" }}</td>
|
||||
<td>{{ form.TVA | add_class:"form-control" }}</td>
|
||||
<td>{{ form.rights | add_class:"form-control" }}</td>
|
||||
<td>{{ form.quantity_ordered }}</td>
|
||||
<td>{{ form.quantity_received | add_class:"form-control" }}</td>
|
||||
{% ifchanged form.category %}
|
||||
<tr class='section'>
|
||||
<td>{{ form.category_name }}</td>
|
||||
<td colspan="5"></td>
|
||||
</tr>
|
||||
{% endifchanged %}
|
||||
<tr>
|
||||
<td>{{ form.name }}</td>
|
||||
<td class="nopadding">{{ form.price_HT | add_class:"form-control" }}</td>
|
||||
<td class="nopadding">{{ form.TVA | add_class:"form-control" }}</td>
|
||||
<td class="nopadding">{{ form.rights | add_class:"form-control" }}</td>
|
||||
<td>{{ form.quantity_ordered }}</td>
|
||||
<td class="nopadding">{{ form.quantity_received | add_class:"form-control" }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{{ formset.management_form }}
|
||||
{% if not perms.kfet.add_inventory %}
|
||||
<div class='auth-form form-horizontal'>
|
||||
|
@ -50,7 +50,5 @@
|
|||
{% endif %}
|
||||
<input type="submit" value="Enregistrer" class="btn btn-primary btn-lg btn-block">
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
|
|
@ -1,24 +1,40 @@
|
|||
{% extends 'kfet/base.html' %}
|
||||
{% extends "kfet/base_col_2.html" %}
|
||||
|
||||
{% block title %}Paramètres{% endblock %}
|
||||
{% block content-header-title %}Paramètres{% endblock %}
|
||||
{% block header-title %}Paramètres{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% block fixed-content %}
|
||||
|
||||
{% include 'kfet/base_messages.html' %}
|
||||
<table>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td>Nom</td>
|
||||
<td>Valeur</td>
|
||||
</tr>
|
||||
{% for setting in settings %}
|
||||
<tr>
|
||||
<td><a href="{% url 'kfet.settings.update' setting.pk %}">Modifier</a></td>
|
||||
<td>{{ setting.name }}</td>
|
||||
<td>{% firstof setting.value_decimal setting.value_duration setting.value_account %}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
<div class="buttons">
|
||||
<a class="btn btn-primary btn-lg" href="{% url 'kfet.settings.update' %}">
|
||||
Modifier
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block main-content %}
|
||||
|
||||
<div class="content-right-block">
|
||||
<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|default_if_none:"" }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
|
|
@ -1,14 +1,12 @@
|
|||
{% extends 'kfet/base.html' %}
|
||||
{% extends "kfet/base_col_1.html" %}
|
||||
|
||||
{% block title %}Modification de {{ settings.name }}{% endblock %}
|
||||
{% block content-header-title %}Modification de {{ settings.name }}{% endblock %}
|
||||
{% block title %}Modification des paramètres{% endblock %}
|
||||
{% block header-title %}Modification des paramètres{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% block main-class %}content-form{% endblock %}
|
||||
|
||||
<form action="" method="post">
|
||||
{% csrf_token %}
|
||||
{{ form.as_p }}
|
||||
<input type="submit" value="Mettre à jour">
|
||||
</form>
|
||||
{% block main-content %}
|
||||
|
||||
{% include "kfet/base_form.html" with authz=perms.kfet.change_settings submit_text="Mettre à jour"%}
|
||||
|
||||
{% endblock %}
|
||||
|
|
|
@ -1,27 +1,12 @@
|
|||
{% extends 'kfet/base.html' %}
|
||||
{% load widget_tweaks %}
|
||||
{% load staticfiles %}
|
||||
{% extends "kfet/base_col_1.html" %}
|
||||
|
||||
{% block title %}Fournisseur - Modification{% endblock %}
|
||||
{% block content-header-title %}Fournisseur - Modification{% endblock %}
|
||||
{% block title %}{{ supplier.name }} - Édition{% endblock %}
|
||||
{% block header-title %}Édition du fournisseur {{ supplier.name }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% block main-class %}content-form{% endblock %}
|
||||
|
||||
{% include 'kfet/base_messages.html' %}
|
||||
{% block main-content %}
|
||||
|
||||
<div class="row form-only">
|
||||
<div class="col-sm-12 col-md-8 col-md-offset-2">
|
||||
<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_supplier %}
|
||||
{% include 'kfet/form_authentication_snippet.html' %}
|
||||
{% endif %}
|
||||
{% include 'kfet/form_submit_snippet.html' with value="Mettre à jour" %}
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% include 'kfet/base_form.html' with authz=perms.kfet.change_supplier submit_text="Mettre à jour" %}
|
||||
|
||||
{% endblock %}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{% extends 'kfet/base.html' %}
|
||||
{% extends "kfet/base_col_1.html" %}
|
||||
{% load staticfiles %}
|
||||
|
||||
{% block extra_head %}
|
||||
|
@ -7,13 +7,14 @@
|
|||
{% endblock %}
|
||||
|
||||
{% block title %}Nouveaux transferts{% endblock %}
|
||||
{% block content-header-title %}Nouveaux transferts{% endblock %}
|
||||
{% block header-title %}Création de transferts{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% block main-size %}col-sm-12{% endblock %}
|
||||
|
||||
{% csrf_token %}
|
||||
{% block main-content %}
|
||||
|
||||
<form id="transfers_form">
|
||||
<form id="transfers_form" style="background-color: transparent;">
|
||||
{% csrf_token %}
|
||||
<div class="transfer_general text-center">
|
||||
<input type="text" name="comment" id="comment" placeholder="Commentaire"><!--
|
||||
--><button type="submit" id="submit" class="btn btn-primary btn-lg">Enregistrer</button>
|
||||
|
|
|
@ -3,10 +3,11 @@
|
|||
from django import template
|
||||
from django.utils.html import escape
|
||||
from django.utils.safestring import mark_safe
|
||||
from kfet.models import Settings
|
||||
from math import floor
|
||||
import re
|
||||
|
||||
from kfet.config import kfet_config
|
||||
|
||||
register = template.Library()
|
||||
|
||||
|
||||
|
@ -27,7 +28,6 @@ def highlight_user(user, q):
|
|||
return highlight_text(text, q)
|
||||
|
||||
|
||||
|
||||
@register.filter(is_safe=True)
|
||||
def highlight_clipper(clipper, q):
|
||||
if clipper.fullname:
|
||||
|
@ -37,8 +37,12 @@ def highlight_clipper(clipper, q):
|
|||
return highlight_text(text, q)
|
||||
|
||||
|
||||
|
||||
@register.filter()
|
||||
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)
|
||||
|
||||
|
||||
@register.filter()
|
||||
def widget_type(field):
|
||||
return field.field.widget.__class__.__name__
|
||||
|
|
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)
|
56
kfet/tests/test_forms.py
Normal file
56
kfet/tests/test_forms.py
Normal file
|
@ -0,0 +1,56 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from django.test import TestCase
|
||||
from django.contrib.auth.models import User, Group
|
||||
|
||||
from kfet.forms import UserGroupForm
|
||||
|
||||
|
||||
class UserGroupFormTests(TestCase):
|
||||
"""Test suite for UserGroupForm."""
|
||||
|
||||
def setUp(self):
|
||||
# create user
|
||||
self.user = User.objects.create(username="foo", password="foo")
|
||||
|
||||
# create some K-Fêt groups
|
||||
prefix_name = "K-Fêt "
|
||||
names = ["Group 1", "Group 2", "Group 3"]
|
||||
self.kfet_groups = [
|
||||
Group.objects.create(name=prefix_name+name)
|
||||
for name in names
|
||||
]
|
||||
|
||||
# create a non-K-Fêt group
|
||||
self.other_group = Group.objects.create(name="Other group")
|
||||
|
||||
def test_choices(self):
|
||||
"""Only K-Fêt groups are selectable."""
|
||||
form = UserGroupForm(instance=self.user)
|
||||
groups_field = form.fields['groups']
|
||||
self.assertQuerysetEqual(
|
||||
groups_field.queryset,
|
||||
[repr(g) for g in self.kfet_groups],
|
||||
ordered=False,
|
||||
)
|
||||
|
||||
def test_keep_others(self):
|
||||
"""User stays in its non-K-Fêt groups."""
|
||||
user = self.user
|
||||
|
||||
# add user to a non-K-Fêt group
|
||||
user.groups.add(self.other_group)
|
||||
|
||||
# add user to some K-Fêt groups through UserGroupForm
|
||||
data = {
|
||||
'groups': [group.pk for group in self.kfet_groups],
|
||||
}
|
||||
form = UserGroupForm(data, instance=user)
|
||||
|
||||
form.is_valid()
|
||||
form.save()
|
||||
self.assertQuerysetEqual(
|
||||
user.groups.all(),
|
||||
[repr(g) for g in [self.other_group] + self.kfet_groups],
|
||||
ordered=False,
|
||||
)
|
|
@ -5,7 +5,7 @@ from unittest.mock import patch
|
|||
from django.test import TestCase, Client
|
||||
from django.contrib.auth.models import User, Permission
|
||||
|
||||
from .models import Account, Article, ArticleCategory
|
||||
from kfet.models import Account, Article, ArticleCategory
|
||||
|
||||
|
||||
class TestStats(TestCase):
|
57
kfet/tests/test_views.py
Normal file
57
kfet/tests/test_views.py
Normal file
|
@ -0,0 +1,57 @@
|
|||
from decimal import Decimal
|
||||
|
||||
from django.test import TestCase, Client
|
||||
from django.contrib.auth.models import User
|
||||
from django.utils import timezone
|
||||
|
||||
from ..models import Account, OperationGroup, Checkout, Operation
|
||||
|
||||
|
||||
class AccountTests(TestCase):
|
||||
"""Account related views"""
|
||||
|
||||
def setUp(self):
|
||||
# A user and its account
|
||||
self.user = User.objects.create_user(username="foobar", password="foo")
|
||||
acc = Account.objects.create(
|
||||
trigramme="FOO", cofprofile=self.user.profile
|
||||
)
|
||||
|
||||
# Dummy operations and operation groups
|
||||
checkout = Checkout.objects.create(
|
||||
created_by=acc, name="checkout",
|
||||
valid_from=timezone.now(),
|
||||
valid_to=timezone.now() + timezone.timedelta(days=365)
|
||||
)
|
||||
opeg_data = [
|
||||
(timezone.now(), Decimal('10')),
|
||||
(timezone.now() - timezone.timedelta(days=3), Decimal('3')),
|
||||
]
|
||||
OperationGroup.objects.bulk_create([
|
||||
OperationGroup(
|
||||
on_acc=acc, checkout=checkout, at=at, is_cof=False,
|
||||
amount=amount
|
||||
)
|
||||
for (at, amount) in opeg_data
|
||||
])
|
||||
self.operation_groups = OperationGroup.objects.order_by("-amount")
|
||||
Operation.objects.create(
|
||||
group=self.operation_groups[0],
|
||||
type=Operation.PURCHASE,
|
||||
amount=Decimal('10')
|
||||
)
|
||||
Operation.objects.create(
|
||||
group=self.operation_groups[1],
|
||||
type=Operation.PURCHASE,
|
||||
amount=Decimal('3')
|
||||
)
|
||||
|
||||
def test_account_read(self):
|
||||
"""We can query the "Accout - Read" page."""
|
||||
client = Client()
|
||||
self.assertTrue(client.login(
|
||||
username="foobar",
|
||||
password="foo"
|
||||
))
|
||||
resp = client.get("/k-fet/accounts/FOO")
|
||||
self.assertEqual(200, resp.status_code)
|
|
@ -193,7 +193,7 @@ urlpatterns = [
|
|||
permission_required('kfet.change_settings')
|
||||
(views.SettingsList.as_view()),
|
||||
name='kfet.settings'),
|
||||
url(r'^settings/(?P<pk>\d+)/edit$',
|
||||
url(r'^settings/edit$',
|
||||
permission_required('kfet.change_settings')
|
||||
(views.SettingsUpdate.as_view()),
|
||||
name='kfet.settings.update'),
|
||||
|
|
531
kfet/views.py
531
kfet/views.py
|
@ -6,7 +6,9 @@ from urllib.parse import urlencode
|
|||
from django.shortcuts import render, get_object_or_404, redirect
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.core.cache import cache
|
||||
from django.views.generic import ListView, DetailView, TemplateView, View
|
||||
from django.views.generic import (
|
||||
DetailView, FormView, ListView, TemplateView, View,
|
||||
)
|
||||
from django.views.generic.detail import BaseDetailView
|
||||
from django.views.generic.edit import CreateView, UpdateView
|
||||
from django.core.urlresolvers import reverse, reverse_lazy
|
||||
|
@ -24,9 +26,11 @@ 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
|
||||
|
||||
from kfet.config import kfet_config
|
||||
from kfet.decorators import teamkfet_required
|
||||
from kfet.models import (
|
||||
Account, Checkout, Article, Settings, AccountNegative,
|
||||
Account, Checkout, Article, AccountNegative,
|
||||
CheckoutStatement, GenericTeamToken, Supplier, SupplierArticle, Inventory,
|
||||
InventoryArticle, Order, OrderArticle, Operation, OperationGroup,
|
||||
TransferGroup, Transfer, ArticleCategory)
|
||||
|
@ -37,9 +41,9 @@ from kfet.forms import (
|
|||
GroupForm, CheckoutForm, CheckoutRestrictForm, CheckoutStatementCreateForm,
|
||||
CheckoutStatementUpdateForm, ArticleForm, ArticleRestrictForm,
|
||||
KPsulOperationGroupForm, KPsulAccountForm, KPsulCheckoutForm,
|
||||
KPsulOperationFormSet, AddcostForm, FilterHistoryForm, SettingsForm,
|
||||
KPsulOperationFormSet, AddcostForm, FilterHistoryForm,
|
||||
TransferFormSet, InventoryArticleForm, OrderArticleForm,
|
||||
OrderArticleToInventoryForm, CategoryForm
|
||||
OrderArticleToInventoryForm, CategoryForm, KFetConfigForm
|
||||
)
|
||||
from collections import defaultdict
|
||||
from kfet import consumers
|
||||
|
@ -48,18 +52,28 @@ from decimal import Decimal
|
|||
import django_cas_ng
|
||||
import heapq
|
||||
import statistics
|
||||
from kfet.statistic import ScaleMixin, last_stats_manifest, tot_ventes
|
||||
from kfet.statistic import ScaleMixin, last_stats_manifest, tot_ventes, WeekScale
|
||||
|
||||
|
||||
class Home(TemplateView):
|
||||
template_name = "kfet/home.html"
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(TemplateView, self).get_context_data(**kwargs)
|
||||
articles = Article.objects.all().filter(is_sold=True, hidden=False)
|
||||
context['pressions'] = articles.filter(category__name='Pression')
|
||||
context['articles'] = (articles.exclude(category__name='Pression')
|
||||
.order_by('category'))
|
||||
context = super().get_context_data(**kwargs)
|
||||
articles = list(
|
||||
Article.objects
|
||||
.filter(is_sold=True, hidden=False)
|
||||
.select_related('category')
|
||||
.order_by('category__name')
|
||||
)
|
||||
pressions, others = [], []
|
||||
while len(articles) > 0:
|
||||
article = articles.pop()
|
||||
if article.category.name == 'Pression':
|
||||
pressions.append(article)
|
||||
else:
|
||||
others.append(article)
|
||||
context['pressions'], context['articles'] = pressions, others
|
||||
return context
|
||||
|
||||
@method_decorator(login_required)
|
||||
|
@ -141,6 +155,8 @@ def login_genericteam(request):
|
|||
user = authenticate(username="kfet_genericteam", token=token.token)
|
||||
login(request, user)
|
||||
|
||||
messages.success(request, "Connecté en utilisateur partagé")
|
||||
|
||||
if need_cas_logout:
|
||||
# Vue de déconnexion de CAS
|
||||
return logout_cas
|
||||
|
@ -404,31 +420,31 @@ def account_create_ajax(request, username=None, login_clipper=None,
|
|||
'user_form' : forms['user_form'],
|
||||
})
|
||||
|
||||
|
||||
# Account - Read
|
||||
|
||||
@login_required
|
||||
def account_read(request, trigramme):
|
||||
try:
|
||||
account = Account.objects.select_related('negative').get(trigramme=trigramme)
|
||||
except Account.DoesNotExist:
|
||||
raise Http404
|
||||
account = get_object_or_404(Account, trigramme=trigramme)
|
||||
|
||||
# Checking permissions
|
||||
if not request.user.has_perm('kfet.is_team') \
|
||||
and request.user != account.user:
|
||||
raise PermissionDenied
|
||||
|
||||
addcosts = (OperationGroup.objects
|
||||
.filter(opes__addcost_for=account,opes__canceled_at=None)
|
||||
.extra({'date':"date(at)"})
|
||||
.values('date')
|
||||
.annotate(sum_addcosts=Sum('opes__addcost_amount'))
|
||||
.order_by('-date'))
|
||||
addcosts = (
|
||||
OperationGroup.objects
|
||||
.filter(opes__addcost_for=account,
|
||||
opes__canceled_at=None)
|
||||
.extra({'date': "date(at)"})
|
||||
.values('date')
|
||||
.annotate(sum_addcosts=Sum('opes__addcost_amount'))
|
||||
.order_by('-date')
|
||||
)
|
||||
|
||||
return render(request, "kfet/account_read.html", {
|
||||
'account' : account,
|
||||
'account': account,
|
||||
'addcosts': addcosts,
|
||||
'settings': { 'subvention_cof': Settings.SUBVENTION_COF() },
|
||||
})
|
||||
|
||||
# Account - Update
|
||||
|
@ -575,13 +591,22 @@ def account_update(request, trigramme):
|
|||
'pwd_form': pwd_form,
|
||||
})
|
||||
|
||||
|
||||
@permission_required('kfet.manage_perms')
|
||||
def account_group(request):
|
||||
groups = (Group.objects
|
||||
.filter(name__icontains='K-Fêt')
|
||||
.prefetch_related('permissions', 'user_set__profile__account_kfet')
|
||||
user_pre = Prefetch(
|
||||
'user_set',
|
||||
queryset=User.objects.select_related('profile__account_kfet'),
|
||||
)
|
||||
return render(request, 'kfet/account_group.html', { 'groups': groups })
|
||||
groups = (
|
||||
Group.objects
|
||||
.filter(name__icontains='K-Fêt')
|
||||
.prefetch_related('permissions', user_pre)
|
||||
)
|
||||
return render(request, 'kfet/account_group.html', {
|
||||
'groups': groups,
|
||||
})
|
||||
|
||||
|
||||
class AccountGroupCreate(SuccessMessageMixin, CreateView):
|
||||
model = Group
|
||||
|
@ -590,34 +615,27 @@ class AccountGroupCreate(SuccessMessageMixin, CreateView):
|
|||
success_message = 'Nouveau groupe : %(name)s'
|
||||
success_url = reverse_lazy('kfet.account.group')
|
||||
|
||||
class AccountGroupUpdate(UpdateView):
|
||||
class AccountGroupUpdate(SuccessMessageMixin, UpdateView):
|
||||
queryset = Group.objects.filter(name__icontains='K-Fêt')
|
||||
template_name = 'kfet/account_group_form.html'
|
||||
form_class = GroupForm
|
||||
success_message = 'Groupe modifié : %(name)s'
|
||||
success_url = reverse_lazy('kfet.account.group')
|
||||
|
||||
|
||||
class AccountNegativeList(ListView):
|
||||
queryset = (AccountNegative.objects
|
||||
queryset = (
|
||||
AccountNegative.objects
|
||||
.select_related('account', 'account__cofprofile__user')
|
||||
.exclude(account__trigramme='#13'))
|
||||
.exclude(account__trigramme='#13')
|
||||
)
|
||||
template_name = 'kfet/account_negative.html'
|
||||
context_object_name = 'negatives'
|
||||
|
||||
def get_context_data(self, **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
|
||||
.exclude(account__trigramme='#13')
|
||||
.aggregate(
|
||||
bal = Coalesce(Sum('account__balance'),0),
|
||||
offset = Coalesce(Sum('balance_offset'),0),
|
||||
)
|
||||
)
|
||||
context['negatives_sum'] = negs_sum['bal'] - negs_sum['offset']
|
||||
real_balances = (neg.account.real_balance for neg in self.object_list)
|
||||
context['negatives_sum'] = sum(real_balances)
|
||||
return context
|
||||
|
||||
# -----
|
||||
|
@ -820,12 +838,18 @@ class CategoryUpdate(SuccessMessageMixin, UpdateView):
|
|||
|
||||
# Article - General
|
||||
class ArticleList(ListView):
|
||||
queryset = (Article.objects
|
||||
.select_related('category')
|
||||
.prefetch_related(Prefetch('inventories',
|
||||
queryset=Inventory.objects.order_by('-at'),
|
||||
to_attr='inventory'))
|
||||
.order_by('category', '-is_sold', 'name'))
|
||||
queryset = (
|
||||
Article.objects
|
||||
.select_related('category')
|
||||
.prefetch_related(
|
||||
Prefetch(
|
||||
'inventories',
|
||||
queryset=Inventory.objects.order_by('-at'),
|
||||
to_attr='inventory',
|
||||
)
|
||||
)
|
||||
.order_by('category__name', '-is_sold', 'name')
|
||||
)
|
||||
template_name = 'kfet/article.html'
|
||||
context_object_name = 'articles'
|
||||
|
||||
|
@ -937,7 +961,6 @@ class ArticleUpdate(SuccessMessageMixin, UpdateView):
|
|||
return super(ArticleUpdate, self).form_valid(form)
|
||||
|
||||
|
||||
|
||||
# -----
|
||||
# K-Psul
|
||||
# -----
|
||||
|
@ -947,29 +970,24 @@ def kpsul(request):
|
|||
data = {}
|
||||
data['operationgroup_form'] = KPsulOperationGroupForm()
|
||||
data['trigramme_form'] = KPsulAccountForm()
|
||||
initial = {}
|
||||
try:
|
||||
checkout = Checkout.objects.filter(
|
||||
is_protected=False, valid_from__lte=timezone.now(),
|
||||
valid_to__gte=timezone.now()).get()
|
||||
initial['checkout'] = checkout
|
||||
except (Checkout.DoesNotExist, Checkout.MultipleObjectsReturned):
|
||||
pass
|
||||
data['checkout_form'] = KPsulCheckoutForm(initial=initial)
|
||||
operation_formset = KPsulOperationFormSet(queryset=Operation.objects.none())
|
||||
data['operation_formset'] = operation_formset
|
||||
data['checkout_form'] = KPsulCheckoutForm()
|
||||
data['operation_formset'] = KPsulOperationFormSet(
|
||||
queryset=Operation.objects.none(),
|
||||
)
|
||||
return render(request, 'kfet/kpsul.html', data)
|
||||
|
||||
|
||||
@teamkfet_required
|
||||
def kpsul_get_settings(request):
|
||||
addcost_for = Settings.ADDCOST_FOR()
|
||||
addcost_for = kfet_config.addcost_for
|
||||
data = {
|
||||
'subvention_cof': Settings.SUBVENTION_COF(),
|
||||
'addcost_for' : addcost_for and addcost_for.trigramme or '',
|
||||
'addcost_amount': Settings.ADDCOST_AMOUNT(),
|
||||
'subvention_cof': kfet_config.subvention_cof,
|
||||
'addcost_for': addcost_for and addcost_for.trigramme or '',
|
||||
'addcost_amount': kfet_config.addcost_amount,
|
||||
}
|
||||
return JsonResponse(data)
|
||||
|
||||
|
||||
@teamkfet_required
|
||||
def account_read_json(request):
|
||||
trigramme = request.POST.get('trigramme', '')
|
||||
|
@ -1009,6 +1027,7 @@ def kpsul_checkout_data(request):
|
|||
raise Http404
|
||||
return JsonResponse(data)
|
||||
|
||||
|
||||
@teamkfet_required
|
||||
def kpsul_update_addcost(request):
|
||||
addcost_form = AddcostForm(request.POST)
|
||||
|
@ -1028,15 +1047,15 @@ def kpsul_update_addcost(request):
|
|||
|
||||
trigramme = addcost_form.cleaned_data['trigramme']
|
||||
account = trigramme and Account.objects.get(trigramme=trigramme) or None
|
||||
Settings.objects.filter(name='ADDCOST_FOR').update(value_account=account)
|
||||
(Settings.objects.filter(name='ADDCOST_AMOUNT')
|
||||
.update(value_decimal=addcost_form.cleaned_data['amount']))
|
||||
cache.delete('ADDCOST_FOR')
|
||||
cache.delete('ADDCOST_AMOUNT')
|
||||
amount = addcost_form.cleaned_data['amount']
|
||||
|
||||
kfet_config.set(addcost_for=account,
|
||||
addcost_amount=amount)
|
||||
|
||||
data = {
|
||||
'addcost': {
|
||||
'for': trigramme and account.trigramme or None,
|
||||
'amount': addcost_form.cleaned_data['amount'],
|
||||
'for': account and account.trigramme or None,
|
||||
'amount': amount,
|
||||
}
|
||||
}
|
||||
consumers.KPsul.group_send('kfet.kpsul', data)
|
||||
|
@ -1080,10 +1099,10 @@ def kpsul_perform_operations(request):
|
|||
operations = operation_formset.save(commit=False)
|
||||
|
||||
# Retrieving COF grant
|
||||
cof_grant = Settings.SUBVENTION_COF()
|
||||
cof_grant = kfet_config.subvention_cof
|
||||
# Retrieving addcosts data
|
||||
addcost_amount = Settings.ADDCOST_AMOUNT()
|
||||
addcost_for = Settings.ADDCOST_FOR()
|
||||
addcost_amount = kfet_config.addcost_amount
|
||||
addcost_for = kfet_config.addcost_for
|
||||
|
||||
# Initializing vars
|
||||
required_perms = set() # Required perms to perform all operations
|
||||
|
@ -1159,22 +1178,15 @@ def kpsul_perform_operations(request):
|
|||
with transaction.atomic():
|
||||
# If not cash account,
|
||||
# saving account's balance and adding to Negative if not in
|
||||
if not operationgroup.on_acc.is_cash:
|
||||
Account.objects.filter(pk=operationgroup.on_acc.pk).update(
|
||||
balance=F('balance') + operationgroup.amount)
|
||||
operationgroup.on_acc.refresh_from_db()
|
||||
if operationgroup.on_acc.balance < 0:
|
||||
if hasattr(operationgroup.on_acc, 'negative'):
|
||||
if not operationgroup.on_acc.negative.start:
|
||||
operationgroup.on_acc.negative.start = timezone.now()
|
||||
operationgroup.on_acc.negative.save()
|
||||
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()
|
||||
on_acc = operationgroup.on_acc
|
||||
if not on_acc.is_cash:
|
||||
(
|
||||
Account.objects
|
||||
.filter(pk=on_acc.pk)
|
||||
.update(balance=F('balance') + operationgroup.amount)
|
||||
)
|
||||
on_acc.refresh_from_db()
|
||||
on_acc.update_negative()
|
||||
|
||||
# Updating checkout's balance
|
||||
if to_checkout_balance:
|
||||
|
@ -1274,7 +1286,7 @@ def kpsul_cancel_operations(request):
|
|||
opes = [] # Pas déjà annulée
|
||||
required_perms = set()
|
||||
stop_all = False
|
||||
cancel_duration = Settings.CANCEL_DURATION()
|
||||
cancel_duration = kfet_config.cancel_duration
|
||||
to_accounts_balances = defaultdict(lambda:0) # Modifs à faire sur les balances des comptes
|
||||
to_groups_amounts = defaultdict(lambda:0) # ------ sur les montants des groupes d'opé
|
||||
to_checkouts_balances = defaultdict(lambda:0) # ------ sur les balances de caisses
|
||||
|
@ -1365,8 +1377,15 @@ def kpsul_cancel_operations(request):
|
|||
(Operation.objects.filter(pk__in=opes)
|
||||
.update(canceled_by=canceled_by, canceled_at=canceled_at))
|
||||
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:
|
||||
Checkout.objects.filter(pk=checkout.pk).update(
|
||||
balance = F('balance') + to_checkouts_balances[checkout])
|
||||
|
@ -1502,34 +1521,28 @@ def kpsul_articles_data(request):
|
|||
.filter(is_sold=True))
|
||||
return JsonResponse({ 'articles': list(articles) })
|
||||
|
||||
|
||||
@teamkfet_required
|
||||
def history(request):
|
||||
data = {
|
||||
'filter_form': FilterHistoryForm(),
|
||||
'settings': {
|
||||
'subvention_cof': Settings.SUBVENTION_COF(),
|
||||
}
|
||||
}
|
||||
return render(request, 'kfet/history.html', data)
|
||||
|
||||
|
||||
# -----
|
||||
# Settings views
|
||||
# -----
|
||||
|
||||
class SettingsList(ListView):
|
||||
model = Settings
|
||||
context_object_name = 'settings'
|
||||
|
||||
class SettingsList(TemplateView):
|
||||
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):
|
||||
model = Settings
|
||||
form_class = SettingsForm
|
||||
class SettingsUpdate(SuccessMessageMixin, FormView):
|
||||
form_class = KFetConfigForm
|
||||
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')
|
||||
|
||||
def form_valid(self, form):
|
||||
|
@ -1537,9 +1550,10 @@ class SettingsUpdate(SuccessMessageMixin, UpdateView):
|
|||
if not self.request.user.has_perm('kfet.change_settings'):
|
||||
form.add_error(None, 'Permission refusée')
|
||||
return self.form_invalid(form)
|
||||
# Creating
|
||||
Settings.empty_cache()
|
||||
return super(SettingsUpdate, self).form_valid(form)
|
||||
form.save()
|
||||
return super().form_valid(form)
|
||||
|
||||
|
||||
|
||||
# -----
|
||||
# Transfer views
|
||||
|
@ -1547,13 +1561,25 @@ class SettingsUpdate(SuccessMessageMixin, UpdateView):
|
|||
|
||||
@teamkfet_required
|
||||
def transfers(request):
|
||||
transfergroups = (TransferGroup.objects
|
||||
.prefetch_related('transfers')
|
||||
.order_by('-at'))
|
||||
transfers_pre = Prefetch(
|
||||
'transfers',
|
||||
queryset=(
|
||||
Transfer.objects
|
||||
.select_related('from_acc', 'to_acc')
|
||||
),
|
||||
)
|
||||
|
||||
transfergroups = (
|
||||
TransferGroup.objects
|
||||
.select_related('valid_by')
|
||||
.prefetch_related(transfers_pre)
|
||||
.order_by('-at')
|
||||
)
|
||||
return render(request, 'kfet/transfers.html', {
|
||||
'transfergroups': transfergroups,
|
||||
})
|
||||
|
||||
|
||||
@teamkfet_required
|
||||
def transfers_create(request):
|
||||
transfer_formset = TransferFormSet(queryset=Transfer.objects.none())
|
||||
|
@ -1665,7 +1691,7 @@ def cancel_transfers(request):
|
|||
transfers = [] # Pas déjà annulée
|
||||
required_perms = set()
|
||||
stop_all = False
|
||||
cancel_duration = Settings.CANCEL_DURATION()
|
||||
cancel_duration = kfet_config.cancel_duration
|
||||
to_accounts_balances = defaultdict(lambda:0) # Modifs à faire sur les balances des comptes
|
||||
for transfer in transfers_all:
|
||||
if transfer.canceled_at:
|
||||
|
@ -1697,9 +1723,6 @@ def cancel_transfers(request):
|
|||
if stop:
|
||||
negative_accounts.append(account.trigramme)
|
||||
|
||||
print(required_perms)
|
||||
print(request.user.get_all_permissions())
|
||||
|
||||
if stop_all or not request.user.has_perms(required_perms):
|
||||
missing_perms = get_missing_perms(required_perms, request.user)
|
||||
if missing_perms:
|
||||
|
@ -1837,68 +1860,54 @@ class OrderList(ListView):
|
|||
context['suppliers'] = Supplier.objects.order_by('name')
|
||||
return context
|
||||
|
||||
|
||||
@teamkfet_required
|
||||
def order_create(request, pk):
|
||||
supplier = get_object_or_404(Supplier, pk=pk)
|
||||
|
||||
articles = (Article.objects
|
||||
.filter(suppliers=supplier.pk)
|
||||
.distinct()
|
||||
.select_related('category')
|
||||
.order_by('category__name', 'name'))
|
||||
articles = (
|
||||
Article.objects
|
||||
.filter(suppliers=supplier.pk)
|
||||
.distinct()
|
||||
.select_related('category')
|
||||
.order_by('category__name', 'name')
|
||||
)
|
||||
|
||||
initial = []
|
||||
today = timezone.now().date()
|
||||
sales_q = (Operation.objects
|
||||
# Force hit to cache
|
||||
articles = list(articles)
|
||||
|
||||
sales_q = (
|
||||
Operation.objects
|
||||
.select_related('group')
|
||||
.filter(article__in=articles, canceled_at=None)
|
||||
.values('article'))
|
||||
sales_s1 = (sales_q
|
||||
.filter(
|
||||
group__at__gte = today-timedelta(weeks=5),
|
||||
group__at__lt = today-timedelta(weeks=4))
|
||||
.values('article')
|
||||
.annotate(nb=Sum('article_nb'))
|
||||
)
|
||||
sales_s1 = { d['article']:d['nb'] for d in sales_s1 }
|
||||
sales_s2 = (sales_q
|
||||
.filter(
|
||||
group__at__gte = today-timedelta(weeks=4),
|
||||
group__at__lt = today-timedelta(weeks=3))
|
||||
.annotate(nb=Sum('article_nb'))
|
||||
)
|
||||
sales_s2 = { d['article']:d['nb'] for d in sales_s2 }
|
||||
sales_s3 = (sales_q
|
||||
.filter(
|
||||
group__at__gte = today-timedelta(weeks=3),
|
||||
group__at__lt = today-timedelta(weeks=2))
|
||||
.annotate(nb=Sum('article_nb'))
|
||||
)
|
||||
sales_s3 = { d['article']:d['nb'] for d in sales_s3 }
|
||||
sales_s4 = (sales_q
|
||||
.filter(
|
||||
group__at__gte = today-timedelta(weeks=2),
|
||||
group__at__lt = today-timedelta(weeks=1))
|
||||
.annotate(nb=Sum('article_nb'))
|
||||
)
|
||||
sales_s4 = { d['article']:d['nb'] for d in sales_s4 }
|
||||
sales_s5 = (sales_q
|
||||
.filter(group__at__gte = today-timedelta(weeks=1))
|
||||
.annotate(nb=Sum('article_nb'))
|
||||
)
|
||||
sales_s5 = { d['article']:d['nb'] for d in sales_s5 }
|
||||
scale = WeekScale(last=True, n_steps=5, std_chunk=False)
|
||||
chunks = scale.chunkify_qs(sales_q, field='group__at')
|
||||
|
||||
sales = [
|
||||
{d['article']: d['nb'] for d in chunk}
|
||||
for chunk in chunks
|
||||
]
|
||||
|
||||
initial = []
|
||||
|
||||
for article in articles:
|
||||
v_s1 = sales_s1.get(article.pk, 0)
|
||||
v_s2 = sales_s2.get(article.pk, 0)
|
||||
v_s3 = sales_s3.get(article.pk, 0)
|
||||
v_s4 = sales_s4.get(article.pk, 0)
|
||||
v_s5 = sales_s5.get(article.pk, 0)
|
||||
v_all = [v_s1, v_s2, v_s3, v_s4, v_s5]
|
||||
# Get sales for each 5 last weeks
|
||||
v_all = [chunk.get(article.pk, 0) for chunk in sales]
|
||||
# Take the 3 greatest (eg to avoid 2 weeks of vacations)
|
||||
v_3max = heapq.nlargest(3, v_all)
|
||||
# Get average and standard deviation
|
||||
v_moy = statistics.mean(v_3max)
|
||||
v_et = statistics.pstdev(v_3max, v_moy)
|
||||
# Expected sales for next week
|
||||
v_prev = v_moy + v_et
|
||||
# We want to have 1.5 * the expected sales in stock
|
||||
# (because sometimes some articles are not delivered)
|
||||
c_rec_tot = max(v_prev * 1.5 - article.stock, 0)
|
||||
# If ordered quantity is close enough to a level which can led to free
|
||||
# boxes, we increase it to this level.
|
||||
if article.box_capacity:
|
||||
c_rec_temp = c_rec_tot / article.box_capacity
|
||||
if c_rec_temp >= 10:
|
||||
|
@ -1916,11 +1925,7 @@ def order_create(request, pk):
|
|||
'category__name': article.category.name,
|
||||
'stock': article.stock,
|
||||
'box_capacity': article.box_capacity,
|
||||
'v_s1': v_s1,
|
||||
'v_s2': v_s2,
|
||||
'v_s3': v_s3,
|
||||
'v_s4': v_s4,
|
||||
'v_s5': v_s5,
|
||||
'v_all': v_all,
|
||||
'v_moy': round(v_moy),
|
||||
'v_et': round(v_et),
|
||||
'v_prev': round(v_prev),
|
||||
|
@ -1928,8 +1933,9 @@ def order_create(request, pk):
|
|||
})
|
||||
|
||||
cls_formset = formset_factory(
|
||||
form = OrderArticleForm,
|
||||
extra = 0)
|
||||
form=OrderArticleForm,
|
||||
extra=0,
|
||||
)
|
||||
|
||||
if request.POST:
|
||||
formset = cls_formset(request.POST, initial=initial)
|
||||
|
@ -1946,14 +1952,15 @@ def order_create(request, pk):
|
|||
order.save()
|
||||
saved = True
|
||||
|
||||
article = articles.get(pk=form.cleaned_data['article'].pk)
|
||||
article = form.cleaned_data['article']
|
||||
q_ordered = form.cleaned_data['quantity_ordered']
|
||||
if article.box_capacity:
|
||||
q_ordered *= article.box_capacity
|
||||
OrderArticle.objects.create(
|
||||
order = order,
|
||||
article = article,
|
||||
quantity_ordered = q_ordered)
|
||||
order=order,
|
||||
article=article,
|
||||
quantity_ordered=q_ordered,
|
||||
)
|
||||
if saved:
|
||||
messages.success(request, 'Commande créée')
|
||||
return redirect('kfet.order.read', order.pk)
|
||||
|
@ -1963,11 +1970,15 @@ def order_create(request, pk):
|
|||
else:
|
||||
formset = cls_formset(initial=initial)
|
||||
|
||||
scale.label_fmt = "S -{rev_i}"
|
||||
|
||||
return render(request, 'kfet/order_create.html', {
|
||||
'supplier': supplier,
|
||||
'formset' : formset,
|
||||
'formset': formset,
|
||||
'scale': scale,
|
||||
})
|
||||
|
||||
|
||||
class OrderRead(DetailView):
|
||||
model = Order
|
||||
template_name = 'kfet/order_read.html'
|
||||
|
@ -2006,6 +2017,7 @@ class OrderRead(DetailView):
|
|||
context['mail'] = mail
|
||||
return context
|
||||
|
||||
|
||||
@teamkfet_required
|
||||
def order_to_inventory(request, pk):
|
||||
order = get_object_or_404(Order, pk=pk)
|
||||
|
@ -2013,28 +2025,36 @@ def order_to_inventory(request, pk):
|
|||
if hasattr(order, 'inventory'):
|
||||
raise Http404
|
||||
|
||||
articles = (Article.objects
|
||||
.filter(orders=order.pk)
|
||||
.select_related('category')
|
||||
.prefetch_related(Prefetch('orderarticle_set',
|
||||
queryset = OrderArticle.objects.filter(order=order),
|
||||
to_attr = 'order'))
|
||||
.prefetch_related(Prefetch('supplierarticle_set',
|
||||
queryset = (SupplierArticle.objects
|
||||
.filter(supplier=order.supplier)
|
||||
.order_by('-at')),
|
||||
to_attr = 'supplier'))
|
||||
.order_by('category__name', 'name'))
|
||||
supplier_prefetch = Prefetch(
|
||||
'article__supplierarticle_set',
|
||||
queryset=(
|
||||
SupplierArticle.objects
|
||||
.filter(supplier=order.supplier)
|
||||
.order_by('-at')
|
||||
),
|
||||
to_attr='supplier',
|
||||
)
|
||||
|
||||
order_articles = (
|
||||
OrderArticle.objects
|
||||
.filter(order=order.pk)
|
||||
.select_related('article', 'article__category')
|
||||
.prefetch_related(
|
||||
supplier_prefetch,
|
||||
)
|
||||
.order_by('article__category__name', 'article__name')
|
||||
)
|
||||
|
||||
initial = []
|
||||
for article in articles:
|
||||
for order_article in order_articles:
|
||||
article = order_article.article
|
||||
initial.append({
|
||||
'article': article.pk,
|
||||
'name': article.name,
|
||||
'category': article.category_id,
|
||||
'category__name': article.category.name,
|
||||
'quantity_ordered': article.order[0].quantity_ordered,
|
||||
'quantity_received': article.order[0].quantity_ordered,
|
||||
'quantity_ordered': order_article.quantity_ordered,
|
||||
'quantity_received': order_article.quantity_ordered,
|
||||
'price_HT': article.supplier[0].price_HT,
|
||||
'TVA': article.supplier[0].TVA,
|
||||
'rights': article.supplier[0].rights,
|
||||
|
@ -2049,31 +2069,50 @@ def order_to_inventory(request, pk):
|
|||
messages.error(request, 'Permission refusée')
|
||||
elif formset.is_valid():
|
||||
with transaction.atomic():
|
||||
inventory = Inventory()
|
||||
inventory.order = order
|
||||
inventory.by = request.user.profile.account_kfet
|
||||
inventory.save()
|
||||
inventory = Inventory.objects.create(
|
||||
order=order, by=request.user.profile.account_kfet,
|
||||
)
|
||||
new_supplierarticle = []
|
||||
new_inventoryarticle = []
|
||||
for form in formset:
|
||||
q_received = form.cleaned_data['quantity_received']
|
||||
article = form.cleaned_data['article']
|
||||
SupplierArticle.objects.create(
|
||||
supplier = order.supplier,
|
||||
article = article,
|
||||
price_HT = form.cleaned_data['price_HT'],
|
||||
TVA = form.cleaned_data['TVA'],
|
||||
rights = form.cleaned_data['rights'])
|
||||
(OrderArticle.objects
|
||||
.filter(order=order, article=article)
|
||||
.update(quantity_received = q_received))
|
||||
InventoryArticle.objects.create(
|
||||
inventory = inventory,
|
||||
article = article,
|
||||
stock_old = article.stock,
|
||||
stock_new = article.stock + q_received)
|
||||
|
||||
price_HT = form.cleaned_data['price_HT']
|
||||
TVA = form.cleaned_data['TVA']
|
||||
rights = form.cleaned_data['rights']
|
||||
|
||||
if any((form.initial['price_HT'] != price_HT,
|
||||
form.initial['TVA'] != TVA,
|
||||
form.initial['rights'] != rights)):
|
||||
new_supplierarticle.append(
|
||||
SupplierArticle(
|
||||
supplier=order.supplier,
|
||||
article=article,
|
||||
price_HT=price_HT,
|
||||
TVA=TVA,
|
||||
rights=rights,
|
||||
)
|
||||
)
|
||||
(
|
||||
OrderArticle.objects
|
||||
.filter(order=order, article=article)
|
||||
.update(quantity_received=q_received)
|
||||
)
|
||||
new_inventoryarticle.append(
|
||||
InventoryArticle(
|
||||
inventory=inventory,
|
||||
article=article,
|
||||
stock_old=article.stock,
|
||||
stock_new=article.stock + q_received,
|
||||
)
|
||||
)
|
||||
article.stock += q_received
|
||||
if q_received > 0:
|
||||
article.is_sold = True
|
||||
article.save()
|
||||
SupplierArticle.objects.bulk_create(new_supplierarticle)
|
||||
InventoryArticle.objects.bulk_create(new_inventoryarticle)
|
||||
messages.success(request, "C'est tout bon !")
|
||||
return redirect('kfet.order')
|
||||
else:
|
||||
|
@ -2083,6 +2122,7 @@ def order_to_inventory(request, pk):
|
|||
|
||||
return render(request, 'kfet/order_to_inventory.html', {
|
||||
'formset': formset,
|
||||
'order': order,
|
||||
})
|
||||
|
||||
class SupplierUpdate(SuccessMessageMixin, UpdateView):
|
||||
|
@ -2258,10 +2298,13 @@ class AccountStatBalance(PkUrlMixin, JSONDetailView):
|
|||
# prepare querysets
|
||||
# TODO: retirer les opgroup dont tous les op sont annulées
|
||||
opegroups = OperationGroup.objects.filter(on_acc=account)
|
||||
recv_transfers = Transfer.objects.filter(to_acc=account,
|
||||
canceled_at=None)
|
||||
sent_transfers = Transfer.objects.filter(from_acc=account,
|
||||
canceled_at=None)
|
||||
transfers = (
|
||||
Transfer.objects
|
||||
.filter(canceled_at=None)
|
||||
.select_related('group')
|
||||
)
|
||||
recv_transfers = transfers.filter(to_acc=account)
|
||||
sent_transfers = transfers.filter(from_acc=account)
|
||||
|
||||
# apply filters
|
||||
if begin_date is not None:
|
||||
|
@ -2289,13 +2332,11 @@ class AccountStatBalance(PkUrlMixin, JSONDetailView):
|
|||
actions.append({
|
||||
'at': (begin_date or account.created_at).isoformat(),
|
||||
'amount': 0,
|
||||
'label': 'début',
|
||||
'balance': 0,
|
||||
})
|
||||
actions.append({
|
||||
'at': (end_date or timezone.now()).isoformat(),
|
||||
'amount': 0,
|
||||
'label': 'fin',
|
||||
'balance': 0,
|
||||
})
|
||||
|
||||
|
@ -2303,21 +2344,18 @@ class AccountStatBalance(PkUrlMixin, JSONDetailView):
|
|||
{
|
||||
'at': ope_grp.at.isoformat(),
|
||||
'amount': ope_grp.amount,
|
||||
'label': str(ope_grp),
|
||||
'balance': 0,
|
||||
} for ope_grp in opegroups
|
||||
] + [
|
||||
{
|
||||
'at': tr.group.at.isoformat(),
|
||||
'amount': tr.amount,
|
||||
'label': str(tr),
|
||||
'balance': 0,
|
||||
} for tr in recv_transfers
|
||||
] + [
|
||||
{
|
||||
'at': tr.group.at.isoformat(),
|
||||
'amount': -tr.amount,
|
||||
'label': str(tr),
|
||||
'balance': 0,
|
||||
} for tr in sent_transfers
|
||||
]
|
||||
|
@ -2410,13 +2448,19 @@ class AccountStatOperation(ScaleMixin, PkUrlMixin, JSONDetailView):
|
|||
# à 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(group__on_acc=self.object)
|
||||
.filter(canceled_at=None)
|
||||
)
|
||||
all_operations = (
|
||||
Operation.objects
|
||||
.filter(group__on_acc=self.object,
|
||||
canceled_at=None)
|
||||
.values('article_nb', 'group__at')
|
||||
.order_by('group__at')
|
||||
)
|
||||
if types is not None:
|
||||
all_operations = all_operations.filter(type__in=types)
|
||||
chunks = self.chunkify_qs(all_operations, scale, field='group__at')
|
||||
chunks = scale.get_by_chunks(
|
||||
all_operations, field_db='group__at',
|
||||
field_callback=(lambda d: d['group__at']),
|
||||
)
|
||||
return chunks
|
||||
|
||||
def get_context_data(self, *args, **kwargs):
|
||||
|
@ -2432,7 +2476,8 @@ class AccountStatOperation(ScaleMixin, PkUrlMixin, JSONDetailView):
|
|||
# On compte les opérations
|
||||
nb_ventes = []
|
||||
for chunk in operations:
|
||||
nb_ventes.append(tot_ventes(chunk))
|
||||
ventes = sum(ope['article_nb'] for ope in chunk)
|
||||
nb_ventes.append(ventes)
|
||||
|
||||
context['charts'] = [{"color": "rgb(255, 99, 132)",
|
||||
"label": "NB items achetés",
|
||||
|
@ -2483,29 +2528,39 @@ class ArticleStatSales(ScaleMixin, JSONDetailView):
|
|||
context = {'labels': old_ctx['labels']}
|
||||
scale = self.scale
|
||||
|
||||
# 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 = (
|
||||
all_purchases = (
|
||||
Operation.objects
|
||||
.filter(type=Operation.PURCHASE,
|
||||
article=self.object,
|
||||
canceled_at=None,
|
||||
)
|
||||
.filter(
|
||||
type=Operation.PURCHASE,
|
||||
article=self.object,
|
||||
canceled_at=None,
|
||||
)
|
||||
.values('group__at', 'article_nb')
|
||||
.order_by('group__at')
|
||||
)
|
||||
chunks = self.chunkify_qs(all_operations, scale, field='group__at')
|
||||
liq_only = all_purchases.filter(group__on_acc__trigramme='LIQ')
|
||||
liq_exclude = all_purchases.exclude(group__on_acc__trigramme='LIQ')
|
||||
|
||||
chunks_liq = scale.get_by_chunks(
|
||||
liq_only, field_db='group__at',
|
||||
field_callback=lambda d: d['group__at'],
|
||||
)
|
||||
chunks_no_liq = scale.get_by_chunks(
|
||||
liq_exclude, field_db='group__at',
|
||||
field_callback=lambda d: d['group__at'],
|
||||
)
|
||||
|
||||
# On compte les opérations
|
||||
nb_ventes = []
|
||||
nb_accounts = []
|
||||
nb_liq = []
|
||||
for qs in chunks:
|
||||
nb_ventes.append(
|
||||
tot_ventes(qs))
|
||||
nb_liq.append(
|
||||
tot_ventes(qs.filter(group__on_acc__trigramme='LIQ')))
|
||||
nb_accounts.append(
|
||||
tot_ventes(qs.exclude(group__on_acc__trigramme='LIQ')))
|
||||
for chunk_liq, chunk_no_liq in zip(chunks_liq, chunks_no_liq):
|
||||
sum_accounts = sum(ope['article_nb'] for ope in chunk_no_liq)
|
||||
sum_liq = sum(ope['article_nb'] for ope in chunk_liq)
|
||||
nb_ventes.append(sum_accounts + sum_liq)
|
||||
nb_accounts.append(sum_accounts)
|
||||
nb_liq.append(sum_liq)
|
||||
|
||||
context['charts'] = [{"color": "rgb(255, 99, 132)",
|
||||
"label": "Toutes consommations",
|
||||
"values": nb_ventes},
|
||||
|
|
|
@ -8,9 +8,9 @@ DBNAME="cof_gestion"
|
|||
DBPASSWD="4KZt3nGPLVeWSvtBZPSM3fSzXpzEU4"
|
||||
|
||||
# Installation de paquets utiles
|
||||
apt-get update && apt-get install -y python3-pip python3-dev python3-venv \
|
||||
libmysqlclient-dev libjpeg-dev git redis-server
|
||||
pip install -U pip
|
||||
apt-get update && apt-get upgrade -y
|
||||
apt-get install -y python3-pip python3-dev python3-venv libmysqlclient-dev \
|
||||
libjpeg-dev git redis-server
|
||||
|
||||
# Configuration et installation de mysql. Le mot de passe root est le même que
|
||||
# le mot de passe pour l'utilisateur local - pour rappel, ceci est une instance
|
||||
|
@ -23,6 +23,11 @@ apt-get install -y mysql-server
|
|||
mysql -uroot -p$DBPASSWD -e "CREATE DATABASE $DBNAME; GRANT ALL PRIVILEGES ON $DBNAME.* TO '$DBUSER'@'localhost' IDENTIFIED BY '$DBPASSWD'"
|
||||
mysql -uroot -p$DBPASSWD -e "GRANT ALL PRIVILEGES ON test_$DBNAME.* TO '$DBUSER'@'localhost'"
|
||||
|
||||
# Configuration de redis
|
||||
REDIS_PASSWD="dummy"
|
||||
redis-cli CONFIG SET requirepass $REDIS_PASSWD
|
||||
redis-cli -a $REDIS_PASSWD CONFIG REWRITE
|
||||
|
||||
# Installation et configuration d'Apache
|
||||
apt-get install -y apache2
|
||||
a2enmod proxy proxy_http proxy_wstunnel headers
|
||||
|
@ -36,7 +41,7 @@ chown -R ubuntu:www-data /var/www/static
|
|||
# Mise en place du .bash_profile pour tout configurer lors du `vagrant ssh`
|
||||
cat >> ~ubuntu/.bashrc <<EOF
|
||||
# On utilise la version de développement de GestioCOF
|
||||
export DJANGO_SETTINGS_MODULE='cof.settings_dev'
|
||||
export DJANGO_SETTINGS_MODULE='cof.settings.dev'
|
||||
|
||||
# Identifiants MySQL
|
||||
export DBUSER="$DBUSER"
|
||||
|
@ -62,7 +67,8 @@ sudo -H -u ubuntu ~ubuntu/venv/bin/pip install -U pip
|
|||
sudo -H -u ubuntu ~ubuntu/venv/bin/pip install -r requirements.txt -r requirements-devel.txt
|
||||
|
||||
# Préparation de Django
|
||||
sudo -H -u ubuntu DJANGO_SETTINGS_MODULE='cof.settings_dev' DBUSER=$DBUSER DBNAME=$DBNAME DBPASSWD=$DBPASSWD bash provisioning/prepare_django.sh
|
||||
if [ ! -f cof/settings/secret.py ]; then sudo -H -u ubuntu ln -s secret_example.py cof/settings/secret.py; fi
|
||||
sudo -H -u ubuntu DJANGO_SETTINGS_MODULE='cof.settings.dev' DBUSER=$DBUSER DBNAME=$DBNAME DBPASSWD=$DBPASSWD bash provisioning/prepare_django.sh
|
||||
|
||||
# Installation du cron pour les mails de rappels
|
||||
sudo -H -u ubuntu crontab provisioning/cron.dev
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# On utilise la version de développement de GestioCOF
|
||||
DJANGO_SETTINGS_MODULE='cof.settings_dev'
|
||||
DJANGO_SETTINGS_MODULE='cof.settings.dev'
|
||||
|
||||
# Identifiants MySQL
|
||||
DBUSER="cof_gestion"
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
command=/home/ubuntu/venv/bin/python /vagrant/manage.py runworker
|
||||
directory=/vagrant/
|
||||
user=ubuntu
|
||||
environment=DBUSER={DBUSER},DBNAME={DBNAME},DBPASSWD={DBPASSWD},DJANGO_SETTINGS_MODULE="cof.settings_dev"
|
||||
environment=DBUSER={DBUSER},DBNAME={DBNAME},DBPASSWD={DBPASSWD},DJANGO_SETTINGS_MODULE="cof.settings.dev"
|
||||
autostart=true
|
||||
autorestart=true
|
||||
redirect_stderr=true
|
||||
|
@ -11,7 +11,7 @@ redirect_stderr=true
|
|||
|
||||
[program:interface]
|
||||
command=/home/ubuntu/venv/bin/daphne -b 127.0.0.1 -p 8001 cof.asgi:channel_layer
|
||||
environment=DBUSER={DBUSER},DBNAME={DBNAME},DBPASSWD={DBPASSWD},DJANGO_SETTINGS_MODULE="cof.settings_dev"
|
||||
environment=DBUSER={DBUSER},DBNAME={DBNAME},DBPASSWD={DBPASSWD},DJANGO_SETTINGS_MODULE="cof.settings.dev"
|
||||
directory=/vagrant/
|
||||
redirect_stderr=true
|
||||
autostart=true
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
-r requirements.txt
|
||||
django-debug-toolbar
|
||||
django-debug-panel
|
||||
ipython
|
||||
|
|
|
@ -3,6 +3,7 @@ Django==1.8.*
|
|||
django-autocomplete-light==2.3.3
|
||||
django-autoslug==1.9.3
|
||||
django-cas-ng==3.5.5
|
||||
django-djconfig==0.5.3
|
||||
django-grappelli==2.8.1
|
||||
django-recaptcha==1.0.5
|
||||
mysqlclient==1.3.7
|
||||
|
@ -11,13 +12,13 @@ six==1.10.0
|
|||
unicodecsv==0.14.1
|
||||
icalendar==3.10
|
||||
django-bootstrap-form==3.2.1
|
||||
asgiref==0.14.0
|
||||
daphne==0.14.3
|
||||
asgi-redis==0.14.0
|
||||
asgiref==1.1.1
|
||||
daphne==1.2.0
|
||||
asgi-redis==1.3.0
|
||||
statistics==1.0.3.5
|
||||
future==0.15.2
|
||||
django-widget-tweaks==1.4.1
|
||||
git+https://git.eleves.ens.fr/cof-geek/django_custommail.git#egg=django_custommail
|
||||
ldap3
|
||||
git+https://github.com/Aureplop/channels.git#egg=channels
|
||||
channels==1.1.3
|
||||
python-dateutil
|
||||
|
|
Loading…
Reference in a new issue