mirror of
https://gitlab2.federez.net/re2o/re2o
synced 2024-11-23 03:43:12 +00:00
Split the membership duration from the connection duration
changes: Article: remove COTISATION_TYPE, duration(_days), type_cotisation add duration(_days)_connection, duration(_days)_membership Vente: remove COTISATION_TYPE, duration(_days), type_cotisation add duration(_days)_connection, duration(_days)_membership add method `test_membership_or_connection()` to replace `bool(type_cotisation)` Cotisation: remove COTISATION_TYPE, date_start, date_end, type_cotisation add date_start_con, date_end_con, date_start_memb, date_end_memb create_cotis(date_start=False) -> create_cotis(date_start_con=False, date_start_memb=False) + migration + changes to use the new models in the remaining of the code
This commit is contained in:
parent
2176ad973b
commit
0b4bcc21cd
11 changed files with 641 additions and 174 deletions
|
@ -64,8 +64,10 @@ class VenteSerializer(NamespacedHMSerializer):
|
||||||
"number",
|
"number",
|
||||||
"name",
|
"name",
|
||||||
"prix",
|
"prix",
|
||||||
"duration",
|
"duration_connection",
|
||||||
"type_cotisation",
|
"duration_days_connection",
|
||||||
|
"duration_membership",
|
||||||
|
"duration_days_membership",
|
||||||
"prix_total",
|
"prix_total",
|
||||||
"api_url",
|
"api_url",
|
||||||
)
|
)
|
||||||
|
@ -77,7 +79,7 @@ class ArticleSerializer(NamespacedHMSerializer):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = cotisations.Article
|
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):
|
class BanqueSerializer(NamespacedHMSerializer):
|
||||||
|
@ -104,7 +106,7 @@ class CotisationSerializer(NamespacedHMSerializer):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = cotisations.Cotisation
|
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):
|
class ReminderUsersSerializer(UserSerializer):
|
||||||
|
@ -124,4 +126,4 @@ class ReminderSerializer(serializers.ModelSerializer):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = preferences.Reminder
|
model = preferences.Reminder
|
||||||
fields = ("days", "message", "users_to_remind")
|
fields = ("days", "message", "users_to_remind")
|
||||||
|
|
|
@ -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)'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -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',
|
||||||
|
# ),
|
||||||
|
]
|
|
@ -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',
|
||||||
|
),
|
||||||
|
]
|
|
@ -283,7 +283,8 @@ class Facture(BaseInvoice):
|
||||||
"""Returns every subscription associated with this invoice."""
|
"""Returns every subscription associated with this invoice."""
|
||||||
return Cotisation.objects.filter(
|
return Cotisation.objects.filter(
|
||||||
vente__in=self.vente_set.filter(
|
vente__in=self.vente_set.filter(
|
||||||
Q(type_cotisation="All") | Q(type_cotisation="Adhesion")
|
~(Q(duration_membership__isnull=True) | Q(duration_membership=0)) |\
|
||||||
|
~(Q(duration_days_membership__isnull=True) | Q(duration_days_membership=0))
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -297,33 +298,18 @@ class Facture(BaseInvoice):
|
||||||
for purchase in self.vente_set.all():
|
for purchase in self.vente_set.all():
|
||||||
if hasattr(purchase, "cotisation"):
|
if hasattr(purchase, "cotisation"):
|
||||||
cotisation = purchase.cotisation
|
cotisation = purchase.cotisation
|
||||||
if cotisation.type_cotisation == "Connexion":
|
cotisation.date_start_con = date_con
|
||||||
cotisation.date_start = date_con
|
date_con += relativedelta(
|
||||||
date_con += relativedelta(
|
months=(purchase.duration_connection or 0) * purchase.number,
|
||||||
months=(purchase.duration or 0) * purchase.number,
|
days=(purchase.duration_days_connection or 0) * purchase.number,
|
||||||
days=(purchase.duration_days or 0) * purchase.number,
|
)
|
||||||
)
|
cotisation.date_end_con = date_con
|
||||||
cotisation.date_end = date_con
|
cotisation.date_start_memb = date_adh
|
||||||
elif cotisation.type_cotisation == "Adhesion":
|
date_adh += relativedelta(
|
||||||
cotisation.date_start = date_adh
|
months=(purchase.duration_membership or 0) * purchase.number,
|
||||||
date_adh += relativedelta(
|
days=(purchase.duration_days_membership or 0) * purchase.number,
|
||||||
months=(purchase.duration or 0) * purchase.number,
|
)
|
||||||
days=(purchase.duration_days or 0) * purchase.number,
|
cotisation.date_end_memb = date_adh
|
||||||
)
|
|
||||||
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.save()
|
cotisation.save()
|
||||||
purchase.facture = self
|
purchase.facture = self
|
||||||
purchase.save()
|
purchase.save()
|
||||||
|
@ -450,13 +436,6 @@ class Vente(RevMixin, AclMixin, models.Model):
|
||||||
the effect of the purchase on the time agreed for this user)
|
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
|
# TODO : change facture to invoice
|
||||||
facture = models.ForeignKey(
|
facture = models.ForeignKey(
|
||||||
"BaseInvoice", on_delete=models.CASCADE, verbose_name=_("invoice")
|
"BaseInvoice", on_delete=models.CASCADE, verbose_name=_("invoice")
|
||||||
|
@ -465,28 +444,31 @@ class Vente(RevMixin, AclMixin, models.Model):
|
||||||
number = models.IntegerField(
|
number = models.IntegerField(
|
||||||
validators=[MinValueValidator(1)], verbose_name=_("amount")
|
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"))
|
name = models.CharField(max_length=255, verbose_name=_("article"))
|
||||||
# TODO : change prix to price
|
# TODO : change prix to price
|
||||||
# TODO : this field is not needed if you use Article ForeignKey
|
# TODO : this field is not needed if you use Article ForeignKey
|
||||||
prix = models.DecimalField(max_digits=5, decimal_places=2, verbose_name=_("price"))
|
prix = models.DecimalField(max_digits=5, decimal_places=2, verbose_name=_("price"))
|
||||||
# TODO : this field is not needed if you use Article ForeignKey
|
# TODO : this field is not needed if you use Article ForeignKey
|
||||||
duration = models.PositiveIntegerField(
|
duration_connection = models.PositiveIntegerField(
|
||||||
blank=True, null=True, verbose_name=_("duration (in months)")
|
blank=True, null=True, verbose_name=_("duration of the connection (in months)")
|
||||||
)
|
)
|
||||||
duration_days = models.PositiveIntegerField(
|
duration_days_connection = models.PositiveIntegerField(
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
null=True,
|
||||||
validators=[MinValueValidator(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
|
duration_membership = models.PositiveIntegerField(
|
||||||
type_cotisation = models.CharField(
|
blank=True, null=True, verbose_name=_("duration of the membership (in months)")
|
||||||
choices=COTISATION_TYPE,
|
)
|
||||||
|
duration_days_membership = models.PositiveIntegerField(
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
null=True,
|
||||||
max_length=255,
|
validators=[MinValueValidator(0)],
|
||||||
verbose_name=_("subscription type"),
|
verbose_name=_("duration of the membership (in days, will be added to duration in months)"),
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
@ -511,13 +493,17 @@ class Vente(RevMixin, AclMixin, models.Model):
|
||||||
"""
|
"""
|
||||||
if hasattr(self, "cotisation"):
|
if hasattr(self, "cotisation"):
|
||||||
cotisation = self.cotisation
|
cotisation = self.cotisation
|
||||||
cotisation.date_end = cotisation.date_start + relativedelta(
|
cotisation.date_end_memb = cotisation.date_start_memb + relativedelta(
|
||||||
months=(self.duration or 0) * self.number,
|
months=(self.duration_membership or 0) * self.number,
|
||||||
days=(self.duration_days 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
|
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.
|
||||||
"""
|
"""
|
||||||
|
@ -525,20 +511,29 @@ class Vente(RevMixin, AclMixin, models.Model):
|
||||||
invoice = self.facture.facture
|
invoice = self.facture.facture
|
||||||
except Facture.DoesNotExist:
|
except Facture.DoesNotExist:
|
||||||
return
|
return
|
||||||
if not hasattr(self, "cotisation") and self.type_cotisation:
|
if not hasattr(self, "cotisation") and (self.duration_membership or self.duration_days_membership):
|
||||||
cotisation = Cotisation(vente=self)
|
cotisation = Cotisation(vente=self)
|
||||||
cotisation.type_cotisation = self.type_cotisation
|
if date_start_con:
|
||||||
if date_start:
|
cotisation.date_start_con = date_start_con
|
||||||
cotisation.date_start = date_start
|
cotisation.date_end_con = cotisation.date_start_con + relativedelta(
|
||||||
cotisation.date_end = cotisation.date_start + relativedelta(
|
months=(self.duration_connection or 0) * self.number,
|
||||||
months=(self.duration or 0) * self.number,
|
days=(self.duration_days_connection or 0) * self.number,
|
||||||
days=(self.duration_days 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()
|
self.save()
|
||||||
cotisation.save()
|
cotisation.save()
|
||||||
else:
|
else:
|
||||||
cotisation.date_start = invoice.date
|
cotisation.date_start_con = invoice.date
|
||||||
cotisation.date_end = 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):
|
def save(self, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
|
@ -546,9 +541,6 @@ class Vente(RevMixin, AclMixin, models.Model):
|
||||||
It also update the associated cotisation in the changes have some
|
It also update the associated cotisation in the changes have some
|
||||||
effect on the user's cotisation
|
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()
|
self.update_cotisation()
|
||||||
super(Vente, self).save(*args, **kwargs)
|
super(Vente, self).save(*args, **kwargs)
|
||||||
|
|
||||||
|
@ -629,6 +621,13 @@ class Vente(RevMixin, AclMixin, models.Model):
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return str(self.name) + " " + str(self.facture)
|
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
|
# TODO : change vente to purchase
|
||||||
@receiver(post_save, sender=Vente)
|
@receiver(post_save, sender=Vente)
|
||||||
|
@ -645,7 +644,7 @@ def vente_post_save(**kwargs):
|
||||||
if hasattr(purchase, "cotisation"):
|
if hasattr(purchase, "cotisation"):
|
||||||
purchase.cotisation.vente = purchase
|
purchase.cotisation.vente = purchase
|
||||||
purchase.cotisation.save()
|
purchase.cotisation.save()
|
||||||
if purchase.type_cotisation:
|
if purchase.test_membership_or_connection():
|
||||||
purchase.create_cotis()
|
purchase.create_cotis()
|
||||||
purchase.cotisation.save()
|
purchase.cotisation.save()
|
||||||
user = purchase.facture.facture.user
|
user = purchase.facture.facture.user
|
||||||
|
@ -677,56 +676,54 @@ class Article(RevMixin, AclMixin, models.Model):
|
||||||
It's represented by:
|
It's represented by:
|
||||||
* a name
|
* a name
|
||||||
* a price
|
* a price
|
||||||
* a cotisation type (indicating if this article reprensents a
|
* a duration for the membership
|
||||||
cotisation or not)
|
* a duration for the connection
|
||||||
* a duration (if it is a cotisation)
|
|
||||||
* a type of user (indicating what kind of user can buy this article)
|
* 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 = (
|
USER_TYPES = (
|
||||||
("Adherent", _("Member")),
|
("Adherent", _("Member")),
|
||||||
("Club", _("Club")),
|
("Club", _("Club")),
|
||||||
("All", _("Both of them")),
|
("All", _("Both of them")),
|
||||||
)
|
)
|
||||||
|
|
||||||
COTISATION_TYPE = (
|
|
||||||
("Connexion", _("Connection")),
|
|
||||||
("Adhesion", _("Membership")),
|
|
||||||
("All", _("Both of them")),
|
|
||||||
)
|
|
||||||
|
|
||||||
name = models.CharField(max_length=255, verbose_name=_("designation"))
|
name = models.CharField(max_length=255, verbose_name=_("designation"))
|
||||||
# TODO : change prix to price
|
# TODO : change prix to price
|
||||||
prix = models.DecimalField(
|
prix = models.DecimalField(
|
||||||
max_digits=5, decimal_places=2, verbose_name=_("unit price")
|
max_digits=5, decimal_places=2, verbose_name=_("unit price")
|
||||||
)
|
)
|
||||||
duration = models.PositiveIntegerField(
|
|
||||||
|
duration_membership = models.PositiveIntegerField(
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
null=True,
|
||||||
validators=[MinValueValidator(0)],
|
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,
|
blank=True,
|
||||||
null=True,
|
null=True,
|
||||||
validators=[MinValueValidator(0)],
|
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)"),
|
||||||
|
)
|
||||||
|
|
||||||
type_user = models.CharField(
|
type_user = models.CharField(
|
||||||
choices=USER_TYPES,
|
choices=USER_TYPES,
|
||||||
default="All",
|
default="All",
|
||||||
max_length=255,
|
max_length=255,
|
||||||
verbose_name=_("type of users concerned"),
|
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(
|
available_for_everyone = models.BooleanField(
|
||||||
default=False, verbose_name=_("is available for every user")
|
default=False, verbose_name=_("is available for every user")
|
||||||
)
|
)
|
||||||
|
@ -744,8 +741,6 @@ class Article(RevMixin, AclMixin, models.Model):
|
||||||
def clean(self):
|
def clean(self):
|
||||||
if self.name.lower() == "solde":
|
if self.name.lower() == "solde":
|
||||||
raise ValidationError(_("Solde is a reserved article name."))
|
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):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
@ -882,7 +877,7 @@ class Paiement(RevMixin, AclMixin, models.Model):
|
||||||
|
|
||||||
# In case a cotisation was bought, inform the user, the
|
# In case a cotisation was bought, inform the user, the
|
||||||
# cotisation time has been extended too
|
# 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(
|
messages.success(
|
||||||
request,
|
request,
|
||||||
_(
|
_(
|
||||||
|
@ -943,31 +938,21 @@ class Cotisation(RevMixin, AclMixin, models.Model):
|
||||||
The model defining a cotisation. It holds information about the time a user
|
The model defining a cotisation. It holds information about the time a user
|
||||||
is allowed when he has paid something.
|
is allowed when he has paid something.
|
||||||
It characterised by :
|
It characterised by :
|
||||||
* a date_start (the date when the cotisaiton begins/began
|
* a date_start_memb (the date when the membership begins/began
|
||||||
* a date_end (the date when the cotisation ends/ended
|
* a date_end_memb (the date when the membership ends/ended
|
||||||
* a type of cotisation (which indicates the implication of such
|
* a date_start_con (the date when the connection begins/began)
|
||||||
cotisation)
|
* a date_end_con (the date when the connection ends/ended)
|
||||||
* a purchase (the related objects this cotisation is linked to)
|
* 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
|
# TODO : change vente to purchase
|
||||||
vente = models.OneToOneField(
|
vente = models.OneToOneField(
|
||||||
"Vente", on_delete=models.CASCADE, null=True, verbose_name=_("purchase")
|
"Vente", on_delete=models.CASCADE, null=True, verbose_name=_("purchase")
|
||||||
)
|
)
|
||||||
type_cotisation = models.CharField(
|
date_start_con = models.DateTimeField(verbose_name=_("start date for the connection"))
|
||||||
choices=COTISATION_TYPE,
|
date_end_con = models.DateTimeField(verbose_name=_("end date for the connection"))
|
||||||
max_length=255,
|
date_start_memb = models.DateTimeField(verbose_name=_("start date for the membership"))
|
||||||
default="All",
|
date_end_memb = models.DateTimeField(verbose_name=_("end date for the membership"))
|
||||||
verbose_name=_("subscription type"),
|
|
||||||
)
|
|
||||||
date_start = models.DateTimeField(verbose_name=_("start date"))
|
|
||||||
date_end = models.DateTimeField(verbose_name=_("end date"))
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
permissions = (
|
permissions = (
|
||||||
|
@ -1037,9 +1022,14 @@ class Cotisation(RevMixin, AclMixin, models.Model):
|
||||||
return (
|
return (
|
||||||
str(self.vente)
|
str(self.vente)
|
||||||
+ "from "
|
+ "from "
|
||||||
+ str(self.date_start)
|
+ str(self.date_start_memb)
|
||||||
+ " to "
|
+ " 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."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -32,9 +32,10 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
<tr>
|
<tr>
|
||||||
<th>{% trans "Article" %}</th>
|
<th>{% trans "Article" %}</th>
|
||||||
<th>{% trans "Price" %}</th>
|
<th>{% trans "Price" %}</th>
|
||||||
<th>{% trans "Subscription type" %}</th>
|
<th>{% trans "Duration membership (in months)" %}</th>
|
||||||
<th>{% trans "Duration (in months)" %}</th>
|
<th>{% trans "Duration membership (in days)" %}</th>
|
||||||
<th>{% trans "Duration (in days)" %}</th>
|
<th>{% trans "Duration connection (in months)" %}</th>
|
||||||
|
<th>{% trans "Duration connection (in days)" %}</th>
|
||||||
<th>{% trans "Concerned users" %}</th>
|
<th>{% trans "Concerned users" %}</th>
|
||||||
<th>{% trans "Available for everyone" %}</th>
|
<th>{% trans "Available for everyone" %}</th>
|
||||||
<th></th>
|
<th></th>
|
||||||
|
@ -44,9 +45,10 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ article.name }}</td>
|
<td>{{ article.name }}</td>
|
||||||
<td>{{ article.prix }}</td>
|
<td>{{ article.prix }}</td>
|
||||||
<td>{{ article.type_cotisation }}</td>
|
<td>{{ article.duration_membership }}</td>
|
||||||
<td>{{ article.duration }}</td>
|
<td>{{ article.duration_days_membership }}</td>
|
||||||
<td>{{ article.duration_days }}</td>
|
<td>{{ article.duration_connection }}</td>
|
||||||
|
<td>{{ article.duration_days_connection }}</td>
|
||||||
<td>{{ article.type_user }}</td>
|
<td>{{ article.type_user }}</td>
|
||||||
<td>{{ article.available_for_everyone | tick }}</td>
|
<td>{{ article.available_for_everyone | tick }}</td>
|
||||||
<td class="text-right">
|
<td class="text-right">
|
||||||
|
|
|
@ -19,15 +19,17 @@ class VenteModelTests(TestCase):
|
||||||
def test_one_day_cotisation(self):
|
def test_one_day_cotisation(self):
|
||||||
"""
|
"""
|
||||||
It should be possible to have one day membership.
|
It should be possible to have one day membership.
|
||||||
|
Add one day of membership and one day of connection.
|
||||||
"""
|
"""
|
||||||
date = timezone.now()
|
date = timezone.now()
|
||||||
purchase = Vente.objects.create(
|
purchase = Vente.objects.create(
|
||||||
facture=self.f,
|
facture=self.f,
|
||||||
number=1,
|
number=1,
|
||||||
name="Test purchase",
|
name="Test purchase",
|
||||||
duration=0,
|
duration_connection=0,
|
||||||
duration_days=1,
|
duration_days_connection=1,
|
||||||
type_cotisation="All",
|
duration_membership=0,
|
||||||
|
duration_days_membership=1,
|
||||||
prix=0,
|
prix=0,
|
||||||
)
|
)
|
||||||
self.f.reorder_purchases()
|
self.f.reorder_purchases()
|
||||||
|
@ -36,48 +38,66 @@ class VenteModelTests(TestCase):
|
||||||
datetime.timedelta(days=1),
|
datetime.timedelta(days=1),
|
||||||
delta=datetime.timedelta(seconds=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):
|
def test_one_month_cotisation(self):
|
||||||
"""
|
"""
|
||||||
It should be possible to have one day membership.
|
It should be possible to have one day membership.
|
||||||
|
Add one mounth of membership and one mounth of connection
|
||||||
"""
|
"""
|
||||||
date = timezone.now()
|
date = timezone.now()
|
||||||
Vente.objects.create(
|
Vente.objects.create(
|
||||||
facture=self.f,
|
facture=self.f,
|
||||||
number=1,
|
number=1,
|
||||||
name="Test purchase",
|
name="Test purchase",
|
||||||
duration=1,
|
duration_connection=1,
|
||||||
duration_days=0,
|
duration_days_connection=0,
|
||||||
type_cotisation="All",
|
duration_membership=1,
|
||||||
|
duration_days_membership=0,
|
||||||
prix=0,
|
prix=0,
|
||||||
)
|
)
|
||||||
self.f.reorder_purchases()
|
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)
|
expected_end = date + relativedelta(months=1)
|
||||||
self.assertEqual(end.day, expected_end.day)
|
self.assertEqual(end_con.day, expected_end.day)
|
||||||
self.assertEqual(end.month, expected_end.month)
|
self.assertEqual(end_con.month, expected_end.month)
|
||||||
self.assertEqual(end.year, expected_end.year)
|
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):
|
def test_one_month_and_one_week_cotisation(self):
|
||||||
"""
|
"""
|
||||||
It should be possible to have one day membership.
|
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()
|
date = timezone.now()
|
||||||
Vente.objects.create(
|
Vente.objects.create(
|
||||||
facture=self.f,
|
facture=self.f,
|
||||||
number=1,
|
number=1,
|
||||||
name="Test purchase",
|
name="Test purchase",
|
||||||
duration=1,
|
duration_connection=1,
|
||||||
duration_days=7,
|
duration_days_connection=7,
|
||||||
type_cotisation="All",
|
duration_membership=1,
|
||||||
|
duration_days_membership=7,
|
||||||
prix=0,
|
prix=0,
|
||||||
)
|
)
|
||||||
self.f.reorder_purchases()
|
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)
|
expected_end = date + relativedelta(months=1, days=7)
|
||||||
self.assertEqual(end.day, expected_end.day)
|
self.assertEqual(end_con.day, expected_end.day)
|
||||||
self.assertEqual(end.month, expected_end.month)
|
self.assertEqual(end_con.month, expected_end.month)
|
||||||
self.assertEqual(end.year, expected_end.year)
|
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):
|
def test_date_start_cotisation(self):
|
||||||
"""
|
"""
|
||||||
|
@ -87,15 +107,140 @@ class VenteModelTests(TestCase):
|
||||||
facture=self.f,
|
facture=self.f,
|
||||||
number=1,
|
number=1,
|
||||||
name="Test purchase",
|
name="Test purchase",
|
||||||
duration=0,
|
duration_connection=0,
|
||||||
duration_days=1,
|
duration_days_connection=1,
|
||||||
type_cotisation = 'All',
|
duration_membership=0,
|
||||||
|
duration_deys_membership=1,
|
||||||
prix=0
|
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()
|
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):
|
def tearDown(self):
|
||||||
self.f.delete()
|
self.f.delete()
|
||||||
|
@ -121,9 +266,10 @@ class FactureModelTests(TestCase):
|
||||||
facture=invoice1,
|
facture=invoice1,
|
||||||
number=1,
|
number=1,
|
||||||
name="Test purchase",
|
name="Test purchase",
|
||||||
duration=1,
|
duration_connection=1,
|
||||||
duration_days=0,
|
duration_days_connection=0,
|
||||||
type_cotisation="All",
|
duration_membership=1,
|
||||||
|
duration_days_membership=0,
|
||||||
prix=0,
|
prix=0,
|
||||||
)
|
)
|
||||||
invoice1.reorder_purchases()
|
invoice1.reorder_purchases()
|
||||||
|
@ -134,16 +280,20 @@ class FactureModelTests(TestCase):
|
||||||
facture=invoice2,
|
facture=invoice2,
|
||||||
number=1,
|
number=1,
|
||||||
name="Test purchase",
|
name="Test purchase",
|
||||||
duration=1,
|
duration_connection=1,
|
||||||
duration_days=0,
|
duration_days_connection=0,
|
||||||
type_cotisation="All",
|
duration_membership=1,
|
||||||
|
duration_days_membership=0,
|
||||||
prix=0,
|
prix=0,
|
||||||
)
|
)
|
||||||
invoice1.reorder_purchases()
|
invoice1.reorder_purchases()
|
||||||
delta = relativedelta(self.user.end_connexion(), date)
|
delta_con = relativedelta(self.user.end_connexion(), date)
|
||||||
delta.microseconds = 0
|
delta_memb = relativedelta(self.user.end_adhesion(), date)
|
||||||
|
delta_con.microseconds = 0
|
||||||
|
delta_memb.microseconds = 0
|
||||||
try:
|
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:
|
except Exception as e:
|
||||||
invoice1.delete()
|
invoice1.delete()
|
||||||
invoice2.delete()
|
invoice2.delete()
|
||||||
|
|
|
@ -38,25 +38,28 @@ class NewFactureTests(TestCase):
|
||||||
self.article_one_day = Article.objects.create(
|
self.article_one_day = Article.objects.create(
|
||||||
name="One day",
|
name="One day",
|
||||||
prix=0,
|
prix=0,
|
||||||
duration=0,
|
duration_connection=0,
|
||||||
duration_days=1,
|
duration_days_connection=1,
|
||||||
type_cotisation="All",
|
duration_membership=0,
|
||||||
|
duration_days_membership=1,
|
||||||
available_for_everyone=True,
|
available_for_everyone=True,
|
||||||
)
|
)
|
||||||
self.article_one_month = Article.objects.create(
|
self.article_one_month = Article.objects.create(
|
||||||
name="One day",
|
name="One mounth",
|
||||||
prix=0,
|
prix=0,
|
||||||
duration=1,
|
duration_connection=1,
|
||||||
duration_days=0,
|
duration_days_connection=0,
|
||||||
type_cotisation="All",
|
duration_membership=1,
|
||||||
|
duration_days_membership=0,
|
||||||
available_for_everyone=True,
|
available_for_everyone=True,
|
||||||
)
|
)
|
||||||
self.article_one_month_and_one_week = Article.objects.create(
|
self.article_one_month_and_one_week = Article.objects.create(
|
||||||
name="One day",
|
name="One mounth and one week",
|
||||||
prix=0,
|
prix=0,
|
||||||
duration=1,
|
duration_connection=1,
|
||||||
duration_days=7,
|
duration_days_connection=7,
|
||||||
type_cotisation="All",
|
duration_membership=1,
|
||||||
|
duration_days_membership=7,
|
||||||
available_for_everyone=True,
|
available_for_everyone=True,
|
||||||
)
|
)
|
||||||
self.client.login(username="testUser", password="plopiplop")
|
self.client.login(username="testUser", password="plopiplop")
|
||||||
|
|
|
@ -105,8 +105,8 @@ def send_mail_voucher(invoice, request=None):
|
||||||
"lastname": invoice.user.surname,
|
"lastname": invoice.user.surname,
|
||||||
"email": invoice.user.email,
|
"email": invoice.user.email,
|
||||||
"phone": invoice.user.telephone,
|
"phone": invoice.user.telephone,
|
||||||
"date_end": invoice.get_subscription().latest("date_end").date_end,
|
"date_end": invoice.get_subscription().latest("date_end").date_end_memb,
|
||||||
"date_begin": invoice.get_subscription().earliest("date_start").date_start,
|
"date_begin": invoice.get_subscription().earliest("date_start").date_start_memb,
|
||||||
}
|
}
|
||||||
templatename = CotisationsOption.get_cached_value(
|
templatename = CotisationsOption.get_cached_value(
|
||||||
"voucher_template"
|
"voucher_template"
|
||||||
|
@ -118,7 +118,7 @@ def send_mail_voucher(invoice, request=None):
|
||||||
"name": "{} {}".format(invoice.user.name, invoice.user.surname),
|
"name": "{} {}".format(invoice.user.name, invoice.user.surname),
|
||||||
"asso_email": AssoOption.get_cached_value("contact"),
|
"asso_email": AssoOption.get_cached_value("contact"),
|
||||||
"asso_name": AssoOption.get_cached_value("name"),
|
"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(
|
mail = EmailMessage(
|
||||||
|
|
|
@ -130,11 +130,12 @@ def new_facture(request, user, userid):
|
||||||
facture=new_invoice_instance,
|
facture=new_invoice_instance,
|
||||||
name=article.name,
|
name=article.name,
|
||||||
prix=article.prix,
|
prix=article.prix,
|
||||||
type_cotisation=article.type_cotisation,
|
duration_connection=article.duration_connection,
|
||||||
duration=article.duration,
|
duration_days_connection=article.duration_days_connection,
|
||||||
duration_days=article.duration_days,
|
duration_membership=article.duration_membership,
|
||||||
|
duration_days_membership=article.duration_days_membership,
|
||||||
number=quantity,
|
number=quantity,
|
||||||
)
|
)
|
||||||
purchases.append(new_purchase)
|
purchases.append(new_purchase)
|
||||||
p = find_payment_method(new_invoice_instance.paiement)
|
p = find_payment_method(new_invoice_instance.paiement)
|
||||||
if hasattr(p, "check_price"):
|
if hasattr(p, "check_price"):
|
||||||
|
@ -262,8 +263,10 @@ def new_custom_invoice(request):
|
||||||
facture=new_invoice_instance,
|
facture=new_invoice_instance,
|
||||||
name=article.name,
|
name=article.name,
|
||||||
prix=article.prix,
|
prix=article.prix,
|
||||||
type_cotisation=article.type_cotisation,
|
duration_membership=article.duration_membership,
|
||||||
duration=article.duration,
|
duration_days_membership=article.duration_membership,
|
||||||
|
duration_connection=article.duration_connection,
|
||||||
|
duration_days_connection=article.duration_days_connection,
|
||||||
number=quantity,
|
number=quantity,
|
||||||
)
|
)
|
||||||
discount_form.apply_to_invoice(new_invoice_instance)
|
discount_form.apply_to_invoice(new_invoice_instance)
|
||||||
|
|
|
@ -703,8 +703,7 @@ class User(
|
||||||
facture__in=Facture.objects.filter(user=self).exclude(valid=False)
|
facture__in=Facture.objects.filter(user=self).exclude(valid=False)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.filter(Q(type_cotisation="All") | Q(type_cotisation="Adhesion"))
|
.aggregate(models.Max("date_end_memb"))["date_end_memb__max"]
|
||||||
.aggregate(models.Max("date_end"))["date_end__max"]
|
|
||||||
)
|
)
|
||||||
return date_max
|
return date_max
|
||||||
|
|
||||||
|
@ -724,8 +723,7 @@ class User(
|
||||||
facture__in=Facture.objects.filter(user=self).exclude(valid=False)
|
facture__in=Facture.objects.filter(user=self).exclude(valid=False)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.filter(Q(type_cotisation="All") | Q(type_cotisation="Connexion"))
|
.aggregate(models.Max("date_end_con"))["date_end_con__max"]
|
||||||
.aggregate(models.Max("date_end"))["date_end__max"]
|
|
||||||
)
|
)
|
||||||
return date_max
|
return date_max
|
||||||
|
|
||||||
|
@ -746,6 +744,10 @@ class User(
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
return True
|
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):
|
def is_connected(self):
|
||||||
"""Methods, calculate and returns if the user has a valid membership AND a
|
"""Methods, calculate and returns if the user has a valid membership AND a
|
||||||
|
@ -765,6 +767,10 @@ class User(
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
return self.is_adherent()
|
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):
|
def end_ban(self):
|
||||||
"""Methods, calculate and returns the end of a ban value date
|
"""Methods, calculate and returns the end of a ban value date
|
||||||
|
@ -926,7 +932,8 @@ class User(
|
||||||
"""
|
"""
|
||||||
if self.state == self.STATE_NOT_YET_ACTIVE:
|
if self.state == self.STATE_NOT_YET_ACTIVE:
|
||||||
if self.facture_set.filter(valid=True).filter(
|
if self.facture_set.filter(valid=True).filter(
|
||||||
Q(vente__type_cotisation="All") | Q(vente__type_cotisation="Adhesion")
|
~(Q(vente__duration_membership__isnull=True) | Q(vente__duration_membership=0)) | \
|
||||||
|
~(Q(vente__duration_days_membership__isnull=True) | Q(vente__duration_days_membership=0))
|
||||||
).exists() or OptionalUser.get_cached_value("all_users_active"):
|
).exists() or OptionalUser.get_cached_value("all_users_active"):
|
||||||
self.state = self.STATE_ACTIVE
|
self.state = self.STATE_ACTIVE
|
||||||
self.save()
|
self.save()
|
||||||
|
|
Loading…
Reference in a new issue