funkcje rezerw i przesuniec
This commit is contained in:
19
alters.txt
19
alters.txt
@@ -1,17 +1,2 @@
|
||||
ALTER TABLE zbiorka ALTER COLUMN numer_konta DROP NOT NULL;
|
||||
ALTER TABLE zbiorka ALTER COLUMN numer_telefonu_blik DROP NOT NULL;
|
||||
|
||||
_______________________________
|
||||
|
||||
PGSQL
|
||||
ALTER TABLE wplata ADD COLUMN ukryta boolean NOT NULL DEFAULT false;
|
||||
ALTER TABLE wydatek ADD COLUMN ukryta boolean NOT NULL DEFAULT false;
|
||||
|
||||
-- po migracji można zdjąć DEFAULT (opcjonalnie)
|
||||
ALTER TABLE wplata ALTER COLUMN ukryta DROP DEFAULT;
|
||||
ALTER TABLE wydatek ALTER COLUMN ukryta DROP DEFAULT;
|
||||
|
||||
|
||||
SQLite
|
||||
ALTER TABLE wplata ADD COLUMN ukryta INTEGER NOT NULL DEFAULT 0;
|
||||
ALTER TABLE wydatek ADD COLUMN ukryta INTEGER NOT NULL DEFAULT 0;
|
||||
ALTER TABLE zbiorka ADD COLUMN typ_zbiorki VARCHAR(20) NOT NULL DEFAULT 'standardowa';
|
||||
CREATE INDEX idx_zbiorka_typ ON zbiorka(typ_zbiorki);
|
||||
336
app.py
336
app.py
@@ -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
|
||||
|
||||
19
static/js/formularz_rezerwy.js
Normal file
19
static/js/formularz_rezerwy.js
Normal file
@@ -0,0 +1,19 @@
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const uzyjKonta = document.getElementById('uzyj_konta');
|
||||
const kontoField = document.getElementById('konto-field');
|
||||
const uzyjBlik = document.getElementById('uzyj_blik');
|
||||
const blikField = document.getElementById('blik-field');
|
||||
|
||||
if (uzyjKonta && kontoField) {
|
||||
uzyjKonta.addEventListener('change', function() {
|
||||
kontoField.style.display = this.checked ? 'block' : 'none';
|
||||
});
|
||||
}
|
||||
|
||||
if (uzyjBlik && blikField) {
|
||||
uzyjBlik.addEventListener('change', function() {
|
||||
blikField.style.display = this.checked ? 'block' : 'none';
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -11,6 +11,9 @@
|
||||
<a href="{{ url_for('formularz_zbiorek') }}" class="btn btn-primary">
|
||||
➕ Dodaj zbiórkę
|
||||
</a>
|
||||
<a href="{{ url_for('lista_rezerwowych') }}" class="btn btn-outline-light">
|
||||
Listy rezerwowe
|
||||
</a>
|
||||
<a href="{{ url_for('admin_ustawienia') }}" class="btn btn-outline-light">
|
||||
Ustawienia główne
|
||||
</a>
|
||||
|
||||
85
templates/admin/dodaj_przesuniecie.html
Normal file
85
templates/admin/dodaj_przesuniecie.html
Normal file
@@ -0,0 +1,85 @@
|
||||
{% extends 'base.html' %}
|
||||
{% block title %}Przesuń środki - {{ zbiorka.nazwa }}{% endblock %}
|
||||
{% block content %}
|
||||
|
||||
<div class="container my-5">
|
||||
<!-- Nagłówek -->
|
||||
<div class="mb-4">
|
||||
<h1 class="mb-2">
|
||||
<i class="bi bi-arrow-left-right text-primary"></i> Przesuń środki z: {{ zbiorka.nazwa }}
|
||||
</h1>
|
||||
<p class="text-muted">Dostępne środki: <strong class="text-success">{{ zbiorka.stan|round(2) }} PLN</strong></p>
|
||||
</div>
|
||||
|
||||
<!-- Formularz -->
|
||||
<form method="POST">
|
||||
<div class="card shadow-sm mb-3">
|
||||
<div class="card-header bg-transparent">
|
||||
<h5 class="mb-0">
|
||||
<i class="bi bi-box-arrow-in-down text-success"></i> Cel przesunięcia
|
||||
</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="mb-3">
|
||||
<label for="zbiorka_cel_id" class="form-label fw-semibold">
|
||||
Docelowa zbiórka <span class="text-danger">*</span>
|
||||
</label>
|
||||
<select class="form-select" id="zbiorka_cel_id" name="zbiorka_cel_id" required>
|
||||
<option value="">-- Wybierz zbiórkę docelową --</option>
|
||||
{% for zb in dostepne_zbiorki %}
|
||||
<option value="{{ zb.id }}">
|
||||
{% if zb.typ_zbiorki == 'rezerwa' %}
|
||||
{{ zb.nazwa }} (Rezerwa)
|
||||
{% else %}
|
||||
{{ zb.nazwa }}
|
||||
{% endif %}
|
||||
· Stan: {{ zb.stan|round(2) }} PLN
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="kwota" class="form-label fw-semibold">
|
||||
Kwota (PLN) <span class="text-danger">*</span>
|
||||
</label>
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" id="kwota" name="kwota"
|
||||
placeholder="np. 100.00" required>
|
||||
<span class="input-group-text">PLN</span>
|
||||
</div>
|
||||
<small class="text-muted">
|
||||
<i class="bi bi-info-circle"></i> Można użyć przecinka lub kropki
|
||||
</small>
|
||||
</div>
|
||||
|
||||
<div class="mb-0">
|
||||
<label for="opis" class="form-label fw-semibold">
|
||||
Opis <span class="text-muted">(opcjonalny)</span>
|
||||
</label>
|
||||
<textarea class="form-control" id="opis" name="opis" rows="3"
|
||||
placeholder="Dodatkowe informacje o przesunięciu"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Alert informacyjny -->
|
||||
<div class="alert alert-info d-flex align-items-start mb-3">
|
||||
<i class="bi bi-lightbulb fs-5 me-2"></i>
|
||||
<div class="small">
|
||||
<strong>Jak to działa:</strong> Kwota zostanie odjęta ze źródłowej zbiórki i dodana do docelowej.
|
||||
W obu zbiórkach pojawi się wpis o przesunięciu w historii transakcji.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="d-flex gap-2">
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<i class="bi bi-arrow-left-right"></i> Przesuń środki
|
||||
</button>
|
||||
<a href="{{ url_for('transakcje_zbiorki', zbiorka_id=zbiorka.id) }}"
|
||||
class="btn btn-secondary">Anuluj</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
170
templates/admin/formularz_rezerwy.html
Normal file
170
templates/admin/formularz_rezerwy.html
Normal file
@@ -0,0 +1,170 @@
|
||||
{# templates/admin/formularz_rezerwy.html #}
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% set has_obj = zbiorka is not none %}
|
||||
{% set is_edit = has_obj and zbiorka.id is not none %}
|
||||
|
||||
{% block title %}{{ 'Edytuj listę rezerwową' if is_edit else 'Dodaj listę rezerwową' }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container my-4">
|
||||
|
||||
<!-- Nawigacja / powrót -->
|
||||
<div class="d-flex align-items-center gap-2 mb-3">
|
||||
{% if is_edit and zbiorka and zbiorka.id %}
|
||||
<a href="{{ url_for('zbiorka', zbiorka_id=zbiorka.id) }}" class="btn btn-sm btn-outline-light">← Szczegóły
|
||||
listy</a>
|
||||
{% else %}
|
||||
<a href="{{ url_for('lista_rezerwowych') }}" class="btn btn-sm btn-outline-light">← Listy rezerwowe</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="card shadow-sm">
|
||||
<div
|
||||
class="card-header bg-secondary text-white d-flex flex-wrap align-items-center justify-content-between gap-2">
|
||||
<h3 class="card-title mb-0">
|
||||
{{ 'Edytuj listę rezerwową' if is_edit else 'Dodaj nową listę rezerwową' }}
|
||||
</h3>
|
||||
|
||||
{% if is_edit %}
|
||||
<div class="d-flex flex-wrap align-items-center gap-2">
|
||||
<span class="badge bg-dark border" style="border-color: var(--border);">
|
||||
Stan: {{ (zbiorka.stan or 0)|round(2) }} PLN
|
||||
</span>
|
||||
<span class="badge bg-info">Lista rezerwowa</span>
|
||||
</div>
|
||||
{% else %}
|
||||
<small class="opacity-75">Utwórz dedykowaną listę do zarządzania środkami</small>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="card-body">
|
||||
<form method="post" novalidate id="{{ 'form-edit-rezerwa' if is_edit else 'form-add-rezerwa' }}">
|
||||
|
||||
<!-- ======================================== -->
|
||||
<!-- PODSTAWOWE DANE -->
|
||||
<!-- ======================================== -->
|
||||
<h5 class="mb-3">Podstawowe dane</h5>
|
||||
|
||||
<div class="row g-3 mb-4">
|
||||
<div class="col-md-6">
|
||||
<label for="nazwa" class="form-label">Nazwa listy <span class="text-danger">*</span></label>
|
||||
<input type="text" class="form-control" id="nazwa" name="nazwa" required
|
||||
value="{{ zbiorka.nazwa if is_edit else '' }}"
|
||||
placeholder="np. Nadpłaty, Środki rezerwowe">
|
||||
<small class="form-text text-muted">Unikalny identyfikator tej listy rezerwowej</small>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<label for="opis" class="form-label">Opis (opcjonalny)</label>
|
||||
<textarea class="form-control" id="opis" name="opis" rows="3"
|
||||
placeholder="Krótki opis przeznaczenia tej listy">{{ zbiorka.opis if is_edit else '' }}</textarea>
|
||||
<small class="form-text text-muted">Krótki opis, który pomoże w identyfikacji</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr class="my-4">
|
||||
|
||||
<!-- ======================================== -->
|
||||
<!-- KANAŁY PŁATNOŚCI -->
|
||||
<!-- ======================================== -->
|
||||
<h5 class="mb-3">Kanały płatności</h5>
|
||||
<p class="text-muted small mb-3">
|
||||
Określ, czy ta lista ma akceptować bezpośrednie wpłaty od użytkowników.
|
||||
</p>
|
||||
|
||||
<div class="row g-3 mb-4">
|
||||
<!-- Przelew -->
|
||||
<div class="col-md-6">
|
||||
<div class="border rounded p-3 h-100">
|
||||
<div class="form-check mb-2">
|
||||
<input class="form-check-input" type="checkbox" id="uzyj_konta" name="uzyj_konta"
|
||||
{% if is_edit and zbiorka.uzyj_konta %}checked{% endif %}>
|
||||
<label class="form-check-label fw-bold" for="uzyj_konta">
|
||||
Włącz wpłaty przelewem
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div id="konto-field" style="display: {% if is_edit and zbiorka.uzyj_konta %}block{% else %}none{% endif %};">
|
||||
<label for="numer_konta" class="form-label small">Numer konta</label>
|
||||
<input type="text" class="form-control form-control-sm" id="numer_konta"
|
||||
name="numer_konta"
|
||||
value="{{ zbiorka.numer_konta if is_edit else (global_settings.numer_konta if global_settings else '') }}"
|
||||
placeholder="26 cyfr numeru konta">
|
||||
<small class="form-text text-muted">
|
||||
{% if global_settings and global_settings.numer_konta %}
|
||||
Domyślnie: {{ global_settings.numer_konta }}
|
||||
{% else %}
|
||||
Zostaw puste dla globalnego numeru
|
||||
{% endif %}
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- BLIK -->
|
||||
<div class="col-md-6">
|
||||
<div class="border rounded p-3 h-100">
|
||||
<div class="form-check mb-2">
|
||||
<input class="form-check-input" type="checkbox" id="uzyj_blik" name="uzyj_blik"
|
||||
{% if is_edit and zbiorka.uzyj_blik %}checked{% endif %}>
|
||||
<label class="form-check-label fw-bold" for="uzyj_blik">
|
||||
Włącz wpłaty przez BLIK
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div id="blik-field" style="display: {% if is_edit and zbiorka.uzyj_blik %}block{% else %}none{% endif %};">
|
||||
<label for="numer_telefonu_blik" class="form-label small">Numer telefonu BLIK</label>
|
||||
<input type="text" class="form-control form-control-sm" id="numer_telefonu_blik"
|
||||
name="numer_telefonu_blik"
|
||||
value="{{ zbiorka.numer_telefonu_blik if is_edit else (global_settings.numer_telefonu_blik if global_settings else '') }}"
|
||||
placeholder="9 cyfr numeru telefonu">
|
||||
<small class="form-text text-muted">
|
||||
{% if global_settings and global_settings.numer_telefonu_blik %}
|
||||
Domyślnie: {{ global_settings.numer_telefonu_blik }}
|
||||
{% else %}
|
||||
Zostaw puste dla globalnego numeru
|
||||
{% endif %}
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if not is_edit %}
|
||||
<!-- Alert informacyjny tylko przy tworzeniu -->
|
||||
<div class="alert alert-info mb-4">
|
||||
<strong>Wskazówka:</strong> Lista rezerwowa to pomocnicze miejsce do gromadzenia środków,
|
||||
które mogą być później przesuwane do konkretnych zbiórek. Jest ukryta dla użytkowników
|
||||
nieadministracyjnych i nie pojawia się na stronie głównej.
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<hr class="my-4">
|
||||
|
||||
<!-- Przyciski akcji -->
|
||||
<div class="d-flex flex-wrap gap-2 justify-content-between align-items-center">
|
||||
<div class="d-flex gap-2">
|
||||
<button type="submit" class="btn btn-primary">
|
||||
{{ 'Zapisz zmiany' if is_edit else 'Utwórz listę rezerwową' }}
|
||||
</button>
|
||||
<a href="{{ url_for('lista_rezerwowych') }}" class="btn btn-outline-light">Anuluj</a>
|
||||
</div>
|
||||
|
||||
{% if is_edit %}
|
||||
<div>
|
||||
<a href="{{ url_for('transakcje_zbiorki', zbiorka_id=zbiorka.id) }}"
|
||||
class="btn btn-outline-light btn-sm">Zobacz transakcje</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% block extra_scripts %}
|
||||
{{ super() }}
|
||||
<script src="{{ url_for('static', filename='js/formularz_rezerwy.js') }}?v={{ APP_VERSION }}"></script>
|
||||
{% endblock %}
|
||||
146
templates/admin/lista_rezerwowych.html
Normal file
146
templates/admin/lista_rezerwowych.html
Normal file
@@ -0,0 +1,146 @@
|
||||
{% extends 'base.html' %}
|
||||
{% block title %}Listy rezerwowe{% endblock %}
|
||||
{% block content %}
|
||||
|
||||
<div class="container my-4">
|
||||
<div class="d-flex flex-wrap align-items-center justify-content-between gap-2 mb-4">
|
||||
<h2 class="mb-0">Listy rezerwowe</h2>
|
||||
<div class="d-flex flex-wrap gap-2">
|
||||
<a href="{{ url_for('dodaj_rezerwe') }}" class="btn btn-primary">
|
||||
➕ Dodaj listę rezerwową
|
||||
</a>
|
||||
<a href="{{ url_for('admin_dashboard') }}" class="btn btn-outline-light">
|
||||
Panel Admina
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if rezerwy %}
|
||||
<div class="table-responsive mb-5">
|
||||
<table class="table table-dark table-striped table-hover align-middle">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:72px;">ID</th>
|
||||
<th>Nazwa</th>
|
||||
<th style="width:140px;">Widoczność</th>
|
||||
<th style="width:1%;">Opcje</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for r in rezerwy %}
|
||||
<tr>
|
||||
<td class="text-muted">{{ r.id }}</td>
|
||||
<td>
|
||||
<div class="d-flex flex-column">
|
||||
<span class="fw-semibold">
|
||||
<i class="bi bi-wallet2"></i> {{ r.nazwa }}
|
||||
</span>
|
||||
<small class="text-muted">
|
||||
Stan: {{ r.stan|round(2) }} PLN
|
||||
{% if r.opis %}
|
||||
· {{ r.opis[:50] }}{% if r.opis|length > 50 %}...{% endif %}
|
||||
{% endif %}
|
||||
</small>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
{% if r.ukryta %}
|
||||
<span class="badge bg-secondary border" style="border-color: var(--border);">Ukryta</span>
|
||||
{% else %}
|
||||
<span class="badge bg-success">Widoczna</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="text-end">
|
||||
<!-- Grupa akcji: główne + rozwijane -->
|
||||
<div class="btn-group">
|
||||
<a href="{{ url_for('edytuj_rezerwe', rezerwa_id=r.id) }}"
|
||||
class="btn btn-sm btn-outline-light">Edytuj</a>
|
||||
<button class="btn btn-sm btn-outline-light dropdown-toggle dropdown-toggle-split"
|
||||
data-bs-toggle="dropdown" aria-expanded="false" aria-label="Więcej opcji">
|
||||
<span class="visually-hidden">Więcej</span>
|
||||
</button>
|
||||
<ul class="dropdown-menu dropdown-menu-dark dropdown-menu-end shadow">
|
||||
<li>
|
||||
<a class="dropdown-item"
|
||||
href="{{ url_for('zbiorka', zbiorka_id=r.id) }}">
|
||||
<i class="bi bi-eye"></i> Otwórz
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<hr class="dropdown-divider">
|
||||
</li>
|
||||
<li>
|
||||
<a class="dropdown-item"
|
||||
href="{{ url_for('dodaj_wplate', zbiorka_id=r.id, next=url_for('zbiorka', zbiorka_id=r.id)) }}">
|
||||
<i class="bi bi-plus-circle"></i> Dodaj wpłatę
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="dropdown-item"
|
||||
href="{{ url_for('dodaj_wydatek', zbiorka_id=r.id, next=url_for('zbiorka', zbiorka_id=r.id)) }}">
|
||||
<i class="bi bi-dash-circle"></i> Dodaj wydatek
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="dropdown-item"
|
||||
href="{{ url_for('dodaj_przesuniecie', zbiorka_id=r.id, next=url_for('zbiorka', zbiorka_id=r.id)) }}">
|
||||
<i class="bi bi-arrow-left-right"></i> Przesuń środki
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="dropdown-item"
|
||||
href="{{ url_for('transakcje_zbiorki', zbiorka_id=r.id) }}">
|
||||
<i class="bi bi-list-ul"></i> Transakcje
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="dropdown-item"
|
||||
href="{{ url_for('edytuj_stan', zbiorka_id=r.id) }}">
|
||||
<i class="bi bi-pencil-square"></i> Edytuj stan
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<hr class="dropdown-divider">
|
||||
</li>
|
||||
<li>
|
||||
<form action="{{ url_for('zmien_widzialnosc', zbiorka_id=r.id) }}"
|
||||
method="post" class="m-0">
|
||||
<button type="submit" class="dropdown-item">
|
||||
<i class="bi bi-eye{% if r.ukryta %}-slash{% endif %}"></i>
|
||||
{% if r.ukryta %}Pokaż{% else %}Ukryj{% endif %}
|
||||
</button>
|
||||
</form>
|
||||
</li>
|
||||
<li>
|
||||
<hr class="dropdown-divider">
|
||||
</li>
|
||||
<li>
|
||||
<form action="{{ url_for('usun_rezerwe', rezerwa_id=r.id) }}" method="post"
|
||||
class="m-0"
|
||||
onsubmit="return confirm('Czy na pewno usunąć listę \'{{ r.nazwa }}\'?\n\nUWAGA: Zostaną usunięte wszystkie transakcje powiązane z tą listą!');">
|
||||
<button type="submit" class="dropdown-item text-danger">
|
||||
<i class="bi bi-trash"></i> Usuń
|
||||
</button>
|
||||
</form>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% else %}
|
||||
<!-- Empty state -->
|
||||
<div class="card">
|
||||
<div class="card-body text-center py-5">
|
||||
<h5 class="mb-2">Brak list rezerwowych</h5>
|
||||
<p class="text-muted mb-4">Nie masz jeszcze żadnych list rezerwowych. Utwórz pierwszą, aby zarządzać nadpłatami i środkami rezerwowymi.</p>
|
||||
<a href="{{ url_for('dodaj_rezerwe') }}" class="btn btn-primary">Dodaj listę rezerwową</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
119
templates/admin/przesun_wplate.html
Normal file
119
templates/admin/przesun_wplate.html
Normal file
@@ -0,0 +1,119 @@
|
||||
{% extends 'base.html' %}
|
||||
{% block title %}Przesuń wpłatę - {{ zbiorka.nazwa }}{% endblock %}
|
||||
{% block content %}
|
||||
|
||||
<div class="container my-5">
|
||||
<!-- Nagłówek -->
|
||||
<div class="mb-4">
|
||||
<h1 class="mb-2">
|
||||
<i class="bi bi-arrow-left-right text-primary"></i> Przesuń konkretną wpłatę
|
||||
</h1>
|
||||
<p class="text-muted">Przenieś wybraną wpłatę do innej zbiórki</p>
|
||||
</div>
|
||||
|
||||
<!-- Szczegóły wpłaty -->
|
||||
<div class="card shadow-sm mb-4">
|
||||
<div class="card-header bg-transparent">
|
||||
<h5 class="mb-0">
|
||||
<i class="bi bi-receipt text-success"></i> Szczegóły wpłaty
|
||||
</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row g-3">
|
||||
<div class="col-md-6">
|
||||
<div class="border-start border-primary border-3 ps-3">
|
||||
<small class="text-muted d-block">Źródło</small>
|
||||
<strong class="fs-6">{{ zbiorka.nazwa }}</strong>
|
||||
{% if zbiorka.typ_zbiorki == 'rezerwa' %}
|
||||
<span class="badge bg-info ms-2">Lista rezerwowa</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="border-start border-success border-3 ps-3">
|
||||
<small class="text-muted d-block">Kwota</small>
|
||||
<strong class="fs-4 text-success">{{ wplata.kwota|round(2) }} PLN</strong>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="border-start border-secondary border-3 ps-3">
|
||||
<small class="text-muted d-block">Data wpłaty</small>
|
||||
<strong>{{ wplata.data|dt("%d.%m.%Y %H:%M") }}</strong>
|
||||
</div>
|
||||
</div>
|
||||
{% if wplata.opis %}
|
||||
<div class="col-md-6">
|
||||
<div class="border-start border-secondary border-3 ps-3">
|
||||
<small class="text-muted d-block">Opis oryginalny</small>
|
||||
<strong>{{ wplata.opis }}</strong>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Formularz przesunięcia -->
|
||||
<form method="POST">
|
||||
<div class="card shadow-sm mb-3">
|
||||
<div class="card-header bg-transparent">
|
||||
<h5 class="mb-0">
|
||||
<i class="bi bi-box-arrow-right text-warning"></i> Cel przesunięcia
|
||||
</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="mb-3">
|
||||
<label for="zbiorka_cel_id" class="form-label fw-semibold">
|
||||
Docelowa zbiórka <span class="text-danger">*</span>
|
||||
</label>
|
||||
<select class="form-select" id="zbiorka_cel_id" name="zbiorka_cel_id" required>
|
||||
<option value="">-- Wybierz zbiórkę docelową --</option>
|
||||
{% for zb in dostepne_zbiorki %}
|
||||
<option value="{{ zb.id }}">
|
||||
{% if zb.typ_zbiorki == 'rezerwa' %}
|
||||
[Rezerwa] {{ zb.nazwa }} · Stan: {{ zb.stan|round(2) }} PLN
|
||||
{% else %}
|
||||
{{ zb.nazwa }} · Stan: {{ zb.stan|round(2) }} PLN
|
||||
{% endif %}
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="mb-0">
|
||||
<label for="opis" class="form-label fw-semibold">
|
||||
Dodatkowy opis przesunięcia <span class="text-muted">(opcjonalny)</span>
|
||||
</label>
|
||||
<textarea class="form-control" id="opis" name="opis" rows="3"
|
||||
placeholder="np. Powód przesunięcia, notatki..."></textarea>
|
||||
<small class="text-muted">
|
||||
<i class="bi bi-info-circle"></i> Oryginalny opis wpłaty zostanie zachowany
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Alert informacyjny -->
|
||||
<div class="alert alert-info d-flex align-items-start mb-3">
|
||||
<i class="bi bi-lightbulb fs-5 me-2"></i>
|
||||
<div class="small">
|
||||
<strong>Jak to działa:</strong>
|
||||
<ul class="mb-0 mt-1">
|
||||
<li>Wpłata zostanie przeniesiona do wybranej zbiórki wraz z całą historią</li>
|
||||
<li>Zostanie utworzony wpis o przesunięciu w obu zbiórkach</li>
|
||||
<li>Stany finansowe zostaną automatycznie zaktualizowane</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="d-flex gap-2">
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<i class="bi bi-arrow-left-right"></i> Wykonaj przesunięcie
|
||||
</button>
|
||||
<a href="{{ url_for('transakcje_zbiorki', zbiorka_id=zbiorka.id) }}"
|
||||
class="btn btn-secondary">Anuluj</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
@@ -14,6 +14,9 @@
|
||||
<a class="btn btn-sm btn-outline-light" href="{{ url_for('dodaj_wydatek', zbiorka_id=zbiorka.id) }}">
|
||||
Dodaj wydatek
|
||||
</a>
|
||||
<a class="btn btn-sm btn-outline-light" href="{{ url_for('dodaj_przesuniecie', zbiorka_id=zbiorka.id) }}">
|
||||
Przesuń środki
|
||||
</a>
|
||||
<a class="btn btn-sm btn-outline-light" href="{{ url_for('edytuj_stan', zbiorka_id=zbiorka.id) }}">
|
||||
Edytuj stan
|
||||
</a>
|
||||
@@ -61,6 +64,10 @@
|
||||
|
||||
<div class="d-inline-flex flex-nowrap align-items-center gap-2">
|
||||
{% if a.typ == 'wpłata' %}
|
||||
<a class="btn btn btn-sm btn-outline-light btn-edit-wplata"
|
||||
href="{{ url_for('przesun_wplate', zbiorka_id=zbiorka.id, wplata_id=a.id) }}"
|
||||
title="Przesuń tę wpłatę"> Przesuń
|
||||
</a>
|
||||
<button class="btn btn-sm btn-outline-light btn-edit-wplata" data-id="{{ a.id }}"
|
||||
data-kwota="{{ '%.2f'|format(a.kwota) }}" data-opis="{{ a.opis|e if a.opis }}"
|
||||
data-action="{{ url_for('zapisz_wplate', wplata_id=a.id) }}">
|
||||
@@ -114,7 +121,7 @@
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr>
|
||||
<td colspan="5" class="text-center text-muted py-4">Brak transakcji.</td>
|
||||
<td colspan="6" class="text-center text-muted py-4">Brak transakcji.</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
|
||||
@@ -7,14 +7,22 @@
|
||||
{# Wyliczenia postępu finansowego #}
|
||||
{% set has_cel = (zbiorka.cel is defined and zbiorka.cel and zbiorka.cel > 0) %}
|
||||
{% set progress = (zbiorka.stan / zbiorka.cel * 100) if has_cel else 0 %}
|
||||
{% set progress_clamped = 100 if progress > 100 else (0 if progress < 0 else progress) %} {% set
|
||||
is_done=(progress_clamped>= 100) %}
|
||||
{% set progress_clamped = 100 if progress > 100 else (0 if progress < 0 else progress) %}
|
||||
{% set is_done=(progress_clamped>= 100) %}
|
||||
|
||||
<!-- Nagłówek -->
|
||||
<div class="d-flex flex-wrap align-items-center justify-content-between gap-2 mb-3">
|
||||
<h2 class="mb-0">{{ zbiorka.nazwa }}</h2>
|
||||
<h2 class="mb-0">
|
||||
{% if zbiorka.typ_zbiorki == 'rezerwa' %}
|
||||
<i class="bi bi-wallet2"></i>
|
||||
{% endif %}
|
||||
{{ zbiorka.nazwa }}
|
||||
</h2>
|
||||
<div class="d-flex flex-wrap align-items-center gap-2">
|
||||
{% if is_done %}
|
||||
{% if zbiorka.typ_zbiorki == 'rezerwa' %}
|
||||
<span class="badge bg-info">Lista rezerwowa</span>
|
||||
{% endif %}
|
||||
{% if is_done and zbiorka.typ_zbiorki != 'rezerwa' %}
|
||||
<span class="badge rounded-pill" style="background: var(--accent); color:#111;">Zrealizowana</span>
|
||||
{% endif %}
|
||||
{% if zbiorka.ukryj_kwote %}
|
||||
@@ -97,6 +105,8 @@
|
||||
{% set suma_pct = (suma_kupione / suma_all * 100) if suma_all > 0 else 0 %}
|
||||
{% endif %}
|
||||
|
||||
{# Pokazuj sekcję postępu TYLKO dla standardowych zbiórek, NIE dla rezerwowych #}
|
||||
{% if zbiorka.typ_zbiorki != 'rezerwa' %}
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-body">
|
||||
<div class="d-flex flex-wrap align-items-center justify-content-between gap-2 mb-2">
|
||||
@@ -162,6 +172,8 @@
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{# Koniec warunku dla typu zbiórki #}
|
||||
|
||||
</div>
|
||||
|
||||
@@ -174,8 +186,14 @@
|
||||
<div class="card-body d-flex flex-column gap-3">
|
||||
|
||||
<div class="d-flex align-items-center justify-content-between">
|
||||
<h5 class="mb-0">Jak wspomóc?</h5>
|
||||
{% if has_cel and not zbiorka.ukryj_kwote %}
|
||||
<h5 class="mb-0">
|
||||
{% if zbiorka.typ_zbiorki == 'rezerwa' %}
|
||||
Wpłaty
|
||||
{% else %}
|
||||
Jak wspomóc?
|
||||
{% endif %}
|
||||
</h5>
|
||||
{% if has_cel and not zbiorka.ukryj_kwote and zbiorka.typ_zbiorki != 'rezerwa' %}
|
||||
{% set brak = (zbiorka.cel - zbiorka.stan) %}
|
||||
{% if brak > 0 %}
|
||||
<span class="badge bg-warning text-dark border border-warning">Brakuje: {{ brak|round(2) }} PLN</span>
|
||||
@@ -223,17 +241,21 @@
|
||||
|
||||
{% if not zbiorka.ukryj_kwote %}
|
||||
<ul class="list-group list-group-flush small">
|
||||
{% if has_cel %}
|
||||
<li class="list-group-item bg-transparent d-flex justify-content-between">
|
||||
<span>Cel</span>
|
||||
<span class="fw-semibold">{{ zbiorka.cel|round(2) }} PLN</span>
|
||||
</li>
|
||||
{% if zbiorka.typ_zbiorki != 'rezerwa' %}
|
||||
{% if has_cel %}
|
||||
<li class="list-group-item bg-transparent d-flex justify-content-between">
|
||||
<span>Cel</span>
|
||||
<span class="fw-semibold">{{ zbiorka.cel|round(2) }} PLN</span>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
<li class="list-group-item bg-transparent d-flex justify-content-between">
|
||||
<span>Stan</span>
|
||||
<span class="fw-semibold text-success">{{ zbiorka.stan|round(2) }} PLN</span>
|
||||
</li>
|
||||
{% if has_cel %}
|
||||
|
||||
{% if zbiorka.typ_zbiorki != 'rezerwa' and has_cel %}
|
||||
<li class="list-group-item bg-transparent d-flex justify-content-between">
|
||||
<span>
|
||||
{% if brak > 0 %}Brakuje{% elif brak == 0 %}Cel{% else %}Nadwyżka{% endif %}
|
||||
@@ -259,10 +281,18 @@
|
||||
wpłatę</a>
|
||||
<a href="{{ url_for('dodaj_wydatek', zbiorka_id=zbiorka.id) }}" class="btn btn-outline-light btn-sm">Dodaj
|
||||
wydatek</a>
|
||||
<a href="{{ url_for('dodaj_przesuniecie', zbiorka_id=zbiorka.id) }}" class="btn btn-outline-light btn-sm">
|
||||
<i class="bi bi-arrow-left-right"></i> Przesuń środki
|
||||
</a>
|
||||
<a href="{{ url_for('edytuj_stan', zbiorka_id=zbiorka.id) }}" class="btn btn-outline-light btn-sm">Edytuj
|
||||
stan</a>
|
||||
{% if zbiorka.typ_zbiorki != 'rezerwa' %}
|
||||
<a href="{{ url_for('formularz_zbiorek', zbiorka_id=zbiorka.id) }}"
|
||||
class="btn btn-outline-light btn-sm">Edytuj opis</a>
|
||||
{% else %}
|
||||
<a href="{{ url_for('edytuj_rezerwe', rezerwa_id=zbiorka.id) }}"
|
||||
class="btn btn-outline-light btn-sm">Edytuj rezerwę</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
@@ -293,16 +323,41 @@
|
||||
<li class="list-group-item bg-transparent d-flex flex-wrap justify-content-between align-items-center">
|
||||
<div class="me-3">
|
||||
<strong>{{ a.data|dt("%d.%m.%Y %H:%M") }}</strong>
|
||||
<span class="badge {% if a.typ == 'wpłata' %}bg-success{% else %}bg-danger{% endif %} ms-2">
|
||||
{{ a.typ|capitalize }}
|
||||
</span>
|
||||
|
||||
{% if a.typ == 'wpłata' %}
|
||||
<span class="badge bg-success ms-2">Wpłata</span>
|
||||
{% elif a.typ == 'wydatek' %}
|
||||
<span class="badge bg-danger ms-2">Wydatek</span>
|
||||
{% elif a.typ == 'przesunięcie_przych' %}
|
||||
<span class="badge bg-info ms-2">Przesunięcie (↓ przychód)</span>
|
||||
{% elif a.typ == 'przesunięcie_wych' %}
|
||||
<span class="badge bg-warning text-dark ms-2">Przesunięcie (↑ wychód)</span>
|
||||
{% endif %}
|
||||
|
||||
{% if a.opis %}
|
||||
<span class="text-muted">— {{ a.opis }}</span>
|
||||
{% endif %}
|
||||
|
||||
{% if a.typ in ['przesunięcie_przych', 'przesunięcie_wych'] and a.zbiorka_id %}
|
||||
<a href="{{ url_for('zbiorka', zbiorka_id=a.zbiorka_id) }}"
|
||||
class="ms-2 text-decoration-none small">
|
||||
<i class="bi bi-link-45deg"></i>
|
||||
{% if a.typ == 'przesunięcie_przych' %}
|
||||
z: {{ a.zbiorka_nazwa }}
|
||||
{% else %}
|
||||
do: {{ a.zbiorka_nazwa }}
|
||||
{% endif %}
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% if not zbiorka.ukryj_kwote %}
|
||||
<span class="badge bg-dark border ms-auto" style="border-color: var(--border);">
|
||||
{% if a.typ == 'wpłata' %}+{% else %}-{% endif %} {{ a.kwota|round(2) }} PLN
|
||||
{% if a.typ == 'wpłata' or a.typ == 'przesunięcie_przych' %}
|
||||
+{{ a.kwota|round(2) }} PLN
|
||||
{% else %}
|
||||
-{{ a.kwota|round(2) }} PLN
|
||||
{% endif %}
|
||||
</span>
|
||||
{% endif %}
|
||||
</li>
|
||||
@@ -317,7 +372,6 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Akcje dolne -->
|
||||
<div class="d-flex gap-2 justify-content-between mt-3">
|
||||
<div></div>
|
||||
@@ -325,10 +379,10 @@
|
||||
</div>
|
||||
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_scripts %}
|
||||
{{ super() }}
|
||||
<script src="{{ url_for('static', filename='js/zbiorka.js') }}?v={{ APP_VERSION }}"></script>
|
||||
<script src="{{ url_for('static', filename='js/progress.js') }}?v={{ APP_VERSION }}"></script>
|
||||
{% endblock %}
|
||||
{% block extra_scripts %}
|
||||
{{ super() }}
|
||||
<script src="{{ url_for('static', filename='js/zbiorka.js') }}?v={{ APP_VERSION }}"></script>
|
||||
<script src="{{ url_for('static', filename='js/progress.js') }}?v={{ APP_VERSION }}"></script>
|
||||
{% endblock %}
|
||||
|
||||
Reference in New Issue
Block a user