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
@@ -6,11 +6,15 @@ import { getSessionCustomerId, requireSession } from '../middleware/session.js'
|
||||
import { getDocument, listDocuments, Query } from '../services/appwriteAdmin.js'
|
||||
import { getCustomerActivity } from '../services/customerActivity.js'
|
||||
import { customerOrder, customerWorkEntries } from '../services/customerProjectView.js'
|
||||
import { fetchInvoicePdf, getInvoiceRaw, listInvoicesForClient } from '../services/invoiceNinja.js'
|
||||
import { CustomerInvoiceError, TRY_LATER, isStripeInvoiceId, listCustomerInvoices, stripeInvoicePdf } from '../services/customerInvoices.js'
|
||||
import { fetchInvoicePdf, getInvoiceRaw } from '../services/invoiceNinja.js'
|
||||
|
||||
const router = Router()
|
||||
|
||||
/** Rechnungen des eingeloggten Kunden (InvoiceNinja, sofern verknuepft). */
|
||||
/**
|
||||
* Rechnungen des eingeloggten Kunden: Stripe (Abo, Funktionen, Projektrechnungen aus dem WOMS) und InvoiceNinja
|
||||
* (Altbestand, sofern verknuepft), neueste zuerst. incomplete: eine der Quellen hat nicht geantwortet.
|
||||
*/
|
||||
router.get('/invoices', requireSession, async (req, res) => {
|
||||
try {
|
||||
const customer = await getDocument(
|
||||
@@ -20,18 +24,37 @@ router.get('/invoices', requireSession, async (req, res) => {
|
||||
if (!customer) {
|
||||
return res.status(404).json({ error: 'Kunde nicht gefunden' })
|
||||
}
|
||||
if (!customer.invoiceNinjaClientId) {
|
||||
return res.json({ linked: false, invoices: [] })
|
||||
}
|
||||
const invoices = await listInvoicesForClient(customer.invoiceNinjaClientId)
|
||||
return res.json({ linked: true, invoices })
|
||||
return res.json(await listCustomerInvoices(customer))
|
||||
} catch (err) {
|
||||
return res.status(500).json({ error: err.message || 'Rechnungen konnten nicht geladen werden' })
|
||||
if (err instanceof CustomerInvoiceError) return res.status(err.status).json({ error: err.message })
|
||||
console.error('[rechnungen] Liste fehlgeschlagen:', err?.type || err?.name || 'Error', err?.statusCode ?? err?.status ?? '')
|
||||
return res.status(500).json({ error: TRY_LATER })
|
||||
}
|
||||
})
|
||||
|
||||
/** Rechnungs-PDF des eingeloggten Kunden (Download ueber Server-Proxy). */
|
||||
/** PDF an den Browser (Download), nie zwischengespeichert. */
|
||||
function sendPdf(res, pdf, filename) {
|
||||
res.writeHead(200, {
|
||||
'Content-Type': 'application/pdf',
|
||||
'Content-Disposition': `attachment; filename="${filename}"`,
|
||||
'Cache-Control': 'no-store',
|
||||
})
|
||||
return res.end(pdf)
|
||||
}
|
||||
|
||||
/** Rechnungs-PDF des eingeloggten Kunden (Download ueber Server-Proxy; InvoiceNinja oder Stripe). */
|
||||
router.get('/invoices/:invoiceId/pdf', requireSession, async (req, res) => {
|
||||
// Stripe-Rechnung (in_...): Besitz ueber die Stripe-Kunden des Kunden, PDF frisch von Stripe
|
||||
if (isStripeInvoiceId(req.params.invoiceId)) {
|
||||
try {
|
||||
const { pdf, filename } = await stripeInvoicePdf(getSessionCustomerId(req), req.params.invoiceId)
|
||||
return sendPdf(res, pdf, filename)
|
||||
} catch (err) {
|
||||
if (err instanceof CustomerInvoiceError) return res.status(err.status).json({ error: err.message })
|
||||
console.error('[rechnungen] PDF-Abruf fehlgeschlagen:', err?.type || err?.name || 'Error', err?.statusCode ?? err?.status ?? '')
|
||||
return res.status(500).json({ error: 'PDF konnte nicht geladen werden' })
|
||||
}
|
||||
}
|
||||
try {
|
||||
const customer = await getDocument(
|
||||
config.collections.customers,
|
||||
@@ -53,12 +76,7 @@ router.get('/invoices/:invoiceId/pdf', requireSession, async (req, res) => {
|
||||
}
|
||||
|
||||
const pdf = await fetchInvoicePdf(invitationKey)
|
||||
res.writeHead(200, {
|
||||
'Content-Type': 'application/pdf',
|
||||
'Content-Disposition': `attachment; filename="Rechnung-${invoice.number || invoice.id}.pdf"`,
|
||||
'Cache-Control': 'no-store',
|
||||
})
|
||||
return res.end(pdf)
|
||||
return sendPdf(res, pdf, `Rechnung-${invoice.number || invoice.id}.pdf`)
|
||||
} catch (err) {
|
||||
return res.status(err.status || 500).json({ error: err.message || 'PDF konnte nicht geladen werden' })
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user