Skip to content

Commit 84401f0

Browse files
authored
Merge pull request #60 from Pitastic/more-rules
More rules
2 parents 43c39d3 + d5cb6e5 commit 84401f0

25 files changed

Lines changed: 790 additions & 150 deletions

.github/workflows/python-app.yml

Lines changed: 60 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,73 @@
22
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python
33

44
name: Python application
5-
65
on:
76
workflow_dispatch:
87
push:
98
branches: [ "master" ]
109
pull_request:
1110
branches: [ "master" ]
12-
1311
permissions:
1412
contents: read
15-
1613
jobs:
14+
PyTest_TinyDB:
15+
runs-on: ubuntu-latest
16+
steps:
17+
- uses: actions/checkout@v3
18+
- name: Set up Python 3.12
19+
uses: actions/setup-python@v3
20+
with:
21+
python-version: "3.12"
22+
- name: Install dependencies
23+
run: |
24+
python -m pip install --upgrade pip
25+
pip install pytest
26+
pip install -r requirements.txt
27+
pip install -r tests/requirements.txt
28+
- name: Test with pytest
29+
run: |
30+
set +e
1731
18-
PyTest:
32+
test_output="$(PYTHONPATH=. pytest -rN)"
33+
exitcode=$?
34+
35+
test_failed=$(sed -n '$s/^=*\s*\([0-9]*\)\sfailed.*/\1/p' <<< "$test_output" |tail -n1)
36+
if [[ -z $test_failed ]]; then test_failed=0 ; fi
37+
38+
test_passed=$(sed -n -E 's|^=*\s([0-9]+\sfailed,\s)?(([0-9]*)\spassed,\s)?.*|\3|p' <<< "$test_output" | tail -n1)
39+
if [[ -z $test_passed ]]; then test_passed=0 ; fi
1940
20-
runs-on: ubuntu-latest
41+
test_skipped=$(tail -n1 <<< "$test_output" | rev |sed -E 's|^(.*deppiks\s([0-9]*))?.*|\2|')
42+
if [[ -z $test_skipped ]]; then echo test_skipped=0 ; fi
2143
44+
all_tests=$(expr ${test_passed} + ${test_failed})
45+
46+
echo "### Results (with TinyDB)" >> $GITHUB_STEP_SUMMARY
47+
echo "" >> $GITHUB_STEP_SUMMARY
48+
echo "| :checkered_flag: | :arrow_right_hook: | :x: |" >> $GITHUB_STEP_SUMMARY
49+
echo "| ------------- | ------------- | ------------- |" >> $GITHUB_STEP_SUMMARY
50+
echo "| $test_passed | $test_skipped | $test_failed |" >> $GITHUB_STEP_SUMMARY
51+
52+
echo "$test_output"
53+
if [[ $exitcode != 0 ]]; then
54+
exit $exitcode
55+
fi
56+
exit 0
57+
PyTest_MongoDB:
58+
runs-on: ubuntu-latest
59+
services:
60+
mongodb:
61+
image: mongo:8.2.4
62+
env:
63+
MONGO_INITDB_ROOT_USERNAME: testuser
64+
MONGO_INITDB_ROOT_PASSWORD: testpassword
65+
ports:
66+
- 27017:27017
67+
options: >-
68+
--health-cmd "echo 'db.runCommand("ping").ok' | mongosh localhost:27017/test --quiet"
69+
--health-interval 20s
70+
--health-timeout 5s
71+
--health-retries 5
2272
steps:
2373
- uses: actions/checkout@v3
2474
- name: Set up Python 3.12
@@ -35,6 +85,10 @@ jobs:
3585
run: |
3686
set +e
3787
88+
sed -i -E s"|(DATABASE_BACKEND = )('tiny').*$|\1'mongo'|m" tests/config.py
89+
sed -i -E s"|(DATABASE_URI = )('/tmp.*').*$|\1'mongodb://testuser:testpassword@localhost:27017'|" tests/config.py
90+
sed -i -E s"|(DATABASE_NAME = )('testdata.json').*$|\1'testdata'|m" tests/config.py
91+
3892
test_output="$(PYTHONPATH=. pytest -rN)"
3993
exitcode=$?
4094
@@ -49,7 +103,7 @@ jobs:
49103
50104
all_tests=$(expr ${test_passed} + ${test_failed})
51105
52-
echo "### Results" >> $GITHUB_STEP_SUMMARY
106+
echo "### Results (with MongoDB)" >> $GITHUB_STEP_SUMMARY
53107
echo "" >> $GITHUB_STEP_SUMMARY
54108
echo "| :checkered_flag: | :arrow_right_hook: | :x: |" >> $GITHUB_STEP_SUMMARY
55109
echo "| ------------- | ------------- | ------------- |" >> $GITHUB_STEP_SUMMARY
@@ -60,11 +114,8 @@ jobs:
60114
exit $exitcode
61115
fi
62116
exit 0
63-
64117
PyLint:
65-
66118
runs-on: ubuntu-latest
67-
68119
steps:
69120
- name: Checkout repository
70121
uses: actions/checkout@v3

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,4 +160,4 @@ cython_debug/
160160
# End of https://www.toptal.com/developers/gitignore/api/visualstudiocode,python
161161

