Files
kaffeekasse-saas/mandant-einstellungen.php
T
clemens c16b105f00 Erweiterte Bezahloptionen: Barzahlung, PayPal und Ueberweisung
Bisher gab es nur PayPal. Jetzt sind pro Mandant drei unabhaengig
aktivierbare Zahlungswege konfigurierbar (mandant-einstellungen.php):

- Barzahlung mit frei waehlbarem Ansprechpartner.
- PayPal wie bisher, mit Zahlungslink.
- Ueberweisung mit Kontoinhaber und IBAN (Format wird geprueft,
  Leerzeichen werden normalisiert).

Das Mitglieder-Dashboard (index.php) zeigt unter "Bezahlen" alle
aktivierten Methoden mit dem jeweils offenen Betrag; die IBAN wird zur
besseren Lesbarkeit in Viererbloecken angezeigt.

Neue tenant_settings-Spalten via Migration 0021 (cash_enabled,
cash_contact, bank_transfer_enabled, bank_account_holder, bank_iban).
2026-07-21 16:13:28 +02:00

314 lines
20 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
require_once __DIR__ . '/functions.php';
require_once __DIR__ . '/app/audit.php';
require_once __DIR__ . '/app/billing.php';
require_once __DIR__ . '/app/tenant-logo.php';
$pdo = app_db_pdo();
$user = saas_require_login();
$canManageSettings = saas_can_manage_tenant_settings($user);
$errors = [];
$saved = false;
$settings = null;
if ($canManageSettings) {
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
app_require_csrf();
$result = saas_update_tenant_settings($pdo, (int)$user['tenant_id'], $_POST);
if ($result['ok']) {
$saved = true;
$settings = $result['settings'];
app_audit_log($pdo, (int)$user['tenant_id'], (int)$user['user_id'], 'tenant_settings.updated', 'tenant', (int)$user['tenant_id']);
$tenantIdLogo = (int)$user['tenant_id'];
if (!empty($_POST['pdf_watermark_logo_remove'])) {
tenant_logo_delete((string)($settings['pdf_watermark_logo'] ?? ''));
$pdo->prepare("UPDATE tenant_settings SET pdf_watermark_logo = '' WHERE tenant_id = ?")->execute([$tenantIdLogo]);
$settings['pdf_watermark_logo'] = '';
} elseif (isset($_FILES['pdf_watermark_logo_file']) && ($_FILES['pdf_watermark_logo_file']['error'] ?? UPLOAD_ERR_NO_FILE) === UPLOAD_ERR_OK) {
$logoResult = tenant_logo_store($tenantIdLogo, $_FILES['pdf_watermark_logo_file']);
if ($logoResult['ok']) {
$pdo->prepare('UPDATE tenant_settings SET pdf_watermark_logo = ? WHERE tenant_id = ?')->execute([$logoResult['filename'], $tenantIdLogo]);
$settings['pdf_watermark_logo'] = $logoResult['filename'];
} else {
$errors[] = $logoResult['error'];
}
}
} else {
$errors = $result['errors'];
$settings = [
'name' => $_POST['tenant_name'] ?? '',
'timezone' => $_POST['timezone'] ?? 'Europe/Berlin',
'locale' => $_POST['locale'] ?? 'de-DE',
'currency_code' => $_POST['currency_code'] ?? 'EUR',
'mark_price_cents' => saas_parse_money_cents($_POST['mark_price'] ?? '') ?? 0,
'self_entry_enabled' => !empty($_POST['self_entry_enabled']) ? 1 : 0,
'paypal_enabled' => !empty($_POST['paypal_enabled']) ? 1 : 0,
'paypal_url_template' => $_POST['paypal_url_template'] ?? '',
'cash_enabled' => !empty($_POST['cash_enabled']) ? 1 : 0,
'cash_contact' => $_POST['cash_contact'] ?? '',
'bank_transfer_enabled' => !empty($_POST['bank_transfer_enabled']) ? 1 : 0,
'bank_account_holder' => $_POST['bank_account_holder'] ?? '',
'bank_iban' => $_POST['bank_iban'] ?? '',
'sheet_window_days' => $_POST['sheet_window_days'] ?? 100,
'negative_warning_cents' => -abs(saas_parse_money_cents($_POST['negative_warning'] ?? '') ?? 0),
'pdf_show_empty_rows' => !empty($_POST['pdf_show_empty_rows']) ? 1 : 0,
'pdf_split_mode' => $_POST['pdf_split_mode'] ?? 'drinking_behavior',
'pdf_row_height_px' => $_POST['pdf_row_height_px'] ?? 16,
'pdf_watermark_text' => $_POST['pdf_watermark_text'] ?? '',
'pdf_footer_text' => $_POST['pdf_footer_text'] ?? '',
'payment_reminder_enabled' => !empty($_POST['payment_reminder_enabled']) ? 1 : 0,
'payment_reminder_interval_days' => $_POST['payment_reminder_interval_days'] ?? 7,
];
}
} else {
$settings = saas_fetch_tenant_settings($pdo, (int)$user['tenant_id']);
}
$auditLog = app_fetch_audit_log($pdo, (int)$user['tenant_id'], 50);
$billingCheck = billing_check_tenant($pdo, (int)$user['tenant_id']);
$billingPlans = billing_plans();
$billing = billing_fetch_or_init($pdo, (int)$user['tenant_id']);
}
include 'header.php';
include 'headerline.php';
include 'nav.php';
?>
<section id="banner">
<div class="content">
<h2>Mandant-Einstellungen</h2>
<?php if (!$canManageSettings): ?>
<?php http_response_code(403); ?>
<div class="hint-box error"><p>Du hast keine Berechtigung für diese Einstellungen.</p></div>
<?php elseif ($settings === null): ?>
<div class="hint-box error"><p>Die Einstellungen konnten nicht geladen werden.</p></div>
<?php else: ?>
<?php if ($saved): ?>
<div class="hint-box success"><p>Die Einstellungen wurden gespeichert.</p></div>
<?php endif; ?>
<?php if ($errors !== []): ?>
<div class="hint-box error">
<?php foreach ($errors as $error): ?>
<p><?php echo saas_html($error); ?></p>
<?php endforeach; ?>
</div>
<?php endif; ?>
<form method="post" action="mandant-einstellungen.php" enctype="multipart/form-data">
<?php echo app_csrf_field(); ?>
<div class="row">
<div class="col-6 col-12-small">
<label for="tenant_name">Kundenname</label>
<input type="text" name="tenant_name" id="tenant_name" value="<?php echo saas_html($settings['name']); ?>" required>
</div>
<div class="col-6 col-12-small">
<label for="timezone">Zeitzone</label>
<input type="text" name="timezone" id="timezone" value="<?php echo saas_html($settings['timezone']); ?>" required>
</div>
<div class="col-6 col-12-small">
<label for="locale">Locale</label>
<input type="text" name="locale" id="locale" value="<?php echo saas_html($settings['locale']); ?>" required>
</div>
<div class="col-6 col-12-small">
<label for="currency_code">Währung</label>
<input type="text" name="currency_code" id="currency_code" value="<?php echo saas_html($settings['currency_code']); ?>" maxlength="3" required>
</div>
<div class="col-6 col-12-small">
<label for="mark_price">Preis pro Strich</label>
<input type="text" name="mark_price" id="mark_price" value="<?php echo saas_html(saas_format_money_cents($settings['mark_price_cents'])); ?>" required>
</div>
<div class="col-6 col-12-small">
<label for="sheet_window_days">Listenfenster in Tagen</label>
<input type="number" name="sheet_window_days" id="sheet_window_days" min="1" max="365" value="<?php echo saas_html($settings['sheet_window_days']); ?>" required>
</div>
<div class="col-6 col-12-small">
<label for="negative_warning">Schulden-Warnschwelle</label>
<input type="text" name="negative_warning" id="negative_warning" value="<?php echo saas_html(saas_format_money_cents(abs((int)$settings['negative_warning_cents']))); ?>" required>
</div>
<div class="col-12">
<h3>Bezahloptionen</h3>
<p>Aktiviere die Zahlungswege, die deine Mitglieder nutzen können. Nur aktivierte Optionen werden im Mitglieder-Dashboard angezeigt.</p>
</div>
<div class="col-6 col-12-small">
<input type="checkbox" id="cash_enabled" name="cash_enabled" value="1" <?php echo (int)$settings['cash_enabled'] === 1 ? 'checked' : ''; ?>>
<label for="cash_enabled">Barzahlung anbieten</label>
</div>
<div class="col-6 col-12-small">
<label for="cash_contact">Ansprechpartner für Barzahlung</label>
<input type="text" name="cash_contact" id="cash_contact" maxlength="255" value="<?php echo saas_html($settings['cash_contact']); ?>">
</div>
<div class="col-6 col-12-small">
<input type="checkbox" id="paypal_enabled" name="paypal_enabled" value="1" <?php echo (int)$settings['paypal_enabled'] === 1 ? 'checked' : ''; ?>>
<label for="paypal_enabled">PayPal anbieten</label>
</div>
<div class="col-6 col-12-small">
<label for="paypal_url_template">PayPal-Link</label>
<input type="text" name="paypal_url_template" id="paypal_url_template" value="<?php echo saas_html($settings['paypal_url_template']); ?>">
</div>
<div class="col-6 col-12-small">
<input type="checkbox" id="bank_transfer_enabled" name="bank_transfer_enabled" value="1" <?php echo (int)$settings['bank_transfer_enabled'] === 1 ? 'checked' : ''; ?>>
<label for="bank_transfer_enabled">Überweisung anbieten</label>
</div>
<div class="col-6 col-12-small">
<label for="bank_account_holder">Kontoinhaber (Überweisung)</label>
<input type="text" name="bank_account_holder" id="bank_account_holder" maxlength="255" value="<?php echo saas_html($settings['bank_account_holder']); ?>">
</div>
<div class="col-12">
<label for="bank_iban">IBAN (Überweisung)</label>
<input type="text" name="bank_iban" id="bank_iban" maxlength="40" value="<?php echo saas_html($settings['bank_iban']); ?>">
</div>
<div class="col-6 col-12-small">
<input type="checkbox" id="self_entry_enabled" name="self_entry_enabled" value="1" <?php echo (int)$settings['self_entry_enabled'] === 1 ? 'checked' : ''; ?>>
<label for="self_entry_enabled">Web-Striche erlauben</label>
</div>
<div class="col-6 col-12-small">
<input type="checkbox" id="pdf_show_empty_rows" name="pdf_show_empty_rows" value="1" <?php echo (int)$settings['pdf_show_empty_rows'] === 1 ? 'checked' : ''; ?>>
<label for="pdf_show_empty_rows">Freie Zeilen für neue Mitglieder im Ausdruck</label>
</div>
<div class="col-6 col-12-small">
<label for="pdf_split_mode">Sortierung im Ausdruck (bei mehreren Seiten)</label>
<select name="pdf_split_mode" id="pdf_split_mode">
<option value="drinking_behavior" <?php echo $settings['pdf_split_mode'] === 'drinking_behavior' ? 'selected' : ''; ?>>Nach Trinkverhalten (Vieltrinker vorne)</option>
<option value="alphabetical" <?php echo $settings['pdf_split_mode'] === 'alphabetical' ? 'selected' : ''; ?>>Alphabetisch</option>
</select>
</div>
<div class="col-6 col-12-small">
<label for="pdf_row_height_px">Zeilenhöhe im Ausdruck (1060px)</label>
<input type="number" name="pdf_row_height_px" id="pdf_row_height_px" min="10" max="60" value="<?php echo saas_html($settings['pdf_row_height_px']); ?>" required>
</div>
<div class="col-12">
<label for="pdf_watermark_text">Wasserzeichen-Text im Ausdruck (leer = kein Wasserzeichen)</label>
<input type="text" name="pdf_watermark_text" id="pdf_watermark_text" maxlength="500" value="<?php echo saas_html($settings['pdf_watermark_text']); ?>">
</div>
<div class="col-12">
<label for="pdf_watermark_logo_file">Logo als Wasserzeichen (PNG/JPEG, ersetzt den Text im Ausdruck)</label>
<input type="file" name="pdf_watermark_logo_file" id="pdf_watermark_logo_file" accept="image/png,image/jpeg">
<?php if (trim((string)($settings['pdf_watermark_logo'] ?? '')) !== ''): ?>
<div class="form-check">
<input class="form-check-input" type="checkbox" name="pdf_watermark_logo_remove" id="pdf_watermark_logo_remove" value="1">
<label class="form-check-label" for="pdf_watermark_logo_remove">Hinterlegtes Logo entfernen (wieder Text-Wasserzeichen verwenden)</label>
</div>
<?php else: ?>
<small>Aktuell kein Logo hinterlegt.</small>
<?php endif; ?>
</div>
<div class="col-12">
<label for="pdf_footer_text">Hinweis in der Fußzeile des Ausdrucks (leer = keine Fußzeile)</label>
<input type="text" name="pdf_footer_text" id="pdf_footer_text" maxlength="500" value="<?php echo saas_html($settings['pdf_footer_text']); ?>">
</div>
<div class="col-6 col-12-small">
<input type="checkbox" id="payment_reminder_enabled" name="payment_reminder_enabled" value="1" <?php echo (int)$settings['payment_reminder_enabled'] === 1 ? 'checked' : ''; ?>>
<label for="payment_reminder_enabled">Automatische Zahlungserinnerung ab Warnschwelle</label>
</div>
<div class="col-6 col-12-small">
<label for="payment_reminder_interval_days">Erinnerungs-Intervall in Tagen</label>
<input type="number" name="payment_reminder_interval_days" id="payment_reminder_interval_days" min="1" max="90" value="<?php echo saas_html($settings['payment_reminder_interval_days']); ?>">
</div>
</div>
<ul class="actions">
<li><button type="submit">Speichern</button></li>
<li><a href="konto.php" class="button alt">Zurück</a></li>
</ul>
</form>
<h3>Abo &amp; Tarif</h3>
<p>
Aktueller Tarif: <b><?php echo saas_html($billingPlans[$billingCheck['current_plan']]['label'] ?? $billingCheck['current_plan']); ?></b><br>
Aktive Teilnehmer: <?php echo (int)$billingCheck['active_participants']; ?>
</p>
<?php if (isset($_GET['upgrade']) && $_GET['upgrade'] === 'success'): ?>
<div class="hint-box success"><p>Danke! Die Änderung wird verarbeitet, der Tarif ist gleich aktuell.</p></div>
<?php elseif (isset($_GET['upgrade']) && $_GET['upgrade'] === 'cancelled'): ?>
<div class="hint-box error"><p>Der Bezahlvorgang wurde abgebrochen, es wurde nichts geändert.</p></div>
<?php endif; ?>
<?php if ($billingCheck['needs_upgrade']): ?>
<div class="hint-box error">
<p>Mit <?php echo (int)$billingCheck['active_participants']; ?> aktiven Teilnehmern benötigt ihr mindestens den
Tarif „<?php echo saas_html($billingPlans[$billingCheck['required_plan']]['label'] ?? $billingCheck['required_plan']); ?>".</p>
</div>
<?php else: ?>
<div class="hint-box success"><p>Euer aktueller Tarif deckt eure Teilnehmerzahl ab.</p></div>
<?php endif; ?>
<table>
<tr>
<th>Tarif</th>
<th>Max. aktive Teilnehmer</th>
<th>Preis/Monat</th>
<th>Status</th>
<th></th>
</tr>
<?php foreach ($billingPlans as $planCode => $plan): ?>
<?php
$isCurrent = $planCode === $billingCheck['current_plan'];
$selectable = billing_plan_selectable_for($pdo, (int)$user['tenant_id'], $planCode);
$isSelfService = $plan['stripe_lookup_key'] !== null || $planCode === 'free';
?>
<tr<?php echo $isCurrent ? ' class="hint-box success"' : ''; ?>>
<td><?php echo saas_html($plan['label']); ?></td>
<td><?php echo $plan['max_participants'] !== null ? (int)$plan['max_participants'] : 'unbegrenzt'; ?></td>
<td><?php echo $plan['price_cents'] > 0 ? saas_html(saas_format_money_cents($plan['price_cents'])) . ' €' : 'kostenlos'; ?></td>
<td>
<?php if ($isCurrent): ?>
aktuell gebucht
<?php elseif (!$selectable): ?>
deckt eure <?php echo (int)$billingCheck['active_participants']; ?> Teilnehmer nicht ab
<?php endif; ?>
</td>
<td>
<?php if ($isCurrent): ?>
<?php elseif (!$isSelfService): ?>
<a href="mailto:info@ctb-it.de" class="button small">Anfragen</a>
<?php elseif (!$selectable): ?>
<button type="button" class="button small" disabled>Nicht wählbar</button>
<?php else: ?>
<form method="post" action="abo-upgrade.php" onsubmit="return confirm('Tarif wirklich auf ' + <?php echo json_encode($plan['label']); ?> + ' ändern?');">
<?php echo app_csrf_field(); ?>
<input type="hidden" name="plan_code" value="<?php echo saas_html($planCode); ?>">
<button type="submit" class="button small primary">
<?php echo $planCode === 'free' ? 'Kündigen / auf Kostenlos wechseln' : 'Buchen'; ?>
</button>
</form>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
</table>
<p><small>Ein Wechsel zwischen bezahlten Tarifen wird bei Stripe anteilig verrechnet (Proration) und
gilt sofort. Details zu den Tarifgrenzen stehen unter <a href="preise.php">Preise</a>.</small></p>
<?php if (($billing['stripe_customer_id'] ?? null) !== null): ?>
<form method="post" action="abo-portal.php">
<?php echo app_csrf_field(); ?>
<button type="submit">Zahlungsmethode verwalten</button>
</form>
<?php endif; ?>
<h3>Protokoll</h3>
<p>Die letzten Admin-Aktionen für diesen Mandanten.</p>
<table>
<tr><th>Datum</th><th>Wer</th><th>Aktion</th><th>Betrifft</th></tr>
<?php if ($auditLog === []): ?>
<tr><td colspan="4">Noch keine protokollierten Aktionen.</td></tr>
<?php endif; ?>
<?php foreach ($auditLog as $eintrag): ?>
<tr>
<td><?php echo saas_html($eintrag['created_at']); ?></td>
<td><?php echo saas_html($eintrag['actor_name'] ?? '—'); ?></td>
<td><?php echo saas_html($eintrag['action']); ?></td>
<td><?php echo saas_html($eintrag['subject_type']); ?><?php echo $eintrag['subject_id'] !== null ? ' #' . (int)$eintrag['subject_id'] : ''; ?></td>
</tr>
<?php endforeach; ?>
</table>
<?php endif; ?>
</div>
</section>
<?php include 'footer.php'; ?>