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

1import logging 

2 

3from django.contrib.auth import get_user_model 

4from django.db.models.signals import post_delete, post_save 

5 

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 

14 

15logger = logging.getLogger(__name__) 

16User = get_user_model() 

17 

18 

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 

35 

36 if not instance.user.first_name or not instance.user.last_name: 

37 return 

38 

39 if hasattr(instance.user, "moneybird_contact"): 

40 instance.user.moneybird_contact.needs_synchronization = True 

41 instance.user.moneybird_contact.save() 

42 

43 

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() 

50 

51 

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 

62 

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 

69 

70 if hasattr(instance, "moneybird_contact"): 

71 instance.moneybird_contact.needs_synchronization = True 

72 instance.moneybird_contact.save() 

73 

74 

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() 

81 

82 

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 

93 

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() 

98 

99 

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() 

107 

108 

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() 

136 

137 

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. 

145 

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 

155 

156 invoice = MoneybirdExternalInvoice.get_for_object(instance) 

157 if invoice: 

158 invoice.needs_deletion = True 

159 invoice.save() 

160 

161 

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) 

169 

170 

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)