forms.py 13.4 KB
Newer Older
lhark's avatar
lhark committed
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.
chirac's avatar
chirac committed
22
"""
23 24 25 26 27 28 29 30 31 32 33 34
Forms for the 'cotisation' app of re2o. It highly depends on 
: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.
chirac's avatar
chirac committed
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,MaxValueValidator
42
from django.utils.translation import ugettext as _
43
from django.utils.translation import ugettext_lazy as _l
44

chirac's avatar
chirac committed
45
from .models import Article, Paiement, Facture, Banque
46 47
from preferences.models import OptionalUser
from users.models import User
48

49
from re2o.field_permissions import FieldPermissionFormMixin
50
from re2o.mixins import FormRevMixin 
51

52
class NewFactureForm(FormRevMixin, ModelForm):
53 54 55 56
    """
    Form used to create a new invoice by using a payment method, a bank and a
    cheque number.
    """
57
    def __init__(self, *args, **kwargs):
58
        prefix = kwargs.pop('prefix', self.Meta.model.__name__)
59
        super(NewFactureForm, self).__init__(*args, prefix=prefix, **kwargs)
60 61
        # TODO : remove the use of cheque and banque and paiement
        #        for something more generic or at least in English
62 63
        self.fields['cheque'].required = False
        self.fields['banque'].required = False
64 65 66 67
        self.fields['cheque'].label = _("Cheque number")
        self.fields['banque'].empty_label = _("Not specified")
        self.fields['paiement'].empty_label = \
            _("Select a payment method")
chirac's avatar
chirac committed
68 69 70 71
        paiement_list = Paiement.objects.filter(type_paiement=1)
        if paiement_list:
            self.fields['paiement'].widget\
                .attrs['data-cheque'] = paiement_list.first().id
72 73 74

    class Meta:
        model = Facture
chirac's avatar
chirac committed
75
        fields = ['paiement', 'banque', 'cheque']
76 77

    def clean(self):
chirac's avatar
chirac committed
78
        cleaned_data = super(NewFactureForm, self).clean()
79 80 81
        paiement = cleaned_data.get('paiement')
        cheque = cleaned_data.get('cheque')
        banque = cleaned_data.get('banque')
82
        if not paiement:
83
            raise forms.ValidationError(
84
                _("A payment method must be specified.")
85 86 87
            )
        elif paiement.type_paiement == 'check' and not (cheque and banque):
            raise forms.ValidationError(
88
                _("A cheque number and a bank must be specified.")
89
            )
90 91
        return cleaned_data

chirac's avatar
chirac committed
92

chibrac's avatar
chibrac committed
93
class CreditSoldeForm(NewFactureForm):
94 95 96 97
    """
    Form used to make some operations on the user's balance if the option is
    activated.
    """
chibrac's avatar
chibrac committed
98 99
    class Meta(NewFactureForm.Meta):
        model = Facture
chirac's avatar
chirac committed
100
        fields = ['paiement', 'banque', 'cheque']
chibrac's avatar
chibrac committed
101 102 103

    def __init__(self, *args, **kwargs):
        super(CreditSoldeForm, self).__init__(*args, **kwargs)
104
        # TODO : change solde to balance
chirac's avatar
chirac committed
105 106
        self.fields['paiement'].queryset = Paiement.objects.exclude(
            moyen='solde'
107
        ).exclude(moyen='Solde')
chibrac's avatar
chibrac committed
108 109 110

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

chirac's avatar
chirac committed
111

112
class SelectUserArticleForm(FormRevMixin, Form):
113 114 115
    """
    Form used to select an article during the creation of an invoice for a member.
    """
chirac's avatar
chirac committed
116
    article = forms.ModelChoiceField(
117
        queryset=Article.objects.filter(Q(type_user='All') | Q(type_user='Adherent')),
118
        label=_l("Article"),
119 120 121
        required=True
    )
    quantity = forms.IntegerField(
122
        label=_l("Quantity"),
123 124 125 126 127 128
        validators=[MinValueValidator(1)],
        required=True
    )


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

143
# TODO : change Facture to Invoice
Dalahro's avatar
Dalahro committed
144
class NewFactureFormPdf(Form):
145 146 147
    """
    Form used to create a custom PDF invoice.
    """
chirac's avatar
chirac committed
148 149
    article = forms.ModelMultipleChoiceField(
        queryset=Article.objects.all(),
150
        label=_l("Article")
chirac's avatar
chirac committed
151 152
    )
    number = forms.IntegerField(
153
        label=_l("Quantity"),
chirac's avatar
chirac committed
154 155
        validators=[MinValueValidator(1)]
    )
156
    paid = forms.BooleanField(label=_l("Paid"), required=False)
157
    # TODO : change dest field to recipient
158
    dest = forms.CharField(required=True, max_length=255, label=_l("Recipient"))
159
    # TODO : change chambre field to address
160
    chambre = forms.CharField(required=False, max_length=10, label=_l("Address"))
161
    # TODO : change fid field to invoice_id
chirac's avatar
chirac committed
162 163 164
    fid = forms.CharField(
        required=True,
        max_length=10,
165
        label=_l("Invoice number")
chirac's avatar
chirac committed
166 167
    )

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

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

187

188
class ArticleForm(FormRevMixin, ModelForm):
189 190 191
    """
    Form used to create an article.
    """
192 193 194 195 196
    class Meta:
        model = Article
        fields = '__all__'

    def __init__(self, *args, **kwargs):
197
        prefix = kwargs.pop('prefix', self.Meta.model.__name__)
198
        super(ArticleForm, self).__init__(*args, prefix=prefix, **kwargs)
199
        self.fields['name'].label = _("Article name")
200

chirac's avatar
chirac committed
201

202
class DelArticleForm(FormRevMixin, Form):
203 204 205 206
    """
    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
