Gestion des commandes K-Psul donnant un négatif
* Settings - New: OVERDRAFT_AMOUNT Découvert autorisé par défaut - New: OVERDRAFT_DURATION Durée maximum d'un découvert par défaut * K-Psul : Gestion des commandes aboutissant à un négatif - Si une commande aboutit à un nouveau solde négatif, demande la permission 'kfet.perform_negative_operations' - Si le total de la commande est négatif, vérifie que ni la contrainte de temps de découvert, ni celle de montant maximum n'est outrepassée. Si ce n'est pas le cas, la commande ne peut être enregistrée jusqu'à définir des "règles de négatif" pour le compte concerné. La durée maximum d'un découvert est celle dans AccountNegative si elle y est définie pour le compte concerné, sinon celle par défaut (Settings.OVERDRAFT_DURATION). Il en est de même pour le découvert maximum autorisé. Attention: le découvert doit être exprimé sous forme de valeur positive aussi bien dans AccountNegative que pour Settings.OVERDRAFT_AMOUNT. - Si les permissions nécessaires sont présentes, qu'il n'y a pas de blocage et que le compte n'a pas encore d'entrée dans AccountNegative, création d'une entrée avec start=now() - Si la balance d'un compte est positive après une commande, supprime l'entrée dans AccountNegative associée au compte si le "décalage de zéro" (donné par balance_offset) est nul. Sinon cela veut dire que le compte n'est pas réellement en positif. * Modèles - Fix: Account.save() fonctionne dans le cas où data est vide - Modif: AccountNegative - Valeurs par défaut, NULL...
This commit is contained in:
parent
897986fec8
commit
510e16eecf
7 changed files with 183 additions and 22 deletions
20
kfet/migrations/0020_auto_20160808_0450.py
Normal file
20
kfet/migrations/0020_auto_20160808_0450.py
Normal file
|
@ -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),
|
||||
),
|
||||
]
|
19
kfet/migrations/0021_auto_20160808_0506.py
Normal file
19
kfet/migrations/0021_auto_20160808_0506.py
Normal file
|
@ -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),
|
||||
),
|
||||
]
|
24
kfet/migrations/0022_auto_20160808_0512.py
Normal file
24
kfet/migrations/0022_auto_20160808_0512.py
Normal file
|
@ -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),
|
||||
),
|
||||
]
|
24
kfet/migrations/0023_auto_20160808_0535.py
Normal file
24
kfet/migrations/0023_auto_20160808_0535.py
Normal file
|
@ -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),
|
||||
),
|
||||
]
|
19
kfet/migrations/0024_settings_value_duration.py
Normal file
19
kfet/migrations/0024_settings_value_duration.py
Normal file
|
@ -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),
|
||||
),
|
||||
]
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in a new issue