# 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 # Copyright © 2018 Hugo Levy-Falk # # 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. """ 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. """ from __future__ import unicode_literals from django import forms from django.forms import ModelForm, Form from django.core.validators import MinValueValidator from django.utils.translation import ugettext_lazy as _ from django.shortcuts import get_object_or_404 from re2o.field_permissions import FieldPermissionFormMixin from re2o.mixins import FormRevMixin from .models import ( Article, Paiement, Facture, Banque, CustomInvoice, Vente, CostEstimate, ) from .payment_methods import balance class FactureForm(FieldPermissionFormMixin, FormRevMixin, ModelForm): """ Form used to manage and create an invoice and its fields. """ def __init__(self, *args, creation=False, **kwargs): user = kwargs['user'] prefix = kwargs.pop('prefix', self.Meta.model.__name__) super(FactureForm, self).__init__(*args, prefix=prefix, **kwargs) self.fields['paiement'].empty_label = \ _("Select a payment method") self.fields['paiement'].queryset = Paiement.find_allowed_payments(user) if not creation: self.fields['user'].label = _("Member") self.fields['user'].empty_label = \ _("Select the proprietary member") self.fields['validity'].label = _("Validated invoice") else: self.fields = {'paiement': self.fields['paiement']} class Meta: model = Facture fields = '__all__' def clean(self): cleaned_data = super(FactureForm, self).clean() paiement = cleaned_data.get('paiement') if not paiement: raise forms.ValidationError( _("A payment method must be specified.") ) return cleaned_data class SelectArticleForm(FormRevMixin, Form): """ Form used to select an article during the creation of an invoice for a member. """ article = forms.ModelChoiceField( queryset=Article.objects.none(), label=_("Article"), required=True ) quantity = forms.IntegerField( label=_("Quantity"), validators=[MinValueValidator(1)], required=True ) def __init__(self, *args, **kwargs): user = kwargs.pop('user') target_user = kwargs.pop('target_user', None) super(SelectArticleForm, self).__init__(*args, **kwargs) self.fields['article'].queryset = Article.find_allowed_articles( user, target_user) class DiscountForm(Form): """ Form used in oder to create a discount on an invoice. """ is_relative = forms.BooleanField( label=_("Discount is in percentage."), required=False, ) discount = forms.DecimalField( label=_("Discount"), max_value=100, min_value=0, max_digits=5, decimal_places=2, required=False, ) def apply_to_invoice(self, invoice): invoice_price = invoice.prix_total() discount = self.cleaned_data['discount'] is_relative = self.cleaned_data['is_relative'] if is_relative: amount = discount/100 * invoice_price else: amount = discount if amount: name = _("{}% discount") if is_relative else _("{}€ discount") name = name.format(discount) Vente.objects.create( facture=invoice, name=name, prix=-amount, number=1 ) class CustomInvoiceForm(FormRevMixin, ModelForm): """ Form used to create a custom invoice. """ class Meta: model = CustomInvoice fields = '__all__' class CostEstimateForm(FormRevMixin, ModelForm): """ Form used to create a cost estimate. """ class Meta: model = CostEstimate exclude = ['paid', 'final_invoice'] class ArticleForm(FormRevMixin, ModelForm): """ Form used to create an article. """ class Meta: model = Article fields = '__all__' class DelArticleForm(FormRevMixin, Form): """ Form used to delete one or more of the currently available articles. The user must choose the one to delete by checking the boxes. """ articles = forms.ModelMultipleChoiceField( queryset=Article.objects.none(), label=_("Available articles"), widget=forms.CheckboxSelectMultiple ) 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() class PaymentForm(FormRevMixin, ModelForm): """ 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. """ class Meta: model = Paiement # TODO : change moyen to method and type_paiement to payment_type fields = ['moyen', 'available_for_everyone'] def __init__(self, *args, **kwargs): prefix = kwargs.pop('prefix', self.Meta.model.__name__) super(PaymentForm, self).__init__(*args, prefix=prefix, **kwargs) self.fields['moyen'].label = _("Payment method name") class DelPaymentForm(FormRevMixin, Form): """ Form used to delete one or more payment methods. The user must choose the one to delete by checking the boxes. """ # TODO : change paiement to payment paiements = forms.ModelMultipleChoiceField( queryset=Paiement.objects.none(), label=_("Available payment methods"), widget=forms.CheckboxSelectMultiple ) def __init__(self, *args, **kwargs): instances = kwargs.pop('instances', None) super(DelPaymentForm, self).__init__(*args, **kwargs) if instances: self.fields['paiements'].queryset = instances else: self.fields['paiements'].queryset = Paiement.objects.all() class BankForm(FormRevMixin, ModelForm): """ Form used to create a bank. """ class Meta: # TODO : change banque to bank model = Banque fields = '__all__' class DelBankForm(FormRevMixin, Form): """ Form used to delete one or more banks. The use must choose the one to delete by checking the boxes. """ # TODO : change banque to bank banques = forms.ModelMultipleChoiceField( queryset=Banque.objects.none(), label=_("Available banks"), widget=forms.CheckboxSelectMultiple ) def __init__(self, *args, **kwargs): instances = kwargs.pop('instances', None) super(DelBankForm, self).__init__(*args, **kwargs) if instances: self.fields['banques'].queryset = instances else: self.fields['banques'].queryset = Banque.objects.all() class RefillBalanceForm(FormRevMixin, Form): """ Form used to refill a user's balance """ value = forms.DecimalField( label=_("Amount"), min_value=0.01, validators=[] ) payment = forms.ModelChoiceField( queryset=Paiement.objects.none(), label=_("Payment method") ) def __init__(self, *args, user=None, user_source=None, **kwargs): self.user = user super(RefillBalanceForm, self).__init__(*args, **kwargs) self.fields['payment'].empty_label = \ _("Select a payment method") self.fields['payment'].queryset = Paiement.find_allowed_payments( user_source).exclude(is_balance=True) def clean(self): """ Returns a cleaned value from the received form by validating the value is well inside the possible limits """ value = self.cleaned_data['value'] balance_method = get_object_or_404(balance.PaymentMethod) if balance_method.maximum_balance is not None and \ value + self.user.solde > balance_method.maximum_balance: raise forms.ValidationError( _("Requested amount is too high. Your balance can't exceed" " %(max_online_balance)s €.") % { 'max_online_balance': balance_method.maximum_balance } ) return self.cleaned_data