// Druck-/PDF-Helfer: erzeugt eine eigenständige HTML-Seite mit der Rechnung/Gutschrift // und öffnet sie als neues Fenster zum Drucken oder als PDF speichern. function buildDocumentHTML({ titel, rechnungsNr, datum, student, posten, total, typ }) { const isCredit = typ === 'gutschrift'; const farbe = isCredit ? '#047857' : '#2563eb'; const titleLabel = isCredit ? 'GUTSCHRIFT' : 'RECHNUNG'; const totalSign = isCredit ? '−' : ''; const fusszeile = isCredit ? 'Der Gutschriftbetrag wird auf das hinterlegte Konto überwiesen oder mit künftigen Rechnungen verrechnet.' : 'Zahlbar innerhalb von 14 Tagen ohne Abzug. Bitte geben Sie bei Überweisung die Rechnungsnummer an.'; const rows = posten.map((p, i) => ` ${String(i+1).padStart(2,'0')}
${escapeHtml(p.titel)}
${escapeHtml(p.verlag || '')}
${escapeHtml((p.isbn || '').slice(-13))} ${(p.betrag).toFixed(2).replace('.',',')} € `).join(''); return ` ${escapeHtml(titel)}
Städtisches Gymnasium
Schulstraße 12 · 52538 Gangelt · Tel. 02454 / 12345
${titleLabel}
Nr. ${escapeHtml(rechnungsNr)}
Empfänger
${escapeHtml(student.name)}
Klasse ${escapeHtml(student.klasse)}
${escapeHtml(student.strasse || '—')}
${escapeHtml((student.plz || '') + ' ' + (student.ort || ''))}
Datum
${escapeHtml(datum)}
Schüler-ID
${escapeHtml(student.id)}
${rows}
Pos Bezeichnung ISBN ${isCredit ? 'Gutschrift' : 'Preis'}
Zwischensumme ${totalSign}${total.toFixed(2).replace('.',',')} €
USt. (befreit § 4 Nr. 21 UStG)
${isCredit ? 'Gutschriftbetrag' : 'Gesamt'} ${totalSign}${total.toFixed(2).replace('.',',')} €
`; } function escapeHtml(str) { return String(str ?? '').replace(/[&<>"']/g, c => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c])); } function addProtectedPreviewChrome(w) { const d = w.document; if (!d || !d.body || !d.head) return; const style = d.createElement('style'); style.textContent = ` .codex-print-toolbar { position: fixed; top: 12px; right: 12px; z-index: 9999; display: flex; gap: 8px; } .codex-print-toolbar button { font: inherit; font-size: 12px; padding: 8px 14px; border-radius: 7px; border: 1px solid #d1d5db; background: #fff; color: #0f172a; cursor: pointer; box-shadow: 0 2px 6px rgba(15, 23, 42, 0.15); } .codex-print-toolbar button.primary { background: #1d4ed8; border-color: #1d4ed8; color: #fff; } @media screen { html, body { margin: 0 !important; padding: 0 !important; background: #e5e7eb !important; } body { padding: 16px !important; } .codex-print-page { max-width: 210mm; min-height: 297mm; margin: 44px auto 20px; background: #fff; box-shadow: 0 10px 28px rgba(15, 23, 42, 0.18); padding: 18mm; overflow: hidden; } .footer { position: static !important; left: auto !important; right: auto !important; bottom: auto !important; margin-top: 16mm !important; } } @media print { .codex-print-toolbar { display: none !important; } html, body { background: #fff !important; margin: 0 !important; padding: 0 !important; } .codex-print-page { max-width: none !important; min-height: auto !important; margin: 0 !important; padding: 0 !important; box-shadow: none !important; overflow: visible !important; } } `; d.head.appendChild(style); const toolbar = d.createElement('div'); toolbar.className = 'codex-print-toolbar'; toolbar.innerHTML = ` `; const page = d.createElement('div'); page.className = 'codex-print-page'; while (d.body.firstChild) { page.appendChild(d.body.firstChild); } d.body.appendChild(toolbar); d.body.appendChild(page); const printBtn = toolbar.querySelector('[data-action="print"]'); const closeBtn = toolbar.querySelector('[data-action="close"]'); if (printBtn) printBtn.addEventListener('click', () => w.print()); if (closeBtn) closeBtn.addEventListener('click', () => w.close()); } function writeToPrintWindow(w, html, autoPrint = false, options = {}) { const { addPreviewChrome = false } = options; w.document.open(); w.document.write(html); w.document.close(); if (addPreviewChrome) { addProtectedPreviewChrome(w); } if (autoPrint) { setTimeout(() => { try { w.focus(); w.print(); } catch(e){} }, 300); } } function openPrintWindow(html, autoPrint = false) { const w = window.open('', '_blank'); if (!w) { window.showToast('error', 'Bitte Pop-ups für diese Seite erlauben, um die Rechnung anzuzeigen.'); return; } writeToPrintWindow(w, html, autoPrint); } async function openProtectedDocument(path, autoPrint = false, options = {}) { const { addPreviewChrome = true } = options; const w = window.open('', '_blank'); if (!w) { window.showToast('error', 'Bitte Pop-ups für diese Seite erlauben, um das Dokument anzuzeigen.'); return; } w.document.write('Dokument wird geladen...'); const token = localStorage.getItem('token'); const headers = {}; if (token) { headers.Authorization = `Bearer ${token}`; } try { let res = await fetch(path, { headers }); if (!res.ok && res.status === 501 && path.includes('/pdf')) { const htmlPath = path.replace(/\/pdf(\?|$)/, '/html$1'); res = await fetch(htmlPath, { headers }); } if (!res.ok) { throw new Error(`Dokument konnte nicht geladen werden (HTTP ${res.status})`); } const contentType = (res.headers.get('content-type') || '').toLowerCase(); if (contentType.includes('application/pdf')) { const blob = await res.blob(); const blobUrl = URL.createObjectURL(blob); w.location.replace(blobUrl); window.setTimeout(() => URL.revokeObjectURL(blobUrl), 60_000); return; } const html = await res.text(); writeToPrintWindow(w, html, autoPrint, { addPreviewChrome }); } catch (error) { try { w.document.open(); w.document.write(`

Dokument konnte nicht geöffnet werden

${escapeHtml(error?.message || 'Unbekannter Fehler')}

`); w.document.close(); } catch (e) {} throw error; } } function downloadAsHTMLFile(html, filename) { const blob = new Blob([html], { type: 'text/html;charset=utf-8' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = filename; document.body.appendChild(a); a.click(); setTimeout(() => { document.body.removeChild(a); URL.revokeObjectURL(url); }, 100); } window.buildDocumentHTML = buildDocumentHTML; window.openPrintWindow = openPrintWindow; window.openProtectedDocument = openProtectedDocument; window.downloadAsHTMLFile = downloadAsHTMLFile;