Fix #434 Use the debts lib to solve settlements.

This commit is contained in:
Alexis M 2019-09-24 19:37:16 +02:00
parent 9fc7fc768e
commit 74c51be5a3
7 changed files with 31 additions and 60 deletions

3
.gitignore vendored
View file

@ -7,3 +7,6 @@ docs/_build/
.tox .tox
dist dist
.cache/ .cache/
build
.vscode
.env

View file

@ -7,6 +7,7 @@ This document describes changes between each past release.
================ ================
- Add support for espanol latino america (es_419) - Add support for espanol latino america (es_419)
- Use the external debts lib to solve settlements (#476)
4.1.3 (2019-09-18) 4.1.3 (2019-09-18)

0
ihatemoney/budget.db Normal file
View file

View file

@ -4,6 +4,7 @@ from datetime import datetime
from flask_sqlalchemy import SQLAlchemy, BaseQuery from flask_sqlalchemy import SQLAlchemy, BaseQuery
from flask import g, current_app from flask import g, current_app
from debts import settle
from sqlalchemy import orm from sqlalchemy import orm
from itsdangerous import (TimedJSONWebSignatureSerializer, URLSafeSerializer, from itsdangerous import (TimedJSONWebSignatureSerializer, URLSafeSerializer,
BadSignature, SignatureExpired) BadSignature, SignatureExpired)
@ -106,46 +107,14 @@ class Project(db.Model):
return pretty_transactions return pretty_transactions
# cache value for better performance # cache value for better performance
balance = self.balance members = {person.id: person for person in self.members}
credits, debts, transactions = [], [], [] settle_plan = settle(self.balance.items()) or []
# Create lists of credits and debts
for person in self.members:
if round(balance[person.id], 2) > 0:
credits.append({"person": person, "balance": balance[person.id]})
elif round(balance[person.id], 2) < 0:
debts.append({"person": person, "balance": -balance[person.id]})
# Try and find exact matches transactions = [{
for credit in credits: 'ower': members[ower_id],
match = self.exactmatch(round(credit["balance"], 2), debts) 'receiver': members[receiver_id],
if match: 'amount': amount
for m in match: } for ower_id, amount, receiver_id in settle_plan]
transactions.append({
"ower": m["person"],
"receiver": 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"],
"receiver": 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"],
"receiver": credits[0]["person"],
"amount": credits[0]["balance"]
})
debts[0]["balance"] = debts[0]["balance"] - credits[0]["balance"]
del credits[0]
return prettify(transactions, pretty_output) return prettify(transactions, pretty_output)

View file

@ -859,7 +859,7 @@ class BudgetTestCase(IhatemoneyTestCase):
members[t['receiver']] += t['amount'] members[t['receiver']] += t['amount']
balance = models.Project.query.get("raclette").balance balance = models.Project.query.get("raclette").balance
for m, a in members.items(): for m, a in members.items():
self.assertEqual(a, balance[m.id]) assert abs(a - balance[m.id]) < 0.01
return return
def test_settle_zero(self): def test_settle_zero(self):
@ -980,18 +980,23 @@ class BudgetTestCase(IhatemoneyTestCase):
# generate json export of transactions # generate json export of transactions
resp = self.client.get("/raclette/export/transactions.json") resp = self.client.get("/raclette/export/transactions.json")
expected = [{"amount": 127.33, "receiver": "fred", "ower": "alexis"}, expected = [
{"amount": 55.34, "receiver": "fred", "ower": "tata"}, {"amount": 2.00, "receiver": "fred", "ower": "p\xe9p\xe9"},
{"amount": 2.00, "receiver": "fred", "ower": "p\xe9p\xe9"}] {"amount": 55.34, "receiver": "fred", "ower": "tata"},
{"amount": 127.33, "receiver": "fred", "ower": "alexis"},
]
self.assertEqual(json.loads(resp.data.decode('utf-8')), expected) self.assertEqual(json.loads(resp.data.decode('utf-8')), expected)
# generate csv export of transactions # generate csv export of transactions
resp = self.client.get("/raclette/export/transactions.csv") resp = self.client.get("/raclette/export/transactions.csv")
expected = ["amount,receiver,ower", expected = [
"127.33,fred,alexis", "amount,receiver,ower",
"55.34,fred,tata", "2.0,fred,pépé",
"2.0,fred,pépé"] "55.34,fred,tata",
"127.33,fred,alexis",
]
received_lines = resp.data.decode('utf-8').split("\n") received_lines = resp.data.decode('utf-8').split("\n")
for i, line in enumerate(expected): for i, line in enumerate(expected):

View file

@ -1,8 +1,9 @@
alembic==1.1.0 alembic==1.2.0
aniso8601==8.0.0 aniso8601==8.0.0
Babel==2.7.0 Babel==2.7.0
blinker==1.4 blinker==1.4
Click==7.0 Click==7.0
debts==0.4
dnspython==1.16.0 dnspython==1.16.0
email-validator==1.0.4 email-validator==1.0.4
Flask==1.1.1 Flask==1.1.1
@ -12,7 +13,7 @@ Flask-Mail==0.9.1
Flask-Migrate==2.5.2 Flask-Migrate==2.5.2
Flask-RESTful==0.3.7 Flask-RESTful==0.3.7
Flask-Script==2.0.6 Flask-Script==2.0.6
Flask-SQLAlchemy==2.4.0 Flask-SQLAlchemy==2.4.1
Flask-WTF==0.14.2 Flask-WTF==0.14.2
idna==2.8 idna==2.8
itsdangerous==1.1.0 itsdangerous==1.1.0
@ -23,5 +24,5 @@ python-dateutil==2.8.0
pytz==2019.2 pytz==2019.2
six==1.12.0 six==1.12.0
SQLAlchemy==1.3.8 SQLAlchemy==1.3.8
Werkzeug==0.15.6 Werkzeug==0.16.0
WTForms==2.2.1 WTForms==2.2.1

View file

@ -7,14 +7,6 @@ from setuptools import setup, find_packages
here = os.path.abspath(os.path.dirname(__file__)) here = os.path.abspath(os.path.dirname(__file__))
def parse_requirements(filename):
""" load requirements from a pip requirements file """
with open(filename) as lines:
lineiter = (line.strip() for line in lines)
return [line for line in lineiter if line and not line.startswith("#")]
README = open('README.rst', encoding='utf-8').read() README = open('README.rst', encoding='utf-8').read()
CHANGELOG = open('CHANGELOG.rst', encoding='utf-8').read() CHANGELOG = open('CHANGELOG.rst', encoding='utf-8').read()
@ -31,7 +23,6 @@ ENTRY_POINTS = {
], ],
} }
setup(name='ihatemoney', setup(name='ihatemoney',
version='4.2.dev0', version='4.2.dev0',
description='A simple shared budget manager web application.', description='A simple shared budget manager web application.',
@ -69,5 +60,6 @@ setup(name='ihatemoney',
"flask-cors", "flask-cors",
"six", "six",
"itsdangerous", "itsdangerous",
"email_validator"], "email_validator",
"debts"],
entry_points=ENTRY_POINTS) entry_points=ENTRY_POINTS)