diff --git a/apps/member/migrations/0003_create_bde_and_kfet.py b/apps/member/migrations/0003_create_bde_and_kfet.py index 0464569f..5f218040 100644 --- a/apps/member/migrations/0003_create_bde_and_kfet.py +++ b/apps/member/migrations/0003_create_bde_and_kfet.py @@ -27,8 +27,8 @@ def create_bde_and_kfet(apps, schema_editor): parent_club_id=1, email="tresorerie.bde@example.com", require_memberships=True, - membership_fee_paid=500, - membership_fee_unpaid=500, + membership_fee_paid=3500, + membership_fee_unpaid=3500, membership_duration=396, membership_start="2020-08-01", membership_end="2021-09-30", diff --git a/apps/member/views.py b/apps/member/views.py index d0ae106a..033533ff 100644 --- a/apps/member/views.py +++ b/apps/member/views.py @@ -138,7 +138,7 @@ class UserDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView): """ We can't display information of a not registered user. """ - return super().get_queryset().filter(profile__registration_valid=True) + return super().get_queryset(**kwargs).filter(profile__registration_valid=True) def get_context_data(self, **kwargs): """ diff --git a/apps/note/models/transactions.py b/apps/note/models/transactions.py index b89b405f..c28d66da 100644 --- a/apps/note/models/transactions.py +++ b/apps/note/models/transactions.py @@ -175,7 +175,7 @@ class Transaction(PolymorphicModel): created = self.pk is None to_transfer = self.amount * self.quantity - if not created: + if not created and not self.valid and not hasattr(self, "_force_save"): # Revert old transaction old_transaction = Transaction.objects.get(pk=self.pk) # Check that nothing important changed diff --git a/apps/permission/views.py b/apps/permission/views.py index 70aa7184..343152f5 100644 --- a/apps/permission/views.py +++ b/apps/permission/views.py @@ -8,6 +8,7 @@ from django.contrib.auth.models import User from django.core.exceptions import PermissionDenied from django.db.models import Q from django.forms import HiddenInput +from django.http import Http404 from django.utils.translation import gettext_lazy as _ from django.views.generic import UpdateView, TemplateView, CreateView from member.models import Membership @@ -24,9 +25,20 @@ class ProtectQuerysetMixin: Display 404 error if the user can't see an object, remove the fields the user can't update on an update form (useful if the user can't change only specified fields). """ - def get_queryset(self, **kwargs): + def get_queryset(self, filter_permissions=True, **kwargs): qs = super().get_queryset(**kwargs) - return qs.filter(PermissionBackend.filter_queryset(self.request.user, qs.model, "view")).distinct() + return qs.filter(PermissionBackend.filter_queryset(self.request.user, qs.model, "view")).distinct()\ + if filter_permissions else qs + + def get_object(self, queryset=None): + try: + return super().get_object(queryset) + except Http404 as e: + try: + super().get_object(self.get_queryset(filter_permissions=False)) + raise PermissionDenied() + except Http404: + raise e def get_form(self, form_class=None): form = super().get_form(form_class) diff --git a/apps/registration/tests/test_registration.py b/apps/registration/tests/test_registration.py index e2191445..adb773f9 100644 --- a/apps/registration/tests/test_registration.py +++ b/apps/registration/tests/test_registration.py @@ -364,7 +364,7 @@ class TestValidateRegistration(TestCase): self.assertTrue(Membership.objects.filter(club__name="Kfet", user=self.user).exists()) self.assertTrue(SogeCredit.objects.filter(user=self.user).exists()) self.assertEqual(Transaction.objects.filter( - Q(source=self.user.note) | Q(destination=self.user.note)).count(), 2) + Q(source=self.user.note) | Q(destination=self.user.note)).count(), 3) self.assertFalse(Transaction.objects.filter(valid=True).exists()) response = self.client.get(self.user.profile.get_absolute_url()) diff --git a/apps/registration/views.py b/apps/registration/views.py index 7a924591..1f71931e 100644 --- a/apps/registration/views.py +++ b/apps/registration/views.py @@ -21,6 +21,7 @@ from note.templatetags.pretty_money import pretty_money from permission.backends import PermissionBackend from permission.models import Role from permission.views import ProtectQuerysetMixin +from treasury.models import SogeCredit from .forms import SignUpForm, ValidationForm from .tables import FutureUserTable @@ -219,6 +220,9 @@ class FutureUserDetailView(ProtectQuerysetMixin, LoginRequiredMixin, FormMixin, fee += bde.membership_fee_paid if user.profile.paid else bde.membership_fee_unpaid kfet = Club.objects.get(name="Kfet") fee += kfet.membership_fee_paid if user.profile.paid else kfet.membership_fee_unpaid + # In 2020, for COVID-19 reasons, the BDE offered 80 € to each new member that opens a Sogé account, + # since there is no WEI. + fee += 8000 ctx["total_fee"] = "{:.02f}".format(fee / 100, ) return ctx @@ -342,6 +346,11 @@ class FutureUserDetailView(ProtectQuerysetMixin, LoginRequiredMixin, FormMixin, membership.roles.add(Role.objects.get(name="Adhérent Kfet")) membership.save() + if soge: + soge_credit = SogeCredit.objects.get(user=user) + # Update the credit transaction amount + soge_credit.save() + return ret def get_success_url(self): diff --git a/apps/treasury/models.py b/apps/treasury/models.py index 175829fe..d611650f 100644 --- a/apps/treasury/models.py +++ b/apps/treasury/models.py @@ -291,11 +291,11 @@ class SogeCredit(models.Model): @property def valid(self): - return self.credit_transaction is not None + return self.credit_transaction.valid @property def amount(self): - return sum(transaction.total for transaction in self.transactions.all()) + return sum(transaction.total for transaction in self.transactions.all()) + 8000 def invalidate(self): """ @@ -304,11 +304,7 @@ class SogeCredit(models.Model): """ if self.valid: self.credit_transaction.valid = False - self.credit_transaction._force_save = True self.credit_transaction.save() - self.credit_transaction._force_delete = True - self.credit_transaction.delete() - self.credit_transaction = None for transaction in self.transactions.all(): transaction.valid = False transaction._force_save = True @@ -321,17 +317,10 @@ class SogeCredit(models.Model): # First invalidate all transaction and delete the credit if already did (and force mode) self.invalidate() - self.credit_transaction = SpecialTransaction.objects.create( - source=NoteSpecial.objects.get(special_type="Virement bancaire"), - destination=self.user.note, - quantity=1, - amount=self.amount, - reason="Crédit société générale", - last_name=self.user.last_name, - first_name=self.user.first_name, - bank="Société générale", - created_at=self.transactions.order_by("-created_at").first().created_at, - ) + # Refresh credit amount + self.save() + self.credit_transaction.valid = True + self.credit_transaction.save() self.save() for transaction in self.transactions.all(): @@ -340,6 +329,25 @@ class SogeCredit(models.Model): transaction.created_at = timezone.now() transaction.save() + def save(self, *args, **kwargs): + if not self.credit_transaction: + self.credit_transaction = SpecialTransaction.objects.create( + source=NoteSpecial.objects.get(special_type="Virement bancaire"), + destination=self.user.note, + quantity=1, + amount=0, + reason="Crédit société générale", + last_name=self.user.last_name, + first_name=self.user.first_name, + bank="Société générale", + valid=False, + ) + elif not self.valid: + self.credit_transaction.amount = self.amount + self.credit_transaction._force_save = True + self.credit_transaction.save() + super().save(*args, **kwargs) + def delete(self, **kwargs): """ Deleting a SogeCredit is equivalent to say that the Société générale didn't pay. @@ -358,6 +366,9 @@ class SogeCredit(models.Model): transaction.valid = True transaction.created_at = timezone.now() transaction.save() + self.credit_transaction.valid = False + self.credit_transaction.reason += " (invalide)" + self.credit_transaction.save() super().delete(**kwargs) class Meta: diff --git a/apps/treasury/templates/treasury/sogecredit_list.html b/apps/treasury/templates/treasury/sogecredit_list.html index 1eb1aba5..c561da72 100644 --- a/apps/treasury/templates/treasury/sogecredit_list.html +++ b/apps/treasury/templates/treasury/sogecredit_list.html @@ -30,7 +30,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
diff --git a/apps/treasury/tests/test_treasury.py b/apps/treasury/tests/test_treasury.py index 0d6e5d62..505453b9 100644 --- a/apps/treasury/tests/test_treasury.py +++ b/apps/treasury/tests/test_treasury.py @@ -353,7 +353,6 @@ class TestSogeCredits(TestCase): soge_credit.refresh_from_db() self.assertTrue(soge_credit.valid) self.user.note.refresh_from_db() - self.assertEqual(self.user.note.balance, 0) self.assertEqual( Transaction.objects.filter(Q(source=self.user.note) | Q(destination=self.user.note)).count(), 3) self.assertTrue(self.user.profile.soge) @@ -391,7 +390,7 @@ class TestSogeCredits(TestCase): self.user.note.refresh_from_db() self.assertEqual(self.user.note.balance, 0) self.assertEqual( - Transaction.objects.filter(Q(source=self.user.note) | Q(destination=self.user.note)).count(), 3) + Transaction.objects.filter(Q(source=self.user.note) | Q(destination=self.user.note)).count(), 4) self.assertFalse(self.user.profile.soge) def test_invoice_api(self): diff --git a/apps/treasury/views.py b/apps/treasury/views.py index a8e03cbe..ecefc5cf 100644 --- a/apps/treasury/views.py +++ b/apps/treasury/views.py @@ -425,11 +425,8 @@ class SogeCreditListView(LoginRequiredMixin, ProtectQuerysetMixin, SingleTableVi | Q(user__note__alias__normalized_name__iregex="^" + Alias.normalize(pattern)) ) - if "valid" in self.request.GET: - q = Q(credit_transaction=None) - if not self.request.GET["valid"]: - q = ~q - qs = qs.filter(q) + if "valid" not in self.request.GET or not self.request.GET["valid"]: + qs = qs.filter(credit_transaction__valid=False) return qs[:20]