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,
|
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):
|
class Wplata(db.Model):
|
||||||
id = db.Column(db.Integer, primary_key=True)
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
@@ -80,6 +89,18 @@ class Wplata(db.Model):
|
|||||||
zbiorka = db.relationship("Zbiorka", back_populates="wplaty")
|
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):
|
class GlobalSettings(db.Model):
|
||||||
id = db.Column(db.Integer, primary_key=True)
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
numer_konta = db.Column(db.String(50), nullable=False)
|
numer_konta = db.Column(db.String(50), nullable=False)
|
||||||
@@ -193,10 +214,20 @@ def page_not_found(e):
|
|||||||
@app.route("/zbiorka/<int:zbiorka_id>")
|
@app.route("/zbiorka/<int:zbiorka_id>")
|
||||||
def zbiorka(zbiorka_id):
|
def zbiorka(zbiorka_id):
|
||||||
zb = Zbiorka.query.get_or_404(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):
|
if zb.ukryta and (not current_user.is_authenticated or not current_user.is_admin):
|
||||||
abort(404)
|
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
|
# 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(
|
@app.route(
|
||||||
"/admin/zbiorka/oznacz/niezrealizowana/<int:zbiorka_id>",
|
"/admin/zbiorka/oznacz/niezrealizowana/<int:zbiorka_id>",
|
||||||
methods=["POST"],
|
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
|
href="{{ url_for('dodaj_wplate', zbiorka_id=z.id) }}">Dodaj
|
||||||
wpłatę</a>
|
wpłatę</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<a class="dropdown-item"
|
||||||
|
href="{{ url_for('dodaj_wydatek', zbiorka_id=z.id) }}">Dodaj
|
||||||
|
wydatek</a>
|
||||||
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a class="dropdown-item"
|
<a class="dropdown-item"
|
||||||
href="{{ url_for('edytuj_stan', zbiorka_id=z.id) }}">Edytuj stan</a>
|
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
|
href="{{ url_for('dodaj_wplate', zbiorka_id=z.id) }}">Dodaj
|
||||||
wpłatę</a>
|
wpłatę</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<a class="dropdown-item"
|
||||||
|
href="{{ url_for('dodaj_wydatek', zbiorka_id=z.id) }}">Dodaj
|
||||||
|
wydatek</a>
|
||||||
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a class="dropdown-item"
|
<a class="dropdown-item"
|
||||||
href="{{ url_for('edytuj_stan', zbiorka_id=z.id) }}">Edytuj stan</a>
|
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 %}
|
{% if current_user.is_authenticated and current_user.is_admin %}
|
||||||
<div class="d-grid mt-3">
|
<div class="d-grid mt-3">
|
||||||
<a href="{{ url_for('dodaj_wplate', zbiorka_id=zbiorka.id) }}" class="btn btn-primary">Dodaj
|
<a href="{{ url_for('dodaj_wplate', zbiorka_id=zbiorka.id) }}" class="btn btn-primary">Dodaj wpłatę</a>
|
||||||
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>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
@@ -106,35 +109,40 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Historia wpłat -->
|
<!-- Aktywność (wpłaty + wydatki) -->
|
||||||
<div class="card shadow-sm mt-4">
|
<div class="card shadow-sm mt-4">
|
||||||
<div class="card-header d-flex align-items-center justify-content-between">
|
<div class="card-header d-flex align-items-center justify-content-between">
|
||||||
<h5 class="card-title mb-0">Historia wpłat</h5>
|
<h5 class="card-title mb-0">Aktywność</h5>
|
||||||
{% if zbiorka.wplaty|length > 0 %}
|
{% if aktywnosci and aktywnosci|length > 0 %}
|
||||||
<small class="text-muted">Łącznie pozycji: {{ zbiorka.wplaty|length }}</small>
|
<small class="text-muted">Łącznie pozycji: {{ aktywnosci|length }}</small>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<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">
|
<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">
|
<li class="list-group-item bg-transparent d-flex flex-wrap justify-content-between align-items-center">
|
||||||
<div class="me-3">
|
<div class="me-3">
|
||||||
<strong>{{ w.data.strftime('%Y-%m-%d %H:%M:%S') }}</strong>
|
<strong>{{ a.data.strftime('%Y-%m-%d %H:%M:%S') }}</strong>
|
||||||
{% if w.opis %}
|
<span class="badge {% if a.typ == 'wpłata' %}bg-success{% else %}bg-danger{% endif %} ms-2">
|
||||||
<span class="text-muted">— {{ w.opis }}</span>
|
{{ a.typ|capitalize }}
|
||||||
|
</span>
|
||||||
|
{% if a.opis %}
|
||||||
|
<span class="text-muted">— {{ a.opis }}</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
{% if not zbiorka.ukryj_kwote %}
|
||||||
<span class="badge bg-dark border ms-auto" style="border-color: var(--border);">
|
<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>
|
</span>
|
||||||
|
{% endif %}
|
||||||
</li>
|
</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="text-center py-4">
|
<div class="text-center py-4">
|
||||||
<h6 class="mb-1">Brak wpłat</h6>
|
<h6 class="mb-1">Brak aktywności</h6>
|
||||||
<p class="text-muted mb-0">Gdy pojawią się pierwsze wpłaty, zobaczysz je tutaj.</p>
|
<p class="text-muted mb-0">Gdy pojawią się pierwsze wpłaty lub wydatki, zobaczysz je tutaj.</p>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
Reference in New Issue
Block a user