162162
# Credentials:
163-
config.conf
163+
settings/*/0[^0]*.json

Models.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ Frei wählbarer Name der Regel.
7777

7878
#### .regex, r-str
7979

80-
Regex String, der auf den Buchungstext angewendet werden soll. Er muss genau eine Matching-Group enthalten. Der Wert dieses Treffers (der Gruppe) wird als Wert mit dem Namen der Regel in der Transaktion als Ergebnis gespeichert.
80+
Regex String, der auf den Buchungstext angewendet werden soll. Es wird immer die erste Matching-Group übernommen. Der Wert dieses Treffers (der Gruppe) wird als Wert mit dem Namen der Regel in der Transaktion als Ergebnis gespeichert.
8181

8282
## Rule Objects
8383

@@ -157,6 +157,7 @@ Dabei haben die Operatoren folgende Bedeutung:
157157
- `in`: Mindestens ein Wert der Vergleichsliste muss in dem Listenwert aus der Datenbank vorkommen.
158158
- `all`: Alle Werte der Vergleichsliste müssen in dem Listenwert aus der Datenbank vorkommen.
159159
- `notin`: Kein Wert der Vergleichsliste darf in dem Listenwert aus der Datenbank vorkommen.
160+
- `exact` : Alle Werte der Vergleichsliste und keine anderen müssen in dem Listenwert aus der Datenbank vorkommen (unabhängig von der Reihenfolge).
160161
- `regex`: Regex String, der auf den Buchungstext angewendet werden soll. Ein Teil-Treffer des RegExes wird als Treffer gewertet.
161162

162163
#### .tags, list (nur bei metatype: `rule`)

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,12 @@ Daher sollte man beachten:
119119

120120
In diesem Repository werden nur Basis-Regeln mitgeliefert, da speziellere und genauere Regeln sehr individuell auf einzelne Personen zugeschnitten sind. So schreibt zum Beispiel eine Versicherung die Versichertennummer mit in die Abbuchungen, was einen sehr guten Tagging-Indikator darstellt, jedoch nur für einen speziellen Nutzer dieses Programms. Das schreiben eigener Regeln ist daher unumgänglich, um bessere Ergebnisse zu erzielen.
121121

122+
### Wahl der Datenbankengine
123+
124+
TinyDB sollte nur bei kleinen Instanzen mit einzelnen Benutzern gewählt werden. Ein paralleler Zugriff ist mit PynanceParser zwar möglich, allerdings sinkt die Performance und die Fehleranfälligkeit steigt mit der Anzahl der Requests und der Anzahl der Einträge in der Datenbank. Insbesondere bei I/O-schwacher Hardware (z.B. Raspberry mit SD Karte) kann es schnell zum Crash des Servers kommen.
125+
126+
Für die produktive Nutzung wird MongoDB daher empfohlen!
127+
122128
## Anpassungen / Contribution
123129

124130
**You're Welcome !** :tada:

app/routes.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ def welcome() -> str:
101101
ibans = parent.db_handler.list_ibans()
102102
groups = parent.db_handler.list_groups()
103103
meta = parent.db_handler.filter_metadata(condition=None)
104+
meta.sort(key=lambda m: (m.get('metatype'), m.get('name')))
104105
return render_template('index.html', ibans=ibans, groups=groups, meta=meta)
105106

106107
@current_app.route('/<iban>', methods=['GET'])
@@ -159,6 +160,8 @@ def iban(iban) -> str:
159160
if t not in tags:
160161
tags.append(t)
161162

163+
tags.sort()
164+
162165
# All distinct Categories
163166
# (must be filtered on our own because TinyDB doesn't support 'distinct' queries)
164167
cats = []
@@ -168,6 +171,8 @@ def iban(iban) -> str:
168171
if c and c not in cats:
169172
cats.append(c)
170173

174+
cats.sort()
175+
171176
return render_template('iban.html', transactions=rows[:entries_per_page],
172177
IBAN=iban, tags=tags, categories=cats,
173178
rules=rulenames, filters=frontend_filters)
@@ -446,7 +451,6 @@ def uploadRules(metadata):
446451
Returns:
447452
json: Informationen zur Datei und Ergebnis der Untersuchung.
448453
"""
449-
print(request.files)
450454
input_file = request.files.get('settings-input')
451455
if not input_file:
452456
return {'error': 'Es wurde keine Datei übermittelt.'}, 400
@@ -553,7 +557,7 @@ def tag_and_cat(iban) -> dict:
553557
iban,
554558
category=custom_rule.get('category'),
555559
tags=custom_rule.get('tags'),
556-
filters=custom_rule.get('filters'),
560+
filters=custom_rule.get('filter'),
557561
parsed_keys=list(custom_rule.get('parsed', {}).keys()),
558562
parsed_vals=list(custom_rule.get('parsed', {}).values()),
559563
multi=custom_rule.get('multi', 'AND'),

app/static/js/functions.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,10 +64,10 @@ function getFilteredList() {
6464
}
6565

6666
const tags = document.getElementById('filter-tag-result').value;
67-
if (tags) {
67+
const tag_mode = document.getElementById('filter-tag-mode').value;
68+
if (tags || tag_mode == 'exact') {
6869
query_args = query_args + arg_concat + 'tags=' + tags;
6970
arg_concat = '&';
70-
const tag_mode = document.getElementById('filter-tag-mode').value;
7171
if (tag_mode) {
7272
query_args = query_args + arg_concat + 'tag_mode=' + tag_mode;
7373
arg_concat = '&';

app/static/js/iban.js

Lines changed: 38 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,15 @@
11
"use strict";
22

3+
const selectAllCheckbox = document.getElementById('select-all');
34
let ROW_CHECKBOXES = null;
45
let PAGE = 1;
56

67
document.addEventListener('DOMContentLoaded', function () {
78

89
// enabling/disabling the edit button based on checkbox selection
9-
const selectAllCheckbox = document.getElementById('select-all');
1010
ROW_CHECKBOXES = document.querySelectorAll('.row-checkbox');
11-
12-
selectAllCheckbox.addEventListener('change', function () {
13-
ROW_CHECKBOXES.forEach(checkbox => {
14-
checkbox.checked = selectAllCheckbox.checked;
15-
});
16-
updateEditButtonState();
17-
listTxElements();
18-
});
19-
20-
ROW_CHECKBOXES.forEach(checkbox => {
21-
checkbox.checked = false;
22-
checkbox.addEventListener('change', function () {
23-
if (!this.checked) {
24-
selectAllCheckbox.checked = false;
25-
} else if (Array.from(ROW_CHECKBOXES).every(cb => cb.checked)) {
26-
selectAllCheckbox.checked = true;
27-
}
28-
updateEditButtonState();
29-
listTxElements();
30-
});
31-
});
11+
selectAllCheckbox.addEventListener('change', set_all_checkboxes);
12+
ROW_CHECKBOXES.forEach(checkbox => set_row_checkboxes(checkbox));
3213

3314
// Tag Chip Bullets
3415
const inputTagContainers = [
@@ -68,6 +49,35 @@ document.addEventListener('DOMContentLoaded', function () {
6849
// -- DOM Functions -----------------------------------------------------------
6950
// ----------------------------------------------------------------------------
7051

52+
/**
53+
* Set all checkboxes to the state of the headerbox
54+
*/
55+
function set_all_checkboxes() {
56+
ROW_CHECKBOXES.forEach(checkbox => {
57+
checkbox.checked = this.checked;
58+
});
59+
updateEditButtonState();
60+
listTxElements();
61+
}
62+
63+
/**
64+
* Set an eventlistener for every box and change the header when unselected
65+
*
66+
* @param {DOMElement} checkbox
67+
*/
68+
function set_row_checkboxes(checkbox){
69+
checkbox.checked = false;
70+
checkbox.addEventListener('change', function () {
71+
if (!this.checked) {
72+
selectAllCheckbox.checked = false;
73+
} else if (Array.from(ROW_CHECKBOXES).every(cb => cb.checked)) {
74+
selectAllCheckbox.checked = true;
75+
}
76+
updateEditButtonState();
77+
listTxElements();
78+
});
79+
}
80+
7181
/**
7282
* Clears information from a result Box
7383
*
@@ -384,6 +394,12 @@ function loadMore() {
384394

385395
// Append new Rows
386396
document.querySelector('.transactions tbody').innerHTML += responseText;
397+
398+
// enabling/disabling the edit button based on checkbox selection
399+
const selectAllCheckbox = document.getElementById('select-all');
400+
ROW_CHECKBOXES = document.querySelectorAll('.row-checkbox');
401+
selectAllCheckbox.addEventListener('change', set_all_checkboxes);
402+
ROW_CHECKBOXES.forEach(checkbox => set_row_checkboxes(checkbox));
387403
});
388404

389405
// Call URI

app/templates/iban.html

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,10 @@ <h2>Transaktions Details</h2>
218218
<th>Art:</th>
219219
<td class="art"></td>
220220
</tr>
221+
<tr>
222+
<th>Konto:</th>
223+
<td class="iban"></td>
224+
</tr>
221225
<tr>
222226
<th>Gegenkonto:</th>
223227
<td class="peer"></td>

app/templates/macros.html

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
{{ transaction.date_tx | ctime }}
2020
<small class="secondary">({{ transaction.valuta | ctime }})</small>
2121
</td>
22-
<td>{{ transaction.text_tx[:90] }}{% if transaction.text_tx|length > 60 %} ...{%endif%}</td>
22+
<td>{{ transaction.text_tx[:90] }}{% if transaction.text_tx|length > 90 %} ...{%endif%}</td>
2323
<td>
2424
{% if transaction.category %}
2525
<a class="tag-chip category {{generate_class(transaction.category)}}" href="?category={{transaction.category}}" data-tooltip="Add Filter">
@@ -123,6 +123,7 @@ <h2>Transaktionen filtern</h2>
123123
<option value="in" {{"selected" if filters.tag_mode and filters.tag_mode == 'in'}}>eines davon</option>
124124
<option value="all" {{"selected" if filters.tag_mode and filters.tag_mode == 'all'}}>alle davon</option>
125125
<option value="notin" {{"selected" if filters.tag_mode and filters.tag_mode == 'notin'}}>keine davon</option>
126+
<option value="exact" {{"selected" if filters.tag_mode and filters.tag_mode == 'exact'}}>genau diese</option>
126127
</select>
127128
</div>
128129
</label>

app/templates/tx.html

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,6 @@ <h3>
3535
</tr>
3636
</thead>
3737
<tbody>
38-
<tr>
39-
<td>Art:</td>
40-
<td>{{tx.art}}</td>
41-
</tr>
4238
<tr>
4339
<td>Datum (Wertstellnug)</td>
4440
<td>{{ tx.valuta | ctime }}</td>
@@ -47,9 +43,21 @@ <h3>
4743
<td>Datum (Transaktion)</td>
4844
<td>{{ tx.date_tx | ctime }}</td>
4945
</tr>
46+
<tr>
47+
<td>Art:</td>
48+
<td>{{tx.art}}</td>
49+
</tr>
50+
<tr>
51+
<td>Konto:</td>
52+
<td>{{tx.iban}}</td>
53+
</tr>
54+
<tr>
55+
<td>Gegenkonto:</td>
56+
<td>{{tx.gegenkonto}}</td>
57+
</tr>
5058
<tr>
5159
<td>Betrag</td>
52-
<td>{{tx.amount}} {{tx.currency}}</td>
60+
<td>{{ "%.2f"|format(tx.amount|round(2)) }} {{tx.currency}}</td>
5361
</tr>
5462
<tr>
5563
<td>Buchungstext</td>

0 commit comments

Comments
 (0)