summaryrefslogtreecommitdiff
path: root/postgresqleu/confsponsor/forms.py
blob: 0656e8bfa3b92acec7f94db87268df199f2f6005 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
from django import forms
from django.forms import ValidationError
from django.forms.utils import ErrorList
from django.db.models import Q
from django.core.validators import MaxValueValidator, MinValueValidator
from django.contrib.auth.models import User
from django.conf import settings

from .models import Sponsor, SponsorMail, SponsorshipLevel, SponsorshipContract
from .models import vat_status_choices
from .models import Shipment
from postgresqleu.confreg.models import RegistrationType, DiscountCode
from postgresqleu.countries.models import EuropeCountry

from postgresqleu.confreg.models import ConferenceAdditionalOption
from postgresqleu.confreg.twitter import get_all_conference_social_media
from postgresqleu.util.fields import UserModelChoiceField
from postgresqleu.util.validators import BeforeValidator, AfterValidator
from postgresqleu.util.validators import Http200Validator
from postgresqleu.util.widgets import Bootstrap4CheckboxSelectMultiple, EmailTextWidget
from postgresqleu.util.widgets import Bootstrap4HtmlDateTimeInput
from postgresqleu.util.time import today_conference

from datetime import timedelta
from decimal import Decimal


def _int_with_default(s, default):
    try:
        return int(s)
    except ValueError:
        return default
    except TypeError:
        return default


class SponsorSignupForm(forms.Form):
    name = forms.CharField(label="Company name *", min_length=3, max_length=100, help_text="This name is used on invoices and in internal communication")
    displayname = forms.CharField(label="Display name *", min_length=3, max_length=100, help_text="This name is displayed on websites and in public communication")
    address = forms.CharField(label="Company invoice address *", min_length=10, max_length=500, widget=forms.Textarea, help_text="The sponsor name is automatically included at beginning of address. The VAT number is automatically included at end of address.")
    vatstatus = forms.ChoiceField(label="Company VAT status", choices=vat_status_choices)
    vatnumber = forms.CharField(label="EU VAT Number", min_length=5, max_length=50, help_text="Enter EU VAT Number to be included on invoices if assigned one. Leave empty if outside the EU or without assigned VAT number.", required=False)
    url = forms.URLField(label="Company URL *", validators=[Http200Validator, ])

    def __init__(self, conference, *args, **kwargs):
        self.conference = conference

        super(SponsorSignupForm, self).__init__(*args, **kwargs)

        for classname, social, impl in sorted(get_all_conference_social_media('sponsor'), key=lambda x: x[1]):
            fn = "social_{}".format(social)
            self.fields[fn] = forms.CharField(label="Company {}".format(social.title()), max_length=250, required=False)
            if hasattr(impl, 'get_field_help'):
                self.fields[fn].help_text = impl.get_field_help('sponsor')

        if not settings.EU_VAT:
            del self.fields['vatstatus']
            del self.fields['vatnumber']

    def clean_name(self):
        if Sponsor.objects.filter(conference=self.conference, name__iexact=self.cleaned_data['name']).exists():
            raise ValidationError("A sponsor with this name is already signed up for this conference!")
        return self.cleaned_data['name']

    def clean_displayname(self):
        if Sponsor.objects.filter(conference=self.conference, displayname__iexact=self.cleaned_data['displayname']).exists():
            raise ValidationError("A sponsor with this display name is already signed up for this conference!")
        return self.cleaned_data['displayname']

    def clean_vatnumber(self):
        # EU VAT numbers begin with a two letter country-code, so let's
        # validate that first
        v = self.cleaned_data['vatnumber'].upper().replace(' ', '')

        if v == "":
            # We allow empty VAT numbers, for sponsors from outside of
            # europe.
            return v

        countrycode = v[:2]
        if countrycode == 'EL':
            # Greece for some reason uses EL instead of their ISO code GR
            # (ref: https://en.wikipedia.org/wiki/VAT_identification_number#Structure)
            countrycode = 'GR'

        if not EuropeCountry.objects.filter(iso=countrycode).exists():
            raise ValidationError("VAT numbers must begin with the two letter country code")
        if settings.EU_VAT_VALIDATE:
            from . import vatutil
            r = vatutil.validate_eu_vat_number(v)
            if r:
                raise ValidationError("Invalid VAT number: %s" % r)
        return v

    def clean(self):
        cleaned_data = super(SponsorSignupForm, self).clean()

        for classname, social, impl in get_all_conference_social_media('sponsor'):
            fn = 'social_{}'.format(social)
            if cleaned_data.get(fn, None):
                try:
                    cleaned_data[fn] = impl.clean_identifier_form_value('sponsor', cleaned_data[fn])
                except ValidationError as v:
                    self.add_error(fn, v)

        if settings.EU_VAT:
            if int(cleaned_data['vatstatus']) == 0:
                # Company inside EU and has VAT number
                if not cleaned_data.get('vatnumber', None):
                    self.add_error('vatnumber', 'VAT number must be specified for companies inside EU with VAT number')
            elif int(cleaned_data['vatstatus']) == 1:
                # Company inside EU but without VAT number
                if cleaned_data.get('vatnumber', None):
                    self.add_error('vatnumber', 'VAT number should not be specified for companies without one!')
            else:
                # Company outside EU
                if cleaned_data.get('vatnumber', None):
                    self.add_error('vatnumber', 'VAT number should not be specified for companies outside EU')

        return cleaned_data


