Steuerkram/src/Views/settings/form.php

655 lines
36 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php use Steuerkram\View; $h = fn($v) => View::h($v); ?>
<form id="settings-form" method="post" action="/settings">
<input type="hidden" id="hf-mail-subject" name="mail_subject_template"
value="<?= $h($settings['mail_subject_template'] ?? 'Rechnung %RNR%') ?>">
<input type="hidden" id="hf-mail-body" name="mail_body_template"
value="<?= $h($settings['mail_body_template'] ?? '') ?>">
<div style="display:grid;grid-template-columns:1fr 1fr;gap:1.5rem;align-items:start">
<!-- Firmendaten -->
<div>
<div class="card mb-2">
<div class="card-header"><div class="card-title">Firmendaten</div></div>
<div class="card-body">
<div class="form-group">
<label>Firma / Name *</label>
<input type="text" name="firma" value="<?= $h($settings['firma'] ?? '') ?>"
class="<?= isset($errors['firma']) ? 'input-error' : '' ?>" required>
<?php if (isset($errors['firma'])): ?><div class="error-text"><?= $h($errors['firma']) ?></div><?php endif; ?>
</div>
<div class="form-group">
<label>Inhaber</label>
<input type="text" name="inhaber" value="<?= $h($settings['inhaber'] ?? '') ?>">
</div>
<div class="form-group">
<label>Straße *</label>
<input type="text" name="strasse" value="<?= $h($settings['strasse'] ?? '') ?>" required>
</div>
<div style="display:grid;grid-template-columns:100px 1fr;gap:.5rem">
<div class="form-group">
<label>PLZ *</label>
<input type="text" name="plz" value="<?= $h($settings['plz'] ?? '') ?>" required>
</div>
<div class="form-group">
<label>Ort *</label>
<input type="text" name="ort" value="<?= $h($settings['ort'] ?? '') ?>" required>
</div>
</div>
<div class="form-grid form-grid-2">
<div class="form-group">
<label>Telefon</label>
<input type="tel" name="telefon" value="<?= $h($settings['telefon'] ?? '') ?>">
</div>
<div class="form-group">
<label>E-Mail</label>
<input type="email" name="email" value="<?= $h($settings['email'] ?? '') ?>">
</div>
</div>
<div class="form-group">
<label>Website</label>
<input type="url" name="website" value="<?= $h($settings['website'] ?? '') ?>">
</div>
</div>
</div>
<div class="card mb-2">
<div class="card-header"><div class="card-title">Steuerdaten</div></div>
<div class="card-body">
<div class="form-group">
<label>Umsatzsteuer-ID</label>
<input type="text" name="ust_id" value="<?= $h($settings['ust_id'] ?? '') ?>" placeholder="DE123456789">
</div>
<div class="form-group">
<label>Steuernummer (Finanzamt)</label>
<input type="text" name="steuernummer" value="<?= $h($settings['steuernummer'] ?? '') ?>">
</div>
<div class="form-group">
<label>Finanzamt</label>
<input type="text" name="finanzamt" value="<?= $h($settings['finanzamt'] ?? '') ?>">
</div>
<div class="form-group">
<label>ELSTER-Steuernummer (13-stellig, Format: FFFBBBBBBUUUUP)</label>
<input type="text" name="ustva_steuernummer" value="<?= $h($settings['ustva_steuernummer'] ?? '') ?>"
placeholder="z.B. 2893012345678">
<div class="form-hint">Wird für die ELSTER XML-Datei benötigt</div>
</div>
<div class="form-group">
<label>Bundesland (für ELSTER)</label>
<select name="ustva_bundesland">
<option value="">– wählen –</option>
<?php foreach (['BW'=>'Baden-Württemberg','BY'=>'Bayern','BE'=>'Berlin','BB'=>'Brandenburg','HB'=>'Bremen','HH'=>'Hamburg','HE'=>'Hessen','MV'=>'Mecklenburg-Vorpommern','NI'=>'Niedersachsen','NW'=>'Nordrhein-Westfalen','RP'=>'Rheinland-Pfalz','SL'=>'Saarland','SN'=>'Sachsen','ST'=>'Sachsen-Anhalt','SH'=>'Schleswig-Holstein','TH'=>'Thüringen'] as $code => $name): ?>
<option value="<?= $code ?>"<?= ($settings['ustva_bundesland'] ?? '') === $code ? ' selected' : '' ?>><?= $name ?></option>
<?php endforeach; ?>
</select>
</div>
</div>
</div>
<div class="card mb-2">
<div class="card-header"><div class="card-title">Bankdaten</div></div>
<div class="card-body">
<div class="form-group">
<label>Bankname</label>
<input type="text" name="bank_name" value="<?= $h($settings['bank_name'] ?? '') ?>">
</div>
<div class="form-group">
<label>IBAN</label>
<input type="text" name="iban" value="<?= $h($settings['iban'] ?? '') ?>" placeholder="DE89 3704 0044 0532 0130 00">
</div>
<div class="form-group">
<label>BIC</label>
<input type="text" name="bic" value="<?= $h($settings['bic'] ?? '') ?>">
</div>
</div>
</div>
<div class="card mb-2">
<div class="card-header"><div class="card-title">Nummernkreise</div></div>
<div class="card-body">
<div class="form-grid form-grid-2">
<div class="form-group">
<label>Rechnungs-Präfix</label>
<input type="text" name="invoice_nr_prefix" value="<?= $h($settings['invoice_nr_prefix'] ?? 'AR') ?>" maxlength="10">
</div>
<div class="form-group">
<label>Beleg-Präfix</label>
<input type="text" name="beleg_nr_prefix" value="<?= $h($settings['beleg_nr_prefix'] ?? 'BLG') ?>" maxlength="10">
</div>
<div class="form-group">
<label>Anlagen-Präfix</label>
<input type="text" name="anlage_nr_prefix" value="<?= $h($settings['anlage_nr_prefix'] ?? 'ANL') ?>" maxlength="10">
</div>
<div class="form-group">
<label>Kunden-Präfix</label>
<input type="text" name="customer_nr_prefix" value="<?= $h($settings['customer_nr_prefix'] ?? 'K-') ?>" maxlength="10">
</div>
</div>
<div class="form-group">
<label>Standard-Zahlungsziel (Tage)</label>
<input type="number" name="payment_days_default" value="<?= $h($settings['payment_days_default'] ?? 14) ?>" min="0">
</div>
</div>
</div>
</div>
<!-- rechte Spalte -->
<div>
<!-- Betriebsmodus -->
<div class="card mb-2" style="border:2px solid <?= ($settings['test_mode'] ?? 1) ? 'var(--color-warning,#f59e0b)' : 'var(--color-success,#22c55e)' ?>">
<div class="card-header">
<div class="card-title">Betriebsmodus</div>
<?php if ($settings['test_mode'] ?? 1): ?>
<span class="badge badge-yellow">Testinstallation</span>
<?php else: ?>
<span class="badge badge-green">Produktiv</span>
<?php endif; ?>
</div>
<div class="card-body">
<label style="display:flex;align-items:center;gap:.75rem;cursor:pointer;user-select:none">
<div style="position:relative;width:48px;height:26px;flex-shrink:0">
<input type="checkbox" name="test_mode" id="test_mode_toggle"
<?= ($settings['test_mode'] ?? 1) ? 'checked' : '' ?>
style="opacity:0;width:0;height:0;position:absolute"
onchange="updateModeLabel(this)">
<span id="toggle_track" style="
position:absolute;inset:0;border-radius:13px;transition:background .2s;
background:<?= ($settings['test_mode'] ?? 1) ? '#f59e0b' : '#22c55e' ?>;
"></span>
<span id="toggle_thumb" style="
position:absolute;top:3px;width:20px;height:20px;border-radius:50%;
background:#fff;transition:left .2s;box-shadow:0 1px 3px rgba(0,0,0,.3);
left:<?= ($settings['test_mode'] ?? 1) ? '3px' : '25px' ?>;
"></span>
</div>
<span id="mode_label" style="font-weight:600">
<?= ($settings['test_mode'] ?? 1) ? 'Testinstallation (Löschen von Buchungen erlaubt)' : 'Produktivbetrieb (GoBD-strikt)' ?>
</span>
</label>
<div class="form-hint mt-1" id="mode_hint" style="color:<?= ($settings['test_mode'] ?? 1) ? '#92400e' : '#166534' ?>">
<?php if ($settings['test_mode'] ?? 1): ?>
Im Testmodus können Buchungen und Belege direkt gelöscht werden. Nicht GoBD-konform.
<?php else: ?>
Im Produktivmodus sind Buchungen unveränderlich. Korrekturen nur per Stornobuchung moeglich.
<?php endif; ?>
</div>
</div>
</div>
<!-- Verschlüsselungs-Schlüssel (APP_KEY) -->
<?php
$appKey = (string)\Steuerkram\Config::get('app_key', '');
$appKeyValid = $appKey !== '' && preg_match('/^[0-9a-f]{64}$/i', $appKey) === 1;
$appKeyColor = $appKeyValid ? 'var(--color-success,#22c55e)' : 'var(--color-danger,#dc2626)';
?>
<div class="card mb-2" style="border:2px solid <?= $appKeyColor ?>">
<div class="card-header">
<div class="card-title">App-Verschlüsselungsschlüssel</div>
<?php if ($appKeyValid): ?>
<span class="badge badge-green">Gesetzt</span>
<?php elseif ($appKey !== ''): ?>
<span class="badge badge-red">Ungültig</span>
<?php else: ?>
<span class="badge badge-red">Fehlt</span>
<?php endif; ?>
</div>
<div class="card-body">
<p class="text-sm" style="margin:0 0 .75rem 0">
<?php if ($appKeyValid): ?>
<span style="color:#166534">✓ <code>APP_KEY</code> ist gültig (64-stellige Hex-Zeichenkette).</span><br>
Wird zum Verschlüsseln sensibler Felder (PayPal-Signature, FinTS-PIN) genutzt.
<strong>Niemals ändern</strong> – sonst sind alle bestehenden verschlüsselten Werte verloren.
<?php elseif ($appKey !== ''): ?>
<span style="color:#dc2626">✗ <code>APP_KEY</code> ist gesetzt, aber kein gültiges Format (erwartet: 64 Hex-Zeichen).</span><br>
Bitte prüfen und korrigieren.
<?php else: ?>
<span style="color:#dc2626">✗ <code>APP_KEY</code> ist nicht gesetzt.</span><br>
Ohne Schlüssel können keine Bank-Direktverbindungen mit PIN/Secret gespeichert werden.
<?php endif; ?>
</p>
<?php if (!$appKeyValid): ?>
<button type="button" id="btn-generate-app-key" class="btn btn-primary btn-sm">
Neuen Schlüssel generieren
</button>
<?php endif; ?>
<div id="app-key-result" style="display:none;margin-top:.75rem">
<div class="text-sm" style="margin-bottom:.4rem;font-weight:600">Neuer Schlüssel:</div>
<div style="display:flex;gap:.5rem;align-items:stretch">
<code id="app-key-value"
style="flex:1;padding:.5rem .6rem;background:#f1f5f9;border:1px solid var(--color-border,#e2e8f0);border-radius:var(--radius);font-family:monospace;font-size:.8rem;word-break:break-all;user-select:all"></code>
<button type="button" id="btn-copy-app-key" class="btn btn-secondary btn-sm" title="In Zwischenablage kopieren">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24"
fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<rect x="9" y="9" width="13" height="13" rx="2"/>
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/>
</svg>
</button>
</div>
<div class="text-sm" style="margin-top:.75rem;color:#92400e;background:#fffbeb;border:1px solid #fde68a;border-radius:var(--radius);padding:.6rem .75rem">
<strong>So aktivierst du den Schlüssel:</strong>
<ol style="margin:.4rem 0 0 1.2rem;padding:0">
<li>Schlüssel oben kopieren.</li>
<li>Auf dem Host in <code>.env</code> die Zeile <code>APP_KEY=...</code> mit dem neuen Wert ergänzen.</li>
<li>Container neu starten: <code>docker compose restart web</code></li>
</ol>
Der Schlüssel wird <strong>nur einmal angezeigt</strong> und nirgends gespeichert.
Sichere ihn auch im Backup mit – ohne ihn sind alle verschlüsselten Werte verloren.
</div>
</div>
</div>
</div>
<!-- KI-Kontierungsvorschlag -->
<div class="card mb-2">
<div class="card-header"><div class="card-title">KI-Kontierungsvorschlag</div></div>
<div class="card-body">
<label style="display:flex;align-items:center;gap:.75rem;cursor:pointer;user-select:none">
<input type="checkbox" name="ki_vorschlag_aktiv" value="1"
<?= !empty($settings['ki_vorschlag_aktiv']) ? 'checked' : '' ?>>
<span style="font-weight:600">Automatischen Kontierungsvorschlag erzeugen</span>
</label>
<p class="text-muted text-sm" style="margin:.5rem 0 .75rem 1.75rem">
Beim automatischen Nextcloud-Import wird für jeden Beleg ein Kontierungsvorschlag per LLM erzeugt.
Konfiguration via <code>.env</code> (lokales Ollama oder Remote-API).
</p>
<?php
$llm = new \Steuerkram\Services\LlmService();
$providerLabel = $llm->getProviderLabel();
$reachable = $providerLabel !== null ? $llm->isReachable() : false;
$ollamaUrlEnv = getenv('OLLAMA_URL') ?: ($_ENV['OLLAMA_URL'] ?? '');
?>
<p class="text-sm" style="margin:0 0 0 1.75rem;color:<?= $reachable ? 'var(--color-success,#16a34a)' : '#dc2626' ?>">
<?php if ($reachable): ?>
✓ Provider erreichbar: <?= $h($providerLabel) ?>
<?php elseif ($providerLabel): ?>
✗ Provider konfiguriert (<?= $h($providerLabel) ?>), aber Server nicht erreichbar.
Prüfe ob Ollama auf dem Host läuft und auf allen Interfaces hört (<code>OLLAMA_HOST=0.0.0.0:11434</code>).
Details im PHP-Fehlerlog des Webservers (<code>docker compose logs web</code>).
<?php elseif ($ollamaUrlEnv !== ''): ?>
✗ OLLAMA_URL gesetzt (<?= $h(rtrim($ollamaUrlEnv, '/')) ?>), aber kein Provider erkannt.
<?php else: ?>
— Kein LLM-Provider konfiguriert (Auto-Discovery localhost:11434 fehlgeschlagen)
<?php endif; ?>
</p>
</div>
</div>
<!-- Nextcloud-Import -->
<div class="card mb-2">
<div class="card-header"><div class="card-title">Nextcloud-Belegimport</div></div>
<div class="card-body">
<p class="text-muted text-sm" style="margin:0 0 .75rem">
Belege werden automatisch per WebDAV aus dem angegebenen Nextcloud-Ordner importiert.
Verarbeitete Dateien werden in den Erledigte-Ordner verschoben (oder gelöscht, wenn leer).
</p>
<div class="form-group">
<label>Nextcloud-URL</label>
<input type="url" name="nextcloud_url"
value="<?= $h($settings['nextcloud_url'] ?? '') ?>"
placeholder="https://cloud.example.com">
</div>
<div class="form-grid form-grid-2">
<div class="form-group">
<label>Benutzername</label>
<input type="text" name="nextcloud_user"
value="<?= $h($settings['nextcloud_user'] ?? '') ?>"
autocomplete="off">
</div>
<div class="form-group">
<label>App-Passwort</label>
<input type="password" name="nextcloud_pass"
value="<?= $h($settings['nextcloud_pass'] ?? '') ?>"
autocomplete="new-password"
placeholder="<?= !empty($settings['nextcloud_pass']) ? '(gespeichert)' : '' ?>">
<div class="form-hint">Empfohlen: App-Passwort aus Nextcloud → Einstellungen → Sicherheit</div>
</div>
</div>
<div class="form-group">
<label>Import-Ordner</label>
<input type="text" name="nextcloud_folder"
value="<?= $h($settings['nextcloud_folder'] ?? '/Belege/Import') ?>"
placeholder="/Belege/Import">
</div>
<div class="form-group">
<label>Erledigte-Ordner <span class="text-muted">(leer = Datei löschen)</span></label>
<input type="text" name="nextcloud_done"
value="<?= $h($settings['nextcloud_done'] ?? '') ?>"
placeholder="/Belege/Importiert">
</div>
<div class="form-hint">
Import manuell starten:
<code>docker compose exec web php scripts/nextcloud-import.php</code>
</div>
</div>
</div>
<!-- E-Mail-Versand -->
<div class="card mb-2">
<div class="card-header"><div class="card-title">E-Mail-Versand (SMTP)</div></div>
<div class="card-body">
<div class="form-group">
<label>SMTP-Host</label>
<input type="text" name="mail_smtp_host" value="<?= $h($settings['mail_smtp_host'] ?? '') ?>" placeholder="smtp.example.com">
</div>
<div class="form-grid form-grid-2">
<div class="form-group">
<label>Port</label>
<input type="number" name="mail_smtp_port" value="<?= $h($settings['mail_smtp_port'] ?? 587) ?>" min="1" max="65535">
</div>
<div class="form-group">
<label>Verschlüsselung</label>
<select name="mail_smtp_encryption">
<option value="tls" <?= ($settings['mail_smtp_encryption'] ?? 'tls') === 'tls' ? 'selected' : '' ?>>STARTTLS (Port 587)</option>
<option value="ssl" <?= ($settings['mail_smtp_encryption'] ?? '') === 'ssl' ? 'selected' : '' ?>>SSL/TLS (Port 465)</option>
<option value="none" <?= ($settings['mail_smtp_encryption'] ?? '') === 'none' ? 'selected' : '' ?>>Keine</option>
</select>
</div>
</div>
<div class="form-grid form-grid-2">
<div class="form-group">
<label>SMTP-Benutzername</label>
<input type="text" name="mail_smtp_user" value="<?= $h($settings['mail_smtp_user'] ?? '') ?>" autocomplete="off">
</div>
<div class="form-group">
<label>SMTP-Passwort</label>
<input type="password" name="mail_smtp_pass" value="<?= $h($settings['mail_smtp_pass'] ?? '') ?>" autocomplete="new-password">
</div>
</div>
<div class="form-grid form-grid-2">
<div class="form-group">
<label>Absendername</label>
<input type="text" name="mail_from_name" value="<?= $h($settings['mail_from_name'] ?? '') ?>">
</div>
<div class="form-group">
<label>Absender-E-Mail</label>
<input type="email" name="mail_from_email" value="<?= $h($settings['mail_from_email'] ?? '') ?>">
</div>
</div>
<div style="display:flex;align-items:center;gap:.5rem;flex-wrap:wrap">
<button type="button" class="btn btn-secondary" id="btn-mail-text">Text bearbeiten</button>
<button type="button" class="btn btn-secondary" id="btn-mail-test">Test-Mail senden</button>
<span id="mail-test-result" style="font-size:.8rem"></span>
</div>
</div>
</div>
<!-- Logo -->
<div class="card mb-2">
<div class="card-header"><div class="card-title">Logo</div></div>
<div class="card-body">
<?php if (!empty($settings['logo_path'])): ?>
<div style="text-align:center;margin-bottom:1rem">
<img src="/<?= $h($settings['logo_path']) ?>" alt="Logo" style="max-height:80px;max-width:400px">
</div>
<?php endif; ?>
<form method="post" action="/settings/logo" enctype="multipart/form-data">
<div class="form-group">
<label>Logo hochladen (PNG, JPG)</label>
<input type="file" name="logo" accept=".png,.jpg,.jpeg">
<div class="form-hint">Empfohlen: transparentes PNG, mind. 300px breit</div>
</div>
<div style="display:flex;gap:.5rem;flex-wrap:wrap">
<button type="submit" class="btn btn-secondary">Hochladen</button>
<?php if (!empty($settings['logo_path'])): ?>
<button type="submit" form="delete-logo-form"
style="background:none;border:none;cursor:pointer;color:var(--color-danger);padding:.2rem;line-height:1;display:inline-flex;align-items:center"
title="Logo löschen">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24"
fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="3,6 5,6 21,6"/>
<path d="M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6"/>
<path d="M10 11v6M14 11v6"/>
<path d="M9 6V4a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v2"/>
</svg>
</button>
<?php endif; ?>
</div>
</form>
<?php if (!empty($settings['logo_path'])): ?>
<form id="delete-logo-form" method="post" action="/settings/logo/delete"></form>
<?php endif; ?>
<div style="margin-top:1rem;border-top:1px solid var(--color-border,#e5e7eb);padding-top:1rem">
<div class="form-hint" style="margin-bottom:.75rem">Darstellung in der Rechnung</div>
<div class="form-grid form-grid-2">
<div class="form-group">
<label>Max-Breite</label>
<input type="number" name="logo_max_width" form="settings-form"
value="<?= $h($settings['logo_max_width'] ?? 600) ?>" min="50" max="2000">
</div>
<div class="form-group">
<label>Max-Höhe</label>
<input type="number" name="logo_max_height" form="settings-form"
value="<?= $h($settings['logo_max_height'] ?? 100) ?>" min="20" max="500">
</div>
</div>
<div class="form-group">
<label>Position</label>
<div style="display:flex;gap:1.5rem;margin-top:.35rem">
<?php foreach (['center' => 'Zentriert', 'left' => 'Links', 'right' => 'Rechts'] as $val => $label): ?>
<label style="display:flex;align-items:center;gap:.35rem;cursor:pointer">
<input type="radio" name="logo_position" value="<?= $val ?>" form="settings-form"
<?= ($settings['logo_position'] ?? 'center') === $val ? 'checked' : '' ?>
onchange="logoPositionChanged()">
<?= $label ?>
</label>
<?php endforeach; ?>
</div>
</div>
<?php $showMargins = in_array($settings['logo_position'] ?? 'center', ['left', 'right']); ?>
<div id="logo-margin-fields" style="display:<?= $showMargins ? 'block' : 'none' ?>">
<div class="form-grid form-grid-2">
<div class="form-group">
<label>Rand Seite</label>
<input type="number" name="logo_margin_side" form="settings-form"
value="<?= $h($settings['logo_margin_side'] ?? 75) ?>" min="0">
</div>
<div class="form-group">
<label>Rand oben</label>
<input type="number" name="logo_margin_top" form="settings-form"
value="<?= $h($settings['logo_margin_top'] ?? 6) ?>" min="0">
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</form>
<!-- Beleg-Vorschau -->
<div class="card mb-2">
<div class="card-header"><div class="card-title">Beleg-Vorschau (Tooltip)</div></div>
<div class="card-body">
<p class="text-muted text-sm" style="margin-bottom:1rem">Größe der Vorschau beim Hover über Beleg-Links in der Bank-Buchungs-Maske.</p>
<div class="form-grid form-grid-2">
<div class="form-group">
<label for="beleg_vorschau_breite">Breite (px)</label>
<input type="number" id="beleg_vorschau_breite" name="beleg_vorschau_breite" form="settings-form"
value="<?= $h($settings['beleg_vorschau_breite'] ?? 200) ?>" min="50" max="800">
</div>
<div class="form-group">
<label for="beleg_vorschau_hoehe">Höhe (px)</label>
<input type="number" id="beleg_vorschau_hoehe" name="beleg_vorschau_hoehe" form="settings-form"
value="<?= $h($settings['beleg_vorschau_hoehe'] ?? 300) ?>" min="50" max="1200">
</div>
</div>
</div>
</div>
<!-- Modal: E-Mail-Text bearbeiten -->
<div id="mail-text-modal" style="display:none;position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:1000;align-items:center;justify-content:center">
<div style="background:#fff;border-radius:8px;padding:1.5rem;width:520px;max-width:95vw;box-shadow:0 8px 32px rgba(0,0,0,.2)">
<h3 style="margin:0 0 1rem">E-Mail-Text bearbeiten</h3>
<p style="font-size:.78rem;color:#666;margin:0 0 1rem">
Platzhalter: <code>%RNR%</code> = Rechnungsnummer &nbsp;·&nbsp; <code>%FNAME%</code> = Firmenname &nbsp;·&nbsp; <code>%INHABER%</code> = Inhaber
</p>
<div class="form-group">
<label>Betreff</label>
<input type="text" id="mail-text-subject" style="width:100%"
value="<?= $h($settings['mail_subject_template'] ?? 'Rechnung %RNR%') ?>">
</div>
<div class="form-group">
<label>Text</label>
<textarea id="mail-text-body" rows="8" style="width:100%;resize:vertical;font-family:inherit;font-size:.85rem"><?= $h($settings['mail_body_template'] ?? '') ?></textarea>
</div>
<div style="display:flex;gap:.75rem;justify-content:flex-end;margin-top:1.25rem">
<button type="button" class="btn btn-secondary" id="btn-mail-text-cancel">Abbrechen</button>
<button type="button" class="btn btn-primary" id="btn-mail-text-save">Speichern</button>
</div>
</div>
</div>
<script>
document.getElementById('btn-mail-test')?.addEventListener('click', function () {
var btn = this;
var result = document.getElementById('mail-test-result');
btn.disabled = true;
result.textContent = 'Sende…';
result.style.color = '';
fetch('/settings/mail-test', { method: 'POST', headers: { 'X-Requested-With': 'XMLHttpRequest' } })
.then(function (r) { return r.json(); })
.then(function (data) {
if (data.ok) {
result.textContent = '✓ Test-Mail erfolgreich gesendet.';
result.style.color = 'green';
} else {
result.textContent = '✗ ' + (data.error || 'Unbekannter Fehler');
result.style.color = 'red';
}
})
.catch(function () {
result.textContent = '✗ Netzwerkfehler';
result.style.color = 'red';
})
.finally(function () { btn.disabled = false; });
});
</script>
<script>
function updateModeLabel(cb) {
const track = document.getElementById('toggle_track');
const thumb = document.getElementById('toggle_thumb');
const label = document.getElementById('mode_label');
const hint = document.getElementById('mode_hint');
if (cb.checked) {
track.style.background = '#f59e0b';
thumb.style.left = '3px';
label.textContent = 'Testinstallation (Löschen von Buchungen erlaubt)';
hint.textContent = 'Im Testmodus können Buchungen und Belege direkt gelöscht werden. Nicht GoBD-konform.';
hint.style.color = '#92400e';
cb.closest('.card').style.borderColor = '#f59e0b';
cb.closest('.card').querySelector('.badge').className = 'badge badge-yellow';
cb.closest('.card').querySelector('.badge').textContent = 'Testinstallation';
} else {
track.style.background = '#22c55e';
thumb.style.left = '25px';
label.textContent = 'Produktivbetrieb (GoBD-strikt)';
hint.textContent = 'Im Produktivmodus sind Buchungen unveränderlich. Korrekturen nur per Stornobuchung möglich.';
hint.style.color = '#166534';
cb.closest('.card').style.borderColor = '#22c55e';
cb.closest('.card').querySelector('.badge').className = 'badge badge-green';
cb.closest('.card').querySelector('.badge').textContent = 'Produktiv';
}
}
function logoPositionChanged() {
const pos = document.querySelector('input[name="logo_position"]:checked')?.value;
document.getElementById('logo-margin-fields').style.display =
(pos === 'left' || pos === 'right') ? 'block' : 'none';
}
// E-Mail-Text-Modal
document.getElementById('btn-mail-text')?.addEventListener('click', function () {
document.getElementById('mail-text-modal').style.display = 'flex';
});
document.getElementById('btn-mail-text-cancel')?.addEventListener('click', function () {
document.getElementById('mail-text-modal').style.display = 'none';
});
document.getElementById('btn-mail-text-save')?.addEventListener('click', function () {
var btn = this;
var subject = document.getElementById('mail-text-subject').value.trim();
var body = document.getElementById('mail-text-body').value;
var result = document.getElementById('mail-test-result');
if (!subject) {
document.getElementById('mail-text-subject').focus();
return;
}
btn.disabled = true;
var fd = new FormData();
fd.append('mail_subject_template', subject);
fd.append('mail_body_template', body);
fetch('/settings/mail-template', { method: 'POST', body: fd })
.then(function (r) { return r.json(); })
.then(function (data) {
if (data.ok) {
// Versteckte Felder im Hauptformular synchron halten
document.getElementById('hf-mail-subject').value = subject;
document.getElementById('hf-mail-body').value = body;
document.getElementById('mail-text-modal').style.display = 'none';
result.textContent = '✓ E-Mail-Text gespeichert.';
result.style.color = 'green';
setTimeout(function () { result.textContent = ''; }, 3000);
} else {
result.textContent = '✗ ' + (data.error || 'Fehler beim Speichern.');
result.style.color = 'red';
}
})
.catch(function () {
result.textContent = '✗ Netzwerkfehler.';
result.style.color = 'red';
})
.finally(function () { btn.disabled = false; });
});
document.getElementById('btn-generate-app-key')?.addEventListener('click', function () {
const btn = this;
const result = document.getElementById('app-key-result');
const out = document.getElementById('app-key-value');
btn.disabled = true;
fetch('/settings/generate-app-key', { method: 'POST' })
.then(r => r.json())
.then(data => {
if (data && data.key) {
out.textContent = data.key;
result.style.display = 'block';
btn.style.display = 'none';
} else {
alert('Schlüssel konnte nicht erzeugt werden.');
btn.disabled = false;
}
})
.catch(() => {
alert('Netzwerkfehler beim Erzeugen des Schlüssels.');
btn.disabled = false;
});
});
document.getElementById('btn-copy-app-key')?.addEventListener('click', function () {
const out = document.getElementById('app-key-value');
const txt = out.textContent || '';
if (!txt) return;
const btn = this;
const restore = btn.innerHTML;
navigator.clipboard.writeText(txt)
.then(() => {
btn.textContent = '✓';
setTimeout(() => { btn.innerHTML = restore; }, 1500);
})
.catch(() => {
const range = document.createRange();
range.selectNodeContents(out);
const sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
});
});
</script>