diff --git a/ihatemoney/models.py b/ihatemoney/models.py
index c591b85b6..af11da28d 100644
--- a/ihatemoney/models.py
+++ b/ihatemoney/models.py
@@ -752,6 +752,22 @@ def pay_each_default(self, amount):
else:
return 0
+ @property
+ def involves_deactivated_members(self):
+ """Check whether the bill contains deactivated member.
+ Return:
+ True if it contains deactivated member,
+ False if not.
+ """
+ owers_id = [int(m.id) for m in self.owers]
+ bill_member_id_list = owers_id + [self.payer_id]
+ deactivated_member_number = (
+ Person.query.filter(Person.id.in_(bill_member_id_list))
+ .filter(Person.activated.is_(False))
+ .count()
+ )
+ return deactivated_member_number != 0
+
def __str__(self):
return self.what
diff --git a/ihatemoney/templates/list_bills.html b/ihatemoney/templates/list_bills.html
index 79e252625..7f4350e81 100644
--- a/ihatemoney/templates/list_bills.html
+++ b/ihatemoney/templates/list_bills.html
@@ -148,10 +148,22 @@
{{ _('Add a bill') }}
- {{ _('edit') }}
+ {{ _('edit') }}
{% if bill.external_link %}
{{ _('show') }}
diff --git a/ihatemoney/tests/budget_test.py b/ihatemoney/tests/budget_test.py
index 093d9d17d..e0e56afd4 100644
--- a/ihatemoney/tests/budget_test.py
+++ b/ihatemoney/tests/budget_test.py
@@ -870,6 +870,122 @@ def test_weighted_balance(self):
balance = self.get_project("raclette").balance
assert set(balance.values()) == set([6, -6])
+ def test_edit_bill_with_deactivated_member(self):
+ """
+ Bills involving deactivated members should not allowed to be edited or deleted.
+ """
+ self.post_project("raclette")
+
+ # add two participants
+ self.client.post("/raclette/members/add", data={"name": "zorglub"})
+ self.client.post("/raclette/members/add", data={"name": "fred"})
+
+ members_ids = [m.id for m in self.get_project("raclette").members]
+
+ # create one bill
+ self.client.post(
+ "/raclette/add",
+ data={
+ "date": "2011-08-10",
+ "what": "fromage à raclette",
+ "payer": members_ids[0],
+ "payed_for": members_ids,
+ "amount": "25",
+ },
+ )
+ bill = models.Bill.query.one()
+ self.assertEqual(bill.amount, 25)
+
+ # deactivate one user
+ self.client.post(
+ "/raclette/members/%s/delete" % self.get_project("raclette").members[-1].id
+ )
+ self.assertEqual(len(self.get_project("raclette").members), 2)
+ self.assertEqual(len(self.get_project("raclette").active_members), 1)
+
+ # editing would fail because the bill involves deactivated user
+ self.client.post(
+ f"/raclette/edit/{bill.id}",
+ data={
+ "date": "2011-08-10",
+ "what": "fromage à raclette",
+ "payer": members_ids[0],
+ "payed_for": members_ids,
+ "amount": "10",
+ },
+ )
+ bill = models.Bill.query.one()
+ self.assertNotEqual(bill.amount, 10, "bill edition")
+
+ # reactivate the user
+ self.client.post(
+ "/raclette/members/%s/reactivate"
+ % self.get_project("raclette").members[-1].id
+ )
+ self.assertEqual(len(self.get_project("raclette").active_members), 2)
+
+ # try to edit the bill again. It should succeed
+ self.client.post(
+ f"/raclette/edit/{bill.id}",
+ data={
+ "date": "2011-08-10",
+ "what": "fromage à raclette",
+ "payer": members_ids[0],
+ "payed_for": members_ids,
+ "amount": "10",
+ },
+ )
+ bill = models.Bill.query.one()
+ self.assertEqual(bill.amount, 10, "bill edition")
+
+ def test_delete_bill_with_deactivated_member(self):
+ """
+ Bills involving deactivated members should not allowed to be edited or deleted.
+ """
+ self.post_project("raclette")
+
+ # add two participants
+ self.client.post("/raclette/members/add", data={"name": "zorglub"})
+ self.client.post("/raclette/members/add", data={"name": "fred"})
+
+ members_ids = [m.id for m in self.get_project("raclette").members]
+
+ # create one bill
+ self.client.post(
+ "/raclette/add",
+ data={
+ "date": "2011-08-10",
+ "what": "fromage à raclette",
+ "payer": members_ids[0],
+ "payed_for": members_ids,
+ "amount": "25",
+ },
+ )
+ bill = models.Bill.query.one()
+ self.assertEqual(bill.amount, 25)
+
+ # deactivate one user
+ self.client.post(
+ "/raclette/members/%s/delete" % self.get_project("raclette").members[-1].id
+ )
+ self.assertEqual(len(self.get_project("raclette").active_members), 1)
+
+ # deleting should fail because the bill involves deactivated user
+ response = self.client.get(f"/raclette/delete/{bill.id}")
+ self.assertEqual(response.status_code, 405)
+ self.assertEqual(1, len(models.Bill.query.all()), "bill deletion")
+
+ # reactivate the user
+ self.client.post(
+ "/raclette/members/%s/reactivate"
+ % self.get_project("raclette").members[-1].id
+ )
+ self.assertEqual(len(self.get_project("raclette").active_members), 2)
+
+ # try to delete the bill again. It should succeed
+ self.client.post(f"/raclette/delete/{bill.id}")
+ self.assertEqual(0, len(models.Bill.query.all()), "bill deletion")
+
def test_trimmed_members(self):
self.post_project("raclette")
diff --git a/ihatemoney/web.py b/ihatemoney/web.py
index 0d0bdd201..d3706e408 100644
--- a/ihatemoney/web.py
+++ b/ihatemoney/web.py
@@ -800,6 +800,10 @@ def delete_bill(bill_id):
if not bill:
return redirect(url_for(".list_bills"))
+ # Check if the bill contains deactivated member. If yes, stop deleting.
+ if bill.involves_deactivated_members:
+ return redirect(url_for(".list_bills"))
+
db.session.delete(bill)
db.session.commit()
flash(_("The bill has been deleted"))
@@ -814,6 +818,10 @@ def edit_bill(bill_id):
if not bill:
raise NotFound()
+ # Check if the bill contains deactivated member. If yes, stop editing.
+ if bill.involves_deactivated_members:
+ return redirect(url_for(".list_bills"))
+
form = get_billform_for(g.project, set_default=False)
if request.method == "POST" and form.validate():
|