aboutsummaryrefslogtreecommitdiff
path: root/budget
diff options
context:
space:
mode:
Diffstat (limited to 'budget')
-rw-r--r--budget/models.py9
-rw-r--r--budget/templates/list_bills.html6
-rw-r--r--budget/templates/settle_bills.html6
-rw-r--r--budget/tests.py40
-rw-r--r--budget/web.py4
5 files changed, 54 insertions, 11 deletions
diff --git a/budget/models.py b/budget/models.py
index 852b3e1..3aac120 100644
--- a/budget/models.py
+++ b/budget/models.py
@@ -37,7 +37,7 @@ class Project(db.Model):
# for each person
for person in self.members:
# get the list of bills he has to pay
- bills = Bill.query.filter(Bill.owers.contains(person))
+ bills = Bill.query.options(orm.subqueryload(Bill.owers)).filter(Bill.owers.contains(person))
for bill in bills.all():
if person != bill.payer:
share = bill.pay_each() * person.weight
@@ -61,9 +61,9 @@ class Project(db.Model):
credits, debts, transactions = [],[],[]
# Create lists of credits and debts
for person in self.members:
- if balance[person.id] > 0:
+ if round(balance[person.id], 2) > 0:
credits.append({"person": person, "balance": balance[person.id]})
- elif balance[person.id] < 0:
+ elif round(balance[person.id], 2) < 0:
debts.append({"person": person, "balance": -balance[person.id]})
# Try and find exact matches
for credit in credits:
@@ -111,7 +111,8 @@ class Project(db.Model):
.filter(Bill.payer_id == Person.id)\
.filter(Person.project_id == Project.id)\
.filter(Project.id == self.id)\
- .order_by(Bill.date.desc())
+ .order_by(Bill.date.desc())\
+ .order_by(Bill.id.desc())
def remove_member(self, member_id):
"""Remove a member from the project.
diff --git a/budget/templates/list_bills.html b/budget/templates/list_bills.html
index f081334..72b9032 100644
--- a/budget/templates/list_bills.html
+++ b/budget/templates/list_bills.html
@@ -65,7 +65,7 @@
<div id="table_overflow">
<table class="balance table">
{% set balance = g.project.balance %}
- {% for member in g.project.members | sort(attribute='name') if member.activated or balance[member.id] != 0 %}
+ {% for member in g.project.members | sort(attribute='name') if member.activated or balance[member.id]|round(2) != 0 %}
<tr id="bal-member-{{ member.id }}" action={% if member.activated %}delete{% else %}reactivate{% endif %}>
<td class="balance-name">{{ member.name }}
<span class="light{% if not g.project.uses_weights %} extra-info{% endif %}">(x{{ member.weight|minimal_round(1) }})</span>
@@ -82,8 +82,8 @@
<form class="action reactivate" action="{{ url_for(".reactivate", member_id=member.id) }}" method="POST">
<button type="submit">{{ _("reactivate") }}</button></form></td>
{% endif %}
- <td class="balance-value {% if balance[member.id] > 0 %}positive{% elif balance[member.id] < 0 %}negative{% endif %}">
- {% if balance[member.id] > 0 %}+{% endif %}{{ "%.2f" | format(balance[member.id]) }}
+ <td class="balance-value {% if balance[member.id]|round(2) > 0 %}positive{% elif balance[member.id]|round(2) < 0 %}negative{% endif %}">
+ {% if balance[member.id]|round(2) > 0 %}+{% endif %}{{ "%.2f" | format(balance[member.id]) }}
</td>
</tr>
{% endfor %}
diff --git a/budget/templates/settle_bills.html b/budget/templates/settle_bills.html
index 4066b16..16c60b3 100644
--- a/budget/templates/settle_bills.html
+++ b/budget/templates/settle_bills.html
@@ -11,11 +11,11 @@
<div id="table_overflow">
<table class="balance table">
{% set balance = g.project.balance %}
- {% for member in g.project.members | sort(attribute='name') if member.activated or balance[member.id] != 0 %}
+ {% for member in g.project.members | sort(attribute='name') if member.activated or balance[member.id]|round(2) != 0 %}
<tr id="bal-member-{{ member.id }}" action={% if member.activated %}delete{% else %}reactivate{% endif %}>
<td class="balance-name">{{ member.name }}</td>
- <td class="balance-value {% if balance[member.id] > 0 %}positive{% elif balance[member.id] < 0 %}negative{% endif %}">
- {% if balance[member.id] > 0 %}+{% endif %}{{ "%.2f" | format(balance[member.id]) }}
+ <td class="balance-value {% if balance[member.id]|round(2) > 0 %}positive{% elif balance[member.id]|round(2) < 0 %}negative{% endif %}">
+ {% if balance[member.id]|round(2) > 0 %}+{% endif %}{{ "%.2f" | format(balance[member.id]) }}
</td>
</tr>
{% endfor %}
diff --git a/budget/tests.py b/budget/tests.py
index 8c1f973..0e40825 100644
--- a/budget/tests.py
+++ b/budget/tests.py
@@ -580,6 +580,46 @@ class BudgetTestCase(TestCase):
self.assertEqual(a, balance[m.id])
return
+ def test_settle_zero(self):
+ self.post_project("raclette")
+
+ # add members
+ self.app.post("/raclette/members/add", data={'name': 'alexis'})
+ self.app.post("/raclette/members/add", data={'name': 'fred'})
+ self.app.post("/raclette/members/add", data={'name': 'tata'})
+
+ # create bills
+ self.app.post("/raclette/add", data={
+ 'date': '2016-12-31',
+ 'what': u'fromage à raclette',
+ 'payer': 1,
+ 'payed_for': [1, 2, 3],
+ 'amount': '10.0',
+ })
+
+ self.app.post("/raclette/add", data={
+ 'date': '2016-12-31',
+ 'what': u'red wine',
+ 'payer': 2,
+ 'payed_for': [1, 3],
+ 'amount': '20',
+ })
+
+ self.app.post("/raclette/add", data={
+ 'date': '2017-01-01',
+ 'what': u'refund',
+ 'payer': 3,
+ 'payed_for': [2],
+ 'amount': '13.33',
+ })
+ project = models.Project.query.get('raclette')
+ transactions = project.get_transactions_to_settle_bill()
+ members = defaultdict(int)
+ # There should not be any zero-amount transfer after rounding
+ for t in transactions:
+ rounded_amount = round(t['amount'], 2)
+ self.assertNotEqual(0.0, rounded_amount,
+ msg='%f is equal to zero after rounding' % t['amount'])
class APITestCase(TestCase):
diff --git a/budget/web.py b/budget/web.py
index 63fbe4d..87aef26 100644
--- a/budget/web.py
+++ b/budget/web.py
@@ -15,6 +15,7 @@ from flask.ext.mail import Mail, Message
from flask.ext.babel import get_locale, gettext as _
from smtplib import SMTPRecipientsRefused
import werkzeug
+from sqlalchemy import orm
# local modules
from models import db, Project, Person, Bill
@@ -277,7 +278,8 @@ def list_bills():
# set the last selected payer as default choice if exists
if 'last_selected_payer' in session:
bill_form.payer.data = session['last_selected_payer']
- bills = g.project.get_bills()
+ # Preload the "owers" relationship for all bills
+ bills = g.project.get_bills().options(orm.subqueryload(Bill.owers))
return render_template("list_bills.html",
bills=bills, member_form=MemberForm(g.project),