8
0
Fork 0
mirror of https://gitlab2.federez.net/re2o/re2o synced 2024-11-04 17:06:27 +00:00

Merge branch 'fix_memb_adh' into 'dev'

Fix memb adh

See merge request re2o/re2o!562
This commit is contained in:
grizzly 2020-10-17 00:40:18 +02:00
commit b0fdeca744
15 changed files with 767 additions and 202 deletions

View file

@ -64,8 +64,10 @@ class VenteSerializer(NamespacedHMSerializer):
"number",
"name",
"prix",
"duration",
"type_cotisation",
"duration_connection",
"duration_days_connection",
"duration_membership",
"duration_days_membership",
"prix_total",
"api_url",
)
@ -77,7 +79,7 @@ class ArticleSerializer(NamespacedHMSerializer):
class Meta:
model = cotisations.Article
fields = ("name", "prix", "duration", "type_user", "type_cotisation", "api_url")
fields = ("name", "prix", "duration_membership", "duration_days_membership", "duration_connection", "duration_days_connection", "type_user", "api_url")
class BanqueSerializer(NamespacedHMSerializer):
@ -104,7 +106,7 @@ class CotisationSerializer(NamespacedHMSerializer):
class Meta:
model = cotisations.Cotisation
fields = ("vente", "type_cotisation", "date_start", "date_end", "api_url")
fields = ("vente", "type_cotisation", "date_start_con", "date_end_con", "date_start_memb", "date_end_memb", "api_url")
class ReminderUsersSerializer(UserSerializer):
@ -124,4 +126,4 @@ class ReminderSerializer(serializers.ModelSerializer):
class Meta:
model = preferences.Reminder
fields = ("days", "message", "users_to_remind")
fields = ("days", "message", "users_to_remind")

View file

@ -0,0 +1,117 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.29 on 2020-09-20 17:19
from __future__ import unicode_literals
import django.core.validators
from django.db import migrations, models
import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
('cotisations', '0042_auto_20191120_0159'),
]
operations = [
# migrations.RemoveField(
# model_name='article',
# name='duration',
# ),
# migrations.RemoveField(
# model_name='article',
# name='duration_days',
# ),
# migrations.RemoveField(
# model_name='article',
# name='type_cotisation',
# ),
# migrations.RemoveField(
# model_name='cotisation',
# name='date_end',
# ),
# migrations.RemoveField(
# model_name='cotisation',
# name='date_start',
# ),
# migrations.RemoveField(
# model_name='cotisation',
# name='type_cotisation',
# ),
# migrations.RemoveField(
# model_name='vente',
# name='duration',
# ),
# migrations.RemoveField(
# model_name='vente',
# name='duration_days',
# ),
# migrations.RemoveField(
# model_name='vente',
# name='type_cotisation',
# ),
migrations.AddField(
model_name='article',
name='duration_connection',
field=models.PositiveIntegerField(blank=True, null=True, validators=[django.core.validators.MinValueValidator(0)], verbose_name='duration of the connection (in months)'),
),
migrations.AddField(
model_name='article',
name='duration_days_connection',
field=models.PositiveIntegerField(blank=True, null=True, validators=[django.core.validators.MinValueValidator(0)], verbose_name='duration of the connection (in days, will be added to duration in months)'),
),
migrations.AddField(
model_name='article',
name='duration_days_membership',
field=models.PositiveIntegerField(blank=True, null=True, validators=[django.core.validators.MinValueValidator(0)], verbose_name='duration of the membership (in days, will be added to duration in months)'),
),
migrations.AddField(
model_name='article',
name='duration_membership',
field=models.PositiveIntegerField(blank=True, null=True, validators=[django.core.validators.MinValueValidator(0)], verbose_name='duration of the membership (in months)'),
),
migrations.AddField(
model_name='cotisation',
name='date_end_con',
field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='end date for the connection'),
preserve_default=False,
),
migrations.AddField(
model_name='cotisation',
name='date_end_memb',
field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='end date for the membership'),
preserve_default=False,
),
migrations.AddField(
model_name='cotisation',
name='date_start_con',
field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='start date for the connection'),
preserve_default=False,
),
migrations.AddField(
model_name='cotisation',
name='date_start_memb',
field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='start date for the membership'),
preserve_default=False,
),
migrations.AddField(
model_name='vente',
name='duration_connection',
field=models.PositiveIntegerField(blank=True, null=True, verbose_name='duration of the connection (in months)'),
),
migrations.AddField(
model_name='vente',
name='duration_days_connection',
field=models.PositiveIntegerField(blank=True, null=True, validators=[django.core.validators.MinValueValidator(0)], verbose_name='duration of the connection (in days, will be added to duration in months)'),
),
migrations.AddField(
model_name='vente',
name='duration_days_membership',
field=models.PositiveIntegerField(blank=True, null=True, validators=[django.core.validators.MinValueValidator(0)], verbose_name='duration of the membership (in days, will be added to duration in months)'),
),
migrations.AddField(
model_name='vente',
name='duration_membership',
field=models.PositiveIntegerField(blank=True, null=True, verbose_name='duration of the membership (in months)'),
),
]

View file

