Fundraising in PDF.
[wolnelektury.git] / src / pz / models.py
1 import re
2 from django.db import models
3 from django.utils.timezone import now
4 from .bank import parse_export_feedback, parse_payment_feedback
5
6
7 class Campaign(models.Model):
8     name = models.CharField('nazwa', max_length=255, unique=True)
9     description = models.TextField('opis', blank=True)
10
11     class Meta:
12         verbose_name = 'kampania'
13         verbose_name_plural = 'kampanie'
14
15     def __str__(self):
16         return self.name
17
18
19 class Fundraiser(models.Model):
20     name = models.CharField('imię i nazwisko', max_length=255, unique=True)
21
22     class Meta:
23         verbose_name = 'fundraiser'
24         verbose_name_plural = 'fundraiserki i fundraiserzy'
25
26     def __str__(self):
27         return self.name
28
29
30 class DirectDebit(models.Model):
31     first_name = models.CharField('imię', max_length=255, blank=True)
32     last_name = models.CharField('nazwisko', max_length=255, blank=True)
33     sex = models.CharField('płeć', max_length=1, blank=True, choices=[
34         ('M', 'M'),
35         ('F', 'K'),
36     ])
37     date_of_birth = models.DateField('data urodzenia', null=True, blank=True)
38     street = models.CharField('ulica', max_length=255, blank=True)
39     building = models.CharField('nr domu', max_length=255, blank=True)
40     flat = models.CharField('nr mieszkania', max_length=255, blank=True)
41     town = models.CharField('miejscowość', max_length=255, blank=True)
42     postal_code = models.CharField('kod pocztowy', max_length=255, blank=True)
43     phone = models.CharField('telefon', max_length=255, blank=True)
44     email = models.CharField('e-mail', max_length=255, blank=True)
45     iban = models.CharField('nr rachunku', max_length=255, blank=True)
46     iban_valid = models.BooleanField('prawidłowy IBAN', default=False, null=True)
47     is_consumer = models.BooleanField('konsument', default=True)
48     payment_id = models.CharField('identyfikator płatności', max_length=255, blank=True, unique=True)
49     agree_fundraising = models.BooleanField('zgoda na kontakt fundraisingowy', default=False)
50     agree_newsletter = models.BooleanField('zgoda na newsletter', default=False)
51
52     acquisition_date = models.DateField(
53         'data pozyskania', help_text='Data z formularza',
54         null=True, blank=True)
55     submission_date = models.DateField(
56         'data dostarczenia', null=True, blank=True,
57         help_text='Data złożenia formularza przez fundraisera')
58     bank_submission_date = models.DateField(
59         'data złożenia do banku', null=True, blank=True,
60         help_text='Data przesłania danych z formularza do banku')
61     bank_acceptance_date = models.DateField(
62         'data akceptacji przez bank', null=True, blank=True,
63         help_text='Data kiedy bank przekazał informację o akceptacji danych z formularza')
64
65     fundraiser = models.ForeignKey(Fundraiser, models.PROTECT, blank=True, null=True, verbose_name='fundraiser')
66     fundraiser_commission = models.IntegerField('prowizja fundraisera', null=True, blank=True)
67     fundraiser_bonus = models.IntegerField('bonus fundraisera', null=True, blank=True)
68     fundraiser_bill = models.CharField('nr rachunku wystawionego przez fundraisera', max_length=255, blank=True)
69
70     amount = models.IntegerField('kwota', null=True, blank=True)
71
72     notes = models.TextField('uwagi', blank=True)
73
74     needs_redo = models.BooleanField('do powtórki', default=False)
75     cancelled_at = models.DateTimeField('anulowane', null=True, blank=True)
76     optout = models.BooleanField('optout', default=False)
77
78     campaign = models.ForeignKey(Campaign, models.PROTECT, null=True, blank=True, verbose_name='kampania')
79
80     latest_status = models.CharField(max_length=255, blank=True)
81
82     nosignature = models.BooleanField('Bez podpisu', default=False)
83     
84     class Meta:
85         verbose_name = 'polecenie zapłaty'
86         verbose_name_plural = 'polecenia zapłaty'
87
88     def __str__(self):
89         return "{} {}".format(self.payment_id, self.latest_status)
90
91     def get_latest_status(self):
92         line = self.bankexportfeedbackline_set.order_by('-feedback__created_at').first()
93         if line is None: return ""
94         return line.comment
95
96     def save(self, **kwargs):
97         self.iban_valid = not self.iban_warning() if self.iban else None
98         self.latest_status = self.get_latest_status()
99         super().save(**kwargs)
100
101     @classmethod
102     def get_next_payment_id(cls):
103         # Find the last object added.
104         last = cls.objects.order_by('-id').first()
105         if last is None:
106             return ''
107         match = re.match(r'^(.*?)(\d+)$', last.payment_id)
108         if match is None:
109             return ''
110         prefix = match.group(1)
111         number = int(match.group(2))
112         number_length = len(match.group(2))
113         while True:
114             number += 1
115             payment_id = f'{prefix}{number:0{number_length}}'
116             if not cls.objects.filter(payment_id=payment_id).exists():
117                 break
118         return payment_id
119
120     @property
121     def full_name(self):
122         return ' '.join((self.first_name, self.last_name)).strip()
123
124     @property
125     def street_address(self):
126         street_addr = self.street
127         if self.building:
128             street_addr += ' ' + self.building
129         if self.flat:
130             street_addr += ' m. ' + self.flat
131         street_addr = street_addr.strip()
132         return street_addr
133
134     def iban_warning(self):
135         if not self.iban:
136             return 'No IBAN'
137         if len(self.iban) != 26:
138             return 'Bad IBAN length'
139         if int(self.iban[2:] + '2521' + self.iban[:2]) % 97 != 1:
140             return 'This IBAN number looks invalid'
141         return ''
142     iban_warning.short_description = ''
143
144
145 class BankExportFeedback(models.Model):
146     created_at = models.DateTimeField(auto_now_add=True)
147     csv = models.FileField(upload_to='pz/feedback/')
148
149     def save(self, **kwargs):
150         super().save(**kwargs)
151         try:
152             self.save_payment_items()
153         except AssertionError:
154             self.save_export_feedback_items()
155
156     def save_payment_items(self):
157         for payment_id, booking_date, is_dd, realised, reject_code in parse_payment_feedback(self.csv.open()):
158             debit = DirectDebit.objects.get(payment_id = payment_id)
159             b, created = self.payment_set.get_or_create(
160                 debit=debit,
161                 defaults={
162                     'booking_date': booking_date,
163                     'is_dd': is_dd,
164                     'realised': realised,
165                     'reject_code': reject_code,
166                 }
167             )
168             if not created:
169                 b.booking_date = booking_date
170                 b.is_dd = is_dd
171                 b.realised = realised
172                 b.reject_code = reject_code
173                 b.save()
174         
175     def save_export_feedback_items(self):
176         for payment_id, status, comment in parse_export_feedback(self.csv.open()):
177             debit = DirectDebit.objects.get(payment_id = payment_id)
178             b, created = self.bankexportfeedbackline_set.get_or_create(
179                 debit=debit,
180                 defaults={
181                     "status": status,
182                     "comment": comment,
183                 }
184             )
185             if not created:
186                 b.status = status
187                 b.comment = comment
188                 b.save()
189             if status == 1 and not debit.bank_acceptance_date:
190                 debit.bank_acceptance_date = now().date()
191             debit.save()
192
193
194 class BankExportFeedbackLine(models.Model):
195     feedback = models.ForeignKey(BankExportFeedback, models.CASCADE)
196     debit = models.ForeignKey(DirectDebit, models.CASCADE)
197     status = models.SmallIntegerField()
198     comment = models.CharField(max_length=255)
199
200
201 class Payment(models.Model):
202     feedback = models.ForeignKey(BankExportFeedback, models.CASCADE)
203     debit = models.ForeignKey(DirectDebit, models.CASCADE)
204     booking_date = models.DateField()
205     is_dd = models.BooleanField()
206     realised = models.BooleanField()
207     reject_code = models.CharField(max_length=128, blank=True)
208
209     
210
211 class BankOrder(models.Model):
212     payment_date = models.DateField(null=True, blank=True)
213     sent = models.DateTimeField(null=True, blank=True)
214     debits = models.ManyToManyField(DirectDebit, blank=True)