Skip to content
Open
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
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,8 @@ Beim ersten Start wird die Datenbank mit einigen Beispielkonten angelegt.
* Gewinn- und Verlustrechnung
* Bilanz
* Umsatzsteuervoranmeldung
* Modernes, responsives Frontend mit Vue unter `/vue_app`

Das Layout verwendet ein Bootswatch-Theme, wodurch die Anwendung auch auf Handy und Tablet gut aussieht.

Die Umsetzung dient nur als Demonstration und ist nicht für den produktiven Einsatz geeignet.
37 changes: 36 additions & 1 deletion app/routes.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
from flask import render_template, request, redirect, url_for
from flask import render_template, request, redirect, url_for, jsonify
from . import app, db
from .models import Account, Entry

@app.route('/')
def index():
return render_template('index.html')

@app.route('/vue_app')
def vue_app():
return render_template('vue_app.html')

@app.route('/accounts')
def accounts():
accounts = Account.query.order_by(Account.number).all()
Expand All @@ -26,6 +30,37 @@ def entries():
entries = Entry.query.order_by(Entry.entry_date.desc()).all()
return render_template('entries.html', entries=entries, accounts=accounts)

@app.route('/api/accounts')
def api_accounts():
accounts = Account.query.order_by(Account.number).all()
return jsonify([{'id': a.id, 'number': a.number, 'name': a.name, 'type': a.type} for a in accounts])

@app.route('/api/entries', methods=['GET', 'POST'])
def api_entries():
if request.method == 'POST':
data = request.get_json()
entry = Entry(
debit_id=data['debit_id'],
credit_id=data['credit_id'],
amount=data['amount'],
description=data.get('description', '')
)
db.session.add(entry)
db.session.commit()
return jsonify({'status': 'success'})
entries = Entry.query.order_by(Entry.entry_date.desc()).all()
result = []
for e in entries:
result.append({
'id': e.id,
'entry_date': e.entry_date.isoformat(),
'description': e.description,
'debit': {'id': e.debit.id, 'number': e.debit.number, 'name': e.debit.name},
'credit': {'id': e.credit.id, 'number': e.credit.number, 'name': e.credit.name},
'amount': e.amount
})
return jsonify(result)

@app.route('/profit_loss')
def profit_loss():
income_accounts = Account.query.filter(Account.type=='income').all()
Expand Down
38 changes: 38 additions & 0 deletions app/static/js/app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
const { createApp } = Vue;

createApp({
data() {
return {
accounts: [],
entries: [],
newEntry: {
debit_id: '',
credit_id: '',
amount: '',
description: ''
}
};
},
methods: {
fetchAccounts() {
axios.get('/api/accounts').then(res => {
this.accounts = res.data;
});
},
fetchEntries() {
axios.get('/api/entries').then(res => {
this.entries = res.data;
});
},
addEntry() {
axios.post('/api/entries', this.newEntry).then(() => {
this.fetchEntries();
this.newEntry = { debit_id: '', credit_id: '', amount: '', description: '' };
});
}
},
mounted() {
this.fetchAccounts();
this.fetchEntries();
}
}).mount('#app');
16 changes: 10 additions & 6 deletions app/templates/accounts.html
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
{% extends 'base.html' %}
{% block content %}
<h2>Konten</h2>
<table border="1">
<tr><th>Nummer</th><th>Name</th><th>Typ</th></tr>
{% for acc in accounts %}
<tr><td>{{ acc.number }}</td><td>{{ acc.name }}</td><td>{{ acc.type }}</td></tr>
{% endfor %}
<h2 class="mb-3">Konten</h2>
<table class="table table-striped table-bordered">
<thead>
<tr><th>Nummer</th><th>Name</th><th>Typ</th></tr>
</thead>
<tbody>
{% for acc in accounts %}
<tr><td>{{ acc.number }}</td><td>{{ acc.name }}</td><td>{{ acc.type }}</td></tr>
{% endfor %}
</tbody>
</table>
{% endblock %}
32 changes: 18 additions & 14 deletions app/templates/balance.html
Original file line number Diff line number Diff line change
@@ -1,18 +1,22 @@
{% extends 'base.html' %}
{% block content %}
<h2>Bilanz</h2>
<table border="1">
<tr><th>Aktiva</th><th>Betrag</th><th>Passiva</th><th>Betrag</th></tr>
{% for left, right in rows %}
<tr>
<td>{{ left.name if left else '' }}</td>
<td>{{ '%.2f'|format(left.amount) if left else '' }}</td>
<td>{{ right.name if right else '' }}</td>
<td>{{ '%.2f'|format(right.amount) if right else '' }}</td>
</tr>
{% endfor %}
<tr><td><strong>Summe Aktiva</strong></td><td>{{ '%.2f'|format(asset_total) }}</td>
<td><strong>Summe Passiva</strong></td><td>{{ '%.2f'|format(liability_total) }}</td></tr>
<tr><td colspan="2"></td><td><strong>Eigenkapital</strong></td><td>{{ '%.2f'|format(equity) }}</td></tr>
<h2 class="mb-3">Bilanz</h2>
<table class="table table-striped table-bordered">
<thead>
<tr><th>Aktiva</th><th>Betrag</th><th>Passiva</th><th>Betrag</th></tr>
</thead>
<tbody>
{% for left, right in rows %}
<tr>
<td>{{ left.name if left else '' }}</td>
<td>{{ '%.2f'|format(left.amount) if left else '' }}</td>
<td>{{ right.name if right else '' }}</td>
<td>{{ '%.2f'|format(right.amount) if right else '' }}</td>
</tr>
{% endfor %}
<tr><td><strong>Summe Aktiva</strong></td><td>{{ '%.2f'|format(asset_total) }}</td>
<td><strong>Summe Passiva</strong></td><td>{{ '%.2f'|format(liability_total) }}</td></tr>
<tr><td colspan="2"></td><td><strong>Eigenkapital</strong></td><td>{{ '%.2f'|format(equity) }}</td></tr>
</tbody>
</table>
{% endblock %}
32 changes: 23 additions & 9 deletions app/templates/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,32 @@
<html lang="de">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Buchhaltung</title>
<link href="https://cdn.jsdelivr.net/npm/bootswatch@5.3.2/dist/flatly/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<nav>
<a href="/">Home</a> |
<a href="/accounts">Konten</a> |
<a href="/entries">Buchungen</a> |
<a href="/profit_loss">G+V</a> |
<a href="/balance">Bilanz</a> |
<a href="/vat">UVA</a>
<nav class="navbar navbar-expand-lg navbar-dark bg-primary">
<div class="container-fluid">
<a class="navbar-brand" href="/">Buchhaltung</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
<li class="nav-item"><a class="nav-link" href="/accounts">Konten</a></li>
<li class="nav-item"><a class="nav-link" href="/entries">Buchungen</a></li>
<li class="nav-item"><a class="nav-link" href="/profit_loss">G+V</a></li>
<li class="nav-item"><a class="nav-link" href="/balance">Bilanz</a></li>
<li class="nav-item"><a class="nav-link" href="/vat">UVA</a></li>
<li class="nav-item"><a class="nav-link" href="/vue_app">Vue&nbsp;Demo</a></li>
</ul>
</div>
</div>
</nav>
<hr>
{% block content %}{% endblock %}
<div class="container mt-4">
{% block content %}{% endblock %}
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>
65 changes: 45 additions & 20 deletions app/templates/entries.html
Original file line number Diff line number Diff line change
@@ -1,25 +1,50 @@
{% extends 'base.html' %}
{% block content %}
<h2>Buchungen</h2>
<form method="post">
Soll: <select name="debit">
{% for acc in accounts %}
<option value="{{ acc.id }}">{{ acc.number }} {{ acc.name }}</option>
{% endfor %}
</select>
Haben: <select name="credit">
{% for acc in accounts %}
<option value="{{ acc.id }}">{{ acc.number }} {{ acc.name }}</option>
{% endfor %}
</select>
Betrag: <input type="number" step="0.01" name="amount">
Text: <input type="text" name="description">
<input type="submit" value="Buchen">
<h2 class="mb-3">Buchungen</h2>
<form method="post" class="row g-3 mb-4">
<div class="col-md-3">
<label class="form-label">Soll</label>
<select name="debit" class="form-select" required>
{% for acc in accounts %}
<option value="{{ acc.id }}">{{ acc.number }} {{ acc.name }}</option>
{% endfor %}
</select>
</div>
<div class="col-md-3">
<label class="form-label">Haben</label>
<select name="credit" class="form-select" required>
{% for acc in accounts %}
<option value="{{ acc.id }}">{{ acc.number }} {{ acc.name }}</option>
{% endfor %}
</select>
</div>
<div class="col-md-2">
<label class="form-label">Betrag</label>
<input type="number" step="0.01" name="amount" class="form-control" required>
</div>
<div class="col-md-3">
<label class="form-label">Text</label>
<input type="text" name="description" class="form-control">
</div>
<div class="col-md-1 d-flex align-items-end">
<button type="submit" class="btn btn-primary w-100">Buchen</button>
</div>
</form>
<table border="1">
<tr><th>Datum</th><th>Text</th><th>Soll</th><th>Haben</th><th>Betrag</th></tr>
{% for e in entries %}
<tr><td>{{ e.entry_date }}</td><td>{{ e.description }}</td><td>{{ e.debit.number }}</td><td>{{ e.credit.number }}</td><td>{{ e.amount }}</td></tr>
{% endfor %}

