From 27b0e3737d53df2a167baf5202a6fe8d4d121373 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Delobelle?= Date: Fri, 26 Aug 2016 15:30:40 +0200 Subject: [PATCH] Ajout faire des transferts --- kfet/forms.py | 42 +++++++- kfet/models.py | 33 ++++-- kfet/static/kfet/css/transfers_form.css | 58 ++++++++++ kfet/static/kfet/js/kfet.js | 94 ++++++++++++++--- kfet/templates/kfet/kpsul.html | 75 ++----------- kfet/templates/kfet/transfers.html | 16 +++ kfet/templates/kfet/transfers_create.html | 122 ++++++++++++++++++++++ kfet/urls.py | 22 +++- kfet/views.py | 86 +++++++++++++-- 9 files changed, 445 insertions(+), 103 deletions(-) create mode 100644 kfet/static/kfet/css/transfers_form.css create mode 100644 kfet/templates/kfet/transfers.html create mode 100644 kfet/templates/kfet/transfers_create.html diff --git a/kfet/forms.py b/kfet/forms.py index 8302067a..6709beb1 100644 --- a/kfet/forms.py +++ b/kfet/forms.py @@ -3,10 +3,12 @@ from django import forms from django.core.exceptions import ValidationError 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, inlineformset_factory +from django.forms.models import BaseInlineFormSet from django.utils import timezone from kfet.models import (Account, Checkout, Article, OperationGroup, Operation, - CheckoutStatement, ArticleCategory, Settings, AccountNegative) + CheckoutStatement, ArticleCategory, Settings, AccountNegative, Transfer, + TransferGroup) from gestioncof.models import CofProfile # ----- @@ -346,3 +348,39 @@ class SettingsForm(forms.ModelForm): class FilterHistoryForm(forms.Form): checkouts = forms.ModelMultipleChoiceField(queryset = Checkout.objects.all()) accounts = forms.ModelMultipleChoiceField(queryset = Account.objects.all()) + +# ----- +# Transfer forms +# ----- + +class TransferGroupForm(forms.ModelForm): + class Meta: + model = TransferGroup + fields = ['comment'] + +class TransferForm(forms.ModelForm): + from_acc = forms.ModelChoiceField( + queryset = Account.objects.exclude(trigramme__in=['LIQ', '#13']), + widget = forms.HiddenInput() + ) + to_acc = forms.ModelChoiceField( + queryset = Account.objects.exclude(trigramme__in=['LIQ', '#13']), + widget = forms.HiddenInput() + ) + + def clean_amount(self): + amount = self.cleaned_data['amount'] + if amount <= 0: + raise forms.ValidationError("Montant invalide") + return amount + + class Meta: + model = Transfer + fields = ['from_acc', 'to_acc', 'amount'] + +TransferFormSet = modelformset_factory( + Transfer, + form = TransferForm, + min_num = 1, validate_min = True, + extra = 9, +) diff --git a/kfet/models.py b/kfet/models.py index a2d50cdc..51560d4b 100644 --- a/kfet/models.py +++ b/kfet/models.py @@ -101,12 +101,9 @@ class Account(models.Model): data['is_free'] = True return data - def perms_to_perform_operation(self, amount, overdraft_duration_max=None, \ - overdraft_amount_max=None): - if overdraft_duration_max is None: - overdraft_duration_max = Settings.OVERDRAFT_DURATION() - if overdraft_amount_max is None: - overdraft_amount_max = Settings.OVERDRAFT_AMOUNT() + def perms_to_perform_operation(self, amount): + overdraft_duration_max = Settings.OVERDRAFT_DURATION() + overdraft_amount_max = Settings.OVERDRAFT_AMOUNT() perms = set() stop_ope = False # Checking is cash account @@ -572,17 +569,27 @@ class Settings(models.Model): @staticmethod def OVERDRAFT_DURATION(): + overdraft_duration = cache.get('OVERDRAFT_DURATION') + if overdraft_duration: + return overdraft_duration try: - return Settings.setting_inst("OVERDRAFT_DURATION").value_duration + overdraft_duration = Settings.setting_inst("OVERDRAFT_DURATION").value_duration except Settings.DoesNotExist: - return timedelta() + 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: - return Settings.setting_inst("OVERDRAFT_AMOUNT").value_decimal + overdraft_amount = Settings.setting_inst("OVERDRAFT_AMOUNT").value_decimal except Settings.DoesNotExist: - return 0 + overdraft_amount = 0 + cache.set('OVERDRAFT_AMOUNT', overdraft_amount) + return overdraft_amount def CANCEL_DURATION(): try: @@ -614,5 +621,11 @@ class Settings(models.Model): s.value_duration = timedelta(minutes=5) # 5min s.save() + @staticmethod + def empty_cache(): + cache.delete_many([ + 'SUBVENTION_COF','OVERDRAFT_DURATION', 'OVERDRAFT_AMOUNT', + ]) + class GenericTeamToken(models.Model): token = models.CharField(max_length = 50, unique = True) diff --git a/kfet/static/kfet/css/transfers_form.css b/kfet/static/kfet/css/transfers_form.css new file mode 100644 index 00000000..f222e490 --- /dev/null +++ b/kfet/static/kfet/css/transfers_form.css @@ -0,0 +1,58 @@ +.transfer_formset { + background:#FFF; +} + +.transfer_formset thead { + height:40px; + background:#c8102e; + color:#fff; + font-size:20px; + font-weight:bold; + text-align:center; +} + +.transfer_form { + height:50px; +} + +.transfer_form td { + padding:0 !important; +} + +.transfer_form input { + border:0; + border-radius:0; + + width:100%; + height:100%; + + font-family:'Roboto Mono'; + font-size:25px; + font-weight:bold; + + text-align:center; + text-transform:uppercase; +} + +.transfer_form .from_acc_data, .transfer_form .to_acc_data { + width:30%; + text-align:center; + vertical-align:middle; + font-size:20px; +} + +.transfer_form .from_acc, .transfer_form .to_acc { + width:15%; +} + +.transfer_form .from_acc { + border-left:1px solid #ddd; +} + +.transfer_form .to_acc { + border-right:1px solid #ddd; +} + +.transfer_form .amount { + width:10%; +} diff --git a/kfet/static/kfet/js/kfet.js b/kfet/static/kfet/js/kfet.js index 719fb3d8..1bdeef8e 100644 --- a/kfet/static/kfet/js/kfet.js +++ b/kfet/static/kfet/js/kfet.js @@ -9,21 +9,22 @@ $(document).ready(function() { } }); - // Retrieving csrf token - csrftoken = Cookies.get('csrftoken'); - // Appending csrf token to ajax post requests - function csrfSafeMethod(method) { - // these HTTP methods do not require CSRF protection - return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method)); - } - $.ajaxSetup({ - beforeSend: function(xhr, settings) { - if (!csrfSafeMethod(settings.type) && !this.crossDomain) { - xhr.setRequestHeader("X-CSRFToken", csrftoken); - } + if (typeof Cookies !== 'undefined') { + // Retrieving csrf token + csrftoken = Cookies.get('csrftoken'); + // Appending csrf token to ajax post requests + function csrfSafeMethod(method) { + // these HTTP methods do not require CSRF protection + return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method)); } - }); - + $.ajaxSetup({ + beforeSend: function(xhr, settings) { + if (!csrfSafeMethod(settings.type) && !this.crossDomain) { + xhr.setRequestHeader("X-CSRFToken", csrftoken); + } + } + }); + } }); function dateUTCToParis(date) { @@ -40,3 +41,68 @@ function amountToUKF(amount, is_cof=false) { var coef_cof = is_cof ? 1 + settings['subvention_cof'] / 100 : 1; return Math.round(amount * coef_cof * 10); } + +function isValidTrigramme(trigramme) { + var pattern = /^[^a-z]{3}$/; + return trigramme.match(pattern); +} + +function getErrorsHtml(data) { + var content = ''; + if ('operation_group' in data['errors']) { + content += 'Général'; + content += ''; + } + if ('missing_perms' in data['errors']) { + content += 'Permissions manquantes'; + content += ''; + } + if ('negative' in data['errors']) + content += 'Autorisation de négatif requise'; + if ('addcost' in data['errors']) { + content += ''; + } + return content; +} + +function requestAuth(data, callback, focus_next = null) { + var content = getErrorsHtml(data); + content += '', + $.confirm({ + title: 'Authentification requise', + content: content, + backgroundDismiss: true, + animation:'top', + closeAnimation:'bottom', + keyboardEnabled: true, + confirm: function() { + var password = this.$content.find('input').val(); + callback(password); + }, + onOpen: function() { + var that = this; + this.$content.find('input').on('keypress', function(e) { + if (e.keyCode == 13) + that.$confirmButton.click(); + }); + }, + onClose: function() { + if (focus_next) + this._lastFocused = focus_next; + } + }); +} + diff --git a/kfet/templates/kfet/kpsul.html b/kfet/templates/kfet/kpsul.html index 2b64c8d5..eed7fc6d 100644 --- a/kfet/templates/kfet/kpsul.html +++ b/kfet/templates/kfet/kpsul.html @@ -141,9 +141,8 @@ $(document).ready(function() { // Initializing var account_container = $('#account'); - var triInput = $('#id_trigramme') - var triPattern = /^[^a-z]{3}$/ - var account_data = {} + var triInput = $('#id_trigramme'); + var account_data = {}; var account_data_default = { 'id' : 0, 'name' : '', @@ -155,7 +154,7 @@ $(document).ready(function() { 'is_frozen' : false, 'departement': '', 'nickname' : '', - } + }; // Display data function displayAccountData() { @@ -212,7 +211,7 @@ $(document).ready(function() { function retrieveAccountData(tri) { $.ajax({ dataType: "json", - url : "{% url 'kfet.kpsul.account_data' %}", + url : "{% url 'kfet.account.read.json' %}", method : "POST", data : { trigramme: tri }, }) @@ -229,7 +228,7 @@ $(document).ready(function() { triInput.on('input', function() { var tri = triInput.val().toUpperCase(); // Checking if tri is valid to avoid sending requests - if (tri.match(triPattern)) { + if (isValidTrigramme(tri)) { retrieveAccountData(tri); } else { resetAccountData(); @@ -318,31 +317,6 @@ $(document).ready(function() { // Auth // ----- - function requestAuth(data, callback) { - var content = getErrorsHtml(data); - content += '', - $.confirm({ - title: 'Authentification requise', - content: content, - backgroundDismiss: true, - animation:'top', - closeAnimation:'bottom', - keyboardEnabled: true, - confirm: function() { - var password = this.$content.find('input').val(); - callback(password); - }, - onOpen: function() { - var that = this; - this.$content.find('input').on('keypress', function(e) { - if (e.keyCode == 13) - that.$confirmButton.click(); - }); - }, - onClose: function() { this._lastFocused = articleSelect; } - }); - } - function askComment(callback) { var comment = $('#id_comment').val(); $.confirm({ @@ -374,38 +348,7 @@ $(document).ready(function() { // ----- // Errors ajax // ----- - - function getErrorsHtml(data) { - var content = ''; - if ('operation_group' in data['errors']) { - content += 'Général'; - content += ''; - } - if ('missing_perms' in data['errors']) { - content += 'Permissions manquantes'; - content += ''; - } - if ('negative' in data['errors']) - content += 'Autorisation de négatif requise'; - if ('addcost' in data['errors']) { - content += ''; - } - return content; - } - +x function displayErrors(html) { $.alert({ title: 'Erreurs', @@ -445,7 +388,7 @@ $(document).ready(function() { var data = $xhr.responseJSON; switch ($xhr.status) { case 403: - requestAuth(data, performOperations); + requestAuth(data, performOperations, articleSelect); break; case 400: if ('need_comment' in data['errors']) { @@ -493,7 +436,7 @@ $(document).ready(function() { case 403: requestAuth(data, function(password) { cancelOperations(opes_array, password); - }); + }, triInput); break; case 400: displayErrors(getErrorsHtml(data)); @@ -1023,7 +966,7 @@ $(document).ready(function() { case 403: requestAuth(data, function(password) { sendAddcost(trigramme, amount, password); - }); + }, triInput); break; case 400: askAddcost(getErrorsHtml(data)); diff --git a/kfet/templates/kfet/transfers.html b/kfet/templates/kfet/transfers.html new file mode 100644 index 00000000..4ed58465 --- /dev/null +++ b/kfet/templates/kfet/transfers.html @@ -0,0 +1,16 @@ +{% extends 'kfet/base.html' %} + +{% block title %}Transferts{% endblock %} +{% block content-header-title %}Transferts{% endblock %} + +{% block content %} + +
+
+ {% csrf_token %} + {{ form }} + +
+
+ +{% endblock %} diff --git a/kfet/templates/kfet/transfers_create.html b/kfet/templates/kfet/transfers_create.html new file mode 100644 index 00000000..a28ab892 --- /dev/null +++ b/kfet/templates/kfet/transfers_create.html @@ -0,0 +1,122 @@ +{% extends 'kfet/base.html' %} +{% load staticfiles %} + +{% block extra_head %} + + +{% endblock %} + +{% block title %}Nouveaux transferts{% endblock %} +{% block content-header-title %}Nouveaux transferts{% endblock %} + +{% block content %} + +{% csrf_token %} + +
+ + + + + + + + + + + + {% for form in transfer_formset %} + + + + + + + + {% endfor %} + +
DeVers
+ + {{ form.from_acc }} + {{ form.amount }} + + {{ form.to_acc }} +
+
+ + + {{ transfer_formset.management_form }} + +
+
+ + + +{% endblock %} diff --git a/kfet/urls.py b/kfet/urls.py index 51e6c785..ef8f731a 100644 --- a/kfet/urls.py +++ b/kfet/urls.py @@ -117,8 +117,6 @@ urlpatterns = [ # ----- url('^k-psul/$', views.kpsul, name = 'kfet.kpsul'), - url('^k-psul/account_data$', views.kpsul_account_data, - name = 'kfet.kpsul.account_data'), url('^k-psul/checkout_data$', views.kpsul_checkout_data, name = 'kfet.kpsul.checkout_data'), url('^k-psul/perform_operations$', views.kpsul_perform_operations, @@ -136,17 +134,31 @@ urlpatterns = [ # JSON urls # ----- - url('^history.json$', views.history_json, + url(r'^history.json$', views.history_json, name = 'kfet.history.json'), + url(r'^accounts/read.json$', views.account_read_json, + name = 'kfet.account.read.json'), + # ----- # Settings urls # ----- - url('^settings/$', + url(r'^settings/$', permission_required('kfet.change_settings')(views.SettingsList.as_view()), name = 'kfet.settings'), - url('^settings/(?P\d+)/edit$', + url(r'^settings/(?P\d+)/edit$', permission_required('kfet.change_settings')(views.SettingsUpdate.as_view()), name = 'kfet.settings.update'), + + # ----- + # Transfers urls + # ----- + + url(r'^transfers/$', views.home, + name = 'kfet.transfers'), + url(r'^transfers/new$', views.transfer_create, + name = 'kfet.transfers.create'), + url(r'^transfers/perform$', views.perform_transfers, + name = 'kfet.transfers.perform'), ] diff --git a/kfet/views.py b/kfet/views.py index 1596fc44..65199277 100644 --- a/kfet/views.py +++ b/kfet/views.py @@ -600,7 +600,7 @@ def kpsul_get_settings(request): return JsonResponse(data) @permission_required('kfet.is_team') -def kpsul_account_data(request): +def account_read_json(request): trigramme = request.POST.get('trigramme', '') account = get_object_or_404(Account, trigramme=trigramme) data = { 'id': account.pk, 'name': account.name, 'email': account.email, @@ -931,13 +931,9 @@ def kpsul_cancel_operations(request): return JsonResponse(data) # Checking permissions or stop - overdraft_duration_max = Settings.OVERDRAFT_DURATION() - overdraft_amount_max = Settings.OVERDRAFT_AMOUNT() for account in to_accounts_balances: (perms, stop) = account.perms_to_perform_operation( - amount = to_accounts_balances[account], - overdraft_duration_max = overdraft_duration_max, - overdraft_amount_max = overdraft_amount_max) + amount = to_accounts_balances[account]) required_perms |= perms stop_all = stop_all or stop @@ -1125,5 +1121,83 @@ class SettingsUpdate(SuccessMessageMixin, UpdateView): form.add_error(None, 'Permission refusée') return self.form_invalid(form) # Creating + Settings.empty_cache() return super(SettingsUpdate, self).form_valid(form) +# ----- +# Transfer views +# ----- + +def transfer_create(request): + transfer_formset = TransferFormSet(queryset=Transfer.objects.none()) + return render(request, 'kfet/transfers_create.html', + { 'transfer_formset': transfer_formset }) + +def perform_transfers(request): + data = { 'errors': {}, 'transfers': [], 'transfergroup': 0 } + + # Checking transfer_formset + transfer_formset = TransferFormSet(request.POST) + if not transfer_formset.is_valid(): + return JsonResponse({ 'errors': list(transfer_formset.errors)}, status=400) + + transfers = transfer_formset.save(commit = False) + + # Initializing vars + required_perms = set() # Required perms to perform all transfers + to_accounts_balances = defaultdict(lambda:0) # For balances of accounts + + for transfer in transfers: + to_accounts_balances[transfer.from_acc] -= transfer.amount + to_accounts_balances[transfer.to_acc] += transfer.amount + + stop_all = False + + # Checking if ok on all accounts + for account in to_accounts_balances: + (perms, stop) = account.perms_to_perform_operation( + amount = to_accounts_balances[account]) + required_perms |= perms + stop_all = stop_all or stop + + if stop_all or not request.user.has_perms(required_perms): + missing_perms = get_missing_perms(required_perms, request.user) + if missing_perms: + data['errors']['missing_perms'] = missing_perms + if stop_all: + data['errors']['negative'] = True + return JsonResponse(data, status=403) + + with transaction.atomic(): + # Updating balances accounts + for account in to_accounts_balances: + Account.objects.filter(pk=account.pk).update( + balance = F('balance') + to_accounts_balances[account]) + account.refresh_from_db() + if account.balance < 0: + if hasattr(account, 'negative'): + if not account.negative.start: + account.negative.start = timezone.now() + account.negative.save() + else: + negative = AccountNegative( + account = account, start = timezone.now()) + negative.save() + elif (hasattr(account, 'negative') + and not account.negative.balance_offset): + account.negative.delete() + + # Creating transfer group + transfergroup = TransferGroup() + if required_perms: + transfergroup.valid_by = request.user.profile.account_kfet + transfergroup.save() + data['transfergroup'] = transfergroup.pk + + # Saving all transfers with group + for transfer in transfers: + transfer.group = transfergroup + transfer.save() + data['transfers'].append(transfer.pk) + + return JsonResponse(data)