class SponsorSendEmailForm(forms.ModelForm):
    confirm = forms.BooleanField(label="Confirm", required=False)

    class Meta:
        model = SponsorMail
        exclude = ('conference', 'sent', )
        widgets = {
            'message': EmailTextWidget(),
        }

    def __init__(self, conference, sendto, *args, **kwargs):
        self.conference = conference
        self.sendto = sendto
        super(SponsorSendEmailForm, self).__init__(*args, **kwargs)
        if self.sendto == 'level':
            self.fields['levels'].widget = forms.CheckboxSelectMultiple()
            self.fields['levels'].queryset = SponsorshipLevel.objects.filter(conference=self.conference)
            self.fields['levels'].required = True
            del self.fields['sponsors']
        else:
            self.fields['sponsors'].widget = forms.CheckboxSelectMultiple()
            self.fields['sponsors'].queryset = Sponsor.objects.select_related('level').filter(conference=self.conference)
            self.fields['sponsors'].required = True
            self.fields['sponsors'].label_from_instance = lambda s: "{0} ({1}, {2})".format(s, s.level.levelname, s.confirmed and "confirmed {:%Y-%m-%d %H:%M}".format(s.confirmedat) or "NOT confirmed")
            del self.fields['levels']

        self.fields['subject'].help_text = 'Subject will be prefixed with <strong>[{}]</strong>'.format(conference)

        if not ((self.data.get('levels') or self.data.get('sponsors')) and self.data.get('subject') and self.data.get('message')):
            del self.fields['confirm']

    def clean_confirm(self):
        if not self.cleaned_data['confirm']:
            raise ValidationError("Please check this box to confirm that you are really sending this email! There is no going back!")


class PurchaseVouchersForm(forms.Form):
    regtype = forms.ModelChoiceField(queryset=None, required=True, label="Registration type")
    num = forms.IntegerField(required=True, initial=2,
                             label="Number of vouchers",
                             validators=[MinValueValidator(1), ])
    confirm = forms.BooleanField(help_text="Check this form to confirm that you will pay the generated invoice")

    def __init__(self, conference, *args, **kwargs):
        self.conference = conference
        super(PurchaseVouchersForm, self).__init__(*args, **kwargs)
        activeQ = Q(activeuntil__isnull=True) | Q(activeuntil__gte=today_conference())
        if self.data and self.data.get('regtype', None) and self.data.get('num', None) and _int_with_default(self.data['num'], 0) > 0:
            RegistrationType.objects.get(pk=self.data['regtype'])
            self.fields['confirm'].help_text = 'Check this box to confirm that you will pay the generated invoice'
            self.fields['num'].widget.attrs['readonly'] = True
            self.fields['regtype'].queryset = RegistrationType.objects.filter(pk=self.data['regtype'])
        else:
            self.fields['regtype'].queryset = RegistrationType.objects.filter(Q(conference=self.conference, active=True, specialtype__isnull=True, cost__gt=0) & activeQ)
            del self.fields['confirm']