@ -0,0 +1,140 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.29 on 2020-09-20 17:19
from __future__ import unicode_literals
import django.core.validators
from django.db import migrations, models
import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
('cotisations', '0043_separation_membership_connection_p1'),
]
def split_dates(apps, schema_editor):
db_alias = schema_editor.connection.alias
cotisation = apps.get_model("cotisations", "Cotisation")
cotisations = cotisation.objects.using(db_alias).all()
for cotis in cotisations:
cotis.date_start_con = cotis.date_start
cotis.date_start_memb = cotis.date_start
cotis.date_end_con = cotis.date_end
cotis.date_end_memb = cotis.date_end
if cotis.type_cotisation == 'Connexion':
cotis.date_end_memb = cotis.date_start
if cotis.type_cotisation == 'Adhesion':
cotis.date_end_con = cotis.date_start
cotis.save()
def split_duration_articles_and_ventes(apps, schema_editor):
def split_duration(e):
e.duration_membership = e.duration
e.duration_connection = e.duration
e.duration_days_membership = e.duration_days
e.duration_days_connection = e.duration_days
if e.type_cotisation == 'Connexion':
e.duration_membership = 0
e.duration_days_membership = 0
if e.type_cotisation == 'Adhesion':
e.duration_connection = 0
e.duration_days_connection = 0
e.save()
db_alias = schema_editor.connection.alias
article = apps.get_model("cotisations", "Article")
vente = apps.get_model("cotisations", "Vente")
for a in article.objects.using(db_alias).all():
split_duration(a)
for v in vente.objects.using(db_alias).all():
split_duration(v)
def unsplit_dates(apps, schema_editor):
db_alias = schema_editor.connection.alias
cotisation = apps.get_model("cotisations", "Cotisation")
cotisations = cotisation.objects.using(db_alias).all()
for cotis in cotisations:
connection = cotis.date_start_con != cotis.date_end_con
adhesion = cotis.date_start_memb != cotis.date_end_memb
cotis.date_start = cotis.date_start_con
cotis.date_end = max(cotis.date_end_con, cotis.date_end_memb)
if connection:
cotis.type_cotisation = 'Connexion'
if adhesion:
cotis.type_cotisation = 'Adhesion'
if connection and adhesion:
cotis.type_cotisation = 'All'
if not (connection or adhesion):
cotis.type_cotisation = None
cotis.save()
def unsplit_duration_articles_and_ventes(apps, schema_editor):
def unsplit_duration(e):
e.duration = max(e.duration_membership, e.duration_connection)
e.duration_days = max(e.duration_days_membership, e.duration_days_connection)
connection = not (((e.duration_connection == 0) or (e.duration_connection__isnull)) and \
((e.duration_days_connection == 0) or (e.duration_days_connection__isnull)))
membership = not (((e.duration_membership == 0) or (e.duration_membership__isnull)) and \
((e.duration_days_membership == 0) or (e.duration_days_membership__isnull)))
if connection:
e.type_cotisation = 'Connection'
if membership:
e.type_cotisation = 'Adhesion'
if connection and membership:
e.type_cotisation = 'All'
if not (connection or membership):
e.type_cotisation = None
e.save()
db_alias = schema_editor.connection.alias
article = apps.get_model("cotisations", "Article")
vente = apps.get_model("cotisations", "Vente")
for a in article.objects.using(db_alias).all():
unsplit_duration(a)
for v in vente.objects.using(db_alias).all():
unsplit_duration(v)
operations = [
migrations.RunPython(split_dates, unsplit_dates),
migrations.RunPython(split_duration_articles_and_ventes, unsplit_duration_articles_and_ventes),
# migrations.RemoveField(
# model_name='article',
# name='duration',
# ),
# migrations.RemoveField(
# model_name='article',
# name='duration_days',
# ),
# migrations.RemoveField(
# model_name='article',
# name='type_cotisation',
# ),
# migrations.RemoveField(
# model_name='cotisation',
# name='date_end',
# ),
# migrations.RemoveField(
# model_name='cotisation',
# name='date_start',
# ),
# migrations.RemoveField(
# model_name='cotisation',
# name='type_cotisation',
# ),
# migrations.RemoveField(
# model_name='vente',
# name='duration',
# ),
# migrations.RemoveField(
# model_name='vente',
# name='duration_days',
# ),
# migrations.RemoveField(
# model_name='vente',
# name='type_cotisation',
# ),
]

View file

@ -0,0 +1,53 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.29 on 2020-09-20 17:19
from __future__ import unicode_literals
import django.core.validators
from django.db import migrations, models
import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
('cotisations', '0044_separation_membership_connection_p2'),
]
operations = [
migrations.RemoveField(
model_name='article',
name='duration',
),
migrations.RemoveField(
model_name='article',
name='duration_days',
),
migrations.RemoveField(
model_name='article',
name='type_cotisation',
),
migrations.RemoveField(
model_name='cotisation',
name='date_end',
),
migrations.RemoveField(
model_name='cotisation',
name='date_start',
),
migrations.RemoveField(
model_name='cotisation',
name='type_cotisation',
),
migrations.RemoveField(
model_name='vente',
name='duration',
),
migrations.RemoveField(
model_name='vente',
name='duration_days',
),
migrations.RemoveField(
model_name='vente',
name='type_cotisation',
),
]

View file

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.29 on 2020-09-25 16:45
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cotisations', '0045_separation_membership_connection_p3'),
]
operations = [
migrations.AddField(
model_name='article',
name='need_membership',
field=models.BooleanField(default=True, verbose_name='can be purcharsed without membership'),
),
]

View file

@ -0,0 +1,28 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.29 on 2020-09-25 16:45
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cotisations', '0046_article_need_membership'),
]
def init_need_membership(apps, schema_editor):
db_alias = schema_editor.connection.alias
article = apps.get_model("cotisations", "Article")
articles = article.objects.using(db_alias).all()
for art in articles:
v = False
v = v or bool(art.duration_membership)
v = v or bool(art.duration_days_membership)
v = v or not (bool(art.duration_connection) or bool(art.duration_days_connection))
art.need_membership = v
art.save()
operations = [
migrations.RunPython(init_need_membership, lambda *args, **kargs: None),
]

