Rechnungen: Stripe-Rechnungen im Kundenportal anzeigen (Abo, Funktionen, Projektrechnungen)
Der Tab "Rechnungen" las nur InvoiceNinja. Seit dem Finanzen-Livegang laufen Rechnungen aber ueber Stripe; die bezahlte Abo-Rechnung eines Kunden fehlte deshalb im Portal. - /api/invoices fuehrt Stripe (alle Stripe-Kunden des Kunden: Abo-Spiegel und Suche nach metadata.webklarCustomerId) und InvoiceNinja zusammen, neueste zuerst; Entwuerfe und 0-EUR-Rechnungen bleiben verborgen. Faellt eine Quelle aus, kommt der Rest mit incomplete. - /api/invoices/:id/pdf laedt Stripe-PDFs frisch ueber den Server, nur fuer eigene Rechnungen (sonst 404) und nur von *.stripe.com. - Seite: Status-Farben fuer Bezahlt/Offen/Ueberfaellig/Storniert, "Ansehen" statt "Ansehen / Zahlen" bei bezahlten Rechnungen, Hinweis bei unvollstaendiger Liste; der Abo-Hinweis verweist nur noch fuer die Zahlungsart auf "Abo verwalten & Rechnungen". Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
df8b952b81
commit
ff7fe35c06
6 files changed
+606
-34
No files matched your search
+16
-12
@@ -588,41 +588,45 @@ async function loadCustomerInvoices() {
|
||||
const body = document.getElementById('invoices-body')
|
||||
if (!body) return
|
||||
try {
|
||||
// Stripe (Abo, Funktionen, Projektrechnungen) und InvoiceNinja (Altbestand), neueste zuerst
|
||||
const data = await api('/api/invoices')
|
||||
const invoices = data.invoices || []
|
||||
// Zaehler in der Seitenleiste: Rechnungen mit offenem Betrag
|
||||
const openCount = (data.invoices || []).filter((i) => Number(i.balance) > 0).length
|
||||
const openCount = invoices.filter((i) => Number(i.balance) > 0).length
|
||||
const badge = document.getElementById('invoices-badge')
|
||||
if (badge) {
|
||||
badge.textContent = openCount
|
||||
badge.classList.toggle('hidden', !openCount)
|
||||
}
|
||||
if (!data.linked) {
|
||||
body.innerHTML = '<p class="muted">Für dein Konto sind noch keine Rechnungen hinterlegt.</p>'
|
||||
return
|
||||
}
|
||||
if (!data.invoices.length) {
|
||||
body.innerHTML = '<p class="muted">Es liegen noch keine Rechnungen vor.</p>'
|
||||
// Eine der Quellen hat nicht geantwortet: sagen, statt Rechnungen still wegzulassen
|
||||
const gap = data.incomplete
|
||||
? '<p class="kb-note">Deine Rechnungen konnten gerade nicht vollständig geladen werden. Bitte versuche es später noch einmal.</p>'
|
||||
: ''
|
||||
if (!invoices.length) {
|
||||
body.innerHTML = gap || '<p class="muted">Es liegen noch keine Rechnungen vor.</p>'
|
||||
return
|
||||
}
|
||||
const pill = (i) => (i.overdue ? 'pill-red' : i.paid ? 'pill-ok' : i.cancelled ? 'pill-muted' : 'pill-warn')
|
||||
const hasPdf = (i) => i.hasPdf !== false
|
||||
// Klassen je Spalte: in der App-Ansicht wird jede Zeile zur Karte (kundenbereich.css)
|
||||
body.innerHTML = `
|
||||
<div class="kb-table-wrap"><table class="simple-table kb-invoices">
|
||||
<thead><tr><th>Nr.</th><th>Datum</th><th>Betrag</th><th>Offen</th><th>Status</th><th></th></tr></thead>
|
||||
<tbody>
|
||||
${data.invoices.map((i) => `
|
||||
${invoices.map((i) => `
|
||||
<tr class="${Number(i.balance) > 0 ? 'is-open' : 'is-paid'}">
|
||||
<td class="inv-no">${escapeHtml(i.number)}</td>
|
||||
<td class="inv-date">${fmtDateShort(i.date)}</td>
|
||||
<td class="inv-amount">${fmtEuro(i.amount)}</td>
|
||||
<td class="inv-open">${fmtEuro(i.balance)}</td>
|
||||
<td class="inv-status"><span class="pill ${i.overdue ? 'pill-red' : i.statusId === 4 ? 'pill-ok' : 'pill-warn'}">${escapeHtml(i.status)}</span></td>
|
||||
<td class="inv-status"><span class="pill ${pill(i)}">${escapeHtml(i.status)}</span></td>
|
||||
<td class="inv-actions" style="white-space:nowrap">
|
||||
${i.portalUrl ? `<a class="btn-link" href="${escapeAttr(i.portalUrl)}" target="_blank" rel="noreferrer">Ansehen / Zahlen</a><span class="inv-sep" aria-hidden="true"> · </span>` : ''}
|
||||
<a class="btn-link" href="/api/invoices/${escapeAttr(i.id)}/pdf">PDF</a>
|
||||
${i.portalUrl ? `<a class="btn-link" href="${escapeAttr(i.portalUrl)}" target="_blank" rel="noreferrer">${Number(i.balance) > 0 ? 'Ansehen / Zahlen' : 'Ansehen'}</a>${hasPdf(i) ? '<span class="inv-sep" aria-hidden="true"> · </span>' : ''}` : ''}
|
||||
${hasPdf(i) ? `<a class="btn-link" href="/api/invoices/${encodeURIComponent(i.id)}/pdf">PDF</a>` : ''}
|
||||
</td>
|
||||
</tr>`).join('')}
|
||||
</tbody>
|
||||
</table></div>`
|
||||
</table></div>${gap}`
|
||||
} catch (err) {
|
||||
body.innerHTML = `<p class="muted">Rechnungen konnten nicht geladen werden: ${escapeHtml(err.message)}</p>`
|
||||
}
|
||||
|
||||
@@ -192,7 +192,7 @@
|
||||
<p>Deine Rechnungen ansehen, bezahlen und herunterladen.</p>
|
||||
</header>
|
||||
<div class="kb-panel" id="invoices-body"><p class="kb-empty">Lade…</p></div>
|
||||
<p class="kb-note hidden" id="invoices-abo-note">Rechnungen zum Wartungs-Abo findest du über dein <button type="button" class="kb-link" data-open-account>Profilbild</button> oben rechts unter „Abo verwalten & Rechnungen“.</p>
|
||||
<p class="kb-note hidden" id="invoices-abo-note">Die Zahlungsart für dein Abo änderst du über dein <button type="button" class="kb-link" data-open-account>Profilbild</button> oben rechts unter „Abo verwalten & Rechnungen“.</p>
|
||||
</section>
|
||||
|
||||
<!-- Chat -->
|
||||
|
||||
Reference in new issue
Block a user