diff --git a/kfet/migrations/0020_auto_20160808_0450.py b/kfet/migrations/0020_auto_20160808_0450.py new file mode 100644 index 00000000..2ecc18ee --- /dev/null +++ b/kfet/migrations/0020_auto_20160808_0450.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations +import datetime + + +class Migration(migrations.Migration): + + dependencies = [ + ('kfet', '0019_auto_20160808_0343'), + ] + + operations = [ + migrations.AlterField( + model_name='accountnegative', + name='start', + field=models.DateTimeField(default=datetime.datetime.now, blank=True, null=True), + ), + ] diff --git a/kfet/migrations/0021_auto_20160808_0506.py b/kfet/migrations/0021_auto_20160808_0506.py new file mode 100644 index 00000000..61a7ef65 --- /dev/null +++ b/kfet/migrations/0021_auto_20160808_0506.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('kfet', '0020_auto_20160808_0450'), + ] + + operations = [ + migrations.AlterField( + model_name='accountnegative', + name='start', + field=models.DateTimeField(default=None, blank=True, null=True), + ), + ] diff --git a/kfet/migrations/0022_auto_20160808_0512.py b/kfet/migrations/0022_auto_20160808_0512.py new file mode 100644 index 00000000..ba5de03e --- /dev/null +++ b/kfet/migrations/0022_auto_20160808_0512.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('kfet', '0021_auto_20160808_0506'), + ] + + operations = [ + migrations.AlterField( + model_name='accountnegative', + name='authorized_overdraft', + field=models.DecimalField(blank=True, decimal_places=2, null=True, default=None, max_digits=6), + ), + migrations.AlterField( + model_name='accountnegative', + name='balance_offset', + field=models.DecimalField(blank=True, decimal_places=2, null=True, default=None, max_digits=6), + ), + ] diff --git a/kfet/migrations/0023_auto_20160808_0535.py b/kfet/migrations/0023_auto_20160808_0535.py new file mode 100644 index 00000000..7e4d7051 --- /dev/null +++ b/kfet/migrations/0023_auto_20160808_0535.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('kfet', '0022_auto_20160808_0512'), + ] + + operations = [ + migrations.RenameField( + model_name='accountnegative', + old_name='authorized_overdraft', + new_name='authz_overdraft_amount', + ), + migrations.AddField( + model_name='accountnegative', + name='authz_overdraft_until', + field=models.DateTimeField(null=True, default=None, blank=True), + ), + ] diff --git a/kfet/migrations/0024_settings_value_duration.py b/kfet/migrations/0024_settings_value_duration.py new file mode 100644 index 00000000..1f90b1c9 --- /dev/null +++ b/kfet/migrations/0024_settings_value_duration.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('kfet', '0023_auto_20160808_0535'), + ] + + operations = [ + migrations.AddField( + model_name='settings', + name='value_duration', + field=models.DurationField(null=True, default=None, blank=True), + ), + ] diff --git a/kfet/models.py b/kfet/models.py index 5f740d27..10564aa9 100644 --- a/kfet/models.py +++ b/kfet/models.py @@ -4,14 +4,15 @@ from django.core.exceptions import PermissionDenied, ValidationError from django.core.validators import RegexValidator from gestioncof.models import CofProfile from django.utils.six.moves import reduce -import datetime +from django.utils import timezone +from datetime import date, timedelta import re def choices_length(choices): return reduce(lambda m, choice: max(m, len(choice[0])), choices, 0) def default_promo(): - now = datetime.date.today() + now = date.today() return now.month <= 8 and now.year-1 or now.year class Account(models.Model): @@ -28,7 +29,7 @@ class Account(models.Model): default = 0) is_frozen = models.BooleanField(default = False) # Optional - PROMO_CHOICES = [(r,r) for r in range(1980, datetime.date.today().year+1)] + PROMO_CHOICES = [(r,r) for r in range(1980, date.today().year+1)] promo = models.IntegerField( choices = PROMO_CHOICES, blank = True, null = True, default = default_promo()) @@ -95,12 +96,32 @@ class Account(models.Model): def perms_to_perform_operation(self, amount): perms = [] + stop_ope = False + # Checking is frozen account if self.is_frozen: perms.append('kfet.override_frozen_protection') new_balance = self.balance + amount - if new_balance < 0: + if new_balance < 0 and amount < 0: + # Retrieving overdraft amount limit + if (hasattr(self, 'negative') + and self.negative.authz_overdraft_amount is not None): + overdraft_amount = - self.negative.authz_overdraft_amount + else: + overdraft_amount = - Settings.OVERDRAFT_AMOUNT() + # Retrieving overdraft datetime limit + if (hasattr(self, 'negative') + and self.negative.authz_overdraft_until is not None): + overdraft_until = self.negative.authz_overdraft_until + elif hasattr(self, 'negative'): + overdraft_until = \ + self.negative.start + Settings.OVERDRAFT_DURATION() + else: + overdraft_until = timezone.now() + Settings.OVERDRAFT_DURATION() + # Checking it doesn't break 1 rule + if new_balance < overdraft_amount or timezone.now() > overdraft_until: + stop_ope = True perms.append('kfet.perform_negative_operations') - return perms + return perms, stop_ope # Surcharge Méthode save() avec gestions de User et CofProfile # Args: @@ -109,7 +130,7 @@ class Account(models.Model): # - Enregistre User, CofProfile à partir de "data" # - Enregistre Account def save(self, data = {}, *args, **kwargs): - if self.pk: + if self.pk and data: # Account update # Updating User with data @@ -122,7 +143,7 @@ class Account(models.Model): cof = self.cofprofile cof.departement = data.get("departement", cof.departement) cof.save() - else: + elif data: # New account # Checking if user has already an account @@ -151,7 +172,8 @@ class Account(models.Model): if "departement" in data: cof.departement = data['departement'] cof.save() - self.cofprofile = cof + if data: + self.cofprofile = cof super(Account, self).save(*args, **kwargs) # Surcharge de delete @@ -168,13 +190,16 @@ class AccountNegative(models.Model): account = models.OneToOneField( Account, on_delete = models.PROTECT, related_name = "negative") - start = models.DateTimeField(default = datetime.datetime.now) + start = models.DateTimeField( + blank = True, null = True, default = None) balance_offset = models.DecimalField( max_digits = 6, decimal_places = 2, - default = 0) - authorized_overdraft = models.DecimalField( + blank = True, null = True, default = None) + authz_overdraft_amount = models.DecimalField( max_digits = 6, decimal_places = 2, - default = 0) + blank = True, null = True, default = None) + authz_overdraft_until = models.DateTimeField( + blank = True, null = True, default = None) comment = models.CharField(max_length = 255, blank = True) class Checkout(models.Model): @@ -440,6 +465,8 @@ class Settings(models.Model): 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): @@ -465,3 +492,17 @@ class Settings(models.Model): return Settings.setting_inst("ADDCOST_FOR").value_account except Settings.DoesNotExist: return None; + + @staticmethod + def OVERDRAFT_DURATION(): + try: + return Settings.setting_inst("OVERDRAFT_DURATION").value_duration + except Settings.DoesNotExist: + return timedelta() + + @staticmethod + def OVERDRAFT_AMOUNT(): + try: + return Settings.setting_inst("OVERDRAFT_AMOUNT").value_decimal + except Settings.DoesNotExist: + return 0 diff --git a/kfet/views.py b/kfet/views.py index a5d330fb..ca1fbb68 100644 --- a/kfet/views.py +++ b/kfet/views.py @@ -10,8 +10,9 @@ from django.contrib.auth.models import User, Permission from django.http import HttpResponse, JsonResponse, Http404 from django.forms import modelformset_factory from django.db import IntegrityError, transaction +from django.utils import timezone from gestioncof.models import CofProfile, Clipper -from kfet.models import Account, Checkout, Article, Settings +from kfet.models import Account, Checkout, Article, Settings, AccountNegative from kfet.forms import * from collections import defaultdict @@ -455,15 +456,15 @@ def kpsul_perform_operations(request): # Using select_for_update where it is critical try: with transaction.atomic(): - on_acc = operationgroup.on_acc + on_acc = operationgroup.on_acc on_acc = Account.objects.select_for_update().get(pk=on_acc.pk) # Adding required permissions to perform operation group - opegroup_perms = on_acc.perms_to_perform_operation( + (opegroup_perms, stop_ope) = on_acc.perms_to_perform_operation( amount = operationgroup.amount) required_perms += opegroup_perms # Checking authenticated user has all perms - if not request.user.has_perms(required_perms): + if stop_ope or not request.user.has_perms(required_perms): raise PermissionDenied # If 1 perm is required, saving who perform the operations @@ -473,9 +474,22 @@ def kpsul_perform_operations(request): # Filling cof status for statistics operationgroup.is_cof = on_acc.is_cof - # Saving account's balance + # Saving account's balance and adding to Negative if not in on_acc.balance += operationgroup.amount + if on_acc.balance < 0: + if hasattr(on_acc, 'negative'): + if not on_acc.negative.start: + on_acc.negative.start = timezone.now() + on_acc.negative.save() + else: + negative = AccountNegative( + account = on_acc, start = timezone.now()) + negative.save() + elif (hasattr(on_acc, 'negative') + and not on_acc.negative.balance_offset): + on_acc.negative.delete() on_acc.save() + # Saving addcost_for with new balance if there is one if is_addcost: addcost_for.balance += addcost_total @@ -494,16 +508,16 @@ def kpsul_perform_operations(request): operation.article.save() data['operations'].append(operation.pk) except PermissionDenied: - # Sending BAD_REQUEST with missing perms + # Sending BAD_REQUEST with missing perms or url to manage negative missing_perms = \ [ Permission.objects.get(codename=codename).name for codename in ( (perm.split('.'))[1] for perm in required_perms if not request.user.has_perm(perm) )] - data['errors'].append({'missing_perms': missing_perms }) + if missing_perms: + data['errors'].append({'missing_perms': missing_perms }) + if stop_ope: + data['errors'].append({'negative': 'url to manage negative'}) return JsonResponse(data, status=403) - except IntegrityError: - data['errors'].append('DB error') - return JsonResponse(data, status=500) return JsonResponse(data)