Merge branch 'Aufinal/remove_negative' into 'master'
Fonctionnement du négatif + erreurs de K-Psul Closes #279 See merge request klub-dev-ens/gestioCOF!494
This commit is contained in:
commit
a5c822e7f7
13 changed files with 450 additions and 491 deletions
|
@ -14,7 +14,6 @@ from djconfig.forms import ConfigForm
|
||||||
from gestioncof.models import CofProfile
|
from gestioncof.models import CofProfile
|
||||||
from kfet.models import (
|
from kfet.models import (
|
||||||
Account,
|
Account,
|
||||||
AccountNegative,
|
|
||||||
Article,
|
Article,
|
||||||
ArticleCategory,
|
ArticleCategory,
|
||||||
Checkout,
|
Checkout,
|
||||||
|
@ -158,17 +157,6 @@ class UserInfoForm(UserForm):
|
||||||
fields = ["first_name", "last_name"]
|
fields = ["first_name", "last_name"]
|
||||||
|
|
||||||
|
|
||||||
class AccountNegativeForm(forms.ModelForm):
|
|
||||||
class Meta:
|
|
||||||
model = AccountNegative
|
|
||||||
fields = [
|
|
||||||
"authz_overdraft_amount",
|
|
||||||
"authz_overdraft_until",
|
|
||||||
"comment",
|
|
||||||
]
|
|
||||||
widgets = {"authz_overdraft_until": DateTimeWidget()}
|
|
||||||
|
|
||||||
|
|
||||||
# -----
|
# -----
|
||||||
# Checkout forms
|
# Checkout forms
|
||||||
# -----
|
# -----
|
||||||
|
@ -549,7 +537,7 @@ class TransferForm(forms.ModelForm):
|
||||||
def clean_amount(self):
|
def clean_amount(self):
|
||||||
amount = self.cleaned_data["amount"]
|
amount = self.cleaned_data["amount"]
|
||||||
if amount <= 0:
|
if amount <= 0:
|
||||||
raise forms.ValidationError("Montant invalide")
|
raise forms.ValidationError("Le montant d'un transfert doit être positif")
|
||||||
return amount
|
return amount
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|
30
kfet/migrations/0078_negative_end.py
Normal file
30
kfet/migrations/0078_negative_end.py
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
# Generated by Django 2.2.17 on 2021-02-28 01:00
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("kfet", "0077_delete_frozen_permission"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name="accountnegative",
|
||||||
|
name="authz_overdraft_amount",
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name="accountnegative",
|
||||||
|
name="authz_overdraft_until",
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name="accountnegative",
|
||||||
|
name="comment",
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="accountnegative",
|
||||||
|
name="end",
|
||||||
|
field=models.DateTimeField(blank=True, default=None, null=True),
|
||||||
|
),
|
||||||
|
]
|
|
@ -170,41 +170,23 @@ class Account(models.Model):
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def perms_to_perform_operation(self, amount):
|
def perms_to_perform_operation(self, amount):
|
||||||
overdraft_duration_max = kfet_config.overdraft_duration
|
|
||||||
overdraft_amount_max = kfet_config.overdraft_amount
|
|
||||||
perms = set()
|
perms = set()
|
||||||
stop_ope = False
|
|
||||||
# Checking is cash account
|
# Checking is cash account
|
||||||
if self.is_cash:
|
if self.is_cash:
|
||||||
# Yes, so no perms and no stop
|
# Yes, so no perms and no stop
|
||||||
return set(), False
|
return set(), False
|
||||||
|
|
||||||
if self.need_comment:
|
if self.need_comment:
|
||||||
perms.add("kfet.perform_commented_operations")
|
perms.add("kfet.perform_commented_operations")
|
||||||
|
|
||||||
new_balance = self.balance + amount
|
new_balance = self.balance + amount
|
||||||
|
if new_balance < -kfet_config.overdraft_amount:
|
||||||
|
return set(), True
|
||||||
|
|
||||||
if new_balance < 0 and amount < 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 = -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 + overdraft_duration_max
|
|
||||||
else:
|
|
||||||
overdraft_until = timezone.now() + overdraft_duration_max
|
|
||||||
# Checking it doesn't break 1 rule
|
|
||||||
if new_balance < overdraft_amount or timezone.now() > overdraft_until:
|
|
||||||
stop_ope = True
|
|
||||||
perms.add("kfet.perform_negative_operations")
|
perms.add("kfet.perform_negative_operations")
|
||||||
return perms, stop_ope
|
|
||||||
|
return perms, False
|
||||||
|
|
||||||
# Surcharge Méthode save() avec gestions de User et CofProfile
|
# Surcharge Méthode save() avec gestions de User et CofProfile
|
||||||
# Args:
|
# Args:
|
||||||
|
@ -267,16 +249,25 @@ class Account(models.Model):
|
||||||
|
|
||||||
def update_negative(self):
|
def update_negative(self):
|
||||||
if self.balance < 0:
|
if self.balance < 0:
|
||||||
if hasattr(self, "negative") and not self.negative.start:
|
# On met à jour le début de négatif seulement si la fin du négatif précédent
|
||||||
|
# est "vieille"
|
||||||
|
if (
|
||||||
|
hasattr(self, "negative")
|
||||||
|
and self.negative.end is not None
|
||||||
|
and timezone.now() > self.negative.end + kfet_config.cancel_duration
|
||||||
|
):
|
||||||
self.negative.start = timezone.now()
|
self.negative.start = timezone.now()
|
||||||
|
self.negative.end = None
|
||||||
self.negative.save()
|
self.negative.save()
|
||||||
elif not hasattr(self, "negative"):
|
elif not hasattr(self, "negative"):
|
||||||
self.negative = AccountNegative.objects.create(
|
self.negative = AccountNegative.objects.create(
|
||||||
account=self, start=timezone.now()
|
account=self, start=timezone.now()
|
||||||
)
|
)
|
||||||
elif hasattr(self, "negative"):
|
elif hasattr(self, "negative"):
|
||||||
# self.balance >= 0
|
if self.negative.end is None:
|
||||||
# TODO: méchanisme pour éviter de contourner le délai de négatif ?
|
self.negative.end = timezone.now()
|
||||||
|
elif timezone.now() > self.negative.end + kfet_config.cancel_duration:
|
||||||
|
# Idem: on supprime le négatif après une légère période
|
||||||
self.negative.delete()
|
self.negative.delete()
|
||||||
|
|
||||||
class UserHasAccount(Exception):
|
class UserHasAccount(Exception):
|
||||||
|
@ -302,26 +293,11 @@ class AccountNegative(models.Model):
|
||||||
Account, on_delete=models.CASCADE, related_name="negative"
|
Account, on_delete=models.CASCADE, related_name="negative"
|
||||||
)
|
)
|
||||||
start = models.DateTimeField(blank=True, null=True, default=None)
|
start = models.DateTimeField(blank=True, null=True, default=None)
|
||||||
authz_overdraft_amount = models.DecimalField(
|
end = models.DateTimeField(blank=True, null=True, default=None)
|
||||||
"négatif autorisé",
|
|
||||||
max_digits=6,
|
|
||||||
decimal_places=2,
|
|
||||||
blank=True,
|
|
||||||
null=True,
|
|
||||||
default=None,
|
|
||||||
)
|
|
||||||
authz_overdraft_until = models.DateTimeField(
|
|
||||||
"expiration du négatif", blank=True, null=True, default=None
|
|
||||||
)
|
|
||||||
comment = models.CharField("commentaire", max_length=255, blank=True)
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
permissions = (("view_negs", "Voir la liste des négatifs"),)
|
permissions = (("view_negs", "Voir la liste des négatifs"),)
|
||||||
|
|
||||||
@property
|
|
||||||
def until_default(self):
|
|
||||||
return self.start + kfet_config.overdraft_duration
|
|
||||||
|
|
||||||
|
|
||||||
class CheckoutQuerySet(models.QuerySet):
|
class CheckoutQuerySet(models.QuerySet):
|
||||||
def is_valid(self):
|
def is_valid(self):
|
||||||
|
|
|
@ -257,11 +257,11 @@ function KHistory(options = {}) {
|
||||||
switch ($xhr.status) {
|
switch ($xhr.status) {
|
||||||
case 403:
|
case 403:
|
||||||
requestAuth(data, function (password) {
|
requestAuth(data, function (password) {
|
||||||
this.cancel(opes, password);
|
that._cancel(type, opes, password);
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case 400:
|
case 400:
|
||||||
displayErrors(getErrorsHtml(data));
|
displayErrors(data);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
window.lock = 0;
|
window.lock = 0;
|
||||||
|
|
|
@ -106,62 +106,25 @@ function amountToUKF(amount, is_cof = false, account = false) {
|
||||||
return rounding(amount * coef_cof * 10);
|
return rounding(amount * coef_cof * 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getErrorsHtml(data) {
|
function getErrorsHtml(data, is_error = true) {
|
||||||
var content = '';
|
if (is_error) {
|
||||||
if (!data)
|
data = data.map(error => error.message)
|
||||||
return "L'utilisateur n'est pas dans l'équipe";
|
|
||||||
if ('operation_group' in data['errors']) {
|
|
||||||
content += 'Général';
|
|
||||||
content += '<ul>';
|
|
||||||
if (data['errors']['operation_group'].indexOf('on_acc') != -1)
|
|
||||||
content += '<li>Pas de compte sélectionné</li>';
|
|
||||||
if (data['errors']['operation_group'].indexOf('checkout') != -1)
|
|
||||||
content += '<li>Pas de caisse sélectionnée</li>';
|
|
||||||
content += '</ul>';
|
|
||||||
}
|
}
|
||||||
if ('missing_perms' in data['errors']) {
|
|
||||||
content += 'Permissions manquantes';
|
var content = is_error ? "Général :" : "Permissions manquantes :";
|
||||||
content += '<ul>';
|
content += "<ul>";
|
||||||
for (var i = 0; i < data['errors']['missing_perms'].length; i++)
|
for (const message of data) {
|
||||||
content += '<li>' + data['errors']['missing_perms'][i] + '</li>';
|
content += '<li>' + message + '</li>';
|
||||||
content += '</ul>';
|
|
||||||
}
|
|
||||||
if ('negative' in data['errors']) {
|
|
||||||
if (window.location.pathname.startsWith('/gestion/')) {
|
|
||||||
var url_base = '/gestion/k-fet/accounts/';
|
|
||||||
} else {
|
|
||||||
var url_base = '/k-fet/accounts/';
|
|
||||||
}
|
|
||||||
for (var i = 0; i < data['errors']['negative'].length; i++) {
|
|
||||||
content += '<a class="btn btn-primary" href="' + url_base + data['errors']['negative'][i] + '/edit" target="_blank" style="width:100%">Autorisation de négatif requise pour ' + data['errors']['negative'][i] + '</a>';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ('addcost' in data['errors']) {
|
|
||||||
content += '<ul>';
|
|
||||||
if (data['errors']['addcost'].indexOf('__all__') != -1)
|
|
||||||
content += '<li>Compte invalide</li>';
|
|
||||||
if (data['errors']['addcost'].indexOf('amount') != -1)
|
|
||||||
content += '<li>Montant invalide</li>';
|
|
||||||
content += '</ul>';
|
|
||||||
}
|
|
||||||
if ('account' in data['errors']) {
|
|
||||||
content += 'Général';
|
|
||||||
content += '<ul>';
|
|
||||||
content += '<li>Opération invalide sur le compte ' + data['errors']['account'] + '</li>';
|
|
||||||
content += '</ul>';
|
|
||||||
}
|
|
||||||
if ('frozen' in data['errors']) {
|
|
||||||
content += 'Général';
|
|
||||||
content += '<ul>';
|
|
||||||
content += '<li>Les comptes suivants sont gelés : ' + data['errors']['frozen'].join(", ") + '</li>';
|
|
||||||
content += '</ul>';
|
|
||||||
}
|
}
|
||||||
|
content += "</ul>";
|
||||||
|
|
||||||
return content;
|
return content;
|
||||||
}
|
}
|
||||||
|
|
||||||
function requestAuth(data, callback, focus_next = null) {
|
function requestAuth(data, callback, focus_next = null) {
|
||||||
var content = getErrorsHtml(data);
|
var content = getErrorsHtml(data["missing_perms"], is_error = false);
|
||||||
content += '<div class="capslock"><span class="glyphicon glyphicon-lock"></span><input type="password" name="password" autofocus><div>',
|
content += '<div class="capslock"><span class="glyphicon glyphicon-lock"></span><input type="password" name="password" autofocus><div>';
|
||||||
|
|
||||||
$.confirm({
|
$.confirm({
|
||||||
title: 'Authentification requise',
|
title: 'Authentification requise',
|
||||||
content: content,
|
content: content,
|
||||||
|
@ -212,10 +175,11 @@ function requestAuth(data, callback, focus_next = null) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function displayErrors(html) {
|
function displayErrors(data) {
|
||||||
|
const content = getErrorsHtml(data["errors"], is_error = true);
|
||||||
$.alert({
|
$.alert({
|
||||||
title: 'Erreurs',
|
title: 'Erreurs',
|
||||||
content: html,
|
content: content,
|
||||||
backgroundDismiss: true,
|
backgroundDismiss: true,
|
||||||
animation: 'top',
|
animation: 'top',
|
||||||
closeAnimation: 'bottom',
|
closeAnimation: 'bottom',
|
||||||
|
|
|
@ -10,26 +10,12 @@
|
||||||
{{ negatives|length }}
|
{{ negatives|length }}
|
||||||
<span class="sub">compte{{ negatives|length|pluralize }} en négatif</span>
|
<span class="sub">compte{{ negatives|length|pluralize }} en négatif</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="text">
|
<div class="heading">
|
||||||
<b>Total:</b> {{ negatives_sum|floatformat:2 }}€
|
{{ negatives_sum|floatformat:2 }}€
|
||||||
</div>
|
<span class="sub">de négatif total</span>
|
||||||
<div class="text">
|
|
||||||
<b>Plafond par défaut</b>
|
|
||||||
<ul class="list-unstyled">
|
|
||||||
<li>Montant: {{ kfet_config.overdraft_amount }}€</li>
|
|
||||||
<li>Pendant: {{ kfet_config.overdraft_duration }}</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
</div>
|
||||||
</aside>
|
</aside>
|
||||||
|
|
||||||
{% if perms.kfet.change_settings %}
|
|
||||||
<div class="buttons">
|
|
||||||
<div class="full">
|
|
||||||
<button type="button" class="btn btn-primary" href="{% url 'kfet.settings' %}">Modifier les valeurs par défaut</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block main %}
|
{% block main %}
|
||||||
|
@ -43,8 +29,6 @@
|
||||||
<td>Nom</td>
|
<td>Nom</td>
|
||||||
<td class="text-right">Balance</td>
|
<td class="text-right">Balance</td>
|
||||||
<td data-sorter="shortDate">Début</td>
|
<td data-sorter="shortDate">Début</td>
|
||||||
<td>Découvert autorisé</td>
|
|
||||||
<td data-sorter="shortDate">Jusqu'au</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
|
@ -60,10 +44,6 @@
|
||||||
<td title="{{ neg.start }}">
|
<td title="{{ neg.start }}">
|
||||||
{{ neg.start|date:'d/m/Y H:i'}}
|
{{ neg.start|date:'d/m/Y H:i'}}
|
||||||
</td>
|
</td>
|
||||||
<td>{{ neg.authz_overdraft_amount|default_if_none:'' }}</td>
|
|
||||||
<td title="{{ neg.authz_overdraft_until }}">
|
|
||||||
{{ neg.authz_overdraft_until|date:'d/m/Y H:i' }}
|
|
||||||
</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|
|
@ -35,23 +35,9 @@ Modification de mes informations
|
||||||
{% include 'kfet/form_snippet.html' with form=frozen_form %}
|
{% include 'kfet/form_snippet.html' with form=frozen_form %}
|
||||||
{% include 'kfet/form_snippet.html' with form=group_form %}
|
{% include 'kfet/form_snippet.html' with form=group_form %}
|
||||||
{% include 'kfet/form_snippet.html' with form=pwd_form %}
|
{% include 'kfet/form_snippet.html' with form=pwd_form %}
|
||||||
{% include 'kfet/form_snippet.html' with form=negative_form %}
|
|
||||||
{% if perms.kfet.is_team %}
|
|
||||||
{% include 'kfet/form_authentication_snippet.html' %}
|
{% include 'kfet/form_authentication_snippet.html' %}
|
||||||
{% endif %}
|
|
||||||
{% include 'kfet/form_submit_snippet.html' with value="Mettre à jour" %}
|
{% include 'kfet/form_submit_snippet.html' with value="Mettre à jour" %}
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<script type="text/javascript">
|
|
||||||
|
|
||||||
$(document).ready(function () {
|
|
||||||
$('#id_authz_overdraft_until').datetimepicker({
|
|
||||||
format: 'YYYY-MM-DD HH:mm',
|
|
||||||
stepping: 5,
|
|
||||||
locale: 'fr',
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
|
@ -376,10 +376,10 @@ $(document).ready(function() {
|
||||||
requestAuth(data, performOperations, articleSelect);
|
requestAuth(data, performOperations, articleSelect);
|
||||||
break;
|
break;
|
||||||
case 400:
|
case 400:
|
||||||
if ('need_comment' in data['errors']) {
|
if ('need_comment' in data) {
|
||||||
askComment(performOperations);
|
askComment(performOperations);
|
||||||
} else {
|
} else {
|
||||||
displayErrors(getErrorsHtml(data));
|
displayErrors(data);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -1074,7 +1074,7 @@ $(document).ready(function() {
|
||||||
}, triInput);
|
}, triInput);
|
||||||
break;
|
break;
|
||||||
case 400:
|
case 400:
|
||||||
askAddcost(getErrorsHtml(data));
|
askAddcost(getErrorsHtml(data["errors"], is_error=true));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -54,12 +54,6 @@
|
||||||
{% if account.negative.start %}
|
{% if account.negative.start %}
|
||||||
<li>Depuis le <b>{{ account.negative.start|date:"d/m/Y à H:i" }}</b></li>
|
<li>Depuis le <b>{{ account.negative.start|date:"d/m/Y à H:i" }}</b></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<li>
|
|
||||||
Plafond :
|
|
||||||
<b>{{ account.negative.authz_overdraft_amount|default:kfet_config.overdraft_amount }} €</b>
|
|
||||||
jusqu'au
|
|
||||||
<b>{{ account.negative.authz_overdraft_until|default:account.negative.until_default|date:"d/m/Y à H:i" }}</b>
|
|
||||||
</li>
|
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
|
@ -72,7 +72,7 @@ $(document).ready(function () {
|
||||||
var $next = $form.next('.transfer_form').find('.from_acc input');
|
var $next = $form.next('.transfer_form').find('.from_acc input');
|
||||||
}
|
}
|
||||||
var $input_id = $input.next('input');
|
var $input_id = $input.next('input');
|
||||||
if (isValidTrigramme(trigramme)) {
|
if (trigramme.is_valid_trigramme()) {
|
||||||
getAccountData(trigramme, function(data) {
|
getAccountData(trigramme, function(data) {
|
||||||
$input_id.val(data.id);
|
$input_id.val(data.id);
|
||||||
$data.text(data.name);
|
$data.text(data.name);
|
||||||
|
@ -122,7 +122,7 @@ $(document).ready(function () {
|
||||||
requestAuth(data, performTransfers);
|
requestAuth(data, performTransfers);
|
||||||
break;
|
break;
|
||||||
case 400:
|
case 400:
|
||||||
displayErrors(getErrorsHtml(data));
|
displayErrors(data);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
import datetime
|
from datetime import datetime, timedelta, timezone as tz
|
||||||
|
from decimal import Decimal
|
||||||
|
from unittest import mock
|
||||||
|
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
|
||||||
from kfet.models import Account, Checkout
|
from kfet.models import Account, AccountNegative, Checkout
|
||||||
|
|
||||||
from .utils import create_user
|
from .utils import create_user
|
||||||
|
|
||||||
|
@ -28,6 +30,56 @@ class AccountTests(TestCase):
|
||||||
with self.assertRaises(Account.DoesNotExist):
|
with self.assertRaises(Account.DoesNotExist):
|
||||||
Account.objects.get_by_password("bernard")
|
Account.objects.get_by_password("bernard")
|
||||||
|
|
||||||
|
@mock.patch("django.utils.timezone.now")
|
||||||
|
def test_negative_creation(self, mock_now):
|
||||||
|
now = datetime(2005, 7, 15, tzinfo=tz.utc)
|
||||||
|
mock_now.return_value = now
|
||||||
|
self.account.balance = Decimal(-10)
|
||||||
|
self.account.update_negative()
|
||||||
|
|
||||||
|
self.assertTrue(hasattr(self.account, "negative"))
|
||||||
|
self.assertEqual(self.account.negative.start, now)
|
||||||
|
|
||||||
|
@mock.patch("django.utils.timezone.now")
|
||||||
|
def test_negative_no_reset(self, mock_now):
|
||||||
|
now = datetime(2005, 7, 15, tzinfo=tz.utc)
|
||||||
|
mock_now.return_value = now
|
||||||
|
|
||||||
|
self.account.balance = Decimal(-10)
|
||||||
|
AccountNegative.objects.create(
|
||||||
|
account=self.account, start=now - timedelta(minutes=3)
|
||||||
|
)
|
||||||
|
self.account.refresh_from_db()
|
||||||
|
|
||||||
|
self.account.balance = Decimal(5)
|
||||||
|
self.account.update_negative()
|
||||||
|
self.assertTrue(hasattr(self.account, "negative"))
|
||||||
|
|
||||||
|
self.account.balance = Decimal(-10)
|
||||||
|
self.account.update_negative()
|
||||||
|
self.assertEqual(self.account.negative.start, now - timedelta(minutes=3))
|
||||||
|
|
||||||
|
@mock.patch("django.utils.timezone.now")
|
||||||
|
def test_negative_eventually_resets(self, mock_now):
|
||||||
|
now = datetime(2005, 7, 15, tzinfo=tz.utc)
|
||||||
|
mock_now.return_value = now
|
||||||
|
|
||||||
|
self.account.balance = Decimal(-10)
|
||||||
|
AccountNegative.objects.create(
|
||||||
|
account=self.account, start=now - timedelta(minutes=20)
|
||||||
|
)
|
||||||
|
self.account.refresh_from_db()
|
||||||
|
self.account.balance = Decimal(5)
|
||||||
|
|
||||||
|
mock_now.return_value = now - timedelta(minutes=10)
|
||||||
|
self.account.update_negative()
|
||||||
|
|
||||||
|
mock_now.return_value = now
|
||||||
|
self.account.update_negative()
|
||||||
|
self.account.refresh_from_db()
|
||||||
|
|
||||||
|
self.assertFalse(hasattr(self.account, "negative"))
|
||||||
|
|
||||||
|
|
||||||
class CheckoutTests(TestCase):
|
class CheckoutTests(TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
@ -39,7 +91,7 @@ class CheckoutTests(TestCase):
|
||||||
self.c = Checkout(
|
self.c = Checkout(
|
||||||
created_by=self.u_acc,
|
created_by=self.u_acc,
|
||||||
valid_from=self.now,
|
valid_from=self.now,
|
||||||
valid_to=self.now + datetime.timedelta(days=1),
|
valid_to=self.now + timedelta(days=1),
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_initial_statement(self):
|
def test_initial_statement(self):
|
||||||
|
|
|
@ -15,7 +15,6 @@ from ..auth.utils import hash_password
|
||||||
from ..config import kfet_config
|
from ..config import kfet_config
|
||||||
from ..models import (
|
from ..models import (
|
||||||
Account,
|
Account,
|
||||||
AccountNegative,
|
|
||||||
Article,
|
Article,
|
||||||
ArticleCategory,
|
ArticleCategory,
|
||||||
Checkout,
|
Checkout,
|
||||||
|
@ -1856,7 +1855,10 @@ class KPsulPerformOperationsViewTests(ViewTestCaseMixin, TestCase):
|
||||||
|
|
||||||
self.assertEqual(resp.status_code, 400)
|
self.assertEqual(resp.status_code, 400)
|
||||||
json_data = json.loads(resp.content.decode("utf-8"))
|
json_data = json.loads(resp.content.decode("utf-8"))
|
||||||
self.assertEqual(json_data["errors"]["operation_group"], ["on_acc"])
|
self.assertCountEqual(
|
||||||
|
[e["code"] for e in json_data["errors"]],
|
||||||
|
["invalid_on_acc", "invalid_formset"],
|
||||||
|
)
|
||||||
|
|
||||||
def test_group_on_acc_expects_comment(self):
|
def test_group_on_acc_expects_comment(self):
|
||||||
user_add_perms(self.users["team"], ["kfet.perform_commented_operations"])
|
user_add_perms(self.users["team"], ["kfet.perform_commented_operations"])
|
||||||
|
@ -1899,7 +1901,7 @@ class KPsulPerformOperationsViewTests(ViewTestCaseMixin, TestCase):
|
||||||
|
|
||||||
self.assertEqual(resp.status_code, 400)
|
self.assertEqual(resp.status_code, 400)
|
||||||
json_data = json.loads(resp.content.decode("utf-8"))
|
json_data = json.loads(resp.content.decode("utf-8"))
|
||||||
self.assertEqual(json_data["errors"]["need_comment"], True)
|
self.assertEqual(json_data["need_comment"], True)
|
||||||
|
|
||||||
def test_invalid_group_on_acc_needs_comment_requires_perm(self):
|
def test_invalid_group_on_acc_needs_comment_requires_perm(self):
|
||||||
self.account.trigramme = "#13"
|
self.account.trigramme = "#13"
|
||||||
|
@ -1922,8 +1924,8 @@ class KPsulPerformOperationsViewTests(ViewTestCaseMixin, TestCase):
|
||||||
self.assertEqual(resp.status_code, 403)
|
self.assertEqual(resp.status_code, 403)
|
||||||
json_data = json.loads(resp.content.decode("utf-8"))
|
json_data = json.loads(resp.content.decode("utf-8"))
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
json_data["errors"]["missing_perms"],
|
json_data["missing_perms"],
|
||||||
["[kfet] Enregistrer des commandes avec commentaires"],
|
["Enregistrer des commandes avec commentaires"],
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_error_on_acc_frozen(self):
|
def test_error_on_acc_frozen(self):
|
||||||
|
@ -1945,7 +1947,7 @@ class KPsulPerformOperationsViewTests(ViewTestCaseMixin, TestCase):
|
||||||
|
|
||||||
self.assertEqual(resp.status_code, 400)
|
self.assertEqual(resp.status_code, 400)
|
||||||
json_data = json.loads(resp.content.decode("utf-8"))
|
json_data = json.loads(resp.content.decode("utf-8"))
|
||||||
self.assertEqual(json_data["errors"]["frozen"], [self.account.trigramme])
|
self.assertEqual([e["code"] for e in json_data["errors"]], ["frozen_acc"])
|
||||||
|
|
||||||
def test_invalid_group_checkout(self):
|
def test_invalid_group_checkout(self):
|
||||||
self.checkout.valid_from -= timedelta(days=300)
|
self.checkout.valid_from -= timedelta(days=300)
|
||||||
|
@ -1957,7 +1959,10 @@ class KPsulPerformOperationsViewTests(ViewTestCaseMixin, TestCase):
|
||||||
|
|
||||||
self.assertEqual(resp.status_code, 400)
|
self.assertEqual(resp.status_code, 400)
|
||||||
json_data = json.loads(resp.content.decode("utf-8"))
|
json_data = json.loads(resp.content.decode("utf-8"))
|
||||||
self.assertEqual(json_data["errors"]["operation_group"], ["checkout"])
|
self.assertCountEqual(
|
||||||
|
[e["code"] for e in json_data["errors"]],
|
||||||
|
["invalid_checkout", "invalid_formset"],
|
||||||
|
)
|
||||||
|
|
||||||
def test_invalid_group_expects_one_operation(self):
|
def test_invalid_group_expects_one_operation(self):
|
||||||
data = dict(self.base_post_data)
|
data = dict(self.base_post_data)
|
||||||
|
@ -1965,7 +1970,10 @@ class KPsulPerformOperationsViewTests(ViewTestCaseMixin, TestCase):
|
||||||
|
|
||||||
self.assertEqual(resp.status_code, 400)
|
self.assertEqual(resp.status_code, 400)
|
||||||
json_data = json.loads(resp.content.decode("utf-8"))
|
json_data = json.loads(resp.content.decode("utf-8"))
|
||||||
self.assertEqual(json_data["errors"]["operations"], [])
|
self.assertCountEqual(
|
||||||
|
[e["code"] for e in json_data["errors"]],
|
||||||
|
["invalid_formset"],
|
||||||
|
)
|
||||||
|
|
||||||
def test_purchase_with_user_is_nof_cof(self):
|
def test_purchase_with_user_is_nof_cof(self):
|
||||||
self.account.cofprofile.is_cof = False
|
self.account.cofprofile.is_cof = False
|
||||||
|
@ -2023,12 +2031,7 @@ class KPsulPerformOperationsViewTests(ViewTestCaseMixin, TestCase):
|
||||||
# Check response content
|
# Check response content
|
||||||
self.assertDictEqual(
|
self.assertDictEqual(
|
||||||
json_data,
|
json_data,
|
||||||
{
|
{"errors": []},
|
||||||
"operationgroup": operation_group.pk,
|
|
||||||
"operations": [operation.pk],
|
|
||||||
"warnings": {},
|
|
||||||
"errors": {},
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Check object updates
|
# Check object updates
|
||||||
|
@ -2179,9 +2182,9 @@ class KPsulPerformOperationsViewTests(ViewTestCaseMixin, TestCase):
|
||||||
|
|
||||||
self.assertEqual(resp.status_code, 400)
|
self.assertEqual(resp.status_code, 400)
|
||||||
json_data = json.loads(resp.content.decode("utf-8"))
|
json_data = json.loads(resp.content.decode("utf-8"))
|
||||||
self.assertEqual(
|
self.assertCountEqual(
|
||||||
json_data["errors"]["operations"],
|
[e["code"] for e in json_data["errors"]],
|
||||||
[{"__all__": ["Un achat nécessite un article et une quantité"]}],
|
["invalid_formset"],
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_invalid_purchase_expects_article_nb(self):
|
def test_invalid_purchase_expects_article_nb(self):
|
||||||
|
@ -2199,9 +2202,9 @@ class KPsulPerformOperationsViewTests(ViewTestCaseMixin, TestCase):
|
||||||
|
|
||||||
self.assertEqual(resp.status_code, 400)
|
self.assertEqual(resp.status_code, 400)
|
||||||
json_data = json.loads(resp.content.decode("utf-8"))
|
json_data = json.loads(resp.content.decode("utf-8"))
|
||||||
self.assertEqual(
|
self.assertCountEqual(
|
||||||
json_data["errors"]["operations"],
|
[e["code"] for e in json_data["errors"]],
|
||||||
[{"__all__": ["Un achat nécessite un article et une quantité"]}],
|
["invalid_formset"],
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_invalid_purchase_expects_article_nb_greater_than_1(self):
|
def test_invalid_purchase_expects_article_nb_greater_than_1(self):
|
||||||
|
@ -2219,16 +2222,9 @@ class KPsulPerformOperationsViewTests(ViewTestCaseMixin, TestCase):
|
||||||
|
|
||||||
self.assertEqual(resp.status_code, 400)
|
self.assertEqual(resp.status_code, 400)
|
||||||
json_data = json.loads(resp.content.decode("utf-8"))
|
json_data = json.loads(resp.content.decode("utf-8"))
|
||||||
self.assertEqual(
|
self.assertCountEqual(
|
||||||
json_data["errors"]["operations"],
|
[e["code"] for e in json_data["errors"]],
|
||||||
[
|
["invalid_formset"],
|
||||||
{
|
|
||||||
"__all__": ["Un achat nécessite un article et une quantité"],
|
|
||||||
"article_nb": [
|
|
||||||
"Assurez-vous que cette valeur est supérieure ou " "égale à 1."
|
|
||||||
],
|
|
||||||
}
|
|
||||||
],
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_invalid_operation_not_purchase_with_cash(self):
|
def test_invalid_operation_not_purchase_with_cash(self):
|
||||||
|
@ -2247,7 +2243,10 @@ class KPsulPerformOperationsViewTests(ViewTestCaseMixin, TestCase):
|
||||||
|
|
||||||
self.assertEqual(resp.status_code, 400)
|
self.assertEqual(resp.status_code, 400)
|
||||||
json_data = json.loads(resp.content.decode("utf-8"))
|
json_data = json.loads(resp.content.decode("utf-8"))
|
||||||
self.assertEqual(json_data["errors"]["account"], "LIQ")
|
self.assertCountEqual(
|
||||||
|
[e["code"] for e in json_data["errors"]],
|
||||||
|
["invalid_liq"],
|
||||||
|
)
|
||||||
|
|
||||||
def test_deposit(self):
|
def test_deposit(self):
|
||||||
user_add_perms(self.users["team"], ["kfet.perform_deposit"])
|
user_add_perms(self.users["team"], ["kfet.perform_deposit"])
|
||||||
|
@ -2300,12 +2299,7 @@ class KPsulPerformOperationsViewTests(ViewTestCaseMixin, TestCase):
|
||||||
|
|
||||||
self.assertDictEqual(
|
self.assertDictEqual(
|
||||||
json_data,
|
json_data,
|
||||||
{
|
{"errors": []},
|
||||||
"operationgroup": operation_group.pk,
|
|
||||||
"operations": [operation.pk],
|
|
||||||
"warnings": {},
|
|
||||||
"errors": {},
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
|
|
||||||
self.account.refresh_from_db()
|
self.account.refresh_from_db()
|
||||||
|
@ -2364,8 +2358,9 @@ class KPsulPerformOperationsViewTests(ViewTestCaseMixin, TestCase):
|
||||||
|
|
||||||
self.assertEqual(resp.status_code, 400)
|
self.assertEqual(resp.status_code, 400)
|
||||||
json_data = json.loads(resp.content.decode("utf-8"))
|
json_data = json.loads(resp.content.decode("utf-8"))
|
||||||
self.assertEqual(
|
self.assertCountEqual(
|
||||||
json_data["errors"]["operations"], [{"__all__": ["Bad request"]}]
|
[e["code"] for e in json_data["errors"]],
|
||||||
|
["invalid_formset"],
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_invalid_deposit_too_many_params(self):
|
def test_invalid_deposit_too_many_params(self):
|
||||||
|
@ -2383,8 +2378,9 @@ class KPsulPerformOperationsViewTests(ViewTestCaseMixin, TestCase):
|
||||||
|
|
||||||
self.assertEqual(resp.status_code, 400)
|
self.assertEqual(resp.status_code, 400)
|
||||||
json_data = json.loads(resp.content.decode("utf-8"))
|
json_data = json.loads(resp.content.decode("utf-8"))
|
||||||
self.assertEqual(
|
self.assertCountEqual(
|
||||||
json_data["errors"]["operations"], [{"__all__": ["Bad request"]}]
|
[e["code"] for e in json_data["errors"]],
|
||||||
|
["invalid_formset"],
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_invalid_deposit_expects_positive_amount(self):
|
def test_invalid_deposit_expects_positive_amount(self):
|
||||||
|
@ -2402,8 +2398,9 @@ class KPsulPerformOperationsViewTests(ViewTestCaseMixin, TestCase):
|
||||||
|
|
||||||
self.assertEqual(resp.status_code, 400)
|
self.assertEqual(resp.status_code, 400)
|
||||||
json_data = json.loads(resp.content.decode("utf-8"))
|
json_data = json.loads(resp.content.decode("utf-8"))
|
||||||
self.assertEqual(
|
self.assertCountEqual(
|
||||||
json_data["errors"]["operations"], [{"__all__": ["Charge non positive"]}]
|
[e["code"] for e in json_data["errors"]],
|
||||||
|
["invalid_formset"],
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_invalid_deposit_requires_perm(self):
|
def test_invalid_deposit_requires_perm(self):
|
||||||
|
@ -2421,9 +2418,7 @@ class KPsulPerformOperationsViewTests(ViewTestCaseMixin, TestCase):
|
||||||
|
|
||||||
self.assertEqual(resp.status_code, 403)
|
self.assertEqual(resp.status_code, 403)
|
||||||
json_data = json.loads(resp.content.decode("utf-8"))
|
json_data = json.loads(resp.content.decode("utf-8"))
|
||||||
self.assertEqual(
|
self.assertEqual(json_data["missing_perms"], ["Effectuer une charge"])
|
||||||
json_data["errors"]["missing_perms"], ["[kfet] Effectuer une charge"]
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_withdraw(self):
|
def test_withdraw(self):
|
||||||
data = dict(
|
data = dict(
|
||||||
|
@ -2475,12 +2470,7 @@ class KPsulPerformOperationsViewTests(ViewTestCaseMixin, TestCase):
|
||||||
|
|
||||||
self.assertDictEqual(
|
self.assertDictEqual(
|
||||||
json_data,
|
json_data,
|
||||||
{
|
{"errors": []},
|
||||||
"operationgroup": operation_group.pk,
|
|
||||||
"operations": [operation.pk],
|
|
||||||
"warnings": {},
|
|
||||||
"errors": {},
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
|
|
||||||
self.account.refresh_from_db()
|
self.account.refresh_from_db()
|
||||||
|
@ -2539,8 +2529,9 @@ class KPsulPerformOperationsViewTests(ViewTestCaseMixin, TestCase):
|
||||||
|
|
||||||
self.assertEqual(resp.status_code, 400)
|
self.assertEqual(resp.status_code, 400)
|
||||||
json_data = json.loads(resp.content.decode("utf-8"))
|
json_data = json.loads(resp.content.decode("utf-8"))
|
||||||
self.assertEqual(
|
self.assertCountEqual(
|
||||||
json_data["errors"]["operations"], [{"__all__": ["Bad request"]}]
|
[e["code"] for e in json_data["errors"]],
|
||||||
|
["invalid_formset"],
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_invalid_withdraw_too_many_params(self):
|
def test_invalid_withdraw_too_many_params(self):
|
||||||
|
@ -2558,8 +2549,9 @@ class KPsulPerformOperationsViewTests(ViewTestCaseMixin, TestCase):
|
||||||
|
|
||||||
self.assertEqual(resp.status_code, 400)
|
self.assertEqual(resp.status_code, 400)
|
||||||
json_data = json.loads(resp.content.decode("utf-8"))
|
json_data = json.loads(resp.content.decode("utf-8"))
|
||||||
self.assertEqual(
|
self.assertCountEqual(
|
||||||
json_data["errors"]["operations"], [{"__all__": ["Bad request"]}]
|
[e["code"] for e in json_data["errors"]],
|
||||||
|
["invalid_formset"],
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_invalid_withdraw_expects_negative_amount(self):
|
def test_invalid_withdraw_expects_negative_amount(self):
|
||||||
|
@ -2577,8 +2569,9 @@ class KPsulPerformOperationsViewTests(ViewTestCaseMixin, TestCase):
|
||||||
|
|
||||||
self.assertEqual(resp.status_code, 400)
|
self.assertEqual(resp.status_code, 400)
|
||||||
json_data = json.loads(resp.content.decode("utf-8"))
|
json_data = json.loads(resp.content.decode("utf-8"))
|
||||||
self.assertEqual(
|
self.assertCountEqual(
|
||||||
json_data["errors"]["operations"], [{"__all__": ["Retrait non négatif"]}]
|
[e["code"] for e in json_data["errors"]],
|
||||||
|
["invalid_formset"],
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_edit(self):
|
def test_edit(self):
|
||||||
|
@ -2634,12 +2627,7 @@ class KPsulPerformOperationsViewTests(ViewTestCaseMixin, TestCase):
|
||||||
|
|
||||||
self.assertDictEqual(
|
self.assertDictEqual(
|
||||||
json_data,
|
json_data,
|
||||||
{
|
{"errors": []},
|
||||||
"operationgroup": operation_group.pk,
|
|
||||||
"operations": [operation.pk],
|
|
||||||
"warnings": {},
|
|
||||||
"errors": {},
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
|
|
||||||
self.account.refresh_from_db()
|
self.account.refresh_from_db()
|
||||||
|
@ -2700,8 +2688,8 @@ class KPsulPerformOperationsViewTests(ViewTestCaseMixin, TestCase):
|
||||||
self.assertEqual(resp.status_code, 403)
|
self.assertEqual(resp.status_code, 403)
|
||||||
json_data = json.loads(resp.content.decode("utf-8"))
|
json_data = json.loads(resp.content.decode("utf-8"))
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
json_data["errors"]["missing_perms"],
|
json_data["missing_perms"],
|
||||||
["[kfet] Modifier la balance d'un compte"],
|
["Modifier la balance d'un compte"],
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_invalid_edit_expects_comment(self):
|
def test_invalid_edit_expects_comment(self):
|
||||||
|
@ -2721,7 +2709,7 @@ class KPsulPerformOperationsViewTests(ViewTestCaseMixin, TestCase):
|
||||||
|
|
||||||
self.assertEqual(resp.status_code, 400)
|
self.assertEqual(resp.status_code, 400)
|
||||||
json_data = json.loads(resp.content.decode("utf-8"))
|
json_data = json.loads(resp.content.decode("utf-8"))
|
||||||
self.assertEqual(json_data["errors"]["need_comment"], True)
|
self.assertEqual(json_data["need_comment"], True)
|
||||||
|
|
||||||
def _setup_addcost(self):
|
def _setup_addcost(self):
|
||||||
self.register_user("addcost", create_user("addcost", "ADD"))
|
self.register_user("addcost", create_user("addcost", "ADD"))
|
||||||
|
@ -3008,62 +2996,10 @@ class KPsulPerformOperationsViewTests(ViewTestCaseMixin, TestCase):
|
||||||
self.assertEqual(resp.status_code, 403)
|
self.assertEqual(resp.status_code, 403)
|
||||||
json_data = json.loads(resp.content.decode("utf-8"))
|
json_data = json.loads(resp.content.decode("utf-8"))
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
json_data["errors"],
|
json_data["missing_perms"],
|
||||||
{"missing_perms": ["[kfet] Enregistrer des commandes en négatif"]},
|
["Enregistrer des commandes en négatif"],
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_invalid_negative_exceeds_allowed_duration_from_config(self):
|
|
||||||
user_add_perms(self.users["team"], ["kfet.perform_negative_operations"])
|
|
||||||
kfet_config.set(overdraft_duration=timedelta(days=5))
|
|
||||||
self.account.balance = Decimal("1.00")
|
|
||||||
self.account.save()
|
|
||||||
self.account.negative = AccountNegative.objects.create(
|
|
||||||
account=self.account, start=timezone.now() - timedelta(days=5, minutes=1)
|
|
||||||
)
|
|
||||||
|
|
||||||
data = dict(
|
|
||||||
self.base_post_data,
|
|
||||||
**{
|
|
||||||
"form-TOTAL_FORMS": "1",
|
|
||||||
"form-0-type": "purchase",
|
|
||||||
"form-0-amount": "",
|
|
||||||
"form-0-article": str(self.article.pk),
|
|
||||||
"form-0-article_nb": "2",
|
|
||||||
}
|
|
||||||
)
|
|
||||||
resp = self.client.post(self.url, data)
|
|
||||||
|
|
||||||
self.assertEqual(resp.status_code, 403)
|
|
||||||
json_data = json.loads(resp.content.decode("utf-8"))
|
|
||||||
self.assertEqual(json_data["errors"], {"negative": ["000"]})
|
|
||||||
|
|
||||||
def test_invalid_negative_exceeds_allowed_duration_from_account(self):
|
|
||||||
user_add_perms(self.users["team"], ["kfet.perform_negative_operations"])
|
|
||||||
kfet_config.set(overdraft_duration=timedelta(days=5))
|
|
||||||
self.account.balance = Decimal("1.00")
|
|
||||||
self.account.save()
|
|
||||||
self.account.negative = AccountNegative.objects.create(
|
|
||||||
account=self.account,
|
|
||||||
start=timezone.now() - timedelta(days=3),
|
|
||||||
authz_overdraft_until=timezone.now() - timedelta(seconds=1),
|
|
||||||
)
|
|
||||||
|
|
||||||
data = dict(
|
|
||||||
self.base_post_data,
|
|
||||||
**{
|
|
||||||
"form-TOTAL_FORMS": "1",
|
|
||||||
"form-0-type": "purchase",
|
|
||||||
"form-0-amount": "",
|
|
||||||
"form-0-article": str(self.article.pk),
|
|
||||||
"form-0-article_nb": "2",
|
|
||||||
}
|
|
||||||
)
|
|
||||||
resp = self.client.post(self.url, data)
|
|
||||||
|
|
||||||
self.assertEqual(resp.status_code, 403)
|
|
||||||
json_data = json.loads(resp.content.decode("utf-8"))
|
|
||||||
self.assertEqual(json_data["errors"], {"negative": ["000"]})
|
|
||||||
|
|
||||||
def test_invalid_negative_exceeds_amount_allowed_from_config(self):
|
def test_invalid_negative_exceeds_amount_allowed_from_config(self):
|
||||||
user_add_perms(self.users["team"], ["kfet.perform_negative_operations"])
|
user_add_perms(self.users["team"], ["kfet.perform_negative_operations"])
|
||||||
kfet_config.set(overdraft_amount=Decimal("-1.00"))
|
kfet_config.set(overdraft_amount=Decimal("-1.00"))
|
||||||
|
@ -3083,38 +3019,13 @@ class KPsulPerformOperationsViewTests(ViewTestCaseMixin, TestCase):
|
||||||
)
|
)
|
||||||
resp = self.client.post(self.url, data)
|
resp = self.client.post(self.url, data)
|
||||||
|
|
||||||
self.assertEqual(resp.status_code, 403)
|
self.assertEqual(resp.status_code, 400)
|
||||||
json_data = json.loads(resp.content.decode("utf-8"))
|
json_data = json.loads(resp.content.decode("utf-8"))
|
||||||
self.assertEqual(json_data["errors"], {"negative": ["000"]})
|
self.assertCountEqual(
|
||||||
|
[e["code"] for e in json_data["errors"]],
|
||||||
def test_invalid_negative_exceeds_amount_allowed_from_account(self):
|
["negative"],
|
||||||
user_add_perms(self.users["team"], ["kfet.perform_negative_operations"])
|
|
||||||
kfet_config.set(overdraft_amount=Decimal("10.00"))
|
|
||||||
self.account.balance = Decimal("1.00")
|
|
||||||
self.account.save()
|
|
||||||
self.account.update_negative()
|
|
||||||
self.account.negative = AccountNegative.objects.create(
|
|
||||||
account=self.account,
|
|
||||||
start=timezone.now() - timedelta(days=3),
|
|
||||||
authz_overdraft_amount=Decimal("1.00"),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
data = dict(
|
|
||||||
self.base_post_data,
|
|
||||||
**{
|
|
||||||
"form-TOTAL_FORMS": "1",
|
|
||||||
"form-0-type": "purchase",
|
|
||||||
"form-0-amount": "",
|
|
||||||
"form-0-article": str(self.article.pk),
|
|
||||||
"form-0-article_nb": "2",
|
|
||||||
}
|
|
||||||
)
|
|
||||||
resp = self.client.post(self.url, data)
|
|
||||||
|
|
||||||
self.assertEqual(resp.status_code, 403)
|
|
||||||
json_data = json.loads(resp.content.decode("utf-8"))
|
|
||||||
self.assertEqual(json_data["errors"], {"negative": ["000"]})
|
|
||||||
|
|
||||||
def test_multi_0(self):
|
def test_multi_0(self):
|
||||||
article2 = Article.objects.create(
|
article2 = Article.objects.create(
|
||||||
name="Article 2",
|
name="Article 2",
|
||||||
|
@ -3198,12 +3109,7 @@ class KPsulPerformOperationsViewTests(ViewTestCaseMixin, TestCase):
|
||||||
# Check response content
|
# Check response content
|
||||||
self.assertDictEqual(
|
self.assertDictEqual(
|
||||||
json_data,
|
json_data,
|
||||||
{
|
{"errors": []},
|
||||||
"operationgroup": operation_group.pk,
|
|
||||||
"operations": [operation_list[0].pk, operation_list[1].pk],
|
|
||||||
"warnings": {},
|
|
||||||
"errors": {},
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Check object updates
|
# Check object updates
|
||||||
|
@ -3342,7 +3248,10 @@ class KPsulCancelOperationsViewTests(ViewTestCaseMixin, TestCase):
|
||||||
|
|
||||||
self.assertEqual(resp.status_code, 400)
|
self.assertEqual(resp.status_code, 400)
|
||||||
json_data = json.loads(resp.content.decode("utf-8"))
|
json_data = json.loads(resp.content.decode("utf-8"))
|
||||||
self.assertEqual(json_data["errors"], {})
|
self.assertCountEqual(
|
||||||
|
[e["code"] for e in json_data["errors"]],
|
||||||
|
["invalid_request"],
|
||||||
|
)
|
||||||
|
|
||||||
def test_invalid_operation_not_exist(self):
|
def test_invalid_operation_not_exist(self):
|
||||||
data = {"operations[]": ["1000"]}
|
data = {"operations[]": ["1000"]}
|
||||||
|
@ -3350,7 +3259,10 @@ class KPsulCancelOperationsViewTests(ViewTestCaseMixin, TestCase):
|
||||||
|
|
||||||
self.assertEqual(resp.status_code, 400)
|
self.assertEqual(resp.status_code, 400)
|
||||||
json_data = json.loads(resp.content.decode("utf-8"))
|
json_data = json.loads(resp.content.decode("utf-8"))
|
||||||
self.assertEqual(json_data["errors"], {"opes_notexisting": [1000]})
|
self.assertCountEqual(
|
||||||
|
[e["code"] for e in json_data["errors"]],
|
||||||
|
["cancel_missing"],
|
||||||
|
)
|
||||||
|
|
||||||
@mock.patch("django.utils.timezone.now")
|
@mock.patch("django.utils.timezone.now")
|
||||||
def test_purchase(self, now_mock):
|
def test_purchase(self, now_mock):
|
||||||
|
@ -3414,7 +3326,7 @@ class KPsulCancelOperationsViewTests(ViewTestCaseMixin, TestCase):
|
||||||
"canceled_by__trigramme": None,
|
"canceled_by__trigramme": None,
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"errors": {},
|
"errors": [],
|
||||||
"warnings": {},
|
"warnings": {},
|
||||||
"opegroups_to_update": [
|
"opegroups_to_update": [
|
||||||
{
|
{
|
||||||
|
@ -3602,7 +3514,7 @@ class KPsulCancelOperationsViewTests(ViewTestCaseMixin, TestCase):
|
||||||
"canceled_by__trigramme": None,
|
"canceled_by__trigramme": None,
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"errors": {},
|
"errors": [],
|
||||||
"warnings": {},
|
"warnings": {},
|
||||||
"opegroups_to_update": [
|
"opegroups_to_update": [
|
||||||
{
|
{
|
||||||
|
@ -3689,7 +3601,7 @@ class KPsulCancelOperationsViewTests(ViewTestCaseMixin, TestCase):
|
||||||
"canceled_by__trigramme": None,
|
"canceled_by__trigramme": None,
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"errors": {},
|
"errors": [],
|
||||||
"warnings": {},
|
"warnings": {},
|
||||||
"opegroups_to_update": [
|
"opegroups_to_update": [
|
||||||
{
|
{
|
||||||
|
@ -3776,7 +3688,7 @@ class KPsulCancelOperationsViewTests(ViewTestCaseMixin, TestCase):
|
||||||
"canceled_by__trigramme": None,
|
"canceled_by__trigramme": None,
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"errors": {},
|
"errors": [],
|
||||||
"warnings": {},
|
"warnings": {},
|
||||||
"opegroups_to_update": [
|
"opegroups_to_update": [
|
||||||
{
|
{
|
||||||
|
@ -3839,8 +3751,8 @@ class KPsulCancelOperationsViewTests(ViewTestCaseMixin, TestCase):
|
||||||
self.assertEqual(resp.status_code, 403)
|
self.assertEqual(resp.status_code, 403)
|
||||||
json_data = json.loads(resp.content.decode("utf-8"))
|
json_data = json.loads(resp.content.decode("utf-8"))
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
json_data["errors"],
|
json_data["missing_perms"],
|
||||||
{"missing_perms": ["[kfet] Annuler des commandes non récentes"]},
|
["Annuler des commandes non récentes"],
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_already_canceled(self):
|
def test_already_canceled(self):
|
||||||
|
@ -3964,9 +3876,12 @@ class KPsulCancelOperationsViewTests(ViewTestCaseMixin, TestCase):
|
||||||
data = {"operations[]": [str(operation.pk)]}
|
data = {"operations[]": [str(operation.pk)]}
|
||||||
resp = self.client.post(self.url, data)
|
resp = self.client.post(self.url, data)
|
||||||
|
|
||||||
self.assertEqual(resp.status_code, 403)
|
self.assertEqual(resp.status_code, 400)
|
||||||
json_data = json.loads(resp.content.decode("utf-8"))
|
json_data = json.loads(resp.content.decode("utf-8"))
|
||||||
self.assertEqual(json_data["errors"], {"negative": [self.account.trigramme]})
|
self.assertCountEqual(
|
||||||
|
[e["code"] for e in json_data["errors"]],
|
||||||
|
["negative"],
|
||||||
|
)
|
||||||
|
|
||||||
def test_invalid_negative_requires_perms(self):
|
def test_invalid_negative_requires_perms(self):
|
||||||
kfet_config.set(overdraft_amount=Decimal("40.00"))
|
kfet_config.set(overdraft_amount=Decimal("40.00"))
|
||||||
|
@ -3985,8 +3900,8 @@ class KPsulCancelOperationsViewTests(ViewTestCaseMixin, TestCase):
|
||||||
self.assertEqual(resp.status_code, 403)
|
self.assertEqual(resp.status_code, 403)
|
||||||
json_data = json.loads(resp.content.decode("utf-8"))
|
json_data = json.loads(resp.content.decode("utf-8"))
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
json_data["errors"],
|
json_data["missing_perms"],
|
||||||
{"missing_perms": ["[kfet] Enregistrer des commandes en négatif"]},
|
["Enregistrer des commandes en négatif"],
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_partial_0(self):
|
def test_partial_0(self):
|
||||||
|
@ -4036,7 +3951,7 @@ class KPsulCancelOperationsViewTests(ViewTestCaseMixin, TestCase):
|
||||||
"canceled_by__trigramme": None,
|
"canceled_by__trigramme": None,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
"errors": {},
|
"errors": [],
|
||||||
"warnings": {"already_canceled": [operation3.pk]},
|
"warnings": {"already_canceled": [operation3.pk]},
|
||||||
"opegroups_to_update": [
|
"opegroups_to_update": [
|
||||||
{
|
{
|
||||||
|
|
296
kfet/views.py
296
kfet/views.py
|
@ -15,7 +15,7 @@ from django.contrib.messages.views import SuccessMessageMixin
|
||||||
from django.core.exceptions import SuspiciousOperation, ValidationError
|
from django.core.exceptions import SuspiciousOperation, ValidationError
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
from django.db.models import Count, F, Max, OuterRef, Prefetch, Q, Subquery, Sum
|
from django.db.models import Count, F, Max, OuterRef, Prefetch, Q, Subquery, Sum
|
||||||
from django.forms import formset_factory
|
from django.forms import ValidationError, formset_factory
|
||||||
from django.http import (
|
from django.http import (
|
||||||
Http404,
|
Http404,
|
||||||
HttpResponseBadRequest,
|
HttpResponseBadRequest,
|
||||||
|
@ -39,7 +39,6 @@ from kfet.decorators import teamkfet_required
|
||||||
from kfet.forms import (
|
from kfet.forms import (
|
||||||
AccountForm,
|
AccountForm,
|
||||||
AccountFrozenForm,
|
AccountFrozenForm,
|
||||||
AccountNegativeForm,
|
|
||||||
AccountNoTriForm,
|
AccountNoTriForm,
|
||||||
AccountPwdForm,
|
AccountPwdForm,
|
||||||
AccountStatForm,
|
AccountStatForm,
|
||||||
|
@ -355,11 +354,6 @@ def account_update(request, trigramme):
|
||||||
frozen_form = AccountFrozenForm(request.POST, instance=account)
|
frozen_form = AccountFrozenForm(request.POST, instance=account)
|
||||||
pwd_form = AccountPwdForm()
|
pwd_form = AccountPwdForm()
|
||||||
|
|
||||||
if hasattr(account, "negative"):
|
|
||||||
negative_form = AccountNegativeForm(instance=account.negative)
|
|
||||||
else:
|
|
||||||
negative_form = None
|
|
||||||
|
|
||||||
if request.method == "POST":
|
if request.method == "POST":
|
||||||
self_update = request.user == account.user
|
self_update = request.user == account.user
|
||||||
account_form = AccountForm(request.POST, instance=account)
|
account_form = AccountForm(request.POST, instance=account)
|
||||||
|
@ -381,14 +375,6 @@ def account_update(request, trigramme):
|
||||||
elif group_form.has_changed():
|
elif group_form.has_changed():
|
||||||
warnings.append("statut d'équipe")
|
warnings.append("statut d'équipe")
|
||||||
|
|
||||||
if hasattr(account, "negative"):
|
|
||||||
negative_form = AccountNegativeForm(request.POST, instance=account.negative)
|
|
||||||
|
|
||||||
if request.user.has_perm("kfet.change_accountnegative"):
|
|
||||||
forms.append(negative_form)
|
|
||||||
elif negative_form.has_changed():
|
|
||||||
warnings.append("négatifs")
|
|
||||||
|
|
||||||
# Il ne faut pas valider `pwd_form` si elle est inchangée
|
# Il ne faut pas valider `pwd_form` si elle est inchangée
|
||||||
if pwd_form.has_changed():
|
if pwd_form.has_changed():
|
||||||
if self_update or request.user.has_perm("kfet.change_account_password"):
|
if self_update or request.user.has_perm("kfet.change_account_password"):
|
||||||
|
@ -437,7 +423,6 @@ def account_update(request, trigramme):
|
||||||
"account_form": account_form,
|
"account_form": account_form,
|
||||||
"frozen_form": frozen_form,
|
"frozen_form": frozen_form,
|
||||||
"group_form": group_form,
|
"group_form": group_form,
|
||||||
"negative_form": negative_form,
|
|
||||||
"pwd_form": pwd_form,
|
"pwd_form": pwd_form,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
@ -482,9 +467,11 @@ class AccountDelete(PermissionRequiredMixin, DeleteView):
|
||||||
|
|
||||||
|
|
||||||
class AccountNegativeList(ListView):
|
class AccountNegativeList(ListView):
|
||||||
queryset = AccountNegative.objects.select_related(
|
queryset = (
|
||||||
"account", "account__cofprofile__user"
|
AccountNegative.objects.select_related("account", "account__cofprofile__user")
|
||||||
).exclude(account__trigramme="#13")
|
.filter(account__balance__lt=0)
|
||||||
|
.exclude(account__trigramme="#13")
|
||||||
|
)
|
||||||
template_name = "kfet/account_negative.html"
|
template_name = "kfet/account_negative.html"
|
||||||
context_object_name = "negatives"
|
context_object_name = "negatives"
|
||||||
|
|
||||||
|
@ -977,15 +964,18 @@ def kpsul_checkout_data(request):
|
||||||
@kfet_password_auth
|
@kfet_password_auth
|
||||||
def kpsul_update_addcost(request):
|
def kpsul_update_addcost(request):
|
||||||
addcost_form = AddcostForm(request.POST)
|
addcost_form = AddcostForm(request.POST)
|
||||||
|
data = {"errors": []}
|
||||||
|
|
||||||
if not addcost_form.is_valid():
|
if not addcost_form.is_valid():
|
||||||
data = {"errors": {"addcost": list(addcost_form.errors)}}
|
for (field, errors) in addcost_form.errors.items():
|
||||||
|
for error in errors:
|
||||||
|
data["errors"].append({"code": f"invalid_{field}", "message": error})
|
||||||
|
|
||||||
return JsonResponse(data, status=400)
|
return JsonResponse(data, status=400)
|
||||||
|
|
||||||
required_perms = ["kfet.manage_addcosts"]
|
required_perms = ["kfet.manage_addcosts"]
|
||||||
if not request.user.has_perms(required_perms):
|
if not request.user.has_perms(required_perms):
|
||||||
data = {
|
data["missing_perms"] = get_missing_perms(required_perms, request.user)
|
||||||
"errors": {"missing_perms": get_missing_perms(required_perms, request.user)}
|
|
||||||
}
|
|
||||||
return JsonResponse(data, status=403)
|
return JsonResponse(data, status=403)
|
||||||
|
|
||||||
trigramme = addcost_form.cleaned_data["trigramme"]
|
trigramme = addcost_form.cleaned_data["trigramme"]
|
||||||
|
@ -1000,14 +990,13 @@ def kpsul_update_addcost(request):
|
||||||
|
|
||||||
|
|
||||||
def get_missing_perms(required_perms: List[str], user: User) -> List[str]:
|
def get_missing_perms(required_perms: List[str], user: User) -> List[str]:
|
||||||
def get_perm_description(app_label: str, codename: str) -> str:
|
def get_perm_name(app_label: str, codename: str) -> str:
|
||||||
name = Permission.objects.values_list("name", flat=True).get(
|
return Permission.objects.values_list("name", flat=True).get(
|
||||||
codename=codename, content_type__app_label=app_label
|
codename=codename, content_type__app_label=app_label
|
||||||
)
|
)
|
||||||
return "[{}] {}".format(app_label, name)
|
|
||||||
|
|
||||||
missing_perms = [
|
missing_perms = [
|
||||||
get_perm_description(*perm.split("."))
|
get_perm_name(*perm.split("."))
|
||||||
for perm in required_perms
|
for perm in required_perms
|
||||||
if not user.has_perm(perm)
|
if not user.has_perm(perm)
|
||||||
]
|
]
|
||||||
|
@ -1019,17 +1008,31 @@ def get_missing_perms(required_perms: List[str], user: User) -> List[str]:
|
||||||
@kfet_password_auth
|
@kfet_password_auth
|
||||||
def kpsul_perform_operations(request):
|
def kpsul_perform_operations(request):
|
||||||
# Initializing response data
|
# Initializing response data
|
||||||
data = {"operationgroup": 0, "operations": [], "warnings": {}, "errors": {}}
|
data = {"errors": []}
|
||||||
|
|
||||||
# Checking operationgroup
|
# Checking operationgroup
|
||||||
operationgroup_form = KPsulOperationGroupForm(request.POST)
|
operationgroup_form = KPsulOperationGroupForm(request.POST)
|
||||||
if not operationgroup_form.is_valid():
|
if not operationgroup_form.is_valid():
|
||||||
data["errors"]["operation_group"] = list(operationgroup_form.errors)
|
for field in operationgroup_form.errors:
|
||||||
|
verbose_field, feminin = (
|
||||||
|
("compte", "") if field == "on_acc" else ("caisse", "e")
|
||||||
|
)
|
||||||
|
data["errors"].append(
|
||||||
|
{
|
||||||
|
"code": f"invalid_{field}",
|
||||||
|
"message": f"Pas de {verbose_field} sélectionné{feminin}",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
# Checking operation_formset
|
# Checking operation_formset
|
||||||
operation_formset = KPsulOperationFormSet(request.POST)
|
operation_formset = KPsulOperationFormSet(request.POST)
|
||||||
if not operation_formset.is_valid():
|
if not operation_formset.is_valid():
|
||||||
data["errors"]["operations"] = list(operation_formset.errors)
|
data["errors"].append(
|
||||||
|
{
|
||||||
|
"code": "invalid_formset",
|
||||||
|
"message": "Formulaire d'opérations vide ou invalide",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
# Returning BAD REQUEST if errors
|
# Returning BAD REQUEST if errors
|
||||||
if data["errors"]:
|
if data["errors"]:
|
||||||
|
@ -1038,6 +1041,7 @@ def kpsul_perform_operations(request):
|
||||||
# Pre-saving (no commit)
|
# Pre-saving (no commit)
|
||||||
operationgroup = operationgroup_form.save(commit=False)
|
operationgroup = operationgroup_form.save(commit=False)
|
||||||
operations = operation_formset.save(commit=False)
|
operations = operation_formset.save(commit=False)
|
||||||
|
on_acc = operationgroup.on_acc
|
||||||
|
|
||||||
# Retrieving COF grant
|
# Retrieving COF grant
|
||||||
cof_grant = kfet_config.subvention_cof
|
cof_grant = kfet_config.subvention_cof
|
||||||
|
@ -1051,13 +1055,13 @@ def kpsul_perform_operations(request):
|
||||||
to_addcost_for_balance = 0 # For balance of addcost_for
|
to_addcost_for_balance = 0 # For balance of addcost_for
|
||||||
to_checkout_balance = 0 # For balance of selected checkout
|
to_checkout_balance = 0 # For balance of selected checkout
|
||||||
to_articles_stocks = defaultdict(lambda: 0) # For stocks articles
|
to_articles_stocks = defaultdict(lambda: 0) # For stocks articles
|
||||||
is_addcost = all(
|
is_addcost = all((addcost_for, addcost_amount, addcost_for != on_acc))
|
||||||
(addcost_for, addcost_amount, addcost_for != operationgroup.on_acc)
|
need_comment = on_acc.need_comment
|
||||||
)
|
|
||||||
need_comment = operationgroup.on_acc.need_comment
|
|
||||||
|
|
||||||
if operationgroup.on_acc.is_frozen:
|
if on_acc.is_frozen:
|
||||||
data["errors"]["frozen"] = [operationgroup.on_acc.trigramme]
|
data["errors"].append(
|
||||||
|
{"code": "frozen_acc", "message": f"Le compte {on_acc.trigramme} est gelé"}
|
||||||
|
)
|
||||||
|
|
||||||
# Filling data of each operations
|
# Filling data of each operations
|
||||||
# + operationgroup + calculating other stuffs
|
# + operationgroup + calculating other stuffs
|
||||||
|
@ -1069,19 +1073,23 @@ def kpsul_perform_operations(request):
|
||||||
operation.addcost_amount = addcost_amount * operation.article_nb
|
operation.addcost_amount = addcost_amount * operation.article_nb
|
||||||
operation.amount -= operation.addcost_amount
|
operation.amount -= operation.addcost_amount
|
||||||
to_addcost_for_balance += operation.addcost_amount
|
to_addcost_for_balance += operation.addcost_amount
|
||||||
if operationgroup.on_acc.is_cash:
|
if on_acc.is_cash:
|
||||||
to_checkout_balance += -operation.amount
|
to_checkout_balance += -operation.amount
|
||||||
if (
|
if on_acc.is_cof and operation.article.category.has_reduction:
|
||||||
operationgroup.on_acc.is_cof
|
|
||||||
and operation.article.category.has_reduction
|
|
||||||
):
|
|
||||||
if is_addcost and operation.article.category.has_addcost:
|
if is_addcost and operation.article.category.has_addcost:
|
||||||
operation.addcost_amount /= cof_grant_divisor
|
operation.addcost_amount /= cof_grant_divisor
|
||||||
operation.amount = operation.amount / cof_grant_divisor
|
operation.amount = operation.amount / cof_grant_divisor
|
||||||
to_articles_stocks[operation.article] -= operation.article_nb
|
to_articles_stocks[operation.article] -= operation.article_nb
|
||||||
else:
|
else:
|
||||||
if operationgroup.on_acc.is_cash:
|
if on_acc.is_cash:
|
||||||
data["errors"]["account"] = "LIQ"
|
data["errors"].append(
|
||||||
|
{
|
||||||
|
"code": "invalid_liq",
|
||||||
|
"message": (
|
||||||
|
"Impossible de compter autre chose que des achats sur LIQ"
|
||||||
|
),
|
||||||
|
}
|
||||||
|
)
|
||||||
if operation.type != Operation.EDIT:
|
if operation.type != Operation.EDIT:
|
||||||
to_checkout_balance += operation.amount
|
to_checkout_balance += operation.amount
|
||||||
operationgroup.amount += operation.amount
|
operationgroup.amount += operation.amount
|
||||||
|
@ -1090,41 +1098,42 @@ def kpsul_perform_operations(request):
|
||||||
if operation.type == Operation.EDIT:
|
if operation.type == Operation.EDIT:
|
||||||
required_perms.add("kfet.edit_balance_account")
|
required_perms.add("kfet.edit_balance_account")
|
||||||
need_comment = True
|
need_comment = True
|
||||||
if operationgroup.on_acc.is_cof:
|
if on_acc.is_cof:
|
||||||
to_addcost_for_balance = to_addcost_for_balance / cof_grant_divisor
|
to_addcost_for_balance = to_addcost_for_balance / cof_grant_divisor
|
||||||
|
|
||||||
(perms, stop) = operationgroup.on_acc.perms_to_perform_operation(
|
(perms, stop) = on_acc.perms_to_perform_operation(amount=operationgroup.amount)
|
||||||
amount=operationgroup.amount
|
|
||||||
)
|
|
||||||
required_perms |= perms
|
required_perms |= perms
|
||||||
|
|
||||||
|
if stop:
|
||||||
|
data["errors"].append(
|
||||||
|
{
|
||||||
|
"code": "negative",
|
||||||
|
"message": f"Le compte {on_acc.trigramme} a un solde insuffisant.",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
if need_comment:
|
if need_comment:
|
||||||
operationgroup.comment = operationgroup.comment.strip()
|
operationgroup.comment = operationgroup.comment.strip()
|
||||||
if not operationgroup.comment:
|
if not operationgroup.comment:
|
||||||
data["errors"]["need_comment"] = True
|
data["need_comment"] = True
|
||||||
|
|
||||||
if data["errors"]:
|
if data["errors"] or "need_comment" in data:
|
||||||
return JsonResponse(data, status=400)
|
return JsonResponse(data, status=400)
|
||||||
|
|
||||||
if stop or not request.user.has_perms(required_perms):
|
if not request.user.has_perms(required_perms):
|
||||||
missing_perms = get_missing_perms(required_perms, request.user)
|
data["missing_perms"] = get_missing_perms(required_perms, request.user)
|
||||||
if missing_perms:
|
|
||||||
data["errors"]["missing_perms"] = missing_perms
|
|
||||||
if stop:
|
|
||||||
data["errors"]["negative"] = [operationgroup.on_acc.trigramme]
|
|
||||||
return JsonResponse(data, status=403)
|
return JsonResponse(data, status=403)
|
||||||
|
|
||||||
# If 1 perm is required, filling who perform the operations
|
# If 1 perm is required, filling who perform the operations
|
||||||
if required_perms:
|
if required_perms:
|
||||||
operationgroup.valid_by = request.user.profile.account_kfet
|
operationgroup.valid_by = request.user.profile.account_kfet
|
||||||
# Filling cof status for statistics
|
# Filling cof status for statistics
|
||||||
operationgroup.is_cof = operationgroup.on_acc.is_cof
|
operationgroup.is_cof = on_acc.is_cof
|
||||||
|
|
||||||
# Starting transaction to ensure data consistency
|
# Starting transaction to ensure data consistency
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
# If not cash account,
|
# If not cash account,
|
||||||
# saving account's balance and adding to Negative if not in
|
# saving account's balance and adding to Negative if not in
|
||||||
on_acc = operationgroup.on_acc
|
|
||||||
if not on_acc.is_cash:
|
if not on_acc.is_cash:
|
||||||
(
|
(
|
||||||
Account.objects.filter(pk=on_acc.pk).update(
|
Account.objects.filter(pk=on_acc.pk).update(
|
||||||
|
@ -1148,13 +1157,10 @@ def kpsul_perform_operations(request):
|
||||||
|
|
||||||
# Saving operation group
|
# Saving operation group
|
||||||
operationgroup.save()
|
operationgroup.save()
|
||||||
data["operationgroup"] = operationgroup.pk
|
|
||||||
|
|
||||||
# Filling operationgroup id for each operations and saving
|
# Filling operationgroup id for each operations and saving
|
||||||
for operation in operations:
|
for operation in operations:
|
||||||
operation.group = operationgroup
|
operation.group = operationgroup
|
||||||
operation.save()
|
operation.save()
|
||||||
data["operations"].append(operation.pk)
|
|
||||||
|
|
||||||
# Updating articles stock
|
# Updating articles stock
|
||||||
for article in to_articles_stocks:
|
for article in to_articles_stocks:
|
||||||
|
@ -1177,7 +1183,7 @@ def kpsul_perform_operations(request):
|
||||||
"valid_by__trigramme": (
|
"valid_by__trigramme": (
|
||||||
operationgroup.valid_by and operationgroup.valid_by.trigramme or None
|
operationgroup.valid_by and operationgroup.valid_by.trigramme or None
|
||||||
),
|
),
|
||||||
"on_acc__trigramme": operationgroup.on_acc.trigramme,
|
"on_acc__trigramme": on_acc.trigramme,
|
||||||
"entries": [],
|
"entries": [],
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -1218,7 +1224,7 @@ def kpsul_perform_operations(request):
|
||||||
@kfet_password_auth
|
@kfet_password_auth
|
||||||
def cancel_operations(request):
|
def cancel_operations(request):
|
||||||
# Pour la réponse
|
# Pour la réponse
|
||||||
data = {"canceled": [], "warnings": {}, "errors": {}}
|
data = {"canceled": [], "warnings": {}, "errors": []}
|
||||||
|
|
||||||
# Checking if BAD REQUEST (opes_pk not int or not existing)
|
# Checking if BAD REQUEST (opes_pk not int or not existing)
|
||||||
try:
|
try:
|
||||||
|
@ -1227,29 +1233,41 @@ def cancel_operations(request):
|
||||||
map(int, filter(None, request.POST.getlist("operations[]", [])))
|
map(int, filter(None, request.POST.getlist("operations[]", [])))
|
||||||
)
|
)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
|
data["errors"].append(
|
||||||
|
{"code": "invalid_request", "message": "Requête invalide !"}
|
||||||
|
)
|
||||||
return JsonResponse(data, status=400)
|
return JsonResponse(data, status=400)
|
||||||
|
|
||||||
opes_all = Operation.objects.select_related(
|
opes_all = Operation.objects.select_related(
|
||||||
"group", "group__on_acc", "group__on_acc__negative"
|
"group", "group__on_acc", "group__on_acc__negative"
|
||||||
).filter(pk__in=opes_post)
|
).filter(pk__in=opes_post)
|
||||||
opes_pk = [ope.pk for ope in opes_all]
|
opes_pk = [ope.pk for ope in opes_all]
|
||||||
opes_notexisting = [ope for ope in opes_post if ope not in opes_pk]
|
opes_notexisting = [ope for ope in opes_post if ope not in opes_pk]
|
||||||
if opes_notexisting:
|
if opes_notexisting:
|
||||||
data["errors"]["opes_notexisting"] = opes_notexisting
|
data["errors"].append(
|
||||||
|
{
|
||||||
|
"code": "cancel_missing",
|
||||||
|
"message": "Opérations inexistantes : {}".format(
|
||||||
|
", ".join(map(str, opes_notexisting))
|
||||||
|
),
|
||||||
|
}
|
||||||
|
)
|
||||||
return JsonResponse(data, status=400)
|
return JsonResponse(data, status=400)
|
||||||
|
|
||||||
opes_already_canceled = [] # Déjà annulée
|
opes_already_canceled = [] # Déjà annulée
|
||||||
opes = [] # Pas déjà annulée
|
opes = [] # Pas déjà annulée
|
||||||
required_perms = set()
|
required_perms = set()
|
||||||
stop_all = False
|
|
||||||
cancel_duration = kfet_config.cancel_duration
|
cancel_duration = kfet_config.cancel_duration
|
||||||
to_accounts_balances = defaultdict(
|
|
||||||
lambda: 0
|
# Modifs à faire sur les balances des comptes
|
||||||
) # Modifs à faire sur les balances des comptes
|
to_accounts_balances = defaultdict(int)
|
||||||
to_groups_amounts = defaultdict(
|
# ------ sur les montants des groupes d'opé
|
||||||
lambda: 0
|
to_groups_amounts = defaultdict(int)
|
||||||
) # ------ sur les montants des groupes d'opé
|
# ------ sur les balances de caisses
|
||||||
to_checkouts_balances = defaultdict(lambda: 0) # ------ sur les balances de caisses
|
to_checkouts_balances = defaultdict(int)
|
||||||
to_articles_stocks = defaultdict(lambda: 0) # ------ sur les stocks d'articles
|
# ------ sur les stocks d'articles
|
||||||
|
to_articles_stocks = defaultdict(int)
|
||||||
|
|
||||||
for ope in opes_all:
|
for ope in opes_all:
|
||||||
if ope.canceled_at:
|
if ope.canceled_at:
|
||||||
# Opération déjà annulée, va pour un warning en Response
|
# Opération déjà annulée, va pour un warning en Response
|
||||||
|
@ -1320,16 +1338,22 @@ def cancel_operations(request):
|
||||||
amount=to_accounts_balances[account]
|
amount=to_accounts_balances[account]
|
||||||
)
|
)
|
||||||
required_perms |= perms
|
required_perms |= perms
|
||||||
stop_all = stop_all or stop
|
|
||||||
if stop:
|
if stop:
|
||||||
negative_accounts.append(account.trigramme)
|
negative_accounts.append(account.trigramme)
|
||||||
|
|
||||||
if stop_all or not request.user.has_perms(required_perms):
|
if negative_accounts:
|
||||||
missing_perms = get_missing_perms(required_perms, request.user)
|
data["errors"].append(
|
||||||
if missing_perms:
|
{
|
||||||
data["errors"]["missing_perms"] = missing_perms
|
"code": "negative",
|
||||||
if stop_all:
|
"message": "Solde insuffisant pour les comptes suivants : {}".format(
|
||||||
data["errors"]["negative"] = negative_accounts
|
", ".join(negative_accounts)
|
||||||
|
),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return JsonResponse(data, status=400)
|
||||||
|
|
||||||
|
if not request.user.has_perms(required_perms):
|
||||||
|
data["missing_perms"] = get_missing_perms(required_perms, request.user)
|
||||||
return JsonResponse(data, status=403)
|
return JsonResponse(data, status=403)
|
||||||
|
|
||||||
canceled_by = required_perms and request.user.profile.account_kfet or None
|
canceled_by = required_perms and request.user.profile.account_kfet or None
|
||||||
|
@ -1657,12 +1681,36 @@ def transfers_create(request):
|
||||||
@teamkfet_required
|
@teamkfet_required
|
||||||
@kfet_password_auth
|
@kfet_password_auth
|
||||||
def perform_transfers(request):
|
def perform_transfers(request):
|
||||||
data = {"errors": {}, "transfers": [], "transfergroup": 0}
|
data = {"errors": []}
|
||||||
|
|
||||||
# Checking transfer_formset
|
# Checking transfer_formset
|
||||||
transfer_formset = TransferFormSet(request.POST)
|
transfer_formset = TransferFormSet(request.POST)
|
||||||
|
try:
|
||||||
if not transfer_formset.is_valid():
|
if not transfer_formset.is_valid():
|
||||||
return JsonResponse({"errors": list(transfer_formset.errors)}, status=400)
|
for form_errors in transfer_formset.errors:
|
||||||
|
for (field, errors) in form_errors.items():
|
||||||
|
if field == "amount":
|
||||||
|
for error in errors:
|
||||||
|
data["errors"].append({"code": "amount", "message": error})
|
||||||
|
else:
|
||||||
|
# C'est compliqué de trouver le compte qui pose problème...
|
||||||
|
acc_error = True
|
||||||
|
|
||||||
|
if acc_error:
|
||||||
|
data["errors"].append(
|
||||||
|
{
|
||||||
|
"code": "invalid_acc",
|
||||||
|
"message": "L'un des comptes est invalide ou manquant",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return JsonResponse(data, status=400)
|
||||||
|
|
||||||
|
except ValidationError:
|
||||||
|
data["errors"].append(
|
||||||
|
{"code": "invalid_request", "message": "Requête invalide"}
|
||||||
|
)
|
||||||
|
return JsonResponse(data, status=400)
|
||||||
|
|
||||||
transfers = transfer_formset.save(commit=False)
|
transfers = transfer_formset.save(commit=False)
|
||||||
|
|
||||||
|
@ -1670,14 +1718,12 @@ def perform_transfers(request):
|
||||||
required_perms = set(
|
required_perms = set(
|
||||||
["kfet.add_transfer"]
|
["kfet.add_transfer"]
|
||||||
) # Required perms to perform all transfers
|
) # Required perms to perform all transfers
|
||||||
to_accounts_balances = defaultdict(lambda: 0) # For balances of accounts
|
to_accounts_balances = defaultdict(int) # For balances of accounts
|
||||||
|
|
||||||
for transfer in transfers:
|
for transfer in transfers:
|
||||||
to_accounts_balances[transfer.from_acc] -= transfer.amount
|
to_accounts_balances[transfer.from_acc] -= transfer.amount
|
||||||
to_accounts_balances[transfer.to_acc] += transfer.amount
|
to_accounts_balances[transfer.to_acc] += transfer.amount
|
||||||
|
|
||||||
stop_all = False
|
|
||||||
|
|
||||||
negative_accounts = []
|
negative_accounts = []
|
||||||
# Checking if ok on all accounts
|
# Checking if ok on all accounts
|
||||||
frozen = set()
|
frozen = set()
|
||||||
|
@ -1689,20 +1735,34 @@ def perform_transfers(request):
|
||||||
amount=to_accounts_balances[account]
|
amount=to_accounts_balances[account]
|
||||||
)
|
)
|
||||||
required_perms |= perms
|
required_perms |= perms
|
||||||
stop_all = stop_all or stop
|
|
||||||
if stop:
|
if stop:
|
||||||
negative_accounts.append(account.trigramme)
|
negative_accounts.append(account.trigramme)
|
||||||
|
|
||||||
if len(frozen):
|
if frozen:
|
||||||
data["errors"]["frozen"] = list(frozen)
|
data["errors"].append(
|
||||||
|
{
|
||||||
|
"code": "frozen",
|
||||||
|
"message": "Les comptes suivants sont gelés : {}".format(
|
||||||
|
", ".join(frozen)
|
||||||
|
),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
if negative_accounts:
|
||||||
|
data["errors"].append(
|
||||||
|
{
|
||||||
|
"code": "negative",
|
||||||
|
"message": "Solde insuffisant pour les comptes suivants : {}".format(
|
||||||
|
", ".join(negative_accounts)
|
||||||
|
),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
if data["errors"]:
|
||||||
return JsonResponse(data, status=400)
|
return JsonResponse(data, status=400)
|
||||||
|
|
||||||
if stop_all or not request.user.has_perms(required_perms):
|
if not request.user.has_perms(required_perms):
|
||||||
missing_perms = get_missing_perms(required_perms, request.user)
|
data["missing_perms"] = get_missing_perms(required_perms, request.user)
|
||||||
if missing_perms:
|
|
||||||
data["errors"]["missing_perms"] = missing_perms
|
|
||||||
if stop_all:
|
|
||||||
data["errors"]["negative"] = negative_accounts
|
|
||||||
return JsonResponse(data, status=403)
|
return JsonResponse(data, status=403)
|
||||||
|
|
||||||
# Creating transfer group
|
# Creating transfer group
|
||||||
|
@ -1724,22 +1784,20 @@ def perform_transfers(request):
|
||||||
|
|
||||||
# Saving transfer group
|
# Saving transfer group
|
||||||
transfergroup.save()
|
transfergroup.save()
|
||||||
data["transfergroup"] = transfergroup.pk
|
|
||||||
|
|
||||||
# Saving all transfers with group
|
# Saving all transfers with group
|
||||||
for transfer in transfers:
|
for transfer in transfers:
|
||||||
transfer.group = transfergroup
|
transfer.group = transfergroup
|
||||||
transfer.save()
|
transfer.save()
|
||||||
data["transfers"].append(transfer.pk)
|
|
||||||
|
|
||||||
return JsonResponse(data)
|
return JsonResponse({})
|
||||||
|
|
||||||
|
|
||||||
@teamkfet_required
|
@teamkfet_required
|
||||||
@kfet_password_auth
|
@kfet_password_auth
|
||||||
def cancel_transfers(request):
|
def cancel_transfers(request):
|
||||||
# Pour la réponse
|
# Pour la réponse
|
||||||
data = {"canceled": [], "warnings": {}, "errors": {}}
|
data = {"canceled": [], "warnings": {}, "errors": []}
|
||||||
|
|
||||||
# Checking if BAD REQUEST (transfers_pk not int or not existing)
|
# Checking if BAD REQUEST (transfers_pk not int or not existing)
|
||||||
try:
|
try:
|
||||||
|
@ -1748,7 +1806,11 @@ def cancel_transfers(request):
|
||||||
map(int, filter(None, request.POST.getlist("transfers[]", [])))
|
map(int, filter(None, request.POST.getlist("transfers[]", [])))
|
||||||
)
|
)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
|
data["errors"].append(
|
||||||
|
{"code": "invalid_request", "message": "Requête invalide !"}
|
||||||
|
)
|
||||||
return JsonResponse(data, status=400)
|
return JsonResponse(data, status=400)
|
||||||
|
|
||||||
transfers_all = Transfer.objects.select_related(
|
transfers_all = Transfer.objects.select_related(
|
||||||
"group", "from_acc", "from_acc__negative", "to_acc", "to_acc__negative"
|
"group", "from_acc", "from_acc__negative", "to_acc", "to_acc__negative"
|
||||||
).filter(pk__in=transfers_post)
|
).filter(pk__in=transfers_post)
|
||||||
|
@ -1757,17 +1819,23 @@ def cancel_transfers(request):
|
||||||
transfer for transfer in transfers_post if transfer not in transfers_pk
|
transfer for transfer in transfers_post if transfer not in transfers_pk
|
||||||
]
|
]
|
||||||
if transfers_notexisting:
|
if transfers_notexisting:
|
||||||
data["errors"]["transfers_notexisting"] = transfers_notexisting
|
data["errors"].append(
|
||||||
|
{
|
||||||
|
"code": "cancel_missing",
|
||||||
|
"message": "Transferts inexistants : {}".format(
|
||||||
|
", ".join(map(str, transfers_notexisting))
|
||||||
|
),
|
||||||
|
}
|
||||||
|
)
|
||||||
return JsonResponse(data, status=400)
|
return JsonResponse(data, status=400)
|
||||||
|
|
||||||
transfers_already_canceled = [] # Déjà annulée
|
transfers_already_canceled = [] # Déjà annulés
|
||||||
transfers = [] # Pas déjà annulée
|
transfers = [] # Pas déjà annulés
|
||||||
required_perms = set()
|
required_perms = set()
|
||||||
stop_all = False
|
|
||||||
cancel_duration = kfet_config.cancel_duration
|
cancel_duration = kfet_config.cancel_duration
|
||||||
to_accounts_balances = defaultdict(
|
|
||||||
lambda: 0
|
# Modifs à faire sur les balances des comptes
|
||||||
) # Modifs à faire sur les balances des comptes
|
to_accounts_balances = defaultdict(int)
|
||||||
for transfer in transfers_all:
|
for transfer in transfers_all:
|
||||||
if transfer.canceled_at:
|
if transfer.canceled_at:
|
||||||
# Transfert déjà annulé, va pour un warning en Response
|
# Transfert déjà annulé, va pour un warning en Response
|
||||||
|
@ -1795,16 +1863,22 @@ def cancel_transfers(request):
|
||||||
amount=to_accounts_balances[account]
|
amount=to_accounts_balances[account]
|
||||||
)
|
)
|
||||||
required_perms |= perms
|
required_perms |= perms
|
||||||
stop_all = stop_all or stop
|
|
||||||
if stop:
|
if stop:
|
||||||
negative_accounts.append(account.trigramme)
|
negative_accounts.append(account.trigramme)
|
||||||
|
|
||||||
if stop_all or not request.user.has_perms(required_perms):
|
if negative_accounts:
|
||||||
missing_perms = get_missing_perms(required_perms, request.user)
|
data["errors"].append(
|
||||||
if missing_perms:
|
{
|
||||||
data["errors"]["missing_perms"] = missing_perms
|
"code": "negative",
|
||||||
if stop_all:
|
"message": "Solde insuffisant pour les comptes suivants : {}".format(
|
||||||
data["errors"]["negative"] = negative_accounts
|
", ".join(negative_accounts)
|
||||||
|
),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return JsonResponse(data, status=400)
|
||||||
|
|
||||||
|
if not request.user.has_perms(required_perms):
|
||||||
|
data["missing_perms"] = get_missing_perms(required_perms, request.user)
|
||||||
return JsonResponse(data, status=403)
|
return JsonResponse(data, status=403)
|
||||||
|
|
||||||
canceled_by = required_perms and request.user.profile.account_kfet or None
|
canceled_by = required_perms and request.user.profile.account_kfet or None
|
||||||
|
|
Loading…
Reference in a new issue