View file

@ -0,0 +1,50 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.29 on 2020-10-16 22:18
from __future__ import unicode_literals
import django.core.validators
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cotisations', '0047_article_need_membership_init'),
]
def set_value_to_0(apps, schema_editor):
ventes = apps.get_model("cotisations", "Vente")
ventes.filter(duration_connection__isnull=True).update(duration_connection=0)
ventes.filter(duration_days_connection__isnull=True).update(duration_days_connection=0)
ventes.filter(duration_membership__isnull=True).update(duration_membership=0)
ventes.filter(duration_days_membership__isnull=True).update(duration_days_membership=0)
operations = [
migrations.RunPython(set_value_to_0),
migrations.AlterField(
model_name='article',
name='need_membership',
field=models.BooleanField(default=True, verbose_name='need membership to be purchased'),
),
migrations.AlterField(
model_name='vente',
name='duration_connection',
field=models.PositiveIntegerField(default=0, verbose_name='duration of the connection (in months)'),
),
migrations.AlterField(
model_name='vente',
name='duration_days_connection',
field=models.PositiveIntegerField(default=0, validators=[django.core.validators.MinValueValidator(0)], verbose_name='duration of the connection (in days, will be added to duration in months)'),
),
migrations.AlterField(
model_name='vente',
name='duration_days_membership',
field=models.PositiveIntegerField(default=0, validators=[django.core.validators.MinValueValidator(0)], verbose_name='duration of the membership (in days, will be added to duration in months)'),
),
migrations.AlterField(
model_name='vente',
name='duration_membership',
field=models.PositiveIntegerField(default=0, verbose_name='duration of the membership (in months)'),
),
]

View file

