mirror of
https://gitlab.crans.org/bde/nk20
synced 2025-06-20 17:41:55 +02:00
Handle credits from the Société générale
This commit is contained in:
@ -28,7 +28,7 @@ class ProfileForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = Profile
|
||||
fields = '__all__'
|
||||
exclude = ('user', 'email_confirmed', 'registration_valid', 'soge', )
|
||||
exclude = ('user', 'email_confirmed', 'registration_valid', )
|
||||
|
||||
|
||||
class ClubForm(forms.ModelForm):
|
||||
|
@ -64,11 +64,12 @@ class Profile(models.Model):
|
||||
default=False,
|
||||
)
|
||||
|
||||
soge = models.BooleanField(
|
||||
verbose_name=_("Société générale"),
|
||||
help_text=_("Has the user ever be paid by the Société générale?"),
|
||||
default=False,
|
||||
)
|
||||
@property
|
||||
def soge(self):
|
||||
if "treasury" in settings.INSTALLED_APPS:
|
||||
from treasury.models import SogeCredit
|
||||
return SogeCredit.objects.filter(user=self.user, credit_transaction=None).exists()
|
||||
return False
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('user profile')
|
||||
@ -309,7 +310,20 @@ class Membership(models.Model):
|
||||
reason="Adhésion " + self.club.name,
|
||||
)
|
||||
transaction._force_save = True
|
||||
transaction.save(force_insert=True)
|
||||
print(hasattr(self, '_soge'))
|
||||
if hasattr(self, '_soge') and "treasury" in settings.INSTALLED_APPS:
|
||||
# If the soge pays, then the transaction is unvalidated in a first time, then submitted for control
|
||||
# to treasurers.
|
||||
transaction.valid = False
|
||||
from treasury.models import SogeCredit
|
||||
soge_credit = SogeCredit.objects.get_or_create(user=self.user)[0]
|
||||
soge_credit.refresh_from_db()
|
||||
transaction.save(force_insert=True)
|
||||
transaction.refresh_from_db()
|
||||
soge_credit.transactions.add(transaction)
|
||||
soge_credit.save()
|
||||
else:
|
||||
transaction.save(force_insert=True)
|
||||
|
||||
def __str__(self):
|
||||
return _("Membership of {user} for the club {club}").format(user=self.user.username, club=self.club.name, )
|
||||
|
@ -18,7 +18,7 @@ from django.views.generic.edit import FormMixin
|
||||
from django_tables2.views import SingleTableView
|
||||
from rest_framework.authtoken.models import Token
|
||||
from note.forms import ImageForm
|
||||
from note.models import Alias, NoteUser, NoteSpecial
|
||||
from note.models import Alias, NoteUser
|
||||
from note.models.transactions import Transaction, SpecialTransaction
|
||||
from note.tables import HistoryTable, AliasTable
|
||||
from permission.backends import PermissionBackend
|
||||
@ -128,7 +128,8 @@ class UserDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
|
||||
context = super().get_context_data(**kwargs)
|
||||
user = context['user_object']
|
||||
history_list = \
|
||||
Transaction.objects.all().filter(Q(source=user.note) | Q(destination=user.note)).order_by("-id")\
|
||||
Transaction.objects.all().filter(Q(source=user.note) | Q(destination=user.note))\
|
||||
.order_by("-created_at", "-id")\
|
||||
.filter(PermissionBackend.filter_queryset(self.request.user, Transaction, "view"))
|
||||
history_table = HistoryTable(history_list, prefix='transaction-')
|
||||
history_table.paginate(per_page=20, page=self.request.GET.get("transaction-page", 1))
|
||||
@ -314,7 +315,8 @@ class ClubDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
|
||||
club.update_membership_dates()
|
||||
|
||||
club_transactions = Transaction.objects.all().filter(Q(source=club.note) | Q(destination=club.note))\
|
||||
.filter(PermissionBackend.filter_queryset(self.request.user, Transaction, "view")).order_by('-id')
|
||||
.filter(PermissionBackend.filter_queryset(self.request.user, Transaction, "view"))\
|
||||
.order_by('-created_at', '-id')
|
||||
history_table = HistoryTable(club_transactions, prefix="history-")
|
||||
history_table.paginate(per_page=20, page=self.request.GET.get('history-page', 1))
|
||||
context['history_list'] = history_table
|
||||
@ -365,6 +367,15 @@ class ClubUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView):
|
||||
form_class = ClubForm
|
||||
template_name = "member/club_form.html"
|
||||
|
||||
def get_queryset(self, **kwargs):
|
||||
qs = super().get_queryset(**kwargs)
|
||||
|
||||
# Don't update a WEI club through this view
|
||||
if "wei" in settings.INSTALLED_APPS:
|
||||
qs = qs.filter(weiclub=None)
|
||||
|
||||
return qs
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse_lazy("member:club_detail", kwargs={"pk": self.object.pk})
|
||||
|
||||
@ -396,7 +407,7 @@ class ClubAddMemberView(ProtectQuerysetMixin, LoginRequiredMixin, CreateView):
|
||||
if "club_pk" in self.kwargs:
|
||||
# We create a new membership.
|
||||
club = Club.objects.filter(PermissionBackend.filter_queryset(self.request.user, Club, "view"))\
|
||||
.get(pk=self.kwargs["club_pk"])
|
||||
.get(pk=self.kwargs["club_pk"], weiclub=None)
|
||||
form.fields['credit_amount'].initial = club.membership_fee_paid
|
||||
form.fields['roles'].initial = Role.objects.filter(name="Membre de club").all()
|
||||
|
||||
@ -463,17 +474,11 @@ class ClubAddMemberView(ProtectQuerysetMixin, LoginRequiredMixin, CreateView):
|
||||
bank = form.cleaned_data["bank"]
|
||||
soge = form.cleaned_data["soge"] and not user.profile.soge and club.name == "BDE"
|
||||
|
||||
# If Société générale pays, then we auto-fill some data
|
||||
# If Société générale pays, then we store that information but the payment must be controlled by treasurers
|
||||
# later. The membership transaction will be invalidated.
|
||||
if soge:
|
||||
credit_type = NoteSpecial.objects.get(special_type="Virement bancaire")
|
||||
bde = club
|
||||
kfet = Club.objects.get(name="Kfet")
|
||||
if user.profile.paid:
|
||||
fee = bde.membership_fee_paid + kfet.membership_fee_paid
|
||||
else:
|
||||
fee = bde.membership_fee_unpaid + kfet.membership_fee_unpaid
|
||||
credit_amount = fee
|
||||
bank = "Société générale"
|
||||
credit_type = None
|
||||
form.instance._soge = True
|
||||
|
||||
if credit_type is None:
|
||||
credit_amount = 0
|
||||
@ -546,11 +551,9 @@ class ClubAddMemberView(ProtectQuerysetMixin, LoginRequiredMixin, CreateView):
|
||||
|
||||
ret = super().form_valid(form)
|
||||
|
||||
# If Société générale pays, then we store the information: the bank can't pay twice to a same person.
|
||||
# If Société générale pays, then we assume that this is the BDE membership, and we auto-renew the
|
||||
# Kfet membership.
|
||||
if soge:
|
||||
user.profile.soge = True
|
||||
user.profile.save()
|
||||
|
||||
kfet = Club.objects.get(name="Kfet")
|
||||
kfet_fee = kfet.membership_fee_paid if user.profile.paid else kfet.membership_fee_unpaid
|
||||
|
||||
@ -562,13 +565,16 @@ class ClubAddMemberView(ProtectQuerysetMixin, LoginRequiredMixin, CreateView):
|
||||
date_end__gte=datetime.today(),
|
||||
)
|
||||
|
||||
membership = Membership.objects.create(
|
||||
membership = Membership(
|
||||
club=kfet,
|
||||
user=user,
|
||||
fee=kfet_fee,
|
||||
date_start=old_membership.get().date_end + timedelta(days=1)
|
||||
if old_membership.exists() else form.instance.date_start,
|
||||
)
|
||||
membership._soge = True
|
||||
membership.save()
|
||||
membership.refresh_from_db()
|
||||
if old_membership.exists():
|
||||
membership.roles.set(old_membership.get().roles.all())
|
||||
else:
|
||||
|
@ -8,6 +8,7 @@ from django.utils.translation import gettext_lazy as _
|
||||
from polymorphic.models import PolymorphicModel
|
||||
|
||||
from .notes import Note, NoteClub, NoteSpecial
|
||||
from ..templatetags.pretty_money import pretty_money
|
||||
|
||||
"""
|
||||
Defines transactions
|
||||
@ -198,6 +199,14 @@ class Transaction(PolymorphicModel):
|
||||
self.source.save()
|
||||
self.destination.save()
|
||||
|
||||
def delete(self, **kwargs):
|
||||
"""
|
||||
Whenever we want to delete a transaction (caution with this), we ensure the transaction is invalid first.
|
||||
"""
|
||||
self.valid = False
|
||||
self.save(**kwargs)
|
||||
super().delete(**kwargs)
|
||||
|
||||
@property
|
||||
def total(self):
|
||||
return self.amount * self.quantity
|
||||
@ -206,6 +215,10 @@ class Transaction(PolymorphicModel):
|
||||
def type(self):
|
||||
return _('Transfer')
|
||||
|
||||
def __str__(self):
|
||||
return self.__class__.__name__ + " from " + str(self.source) + " to " + str(self.destination) + " of "\
|
||||
+ pretty_money(self.quantity * self.amount) + ("" if self.valid else " invalid")
|
||||
|
||||
|
||||
class RecurrentTransaction(Transaction):
|
||||
"""
|
||||
|
@ -10,7 +10,7 @@ def save_user_note(instance, raw, **_kwargs):
|
||||
# When provisionning data, do not try to autocreate
|
||||
return
|
||||
|
||||
if (instance.is_superuser or instance.profile.registration_valid) and instance.is_active:
|
||||
if instance.is_superuser or instance.profile.registration_valid:
|
||||
# Create note only when the registration is validated
|
||||
from note.models import NoteUser
|
||||
NoteUser.objects.get_or_create(user=instance)
|
||||
|
@ -30,7 +30,7 @@ class TransactionCreateView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTabl
|
||||
table_class = HistoryTable
|
||||
|
||||
def get_queryset(self, **kwargs):
|
||||
return super().get_queryset(**kwargs).order_by("-id").all()[:20]
|
||||
return super().get_queryset(**kwargs).order_by("-created_at", "-id").all()[:20]
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
"""
|
||||
@ -93,7 +93,7 @@ class ConsoView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView):
|
||||
table_class = HistoryTable
|
||||
|
||||
def get_queryset(self, **kwargs):
|
||||
return super().get_queryset(**kwargs).order_by("-id").all()[:20]
|
||||
return super().get_queryset(**kwargs).order_by("-created_at", "-id").all()[:20]
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
"""
|
||||
|
@ -16,13 +16,12 @@ from django.views.generic.edit import FormMixin
|
||||
from django_tables2 import SingleTableView
|
||||
from member.forms import ProfileForm
|
||||
from member.models import Membership, Club, Role
|
||||
from note.models import SpecialTransaction, NoteSpecial
|
||||
from note.models import SpecialTransaction
|
||||
from note.templatetags.pretty_money import pretty_money
|
||||
from permission.backends import PermissionBackend
|
||||
from permission.views import ProtectQuerysetMixin
|
||||
from wei.models import WEIClub
|
||||
|
||||
from .forms import SignUpForm, ValidationForm, WEISignupForm
|
||||
from .forms import SignUpForm, ValidationForm
|
||||
from .tables import FutureUserTable
|
||||
from .tokens import email_validation_token
|
||||
|
||||
@ -40,16 +39,6 @@ class UserCreateView(CreateView):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context["profile_form"] = self.second_form()
|
||||
|
||||
if "wei" in settings.INSTALLED_APPS:
|
||||
from wei.forms import WEIRegistrationForm
|
||||
wei_form = WEIRegistrationForm()
|
||||
del wei_form.fields["user"]
|
||||
del wei_form.fields["caution_check"]
|
||||
del wei_form.fields["first_year"]
|
||||
del wei_form.fields["information_json"]
|
||||
context["wei_form"] = wei_form
|
||||
context["wei_registration_form"] = WEISignupForm()
|
||||
|
||||
return context
|
||||
|
||||
def form_valid(self, form):
|
||||
@ -62,23 +51,6 @@ class UserCreateView(CreateView):
|
||||
if not profile_form.is_valid():
|
||||
return self.form_invalid(form)
|
||||
|
||||
wei_form = None
|
||||
self.wei = False
|
||||
|
||||
if "wei" in settings.INSTALLED_APPS:
|
||||
wei_signup_form = WEISignupForm(self.request.POST)
|
||||
if wei_signup_form.is_valid() and wei_signup_form.cleaned_data["wei_registration"]:
|
||||
from wei.forms import WEIRegistrationForm
|
||||
wei_form = WEIRegistrationForm(self.request.POST)
|
||||
del wei_form.fields["user"]
|
||||
del wei_form.fields["caution_check"]
|
||||
del wei_form.fields["first_year"]
|
||||
|
||||
if not wei_form.is_valid():
|
||||
return self.form_invalid(wei_form)
|
||||
|
||||
self.wei = True
|
||||
|
||||
# Save the user and the profile
|
||||
user = form.save(commit=False)
|
||||
user.is_active = False
|
||||
@ -92,21 +64,10 @@ class UserCreateView(CreateView):
|
||||
|
||||
user.profile.send_email_validation_link()
|
||||
|
||||
if self.wei:
|
||||
wei_registration = wei_form.instance
|
||||
wei_registration.user = user
|
||||
wei_registration.wei = WEIClub.objects.order_by('date_start').last()
|
||||
wei_registration.caution_check = False
|
||||
wei_registration.first_year = True
|
||||
wei_registration.save()
|
||||
|
||||
return super().form_valid(form)
|
||||
|
||||
def get_success_url(self):
|
||||
if self.wei:
|
||||
return reverse_lazy('registration:email_validation_sent') # TODO Load WEI survey
|
||||
else:
|
||||
return reverse_lazy('registration:email_validation_sent')
|
||||
return reverse_lazy('registration:email_validation_sent')
|
||||
|
||||
|
||||
class UserValidateView(TemplateView):
|
||||
@ -304,17 +265,17 @@ class FutureUserDetailView(ProtectQuerysetMixin, LoginRequiredMixin, FormMixin,
|
||||
fee += kfet_fee
|
||||
|
||||
if soge:
|
||||
# Fill payment information if Société Générale pays the inscription
|
||||
credit_type = NoteSpecial.objects.get(special_type="Virement bancaire")
|
||||
credit_amount = fee
|
||||
bank = "Société générale"
|
||||
# If the bank pays, then we don't credit now. Treasurers will validate the transaction
|
||||
# and credit the note later.
|
||||
credit_type = None
|
||||
|
||||
print("OK")
|
||||
if credit_type is None:
|
||||
credit_amount = 0
|
||||
|
||||
if join_Kfet and not join_BDE:
|
||||
form.add_error('join_Kfet', _("You must join BDE club before joining Kfet club."))
|
||||
|
||||
if fee > credit_amount:
|
||||
if fee > credit_amount and not soge:
|
||||
# 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 {}")
|
||||
@ -336,10 +297,9 @@ class FutureUserDetailView(ProtectQuerysetMixin, LoginRequiredMixin, FormMixin,
|
||||
ret = super().form_valid(form)
|
||||
user.is_active = user.profile.email_confirmed or user.is_superuser
|
||||
user.profile.registration_valid = True
|
||||
# Store if Société générale paid for next years
|
||||
user.profile.soge = soge
|
||||
user.save()
|
||||
user.profile.save()
|
||||
user.refresh_from_db()
|
||||
|
||||
if credit_type is not None and credit_amount > 0:
|
||||
# Credit the note
|
||||
@ -357,21 +317,29 @@ class FutureUserDetailView(ProtectQuerysetMixin, LoginRequiredMixin, FormMixin,
|
||||
|
||||
if join_BDE:
|
||||
# Create membership for the user to the BDE starting today
|
||||
membership = Membership.objects.create(
|
||||
membership = Membership(
|
||||
club=bde,
|
||||
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"))
|
||||
membership.save()
|
||||
|
||||
if join_Kfet:
|
||||
# Create membership for the user to the Kfet starting today
|
||||
membership = Membership.objects.create(
|
||||
membership = Membership(
|
||||
club=kfet,
|
||||
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()
|
||||
|
||||
|
@ -3,7 +3,7 @@
|
||||
|
||||
from django.contrib import admin
|
||||
|
||||
from .models import RemittanceType, Remittance
|
||||
from .models import RemittanceType, Remittance, SogeCredit
|
||||
|
||||
|
||||
@admin.register(RemittanceType)
|
||||
@ -25,3 +25,6 @@ class RemittanceAdmin(admin.ModelAdmin):
|
||||
if not obj:
|
||||
return True
|
||||
return not obj.closed and super().has_change_permission(request, obj)
|
||||
|
||||
|
||||
admin.site.register(SogeCredit)
|
||||
|
@ -4,7 +4,7 @@
|
||||
from rest_framework import serializers
|
||||
from note.api.serializers import SpecialTransactionSerializer
|
||||
|
||||
from ..models import Invoice, Product, RemittanceType, Remittance
|
||||
from ..models import Invoice, Product, RemittanceType, Remittance, SogeCredit
|
||||
|
||||
|
||||
class ProductSerializer(serializers.ModelSerializer):
|
||||
@ -60,3 +60,14 @@ 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.
|
||||
"""
|
||||
|
||||
class Meta:
|
||||
model = SogeCredit
|
||||
fields = '__all__'
|
||||
|
@ -1,7 +1,7 @@
|
||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from .views import InvoiceViewSet, ProductViewSet, RemittanceViewSet, RemittanceTypeViewSet
|
||||
from .views import InvoiceViewSet, ProductViewSet, RemittanceViewSet, RemittanceTypeViewSet, SogeCreditViewSet
|
||||
|
||||
|
||||
def register_treasury_urls(router, path):
|
||||
@ -12,3 +12,4 @@ 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)
|
||||
|
@ -5,8 +5,9 @@ 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
|
||||
from ..models import Invoice, Product, RemittanceType, Remittance
|
||||
from .serializers import InvoiceSerializer, ProductSerializer, RemittanceTypeSerializer, RemittanceSerializer,\
|
||||
SogeCreditSerializer
|
||||
from ..models import Invoice, Product, RemittanceType, Remittance, SogeCredit
|
||||
|
||||
|
||||
class InvoiceViewSet(ReadProtectedModelViewSet):
|
||||
@ -39,7 +40,7 @@ class RemittanceTypeViewSet(ReadProtectedModelViewSet):
|
||||
The djangorestframework plugin will get all `RemittanceType` objects, serialize it to JSON with the given serializer
|
||||
then render it on /api/treasury/remittance_type/
|
||||
"""
|
||||
queryset = RemittanceType.objects.all()
|
||||
queryset = RemittanceType.objects
|
||||
serializer_class = RemittanceTypeSerializer
|
||||
|
||||
|
||||
@ -49,5 +50,15 @@ class RemittanceViewSet(ReadProtectedModelViewSet):
|
||||
The djangorestframework plugin will get all `Remittance` objects, serialize it to JSON with the given serializer,
|
||||
then render it on /api/treasury/remittance/
|
||||
"""
|
||||
queryset = Remittance.objects.all()
|
||||
queryset = Remittance.objects
|
||||
serializer_class = RemittanceSerializer
|
||||
|
||||
|
||||
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
|
||||
serializer_class = SogeCreditSerializer
|
||||
|
@ -1,11 +1,13 @@
|
||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
from datetime import datetime
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.db import models
|
||||
from django.db.models import Q
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from note.models import NoteSpecial, SpecialTransaction
|
||||
from note.models import NoteSpecial, SpecialTransaction, MembershipTransaction
|
||||
|
||||
|
||||
class Invoice(models.Model):
|
||||
@ -207,3 +209,90 @@ class SpecialTransactionProxy(models.Model):
|
||||
class Meta:
|
||||
verbose_name = _("special transaction proxy")
|
||||
verbose_name_plural = _("special transaction proxies")
|
||||
|
||||
|
||||
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="+",
|
||||
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 is not None
|
||||
|
||||
@property
|
||||
def amount(self):
|
||||
return sum(transaction.quantity * transaction.amount for transaction in self.transactions.all())
|
||||
|
||||
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()
|
||||
self.credit_transaction.delete()
|
||||
self.credit_transaction = None
|
||||
for transaction in self.transactions.all():
|
||||
transaction.valid = False
|
||||
transaction.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()
|
||||
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",
|
||||
)
|
||||
self.save()
|
||||
|
||||
for transaction in self.transactions.all():
|
||||
transaction.valid = True
|
||||
transaction.created_at = datetime.now()
|
||||
transaction.save()
|
||||
|
||||
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...
|
||||
"""
|
||||
self.invalidate()
|
||||
for transaction in self.transactions.all():
|
||||
transaction.valid = True
|
||||
transaction.created_at = datetime.now()
|
||||
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")
|
||||
|
@ -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
|
||||
from .models import Invoice, Remittance, SogeCredit
|
||||
|
||||
|
||||
class InvoiceTable(tables.Table):
|
||||
@ -101,3 +101,28 @@ class SpecialTransactionTable(tables.Table):
|
||||
model = SpecialTransaction
|
||||
template_name = 'django_tables2/bootstrap4.html'
|
||||
fields = ('id', 'source', 'destination', 'last_name', 'first_name', 'bank', 'amount', 'reason',)
|
||||
|
||||
|
||||
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', 'amount', 'valid', )
|
||||
|
@ -4,7 +4,8 @@
|
||||
from django.urls import path
|
||||
|
||||
from .views import InvoiceCreateView, InvoiceListView, InvoiceUpdateView, InvoiceRenderView, RemittanceListView,\
|
||||
RemittanceCreateView, RemittanceUpdateView, LinkTransactionToRemittanceView, UnlinkTransactionToRemittanceView
|
||||
RemittanceCreateView, RemittanceUpdateView, LinkTransactionToRemittanceView, UnlinkTransactionToRemittanceView,\
|
||||
SogeCreditListView, SogeCreditManageView
|
||||
|
||||
app_name = 'treasury'
|
||||
urlpatterns = [
|
||||
@ -21,4 +22,7 @@ urlpatterns = [
|
||||
path('remittance/link_transaction/<int:pk>/', LinkTransactionToRemittanceView.as_view(), name='link_transaction'),
|
||||
path('remittance/unlink_transaction/<int:pk>/', UnlinkTransactionToRemittanceView.as_view(),
|
||||
name='unlink_transaction'),
|
||||
|
||||
path('soge-credits/list/', SogeCreditListView.as_view(), name='soge_credits'),
|
||||
path('soge-credits/manage/<int:pk>/', SogeCreditManageView.as_view(), name='manage_soge_credit'),
|
||||
]
|
||||
|
@ -10,21 +10,23 @@ from crispy_forms.helper import FormHelper
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.db.models import Q
|
||||
from django.forms import Form
|
||||
from django.http import HttpResponse
|
||||
from django.shortcuts import redirect
|
||||
from django.template.loader import render_to_string
|
||||
from django.urls import reverse_lazy
|
||||
from django.views.generic import CreateView, UpdateView
|
||||
from django.views.generic import CreateView, UpdateView, DetailView
|
||||
from django.views.generic.base import View, TemplateView
|
||||
from django.views.generic.edit import BaseFormView
|
||||
from django_tables2 import SingleTableView
|
||||
from note.models import SpecialTransaction, NoteSpecial
|
||||
from note.models import SpecialTransaction, NoteSpecial, Alias
|
||||
from note_kfet.settings.base import BASE_DIR
|
||||
from permission.backends import PermissionBackend
|
||||
from permission.views import ProtectQuerysetMixin
|
||||
|
||||
from .forms import InvoiceForm, ProductFormSet, ProductFormSetHelper, RemittanceForm, LinkTransactionToRemittanceForm
|
||||
from .models import Invoice, Product, Remittance, SpecialTransactionProxy
|
||||
from .tables import InvoiceTable, RemittanceTable, SpecialTransactionTable
|
||||
from .models import Invoice, Product, Remittance, SpecialTransactionProxy, SogeCredit
|
||||
from .tables import InvoiceTable, RemittanceTable, SpecialTransactionTable, SogeCreditTable
|
||||
|
||||
|
||||
class InvoiceCreateView(ProtectQuerysetMixin, LoginRequiredMixin, CreateView):
|
||||
@ -307,3 +309,61 @@ 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
|
||||
|
||||
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 not pattern:
|
||||
return qs.none()
|
||||
|
||||
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))
|
||||
)
|
||||
else:
|
||||
qs = qs.none()
|
||||
|
||||
if "valid" in self.request.GET:
|
||||
q = Q(credit_transaction=None)
|
||||
if not self.request.GET["valid"]:
|
||||
q = ~q
|
||||
qs = qs.filter(q)
|
||||
|
||||
return qs[:20]
|
||||
|
||||
|
||||
class SogeCreditManageView(LoginRequiredMixin, ProtectQuerysetMixin, BaseFormView, DetailView):
|
||||
"""
|
||||
Manage credits from the Société générale.
|
||||
"""
|
||||
model = SogeCredit
|
||||
form_class = Form
|
||||
|
||||
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')
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
import json
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.models import User
|
||||
from django.db import models
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
@ -312,3 +313,14 @@ class WEIMembership(Membership):
|
||||
)
|
||||
transaction._force_save = True
|
||||
transaction.save(force_insert=True)
|
||||
|
||||
if self.registration.soge_credit and "treasury" in settings.INSTALLED_APPS:
|
||||
# If the soge pays, then the transaction is unvalidated in a first time, then submitted for control
|
||||
# to treasurers.
|
||||
transaction.refresh_from_db()
|
||||
from treasury.models import SogeCredit
|
||||
soge_credit = SogeCredit.objects.get_or_create(user=self.user)[0]
|
||||
soge_credit.refresh_from_db()
|
||||
transaction.save()
|
||||
soge_credit.transactions.add(transaction)
|
||||
soge_credit.save()
|
||||
|
@ -73,7 +73,8 @@ class WEIDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
|
||||
club = context["club"]
|
||||
|
||||
club_transactions = Transaction.objects.all().filter(Q(source=club.note) | Q(destination=club.note)) \
|
||||
.filter(PermissionBackend.filter_queryset(self.request.user, Transaction, "view")).order_by('-id')
|
||||
.filter(PermissionBackend.filter_queryset(self.request.user, Transaction, "view"))\
|
||||
.order_by('-created_at', '-id')
|
||||
history_table = HistoryTable(club_transactions, prefix="history-")
|
||||
history_table.paginate(per_page=20, page=self.request.GET.get('history-page', 1))
|
||||
context['history_list'] = history_table
|
||||
@ -742,9 +743,10 @@ class WEIValidateRegistrationView(ProtectQuerysetMixin, LoginRequiredMixin, Crea
|
||||
return reverse_lazy("wei:wei_detail", kwargs={"pk": self.object.club.pk})
|
||||
|
||||
|
||||
class WEISurveyView(BaseFormView, DetailView):
|
||||
class WEISurveyView(LoginRequiredMixin, BaseFormView, DetailView):
|
||||
"""
|
||||
Display the survey for the WEI for first
|
||||
Display the survey for the WEI for first year members.
|
||||
Warning: this page is accessible for anyone that is connected, the view doesn't extend ProtectQuerySetMixin.
|
||||
"""
|
||||
model = WEIRegistration
|
||||
template_name = "wei/survey.html"
|
||||
@ -800,7 +802,7 @@ class WEISurveyView(BaseFormView, DetailView):
|
||||
return reverse_lazy('wei:wei_survey', args=(self.get_object().pk,))
|
||||
|
||||
|
||||
class WEISurveyEndView(TemplateView):
|
||||
class WEISurveyEndView(LoginRequiredMixin, TemplateView):
|
||||
template_name = "wei/survey_end.html"
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
@ -810,7 +812,7 @@ class WEISurveyEndView(TemplateView):
|
||||
return context
|
||||
|
||||
|
||||
class WEIClosedView(TemplateView):
|
||||
class WEIClosedView(LoginRequiredMixin, TemplateView):
|
||||
template_name = "wei/survey_closed.html"
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
|
Reference in New Issue
Block a user