Fix+Polish: Phosphor-Icons Danke-Overlay, Quartalsbericht paid_amount
Giftköder Danke-Overlay (poison.js): - Emoji 🚨/🐾/📡 durch Phosphor-Icons ersetzt: siren, paw-print, wifi-slash Quartalsbericht (invoices.py + admin.js): - Backend: _effective_gross() — für bezahlte Rechnungen wird paid_amount statt amount_gross für die Quartalssumme verwendet (Kulanz/Teilzahlung korrekt) - Admin-Preview: effectiveAmt in der Vorschau-Tabelle, bei Abweichung Hinweis "(RG: xx,xx €)" für Nachvollziehbarkeit - CSV: Spalte "Betrag (eingegangen)" + separate Spalte "Rechnungsbetrag" - SW by-v995, APP_VER 995
This commit is contained in:
parent
57192ea010
commit
c59326af17
6 changed files with 32 additions and 17 deletions
|
|
@ -410,7 +410,7 @@ async def serve_media(path: str, request: _Request):
|
||||||
raise _HE(404, "Nicht gefunden.")
|
raise _HE(404, "Nicht gefunden.")
|
||||||
return _media_response(filepath)
|
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")
|
@app.get("/.well-known/assetlinks.json")
|
||||||
async def assetlinks():
|
async def assetlinks():
|
||||||
|
|
|
||||||
|
|
@ -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 ""))
|
entries.sort(key=lambda e: (e.get("created_at") or ""))
|
||||||
|
|
||||||
# Summen: alle Einträge — Storno (-) und Original (+) heben sich gegenseitig auf
|
# 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)
|
total_tax = sum(e.get("tax_amount") or 0 for e in entries)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
Router, State-Management, Navigation, Initialisierung.
|
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 APP_VERSION = '1.6.0'; // ← semantische Version, wird bei make release gesetzt
|
||||||
const IS_STAGING = location.hostname === 'staging.banyaro.app';
|
const IS_STAGING = location.hostname === 'staging.banyaro.app';
|
||||||
// Cache-Bust-Parameter nach Update-Reload sofort entfernen
|
// Cache-Bust-Parameter nach Update-Reload sofort entfernen
|
||||||
|
|
|
||||||
|
|
@ -4415,17 +4415,19 @@ window.Page_admin = (() => {
|
||||||
const escape = v => `"${String(v || '').replace(/"/g, '""')}"`;
|
const escape = v => `"${String(v || '').replace(/"/g, '""')}"`;
|
||||||
|
|
||||||
const statusLabel = { paid: 'Bezahlt', sent: 'Versendet', cancelled: 'Storniert (Original)', storno: 'Stornorechnung' };
|
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 header = 'Nummer;Empfaenger;E-Mail;Datum;Leistungszeitraum;Betrag (eingegangen);Rechnungsbetrag;Status;Versendet am;Zahlungseingang\n';
|
||||||
const csvRows = data.invoices.map(inv => [
|
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.invoice_number,
|
||||||
inv.recipient_name, inv.recipient_email || '',
|
inv.recipient_name, inv.recipient_email || '',
|
||||||
fmtDate(inv.created_at), inv.service_period || '',
|
fmtDate(inv.created_at), inv.service_period || '',
|
||||||
|
fmtEur(effectiveAmt),
|
||||||
fmtEur(inv.amount_gross),
|
fmtEur(inv.amount_gross),
|
||||||
inv.paid_amount != null ? fmtEur(inv.paid_amount) : '',
|
|
||||||
statusLabel[inv.status] || inv.status,
|
statusLabel[inv.status] || inv.status,
|
||||||
fmtDate(inv.sent_at), fmtDate(inv.paid_at)
|
fmtDate(inv.sent_at), fmtDate(inv.paid_at)
|
||||||
].map(escape).join(';')
|
].map(escape).join(';');
|
||||||
).join('\n');
|
}).join('\n');
|
||||||
|
|
||||||
const blob = new Blob(['' + header + csvRows], { type: 'text/csv;charset=utf-8;' });
|
const blob = new Blob(['' + header + csvRows], { type: 'text/csv;charset=utf-8;' });
|
||||||
const url = URL.createObjectURL(blob);
|
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 sL = { draft:'Entwurf', sent:'Versendet', paid:'Bezahlt', cancelled:'Storniert (Orig.)', storno:'Stornorechnung' };
|
||||||
const rows2 = data.invoices.map((inv, i) => {
|
const rows2 = data.invoices.map((inv, i) => {
|
||||||
const isStorno = inv.status === 'storno';
|
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)
|
||||||
|
? ` <span style="font-size:var(--text-xs);color:var(--c-text-muted)">(RG: ${_fmtE(inv.amount_gross)})</span>` : '';
|
||||||
return `
|
return `
|
||||||
<tr style="${i%2===1?'background:var(--c-surface-2)':''}">
|
<tr style="${i%2===1?'background:var(--c-surface-2)':''}">
|
||||||
<td class="adm-td" style="font-family:monospace;font-size:var(--text-xs);${isStorno?'color:var(--c-danger)':''}">${_esc(inv.invoice_number)}</td>
|
<td class="adm-td" style="font-family:monospace;font-size:var(--text-xs);${isStorno?'color:var(--c-danger)':''}">${_esc(inv.invoice_number)}</td>
|
||||||
<td class="adm-td">${_esc(inv.recipient_name)}</td>
|
<td class="adm-td">${_esc(inv.recipient_name)}</td>
|
||||||
<td class="adm-td" style="text-align:right;font-weight:600;${amtColor}">${_fmtE(inv.amount_gross)}</td>
|
<td class="adm-td" style="text-align:right;font-weight:600;${amtColor}">${_fmtE(effectiveAmt)}${amtNote}</td>
|
||||||
<td class="adm-td" style="${isStorno?'color:var(--c-danger)':''}">${sL[inv.status]||inv.status}</td>
|
<td class="adm-td" style="${isStorno?'color:var(--c-danger)':''}">${sL[inv.status]||inv.status}</td>
|
||||||
<td class="adm-td" style="font-size:var(--text-xs);color:var(--c-text-muted)">${_fmtD(inv.created_at)}</td>
|
<td class="adm-td" style="font-size:var(--text-xs);color:var(--c-text-muted)">${_fmtD(inv.created_at)}</td>
|
||||||
</tr>`;
|
</tr>`;
|
||||||
|
|
|
||||||
|
|
@ -583,21 +583,25 @@ window.Page_poison = (() => {
|
||||||
// ----------------------------------------------------------
|
// ----------------------------------------------------------
|
||||||
function _showPoisonThanks(isQueued) {
|
function _showPoisonThanks(isQueued) {
|
||||||
const offlineNote = isQueued
|
const offlineNote = isQueued
|
||||||
? `<p style="font-size:var(--text-xs);color:var(--c-text-muted);margin:var(--space-3) 0 0">
|
? `<p style="font-size:var(--text-xs);color:var(--c-text-muted);margin:var(--space-3) 0 0;display:flex;align-items:center;justify-content:center;gap:var(--space-2)">
|
||||||
📡 Wird synchronisiert sobald du wieder online bist.
|
<svg class="ph-icon" aria-hidden="true" style="flex-shrink:0"><use href="/icons/phosphor.svg#wifi-slash"></use></svg>
|
||||||
|
Wird synchronisiert sobald du wieder online bist.
|
||||||
</p>`
|
</p>`
|
||||||
: '';
|
: '';
|
||||||
UI.modal.open({
|
UI.modal.open({
|
||||||
title: '✅ Danke für deine Meldung!',
|
title: 'Danke für deine Meldung!',
|
||||||
body: `
|
body: `
|
||||||
<div style="text-align:center;padding:var(--space-2) 0 var(--space-4)">
|
<div style="text-align:center;padding:var(--space-2) 0 var(--space-4)">
|
||||||
<div style="font-size:3rem;margin-bottom:var(--space-4)">🚨</div>
|
<div style="margin-bottom:var(--space-4)">
|
||||||
|
<svg class="ph-icon" aria-hidden="true" style="width:48px;height:48px;color:var(--c-danger)"><use href="/icons/phosphor.svg#siren"></use></svg>
|
||||||
|
</div>
|
||||||
<p style="color:var(--c-text);font-size:var(--text-base);line-height:1.7;margin:0">
|
<p style="color:var(--c-text);font-size:var(--text-base);line-height:1.7;margin:0">
|
||||||
Wir kümmern uns darum und melden es den anderen Nutzern in der Umgebung.
|
Wir kümmern uns darum und melden es den anderen Nutzern in der Umgebung.
|
||||||
</p>
|
</p>
|
||||||
<p style="color:var(--c-text-secondary);font-size:var(--text-sm);
|
<p style="color:var(--c-text-secondary);font-size:var(--text-sm);
|
||||||
margin:var(--space-2) 0 0;line-height:1.5">
|
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! 🐾
|
<svg class="ph-icon" aria-hidden="true" style="flex-shrink:0;color:var(--c-primary)"><use href="/icons/phosphor.svg#paw-print"></use></svg>
|
||||||
|
Vielen Dank, dass du die Community schützt!
|
||||||
</p>
|
</p>
|
||||||
${offlineNote}
|
${offlineNote}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
Offline-Cache + Push Notifications + Tile-Cache
|
Offline-Cache + Push Notifications + Tile-Cache
|
||||||
============================================================ */
|
============================================================ */
|
||||||
|
|
||||||
const CACHE_VERSION = 'by-v994';
|
const CACHE_VERSION = 'by-v995';
|
||||||
const CACHE_STATIC = `${CACHE_VERSION}-static`;
|
const CACHE_STATIC = `${CACHE_VERSION}-static`;
|
||||||
const CACHE_TILES = 'ban-yaro-tiles-v1'; // bleibt über SW-Updates erhalten
|
const CACHE_TILES = 'ban-yaro-tiles-v1'; // bleibt über SW-Updates erhalten
|
||||||
const CACHE_API = 'ban-yaro-api-v1'; // API-Response-Cache
|
const CACHE_API = 'ban-yaro-api-v1'; // API-Response-Cache
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue