import and export bill feature updated with bill type, tests modified to reflect the behavior

This commit is contained in:
Ruitao Li 2022-12-11 21:12:57 -05:00
parent 48d50f7249
commit 4a84ca6d04
11 changed files with 84 additions and 64 deletions

View file

@ -1,5 +1,5 @@
# Verbose and documented settings are in conf-templates/ihatemoney.cfg.j2
DEBUG = SQLACHEMY_ECHO = True
DEBUG = SQLACHEMY_ECHO = False
SQLALCHEMY_DATABASE_URI = "sqlite:////tmp/ihatemoney.db"
SQLALCHEMY_TRACK_MODIFICATIONS = False
SECRET_KEY = "tralala"
@ -10,7 +10,7 @@ ADMIN_PASSWORD = ""
ALLOW_PUBLIC_PROJECT_CREATION = True
ACTIVATE_ADMIN_DASHBOARD = False
SESSION_COOKIE_SECURE = False
TEMPLATES_AUTO_RELOAD=True
TEMPLATES_AUTO_RELOAD = True
SUPPORTED_LANGUAGES = [
"de",
"el",

View file

@ -339,9 +339,7 @@ class BillForm(FlaskForm):
payed_for = SelectMultipleField(
_("For Who?"), validators=[DataRequired()], coerce=int
)
bill_type = SelectField(
_("Bill Type"), validators=[DataRequired()], coerce=str
)
bill_type = SelectField(_("Bill Type"), validators=[DataRequired()], coerce=str)
submit = SubmitField(_("Submit"))
submit2 = SubmitField(_("Submit and add a new one"))
@ -355,7 +353,7 @@ class BillForm(FlaskForm):
payer_id=self.payer.data,
project_default_currency=project.default_currency,
what=self.what.data,
bill_type=self.bill_type.data
bill_type=self.bill_type.data,
)
def save(self, bill, project):

View file

@ -1,26 +0,0 @@
"""add bill type into bill_version
Revision ID: 3263a8f198b0
Revises: 7a9b38559992
Create Date: 2022-12-10 20:00:48.611280
"""
# revision identifiers, used by Alembic.
revision = '3263a8f198b0'
down_revision = '7a9b38559992'
from alembic import op
import sqlalchemy as sa
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('bill_version', sa.Column('bill_type', sa.UnicodeText(), autoincrement=False, nullable=True))
# ### end Alembic commands ###
def downgrade():
pass
# ### end Alembic commands ###

View file

