opcje wydatkow w zbiorce
This commit is contained in:
60
app.py
60
app.py
@@ -65,6 +65,15 @@ class Zbiorka(db.Model):
|
||||
passive_deletes=True,
|
||||
)
|
||||
|
||||
wydatki = db.relationship(
|
||||
"Wydatek",
|
||||
backref="zbiorka",
|
||||
lazy=True,
|
||||
order_by="Wydatek.data.desc()",
|
||||
cascade="all, delete-orphan",
|
||||
passive_deletes=True,
|
||||
)
|
||||
|
||||
|
||||
class Wplata(db.Model):
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
@@ -80,6 +89,18 @@ class Wplata(db.Model):
|
||||
zbiorka = db.relationship("Zbiorka", back_populates="wplaty")
|
||||
|
||||
|
||||
class Wydatek(db.Model):
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
zbiorka_id = db.Column(
|
||||
db.Integer,
|
||||
db.ForeignKey("zbiorka.id", ondelete="CASCADE"),
|
||||
nullable=False,
|
||||
)
|
||||
kwota = db.Column(db.Float, nullable=False)
|
||||
data = db.Column(db.DateTime, default=datetime.utcnow)
|
||||
opis = db.Column(db.Text, nullable=True)
|
||||
|
||||
|
||||
class GlobalSettings(db.Model):
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
numer_konta = db.Column(db.String(50), nullable=False)
|
||||
@@ -193,10 +214,20 @@ def page_not_found(e):
|
||||
@app.route("/zbiorka/<int:zbiorka_id>")
|
||||
def zbiorka(zbiorka_id):
|
||||
zb = Zbiorka.query.get_or_404(zbiorka_id)
|
||||
# Jeżeli zbiórka jest ukryta i użytkownik nie jest administratorem, zwróć 404
|
||||
if zb.ukryta and (not current_user.is_authenticated or not current_user.is_admin):
|
||||
abort(404)
|
||||
return render_template("zbiorka.html", zbiorka=zb)
|
||||
|
||||
# scalona oś czasu: wpłaty + wydatki
|
||||
aktywnosci = [
|
||||
{"typ": "wpłata", "kwota": w.kwota, "opis": w.opis, "data": w.data}
|
||||
for w in zb.wplaty
|
||||
] + [
|
||||
{"typ": "wydatek", "kwota": x.kwota, "opis": x.opis, "data": x.data}
|
||||
for x in zb.wydatki
|
||||
]
|
||||
aktywnosci.sort(key=lambda a: a["data"], reverse=True)
|
||||
|
||||
return render_template("zbiorka.html", zbiorka=zb, aktywnosci=aktywnosci)
|
||||
|
||||
|
||||
# TRASY LOGOWANIA I REJESTRACJI
|
||||
@@ -551,6 +582,31 @@ def admin_ustawienia():
|
||||
)
|
||||
|
||||
|
||||
@app.route("/admin/zbiorka/<int:zbiorka_id>/wydatek/dodaj", methods=["GET", "POST"])
|
||||
@login_required
|
||||
def dodaj_wydatek(zbiorka_id):
|
||||
if not current_user.is_admin:
|
||||
flash("Brak uprawnień", "danger")
|
||||
return redirect(url_for("index"))
|
||||
zb = Zbiorka.query.get_or_404(zbiorka_id)
|
||||
if request.method == "POST":
|
||||
try:
|
||||
kwota = float(request.form["kwota"])
|
||||
if kwota <= 0:
|
||||
raise ValueError
|
||||
except (KeyError, ValueError):
|
||||
flash("Nieprawidłowa kwota", "danger")
|
||||
return redirect(url_for("dodaj_wydatek", zbiorka_id=zbiorka_id))
|
||||
opis = request.form.get("opis", "")
|
||||
nowy_wydatek = Wydatek(zbiorka_id=zb.id, kwota=kwota, opis=opis)
|
||||
zb.stan -= kwota
|
||||
db.session.add(nowy_wydatek)
|
||||
db.session.commit()
|
||||
flash("Wydatek został dodany", "success")
|
||||
return redirect(url_for("admin_dashboard"))
|
||||
return render_template("admin/dodaj_wydatek.html", zbiorka=zb)
|
||||
|
||||
|
||||
@app.route(
|
||||
"/admin/zbiorka/oznacz/niezrealizowana/<int:zbiorka_id>",
|
||||
methods=["POST"],
|
||||
|
5
static/js/dodaj_wydatek.js
Normal file
5
static/js/dodaj_wydatek.js
Normal file
@@ -0,0 +1,5 @@
|
||||
document.addEventListener('input', (e) => {
|
||||
if (e.target && e.target.id === 'opis') {
|
||||
document.getElementById('opisCount').textContent = e.target.value.length;
|
||||
}
|
||||
});
|
@@ -90,6 +90,11 @@
|
||||
href="{{ url_for('dodaj_wplate', zbiorka_id=z.id) }}">Dodaj
|
||||
wpłatę</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="dropdown-item"
|
||||
href="{{ url_for('dodaj_wydatek', zbiorka_id=z.id) }}">Dodaj
|
||||
wydatek</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="dropdown-item"
|
||||
href="{{ url_for('edytuj_stan', zbiorka_id=z.id) }}">Edytuj stan</a>
|
||||
@@ -198,6 +203,11 @@
|
||||
href="{{ url_for('dodaj_wplate', zbiorka_id=z.id) }}">Dodaj
|
||||
wpłatę</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="dropdown-item"
|
||||
href="{{ url_for('dodaj_wydatek', zbiorka_id=z.id) }}">Dodaj
|
||||
wydatek</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="dropdown-item"
|
||||
href="{{ url_for('edytuj_stan', zbiorka_id=z.id) }}">Edytuj stan</a>
|
||||
|
63
templates/admin/dodaj_wydatek.html
Normal file
63
templates/admin/dodaj_wydatek.html
Normal file
@@ -0,0 +1,63 @@
|
||||
{% extends 'base.html' %}
|
||||
{% block title %}Dodaj wydatek{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container my-4">
|
||||
|
||||
<div class="d-flex align-items-center gap-2 mb-3">
|
||||
<a href="{{ url_for('zbiorka', zbiorka_id=zbiorka.id) }}" class="btn btn-sm btn-outline-light border">← Powrót
|
||||
do zbiórki</a>
|
||||
</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">Dodaj wydatek: <span class="fw-semibold">{{ zbiorka.nazwa }}</span></h3>
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
{% if zbiorka.cel %}
|
||||
<span class="badge bg-dark border" style="border-color: var(--border);">Cel: {{ zbiorka.cel|round(2) }}
|
||||
PLN</span>
|
||||
{% endif %}
|
||||
<span class="badge bg-dark border" style="border-color: var(--border);">Stan: {{ zbiorka.stan|round(2)
|
||||
}} PLN</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-body">
|
||||
<form method="post" novalidate>
|
||||
<div class="mb-3">
|
||||
<label for="kwota" class="form-label">Kwota wydatku</label>
|
||||
<div class="input-group">
|
||||
<span class="input-group-text">PLN</span>
|
||||
<input type="number" step="0.01" min="0.01" inputmode="decimal" class="form-control" id="kwota"
|
||||
name="kwota" placeholder="0,00" required aria-describedby="kwotaHelp">
|
||||
</div>
|
||||
<div id="kwotaHelp" class="form-text">Podaj kwotę w złotówkach (min. 0,01).</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="opis" class="form-label">Opis (opcjonalnie)</label>
|
||||
<textarea class="form-control" id="opis" name="opis" rows="3" maxlength="300"
|
||||
aria-describedby="opisHelp"></textarea>
|
||||
<div class="d-flex justify-content-between">
|
||||
<small id="opisHelp" class="form-text text-muted">Krótka notatka do wydatku (widoczna w
|
||||
systemie).</small>
|
||||
<small class="text-muted"><span id="opisCount">0</span>/300</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="d-flex flex-wrap gap-2">
|
||||
<button type="submit" class="btn btn-danger">Dodaj wydatek</button>
|
||||
<a href="{{ url_for('zbiorka', zbiorka_id=zbiorka.id) }}"
|
||||
class="btn btn-outline-light border">Anuluj</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_scripts %}
|
||||
{{ super() }}
|
||||
<script src="{{ url_for('static', filename='js/dodaj_wydatek.js') }}"></script>
|
||||
{% endblock %}
|
@@ -97,8 +97,11 @@
|
||||
|
||||
{% if current_user.is_authenticated and current_user.is_admin %}
|
||||
<div class="d-grid mt-3">
|
||||
<a href="{{ url_for('dodaj_wplate', zbiorka_id=zbiorka.id) }}" class="btn btn-primary">Dodaj
|
||||
wpłatę</a>
|
||||
<a href="{{ url_for('dodaj_wplate', zbiorka_id=zbiorka.id) }}" class="btn btn-primary">Dodaj wpłatę</a>
|
||||
</div>
|
||||
<div class="d-grid mt-2">
|
||||
<a href="{{ url_for('dodaj_wydatek', zbiorka_id=zbiorka.id) }}" class="btn btn-outline-light border">Dodaj
|
||||
wydatek</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
@@ -106,35 +109,40 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Historia wpłat -->
|
||||
<!-- Aktywność (wpłaty + wydatki) -->
|
||||
<div class="card shadow-sm mt-4">
|
||||
<div class="card-header d-flex align-items-center justify-content-between">
|
||||
<h5 class="card-title mb-0">Historia wpłat</h5>
|
||||
{% if zbiorka.wplaty|length > 0 %}
|
||||
<small class="text-muted">Łącznie pozycji: {{ zbiorka.wplaty|length }}</small>
|
||||
<h5 class="card-title mb-0">Aktywność</h5>
|
||||
{% if aktywnosci and aktywnosci|length > 0 %}
|
||||
<small class="text-muted">Łącznie pozycji: {{ aktywnosci|length }}</small>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{% if zbiorka.wplaty and zbiorka.wplaty|length > 0 %}
|
||||
{% if aktywnosci and aktywnosci|length > 0 %}
|
||||
<ul class="list-group list-group-flush">
|
||||
{% for w in zbiorka.wplaty %}
|
||||
{% for a in aktywnosci %}
|
||||
<li class="list-group-item bg-transparent d-flex flex-wrap justify-content-between align-items-center">
|
||||
<div class="me-3">
|
||||
<strong>{{ w.data.strftime('%Y-%m-%d %H:%M:%S') }}</strong>
|
||||
{% if w.opis %}
|
||||
<span class="text-muted">— {{ w.opis }}</span>
|
||||
<strong>{{ a.data.strftime('%Y-%m-%d %H:%M:%S') }}</strong>
|
||||
<span class="badge {% if a.typ == 'wpłata' %}bg-success{% else %}bg-danger{% endif %} ms-2">
|
||||
{{ a.typ|capitalize }}
|
||||
</span>
|
||||
{% if a.opis %}
|
||||
<span class="text-muted">— {{ a.opis }}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if not zbiorka.ukryj_kwote %}
|
||||
<span class="badge bg-dark border ms-auto" style="border-color: var(--border);">
|
||||
{{ w.kwota|round(2) }} PLN
|
||||
{% if a.typ == 'wpłata' %}+{% else %}-{% endif %} {{ a.kwota|round(2) }} PLN
|
||||
</span>
|
||||
{% endif %}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% else %}
|
||||
<div class="text-center py-4">
|
||||
<h6 class="mb-1">Brak wpłat</h6>
|
||||
<p class="text-muted mb-0">Gdy pojawią się pierwsze wpłaty, zobaczysz je tutaj.</p>
|
||||
<h6 class="mb-1">Brak aktywności</h6>
|
||||
<p class="text-muted mb-0">Gdy pojawią się pierwsze wpłaty lub wydatki, zobaczysz je tutaj.</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
Reference in New Issue
Block a user