From cabb2fd6f7e1f51811e749848a2cae428e3d9d00 Mon Sep 17 00:00:00 2001 From: rene Date: Fri, 15 May 2026 13:15:49 +0200 Subject: [PATCH] =?UTF-8?q?Fix:=20iOS=20Modal=20scrollIntoView=20bei=20Tas?= =?UTF-8?q?tatur;=20CSV=20Stornierte=20mit=200=E2=82=AC=20+=20Stornonummer?= =?UTF-8?q?=20(SW=20by-v975)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/main.py | 2 +- backend/routes/invoices.py | 11 +++++++---- backend/static/js/app.js | 2 +- backend/static/js/pages/admin.js | 24 +++++++++++++++--------- backend/static/js/ui.js | 16 +++++++++++++--- backend/static/sw.js | 2 +- 6 files changed, 38 insertions(+), 19 deletions(-) diff --git a/backend/main.py b/backend/main.py index fe2f332..1ff02d8 100644 --- a/backend/main.py +++ b/backend/main.py @@ -408,7 +408,7 @@ async def serve_media(path: str, request: _Request): raise _HE(404, "Nicht gefunden.") return _media_response(filepath) -APP_VER = "974" # muss mit APP_VER in app.js übereinstimmen +APP_VER = "975" # 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 28dcb21..17b6083 100644 --- a/backend/routes/invoices.py +++ b/backend/routes/invoices.py @@ -413,14 +413,17 @@ def get_quarterly(year: int, q: int, admin=Depends(require_admin)): period = f"Q{q} {year} ({labels[q]} – {ends[q]})" with db() as conn: + # Alle Rechnungen außer Entwürfe — Stornierte bleiben mit 0€ für lückenlose Nummerierung rows = conn.execute( - "SELECT * FROM invoices WHERE status IN ('paid','sent') AND created_at >= ? AND created_at <= ? ORDER BY id", + "SELECT * FROM invoices WHERE status != 'draft' AND created_at >= ? AND created_at <= ? ORDER BY id", (from_date, to_date + "T23:59:59Z") ).fetchall() - total_net = sum(r["amount_net"] for r in rows) - total_tax = sum(r["tax_amount"] for r in rows) - total_gross = sum(r["amount_gross"] for r in rows) + # Summen nur für paid/sent (Stornierte zählen nicht zum Umsatz) + active = [r for r in rows if r["status"] in ("paid", "sent")] + total_net = sum(r["amount_net"] for r in active) + total_tax = sum(r["tax_amount"] for r in active) + total_gross = sum(r["amount_gross"] for r in active) return { "period": period, diff --git a/backend/static/js/app.js b/backend/static/js/app.js index c3be70d..314d227 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 = '974'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen +const APP_VER = '975'; // ← 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 9559af1..9dfc165 100644 --- a/backend/static/js/pages/admin.js +++ b/backend/static/js/pages/admin.js @@ -4355,15 +4355,21 @@ window.Page_admin = (() => { const fmtDate = iso => iso ? new Date(iso).toLocaleDateString('de-DE') : ''; const escape = v => `"${String(v || '').replace(/"/g, '""')}"`; - const header = 'Nummer;Empfaenger;E-Mail;Rechnungsdatum;Leistungszeitraum;Nettobetrag;Bruttobetrag;Eingegangener Betrag;Status;Versendet am;Zahlungseingang\n'; - const csvRows = data.invoices.map(inv => - [inv.invoice_number, inv.recipient_name, inv.recipient_email || '', - fmtDate(inv.created_at), inv.service_period || '', - fmtEur(inv.amount_net), fmtEur(inv.amount_gross), - inv.paid_amount != null ? fmtEur(inv.paid_amount) : '', - inv.status, fmtDate(inv.sent_at), fmtDate(inv.paid_at) - ].map(escape).join(';') - ).join('\n'); + const header = 'Nummer;Stornonummer;Empfaenger;E-Mail;Rechnungsdatum;Leistungszeitraum;Nettobetrag;Bruttobetrag;Eingegangener Betrag;Status;Versendet am;Zahlungseingang\n'; + const csvRows = data.invoices.map(inv => { + const cancelled = inv.status === 'cancelled'; + return [ + inv.invoice_number, + inv.cancellation_number || '', + inv.recipient_name, inv.recipient_email || '', + fmtDate(inv.created_at), inv.service_period || '', + cancelled ? '0.00' : fmtEur(inv.amount_net), + cancelled ? '0.00' : fmtEur(inv.amount_gross), + cancelled ? '0.00' : (inv.paid_amount != null ? fmtEur(inv.paid_amount) : ''), + cancelled ? 'Storniert' : inv.status, + fmtDate(inv.sent_at), fmtDate(inv.paid_at) + ].map(escape).join(';'); + }).join('\n'); const blob = new Blob(['' + header + csvRows], { type: 'text/csv;charset=utf-8;' }); const url = URL.createObjectURL(blob); diff --git a/backend/static/js/ui.js b/backend/static/js/ui.js index a0b69b1..0729963 100644 --- a/backend/static/js/ui.js +++ b/backend/static/js/ui.js @@ -83,7 +83,7 @@ const UI = (() => { document.getElementById('modal-container').appendChild(overlay); document.documentElement.classList.add('modal-open'); - // Tastatur auf Mobilgeräten: Modal nach oben schieben wenn Keyboard erscheint + // Tastatur auf Mobilgeräten: Modal nach oben schieben + fokussiertes Feld einblenden let _vvCleanup = null; const vv = window.visualViewport; if (vv) { @@ -100,16 +100,26 @@ const UI = (() => { }; } - _current = { overlay, onClose, _vvCleanup }; + // Fokussiertes Feld in den sichtbaren Bereich scrollen (iOS) + const _onFocusin = e => { + const el = e.target; + if (el.tagName === 'INPUT' || el.tagName === 'TEXTAREA' || el.tagName === 'SELECT') { + setTimeout(() => el.scrollIntoView({ block: 'nearest', behavior: 'smooth' }), 320); + } + }; + overlay.addEventListener('focusin', _onFocusin); + + _current = { overlay, onClose, _vvCleanup, _onFocusin }; return overlay.querySelector('.modal'); } function close() { if (!_current) return; - const { onClose, _vvCleanup } = _current; + const { onClose, _vvCleanup, _onFocusin } = _current; onClose?.(); _vvCleanup?.(); + if (_onFocusin) _current.overlay.removeEventListener('focusin', _onFocusin); _current.overlay.remove(); document.documentElement.classList.remove('modal-open'); _current = null; diff --git a/backend/static/sw.js b/backend/static/sw.js index da7d824..402bf98 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-v974'; +const CACHE_VERSION = 'by-v975'; 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