mirror of
https://gitlab2.federez.net/re2o/re2o
synced 2024-12-23 15:33:45 +00:00
ACL sur les paiements.
This commit is contained in:
parent
5f1e2380c8
commit
5887eb68ae
6 changed files with 141 additions and 59 deletions
11
CHANGELOG.md
11
CHANGELOG.md
|
@ -94,3 +94,14 @@ If you to restrict the IP which can see the debug, use the `INTERNAL_IPS` option
|
||||||
```
|
```
|
||||||
INTERNAL_IPS = ["10.0.0.1", "10.0.0.2"]
|
INTERNAL_IPS = ["10.0.0.1", "10.0.0.2"]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## MR 174 : Fix online payment + allow users to pay their subscription
|
||||||
|
|
||||||
|
Add the possibility to use custom payment methods. There is also a boolean field on the
|
||||||
|
Payments allowing every user to use some kinds of payment. You have to add the rights `cotisations.use_every_payment` and `cotisations.buy_every_article`
|
||||||
|
to the staff members so they can use every type of payment to buy anything.
|
||||||
|
|
||||||
|
Don't forget to run migrations, several settings previously in the `preferences` app ar now
|
||||||
|
in their own Payment models.
|
||||||
|
|
||||||
|
To have a closer look on how the payments works, pleas go to the wiki.
|
||||||
|
|
|
@ -53,16 +53,16 @@ class NewFactureForm(FormRevMixin, ModelForm):
|
||||||
Form used to create a new invoice by using a payment method, a bank and a
|
Form used to create a new invoice by using a payment method, a bank and a
|
||||||
cheque number.
|
cheque number.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
|
user = kwargs.pop('user')
|
||||||
prefix = kwargs.pop('prefix', self.Meta.model.__name__)
|
prefix = kwargs.pop('prefix', self.Meta.model.__name__)
|
||||||
allowed_payment = kwargs.pop('allowed_payment', None)
|
|
||||||
super(NewFactureForm, self).__init__(*args, prefix=prefix, **kwargs)
|
super(NewFactureForm, self).__init__(*args, prefix=prefix, **kwargs)
|
||||||
# TODO : remove the use of cheque and banque and paiement
|
|
||||||
# for something more generic or at least in English
|
|
||||||
if allowed_payment:
|
|
||||||
self.fields['paiement'].queryset = allowed_payment
|
|
||||||
self.fields['paiement'].empty_label = \
|
self.fields['paiement'].empty_label = \
|
||||||
_("Select a payment method")
|
_("Select a payment method")
|
||||||
|
self.fields['paiement'].queryset = Paiement.objects.filter(
|
||||||
|
pk__in=map(lambda x: x.pk, Paiement.find_allowed_payments(user))
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Facture
|
model = Facture
|
||||||
|
@ -71,16 +71,10 @@ class NewFactureForm(FormRevMixin, ModelForm):
|
||||||
def clean(self):
|
def clean(self):
|
||||||
cleaned_data = super(NewFactureForm, self).clean()
|
cleaned_data = super(NewFactureForm, self).clean()
|
||||||
paiement = cleaned_data.get('paiement')
|
paiement = cleaned_data.get('paiement')
|
||||||
cheque = cleaned_data.get('cheque')
|
|
||||||
banque = cleaned_data.get('banque')
|
|
||||||
if not paiement:
|
if not paiement:
|
||||||
raise forms.ValidationError(
|
raise forms.ValidationError(
|
||||||
_("A payment method must be specified.")
|
_("A payment method must be specified.")
|
||||||
)
|
)
|
||||||
elif paiement.type_paiement == 'check' and not (cheque and banque):
|
|
||||||
raise forms.ValidationError(
|
|
||||||
_("A cheque number and a bank must be specified.")
|
|
||||||
)
|
|
||||||
return cleaned_data
|
return cleaned_data
|
||||||
|
|
||||||
|
|
||||||
|
@ -103,8 +97,7 @@ class CreditSoldeForm(NewFactureForm):
|
||||||
montant = forms.DecimalField(max_digits=5, decimal_places=2, required=True)
|
montant = forms.DecimalField(max_digits=5, decimal_places=2, required=True)
|
||||||
|
|
||||||
|
|
||||||
class SelectUserArticleForm(
|
class SelectUserArticleForm(FormRevMixin, Form):
|
||||||
FormRevMixin, Form):
|
|
||||||
"""
|
"""
|
||||||
Form used to select an article during the creation of an invoice for a
|
Form used to select an article during the creation of an invoice for a
|
||||||
member.
|
member.
|
||||||
|
@ -123,12 +116,11 @@ class SelectUserArticleForm(
|
||||||
)
|
)
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
self_subscription = kwargs.pop('is_self_subscription', False)
|
user = kwargs.pop('user')
|
||||||
super(SelectUserArticleForm, self).__init__(*args, **kwargs)
|
super(SelectUserArticleForm, self).__init__(*args, **kwargs)
|
||||||
if self_subscription:
|
self.fields['article'].queryset = Article.objects.filter(
|
||||||
self.fields['article'].queryset = Article.objects.filter(
|
pk__in=map(lambda x: x.pk, Article.find_allowed_articles(user))
|
||||||
Q(type_user='All') | Q(type_user='Adherent')
|
)
|
||||||
).filter(allow_self_subscription=True)
|
|
||||||
|
|
||||||
|
|
||||||
class SelectClubArticleForm(Form):
|
class SelectClubArticleForm(Form):
|
||||||
|
@ -150,12 +142,11 @@ class SelectClubArticleForm(Form):
|
||||||
)
|
)
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
self_subscription = kwargs.pop('is_self_subscription', False)
|
user = kwargs.pop('user')
|
||||||
super(SelectClubArticleForm, self).__init__(*args, **kwargs)
|
super(SelectClubArticleForm, self).__init__(*args, **kwargs)
|
||||||
if self_subscription:
|
self.fields['article'].queryset = Article.objects.filter(
|
||||||
self.fields['article'].queryset = Article.objects.filter(
|
pk__in=map(lambda x: x.pk, Article.find_allowed_articles(user))
|
||||||
Q(type_user='All') | Q(type_user='Club')
|
)
|
||||||
).filter(allow_self_subscription=True)
|
|
||||||
|
|
||||||
|
|
||||||
# TODO : change Facture to Invoice
|
# TODO : change Facture to Invoice
|
||||||
|
@ -242,7 +233,7 @@ class PaiementForm(FormRevMixin, ModelForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Paiement
|
model = Paiement
|
||||||
# TODO : change moyen to method and type_paiement to payment_type
|
# TODO : change moyen to method and type_paiement to payment_type
|
||||||
fields = ['moyen', 'allow_self_subscription']
|
fields = ['moyen', 'available_for_everyone']
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
prefix = kwargs.pop('prefix', self.Meta.model.__name__)
|
prefix = kwargs.pop('prefix', self.Meta.model.__name__)
|
||||||
|
@ -315,6 +306,7 @@ class NewFactureSoldeForm(NewFactureForm):
|
||||||
"""
|
"""
|
||||||
Form used to create an invoice
|
Form used to create an invoice
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
prefix = kwargs.pop('prefix', self.Meta.model.__name__)
|
prefix = kwargs.pop('prefix', self.Meta.model.__name__)
|
||||||
super(NewFactureSoldeForm, self).__init__(
|
super(NewFactureSoldeForm, self).__init__(
|
||||||
|
|
28
cotisations/migrations/0035_auto_20180703_1005.py
Normal file
28
cotisations/migrations/0035_auto_20180703_1005.py
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.10.7 on 2018-07-03 15:05
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('cotisations', '0034_auto_20180703_0929'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='paiement',
|
||||||
|
options={'permissions': (('view_paiement', "Can see a payement's details"), ('use', 'Can use a payement')), 'verbose_name': 'Payment method', 'verbose_name_plural': 'Payment methods'},
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='paiement',
|
||||||
|
name='allow_self_subscription',
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='paiement',
|
||||||
|
name='available_for_everyone',
|
||||||
|
field=models.BooleanField(default=False, verbose_name='Is available for every user'),
|
||||||
|
),
|
||||||
|
]
|
28
cotisations/migrations/0036_auto_20180703_1056.py
Normal file
28
cotisations/migrations/0036_auto_20180703_1056.py
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.10.7 on 2018-07-03 15:56
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('cotisations', '0035_auto_20180703_1005'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='article',
|
||||||
|
options={'permissions': (('view_article', "Can see an article's details"), ('buy_every_article', 'Can buy every_article')), 'verbose_name': 'Article', 'verbose_name_plural': 'Articles'},
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='article',
|
||||||
|
name='allow_self_subscription',
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='article',
|
||||||
|
name='available_for_everyone',
|
||||||
|
field=models.BooleanField(default=False, verbose_name='Is available for every user'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -49,7 +49,6 @@ from django.contrib import messages
|
||||||
from machines.models import regen
|
from machines.models import regen
|
||||||
from re2o.field_permissions import FieldPermissionModelMixin
|
from re2o.field_permissions import FieldPermissionModelMixin
|
||||||
from re2o.mixins import AclMixin, RevMixin
|
from re2o.mixins import AclMixin, RevMixin
|
||||||
from preferences.models import OptionalUser
|
|
||||||
|
|
||||||
from cotisations.utils import find_payment_method
|
from cotisations.utils import find_payment_method
|
||||||
|
|
||||||
|
@ -227,10 +226,11 @@ class Facture(RevMixin, AclMixin, FieldPermissionModelMixin, models.Model):
|
||||||
:return: a message and a boolean which is True if the user can create
|
:return: a message and a boolean which is True if the user can create
|
||||||
an invoice or if the `options.allow_self_subscription` is set.
|
an invoice or if the `options.allow_self_subscription` is set.
|
||||||
"""
|
"""
|
||||||
if OptionalUser.get_cached_value('allow_self_subscription'):
|
nb_payments = len(Paiement.find_allowed_payments(user_request))
|
||||||
return True, None
|
nb_articles = len(Article.find_allowed_articles(user_request))
|
||||||
return (
|
return (
|
||||||
user_request.has_perm('cotisations.add_facture'),
|
user_request.has_perm('cotisations.add_facture')
|
||||||
|
or (nb_payments*nb_articles),
|
||||||
_("You don't have the right to create an invoice.")
|
_("You don't have the right to create an invoice.")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -522,9 +522,9 @@ class Article(RevMixin, AclMixin, models.Model):
|
||||||
max_length=255,
|
max_length=255,
|
||||||
verbose_name=_l("Type of cotisation")
|
verbose_name=_l("Type of cotisation")
|
||||||
)
|
)
|
||||||
allow_self_subscription = models.BooleanField(
|
available_for_everyone = models.BooleanField(
|
||||||
default=False,
|
default=False,
|
||||||
verbose_name=_l("Is available for self subscription")
|
verbose_name=_l("Is available for every user")
|
||||||
)
|
)
|
||||||
|
|
||||||
unique_together = ('name', 'type_user')
|
unique_together = ('name', 'type_user')
|
||||||
|
@ -532,6 +532,7 @@ class Article(RevMixin, AclMixin, models.Model):
|
||||||
class Meta:
|
class Meta:
|
||||||
permissions = (
|
permissions = (
|
||||||
('view_article', _l("Can see an article's details")),
|
('view_article', _l("Can see an article's details")),
|
||||||
|
('buy_every_article', _l("Can buy every_article"))
|
||||||
)
|
)
|
||||||
verbose_name = "Article"
|
verbose_name = "Article"
|
||||||
verbose_name_plural = "Articles"
|
verbose_name_plural = "Articles"
|
||||||
|
@ -549,6 +550,24 @@ class Article(RevMixin, AclMixin, models.Model):
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
|
def can_buy_article(self, user, *_args, **_kwargs):
|
||||||
|
"""Check if a user can buy this article.
|
||||||
|
|
||||||
|
:param self: The article
|
||||||
|
:param user: The user requesting buying
|
||||||
|
:returns: A boolean stating if usage is granted and an explanation
|
||||||
|
message if the boolean is `False`.
|
||||||
|
"""
|
||||||
|
return (
|
||||||
|
self.available_for_everyone
|
||||||
|
or user.has_perm('cotisations.buy_every_article'),
|
||||||
|
_("You cannot use this Payment.")
|
||||||
|
)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def find_allowed_articles(cls, user):
|
||||||
|
return [p for p in cls.objects.all() if p.can_buy_article(user)[0]]
|
||||||
|
|
||||||
|
|
||||||
class Banque(RevMixin, AclMixin, models.Model):
|
class Banque(RevMixin, AclMixin, models.Model):
|
||||||
"""
|
"""
|
||||||
|
@ -601,14 +620,15 @@ class Paiement(RevMixin, AclMixin, models.Model):
|
||||||
default=0,
|
default=0,
|
||||||
verbose_name=_l("Payment type")
|
verbose_name=_l("Payment type")
|
||||||
)
|
)
|
||||||
allow_self_subscription = models.BooleanField(
|
available_for_everyone = models.BooleanField(
|
||||||
default=False,
|
default=False,
|
||||||
verbose_name=_l("Is available for self subscription")
|
verbose_name=_l("Is available for every user")
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
permissions = (
|
permissions = (
|
||||||
('view_paiement', _l("Can see a payement's details")),
|
('view_paiement', _l("Can see a payement's details")),
|
||||||
|
('use_every_payment', _l("Can use every payement")),
|
||||||
)
|
)
|
||||||
verbose_name = _l("Payment method")
|
verbose_name = _l("Payment method")
|
||||||
verbose_name_plural = _l("Payment methods")
|
verbose_name_plural = _l("Payment methods")
|
||||||
|
@ -671,6 +691,24 @@ class Paiement(RevMixin, AclMixin, models.Model):
|
||||||
kwargs={'userid': invoice.user.pk}
|
kwargs={'userid': invoice.user.pk}
|
||||||
))
|
))
|
||||||
|
|
||||||
|
def can_use_payment(self, user, *_args, **_kwargs):
|
||||||
|
"""Check if a user can use this payment.
|
||||||
|
|
||||||
|
:param self: The payment
|
||||||
|
:param user: The user requesting usage
|
||||||
|
:returns: A boolean stating if usage is granted and an explanation
|
||||||
|
message if the boolean is `False`.
|
||||||
|
"""
|
||||||
|
return (
|
||||||
|
self.available_for_everyone
|
||||||
|
or user.has_perm('cotisations.use_every_payment'),
|
||||||
|
_("You cannot use this Payment.")
|
||||||
|
)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def find_allowed_payments(cls, user):
|
||||||
|
return [p for p in cls.objects.all() if p.can_use_payment(user)[0]]
|
||||||
|
|
||||||
|
|
||||||
class Cotisation(RevMixin, AclMixin, models.Model):
|
class Cotisation(RevMixin, AclMixin, models.Model):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -98,40 +98,22 @@ def new_facture(request, user, userid):
|
||||||
article_list = Article.objects.filter(
|
article_list = Article.objects.filter(
|
||||||
Q(type_user='All') | Q(type_user=request.user.class_name)
|
Q(type_user='All') | Q(type_user=request.user.class_name)
|
||||||
)
|
)
|
||||||
# Building the invocie form and the article formset
|
# Building the invoice form and the article formset
|
||||||
is_self_subscription = False
|
invoice_form = NewFactureForm(
|
||||||
can_create_invoice = request.user.has_perm('cotisations.add_facture')
|
request.POST or None,
|
||||||
allow_self_subscription = OptionalUser.get_cached_value(
|
instance=invoice,
|
||||||
'allow_self_subscription')
|
user=request.user
|
||||||
if not can_create_invoice:
|
)
|
||||||
if allow_self_subscription:
|
|
||||||
is_self_subscription = True
|
|
||||||
article_list = article_list.filter(allow_self_subscription=True)
|
|
||||||
allowed_payment = Paiement.objects.filter(
|
|
||||||
allow_self_subscription=True)
|
|
||||||
invoice_form = NewFactureForm(
|
|
||||||
request.POST or None, instance=invoice, allowed_payment=allowed_payment)
|
|
||||||
else:
|
|
||||||
messages.error(
|
|
||||||
request,
|
|
||||||
_("You cannot subscribe. Please ask to the staff.")
|
|
||||||
)
|
|
||||||
return redirect(reverse(
|
|
||||||
'users:profil',
|
|
||||||
kwargs={'userid': userid}
|
|
||||||
))
|
|
||||||
else:
|
|
||||||
invoice_form = NewFactureForm(request.POST or None, instance=invoice)
|
|
||||||
|
|
||||||
if request.user.is_class_club:
|
if request.user.is_class_club:
|
||||||
article_formset = formset_factory(SelectClubArticleForm)(
|
article_formset = formset_factory(SelectClubArticleForm)(
|
||||||
request.POST or None,
|
request.POST or None,
|
||||||
form_kwargs={'is_self_subscription': is_self_subscription}
|
form_kwargs={'user': request.user}
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
article_formset = formset_factory(SelectUserArticleForm)(
|
article_formset = formset_factory(SelectUserArticleForm)(
|
||||||
request.POST or None,
|
request.POST or None,
|
||||||
form_kwargs={'is_self_subscription': is_self_subscription}
|
form_kwargs={'user': request.user}
|
||||||
)
|
)
|
||||||
|
|
||||||
if invoice_form.is_valid() and article_formset.is_valid():
|
if invoice_form.is_valid() and article_formset.is_valid():
|
||||||
|
@ -156,7 +138,10 @@ def new_facture(request, user, userid):
|
||||||
)
|
)
|
||||||
new_purchase.save()
|
new_purchase.save()
|
||||||
|
|
||||||
return new_invoice_instance.paiement.end_payment(new_invoice_instance, request)
|
return new_invoice_instance.paiement.end_payment(
|
||||||
|
new_invoice_instance,
|
||||||
|
request
|
||||||
|
)
|
||||||
|
|
||||||
messages.error(
|
messages.error(
|
||||||
request,
|
request,
|
||||||
|
|
Loading…
Reference in a new issue