mirror of
https://github.com/YunoHost-Apps/ihatemoney_ynh.git
synced 2024-09-03 19:26:15 +02:00
Split bills function and basic template.
This commit is contained in:
parent
757e86baa1
commit
cb13fbb782
5 changed files with 95 additions and 9 deletions
|
@ -45,10 +45,57 @@ class Project(db.Model):
|
||||||
|
|
||||||
for person in self.members:
|
for person in self.members:
|
||||||
balance = should_receive[person] - should_pay[person]
|
balance = should_receive[person] - should_pay[person]
|
||||||
balances[person.id] = round(balance, 2)
|
balances[person] = round(balance, 2)
|
||||||
|
|
||||||
return balances
|
return balances
|
||||||
|
|
||||||
|
def settle_bill(self):
|
||||||
|
"""Return a list of transactions that could be made to settle the bill"""
|
||||||
|
balances = self.balance
|
||||||
|
credits, debts = list(), list()
|
||||||
|
transactions = list()
|
||||||
|
# Create lists of credits and debts
|
||||||
|
for person in balances.keys():
|
||||||
|
if balances[person] > 0:
|
||||||
|
credits.append({"person": person, "balance": balances[person]})
|
||||||
|
elif balances[person] < 0:
|
||||||
|
debts.append({"person": person, "balance": -balances[person]})
|
||||||
|
# Try and find exact matches
|
||||||
|
for credit in credits:
|
||||||
|
match = self.exactmatch(credit["balance"], debts)
|
||||||
|
if match:
|
||||||
|
for m in match:
|
||||||
|
transactions.append({"ower": m["person"], "payer": credit["person"], "amount": m["balance"]})
|
||||||
|
debts.remove(m)
|
||||||
|
credits.remove(credit)
|
||||||
|
# Split any remaining debts & credits
|
||||||
|
while credits and debts:
|
||||||
|
if credits[0]["balance"] > debts[0]["balance"]:
|
||||||
|
transactions.append({"ower": debts[0]["person"], "payer": credits[0]["person"], "amount": debts[0]["balance"]})
|
||||||
|
credits[0]["balance"] = credits[0]["balance"] - debts[0]["balance"]
|
||||||
|
del debts[0]
|
||||||
|
else:
|
||||||
|
transactions.append({"ower": debts[0]["person"], "payer": credits[0]["person"], "amount": credits[0]["balance"]})
|
||||||
|
debts[0]["balance"] = debts[0]["balance"] - credits[0]["balance"]
|
||||||
|
del credits[0]
|
||||||
|
return transactions
|
||||||
|
|
||||||
|
def exactmatch(self, credit, debts):
|
||||||
|
"""Recursively try and find subsets of 'debts' whose sum is equal to credit"""
|
||||||
|
if not debts:
|
||||||
|
return []
|
||||||
|
if debts[0]["balance"] > credit:
|
||||||
|
return self.exactmatch(credit, debts[1:])
|
||||||
|
elif debts[0]["balance"] == credit:
|
||||||
|
return [debts[0]]
|
||||||
|
else:
|
||||||
|
match = self.exactmatch(credit-debts[0]["balance"], debts[1:])
|
||||||
|
if match:
|
||||||
|
match.append(debts[0])
|
||||||
|
else:
|
||||||
|
match = self.exactmatch(credit, debts[1:])
|
||||||
|
return match
|
||||||
|
|
||||||
def has_bills(self):
|
def has_bills(self):
|
||||||
"""return if the project do have bills or not"""
|
"""return if the project do have bills or not"""
|
||||||
return self.get_bills().count() > 0
|
return self.get_bills().count() > 0
|
||||||
|
|
|
@ -54,7 +54,10 @@
|
||||||
<h3><a class="logo" href="{{ url_for(".home") }}">#! money?</a></h3>
|
<h3><a class="logo" href="{{ url_for(".home") }}">#! money?</a></h3>
|
||||||
{% if g.project %}
|
{% if g.project %}
|
||||||
<ul class="menu">
|
<ul class="menu">
|
||||||
|
{% block navbar %}
|
||||||
<li class="active"><a href="{{ url_for(".list_bills") }}">{{ _("Bills") }}</a></li>
|
<li class="active"><a href="{{ url_for(".list_bills") }}">{{ _("Bills") }}</a></li>
|
||||||
|
<li><a href="{{ url_for(".settle_bill") }}">{{ _("Settle") }}</a></li>
|
||||||
|
{% endblock %}
|
||||||
</ul>
|
</ul>
|
||||||
<ul class="nav secondary-nav">
|
<ul class="nav secondary-nav">
|
||||||
<li class="menu">
|
<li class="menu">
|
||||||
|
|
|
@ -76,15 +76,15 @@
|
||||||
<div id="table_overflow">
|
<div id="table_overflow">
|
||||||
<table class="balance">
|
<table class="balance">
|
||||||
{% set balance = g.project.balance %}
|
{% 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] != 0 %}
|
||||||
<tr id="bal-member-{{ member.id }}" action={% if member.activated %}delete{% else %}reactivate{% endif %}>
|
<tr id="bal-member-{{ member }}" action={% if member.activated %}delete{% else %}reactivate{% endif %}>
|
||||||
<td class="balance-name">{{ member.name }}</td>
|
<td class="balance-name">{{ member.name }}</td>
|
||||||
<td class="balance-value {% if balance[member.id] > 0 %}positive{% elif balance[member.id] < 0 %}negative{% endif %}">
|
<td class="balance-value {% if balance[member] > 0 %}positive{% elif balance[member] < 0 %}negative{% endif %}">
|
||||||
{% if balance[member.id] > 0 %}+{% endif %}{{ balance[member.id] }}
|
{% if balance[member] > 0 %}+{% endif %}{{ balance[member] }}
|
||||||
</td>
|
</td>
|
||||||
{% if member.activated %}
|
{% if member.activated %}
|
||||||
<td class="action delete">
|
<td class="action delete">
|
||||||
<form action="{{ url_for(".remove_member", member_id=member.id) }}" method="POST">
|
<form action="{{ url_for(".remove_member", member_id=member) }}" method="POST">
|
||||||
<button type="submit">{{ _("delete") }}</button></form></td>
|
<button type="submit">{{ _("delete") }}</button></form></td>
|
||||||
{% else %}
|
{% else %}
|
||||||
<td class="action reactivate">
|
<td class="action reactivate">
|
||||||
|
|
35
budget/templates/settle_bill.html
Normal file
35
budget/templates/settle_bill.html
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
{% extends "layout.html" %}
|
||||||
|
|
||||||
|
{% block head %}
|
||||||
|
<script src="{{ url_for("static", filename="js/jquery-ui.js") }}"></script>
|
||||||
|
{% if g.lang != "en" %}
|
||||||
|
<script src="{{ url_for("static", filename="js/i18n/jquery.ui.datepicker-%s.js" % g.lang ) }}"></script>
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
||||||
|
{% block js %}
|
||||||
|
$('#cancel-form').click(function(){location.href={{ url_for(".list_bills") }};});
|
||||||
|
$.datepicker.setDefaults({'dateFormat': 'yy-mm-dd'});
|
||||||
|
$(".datepicker").datepicker($.datepicker.regional['{{ g.lang }}']);
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
{% block navbar %}
|
||||||
|
<li><a href="{{ url_for(".list_bills") }}">{{ _("Bills") }}</a></li>
|
||||||
|
<li class="active"><a href="{{ url_for(".settle_bill") }}">{{ _("Settle") }}</a></li>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<table id="bill_table" class="split_bills common-table zebra-striped">
|
||||||
|
<thead><tr><th>{{ _("Who pays?") }}</th><th>{{ _("To whom?") }}</th><th>{{ _("How much?") }}</th></tr></thead>
|
||||||
|
<tbody>
|
||||||
|
{% for bill in bills %}
|
||||||
|
<tr class="{{ loop.cycle("odd", "even") }}" owers={{bill.owers|join(',','id')}} payer={{bill.payer.id}}>
|
||||||
|
<td>{{ bill.ower }}</td>
|
||||||
|
<td>{{ bill.payer }}</td>
|
||||||
|
<td>{{ "%0.2f"|format(bill.amount) }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
{% endblock %}
|
|
@ -382,10 +382,11 @@ def change_lang(lang):
|
||||||
return redirect(request.headers.get('Referer') or url_for('.home'))
|
return redirect(request.headers.get('Referer') or url_for('.home'))
|
||||||
|
|
||||||
|
|
||||||
@main.route("/<project_id>/compute")
|
@main.route("/<project_id>/settle_bill")
|
||||||
def compute_bills():
|
def settle_bill():
|
||||||
"""Compute the sum each one have to pay to each other and display it"""
|
"""Compute the sum each one have to pay to each other and display it"""
|
||||||
return render_template("compute_bills.html")
|
bills = g.project.settle_bill()
|
||||||
|
return render_template("settle_bill.html", bills=bills)
|
||||||
|
|
||||||
|
|
||||||
@main.route("/<project_id>/archives/create")
|
@main.route("/<project_id>/archives/create")
|
||||||
|
|
Loading…
Reference in a new issue