Repository: svthalia/concrexit

Issue:
Add time-out to registrations and renewals
### What?
Right now a renewal can be open for an indefinite amount of time. This has lead to a lot of people forgetting to pay for their registration or renewal and them staying open.

I think it would be nice to after a month or two, if the renewal has been accepted, but not paid, either send a notification to the board, or to the person in question, so that they can either pay or remove the renewal. Also maybe an auto-removal after a year.


Discussion:

Resolving patch:
diff --git a/website/registrations/emails.py b/website/registrations/emails.py
--- a/website/registrations/emails.py
+++ b/website/registrations/emails.py
@@ -180,3 +180,20 @@ def send_references_information_message(entry: Registration | Renewal) -> None:
             ),
         },
     )
+
+
+def send_reminder_open_registration(registration: Registration) -> None:
+    """Send a notification to the board that a registration has been open for more than one month.
+
+    :param registration: the registration entry
+    """
+    send_email(
+        to=[settings.BOARD_NOTIFICATION_ADDRESS],
+        subject="Open registration for more than one month",
+        txt_template="registrations/email/reminder_open_registration.txt",
+        html_template="registrations/email/reminder_open_registration.html",
+        context={
+            "name": registration.get_full_name(),
+            "created_at": registration.created_at,
+        },
+    )
diff --git a/website/registrations/tasks.py b/website/registrations/tasks.py
--- a/website/registrations/tasks.py
+++ b/website/registrations/tasks.py
@@ -1,8 +1,38 @@
+from datetime import timedelta
+
+from django.utils import timezone
+
 from celery import shared_task
 
-from registrations import services
+from registrations import emails
+
+from . import services
+from .models import Registration, Renewal
 
 
 @shared_task
 def minimise_registrations():
     services.execute_data_minimisation()
+
+
+@shared_task
+def notify_old_entries():
+    # delete entries w updated_at 1 month ago and created_at 3m ago
+    # notify (and update updated_at) entries w updated_at 1 month ago
+
+    Registration.objects.filter(
+        updated_at__lt=timezone.now() - timedelta(days=30),
+        created_at__lt=timezone.now() - timedelta(days=90),
+    ).delete()
+    Renewal.objects.filter(
+        updated_at__lt=timezone.now() - timedelta(days=30),
+        created_at__lt=timezone.now() - timedelta(days=90),
+    ).delete()
+
+    for registration in Registration.objects.filter(
+        updated_at__lt=timezone.now() - timedelta(days=30)
+    ):
+        # send email
+        emails.send_reminder_open_registration(registration)
+        registration.updated_at = timezone.now()
+        registration.save()
diff --git a/website/thaliawebsite/settings.py b/website/thaliawebsite/settings.py
--- a/website/thaliawebsite/settings.py
+++ b/website/thaliawebsite/settings.py
@@ -400,6 +400,10 @@ def show_toolbar(request):
         "task": "registrations.tasks.minimise_registrations",
         "schedule": crontab(minute=0, hour=3, day_of_month=1),
     },
+    "notifyoldentries": {
+        "task": "registrations.tasks.notify_old_entries",
+        "schedule": crontab(minute=0, hour=8, day_of_week=1),
+    },
     "sendscheduledmessages": {
         "task": "pushnotifications.tasks.send_scheduled_messages",
         "schedule": crontab(minute="*/2"),


Test patch:
diff --git a/website/registrations/tests/test_emails.py b/website/registrations/tests/test_emails.py
--- a/website/registrations/tests/test_emails.py
+++ b/website/registrations/tests/test_emails.py
@@ -7,11 +7,15 @@
 from django.template.defaultfilters import floatformat
 from django.test import TestCase
 from django.urls import reverse
-from django.utils import translation
+from django.utils import timezone, translation
+
+from freezegun import freeze_time
 
 from members.models import Member, Profile
+from members.models.membership import Membership
 from registrations import emails
-from registrations.models import Registration, Renewal
+from registrations.models import Entry, Registration, Renewal
+from registrations.tasks import notify_old_entries
 from utils.snippets import send_email
 
 
@@ -214,6 +218,41 @@ def test_send_new_renewal_board_message(self, send_email):
             },
         )
 
+    @mock.patch("registrations.emails.send_email")
+    def test_send_reminder_open_registration(self, send_email):
+        with freeze_time("2024-01-01"):
+            registration = Registration.objects.create(
+                first_name="John",
+                last_name="Doe",
+                email="johndoe@example.com",
+                programme="computingscience",
+                starting_year=2014,
+                address_street="Heyendaalseweg 135",
+                address_street2="",
+                address_postal_code="6525AJ",
+                address_city="Nijmegen",
+                address_country="NL",
+                phone_number="06123456789",
+                birthday=timezone.now().replace(year=1990),
+                length=Entry.MEMBERSHIP_YEAR,
+                membership_type=Membership.MEMBER,
+                status=Entry.STATUS_CONFIRM,
+            )
+
+        with freeze_time("2024-02-10"):
+            notify_old_entries()
+
+        send_email.assert_called_once_with(
+            to=[settings.BOARD_NOTIFICATION_ADDRESS],
+            subject="Open registration for more than one month",
+            txt_template="registrations/email/reminder_open_registration.txt",
+            html_template="registrations/email/reminder_open_registration.html",
+            context={
+                "name": registration.get_full_name(),
+                "created_at": registration.created_at,
+            },
+        )
+
     @mock.patch("registrations.emails.send_email")
     def test_send_references_information_message(self, send_email):
         with self.subTest("Registrations"):