mirror of
https://github.com/spiral-project/ihatemoney.git
synced 2025-05-06 05:01:48 +02:00
added tagging functionality and filtering by date, amount, payer, and tag
This commit is contained in:
parent
026a072235
commit
f2e992cb9e
6 changed files with 97 additions and 3 deletions
|
@ -1,7 +1,7 @@
|
|||
from flask_wtf.form import FlaskForm
|
||||
from wtforms.fields.core import SelectField, SelectMultipleField
|
||||
from wtforms.fields.html5 import DateField, DecimalField, URLField
|
||||
from wtforms.fields.simple import PasswordField, SubmitField, StringField, BooleanField
|
||||
from wtforms.fields.simple import PasswordField, SubmitField, StringField, BooleanField, HiddenField
|
||||
from wtforms.validators import (
|
||||
Email,
|
||||
DataRequired,
|
||||
|
@ -199,6 +199,7 @@ class ResetPasswordForm(FlaskForm):
|
|||
class BillForm(FlaskForm):
|
||||
date = DateField(_("Date"), validators=[DataRequired()], default=datetime.now)
|
||||
what = StringField(_("What?"), validators=[DataRequired()])
|
||||
tag = HiddenField(_("Tag"), default="")
|
||||
payer = SelectField(_("Payer"), validators=[DataRequired()], coerce=int)
|
||||
amount = CalculatorStringField(_("Amount paid"), validators=[DataRequired()])
|
||||
external_link = URLField(
|
||||
|
@ -216,6 +217,9 @@ class BillForm(FlaskForm):
|
|||
bill.payer_id = self.payer.data
|
||||
bill.amount = self.amount.data
|
||||
bill.what = self.what.data
|
||||
tag = list(set(part[1:] for part in bill.what.split() if part.startswith('#')))
|
||||
if tag:
|
||||
bill.tag = tag[0]
|
||||
bill.external_link = self.external_link.data
|
||||
bill.date = self.date.data
|
||||
bill.owers = [Person.query.get(ower, project) for ower in self.payed_for.data]
|
||||
|
@ -225,6 +229,7 @@ class BillForm(FlaskForm):
|
|||
bill.payer_id = self.payer
|
||||
bill.amount = self.amount
|
||||
bill.what = self.what
|
||||
bill.tag = self.tag
|
||||
bill.external_link = ""
|
||||
bill.date = self.date
|
||||
bill.owers = [Person.query.get(ower, project) for ower in self.payed_for]
|
||||
|
@ -235,6 +240,7 @@ class BillForm(FlaskForm):
|
|||
self.payer.data = bill.payer_id
|
||||
self.amount.data = bill.amount
|
||||
self.what.data = bill.what
|
||||
self.tag.data = bill.tag
|
||||
self.external_link.data = bill.external_link
|
||||
self.date.data = bill.date
|
||||
self.payed_for.data = [int(ower.id) for ower in bill.owers]
|
||||
|
|
|
@ -434,6 +434,7 @@ class Bill(db.Model):
|
|||
date = db.Column(db.Date, default=datetime.now)
|
||||
creation_date = db.Column(db.Date, default=datetime.now)
|
||||
what = db.Column(db.UnicodeText)
|
||||
tag = db.Column(db.UnicodeText)
|
||||
external_link = db.Column(db.UnicodeText)
|
||||
|
||||
archive = db.Column(db.Integer, db.ForeignKey("archive.id"))
|
||||
|
@ -448,6 +449,7 @@ class Bill(db.Model):
|
|||
"date": self.date,
|
||||
"creation_date": self.creation_date,
|
||||
"what": self.what,
|
||||
"tag": self.tag,
|
||||
"external_link": self.external_link,
|
||||
}
|
||||
|
||||
|
|
|
@ -174,6 +174,14 @@ body {
|
|||
width: 5em;
|
||||
}
|
||||
|
||||
#bill-search {
|
||||
color: white;
|
||||
background: #8f9296;
|
||||
margin-top: 10px;
|
||||
border-radius: .25rem;
|
||||
padding: .375rem .75rem;
|
||||
}
|
||||
|
||||
.invites textarea {
|
||||
width: 800px;
|
||||
height: 100px;
|
||||
|
|
|
@ -17,6 +17,44 @@
|
|||
});
|
||||
});
|
||||
|
||||
// remove duplicate tags
|
||||
var usedTags = {};
|
||||
$("select[name='tag-search-select'] > option").each(function() {
|
||||
if (usedTags[this.text]) {
|
||||
$(this).remove();
|
||||
} else {
|
||||
usedTags[this.text] = this.value;
|
||||
}
|
||||
});
|
||||
|
||||
// add default values to payer select
|
||||
$('#payer-search-select').prepend(new Option('', '', true, true));
|
||||
|
||||
// add default values to date select
|
||||
$('#date-search-select').prepend(new Option('', '', true, true));
|
||||
|
||||
$('#btn-bill-filter').click(function() {
|
||||
var dateValue = $('#date-search-select').val();
|
||||
var payerValue = $('#payer-search-select option:selected').val();
|
||||
var amountValue = $('#amount-search-select').val();
|
||||
if (amountValue.substr(amountValue.length - 3) === '.00') {
|
||||
amountValue = amountValue.substr(0, amountValue.length - 3)
|
||||
}
|
||||
var amountWithZero = amountValue + ".0"
|
||||
var tagValue = $('#tag-search-select').val();
|
||||
|
||||
var matching = $('#bill_table tbody tr').filter(function(){
|
||||
return (payerValue != "" && $(this).attr('payer') !== payerValue) || $(this).attr('date') !== dateValue || (amountValue != "" && $(this).attr('amount') !== amountValue && $(this).attr('amount') !== amountWithZero) || (tagValue != "" && $(this).attr('tag') !== tagValue);
|
||||
});
|
||||
|
||||
matching.hide();
|
||||
$('#bill_table tbody tr').not(matching).show(200);
|
||||
});
|
||||
|
||||
$('#btn-bill-showall').click(function() {
|
||||
$('#bill_table tbody tr').show(200);
|
||||
});
|
||||
|
||||
var highlight_owers = function(){
|
||||
var ower_ids = $(this).attr("owers").split(',');
|
||||
var payer_id = $(this).attr("payer");
|
||||
|
@ -110,11 +148,38 @@
|
|||
{% if bills.total > 0 %}
|
||||
<div class="clearfix"></div>
|
||||
|
||||
<div id="bill-search">
|
||||
<span>Filter by:</span>
|
||||
<form class="form-inline mt-2">
|
||||
<div class="form-group mr-sm-2">
|
||||
<label for="date-select" class="mr-sm-2">{{ _("Date") }}</label>
|
||||
{{ bill_form.date(id="date-search-select", class="form-control custom-select", placeholder='') | safe }}
|
||||
<label for="payer-select" class="mr-sm-2" style="margin-left: 8px">{{ _("Payer") }}</label>
|
||||
{{ bill_form.payer(id="payer-search-select", class="form-control custom-select", placeholder='') | safe }}
|
||||
<label for="amount-select" class="mr-sm-2" style="margin-left: 8px">{{ _("Amount") }}</label>
|
||||
{{ bill_form.amount(id="amount-search-select", class="form-control", style="width: 100px", placeholder='') | safe }}
|
||||
<label for="tag-select" class="mr-sm-2" style="margin-left: 8px">{{ _("Tag") }}</label>
|
||||
<select name="tag-search-select" id="tag-search-select" style="height: 38px; margin-right: 8px">
|
||||
<option value=""></option>
|
||||
{% for bill in bills.items %}
|
||||
{% if bill.tag %}
|
||||
<option value="{{bill.tag}}">{{bill.tag}}</option>
|
||||
{% endif %}
|
||||
{% endfor %}}
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<button id="btn-bill-filter" type="button" class="btn btn-secondary mr-sm-2">{{ _("Apply Filter") }}</button>
|
||||
<button id="btn-bill-showall" type="button" class="btn btn-secondary">{{ _("Show All") }}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<table id="bill_table" class="col table table-striped table-hover table-responsive-sm">
|
||||
<thead><tr><th>{{ _("When?") }}</th><th>{{ _("Who paid?") }}</<th><th>{{ _("For what?") }}</th><th>{{ _("For whom?") }}</th><th>{{ _("How much?") }}</th><th>{{ _("Actions") }}</th></tr></thead>
|
||||
<tbody>
|
||||
{% for bill in bills.items %}
|
||||
<tr owers="{{bill.owers|join(',','id')}}" payer="{{bill.payer.id}}">
|
||||
<tr owers="{{bill.owers|join(',','id')}}" payer="{{bill.payer.id}}" date="{{bill.date}}" amount="{{bill.amount}}" tag="{{bill.tag}}">
|
||||
<td>
|
||||
<span data-toggle="tooltip" data-placement="top"
|
||||
title="{{ _('Added on %(date)s', date=bill.creation_date if bill.creation_date else bill.date) }}">
|
||||
|
|
|
@ -1215,6 +1215,7 @@ class BudgetTestCase(IhatemoneyTestCase):
|
|||
{
|
||||
"date": "2017-01-01",
|
||||
"what": "refund",
|
||||
"tag": "",
|
||||
"amount": 13.33,
|
||||
"payer_name": "tata",
|
||||
"payer_weight": 1.0,
|
||||
|
@ -1223,6 +1224,7 @@ class BudgetTestCase(IhatemoneyTestCase):
|
|||
{
|
||||
"date": "2016-12-31",
|
||||
"what": "red wine",
|
||||
"tag": "",
|
||||
"amount": 200.0,
|
||||
"payer_name": "fred",
|
||||
"payer_weight": 1.0,
|
||||
|
@ -1231,6 +1233,7 @@ class BudgetTestCase(IhatemoneyTestCase):
|
|||
{
|
||||
"date": "2016-12-31",
|
||||
"what": "fromage a raclette",
|
||||
"tag": "",
|
||||
"amount": 10.0,
|
||||
"payer_name": "alexis",
|
||||
"payer_weight": 2.0,
|
||||
|
@ -1290,6 +1293,7 @@ class BudgetTestCase(IhatemoneyTestCase):
|
|||
data={
|
||||
"date": "2016-12-31",
|
||||
"what": "red wine",
|
||||
"tag": "",
|
||||
"payer": 2,
|
||||
"payed_for": [1, 3],
|
||||
"amount": "200",
|
||||
|
@ -1300,6 +1304,7 @@ class BudgetTestCase(IhatemoneyTestCase):
|
|||
{
|
||||
"date": "2017-01-01",
|
||||
"what": "refund",
|
||||
"tag": "",
|
||||
"amount": 13.33,
|
||||
"payer_name": "tata",
|
||||
"payer_weight": 1.0,
|
||||
|
@ -1308,6 +1313,7 @@ class BudgetTestCase(IhatemoneyTestCase):
|
|||
{ # This expense does not have to be present twice.
|
||||
"date": "2016-12-31",
|
||||
"what": "red wine",
|
||||
"tag": "",
|
||||
"amount": 200.0,
|
||||
"payer_name": "fred",
|
||||
"payer_weight": 1.0,
|
||||
|
@ -1316,6 +1322,7 @@ class BudgetTestCase(IhatemoneyTestCase):
|
|||
{
|
||||
"date": "2016-12-31",
|
||||
"what": "fromage a raclette",
|
||||
"tag": "",
|
||||
"amount": 10.0,
|
||||
"payer_name": "alexis",
|
||||
"payer_weight": 2.0,
|
||||
|
@ -1380,6 +1387,7 @@ class BudgetTestCase(IhatemoneyTestCase):
|
|||
{ # amount missing
|
||||
"date": "2017-01-01",
|
||||
"what": "refund",
|
||||
"tag": "",
|
||||
"payer_name": "tata",
|
||||
"payer_weight": 1.0,
|
||||
"owers": ["fred"],
|
||||
|
@ -1784,6 +1792,7 @@ class APITestCase(IhatemoneyTestCase):
|
|||
}
|
||||
|
||||
got = json.loads(req.data.decode("utf-8"))
|
||||
del got["tag"]
|
||||
self.assertEqual(
|
||||
datetime.date.today(),
|
||||
datetime.datetime.strptime(got["creation_date"], "%Y-%m-%d").date(),
|
||||
|
@ -1858,6 +1867,7 @@ class APITestCase(IhatemoneyTestCase):
|
|||
datetime.datetime.strptime(got["creation_date"], "%Y-%m-%d").date(),
|
||||
)
|
||||
del got["creation_date"]
|
||||
del got["tag"]
|
||||
self.assertDictEqual(expected, got)
|
||||
|
||||
# delete a bill
|
||||
|
@ -1934,6 +1944,7 @@ class APITestCase(IhatemoneyTestCase):
|
|||
datetime.datetime.strptime(got["creation_date"], "%Y-%m-%d").date(),
|
||||
)
|
||||
del got["creation_date"]
|
||||
del got["tag"]
|
||||
self.assertDictEqual(expected, got)
|
||||
|
||||
# should raise errors
|
||||
|
@ -2075,6 +2086,7 @@ class APITestCase(IhatemoneyTestCase):
|
|||
datetime.datetime.strptime(got["creation_date"], "%Y-%m-%d").date(),
|
||||
)
|
||||
del got["creation_date"]
|
||||
del got["tag"]
|
||||
self.assertDictEqual(expected, got)
|
||||
|
||||
# getting it should return a 404
|
||||
|
|
|
@ -426,7 +426,7 @@ def import_project(file, project):
|
|||
json_file = json.load(file)
|
||||
|
||||
# Check if JSON is correct
|
||||
attr = ["what", "payer_name", "payer_weight", "amount", "date", "owers"]
|
||||
attr = ["what", "tag", "payer_name", "payer_weight", "amount", "date", "owers"]
|
||||
attr.sort()
|
||||
for e in json_file:
|
||||
if len(e) != len(attr):
|
||||
|
@ -483,6 +483,7 @@ def import_project(file, project):
|
|||
bill = Bill()
|
||||
form = get_billform_for(project)
|
||||
form.what = b["what"]
|
||||
form.tag = b["tag"]
|
||||
form.amount = b["amount"]
|
||||
form.date = parse(b["date"])
|
||||
form.payer = id_dict[b["payer_name"]]
|
||||
|
|
Loading…
Reference in a new issue