forked from DGNum/gestioCOF
Ajout des annulations sur K-Psul
This commit is contained in:
parent
070752bd01
commit
2c2f82a0f7
6 changed files with 210 additions and 13 deletions
18
kfet/migrations/0025_auto_20160809_0750.py
Normal file
18
kfet/migrations/0025_auto_20160809_0750.py
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import models, migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('kfet', '0024_settings_value_duration'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='globalpermissions',
|
||||||
|
options={'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')), 'managed': False},
|
||||||
|
),
|
||||||
|
]
|
19
kfet/migrations/0026_auto_20160809_0810.py
Normal file
19
kfet/migrations/0026_auto_20160809_0810.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', '0025_auto_20160809_0750'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='settings',
|
||||||
|
name='name',
|
||||||
|
field=models.CharField(db_index=True, max_length=45, unique=True),
|
||||||
|
),
|
||||||
|
]
|
|
@ -99,8 +99,13 @@ class Account(models.Model):
|
||||||
data['is_free'] = True
|
data['is_free'] = True
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def perms_to_perform_operation(self, amount):
|
def perms_to_perform_operation(self, amount, overdraft_duration_max=None, \
|
||||||
perms = []
|
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()
|
||||||
|
perms = set()
|
||||||
stop_ope = False
|
stop_ope = False
|
||||||
# Checking is cash account
|
# Checking is cash account
|
||||||
if self.is_cash:
|
if self.is_cash:
|
||||||
|
@ -108,7 +113,7 @@ class Account(models.Model):
|
||||||
return [], False
|
return [], False
|
||||||
# Checking is frozen account
|
# Checking is frozen account
|
||||||
if self.is_frozen:
|
if self.is_frozen:
|
||||||
perms.append('kfet.override_frozen_protection')
|
perms.add('kfet.override_frozen_protection')
|
||||||
new_balance = self.balance + amount
|
new_balance = self.balance + amount
|
||||||
if new_balance < 0 and amount < 0:
|
if new_balance < 0 and amount < 0:
|
||||||
# Retrieving overdraft amount limit
|
# Retrieving overdraft amount limit
|
||||||
|
@ -116,20 +121,20 @@ class Account(models.Model):
|
||||||
and self.negative.authz_overdraft_amount is not None):
|
and self.negative.authz_overdraft_amount is not None):
|
||||||
overdraft_amount = - self.negative.authz_overdraft_amount
|
overdraft_amount = - self.negative.authz_overdraft_amount
|
||||||
else:
|
else:
|
||||||
overdraft_amount = - Settings.OVERDRAFT_AMOUNT()
|
overdraft_amount = - overdraft_amount_max
|
||||||
# Retrieving overdraft datetime limit
|
# Retrieving overdraft datetime limit
|
||||||
if (hasattr(self, 'negative')
|
if (hasattr(self, 'negative')
|
||||||
and self.negative.authz_overdraft_until is not None):
|
and self.negative.authz_overdraft_until is not None):
|
||||||
overdraft_until = self.negative.authz_overdraft_until
|
overdraft_until = self.negative.authz_overdraft_until
|
||||||
elif hasattr(self, 'negative'):
|
elif hasattr(self, 'negative'):
|
||||||
overdraft_until = \
|
overdraft_until = \
|
||||||
self.negative.start + Settings.OVERDRAFT_DURATION()
|
self.negative.start + overdraft_duration_max
|
||||||
else:
|
else:
|
||||||
overdraft_until = timezone.now() + Settings.OVERDRAFT_DURATION()
|
overdraft_until = timezone.now() + overdraft_duration_max
|
||||||
# Checking it doesn't break 1 rule
|
# Checking it doesn't break 1 rule
|
||||||
if new_balance < overdraft_amount or timezone.now() > overdraft_until:
|
if new_balance < overdraft_amount_max or timezone.now() > overdraft_until:
|
||||||
stop_ope = True
|
stop_ope = True
|
||||||
perms.append('kfet.perform_negative_operations')
|
perms.add('kfet.perform_negative_operations')
|
||||||
return perms, stop_ope
|
return perms, stop_ope
|
||||||
|
|
||||||
# Surcharge Méthode save() avec gestions de User et CofProfile
|
# Surcharge Méthode save() avec gestions de User et CofProfile
|
||||||
|
@ -462,12 +467,14 @@ class GlobalPermissions(models.Model):
|
||||||
('perform_negative_operations',
|
('perform_negative_operations',
|
||||||
'Enregistrer des commandes en négatif'),
|
'Enregistrer des commandes en négatif'),
|
||||||
('override_frozen_protection', "Forcer le gel d'un compte"),
|
('override_frozen_protection', "Forcer le gel d'un compte"),
|
||||||
|
('cancel_old_operations', 'Annuler des commandes non récentes'),
|
||||||
)
|
)
|
||||||
|
|
||||||
class Settings(models.Model):
|
class Settings(models.Model):
|
||||||
name = models.CharField(
|
name = models.CharField(
|
||||||
max_length = 45,
|
max_length = 45,
|
||||||
unique = True)
|
unique = True,
|
||||||
|
db_index = True)
|
||||||
value_decimal = models.DecimalField(
|
value_decimal = models.DecimalField(
|
||||||
max_digits = 6, decimal_places = 2,
|
max_digits = 6, decimal_places = 2,
|
||||||
blank = True, null = True, default = None)
|
blank = True, null = True, default = None)
|
||||||
|
@ -515,3 +522,9 @@ class Settings(models.Model):
|
||||||
return Settings.setting_inst("OVERDRAFT_AMOUNT").value_decimal
|
return Settings.setting_inst("OVERDRAFT_AMOUNT").value_decimal
|
||||||
except Settings.DoesNotExist:
|
except Settings.DoesNotExist:
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
def CANCEL_DURATION():
|
||||||
|
try:
|
||||||
|
return Settings.setting_inst("CANCEL_DURATION").value_duration
|
||||||
|
except Settings.DoesNotExist:
|
||||||
|
return timedelta()
|
||||||
|
|
|
@ -39,6 +39,13 @@
|
||||||
|
|
||||||
<button type="button" id="perform_operations">Valider</button>
|
<button type="button" id="perform_operations">Valider</button>
|
||||||
|
|
||||||
|
<form id="cancel_form">
|
||||||
|
Opé annul 1:<input type="text" name="operation"><br>
|
||||||
|
Opé annul 2:<input type="text" name="operation">
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<button type="button" id="cancel_operations">Annuler</button>
|
||||||
|
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
$(document).ready(function() {
|
$(document).ready(function() {
|
||||||
// -----
|
// -----
|
||||||
|
@ -188,7 +195,7 @@ $(document).ready(function() {
|
||||||
var operations = $('#operation_formset');
|
var operations = $('#operation_formset');
|
||||||
|
|
||||||
function performOperations() {
|
function performOperations() {
|
||||||
data = operationGroup.serialize() + '&' + operations.serialize();
|
var data = operationGroup.serialize() + '&' + operations.serialize();
|
||||||
$.ajax({
|
$.ajax({
|
||||||
dataType: "json",
|
dataType: "json",
|
||||||
url : "{% url 'kfet.kpsul.perform_operations' %}",
|
url : "{% url 'kfet.kpsul.perform_operations' %}",
|
||||||
|
@ -208,6 +215,33 @@ $(document).ready(function() {
|
||||||
performButton.on('click', function() {
|
performButton.on('click', function() {
|
||||||
performOperations();
|
performOperations();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Cancel operations
|
||||||
|
|
||||||
|
var cancelButton = $('#cancel_operations');
|
||||||
|
var cancelForm = $('#cancel_form');
|
||||||
|
|
||||||
|
function cancelOperations() {
|
||||||
|
var data = cancelForm.serialize();
|
||||||
|
$.ajax({
|
||||||
|
dataType: "json",
|
||||||
|
url : "{% url 'kfet.kpsul.cancel_operations' %}",
|
||||||
|
method : "POST",
|
||||||
|
data : data,
|
||||||
|
})
|
||||||
|
.done(function(data) {
|
||||||
|
console.log(data);
|
||||||
|
})
|
||||||
|
.always(function($xhr) {
|
||||||
|
var data = $xhr.responseJSON;
|
||||||
|
console.log(data);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Event listeners
|
||||||
|
cancelButton.on('click', function() {
|
||||||
|
cancelOperations();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -90,4 +90,6 @@ urlpatterns = [
|
||||||
name = 'kfet.kpsul.checkout_data'),
|
name = 'kfet.kpsul.checkout_data'),
|
||||||
url('^k-psul/perform_operations$', views.kpsul_perform_operations,
|
url('^k-psul/perform_operations$', views.kpsul_perform_operations,
|
||||||
name = 'kfet.kpsul.perform_operations'),
|
name = 'kfet.kpsul.perform_operations'),
|
||||||
|
url('^k-psul/cancel_operations$', views.kpsul_cancel_operations,
|
||||||
|
name = 'kfet.kpsul.cancel_operations'),
|
||||||
]
|
]
|
||||||
|
|
117
kfet/views.py
117
kfet/views.py
|
@ -10,6 +10,7 @@ from django.contrib.auth.models import User, Permission
|
||||||
from django.http import HttpResponse, JsonResponse, Http404
|
from django.http import HttpResponse, JsonResponse, Http404
|
||||||
from django.forms import modelformset_factory
|
from django.forms import modelformset_factory
|
||||||
from django.db import IntegrityError, transaction
|
from django.db import IntegrityError, transaction
|
||||||
|
from django.db.models import F
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from gestioncof.models import CofProfile, Clipper
|
from gestioncof.models import CofProfile, Clipper
|
||||||
from kfet.models import Account, Checkout, Article, Settings, AccountNegative
|
from kfet.models import Account, Checkout, Article, Settings, AccountNegative
|
||||||
|
@ -385,6 +386,15 @@ def kpsul_checkout_data(request):
|
||||||
'valid_from': checkout.valid_from, 'valid_to': checkout.valid_to }
|
'valid_from': checkout.valid_from, 'valid_to': checkout.valid_to }
|
||||||
return JsonResponse(data)
|
return JsonResponse(data)
|
||||||
|
|
||||||
|
def get_missing_perms(required_perms, user):
|
||||||
|
missing_perms_codenames = [ (perm.split('.'))[1]
|
||||||
|
for perm in required_perms if not user.has_perm(perm)]
|
||||||
|
missing_perms = list(
|
||||||
|
Permission.objects
|
||||||
|
.filter(codename__in=missing_perms_codenames)
|
||||||
|
.values_list('name', flat=True))
|
||||||
|
return missing_perms
|
||||||
|
|
||||||
@permission_required('kfet.is_team')
|
@permission_required('kfet.is_team')
|
||||||
def kpsul_perform_operations(request):
|
def kpsul_perform_operations(request):
|
||||||
# Initializing response data
|
# Initializing response data
|
||||||
|
@ -423,7 +433,7 @@ def kpsul_perform_operations(request):
|
||||||
addcost_for = Settings.ADDCOST_FOR()
|
addcost_for = Settings.ADDCOST_FOR()
|
||||||
|
|
||||||
# Initializing vars
|
# Initializing vars
|
||||||
required_perms = []
|
required_perms = set()
|
||||||
cof_grant_divisor = 1 + cof_grant / 100
|
cof_grant_divisor = 1 + cof_grant / 100
|
||||||
is_addcost = (addcost_for and addcost_amount
|
is_addcost = (addcost_for and addcost_amount
|
||||||
and addcost_for != operationgroup.on_acc)
|
and addcost_for != operationgroup.on_acc)
|
||||||
|
@ -467,7 +477,7 @@ def kpsul_perform_operations(request):
|
||||||
operationgroup.amount += operation.amount
|
operationgroup.amount += operation.amount
|
||||||
# 4
|
# 4
|
||||||
if operation.type == Operation.DEPOSIT:
|
if operation.type == Operation.DEPOSIT:
|
||||||
required_perms.append('kfet.perform_deposit')
|
required_perms.add('kfet.perform_deposit')
|
||||||
|
|
||||||
# Starting transaction to ensure data consistency
|
# Starting transaction to ensure data consistency
|
||||||
# Using select_for_update where it is critical
|
# Using select_for_update where it is critical
|
||||||
|
@ -478,7 +488,7 @@ def kpsul_perform_operations(request):
|
||||||
# Adding required permissions to perform operation group
|
# Adding required permissions to perform operation group
|
||||||
(opegroup_perms, stop_ope) = on_acc.perms_to_perform_operation(
|
(opegroup_perms, stop_ope) = on_acc.perms_to_perform_operation(
|
||||||
amount = operationgroup.amount)
|
amount = operationgroup.amount)
|
||||||
required_perms += opegroup_perms
|
required_perms |= opegroup_perms
|
||||||
|
|
||||||
# Checking authenticated user has all perms
|
# Checking authenticated user has all perms
|
||||||
if stop_ope or not request.user.has_perms(required_perms):
|
if stop_ope or not request.user.has_perms(required_perms):
|
||||||
|
@ -544,3 +554,104 @@ def kpsul_perform_operations(request):
|
||||||
return JsonResponse(data, status=403)
|
return JsonResponse(data, status=403)
|
||||||
|
|
||||||
return JsonResponse(data)
|
return JsonResponse(data)
|
||||||
|
|
||||||
|
@permission_required('kfet.is_team')
|
||||||
|
def kpsul_cancel_operations(request):
|
||||||
|
# Pour la réponse
|
||||||
|
data = { 'canceled': [], 'warnings': {}, 'errors': {}}
|
||||||
|
|
||||||
|
# Checking if BAD REQUEST (opes_pk not int or not existing)
|
||||||
|
try:
|
||||||
|
# Set pour virer les doublons
|
||||||
|
opes_post = set(map(int, filter(None, request.POST.getlist('operation', []))))
|
||||||
|
except ValueError:
|
||||||
|
return JsonResponse(data, status=400)
|
||||||
|
opes_all = Operation.objects.select_related('group', 'group__on_acc', 'group__on_acc__negative').filter(pk__in=opes_post)
|
||||||
|
opes_pk = [ ope.pk for ope in opes_all ]
|
||||||
|
opes_notexisting = [ ope for ope in opes_post if ope not in opes_pk ]
|
||||||
|
if opes_notexisting:
|
||||||
|
data['errors']['opes_notexisting'] = opes_notexisting
|
||||||
|
return JsonResponse(data, status=400)
|
||||||
|
|
||||||
|
opes_already_canceled = [] # Déjà annulée
|
||||||
|
opes = [] # Pas déjà annulée
|
||||||
|
required_perms = set()
|
||||||
|
stop_all = False
|
||||||
|
cancel_duration = Settings.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
|
||||||
|
to_articles_stocks = defaultdict(lambda:0) # ------ sur les stocks d'articles
|
||||||
|
for ope in opes_all:
|
||||||
|
if ope.canceled_at:
|
||||||
|
# Opération déjà annulée, va pour un warning en Response
|
||||||
|
opes_already_canceled.append(ope.pk)
|
||||||
|
else:
|
||||||
|
opes.append(ope.pk)
|
||||||
|
# Si opé il y a plus de CANCEL_DURATION, permission requise
|
||||||
|
if ope.group.at + cancel_duration < timezone.now():
|
||||||
|
required_perms.add('kfet.cancel_old_operations')
|
||||||
|
|
||||||
|
# Calcul de toutes modifs à faire en cas de validation
|
||||||
|
|
||||||
|
# Pour les balances de comptes
|
||||||
|
if not ope.group.on_acc.is_cash:
|
||||||
|
to_accounts_balances[ope.group.on_acc] -= ope.amount
|
||||||
|
if ope.addcost_for and ope.addcost_amount:
|
||||||
|
to_accounts_balances[ope.addcost_for] -= ope.addcost_amount
|
||||||
|
# Pour les groupes d'opés
|
||||||
|
to_groups_amounts[ope.group] -= ope.amount
|
||||||
|
# Pour les balances de caisses
|
||||||
|
if ope.type == Operation.PURCHASE:
|
||||||
|
if ope.group.on_acc.is_cash:
|
||||||
|
to_checkouts_balances[ope.group.on_acc] -= - ope.amount
|
||||||
|
else:
|
||||||
|
to_checkouts_balances[ope.group.on_acc] -= ope.amount
|
||||||
|
# Pour les stocks d'articles
|
||||||
|
if ope.article and ope.article_nb:
|
||||||
|
to_articles_stocks[ope.article] += ope.article_nb
|
||||||
|
|
||||||
|
if not opes:
|
||||||
|
data['warnings']['already_canceled'] = opes_already_canceled
|
||||||
|
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)
|
||||||
|
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():
|
||||||
|
canceled_by = required_perms and request.user.profile.account_kfet or None
|
||||||
|
(Operation.objects.filter(pk__in=opes)
|
||||||
|
.update(canceled_by=canceled_by, canceled_at=timezone.now()))
|
||||||
|
for account in to_accounts_balances:
|
||||||
|
Account.objects.filter(pk=account.pk).update(
|
||||||
|
balance = F('balance') + to_accounts_balances[account])
|
||||||
|
for checkout in to_checkouts_balances:
|
||||||
|
Checkout.objects.filter(pk=checkout.pk).update(
|
||||||
|
balance = F('balance') + to_checkouts_balances[checkout])
|
||||||
|
for group in to_groups_amounts:
|
||||||
|
OperationGroup.objects.filter(pk=group.pk).update(
|
||||||
|
amount = F('amount') + to_groups_amounts[group])
|
||||||
|
for article in to_articles_stocks:
|
||||||
|
Article.objects.filter(pk=article.pk).update(
|
||||||
|
stock = F('stock') + to_articles_stocks[article])
|
||||||
|
|
||||||
|
data['canceled'] = opes
|
||||||
|
if opes_already_canceled:
|
||||||
|
data['warnings']['already_canceled'] = opes_already_canceled
|
||||||
|
return JsonResponse(data)
|
||||||
|
|
Loading…
Reference in a new issue