class PurchaseDiscountForm(forms.Form):
    code = forms.CharField(required=True, max_length=100, min_length=4,
                           help_text='Enter the code you want to use to provide the discount.')
    amount = forms.IntegerField(required=False, initial=0,
                                label="Fixed discount in {0}".format(settings.CURRENCY_ABBREV),
                                validators=[MinValueValidator(0), ])
    percent = forms.IntegerField(required=False, initial=0,
                                 label="Percent discount",
                                 validators=[MinValueValidator(0), MaxValueValidator(100), ])
    maxuses = forms.IntegerField(required=True, initial=1,
                                 label="Maximum uses",
                                 validators=[MinValueValidator(1), MaxValueValidator(30), ])
    expires = forms.DateField(required=True, label="Expiry date")
    requiredoptions = forms.ModelMultipleChoiceField(
        required=False, queryset=None,
        label="Attendee add-ons required to use the code",
        widget=Bootstrap4CheckboxSelectMultiple,
        help_text="Check any additional options that are required. Registrations without those options will not be able to use the discount code."
    )
    confirm = forms.BooleanField(help_text="Check this form to confirm that you will pay the costs generated by the people using this code, as specified by the invoice.")

    def __init__(self, conference, showconfirm=False, *args, **kwargs):
        self.conference = conference
        super(PurchaseDiscountForm, self).__init__(*args, **kwargs)
        self.fields['requiredoptions'].queryset = ConferenceAdditionalOption.objects.filter(conference=conference, public=True)
        self.fields['expires'].initial = conference.startdate - timedelta(days=2)
        self.fields['expires'].validators.append(BeforeValidator(conference.startdate - timedelta(days=1)))
        self.fields['expires'].validators.append(AfterValidator(today_conference() - timedelta(days=1)))
        if not showconfirm:
            del self.fields['confirm']

    def clean_code(self):
        # Check if code is already in use for this conference
        if DiscountCode.objects.filter(conference=self.conference, code=self.cleaned_data['code'].upper()).exists():
            raise ValidationError("This discount code is already in use for this conference")

        # Force to uppercase. CSS takes care of that at the presentation layer
        return self.cleaned_data['code'].upper()

    def clean(self):
        cleaned_data = super(PurchaseDiscountForm, self).clean()

        if 'amount' in cleaned_data and 'percent' in cleaned_data:
            # Only one can be specified
            if _int_with_default(cleaned_data['amount'], 0) > 0 and _int_with_default(cleaned_data['percent'], 0) > 0:
                self._errors['amount'] = ErrorList(['Cannot specify both amount and percent!'])
                self._errors['percent'] = ErrorList(['Cannot specify both amount and percent!'])
            elif _int_with_default(cleaned_data['amount'], 0) == 0 and _int_with_default(cleaned_data['percent'], 0) == 0:
                self._errors['amount'] = ErrorList(['Must specify amount or percent!'])
                self._errors['percent'] = ErrorList(['Must specify amount or percent!'])

        return cleaned_data


class SponsorDetailsForm(forms.ModelForm):
    class Meta:
        model = Sponsor
        fields = ('extra_cc', )


class SponsorRefundForm(forms.Form):
    refundamount = forms.ChoiceField(choices=(
        (0, "Refund full invoice cost"),
        (1, "Refund custom amount"),
        (2, "Don't refund"),
    ), label="Refund amount")
    customrefundamount = forms.DecimalField(decimal_places=2, required=False, label="Custom refund amount (ex VAT)")
    customrefundamountvat = forms.DecimalField(decimal_places=2, required=False, label="Custom VAT refund amount")
    cancelmethod = forms.ChoiceField(choices=(
        (0, "Cancel sponsorship"),
        (1, "Leave sponsorship active"),
    ), label="Cancel method")
    confirm = forms.BooleanField(help_text="Confirm that you want to cancel or refund this sponsorship!")

    def __init__(self, invoice, *args, **kwargs):
        self.invoice = invoice
        super().__init__(*args, **kwargs)
        if not invoice.total_vat:
            self.fields['customrefundamount'].label = 'Custom refund amount'
            del self.fields['customrefundamountvat']

        if 'refundamount' not in self.data or 'cancelmethod' not in self.data:
            del self.fields['confirm']

    def clean(self):
        d = super().clean()
        if int(d['refundamount']) == 1:
            # Custom amount, so make sure both those fields are set
            if d['customrefundamount'] is None:
                self.add_error('customrefundamount', 'This field is required when doing custom amount refund')
            elif Decimal(d['customrefundamount'] <= 0):
                self.add_error('customrefundamount', 'Must be >0 when performing custom refund')
            if self.invoice.total_vat and d['customrefundamountvat'] is None:
                self.add_error('customrefundamountvat', 'This field is required when doing custom amount refund')
        else:
            if d['customrefundamount'] is not None:
                self.add_error('customrefundamount', 'This field must be left empty when doing non-custom refund')
            if self.invoice.total_vat and d['customrefundamountvat'] is not None:
                self.add_error('customrefundamountvat', 'This field must be left empty when doing non-custom refund')

        return d


class SponsorReissueForm(forms.Form):
    confirm = forms.BooleanField(required=True, label='Confirm reissuing of invoice',
                                 help_text='New invoice will be automatically sent to the sponsor')


class SponsorShipmentForm(forms.ModelForm):
    sent_parcels = forms.ChoiceField(choices=[], required=True)

    class Meta:
        model = Shipment
        fields = ('description', 'sent_parcels', 'sent_at', 'trackingnumber', 'shippingcompany', 'trackinglink')
        widgets = {
            'sent_at': Bootstrap4HtmlDateTimeInput,
        }

    fieldsets = [
        {
            'id': 'shipment',
            'legend': 'Shipment',
            'fields': ['description', 'sent_parcels', 'sent_at', ],
        },
        {
            'id': 'tracking',
            'legend': 'Tracking',
            'fields': ['trackingnumber', 'shippingcompany', 'trackinglink', ],
        }
    ]

    def __init__(self, *args, **kwargs):
        super(SponsorShipmentForm, self).__init__(*args, **kwargs)
        self.fields['sent_at'].help_text = "Date and (approximate) time when parcels were sent. <strong>DO NOT</strong> set until shipment is actually sent"
        self.fields['sent_parcels'].choices = [('0', " * Don't know yet"), ] + [(str(x), str(x)) for x in range(1, 20)]
        self.fields['trackinglink'].validators.append(Http200Validator)

    def get(self, name, default=None):
        return self[name]


class ShipmentReceiverForm(forms.ModelForm):
    arrived_parcels = forms.ChoiceField(choices=[], required=True)

    class Meta:
        model = Shipment
        fields = ['arrived_parcels', ]

    def __init__(self, *args, **kwargs):
        super(ShipmentReceiverForm, self).__init__(*args, **kwargs)
        self.fields['arrived_parcels'].choices = [(str(x), str(x)) for x in range(1, 20)]


class SponsorAddContractForm(forms.Form):
    subject = forms.CharField(max_length=100, required=True)
    contract = forms.ModelChoiceField(SponsorshipContract.objects.all())
    manager = UserModelChoiceField(User.objects.all(), label="Manager to send to")
    message = forms.CharField(label="Message to send in signing email", widget=forms.Textarea)

    def __init__(self, sponsor, *args, **kwargs):
        self.sponsor = sponsor
        super().__init__(*args, **kwargs)

        self.fields['subject'].help_text = "Subject of contract, for example 'Training contract'. Will be prefixed with '[{}]' in all emails.".format(self.sponsor.conference.conferencename)
        self.fields['contract'].queryset = SponsorshipContract.objects.filter(conference=self.sponsor.conference, sponsorshiplevel=None)
        if sponsor.signmethod == 1:
            # Manual contracts are always sent to all managers
            del self.fields['manager']
        else:
            self.fields['manager'].queryset = self.sponsor.managers