from django import template
from django.utils.timezone import now
from django.utils.translation import ugettext_lazy as _, ungettext, ugettext, get_language
+from django_countries.fields import CountryField
from catalogue.utils import get_random_hash
from messaging.states import Level
from reporting.utils import render_to_pdf
-from .payment_methods import recurring_payment_method, single_payment_method
+from .payment_methods import methods
from .payu import models as payu_models
+from .civicrm import report_activity
from . import utils
return [int(x) for x in self.monthly_amounts.split(',')]
+class Consent(models.Model):
+ order = models.IntegerField()
+ active = models.BooleanField(default=True)
+ text = models.CharField(max_length=2048)
+ required = models.BooleanField()
+
+ class Meta:
+ ordering = ['order']
+
+ def __str__(self):
+ return self.text
+
+
class Schedule(models.Model):
""" Represents someone taking up a plan. """
key = models.CharField(_('key'), max_length=255, unique=True)
email = models.EmailField(_('email'))
- membership = models.ForeignKey('Membership', verbose_name=_('membership'), null=True, blank=True, on_delete=models.PROTECT)
+ membership = models.ForeignKey('Membership', verbose_name=_('membership'), null=True, blank=True, on_delete=models.SET_NULL)
amount = models.DecimalField(_('amount'), max_digits=10, decimal_places=2)
+ method = models.CharField(_('method'), max_length=32, choices=[
+ (m.slug, m.name) for m in methods
+ ])
monthly = models.BooleanField(_('monthly'), default=True)
yearly = models.BooleanField(_('yearly'), default=False)
-
+
+ source = models.CharField(_('source'), max_length=255, blank=True)
+
is_cancelled = models.BooleanField(_('cancelled'), default=False)
payed_at = models.DateTimeField(_('payed at'), null=True, blank=True)
started_at = models.DateTimeField(_('started at'), auto_now_add=True)
expires_at = models.DateTimeField(_('expires_at'), null=True, blank=True)
email_sent = models.BooleanField(default=False)
+ first_name = models.CharField(max_length=255, blank=True)
+ last_name = models.CharField(max_length=255, blank=True)
+ phone = models.CharField(max_length=255, blank=True)
+ postal = models.CharField(max_length=255, blank=True)
+ postal_code = models.CharField(max_length=255, blank=True)
+ postal_town = models.CharField(max_length=255, blank=True)
+ postal_country = CountryField(default='PL', blank=True)
+
+ consent = models.ManyToManyField(Consent)
+
class Meta:
verbose_name = _('schedule')
verbose_name_plural = _('schedules')
return reverse('club_thanks', args=[self.key])
def get_payment_method(self):
- return recurring_payment_method if self.monthly or self.yearly else single_payment_method
+ return [m for m in methods if m.slug == self.method][0]
def is_expired(self):
return self.expires_at is not None and self.expires_at <= now()
def is_recurring(self):
return self.monthly or self.yearly
+ def set_payed(self):
+ since = self.expires_at
+ n = now()
+ if since is None or since < n:
+ since = n
+ new_exp = self.get_next_installment(since)
+ if self.payed_at is None:
+ self.payed_at = n
+ if self.expires_at is None or self.expires_at < new_exp:
+ self.expires_at = new_exp
+ self.save()
+
+ if not self.email_sent:
+ self.send_email()
+
def get_next_installment(self, date):
if self.yearly:
return utils.add_year(date)
Contact.update(self.email, level, since, self.expires_at)
+
+
+
class Membership(models.Model):
""" Represents a user being recognized as a member of the club. """
user = models.OneToOneField(settings.AUTH_USER_MODEL, verbose_name=_('user'), on_delete=models.CASCADE)
created_at = models.DateTimeField(_('created at'), auto_now_add=True)
- name = models.CharField(max_length=255, blank=True)
- honorary = models.BooleanField(default=False)
+ name = models.CharField(_('name'), max_length=255, blank=True)
+ manual = models.BooleanField(_('manual'), default=False)
+ notes = models.CharField(_('notes'), max_length=2048, blank=True)
+ updated_at = models.DateField(_('updated at'), auto_now=True, blank=True)
class Meta:
verbose_name = _('membership')
def __str__(self):
return str(self.user)
+ def save(self, *args, **kwargs):
+ super().save(*args, **kwargs)
+ self.update_contact()
+
+ def update_contact(self):
+ email = self.user.email
+ if not email:
+ return
+
+ Contact = apps.get_model('messaging', 'Contact')
+ if self.manual:
+ Contact.update(email, Level.MANUAL_MEMBER, self.updated_at)
+ else:
+ Contact.reset(email)
+
@classmethod
def is_active_for(cls, user):
if user.is_anonymous:
membership = user.membership
except cls.DoesNotExist:
return False
- if membership.honorary:
+ if membership.manual:
return True
return Schedule.objects.filter(
expires_at__gt=now(),
self.schedule.get_thanks_url())
def get_description(self):
- return ugettext('Towarzystwo Przyjaciół Wolnych Lektur')
+ return 'Wolne Lektury'
def is_recurring(self):
return self.schedule.get_payment_method().is_recurring
def status_updated(self):
if self.status == 'COMPLETED':
- since = self.schedule.expires_at
- n = now()
- if since is None or since < n:
- since = n
- new_exp = self.schedule.get_next_installment(since)
- if self.schedule.payed_at is None:
- self.schedule.payed_at = n
- if self.schedule.expires_at is None or self.schedule.expires_at < new_exp:
- self.schedule.expires_at = new_exp
- self.schedule.save()
-
- if not self.schedule.email_sent:
- self.schedule.send_email()
+ self.schedule.set_payed()
+ self.report_activity()
+
+ @property
+ def updated_at(self):
+ try:
+ return self.notification_set.all().order_by('-received_at')[0].received_at
+ except IndexError:
+ return None
+
+ def report_activity(self):
+ if self.status not in ['COMPLETED', 'CANCELED', 'REJECTED']:
+ return
+
+ if self.status != 'COMPLETED':
+ name = settings.CIVICRM_ACTIVITIES['Failed contribution']
+ elif self.is_recurring():
+ name = settings.CIVICRM_ACTIVITIES['Recurring contribution']
+ else:
+ name = settings.CIVICRM_ACTIVITIES['Contribution']
+
+ report_activity.delay(
+ self.schedule.email,
+ self.schedule.key,
+ f'payu:{self.id}',
+ name,
+ self.updated_at,
+ {
+ 'kwota': self.schedule.amount,
+ }
+ )
@classmethod
def send_receipt(cls, email, year):
+ Contact = apps.get_model('messaging', 'Contact')
+ Funding = apps.get_model('funding', 'Funding')
+ BillingAgreement = apps.get_model('paypal', 'BillingAgreement')
+ payments = []
+
+ try:
+ contact = Contact.objects.get(email=email)
+ except Contact.DoesNotExist:
+ funding = Funding.objects.filter(
+ email=email,
+ payed_at__year=year,
+ notifications=True).order_by('payed_at').first()
+ if funding is None:
+ print('no notifications')
+ return
+ optout = funding.wl_optout_url()
+ else:
+ if contact.level == Level.OPT_OUT:
+ print('opt-out')
+ return
+ optout = contact.wl_optout_url()
+
qs = cls.objects.filter(status='COMPLETED', schedule__email=email, completed_at__year=year).order_by('completed_at')
- if not qs.exists(): return
+ for order in qs:
+ payments.append({
+ 'timestamp': order.completed_at,
+ 'amount': order.get_amount(),
+ })
+
+ for ba in BillingAgreement.objects.filter(schedule__email=email):
+ payments.extend(ba.get_donations(year))
+
+ fundings = Funding.objects.filter(
+ email=email,
+ payed_at__year=year
+ ).order_by('payed_at')
+ for funding in fundings:
+ payments.append({
+ 'timestamp': funding.payed_at,
+ 'amount': funding.amount,
+ })
+
+ if not payments: return
+
+ payments.sort(key=lambda x: x['timestamp'])
+
ctx = {
"email": email,
"year": year,
"next_year": year + 1,
- "total": qs.aggregate(s=models.Sum('schedule__amount'))['s'],
- "orders": qs,
+ "total": sum(x['amount'] for x in payments),
+ "payments": payments,
+ "optout": optout,
}
temp = tempfile.NamedTemporaryFile(prefix='receipt-', suffix='.pdf', delete=False)
temp.close()
})
message = EmailMessage(
- f'Odlicz od podatku swoje darowizny przekazane dla Wolnych Lektur',
+ 'Odlicz darowiznę na Wolne Lektury od podatku',
template.loader.render_to_string('club/receipt_email.txt', ctx),
settings.CONTACT_EMAIL, [email]
)
class PayUNotification(payu_models.Notification):
order = models.ForeignKey(PayUOrder, models.CASCADE, related_name='notification_set')
-
-