Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
FROM ubuntu:24.04

RUN apt update && apt install python3.12-venv apache2 apache2-utils libapache2-mod-wsgi-py3 python3-pip -y

COPY . /app
WORKDIR /app

RUN /usr/bin/python3.12 -m venv .venv
RUN . .venv/bin/activate && \
.venv/bin/python3.12 -m pip install --upgrade pip && \
.venv/bin/python3.12 -m pip install -r requirements.txt

RUN sed -i -E s"|(DATABASE_BACKEND = )('tiny').*$|\1'mongo'|" app/config.py
RUN sed -i -E s"|(DATABASE_URI = )('/tmp').*$|\1'mongodb://mongo:27017'|" app/config.py
RUN sed -i -E s"|(DATABASE_NAME = )('testdata.json').*$|\1'pynanceparser'|" app/config.py

RUN cp docker/apache2.conf /etc/apache2/sites-available/pynanceparser.conf
RUN a2ensite pynanceparser && a2dissite 000*
RUN ln -sf /dev/stdout /var/log/apache2/access.log && \
ln -sf /dev/stdout /var/log/apache2/error.log

EXPOSE 80

ENTRYPOINT ["/app/docker/entrypoint.sh"]
4 changes: 2 additions & 2 deletions Models.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
'uuid': str, # (generated)
'date_tx': int, # (UTC)
'text_tx': str,
'betrag': float,
'amount': float,
'peer': str,

----------- optional -----------
Expand Down Expand Up @@ -75,7 +75,7 @@ Klassifiziert das Objekt als Regel für das Parsing.

Frei wählbarer Name der Regel.

#### .regex, r-str (optional)
#### .regex, r-str

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.

Expand Down
21 changes: 19 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,24 @@ Analyse und Darstellung von Kontoumsätzen bei mehreren Banken.

## Get Started

### Setup
Am einfachsten mit Docker:

```
git clone https://github.com/Pitastic/PynanceParser.git
cd PynanceParser
docker compose build
docker compose up -d
```

Bei Updates kann das Docker Image neu gebaut und gestartet werden:

```
git pull
docker compose build
docker compose down && docker compose up -d
```

### Standalone non-Docker Setup

