Commit 180ba11a authored by chirac's avatar chirac

Merge branch '222-allow-short-time-membership' into 'dev'

Fix #222

See merge request federez/re2o!454
parents c260d388 034b50bc
Pipeline #2297 passed with stage
in 3 minutes and 11 seconds
# -*- coding: utf-8 -*-
# Generated by Django 1.11.23 on 2019-10-02 21:35
from __future__ import unicode_literals
import django.core.validators
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cotisations', '0039_freepayment'),
]
operations = [
migrations.AddField(
model_name='article',
name='duration_days',
field=models.PositiveIntegerField(blank=True, null=True, validators=[django.core.validators.MinValueValidator(0)], verbose_name='duration (in days, will be added to duration in months)'),
),
migrations.AddField(
model_name='vente',
name='duration_days',
field=models.PositiveIntegerField(blank=True, null=True, validators=[django.core.validators.MinValueValidator(0)], verbose_name='duration (in days, will be added to duration in months)'),
),
]
# -*- coding: utf-8 -*-
# Generated by Django 1.11.23 on 2019-11-03 20:31
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('cotisations', '0040_auto_20191002_2335'),
]
operations = [
migrations.AlterField(
model_name='balancepayment',
name='payment',
field=models.OneToOneField(editable=False, on_delete=django.db.models.deletion.CASCADE, related_name='payment_method_balance', to='cotisations.Paiement'),
),
migrations.AlterField(
model_name='chequepayment',
name='payment',
field=models.OneToOneField(editable=False, on_delete=django.db.models.deletion.CASCADE, related_name='payment_method_cheque', to='cotisations.Paiement'),
),
migrations.AlterField(
model_name='comnpaypayment',
name='payment',
field=models.OneToOneField(editable=False, on_delete=django.db.models.deletion.CASCADE, related_name='payment_method_comnpay', to='cotisations.Paiement'),
),
migrations.AlterField(
model_name='freepayment',
name='payment',
field=models.OneToOneField(editable=False, on_delete=django.db.models.deletion.CASCADE, related_name='payment_method_free', to='cotisations.Paiement'),
),
migrations.AlterField(
model_name='notepayment',
name='payment',
field=models.OneToOneField(editable=False, on_delete=django.db.models.deletion.CASCADE, related_name='payment_method_note', to='cotisations.Paiement'),
),
]
......@@ -290,9 +290,22 @@ class Facture(BaseInvoice):
"""Returns True if this invoice contains at least one subscribtion."""
return bool(self.get_subscription())
def reorder_purchases(self):
date = self.date
for purchase in self.vente_set.all():
if hasattr(purchase, 'cotisation'):
cotisation = purchase.cotisation
cotisation.date_start = date
date += relativedelta(
months=(purchase.duration or 0)*purchase.number,
days=(purchase.duration_days or 0)*purchase.number,
)
purchase.save()
def save(self, *args, **kwargs):
super(Facture, self).save(*args, **kwargs)
if not self.__original_valid and self.valid:
self.reorder_purchases()
send_mail_invoice(self)
if self.is_subscription() \
and not self.__original_control \
......@@ -460,6 +473,12 @@ class Vente(RevMixin, AclMixin, models.Model):
null=True,
verbose_name=_("duration (in months)")
)
duration_days = models.PositiveIntegerField(
blank=True,
null=True,
validators=[MinValueValidator(0)],
verbose_name=_("duration (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,
......@@ -492,7 +511,9 @@ class Vente(RevMixin, AclMixin, models.Model):
if hasattr(self, 'cotisation'):
cotisation = self.cotisation
cotisation.date_end = cotisation.date_start + relativedelta(
months=self.duration*self.number)
months=(self.duration or 0)*self.number,
days=(self.duration_days or 0)*self.number,
)
return
def create_cotis(self, date_start=False):
......@@ -529,9 +550,9 @@ class Vente(RevMixin, AclMixin, models.Model):
date_max = max(end_cotisation, date_start)
cotisation.date_start = date_max
cotisation.date_end = cotisation.date_start + relativedelta(
months=self.duration*self.number
months=(self.duration or 0)*self.number,
days=(self.duration_days or 0)*self.number,
)
return
def save(self, *args, **kwargs):
"""
......@@ -540,7 +561,7 @@ class Vente(RevMixin, AclMixin, models.Model):
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:
if self.type_cotisation and not (self.duration or self.duration_days):
raise ValidationError(
_("Duration must be specified for a subscription.")
)
......@@ -695,6 +716,12 @@ class Article(RevMixin, AclMixin, models.Model):
validators=[MinValueValidator(0)],
verbose_name=_("duration (in months)")
)
duration_days = models.PositiveIntegerField(
blank=True,
null=True,
validators=[MinValueValidator(0)],
verbose_name=_("duration (in days, will be added to duration in months)")
)
type_user = models.CharField(
choices=USER_TYPES,
default='All',
......@@ -729,7 +756,7 @@ class Article(RevMixin, AclMixin, models.Model):
raise ValidationError(
_("Balance is a reserved article name.")
)
if self.type_cotisation and not self.duration:
if self.type_cotisation and not (self.duration or self.duration_days):
raise ValidationError(
_("Duration must be specified for a subscription.")
)
......@@ -1027,7 +1054,7 @@ class Cotisation(RevMixin, AclMixin, models.Model):
return True, None, None
def __str__(self):
return str(self.vente)
return str(self.vente) + "from " + str(self.date_start) + " to " + str(self.date_end)
@receiver(post_save, sender=Cotisation)
......
......@@ -40,7 +40,7 @@ class BalancePayment(PaymentMethodMixin, models.Model):
payment = models.OneToOneField(
Paiement,
on_delete=models.CASCADE,
related_name='payment_method',
related_name='payment_method_balance',
editable=False
)
minimum_balance = models.DecimalField(
......
......@@ -38,7 +38,7 @@ class ChequePayment(PaymentMethodMixin, models.Model):
payment = models.OneToOneField(
Paiement,
on_delete=models.CASCADE,
related_name='payment_method',
related_name='payment_method_cheque',
editable=False
)
......
......@@ -41,7 +41,7 @@ class ComnpayPayment(PaymentMethodMixin, models.Model):
payment = models.OneToOneField(
Paiement,
on_delete=models.CASCADE,
related_name='payment_method',
related_name='payment_method_comnpay',
editable=False
)
payment_credential = models.CharField(
......
......@@ -38,7 +38,7 @@ class FreePayment(PaymentMethodMixin, models.Model):
payment = models.OneToOneField(
Paiement,
on_delete=models.CASCADE,
related_name='payment_method',
related_name='payment_method_free',
editable=False
)
......
......@@ -42,7 +42,7 @@ class NotePayment(PaymentMethodMixin, models.Model):
payment = models.OneToOneField(
Paiement,
on_delete = models.CASCADE,
related_name = 'payment_method',
related_name = 'payment_method_note',
editable = False
)
server = models.CharField(
......
......@@ -34,6 +34,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
<th>{% trans "Price" %}</th>
<th>{% trans "Subscription type" %}</th>
<th>{% trans "Duration (in months)" %}</th>
<th>{% trans "Duration (in days)" %}</th>
<th>{% trans "Concerned users" %}</th>
<th>{% trans "Available for everyone" %}</th>
<th></th>
......@@ -45,6 +46,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
<td>{{ article.prix }}</td>
<td>{{ article.type_cotisation }}</td>
<td>{{ article.duration }}</td>
<td>{{ article.duration_days }}</td>
<td>{{ article.type_user }}</td>
<td>{{ article.available_for_everyone | tick }}</td>
<td class="text-right">
......
from django.test import TestCase
import datetime
from django.utils import timezone
from dateutil.relativedelta import relativedelta
from users.models import User
from .models import Vente, Facture, Cotisation, Paiement
class VenteModelTests(TestCase):
def setUp(self):
self.user = User.objects.create(
pseudo="testUser",
email="test@example.org"
)
self.paiement = Paiement.objects.create(
moyen="test payment"
)
self.f = Facture.objects.create(
user=self.user,
paiement=self.paiement,
valid=True
)
def test_one_day_cotisation(self):
"""
It should be possible to have one day membership.
"""
date = timezone.now()
purchase = Vente.objects.create(
facture=self.f,
number=1,
name="Test purchase",
duration=0,
duration_days=1,
type_cotisation="All",
prix=0,
)
self.assertAlmostEqual(
self.user.end_connexion() - 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.
"""
date = timezone.now()
purchase = Vente.objects.create(
facture=self.f,
number=1,
name="Test purchase",
duration=1,
duration_days=0,
type_cotisation="All",
prix=0,
)
delta = relativedelta(self.user.end_connexion(), date)
delta.microseconds=0
self.assertEqual(
delta,
relativedelta(months=1),
)
def test_one_month_and_one_week_cotisation(self):
"""
It should be possible to have one day membership.
"""
date = timezone.now()
purchase = Vente.objects.create(
facture=self.f,
number=1,
name="Test purchase",
duration=1,
duration_days=7,
type_cotisation="All",
prix=0,
)
delta = relativedelta(self.user.end_connexion(), date)
delta.microseconds=0
self.assertEqual(
delta,
relativedelta(months=1, days=7),
)
def tearDown(self):
self.f.delete()
self.user.delete()
self.paiement.delete()
from django.test import TestCase
from django.urls import reverse
from django.contrib.auth.models import Permission
import datetime
from dateutil.relativedelta import relativedelta
from django.utils import timezone
from users.models import Adherent
from .models import Vente, Facture, Cotisation, Paiement, Article
class NewFactureTests(TestCase):
def tearDown(self):
self.user.facture_set.all().delete()
self.user.delete()
self.paiement.delete()
self.article_one_day.delete()
self.article_one_month.delete()
self.article_one_month_and_one_week.delete()
def setUp(self):
self.user = Adherent.objects.create(
pseudo="testUser",
email="test@example.org",
)
self.user.set_password('plopiplop')
self.user.user_permissions.set(
[
Permission.objects.get_by_natural_key("add_facture", "cotisations", "Facture"),
Permission.objects.get_by_natural_key("use_every_payment", "cotisations", "Paiement"),
]
)
self.user.save()
self.paiement = Paiement.objects.create(
moyen="test payment",
)
self.article_one_day = Article.objects.create(
name="One day",
prix=0,
duration=0,
duration_days=1,
type_cotisation='All',
available_for_everyone=True
)
self.article_one_month = Article.objects.create(
name="One day",
prix=0,
duration=1,
duration_days=0,
type_cotisation='All',
available_for_everyone=True
)
self.article_one_month_and_one_week = Article.objects.create(
name="One day",
prix=0,
duration=1,
duration_days=7,
type_cotisation='All',
available_for_everyone=True
)
self.client.login(
username="testUser",
password="plopiplop"
)
def test_invoice_with_one_day(self):
data = {
"Facture-paiement": self.paiement.pk,
"form-TOTAL_FORMS": 1,
"form-INITIAL_FORMS": 0,
"form-MIN_NUM_FORMS": 0,
"form-MAX_NUM_FORMS": 1000,
"form-0-article": 1,
"form-0-quantity": 1,
}
date = timezone.now()
response = self.client.post(reverse('cotisations:new-facture', kwargs={'userid':self.user.pk}), data)
self.assertEqual(
response.status_code,
302
)
self.assertEqual(
response.url,
"/users/profil/%d"%self.user.pk
)
self.assertAlmostEqual(
self.user.end_connexion() - date,
datetime.timedelta(days=1),
delta=datetime.timedelta(seconds=1)
)
def test_invoice_with_one_month(self):
data = {
"Facture-paiement": self.paiement.pk,
"form-TOTAL_FORMS": 1,
"form-INITIAL_FORMS": 0,
"form-MIN_NUM_FORMS": 0,
"form-MAX_NUM_FORMS": 1000,
"form-0-article": 2,
"form-0-quantity": 1,
}
date = timezone.now()
response = self.client.post(reverse('cotisations:new-facture', kwargs={'userid':self.user.pk}), data)
self.assertEqual(
response.status_code,
302
)
self.assertEqual(
response.url,
"/users/profil/%d"%self.user.pk
)
delta = relativedelta(self.user.end_connexion(), date)
delta.microseconds=0
self.assertEqual(
delta,
relativedelta(months=1),
)
def test_invoice_with_one_month_and_one_week(self):
data = {
"Facture-paiement": self.paiement.pk,
"form-TOTAL_FORMS": 2,
"form-INITIAL_FORMS": 0,
"form-MIN_NUM_FORMS": 0,
"form-MAX_NUM_FORMS": 1000,
"form-0-article": 1,
"form-0-quantity": 7,
"form-1-article": 2,
"form-1-quantity": 1,
}
date = timezone.now()
response = self.client.post(reverse('cotisations:new-facture', kwargs={'userid':self.user.pk}), data)
self.assertEqual(
response.status_code,
302
)
self.assertEqual(
response.url,
"/users/profil/%d"%self.user.pk
)
invoice = self.user.facture_set.first()
delta = relativedelta(self.user.end_connexion(), date)
delta.microseconds=0
self.assertEqual(
delta,
relativedelta(months=1, days=7),
)
def test_several_articles_creates_several_purchases(self):
data = {
"Facture-paiement": self.paiement.pk,
"form-TOTAL_FORMS": 2,
"form-INITIAL_FORMS": 0,
"form-MIN_NUM_FORMS": 0,
"form-MAX_NUM_FORMS": 1000,
"form-0-article": 2,
"form-0-quantity": 1,
"form-1-article": 2,
"form-1-quantity": 1,
}
response = self.client.post(reverse('cotisations:new-facture', kwargs={'userid':self.user.pk}), data)
f = self.user.facture_set.first()
self.assertEqual(f.vente_set.count(), 2)
# Re2o est un logiciel d'administration développé initiallement au rezometz. Il
# se veut agnostique au réseau considéré, de manière à être installable en
# quelques clics.
#
# Copyright © 2017 Gabriel Détraz
# Copyright © 2017 Lara Kermarec
# Copyright © 2017 Augustin Lemesle
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
"""cotisations.tests
The tests for the Cotisations module.
"""
# from django.test import TestCase
# Create your tests here.
......@@ -139,6 +139,7 @@ def new_facture(request, user, userid):
prix=article.prix,
type_cotisation=article.type_cotisation,
duration=article.duration,
duration_days=article.duration_days,
number=quantity
)
purchases.append(new_purchase)
......
from django.test import TestCase
import datetime
from django.utils import timezone
from users.models import User
from cotisations.models import Vente, Facture, Paiement
class UserModelTests(TestCase):
def setUp(self):
self.user = User.objects.create(
pseudo="testUser"
)
def tearDown(self):
self.user.facture_set.all().delete()
self.user.delete()
def test_multiple_cotisations_are_taken_into_account(self):
paiement = Paiement.objects.create(
moyen="test payment"
)
invoice = Facture.objects.create(
user=self.user,
paiement=paiement,
valid=True
)
date = timezone.now()
purchase1 = Vente.objects.create(
facture=invoice,
number=1,
name="Test purchase",
duration=0,
duration_days=1,
type_cotisation="All",
prix=0,
)
purchase2 = Vente.objects.create(
facture=invoice,
number=1,
name="Test purchase",
duration=0,
duration_days=1,
type_cotisation="All",
prix=0,
)
self.assertAlmostEqual(
self.user.end_connexion() - date,
datetime.timedelta(days=2),
delta=datetime.timedelta(seconds=1)
)
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment