first commit
This commit is contained in:
commit
1de2c23995
|
@ -0,0 +1,6 @@
|
||||||
|
DB_HOST=localhost
|
||||||
|
DB_PORT=5432
|
||||||
|
DB_NAME=postgresql
|
||||||
|
DB_USER=postgresql
|
||||||
|
DB_PASSWORD=supersafepassword
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
.env
|
|
@ -0,0 +1,170 @@
|
||||||
|
import jwt as pyjwt
|
||||||
|
import datetime
|
||||||
|
import bcrypt
|
||||||
|
import psycopg2
|
||||||
|
import psycopg2.extras
|
||||||
|
from flask import Flask, request, jsonify
|
||||||
|
import os
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
|
# Flask App erstellen
|
||||||
|
app = Flask(__name__)
|
||||||
|
app.config['SECRET_KEY'] = 'supergeheimeschluessel'
|
||||||
|
|
||||||
|
# .env-Datei laden
|
||||||
|
load_dotenv()
|
||||||
|
|
||||||
|
# Zugriff auf die Umgebungsvariablen
|
||||||
|
DB_HOST = os.getenv("DB_HOST")
|
||||||
|
DB_PORT = os.getenv("DB_PORT")
|
||||||
|
DB_NAME = os.getenv("DB_NAME")
|
||||||
|
DB_USER = os.getenv("DB_USER")
|
||||||
|
DB_PASSWORD = os.getenv("DB_PASSWORD")
|
||||||
|
|
||||||
|
# Verbindung zur PostgreSQL-Datenbank herstellen
|
||||||
|
def connect_db():
|
||||||
|
return psycopg2.connect(
|
||||||
|
dbname=DB_NAME,
|
||||||
|
user=DB_USER,
|
||||||
|
password=DB_PASSWORD,
|
||||||
|
host=DB_HOST,
|
||||||
|
port=DB_PORT
|
||||||
|
)
|
||||||
|
|
||||||
|
def create_tables():
|
||||||
|
conn = connect_db()
|
||||||
|
cur = conn.cursor()
|
||||||
|
cur.execute('''
|
||||||
|
CREATE TABLE IF NOT EXISTS users (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
username TEXT UNIQUE NOT NULL,
|
||||||
|
password TEXT NOT NULL,
|
||||||
|
is_active BOOLEAN DEFAULT TRUE,
|
||||||
|
email TEXT UNIQUE NOT NULL,
|
||||||
|
reset_password_token TEXT,
|
||||||
|
reset_password_expires TIMESTAMP
|
||||||
|
);
|
||||||
|
''')
|
||||||
|
conn.commit()
|
||||||
|
cur.close()
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
def generate_jwt(user_id):
|
||||||
|
payload = {
|
||||||
|
'user_id': user_id,
|
||||||
|
'exp': datetime.datetime.utcnow() + datetime.timedelta(hours=1)
|
||||||
|
}
|
||||||
|
return pyjwt.encode(payload, app.config['SECRET_KEY'], algorithm='HS256')
|
||||||
|
|
||||||
|
def verify_jwt(token):
|
||||||
|
try:
|
||||||
|
payload = pyjwt.decode(token, app.config['SECRET_KEY'], algorithms=['HS256'])
|
||||||
|
return payload['user_id']
|
||||||
|
except pyjwt.ExpiredSignatureError:
|
||||||
|
return None
|
||||||
|
except pyjwt.InvalidTokenError:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def hash_password(password):
|
||||||
|
return bcrypt.hashpw(password.encode('utf-8'), bcrypt.gensalt()).decode('utf-8')
|
||||||
|
|
||||||
|
def check_password(password, hashed):
|
||||||
|
return bcrypt.checkpw(password.encode('utf-8'), hashed.encode('utf-8'))
|
||||||
|
|
||||||
|
@app.route('/register', methods=['POST'])
|
||||||
|
def register():
|
||||||
|
data = request.json
|
||||||
|
username = data.get('username')
|
||||||
|
email = data.get('email')
|
||||||
|
password = data.get('password')
|
||||||
|
|
||||||
|
if not username or not email or not password:
|
||||||
|
return jsonify({'error': 'Alle Felder erforderlich'}), 400
|
||||||
|
|
||||||
|
hashed_password = hash_password(password)
|
||||||
|
|
||||||
|
conn = connect_db()
|
||||||
|
cur = conn.cursor()
|
||||||
|
try:
|
||||||
|
cur.execute("INSERT INTO users (username, email, password) VALUES (%s, %s, %s) RETURNING id", (username, email, hashed_password))
|
||||||
|
user_id = cur.fetchone()[0]
|
||||||
|
conn.commit()
|
||||||
|
except psycopg2.IntegrityError:
|
||||||
|
return jsonify({'error': 'Benutzername oder E-Mail bereits vergeben'}), 400
|
||||||
|
finally:
|
||||||
|
cur.close()
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
return jsonify({'message': 'Benutzer registriert', 'user_id': user_id})
|
||||||
|
|
||||||
|
@app.route('/login', methods=['POST'])
|
||||||
|
def login():
|
||||||
|
data = request.json
|
||||||
|
username = data.get('username')
|
||||||
|
password = data.get('password')
|
||||||
|
|
||||||
|
conn = connect_db()
|
||||||
|
cur = conn.cursor(cursor_factory=psycopg2.extras.DictCursor)
|
||||||
|
cur.execute("SELECT * FROM users WHERE username = %s", (username,))
|
||||||
|
user = cur.fetchone()
|
||||||
|
cur.close()
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
if not user or not check_password(password, user['password']):
|
||||||
|
return jsonify({'error': 'Ungültige Anmeldeinformationen'}), 401
|
||||||
|
|
||||||
|
token = generate_jwt(user['id'])
|
||||||
|
return jsonify({'token': token})
|
||||||
|
|
||||||
|
@app.route('/kasse', methods=['GET'])
|
||||||
|
def get_kasse():
|
||||||
|
token = request.headers.get('Authorization')
|
||||||
|
if not token:
|
||||||
|
return jsonify({'error': 'Kein Token vorhanden'}), 401
|
||||||
|
|
||||||
|
user_id = verify_jwt(token.split()[1])
|
||||||
|
if not user_id:
|
||||||
|
return jsonify({'error': 'Ungültiges Token'}), 401
|
||||||
|
|
||||||
|
conn = connect_db()
|
||||||
|
cur = conn.cursor(cursor_factory=psycopg2.extras.DictCursor)
|
||||||
|
cur.execute("SELECT * FROM kasse ORDER BY timestamp DESC")
|
||||||
|
rows = cur.fetchall()
|
||||||
|
cur.close()
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
return jsonify([dict(row) for row in rows])
|
||||||
|
|
||||||
|
@app.route('/new_entry', methods=['POST'])
|
||||||
|
def new_entry():
|
||||||
|
token = request.headers.get('Authorization')
|
||||||
|
if not token:
|
||||||
|
return jsonify({'error': 'Kein Token vorhanden'}), 401
|
||||||
|
|
||||||
|
user_id = verify_jwt(token.split()[1])
|
||||||
|
if not user_id:
|
||||||
|
return jsonify({'error': 'Ungültiges Token'}), 401
|
||||||
|
|
||||||
|
data = request.json
|
||||||
|
beschreibung = data.get('beschreibung')
|
||||||
|
wert = data.get('wert')
|
||||||
|
typ = data.get('typ')
|
||||||
|
bar = data.get('bar')
|
||||||
|
|
||||||
|
if not beschreibung or wert is None or not typ or bar is None:
|
||||||
|
return jsonify({'error': 'Alle Felder erforderlich'}), 400
|
||||||
|
|
||||||
|
conn = connect_db()
|
||||||
|
cur = conn.cursor()
|
||||||
|
cur.execute("INSERT INTO kasse (beschreibung, wert, typ, bar) VALUES (%s, %s, %s, %s) RETURNING id",
|
||||||
|
(beschreibung, wert, typ, bar))
|
||||||
|
entry_id = cur.fetchone()[0]
|
||||||
|
conn.commit()
|
||||||
|
cur.close()
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
return jsonify({'message': 'Eintrag erstellt', 'entry_id': entry_id})
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
create_tables()
|
||||||
|
app.run(debug=True)
|
|
@ -0,0 +1,227 @@
|
||||||
|
import psycopg2
|
||||||
|
import psycopg2.extras
|
||||||
|
import datetime
|
||||||
|
import os
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
|
# ANSI Escape Codes für Farben und Unterstreichung
|
||||||
|
RESET = "\033[0m" # Zurücksetzen auf Standard
|
||||||
|
|
||||||
|
RED = "\033[31m" # Rote Schrift
|
||||||
|
GREEN = "\033[32m" # Grüne Schrift
|
||||||
|
BLUE = "\033[34m" # Blaue Schrift
|
||||||
|
|
||||||
|
BOLD = "\033[1m" # Fett
|
||||||
|
ITALIC = "\033[3m" # Kursiv
|
||||||
|
UNDERLINE = "\033[4m" # Unterstreichen
|
||||||
|
|
||||||
|
|
||||||
|
# .env-Datei laden
|
||||||
|
load_dotenv()
|
||||||
|
|
||||||
|
# Zugriff auf die Umgebungsvariablen
|
||||||
|
DB_HOST = os.getenv("DB_HOST")
|
||||||
|
DB_PORT = os.getenv("DB_PORT")
|
||||||
|
DB_NAME = os.getenv("DB_NAME")
|
||||||
|
DB_USER = os.getenv("DB_USER")
|
||||||
|
DB_PASSWORD = os.getenv("DB_PASSWORD")
|
||||||
|
|
||||||
|
# Verbindung zur PostgreSQL-Datenbank herstellen
|
||||||
|
def connect_db():
|
||||||
|
return psycopg2.connect(
|
||||||
|
dbname=DB_NAME,
|
||||||
|
user=DB_USER,
|
||||||
|
password=DB_PASSWORD,
|
||||||
|
host=DB_HOST,
|
||||||
|
port=DB_PORT
|
||||||
|
)
|
||||||
|
|
||||||
|
def create_table():
|
||||||
|
conn = connect_db()
|
||||||
|
cur = conn.cursor()
|
||||||
|
cur.execute('''
|
||||||
|
CREATE TABLE IF NOT EXISTS kasse (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
beschreibung TEXT NOT NULL,
|
||||||
|
wert NUMERIC(10,2) NOT NULL,
|
||||||
|
typ TEXT CHECK (typ IN ('Einnahme', 'Ausgabe')) NOT NULL,
|
||||||
|
bar BOOLEAN NOT NULL,
|
||||||
|
timestamp TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||||
|
rechnungsnummer TEXT,
|
||||||
|
kommentar TEXT,
|
||||||
|
anlass TEXT
|
||||||
|
);
|
||||||
|
''')
|
||||||
|
conn.commit()
|
||||||
|
cur.close()
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
def initial_setup():
|
||||||
|
conn = connect_db()
|
||||||
|
cur = conn.cursor()
|
||||||
|
cur.execute("SELECT COUNT(*) FROM kasse;")
|
||||||
|
count = cur.fetchone()[0]
|
||||||
|
|
||||||
|
if count == 0:
|
||||||
|
start_konto = float(input("Startwert Konto: "))
|
||||||
|
start_bar = float(input("Startwert Bar: "))
|
||||||
|
cur.execute("INSERT INTO kasse (beschreibung, wert, typ, bar) VALUES (%s, %s, %s, %s)",
|
||||||
|
('Startwert Konto', start_konto, 'Einnahme', False))
|
||||||
|
cur.execute("INSERT INTO kasse (beschreibung, wert, typ, bar) VALUES (%s, %s, %s, %s)",
|
||||||
|
('Startwert Bar', start_bar, 'Einnahme', True))
|
||||||
|
conn.commit()
|
||||||
|
cur.close()
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
def parse_timestamp(date_str, time_str):
|
||||||
|
if not date_str:
|
||||||
|
date_str = datetime.datetime.now().strftime("%d.%m.%y")
|
||||||
|
if not time_str:
|
||||||
|
time_str = datetime.datetime.now().strftime("%H:%M")
|
||||||
|
return datetime.datetime.strptime(f"{date_str} {time_str}", "%d.%m.%y %H:%M")
|
||||||
|
|
||||||
|
def erfassen(typ):
|
||||||
|
beschreibung = input("Beschreibung: ")
|
||||||
|
wert = float(input("Wert: "))
|
||||||
|
bar = input("Bar? (1 = Ja, 0 = Nein): ") == '1'
|
||||||
|
rechnungsnummer = input("Rechnungsnummer (optional): ") or None
|
||||||
|
kommentar = input("Kommentar (optional): ") or None
|
||||||
|
anlass = input("Anlass (optional): ") or None
|
||||||
|
date_str = input("Datum (TT.MM.JJ, leer für heute): ")
|
||||||
|
time_str = input("Uhrzeit (HH:MM, leer für jetzt): ")
|
||||||
|
timestamp = parse_timestamp(date_str, time_str)
|
||||||
|
|
||||||
|
conn = connect_db()
|
||||||
|
cur = conn.cursor()
|
||||||
|
cur.execute("INSERT INTO kasse (beschreibung, wert, typ, bar, timestamp, rechnungsnummer, kommentar, anlass) VALUES (%s, %s, %s, %s, %s, %s, %s, %s)",
|
||||||
|
(beschreibung, wert, typ, bar, timestamp, rechnungsnummer, kommentar, anlass))
|
||||||
|
conn.commit()
|
||||||
|
cur.close()
|
||||||
|
conn.close()
|
||||||
|
print("Buchung erfasst!")
|
||||||
|
exit = input(f"\nEnter -> zurück zum Menü ")
|
||||||
|
|
||||||
|
def umbuchung():
|
||||||
|
wert = float(input("Betrag der Umbuchung: "))
|
||||||
|
richtung = input("Umbuchung Richtung? (1 = Bar -> Konto, 0 = Konto -> Bar): ") == '1'
|
||||||
|
date_str = input("Datum (TT.MM.JJ, leer für heute): ")
|
||||||
|
time_str = input("Uhrzeit (HH:MM, leer für jetzt): ")
|
||||||
|
timestamp = parse_timestamp(date_str, time_str)
|
||||||
|
|
||||||
|
conn = connect_db()
|
||||||
|
cur = conn.cursor()
|
||||||
|
if richtung:
|
||||||
|
cur.execute("INSERT INTO kasse (beschreibung, wert, typ, bar, timestamp, anlass, gegenueber) VALUES (%s, %s, %s, %s, %s, %s, %s)",
|
||||||
|
('Umbuchung nach Konto', wert, 'Ausgabe', True, timestamp, 'Orga', 'SJTKD'))
|
||||||
|
cur.execute("INSERT INTO kasse (beschreibung, wert, typ, bar, timestamp, anlass, gegenueber) VALUES (%s, %s, %s, %s, %s, %s, %s)",
|
||||||
|
('Umbuchung von Bar', wert, 'Einnahme', False, timestamp, 'Orga', 'SJTKD'))
|
||||||
|
else:
|
||||||
|
cur.execute("INSERT INTO kasse (beschreibung, wert, typ, bar, timestamp, anlass, gegenueber) VALUES (%s, %s, %s, %s, %s, %s, %s)",
|
||||||
|
('Umbuchung nach Bar', wert, 'Ausgabe', False, timestamp, 'Orga', 'SJTKD'))
|
||||||
|
cur.execute("INSERT INTO kasse (beschreibung, wert, typ, bar, timestamp, anlass, gegenueber) VALUES (%s, %s, %s, %s, %s, %s, %s)",
|
||||||
|
('Umbuchung von Konto', wert, 'Einnahme', True, timestamp, 'Orga', 'SJTKD'))
|
||||||
|
conn.commit()
|
||||||
|
cur.close()
|
||||||
|
conn.close()
|
||||||
|
print("Umbuchung erfasst!")
|
||||||
|
exit = input(f"\nEnter -> zurück zum Menü ")
|
||||||
|
|
||||||
|
def kassenbestand():
|
||||||
|
conn = connect_db()
|
||||||
|
cur = conn.cursor()
|
||||||
|
cur.execute("SELECT SUM(wert) FROM kasse WHERE typ='Einnahme' AND bar=True")
|
||||||
|
einnahmen_bar = cur.fetchone()[0] or 0
|
||||||
|
cur.execute("SELECT SUM(wert) FROM kasse WHERE typ='Ausgabe' AND bar=True")
|
||||||
|
ausgaben_bar = cur.fetchone()[0] or 0
|
||||||
|
cur.execute("SELECT SUM(wert) FROM kasse WHERE typ='Einnahme' AND bar=False")
|
||||||
|
einnahmen_konto = cur.fetchone()[0] or 0
|
||||||
|
cur.execute("SELECT SUM(wert) FROM kasse WHERE typ='Ausgabe' AND bar=False")
|
||||||
|
ausgaben_konto = cur.fetchone()[0] or 0
|
||||||
|
|
||||||
|
barbestand = einnahmen_bar - ausgaben_bar
|
||||||
|
kontostand = einnahmen_konto - ausgaben_konto
|
||||||
|
summe = barbestand + kontostand
|
||||||
|
print(UNDERLINE + "Kassenbestand:" + RESET)
|
||||||
|
print(BOLD + "Bar: " + RESET + f"{einnahmen_bar - ausgaben_bar:>10.2f} €")
|
||||||
|
print(BOLD + "Konto: " + RESET + f"{einnahmen_konto - ausgaben_konto:>10.2f} €\n==============")
|
||||||
|
print(BOLD + "Summe: " + RESET + f"{summe:>10.2f} €")
|
||||||
|
|
||||||
|
cur.close()
|
||||||
|
conn.close()
|
||||||
|
exit = input(f"\nEnter -> zurück zum Menü ")
|
||||||
|
|
||||||
|
def anzeigen(filter_typ=None):
|
||||||
|
conn = connect_db()
|
||||||
|
cur = conn.cursor(cursor_factory=psycopg2.extras.DictCursor)
|
||||||
|
if filter_typ:
|
||||||
|
cur.execute("SELECT * FROM kasse WHERE typ = %s ORDER BY timestamp ASC", (filter_typ,))
|
||||||
|
else:
|
||||||
|
cur.execute("SELECT * FROM kasse ORDER BY timestamp ASC")
|
||||||
|
rows = cur.fetchall()
|
||||||
|
for row in rows:
|
||||||
|
formatted_date = row['timestamp'].strftime("%d.%m.%y")
|
||||||
|
print(f"{row['id']:<4} {row['beschreibung']:<35} | " + f"{GREEN if row['typ']=='Einnahme' else RED}" + f"{row['wert']:>7} € " + RESET + f" | {'Bar' if row['bar'] else 'Konto':<6} | {formatted_date} | {row['rechnungsnummer'] or '':<15} | {row['kommentar'] or '':<10} | {row['anlass'] or '':<10}")
|
||||||
|
cur.close()
|
||||||
|
conn.close()
|
||||||
|
exit = input(f"\nEnter -> zurück zum Menü ")
|
||||||
|
|
||||||
|
def anzeigen_anlass():
|
||||||
|
filter_typ = input("Anlass: ")
|
||||||
|
conn = connect_db()
|
||||||
|
cur = conn.cursor(cursor_factory=psycopg2.extras.DictCursor)
|
||||||
|
cur.execute("SELECT * FROM kasse WHERE anlass = %s ORDER BY timestamp ASC", (filter_typ,))
|
||||||
|
rows = cur.fetchall()
|
||||||
|
for row in rows:
|
||||||
|
formatted_date = row['timestamp'].strftime("%d.%m.%y")
|
||||||
|
print(f"{row['id']:<4} {row['beschreibung']:<35} | " + f"{GREEN if row['typ']=='Einnahme' else RED}" + f"{row['wert']:>7} € " + RESET + f" | {'Bar' if row['bar'] else 'Konto':<6} | {formatted_date} | {row['rechnungsnummer'] or '':<10} | {row['kommentar'] or '':<10}")
|
||||||
|
|
||||||
|
cur.execute("SELECT SUM(wert) FROM kasse WHERE typ = 'Einnahme' AND anlass = %s", (filter_typ,))
|
||||||
|
einnahmen = cur.fetchone()[0] or 0
|
||||||
|
|
||||||
|
cur.execute("SELECT SUM(wert) FROM kasse WHERE typ='Ausgabe' AND anlass = %s", (filter_typ,))
|
||||||
|
ausgaben = cur.fetchone()[0] or 0
|
||||||
|
|
||||||
|
print(UNDERLINE + "\nBilanz:" + RESET)
|
||||||
|
text="Einnahmen:"
|
||||||
|
print(BOLD + f"{text:<8}" + RESET + GREEN + f"{einnahmen:>10.2f} €" + RESET)
|
||||||
|
text="Ausgaben: "
|
||||||
|
print(BOLD + f"{text:<8}" + RESET + RED + f"{ausgaben:>10.2f} €" + RESET + f"\n===================")
|
||||||
|
summe = einnahmen - ausgaben
|
||||||
|
COLOR = GREEN
|
||||||
|
if summe<0:
|
||||||
|
COLOR = RED
|
||||||
|
text="Summe: "
|
||||||
|
print(BOLD + f"{text:<8}" + RESET + COLOR + f"{summe:>10.2f} €" + RESET)
|
||||||
|
|
||||||
|
cur.close()
|
||||||
|
conn.close()
|
||||||
|
exit = input(f"\nEnter -> zurück zum Menü ")
|
||||||
|
|
||||||
|
def menu():
|
||||||
|
create_table()
|
||||||
|
initial_setup()
|
||||||
|
while True:
|
||||||
|
print(UNDERLINE + "\nKassenbuch Menü:" + RESET)
|
||||||
|
print(BOLD + "(1)" + RESET + " Ausgabe erfassen")
|
||||||
|
print(BOLD + "(2)" + RESET + " Einnahme erfassen")
|
||||||
|
print(BOLD + "(3)" + RESET + " Kassenbestand anzeigen")
|
||||||
|
print(BOLD + "(4)" + RESET + " Umsätze anzeigen")
|
||||||
|
print(BOLD + "(5)" + RESET + " Ausgaben anzeigen")
|
||||||
|
print(BOLD + "(6)" + RESET + " Einnahmen anzeigen")
|
||||||
|
print(BOLD + "(7)" + RESET + " Umbuchung durchführen")
|
||||||
|
print(BOLD + "(8)" + RESET + " Bilanz Veranstaltung")
|
||||||
|
print(BOLD + "(0)" + RESET + " Beenden")
|
||||||
|
auswahl = input("Auswahl: ")
|
||||||
|
print("\n")
|
||||||
|
if auswahl == '1': erfassen('Ausgabe')
|
||||||
|
elif auswahl == '2': erfassen('Einnahme')
|
||||||
|
elif auswahl == '3': kassenbestand()
|
||||||
|
elif auswahl == '4': anzeigen()
|
||||||
|
elif auswahl == '5': anzeigen('Ausgabe')
|
||||||
|
elif auswahl == '6': anzeigen('Einnahme')
|
||||||
|
elif auswahl == '7': umbuchung()
|
||||||
|
elif auswahl == '8': anzeigen_anlass()
|
||||||
|
elif auswahl == '0': break
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
menu()
|
Loading…
Reference in New Issue