diff --git a/ihatemoney/default_settings.py b/ihatemoney/default_settings.py index 6c23c9fb..b4574240 100644 --- a/ihatemoney/default_settings.py +++ b/ihatemoney/default_settings.py @@ -9,7 +9,8 @@ ACTIVATE_DEMO_PROJECT = True ADMIN_PASSWORD = "" ALLOW_PUBLIC_PROJECT_CREATION = True ACTIVATE_ADMIN_DASHBOARD = False -SESSION_COOKIE_SECURE = True +SESSION_COOKIE_SECURE = False +TEMPLATES_AUTO_RELOAD = True SUPPORTED_LANGUAGES = [ "de", "el", diff --git a/ihatemoney/forms.py b/ihatemoney/forms.py index e9973fdd..3d9b3845 100644 --- a/ihatemoney/forms.py +++ b/ihatemoney/forms.py @@ -77,6 +77,7 @@ def get_billform_for(project, set_default=True, **kwargs): active_members = [(m.id, m.name) for m in project.active_members] + form.bill_type.choices = project.bill_types form.payed_for.choices = form.payer.choices = active_members form.payed_for.default = [m.id for m in project.active_members] @@ -336,8 +337,9 @@ class BillForm(FlaskForm): description=_("A link to an external document, related to this bill"), ) payed_for = SelectMultipleField( - _("For whom?"), validators=[DataRequired()], coerce=int + _("For Who?"), validators=[DataRequired()], coerce=int ) + bill_type = SelectField(_("Bill Type"), validators=[DataRequired()], coerce=str) submit = SubmitField(_("Submit")) submit2 = SubmitField(_("Submit and add a new one")) @@ -351,12 +353,14 @@ class BillForm(FlaskForm): payer_id=self.payer.data, project_default_currency=project.default_currency, what=self.what.data, + bill_type=self.bill_type.data, ) def save(self, bill, project): bill.payer_id = self.payer.data bill.amount = self.amount.data bill.what = self.what.data + bill.bill_type = self.bill_type.data bill.external_link = self.external_link.data bill.date = self.date.data bill.owers = Person.query.get_by_ids(self.payed_for.data, project) @@ -370,6 +374,7 @@ class BillForm(FlaskForm): self.payer.data = bill.payer_id self.amount.data = bill.amount self.what.data = bill.what + self.bill_type.data = bill.bill_type self.external_link.data = bill.external_link self.original_currency.data = bill.original_currency self.date.data = bill.date @@ -393,6 +398,10 @@ class BillForm(FlaskForm): # See https://github.com/python-babel/babel/issues/821 raise ValidationError(f"Result is too high: {field.data}") + def validate_bill_type(self, field): + if (field.data, field.data) not in Project.bill_types: + raise ValidationError(_("Invalid Bill Type")) + class MemberForm(FlaskForm): name = StringField(_("Name"), validators=[DataRequired()], filters=[strip_filter]) diff --git a/ihatemoney/migrations/versions/7a9b38559992_new_bill_type_attribute_added.py b/ihatemoney/migrations/versions/7a9b38559992_new_bill_type_attribute_added.py new file mode 100644 index 00000000..14c73722 --- /dev/null +++ b/ihatemoney/migrations/versions/7a9b38559992_new_bill_type_attribute_added.py @@ -0,0 +1,23 @@ +"""new bill type attribute added + +Revision ID: 7a9b38559992 +Revises: 927ed575acbd +Create Date: 2022-12-10 17:25:38.387643 + +""" + +# revision identifiers, used by Alembic. +revision = "7a9b38559992" +down_revision = "927ed575acbd" + +from alembic import op +import sqlalchemy as sa + + +def upgrade(): + op.add_column("bill", sa.Column("bill_type", sa.UnicodeText())) + op.add_column("bill_version", sa.Column("bill_type", sa.UnicodeText())) + + +def downgrade(): + pass diff --git a/ihatemoney/models.py b/ihatemoney/models.py index 10615d42..e31964b1 100644 --- a/ihatemoney/models.py +++ b/ihatemoney/models.py @@ -74,6 +74,13 @@ 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"), + ] @property def _to_serialize(self): @@ -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, @@ -673,6 +683,7 @@ class Bill(db.Model): date = db.Column(db.Date, default=datetime.datetime.now) creation_date = db.Column(db.Date, default=datetime.datetime.now) what = db.Column(db.UnicodeText) + bill_type = db.Column(db.UnicodeText) external_link = db.Column(db.UnicodeText) original_currency = db.Column(db.String(3)) @@ -692,6 +703,7 @@ class Bill(db.Model): payer_id: int = None, project_default_currency: str = "", what: str = "", + bill_type: str = "", ): super().__init__() self.amount = amount @@ -701,6 +713,7 @@ class Bill(db.Model): self.owers = owers self.payer_id = payer_id self.what = what + self.bill_type = bill_type self.converted_amount = self.currency_helper.exchange_currency( self.amount, self.original_currency, project_default_currency ) @@ -715,6 +728,7 @@ class Bill(db.Model): "date": self.date, "creation_date": self.creation_date, "what": self.what, + "bill_type": self.bill_type, "external_link": self.external_link, "original_currency": self.original_currency, "converted_amount": self.converted_amount, diff --git a/ihatemoney/templates/forms.html b/ihatemoney/templates/forms.html index 48c3df2b..3cea96aa 100644 --- a/ihatemoney/templates/forms.html +++ b/ihatemoney/templates/forms.html @@ -164,6 +164,7 @@ {% include "display_errors.html" %} {{ form.hidden_tag() }} {{ input(form.date, inline=True) }} + {{ input(form.bill_type, inline=True) }} {{ input(form.what, inline=True) }} {{ input(form.payer, inline=True, class="form-control custom-select") }}
diff --git a/ihatemoney/templates/list_bills.html b/ihatemoney/templates/list_bills.html index 27c0e291..26c61503 100644 --- a/ihatemoney/templates/list_bills.html +++ b/ihatemoney/templates/list_bills.html @@ -126,6 +126,7 @@
{{ _("When?") }} + {{ _("Bill Type") }} {{ _("Who paid?") }} {{ _("For what?") }} {{ _("For whom?") }} @@ -141,6 +142,7 @@ {{ bill.date }} + {{ bill.bill_type }} {{ bill.payer }} {{ bill.what }} {% if bill.owers|length == g.project.members|length -%} diff --git a/ihatemoney/tests/api_test.py b/ihatemoney/tests/api_test.py index 69c6ab85..43402a30 100644 --- a/ihatemoney/tests/api_test.py +++ b/ihatemoney/tests/api_test.py @@ -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 @@ -363,6 +364,7 @@ class APITestCase(IhatemoneyTestCase): "what": "fromage", "payer": "1", "payed_for": ["1", "2"], + "bill_type": "Expense", "amount": "25", "external_link": "https://raclette.fr", }, @@ -387,6 +389,7 @@ class APITestCase(IhatemoneyTestCase): {"activated": True, "id": 1, "name": "zorglub", "weight": 1}, {"activated": True, "id": 2, "name": "fred", "weight": 1}, ], + "bill_type": "Expense", "amount": 25.0, "date": "2011-08-10", "id": 1, @@ -418,6 +421,7 @@ class APITestCase(IhatemoneyTestCase): "what": "fromage", "payer": "1", "payed_for": ["1", "2"], + "bill_type": "Expense", "amount": "25", "external_link": "https://raclette.fr", }, @@ -426,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 @@ -437,6 +442,7 @@ class APITestCase(IhatemoneyTestCase): "what": "beer", "payer": "2", "payed_for": ["1", "2"], + "bill_type": "Expense", "amount": "25", "external_link": "https://raclette.fr", }, @@ -458,6 +464,7 @@ class APITestCase(IhatemoneyTestCase): {"activated": True, "id": 1, "name": "zorglub", "weight": 1}, {"activated": True, "id": 2, "name": "fred", "weight": 1}, ], + "bill_type": "Expense", "amount": 25.0, "date": "2011-09-10", "external_link": "https://raclette.fr", @@ -512,6 +519,7 @@ class APITestCase(IhatemoneyTestCase): "what": "fromage", "payer": "1", "payed_for": ["1", "2"], + "bill_type": "Expense", "amount": input_amount, }, headers=self.get_auth("raclette"), @@ -536,6 +544,7 @@ class APITestCase(IhatemoneyTestCase): {"activated": True, "id": 1, "name": "zorglub", "weight": 1}, {"activated": True, "id": 2, "name": "fred", "weight": 1}, ], + "bill_type": "Expense", "amount": expected_amount, "date": "2011-08-10", "id": id, @@ -569,6 +578,7 @@ class APITestCase(IhatemoneyTestCase): "what": "fromage", "payer": "1", "payed_for": ["1", "2"], + "bill_type": "Expense", "amount": amount, }, headers=self.get_auth("raclette"), @@ -615,6 +625,7 @@ class APITestCase(IhatemoneyTestCase): "what": "fromage", "payer": "1", "payed_for": ["1", "2"], + "bill_type": "Expense", "amount": "25", "external_link": "https://raclette.fr", }, @@ -639,6 +650,7 @@ class APITestCase(IhatemoneyTestCase): {"activated": True, "id": 1, "name": "zorglub", "weight": 1}, {"activated": True, "id": 2, "name": "fred", "weight": 1}, ], + "bill_type": "Expense", "amount": 25.0, "date": "2011-08-10", "id": 1, @@ -663,6 +675,7 @@ class APITestCase(IhatemoneyTestCase): "what": "fromage", "payer": "1", "payed_for": ["1", "2"], + "bill_type": "Expense", "amount": "30", "external_link": "https://raclette.fr", "original_currency": "CAD", @@ -684,6 +697,7 @@ class APITestCase(IhatemoneyTestCase): {"activated": True, "id": 1, "name": "zorglub", "weight": 1.0}, {"activated": True, "id": 2, "name": "fred", "weight": 1.0}, ], + "bill_type": "Expense", "amount": 30.0, "date": "2011-08-10", "id": 1, @@ -704,6 +718,7 @@ class APITestCase(IhatemoneyTestCase): "what": "Pierogi", "payer": "1", "payed_for": ["2", "3"], + "bill_type": "Expense", "amount": "80", "original_currency": "PLN", }, @@ -747,6 +762,7 @@ class APITestCase(IhatemoneyTestCase): "what": "fromage", "payer": "1", "payed_for": ["1", "2"], + "bill_type": "Expense", "amount": "25", }, headers=self.get_auth("raclette"), @@ -814,6 +830,7 @@ class APITestCase(IhatemoneyTestCase): "what": "fromage", "payer": "1", "payed_for": ["1", "2"], + "bill_type": "Expense", "amount": "25", }, headers=self.get_auth("raclette"), @@ -836,6 +853,7 @@ class APITestCase(IhatemoneyTestCase): {"activated": True, "id": 1, "name": "zorglub", "weight": 1}, {"activated": True, "id": 2, "name": "freddy familly", "weight": 4}, ], + "bill_type": "Expense", "amount": 25.0, "date": "2011-08-10", "id": 1, @@ -923,6 +941,7 @@ class APITestCase(IhatemoneyTestCase): "what": "fromage", "payer": "1", "payed_for": ["1"], + "bill_type": "Expense", "amount": "0", }, headers=self.get_auth("raclette"), @@ -951,6 +970,7 @@ class APITestCase(IhatemoneyTestCase): "what": "fromage", "payer": "1", "payed_for": ["1"], + "bill_type": "Expense", "amount": "9347242149381274732472348728748723473278472843.12", }, headers=self.get_auth("raclette"), diff --git a/ihatemoney/tests/budget_test.py b/ihatemoney/tests/budget_test.py index fb434fbb..5773a261 100644 --- a/ihatemoney/tests/budget_test.py +++ b/ihatemoney/tests/budget_test.py @@ -411,6 +411,7 @@ class BudgetTestCase(IhatemoneyTestCase): "what": "fromage à raclette", "payer": fred_id, "payed_for": [fred_id], + "bill_type": "Expense", "amount": "25", }, ) @@ -462,6 +463,7 @@ class BudgetTestCase(IhatemoneyTestCase): "what": "fromage à raclette", "payer": zorglub.id, "payed_for": [zorglub.id], + "bill_type": "Expense", "amount": "25", }, ) @@ -635,6 +637,7 @@ class BudgetTestCase(IhatemoneyTestCase): "what": "fromage à raclette", "payer": members_ids[0], "payed_for": members_ids, + "bill_type": "Expense", "amount": "25", }, ) @@ -650,6 +653,7 @@ class BudgetTestCase(IhatemoneyTestCase): "what": "fromage à raclette", "payer": members_ids[0], "payed_for": members_ids, + "bill_type": "Expense", "amount": "10", }, ) @@ -673,6 +677,7 @@ class BudgetTestCase(IhatemoneyTestCase): "what": "fromage à raclette", "payer": members_ids[0], "payed_for": members_ids, + "bill_type": "Expense", "amount": "19", }, ) @@ -684,6 +689,7 @@ class BudgetTestCase(IhatemoneyTestCase): "what": "fromage à raclette", "payer": members_ids[1], "payed_for": members_ids[0], + "bill_type": "Expense", "amount": "20", }, ) @@ -695,6 +701,7 @@ class BudgetTestCase(IhatemoneyTestCase): "what": "fromage à raclette", "payer": members_ids[1], "payed_for": members_ids, + "bill_type": "Expense", "amount": "17", }, ) @@ -710,6 +717,7 @@ class BudgetTestCase(IhatemoneyTestCase): "what": "fromage à raclette", "payer": members_ids[0], "payed_for": members_ids, + "bill_type": "Expense", "amount": "-25", }, ) @@ -724,6 +732,7 @@ class BudgetTestCase(IhatemoneyTestCase): "what": "fromage à raclette", "payer": members_ids[0], "payed_for": members_ids, + "bill_type": "Expense", "amount": "25,02", }, ) @@ -738,6 +747,7 @@ class BudgetTestCase(IhatemoneyTestCase): "what": "fromage à raclette", "payer": members_ids[0], "payed_for": members_ids, + "bill_type": "Expense", "amount": "42", "external_link": "https://example.com/fromage", }, @@ -753,6 +763,7 @@ class BudgetTestCase(IhatemoneyTestCase): "what": "mauvais fromage à raclette", "payer": members_ids[0], "payed_for": members_ids, + "bill_type": "Expense", "amount": "42000", "external_link": "javascript:alert('Tu bluffes, Martoni.')", }, @@ -778,6 +789,7 @@ class BudgetTestCase(IhatemoneyTestCase): "what": "fromage à raclette", "payer": members_ids[0], "payed_for": members_ids, + "bill_type": "Expense", "amount": "10", }, ) @@ -789,6 +801,7 @@ class BudgetTestCase(IhatemoneyTestCase): "what": "pommes de terre", "payer": members_ids[1], "payed_for": members_ids, + "bill_type": "Expense", "amount": "10", }, ) @@ -853,6 +866,7 @@ class BudgetTestCase(IhatemoneyTestCase): "what": "fromage à raclette", "payer": 1, "payed_for": [1, 2, 3], + "bill_type": "Expense", "amount": "24.36", }, ) @@ -864,6 +878,7 @@ class BudgetTestCase(IhatemoneyTestCase): "what": "red wine", "payer": 2, "payed_for": [1], + "bill_type": "Expense", "amount": "19.12", }, ) @@ -875,6 +890,7 @@ class BudgetTestCase(IhatemoneyTestCase): "what": "delicatessen", "payer": 1, "payed_for": [1, 2], + "bill_type": "Expense", "amount": "22", }, ) @@ -977,6 +993,7 @@ class BudgetTestCase(IhatemoneyTestCase): "what": "fromage à raclette", "payer": 1, "payed_for": [1, 2, 3], + "bill_type": "Expense", "amount": "10.0", }, ) @@ -988,6 +1005,7 @@ class BudgetTestCase(IhatemoneyTestCase): "what": "red wine", "payer": 2, "payed_for": [1], + "bill_type": "Expense", "amount": "20", }, ) @@ -999,6 +1017,7 @@ class BudgetTestCase(IhatemoneyTestCase): "what": "delicatessen", "payer": 1, "payed_for": [1, 2], + "bill_type": "Expense", "amount": "10", }, ) @@ -1047,6 +1066,7 @@ class BudgetTestCase(IhatemoneyTestCase): "what": "fromage à raclette", "payer": 2, "payed_for": [1, 2], + "bill_type": "Expense", "amount": "30", }, ) @@ -1072,6 +1092,7 @@ class BudgetTestCase(IhatemoneyTestCase): "what": "ice cream", "payer": 2, "payed_for": [1, 2], + "bill_type": "Expense", "amount": "10", }, ) @@ -1087,6 +1108,7 @@ class BudgetTestCase(IhatemoneyTestCase): "what": "champomy", "payer": 1, "payed_for": [1, 2], + "bill_type": "Expense", "amount": "10", }, ) @@ -1102,6 +1124,7 @@ class BudgetTestCase(IhatemoneyTestCase): "what": "smoothie", "payer": 1, "payed_for": [1, 2], + "bill_type": "Expense", "amount": "20", }, ) @@ -1118,6 +1141,7 @@ class BudgetTestCase(IhatemoneyTestCase): "what": "more champomy", "payer": 2, "payed_for": [1, 2], + "bill_type": "Expense", "amount": "30", }, ) @@ -1150,6 +1174,7 @@ class BudgetTestCase(IhatemoneyTestCase): "what": "fromage à raclette", "payer": 1, "payed_for": [1, 2, 3], + "bill_type": "Expense", "amount": "10.0", }, ) @@ -1161,6 +1186,7 @@ class BudgetTestCase(IhatemoneyTestCase): "what": "red wine", "payer": 2, "payed_for": [1], + "bill_type": "Expense", "amount": "20", }, ) @@ -1172,6 +1198,7 @@ class BudgetTestCase(IhatemoneyTestCase): "what": "delicatessen", "payer": 1, "payed_for": [1, 2], + "bill_type": "Expense", "amount": "10", }, ) @@ -1203,6 +1230,7 @@ class BudgetTestCase(IhatemoneyTestCase): "what": "fromage à raclette", "payer": 1, "payed_for": [1, 2, 3], + "bill_type": "Expense", "amount": "10.0", }, ) @@ -1214,6 +1242,7 @@ class BudgetTestCase(IhatemoneyTestCase): "what": "red wine", "payer": 2, "payed_for": [1, 3], + "bill_type": "Expense", "amount": "20", }, ) @@ -1225,6 +1254,7 @@ class BudgetTestCase(IhatemoneyTestCase): "what": "refund", "payer": 3, "payed_for": [2], + "bill_type": "Expense", "amount": "13.33", }, ) @@ -1259,6 +1289,7 @@ class BudgetTestCase(IhatemoneyTestCase): "what": "fromage à raclette", "payer": 1, "payed_for": [1, 2, 3, 4], + "bill_type": "Expense", "amount": "10.0", }, ) @@ -1277,6 +1308,7 @@ class BudgetTestCase(IhatemoneyTestCase): "what": "roblochon", "payer": 2, "payed_for": [1, 3, 4], + "bill_type": "Expense", "amount": "100.0", } # Try to access bill of another project @@ -1382,6 +1414,7 @@ class BudgetTestCase(IhatemoneyTestCase): "what": "fromage à raclette", "payer": 1, "payed_for": [1, 2, 3], + "bill_type": "Expense", "amount": "10.0", }, ) @@ -1393,6 +1426,7 @@ class BudgetTestCase(IhatemoneyTestCase): "what": "red wine", "payer": 2, "payed_for": [1, 3], + "bill_type": "Expense", "amount": "20", }, ) @@ -1404,6 +1438,7 @@ class BudgetTestCase(IhatemoneyTestCase): "what": "refund", "payer": 3, "payed_for": [2], + "bill_type": "Expense", "amount": "13.33", }, ) @@ -1429,6 +1464,7 @@ class BudgetTestCase(IhatemoneyTestCase): "what": "refund from EUR", "payer": 3, "payed_for": [2], + "bill_type": "Expense", "amount": "20", "original_currency": "EUR", }, @@ -1452,6 +1488,7 @@ class BudgetTestCase(IhatemoneyTestCase): "what": "Poutine", "payer": 3, "payed_for": [2], + "bill_type": "Expense", "amount": "18", "original_currency": "CAD", }, @@ -1508,6 +1545,7 @@ class BudgetTestCase(IhatemoneyTestCase): "what": "fromage à raclette", "payer": 1, "payed_for": [1, 2], + "bill_type": "Expense", "amount": "10.0", "original_currency": "EUR", }, @@ -1542,6 +1580,7 @@ class BudgetTestCase(IhatemoneyTestCase): "what": "fromage à raclette", "payer": 1, "payed_for": [1, 2], + "bill_type": "Expense", "amount": "10.0", "original_currency": "EUR", }, @@ -1554,6 +1593,7 @@ class BudgetTestCase(IhatemoneyTestCase): "what": "aspirine", "payer": 2, "payed_for": [1, 2], + "bill_type": "Expense", "amount": "5.0", "original_currency": "EUR", }, @@ -1587,6 +1627,7 @@ class BudgetTestCase(IhatemoneyTestCase): "what": "fromage à raclette", "payer": 1, "payed_for": [1], + "bill_type": "Expense", "amount": "0", "original_currency": "EUR", }, @@ -1635,6 +1676,7 @@ class BudgetTestCase(IhatemoneyTestCase): "what": "fromage à raclette", "payer": 1, "payed_for": [1], + "bill_type": "Expense", "amount": "9347242149381274732472348728748723473278472843.12", "original_currency": "EUR", }, diff --git a/ihatemoney/tests/history_test.py b/ihatemoney/tests/history_test.py index a8b3e10b..92eef005 100644 --- a/ihatemoney/tests/history_test.py +++ b/ihatemoney/tests/history_test.py @@ -200,6 +200,7 @@ class HistoryTestCase(IhatemoneyTestCase): "what": "fromage à raclette", "payer": user_id, "payed_for": [user_id], + "bill_type": "Expense", "amount": "25", }, follow_redirects=True, @@ -216,6 +217,7 @@ class HistoryTestCase(IhatemoneyTestCase): "what": "fromage à raclette", "payer": user_id, "payed_for": [user_id], + "bill_type": "Expense", "amount": "10", }, follow_redirects=True, @@ -367,6 +369,7 @@ class HistoryTestCase(IhatemoneyTestCase): "what": "fromage à raclette", "payer": 1, "payed_for": [1], + "bill_type": "Expense", "amount": "25", }, follow_redirects=True, @@ -388,6 +391,7 @@ class HistoryTestCase(IhatemoneyTestCase): "what": "new thing", "payer": 1, "payed_for": [1], + "bill_type": "Expense", "amount": "10", }, follow_redirects=True, @@ -486,6 +490,7 @@ class HistoryTestCase(IhatemoneyTestCase): "what": "Bill 1", "payer": 1, "payed_for": [1, 2], + "bill_type": "Expense", "amount": "25", }, ) @@ -496,6 +501,7 @@ class HistoryTestCase(IhatemoneyTestCase): "what": "Bill 2", "payer": 1, "payed_for": [1, 2], + "bill_type": "Expense", "amount": "20", }, ) @@ -514,6 +520,7 @@ class HistoryTestCase(IhatemoneyTestCase): "what": "Bill 1", "payer": 1, "payed_for": [1, 2], + "bill_type": "Expense", "amount": "88", }, ) @@ -555,6 +562,7 @@ class HistoryTestCase(IhatemoneyTestCase): "what": "Bill 1", "payer": 1, "payed_for": [1, 2], + "bill_type": "Expense", "amount": "25", }, ) @@ -579,6 +587,7 @@ class HistoryTestCase(IhatemoneyTestCase): "what": "Bill 2", "payer": 1, "payed_for": [1, 2], + "bill_type": "Expense", "amount": "20", }, ) @@ -641,6 +650,7 @@ class HistoryTestCase(IhatemoneyTestCase): "what": "fromage à raclette", "payer": 1, "payed_for": [1], + "bill_type": "Expense", "amount": "10", "original_currency": "EUR", }, diff --git a/ihatemoney/tests/import_test.py b/ihatemoney/tests/import_test.py index bccb254d..be2b2d81 100644 --- a/ihatemoney/tests/import_test.py +++ b/ihatemoney/tests/import_test.py @@ -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") diff --git a/ihatemoney/tests/main_test.py b/ihatemoney/tests/main_test.py index f0a11d66..b7dbc50f 100644 --- a/ihatemoney/tests/main_test.py +++ b/ihatemoney/tests/main_test.py @@ -127,6 +127,7 @@ class ModelsTestCase(IhatemoneyTestCase): "what": "fromage à raclette", "payer": 1, "payed_for": [1, 2, 3], + "bill_type": "Expense", "amount": "10.0", }, ) @@ -138,6 +139,7 @@ class ModelsTestCase(IhatemoneyTestCase): "what": "red wine", "payer": 2, "payed_for": [1], + "bill_type": "Expense", "amount": "20", }, ) @@ -149,6 +151,7 @@ class ModelsTestCase(IhatemoneyTestCase): "what": "delicatessen", "payer": 1, "payed_for": [1, 2], + "bill_type": "Expense", "amount": "10", }, ) @@ -183,6 +186,7 @@ class ModelsTestCase(IhatemoneyTestCase): "what": "fromage à raclette", "payer": 1, "payed_for": [1, 2, 3], + "bill_type": "Expense", "amount": "10.0", }, ) @@ -194,6 +198,7 @@ class ModelsTestCase(IhatemoneyTestCase): "what": "red wine", "payer": 2, "payed_for": [1], + "bill_type": "Expense", "amount": "20", }, ) @@ -205,6 +210,7 @@ class ModelsTestCase(IhatemoneyTestCase): "what": "delicatessen", "payer": 1, "payed_for": [1, 2], + "bill_type": "Expense", "amount": "10", }, ) diff --git a/ihatemoney/utils.py b/ihatemoney/utils.py index 491eaf07..81eb4326 100644 --- a/ihatemoney/utils.py +++ b/ihatemoney/utils.py @@ -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 diff --git a/ihatemoney/web.py b/ihatemoney/web.py index b94a66d7..f5cd01d5 100644 --- a/ihatemoney/web.py +++ b/ihatemoney/web.py @@ -456,6 +456,7 @@ def import_project(): # Check data attr = [ "amount", + "bill_type", "currency", "date", "owers",