We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Do not throw away bank accounts that may not be deleted during member data minimisation
Closes #2539 

### Summary
Moves bank account minimization to the payments app. This will keep bank accounts 13 months after last usage for collecting a Thalia Pay batch, as is required.


### How to test
1. Do data minimisation
2. Bank accounts will only be removed 13 months after last usage

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `website/payments/services.py`
Content:
```
1 """The services defined by the payments package."""
2 import datetime
3 from typing import Union
4 
5 from django.conf import settings
6 from django.db.models import QuerySet, Q, Sum, Model
7 from django.urls import reverse
8 from django.utils import timezone
9 from django.utils.translation import gettext_lazy as _
10 
11 from members.models import Member
12 from utils.snippets import send_email
13 from .exceptions import PaymentError
14 from .models import Payment, BankAccount, PaymentUser
15 from .payables import payables, Payable
16 
17 
18 def create_payment(
19     model_payable: Union[Model, Payable],
20     processed_by: Member,
21     pay_type: Union[Payment.CASH, Payment.CARD, Payment.WIRE, Payment.TPAY],
22 ) -> Payment:
23     """Create a new payment from a payable object.
24 
25     :param model_payable: Payable or Model object
26     :param processed_by: PaymentUser that processed this payment
27     :param pay_type: Payment type
28     :return: Payment object
29     """
30     if pay_type not in (Payment.CASH, Payment.CARD, Payment.WIRE, Payment.TPAY):
31         raise PaymentError("Invalid payment type")
32 
33     if isinstance(model_payable, Payable):
34         payable = model_payable
35     else:
36         payable = payables.get_payable(model_payable)
37 
38     payer = (
39         PaymentUser.objects.get(pk=payable.payment_payer.pk)
40         if payable.payment_payer
41         else None
42     )
43 
44     if not (
45         (payer and payer == processed_by and pay_type == Payment.TPAY)
46         or (payable.can_manage_payment(processed_by) and pay_type != Payment.TPAY)
47     ):
48         raise PaymentError(
49             _("User processing payment does not have the right permissions")
50         )
51 
52     if payable.payment_amount == 0:
53         raise PaymentError(_("Payment amount 0 is not accepted"))
54 
55     if pay_type == Payment.TPAY and not payer.tpay_enabled:
56         raise PaymentError(_("This user does not have Thalia Pay enabled"))
57 
58     if not payable.paying_allowed:
59         raise PaymentError(_("Payment restricted"))
60 
61     if payable.payment is not None:
62         payable.payment.amount = payable.payment_amount
63         payable.payment.notes = payable.payment_notes
64         payable.payment.topic = payable.payment_topic
65         payable.payment.paid_by = payer
66         payable.payment.processed_by = processed_by
67         payable.payment.type = pay_type
68         payable.payment.save()
69     else:
70         payable.payment = Payment.objects.create(
71             processed_by=processed_by,
72             amount=payable.payment_amount,
73             notes=payable.payment_notes,
74             topic=payable.payment_topic,
75             paid_by=payer,
76             type=pay_type,
77         )
78     return payable.payment
79 
80 
81 def delete_payment(model: Model, member: Member = None, ignore_change_window=False):
82     """Remove a payment from a payable object.
83 
84     :param model: Payable or Model object
85     :param member: member deleting the payment
86     :param ignore_change_window: ignore the payment change window
87     :return:
88     """
89     payable = payables.get_payable(model)
90 
91     if member and not payable.can_manage_payment(member):
92         raise PaymentError(
93             _("User deleting payment does not have the right permissions.")
94         )
95 
96     payment = payable.payment
97     if (
98         payment.created_at
99         < timezone.now() - timezone.timedelta(seconds=settings.PAYMENT_CHANGE_WINDOW)
100         and not ignore_change_window
101     ):
102         raise PaymentError(_("This payment cannot be deleted anymore."))
103     if payment.batch and payment.batch.processed:
104         raise PaymentError(
105             _("This payment has already been processed and hence cannot be deleted.")
106         )
107 
108     payable.payment = None
109     payable.model.save()
110     payment.delete()
111 
112 
113 def update_last_used(queryset: QuerySet, date: datetime.date = None) -> int:
114     """Update the last used field of a BankAccount queryset.
115 
116     :param queryset: Queryset of BankAccounts
117     :param date: date to set last_used to
118     :return: number of affected rows
119     """
120     if not date:
121         date = timezone.now().date()
122 
123     result = queryset.filter(
124         (Q(valid_from__gte=timezone.now()) & Q(valid_until__lt=timezone.now()))
125         | Q(valid_until=None)
126     ).update(last_used=date)
127     return result
128 
129 
130 def revoke_old_mandates() -> int:
131     """Revoke all mandates that have not been used for 36 months or more.
132 
133     :return: number of affected rows
134     """
135     return BankAccount.objects.filter(
136         last_used__lte=(timezone.now() - timezone.timedelta(days=36 * 30))
137     ).update(valid_until=timezone.now().date())
138 
139 
140 def process_batch(batch):
141     """Process a Thalia Pay batch.
142 
143     :param batch: the batch to be processed
144     :return:
145     """
146     batch.processed = True
147 
148     payments = batch.payments_set.select_related("paid_by")
149     for payment in payments:
150         bank_account = payment.paid_by.bank_accounts.last()
151         bank_account.last_used = batch.withdrawal_date
152         bank_account.save()
153 
154     batch.save()
155 
156     send_tpay_batch_processing_emails(batch)
157 
158 
159 def derive_next_mandate_no(member) -> str:
160     accounts = (
161         BankAccount.objects.filter(owner=PaymentUser.objects.get(pk=member.pk))
162         .exclude(mandate_no=None)
163         .filter(mandate_no__regex=BankAccount.MANDATE_NO_DEFAULT_REGEX)
164     )
165     new_mandate_no = 1 + max(
166         [int(account.mandate_no.split("-")[1]) for account in accounts], default=0
167     )
168     return f"{member.pk}-{new_mandate_no}"
169 
170 
171 def send_tpay_batch_processing_emails(batch):
172     """Send withdrawal notice emails to all members in a batch."""
173     member_payments = batch.payments_set.values("paid_by").annotate(total=Sum("amount"))
174     for member_row in member_payments:
175         member = PaymentUser.objects.get(pk=member_row["paid_by"])
176         total_amount = member_row["total"]
177 
178         send_email(
179             member.email,
180             _("Thalia Pay withdrawal notice"),
181             "payments/email/tpay_withdrawal_notice_mail.txt",
182             {
183                 "name": member.get_full_name(),
184                 "batch": batch,
185                 "bank_account": member.bank_accounts.filter(
186                     mandate_no__isnull=False
187                 ).last(),
188                 "creditor_id": settings.SEPA_CREDITOR_ID,
189                 "payments": batch.payments_set.filter(paid_by=member),
190                 "total_amount": total_amount,
191                 "payments_url": (
192                     settings.BASE_URL
193                     + reverse(
194                         "payments:payment-list",
195                     )
196                 ),
197             },
198         )
199     return len(member_payments)
200 
201 
202 def execute_data_minimisation(dry_run=False):
203     """Anonymizes payments older than 7 years."""
204     # Sometimes years are 366 days of course, but better delete 1 or 2 days early than late
205     payment_deletion_period = timezone.now().date() - timezone.timedelta(days=(365 * 7))
206     bankaccount_deletion_period = timezone.now() - datetime.timedelta(days=(31 * 13))
207 
208     queryset_payments = Payment.objects.filter(
209         created_at__lte=payment_deletion_period
210     ).exclude(paid_by__isnull=True)
211 
212     queryset_bankaccounts = BankAccount.objects.filter(
213         Q(owner__paid_payment_set__type=Payment.TPAY),
214         Q(owner__paid_payment_set__batch__isnull=True)
215         | Q(owner__paid_payment_set__batch__processed=False)
216         | Q(
217             owner__paid_payment_set__batch__processing_date__gt=bankaccount_deletion_period
218         ),
219     )
220 
221     if not dry_run:
222         queryset_payments.update(paid_by=None, processed_by=None)
223         queryset_bankaccounts.delete()
224     return queryset_payments
225
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/website/payments/services.py b/website/payments/services.py
--- a/website/payments/services.py
+++ b/website/payments/services.py
@@ -209,12 +209,24 @@
         created_at__lte=payment_deletion_period
     ).exclude(paid_by__isnull=True)
 
-    queryset_bankaccounts = BankAccount.objects.filter(
-        Q(owner__paid_payment_set__type=Payment.TPAY),
-        Q(owner__paid_payment_set__batch__isnull=True)
-        | Q(owner__paid_payment_set__batch__processed=False)
+    # Delete bank accounts that are not valid anymore, and have not been used in the last 13 months
+    # (13 months is the required time we need to keep the mandates for)
+    queryset_bankaccounts = BankAccount.objects.all()
+    queryset_bankaccounts = queryset_bankaccounts.filter(
+        valid_until__lt=timezone.now()
+    )  # Keep valid bank accounts
+    queryset_bankaccounts = queryset_bankaccounts.exclude(  # Also keep bank accounts that
+        Q(
+            owner__paid_payment_set__type=Payment.TPAY
+        ),  # are used for Thalia Pay payments, AND
+        Q(
+            owner__paid_payment_set__batch__isnull=True
+        )  # have a payment that is in no batch, OR
         | Q(
-            owner__paid_payment_set__batch__processing_date__gt=bankaccount_deletion_period
+            owner__paid_payment_set__batch__processed=False
+        )  # have an unprocessed batch, OR
+        | Q(
+            owner__paid_payment_set__batch__processing_date__gt=bankaccount_deletion_period  # or have a processed batch that is not older than 13 months
         ),
     )

{"golden_diff": "diff --git a/website/payments/services.py b/website/payments/services.py\n--- a/website/payments/services.py\n+++ b/website/payments/services.py\n@@ -209,12 +209,24 @@\n         created_at__lte=payment_deletion_period\n     ).exclude(paid_by__isnull=True)\n \n-    queryset_bankaccounts = BankAccount.objects.filter(\n-        Q(owner__paid_payment_set__type=Payment.TPAY),\n-        Q(owner__paid_payment_set__batch__isnull=True)\n-        | Q(owner__paid_payment_set__batch__processed=False)\n+    # Delete bank accounts that are not valid anymore, and have not been used in the last 13 months\n+    # (13 months is the required time we need to keep the mandates for)\n+    queryset_bankaccounts = BankAccount.objects.all()\n+    queryset_bankaccounts = queryset_bankaccounts.filter(\n+        valid_until__lt=timezone.now()\n+    )  # Keep valid bank accounts\n+    queryset_bankaccounts = queryset_bankaccounts.exclude(  # Also keep bank accounts that\n+        Q(\n+            owner__paid_payment_set__type=Payment.TPAY\n+        ),  # are used for Thalia Pay payments, AND\n+        Q(\n+            owner__paid_payment_set__batch__isnull=True\n+        )  # have a payment that is in no batch, OR\n         | Q(\n-            owner__paid_payment_set__batch__processing_date__gt=bankaccount_deletion_period\n+            owner__paid_payment_set__batch__processed=False\n+        )  # have an unprocessed batch, OR\n+        | Q(\n+            owner__paid_payment_set__batch__processing_date__gt=bankaccount_deletion_period  # or have a processed batch that is not older than 13 months\n         ),\n     )\n", "issue": "Do not throw away bank accounts that may not be deleted during member data minimisation\nCloses #2539 \r\n\r\n### Summary\r\nMoves bank account minimization to the payments app. This will keep bank accounts 13 months after last usage for collecting a Thalia Pay batch, as is required.\r\n\r\n\r\n### How to test\r\n1. Do data minimisation\r\n2. Bank accounts will only be removed 13 months after last usage\n", "before_files": [{"content": "\"\"\"The services defined by the payments package.\"\"\"\nimport datetime\nfrom typing import Union\n\nfrom django.conf import settings\nfrom django.db.models import QuerySet, Q, Sum, Model\nfrom django.urls import reverse\nfrom django.utils import timezone\nfrom django.utils.translation import gettext_lazy as _\n\nfrom members.models import Member\nfrom utils.snippets import send_email\nfrom .exceptions import PaymentError\nfrom .models import Payment, BankAccount, PaymentUser\nfrom .payables import payables, Payable\n\n\ndef create_payment(\n    model_payable: Union[Model, Payable],\n    processed_by: Member,\n    pay_type: Union[Payment.CASH, Payment.CARD, Payment.WIRE, Payment.TPAY],\n) -> Payment:\n    \"\"\"Create a new payment from a payable object.\n\n    :param model_payable: Payable or Model object\n    :param processed_by: PaymentUser that processed this payment\n    :param pay_type: Payment type\n    :return: Payment object\n    \"\"\"\n    if pay_type not in (Payment.CASH, Payment.CARD, Payment.WIRE, Payment.TPAY):\n        raise PaymentError(\"Invalid payment type\")\n\n    if isinstance(model_payable, Payable):\n        payable = model_payable\n    else:\n        payable = payables.get_payable(model_payable)\n\n    payer = (\n        PaymentUser.objects.get(pk=payable.payment_payer.pk)\n        if payable.payment_payer\n        else None\n    )\n\n    if not (\n        (payer and payer == processed_by and pay_type == Payment.TPAY)\n        or (payable.can_manage_payment(processed_by) and pay_type != Payment.TPAY)\n    ):\n        raise PaymentError(\n            _(\"User processing payment does not have the right permissions\")\n        )\n\n    if payable.payment_amount == 0:\n        raise PaymentError(_(\"Payment amount 0 is not accepted\"))\n\n    if pay_type == Payment.TPAY and not payer.tpay_enabled:\n        raise PaymentError(_(\"This user does not have Thalia Pay enabled\"))\n\n    if not payable.paying_allowed:\n        raise PaymentError(_(\"Payment restricted\"))\n\n    if payable.payment is not None:\n        payable.payment.amount = payable.payment_amount\n        payable.payment.notes = payable.payment_notes\n        payable.payment.topic = payable.payment_topic\n        payable.payment.paid_by = payer\n        payable.payment.processed_by = processed_by\n        payable.payment.type = pay_type\n        payable.payment.save()\n    else:\n        payable.payment = Payment.objects.create(\n            processed_by=processed_by,\n            amount=payable.payment_amount,\n            notes=payable.payment_notes,\n            topic=payable.payment_topic,\n            paid_by=payer,\n            type=pay_type,\n        )\n    return payable.payment\n\n\ndef delete_payment(model: Model, member: Member = None, ignore_change_window=False):\n    \"\"\"Remove a payment from a payable object.\n\n    :param model: Payable or Model object\n    :param member: member deleting the payment\n    :param ignore_change_window: ignore the payment change window\n    :return:\n    \"\"\"\n    payable = payables.get_payable(model)\n\n    if member and not payable.can_manage_payment(member):\n        raise PaymentError(\n            _(\"User deleting payment does not have the right permissions.\")\n        )\n\n    payment = payable.payment\n    if (\n        payment.created_at\n        < timezone.now() - timezone.timedelta(seconds=settings.PAYMENT_CHANGE_WINDOW)\n        and not ignore_change_window\n    ):\n        raise PaymentError(_(\"This payment cannot be deleted anymore.\"))\n    if payment.batch and payment.batch.processed:\n        raise PaymentError(\n            _(\"This payment has already been processed and hence cannot be deleted.\")\n        )\n\n    payable.payment = None\n    payable.model.save()\n    payment.delete()\n\n\ndef update_last_used(queryset: QuerySet, date: datetime.date = None) -> int:\n    \"\"\"Update the last used field of a BankAccount queryset.\n\n    :param queryset: Queryset of BankAccounts\n    :param date: date to set last_used to\n    :return: number of affected rows\n    \"\"\"\n    if not date:\n        date = timezone.now().date()\n\n    result = queryset.filter(\n        (Q(valid_from__gte=timezone.now()) & Q(valid_until__lt=timezone.now()))\n        | Q(valid_until=None)\n    ).update(last_used=date)\n    return result\n\n\ndef revoke_old_mandates() -> int:\n    \"\"\"Revoke all mandates that have not been used for 36 months or more.\n\n    :return: number of affected rows\n    \"\"\"\n    return BankAccount.objects.filter(\n        last_used__lte=(timezone.now() - timezone.timedelta(days=36 * 30))\n    ).update(valid_until=timezone.now().date())\n\n\ndef process_batch(batch):\n    \"\"\"Process a Thalia Pay batch.\n\n    :param batch: the batch to be processed\n    :return:\n    \"\"\"\n    batch.processed = True\n\n    payments = batch.payments_set.select_related(\"paid_by\")\n    for payment in payments:\n        bank_account = payment.paid_by.bank_accounts.last()\n        bank_account.last_used = batch.withdrawal_date\n        bank_account.save()\n\n    batch.save()\n\n    send_tpay_batch_processing_emails(batch)\n\n\ndef derive_next_mandate_no(member) -> str:\n    accounts = (\n        BankAccount.objects.filter(owner=PaymentUser.objects.get(pk=member.pk))\n        .exclude(mandate_no=None)\n        .filter(mandate_no__regex=BankAccount.MANDATE_NO_DEFAULT_REGEX)\n    )\n    new_mandate_no = 1 + max(\n        [int(account.mandate_no.split(\"-\")[1]) for account in accounts], default=0\n    )\n    return f\"{member.pk}-{new_mandate_no}\"\n\n\ndef send_tpay_batch_processing_emails(batch):\n    \"\"\"Send withdrawal notice emails to all members in a batch.\"\"\"\n    member_payments = batch.payments_set.values(\"paid_by\").annotate(total=Sum(\"amount\"))\n    for member_row in member_payments:\n        member = PaymentUser.objects.get(pk=member_row[\"paid_by\"])\n        total_amount = member_row[\"total\"]\n\n        send_email(\n            member.email,\n            _(\"Thalia Pay withdrawal notice\"),\n            \"payments/email/tpay_withdrawal_notice_mail.txt\",\n            {\n                \"name\": member.get_full_name(),\n                \"batch\": batch,\n                \"bank_account\": member.bank_accounts.filter(\n                    mandate_no__isnull=False\n                ).last(),\n                \"creditor_id\": settings.SEPA_CREDITOR_ID,\n                \"payments\": batch.payments_set.filter(paid_by=member),\n                \"total_amount\": total_amount,\n                \"payments_url\": (\n                    settings.BASE_URL\n                    + reverse(\n                        \"payments:payment-list\",\n                    )\n                ),\n            },\n        )\n    return len(member_payments)\n\n\ndef execute_data_minimisation(dry_run=False):\n    \"\"\"Anonymizes payments older than 7 years.\"\"\"\n    # Sometimes years are 366 days of course, but better delete 1 or 2 days early than late\n    payment_deletion_period = timezone.now().date() - timezone.timedelta(days=(365 * 7))\n    bankaccount_deletion_period = timezone.now() - datetime.timedelta(days=(31 * 13))\n\n    queryset_payments = Payment.objects.filter(\n        created_at__lte=payment_deletion_period\n    ).exclude(paid_by__isnull=True)\n\n    queryset_bankaccounts = BankAccount.objects.filter(\n        Q(owner__paid_payment_set__type=Payment.TPAY),\n        Q(owner__paid_payment_set__batch__isnull=True)\n        | Q(owner__paid_payment_set__batch__processed=False)\n        | Q(\n            owner__paid_payment_set__batch__processing_date__gt=bankaccount_deletion_period\n        ),\n    )\n\n    if not dry_run:\n        queryset_payments.update(paid_by=None, processed_by=None)\n        queryset_bankaccounts.delete()\n    return queryset_payments\n", "path": "website/payments/services.py"}], "after_files": [{"content": "\"\"\"The services defined by the payments package.\"\"\"\nimport datetime\nfrom typing import Union\n\nfrom django.conf import settings\nfrom django.db.models import QuerySet, Q, Sum, Model\nfrom django.urls import reverse\nfrom django.utils import timezone\nfrom django.utils.translation import gettext_lazy as _\n\nfrom members.models import Member\nfrom utils.snippets import send_email\nfrom .exceptions import PaymentError\nfrom .models import Payment, BankAccount, PaymentUser\nfrom .payables import payables, Payable\n\n\ndef create_payment(\n    model_payable: Union[Model, Payable],\n    processed_by: Member,\n    pay_type: Union[Payment.CASH, Payment.CARD, Payment.WIRE, Payment.TPAY],\n) -> Payment:\n    \"\"\"Create a new payment from a payable object.\n\n    :param model_payable: Payable or Model object\n    :param processed_by: PaymentUser that processed this payment\n    :param pay_type: Payment type\n    :return: Payment object\n    \"\"\"\n    if pay_type not in (Payment.CASH, Payment.CARD, Payment.WIRE, Payment.TPAY):\n        raise PaymentError(\"Invalid payment type\")\n\n    if isinstance(model_payable, Payable):\n        payable = model_payable\n    else:\n        payable = payables.get_payable(model_payable)\n\n    payer = (\n        PaymentUser.objects.get(pk=payable.payment_payer.pk)\n        if payable.payment_payer\n        else None\n    )\n\n    if not (\n        (payer and payer == processed_by and pay_type == Payment.TPAY)\n        or (payable.can_manage_payment(processed_by) and pay_type != Payment.TPAY)\n    ):\n        raise PaymentError(\n            _(\"User processing payment does not have the right permissions\")\n        )\n\n    if payable.payment_amount == 0:\n        raise PaymentError(_(\"Payment amount 0 is not accepted\"))\n\n    if pay_type == Payment.TPAY and not payer.tpay_enabled:\n        raise PaymentError(_(\"This user does not have Thalia Pay enabled\"))\n\n    if not payable.paying_allowed:\n        raise PaymentError(_(\"Payment restricted\"))\n\n    if payable.payment is not None:\n        payable.payment.amount = payable.payment_amount\n        payable.payment.notes = payable.payment_notes\n        payable.payment.topic = payable.payment_topic\n        payable.payment.paid_by = payer\n        payable.payment.processed_by = processed_by\n        payable.payment.type = pay_type\n        payable.payment.save()\n    else:\n        payable.payment = Payment.objects.create(\n            processed_by=processed_by,\n            amount=payable.payment_amount,\n            notes=payable.payment_notes,\n            topic=payable.payment_topic,\n            paid_by=payer,\n            type=pay_type,\n        )\n    return payable.payment\n\n\ndef delete_payment(model: Model, member: Member = None, ignore_change_window=False):\n    \"\"\"Remove a payment from a payable object.\n\n    :param model: Payable or Model object\n    :param member: member deleting the payment\n    :param ignore_change_window: ignore the payment change window\n    :return:\n    \"\"\"\n    payable = payables.get_payable(model)\n\n    if member and not payable.can_manage_payment(member):\n        raise PaymentError(\n            _(\"User deleting payment does not have the right permissions.\")\n        )\n\n    payment = payable.payment\n    if (\n        payment.created_at\n        < timezone.now() - timezone.timedelta(seconds=settings.PAYMENT_CHANGE_WINDOW)\n        and not ignore_change_window\n    ):\n        raise PaymentError(_(\"This payment cannot be deleted anymore.\"))\n    if payment.batch and payment.batch.processed:\n        raise PaymentError(\n            _(\"This payment has already been processed and hence cannot be deleted.\")\n        )\n\n    payable.payment = None\n    payable.model.save()\n    payment.delete()\n\n\ndef update_last_used(queryset: QuerySet, date: datetime.date = None) -> int:\n    \"\"\"Update the last used field of a BankAccount queryset.\n\n    :param queryset: Queryset of BankAccounts\n    :param date: date to set last_used to\n    :return: number of affected rows\n    \"\"\"\n    if not date:\n        date = timezone.now().date()\n\n    result = queryset.filter(\n        (Q(valid_from__gte=timezone.now()) & Q(valid_until__lt=timezone.now()))\n        | Q(valid_until=None)\n    ).update(last_used=date)\n    return result\n\n\ndef revoke_old_mandates() -> int:\n    \"\"\"Revoke all mandates that have not been used for 36 months or more.\n\n    :return: number of affected rows\n    \"\"\"\n    return BankAccount.objects.filter(\n        last_used__lte=(timezone.now() - timezone.timedelta(days=36 * 30))\n    ).update(valid_until=timezone.now().date())\n\n\ndef process_batch(batch):\n    \"\"\"Process a Thalia Pay batch.\n\n    :param batch: the batch to be processed\n    :return:\n    \"\"\"\n    batch.processed = True\n\n    payments = batch.payments_set.select_related(\"paid_by\")\n    for payment in payments:\n        bank_account = payment.paid_by.bank_accounts.last()\n        bank_account.last_used = batch.withdrawal_date\n        bank_account.save()\n\n    batch.save()\n\n    send_tpay_batch_processing_emails(batch)\n\n\ndef derive_next_mandate_no(member) -> str:\n    accounts = (\n        BankAccount.objects.filter(owner=PaymentUser.objects.get(pk=member.pk))\n        .exclude(mandate_no=None)\n        .filter(mandate_no__regex=BankAccount.MANDATE_NO_DEFAULT_REGEX)\n    )\n    new_mandate_no = 1 + max(\n        [int(account.mandate_no.split(\"-\")[1]) for account in accounts], default=0\n    )\n    return f\"{member.pk}-{new_mandate_no}\"\n\n\ndef send_tpay_batch_processing_emails(batch):\n    \"\"\"Send withdrawal notice emails to all members in a batch.\"\"\"\n    member_payments = batch.payments_set.values(\"paid_by\").annotate(total=Sum(\"amount\"))\n    for member_row in member_payments:\n        member = PaymentUser.objects.get(pk=member_row[\"paid_by\"])\n        total_amount = member_row[\"total\"]\n\n        send_email(\n            member.email,\n            _(\"Thalia Pay withdrawal notice\"),\n            \"payments/email/tpay_withdrawal_notice_mail.txt\",\n            {\n                \"name\": member.get_full_name(),\n                \"batch\": batch,\n                \"bank_account\": member.bank_accounts.filter(\n                    mandate_no__isnull=False\n                ).last(),\n                \"creditor_id\": settings.SEPA_CREDITOR_ID,\n                \"payments\": batch.payments_set.filter(paid_by=member),\n                \"total_amount\": total_amount,\n                \"payments_url\": (\n                    settings.BASE_URL\n                    + reverse(\n                        \"payments:payment-list\",\n                    )\n                ),\n            },\n        )\n    return len(member_payments)\n\n\ndef execute_data_minimisation(dry_run=False):\n    \"\"\"Anonymizes payments older than 7 years.\"\"\"\n    # Sometimes years are 366 days of course, but better delete 1 or 2 days early than late\n    payment_deletion_period = timezone.now().date() - timezone.timedelta(days=(365 * 7))\n    bankaccount_deletion_period = timezone.now() - datetime.timedelta(days=(31 * 13))\n\n    queryset_payments = Payment.objects.filter(\n        created_at__lte=payment_deletion_period\n    ).exclude(paid_by__isnull=True)\n\n    # Delete bank accounts that are not valid anymore, and have not been used in the last 13 months\n    # (13 months is the required time we need to keep the mandates for)\n    queryset_bankaccounts = BankAccount.objects.all()\n    queryset_bankaccounts = queryset_bankaccounts.filter(\n        valid_until__lt=timezone.now()\n    )  # Keep valid bank accounts\n    queryset_bankaccounts = queryset_bankaccounts.exclude(  # Also keep bank accounts that\n        Q(\n            owner__paid_payment_set__type=Payment.TPAY\n        ),  # are used for Thalia Pay payments, AND\n        Q(\n            owner__paid_payment_set__batch__isnull=True\n        )  # have a payment that is in no batch, OR\n        | Q(\n            owner__paid_payment_set__batch__processed=False\n        )  # have an unprocessed batch, OR\n        | Q(\n            owner__paid_payment_set__batch__processing_date__gt=bankaccount_deletion_period  # or have a processed batch that is not older than 13 months\n        ),\n    )\n\n    if not dry_run:\n        queryset_payments.update(paid_by=None, processed_by=None)\n        queryset_bankaccounts.delete()\n    return queryset_payments\n", "path": "website/payments/services.py"}]}