Coverage for website/moneybirdsynchronization/signals.py: 62.30%
96 statements
« prev ^ index » next coverage.py v7.6.7, created at 2025-08-14 10:31 +0000
« prev ^ index » next coverage.py v7.6.7, created at 2025-08-14 10:31 +0000
1import logging
3from django.contrib.auth import get_user_model
4from django.db.models.signals import post_delete, post_save
6from members.models import Member, Profile
7from moneybirdsynchronization import services
8from moneybirdsynchronization.administration import Administration
9from moneybirdsynchronization.emails import send_sync_error
10from moneybirdsynchronization.models import MoneybirdExternalInvoice
11from payments.models import BankAccount
12from payments.signals import processed_batch
13from utils.models.signals import suspendingreceiver
15logger = logging.getLogger(__name__)
16User = get_user_model()
19@suspendingreceiver(post_save, sender="members.Profile")
20def post_profile_save(sender, instance, **kwargs):
21 """Update the contact in Moneybird when the profile is saved."""
22 updated_fields = kwargs.get("update_fields", None)
23 if updated_fields is not None and not any( 23 ↛ 34line 23 didn't jump to line 34 because the condition on line 23 was never true
24 field in updated_fields
25 for field in [
26 "is_minimized",
27 "address_street",
28 "address_street2",
29 "address_postal_code",
30 "address_city",
31 "address_country",
32 ]
33 ):
34 return
36 if not instance.user.first_name or not instance.user.last_name:
37 return
39 if hasattr(instance.user, "moneybird_contact"):
40 instance.user.moneybird_contact.needs_synchronization = True
41 instance.user.moneybird_contact.save()
44@suspendingreceiver(post_delete, sender="members.Profile")
45def post_profile_delete(sender, instance, **kwargs):
46 """Delete the contact in Moneybird when the profile is deleted."""
47 if hasattr(instance.user, "moneybird_contact"):
48 instance.user.moneybird_contact.needs_synchronization = True
49 instance.user.moneybird_contact.save()
52@suspendingreceiver(
53 post_save,
54 sender=User,
55)
56def post_user_save(sender, instance, **kwargs):
57 """Update the contact in Moneybird when the user is saved."""
58 try:
59 instance.profile
60 except Profile.DoesNotExist:
61 return
63 updated_fields = kwargs.get("update_fields", None)
64 if updated_fields is not None and not any(
65 field in updated_fields for field in ["first_name", "last_name", "email"]
66 ):
67 # Only update the contact when the name is changed
68 return
70 if hasattr(instance, "moneybird_contact"):
71 instance.moneybird_contact.needs_synchronization = True
72 instance.moneybird_contact.save()
75@suspendingreceiver(post_delete, sender=User)
76def post_user_delete(sender, instance, **kwargs):
77 """Delete the contact in Moneybird when the user is deleted."""
78 if hasattr(instance, "moneybird_contact"):
79 instance.moneybird_contact.needs_synchronization = True
80 instance.moneybird_contact.save()
83@suspendingreceiver(post_save, sender=BankAccount)
84def post_bank_account_save(sender, instance, **kwargs):
85 """Update the contact in Moneybird when the bank account is saved."""
86 updated_fields = kwargs.get("update_fields", None)
87 if updated_fields is not None and not any( 87 ↛ 92line 87 didn't jump to line 92 because the condition on line 87 was never true
88 field in updated_fields
89 for field in ["owner", "iban", "bic", "initials", "last_name"]
90 ):
91 # Only update the contact when the bank account is changed
92 return
94 member = Member.objects.get(pk=instance.owner.pk)
95 if hasattr(member, "moneybird_contact"): 95 ↛ 96line 95 didn't jump to line 96 because the condition on line 95 was never true
96 member.moneybird_contact.needs_synchronization = True
97 member.moneybird_contact.save()
100@suspendingreceiver(post_delete, sender=BankAccount)
101def post_bank_account_delete(sender, instance, **kwargs):
102 """Update the contact in Moneybird when the bank account is deleted."""
103 member = Member.objects.get(pk=instance.owner.pk)
104 if hasattr(member, "moneybird_contact"):
105 member.moneybird_contact.needs_synchronization = True
106 member.moneybird_contact.save()
109@suspendingreceiver(
110 post_save,
111 sender="registrations.Renewal",
112 dispatch_uid="mark_renewal_invoice_outdated",
113)
114@suspendingreceiver(
115 post_save,
116 sender="registrations.Registration",
117 dispatch_uid="mark_registration_invoice_outdated",
118)
119@suspendingreceiver(
120 post_save, sender="pizzas.FoodOrder", dispatch_uid="mark_foodorder_invoice_outdated"
121)
122@suspendingreceiver(
123 post_save, sender="sales.Order", dispatch_uid="mark_salesorder_invoice_outdated"
124)
125@suspendingreceiver(
126 post_save,
127 sender="events.EventRegistration",
128 dispatch_uid="mark_eventregistration_invoice_outdated",
129)
130def mark_invoice_outdated(sender, instance, **kwargs):
131 """Mark the invoice as outdated if it exists, so that it will be resynchronized."""
132 invoice = MoneybirdExternalInvoice.get_for_object(instance)
133 if invoice and not invoice.needs_synchronization: 133 ↛ 134line 133 didn't jump to line 134 because the condition on line 133 was never true
134 invoice.needs_synchronization = True
135 invoice.save()
138@suspendingreceiver(post_delete, sender="registrations.Renewal")
139@suspendingreceiver(post_delete, sender="registrations.Registration")
140@suspendingreceiver(post_delete, sender="pizzas.FoodOrder")
141@suspendingreceiver(post_delete, sender="events.EventRegistration")
142@suspendingreceiver(post_delete, sender="sales.Order")
143def post_renewal_delete(sender, instance, **kwargs):
144 """When a payable is deleted, other than during data minimisation, delete the invoice.
146 When objects are deleted for data minimisation, we don't want to delete the
147 Moneybird invoice as well, because we are obligated to store those for 7 years.
148 """
149 # During data minimisation, deletions are marked with a flag. This is currently
150 # the case only for registrations and renewals. The other payables are not deleted
151 # for data minimisation, but bulk-updated to remove personally identifiable
152 # information. Those bulk updates do not trigger post_save signals.
153 if getattr(instance, "__deleting_for_dataminimisation", False):
154 return
156 invoice = MoneybirdExternalInvoice.get_for_object(instance)
157 if invoice:
158 invoice.needs_deletion = True
159 invoice.save()
162@suspendingreceiver(post_delete, sender="moneybirdsynchronization.MoneybirdPayment")
163def post_payment_delete(sender, instance, **kwargs):
164 try:
165 services.delete_moneybird_payment(instance)
166 except Administration.Error as e:
167 logger.exception("Moneybird synchronization error: %s", e)
168 send_sync_error(e, instance)
171@suspendingreceiver(
172 processed_batch,
173)
174def post_processed_batch(sender, instance, **kwargs):
175 try:
176 services.process_thalia_pay_batch(instance)
177 except Administration.Error as e:
178 logger.exception("Moneybird synchronization error: %s", e)
179 send_sync_error(e, instance)