From 00edcabb8ef171c6f88f984dc9fa8cf60962f1b5 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Doderlein Date: Sun, 31 Jul 2022 11:38:35 +0000 Subject: [PATCH 1/2] Remove soge, need to fix perm --- .env_example | 2 +- .gitpod.yml | 35 ++++ apps/member/templates/member/add_members.html | 11 -- .../tests/test_permission_denied.py | 3 - apps/registration/forms.py | 14 -- apps/registration/tables.py | 4 +- .../registration/future_profile_detail.html | 20 -- apps/registration/tests/test_registration.py | 46 ----- apps/registration/views.py | 45 +---- apps/treasury/admin.py | 15 +- apps/treasury/api/serializers.py | 20 +- apps/treasury/api/urls.py | 3 +- apps/treasury/api/views.py | 20 +- apps/treasury/forms.py | 17 +- apps/treasury/models.py | 180 ------------------ apps/treasury/tables.py | 27 +-- .../templates/treasury/invoice_list.html | 3 - .../templates/treasury/remittance_list.html | 3 - apps/treasury/tests/test_treasury.py | 117 +----------- apps/treasury/urls.py | 5 +- apps/treasury/views.py | 73 +------ 21 files changed, 57 insertions(+), 606 deletions(-) create mode 100644 .gitpod.yml diff --git a/.env_example b/.env_example index 7e1dbd3..1793552 100644 --- a/.env_example +++ b/.env_example @@ -1,4 +1,4 @@ -DJANGO_APP_STAGE=prod +DJANGO_APP_STAGE=dev # Only used in dev mode, change to "postgresql" if you want to use PostgreSQL in dev DJANGO_DEV_STORE_METHOD=sqlite DJANGO_DB_HOST=localhost diff --git a/.gitpod.yml b/.gitpod.yml new file mode 100644 index 0000000..b488382 --- /dev/null +++ b/.gitpod.yml @@ -0,0 +1,35 @@ +# This configuration file was automatically generated by Gitpod. +# Please adjust to your needs (see https://www.gitpod.io/docs/config-gitpod-file) +# and commit this file to your remote git repository to share the goodness with others. + +tasks: + - name: Apt update + init: sudo apt update + - name: Apt install + init: sudo apt install --no-install-recommends -y \ + ipython3 python3-setuptools python3-venv python3-dev \ + texlive-xetex gettext libjs-bootstrap4 fonts-font-awesome git + - name : Install requirements + init: pip3 install -r requirements.txt + - name : Setup env + init: cp .env_example .env + - name: Django collectstatic + init: python3 manage.py collectstatic --noinput + - name: Django compilemessages + init: python3 manage.py compilemessages + - name: Django makemigrations + init: python3 manage.py makemigrations + - name: Django migrate + init: python3 manage.py migrate + - name: Django loaddata + init: python3 manage.py loaddata initial + - name: Django create dev superuser + init: python3 manage.py shell -c "from django.contrib.auth.models import User; User.objects.create_superuser('admin', 'admin@example.com', 'adminpass')" + - name: Django start server + command: python3 manage.py runserver 0.0.0.0:8000 +ports: + - port: 8000 + onOpen: open-preview + + + diff --git a/apps/member/templates/member/add_members.html b/apps/member/templates/member/add_members.html index 4fd1a83..07b4931 100644 --- a/apps/member/templates/member/add_members.html +++ b/apps/member/templates/member/add_members.html @@ -60,17 +60,8 @@ SPDX-License-Identifier: GPL-3.0-or-later }); } - soge_field = $("#id_soge"); function fillFields() { - let checked = soge_field.is(':checked'); - if (!checked) { - $("input").attr('disabled', false); - $("#id_user").attr('disabled', true); - $("select").attr('disabled', false); - return; - } - let credit_type = $("#id_credit_type"); credit_type.attr('disabled', true); credit_type.val(4); @@ -83,7 +74,5 @@ SPDX-License-Identifier: GPL-3.0-or-later bank.attr('disabled', true); bank.val('Société générale'); } - - soge_field.change(fillFields); {% endblock %} \ No newline at end of file diff --git a/apps/permission/tests/test_permission_denied.py b/apps/permission/tests/test_permission_denied.py index 1aa3e9e..4a86375 100644 --- a/apps/permission/tests/test_permission_denied.py +++ b/apps/permission/tests/test_permission_denied.py @@ -146,9 +146,6 @@ class TestPermissionDenied(TestCase): response = self.client.get(reverse("treasury:remittance_list")) self.assertEqual(response.status_code, 403) - def test_list_soge_credits(self): - response = self.client.get(reverse("treasury:soge_credits")) - self.assertEqual(response.status_code, 403) class TestLoginRedirect(TestCase): diff --git a/apps/registration/forms.py b/apps/registration/forms.py index 64e79d5..3b6f088 100644 --- a/apps/registration/forms.py +++ b/apps/registration/forms.py @@ -44,15 +44,6 @@ class SignUpForm(UserCreationForm): fields = ('first_name', 'last_name', 'username', 'email', ) -class DeclareSogeAccountOpenedForm(forms.Form): - soge_account = forms.BooleanField( - label=_("I declare that I opened or I will open soon a bank account in the Société générale with the BDE " - "partnership."), - help_text=_("Warning: this engages you to open your bank account. If you finally decides to don't open your " - "account, you will have to pay the BDE membership."), - required=False, - ) - class WEISignupForm(forms.Form): wei_registration = forms.BooleanField( @@ -67,11 +58,6 @@ class ValidationForm(forms.Form): """ Validate the inscription of the new users and pay memberships. """ - soge = forms.BooleanField( - label=_("Inscription paid by Société Générale"), - required=False, - help_text=_("Check this case if the Société Générale paid the inscription."), - ) credit_type = forms.ModelChoiceField( queryset=NoteSpecial.objects, diff --git a/apps/registration/tables.py b/apps/registration/tables.py index 960866a..10b439d 100644 --- a/apps/registration/tables.py +++ b/apps/registration/tables.py @@ -3,7 +3,6 @@ import django_tables2 as tables from django.contrib.auth.models import User -from treasury.models import SogeCredit class FutureUserTable(tables.Table): @@ -22,7 +21,6 @@ class FutureUserTable(tables.Table): fields = ('last_name', 'first_name', 'username', 'email', ) model = User row_attrs = { - 'class': lambda record: 'table-row' - + (' bg-warning' if SogeCredit.objects.filter(user=record).exists() else ''), + 'class': lambda record: 'table-row', 'data-href': lambda record: record.pk } diff --git a/apps/registration/templates/registration/future_profile_detail.html b/apps/registration/templates/registration/future_profile_detail.html index 577ad21..bb42475 100644 --- a/apps/registration/templates/registration/future_profile_detail.html +++ b/apps/registration/templates/registration/future_profile_detail.html @@ -57,12 +57,6 @@ SPDX-License-Identifier: GPL-3.0-or-later

