Files
kaffeekasse-saas/mandant-einstellungen.php
T
clemensandClaude Sonnet 5 ade1fd0ed9 Billing Phase 1: Tarifstatus-Datenmodell und Tariflogik je Mandant
Vorbereitung fuer Stripe Billing (Zahlungseinzug) + bestehendes Dolibarr
des Kunden (Rechnungsstellung/Buchhaltung) statt eines eigenen Rechnungs-
systems oder eines zusaetzlichen ERP nur fuer Kaffeeliste - Begruendung
in docs/billing.md.

- Neue Tabelle tenant_billing (plan_code, subscription_status, sowie
  bereits vorbereitete, noch ungenutzte Felder fuer Stripe/Dolibarr-IDs).
- app/billing.php: billing_plans() als einzige Quelle der Tarifstufen
  (synchron mit preise.php), billing_required_plan_code() anhand aktiver
  Teilnehmerzahl, billing_check_tenant() vergleicht gebuchten mit
  benoetigtem Tarif - rein informativ, kein Enforcement, solange Stripe
  nicht angebunden ist.
- Neue Mandanten starten automatisch auf plan_code='free' bei der
  Registrierung.
- mandant-einstellungen.php zeigt Owner/Admin ihren aktuellen Tarif und
  Teilnehmerstand, mit Hinweis bei Bedarf einer hoeheren Stufe.
- Back-Office zeigt zusaetzlich zur Mandantenliste den gebuchten und
  (falls abweichend) den tatsaechlich benoetigten Tarif.

Live getestet: Tarifgrenzen (10/25/50/150) mit einem Mandanten unter und
einem ueber dem Freikontingent geprueft, UI in Mandant-Einstellungen und
Back-Office verifiziert. Alle M8-Isolations-/Rollenmatrix-Tests weiterhin
gruen.

Phase 2 (Stripe) und Phase 3 (Dolibarr-Sync) folgen, sobald Test-Keys
beziehungsweise Sandbox-Zugang vorliegen.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-16 23:38:28 +02:00

160 lines
8.5 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';
$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']);
} 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'] ?? '',
'sheet_window_days' => $_POST['sheet_window_days'] ?? 100,
'negative_warning_cents' => -abs(saas_parse_money_cents($_POST['negative_warning'] ?? '') ?? 0),
];
}
} 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();
}
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">
<?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">
<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="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="paypal_enabled" name="paypal_enabled" value="1" <?php echo (int)$settings['paypal_enabled'] === 1 ? 'checked' : ''; ?>>
<label for="paypal_enabled">PayPal anzeigen</label>
</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 ($billingCheck['needs_upgrade']): ?>
<div class="hint-box error">
<p>Mit <?php echo (int)$billingCheck['active_participants']; ?> aktiven Teilnehmern benötigt ihr den
Tarif „<?php echo saas_html($billingPlans[$billingCheck['required_plan']]['label'] ?? $billingCheck['required_plan']); ?>".
Die automatische Abrechnung ist noch in Vorbereitung bitte <a href="mailto:info@ctb-it.de">meldet euch bei uns</a>,
wir stellen den passenden Tarif manuell um. Details zu den Stufen stehen unter <a href="preise.php">Preise</a>.</p>
</div>
<?php else: ?>
<div class="hint-box success"><p>Euer aktueller Tarif deckt eure Teilnehmerzahl ab.</p></div>
<?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'; ?>