UX: Upgrades-Tab — Button zeigt vorhandene Rechnung an, SW by-v1074

- Backend: /admin/upgrade-requests liefert pro Request die offene
  Rechnung (id+number+status) per Subquery aus der invoices-Tabelle
  (status draft|sent → also nicht bezahlt, nicht storniert)
- Frontend: Wenn schon eine Rechnung existiert, wird statt 'Rechnung
  erstellen' (orange) der Button 'Rechnung bearbeiten' (gelb,
  #eab308) gezeigt. Klick lädt die Rechnung und öffnet das Modal im
  Edit-Modus — kein doppeltes Anlegen, Nummerierung bleibt sauber.
This commit is contained in:
rene 2026-05-26 13:50:03 +02:00
parent e5abdcab62
commit 5886e1b269
6 changed files with 53 additions and 12 deletions

View file

@ -410,7 +410,7 @@ async def serve_media(path: str, request: _Request):
raise _HE(404, "Nicht gefunden.")
return _media_response(filepath)
APP_VER = "1073" # muss mit APP_VER in app.js übereinstimmen
APP_VER = "1074" # muss mit APP_VER in app.js übereinstimmen
@app.get("/.well-known/assetlinks.json")
async def assetlinks():

View file

@ -1139,7 +1139,16 @@ async def list_upgrade_requests(user=Depends(require_admin)):
SELECT r.id, r.user_id, r.tier, r.message, r.created_at, r.fulfilled_at,
u.name, u.email, u.billing_address,
u.is_founder, u.is_founder_pending, u.referred_by,
COALESCE((SELECT COUNT(*) FROM users WHERE referred_by=u.id), 0) AS referral_count
COALESCE((SELECT COUNT(*) FROM users WHERE referred_by=u.id), 0) AS referral_count,
(SELECT id FROM invoices i WHERE i.user_id=r.user_id
AND i.status IN ('draft','sent')
ORDER BY i.created_at DESC LIMIT 1) AS existing_invoice_id,
(SELECT invoice_number FROM invoices i WHERE i.user_id=r.user_id
AND i.status IN ('draft','sent')
ORDER BY i.created_at DESC LIMIT 1) AS existing_invoice_number,
(SELECT status FROM invoices i WHERE i.user_id=r.user_id
AND i.status IN ('draft','sent')
ORDER BY i.created_at DESC LIMIT 1) AS existing_invoice_status
FROM upgrade_requests r
JOIN users u ON u.id = r.user_id
ORDER BY r.fulfilled_at IS NOT NULL, r.created_at DESC

View file

@ -101,9 +101,9 @@
</script>
<!-- CSS: Reihenfolge ist wichtig — ?v= zwingt Browser zur Neuladung -->
<link rel="stylesheet" href="/css/design-system.css?v=1073">
<link rel="stylesheet" href="/css/layout.css?v=1073">
<link rel="stylesheet" href="/css/components.css?v=1073">
<link rel="stylesheet" href="/css/design-system.css?v=1074">
<link rel="stylesheet" href="/css/layout.css?v=1074">
<link rel="stylesheet" href="/css/components.css?v=1074">
</head>
<body>
@ -616,10 +616,10 @@
<div id="modal-container"></div>
<!-- JS: Reihenfolge ist wichtig — erst Basis, dann Features -->
<script src="/js/api.js?v=1073"></script>
<script src="/js/ui.js?v=1073"></script>
<script src="/js/app.js?v=1073"></script>
<script src="/js/worlds.js?v=1073"></script>
<script src="/js/api.js?v=1074"></script>
<script src="/js/ui.js?v=1074"></script>
<script src="/js/app.js?v=1074"></script>
<script src="/js/worlds.js?v=1074"></script>
<!-- Feature-Seiten werden lazy geladen -->

View file

@ -3,7 +3,7 @@
Router, State-Management, Navigation, Initialisierung.
============================================================ */
const APP_VER = '1073'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen
const APP_VER = '1074'; // ← 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.

View file

@ -3569,6 +3569,15 @@ window.Page_admin = (() => {
</div>
</div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:var(--space-2);margin-top:var(--space-3)">
${r.existing_invoice_id ? `
<button class="btn adm-invoice-edit-btn"
data-invoice-id="${r.existing_invoice_id}"
title="Rechnung ${_esc(r.existing_invoice_number)} (${_esc(r.existing_invoice_status)}) bearbeiten"
style="background:#eab308;color:#1a1a1a;border:none;
padding:var(--space-2) var(--space-3);border-radius:var(--radius-md);
cursor:pointer;font-size:var(--text-sm);font-weight:600">
${UI.icon('receipt')} Rechnung bearbeiten
</button>` : `
<button class="btn adm-invoice-btn"
data-name="${_esc(r.name)}" data-email="${_esc(r.email)}"
data-tier="${r.tier}" data-address="${_esc(r.billing_address || '')}"
@ -3579,7 +3588,7 @@ window.Page_admin = (() => {
padding:var(--space-2) var(--space-3);border-radius:var(--radius-md);
cursor:pointer;font-size:var(--text-sm);font-weight:600">
${UI.icon('receipt')} Rechnung erstellen
</button>
</button>`}
<button class="btn adm-fulfill-btn" data-id="${r.id}" data-name="${_esc(r.name)}" data-tier="${r.tier}"
style="background:#16a34a;color:#fff;border:none;
padding:var(--space-2) var(--space-3);border-radius:var(--radius-md);
@ -3699,6 +3708,29 @@ window.Page_admin = (() => {
});
});
});
// "Rechnung bearbeiten" — lädt existierenden Entwurf/Sent-Rechnung im Edit-Modus
el.querySelectorAll('.adm-invoice-edit-btn').forEach(btn => {
btn.addEventListener('click', async () => {
try {
const inv = await API.get(`/admin/invoices/${btn.dataset.invoiceId}`);
_openNeueRechnungModal(() => {
_tab = 'rechnungen';
_renderTab();
}, {
recipient_name: inv.recipient_name,
recipient_email: inv.recipient_email,
recipient_address: inv.recipient_address || '',
service_period: inv.service_period || '',
discount_pct: inv.discount_pct || 0,
notes: inv.notes || '',
items: inv.items.map(it => ({ description: it.description, quantity: it.quantity, unit_price: it.unit_price })),
}, inv.id);
} catch (e) {
UI.toast.error(e.message || 'Rechnung konnte nicht geladen werden.');
}
});
});
}
// ------------------------------------------------------------------

View file

@ -4,7 +4,7 @@
============================================================ */
// ← EINZIGE Stelle für die Version — STATIC_ASSETS und CACHE_VERSION leiten sich ab
const VER = '1073';
const VER = '1074';
const CACHE_VERSION = `by-v${VER}`;
const CACHE_STATIC = `${CACHE_VERSION}-static`;
const CACHE_TILES = 'ban-yaro-tiles-v1'; // bleibt über SW-Updates erhalten