from django import forms from django.core.validators import MinValueValidator, MaxValueValidator from django.forms import ValidationError from django.forms import widgets from django.contrib.auth.models import User from django.db.models import Q from django.conf import settings from postgresqleu.util.widgets import HtmlDateInput from .models import Invoice, InvoiceRow, InvoicePaymentMethod from postgresqleu.accounting.models import Account, Object from postgresqleu.invoices.models import VatRate class InvoiceForm(forms.ModelForm): hidden_until_finalized = ('total_amount', 'total_vat', 'remindersent', ) available_in_finalized = ('recipient_user', 'recipient_email', 'allowedmethods', 'extra_bcc_list', ) selectize_multiple_fields = ['recipient_user', ] accounting_account = forms.ChoiceField(choices=[], required=False) accounting_object = forms.ChoiceField(choices=[], required=False) def __init__(self, *args, **kwargs): super(InvoiceForm, self).__init__(*args, **kwargs) # Some fields are hidden until the invoice is final if not self.instance.finalized: for fld in self.hidden_until_finalized: del self.fields[fld] if not settings.EU_VAT: del self.fields['reverse_vat'] if 'data' in kwargs and 'recipient_user' in kwargs['data'] and kwargs['data']['recipient_user'] != '': # Postback with this field, so allow this specifi cuser self.fields['recipient_user'].queryset = User.objects.filter(pk=kwargs['data']['recipient_user']) elif self.instance and self.instance.recipient_user: self.fields['recipient_user'].queryset = User.objects.filter(pk=self.instance.recipient_user.pk) else: self.fields['recipient_user'].queryset = User.objects.filter(pk=-1) self.fields['recipient_user'].label_from_instance = lambda u: '{0} {1} ({2})'.format(u.first_name, u.last_name, u.username) self.fields['canceltime'].widget = widgets.DateTimeInput() self.fields['allowedmethods'].widget = forms.CheckboxSelectMultiple() self.fields['allowedmethods'].queryset = InvoicePaymentMethod.objects.filter() self.fields['allowedmethods'].label_from_instance = lambda x: "{0}{1}".format(x.internaldescription, x.active and " " or " (INACTIVE)") self.fields['accounting_account'].choices = [(0, '----'), ] + [(a.num, "%s: %s" % (a.num, a.name)) for a in Account.objects.filter(Q(availableforinvoicing=True) | Q(num=self.instance.accounting_account))] self.fields['accounting_object'].choices = [('', '----'), ] + [(o.name, o.name) for o in Object.objects.filter(active=True)] if self.instance.finalized: # All fields should be read-only for finalized invoices for fn, f in list(self.fields.items()): if self.instance.ispaid or fn not in self.available_in_finalized: f.required = False if type(f.widget).__name__ in ('TextInput', 'Textarea', 'DateInput', 'DateTimeInput'): f.widget.attrs['readonly'] = "readonly" else: f.widget.attrs['disabled'] = True class Meta: model = Invoice exclude = ['finalized', 'pdf_invoice', 'pdf_receipt', 'paidat', 'paymentdetails', 'paidusing', 'processor', 'processorid', 'deleted', 'deletion_reason', 'refund', 'recipient_secret'] widgets = { # Can't use HtmlDateInput since that truncates to just date # 'invoicedate': HtmlDateInput(), 'duedate': HtmlDateInput(), } def clean(self): if not self.cleaned_data['recipient_user'] and self.cleaned_data.get('recipient_email', None): # User not specified. If we can find one by email, auto-populate # the field. matches = User.objects.filter(email=self.cleaned_data['recipient_email'].lower()) if len(matches) == 1: self.cleaned_data['recipient_user'] = matches[0] if self.cleaned_data['accounting_account'] == "0": # Can't figure out how to store NULL automatically, so overwrite # it when we've seen the magic value of zero. self.cleaned_data['accounting_account'] = None return self.cleaned_data class InvoiceRowForm(forms.ModelForm): class Meta: model = InvoiceRow exclude = [] def __init__(self, *args, **kwargs): super(InvoiceRowForm, self).__init__(*args, **kwargs) self.fields['rowcount'].widget.attrs['class'] = "sumfield" self.fields['rowamount'].widget.attrs['class'] = "sumfield" self.fields['vatrate'].widget.attrs['class'] = "sumfield" self.fields['vatrate'].required = False def clean_rowamount(self): if self.cleaned_data['rowamount'] == 0: raise ValidationError("Must specify an amount!") return self.cleaned_data['rowamount'] def clean_rowcount(self): if self.cleaned_data['rowcount'] <= 0: raise ValidationError("Must specify a count!") return self.cleaned_data['rowcount'] class RefundForm(forms.Form): amount = forms.DecimalField(required=True, label="Amount ex VAT", validators=[MinValueValidator(1), ]) vatrate = forms.ModelChoiceField(queryset=VatRate.objects.all(), required=False) reason = forms.CharField(max_length=100, required=True, help_text="Note! Included in communication to invoice recipient!") confirm = forms.BooleanField() def __init__(self, invoice, *args, **kwargs): super(RefundForm, self).__init__(*args, **kwargs) self.invoice = invoice self.fields['amount'].validators.append(MaxValueValidator(invoice.total_refunds['remaining']['amount'])) if self.data and 'amount' in self.data and 'reason' in self.data: if invoice.can_autorefund: self.fields['confirm'].help_text = "Check this box to confirm that you want to generate an automatic refund of this invoice." else: self.fields['confirm'].help_text = "check this box to confirm that you have already manually refunded this invoice." else: del self.fields['confirm']