mirror of https://gitlab.crans.org/bde/nk20
Merge branch 'happy-new-year' into 'main'
happy new year See merge request bde/nk20!226
This commit is contained in:
commit
ff3c30517e
|
@ -1,4 +1,4 @@
|
||||||
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
@ -123,6 +123,14 @@ class Activity(models.Model):
|
||||||
verbose_name=_('open'),
|
verbose_name=_('open'),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _("activity")
|
||||||
|
verbose_name_plural = _("activities")
|
||||||
|
unique_together = ("name", "date_start", "date_end",)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
@transaction.atomic
|
@transaction.atomic
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
|
@ -144,14 +152,6 @@ class Activity(models.Model):
|
||||||
if settings.DATABASES["default"]["ENGINE"] == 'django.db.backends.postgresql' else refresh_activities()
|
if settings.DATABASES["default"]["ENGINE"] == 'django.db.backends.postgresql' else refresh_activities()
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.name
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
verbose_name = _("activity")
|
|
||||||
verbose_name_plural = _("activities")
|
|
||||||
unique_together = ("name", "date_start", "date_end",)
|
|
||||||
|
|
||||||
|
|
||||||
class Entry(models.Model):
|
class Entry(models.Model):
|
||||||
"""
|
"""
|
||||||
|
@ -252,14 +252,13 @@ class Guest(models.Model):
|
||||||
verbose_name=_("inviter"),
|
verbose_name=_("inviter"),
|
||||||
)
|
)
|
||||||
|
|
||||||
@property
|
class Meta:
|
||||||
def has_entry(self):
|
verbose_name = _("guest")
|
||||||
try:
|
verbose_name_plural = _("guests")
|
||||||
if self.entry:
|
unique_together = ("activity", "last_name", "first_name", )
|
||||||
return True
|
|
||||||
return False
|
def __str__(self):
|
||||||
except AttributeError:
|
return self.first_name + " " + self.last_name
|
||||||
return False
|
|
||||||
|
|
||||||
@transaction.atomic
|
@transaction.atomic
|
||||||
def save(self, force_insert=False, force_update=False, using=None, update_fields=None):
|
def save(self, force_insert=False, force_update=False, using=None, update_fields=None):
|
||||||
|
@ -290,13 +289,14 @@ class Guest(models.Model):
|
||||||
|
|
||||||
return super().save(force_insert, force_update, using, update_fields)
|
return super().save(force_insert, force_update, using, update_fields)
|
||||||
|
|
||||||
def __str__(self):
|
@property
|
||||||
return self.first_name + " " + self.last_name
|
def has_entry(self):
|
||||||
|
try:
|
||||||
class Meta:
|
if self.entry:
|
||||||
verbose_name = _("guest")
|
return True
|
||||||
verbose_name_plural = _("guests")
|
return False
|
||||||
unique_together = ("activity", "last_name", "first_name", )
|
except AttributeError:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
class GuestTransaction(Transaction):
|
class GuestTransaction(Transaction):
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
@ -76,9 +76,6 @@ class Changelog(models.Model):
|
||||||
verbose_name=_('timestamp'),
|
verbose_name=_('timestamp'),
|
||||||
)
|
)
|
||||||
|
|
||||||
def delete(self, using=None, keep_parents=False):
|
|
||||||
raise ValidationError(_("Logs cannot be destroyed."))
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _("changelog")
|
verbose_name = _("changelog")
|
||||||
verbose_name_plural = _("changelogs")
|
verbose_name_plural = _("changelogs")
|
||||||
|
@ -86,3 +83,6 @@ class Changelog(models.Model):
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return _("Changelog of type \"{action}\" for model {model} at {timestamp}").format(
|
return _("Changelog of type \"{action}\" for model {model} at {timestamp}").format(
|
||||||
action=self.get_action_display(), model=str(self.model), timestamp=str(self.timestamp))
|
action=self.get_action_display(), model=str(self.model), timestamp=str(self.timestamp))
|
||||||
|
|
||||||
|
def delete(self, using=None, keep_parents=False):
|
||||||
|
raise ValidationError(_("Logs cannot be destroyed."))
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
|
@ -28,7 +28,6 @@ class Profile(models.Model):
|
||||||
We do not want to patch the Django Contrib :model:`auth.User`model;
|
We do not want to patch the Django Contrib :model:`auth.User`model;
|
||||||
so this model add an user profile with additional information.
|
so this model add an user profile with additional information.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
user = models.OneToOneField(
|
user = models.OneToOneField(
|
||||||
settings.AUTH_USER_MODEL,
|
settings.AUTH_USER_MODEL,
|
||||||
on_delete=models.CASCADE,
|
on_delete=models.CASCADE,
|
||||||
|
@ -139,6 +138,17 @@ class Profile(models.Model):
|
||||||
default=False
|
default=False
|
||||||
)
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _('user profile')
|
||||||
|
verbose_name_plural = _('user profile')
|
||||||
|
indexes = [models.Index(fields=['user'])]
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return str(self.user)
|
||||||
|
|
||||||
|
def get_absolute_url(self):
|
||||||
|
return reverse('member:user_detail', args=(self.user_id,))
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def ens_year(self):
|
def ens_year(self):
|
||||||
"""
|
"""
|
||||||
|
@ -163,17 +173,6 @@ class Profile(models.Model):
|
||||||
return SogeCredit.objects.filter(user=self.user, credit_transaction__isnull=False).exists()
|
return SogeCredit.objects.filter(user=self.user, credit_transaction__isnull=False).exists()
|
||||||
return False
|
return False
|
||||||
|
|
||||||
class Meta:
|
|
||||||
verbose_name = _('user profile')
|
|
||||||
verbose_name_plural = _('user profile')
|
|
||||||
indexes = [models.Index(fields=['user'])]
|
|
||||||
|
|
||||||
def get_absolute_url(self):
|
|
||||||
return reverse('member:user_detail', args=(self.user_id,))
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return str(self.user)
|
|
||||||
|
|
||||||
def send_email_validation_link(self):
|
def send_email_validation_link(self):
|
||||||
subject = "[Note Kfet] " + str(_("Activate your Note Kfet account"))
|
subject = "[Note Kfet] " + str(_("Activate your Note Kfet account"))
|
||||||
token = email_validation_token.make_token(self.user)
|
token = email_validation_token.make_token(self.user)
|
||||||
|
@ -205,9 +204,11 @@ class Club(models.Model):
|
||||||
max_length=255,
|
max_length=255,
|
||||||
unique=True,
|
unique=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
email = models.EmailField(
|
email = models.EmailField(
|
||||||
verbose_name=_('email'),
|
verbose_name=_('email'),
|
||||||
)
|
)
|
||||||
|
|
||||||
parent_club = models.ForeignKey(
|
parent_club = models.ForeignKey(
|
||||||
'self',
|
'self',
|
||||||
null=True,
|
null=True,
|
||||||
|
@ -258,6 +259,27 @@ class Club(models.Model):
|
||||||
help_text=_('Maximal date of a membership, after which members must renew it.'),
|
help_text=_('Maximal date of a membership, after which members must renew it.'),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _("club")
|
||||||
|
verbose_name_plural = _("clubs")
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
@transaction.atomic
|
||||||
|
def save(self, force_insert=False, force_update=False, using=None,
|
||||||
|
update_fields=None):
|
||||||
|
if not self.require_memberships:
|
||||||
|
self.membership_fee_paid = 0
|
||||||
|
self.membership_fee_unpaid = 0
|
||||||
|
self.membership_duration = None
|
||||||
|
self.membership_start = None
|
||||||
|
self.membership_end = None
|
||||||
|
super().save(force_insert, force_update, update_fields)
|
||||||
|
|
||||||
|
def get_absolute_url(self):
|
||||||
|
return reverse_lazy('member:club_detail', args=(self.pk,))
|
||||||
|
|
||||||
def update_membership_dates(self):
|
def update_membership_dates(self):
|
||||||
"""
|
"""
|
||||||
This function is called each time the club detail view is displayed.
|
This function is called each time the club detail view is displayed.
|
||||||
|
@ -278,27 +300,6 @@ class Club(models.Model):
|
||||||
self._force_save = True
|
self._force_save = True
|
||||||
self.save(force_update=True)
|
self.save(force_update=True)
|
||||||
|
|
||||||
@transaction.atomic
|
|
||||||
def save(self, force_insert=False, force_update=False, using=None,
|
|
||||||
update_fields=None):
|
|
||||||
if not self.require_memberships:
|
|
||||||
self.membership_fee_paid = 0
|
|
||||||
self.membership_fee_unpaid = 0
|
|
||||||
self.membership_duration = None
|
|
||||||
self.membership_start = None
|
|
||||||
self.membership_end = None
|
|
||||||
super().save(force_insert, force_update, update_fields)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
verbose_name = _("club")
|
|
||||||
verbose_name_plural = _("clubs")
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.name
|
|
||||||
|
|
||||||
def get_absolute_url(self):
|
|
||||||
return reverse_lazy('member:club_detail', args=(self.pk,))
|
|
||||||
|
|
||||||
|
|
||||||
class Membership(models.Model):
|
class Membership(models.Model):
|
||||||
"""
|
"""
|
||||||
|
@ -338,6 +339,66 @@ class Membership(models.Model):
|
||||||
verbose_name=_('fee'),
|
verbose_name=_('fee'),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _('membership')
|
||||||
|
verbose_name_plural = _('memberships')
|
||||||
|
indexes = [models.Index(fields=['user'])]
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return _("Membership of {user} for the club {club}").format(user=self.user.username, club=self.club.name, )
|
||||||
|
|
||||||
|
@transaction.atomic
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
Calculate fee and end date before saving the membership and creating the transaction if needed.
|
||||||
|
"""
|
||||||
|
# Ensure that club membership dates are valid
|
||||||
|
old_membership_start = self.club.membership_start
|
||||||
|
self.club.update_membership_dates()
|
||||||
|
if self.club.membership_start != old_membership_start:
|
||||||
|
self.club.save()
|
||||||
|
|
||||||
|
created = not self.pk
|
||||||
|
if not created:
|
||||||
|
for role in self.roles.all():
|
||||||
|
club = role.for_club
|
||||||
|
if club is not None:
|
||||||
|
if club.pk != self.club_id:
|
||||||
|
raise ValidationError(_('The role {role} does not apply to the club {club}.')
|
||||||
|
.format(role=role.name, club=club.name))
|
||||||
|
else:
|
||||||
|
if Membership.objects.filter(
|
||||||
|
user=self.user,
|
||||||
|
club=self.club,
|
||||||
|
date_start__lte=self.date_start,
|
||||||
|
date_end__gte=self.date_start,
|
||||||
|
).exists():
|
||||||
|
raise ValidationError(_('User is already a member of the club'))
|
||||||
|
|
||||||
|
if self.club.parent_club is not None:
|
||||||
|
# Check that the user is already a member of the parent club if the membership is created
|
||||||
|
if not Membership.objects.filter(
|
||||||
|
user=self.user,
|
||||||
|
club=self.club.parent_club,
|
||||||
|
date_start__gte=self.club.parent_club.membership_start,
|
||||||
|
).exists():
|
||||||
|
if hasattr(self, '_force_renew_parent') and self._force_renew_parent:
|
||||||
|
self.renew_parent()
|
||||||
|
else:
|
||||||
|
raise ValidationError(_('User is not a member of the parent club')
|
||||||
|
+ ' ' + self.club.parent_club.name)
|
||||||
|
|
||||||
|
self.fee = self.club.membership_fee_paid if self.user.profile.paid else self.club.membership_fee_unpaid
|
||||||
|
|
||||||
|
self.date_end = self.date_start + datetime.timedelta(days=self.club.membership_duration) \
|
||||||
|
if self.club.membership_duration is not None else self.date_start + datetime.timedelta(days=424242)
|
||||||
|
if self.club.membership_end is not None and self.date_end > self.club.membership_end:
|
||||||
|
self.date_end = self.club.membership_end
|
||||||
|
|
||||||
|
super().save(*args, **kwargs)
|
||||||
|
|
||||||
|
self.make_transaction()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def valid(self):
|
def valid(self):
|
||||||
"""
|
"""
|
||||||
|
@ -415,58 +476,6 @@ class Membership(models.Model):
|
||||||
parent_membership.roles.set(Role.objects.filter(name="Membre de club").all())
|
parent_membership.roles.set(Role.objects.filter(name="Membre de club").all())
|
||||||
parent_membership.save()
|
parent_membership.save()
|
||||||
|
|
||||||
@transaction.atomic
|
|
||||||
def save(self, *args, **kwargs):
|
|
||||||
"""
|
|
||||||
Calculate fee and end date before saving the membership and creating the transaction if needed.
|
|
||||||
"""
|
|
||||||
# Ensure that club membership dates are valid
|
|
||||||
old_membership_start = self.club.membership_start
|
|
||||||
self.club.update_membership_dates()
|
|
||||||
if self.club.membership_start != old_membership_start:
|
|
||||||
self.club.save()
|
|
||||||
|
|
||||||
created = not self.pk
|
|
||||||
if not created:
|
|
||||||
for role in self.roles.all():
|
|
||||||
club = role.for_club
|
|
||||||
if club is not None:
|
|
||||||
if club.pk != self.club_id:
|
|
||||||
raise ValidationError(_('The role {role} does not apply to the club {club}.')
|
|
||||||
.format(role=role.name, club=club.name))
|
|
||||||
else:
|
|
||||||
if Membership.objects.filter(
|
|
||||||
user=self.user,
|
|
||||||
club=self.club,
|
|
||||||
date_start__lte=self.date_start,
|
|
||||||
date_end__gte=self.date_start,
|
|
||||||
).exists():
|
|
||||||
raise ValidationError(_('User is already a member of the club'))
|
|
||||||
|
|
||||||
if self.club.parent_club is not None:
|
|
||||||
# Check that the user is already a member of the parent club if the membership is created
|
|
||||||
if not Membership.objects.filter(
|
|
||||||
user=self.user,
|
|
||||||
club=self.club.parent_club,
|
|
||||||
date_start__gte=self.club.parent_club.membership_start,
|
|
||||||
).exists():
|
|
||||||
if hasattr(self, '_force_renew_parent') and self._force_renew_parent:
|
|
||||||
self.renew_parent()
|
|
||||||
else:
|
|
||||||
raise ValidationError(_('User is not a member of the parent club')
|
|
||||||
+ ' ' + self.club.parent_club.name)
|
|
||||||
|
|
||||||
self.fee = self.club.membership_fee_paid if self.user.profile.paid else self.club.membership_fee_unpaid
|
|
||||||
|
|
||||||
self.date_end = self.date_start + datetime.timedelta(days=self.club.membership_duration) \
|
|
||||||
if self.club.membership_duration is not None else self.date_start + datetime.timedelta(days=424242)
|
|
||||||
if self.club.membership_end is not None and self.date_end > self.club.membership_end:
|
|
||||||
self.date_end = self.club.membership_end
|
|
||||||
|
|
||||||
super().save(*args, **kwargs)
|
|
||||||
|
|
||||||
self.make_transaction()
|
|
||||||
|
|
||||||
def make_transaction(self):
|
def make_transaction(self):
|
||||||
"""
|
"""
|
||||||
Create Membership transaction associated to this membership.
|
Create Membership transaction associated to this membership.
|
||||||
|
@ -504,11 +513,3 @@ class Membership(models.Model):
|
||||||
soge_credit.save()
|
soge_credit.save()
|
||||||
else:
|
else:
|
||||||
transaction.save(force_insert=True)
|
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, )
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
verbose_name = _('membership')
|
|
||||||
verbose_name_plural = _('memberships')
|
|
||||||
indexes = [models.Index(fields=['user'])]
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
import unicodedata
|
import unicodedata
|
||||||
|
@ -293,6 +293,11 @@ class Alias(models.Model):
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
|
@transaction.atomic
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
self.clean()
|
||||||
|
super().save(*args, **kwargs)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def normalize(string):
|
def normalize(string):
|
||||||
"""
|
"""
|
||||||
|
@ -321,11 +326,6 @@ class Alias(models.Model):
|
||||||
pass
|
pass
|
||||||
self.normalized_name = normalized_name
|
self.normalized_name = normalized_name
|
||||||
|
|
||||||
@transaction.atomic
|
|
||||||
def save(self, *args, **kwargs):
|
|
||||||
self.clean()
|
|
||||||
super().save(*args, **kwargs)
|
|
||||||
|
|
||||||
def delete(self, using=None, keep_parents=False):
|
def delete(self, using=None, keep_parents=False):
|
||||||
if self.name == str(self.note):
|
if self.name == str(self.note):
|
||||||
raise ValidationError(_("You can't delete your main alias."),
|
raise ValidationError(_("You can't delete your main alias."),
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
|
@ -59,6 +59,7 @@ class TransactionTemplate(models.Model):
|
||||||
amount = models.PositiveIntegerField(
|
amount = models.PositiveIntegerField(
|
||||||
verbose_name=_('amount'),
|
verbose_name=_('amount'),
|
||||||
)
|
)
|
||||||
|
|
||||||
category = models.ForeignKey(
|
category = models.ForeignKey(
|
||||||
TemplateCategory,
|
TemplateCategory,
|
||||||
on_delete=models.PROTECT,
|
on_delete=models.PROTECT,
|
||||||
|
@ -87,12 +88,12 @@ class TransactionTemplate(models.Model):
|
||||||
verbose_name = _("transaction template")
|
verbose_name = _("transaction template")
|
||||||
verbose_name_plural = _("transaction templates")
|
verbose_name_plural = _("transaction templates")
|
||||||
|
|
||||||
def get_absolute_url(self):
|
|
||||||
return reverse('note:template_update', args=(self.pk,))
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
|
def get_absolute_url(self):
|
||||||
|
return reverse('note:template_update', args=(self.pk,))
|
||||||
|
|
||||||
|
|
||||||
class Transaction(PolymorphicModel):
|
class Transaction(PolymorphicModel):
|
||||||
"""
|
"""
|
||||||
|
@ -101,7 +102,6 @@ class Transaction(PolymorphicModel):
|
||||||
amount is store in centimes of currency, making it a positive integer
|
amount is store in centimes of currency, making it a positive integer
|
||||||
value. (from someone to someone else)
|
value. (from someone to someone else)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
source = models.ForeignKey(
|
source = models.ForeignKey(
|
||||||
Note,
|
Note,
|
||||||
on_delete=models.PROTECT,
|
on_delete=models.PROTECT,
|
||||||
|
@ -166,6 +166,50 @@ class Transaction(PolymorphicModel):
|
||||||
models.Index(fields=['destination']),
|
models.Index(fields=['destination']),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
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")
|
||||||
|
|
||||||
|
@transaction.atomic
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
When saving, also transfer money between two notes
|
||||||
|
"""
|
||||||
|
if self.source.pk == self.destination.pk:
|
||||||
|
# When source == destination, no money is transferred and no transaction is created
|
||||||
|
return
|
||||||
|
|
||||||
|
self.source = Note.objects.select_for_update().get(pk=self.source_id)
|
||||||
|
self.destination = Note.objects.select_for_update().get(pk=self.destination_id)
|
||||||
|
|
||||||
|
# Check that the amounts stay between big integer bounds
|
||||||
|
diff_source, diff_dest = self.validate()
|
||||||
|
|
||||||
|
if not (hasattr(self, '_force_save') and self._force_save) \
|
||||||
|
and (not self.source.is_active or not self.destination.is_active):
|
||||||
|
raise ValidationError(_("The transaction can't be saved since the source note "
|
||||||
|
"or the destination note is not active."))
|
||||||
|
|
||||||
|
# If the aliases are not entered, we assume that the used alias is the name of the note
|
||||||
|
if not self.source_alias:
|
||||||
|
self.source_alias = str(self.source)
|
||||||
|
|
||||||
|
if not self.destination_alias:
|
||||||
|
self.destination_alias = str(self.destination)
|
||||||
|
|
||||||
|
# We save first the transaction, in case of the user has no right to transfer money
|
||||||
|
super().save(*args, **kwargs)
|
||||||
|
|
||||||
|
# Save notes
|
||||||
|
self.source.refresh_from_db()
|
||||||
|
self.source.balance += diff_source
|
||||||
|
self.source._force_save = True
|
||||||
|
self.source.save()
|
||||||
|
self.destination.refresh_from_db()
|
||||||
|
self.destination.balance += diff_dest
|
||||||
|
self.destination._force_save = True
|
||||||
|
self.destination.save()
|
||||||
|
|
||||||
def validate(self):
|
def validate(self):
|
||||||
previous_source_balance = self.source.balance
|
previous_source_balance = self.source.balance
|
||||||
previous_dest_balance = self.destination.balance
|
previous_dest_balance = self.destination.balance
|
||||||
|
@ -208,46 +252,6 @@ class Transaction(PolymorphicModel):
|
||||||
|
|
||||||
return source_balance - previous_source_balance, dest_balance - previous_dest_balance
|
return source_balance - previous_source_balance, dest_balance - previous_dest_balance
|
||||||
|
|
||||||
@transaction.atomic
|
|
||||||
def save(self, *args, **kwargs):
|
|
||||||
"""
|
|
||||||
When saving, also transfer money between two notes
|
|
||||||
"""
|
|
||||||
if self.source.pk == self.destination.pk:
|
|
||||||
# When source == destination, no money is transferred and no transaction is created
|
|
||||||
return
|
|
||||||
|
|
||||||
self.source = Note.objects.select_for_update().get(pk=self.source_id)
|
|
||||||
self.destination = Note.objects.select_for_update().get(pk=self.destination_id)
|
|
||||||
|
|
||||||
# Check that the amounts stay between big integer bounds
|
|
||||||
diff_source, diff_dest = self.validate()
|
|
||||||
|
|
||||||
if not (hasattr(self, '_force_save') and self._force_save) \
|
|
||||||
and (not self.source.is_active or not self.destination.is_active):
|
|
||||||
raise ValidationError(_("The transaction can't be saved since the source note "
|
|
||||||
"or the destination note is not active."))
|
|
||||||
|
|
||||||
# If the aliases are not entered, we assume that the used alias is the name of the note
|
|
||||||
if not self.source_alias:
|
|
||||||
self.source_alias = str(self.source)
|
|
||||||
|
|
||||||
if not self.destination_alias:
|
|
||||||
self.destination_alias = str(self.destination)
|
|
||||||
|
|
||||||
# We save first the transaction, in case of the user has no right to transfer money
|
|
||||||
super().save(*args, **kwargs)
|
|
||||||
|
|
||||||
# Save notes
|
|
||||||
self.source.refresh_from_db()
|
|
||||||
self.source.balance += diff_source
|
|
||||||
self.source._force_save = True
|
|
||||||
self.source.save()
|
|
||||||
self.destination.refresh_from_db()
|
|
||||||
self.destination.balance += diff_dest
|
|
||||||
self.destination._force_save = True
|
|
||||||
self.destination.save()
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def total(self):
|
def total(self):
|
||||||
return self.amount * self.quantity
|
return self.amount * self.quantity
|
||||||
|
@ -256,46 +260,40 @@ class Transaction(PolymorphicModel):
|
||||||
def type(self):
|
def type(self):
|
||||||
return _('Transfer')
|
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):
|
class RecurrentTransaction(Transaction):
|
||||||
"""
|
"""
|
||||||
Special type of :model:`note.Transaction` associated to a :model:`note.TransactionTemplate`.
|
Special type of :model:`note.Transaction` associated to a :model:`note.TransactionTemplate`.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
template = models.ForeignKey(
|
template = models.ForeignKey(
|
||||||
TransactionTemplate,
|
TransactionTemplate,
|
||||||
on_delete=models.PROTECT,
|
on_delete=models.PROTECT,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _("recurrent transaction")
|
||||||
|
verbose_name_plural = _("recurrent transactions")
|
||||||
|
|
||||||
|
@transaction.atomic
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
self.clean()
|
||||||
|
return super().save(*args, **kwargs)
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
if self.template.destination != self.destination and not (hasattr(self, '_force_save') and self._force_save):
|
if self.template.destination != self.destination and not (hasattr(self, '_force_save') and self._force_save):
|
||||||
raise ValidationError(
|
raise ValidationError(
|
||||||
_("The destination of this transaction must equal to the destination of the template."))
|
_("The destination of this transaction must equal to the destination of the template."))
|
||||||
return super().clean()
|
return super().clean()
|
||||||
|
|
||||||
@transaction.atomic
|
|
||||||
def save(self, *args, **kwargs):
|
|
||||||
self.clean()
|
|
||||||
return super().save(*args, **kwargs)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def type(self):
|
def type(self):
|
||||||
return _('Template')
|
return _('Template')
|
||||||
|
|
||||||
class Meta:
|
|
||||||
verbose_name = _("recurrent transaction")
|
|
||||||
verbose_name_plural = _("recurrent transactions")
|
|
||||||
|
|
||||||
|
|
||||||
class SpecialTransaction(Transaction):
|
class SpecialTransaction(Transaction):
|
||||||
"""
|
"""
|
||||||
Special type of :model:`note.Transaction` associated to transactions with special notes
|
Special type of :model:`note.Transaction` associated to transactions with special notes
|
||||||
"""
|
"""
|
||||||
|
|
||||||
last_name = models.CharField(
|
last_name = models.CharField(
|
||||||
max_length=255,
|
max_length=255,
|
||||||
verbose_name=_("name"),
|
verbose_name=_("name"),
|
||||||
|
@ -312,6 +310,15 @@ class SpecialTransaction(Transaction):
|
||||||
blank=True,
|
blank=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _("Special transaction")
|
||||||
|
verbose_name_plural = _("Special transactions")
|
||||||
|
|
||||||
|
@transaction.atomic
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
self.clean()
|
||||||
|
super().save(*args, **kwargs)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def type(self):
|
def type(self):
|
||||||
return _('Credit') if isinstance(self.source, NoteSpecial) else _("Debit")
|
return _('Credit') if isinstance(self.source, NoteSpecial) else _("Debit")
|
||||||
|
@ -328,11 +335,6 @@ class SpecialTransaction(Transaction):
|
||||||
raise ValidationError(_("A special transaction is only possible between a"
|
raise ValidationError(_("A special transaction is only possible between a"
|
||||||
" Note associated to a payment method and a User or a Club"))
|
" Note associated to a payment method and a User or a Club"))
|
||||||
|
|
||||||
@transaction.atomic
|
|
||||||
def save(self, *args, **kwargs):
|
|
||||||
self.clean()
|
|
||||||
super().save(*args, **kwargs)
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def validate_payment_form(form):
|
def validate_payment_form(form):
|
||||||
"""
|
"""
|
||||||
|
@ -363,17 +365,11 @@ class SpecialTransaction(Transaction):
|
||||||
|
|
||||||
return not error
|
return not error
|
||||||
|
|
||||||
class Meta:
|
|
||||||
verbose_name = _("Special transaction")
|
|
||||||
verbose_name_plural = _("Special transactions")
|
|
||||||
|
|
||||||
|
|
||||||
class MembershipTransaction(Transaction):
|
class MembershipTransaction(Transaction):
|
||||||
"""
|
"""
|
||||||
Special type of :model:`note.Transaction` associated to a :model:`member.Membership`.
|
Special type of :model:`note.Transaction` associated to a :model:`member.Membership`.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
membership = models.OneToOneField(
|
membership = models.OneToOneField(
|
||||||
'member.Membership',
|
'member.Membership',
|
||||||
on_delete=models.PROTECT,
|
on_delete=models.PROTECT,
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
import functools
|
import functools
|
||||||
|
@ -26,6 +26,15 @@ class InstancedPermission:
|
||||||
self.mask = mask
|
self.mask = mask
|
||||||
self.kwargs = kwargs
|
self.kwargs = kwargs
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
if self.field:
|
||||||
|
return _("Can {type} {model}.{field} in {query}").format(type=self.type, model=self.model, field=self.field, query=self.query)
|
||||||
|
else:
|
||||||
|
return _("Can {type} {model} in {query}").format(type=self.type, model=self.model, query=self.query)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.__repr__()
|
||||||
|
|
||||||
def applies(self, obj, permission_type, field_name=None):
|
def applies(self, obj, permission_type, field_name=None):
|
||||||
"""
|
"""
|
||||||
Returns True if the permission applies to
|
Returns True if the permission applies to
|
||||||
|
@ -84,21 +93,11 @@ class InstancedPermission:
|
||||||
# noinspection PyProtectedMember
|
# noinspection PyProtectedMember
|
||||||
self.query = Permission._about(self.raw_query, **self.kwargs)
|
self.query = Permission._about(self.raw_query, **self.kwargs)
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
if self.field:
|
|
||||||
return _("Can {type} {model}.{field} in {query}").format(type=self.type, model=self.model, field=self.field, query=self.query)
|
|
||||||
else:
|
|
||||||
return _("Can {type} {model} in {query}").format(type=self.type, model=self.model, query=self.query)
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.__repr__()
|
|
||||||
|
|
||||||
|
|
||||||
class PermissionMask(models.Model):
|
class PermissionMask(models.Model):
|
||||||
"""
|
"""
|
||||||
Permissions that are hidden behind a mask
|
Permissions that are hidden behind a mask
|
||||||
"""
|
"""
|
||||||
|
|
||||||
rank = models.PositiveSmallIntegerField(
|
rank = models.PositiveSmallIntegerField(
|
||||||
unique=True,
|
unique=True,
|
||||||
verbose_name=_('rank'),
|
verbose_name=_('rank'),
|
||||||
|
@ -110,13 +109,13 @@ class PermissionMask(models.Model):
|
||||||
verbose_name=_('description'),
|
verbose_name=_('description'),
|
||||||
)
|
)
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.description
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _("permission mask")
|
verbose_name = _("permission mask")
|
||||||
verbose_name_plural = _("permission masks")
|
verbose_name_plural = _("permission masks")
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.description
|
||||||
|
|
||||||
|
|
||||||
class Permission(models.Model):
|
class Permission(models.Model):
|
||||||
|
|
||||||
|
@ -194,16 +193,19 @@ class Permission(models.Model):
|
||||||
verbose_name = _("permission")
|
verbose_name = _("permission")
|
||||||
verbose_name_plural = _("permissions")
|
verbose_name_plural = _("permissions")
|
||||||
|
|
||||||
def clean(self):
|
def __str__(self):
|
||||||
self.query = json.dumps(json.loads(self.query))
|
return self.description
|
||||||
if self.field and self.type not in {'view', 'change'}:
|
|
||||||
raise ValidationError(_("Specifying field applies only to view and change permission types."))
|
|
||||||
|
|
||||||
@transaction.atomic
|
@transaction.atomic
|
||||||
def save(self, **kwargs):
|
def save(self, **kwargs):
|
||||||
self.full_clean()
|
self.full_clean()
|
||||||
super().save()
|
super().save()
|
||||||
|
|
||||||
|
def clean(self):
|
||||||
|
self.query = json.dumps(json.loads(self.query))
|
||||||
|
if self.field and self.type not in {'view', 'change'}:
|
||||||
|
raise ValidationError(_("Specifying field applies only to view and change permission types."))
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def compute_f(oper, **kwargs):
|
def compute_f(oper, **kwargs):
|
||||||
if isinstance(oper, list):
|
if isinstance(oper, list):
|
||||||
|
@ -317,9 +319,6 @@ class Permission(models.Model):
|
||||||
# query = self._about(query, **kwargs)
|
# query = self._about(query, **kwargs)
|
||||||
return InstancedPermission(self.model, query, self.type, self.field, self.mask, **kwargs)
|
return InstancedPermission(self.model, query, self.type, self.field, self.mask, **kwargs)
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.description
|
|
||||||
|
|
||||||
|
|
||||||
class Role(models.Model):
|
class Role(models.Model):
|
||||||
"""
|
"""
|
||||||
|
@ -344,9 +343,9 @@ class Role(models.Model):
|
||||||
default=None,
|
default=None,
|
||||||
)
|
)
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.name
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _("role permissions")
|
verbose_name = _("role permissions")
|
||||||
verbose_name_plural = _("role permissions")
|
verbose_name_plural = _("role permissions")
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# Copyright (C) 2018-2023 by BDE ENS Paris-Saclay
|
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
from datetime import date
|
from datetime import date
|
||||||
|
|
||||||
|
@ -20,7 +20,6 @@ class Invoice(models.Model):
|
||||||
"""
|
"""
|
||||||
An invoice model that can generates a true invoice.
|
An invoice model that can generates a true invoice.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
id = models.PositiveIntegerField(
|
id = models.PositiveIntegerField(
|
||||||
primary_key=True,
|
primary_key=True,
|
||||||
verbose_name=_("Invoice identifier"),
|
verbose_name=_("Invoice identifier"),
|
||||||
|
@ -81,6 +80,13 @@ class Invoice(models.Model):
|
||||||
verbose_name=_("tex source"),
|
verbose_name=_("tex source"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _("invoice")
|
||||||
|
verbose_name_plural = _("invoices")
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return _("Invoice #{id}").format(id=self.id)
|
||||||
|
|
||||||
@transaction.atomic
|
@transaction.atomic
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
|
@ -111,19 +117,11 @@ class Invoice(models.Model):
|
||||||
|
|
||||||
return super().save(*args, **kwargs)
|
return super().save(*args, **kwargs)
|
||||||
|
|
||||||
class Meta:
|
|
||||||
verbose_name = _("invoice")
|
|
||||||
verbose_name_plural = _("invoices")
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return _("Invoice #{id}").format(id=self.id)
|
|
||||||
|
|
||||||
|
|
||||||
class Product(models.Model):
|
class Product(models.Model):
|
||||||
"""
|
"""
|
||||||
Product that appears on an invoice.
|
Product that appears on an invoice.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
invoice = models.ForeignKey(
|
invoice = models.ForeignKey(
|
||||||
Invoice,
|
Invoice,
|
||||||
on_delete=models.CASCADE,
|
on_delete=models.CASCADE,
|
||||||
|
@ -147,6 +145,13 @@ class Product(models.Model):
|
||||||
verbose_name=_("Unit price"),
|
verbose_name=_("Unit price"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _("product")
|
||||||
|
verbose_name_plural = _("products")
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.designation} ({self.invoice})"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def amount_euros(self):
|
def amount_euros(self):
|
||||||
return "{:.2f}".format(self.amount / 100)
|
return "{:.2f}".format(self.amount / 100)
|
||||||
|
@ -159,37 +164,28 @@ class Product(models.Model):
|
||||||
def total_euros(self):
|
def total_euros(self):
|
||||||
return "{:.2f}".format(self.total / 100)
|
return "{:.2f}".format(self.total / 100)
|
||||||
|
|
||||||
class Meta:
|
|
||||||
verbose_name = _("product")
|
|
||||||
verbose_name_plural = _("products")
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return f"{self.designation} ({self.invoice})"
|
|
||||||
|
|
||||||
|
|
||||||
class RemittanceType(models.Model):
|
class RemittanceType(models.Model):
|
||||||
"""
|
"""
|
||||||
Store what kind of remittances can be stored.
|
Store what kind of remittances can be stored.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
note = models.OneToOneField(
|
note = models.OneToOneField(
|
||||||
NoteSpecial,
|
NoteSpecial,
|
||||||
on_delete=models.CASCADE,
|
on_delete=models.CASCADE,
|
||||||
)
|
)
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return str(self.note)
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _("remittance type")
|
verbose_name = _("remittance type")
|
||||||
verbose_name_plural = _("remittance types")
|
verbose_name_plural = _("remittance types")
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return str(self.note)
|
||||||
|
|
||||||
|
|
||||||
class Remittance(models.Model):
|
class Remittance(models.Model):
|
||||||
"""
|
"""
|
||||||
Treasurers want to regroup checks or bank transfers in bank remittances.
|
Treasurers want to regroup checks or bank transfers in bank remittances.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
date = models.DateTimeField(
|
date = models.DateTimeField(
|
||||||
default=timezone.now,
|
default=timezone.now,
|
||||||
verbose_name=_("Date"),
|
verbose_name=_("Date"),
|
||||||
|
@ -215,6 +211,17 @@ class Remittance(models.Model):
|
||||||
verbose_name = _("remittance")
|
verbose_name = _("remittance")
|
||||||
verbose_name_plural = _("remittances")
|
verbose_name_plural = _("remittances")
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return _("Remittance #{:d}: {}").format(self.id, self.comment, )
|
||||||
|
|
||||||
|
@transaction.atomic
|
||||||
|
def save(self, force_insert=False, force_update=False, using=None, update_fields=None):
|
||||||
|
# Check if all transactions have the right type.
|
||||||
|
if self.transactions.exists() and self.transactions.filter(~Q(source=self.remittance_type.note)).exists():
|
||||||
|
raise ValidationError("All transactions in a remittance must have the same type")
|
||||||
|
|
||||||
|
return super().save(force_insert, force_update, using, update_fields)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def transactions(self):
|
def transactions(self):
|
||||||
"""
|
"""
|
||||||
|
@ -237,17 +244,6 @@ class Remittance(models.Model):
|
||||||
"""
|
"""
|
||||||
return sum(transaction.total for transaction in self.transactions.all())
|
return sum(transaction.total for transaction in self.transactions.all())
|
||||||
|
|
||||||
@transaction.atomic
|
|
||||||
def save(self, force_insert=False, force_update=False, using=None, update_fields=None):
|
|
||||||
# Check if all transactions have the right type.
|
|
||||||
if self.transactions.exists() and self.transactions.filter(~Q(source=self.remittance_type.note)).exists():
|
|
||||||
raise ValidationError("All transactions in a remittance must have the same type")
|
|
||||||
|
|
||||||
return super().save(force_insert, force_update, using, update_fields)
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return _("Remittance #{:d}: {}").format(self.id, self.comment, )
|
|
||||||
|
|
||||||
|
|
||||||
class SpecialTransactionProxy(models.Model):
|
class SpecialTransactionProxy(models.Model):
|
||||||
"""
|
"""
|
||||||
|
@ -255,7 +251,6 @@ class SpecialTransactionProxy(models.Model):
|
||||||
That's why we create a proxy in this app, to link special transactions and remittances.
|
That's why we create a proxy in this app, to link special transactions and remittances.
|
||||||
If it isn't very clean, it does what we want.
|
If it isn't very clean, it does what we want.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
transaction = models.OneToOneField(
|
transaction = models.OneToOneField(
|
||||||
SpecialTransaction,
|
SpecialTransaction,
|
||||||
on_delete=models.CASCADE,
|
on_delete=models.CASCADE,
|
||||||
|
@ -301,6 +296,43 @@ class SogeCredit(models.Model):
|
||||||
null=True,
|
null=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
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))
|
||||||
|
|
||||||
|
@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)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def valid(self):
|
def valid(self):
|
||||||
return self.credit_transaction and self.credit_transaction.valid
|
return self.credit_transaction and self.credit_transaction.valid
|
||||||
|
@ -390,36 +422,6 @@ class SogeCredit(models.Model):
|
||||||
tr._force_save = True
|
tr._force_save = True
|
||||||
tr.save()
|
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):
|
def delete(self, **kwargs):
|
||||||
"""
|
"""
|
||||||
Deleting a SogeCredit is equivalent to say that the Société générale didn't pay.
|
Deleting a SogeCredit is equivalent to say that the Société générale didn't pay.
|
||||||
|
@ -447,10 +449,3 @@ class SogeCredit(models.Model):
|
||||||
self.credit_transaction._force_save = True
|
self.credit_transaction._force_save = True
|
||||||
self.credit_transaction.save()
|
self.credit_transaction.save()
|
||||||
super().delete(**kwargs)
|
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))
|
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 2.2.28 on 2024-01-11 14:45
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('wei', '0007_help_text_emergency_contact'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='weiclub',
|
||||||
|
name='year',
|
||||||
|
field=models.PositiveIntegerField(default=2024, unique=True, verbose_name='year'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -1,4 +1,4 @@
|
||||||
# Copyright (C) 2018-2023 by BDE ENS Paris-Saclay
|
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
import json
|
import json
|
||||||
|
@ -33,6 +33,10 @@ class WEIClub(Club):
|
||||||
verbose_name=_("date end"),
|
verbose_name=_("date end"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _("WEI")
|
||||||
|
verbose_name_plural = _("WEI")
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_current_wei(self):
|
def is_current_wei(self):
|
||||||
"""
|
"""
|
||||||
|
@ -46,10 +50,6 @@ class WEIClub(Club):
|
||||||
"""
|
"""
|
||||||
return
|
return
|
||||||
|
|
||||||
class Meta:
|
|
||||||
verbose_name = _("WEI")
|
|
||||||
verbose_name_plural = _("WEI")
|
|
||||||
|
|
||||||
|
|
||||||
class Bus(models.Model):
|
class Bus(models.Model):
|
||||||
"""
|
"""
|
||||||
|
@ -84,6 +84,14 @@ class Bus(models.Model):
|
||||||
help_text=_("Information about the survey for new members, encoded in JSON"),
|
help_text=_("Information about the survey for new members, encoded in JSON"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _("Bus")
|
||||||
|
verbose_name_plural = _("Buses")
|
||||||
|
unique_together = ('wei', 'name',)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def information(self):
|
def information(self):
|
||||||
"""
|
"""
|
||||||
|
@ -106,14 +114,6 @@ class Bus(models.Model):
|
||||||
registrations = [r for r in registrations if 'selected_bus_pk' in r.information]
|
registrations = [r for r in registrations if 'selected_bus_pk' in r.information]
|
||||||
return sum(1 for r in registrations if r.information['selected_bus_pk'] == self.pk)
|
return sum(1 for r in registrations if r.information['selected_bus_pk'] == self.pk)
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.name
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
verbose_name = _("Bus")
|
|
||||||
verbose_name_plural = _("Buses")
|
|
||||||
unique_together = ('wei', 'name',)
|
|
||||||
|
|
||||||
|
|
||||||
class BusTeam(models.Model):
|
class BusTeam(models.Model):
|
||||||
"""
|
"""
|
||||||
|
@ -142,20 +142,19 @@ class BusTeam(models.Model):
|
||||||
verbose_name=_("description"),
|
verbose_name=_("description"),
|
||||||
)
|
)
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.name + " (" + str(self.bus) + ")"
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
unique_together = ('bus', 'name',)
|
unique_together = ('bus', 'name',)
|
||||||
verbose_name = _("Bus team")
|
verbose_name = _("Bus team")
|
||||||
verbose_name_plural = _("Bus teams")
|
verbose_name_plural = _("Bus teams")
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name + " (" + str(self.bus) + ")"
|
||||||
|
|
||||||
|
|
||||||
class WEIRole(Role):
|
class WEIRole(Role):
|
||||||
"""
|
"""
|
||||||
A Role for the WEI can be bus chief, team chief, free electron, ...
|
A Role for the WEI can be bus chief, team chief, free electron, ...
|
||||||
"""
|
"""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _("WEI Role")
|
verbose_name = _("WEI Role")
|
||||||
verbose_name_plural = _("WEI Roles")
|
verbose_name_plural = _("WEI Roles")
|
||||||
|
@ -165,7 +164,6 @@ class WEIRegistration(models.Model):
|
||||||
"""
|
"""
|
||||||
Store personal data that can be useful for the WEI.
|
Store personal data that can be useful for the WEI.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
user = models.ForeignKey(
|
user = models.ForeignKey(
|
||||||
User,
|
User,
|
||||||
on_delete=models.PROTECT,
|
on_delete=models.PROTECT,
|
||||||
|
@ -258,6 +256,14 @@ class WEIRegistration(models.Model):
|
||||||
"encoded in JSON"),
|
"encoded in JSON"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
unique_together = ('user', 'wei',)
|
||||||
|
verbose_name = _("WEI User")
|
||||||
|
verbose_name_plural = _("WEI Users")
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return str(self.user)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def information(self):
|
def information(self):
|
||||||
"""
|
"""
|
||||||
|
@ -307,14 +313,6 @@ class WEIRegistration(models.Model):
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return str(self.user)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
unique_together = ('user', 'wei',)
|
|
||||||
verbose_name = _("WEI User")
|
|
||||||
verbose_name_plural = _("WEI Users")
|
|
||||||
|
|
||||||
|
|
||||||
class WEIMembership(Membership):
|
class WEIMembership(Membership):
|
||||||
bus = models.ForeignKey(
|
bus = models.ForeignKey(
|
||||||
|
|
Loading…
Reference in New Issue