funkcje rezerw i przesuniec

This commit is contained in:
Mateusz Gruszczyński
2025-12-11 13:47:57 +01:00
parent 3e4d1ba78c
commit 5220b8cf2c
10 changed files with 956 additions and 50 deletions

336
app.py
View File

@@ -94,6 +94,8 @@ class Zbiorka(db.Model):
pokaz_postep_kwotowo = db.Column(db.Boolean, default=True, nullable=False)
uzyj_konta = db.Column(db.Boolean, default=True, nullable=False)
uzyj_blik = db.Column(db.Boolean, default=True, nullable=False)
typ_zbiorki = db.Column(db.String(20), default="standardowa", nullable=False)
nazwa_typu_rezerwy = db.Column(db.String(100), nullable=True)
wplaty = db.relationship(
"Wplata",
@@ -159,6 +161,33 @@ class Wydatek(db.Model):
opis = db.Column(db.Text, nullable=True)
ukryta = db.Column(db.Boolean, nullable=False, default=False)
class Przesuniecie(db.Model):
id = db.Column(db.Integer, primary_key=True)
zbiorka_zrodlo_id = db.Column(
db.Integer,
db.ForeignKey("zbiorka.id", ondelete="CASCADE"),
nullable=False,
)
zbiorka_cel_id = db.Column(
db.Integer,
db.ForeignKey("zbiorka.id", ondelete="CASCADE"),
nullable=False,
)
kwota = db.Column(Numeric(12, 2), nullable=False)
data = db.Column(db.DateTime, default=datetime.utcnow)
opis = db.Column(db.Text, nullable=True)
ukryta = db.Column(db.Boolean, nullable=False, default=False)
wplata_id = db.Column(
db.Integer,
db.ForeignKey("wplata.id", ondelete="SET NULL"),
nullable=True,
)
zbiorka_zrodlo = db.relationship("Zbiorka", foreign_keys=[zbiorka_zrodlo_id], backref="przesuniecia_wychodzace")
zbiorka_cel = db.relationship("Zbiorka", foreign_keys=[zbiorka_cel_id], backref="przesuniecia_przychodzace")
wplata = db.relationship("Wplata", foreign_keys=[wplata_id], backref="przesuniecia")
class UstawieniaGlobalne(db.Model):
__tablename__ = "ustawienia_globalne"
@@ -327,27 +356,59 @@ def zbiorka(zbiorka_id):
zb = db.session.get(Zbiorka, zbiorka_id)
if zb is None:
abort(404)
if zb.ukryta and (not current_user.is_authenticated or not current_user.czy_admin):
abort(404)
is_admin = current_user.is_authenticated and current_user.czy_admin
show_hidden = is_admin and (request.args.get("show_hidden") in ("1", "true", "yes"))
# wpłaty / wydatki z filtrem ukrycia
wplaty = [
{"typ": "wpłata", "kwota": w.kwota, "opis": w.opis, "data": w.data, "ukryta": getattr(w, "ukryta", False)}
for w in zb.wplaty
if show_hidden or not getattr(w, "ukryta", False)
]
wydatki = [
{"typ": "wydatek", "kwota": x.kwota, "opis": x.opis, "data": x.data, "ukryta": getattr(x, "ukryta", False)}
for x in zb.wydatki
if show_hidden or not getattr(x, "ukryta", False)
]
aktywnosci = wplaty + wydatki
# Przesunięcia przychodzące
przesuniecia_przych = [
{
"typ": "przesunięcie_przych",
"kwota": p.kwota,
"opis": p.opis or f"Przesunięcie z: {p.zbiorka_zrodlo.nazwa}",
"data": p.data,
"zbiorka_id": p.zbiorka_zrodlo_id,
"zbiorka_nazwa": p.zbiorka_zrodlo.nazwa,
"ukryta": getattr(p, "ukryta", False)
}
for p in zb.przesuniecia_przychodzace
if show_hidden or not getattr(p, "ukryta", False)
]
# Przesunięcia wychodzące
przesuniecia_wych = [
{
"typ": "przesunięcie_wych",
"kwota": p.kwota,
"opis": p.opis or f"Przesunięcie do: {p.zbiorka_cel.nazwa}",
"data": p.data,
"zbiorka_id": p.zbiorka_cel_id,
"zbiorka_nazwa": p.zbiorka_cel.nazwa,
"ukryta": getattr(p, "ukryta", False)
}
for p in zb.przesuniecia_wychodzace
if show_hidden or not getattr(p, "ukryta", False)
]
aktywnosci = wplaty + wydatki + przesuniecia_przych + przesuniecia_wych
aktywnosci.sort(key=lambda a: a["data"], reverse=True)
return render_template("zbiorka.html", zbiorka=zb, aktywnosci=aktywnosci, show_hidden=show_hidden)
@@ -428,15 +489,20 @@ def admin_dashboard():
if not current_user.czy_admin:
flash("Brak uprawnień do panelu administracyjnego", "danger")
return redirect(url_for("index"))
active_zbiorki = Zbiorka.query.filter_by(zrealizowana=False).all()
completed_zbiorki = Zbiorka.query.filter_by(zrealizowana=True).all()
active_zbiorki = Zbiorka.query.filter_by(zrealizowana=False).filter(
Zbiorka.typ_zbiorki != 'rezerwa'
).all()
completed_zbiorki = Zbiorka.query.filter_by(zrealizowana=True).filter(
Zbiorka.typ_zbiorki != 'rezerwa'
).all()
return render_template(
"admin/dashboard.html",
active_zbiorki=active_zbiorki,
completed_zbiorki=completed_zbiorki,
)
@app.route("/admin/zbiorka/dodaj", methods=["GET", "POST"])
@app.route("/admin/zbiorka/edytuj/<int:zbiorka_id>", methods=["GET", "POST"])
@login_required
@@ -661,6 +727,75 @@ def dodaj_wplate(zbiorka_id):
return redirect(next_url or url_for("transakcje_zbiorki", zbiorka_id=zb.id))
return render_template("admin/dodaj_wplate.html", zbiorka=zb)
@app.route("/admin/zbiorka/<int:zbiorka_id>/wplata/<int:wplata_id>/przesun", methods=["GET", "POST"])
@login_required
def przesun_wplate(zbiorka_id, wplata_id):
if not current_user.czy_admin:
flash("Brak uprawnień", "danger")
return redirect(url_for("index"))
zb_zrodlo = db.session.get(Zbiorka, zbiorka_id)
if zb_zrodlo is None:
abort(404)
wplata = db.session.get(Wplata, wplata_id)
if wplata is None or wplata.zbiorka_id != zbiorka_id:
abort(404)
if request.method == "POST":
zbiorka_cel_id = request.form.get("zbiorka_cel_id")
if not zbiorka_cel_id:
flash("Wybierz docelową zbiórkę", "danger")
return redirect(url_for("przesun_wplate", zbiorka_id=zbiorka_id, wplata_id=wplata_id))
zb_cel = db.session.get(Zbiorka, int(zbiorka_cel_id))
if zb_cel is None:
flash("Docelowa zbiórka nie istnieje", "danger")
return redirect(url_for("przesun_wplate", zbiorka_id=zbiorka_id, wplata_id=wplata_id))
if zb_zrodlo.stan < wplata.kwota:
flash("Niewystarczające środki w źródłowej zbiórce", "danger")
return redirect(url_for("przesun_wplate", zbiorka_id=zbiorka_id, wplata_id=wplata_id))
opis_dodatkowy = request.form.get("opis", "").strip()
# Opis przesunięcia
if opis_dodatkowy:
opis_przesuniecia = f"Przesunięcie wpłaty: {wplata.opis or 'bez opisu'} - {opis_dodatkowy}"
else:
opis_przesuniecia = f"Przesunięcie wpłaty: {wplata.opis or 'bez opisu'}"
# Utwórz przesunięcie
nowe_przesuniecie = Przesuniecie(
zbiorka_zrodlo_id=zb_zrodlo.id,
zbiorka_cel_id=zb_cel.id,
kwota=wplata.kwota,
opis=opis_przesuniecia,
wplata_id=wplata.id
)
# Zaktualizuj stany
zb_zrodlo.stan = (zb_zrodlo.stan or Decimal("0")) - wplata.kwota
zb_cel.stan = (zb_cel.stan or Decimal("0")) + wplata.kwota
# Przenieś wpłatę do nowej zbiórki
wplata.zbiorka_id = zb_cel.id
db.session.add(nowe_przesuniecie)
db.session.commit()
flash(f"Przesunięto wpłatę {wplata.kwota} PLN do zbiórki '{zb_cel.nazwa}'", "success")
next_url = request.args.get("next")
return redirect(next_url or url_for("transakcje_zbiorki", zbiorka_id=zb_zrodlo.id))
# GET - wyświetl formularz
dostepne_zbiorki = Zbiorka.query.filter(Zbiorka.id != zbiorka_id).all()
return render_template("admin/przesun_wplate.html",
zbiorka=zb_zrodlo,
wplata=wplata,
dostepne_zbiorki=dostepne_zbiorki)
@app.route("/admin/zbiorka/usun/<int:zbiorka_id>", methods=["POST"])
@login_required
@@ -1053,7 +1188,6 @@ def zapisz_wydatek(wydatek_id):
delta = nowa_kwota - (x.kwota or Decimal("0"))
x.kwota = nowa_kwota
x.opis = request.form.get("opis", "")
# wydatki zmniejszają stan; jeżeli delta>0, stan spada bardziej
zb.stan = (zb.stan or Decimal("0")) - delta
db.session.commit()
flash("Wydatek zaktualizowany", "success")
@@ -1076,6 +1210,190 @@ def usun_wydatek(wydatek_id):
return redirect(url_for("transakcje_zbiorki", zbiorka_id=zb.id))
@app.route("/admin/zbiorka/<int:zbiorka_id>/przesuniecie/dodaj", methods=["GET", "POST"])
@login_required
def dodaj_przesuniecie(zbiorka_id):
if not current_user.czy_admin:
flash("Brak uprawnień", "danger")
return redirect(url_for("index"))
zb_zrodlo = db.session.get(Zbiorka, zbiorka_id)
if zb_zrodlo is None:
abort(404)
if request.method == "POST":
try:
kwota = parse_amount(request.form.get("kwota"))
if kwota <= 0:
raise InvalidOperation
except (InvalidOperation, ValueError):
flash("Nieprawidłowa kwota (musi być > 0)", "danger")
return redirect(url_for("dodaj_przesuniecie", zbiorka_id=zbiorka_id))
zbiorka_cel_id = request.form.get("zbiorka_cel_id")
if not zbiorka_cel_id:
flash("Wybierz docelową zbiórkę", "danger")
return redirect(url_for("dodaj_przesuniecie", zbiorka_id=zbiorka_id))
zb_cel = db.session.get(Zbiorka, int(zbiorka_cel_id))
if zb_cel is None:
flash("Docelowa zbiórka nie istnieje", "danger")
return redirect(url_for("dodaj_przesuniecie", zbiorka_id=zbiorka_id))
if zb_zrodlo.stan < kwota:
flash("Niewystarczające środki w źródłowej zbiórce", "danger")
return redirect(url_for("dodaj_przesuniecie", zbiorka_id=zbiorka_id))
opis = request.form.get("opis", "")
nowe_przesuniecie = Przesuniecie(
zbiorka_zrodlo_id=zb_zrodlo.id,
zbiorka_cel_id=zb_cel.id,
kwota=kwota,
opis=opis
)
zb_zrodlo.stan = (zb_zrodlo.stan or Decimal("0")) - kwota
zb_cel.stan = (zb_cel.stan or Decimal("0")) + kwota
db.session.add(nowe_przesuniecie)
db.session.commit()
flash(f"Przesunięto {kwota} PLN do zbiórki '{zb_cel.nazwa}'", "success")
next_url = request.args.get("next")
return redirect(next_url or url_for("transakcje_zbiorki", zbiorka_id=zb_zrodlo.id))
dostepne_zbiorki = Zbiorka.query.filter(Zbiorka.id != zbiorka_id).all()
return render_template("admin/dodaj_przesuniecie.html", zbiorka=zb_zrodlo, dostepne_zbiorki=dostepne_zbiorki)
@app.route("/admin/rezerwy")
@login_required
def lista_rezerwowych():
if not current_user.czy_admin:
flash("Brak uprawnień", "danger")
return redirect(url_for("index"))
rezerwy = Zbiorka.query.filter_by(typ_zbiorki="rezerwa").all()
return render_template("admin/lista_rezerwowych.html", rezerwy=rezerwy)
@app.route("/admin/rezerwa/dodaj", methods=["GET", "POST"])
@login_required
def dodaj_rezerwe():
if not current_user.czy_admin:
flash("Brak uprawnień", "danger")
return redirect(url_for("index"))
if request.method == "POST":
nazwa = request.form.get("nazwa", "").strip()
if not nazwa:
flash("Nazwa jest wymagana", "danger")
global_settings = UstawieniaGlobalne.query.first()
return render_template("admin/formularz_rezerwy.html", zbiorka=None, global_settings=global_settings)
opis = request.form.get("opis", "").strip()
global_settings = UstawieniaGlobalne.query.first()
uzyj_konta = "uzyj_konta" in request.form
uzyj_blik = "uzyj_blik" in request.form
numer_konta = request.form.get("numer_konta", "").strip()
numer_telefonu_blik = request.form.get("numer_telefonu_blik", "").strip()
if uzyj_konta and not numer_konta:
if global_settings and global_settings.numer_konta:
numer_konta = global_settings.numer_konta
if uzyj_blik and not numer_telefonu_blik:
if global_settings and global_settings.numer_telefonu_blik:
numer_telefonu_blik = global_settings.numer_telefonu_blik
nowa_rezerwa = Zbiorka(
nazwa=nazwa,
opis=opis,
cel=Decimal("0"),
stan=Decimal("0"),
typ_zbiorki="rezerwa",
ukryta=True,
ukryj_kwote=False,
pokaz_postep_finanse=False,
pokaz_postep_pozycje=False,
pokaz_postep_kwotowo=False,
uzyj_konta=uzyj_konta,
uzyj_blik=uzyj_blik,
numer_konta=numer_konta if uzyj_konta else "",
numer_telefonu_blik=numer_telefonu_blik if uzyj_blik else ""
)
db.session.add(nowa_rezerwa)
db.session.commit()
flash(f"Lista rezerwowa '{nazwa}' została utworzona", "success")
return redirect(url_for("lista_rezerwowych"))
global_settings = UstawieniaGlobalne.query.first()
return render_template("admin/formularz_rezerwy.html", zbiorka=None, global_settings=global_settings)
@app.route("/admin/rezerwa/edytuj/<int:rezerwa_id>", methods=["GET", "POST"])
@login_required
def edytuj_rezerwe(rezerwa_id):
if not current_user.czy_admin:
flash("Brak uprawnień", "danger")
return redirect(url_for("index"))
zb = db.session.get(Zbiorka, rezerwa_id)
if zb is None or zb.typ_zbiorki != "rezerwa":
abort(404)
if request.method == "POST":
nazwa = request.form.get("nazwa", "").strip()
if not nazwa:
flash("Nazwa jest wymagana", "danger")
global_settings = UstawieniaGlobalne.query.first()
return render_template("admin/formularz_rezerwy.html", zbiorka=zb, global_settings=global_settings)
opis = request.form.get("opis", "").strip()
uzyj_konta = "uzyj_konta" in request.form
uzyj_blik = "uzyj_blik" in request.form
numer_konta = request.form.get("numer_konta", "").strip()
numer_telefonu_blik = request.form.get("numer_telefonu_blik", "").strip()
zb.nazwa = nazwa
zb.opis = opis
zb.uzyj_konta = uzyj_konta
zb.uzyj_blik = uzyj_blik
zb.numer_konta = numer_konta if uzyj_konta else ""
zb.numer_telefonu_blik = numer_telefonu_blik if uzyj_blik else ""
db.session.commit()
flash(f"Lista rezerwowa '{nazwa}' została zaktualizowana", "success")
return redirect(url_for("lista_rezerwowych"))
global_settings = UstawieniaGlobalne.query.first()
return render_template("admin/formularz_rezerwy.html", zbiorka=zb, global_settings=global_settings)
@app.route("/admin/rezerwa/usun/<int:rezerwa_id>", methods=["POST"])
@login_required
def usun_rezerwe(rezerwa_id):
if not current_user.czy_admin:
flash("Brak uprawnień", "danger")
return redirect(url_for("index"))
zb = db.session.get(Zbiorka, rezerwa_id)
if zb is None or zb.typ_zbiorki != "rezerwa":
abort(404)
nazwa = zb.nazwa
db.session.delete(zb)
db.session.commit()
flash(f"Lista rezerwowa '{nazwa}' została usunięta", "success")
return redirect(url_for("lista_rezerwowych"))
@app.route("/favicon.ico")
def favicon():
return "", 204