mirror of
https://github.com/spiral-project/ihatemoney.git
synced 2025-05-05 12:41:49 +02:00
Add a statistics tab
This commit is contained in:
parent
ec4a099f18
commit
269b37894a
8 changed files with 154 additions and 0 deletions
|
@ -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
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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>
|
||||
|
|
35
ihatemoney/templates/statistics.html
Normal file
35
ihatemoney/templates/statistics.html
Normal file
|
@ -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 %}
|
|
@ -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")
|
||||
|
|
Binary file not shown.
|
@ -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"
|
||||
|
|
|
@ -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())
|
||||
|
|
Loading…
Reference in a new issue