207
    articles = forms.ModelMultipleChoiceField(
208
        queryset=Article.objects.none(),
209
        label=_l("Existing articles"),
chirac's avatar
chirac committed
210 211 212
        widget=forms.CheckboxSelectMultiple
    )

213 214 215 216 217 218 219 220
    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()

221

222
# TODO : change Paiement to Payment
223
class PaiementForm(FormRevMixin, ModelForm):
224 225 226 227 228
    """
    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.
    """
229 230
    class Meta:
        model = Paiement
231
        # TODO : change moyen to method and type_paiement to payment_type
Gabriel Detraz's avatar
Gabriel Detraz committed
232
        fields = ['moyen', 'type_paiement']
233 234

    def __init__(self, *args, **kwargs):
235
        prefix = kwargs.pop('prefix', self.Meta.model.__name__)
236
        super(PaiementForm, self).__init__(*args, prefix=prefix, **kwargs)
237 238 239
        self.fields['moyen'].label = _("Payment method name")
        self.fields['type_paiement'].label = _("Payment type")
        self.fields['type_paiement'].help_text = \
240
            _("The payement type is used for specific behaviour.\
241 242
            The \"cheque\" type means a cheque number and a bank name\
            may be added when using this payment method.")
243

chirac's avatar
chirac committed
244

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

258 259 260 261 262 263 264 265
    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()

266

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

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

chirac's avatar
chirac committed
282

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

    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()
303 304


305
# TODO : change facture to Invoice
306
class NewFactureSoldeForm(NewFactureForm):
307 308 309
    """
    Form used to create an invoice
    """
310 311 312 313
    def __init__(self, *args, **kwargs):
        prefix = kwargs.pop('prefix', self.Meta.model.__name__)
        self.fields['cheque'].required = False
        self.fields['banque'].required = False
314 315 316 317 318
        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
319 320 321 322 323 324
        paiement_list = Paiement.objects.filter(type_paiement=1)
        if paiement_list:
            self.fields['paiement'].widget\
                .attrs['data-cheque'] = paiement_list.first().id

    class Meta:
325
        # TODO : change facture to invoice
326
        model = Facture
327
        # TODO : change paiement to payment and baque to bank
328 329 330 331 332
        fields = ['paiement', 'banque']


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


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

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

    def clean_value(self):
        value = self.cleaned_data['value']
368
        if value < OptionalUser.get_cached_value('min_online_payment'):
369 370
            raise forms.ValidationError(
                _("Requested amount is too small. Minimum amount possible : \
371
                %(min_online_amount)s €.") % {
372 373 374 375 376
                    min_online_amount: OptionalUser.get_cached_value(
                        'min_online_payment'
                    )
                }
            )
377
        if value + self.user.solde > OptionalUser.get_cached_value('max_solde'):
378 379
            raise forms.ValidationError(
                _("Requested amount is too high. Your balance can't exceed \
380
                %(max_online_balance)s €.") % {
381 382 383 384 385
                    max_online_balance: OptionalUser.get_cached_value(
                        'max_solde'
                    )
                }
            )
386
        return value