{% trans "Validate account" %}

- {% if declare_soge_account %} -
- {% trans "The user declared that he/she opened a bank account in the Société générale." %} -
- {% endif %} -
{% csrf_token %} {{ form|crispy }} @@ -78,16 +72,8 @@ SPDX-License-Identifier: GPL-3.0-or-later {% block extrajavascript %} {% endblock %} diff --git a/apps/registration/tests/test_registration.py b/apps/registration/tests/test_registration.py index 18cf16d..4bf27fa 100644 --- a/apps/registration/tests/test_registration.py +++ b/apps/registration/tests/test_registration.py @@ -10,7 +10,6 @@ from django.utils.http import urlsafe_base64_encode from member.models import Club, Membership from note.models import NoteUser, NoteSpecial, Transaction from registration.tokens import email_validation_token -from treasury.models import SogeCredit """ Check that pre-registrations and validations are working as well. @@ -190,7 +189,6 @@ class TestValidateRegistration(TestCase): # BDE Membership is mandatory response = self.client.post(reverse("registration:future_user_detail", args=(self.user.pk,)), data=dict( - soge=False, credit_type=NoteSpecial.objects.get(special_type="Chèque").id, credit_amount=4200, last_name="TOTO", @@ -204,7 +202,6 @@ class TestValidateRegistration(TestCase): # Same response = self.client.post(reverse("registration:future_user_detail", args=(self.user.pk,)), data=dict( - soge=False, credit_type="", credit_amount=0, last_name="TOTO", @@ -218,7 +215,6 @@ class TestValidateRegistration(TestCase): # The BDE membership is not free response = self.client.post(reverse("registration:future_user_detail", args=(self.user.pk,)), data=dict( - soge=False, credit_type=NoteSpecial.objects.get(special_type="Espèces").id, credit_amount=0, last_name="TOTO", @@ -232,7 +228,6 @@ class TestValidateRegistration(TestCase): # Last and first names are required for a credit response = self.client.post(reverse("registration:future_user_detail", args=(self.user.pk,)), data=dict( - soge=False, credit_type=NoteSpecial.objects.get(special_type="Chèque").id, credit_amount=4000, last_name="", @@ -249,7 +244,6 @@ class TestValidateRegistration(TestCase): self.user.username = "admïntoto" self.user.save() response = self.client.post(reverse("registration:future_user_detail", args=(self.user.pk,)), data=dict( - soge=False, credit_type=NoteSpecial.objects.get(special_type="Chèque").id, credit_amount=500, last_name="TOTO", @@ -275,7 +269,6 @@ class TestValidateRegistration(TestCase): self.user.profile.save() response = self.client.post(reverse("registration:future_user_detail", args=(self.user.pk,)), data=dict( - soge=False, credit_type=NoteSpecial.objects.get(special_type="Chèque").id, credit_amount=500, last_name="TOTO", @@ -290,7 +283,6 @@ class TestValidateRegistration(TestCase): self.assertTrue(NoteUser.objects.filter(user=self.user).exists()) self.assertTrue(Membership.objects.filter(club__name="BDE", user=self.user).exists()) self.assertFalse(Membership.objects.filter(club__name="Kfet", user=self.user).exists()) - self.assertFalse(SogeCredit.objects.filter(user=self.user).exists()) self.assertEqual(Transaction.objects.filter( Q(source=self.user.note) | Q(destination=self.user.note)).count(), 2) @@ -311,7 +303,6 @@ class TestValidateRegistration(TestCase): self.user.profile.save() response = self.client.post(reverse("registration:future_user_detail", args=(self.user.pk,)), data=dict( - soge=False, credit_type=NoteSpecial.objects.get(special_type="Espèces").id, credit_amount=4000, last_name="TOTO", @@ -326,49 +317,12 @@ class TestValidateRegistration(TestCase): self.assertTrue(NoteUser.objects.filter(user=self.user).exists()) self.assertTrue(Membership.objects.filter(club__name="BDE", user=self.user).exists()) self.assertTrue(Membership.objects.filter(club__name="Kfet", user=self.user).exists()) - self.assertFalse(SogeCredit.objects.filter(user=self.user).exists()) self.assertEqual(Transaction.objects.filter( Q(source=self.user.note) | Q(destination=self.user.note)).count(), 3) response = self.client.get(self.user.profile.get_absolute_url()) self.assertEqual(response.status_code, 200) - def test_validate_kfet_registration_with_soge(self): - """ - The user joins the BDE and the Kfet, but the membership is paid by the Société générale. - """ - response = self.client.get(reverse("registration:future_user_detail", args=(self.user.pk,))) - self.assertEqual(response.status_code, 200) - - response = self.client.get(self.user.profile.get_absolute_url()) - self.assertEqual(response.status_code, 404) - - self.user.profile.email_confirmed = True - self.user.profile.save() - - response = self.client.post(reverse("registration:future_user_detail", args=(self.user.pk,)), data=dict( - soge=True, - credit_type=NoteSpecial.objects.get(special_type="Espèces").id, - credit_amount=4000, - last_name="TOTO", - first_name="Toto", - bank="Société générale", - join_bde=True, - join_kfet=True, - )) - self.assertRedirects(response, self.user.profile.get_absolute_url(), 302, 200) - self.user.profile.refresh_from_db() - self.assertTrue(self.user.profile.registration_valid) - self.assertTrue(NoteUser.objects.filter(user=self.user).exists()) - self.assertTrue(Membership.objects.filter(club__name="BDE", user=self.user).exists()) - 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(), 3) - self.assertFalse(Transaction.objects.filter(valid=True).exists()) - - response = self.client.get(self.user.profile.get_absolute_url()) - self.assertEqual(response.status_code, 200) def test_invalidate_registration(self): """ diff --git a/apps/registration/views.py b/apps/registration/views.py index b256f59..684d9fc 100644 --- a/apps/registration/views.py +++ b/apps/registration/views.py @@ -22,9 +22,8 @@ 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, DeclareSogeAccountOpenedForm +from .forms import SignUpForm, ValidationForm from .tables import FutureUserTable from .tokens import email_validation_token @@ -42,7 +41,6 @@ class UserCreateView(CreateView): def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context["profile_form"] = self.second_form(self.request.POST if self.request.POST else None) - context["soge_form"] = DeclareSogeAccountOpenedForm(self.request.POST if self.request.POST else None) del context["profile_form"].fields["section"] del context["profile_form"].fields["report_frequency"] del context["profile_form"].fields["last_report"] @@ -75,13 +73,6 @@ class UserCreateView(CreateView): user.profile.send_email_validation_link() - soge_form = DeclareSogeAccountOpenedForm(self.request.POST) - if "soge_account" in soge_form.data and soge_form.data["soge_account"]: - # If the user declares that a bank account got opened, prepare the soge credit to warn treasurers - soge_credit = SogeCredit(user=user) - soge_credit._force_save = True - soge_credit.save() - return super().form_valid(form) def get_success_url(self): @@ -239,8 +230,6 @@ class FutureUserDetailView(ProtectQuerysetMixin, LoginRequiredMixin, FormMixin, fee += kfet.membership_fee_paid if user.profile.paid else kfet.membership_fee_unpaid ctx["total_fee"] = "{:.02f}".format(fee / 100, ) - ctx["declare_soge_account"] = SogeCredit.objects.filter(user=user).exists() - return ctx def get_form(self, form_class=None): @@ -263,7 +252,6 @@ class FutureUserDetailView(ProtectQuerysetMixin, LoginRequiredMixin, FormMixin, return self.form_invalid(form) # Get form data - soge = form.cleaned_data["soge"] credit_type = form.cleaned_data["credit_type"] credit_amount = form.cleaned_data["credit_amount"] last_name = form.cleaned_data["last_name"] @@ -272,10 +260,6 @@ class FutureUserDetailView(ProtectQuerysetMixin, LoginRequiredMixin, FormMixin, join_bde = form.cleaned_data["join_bde"] join_kfet = form.cleaned_data["join_kfet"] - if soge: - # If Société Générale pays the inscription, the user automatically joins the two clubs. - join_bde = True - join_kfet = True if not join_bde: # This software belongs to the BDE. @@ -295,12 +279,12 @@ class FutureUserDetailView(ProtectQuerysetMixin, LoginRequiredMixin, FormMixin, # If the bank pays, then we don't credit now. Treasurers will validate the transaction # and credit the note later. - credit_type = None if soge else credit_type + credit_type = credit_type # If the user does not select any payment method, then no credit will be performed. credit_amount = 0 if credit_type is None else credit_amount - if fee > credit_amount and not soge: + if fee > credit_amount: # Check if the user credits enough money form.add_error('credit_type', _("The entered amount is not enough for the memberships, should be at least {}") @@ -320,13 +304,6 @@ class FutureUserDetailView(ProtectQuerysetMixin, LoginRequiredMixin, FormMixin, user.profile.save() user.refresh_from_db() - if not soge and SogeCredit.objects.filter(user=user).exists(): - # If the user declared that a bank account was opened but in the validation form the SoGé case was - # unchecked, delete the associated credit - soge_credit = SogeCredit.objects.get(user=user) - soge_credit._force_delete = True - soge_credit.delete() - if credit_type is not None and credit_amount > 0: # Credit the note SpecialTransaction.objects.create( @@ -334,7 +311,7 @@ class FutureUserDetailView(ProtectQuerysetMixin, LoginRequiredMixin, FormMixin, destination=user.note, quantity=1, amount=credit_amount, - reason="Crédit " + ("Société générale" if soge else credit_type.special_type) + " (Inscription)", + reason="Crédit " + credit_type.special_type + " (Inscription)", last_name=last_name, first_name=first_name, bank=bank, @@ -348,8 +325,6 @@ class FutureUserDetailView(ProtectQuerysetMixin, LoginRequiredMixin, FormMixin, user=user, fee=bde_fee, ) - if soge: - membership._soge = True membership.save() membership.refresh_from_db() membership.roles.add(Role.objects.get(name="Adhérent BDE")) @@ -362,18 +337,13 @@ class FutureUserDetailView(ProtectQuerysetMixin, LoginRequiredMixin, FormMixin, user=user, fee=kfet_fee, ) - if soge: - membership._soge = True + membership.save() membership.refresh_from_db() 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): @@ -393,8 +363,7 @@ class FutureUserInvalidateView(ProtectQuerysetMixin, LoginRequiredMixin, View): user = User.objects.filter(profile__registration_valid=False)\ .filter(PermissionBackend.filter_queryset(request, User, "change", "is_valid"))\ .get(pk=self.kwargs["pk"]) - # Delete associated soge credits before - SogeCredit.objects.filter(user=user).delete() + user.delete() diff --git a/apps/treasury/admin.py b/apps/treasury/admin.py index 5069e53..b71ca0a 100644 --- a/apps/treasury/admin.py +++ b/apps/treasury/admin.py @@ -5,7 +5,7 @@ from django.contrib import admin from note_kfet.admin import admin_site from .forms import ProductForm -from .models import RemittanceType, Remittance, SogeCredit, Invoice, Product +from .models import RemittanceType, Remittance, Invoice, Product @admin.register(RemittanceType, site=admin_site) @@ -27,19 +27,6 @@ class RemittanceAdmin(admin.ModelAdmin): return not obj or (not obj.closed and super().has_change_permission(request, obj)) -@admin.register(SogeCredit, site=admin_site) -class SogeCreditAdmin(admin.ModelAdmin): - """ - Admin customisation for Remittance - """ - list_display = ('user', 'valid',) - readonly_fields = ('transactions', 'credit_transaction',) - - def has_add_permission(self, request): - # Don't create a credit manually - return False - - class ProductInline(admin.StackedInline): """ Inline product in invoice admin diff --git a/apps/treasury/api/serializers.py b/apps/treasury/api/serializers.py index 5442fd0..7601618 100644 --- a/apps/treasury/api/serializers.py +++ b/apps/treasury/api/serializers.py @@ -4,7 +4,7 @@ from django.db import transaction from rest_framework import serializers from note.api.serializers import SpecialTransactionSerializer -from ..models import Invoice, Product, RemittanceType, Remittance, SogeCredit +from ..models import Invoice, Product, RemittanceType, Remittance class ProductSerializer(serializers.ModelSerializer): @@ -61,21 +61,3 @@ class RemittanceSerializer(serializers.ModelSerializer): def get_transactions(self, obj): return serializers.ListSerializer(child=SpecialTransactionSerializer()).to_representation(obj.transactions) - -class SogeCreditSerializer(serializers.ModelSerializer): - """ - REST API Serializer for SogeCredit types. - The djangorestframework plugin will analyse the model `SogeCredit` and parse all fields in the API. - """ - - @transaction.atomic - def save(self, **kwargs): - # Update soge transactions after creating a credit - instance = super().save(**kwargs) - instance.update_transactions() - instance.save() - return instance - - class Meta: - model = SogeCredit - fields = '__all__' diff --git a/apps/treasury/api/urls.py b/apps/treasury/api/urls.py index 90c9d33..17be8bf 100644 --- a/apps/treasury/api/urls.py +++ b/apps/treasury/api/urls.py @@ -1,7 +1,7 @@ # Copyright (C) 2018-2021 by BDE ENS Paris-Saclay # SPDX-License-Identifier: GPL-3.0-or-later -from .views import InvoiceViewSet, ProductViewSet, RemittanceViewSet, RemittanceTypeViewSet, SogeCreditViewSet +from .views import InvoiceViewSet, ProductViewSet, RemittanceViewSet, RemittanceTypeViewSet def register_treasury_urls(router, path): @@ -12,4 +12,3 @@ def register_treasury_urls(router, path): router.register(path + '/product', ProductViewSet) router.register(path + '/remittance_type', RemittanceTypeViewSet) router.register(path + '/remittance', RemittanceViewSet) - router.register(path + '/soge_credit', SogeCreditViewSet) diff --git a/apps/treasury/api/views.py b/apps/treasury/api/views.py index e6ba9ce..bd05cb2 100644 --- a/apps/treasury/api/views.py +++ b/apps/treasury/api/views.py @@ -5,9 +5,8 @@ from django_filters.rest_framework import DjangoFilterBackend from rest_framework.filters import SearchFilter from api.viewsets import ReadProtectedModelViewSet -from .serializers import InvoiceSerializer, ProductSerializer, RemittanceTypeSerializer, RemittanceSerializer,\ - SogeCreditSerializer -from ..models import Invoice, Product, RemittanceType, Remittance, SogeCredit +from .serializers import InvoiceSerializer, ProductSerializer, RemittanceTypeSerializer, RemittanceSerializer +from ..models import Invoice, Product, RemittanceType, Remittance class InvoiceViewSet(ReadProtectedModelViewSet): @@ -60,18 +59,3 @@ class RemittanceViewSet(ReadProtectedModelViewSet): filter_backends = [DjangoFilterBackend, SearchFilter] filterset_fields = ['date', 'remittance_type', 'comment', 'closed', 'transaction_proxies__transaction', ] search_fields = ['$remittance_type__note__special_type', '$comment', ] - - -class SogeCreditViewSet(ReadProtectedModelViewSet): - """ - REST API View set. - The djangorestframework plugin will get all `SogeCredit` objects, serialize it to JSON with the given serializer, - then render it on /api/treasury/soge_credit/ - """ - queryset = SogeCredit.objects.order_by('id') - serializer_class = SogeCreditSerializer - filter_backends = [DjangoFilterBackend, SearchFilter] - filterset_fields = ['user', 'user__last_name', 'user__first_name', 'user__email', 'user__note__alias__name', - 'user__note__alias__normalized_name', 'transactions', 'credit_transaction', ] - search_fields = ['$user__last_name', '$user__first_name', '$user__email', '$user__note__alias__name', - '$user__note__alias__normalized_name', ] diff --git a/apps/treasury/forms.py b/apps/treasury/forms.py index 0244118..8a07eab 100644 --- a/apps/treasury/forms.py +++ b/apps/treasury/forms.py @@ -9,7 +9,7 @@ from django.db import transaction from django.utils.translation import gettext_lazy as _ from note_kfet.inputs import AmountInput, Autocomplete -from .models import Invoice, Product, Remittance, SpecialTransactionProxy, SogeCredit +from .models import Invoice, Product, Remittance, SpecialTransactionProxy class InvoiceForm(forms.ModelForm): @@ -163,18 +163,3 @@ class LinkTransactionToRemittanceForm(forms.ModelForm): model = SpecialTransactionProxy fields = ('remittance', ) - -class SogeCreditForm(forms.ModelForm): - class Meta: - model = SogeCredit - fields = ('user', ) - widgets = { - "user": Autocomplete( - User, - attrs={ - 'api_url': '/api/user/', - 'name_field': 'username', - 'placeholder': 'Nom ...', - }, - ), - } diff --git a/apps/treasury/models.py b/apps/treasury/models.py index 427d796..4f8c9d5 100644 --- a/apps/treasury/models.py +++ b/apps/treasury/models.py @@ -273,183 +273,3 @@ class SpecialTransactionProxy(models.Model): def __str__(self): return str(self.transaction) - - -class SogeCredit(models.Model): - """ - Manage the credits from the Société générale. - """ - user = models.OneToOneField( - User, - on_delete=models.PROTECT, - verbose_name=_("user"), - ) - - transactions = models.ManyToManyField( - MembershipTransaction, - related_name="+", - blank=True, - verbose_name=_("membership transactions"), - ) - - credit_transaction = models.OneToOneField( - SpecialTransaction, - on_delete=models.SET_NULL, - verbose_name=_("credit transaction"), - null=True, - ) - - @property - def valid(self): - return self.credit_transaction and self.credit_transaction.valid - - @property - def amount(self): - if self.valid: - return self.credit_transaction.total - amount = sum(transaction.total for transaction in self.transactions.all()) - if 'wei' in settings.INSTALLED_APPS: - from wei.models import WEIMembership - if not WEIMembership.objects.filter(club__weiclub__year=datetime.date.today().year, user=self.user)\ - .exists(): - # 80 € for people that don't go to WEI - amount += 8000 - return amount - - def update_transactions(self): - """ - The Sogé credit may be created after the user already paid its memberships. - We query transactions and update the credit, if it is unvalid. - """ - if self.valid or not self.pk: - return - - bde = Club.objects.get(name="BDE") - kfet = Club.objects.get(name="Kfet") - bde_qs = Membership.objects.filter(user=self.user, club=bde, date_start__gte=bde.membership_start) - kfet_qs = Membership.objects.filter(user=self.user, club=kfet, date_start__gte=kfet.membership_start) - - if bde_qs.exists(): - m = bde_qs.get() - if MembershipTransaction.objects.filter(membership=m).exists(): # non-free membership - if m.transaction not in self.transactions.all(): - self.transactions.add(m.transaction) - - if kfet_qs.exists(): - m = kfet_qs.get() - if MembershipTransaction.objects.filter(membership=m).exists(): # non-free membership - if m.transaction not in self.transactions.all(): - self.transactions.add(m.transaction) - - if 'wei' in settings.INSTALLED_APPS: - from wei.models import WEIClub - wei = WEIClub.objects.order_by('-year').first() - wei_qs = Membership.objects.filter(user=self.user, club=wei, date_start__gte=wei.membership_start) - if wei_qs.exists(): - m = wei_qs.get() - if MembershipTransaction.objects.filter(membership=m).exists(): # non-free membership - if m.transaction not in self.transactions.all(): - self.transactions.add(m.transaction) - - for tr in self.transactions.all(): - tr.valid = False - tr.save() - - def invalidate(self): - """ - Invalidating a Société générale delete the transaction of the bank if it was already created. - Treasurers must know what they do, With Great Power Comes Great Responsibility... - """ - if self.valid: - self.credit_transaction.valid = False - self.credit_transaction.save() - for tr in self.transactions.all(): - tr.valid = False - tr._force_save = True - tr.save() - - def validate(self, force=False): - if self.valid and not force: - # The credit is already done - return - - # First invalidate all transaction and delete the credit if already did (and force mode) - self.invalidate() - # Refresh credit amount - self.save() - self.credit_transaction.valid = True - self.credit_transaction._force_save = True - self.credit_transaction.save() - self.save() - - for tr in self.transactions.all(): - tr.valid = True - tr._force_save = True - tr.created_at = timezone.now() - tr.save() - - @transaction.atomic - def save(self, *args, **kwargs): - # This is a pre-registered user that declared that a SoGé account was opened. - # No note exists yet. - if not NoteUser.objects.filter(user=self.user).exists(): - return super().save(*args, **kwargs) - - if not self.credit_transaction: - credit_transaction = SpecialTransaction( - 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, - ) - credit_transaction._force_save = True - credit_transaction.save() - credit_transaction.refresh_from_db() - self.credit_transaction = credit_transaction - elif not self.valid: - self.credit_transaction.amount = self.amount - self.credit_transaction._force_save = True - self.credit_transaction.save() - - return 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. - Treasurers must know what they do, this is difficult to undo this operation. - With Great Power Comes Great Responsibility... - """ - - total_fee = sum(transaction.total for transaction in self.transactions.all() if not transaction.valid) - if self.user.note.balance < total_fee: - raise ValidationError(_("This user doesn't have enough money to pay the memberships with its note. " - "Please ask her/him to credit the note before invalidating this credit.")) - - self.invalidate() - for tr in self.transactions.all(): - tr._force_save = True - tr.valid = True - tr.created_at = timezone.now() - tr.save() - if self.credit_transaction: - # If the soge credit is deleted while the user is not validated yet, - # there is not credit transaction. - # There is a credit transaction iff the user declares that no bank account - # was opened after the validation of the account. - self.credit_transaction.valid = False - self.credit_transaction.reason += " (invalide)" - self.credit_transaction._force_save = True - self.credit_transaction.save() - super().delete(**kwargs) - - class Meta: - verbose_name = _("Credit from the Société générale") - verbose_name_plural = _("Credits from the Société générale") - - def __str__(self): - return _("Soge credit for {user}").format(user=str(self.user)) diff --git a/apps/treasury/tables.py b/apps/treasury/tables.py index f309c20..0041c25 100644 --- a/apps/treasury/tables.py +++ b/apps/treasury/tables.py @@ -7,7 +7,7 @@ from django_tables2 import A from note.models import SpecialTransaction from note.templatetags.pretty_money import pretty_money -from .models import Invoice, Remittance, SogeCredit +from .models import Invoice, Remittance class InvoiceTable(tables.Table): @@ -120,28 +120,3 @@ class SpecialTransactionTable(tables.Table): template_name = 'django_tables2/bootstrap4.html' fields = ('created_at', 'source', 'destination', 'last_name', 'first_name', 'bank', 'amount', 'reason',) order_by = ('-created_at',) - - -class SogeCreditTable(tables.Table): - user = tables.LinkColumn( - 'treasury:manage_soge_credit', - args=[A('pk')], - ) - - amount = tables.Column( - verbose_name=_("Amount"), - ) - - valid = tables.Column( - verbose_name=_("Valid"), - ) - - def render_amount(self, value): - return pretty_money(value) - - def render_valid(self, value): - return _("Yes") if value else _("No") - - class Meta: - model = SogeCredit - fields = ('user', 'user__last_name', 'user__first_name', 'amount', 'valid', ) diff --git a/apps/treasury/templates/treasury/invoice_list.html b/apps/treasury/templates/treasury/invoice_list.html index d9cd8a3..e42fbf2 100644 --- a/apps/treasury/templates/treasury/invoice_list.html +++ b/apps/treasury/templates/treasury/invoice_list.html @@ -15,9 +15,6 @@ SPDX-License-Identifier: GPL-3.0-or-later {% trans "Remittance" %}s - - {% trans "Société générale credits" %} -
diff --git a/apps/treasury/templates/treasury/remittance_list.html b/apps/treasury/templates/treasury/remittance_list.html index 8ced1ad..7d9e915 100644 --- a/apps/treasury/templates/treasury/remittance_list.html +++ b/apps/treasury/templates/treasury/remittance_list.html @@ -15,9 +15,6 @@ SPDX-License-Identifier: GPL-3.0-or-later {% trans "Remittance" %}s - - {% trans "Société générale credits" %} - diff --git a/apps/treasury/tests/test_treasury.py b/apps/treasury/tests/test_treasury.py index 798a960..c5bcda5 100644 --- a/apps/treasury/tests/test_treasury.py +++ b/apps/treasury/tests/test_treasury.py @@ -10,9 +10,8 @@ from django.urls import reverse from member.models import Membership, Club from note.models import SpecialTransaction, NoteSpecial, Transaction -from ..api.views import InvoiceViewSet, ProductViewSet, RemittanceViewSet, RemittanceTypeViewSet, \ - SogeCreditViewSet -from ..models import Invoice, Product, Remittance, RemittanceType, SogeCredit +from ..api.views import InvoiceViewSet, ProductViewSet, RemittanceViewSet, RemittanceTypeViewSet +from ..models import Invoice, Product, Remittance, RemittanceType class TestInvoices(TestCase): @@ -296,112 +295,6 @@ class TestRemittances(TestCase): self.assertEqual(response.status_code, 200) -class TestSogeCredits(TestCase): - """ - Check that credits from the Société générale are working correctly. - """ - - fixtures = ('initial',) - - def setUp(self) -> None: - self.user = User.objects.create_superuser( - username="admintoto", - password="totototo", - email="admin@example.com", - ) - self.client.force_login(self.user) - sess = self.client.session - sess["permission_mask"] = 42 - sess.save() - - self.kfet = Club.objects.get(name="Kfet") - self.bde = self.kfet.parent_club - - self.kfet_membership = Membership( - user=self.user, - club=self.kfet, - ) - self.kfet_membership._force_renew_parent = True - self.kfet_membership._soge = True - self.kfet_membership.save() - - def test_admin_page(self): - """ - Render the admin page. - """ - response = self.client.get(reverse("admin:index") + "treasury/sogecredit/") - self.assertEqual(response.status_code, 200) - - def test_sogecredit_list(self): - """ - Display the list of all credits. - """ - response = self.client.get(reverse("treasury:soge_credits")) - self.assertEqual(response.status_code, 200) - response = self.client.get(reverse("treasury:soge_credits") + "?search=toto&valid=") - self.assertEqual(response.status_code, 200) - - def test_validate_soge_credit(self): - """ - Try to validate a credit. - """ - soge_credit = SogeCredit.objects.get(user=self.user) - - response = self.client.get(reverse("treasury:manage_soge_credit", args=(soge_credit.pk,))) - self.assertEqual(response.status_code, 200) - - response = self.client.post(reverse("treasury:manage_soge_credit", args=(soge_credit.pk,)), data=dict( - validate=True, - )) - self.assertRedirects(response, reverse("treasury:manage_soge_credit", args=(soge_credit.pk,)), 302, 200) - soge_credit.refresh_from_db() - self.assertTrue(soge_credit.valid) - self.user.note.refresh_from_db() - self.assertEqual( - Transaction.objects.filter(Q(source=self.user.note) | Q(destination=self.user.note)).count(), 3) - self.assertTrue(self.user.profile.soge) - - def test_delete_soge_credit(self): - """ - Try to invalidate a credit. - """ - soge_credit = SogeCredit.objects.get(user=self.user) - - response = self.client.get(reverse("treasury:manage_soge_credit", args=(soge_credit.pk,))) - self.assertEqual(response.status_code, 200) - - self.assertRaises(ValidationError, self.client.post, - reverse("treasury:manage_soge_credit", args=(soge_credit.pk,)), data=dict(delete=True)) - - SpecialTransaction.objects.create( - source=NoteSpecial.objects.get(special_type="Carte bancaire"), - destination=self.user.note, - amount=self.bde.membership_fee_paid + self.kfet.membership_fee_paid, - quantity=1, - reason="Registration is not complete, pliz pay", - last_name="TOTO", - first_name="Toto", - ) - - response = self.client.post(reverse("treasury:manage_soge_credit", args=(soge_credit.pk,)), - data=dict(delete=True)) - # 403 because no SogeCredit exists anymore, then a PermissionDenied is raised - self.assertRedirects(response, reverse("treasury:soge_credits"), 302, 403) - self.assertFalse(SogeCredit.objects.filter(pk=soge_credit.pk)) - 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(), 4) - self.assertFalse(self.user.profile.soge) - - def test_invoice_api(self): - """ - Load some API pages - """ - response = self.client.get("/api/treasury/soge_credit/") - self.assertEqual(response.status_code, 200) - - class TestTreasuryAPI(TestAPI): def setUp(self) -> None: super().setUp() @@ -447,7 +340,6 @@ class TestTreasuryAPI(TestAPI): club=self.kfet, ) self.kfet_membership._force_renew_parent = True - self.kfet_membership._soge = True self.kfet_membership.save() def test_invoice_api(self): @@ -474,8 +366,3 @@ class TestTreasuryAPI(TestAPI): """ self.check_viewset(RemittanceTypeViewSet, "/api/treasury/remittance_type/") - def test_sogecredit_api(self): - """ - Load SogeCredit API page and test all filters and permissions - """ - self.check_viewset(SogeCreditViewSet, "/api/treasury/soge_credit/") diff --git a/apps/treasury/urls.py b/apps/treasury/urls.py index 4fe8792..5e5b1ff 100644 --- a/apps/treasury/urls.py +++ b/apps/treasury/urls.py @@ -5,7 +5,7 @@ from django.urls import path from .views import InvoiceCreateView, InvoiceListView, InvoiceUpdateView, InvoiceDeleteView, InvoiceRenderView,\ RemittanceListView, RemittanceCreateView, RemittanceUpdateView, LinkTransactionToRemittanceView,\ - UnlinkTransactionToRemittanceView, SogeCreditListView, SogeCreditManageView + UnlinkTransactionToRemittanceView app_name = 'treasury' urlpatterns = [ @@ -23,7 +23,4 @@ urlpatterns = [ path('remittance/link_transaction//', LinkTransactionToRemittanceView.as_view(), name='link_transaction'), path('remittance/unlink_transaction//', UnlinkTransactionToRemittanceView.as_view(), name='unlink_transaction'), - - path('soge-credits/list/', SogeCreditListView.as_view(), name='soge_credits'), - path('soge-credits/manage//', SogeCreditManageView.as_view(), name='manage_soge_credit'), ] diff --git a/apps/treasury/views.py b/apps/treasury/views.py index aee6ea0..e6fc017 100644 --- a/apps/treasury/views.py +++ b/apps/treasury/views.py @@ -26,9 +26,9 @@ from permission.backends import PermissionBackend from permission.views import ProtectQuerysetMixin, ProtectedCreateView from .forms import InvoiceForm, ProductFormSet, ProductFormSetHelper, RemittanceForm, \ - LinkTransactionToRemittanceForm, SogeCreditForm -from .models import Invoice, Product, Remittance, SpecialTransactionProxy, SogeCredit -from .tables import InvoiceTable, RemittanceTable, SpecialTransactionTable, SogeCreditTable + LinkTransactionToRemittanceForm +from .models import Invoice, Product, Remittance, SpecialTransactionProxy +from .tables import InvoiceTable, RemittanceTable, SpecialTransactionTable class InvoiceCreateView(ProtectQuerysetMixin, ProtectedCreateView): @@ -393,70 +393,3 @@ class UnlinkTransactionToRemittanceView(LoginRequiredMixin, View): transaction.save() return redirect('treasury:remittance_list') - - -class SogeCreditListView(LoginRequiredMixin, ProtectQuerysetMixin, SingleTableView): - """ - List all Société Générale credits - """ - model = SogeCredit - table_class = SogeCreditTable - extra_context = {"title": _("List of credits from the Société générale")} - - def dispatch(self, request, *args, **kwargs): - # Check that the user is authenticated - if not request.user.is_authenticated: - return self.handle_no_permission() - - if not super().get_queryset().exists(): - raise PermissionDenied(_("You are not able to see the treasury interface.")) - return super().dispatch(request, *args, **kwargs) - - def get_queryset(self, **kwargs): - """ - Filter the table with the given parameter. - :param kwargs: - :return: - """ - qs = super().get_queryset() - if "search" in self.request.GET: - pattern = self.request.GET["search"] - if pattern: - qs = qs.filter( - Q(user__first_name__iregex=pattern) - | Q(user__last_name__iregex=pattern) - | Q(user__note__alias__name__iregex="^" + pattern) - | Q(user__note__alias__normalized_name__iregex="^" + Alias.normalize(pattern)) - ) - - if "valid" not in self.request.GET or not self.request.GET["valid"]: - qs = qs.filter(credit_transaction__valid=False) - - return qs - - def get_context_data(self, **kwargs): - context = super().get_context_data(**kwargs) - context['form'] = SogeCreditForm(self.request.POST or None) - return context - - -class SogeCreditManageView(LoginRequiredMixin, ProtectQuerysetMixin, BaseFormView, DetailView): - """ - Manage credits from the Société générale. - """ - model = SogeCredit - form_class = Form - extra_context = {"title": _("Manage credits from the Société générale")} - - @transaction.atomic - def form_valid(self, form): - if "validate" in form.data: - self.get_object().validate(True) - elif "delete" in form.data: - self.get_object().delete() - return super().form_valid(form) - - def get_success_url(self): - if "validate" in self.request.POST: - return reverse_lazy('treasury:manage_soge_credit', args=(self.get_object().pk,)) - return reverse_lazy('treasury:soge_credits') From 9fb3ccf66afeb5571a675e3898a1fc83e8415f19 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Doderlein Date: Sun, 31 Jul 2022 11:59:30 +0000 Subject: [PATCH 2/2] Fix permission and delete soge in treasury --- apps/permission/fixtures/initial.json | 76 ------------------- .../migrations/0005_delete_sogecredit.py | 16 ++++ 2 files changed, 16 insertions(+), 76 deletions(-) create mode 100644 apps/treasury/migrations/0005_delete_sogecredit.py diff --git a/apps/permission/fixtures/initial.json b/apps/permission/fixtures/initial.json index 771b728..54f448b 100644 --- a/apps/permission/fixtures/initial.json +++ b/apps/permission/fixtures/initial.json @@ -1095,70 +1095,6 @@ "description": "Supprimer un produit" } }, - { - "model": "permission.permission", - "pk": 70, - "fields": { - "model": [ - "treasury", - "sogecredit" - ], - "query": "{}", - "type": "add", - "mask": 1, - "field": "", - "permanent": false, - "description": "Ajouter un crédit de la Soci\u00e9t\u00e9 g\u00e9n\u00e9rale" - } - }, - { - "model": "permission.permission", - "pk": 71, - "fields": { - "model": [ - "treasury", - "sogecredit" - ], - "query": "{}", - "type": "view", - "mask": 3, - "field": "", - "permanent": false, - "description": "Voir tous les crédits de la Soci\u00e9t\u00e9 g\u00e9n\u00e9rale" - } - }, - { - "model": "permission.permission", - "pk": 72, - "fields": { - "model": [ - "treasury", - "sogecredit" - ], - "query": "{}", - "type": "change", - "mask": 1, - "field": "", - "permanent": false, - "description": "Modifier un crédit de la Soci\u00e9t\u00e9 g\u00e9n\u00e9rale" - } - }, - { - "model": "permission.permission", - "pk": 73, - "fields": { - "model": [ - "treasury", - "sogecredit" - ], - "query": "{}", - "type": "delete", - "mask": 3, - "field": "", - "permanent": false, - "description": "Supprimer un crédit de la Soci\u00e9t\u00e9 g\u00e9n\u00e9rale" - } - }, { "model": "permission.permission", "pk": 145, @@ -2044,7 +1980,6 @@ 36, 39, 40, - 70, 152, 153, 154, @@ -2171,9 +2106,6 @@ 67, 68, 69, - 71, - 72, - 73, 146, 147, 150, @@ -2270,10 +2202,6 @@ 67, 68, 69, - 70, - 71, - 72, - 73, 145, 146, 147, @@ -2351,8 +2279,6 @@ 29, 30, 31, - 70, - 72, 166, 167, 168, @@ -2439,8 +2365,6 @@ 56, 57, 58, - 70, - 72, 147, 150, 166, diff --git a/apps/treasury/migrations/0005_delete_sogecredit.py b/apps/treasury/migrations/0005_delete_sogecredit.py new file mode 100644 index 0000000..3989e41 --- /dev/null +++ b/apps/treasury/migrations/0005_delete_sogecredit.py @@ -0,0 +1,16 @@ +# Generated by Django 2.2.28 on 2022-07-31 11:45 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('treasury', '0004_auto_20211005_1544'), + ] + + operations = [ + migrations.DeleteModel( + name='SogeCredit', + ), + ]