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
|
||||
return data
|
||||
|
||||
def perms_to_perform_operation(self, amount):
|
||||
perms = []
|
||||
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()
|
||||
perms = set()
|
||||
stop_ope = False
|
||||
# Checking is cash account
|
||||
if self.is_cash:
|
||||
|
@ -108,7 +113,7 @@ class Account(models.Model):
|
|||
return [], False
|
||||
# Checking is frozen account
|
||||
if self.is_frozen:
|
||||
perms.append('kfet.override_frozen_protection')
|
||||
perms.add('kfet.override_frozen_protection')
|
||||
new_balance = self.balance + amount
|
||||
if new_balance < 0 and amount < 0:
|
||||
# Retrieving overdraft amount limit
|
||||
|
@ -116,20 +121,20 @@ class Account(models.Model):
|
|||
and self.negative.authz_overdraft_amount is not None):
|
||||
overdraft_amount = - self.negative.authz_overdraft_amount
|
||||
else:
|
||||
overdraft_amount = - Settings.OVERDRAFT_AMOUNT()
|
||||
overdraft_amount = - overdraft_amount_max
|
||||
# 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()
|
||||
self.negative.start + overdraft_duration_max
|
||||
else:
|
||||
overdraft_until = timezone.now() + Settings.OVERDRAFT_DURATION()
|
||||
overdraft_until = timezone.now() + overdraft_duration_max
|
||||
# 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
|
||||
perms.append('kfet.perform_negative_operations')
|
||||
perms.add('kfet.perform_negative_operations')
|
||||
return perms, stop_ope
|
||||
|
||||
# Surcharge Méthode save() avec gestions de User et CofProfile
|
||||
|
@ -462,12 +467,14 @@ class GlobalPermissions(models.Model):
|
|||
('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'),
|
||||
)
|
||||
|
||||
class Settings(models.Model):
|
||||
name = models.CharField(
|
||||
max_length = 45,
|
||||
unique = True)
|
||||
unique = True,
|
||||
db_index = True)
|
||||
value_decimal = models.DecimalField(
|
||||
max_digits = 6, decimal_places = 2,
|
||||
blank = True, null = True, default = None)
|
||||
|
@ -515,3 +522,9 @@ class Settings(models.Model):
|
|||
return Settings.setting_inst("OVERDRAFT_AMOUNT").value_decimal
|
||||
except Settings.DoesNotExist:
|
||||
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>
|
||||
|
||||
<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">
|
||||
$(document).ready(function() {
|
||||
// -----
|
||||
|
@ -188,7 +195,7 @@ $(document).ready(function() {
|
|||
var operations = $('#operation_formset');
|
||||
|
||||
function performOperations() {
|
||||
data = operationGroup.serialize() + '&' + operations.serialize();
|
||||
var data = operationGroup.serialize() + '&' + operations.serialize();
|
||||
$.ajax({
|
||||
dataType: "json",
|
||||
url : "{% url 'kfet.kpsul.perform_operations' %}",
|
||||
|
@ -208,6 +215,33 @@ $(document).ready(function() {
|
|||
performButton.on('click', function() {
|
||||
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>
|
||||
|
||||
|
|
|
@ -90,4 +90,6 @@ urlpatterns = [
|
|||
name = 'kfet.kpsul.checkout_data'),
|
||||
url('^k-psul/perform_operations$', views.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.forms import modelformset_factory
|
||||
from django.db import IntegrityError, transaction
|
||||
from django.db.models import F
|
||||
from django.utils import timezone
|
||||
from gestioncof.models import CofProfile, Clipper
|
||||
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 }
|
||||
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')
|
||||
def kpsul_perform_operations(request):
|
||||
# Initializing response data
|
||||
|
@ -423,7 +433,7 @@ def kpsul_perform_operations(request):
|
|||
addcost_for = Settings.ADDCOST_FOR()
|
||||
|
||||
# Initializing vars
|
||||
required_perms = []
|
||||
required_perms = set()
|
||||
cof_grant_divisor = 1 + cof_grant / 100
|
||||
is_addcost = (addcost_for and addcost_amount
|
||||
and addcost_for != operationgroup.on_acc)
|
||||
|
@ -467,7 +477,7 @@ def kpsul_perform_operations(request):
|
|||
operationgroup.amount += operation.amount
|
||||
# 4
|
||||
if operation.type == Operation.DEPOSIT:
|
||||
required_perms.append('kfet.perform_deposit')
|
||||
required_perms.add('kfet.perform_deposit')
|
||||
|
||||
# Starting transaction to ensure data consistency
|
||||
# Using select_for_update where it is critical
|
||||
|
@ -478,7 +488,7 @@ def kpsul_perform_operations(request):
|
|||
# Adding required permissions to perform operation group
|
||||
(opegroup_perms, stop_ope) = on_acc.perms_to_perform_operation(
|
||||
amount = operationgroup.amount)
|
||||
required_perms += opegroup_perms
|
||||
required_perms |= opegroup_perms
|
||||
|
||||
# Checking authenticated user has all 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)
|
||||
|
||||
@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