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:
parent
e5abdcab62
commit
5886e1b269
6 changed files with 53 additions and 12 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 = "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")
|
@app.get("/.well-known/assetlinks.json")
|
||||||
async def assetlinks():
|
async def assetlinks():
|
||||||
|
|
|
||||||
|
|
@ -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,
|
SELECT r.id, r.user_id, r.tier, r.message, r.created_at, r.fulfilled_at,
|
||||||
u.name, u.email, u.billing_address,
|
u.name, u.email, u.billing_address,
|
||||||
u.is_founder, u.is_founder_pending, u.referred_by,
|
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
|
FROM upgrade_requests r
|
||||||
JOIN users u ON u.id = r.user_id
|
JOIN users u ON u.id = r.user_id
|
||||||
ORDER BY r.fulfilled_at IS NOT NULL, r.created_at DESC
|
ORDER BY r.fulfilled_at IS NOT NULL, r.created_at DESC
|
||||||
|
|
|
||||||
|
|
@ -101,9 +101,9 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!-- CSS: Reihenfolge ist wichtig — ?v= zwingt Browser zur Neuladung -->
|
<!-- CSS: Reihenfolge ist wichtig — ?v= zwingt Browser zur Neuladung -->
|
||||||
<link rel="stylesheet" href="/css/design-system.css?v=1073">
|
<link rel="stylesheet" href="/css/design-system.css?v=1074">
|
||||||
<link rel="stylesheet" href="/css/layout.css?v=1073">
|
<link rel="stylesheet" href="/css/layout.css?v=1074">
|
||||||
<link rel="stylesheet" href="/css/components.css?v=1073">
|
<link rel="stylesheet" href="/css/components.css?v=1074">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
|
|
@ -616,10 +616,10 @@
|
||||||
<div id="modal-container"></div>
|
<div id="modal-container"></div>
|
||||||
|
|
||||||
<!-- JS: Reihenfolge ist wichtig — erst Basis, dann Features -->
|
<!-- JS: Reihenfolge ist wichtig — erst Basis, dann Features -->
|
||||||
<script src="/js/api.js?v=1073"></script>
|
<script src="/js/api.js?v=1074"></script>
|
||||||
<script src="/js/ui.js?v=1073"></script>
|
<script src="/js/ui.js?v=1074"></script>
|
||||||
<script src="/js/app.js?v=1073"></script>
|
<script src="/js/app.js?v=1074"></script>
|
||||||
<script src="/js/worlds.js?v=1073"></script>
|
<script src="/js/worlds.js?v=1074"></script>
|
||||||
|
|
||||||
<!-- Feature-Seiten werden lazy geladen -->
|
<!-- Feature-Seiten werden lazy geladen -->
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
Router, State-Management, Navigation, Initialisierung.
|
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 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.
|
||||||
|
|
|
||||||
|
|
@ -3569,6 +3569,15 @@ window.Page_admin = (() => {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:var(--space-2);margin-top:var(--space-3)">
|
<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"
|
<button class="btn adm-invoice-btn"
|
||||||
data-name="${_esc(r.name)}" data-email="${_esc(r.email)}"
|
data-name="${_esc(r.name)}" data-email="${_esc(r.email)}"
|
||||||
data-tier="${r.tier}" data-address="${_esc(r.billing_address || '')}"
|
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);
|
padding:var(--space-2) var(--space-3);border-radius:var(--radius-md);
|
||||||
cursor:pointer;font-size:var(--text-sm);font-weight:600">
|
cursor:pointer;font-size:var(--text-sm);font-weight:600">
|
||||||
${UI.icon('receipt')} Rechnung erstellen
|
${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}"
|
<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;
|
style="background:#16a34a;color:#fff;border:none;
|
||||||
padding:var(--space-2) var(--space-3);border-radius:var(--radius-md);
|
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.');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------
|
// ------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
============================================================ */
|
============================================================ */
|
||||||
|
|
||||||
// ← EINZIGE Stelle für die Version — STATIC_ASSETS und CACHE_VERSION leiten sich ab
|
// ← 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_VERSION = `by-v${VER}`;
|
||||||
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
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue