Browse Source

[wip] Reminder for unpaid bills/invoices

Alexandre Aubin 7 years ago
parent
commit
ea0a159ecc

+ 40 - 0
coin/billing/management/commands/send_reminders_for_unpaid_bills.py

@@ -0,0 +1,40 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+# Standard python libs
+import logging
+
+# Django specific imports
+from argparse import RawTextHelpFormatter
+from django.core.management.base import BaseCommand, CommandError
+
+# Coin specific imports
+from coin.billing.models import Invoice
+
+
+class Command(BaseCommand):
+
+    help = """
+Send a reminder to members for invoices which are due and not paid since a few
+weeks.
+"""
+
+    def create_parser(self, *args, **kwargs):
+        parser = super(Command, self).create_parser(*args, **kwargs)
+        parser.formatter_class = RawTextHelpFormatter
+        return parser
+
+    def add_arguments(self, parser):
+        pass
+
+    def handle(self, *args, **options):
+
+        invoices = Invoice.objects.filter(status="open")
+
+        for invoice in invoices:
+
+            if not invoice.reminder_needed():
+                continue
+
+            invoice.send_reminder(auto=True)
+

+ 71 - 2
coin/billing/models.py

@@ -9,6 +9,7 @@ from decimal import Decimal
 
 from django.conf import settings
 from django.db import models, transaction
+from django.utils import timezone
 from django.utils.encoding import python_2_unicode_compatible
 
 
@@ -16,7 +17,7 @@ from coin.offers.models import OfferSubscription
 from coin.members.models import Member
 from coin.html2pdf import render_as_pdf
 from coin.utils import private_files_storage, start_of_month, end_of_month, \
-                       postgresql_regexp
+                       postgresql_regexp, send_templated_email
 from coin.isp_database.context_processors import branding
 
 accounting_log = logging.getLogger("coin.billing")
@@ -106,6 +107,7 @@ class InvoiceQuerySet(models.QuerySet):
         return self.filter(number__regex=postgresql_regexp(
             InvoiceNumber.RE_INVOICE_NUMBER))
 
+
 class Invoice(models.Model):
 
     INVOICES_STATUS_CHOICES = (
@@ -144,6 +146,10 @@ class Invoice(models.Model):
                                       default=0,
                                       verbose_name='montant payé')
 
+    date_last_reminder_email = models.DateTimeField(null=True, blank=True,
+                        verbose_name="Date du dernier email de relance envoyé")
+
+
     def save(self, *args, **kwargs):
 
         # First save to get a PK
@@ -245,6 +251,69 @@ class Invoice(models.Model):
     def __unicode__(self):
         return '#%s %0.2f€ %s' % (self.number, self.amount(), self.date_due)
 
+    def reminder_needed(self):
+
+        # If there's no member, there's nobody to be reminded
+        if self.member is None:
+            return False
+
+        # If bill is close or not validated yet, nope
+        if self.status != 'open' or not self.validated:
+            return False
+
+        from dateutil.relativedelta import relativedelta
+
+        # If bill is not at least one month old, nope
+        if self.date_due >= timezone.now()+relativedelta(weeks=-4):
+            return False
+
+        # If a reminder has been recently sent, nope
+        if (self.date_last_reminder_email
+            and (self.date_last_reminder_email
+                 >= timezone.now() + relativedelta(weeks=-3))):
+            return False
+
+        return True
+
+    def send_reminder(self, auto=False):
+        """ Envoie un courrier pour rappeler à un abonné qu'une facture est
+        en attente de paiement
+
+        :param bill: id of the bill to remind
+        :param auto: is it an auto email? (changes slightly template content)
+        """
+
+        if not self.reminder_needed():
+            return False
+
+        accounting_log.info("Sending reminder email to %s to pay invoice %s"
+                           % (str(self.member), str(self.number)))
+
+        from coin.isp_database.models import ISPInfo
+
+        isp_info = ISPInfo.objects.first()
+        kwargs = {}
+        # Il peut ne pas y avir d'ISPInfo, ou bien pas d'administrative_email
+        if isp_info and isp_info.administrative_email:
+            kwargs['from_email'] = isp_info.administrative_email
+
+        # Si le dernier courriel de relance a été envoyé il y a moins de trois
+        # semaines, n'envoi pas un nouveau courriel
+        send_templated_email(
+            to=self.member.email,
+            subject_template='billing/emails/reminder_for_unpaid_bill.txt',
+            body_template='billing/emails/reminder_for_unpaid_bill.html',
+            context={'member': self.member, 'branding': isp_info,
+                     'membership_info_url': settings.MEMBER_MEMBERSHIP_INFO_URL,
+                     'today': datetime.date.today,
+                     'auto_sent': auto},
+            **kwargs)
+
+        # Sauvegarde en base la date du dernier envoi de mail de relance
+        self.date_last_call_for_membership_fees_email = timezone.now()
+        self.save()
+        return True
+
     class Meta:
         verbose_name = 'facture'
 
@@ -330,7 +399,7 @@ class Payment(models.Model):
 
         # If this payment is related to a member, update the accounting for
         # this member
-        if self.member != None:
+        if self.member is not None:
             update_accounting_for_member(self.member)
 
     def amount_not_allocated(self):