mirror of
https://github.com/spiral-project/ihatemoney.git
synced 2025-04-30 18:22:38 +02:00
import and export bill feature updated with bill type, tests modified to reflect the behavior
This commit is contained in:
parent
48d50f7249
commit
4a84ca6d04
11 changed files with 84 additions and 64 deletions
|
@ -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"
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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 ###
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 -%}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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")
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -456,6 +456,7 @@ def import_project():
|
|||
# Check data
|
||||
attr = [
|
||||
"amount",
|
||||
"bill_type",
|
||||
"currency",
|
||||
"date",
|
||||
"owers",
|
||||
|
|
Loading…
Reference in a new issue