statystyki i optymalizacje

This commit is contained in:
Mateusz Gruszczyński
2025-12-12 09:23:34 +01:00
parent 20910fa898
commit fe48f589f0
8 changed files with 1303 additions and 421 deletions

209
app.py
View File

@@ -201,6 +201,7 @@ class UstawieniaGlobalne(db.Model):
typ_navbar = db.Column(db.String(10), default="text")
typ_stopka = db.Column(db.String(10), default="text")
stopka_text = db.Column(db.String(200), nullable=True)
kolejnosc_rezerwowych = db.Column(db.String(20), default="id", nullable=False)
@login_manager.user_loader
@@ -336,7 +337,23 @@ def inject_version():
# TRASY PUBLICZNE
@app.route("/")
def index():
zbiorki = Zbiorka.query.filter_by(ukryta=False, zrealizowana=False).all()
settings = UstawieniaGlobalne.query.first()
kolejnosc = settings.kolejnosc_rezerwowych if settings else "id"
standardowe = Zbiorka.query.filter_by(ukryta=False, zrealizowana=False).filter(
Zbiorka.typ_zbiorki != 'rezerwa'
).all()
rezerwowe = Zbiorka.query.filter_by(ukryta=False, zrealizowana=False, typ_zbiorki='rezerwa').all()
# Sortuj według ustawienia
if kolejnosc == "first":
zbiorki = rezerwowe + standardowe
elif kolejnosc == "last":
zbiorki = standardowe + rezerwowe
else: # "id"
zbiorki = sorted(standardowe + rezerwowe, key=lambda z: z.id)
return render_template("index.html", zbiorki=zbiorki)
@@ -351,12 +368,19 @@ def page_not_found(e):
return redirect(url_for("index"))
@app.route("/zbiorka/<int:zbiorka_id>")
@app.route("/zbiorka/<int:zbiorka_id>", endpoint='zbiorka')
@app.route("/rezerwa/<int:zbiorka_id>", endpoint='rezerwa')
def zbiorka(zbiorka_id):
zb = db.session.get(Zbiorka, zbiorka_id)
if zb is None:
abort(404)
# Zabezpieczenie: sprawdź czy URL pasuje do typu zbiórki
poprawny_endpoint = 'rezerwa' if zb.typ_zbiorki == 'rezerwa' else 'zbiorka'
if request.endpoint != poprawny_endpoint:
return redirect(url_for(poprawny_endpoint, zbiorka_id=zbiorka_id), code=301)
if zb.ukryta and (not current_user.is_authenticated or not current_user.czy_admin):
abort(404)
@@ -945,9 +969,10 @@ def admin_ustawienia():
if not current_user.czy_admin:
flash("Brak uprawnień do panelu administracyjnego", "danger")
return redirect(url_for("index"))
client_ip = get_real_ip()
settings = UstawieniaGlobalne.query.first()
if request.method == "POST":
numer_konta = request.form.get("numer_konta")
numer_telefonu_blik = request.form.get("numer_telefonu_blik")
@@ -958,7 +983,8 @@ def admin_ustawienia():
typ_stopka = request.form.get("typ_stopka", "text")
stopka_text = request.form.get("stopka_text") or None
pokaz_logo_w_navbar = (typ_navbar == "logo")
kolejnosc_rezerwowych = request.form.get("kolejnosc_rezerwowych", "id")
if settings is None:
settings = UstawieniaGlobalne(
numer_konta=numer_konta,
@@ -970,6 +996,7 @@ def admin_ustawienia():
typ_navbar=typ_navbar,
typ_stopka=typ_stopka,
stopka_text=stopka_text,
kolejnosc_rezerwowych=kolejnosc_rezerwowych,
)
db.session.add(settings)
else:
@@ -982,11 +1009,12 @@ def admin_ustawienia():
settings.typ_navbar = typ_navbar
settings.typ_stopka = typ_stopka
settings.stopka_text = stopka_text
settings.kolejnosc_rezerwowych = kolejnosc_rezerwowych
db.session.commit()
flash("Ustawienia globalne zostały zaktualizowane", "success")
return redirect(url_for("admin_dashboard"))
return render_template("admin/ustawienia.html", settings=settings, client_ip=client_ip)
@@ -1416,6 +1444,175 @@ def usun_rezerwe(rezerwa_id):
return redirect(url_for("lista_rezerwowych"))
@app.route("/admin/statystyki")
@login_required
def admin_statystyki():
if not current_user.czy_admin:
abort(403)
from sqlalchemy import func, extract
from datetime import datetime, timedelta
# ==================== PODSTAWOWE STATYSTYKI ====================
total_wplaty = db.session.query(func.sum(Wplata.kwota)).filter(Wplata.ukryta == False).scalar() or 0
total_wydatki = db.session.query(func.sum(Wydatek.kwota)).filter(Wydatek.ukryta == False).scalar() or 0
bilans = total_wplaty - total_wydatki
liczba_wplat = db.session.query(func.count(Wplata.id)).filter(Wplata.ukryta == False).scalar() or 0
liczba_wydatkow = db.session.query(func.count(Wydatek.id)).filter(Wydatek.ukryta == False).scalar() or 0
liczba_zbiorek = db.session.query(func.count(Zbiorka.id)).scalar() or 0
# Najwyższa wpłata
najwyzsza_wplata = db.session.query(Wplata).filter(Wplata.ukryta == False).order_by(Wplata.kwota.desc()).first()
# Najwyższy wydatek
najwyzszy_wydatek = db.session.query(Wydatek).filter(Wydatek.ukryta == False).order_by(Wydatek.kwota.desc()).first()
# Średnia wpłata i wydatek
srednia_wplata = total_wplaty / liczba_wplat if liczba_wplat > 0 else 0
sredni_wydatek = total_wydatki / liczba_wydatkow if liczba_wydatkow > 0 else 0
# ==================== STATYSTYKI PRZESUNIĘĆ ====================
total_przesuniec = db.session.query(func.sum(Przesuniecie.kwota)).filter(Przesuniecie.ukryta == False).scalar() or 0
liczba_przesuniec = db.session.query(func.count(Przesuniecie.id)).filter(Przesuniecie.ukryta == False).scalar() or 0
# Top 5 źródeł przesunięć (zbiórki które najczęściej przekazują środki)
top_zrodla_przesuniec = db.session.query(
Zbiorka.nazwa,
func.count(Przesuniecie.id).label('liczba'),
func.sum(Przesuniecie.kwota).label('suma')
).join(Przesuniecie, Przesuniecie.zbiorka_zrodlo_id == Zbiorka.id)\
.filter(Przesuniecie.ukryta == False)\
.group_by(Zbiorka.id, Zbiorka.nazwa)\
.order_by(func.sum(Przesuniecie.kwota).desc())\
.limit(5).all()
# ==================== TOP 10 WPŁAT ====================
top_10_wplat = db.session.query(Wplata)\
.filter(Wplata.ukryta == False)\
.order_by(Wplata.kwota.desc())\
.limit(10).all()
# ==================== AKTYWNOŚĆ CZASOWA ====================
teraz = datetime.now()
rok_temu = teraz - timedelta(days=365)
miesiac_temu = teraz - timedelta(days=30)
tydzien_temu = teraz - timedelta(days=7)
# Aktywność ostatnie 7 dni
wplaty_7dni = db.session.query(
func.count(Wplata.id).label('liczba'),
func.sum(Wplata.kwota).label('suma')
).filter(Wplata.data >= tydzien_temu, Wplata.ukryta == False).first()
wydatki_7dni = db.session.query(
func.count(Wydatek.id).label('liczba'),
func.sum(Wydatek.kwota).label('suma')
).filter(Wydatek.data >= tydzien_temu, Wydatek.ukryta == False).first()
# Aktywność ostatnie 30 dni
wplaty_30dni = db.session.query(
func.count(Wplata.id).label('liczba'),
func.sum(Wplata.kwota).label('suma')
).filter(Wplata.data >= miesiac_temu, Wplata.ukryta == False).first()
wydatki_30dni = db.session.query(
func.count(Wydatek.id).label('liczba'),
func.sum(Wydatek.kwota).label('suma')
).filter(Wydatek.data >= miesiac_temu, Wydatek.ukryta == False).first()
# ==================== STATYSTYKI MIESIĘCZNE (ostatnie 12 miesięcy) ====================
wplaty_miesieczne = db.session.query(
extract('year', Wplata.data).label('rok'),
extract('month', Wplata.data).label('miesiac'),
func.sum(Wplata.kwota).label('suma'),
func.count(Wplata.id).label('liczba')
).filter(
Wplata.data >= rok_temu,
Wplata.ukryta == False
).group_by('rok', 'miesiac').order_by('rok', 'miesiac').all()
wydatki_miesieczne = db.session.query(
extract('year', Wydatek.data).label('rok'),
extract('month', Wydatek.data).label('miesiac'),
func.sum(Wydatek.kwota).label('suma'),
func.count(Wydatek.id).label('liczba')
).filter(
Wydatek.data >= rok_temu,
Wydatek.ukryta == False
).group_by('rok', 'miesiac').order_by('rok', 'miesiac').all()
przesuniecia_miesieczne = db.session.query(
extract('year', Przesuniecie.data).label('rok'),
extract('month', Przesuniecie.data).label('miesiac'),
func.sum(Przesuniecie.kwota).label('suma'),
func.count(Przesuniecie.id).label('liczba')
).filter(
Przesuniecie.data >= rok_temu,
Przesuniecie.ukryta == False
).group_by('rok', 'miesiac').order_by('rok', 'miesiac').all()
# ==================== STATYSTYKI ROCZNE ====================
wplaty_roczne = db.session.query(
extract('year', Wplata.data).label('rok'),
func.sum(Wplata.kwota).label('suma'),
func.count(Wplata.id).label('liczba')
).filter(
Wplata.ukryta == False
).group_by('rok').order_by('rok').all()
wydatki_roczne = db.session.query(
extract('year', Wydatek.data).label('rok'),
func.sum(Wydatek.kwota).label('suma'),
func.count(Wydatek.id).label('liczba')
).filter(
Wydatek.ukryta == False
).group_by('rok').order_by('rok').all()
# ==================== TOP 5 ZBIÓREK ====================
top_zbiorki = db.session.query(
Zbiorka,
func.sum(Wplata.kwota).label('suma_wplat')
).join(Wplata).filter(
Wplata.ukryta == False
).group_by(Zbiorka.id).order_by(func.sum(Wplata.kwota).desc()).limit(5).all()
return render_template(
"admin/statystyki.html",
# Podstawowe
total_wplaty=total_wplaty,
total_wydatki=total_wydatki,
bilans=bilans,
liczba_wplat=liczba_wplat,
liczba_wydatkow=liczba_wydatkow,
liczba_zbiorek=liczba_zbiorek,
najwyzsza_wplata=najwyzsza_wplata,
najwyzszy_wydatek=najwyzszy_wydatek,
srednia_wplata=srednia_wplata,
sredni_wydatek=sredni_wydatek,
# Przesunięcia
total_przesuniec=total_przesuniec,
liczba_przesuniec=liczba_przesuniec,
top_zrodla_przesuniec=top_zrodla_przesuniec,
# Top wpłaty
top_10_wplat=top_10_wplat,
# Aktywność czasowa
wplaty_7dni=wplaty_7dni,
wydatki_7dni=wydatki_7dni,
wplaty_30dni=wplaty_30dni,
wydatki_30dni=wydatki_30dni,
# Miesięczne
wplaty_miesieczne=wplaty_miesieczne,
wydatki_miesieczne=wydatki_miesieczne,
przesuniecia_miesieczne=przesuniecia_miesieczne,
# Roczne
wplaty_roczne=wplaty_roczne,
wydatki_roczne=wydatki_roczne,
# Top zbiórki
top_zbiorki=top_zbiorki
)
@app.route("/favicon.ico")
def favicon():
return "", 204