mirror of
https://github.com/spiral-project/ihatemoney.git
synced 2025-04-28 17:32:38 +02:00
Fix #434 Use the debts lib to solve settlements.
This commit is contained in:
parent
9fc7fc768e
commit
74c51be5a3
7 changed files with 31 additions and 60 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -7,3 +7,6 @@ docs/_build/
|
||||||
.tox
|
.tox
|
||||||
dist
|
dist
|
||||||
.cache/
|
.cache/
|
||||||
|
build
|
||||||
|
.vscode
|
||||||
|
.env
|
|
@ -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
0
ihatemoney/budget.db
Normal 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)
|
||||||
|
|
||||||
|
|
|
@ -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": 2.00, "receiver": "fred", "ower": "p\xe9p\xe9"},
|
||||||
{"amount": 55.34, "receiver": "fred", "ower": "tata"},
|
{"amount": 55.34, "receiver": "fred", "ower": "tata"},
|
||||||
{"amount": 2.00, "receiver": "fred", "ower": "p\xe9p\xe9"}]
|
{"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",
|
||||||
|
"2.0,fred,pépé",
|
||||||
"55.34,fred,tata",
|
"55.34,fred,tata",
|
||||||
"2.0,fred,pépé"]
|
"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):
|
||||||
|
|
|
@ -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
|
||||||
|
|
12
setup.py
12
setup.py
|
@ -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)
|
||||||
|
|
Loading…
Reference in a new issue