<table class="table table-striped table-bordered">
<thead>
<tr><th>Datum</th><th>Text</th><th>Soll</th><th>Haben</th><th>Betrag</th></tr>
</thead>
<tbody>
{% for e in entries %}
<tr>
<td>{{ e.entry_date }}</td>
<td>{{ e.description }}</td>
<td>{{ e.debit.number }}</td>
<td>{{ e.credit.number }}</td>
<td>{{ '%.2f'|format(e.amount) }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endblock %}
2 changes: 1 addition & 1 deletion app/templates/index.html
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{% extends 'base.html' %}
{% block content %}
<h1>Willkommen zur Buchhaltung</h1>
<h1 class="mb-4">Willkommen zur Buchhaltung</h1>
{% endblock %}
28 changes: 16 additions & 12 deletions app/templates/profit_loss.html
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
{% extends 'base.html' %}
{% block content %}
<h2>Gewinn- und Verlustrechnung</h2>
<table border="1">
<tr><th>Konto</th><th>Betrag</th></tr>
{% for name, amount in incomes %}
<tr><td>{{ name }}</td><td>{{ '%.2f'|format(amount) }}</td></tr>
{% endfor %}
<tr><td><strong>Summe Erlöse</strong></td><td>{{ '%.2f'|format(income_total) }}</td></tr>
{% for name, amount in expenses %}
<tr><td>{{ name }}</td><td>{{ '%.2f'|format(amount) }}</td></tr>
{% endfor %}
<tr><td><strong>Summe Aufwendungen</strong></td><td>{{ '%.2f'|format(expense_total) }}</td></tr>
<tr><td><strong>Gewinn/Verlust</strong></td><td>{{ '%.2f'|format(profit) }}</td></tr>
<h2 class="mb-3">Gewinn- und Verlustrechnung</h2>
<table class="table table-striped table-bordered">
<thead>
<tr><th>Konto</th><th>Betrag</th></tr>
</thead>
<tbody>
{% for name, amount in incomes %}
<tr><td>{{ name }}</td><td>{{ '%.2f'|format(amount) }}</td></tr>
{% endfor %}
<tr><td><strong>Summe Erlöse</strong></td><td>{{ '%.2f'|format(income_total) }}</td></tr>
{% for name, amount in expenses %}
<tr><td>{{ name }}</td><td>{{ '%.2f'|format(amount) }}</td></tr>
{% endfor %}
<tr><td><strong>Summe Aufwendungen</strong></td><td>{{ '%.2f'|format(expense_total) }}</td></tr>
<tr><td><strong>Gewinn/Verlust</strong></td><td>{{ '%.2f'|format(profit) }}</td></tr>
</tbody>
</table>
{% endblock %}
12 changes: 7 additions & 5 deletions app/templates/vat.html
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
{% extends 'base.html' %}
{% block content %}
<h2>Umsatzsteuervoranmeldung</h2>
<table border="1">
<tr><td>Umsatzsteuer</td><td>{{ '%.2f'|format(vat_income) }}</td></tr>
<tr><td>Vorsteuer</td><td>{{ '%.2f'|format(vat_expense) }}</td></tr>
<tr><td><strong>Zahllast</strong></td><td>{{ '%.2f'|format(vat_due) }}</td></tr>
<h2 class="mb-3">Umsatzsteuervoranmeldung</h2>
<table class="table table-striped table-bordered w-auto">
<tbody>
<tr><td>Umsatzsteuer</td><td>{{ '%.2f'|format(vat_income) }}</td></tr>
<tr><td>Vorsteuer</td><td>{{ '%.2f'|format(vat_expense) }}</td></tr>
<tr><td><strong>Zahllast</strong></td><td>{{ '%.2f'|format(vat_due) }}</td></tr>
</tbody>
</table>
{% endblock %}
50 changes: 50 additions & 0 deletions app/templates/vue_app.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
{% extends 'base.html' %}
{% block content %}
<div id="app" class="mt-4">
<h2 class="mb-3">Buchungen (Vue)</h2>
<form @submit.prevent="addEntry" class="row g-3">
<div class="col-md-3">
<label class="form-label">Soll</label>
<select v-model="newEntry.debit_id" class="form-select" required>
<option disabled value="">Bitte wählen</option>
<option v-for="acc in accounts" :value="acc.id">{{ acc.number }} {{ acc.name }}</option>
</select>
</div>
<div class="col-md-3">
<label class="form-label">Haben</label>
<select v-model="newEntry.credit_id" class="form-select" required>
<option disabled value="">Bitte wählen</option>
<option v-for="acc in accounts" :value="acc.id">{{ acc.number }} {{ acc.name }}</option>
</select>
</div>
<div class="col-md-2">
<label class="form-label">Betrag</label>
<input type="number" step="0.01" v-model="newEntry.amount" class="form-control" required>
</div>
<div class="col-md-3">
<label class="form-label">Text</label>
<input type="text" v-model="newEntry.description" class="form-control">
</div>
<div class="col-md-1 d-flex align-items-end">
<button type="submit" class="btn btn-primary">Buchen</button>
</div>
</form>
<table class="table table-striped table-bordered mt-4">
<thead>
<tr><th>Datum</th><th>Text</th><th>Soll</th><th>Haben</th><th>Betrag</th></tr>
</thead>
<tbody>
<tr v-for="e in entries" :key="e.id">
<td>{{ e.entry_date }}</td>
<td>{{ e.description }}</td>
<td>{{ e.debit.number }}</td>
<td>{{ e.credit.number }}</td>
<td>{{ e.amount.toFixed(2) }}</td>
</tr>
</tbody>
</table>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.prod.js"></script>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script src="{{ url_for('static', filename='js/app.js') }}"></script>
{% endblock %}