forms.py 13 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
# 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  Goulven 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.
22
"""
23
Forms for the 'cotisation' app of re2o. It highly depends on
24 25 26 27 28 29 30 31 32 33 34
:cotisations:models and is mainly used by :cotisations:views.

The following forms are mainly used to create, edit or delete
anything related to 'cotisations' :
    * Payments Methods
    * Banks
    * Invoices
    * Articles

See the details for each of these operations in the documentation
of each of the method.
35
"""
36 37
from __future__ import unicode_literals

38
from django import forms
39
from django.db.models import Q
Dalahro's avatar
Dalahro committed
40
from django.forms import ModelForm, Form
41
from django.core.validators import MinValueValidator
42
from django.utils.translation import ugettext as _
43
from django.utils.translation import ugettext_lazy as _l
44

45
from preferences.models import OptionalUser
46
from re2o.field_permissions import FieldPermissionFormMixin
47
from re2o.mixins import FormRevMixin
48
from .models import Article, Paiement, Facture, Banque
49

50

51
class NewFactureForm(FormRevMixin, ModelForm):
52 53 54 55
    """
    Form used to create a new invoice by using a payment method, a bank and a
    cheque number.
    """
Hugo LEVY-FALK's avatar
Hugo LEVY-FALK committed
56

57
    def __init__(self, *args, **kwargs):
Hugo LEVY-FALK's avatar
Hugo LEVY-FALK committed
58
        user = kwargs.pop('user')
59
        prefix = kwargs.pop('prefix', self.Meta.model.__name__)
60
        super(NewFactureForm, self).__init__(*args, prefix=prefix, **kwargs)
61 62
        self.fields['paiement'].empty_label = \
            _("Select a payment method")
Hugo LEVY-FALK's avatar
Hugo LEVY-FALK committed
63 64 65
        self.fields['paiement'].queryset = Paiement.objects.filter(
            pk__in=map(lambda x: x.pk, Paiement.find_allowed_payments(user))
        )
66

67 68
    class Meta:
        model = Facture
69
        fields = ['paiement']
70 71

    def clean(self):
chirac's avatar
chirac committed
72
        cleaned_data = super(NewFactureForm, self).clean()
73
        paiement = cleaned_data.get('paiement')
74
        if not paiement:
75
            raise forms.ValidationError(
76
                _("A payment method must be specified.")
77
            )
78 79
        return cleaned_data

chirac's avatar
chirac committed
80

chibrac's avatar
chibrac committed
81
class CreditSoldeForm(NewFactureForm):
82 83 84 85
    """
    Form used to make some operations on the user's balance if the option is
    activated.
    """
chibrac's avatar
chibrac committed
86 87
    class Meta(NewFactureForm.Meta):
        model = Facture
chirac's avatar
chirac committed
88
        fields = ['paiement', 'banque', 'cheque']
chibrac's avatar
chibrac committed
89 90 91

    def __init__(self, *args, **kwargs):
        super(CreditSoldeForm, self).__init__(*args, **kwargs)
92
        # TODO : change solde to balance
chirac's avatar
chirac committed
93 94
        self.fields['paiement'].queryset = Paiement.objects.exclude(
            moyen='solde'
95
        ).exclude(moyen='Solde')
chibrac's avatar
chibrac committed
96 97 98

    montant = forms.DecimalField(max_digits=5, decimal_places=2, required=True)

chirac's avatar
chirac committed
99

Hugo LEVY-FALK's avatar
Hugo LEVY-FALK committed
100
class SelectUserArticleForm(FormRevMixin, Form):
101
    """
102 103
    Form used to select an article during the creation of an invoice for a
    member.
104
    """
chirac's avatar
chirac committed
105
    article = forms.ModelChoiceField(
106 107 108
        queryset=Article.objects.filter(
            Q(type_user='All') | Q(type_user='Adherent')
        ),
109
        label=_l("Article"),
110 111 112
        required=True
    )
    quantity = forms.IntegerField(
113
        label=_l("Quantity"),
114 115 116 117
        validators=[MinValueValidator(1)],
        required=True
    )

118
    def __init__(self, *args, **kwargs):
Hugo LEVY-FALK's avatar
Hugo LEVY-FALK committed
119
        user = kwargs.pop('user')
120
        super(SelectUserArticleForm, self).__init__(*args, **kwargs)
Hugo LEVY-FALK's avatar
Hugo LEVY-FALK committed
121 122 123
        self.fields['article'].queryset = Article.objects.filter(
            pk__in=map(lambda x: x.pk, Article.find_allowed_articles(user))
        )
124

125 126

class SelectClubArticleForm(Form):
127
    """
128 129
    Form used to select an article during the creation of an invoice for a
    club.
130
    """
131
    article = forms.ModelChoiceField(
132 133 134
        queryset=Article.objects.filter(
            Q(type_user='All') | Q(type_user='Club')
        ),
135
        label=_l("Article"),
chirac's avatar
chirac committed
136 137 138
        required=True
    )
    quantity = forms.IntegerField(
139
        label=_l("Quantity"),
chirac's avatar
chirac committed
140 141 142 143
        validators=[MinValueValidator(1)],
        required=True
    )

