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 kfet.models import (
|
||||
Account,
|
||||
AccountNegative,
|
||||
Article,
|
||||
ArticleCategory,
|
||||
Checkout,
|
||||
|
@ -158,17 +157,6 @@ class UserInfoForm(UserForm):
|
|||
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
|
||||
# -----
|
||||
|
@ -549,7 +537,7 @@ class TransferForm(forms.ModelForm):
|
|||
def clean_amount(self):
|
||||
amount = self.cleaned_data["amount"]
|
||||
if amount <= 0:
|
||||
raise forms.ValidationError("Montant invalide")
|
||||
raise forms.ValidationError("Le montant d'un transfert doit être positif")
|
||||
return amount
|
||||
|
||||
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
|
||||
|
||||
def perms_to_perform_operation(self, amount):
|
||||
overdraft_duration_max = kfet_config.overdraft_duration
|
||||
overdraft_amount_max = kfet_config.overdraft_amount
|
||||
perms = set()
|
||||
stop_ope = False
|
||||
# Checking is cash account
|
||||
if self.is_cash:
|
||||
# Yes, so no perms and no stop
|
||||
return set(), False
|
||||
|
||||
if self.need_comment:
|
||||
perms.add("kfet.perform_commented_operations")
|
||||
|
||||
new_balance = self.balance + amount
|
||||
if new_balance < -kfet_config.overdraft_amount:
|
||||
return set(), True
|
||||
|
||||
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")
|
||||
return perms, stop_ope
|
||||
|
||||
return perms, False
|
||||
|
||||
# Surcharge Méthode save() avec gestions de User et CofProfile
|
||||
# Args:
|
||||
|
@ -267,16 +249,25 @@ class Account(models.Model):
|
|||
|
||||
def update_negative(self):
|
||||
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.end = None
|
||||
self.negative.save()
|
||||
elif not hasattr(self, "negative"):
|
||||
self.negative = AccountNegative.objects.create(
|
||||
account=self, start=timezone.now()
|
||||
)
|
||||
elif hasattr(self, "negative"):
|
||||
# self.balance >= 0
|
||||
# TODO: méchanisme pour éviter de contourner le délai de négatif ?
|
||||
if self.negative.end is None:
|
||||
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()
|
||||
|
||||
class UserHasAccount(Exception):
|
||||
|
@ -302,26 +293,11 @@ class AccountNegative(models.Model):
|
|||
Account, on_delete=models.CASCADE, related_name="negative"
|
||||
)
|
||||
start = models.DateTimeField(blank=True, null=True, default=None)
|
||||
authz_overdraft_amount = models.DecimalField(
|
||||
"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)
|
||||
end = models.DateTimeField(blank=True, null=True, default=None)
|
||||
|
||||
class Meta:
|
||||
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):
|
||||
def is_valid(self):
|
||||
|
|
|
@ -257,11 +257,11 @@ function KHistory(options = {}) {
|
|||
switch ($xhr.status) {
|
||||
case 403:
|
||||
requestAuth(data, function (password) {
|
||||
this.cancel(opes, password);
|
||||
that._cancel(type, opes, password);
|
||||
});
|
||||
break;
|
||||
case 400:
|
||||
displayErrors(getErrorsHtml(data));
|
||||
displayErrors(data);
|
||||
break;
|
||||
}
|
||||
window.lock = 0;
|
||||
|
|
|
@ -106,62 +106,25 @@ function amountToUKF(amount, is_cof = false, account = false) {
|
|||
return rounding(amount * coef_cof * 10);
|
||||
}
|
||||
|
||||
function getErrorsHtml(data) {
|
||||
var content = '';
|
||||
if (!data)
|
||||
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>';
|
||||
function getErrorsHtml(data, is_error = true) {
|
||||
if (is_error) {
|
||||
data = data.map(error => error.message)
|
||||
}
|
||||
if ('missing_perms' in data['errors']) {
|
||||
content += 'Permissions manquantes';
|
||||
content += '<ul>';
|
||||
for (var i = 0; i < data['errors']['missing_perms'].length; i++)
|
||||
content += '<li>' + data['errors']['missing_perms'][i] + '</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>';
|
||||
|
||||
var content = is_error ? "Général :" : "Permissions manquantes :";
|
||||
content += "<ul>";
|
||||
for (const message of data) {
|
||||
content += '<li>' + message + '</li>';
|
||||
}
|
||||
content += "</ul>";
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
function requestAuth(data, callback, focus_next = null) {
|
||||
var content = getErrorsHtml(data);
|
||||
content += '<div class="capslock"><span class="glyphicon glyphicon-lock"></span><input type="password" name="password" autofocus><div>',
|
||||
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>';
|
||||
|
||||
$.confirm({
|
||||
title: 'Authentification requise',
|
||||
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({
|
||||
title: 'Erreurs',
|
||||
content: html,
|
||||
content: content,
|
||||
backgroundDismiss: true,
|
||||
animation: 'top',
|
||||
closeAnimation: 'bottom',
|
||||
|
|
|
@ -10,26 +10,12 @@
|
|||
{{ negatives|length }}
|
||||
<span class="sub">compte{{ negatives|length|pluralize }} en négatif</span>
|
||||
</div>
|
||||
<div class="text">
|
||||
<b>Total:</b> {{ negatives_sum|floatformat:2 }}€
|
||||
</div>
|
||||
<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 class="heading">
|
||||
{{ negatives_sum|floatformat:2 }}€
|
||||
<span class="sub">de négatif total</span>
|
||||
</div>
|
||||
</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 %}
|
||||
|
||||
{% block main %}
|
||||
|
@ -43,8 +29,6 @@
|
|||
<td>Nom</td>
|
||||
<td class="text-right">Balance</td>
|
||||
<td data-sorter="shortDate">Début</td>
|
||||
<td>Découvert autorisé</td>
|
||||
<td data-sorter="shortDate">Jusqu'au</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
@ -60,10 +44,6 @@
|
|||
<td title="{{ neg.start }}">
|
||||
{{ neg.start|date:'d/m/Y H:i'}}
|
||||
</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>
|
||||
{% endfor %}
|
||||
</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=group_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' %}
|
||||
{% endif %}
|
||||
{% include 'kfet/form_submit_snippet.html' with value="Mettre à jour" %}
|
||||
</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 %}
|
|
@ -376,10 +376,10 @@ $(document).ready(function() {
|
|||
requestAuth(data, performOperations, articleSelect);
|
||||
break;
|
||||
case 400:
|
||||
if ('need_comment' in data['errors']) {
|
||||
if ('need_comment' in data) {
|
||||
askComment(performOperations);
|
||||
} else {
|
||||
displayErrors(getErrorsHtml(data));
|
||||
displayErrors(data);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
@ -1074,7 +1074,7 @@ $(document).ready(function() {
|
|||
}, triInput);
|
||||
break;
|
||||
case 400:
|
||||
askAddcost(getErrorsHtml(data));
|
||||
askAddcost(getErrorsHtml(data["errors"], is_error=true));
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
|
|
@ -54,12 +54,6 @@
|
|||
{% if account.negative.start %}
|
||||
<li>Depuis le <b>{{ account.negative.start|date:"d/m/Y à H:i" }}</b></li>
|
||||
{% 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>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
|
|
@ -72,7 +72,7 @@ $(document).ready(function () {
|
|||
var $next = $form.next('.transfer_form').find('.from_acc input');
|
||||
}
|
||||
var $input_id = $input.next('input');
|
||||
if (isValidTrigramme(trigramme)) {
|
||||
if (trigramme.is_valid_trigramme()) {
|
||||
getAccountData(trigramme, function(data) {
|
||||
$input_id.val(data.id);
|
||||
$data.text(data.name);
|
||||
|
@ -122,7 +122,7 @@ $(document).ready(function () {
|
|||
requestAuth(data, performTransfers);
|
||||
break;
|
||||
case 400:
|
||||
displayErrors(getErrorsHtml(data));
|
||||
displayErrors(data);
|
||||
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.test import TestCase
|
||||
from django.utils import timezone
|
||||
|
||||
from kfet.models import Account, Checkout
|
||||
from kfet.models import Account, AccountNegative, Checkout
|
||||
|
||||
from .utils import create_user
|
||||
|
||||
|
@ -28,6 +30,56 @@ class AccountTests(TestCase):
|
|||
with self.assertRaises(Account.DoesNotExist):
|
||||
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):
|
||||
def setUp(self):
|
||||
|
@ -39,7 +91,7 @@ class CheckoutTests(TestCase):
|
|||
self.c = Checkout(
|
||||
created_by=self.u_acc,
|
||||
valid_from=self.now,
|
||||
valid_to=self.now + datetime.timedelta(days=1),
|
||||
valid_to=self.now + timedelta(days=1),
|
||||
)
|
||||
|
||||
def test_initial_statement(self):
|
||||
|
|
|
@ -15,7 +15,6 @@ from ..auth.utils import hash_password
|
|||
from ..config import kfet_config
|
||||
from ..models import (
|
||||
Account,
|
||||
AccountNegative,
|
||||
Article,
|
||||
ArticleCategory,
|
||||
Checkout,
|
||||
|
@ -1856,7 +1855,10 @@ class KPsulPerformOperationsViewTests(ViewTestCaseMixin, TestCase):
|
|||
|
||||
self.assertEqual(resp.status_code, 400)
|
||||
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):
|
||||
user_add_perms(self.users["team"], ["kfet.perform_commented_operations"])
|
||||
|
@ -1899,7 +1901,7 @@ class KPsulPerformOperationsViewTests(ViewTestCaseMixin, TestCase):
|
|||
|
||||
self.assertEqual(resp.status_code, 400)
|
||||
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):
|
||||
self.account.trigramme = "#13"
|
||||
|
@ -1922,8 +1924,8 @@ class KPsulPerformOperationsViewTests(ViewTestCaseMixin, TestCase):
|
|||
self.assertEqual(resp.status_code, 403)
|
||||
json_data = json.loads(resp.content.decode("utf-8"))
|
||||
self.assertEqual(
|
||||
json_data["errors"]["missing_perms"],
|
||||
["[kfet] Enregistrer des commandes avec commentaires"],
|
||||
json_data["missing_perms"],
|
||||
["Enregistrer des commandes avec commentaires"],
|
||||
)
|
||||
|
||||
def test_error_on_acc_frozen(self):
|
||||
|
@ -1945,7 +1947,7 @@ class KPsulPerformOperationsViewTests(ViewTestCaseMixin, TestCase):
|
|||
|
||||
self.assertEqual(resp.status_code, 400)
|
||||
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):
|
||||
self.checkout.valid_from -= timedelta(days=300)
|
||||
|
@ -1957,7 +1959,10 @@ class KPsulPerformOperationsViewTests(ViewTestCaseMixin, TestCase):
|
|||
|
||||
self.assertEqual(resp.status_code, 400)
|
||||
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):
|
||||
data = dict(self.base_post_data)
|
||||
|
@ -1965,7 +1970,10 @@ class KPsulPerformOperationsViewTests(ViewTestCaseMixin, TestCase):
|
|||
|
||||
self.assertEqual(resp.status_code, 400)
|
||||
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):
|
||||
self.account.cofprofile.is_cof = False
|
||||
|
@ -2023,12 +2031,7 @@ class KPsulPerformOperationsViewTests(ViewTestCaseMixin, TestCase):
|
|||
# Check response content
|
||||
self.assertDictEqual(
|
||||
json_data,
|
||||
{
|
||||
"operationgroup": operation_group.pk,
|
||||
"operations": [operation.pk],
|
||||
"warnings": {},
|
||||
"errors": {},
|
||||
},
|
||||
{"errors": []},
|
||||
)
|
||||
|
||||
# Check object updates
|
||||
|
@ -2179,9 +2182,9 @@ class KPsulPerformOperationsViewTests(ViewTestCaseMixin, TestCase):
|
|||
|
||||
self.assertEqual(resp.status_code, 400)
|
||||
json_data = json.loads(resp.content.decode("utf-8"))
|
||||
self.assertEqual(
|
||||
json_data["errors"]["operations"],
|
||||
[{"__all__": ["Un achat nécessite un article et une quantité"]}],
|
||||
self.assertCountEqual(
|
||||
[e["code"] for e in json_data["errors"]],
|
||||
["invalid_formset"],
|
||||
)
|
||||
|
||||
def test_invalid_purchase_expects_article_nb(self):
|
||||
|
@ -2199,9 +2202,9 @@ class KPsulPerformOperationsViewTests(ViewTestCaseMixin, TestCase):
|
|||
|
||||
self.assertEqual(resp.status_code, 400)
|
||||
json_data = json.loads(resp.content.decode("utf-8"))
|
||||
self.assertEqual(
|
||||
json_data["errors"]["operations"],
|
||||
[{"__all__": ["Un achat nécessite un article et une quantité"]}],
|
||||
self.assertCountEqual(
|
||||
[e["code"] for e in json_data["errors"]],
|
||||
["invalid_formset"],
|
||||
)
|
||||
|
||||
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)
|
||||
json_data = json.loads(resp.content.decode("utf-8"))
|
||||
self.assertEqual(
|
||||
json_data["errors"]["operations"],
|
||||
[
|
||||
{
|
||||
"__all__": ["Un achat nécessite un article et une quantité"],
|
||||
"article_nb": [
|
||||
"Assurez-vous que cette valeur est supérieure ou " "égale à 1."
|
||||
],
|
||||
}
|
||||
],
|
||||
self.assertCountEqual(
|
||||
[e["code"] for e in json_data["errors"]],
|
||||
["invalid_formset"],
|
||||
)
|
||||
|
||||
def test_invalid_operation_not_purchase_with_cash(self):
|
||||
|
@ -2247,7 +2243,10 @@ class KPsulPerformOperationsViewTests(ViewTestCaseMixin, TestCase):
|
|||
|
||||
self.assertEqual(resp.status_code, 400)
|
||||
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):
|
||||
user_add_perms(self.users["team"], ["kfet.perform_deposit"])
|
||||
|
@ -2300,12 +2299,7 @@ class KPsulPerformOperationsViewTests(ViewTestCaseMixin, TestCase):
|
|||
|
||||
self.assertDictEqual(
|
||||
json_data,
|
||||
{
|
||||
"operationgroup": operation_group.pk,
|
||||
"operations": [operation.pk],
|
||||
"warnings": {},
|
||||
"errors": {},
|
||||
},
|
||||
{"errors": []},
|
||||
)
|
||||
|
||||
self.account.refresh_from_db()
|
||||
|
@ -2364,8 +2358,9 @@ class KPsulPerformOperationsViewTests(ViewTestCaseMixin, TestCase):
|
|||
|
||||
self.assertEqual(resp.status_code, 400)
|
||||
json_data = json.loads(resp.content.decode("utf-8"))
|
||||
self.assertEqual(
|
||||
json_data["errors"]["operations"], [{"__all__": ["Bad request"]}]
|
||||
self.assertCountEqual(
|
||||
[e["code"] for e in json_data["errors"]],
|
||||
["invalid_formset"],
|
||||
)
|
||||
|
||||
def test_invalid_deposit_too_many_params(self):
|
||||
|
@ -2383,8 +2378,9 @@ class KPsulPerformOperationsViewTests(ViewTestCaseMixin, TestCase):
|
|||
|
||||
self.assertEqual(resp.status_code, 400)
|
||||
json_data = json.loads(resp.content.decode("utf-8"))
|
||||
self.assertEqual(
|
||||
json_data["errors"]["operations"], [{"__all__": ["Bad request"]}]
|
||||
self.assertCountEqual(
|
||||
[e["code"] for e in json_data["errors"]],
|
||||
["invalid_formset"],
|
||||
)
|
||||
|
||||
def test_invalid_deposit_expects_positive_amount(self):
|
||||
|
@ -2402,8 +2398,9 @@ class KPsulPerformOperationsViewTests(ViewTestCaseMixin, TestCase):
|
|||
|
||||
self.assertEqual(resp.status_code, 400)
|
||||
json_data = json.loads(resp.content.decode("utf-8"))
|
||||
self.assertEqual(
|
||||
json_data["errors"]["operations"], [{"__all__": ["Charge non positive"]}]
|
||||
self.assertCountEqual(
|
||||
[e["code"] for e in json_data["errors"]],
|
||||
["invalid_formset"],
|
||||
)
|
||||
|
||||
def test_invalid_deposit_requires_perm(self):
|
||||
|
@ -2421,9 +2418,7 @@ class KPsulPerformOperationsViewTests(ViewTestCaseMixin, TestCase):
|
|||
|
||||
self.assertEqual(resp.status_code, 403)
|
||||
json_data = json.loads(resp.content.decode("utf-8"))
|
||||
self.assertEqual(
|
||||
json_data["errors"]["missing_perms"], ["[kfet] Effectuer une charge"]
|
||||
)
|
||||
self.assertEqual(json_data["missing_perms"], ["Effectuer une charge"])
|
||||
|
||||
def test_withdraw(self):
|
||||
data = dict(
|
||||
|
@ -2475,12 +2470,7 @@ class KPsulPerformOperationsViewTests(ViewTestCaseMixin, TestCase):
|
|||
|
||||
self.assertDictEqual(
|
||||
json_data,
|
||||
{
|
||||
"operationgroup": operation_group.pk,
|
||||
"operations": [operation.pk],
|
||||
"warnings": {},
|
||||
"errors": {},
|
||||
},
|
||||
{"errors": []},
|
||||
)
|
||||
|
||||
self.account.refresh_from_db()
|
||||
|
@ -2539,8 +2529,9 @@ class KPsulPerformOperationsViewTests(ViewTestCaseMixin, TestCase):
|
|||
|
||||
self.assertEqual(resp.status_code, 400)
|
||||
json_data = json.loads(resp.content.decode("utf-8"))
|
||||
self.assertEqual(
|
||||
json_data["errors"]["operations"], [{"__all__": ["Bad request"]}]
|
||||
self.assertCountEqual(
|
||||
[e["code"] for e in json_data["errors"]],
|
||||
["invalid_formset"],
|
||||
)
|
||||
|
||||
def test_invalid_withdraw_too_many_params(self):
|
||||
|
@ -2558,8 +2549,9 @@ class KPsulPerformOperationsViewTests(ViewTestCaseMixin, TestCase):
|
|||
|
||||
self.assertEqual(resp.status_code, 400)
|
||||
json_data = json.loads(resp.content.decode("utf-8"))
|
||||
self.assertEqual(
|
||||
json_data["errors"]["operations"], [{"__all__": ["Bad request"]}]
|
||||
self.assertCountEqual(
|
||||
[e["code"] for e in json_data["errors"]],
|
||||
["invalid_formset"],
|
||||
)
|
||||
|
||||
def test_invalid_withdraw_expects_negative_amount(self):
|
||||
|
@ -2577,8 +2569,9 @@ class KPsulPerformOperationsViewTests(ViewTestCaseMixin, TestCase):
|
|||
|
||||
self.assertEqual(resp.status_code, 400)
|
||||
json_data = json.loads(resp.content.decode("utf-8"))
|
||||
self.assertEqual(
|
||||
json_data["errors"]["operations"], [{"__all__": ["Retrait non négatif"]}]
|
||||
self.assertCountEqual(
|
||||
[e["code"] for e in json_data["errors"]],
|
||||
["invalid_formset"],
|
||||
)
|
||||
|
||||
def test_edit(self):
|
||||
|
@ -2634,12 +2627,7 @@ class KPsulPerformOperationsViewTests(ViewTestCaseMixin, TestCase):
|
|||
|
||||
self.assertDictEqual(
|
||||
json_data,
|
||||
{
|
||||
"operationgroup": operation_group.pk,
|
||||
"operations": [operation.pk],
|
||||
"warnings": {},
|
||||
"errors": {},
|
||||
},
|
||||
{"errors": []},
|
||||
)
|
||||
|
||||
self.account.refresh_from_db()
|
||||
|
@ -2700,8 +2688,8 @@ class KPsulPerformOperationsViewTests(ViewTestCaseMixin, TestCase):
|
|||
self.assertEqual(resp.status_code, 403)
|
||||
json_data = json.loads(resp.content.decode("utf-8"))
|
||||
self.assertEqual(
|
||||
json_data["errors"]["missing_perms"],
|
||||
["[kfet] Modifier la balance d'un compte"],
|
||||
json_data["missing_perms"],
|
||||
["Modifier la balance d'un compte"],
|
||||
)
|
||||
|
||||
def test_invalid_edit_expects_comment(self):
|
||||
|
@ -2721,7 +2709,7 @@ class KPsulPerformOperationsViewTests(ViewTestCaseMixin, TestCase):
|
|||
|
||||
self.assertEqual(resp.status_code, 400)
|
||||
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):
|
||||
self.register_user("addcost", create_user("addcost", "ADD"))
|
||||
|
@ -3008,62 +2996,10 @@ class KPsulPerformOperationsViewTests(ViewTestCaseMixin, TestCase):
|
|||
self.assertEqual(resp.status_code, 403)
|
||||
json_data = json.loads(resp.content.decode("utf-8"))
|
||||
self.assertEqual(
|
||||
json_data["errors"],
|
||||
{"missing_perms": ["[kfet] Enregistrer des commandes en négatif"]},
|
||||
json_data["missing_perms"],
|
||||
["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):
|
||||
user_add_perms(self.users["team"], ["kfet.perform_negative_operations"])
|
||||
kfet_config.set(overdraft_amount=Decimal("-1.00"))
|
||||
|
@ -3083,38 +3019,13 @@ class KPsulPerformOperationsViewTests(ViewTestCaseMixin, TestCase):
|
|||
)
|
||||
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"))
|
||||
self.assertEqual(json_data["errors"], {"negative": ["000"]})
|
||||
|
||||
def test_invalid_negative_exceeds_amount_allowed_from_account(self):
|
||||
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"),
|
||||
self.assertCountEqual(
|
||||
[e["code"] for e in json_data["errors"]],
|
||||
["negative"],
|
||||
)
|
||||
|
||||
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):
|
||||
article2 = Article.objects.create(
|
||||
name="Article 2",
|
||||
|
@ -3198,12 +3109,7 @@ class KPsulPerformOperationsViewTests(ViewTestCaseMixin, TestCase):
|
|||
# Check response content
|
||||
self.assertDictEqual(
|
||||
json_data,
|
||||
{
|
||||
"operationgroup": operation_group.pk,
|
||||
"operations": [operation_list[0].pk, operation_list[1].pk],
|
||||
"warnings": {},
|
||||
"errors": {},
|
||||
},
|
||||
{"errors": []},
|
||||
)
|
||||
|
||||
# Check object updates
|
||||
|
@ -3342,7 +3248,10 @@ class KPsulCancelOperationsViewTests(ViewTestCaseMixin, TestCase):
|
|||
|
||||
self.assertEqual(resp.status_code, 400)
|
||||
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):
|
||||
data = {"operations[]": ["1000"]}
|
||||
|
@ -3350,7 +3259,10 @@ class KPsulCancelOperationsViewTests(ViewTestCaseMixin, TestCase):
|
|||
|
||||
self.assertEqual(resp.status_code, 400)
|
||||
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")
|
||||
def test_purchase(self, now_mock):
|
||||
|
@ -3414,7 +3326,7 @@ class KPsulCancelOperationsViewTests(ViewTestCaseMixin, TestCase):
|
|||
"canceled_by__trigramme": None,
|
||||
}
|
||||
],
|
||||
"errors": {},
|
||||
"errors": [],
|
||||
"warnings": {},
|
||||
"opegroups_to_update": [
|
||||
{
|
||||
|
@ -3602,7 +3514,7 @@ class KPsulCancelOperationsViewTests(ViewTestCaseMixin, TestCase):
|
|||
"canceled_by__trigramme": None,
|
||||
}
|
||||
],
|
||||
"errors": {},
|
||||
"errors": [],
|
||||
"warnings": {},
|
||||
"opegroups_to_update": [
|
||||
{
|
||||
|
@ -3689,7 +3601,7 @@ class KPsulCancelOperationsViewTests(ViewTestCaseMixin, TestCase):
|
|||
"canceled_by__trigramme": None,
|
||||
}
|
||||
],
|
||||
"errors": {},
|
||||
"errors": [],
|
||||
"warnings": {},
|
||||
"opegroups_to_update": [
|
||||
{
|
||||
|
@ -3776,7 +3688,7 @@ class KPsulCancelOperationsViewTests(ViewTestCaseMixin, TestCase):
|
|||
"canceled_by__trigramme": None,
|
||||
}
|
||||
],
|
||||
"errors": {},
|
||||
"errors": [],
|
||||
"warnings": {},
|
||||
"opegroups_to_update": [
|
||||
{
|
||||
|
@ -3839,8 +3751,8 @@ class KPsulCancelOperationsViewTests(ViewTestCaseMixin, TestCase):
|
|||
self.assertEqual(resp.status_code, 403)
|
||||
json_data = json.loads(resp.content.decode("utf-8"))
|
||||
self.assertEqual(
|
||||
json_data["errors"],
|
||||
{"missing_perms": ["[kfet] Annuler des commandes non récentes"]},
|
||||
json_data["missing_perms"],
|
||||
["Annuler des commandes non récentes"],
|
||||
)
|
||||
|
||||
def test_already_canceled(self):
|
||||
|
@ -3964,9 +3876,12 @@ class KPsulCancelOperationsViewTests(ViewTestCaseMixin, TestCase):
|
|||
data = {"operations[]": [str(operation.pk)]}
|
||||
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"))
|
||||
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):
|
||||
kfet_config.set(overdraft_amount=Decimal("40.00"))
|
||||
|
@ -3985,8 +3900,8 @@ class KPsulCancelOperationsViewTests(ViewTestCaseMixin, TestCase):
|
|||
self.assertEqual(resp.status_code, 403)
|
||||
json_data = json.loads(resp.content.decode("utf-8"))
|
||||
self.assertEqual(
|
||||
json_data["errors"],
|
||||
{"missing_perms": ["[kfet] Enregistrer des commandes en négatif"]},
|
||||
json_data["missing_perms"],
|
||||
["Enregistrer des commandes en négatif"],
|
||||
)
|
||||
|
||||
def test_partial_0(self):
|
||||
|
@ -4036,7 +3951,7 @@ class KPsulCancelOperationsViewTests(ViewTestCaseMixin, TestCase):
|
|||
"canceled_by__trigramme": None,
|
||||
},
|
||||
],
|
||||
"errors": {},
|
||||
"errors": [],
|
||||
"warnings": {"already_canceled": [operation3.pk]},
|
||||
"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.db import transaction
|
||||
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 (
|
||||
Http404,
|
||||
HttpResponseBadRequest,
|
||||
|
@ -39,7 +39,6 @@ from kfet.decorators import teamkfet_required
|
|||
from kfet.forms import (
|
||||
AccountForm,
|
||||
AccountFrozenForm,
|
||||
AccountNegativeForm,
|
||||
AccountNoTriForm,
|
||||
AccountPwdForm,
|
||||
AccountStatForm,
|
||||
|
@ -355,11 +354,6 @@ def account_update(request, trigramme):
|
|||
frozen_form = AccountFrozenForm(request.POST, instance=account)
|
||||
pwd_form = AccountPwdForm()
|
||||
|
||||
if hasattr(account, "negative"):
|
||||
negative_form = AccountNegativeForm(instance=account.negative)
|
||||
else:
|
||||
negative_form = None
|
||||
|
||||
if request.method == "POST":
|
||||
self_update = request.user == account.user
|
||||
account_form = AccountForm(request.POST, instance=account)
|
||||
|
@ -381,14 +375,6 @@ def account_update(request, trigramme):
|
|||
elif group_form.has_changed():
|
||||
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
|
||||
if pwd_form.has_changed():
|
||||
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,
|
||||
"frozen_form": frozen_form,
|
||||
"group_form": group_form,
|
||||
"negative_form": negative_form,
|
||||
"pwd_form": pwd_form,
|
||||
},
|
||||
)
|
||||
|
@ -482,9 +467,11 @@ class AccountDelete(PermissionRequiredMixin, DeleteView):
|
|||
|
||||
|
||||
class AccountNegativeList(ListView):
|
||||
queryset = AccountNegative.objects.select_related(
|
||||
"account", "account__cofprofile__user"
|
||||
).exclude(account__trigramme="#13")
|
||||
queryset = (
|
||||
AccountNegative.objects.select_related("account", "account__cofprofile__user")
|
||||
.filter(account__balance__lt=0)
|
||||
.exclude(account__trigramme="#13")
|
||||
)
|
||||
template_name = "kfet/account_negative.html"
|
||||
context_object_name = "negatives"
|
||||
|
||||
|
@ -977,15 +964,18 @@ def kpsul_checkout_data(request):
|
|||
@kfet_password_auth
|
||||
def kpsul_update_addcost(request):
|
||||
addcost_form = AddcostForm(request.POST)
|
||||
data = {"errors": []}
|
||||
|
||||
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)
|
||||
|
||||
required_perms = ["kfet.manage_addcosts"]
|
||||
if not request.user.has_perms(required_perms):
|
||||
data = {
|
||||
"errors": {"missing_perms": get_missing_perms(required_perms, request.user)}
|
||||
}
|
||||
data["missing_perms"] = get_missing_perms(required_perms, request.user)
|
||||
return JsonResponse(data, status=403)
|
||||
|
||||
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_perm_description(app_label: str, codename: str) -> str:
|
||||
name = Permission.objects.values_list("name", flat=True).get(
|
||||
def get_perm_name(app_label: str, codename: str) -> str:
|
||||
return Permission.objects.values_list("name", flat=True).get(
|
||||
codename=codename, content_type__app_label=app_label
|
||||
)
|
||||
return "[{}] {}".format(app_label, name)
|
||||
|
||||
missing_perms = [
|
||||
get_perm_description(*perm.split("."))
|
||||
get_perm_name(*perm.split("."))
|
||||
for perm in required_perms
|
||||
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
|
||||
def kpsul_perform_operations(request):
|
||||
# Initializing response data
|
||||
data = {"operationgroup": 0, "operations": [], "warnings": {}, "errors": {}}
|
||||
data = {"errors": []}
|
||||
|
||||
# Checking operationgroup
|
||||
operationgroup_form = KPsulOperationGroupForm(request.POST)
|
||||
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
|
||||
operation_formset = KPsulOperationFormSet(request.POST)
|
||||
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
|
||||
if data["errors"]:
|
||||
|
@ -1038,6 +1041,7 @@ def kpsul_perform_operations(request):
|
|||
# Pre-saving (no commit)
|
||||
operationgroup = operationgroup_form.save(commit=False)
|
||||
operations = operation_formset.save(commit=False)
|
||||
on_acc = operationgroup.on_acc
|
||||
|
||||
# Retrieving COF grant
|
||||
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_checkout_balance = 0 # For balance of selected checkout
|
||||
to_articles_stocks = defaultdict(lambda: 0) # For stocks articles
|
||||
is_addcost = all(
|
||||
(addcost_for, addcost_amount, addcost_for != operationgroup.on_acc)
|
||||
)
|
||||
need_comment = operationgroup.on_acc.need_comment
|
||||
is_addcost = all((addcost_for, addcost_amount, addcost_for != on_acc))
|
||||
need_comment = on_acc.need_comment
|
||||
|
||||
if operationgroup.on_acc.is_frozen:
|
||||
data["errors"]["frozen"] = [operationgroup.on_acc.trigramme]
|
||||
if on_acc.is_frozen:
|
||||
data["errors"].append(
|
||||
{"code": "frozen_acc", "message": f"Le compte {on_acc.trigramme} est gelé"}
|
||||
)
|
||||
|
||||
# Filling data of each operations
|
||||
# + operationgroup + calculating other stuffs
|
||||
|
@ -1069,19 +1073,23 @@ def kpsul_perform_operations(request):
|
|||
operation.addcost_amount = addcost_amount * operation.article_nb
|
||||
operation.amount -= 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
|
||||
if (
|
||||
operationgroup.on_acc.is_cof
|
||||
and operation.article.category.has_reduction
|
||||
):
|
||||
if on_acc.is_cof and operation.article.category.has_reduction:
|
||||
if is_addcost and operation.article.category.has_addcost:
|
||||
operation.addcost_amount /= cof_grant_divisor
|
||||
operation.amount = operation.amount / cof_grant_divisor
|
||||
to_articles_stocks[operation.article] -= operation.article_nb
|
||||
else:
|
||||
if operationgroup.on_acc.is_cash:
|
||||
data["errors"]["account"] = "LIQ"
|
||||
if on_acc.is_cash:
|
||||
data["errors"].append(
|
||||
{
|
||||
"code": "invalid_liq",
|
||||
"message": (
|
||||
"Impossible de compter autre chose que des achats sur LIQ"
|
||||
),
|
||||
}
|
||||
)
|
||||
if operation.type != Operation.EDIT:
|
||||
to_checkout_balance += operation.amount
|
||||
operationgroup.amount += operation.amount
|
||||
|
@ -1090,41 +1098,42 @@ def kpsul_perform_operations(request):
|
|||
if operation.type == Operation.EDIT:
|
||||
required_perms.add("kfet.edit_balance_account")
|
||||
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
|
||||
|
||||
(perms, stop) = operationgroup.on_acc.perms_to_perform_operation(
|
||||
amount=operationgroup.amount
|
||||
)
|
||||
(perms, stop) = on_acc.perms_to_perform_operation(amount=operationgroup.amount)
|
||||
required_perms |= perms
|
||||
|
||||
if stop:
|
||||
data["errors"].append(
|
||||
{
|
||||
"code": "negative",
|
||||
"message": f"Le compte {on_acc.trigramme} a un solde insuffisant.",
|
||||
}
|
||||
)
|
||||
|
||||
if need_comment:
|
||||
operationgroup.comment = operationgroup.comment.strip()
|
||||
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)
|
||||
|
||||
if stop 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:
|
||||
data["errors"]["negative"] = [operationgroup.on_acc.trigramme]
|
||||
if not request.user.has_perms(required_perms):
|
||||
data["missing_perms"] = get_missing_perms(required_perms, request.user)
|
||||
return JsonResponse(data, status=403)
|
||||
|
||||
# If 1 perm is required, filling who perform the operations
|
||||
if required_perms:
|
||||
operationgroup.valid_by = request.user.profile.account_kfet
|
||||
# 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
|
||||
with transaction.atomic():
|
||||
# If not cash account,
|
||||
# saving account's balance and adding to Negative if not in
|
||||
on_acc = operationgroup.on_acc
|
||||
if not on_acc.is_cash:
|
||||
(
|
||||
Account.objects.filter(pk=on_acc.pk).update(
|
||||
|
@ -1148,13 +1157,10 @@ def kpsul_perform_operations(request):
|
|||
|
||||
# Saving operation group
|
||||
operationgroup.save()
|
||||
data["operationgroup"] = operationgroup.pk
|
||||
|
||||
# Filling operationgroup id for each operations and saving
|
||||
for operation in operations:
|
||||
operation.group = operationgroup
|
||||
operation.save()
|
||||
data["operations"].append(operation.pk)
|
||||
|
||||
# Updating articles stock
|
||||
for article in to_articles_stocks:
|
||||
|
@ -1177,7 +1183,7 @@ def kpsul_perform_operations(request):
|
|||
"valid_by__trigramme": (
|
||||
operationgroup.valid_by and operationgroup.valid_by.trigramme or None
|
||||
),
|
||||
"on_acc__trigramme": operationgroup.on_acc.trigramme,
|
||||
"on_acc__trigramme": on_acc.trigramme,
|
||||
"entries": [],
|
||||
}
|
||||
]
|
||||
|
@ -1218,7 +1224,7 @@ def kpsul_perform_operations(request):
|
|||
@kfet_password_auth
|
||||
def cancel_operations(request):
|
||||
# Pour la réponse
|
||||
data = {"canceled": [], "warnings": {}, "errors": {}}
|
||||
data = {"canceled": [], "warnings": {}, "errors": []}
|
||||
|
||||
# Checking if BAD REQUEST (opes_pk not int or not existing)
|
||||
try:
|
||||
|
@ -1227,29 +1233,41 @@ def cancel_operations(request):
|
|||
map(int, filter(None, request.POST.getlist("operations[]", [])))
|
||||
)
|
||||
except ValueError:
|
||||
data["errors"].append(
|
||||
{"code": "invalid_request", "message": "Requête invalide !"}
|
||||
)
|
||||
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
|
||||
data["errors"].append(
|
||||
{
|
||||
"code": "cancel_missing",
|
||||
"message": "Opérations inexistantes : {}".format(
|
||||
", ".join(map(str, 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 = kfet_config.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
|
||||
|
||||
# Modifs à faire sur les balances des comptes
|
||||
to_accounts_balances = defaultdict(int)
|
||||
# ------ sur les montants des groupes d'opé
|
||||
to_groups_amounts = defaultdict(int)
|
||||
# ------ sur les balances de caisses
|
||||
to_checkouts_balances = defaultdict(int)
|
||||
# ------ sur les stocks d'articles
|
||||
to_articles_stocks = defaultdict(int)
|
||||
|
||||
for ope in opes_all:
|
||||
if ope.canceled_at:
|
||||
# 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]
|
||||
)
|
||||
required_perms |= perms
|
||||
stop_all = stop_all or stop
|
||||
if stop:
|
||||
negative_accounts.append(account.trigramme)
|
||||
|
||||
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"] = negative_accounts
|
||||
if negative_accounts:
|
||||
data["errors"].append(
|
||||
{
|
||||
"code": "negative",
|
||||
"message": "Solde insuffisant pour les comptes suivants : {}".format(
|
||||
", ".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)
|
||||
|
||||
canceled_by = required_perms and request.user.profile.account_kfet or None
|
||||
|
@ -1657,12 +1681,36 @@ def transfers_create(request):
|
|||
@teamkfet_required
|
||||
@kfet_password_auth
|
||||
def perform_transfers(request):
|
||||
data = {"errors": {}, "transfers": [], "transfergroup": 0}
|
||||
data = {"errors": []}
|
||||
|
||||
# Checking transfer_formset
|
||||
transfer_formset = TransferFormSet(request.POST)
|
||||
try:
|
||||
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)
|
||||
|
||||
|
@ -1670,14 +1718,12 @@ def perform_transfers(request):
|
|||
required_perms = set(
|
||||
["kfet.add_transfer"]
|
||||
) # 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:
|
||||
to_accounts_balances[transfer.from_acc] -= transfer.amount
|
||||
to_accounts_balances[transfer.to_acc] += transfer.amount
|
||||
|
||||
stop_all = False
|
||||
|
||||
negative_accounts = []
|
||||
# Checking if ok on all accounts
|
||||
frozen = set()
|
||||
|
@ -1689,20 +1735,34 @@ def perform_transfers(request):
|
|||
amount=to_accounts_balances[account]
|
||||
)
|
||||
required_perms |= perms
|
||||
stop_all = stop_all or stop
|
||||
if stop:
|
||||
negative_accounts.append(account.trigramme)
|
||||
|
||||
if len(frozen):
|
||||
data["errors"]["frozen"] = list(frozen)
|
||||
if 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)
|
||||
|
||||
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"] = negative_accounts
|
||||
if not request.user.has_perms(required_perms):
|
||||
data["missing_perms"] = get_missing_perms(required_perms, request.user)
|
||||
return JsonResponse(data, status=403)
|
||||
|
||||
# Creating transfer group
|
||||
|
@ -1724,22 +1784,20 @@ def perform_transfers(request):
|
|||
|
||||
# Saving transfer group
|
||||
transfergroup.save()
|
||||
data["transfergroup"] = transfergroup.pk
|
||||
|
||||
# Saving all transfers with group
|
||||
for transfer in transfers:
|
||||
transfer.group = transfergroup
|
||||
transfer.save()
|
||||
data["transfers"].append(transfer.pk)
|
||||
|
||||
return JsonResponse(data)
|
||||
return JsonResponse({})
|
||||
|
||||
|
||||
@teamkfet_required
|
||||
@kfet_password_auth
|
||||
def cancel_transfers(request):
|
||||
# Pour la réponse
|
||||
data = {"canceled": [], "warnings": {}, "errors": {}}
|
||||
data = {"canceled": [], "warnings": {}, "errors": []}
|
||||
|
||||
# Checking if BAD REQUEST (transfers_pk not int or not existing)
|
||||
try:
|
||||
|
@ -1748,7 +1806,11 @@ def cancel_transfers(request):
|
|||
map(int, filter(None, request.POST.getlist("transfers[]", [])))
|
||||
)
|
||||
except ValueError:
|
||||
data["errors"].append(
|
||||
{"code": "invalid_request", "message": "Requête invalide !"}
|
||||
)
|
||||
return JsonResponse(data, status=400)
|
||||
|
||||
transfers_all = Transfer.objects.select_related(
|
||||
"group", "from_acc", "from_acc__negative", "to_acc", "to_acc__negative"
|
||||
).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
|
||||
]
|
||||
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)
|
||||
|
||||
transfers_already_canceled = [] # Déjà annulée
|
||||
transfers = [] # Pas déjà annulée
|
||||
transfers_already_canceled = [] # Déjà annulés
|
||||
transfers = [] # Pas déjà annulés
|
||||
required_perms = set()
|
||||
stop_all = False
|
||||
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:
|
||||
if transfer.canceled_at:
|
||||
# Transfert déjà annulé, va pour un warning en Response
|
||||
|
@ -1795,16 +1863,22 @@ def cancel_transfers(request):
|
|||
amount=to_accounts_balances[account]
|
||||
)
|
||||
required_perms |= perms
|
||||
stop_all = stop_all or stop
|
||||
if stop:
|
||||
negative_accounts.append(account.trigramme)
|
||||
|
||||
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"] = negative_accounts
|
||||
if negative_accounts:
|
||||
data["errors"].append(
|
||||
{
|
||||
"code": "negative",
|
||||
"message": "Solde insuffisant pour les comptes suivants : {}".format(
|
||||
", ".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)
|
||||
|
||||
canceled_by = required_perms and request.user.profile.account_kfet or None
|
||||
|
|
Loading…
Reference in a new issue