@ -283,7 +283,8 @@ class Facture(BaseInvoice):
"""Returns every subscription associated with this invoice."""
return Cotisation.objects.filter(
vente__in=self.vente_set.filter(
Q(type_cotisation="All") | Q(type_cotisation="Adhesion")
~(Q(duration_membership=0)) |\
~(Q(duration_days_membership=0))
)
)
@ -297,33 +298,18 @@ class Facture(BaseInvoice):
for purchase in self.vente_set.all():
if hasattr(purchase, "cotisation"):
cotisation = purchase.cotisation
if cotisation.type_cotisation == "Connexion":
cotisation.date_start = date_con
date_con += relativedelta(
months=(purchase.duration or 0) * purchase.number,
days=(purchase.duration_days or 0) * purchase.number,
)
cotisation.date_end = date_con
elif cotisation.type_cotisation == "Adhesion":
cotisation.date_start = date_adh
date_adh += relativedelta(
months=(purchase.duration or 0) * purchase.number,
days=(purchase.duration_days or 0) * purchase.number,
)
cotisation.date_end = date_adh
else: # it is assumed that adhesion is required for a connexion
date = min(date_adh, date_con)
cotisation.date_start = date
date_adh += relativedelta(
months=(purchase.duration or 0) * purchase.number,
days=(purchase.duration_days or 0) * purchase.number,
)
date_con += relativedelta(
months=(purchase.duration or 0) * purchase.number,
days=(purchase.duration_days or 0) * purchase.number,
)
date = max(date_adh, date_con)
cotisation.date_end = date
cotisation.date_start_con = date_con
date_con += relativedelta(
months=(purchase.duration_connection or 0) * purchase.number,
days=(purchase.duration_days_connection or 0) * purchase.number,
)
cotisation.date_end_con = date_con
cotisation.date_start_memb = date_adh
date_adh += relativedelta(
months=(purchase.duration_membership or 0) * purchase.number,
days=(purchase.duration_days_membership or 0) * purchase.number,
)
cotisation.date_end_memb = date_adh
cotisation.save()
purchase.facture = self
purchase.save()
@ -450,13 +436,6 @@ class Vente(RevMixin, AclMixin, models.Model):
the effect of the purchase on the time agreed for this user)
"""
# TODO : change this to English
COTISATION_TYPE = (
("Connexion", _("Connection")),
("Adhesion", _("Membership")),
("All", _("Both of them")),
)
# TODO : change facture to invoice
facture = models.ForeignKey(
"BaseInvoice", on_delete=models.CASCADE, verbose_name=_("invoice")
@ -465,28 +444,29 @@ class Vente(RevMixin, AclMixin, models.Model):
number = models.IntegerField(
validators=[MinValueValidator(1)], verbose_name=_("amount")
)
# TODO : change this field for a ForeinKey to Article
# TODO : change this field for a ForeinKey to Article
# Note: With a foreign key, modifing an Article modifis the Purchase, wich is bad.
# To use a foreign key, you need to make Article read only
name = models.CharField(max_length=255, verbose_name=_("article"))
# TODO : change prix to price
# TODO : this field is not needed if you use Article ForeignKey
prix = models.DecimalField(max_digits=5, decimal_places=2, verbose_name=_("price"))
# TODO : this field is not needed if you use Article ForeignKey
duration = models.PositiveIntegerField(
blank=True, null=True, verbose_name=_("duration (in months)")
duration_connection = models.PositiveIntegerField(
default=0, verbose_name=_("duration of the connection (in months)")
)
duration_days = models.PositiveIntegerField(
blank=True,
null=True,
duration_days_connection = models.PositiveIntegerField(
default=0,
validators=[MinValueValidator(0)],
verbose_name=_("duration (in days, will be added to duration in months)"),
verbose_name=_("duration of the connection (in days, will be added to duration in months)"),
)
# TODO : this field is not needed if you use Article ForeignKey
type_cotisation = models.CharField(
choices=COTISATION_TYPE,
blank=True,
null=True,
max_length=255,
verbose_name=_("subscription type"),
duration_membership = models.PositiveIntegerField(
default=0, verbose_name=_("duration of the membership (in months)")
)
duration_days_membership = models.PositiveIntegerField(
default=0,
validators=[MinValueValidator(0)],
verbose_name=_("duration of the membership (in days, will be added to duration in months)"),
)
class Meta:
@ -511,34 +491,48 @@ class Vente(RevMixin, AclMixin, models.Model):
"""
if hasattr(self, "cotisation"):
cotisation = self.cotisation
cotisation.date_end = cotisation.date_start + relativedelta(
months=(self.duration or 0) * self.number,
days=(self.duration_days or 0) * self.number,
cotisation.date_end_memb = cotisation.date_start_memb + relativedelta(
months=(self.duration_membership or 0) * self.number,
days=(self.duration_days_membership or 0) * self.number,
)
cotisation.date_end_con = cotisation.date_start_con + relativedelta(
months=(self.duration_connection or 0) * self.number,
days=(self.duration_days_connection or 0) * self.number,
)
return
def create_cotis(self, date_start=False):
def create_cotis(self, date_start_con=False, date_start_memb=False):
"""
Creates a cotisation without initializing the dates (start and end ar set to self.facture.facture.date) and without saving it. You should use Facture.reorder_purchases to set the right dates.
Creates a cotisation without initializing the dates (start and end ar set to self.facture.facture.date)
and without saving it. You should use Facture.reorder_purchases to set the right dates.
"""
try:
invoice = self.facture.facture
except Facture.DoesNotExist:
return
if not hasattr(self, "cotisation") and self.type_cotisation:
if not hasattr(self, "cotisation") and self.test_membership_or_connection():
cotisation = Cotisation(vente=self)
cotisation.type_cotisation = self.type_cotisation
if date_start:
cotisation.date_start = date_start
cotisation.date_end = cotisation.date_start + relativedelta(
months=(self.duration or 0) * self.number,
days=(self.duration_days or 0) * self.number,
if date_start_con:
cotisation.date_start_con = date_start_con
cotisation.date_end_con = cotisation.date_start_con + relativedelta(
months=(self.duration_connection or 0) * self.number,
days=(self.duration_days_connection or 0) * self.number,
)
self.save()
cotisation.save()
if date_start_memb:
cotisation.date_start_memb = date_start_memb
cotisation.date_end_memb = cotisation.date_start_memb + relativedelta(
months=(self.duration_membership or 0) * self.number,
days=(self.duration_days_membership or 0) * self.number,
)
self.save()
cotisation.save()
else:
cotisation.date_start = invoice.date
cotisation.date_end = invoice.date
cotisation.date_start_con = invoice.date
cotisation.date_start_memb = invoice.date
cotisation.date_end_con = invoice.date
cotisation.date_end_memb = invoice.date
def save(self, *args, **kwargs):
"""
@ -546,9 +540,6 @@ class Vente(RevMixin, AclMixin, models.Model):
It also update the associated cotisation in the changes have some
effect on the user's cotisation
"""
# Checking that if a cotisation is specified, there is also a duration
if self.type_cotisation and not (self.duration or self.duration_days):
raise ValidationError(_("Duration must be specified for a subscription."))
self.update_cotisation()
super(Vente, self).save(*args, **kwargs)
@ -629,6 +620,13 @@ class Vente(RevMixin, AclMixin, models.Model):
def __str__(self):
return str(self.name) + " " + str(self.facture)
def test_membership_or_connection(self):
""" Test if the purchase include membership or connecton
"""
return self.duration_membership or \
self.duration_days_membership or \
self.duration_connection or \
self.duration_days_connection
# TODO : change vente to purchase
@receiver(post_save, sender=Vente)
@ -645,7 +643,7 @@ def vente_post_save(**kwargs):
if hasattr(purchase, "cotisation"):
purchase.cotisation.vente = purchase
purchase.cotisation.save()
if purchase.type_cotisation:
if purchase.test_membership_or_connection():
purchase.create_cotis()
purchase.cotisation.save()
user = purchase.facture.facture.user
@ -677,56 +675,60 @@ class Article(RevMixin, AclMixin, models.Model):
It's represented by:
* a name
* a price
* a cotisation type (indicating if this article reprensents a
cotisation or not)
* a duration (if it is a cotisation)
* a duration for the membership
* a duration for the connection
* if the article can be purchased without membership
* a type of user (indicating what kind of user can buy this article)
"""
# TODO : Either use TYPE or TYPES in both choices but not both
USER_TYPES = (
("Adherent", _("Member")),
("Club", _("Club")),
("All", _("Both of them")),
)
COTISATION_TYPE = (
("Connexion", _("Connection")),
("Adhesion", _("Membership")),
("All", _("Both of them")),
)
name = models.CharField(max_length=255, verbose_name=_("designation"))
# TODO : change prix to price
prix = models.DecimalField(
max_digits=5, decimal_places=2, verbose_name=_("unit price")
)
duration = models.PositiveIntegerField(
duration_membership = models.PositiveIntegerField(
blank=True,
null=True,
validators=[MinValueValidator(0)],
verbose_name=_("duration (in months)"),
verbose_name=_("duration of the membership (in months)")
)
duration_days = models.PositiveIntegerField(
duration_days_membership = models.PositiveIntegerField(
blank=True,
null=True,
validators=[MinValueValidator(0)],
verbose_name=_("duration (in days, will be added to duration in months)"),
verbose_name=_("duration of the membership (in days, will be added to duration in months)"),
)
duration_connection = models.PositiveIntegerField(
blank=True,
null=True,
validators=[MinValueValidator(0)],
verbose_name=_("duration of the connection (in months)")
)
duration_days_connection = models.PositiveIntegerField(
blank=True,
null=True,
validators=[MinValueValidator(0)],
verbose_name=_("duration of the connection (in days, will be added to duration in months)"),
)
need_membership = models.BooleanField(
default=True,
verbose_name=_("need membership to be purchased"),
)
type_user = models.CharField(
choices=USER_TYPES,
default="All",
max_length=255,
verbose_name=_("type of users concerned"),
)
type_cotisation = models.CharField(
choices=COTISATION_TYPE,
default=None,
blank=True,
null=True,
max_length=255,
verbose_name=_("subscription type"),
)
available_for_everyone = models.BooleanField(
default=False, verbose_name=_("is available for every user")
)
@ -744,8 +746,6 @@ class Article(RevMixin, AclMixin, models.Model):
def clean(self):
if self.name.lower() == "solde":
raise ValidationError(_("Solde is a reserved article name."))
if self.type_cotisation and not (self.duration or self.duration_days):
raise ValidationError(_("Duration must be specified for a subscription."))
def __str__(self):
return self.name
@ -790,9 +790,9 @@ class Article(RevMixin, AclMixin, models.Model):
)
if target_user is not None and not target_user.is_adherent():
objects_pool = objects_pool.filter(
Q(type_cotisation="All")
| Q(type_cotisation="Adhesion")
| Q(type_cotisation__isnull=True)
Q(duration_membership__gt=0)
|Q(duration_days_membership__gt=0)
|Q(need_membership=False)
)
if user.has_perm("cotisations.buy_every_article"):
return objects_pool
@ -882,7 +882,7 @@ class Paiement(RevMixin, AclMixin, models.Model):
# In case a cotisation was bought, inform the user, the
# cotisation time has been extended too
if any(sell.type_cotisation for sell in invoice.vente_set.all()):
if any(sell.test_membership_or_connection() for sell in invoice.vente_set.all()):
messages.success(
request,
_(
@ -943,31 +943,21 @@ class Cotisation(RevMixin, AclMixin, models.Model):
The model defining a cotisation. It holds information about the time a user
is allowed when he has paid something.
It characterised by :
* a date_start (the date when the cotisaiton begins/began
* a date_end (the date when the cotisation ends/ended
* a type of cotisation (which indicates the implication of such
cotisation)
* a date_start_memb (the date when the membership begins/began
* a date_end_memb (the date when the membership ends/ended
* a date_start_con (the date when the connection begins/began)
* a date_end_con (the date when the connection ends/ended)
* a purchase (the related objects this cotisation is linked to)
"""
COTISATION_TYPE = (
("Connexion", _("Connection")),
("Adhesion", _("Membership")),
("All", _("Both of them")),
)
# TODO : change vente to purchase
vente = models.OneToOneField(
"Vente", on_delete=models.CASCADE, null=True, verbose_name=_("purchase")
)
type_cotisation = models.CharField(
choices=COTISATION_TYPE,
max_length=255,
default="All",
verbose_name=_("subscription type"),
)
date_start = models.DateTimeField(verbose_name=_("start date"))
date_end = models.DateTimeField(verbose_name=_("end date"))
date_start_con = models.DateTimeField(verbose_name=_("start date for the connection"))
date_end_con = models.DateTimeField(verbose_name=_("end date for the connection"))
date_start_memb = models.DateTimeField(verbose_name=_("start date for the membership"))
date_end_memb = models.DateTimeField(verbose_name=_("end date for the membership"))
class Meta:
permissions = (
@ -1037,9 +1027,14 @@ class Cotisation(RevMixin, AclMixin, models.Model):
return (
str(self.vente)
+ "from "
+ str(self.date_start)
+ str(self.date_start_memb)
+ " to "
+ str(self.date_end)
+ str(self.date_end_memb)
+ " for membership, "
+ str(self.date_start_con)
+ " to "
+ str(self.date_end_con)
+ " for the connection."
)

View file

@ -32,9 +32,10 @@ with this program; if not, write to the Free Software Foundation, Inc.,
<tr>
<th>{% trans "Article" %}</th>
<th>{% trans "Price" %}</th>
<th>{% trans "Subscription type" %}</th>
<th>{% trans "Duration (in months)" %}</th>
<th>{% trans "Duration (in days)" %}</th>
<th>{% trans "Duration membership (in months)" %}</th>
<th>{% trans "Duration membership (in days)" %}</th>
<th>{% trans "Duration connection (in months)" %}</th>
<th>{% trans "Duration connection (in days)" %}</th>
<th>{% trans "Concerned users" %}</th>
<th>{% trans "Available for everyone" %}</th>
<th></th>
@ -44,9 +45,10 @@ with this program; if not, write to the Free Software Foundation, Inc.,
<tr>
<td>{{ article.name }}</td>
<td>{{ article.prix }}</td>
<td>{{ article.type_cotisation }}</td>
<td>{{ article.duration }}</td>
<td>{{ article.duration_days }}</td>
<td>{{ article.duration_membership }}</td>
<td>{{ article.duration_days_membership }}</td>
<td>{{ article.duration_connection }}</td>
<td>{{ article.duration_days_connection }}</td>
<td>{{ article.type_user }}</td>
<td>{{ article.available_for_everyone | tick }}</td>
<td class="text-right">

View file

@ -19,15 +19,17 @@ class VenteModelTests(TestCase):
def test_one_day_cotisation(self):
"""
It should be possible to have one day membership.
Add one day of membership and one day of connection.
"""
date = timezone.now()
purchase = Vente.objects.create(
facture=self.f,
number=1,
name="Test purchase",
duration=0,
duration_days=1,
type_cotisation="All",
duration_connection=0,
duration_days_connection=1,
duration_membership=0,
duration_days_membership=1,
prix=0,
)
self.f.reorder_purchases()
@ -36,48 +38,66 @@ class VenteModelTests(TestCase):
datetime.timedelta(days=1),
delta=datetime.timedelta(seconds=1),
)
self.assertAlmostEqual(
self.user.end_adhesion() - date,
datetime.timedelta(days=1),
delta=datetime.timedelta(seconds=1),
)
def test_one_month_cotisation(self):
"""
It should be possible to have one day membership.
Add one mounth of membership and one mounth of connection
"""
date = timezone.now()
Vente.objects.create(
facture=self.f,
number=1,
name="Test purchase",
duration=1,
duration_days=0,
type_cotisation="All",
duration_connection=1,
duration_days_connection=0,
duration_membership=1,
duration_days_membership=0,
prix=0,
)
self.f.reorder_purchases()
end = self.user.end_connexion()
end_con = self.user.end_connexion()
end_memb = self.user.end_adhesion()
expected_end = date + relativedelta(months=1)
self.assertEqual(end.day, expected_end.day)
self.assertEqual(end.month, expected_end.month)
self.assertEqual(end.year, expected_end.year)
self.assertEqual(end_con.day, expected_end.day)
self.assertEqual(end_con.month, expected_end.month)
self.assertEqual(end_con.year, expected_end.year)
self.assertEqual(end_memb.day, expected_end.day)
self.assertEqual(end_memb.month, expected_end.month)
self.assertEqual(end_memb.year, expected_end.year)
def test_one_month_and_one_week_cotisation(self):
"""
It should be possible to have one day membership.
Add one mounth and one week of membership and one mounth
and one week of connection
"""
date = timezone.now()
Vente.objects.create(
facture=self.f,
number=1,
name="Test purchase",
duration=1,
duration_days=7,
type_cotisation="All",
duration_connection=1,
duration_days_connection=7,
duration_membership=1,
duration_days_membership=7,
prix=0,
)
self.f.reorder_purchases()
end = self.user.end_connexion()
end_con = self.user.end_connexion()
end_memb = self.user.end_adhesion()
expected_end = date + relativedelta(months=1, days=7)
self.assertEqual(end.day, expected_end.day)
self.assertEqual(end.month, expected_end.month)
self.assertEqual(end.year, expected_end.year)
self.assertEqual(end_con.day, expected_end.day)
self.assertEqual(end_con.month, expected_end.month)
self.assertEqual(end_con.year, expected_end.year)
self.assertEqual(end_memb.day, expected_end.day)
self.assertEqual(end_memb.month, expected_end.month)
self.assertEqual(end_memb.year, expected_end.year)
def test_date_start_cotisation(self):
"""
@ -87,15 +107,140 @@ class VenteModelTests(TestCase):
facture=self.f,
number=1,
name="Test purchase",
duration=0,
duration_days=1,
type_cotisation = 'All',
duration_connection=0,
duration_days_connection=1,
duration_membership=0,
duration_deys_membership=1,
prix=0
)
v.create_cotis(date_start=timezone.make_aware(datetime.datetime(1998, 10, 16)))
v.create_cotis(date_start_con=timezone.make_aware(datetime.datetime(1998, 10, 16)), date_start_memb=timezone.make_aware(datetime.datetime(1998, 10, 16)))
v.save()
self.assertEqual(v.cotisation.date_end, timezone.make_aware(datetime.datetime(1998, 10, 17)))
self.assertEqual(v.cotisation.date_end_con, timezone.make_aware(datetime.datetime(1998, 10, 17)))
self.assertEqual(v.cotisation.date_end_memb, timezone.make_aware(datetime.datetime(1998, 10, 17)))
def test_one_day_cotisation_membership_only(self):
"""
It should be possible to have one day membership without connection.
Add one day of membership and no connection.
"""
date = timezone.now()
purchase = Vente.objects.create(
facture=self.f,
number=1,
name="Test purchase",
duration_connection=0,
duration_days_connection=0,
duration_membership=0,
duration_days_membership=1,
prix=0,
)
self.f.reorder_purchases()
self.assertEqual(
self.user.end_connexion(),
None,
)
self.assertAlmostEqual(
self.user.end_adhesion() - date,
datetime.timedelta(days=1),
delta=datetime.timedelta(seconds=1),
)
def test_one_month_cotisation_membership_only(self):
"""
It should be possible to have one month membership.
Add one mounth of membership and no connection
"""
date = timezone.now()
Vente.objects.create(
facture=self.f,
number=1,
name="Test purchase",
duration_connection=0,
duration_days_connection=0,
duration_membership=1,
duration_days_membership=0,
prix=0,
)
self.f.reorder_purchases()
end_con = self.user.end_connexion()
end_memb = self.user.end_adhesion()
expected_end = date + relativedelta(months=1)
self.assertEqual(end_con, None)
self.assertEqual(end_memb.day, expected_end.day)
self.assertEqual(end_memb.month, expected_end.month)
self.assertEqual(end_memb.year, expected_end.year)
def test_one_month_and_one_week_cotisation_membership_only(self):
"""
It should be possible to have one mounth and one week membership.
Add one mounth and one week of membership and no connection.
"""
date = timezone.now()
Vente.objects.create(
facture=self.f,
number=1,
name="Test purchase",
duration_connection=0,
duration_days_connection=0,
duration_membership=1,
duration_days_membership=7,
prix=0,
)
self.f.reorder_purchases()
end_con = self.user.end_connexion()
end_memb = self.user.end_adhesion()
expected_end = date + relativedelta(months=1, days=7)
self.assertEqual(end_con, None)
self.assertEqual(end_memb.day, expected_end.day)
self.assertEqual(end_memb.month, expected_end.month)
self.assertEqual(end_memb.year, expected_end.year)
def test_date_start_cotisation_membership_only(self):
"""
It should be possible to add a cotisation with a specific start date
"""
v = Vente(
facture=self.f,
number=1,
name="Test purchase",
duration_connection=0,
duration_days_connection=0,
duration_membership=0,
duration_days_membership=1,
prix=0
)
v.create_cotis(date_start_con=timezone.make_aware(datetime.datetime(1998, 10, 16)), date_start_memb=timezone.make_aware(datetime.datetime(1998, 10, 16)))
v.save()
self.assertEqual(v.cotisation.date_end_con, timezone.make_aware(datetime.datetime(1998, 10, 17)))
self.assertEqual(v.cotisation.date_end_memb, timezone.make_aware(datetime.datetime(1998, 10, 16)))
def test_cotisation_membership_diff_connection(self):
"""
It should be possible to have purchase a membership longer
than the connection.
"""
date = timezone.now()
Vente.objects.create(
facture=self.f,
number=1,
name="Test purchase",
duration_connection=1,
duration_days_connection=0,
duration_membership=2,
duration_days_membership=0,
prix=0,
)
self.f.reorder_purchases()
end_con = self.user.end_connexion()
end_memb = self.user.end_adhesion()
expected_end_con = date + relativedelta(months=1)
expected_end_memb = date + relativedelta(months=2)
self.assertEqual(end_con.day, expected_end_con.day)
self.assertEqual(end_con.month, expected_end_con.month)
self.assertEqual(end_con.year, expected_end_con.year)
self.assertEqual(end_memb.day, expected_end_memb.day)
self.assertEqual(end_memb.month, expected_end_memb.month)
self.assertEqual(end_memb.year, expected_end_memb.year)
def tearDown(self):
self.f.delete()
@ -121,9 +266,10 @@ class FactureModelTests(TestCase):
facture=invoice1,
number=1,
name="Test purchase",
duration=1,
duration_days=0,
type_cotisation="All",
duration_connection=1,
duration_days_connection=0,
duration_membership=1,
duration_days_membership=0,
prix=0,
)
invoice1.reorder_purchases()
@ -134,16 +280,20 @@ class FactureModelTests(TestCase):
facture=invoice2,
number=1,
name="Test purchase",
duration=1,
duration_days=0,
type_cotisation="All",
duration_connection=1,
duration_days_connection=0,
duration_membership=1,
duration_days_membership=0,
prix=0,
)
invoice1.reorder_purchases()
delta = relativedelta(self.user.end_connexion(), date)
delta.microseconds = 0
delta_con = relativedelta(self.user.end_connexion(), date)
delta_memb = relativedelta(self.user.end_adhesion(), date)
delta_con.microseconds = 0
delta_memb.microseconds = 0
try:
self.assertEqual(delta, relativedelta(months=2))
self.assertEqual(delta_con, relativedelta(months=2))
self.assertEqual(delta_memb, relativedelta(months=2))
except Exception as e:
invoice1.delete()
invoice2.delete()

View file

@ -38,25 +38,28 @@ class NewFactureTests(TestCase):
self.article_one_day = Article.objects.create(
name="One day",
prix=0,
duration=0,
duration_days=1,
type_cotisation="All",
duration_connection=0,
duration_days_connection=1,
duration_membership=0,
duration_days_membership=1,
available_for_everyone=True,
)
self.article_one_month = Article.objects.create(
name="One day",
name="One mounth",
prix=0,
duration=1,
duration_days=0,
type_cotisation="All",
duration_connection=1,
duration_days_connection=0,
duration_membership=1,
duration_days_membership=0,
available_for_everyone=True,
)
self.article_one_month_and_one_week = Article.objects.create(
name="One day",
name="One mounth and one week",
prix=0,
duration=1,
duration_days=7,
type_cotisation="All",
duration_connection=1,
duration_days_connection=7,
duration_membership=1,
duration_days_membership=7,
available_for_everyone=True,
)
self.client.login(username="testUser", password="plopiplop")

View file

@ -105,8 +105,8 @@ def send_mail_voucher(invoice, request=None):
"lastname": invoice.user.surname,
"email": invoice.user.email,
"phone": invoice.user.telephone,
"date_end": invoice.get_subscription().latest("date_end").date_end,
"date_begin": invoice.get_subscription().earliest("date_start").date_start,
"date_end": invoice.get_subscription().latest("date_end").date_end_memb,
"date_begin": invoice.get_subscription().earliest("date_start").date_start_memb,
}
templatename = CotisationsOption.get_cached_value(
"voucher_template"
@ -118,7 +118,7 @@ def send_mail_voucher(invoice, request=None):
"name": "{} {}".format(invoice.user.name, invoice.user.surname),
"asso_email": AssoOption.get_cached_value("contact"),
"asso_name": AssoOption.get_cached_value("name"),
"date_end": invoice.get_subscription().latest("date_end").date_end,
"date_end": invoice.get_subscription().latest("date_end_memb").date_end_memb,
}
mail = EmailMessage(

View file

@ -130,11 +130,12 @@ def new_facture(request, user, userid):
facture=new_invoice_instance,
name=article.name,
prix=article.prix,
type_cotisation=article.type_cotisation,
duration=article.duration,
duration_days=article.duration_days,
duration_connection=article.duration_connection,
duration_days_connection=article.duration_days_connection,
duration_membership=article.duration_membership,
duration_days_membership=article.duration_days_membership,
number=quantity,
)
)
purchases.append(new_purchase)
p = find_payment_method(new_invoice_instance.paiement)
if hasattr(p, "check_price"):
@ -262,8 +263,10 @@ def new_custom_invoice(request):
facture=new_invoice_instance,
name=article.name,
prix=article.prix,
type_cotisation=article.type_cotisation,
duration=article.duration,
duration_membership=article.duration_membership,
duration_days_membership=article.duration_membership,
duration_connection=article.duration_connection,
duration_days_connection=article.duration_days_connection,
number=quantity,
)
discount_form.apply_to_invoice(new_invoice_instance)

View file

@ -93,13 +93,10 @@ def all_adherent(search_time=None, including_asso=True):
search_time = timezone.now()
filter_user = Q(
facture__in=Facture.objects.filter(
vente__in=Vente.objects.filter(
Q(type_cotisation="All") | Q(type_cotisation="Adhesion"),
cotisation__in=Cotisation.objects.filter(
vente__in=Vente.objects.filter(
facture__in=Facture.objects.all().exclude(valid=False)
)
).filter(Q(date_start__lt=search_time) & Q(date_end__gt=search_time)),
vente__cotisation__in=Cotisation.objects.filter(
Q(vente__facture__facture__valid=True) &
Q(date_start_memb__lt=search_time) &
Q(date_end_memb__gt=search_time)
)
)
)
@ -186,15 +183,12 @@ def all_has_access(search_time=None, including_asso=True):
)
| Q(
facture__in=Facture.objects.filter(
vente__in=Vente.objects.filter(
cotisation__in=Cotisation.objects.filter(
Q(type_cotisation="All") | Q(type_cotisation="Connexion"),
vente__in=Vente.objects.filter(
facture__in=Facture.objects.all().exclude(valid=False)
),
).filter(
Q(date_start__lt=search_time) & Q(date_end__gt=search_time)
)
vente__cotisation__in=Cotisation.objects.filter(
Q(vente__facture__facture__valid=True) &
Q(date_start_con__lt=search_time) &
Q(date_end_con__gt=search_time) &
Q(date_start_memb__lt=search_time) &
Q(date_end_memb__gt=search_time)
)
)
)

View file

@ -703,8 +703,7 @@ class User(
facture__in=Facture.objects.filter(user=self).exclude(valid=False)
)
)
.filter(Q(type_cotisation="All") | Q(type_cotisation="Adhesion"))
.aggregate(models.Max("date_end"))["date_end__max"]
.aggregate(models.Max("date_end_memb"))["date_end_memb__max"]
)
return date_max
@ -724,8 +723,7 @@ class User(
facture__in=Facture.objects.filter(user=self).exclude(valid=False)
)
)
.filter(Q(type_cotisation="All") | Q(type_cotisation="Connexion"))
.aggregate(models.Max("date_end"))["date_end__max"]
.aggregate(models.Max("date_end_con"))["date_end_con__max"]
)
return date_max
@ -746,6 +744,10 @@ class User(
return False
else:
return True
# it looks wrong, we should check if there is a cotisation where
# were date_start_memb < timezone.now() < date_end_memb,
# in case the user purshased a cotisation starting in the futur
# somehow
def is_connected(self):
"""Methods, calculate and returns if the user has a valid membership AND a
@ -765,6 +767,10 @@ class User(
return False
else:
return self.is_adherent()
# it looks wrong, we should check if there is a cotisation where
# were date_start_con < timezone.now() < date_end_con,
# in case the user purshased a cotisation starting in the futur
# somehow
def end_ban(self):
"""Methods, calculate and returns the end of a ban value date
@ -925,11 +931,13 @@ class User(
"""
if self.state == self.STATE_NOT_YET_ACTIVE:
if self.facture_set.filter(valid=True).filter(
Q(vente__type_cotisation="All") | Q(vente__type_cotisation="Adhesion")
).exists() or OptionalUser.get_cached_value("all_users_active"):
self.state = self.STATE_ACTIVE
self.save()
# Look for ventes with non 0 subscription duration in the invoices set
not_zero = self.facture_set.filter(valid=True).exclude(Q(vente__duration_membership=0)).exists()
days_not_zero = self.facture_set.filter(valid=True).exclude(Q(vente__duration_days_membership=0)).exists()
if(not_zero or days_not_zero\
or OptionalUser.get_cached_value("all_users_active")):
self.state = self.STATE_ACTIVE
self.save()
if self.state == self.STATE_ARCHIVE or self.state == self.STATE_FULL_ARCHIVE:
self.state = self.STATE_ACTIVE
self.unarchive()