diff options
| -rw-r--r-- | CHANGELOG.rst | 4 | ||||
| -rw-r--r-- | ihatemoney/models.py | 10 | ||||
| -rw-r--r-- | ihatemoney/templates/layout.html | 1 | ||||
| -rw-r--r-- | ihatemoney/templates/statistics.html | 35 | ||||
| -rw-r--r-- | ihatemoney/tests/tests.py | 62 | ||||
| -rw-r--r-- | ihatemoney/translations/fr/LC_MESSAGES/messages.mo | bin | 8629 -> 8789 bytes | |||
| -rw-r--r-- | ihatemoney/translations/fr/LC_MESSAGES/messages.po | 20 | ||||
| -rw-r--r-- | ihatemoney/web.py | 22 |
8 files changed, 154 insertions, 0 deletions
diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 33e1de5..94b802c 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -12,6 +12,10 @@ This document describes changes between each past release. - **BREAKING CHANGE** Turn the WSGI file into a python module, renamed from budget/ihatemoney.wsgi to budget/wsgi.py. Please update your Apache configuration! - Changed the recommended gunicorn configuration to use the wsgi module as an entrypoint +### Added + +- Add a statistics tab (#257) +- Add python3.6 support (#259) ### Removed diff --git a/ihatemoney/models.py b/ihatemoney/models.py index 6c71a57..cd896f3 100644 --- a/ihatemoney/models.py +++ b/ihatemoney/models.py @@ -144,6 +144,16 @@ class Project(db.Model): .order_by(Bill.date.desc())\ .order_by(Bill.id.desc()) + def get_member_bills(self, member_id): + """Return the list of bills related to a specific member""" + return Bill.query.join(Person, Project)\ + .filter(Bill.payer_id == Person.id)\ + .filter(Person.project_id == Project.id)\ + .filter(Person.id == member_id)\ + .filter(Project.id == self.id)\ + .order_by(Bill.date.desc())\ + .order_by(Bill.id.desc()) + def get_pretty_bills(self, export_format="json"): """Return a list of project's bills with pretty formatting""" bills = self.get_bills() diff --git a/ihatemoney/templates/layout.html b/ihatemoney/templates/layout.html index 76ae890..36f01f8 100644 --- a/ihatemoney/templates/layout.html +++ b/ihatemoney/templates/layout.html @@ -40,6 +40,7 @@ {% block navbar %} <li class="nav-item{% if current_view == 'list_bills' %} active{% endif %}"><a class="nav-link" href="{{ url_for(".list_bills") }}">{{ _("Bills") }}</a></li> <li class="nav-item{% if current_view == 'settle_bill' %} active{% endif %}"><a class="nav-link" href="{{ url_for(".settle_bill") }}">{{ _("Settle") }}</a></li> + <li class="nav-item{% if current_view == 'statistics' %} active{% endif %}"><a class="nav-link" href="{{ url_for(".statistics") }}">{{ _("Statistics") }}</a></li> {% endblock %} {% endif %} </ul> diff --git a/ihatemoney/templates/statistics.html b/ihatemoney/templates/statistics.html new file mode 100644 index 0000000..061c629 --- /dev/null +++ b/ihatemoney/templates/statistics.html @@ -0,0 +1,35 @@ +{% extends "sidebar_table_layout.html" %} + +{% block sidebar %} + <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]|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]|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 %} + </table> + </div> +{% endblock %} + + +{% block content %} + <table id="bill_table" class="split_bills table table-striped"> + <thead><tr><th>{{ _("Who?") }}</th><th>{{ _("Paid") }}</th><th>{{ _("Spent") }}</th><th>{{ _("Balance") }}</th></tr></thead> + <tbody> + {% for member in members %} + <tr class="{{ loop.cycle("odd", "even") }}"> + <td>{{ member.name }}</td> + <td>{{ "%0.2f"|format(paid[member.id]) }}</td> + <td>{{ "%0.2f"|format(spent[member.id]) }}</td> + <td>{{ "%0.2f"|format(balance[member.id]) }}</td> + </tr> + {% endfor %} + </tbody> + </table> + +{% endblock %} diff --git a/ihatemoney/tests/tests.py b/ihatemoney/tests/tests.py index 86f11f3..ac3551c 100644 --- a/ihatemoney/tests/tests.py +++ b/ihatemoney/tests/tests.py @@ -627,6 +627,68 @@ class BudgetTestCase(IhatemoneyTestCase): response = self.client.get("/dashboard") self.assertEqual(response.status_code, 200) + def test_statistics_page(self): + self.post_project("raclette") + response = self.client.get("/raclette/statistics") + self.assertEqual(response.status_code, 200) + + def test_statistics(self): + self.post_project("raclette") + + # add members + self.client.post("/raclette/members/add", data={'name': 'alexis', 'weight': 2}) + self.client.post("/raclette/members/add", data={'name': 'fred'}) + self.client.post("/raclette/members/add", data={'name': 'tata'}) + # Add a member with a balance=0 : + self.client.post("/raclette/members/add", data={'name': 'toto'}) + + # create bills + self.client.post("/raclette/add", data={ + 'date': '2011-08-10', + 'what': 'fromage à raclette', + 'payer': 1, + 'payed_for': [1, 2, 3], + 'amount': '10.0', + }) + + self.client.post("/raclette/add", data={ + 'date': '2011-08-10', + 'what': 'red wine', + 'payer': 2, + 'payed_for': [1], + 'amount': '20', + }) + + self.client.post("/raclette/add", data={ + 'date': '2011-08-10', + 'what': 'delicatessen', + 'payer': 1, + 'payed_for': [1, 2], + 'amount': '10', + }) + + response = self.client.get("/raclette/statistics") + self.assertIn("<td>alexis</td>\n " + + "<td>20.00</td>\n " + + "<td>31.67</td>\n " + + "<td>-11.67</td>\n", + response.data.decode('utf-8')) + self.assertIn("<td>fred</td>\n " + + "<td>20.00</td>\n " + + "<td>5.83</td>\n " + + "<td>14.17</td>\n", + response.data.decode('utf-8')) + self.assertIn("<td>tata</td>\n " + + "<td>0.00</td>\n " + + "<td>2.50</td>\n " + + "<td>-2.50</td>\n", + response.data.decode('utf-8')) + self.assertIn("<td>toto</td>\n " + + "<td>0.00</td>\n " + + "<td>0.00</td>\n " + + "<td>0.00</td>\n", + response.data.decode('utf-8')) + def test_settle_page(self): self.post_project("raclette") response = self.client.get("/raclette/settle_bills") diff --git a/ihatemoney/translations/fr/LC_MESSAGES/messages.mo b/ihatemoney/translations/fr/LC_MESSAGES/messages.mo Binary files differindex 2f46b71..d6011d5 100644 --- a/ihatemoney/translations/fr/LC_MESSAGES/messages.mo +++ b/ihatemoney/translations/fr/LC_MESSAGES/messages.mo diff --git a/ihatemoney/translations/fr/LC_MESSAGES/messages.po b/ihatemoney/translations/fr/LC_MESSAGES/messages.po index 65c295d..e8b9793 100644 --- a/ihatemoney/translations/fr/LC_MESSAGES/messages.po +++ b/ihatemoney/translations/fr/LC_MESSAGES/messages.po @@ -400,6 +400,10 @@ msgstr "Factures" msgid "Settle" msgstr "Remboursements" +#: templates/layout.html:50 +msgid "Statistics" +msgstr "Statistiques" + #: templates/layout.html:53 msgid "options" msgstr "options" @@ -529,3 +533,19 @@ msgstr "Qui doit payer ?" #: templates/settle_bills.html:31 msgid "To whom?" msgstr "Pour qui ?" + +#: templates/statistics.html:22 +msgid "Who?" +msgstr "Qui ?" + +#: templates/statistics.html:22 +msgid "Paid" +msgstr "A payé" + +#: templates/statistics.html:22 +msgid "Spent" +msgstr "A dépensé" + +#: templates/statistics.html:22 +msgid "Balance" +msgstr "Solde" diff --git a/ihatemoney/web.py b/ihatemoney/web.py index cc2eeac..82e1591 100644 --- a/ihatemoney/web.py +++ b/ihatemoney/web.py @@ -507,6 +507,28 @@ def settle_bill(): ) +@main.route("/<project_id>/statistics") +def statistics(): + """Compute what each member has paid and spent and display it""" + members = g.project.active_members + balance = g.project.balance + paid = {} + spent = {} + for member in members: + paid[member.id] = sum([bill.amount + for bill in g.project.get_member_bills(member.id).all()]) + spent[member.id] = sum([bill.pay_each() * member.weight + for bill in g.project.get_bills().all() if member in bill.owers]) + return render_template( + "statistics.html", + members=members, + balance=balance, + paid=paid, + spent=spent, + current_view='statistics', + ) + + @main.route("/dashboard") def dashboard(): return render_template("dashboard.html", projects=Project.query.all()) |
