Skip to content

Commit 6da8444

Browse files
committed
Create transaction fee match rules based on CPF for memberships
1 parent 8cd54a3 commit 6da8444

File tree

3 files changed

+137
-2
lines changed

3 files changed

+137
-2
lines changed

thebook/bookkeeping/importers/csv.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ def run(self, start_date=None, end_date=None, ignored_memos=None):
140140
# We don't process USD recurring here
141141
continue
142142

143-
current_membership_fees = (75, 85, 110)
143+
current_membership_fees = (75, 85, 110, 120)
144144
recurring_fee_type = (
145145
"Mensalidade"
146146
if transaction_amount in current_membership_fees
@@ -166,7 +166,7 @@ def run(self, start_date=None, end_date=None, ignored_memos=None):
166166
Transaction(
167167
reference=f"{transaction_reference}T",
168168
date=transaction_date,
169-
description=f"Taxa Intermediação - {recurring_fee_type} - {transaction_name}",
169+
description=f"Taxa PayPal - {recurring_fee_type} - {transaction_name}",
170170
amount=transaction_tax,
171171
bank_account=self.bank_account,
172172
category=self.categories[BANK_FEES],

thebook/members/models.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import calendar
22
import datetime
33
import itertools
4+
import re
45

56
from dateutil.relativedelta import relativedelta
67
from localflavor.br.models import BRCPFField
@@ -104,6 +105,10 @@ def __str__(self):
104105
active_status = "Active" if self.active else "Inactive"
105106
return f"{active_status} membership of {self.member.name}"
106107

108+
def save(self, **kwargs):
109+
self.create_receivable_fee_transaction_match_rules()
110+
super().save(**kwargs)
111+
107112
def create_next_receivable_fee(self, commit=True):
108113
if not self.active:
109114
return None
@@ -158,6 +163,23 @@ def _next_period(current_date, payment_interval):
158163

159164
return receivable_fee
160165

166+
def create_receivable_fee_transaction_match_rules(self):
167+
member_cpf = self.member.cpf
168+
member_cpf_only_digits = re.sub(r"[^\d]+", "", member_cpf)
169+
170+
if not member_cpf_only_digits:
171+
return
172+
173+
pattern_1 = f".*{member_cpf_only_digits}.*"
174+
ReceivableFeeTransactionMatchRule.objects.get_or_create(
175+
membership=self, pattern=pattern_1
176+
)
177+
178+
pattern_2 = f".*{member_cpf_only_digits[:3]}.{member_cpf_only_digits[3:6]}.{member_cpf_only_digits[6:9]}-{member_cpf_only_digits[9:]}.*"
179+
ReceivableFeeTransactionMatchRule.objects.get_or_create(
180+
membership=self, pattern=pattern_2
181+
)
182+
161183
@property
162184
def next_membership_fee_payment_date(self):
163185
allowed_months = sorted(

thebook/members/tests/models/test_membership.py

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
Member,
1414
Membership,
1515
ReceivableFee,
16+
ReceivableFeeTransactionMatchRule,
1617
)
1718

1819

@@ -149,3 +150,115 @@ def test_when_membership_is_activated_add_first_receivable_fee(db):
149150

150151
assert membership.receivable_fees.exists()
151152
assert membership.receivable_fees.count() == 1
153+
154+
155+
@pytest.mark.parametrize(
156+
"cpf,expected_patterns",
157+
[
158+
("123.456.789-10", (".*123.456.789-10.*", ".*12345678910.*")),
159+
("12345678910", (".*123.456.789-10.*", ".*12345678910.*")),
160+
],
161+
)
162+
def test_when_membership_is_created_add_receivable_fee_match_rule_with_their_cpf(
163+
db, cpf, expected_patterns
164+
):
165+
"""Some transactions contains the CPF of the member, so this is the simplest rule to match a receivable fee payment"""
166+
member = baker.make(Member, cpf=cpf)
167+
168+
membership = Membership.objects.create(
169+
member=member,
170+
start_date=date(2025, 8, 11),
171+
membership_fee_amount=Decimal("85"),
172+
payment_interval=FeeIntervals.MONTHLY,
173+
active=False,
174+
)
175+
176+
assert (
177+
ReceivableFeeTransactionMatchRule.objects.filter(membership=membership).count()
178+
== 2
179+
)
180+
for expected_pattern in expected_patterns:
181+
assert ReceivableFeeTransactionMatchRule.objects.filter(
182+
membership=membership, pattern=expected_pattern
183+
).exists()
184+
185+
186+
def test_when_membership_is_created_do_not_add_receivable_fee_match_rule_with_member_without_cpf(
187+
db,
188+
):
189+
"""Some transactions contains the CPF of the member, so this is the simplest rule to match a receivable fee payment"""
190+
member = baker.make(Member, cpf="")
191+
192+
membership = Membership.objects.create(
193+
member=member,
194+
start_date=date(2025, 8, 11),
195+
membership_fee_amount=Decimal("85"),
196+
payment_interval=FeeIntervals.MONTHLY,
197+
active=False,
198+
)
199+
200+
assert (
201+
ReceivableFeeTransactionMatchRule.objects.filter(membership=membership).count()
202+
== 0
203+
)
204+
205+
206+
def test_when_membership_do_not_add_duplicated_receivable_fee_match_rule_if_already_exists(
207+
db,
208+
):
209+
"""Some transactions contains the CPF of the member, so this is the simplest rule to match a receivable fee payment"""
210+
member = baker.make(Member, cpf="123.456.789-10")
211+
212+
membership = Membership.objects.create(
213+
member=member,
214+
start_date=date(2025, 8, 11),
215+
membership_fee_amount=Decimal("85"),
216+
payment_interval=FeeIntervals.MONTHLY,
217+
active=False,
218+
)
219+
assert (
220+
ReceivableFeeTransactionMatchRule.objects.filter(membership=membership).count()
221+
== 2
222+
)
223+
224+
membership.membership_fee_amount = Decimal("110")
225+
membership.save()
226+
227+
assert (
228+
ReceivableFeeTransactionMatchRule.objects.filter(membership=membership).count()
229+
== 2
230+
)
231+
232+
233+
def test_keep_existing_receivable_fee_match_rule_that_exist(db):
234+
"""Some transactions contains the CPF of the member, so this is the simplest rule to match a receivable fee payment"""
235+
member = baker.make(Member, cpf="")
236+
membership = Membership.objects.create(
237+
member=member,
238+
start_date=date(2025, 8, 11),
239+
membership_fee_amount=Decimal("85"),
240+
payment_interval=FeeIntervals.MONTHLY,
241+
active=False,
242+
)
243+
ReceivableFeeTransactionMatchRule.objects.create(
244+
membership=membership, pattern=".*Member name.*"
245+
)
246+
247+
member.cpf = "123.456.789-10"
248+
member.save()
249+
250+
membership.save()
251+
252+
assert (
253+
ReceivableFeeTransactionMatchRule.objects.filter(membership=membership).count()
254+
== 3
255+
)
256+
assert ReceivableFeeTransactionMatchRule.objects.filter(
257+
membership=membership, pattern=".*Member name.*"
258+
).exists()
259+
assert ReceivableFeeTransactionMatchRule.objects.filter(
260+
membership=membership, pattern=".*12345678910.*"
261+
).exists()
262+
assert ReceivableFeeTransactionMatchRule.objects.filter(
263+
membership=membership, pattern=".*123.456.789-10.*"
264+
).exists()

0 commit comments

Comments
 (0)