```
python3.12 -m venv .venv
Expand Down Expand Up @@ -111,7 +128,7 @@ Für diesen Zweck gibt es die Möglichkeit im Frontend Regeln auszuprobieren, oh

- Erstelle einen Fork des Repositories
- Erstelle Testdaten, auf die die neuen Regeln treffen können
- (am einfachsten ist eine JSON Datei wie `tests/commerzbank.json`)
- (am einfachsten ist eine JSON Datei wie `tests/input_commerzbank.json`)
- Erstelle einen Test wie in (`test_unit_handler_Tags.py`: `test_parsing_regex()`)
- Tests helfen beim entwickeln, können aber auch durch die Maintainer während des Pull Request erstellt werden
- Stelle einen Pull Request
Expand Down
12 changes: 6 additions & 6 deletions app/config.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
#!/usr/bin/python3
"""App Settings zum Zeitpunkt der Initalisierung von PynanceParser"""


# Logging (will also log in webserver logs if used via wsgi)
LOG_ACCESS_FILE = '/tmp/pynance_access.log'
LOG_ERROR_FILE = '/tmp/pynance_error.log'

# Options:
DATABASE_BACKEND = 'tiny'
#DATABASE_BACKEND = 'mongo'
DATABASE_BACKEND = 'tiny' # or 'mongo'

#DATABASE_URI = 'mongodb://testuser:testpassword@localhost:27017' # For mongo (URI)
DATABASE_URI = '/tmp' # For tiny (/path/to/)
# For tiny: Path to the Folder (/path/to)
# For mongo: MongoDB URI
DATABASE_URI = '/tmp' # or 'mongodb://testuser:testpassword@localhost:27017'

# For tiny: Filename ('testdata.json')
# For mongo: Collection name ('testdata')
DATABASE_NAME = 'testdata.json'
DATABASE_NAME = 'testdata.json' # or 'testdata'
22 changes: 12 additions & 10 deletions app/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,15 +57,16 @@ def iban(iban) -> str:
Args (uri):
iban, str: IBAN zu der die Einträge angezeigt werden sollen.
text, str (query): Volltextsuche im Betreff mit RegEx Support
peer, str (query): Volltextsuche im Gegenkonto mit RegEx Support
peer, str (query): Volltextsuche im Gegenkonto mit RegEx Support
startDate, str (query): Startdatum (Y-m-d) für die Anzeige der Einträge
endDate, str (query): Enddatum (Y-m-d) für die Anzeige der Einträge
category, str (query): Kategorie-Filter
tag, str (query): Tag-Filter, einzelner Eintrag oder kommagetrennte Liste
tag_mode, str (query): Vergleichsmodus für Tag-Filter (siehe Models.md)
betrag_min, float (query): Betragsfilter (größer gleich betrag_min)
betrag_max, float (query): Betragsfilter (kleiner gleich betrag_max)
amount_min, float (query): Betragsfilter (größer gleich amount_min)
amount_max, float (query): Betragsfilter (kleiner gleich amount_max)
page, int (query): Seite für die Paginierung (default: 1)
descending, bool (query): Sortierreihenfolge nach Datum (default: True)
Returns:
html: Startseite mit Navigation
"""
Expand All @@ -77,7 +78,8 @@ def iban(iban) -> str:

# Table with Transactions
current_app.logger.debug(f"Using condition filter: {condition}")
rows = parent.db_handler.select(iban, condition)
sort_order = request.args.get('descending', 'true').lower() == 'true'
rows = parent.db_handler.select(iban, condition, descending=sort_order)

# If pagination is requested, do not serve the whole page and all metadata
entries_per_page = 50
Expand Down Expand Up @@ -168,8 +170,8 @@ def show_stats(iban) -> str:
category, str (query): Kategorie-Filter
tag, str (query): Tag-Filter, einzelner Eintrag oder kommagetrennte Liste
tag_mode, str (query): Vergleichsmodus für Tag-Filter (siehe Models.md)
betrag_min, float (query): Betragsfilter (größer gleich betrag_min)
betrag_max, float (query): Betragsfilter (kleiner gleich betrag_max)
amount_min, float (query): Betragsfilter (größer gleich amount_min)
amount_max, float (query): Betragsfilter (kleiner gleich amount_max)
Returns:
html: Seite mit Grafiken und Statistiken über die slektierten Einträge
(IBAN und optional Query)
Expand All @@ -186,12 +188,12 @@ def show_stats(iban) -> str:
# Calculate TOP categories and tags
sums = {'categories': {}, 'tags': {}}
for row in rows:
betrag = row.get('betrag', 0.0)
amount = row.get('amount', 0.0)
cat = row.get('category', 'unkategorisiert')
if cat not in sums['categories']:
sums['categories'][cat] = 0.0

sums['categories'][cat] += betrag
sums['categories'][cat] += amount

tags = row.get('tags', [])
if not tags:
Expand All @@ -200,7 +202,7 @@ def show_stats(iban) -> str:
if tag not in sums['tags']:
sums['tags'][tag] = 0.0

sums['tags'][tag] += betrag
sums['tags'][tag] += amount

# Sort Sums
sums['categories'] = dict(sorted(sums['categories'].items(),
Expand Down Expand Up @@ -419,7 +421,7 @@ def deleteDatabase(iban):
"""
Leert die Datenbank zu einer IBAN
Args (uri):
iban, str: (optional) IBAN zu der die Datenbank geleert werden soll.
iban, str: IBAN zu der die Datenbank geleert werden soll.
(Default: Primäre IBAN aus der Config)
Returns:
json: Informationen zum Ergebnis des Löschauftrags.
Expand Down
18 changes: 10 additions & 8 deletions app/server.py
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ def create_app(config_path: str) -> Flask:
Returns: FlaskApp
"""
# Logging
loglevel = 'DEBUG'
loglevel = 'INFO'
dictConfig({
'version': 1,
'formatters': {'default': {
Expand Down Expand Up @@ -53,10 +53,12 @@ def create_app(config_path: str) -> Flask:

return app

if __name__ == '__main__':
config = os.path.join(
os.path.dirname(os.path.abspath(__file__)),
'config.py'
)
application = create_app(config)
application.run(host='0.0.0.0', port=8110, debug=True)
config = os.path.join(
os.path.dirname(os.path.abspath(__file__)),
'config.py'
)
application = create_app(config)

if __name__ == "__main__":
# Run the application directly if executed as a standalone script
application.run(host='0.0.0.0', port=8000, debug=True)
4 changes: 2 additions & 2 deletions app/static/css/grid.css
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@
grid-template-rows: 1fr 1fr;
gap: 0.25em;
grid-template-areas:
"checkbox dates category betrag"
"checkbox dates category amount"
"button betreff betreff betreff";
padding: 1em 0;
}
Expand All @@ -107,7 +107,7 @@
.transactions tr td:nth-child(4){grid-area: category;}
.transactions tr td:nth-child(6){
padding-right: 0;
grid-area: betrag;
grid-area: amount;
}
.transactions tr td:nth-child(7){
padding-left: 0;
Expand Down
4 changes: 2 additions & 2 deletions app/static/css/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ button.info {
}

/* Tables */
.transactions .betrag {
.transactions .amount {
white-space: nowrap;
text-align: right;
}
Expand All @@ -108,7 +108,7 @@ button.info {

/* S : Slim button in transaction table */
@media (max-width: 767px) {
.transactions th.betrag {
.transactions th.amount {
text-align: center;
}
.transactions tr {
Expand Down
22 changes: 14 additions & 8 deletions app/static/js/functions.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,17 +74,23 @@ function getFilteredList() {
}
}

let betrag_min = document.getElementById('filter-betrag-min').value;
if (betrag_min) {
betrag_min = betrag_min.replace(',', '.');
query_args = query_args + arg_concat + 'betrag_min=' + betrag_min;
let amount_min = document.getElementById('filter-amount-min').value;
if (amount_min) {
amount_min = amount_min.replace(',', '.');
query_args = query_args + arg_concat + 'amount_min=' + amount_min;
arg_concat = '&';
}

let betrag_max = document.getElementById('filter-betrag-max').value;
if (betrag_max) {
betrag_max = betrag_max.replace(',', '.');
query_args = query_args + arg_concat + 'betrag_max=' + betrag_max;
let amount_max = document.getElementById('filter-amount-max').value;
if (amount_max) {
amount_max = amount_max.replace(',', '.');
query_args = query_args + arg_concat + 'amount_max=' + amount_max;
arg_concat = '&';
}

let sort_order = document.getElementById('filter-descending').value;
if (sort_order) {
query_args = query_args + arg_concat + 'descending=' + sort_order;
arg_concat = '&';
}

Expand Down
2 changes: 1 addition & 1 deletion app/static/js/iban.js
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ function fillTxDetails(result) {
td.appendChild(a_link);
}

} else if (key == 'betrag') {
} else if (key == 'amount') {
// Round
td.innerHTML = r[key].toFixed(2);

Expand Down
4 changes: 2 additions & 2 deletions app/templates/iban.html
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ <h3>
<th>Buchungstext</th>
<th>Kategorie</th>
<th class="hide-m">Tags</th>
<th class="betrag">Betrag</th>
<th class="amount">Betrag</th>
<th>&nbsp;</th>
</tr>
</thead>
Expand Down Expand Up @@ -208,7 +208,7 @@ <h2>Transaktions Details</h2>
</tr>
<tr>
<th>Betrag</th>
<td class="betrag"></td>
<td class="amount"></td>
</tr>
<tr>
<th>Währung:</th>
Expand Down
24 changes: 14 additions & 10 deletions app/templates/macros.html
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@
</a>
{% endfor %}
</td>
<td class="betrag" dir="ltr">
{{ "%.2f"|format(transaction.betrag|round(2)) }}
<td class="amount" dir="ltr">
{{ "%.2f"|format(transaction.amount|round(2)) }}
<small class="secondary">{{ transaction.currency }}</small>
</td>
<td>
Expand All @@ -55,11 +55,11 @@
<article>
<header>
<button rel="prev" aria-label="Close" data-target="filter-popup" onclick="toggleModal(event)"></button>
<h2>Transaktions Details</h2>
<h2>Transaktionen filtern</h2>
</header>

<label>
Datum
Datum / Reihenfolge
<div class="grid">
<input type="date" id="filter-range-start" placeholder="Start Date: d.m.Y"
{% if 'startDate' in filters %}
Expand All @@ -75,6 +75,10 @@ <h2>Transaktions Details</h2>
value=""
{% endif %}
/>
<select id="filter-descending">
<option value="true" {{"selected" if not filters.descending or filters.descending == 'true'}}>absteigend</option>
<option value="false" {{"selected" if filters.descending and filters.descending == 'false'}}>aufsteigend</option>
</select>
</div>
</label>
<label>
Expand Down Expand Up @@ -125,15 +129,15 @@ <h2>Transaktions Details</h2>
<label>
Betrag
<div class="grid">
<input type="number" step="0.01" id="filter-betrag-min" placeholder="Betrag (min)"
{% if "betrag_min" in filters %}
value="{{filters['betrag_min']}}" aria-invalid="false" />
<input type="number" step="0.01" id="filter-amount-min" placeholder="Betrag (min)"
{% if "amount_min" in filters %}
value="{{filters['amount_min']}}" aria-invalid="false" />
{% else %}
value="" />
{% endif %}
<input type="number" step="0.01" id="filter-betrag-max" placeholder="Betrag (max)"
{% if "betrag_max" in filters %}
value="{{filters['betrag_max']}}" aria-invalid="false" />
<input type="number" step="0.01" id="filter-amount-max" placeholder="Betrag (max)"
{% if "amount_max" in filters %}
value="{{filters['amount_max']}}" aria-invalid="false" />
{% else %}
value="" />
{% endif %}
Expand Down
8 changes: 4 additions & 4 deletions app/templates/stats.html
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,10 @@ <h4>Top Beträge</h4>
<h5>Kategorien</h5>
<table class="ranking">
<tbody>
{% for c, betrag in sums.categories.items() %}
{% for c, amount in sums.categories.items() %}
<tr>
<td>{{c}}</td>
<td>{{"%.2f"|format(betrag|round(2))}}</td>
<td>{{"%.2f"|format(amount|round(2))}}</td>
</tr>
{% endfor %}
</tbody>
Expand All @@ -46,10 +46,10 @@ <h5>Kategorien</h5>
<h5>Tags</h5>
<table class="ranking">
<tbody>
{% for t, betrag in sums.tags.items() %}
{% for t, amount in sums.tags.items() %}
<tr>
<td>{{t}}</td>
<td>{{"%.2f"|format(betrag|round(2))}}</td>
<td>{{"%.2f"|format(amount|round(2))}}</td>
</tr>
{% endfor %}
</tbody>
Expand Down
Loading