144
    def __init__(self, *args, **kwargs):
Hugo LEVY-FALK's avatar
Hugo LEVY-FALK committed
145
        user = kwargs.pop('user')
146
        super(SelectClubArticleForm, self).__init__(*args, **kwargs)
Hugo LEVY-FALK's avatar
Hugo LEVY-FALK committed
147 148 149
        self.fields['article'].queryset = Article.objects.filter(
            pk__in=map(lambda x: x.pk, Article.find_allowed_articles(user))
        )
150

151

152
# TODO : change Facture to Invoice
Dalahro's avatar
Dalahro committed
153
class NewFactureFormPdf(Form):
154 155 156
    """
    Form used to create a custom PDF invoice.
    """
157
    paid = forms.BooleanField(label=_l("Paid"), required=False)
158
    # TODO : change dest field to recipient
159 160 161 162 163
    dest = forms.CharField(
        required=True,
        max_length=255,
        label=_l("Recipient")
    )
164
    # TODO : change chambre field to address
165 166 167 168 169 170
    chambre = forms.CharField(
        required=False,
        max_length=10,
        label=_l("Address")
    )

chirac's avatar
chirac committed
171

172
# TODO : change Facture to Invoice
173
class EditFactureForm(FieldPermissionFormMixin, NewFactureForm):
174 175 176 177
    """
    Form used to edit an invoice and its fields : payment method, bank,
    user associated, ...
    """
178
    class Meta(NewFactureForm.Meta):
179
        # TODO : change Facture to Invoice
180 181
        model = Facture
        fields = '__all__'
182 183

    def __init__(self, *args, **kwargs):
184
        # TODO : change Facture to Invoice
185
        super(EditFactureForm, self).__init__(*args, **kwargs)
186 187 188 189
        self.fields['user'].label = _("Member")
        self.fields['user'].empty_label = \
            _("Select the proprietary member")
        self.fields['valid'].label = _("Validated invoice")
190

191

192
class ArticleForm(FormRevMixin, ModelForm):
193 194 195
    """
    Form used to create an article.
    """
196 197 198 199 200
    class Meta:
        model = Article
        fields = '__all__'

    def __init__(self, *args, **kwargs):
201
        prefix = kwargs.pop('prefix', self.Meta.model.__name__)
202
        super(ArticleForm, self).__init__(*args, prefix=prefix, **kwargs)
203
        self.fields['name'].label = _("Article name")
204

chirac's avatar
chirac committed
205

206
class DelArticleForm(FormRevMixin, Form):
207 208 209 210
    """
    Form used to delete one or more of the currently available articles.
    The user must choose the one to delete by checking the boxes.
    """
chirac's avatar
chirac committed
211
    articles = forms.ModelMultipleChoiceField(
212
        queryset=Article.objects.none(),
213
        label=_l("Existing articles"),
chirac's avatar
chirac committed
214 215 216
        widget=forms.CheckboxSelectMultiple
    )

217 218 219 220 221 222 223 224
    def __init__(self, *args, **kwargs):
        instances = kwargs.pop('instances', None)
        super(DelArticleForm, self).__init__(*args, **kwargs)
        if instances:
            self.fields['articles'].queryset = instances
        else:
            self.fields['articles'].queryset = Article.objects.all()

225

226
# TODO : change Paiement to Payment
227
class PaiementForm(FormRevMixin, ModelForm):
228 229 230 231 232
    """
    Form used to create a new payment method.
    The 'cheque' type is used to associate a specific behaviour requiring
    a cheque number and a bank.
    """
233 234
    class Meta:
        model = Paiement
235
        # TODO : change moyen to method and type_paiement to payment_type
Hugo LEVY-FALK's avatar
Hugo LEVY-FALK committed
236
        fields = ['moyen', 'available_for_everyone']
237 238

    def __init__(self, *args, **kwargs):
239
        prefix = kwargs.pop('prefix', self.Meta.model.__name__)
240
        super(PaiementForm, self).__init__(*args, prefix=prefix, **kwargs)
241
        self.fields['moyen'].label = _("Payment method name")
242

chirac's avatar
chirac committed
243

244
# TODO : change paiement to payment
245
class DelPaiementForm(FormRevMixin, Form):
246 247 248 249
    """
    Form used to delete one or more payment methods.
    The user must choose the one to delete by checking the boxes.
    """
250
    # TODO : change paiement to payment
chirac's avatar
chirac committed
251
    paiements = forms.ModelMultipleChoiceField(
252
        queryset=Paiement.objects.none(),
253
        label=_l("Existing payment method"),
chirac's avatar
chirac committed
254 255 256
        widget=forms.CheckboxSelectMultiple
    )

257 258 259 260 261 262 263 264
    def __init__(self, *args, **kwargs):
        instances = kwargs.pop('instances', None)
        super(DelPaiementForm, self).__init__(*args, **kwargs)
        if instances:
            self.fields['paiements'].queryset = instances
        else:
            self.fields['paiements'].queryset = Paiement.objects.all()

265

266
# TODO : change banque to bank
267
class BanqueForm(FormRevMixin, ModelForm):
268 269 270
    """
    Form used to create a bank.
    """
chirac's avatar
chirac committed
271
    class Meta:
272
        # TODO : change banque to bank
chirac's avatar
chirac committed
273 274 275 276
        model = Banque
        fields = ['name']

    def __init__(self, *args, **kwargs):
277
        prefix = kwargs.pop('prefix', self.Meta.model.__name__)
278
        super(BanqueForm, self).__init__(*args, prefix=prefix, **kwargs)
279
        self.fields['name'].label = _("Bank name")
chirac's avatar
chirac committed
280

chirac's avatar
chirac committed
281

282
# TODO : change banque to bank
283
class DelBanqueForm(FormRevMixin, Form):
284 285 286 287
    """
    Form used to delete one or more banks.
    The use must choose the one to delete by checking the boxes.
    """
288
    # TODO : change banque to bank
chirac's avatar
chirac committed
289
    banques = forms.ModelMultipleChoiceField(
290
        queryset=Banque.objects.none(),
291
        label=_l("Existing banks"),
chirac's avatar
chirac committed
292 293
        widget=forms.CheckboxSelectMultiple
    )
294 295 296 297 298 299 300 301

    def __init__(self, *args, **kwargs):
        instances = kwargs.pop('instances', None)
        super(DelBanqueForm, self).__init__(*args, **kwargs)
        if instances:
            self.fields['banques'].queryset = instances
        else:
            self.fields['banques'].queryset = Banque.objects.all()
302 303


304
# TODO : change facture to Invoice
305
class NewFactureSoldeForm(NewFactureForm):
306 307 308
    """
    Form used to create an invoice
    """
Hugo LEVY-FALK's avatar
Hugo LEVY-FALK committed
309

310 311
    def __init__(self, *args, **kwargs):
        prefix = kwargs.pop('prefix', self.Meta.model.__name__)
312 313 314 315 316
        super(NewFactureSoldeForm, self).__init__(
            *args,
            prefix=prefix,
            **kwargs
        )
317 318
        self.fields['cheque'].required = False
        self.fields['banque'].required = False
319 320 321 322 323
        self.fields['cheque'].label = _('Cheque number')
        self.fields['banque'].empty_label = _("Not specified")
        self.fields['paiement'].empty_label = \
            _("Select a payment method")
        # TODO : change paiement to payment
324 325 326 327 328 329
        paiement_list = Paiement.objects.filter(type_paiement=1)
        if paiement_list:
            self.fields['paiement'].widget\
                .attrs['data-cheque'] = paiement_list.first().id

    class Meta:
330
        # TODO : change facture to invoice
331
        model = Facture
332
        # TODO : change paiement to payment and baque to bank
333 334 335 336
        fields = ['paiement', 'banque']

    def clean(self):
        cleaned_data = super(NewFactureSoldeForm, self).clean()
337
        # TODO : change paiement to payment
338 339
        paiement = cleaned_data.get("paiement")
        cheque = cleaned_data.get("cheque")
340
        # TODO : change banque to bank
341
        banque = cleaned_data.get("banque")
342
        # TODO : change paiement to payment
343
        if not paiement:
344 345 346 347
            raise forms.ValidationError(
                _("A payment method must be specified.")
            )
        # TODO : change paiement and banque to payment and bank
348
        elif paiement.type_paiement == "check" and not (cheque and banque):
349 350 351
            raise forms.ValidationError(
                _("A cheque number and a bank must be specified.")
            )
352
        return cleaned_data
353 354


355
# TODO : Better name and docstring
356
class RechargeForm(FormRevMixin, Form):
357 358 359
    """
    Form used to refill a user's balance
    """
360
    value = forms.FloatField(
361
        label=_l("Amount"),
362
        min_value=0.01,
363
        validators=[]
364
    )
365 366 367 368 369 370

    def __init__(self, *args, **kwargs):
        self.user = kwargs.pop('user')
        super(RechargeForm, self).__init__(*args, **kwargs)

    def clean_value(self):
371 372 373 374
        """
        Returns a cleaned vlaue from the received form by validating
        the value is well inside the possible limits
        """
375
        value = self.cleaned_data['value']
376
        if value < OptionalUser.get_cached_value('min_online_payment'):
377 378
            raise forms.ValidationError(
                _("Requested amount is too small. Minimum amount possible : \
379
                %(min_online_amount)s €.") % {
380
                    'min_online_amount': OptionalUser.get_cached_value(
381 382 383 384
                        'min_online_payment'
                    )
                }
            )
385 386
        if value + self.user.solde > \
                OptionalUser.get_cached_value('max_solde'):
387 388
            raise forms.ValidationError(
                _("Requested amount is too high. Your balance can't exceed \
389
                %(max_online_balance)s €.") % {
390
                    'max_online_balance': OptionalUser.get_cached_value(
391 392 393 394
                        'max_solde'
                    )
                }
            )
395
        return value