FAQ pro Mandant statt fest codiertem AOK-Inhalt

faq.php zeigte bislang fuer jeden Mandanten denselben, komplett
AOK-spezifischen Inhalt (Kaffeemaschinen-Bedienung, interner
Ansprechpartner) - fuer andere Kunden unbrauchbar und inhaltlich falsch.

- Neue Tabelle faq_entries (tenant-scoped, Soft-Delete, sort_order).
- Migration uebernimmt die bisherigen AOK-Inhalte einmalig als FAQ des
  migrierten Default-Mandanten; andere Mandanten sehen sie nicht.
- Neue Mandanten bekommen bei der Registrierung automatisch eine kurze
  generische Starter-FAQ (faq_seed_default_entries), frei editierbar.
- faq.php: lesbar fuer alle angemeldeten Mitglieder eines Mandanten,
  Anlegen/Bearbeiten/Entfernen auf owner/admin beschraenkt.
- Landingpage verweist nicht mehr auf faq.php (jetzt interner,
  mandantengebundener Inhalt statt oeffentlicher Marketing-Seite).

Live getestet: AOK-Carry-over fuer Default-Mandant, frisch registrierter
Test-Mandant bekam isolierte generische Starter-FAQ, Anlegen mit
HTML-Payload (korrekt escaped), Bearbeiten und Soft-Delete geprueft.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-15 23:18:09 +02:00
co-authored by Claude Sonnet 5
parent d176619e1d
commit 8b0dcd2c70
7 changed files with 311 additions and 72 deletions
+86
View File
@@ -0,0 +1,86 @@
<?php
declare(strict_types=1);
require_once __DIR__ . '/bootstrap.php';
/**
* @return list<array{id: int, question: string, answer: string, sort_order: int}>
*/
function faq_fetch_all(PDO $pdo, int $tenantId): array
{
$stmt = $pdo->prepare(
'SELECT id, question, answer, sort_order
FROM faq_entries
WHERE tenant_id = ?
AND deleted_at IS NULL
ORDER BY sort_order, id'
);
$stmt->execute([$tenantId]);
return $stmt->fetchAll();
}
function faq_create(PDO $pdo, int $tenantId, string $question, string $answer): bool
{
$question = trim($question);
$answer = trim($answer);
if ($question === '' || $answer === '') {
return false;
}
$stmt = $pdo->prepare('SELECT COALESCE(MAX(sort_order), 0) + 1 FROM faq_entries WHERE tenant_id = ? AND deleted_at IS NULL');
$stmt->execute([$tenantId]);
$nextOrder = (int)$stmt->fetchColumn();
$stmt = $pdo->prepare('INSERT INTO faq_entries (tenant_id, question, answer, sort_order) VALUES (?, ?, ?, ?)');
return $stmt->execute([$tenantId, $question, $answer, $nextOrder]);
}
function faq_update(PDO $pdo, int $tenantId, int $id, string $question, string $answer): bool
{
$question = trim($question);
$answer = trim($answer);
if ($question === '' || $answer === '') {
return false;
}
$stmt = $pdo->prepare(
'UPDATE faq_entries SET question = ?, answer = ? WHERE id = ? AND tenant_id = ? AND deleted_at IS NULL'
);
$stmt->execute([$question, $answer, $id, $tenantId]);
return $stmt->rowCount() > 0;
}
function faq_soft_delete(PDO $pdo, int $tenantId, int $id): bool
{
$stmt = $pdo->prepare(
'UPDATE faq_entries SET deleted_at = NOW() WHERE id = ? AND tenant_id = ? AND deleted_at IS NULL'
);
$stmt->execute([$id, $tenantId]);
return $stmt->rowCount() > 0;
}
/**
* Seeds a small, generic starter FAQ for a brand-new tenant (mirrors the
* excerpt already shown on the public landing page), so new customers do
* not start with a completely empty FAQ. They can edit or delete every
* entry afterwards.
*/
function faq_seed_default_entries(PDO $pdo, int $tenantId): void
{
$starters = [
['Wie starten wir mit unserer Kaffeeliste?', 'Mitglieder unter „Mitglieder verwalten" anlegen, danach direkt Striche und Einzahlungen erfassen. Es ist keine weitere Einrichtung nötig.'],
['Wer kann Mitglieder verwalten oder Einstellungen ändern?', 'Das hängt von der zugewiesenen Rolle ab: Administrator und Kassenwart können Buchungen vornehmen, nur Administrator und Inhaber können Einstellungen und Zugänge verwalten.'],
['Können wir per PayPal bezahlen?', 'Ja, wenn PayPal in den Mandant-Einstellungen aktiviert ist. Einzahlungen lassen sich zusätzlich jederzeit manuell erfassen.'],
['Wie erfahre ich meinen aktuellen Stand?', 'Im eigenen Dashboard unter „Meine Kaffeeliste" ist der aktuelle Stand jederzeit sichtbar.'],
];
$stmt = $pdo->prepare('INSERT INTO faq_entries (tenant_id, question, answer, sort_order) VALUES (?, ?, ?, ?)');
foreach ($starters as $index => [$question, $answer]) {
$stmt->execute([$tenantId, $question, $answer, $index + 1]);
}
}
+3
View File
@@ -4,6 +4,7 @@ declare(strict_types=1);
require_once __DIR__ . '/bootstrap.php';
require_once __DIR__ . '/database.php';
require_once __DIR__ . '/faq.php';
function saas_email_norm(string $email): string
{
@@ -1059,6 +1060,8 @@ function saas_register_tenant_owner(PDO $pdo, array $input): array
);
$stmt->execute([$tenantId, 20, 1, 0, '']);
faq_seed_default_entries($pdo, $tenantId);
if ($existingUser === false) {
$stmt = $pdo->prepare(
'INSERT INTO users (email, email_norm, display_name, password_hash, status)