feat: 4-tier role concept, personal balance & cash register system, left off-canvas drawer navigation

This commit is contained in:
2026-09-22 09:05:14 +02:00
parent 0bdcbe7976
commit 896f1d7d08
8 changed files with 1350 additions and 257 deletions
+297
View File
@@ -0,0 +1,297 @@
{% extends "base.html" %}
{% block title %}Kassensystem & Guthaben – {{ tenant.name }}{% endblock %}
{% block content %}
<div class="container">
<!-- Header -->
<div class="kasse-header">
<div>
<span class="badge-role" style="margin-bottom: 6px;">💰 Kasse & Finanzen</span>
<h1>Brotzeit-Kasse & Guthaben</h1>
<p class="admin-sub">
Transparente Abrechnung, bargeldloses Bestellen und Übersicht aller Buchungen.
</p>
</div>
{% if current_user.role in ['admin', 'organizer'] %}
<div class="kasse-actions">
<a href="/einkauf" class="btn-secondary" style="text-decoration: none;">🛒 Zur Einkaufsliste</a>
<a href="/admin" class="btn-secondary" style="text-decoration: none;">⚙️ Zum Orga-Cockpit</a>
</div>
{% endif %}
</div>
{% if success %}
<div class="alert-success" style="margin-bottom: 24px;">
✓ {{ success }}
</div>
{% endif %}
{% if error %}
<div class="alert-error" style="margin-bottom: 24px;">
✕ {{ error }}
</div>
{% endif %}
<!-- ================= PERSÖNLICHE GUTHABEN-KARTE ================= -->
<div class="user-balance-hero">
<div class="balance-hero-left">
<div class="balance-hero-label">Dein persönliches Guthaben:</div>
<div class="balance-hero-amount {% if current_user.balance > 0 %}positive{% elif current_user.balance < 0 %}negative{% endif %}">
{{ "%.2f"|format(current_user.balance) }} €
</div>
<p class="balance-hero-hint">
{% if current_user.balance > 0 %}
Du hast ausreichend Guthaben für kommende Brotzeitrunden. Bei der Abholung kannst du einfach per 1-Klick abbuchen lassen!
{% elif current_user.balance == 0 %}
Dein Guthaben ist aktuell ausgeglichen (0,00 €). Du kannst Bar zahlen oder dein Konto beim Kassenwart aufladen.
{% else %}
Dein Konto ist im Minus. Bitte gleiche den Betrag beim Kassenwart oder Organisator aus.
{% endif %}
</p>
</div>
<div class="balance-hero-right">
<div class="info-bubble">
<span class="bubble-icon">💡</span>
<div>
<strong>Wie funktioniert das Guthaben?</strong>
<p>Gib deinem Kassenwart oder Organisator einfach deinen gewünschten Betrag (z. B. 20 € bar oder per PayPal). Dieser wird deinem Konto gutgeschrieben und bei jeder Brotzeit automatisch abgezogen.</p>
</div>
</div>
</div>
</div>
<!-- ================= KASSENWART / ORGA BEREICH ================= -->
{% if current_user.role in ['admin', 'organizer'] %}
<div class="kassenwart-section">
<div class="section-badge-row">
<h2>👑 Kassenwart-Cockpit (Team-Kasse)</h2>
<span class="badge-role admin">Admin & Orga Ansicht</span>
</div>
<!-- Stats Grid -->
<div class="stats-grid">
<div class="stat-card">
<div class="stat-label">Gesamter Kassenbestand (Team)</div>
<div class="stat-value success">{{ "%.2f"|format(total_team_balance) }} €</div>
</div>
<div class="stat-card">
<div class="stat-label">Mitarbeiter mit Guthaben</div>
<div class="stat-value">{{ positive_users_count }} Personen</div>
</div>
<div class="stat-card">
<div class="stat-label">Gesamtzahl Buchungen</div>
<div class="stat-value">{{ all_transactions|length }} Vorgänge</div>
</div>
</div>
<div class="kasse-columns">
<!-- Formular: Einzahlung buchen -->
<div class="admin-card">
<h2>➕ Guthaben für Mitarbeiter einzahlen</h2>
<p class="card-hint">Verbuche Barzahlungen, PayPal oder Überweisungen direkt auf das Mitarbeiter-Konto:</p>
<form action="/kasse/deposit" method="POST" class="admin-form">
<div class="input-group">
<label for="deposit_user_id">Mitarbeiter auswählen</label>
<select id="deposit_user_id" name="user_id" required>
<option value="">-- Kollege auswählen --</option>
{% for u in team_members %}
<option value="{{ u.id }}">
{{ u.first_name }} {{ u.last_name }} (Aktuell: {{ "%.2f"|format(u.balance) }} €)
</option>
{% endfor %}
</select>
</div>
<div class="input-group">
<label for="deposit_amount">Betrag in Euro (€)</label>
<div class="quick-amounts">
<button type="button" class="quick-amt-btn" onclick="setDepositAmount('10.00')">+10 €</button>
<button type="button" class="quick-amt-btn" onclick="setDepositAmount('20.00')">+20 €</button>
<button type="button" class="quick-amt-btn" onclick="setDepositAmount('30.00')">+30 €</button>
<button type="button" class="quick-amt-btn" onclick="setDepositAmount('50.00')">+50 €</button>
</div>
<input type="number" id="deposit_amount" name="amount" step="0.50" min="0.50" max="500.00" placeholder="20.00" required>
</div>
<div class="input-group">
<label for="deposit_note">Zahlungsart / Bemerkung</label>
<input type="text" id="deposit_note" name="description" placeholder="z. B. Barzahlung an Kasse oder PayPal" value="Barzahlung">
</div>
<button type="submit" class="btn-primary" style="width: 100%; margin-top: 10px;">
💰 Guthaben jetzt gutschreiben
</button>
</form>
</div>
<!-- Tabelle: Mitarbeiter-Salden -->
<div class="admin-card">
<h2>👥 Team-Guthaben im Überblick</h2>
<p class="card-hint">Aktuelle Salden aller registrierten Kollegen:</p>
<div class="orders-table-wrapper" style="max-height: 400px; overflow-y: auto;">
<table class="data-table">
<thead>
<tr>
<th>Kollege</th>
<th>Rolle</th>
<th style="text-align: right;">Guthaben</th>
<th style="text-align: center;">Aktion</th>
</tr>
</thead>
<tbody>
{% for u in team_members %}
<tr>
<td>
<strong>{{ u.first_name }} {{ u.last_name }}</strong>
<br><small class="text-muted">{{ u.email }}</small>
</td>
<td>
{% if u.role == 'admin' %}
<span class="badge-role admin">Admin</span>
{% elif u.role == 'organizer' %}
<span class="badge-role organizer">Orga</span>
{% else %}
<span class="badge-role employee">Mitarbeiter</span>
{% endif %}
</td>
<td style="text-align: right; font-weight: 800; font-size: 1rem; {% if u.balance > 0 %}color: var(--success);{% elif u.balance < 0 %}color: var(--danger);{% endif %}">
{{ "%.2f"|format(u.balance) }} €
</td>
<td style="text-align: center;">
<button type="button" class="btn-fill-small" onclick="quickSelectUser({{ u.id }}, '{{ u.first_name }} {{ u.last_name }}')">
+ Aufladen
</button>
</td>
</tr>
{% else %}
<tr><td colspan="4" class="empty-cell">Keine Teammitglieder vorhanden.</td></tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
<!-- Vollständiges Buchungsjournal (Für Kassenwart) -->
<div class="admin-card" style="margin-top: 28px;">
<h2>📜 Kassenbuch / Transaktions-Journal</h2>
<p class="card-hint">Revisionssichere Historie aller Einzahlungen und Abbuchungen:</p>
<div class="orders-table-wrapper">
<table class="data-table">
<thead>
<tr>
<th>Datum & Zeit</th>
<th>Mitarbeiter</th>
<th>Typ</th>
<th>Beschreibung / Anlass</th>
<th>Verbucht durch</th>
<th style="text-align: right;">Betrag</th>
</tr>
</thead>
<tbody>
{% for t in all_transactions %}
<tr>
<td>{{ t.created_at.strftime('%d.%m.%Y %H:%M') }}</td>
<td>
<strong>{{ t.user.first_name }} {{ t.user.last_name }}</strong>
</td>
<td>
{% if t.transaction_type == 'deposit' %}
<span class="badge-role" style="background: rgba(34, 197, 94, 0.15); color: var(--success);">+ Einzahlung</span>
{% elif t.transaction_type == 'order_charge' %}
<span class="badge-role" style="background: rgba(56, 189, 248, 0.15); color: var(--accent);">- Brotzeit</span>
{% else %}
<span class="badge-role">{{ t.transaction_type }}</span>
{% endif %}
</td>
<td>{{ t.description or '—' }}</td>
<td>
{% if t.created_by %}
{{ t.created_by.first_name }} {{ t.created_by.last_name }}
{% else %}
System
{% endif %}
</td>
<td style="text-align: right; font-weight: 800; {% if t.amount > 0 %}color: var(--success);{% else %}color: var(--text-main);{% endif %}">
{% if t.amount > 0 %}+{% endif %}{{ "%.2f"|format(t.amount) }} €
</td>
</tr>
{% else %}
<tr><td colspan="6" class="empty-cell">Noch keine Buchungen im Journal vorhanden.</td></tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
{% endif %}
<!-- ================= PERSÖNLICHE BUCHUNGSHISTORIE ================= -->
<div class="admin-card" style="margin-top: 28px;">
<h2>📑 Deine persönlichen Buchungen</h2>
<p class="card-hint">Hier siehst du transparent alle Aufladungen und Abbuchungen deines Kontos:</p>
<div class="orders-table-wrapper">
<table class="data-table">
<thead>
<tr>
<th>Datum</th>
<th>Vorgang</th>
<th>Notiz / Anlass</th>
<th style="text-align: right;">Betrag</th>
</tr>
</thead>
<tbody>
{% for t in user_transactions %}
<tr>
<td>{{ t.created_at.strftime('%d.%m.%Y %H:%M') }}</td>
<td>
{% if t.transaction_type == 'deposit' %}
<span class="badge-role" style="background: rgba(34, 197, 94, 0.15); color: var(--success);">+ Einzahlung</span>
{% elif t.transaction_type == 'order_charge' %}
<span class="badge-role" style="background: rgba(56, 189, 248, 0.15); color: var(--accent);">- Brotzeit</span>
{% else %}
<span class="badge-role">{{ t.transaction_type }}</span>
{% endif %}
</td>
<td>{{ t.description or 'Brotzeit-Abrechnung' }}</td>
<td style="text-align: right; font-weight: 800; {% if t.amount > 0 %}color: var(--success);{% else %}color: var(--text-main);{% endif %}">
{% if t.amount > 0 %}+{% endif %}{{ "%.2f"|format(t.amount) }} €
</td>
</tr>
{% else %}
<tr><td colspan="4" class="empty-cell">Bisher sind noch keine Buchungen für dein Konto vorhanden.</td></tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
<script>
function setDepositAmount(val) {
document.getElementById('deposit_amount').value = val;
}
function quickSelectUser(userId, name) {
const select = document.getElementById('deposit_user_id');
if (select) {
select.value = userId;
select.focus();
document.getElementById('deposit_amount').focus();
}
}
</script>
{% endblock %}