@ -7,8 +7,8 @@ Create Date: 2022-12-10 17:25:38.387643
"""
# revision identifiers, used by Alembic.
revision = '7a9b38559992'
down_revision = '927ed575acbd'
revision = "7a9b38559992"
down_revision = "927ed575acbd"
from alembic import op
import sqlalchemy as sa

View file

@ -74,7 +74,14 @@ class Project(db.Model):
query_class = ProjectQuery
default_currency = db.Column(db.String(3))
bill_types = [("Expense","Expense"), ("Reimbursment","Reimbursment"), ("Refund","Refund"), ("Transfer","Transfer"), ("Payment","Payment")]
bill_types = [
("Expense", "Expense"),
("Reimbursment", "Reimbursment"),
("Refund", "Refund"),
("Transfer", "Transfer"),
("Payment", "Payment"),
]
@property
def _to_serialize(self):
obj = {
@ -334,6 +341,7 @@ class Project(db.Model):
pretty_bills.append(
{
"what": bill.what,
"bill_type": bill.bill_type,
"amount": round(bill.amount, 2),
"currency": bill.original_currency,
"date": str(bill.date),
@ -405,6 +413,7 @@ class Project(db.Model):
new_bill = Bill(
amount=b["amount"],
date=parse(b["date"]),
bill_type=b["bill_type"],
external_link="",
original_currency=b["currency"],
owers=Person.query.get_by_names(b["owers"], self),
@ -533,14 +542,15 @@ class Project(db.Model):
db.session.commit()
operations = (
("Georg", 200, ("Amina", "Georg", "Alice"), "Food shopping"),
("Alice", 20, ("Amina", "Alice"), "Beer !"),
("Amina", 50, ("Amina", "Alice", "Georg"), "AMAP"),
("Georg", 200, ("Amina", "Georg", "Alice"), "Food shopping", "Expense"),
("Alice", 20, ("Amina", "Alice"), "Beer !", "Expense"),
("Amina", 50, ("Amina", "Alice", "Georg"), "AMAP", "Expense"),
)
for (payer, amount, owers, what) in operations:
for (payer, amount, owers, what, bill_type) in operations:
db.session.add(
Bill(
amount=amount,
bill_type=bill_type,
original_currency=project.default_currency,
owers=[members[name] for name in owers],
payer_id=members[payer].id,

View file

@ -105,6 +105,7 @@
<table id="bill_table" class="col table table-striped table-hover table-responsive-sm">
<thead>
<tr><th>{{ _("When?") }}
</th><th>{{ _("Bill Type") }}
</th><th>{{ _("Who paid?") }}
</th><th>{{ _("For what?") }}
</th><th>{{ _("For whom?") }}
@ -120,6 +121,7 @@
{{ bill.date }}
</span>
</td>
<td>{{ bill.bill_type }}</td>
<td>{{ bill.payer }}</td>
<td>{{ bill.what }}</td>
<td>{% if bill.owers|length == g.project.members|length -%}

View file

@ -96,7 +96,8 @@ class APITestCase(IhatemoneyTestCase):
self.assertTrue(400, resp.status_code)
self.assertEqual(
'{"contact_email": ["Invalid email address."]}\n', resp.data.decode("utf-8")
"".join('{"contact_email": ["Invalid email address."]}\n'.split()),
"".join(resp.data.decode("utf-8").split()),
)
# create it
@ -429,7 +430,8 @@ class APITestCase(IhatemoneyTestCase):
self.assertStatus(400, req)
self.assertEqual(
'{"date": ["This field is required."]}\n', req.data.decode("utf-8")
"".join('{"date": ["This field is required."]}'.split()),
"".join(req.data.decode("utf-8").split()),
)
# edit a bill

View file

@ -1207,8 +1207,8 @@ class BudgetTestCase(IhatemoneyTestCase):
members = defaultdict(int)
# We should have the same values between transactions and project balances
for t in transactions:
members[t["ower"]] -= t["bill_type":"Expense", "amount"]
members[t["receiver"]] += t["bill_type":"Expense", "amount"]
members[t["ower"]] -= t["amount"]
members[t["receiver"]] += t["amount"]
balance = self.get_project("raclette").balance
for m, a in members.items():
assert abs(a - balance[m.id]) < 0.01
@ -1263,7 +1263,7 @@ class BudgetTestCase(IhatemoneyTestCase):
# There should not be any zero-amount transfer after rounding
for t in transactions:
rounded_amount = round(t["bill_type":"Expense", "amount"], 2)
rounded_amount = round(t["amount"], 2)
self.assertNotEqual(
0.0,
rounded_amount,

View file

@ -14,6 +14,7 @@ class CommonTestCase(object):
{
"date": "2017-01-01",
"what": "refund",
"bill_type": "Expense",
"amount": 13.33,
"payer_name": "tata",
"payer_weight": 1.0,
@ -22,6 +23,7 @@ class CommonTestCase(object):
{
"date": "2016-12-31",
"what": "red wine",
"bill_type": "Expense",
"amount": 200.0,
"payer_name": "fred",
"payer_weight": 1.0,
@ -29,7 +31,8 @@ class CommonTestCase(object):
},
{
"date": "2016-12-31",
"what": "fromage a raclette",
"what": "a raclette",
"bill_type": "Expense",
"amount": 10.0,
"payer_name": "zorglub",
"payer_weight": 2.0,
@ -71,6 +74,7 @@ class CommonTestCase(object):
if b["what"] == d["what"]:
self.assertEqual(b["payer_name"], d["payer_name"])
self.assertEqual(b["amount"], d["amount"])
self.assertEqual(b["bill_type"], d["bill_type"])
self.assertEqual(b["currency"], d["currency"])
self.assertEqual(b["payer_weight"], d["payer_weight"])
self.assertEqual(b["date"], d["date"])
@ -114,6 +118,7 @@ class CommonTestCase(object):
self.assertEqual(b["amount"], d["amount"])
# Currency should have been stripped
self.assertEqual(b["currency"], "XXX")
self.assertEqual(b["bill_type"], d["bill_type"])
self.assertEqual(b["payer_weight"], d["payer_weight"])
self.assertEqual(b["date"], d["date"])
list_project = [ower for ower in b["owers"]]
@ -172,6 +177,7 @@ class CommonTestCase(object):
self.assertEqual(b["amount"], d["amount"])
# All bills are converted to default project currency
self.assertEqual(b["currency"], "EUR")
self.assertEqual(b["bill_type"], d["bill_type"])
self.assertEqual(b["payer_weight"], d["payer_weight"])
self.assertEqual(b["date"], d["date"])
list_project = [ower for ower in b["owers"]]
@ -210,6 +216,7 @@ class CommonTestCase(object):
if b["what"] == d["what"]:
self.assertEqual(b["payer_name"], d["payer_name"])
self.assertEqual(b["amount"], d["amount"])
self.assertEqual(b["bill_type"], d["bill_type"])
self.assertEqual(b["currency"], "XXX")
self.assertEqual(b["payer_weight"], d["payer_weight"])
self.assertEqual(b["date"], d["date"])
@ -237,6 +244,7 @@ class CommonTestCase(object):
data={
"date": "2016-12-31",
"what": "red wine",
"bill_type": "Expense",
"payer": 2,
"payed_for": [1, 3],
"amount": "200",
@ -266,6 +274,7 @@ class CommonTestCase(object):
if b["what"] == d["what"]:
self.assertEqual(b["payer_name"], d["payer_name"])
self.assertEqual(b["amount"], d["amount"])
self.assertEqual(b["bill_type"], d["bill_type"])
self.assertEqual(b["currency"], d["currency"])
self.assertEqual(b["payer_weight"], d["payer_weight"])
self.assertEqual(b["date"], d["date"])
@ -292,6 +301,7 @@ class CommonTestCase(object):
{
"date": "2017-01-01",
"what": "refund",
"bill_type": "Refund",
"payer_name": "tata",
"payer_weight": 1.0,
"owers": ["fred"],
@ -319,7 +329,8 @@ class ExportTestCase(IhatemoneyTestCase):
"/raclette/add",
data={
"date": "2016-12-31",
"what": "fromage à raclette",
"bill_type": "Expense",
"what": "à raclette",
"payer": 1,
"payed_for": [1, 2, 3, 4],
"amount": "10.0",
@ -330,6 +341,7 @@ class ExportTestCase(IhatemoneyTestCase):
"/raclette/add",
data={
"date": "2016-12-31",
"bill_type": "Expense",
"what": "red wine",
"payer": 2,
"payed_for": [1, 3],
@ -341,6 +353,7 @@ class ExportTestCase(IhatemoneyTestCase):
"/raclette/add",
data={
"date": "2017-01-01",
"bill_type": "Refund",
"what": "refund",
"payer": 3,
"payed_for": [2],
@ -353,6 +366,7 @@ class ExportTestCase(IhatemoneyTestCase):
expected = [
{
"date": "2017-01-01",
"bill_type": "Refund",
"what": "refund",
"amount": 13.33,
"currency": "XXX",
@ -362,6 +376,7 @@ class ExportTestCase(IhatemoneyTestCase):
},
{
"date": "2016-12-31",
"bill_type": "Expense",
"what": "red wine",
"amount": 200.0,
"currency": "XXX",
@ -371,7 +386,8 @@ class ExportTestCase(IhatemoneyTestCase):
},
{
"date": "2016-12-31",
"what": "fromage \xe0 raclette",
"bill_type": "Expense",
"what": "\xe0 raclette",
"amount": 10.0,
"currency": "XXX",
"payer_name": "zorglub",
@ -384,10 +400,10 @@ class ExportTestCase(IhatemoneyTestCase):
# generate csv export of bills
resp = self.client.get("/raclette/export/bills.csv")
expected = [
"date,what,amount,currency,payer_name,payer_weight,owers",
"2017-01-01,refund,XXX,13.33,tata,1.0,fred",
'2016-12-31,red wine,XXX,200.0,fred,1.0,"zorglub, tata"',
'2016-12-31,fromage à raclette,10.0,XXX,zorglub,2.0,"zorglub, fred, tata, pépé"',
"date,what,bill_type,amount,currency,payer_name,payer_weight,owers",
"2017-01-01,refund,Refund,XXX,13.33,tata,1.0,fred",
'2016-12-31,red wine,Expense,XXX,200.0,fred,1.0,"zorglub, tata"',
'2016-12-31,à raclette,Expense,10.0,XXX,zorglub,2.0,"zorglub, fred, tata, pépé"',
]
received_lines = resp.data.decode("utf-8").split("\n")
@ -450,7 +466,8 @@ class ExportTestCase(IhatemoneyTestCase):
"/raclette/add",
data={
"date": "2016-12-31",
"what": "fromage à raclette",
"what": "à raclette",
"bill_type": "Expense",
"payer": 1,
"payed_for": [1, 2, 3, 4],
"amount": "10.0",
@ -463,6 +480,7 @@ class ExportTestCase(IhatemoneyTestCase):
data={
"date": "2016-12-31",
"what": "poutine from Québec",
"bill_type": "Expense",
"payer": 2,
"payed_for": [1, 3],
"amount": "100",
@ -475,6 +493,7 @@ class ExportTestCase(IhatemoneyTestCase):
data={
"date": "2017-01-01",
"what": "refund",
"bill_type": "Refund",
"payer": 3,
"payed_for": [2],
"amount": "13.33",
@ -488,6 +507,7 @@ class ExportTestCase(IhatemoneyTestCase):
{
"date": "2017-01-01",
"what": "refund",
"bill_type": "Refund",
"amount": 13.33,
"currency": "EUR",
"payer_name": "tata",
@ -497,6 +517,7 @@ class ExportTestCase(IhatemoneyTestCase):
{
"date": "2016-12-31",
"what": "poutine from Qu\xe9bec",
"bill_type": "Expense",
"amount": 100.0,
"currency": "CAD",
"payer_name": "fred",
@ -505,7 +526,8 @@ class ExportTestCase(IhatemoneyTestCase):
},
{
"date": "2016-12-31",
"what": "fromage \xe0 raclette",
"what": "\xe0 raclette",
"bill_type": "Expense",
"amount": 10.0,
"currency": "EUR",
"payer_name": "zorglub",
@ -518,10 +540,10 @@ class ExportTestCase(IhatemoneyTestCase):
# generate csv export of bills
resp = self.client.get("/raclette/export/bills.csv")
expected = [
"date,what,amount,currency,payer_name,payer_weight,owers",
"2017-01-01,refund,13.33,EUR,tata,1.0,fred",
'2016-12-31,poutine from Québec,100.0,CAD,fred,1.0,"zorglub, tata"',
'2016-12-31,fromage à raclette,10.0,EUR,zorglub,2.0,"zorglub, fred, tata, pépé"',
"date,what,bill_type,amount,currency,payer_name,payer_weight,owers",
"2017-01-01,refund,Refund,13.33,EUR,tata,1.0,fred",
'2016-12-31,poutine from Québec,Expense,100.0,CAD,fred,1.0,"zorglub, tata"',
'2016-12-31,à raclette,Expense,10.0,EUR,zorglub,2.0,"zorglub, fred, tata, pépé"',
]
received_lines = resp.data.decode("utf-8").split("\n")
@ -608,6 +630,7 @@ class ExportTestCase(IhatemoneyTestCase):
data={
"date": "2016-12-31",
"what": "=COS(36)",
"bill_type": "Expense",
"payer": 1,
"payed_for": [1],
"amount": "10.0",
@ -618,8 +641,8 @@ class ExportTestCase(IhatemoneyTestCase):
# generate csv export of bills
resp = self.client.get("/raclette/export/bills.csv")
expected = [
"date,what,amount,currency,payer_name,payer_weight,owers",
"2016-12-31,'=COS(36),10.0,EUR,zorglub,1.0,zorglub",
"date,what,bill_type,amount,currency,payer_name,payer_weight,owers",
"2016-12-31,'=COS(36),Expense,10.0,EUR,zorglub,1.0,zorglub",
]
received_lines = resp.data.decode("utf-8").split("\n")

View file

@ -216,6 +216,7 @@ def csv2list_of_dicts(csv_to_convert):
r["amount"] = float(r["amount"])
r["payer_weight"] = float(r["payer_weight"])
r["owers"] = [o.strip() for o in r["owers"].split(",")]
r["bill_type"] = str(r["bill_type"])
result.append(r)
return result
@ -299,7 +300,16 @@ def get_members(file):
def same_bill(bill1, bill2):
attr = ["what", "payer_name", "payer_weight", "amount", "currency", "date", "owers"]
attr = [
"what",
"bill_type",
"payer_name",
"payer_weight",
"amount",
"currency",
"date",
"owers",
]
for a in attr:
if bill1[a] != bill2[a]:
return False

View file

@ -456,6 +456,7 @@ def import_project():
# Check data
attr = [
"amount",
"bill_type",
"currency",
"date",
"owers",