Konto: echte "Rechnung downloaden"-Funktion neben "Rechnung ansehen"
Neuer Button lädt die Rechnung als echte PDF-Datei herunter — komplett clientseitig mit jsPDF + jspdf-autotable (beide MIT-lizenziert, per dynamic import erst beim Klick nachgeladen, kein Einfluss auf die normale Seitenladezeit). Zeigt exakt dieselben Daten wie der "Rechnung ansehen"- Dialog (Rechnungsnummer/-datum, Verkäufer, Empfänger, Artikeltabelle, Gesamtbetrag, §19-UStG-Hinweis), sauber formatiert mit Kopfzeile, zwei Spalten für Verkäufer/Empfänger und einer echten Tabelle. Datei heißt "Rechnung-<Bestellnummer>.pdf". Mit Node-Testskript verifiziert, dass die PDF-Erzeugung fehlerfrei ein valides PDF mit korrekten Umlauten liefert. Co-Authored-By: Claude Sonnet 5 <[email protected]>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
e523d6f0e5
commit
d7ba1b9bff
@@ -149,6 +149,16 @@ const angekommeneBestellungen = beispielBestellungen.filter((b) => b.status ===
|
||||
<div class="order-actions">
|
||||
{b.tracking && <a class="btn btn-outline btn-sm" href={`https://www.dhl.de/de/privatkunden/dhl-sendungsverfolgung.html?piececode=${b.tracking}`} target="_blank" rel="noopener">{t.accountDash.trackingBtn}</a>}
|
||||
<button type="button" class="btn btn-outline btn-sm" data-open-invoice={`invoice-${b.nummer}`}>{t.accountDash.invoiceBtn}</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-outline btn-sm"
|
||||
data-download-invoice={b.nummer}
|
||||
data-invoice-datum={b.datum}
|
||||
data-invoice-summe={b.summe}
|
||||
data-invoice-artikel={JSON.stringify(b.artikel.map((a) => ({ name: a.name, menge: a.menge, preis: a.preis })))}
|
||||
data-label-default={t.accountDash.invoiceDownloadBtn}
|
||||
data-label-loading={t.accountDash.invoiceDownloading}
|
||||
>{t.accountDash.invoiceDownloadBtn}</button>
|
||||
<a class="btn btn-outline btn-sm" href="/en/shop/">{t.accountDash.reorderBtn}</a>
|
||||
</div>
|
||||
</div>
|
||||
@@ -282,8 +292,8 @@ const angekommeneBestellungen = beispielBestellungen.filter((b) => b.status ===
|
||||
</section>
|
||||
</Layout>
|
||||
|
||||
<script define:vars={{ loginPath: "/en/konto/", fallbackName: "Customer", greetingTemplate: t.accountDash.greeting("{name}"), reviewedBadgeText: t.accountDash.reviewedBadge }}>
|
||||
window.__accountDashVars = { loginPath, fallbackName, greetingTemplate, reviewedBadgeText };
|
||||
<script define:vars={{ loginPath: "/en/konto/", fallbackName: "Customer", greetingTemplate: t.accountDash.greeting("{name}"), reviewedBadgeText: t.accountDash.reviewedBadge , invoiceLang: lang, invoiceLabels: { title: t.accountDash.invoiceTitle, numberLabel: t.accountDash.invoiceNumberLabel, dateLabel: t.accountDash.invoiceDateLabel, sellerLabel: t.accountDash.invoiceSellerLabel, billToLabel: t.accountDash.invoiceBillToLabel, itemLabel: t.accountDash.invoiceItemLabel, qtyLabel: t.accountDash.invoiceQtyLabel, unitPriceLabel: t.accountDash.invoiceUnitPriceLabel, sumLabel: t.accountDash.invoiceSumLabel, totalLabel: t.accountDash.orderTotal, vatNote: t.common.vatNote }}}>
|
||||
window.__accountDashVars = { loginPath, fallbackName, greetingTemplate, reviewedBadgeText, invoiceLang, invoiceLabels };
|
||||
</script>
|
||||
<script>
|
||||
// Schnellnavigation als echte Filter-Tabs: Klick zeigt NUR den passenden Bereich, blendet den
|
||||
@@ -350,7 +360,8 @@ const angekommeneBestellungen = beispielBestellungen.filter((b) => b.status ===
|
||||
</script>
|
||||
<script>
|
||||
import { getAccount, isLoggedIn, logout, setUsername, setPhoto, hatBestellungBewertet, anzeigeName } from "../../../scripts/account";
|
||||
const { loginPath, fallbackName, greetingTemplate, reviewedBadgeText } = (window as any).__accountDashVars;
|
||||
import { rechnungAlsPdfHerunterladen } from "../../../scripts/invoice-pdf";
|
||||
const { loginPath, fallbackName, greetingTemplate, reviewedBadgeText, invoiceLang, invoiceLabels } = (window as any).__accountDashVars;
|
||||
|
||||
// Zugriffsschutz: diese Seite ist nur für angemeldete Kund:innen gedacht. Es gibt noch kein
|
||||
// echtes Backend, das den Zugriff serverseitig verweigern könnte (Phase 2) — deshalb hier per
|
||||
@@ -432,6 +443,30 @@ const angekommeneBestellungen = beispielBestellungen.filter((b) => b.status ===
|
||||
dialog?.showModal();
|
||||
});
|
||||
});
|
||||
|
||||
// Rechnung als echte PDF-Datei herunterladen (siehe scripts/invoice-pdf.ts) — dieselben
|
||||
// Daten wie im "Rechnung ansehen"-Dialog, per dynamic import erst beim Klick nachgeladen.
|
||||
document.querySelectorAll<HTMLButtonElement>("[data-download-invoice]").forEach((btn) => {
|
||||
btn.addEventListener("click", async () => {
|
||||
const artikel = JSON.parse(btn.dataset.invoiceArtikel || "[]");
|
||||
btn.disabled = true;
|
||||
btn.textContent = btn.dataset.labelLoading || "…";
|
||||
try {
|
||||
await rechnungAlsPdfHerunterladen({
|
||||
nummer: btn.dataset.downloadInvoice || "",
|
||||
datum: btn.dataset.invoiceDatum || "",
|
||||
summe: Number(btn.dataset.invoiceSumme || 0),
|
||||
artikel,
|
||||
empfaenger: anzeigeName(getAccount()) || fallbackName,
|
||||
lang: invoiceLang,
|
||||
labels: invoiceLabels,
|
||||
});
|
||||
} finally {
|
||||
btn.disabled = false;
|
||||
btn.textContent = btn.dataset.labelDefault || "";
|
||||
}
|
||||
});
|
||||
});
|
||||
document.querySelectorAll<HTMLDialogElement>(".invoice-dialog").forEach((dialog) => {
|
||||
dialog.querySelectorAll<HTMLButtonElement>("[data-close-invoice]").forEach((btn) => {
|
||||
btn.addEventListener("click", () => dialog.close());
|
||||
|
||||
Reference in New Issue
Block a user