diff --git a/backend/main.py b/backend/main.py index c2a6929..2a4beb4 100644 --- a/backend/main.py +++ b/backend/main.py @@ -410,7 +410,7 @@ async def serve_media(path: str, request: _Request): raise _HE(404, "Nicht gefunden.") return _media_response(filepath) -APP_VER = "994" # muss mit APP_VER in app.js übereinstimmen +APP_VER = "995" # muss mit APP_VER in app.js übereinstimmen @app.get("/.well-known/assetlinks.json") async def assetlinks(): diff --git a/backend/routes/invoices.py b/backend/routes/invoices.py index 53562f0..28f3d15 100644 --- a/backend/routes/invoices.py +++ b/backend/routes/invoices.py @@ -486,7 +486,13 @@ def get_quarterly(year: int, q: int, admin=Depends(require_admin)): entries.sort(key=lambda e: (e.get("created_at") or "")) # Summen: alle Einträge — Storno (-) und Original (+) heben sich gegenseitig auf - total_gross = sum(e.get("amount_gross") or 0 for e in entries) + # Für bezahlte Rechnungen den tatsächlich eingegangenen Betrag verwenden + def _effective_gross(e): + if e.get("status") == "paid" and e.get("paid_amount") is not None: + return e["paid_amount"] + return e.get("amount_gross") or 0 + + total_gross = sum(_effective_gross(e) for e in entries) total_tax = sum(e.get("tax_amount") or 0 for e in entries) return { diff --git a/backend/static/js/app.js b/backend/static/js/app.js index 67c2516..91ccd34 100644 --- a/backend/static/js/app.js +++ b/backend/static/js/app.js @@ -3,7 +3,7 @@ Router, State-Management, Navigation, Initialisierung. ============================================================ */ -const APP_VER = '994'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen +const APP_VER = '995'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen const APP_VERSION = '1.6.0'; // ← semantische Version, wird bei make release gesetzt const IS_STAGING = location.hostname === 'staging.banyaro.app'; // Cache-Bust-Parameter nach Update-Reload sofort entfernen diff --git a/backend/static/js/pages/admin.js b/backend/static/js/pages/admin.js index c3ae84f..b7acbf5 100644 --- a/backend/static/js/pages/admin.js +++ b/backend/static/js/pages/admin.js @@ -4415,17 +4415,19 @@ window.Page_admin = (() => { const escape = v => `"${String(v || '').replace(/"/g, '""')}"`; const statusLabel = { paid: 'Bezahlt', sent: 'Versendet', cancelled: 'Storniert (Original)', storno: 'Stornorechnung' }; - const header = 'Nummer;Empfaenger;E-Mail;Datum;Leistungszeitraum;Betrag;Eingegangener Betrag;Status;Versendet am;Zahlungseingang\n'; - const csvRows = data.invoices.map(inv => [ + const header = 'Nummer;Empfaenger;E-Mail;Datum;Leistungszeitraum;Betrag (eingegangen);Rechnungsbetrag;Status;Versendet am;Zahlungseingang\n'; + const csvRows = data.invoices.map(inv => { + const effectiveAmt = (inv.status === 'paid' && inv.paid_amount != null) ? inv.paid_amount : inv.amount_gross; + return [ inv.invoice_number, inv.recipient_name, inv.recipient_email || '', fmtDate(inv.created_at), inv.service_period || '', + fmtEur(effectiveAmt), fmtEur(inv.amount_gross), - inv.paid_amount != null ? fmtEur(inv.paid_amount) : '', statusLabel[inv.status] || inv.status, fmtDate(inv.sent_at), fmtDate(inv.paid_at) - ].map(escape).join(';') - ).join('\n'); + ].map(escape).join(';'); + }).join('\n'); const blob = new Blob(['' + header + csvRows], { type: 'text/csv;charset=utf-8;' }); const url = URL.createObjectURL(blob); @@ -4457,12 +4459,15 @@ window.Page_admin = (() => { const sL = { draft:'Entwurf', sent:'Versendet', paid:'Bezahlt', cancelled:'Storniert (Orig.)', storno:'Stornorechnung' }; const rows2 = data.invoices.map((inv, i) => { const isStorno = inv.status === 'storno'; - const amtColor = isStorno ? 'color:var(--c-danger)' : (inv.amount_gross < 0 ? 'color:var(--c-danger)' : ''); + const effectiveAmt = (inv.status === 'paid' && inv.paid_amount != null) ? inv.paid_amount : inv.amount_gross; + const amtColor = isStorno ? 'color:var(--c-danger)' : (effectiveAmt < 0 ? 'color:var(--c-danger)' : ''); + const amtNote = (inv.status === 'paid' && inv.paid_amount != null && Math.abs(inv.paid_amount - inv.amount_gross) >= 0.01) + ? ` (RG: ${_fmtE(inv.amount_gross)})` : ''; return ` ${_esc(inv.invoice_number)} ${_esc(inv.recipient_name)} - ${_fmtE(inv.amount_gross)} + ${_fmtE(effectiveAmt)}${amtNote} ${sL[inv.status]||inv.status} ${_fmtD(inv.created_at)} `; diff --git a/backend/static/js/pages/poison.js b/backend/static/js/pages/poison.js index 2a3988b..150fddc 100644 --- a/backend/static/js/pages/poison.js +++ b/backend/static/js/pages/poison.js @@ -583,21 +583,25 @@ window.Page_poison = (() => { // ---------------------------------------------------------- function _showPoisonThanks(isQueued) { const offlineNote = isQueued - ? `

- 📡 Wird synchronisiert sobald du wieder online bist. + ? `

+ + Wird synchronisiert sobald du wieder online bist.

` : ''; UI.modal.open({ - title: '✅ Danke für deine Meldung!', + title: 'Danke für deine Meldung!', body: `
-
🚨
+
+ +

Wir kümmern uns darum und melden es den anderen Nutzern in der Umgebung.

- Vielen Dank, dass du die Community schützt! 🐾 + margin:var(--space-2) 0 0;line-height:1.5;display:flex;align-items:center;justify-content:center;gap:var(--space-2)"> + + Vielen Dank, dass du die Community schützt!

${offlineNote}
diff --git a/backend/static/sw.js b/backend/static/sw.js index bb12985..599618d 100644 --- a/backend/static/sw.js +++ b/backend/static/sw.js @@ -3,7 +3,7 @@ Offline-Cache + Push Notifications + Tile-Cache ============================================================ */ -const CACHE_VERSION = 'by-v994'; +const CACHE_VERSION = 'by-v995'; const CACHE_STATIC = `${CACHE_VERSION}-static`; const CACHE_TILES = 'ban-yaro-tiles-v1'; // bleibt über SW-Updates erhalten const CACHE_API = 'ban-yaro-api-v1'; // API-Response-Cache