This commit is contained in:
cay 2026-03-27 13:47:51 +00:00
parent 596f60c1a4
commit 97ad1359e1
3 changed files with 77 additions and 1 deletions

View File

@ -342,5 +342,20 @@ router.get('/export/pdf/:invoiceId', requireAdmin, async (req, res) => {
}
});
// POST Rechnung stornieren
router.post('/invoices/:id/cancel', requireAdmin, async (req, res) => {
const period = req.body.period || currentPeriod();
try {
await db.query(
"UPDATE invoices SET status='cancelled' WHERE id=?",
[req.params.id]
);
res.redirect(`/admin/billing?period=${period}&success=Rechnung+storniert`);
} catch (err) {
res.redirect(`/admin/billing?period=${period}&error=Fehler+beim+Stornieren`);
}
});
module.exports = router;
module.exports.currentPeriod = currentPeriod;

View File

@ -93,6 +93,16 @@ router.get('/', requireAdmin, async (req, res) => {
ORDER BY m.effective_end ASC
`);
// Stornierte Rechnungen
const [cancelledInvoices] = await db.query(`
SELECT i.*, m.first_name, m.last_name, m.email, t.name as tariff_name
FROM invoices i
JOIN memberships m ON i.membership_id = m.id
LEFT JOIN tariffs t ON m.tariff_id = t.id
WHERE i.status = 'cancelled'
ORDER BY i.created_at DESC
`);
// Alle Mitglieder für Dropdowns
const [members] = await db.query(`
SELECT m.id, m.first_name, m.last_name
@ -121,6 +131,7 @@ router.get('/', requireAdmin, async (req, res) => {
expiringContracts,
members,
openInvoicesDropdown,
cancelledInvoices,
success: req.query.success || null,
error: req.query.error || null
});

View File

@ -74,6 +74,7 @@
<button class="ftab" onclick="showTab('chargebacks', this)">↩️ Rückläufer</button>
<button class="ftab" onclick="showTab('dunning', this)">📬 Mahngebühren</button>
<button class="ftab" onclick="showTab('expiring', this)">⏳ Auslaufende Verträge</button>
<button class="ftab" onclick="showTab('cancelled', this)">🚫 Storniert</button>
<button class="ftab" onclick="showTab('settings', this)">⚙️ Einstellungen</button>
</div>
@ -285,6 +286,55 @@
</div>
</div>
<!-- ===== TAB: STORNIERT ===== -->
<div class="ftab-content" id="tab-cancelled">
<div class="finance-card">
<h3>Stornierte Rechnungen (<%= cancelledInvoices.length %>)</h3>
<% if (cancelledInvoices.length === 0) { %>
<p class="karte-empty">Keine stornierten Rechnungen vorhanden.</p>
<% } else { %>
<div class="table-wrap">
<table class="admin-table">
<thead>
<tr>
<th>Nr.</th>
<th>Mitglied</th>
<th>Tarif</th>
<th>Periode</th>
<th>Betrag</th>
<th>Storniert am</th>
<th>Aktion</th>
</tr>
</thead>
<tbody>
<% cancelledInvoices.forEach(inv => { %>
<tr>
<td class="invoice-nr">PF24-<%= String(inv.id).padStart(6,'0') %></td>
<td>
<strong><%= inv.last_name %>, <%= inv.first_name %></strong><br>
<small class="text-muted"><%= inv.email %></small>
</td>
<td><%= inv.tariff_name || '' %></td>
<td><%= inv.period %></td>
<td><span style="text-decoration:line-through;color:var(--text-muted)">
<%= Number(inv.amount).toFixed(2).replace('.', ',') %> €
</span></td>
<td><%= new Date(inv.created_at).toLocaleDateString('de-DE') %></td>
<td>
<a href="/admin/billing?period=<%= inv.period %>" class="btn btn-sm btn-outline">
Zur Abrechnung
</a>
</td>
</tr>
<% }) %>
</tbody>
</table>
</div>
<% } %>
</div>
</div>
<!-- ===== TAB: EINSTELLUNGEN ===== -->
<div class="ftab-content" id="tab-settings">
<div class="finance-card" style="max-width:400px">
@ -579,7 +629,7 @@ new Chart(ctx, {
// Tabs
const tabMap = {
chart: 0, open: 1, chargebacks: 2, dunning: 3, expiring: 4, settings: 5
chart: 0, open: 1, chargebacks: 2, dunning: 3, expiring: 4, cancelled: 5, settings: 6
};
function showTab(name, el) {