Files
kaffeekasse-saas/mitarbeiterverwalten.php
T

439 lines
18 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
ob_start();
include "functions.php";
require_once __DIR__ . "/app/ledger.php";
require_once __DIR__ . "/app/saas-mail.php";
require_once __DIR__ . "/app/audit.php";
require_once __DIR__ . "/app/billing.php";
require_once __DIR__ . "/app/imports.php";
app_require_csrf();
include "header.php";
include "headerline.php";
include "nav.php";
?>
<!-- Banner -->
<section id="banner">
<div class="content">
<?php
$pdo = app_db_pdo();
$saasUser = saas_current_user($pdo);
$tenantId = 0;
$hasAccess = false;
if ($saasUser !== null && saas_user_has_role(['owner', 'admin'], $saasUser)) {
$tenantId = (int)$saasUser['tenant_id'];
$hasAccess = true;
}
if($hasAccess){
$meldung = null;
$fehler = null;
$einladungslink = null;
$bearbeitenId = null;
$actorUserId = $saasUser['user_id'] ?? null;
if (isset($_SESSION['flash_mitglieder']) && is_array($_SESSION['flash_mitglieder'])) {
$flash = $_SESSION['flash_mitglieder'];
unset($_SESSION['flash_mitglieder']);
$meldung = isset($flash['meldung']) ? (string)$flash['meldung'] : null;
$fehler = isset($flash['fehler']) ? (string)$flash['fehler'] : null;
$einladungslink = isset($flash['einladungslink']) ? (string)$flash['einladungslink'] : null;
}
if ($_SERVER["REQUEST_METHOD"] == "POST") {
$aktion = $_POST["aktion"] ?? '';
if ($aktion === 'bearbeiten') {
$bearbeitenId = (int)$_POST["mitgliedID"];
} elseif ($aktion === 'anlegen') {
$name = trim((string)($_POST["name"] ?? ''));
$email = trim((string)($_POST["email"] ?? ''));
$paypalname = trim((string)($_POST["paypalname"] ?? ''));
$aktiv = isset($_POST["aktiv"]);
if ($name === '' || !filter_var($email, FILTER_VALIDATE_EMAIL)) {
$fehler = 'Bitte einen Namen und eine gültige E-Mail-Adresse angeben.';
} elseif ($aktiv && !billing_has_capacity_for($pdo, $tenantId)) {
$fehler = billing_capacity_error_message($pdo, $tenantId);
} else {
try {
$neueId = ledger_create_participant($pdo, $tenantId, $name, $email, $paypalname, $aktiv);
app_audit_log($pdo, $tenantId, $actorUserId, 'participant.created', 'participant', $neueId, ['name' => $name, 'email' => $email]);
$meldung = 'Mitglied wurde angelegt.';
} catch (Throwable $e) {
$fehler = 'Das Mitglied konnte nicht angelegt werden (E-Mail eventuell schon vergeben).';
}
}
} elseif ($aktion === 'bearbeitenspeichern') {
$participantId = (int)$_POST["mitgliedID"];
$name = trim((string)($_POST["name"] ?? ''));
$email = trim((string)($_POST["email"] ?? ''));
$paypalname = trim((string)($_POST["paypalname"] ?? ''));
$aktiv = isset($_POST["aktiv"]);
$warAktiv = false;
foreach (ledger_fetch_participants_for_admin($pdo, $tenantId) as $m) {
if ($m['participant_id'] === $participantId) {
$warAktiv = (bool)$m['active'];
break;
}
}
if ($name === '' || !filter_var($email, FILTER_VALIDATE_EMAIL)) {
$fehler = 'Bitte einen Namen und eine gültige E-Mail-Adresse angeben.';
} elseif ($aktiv && !$warAktiv && !billing_has_capacity_for($pdo, $tenantId)) {
$fehler = billing_capacity_error_message($pdo, $tenantId);
} elseif (ledger_update_participant($pdo, $tenantId, $participantId, $name, $email, $paypalname, $aktiv)) {
app_audit_log($pdo, $tenantId, $actorUserId, 'participant.updated', 'participant', $participantId, ['name' => $name, 'email' => $email]);
$meldung = 'Mitglied wurde gespeichert.';
} else {
$fehler = 'Das Mitglied konnte nicht gespeichert werden.';
}
} elseif ($aktion === 'aktivieren' || $aktion === 'deaktivieren') {
$participantId = (int)$_POST["mitgliedID"];
if ($aktion === 'aktivieren' && !billing_has_capacity_for($pdo, $tenantId)) {
$fehler = billing_capacity_error_message($pdo, $tenantId);
} elseif (ledger_set_participant_active($pdo, $tenantId, $participantId, $aktion === 'aktivieren')) {
app_audit_log($pdo, $tenantId, $actorUserId, $aktion === 'aktivieren' ? 'participant.activated' : 'participant.deactivated', 'participant', $participantId);
$meldung = $aktion === 'aktivieren' ? 'Mitglied wurde aktiviert.' : 'Mitglied wurde deaktiviert.';
} else {
$fehler = 'Der Status konnte nicht geändert werden.';
}
} elseif ($aktion === 'zugang_gewaehren') {
$participantId = (int)$_POST["mitgliedID"];
$rolle = (string)($_POST["rolle"] ?? '');
// Einladungen legen fremde Konten an und verschicken Mails nach
// aussen. Wer die eigene Adresse noch nicht bestaetigt hat, kann
// das nicht im Namen eines womoeglich fremden Postfachs tun.
if (!saas_email_verified($saasUser)) {
$ergebnis = ['ok' => false, 'errors' => [
'Bevor du Mitglieder einladen kannst, muss deine eigene E-Mail-Adresse bestätigt sein. Den Link kannst du unter "Kundenkonto" erneut anfordern.',
]];
} else {
$ergebnis = saas_grant_participant_access($pdo, $tenantId, $participantId, $rolle);
}
if ($ergebnis['ok']) {
$einladendeTenantSettings = saas_fetch_tenant_settings($pdo, $tenantId);
$mailErgebnis = saas_send_invite_mail(
$ergebnis['user_email'],
$einladendeTenantSettings['name'] ?? 'Kaffeeliste',
$rolle,
$ergebnis['token']
);
app_audit_log($pdo, $tenantId, $actorUserId, 'participant.access_granted', 'participant', $participantId, ['role' => $rolle]);
$meldung = 'Zugang wurde gewährt, Einladung wurde verschickt.';
if (saas_should_show_auth_links()) {
$einladungslink = saas_app_url('passwort-zuruecksetzen.php?token=' . urlencode($ergebnis['token']));
}
} else {
$fehler = implode(' ', $ergebnis['errors']);
}
} elseif ($aktion === 'zugang_entziehen') {
$participantId = (int)$_POST["mitgliedID"];
$ergebnis = saas_revoke_participant_access($pdo, $tenantId, $participantId);
if ($ergebnis['ok']) {
app_audit_log($pdo, $tenantId, $actorUserId, 'participant.access_revoked', 'participant', $participantId);
$meldung = 'Zugang wurde entzogen.';
} else {
$fehler = implode(' ', $ergebnis['errors']);
}
} elseif ($aktion === 'anonymisieren') {
$participantId = (int)$_POST["mitgliedID"];
$mitgliedRolle = null;
foreach (ledger_fetch_participants_for_admin($pdo, $tenantId) as $m) {
if ($m['participant_id'] === $participantId) {
$mitgliedRolle = $m['role'];
break;
}
}
if ($mitgliedRolle === 'owner') {
$fehler = 'Der Inhaber kann nicht anonymisiert werden.';
} elseif (ledger_anonymize_participant($pdo, $tenantId, $participantId)) {
app_audit_log($pdo, $tenantId, $actorUserId, 'participant.anonymized', 'participant', $participantId);
$meldung = 'Mitglied wurde anonymisiert. Name und E-Mail sind entfernt, Buchungen bleiben für die Kassenführung erhalten.';
} else {
$fehler = 'Das Mitglied konnte nicht anonymisiert werden.';
}
} elseif ($aktion === 'csv_import') {
$file = $_FILES['mitglieder_csv'] ?? null;
if ($file === null || ($file['error'] ?? UPLOAD_ERR_NO_FILE) !== UPLOAD_ERR_OK) {
$fehler = 'Bitte eine CSV-Datei auswählen.';
} elseif (($file['size'] ?? 0) <= 0 || $file['size'] > 2 * 1024 * 1024) {
$fehler = 'Die CSV-Datei ist leer oder größer als 2 MB.';
} elseif (strtolower(pathinfo((string)$file['name'], PATHINFO_EXTENSION)) !== 'csv') {
$fehler = 'Es sind nur CSV-Dateien erlaubt.';
} elseif (!is_uploaded_file((string)$file['tmp_name'])) {
$fehler = 'Die hochgeladene Datei konnte nicht verifiziert werden.';
} else {
try {
$zeilen = imports_parse_member_csv((string)$file['tmp_name']);
$angelegt = 0;
$uebersprungen = 0;
$inaktivWegenKapazitaet = 0;
$fehlerhaft = 0;
foreach ($zeilen as $z) {
if ($z['name'] === '' || !filter_var($z['email'], FILTER_VALIDATE_EMAIL)) {
$fehlerhaft++;
continue;
}
$aktiv = $z['active'];
if ($aktiv && !billing_has_capacity_for($pdo, $tenantId)) {
$aktiv = false;
$inaktivWegenKapazitaet++;
}
try {
$neueId = ledger_create_participant($pdo, $tenantId, $z['name'], $z['email'], $z['paypal_name'] !== '' ? $z['paypal_name'] : null, $aktiv);
app_audit_log($pdo, $tenantId, $actorUserId, 'participant.created', 'participant', $neueId, ['name' => $z['name'], 'email' => $z['email'], 'quelle' => 'csv_import']);
$angelegt++;
} catch (Throwable $e) {
$uebersprungen++;
}
}
$teile = ["{$angelegt} angelegt"];
if ($uebersprungen > 0) {
$teile[] = "{$uebersprungen} übersprungen (bereits vorhanden)";
}
if ($inaktivWegenKapazitaet > 0) {
$teile[] = "{$inaktivWegenKapazitaet} als inaktiv angelegt (Tarif-Kapazität erschöpft)";
}
if ($fehlerhaft > 0) {
$teile[] = "{$fehlerhaft} fehlerhaft (Name/E-Mail ungültig)";
}
$meldung = 'CSV-Import: ' . implode(', ', $teile) . '.';
} catch (Throwable $e) {
$fehler = 'Die CSV-Datei konnte nicht verarbeitet werden.';
}
}
}
if ($aktion !== 'bearbeiten') {
$_SESSION['flash_mitglieder'] = [
'meldung' => $meldung,
'fehler' => $fehler,
'einladungslink' => $einladungslink,
];
header('Location: mitarbeiterverwalten.php');
exit;
}
}
$mitglieder = ledger_fetch_participants_for_admin($pdo, $tenantId);
$billingInfo = billing_fetch_or_init($pdo, $tenantId);
$billingPlan = billing_plans()[$billingInfo['plan_code']] ?? null;
$aktiveMitgliederAnzahl = billing_count_active_participants($pdo, $tenantId);
$rollenLabels = ['owner' => 'Inhaber', 'admin' => 'Administrator', 'treasurer' => 'Kassenwart', 'member' => 'Mitglied', 'viewer' => 'Betrachter'];
$bearbeitenMitglied = null;
if ($bearbeitenId !== null) {
foreach ($mitglieder as $m) {
if ($m['participant_id'] === $bearbeitenId) {
$bearbeitenMitglied = $m;
break;
}
}
}
?>
<h2>Mitglieder verwalten</h2>
<?php if ($meldung !== null): ?>
<div class="hint-box success"><p><?php echo saas_html($meldung); ?></p></div>
<?php endif; ?>
<?php if ($fehler !== null): ?>
<div class="hint-box error"><p><?php echo saas_html($fehler); ?></p></div>
<?php endif; ?>
<?php if ($einladungslink !== null): ?>
<div class="hint-box"><p>Dev-Link zur Einladung: <a href="<?php echo saas_html($einladungslink); ?>"><?php echo saas_html($einladungslink); ?></a></p></div>
<?php endif; ?>
<p>Aktive Mitglieder: <?php echo (int)$aktiveMitgliederAnzahl; ?><?php echo $billingPlan['max_participants'] !== null ? ' von maximal ' . (int)$billingPlan['max_participants'] . ' im Tarif „' . saas_html($billingPlan['label']) . '"' : ' (Tarif „' . saas_html($billingPlan['label'] ?? $billingInfo['plan_code']) . '", unbegrenzt)'; ?>.
<?php if ($billingPlan['max_participants'] !== null && $aktiveMitgliederAnzahl >= $billingPlan['max_participants']): ?>
Limit erreicht für weitere aktive Mitglieder ist ein <a href="mandant-einstellungen.php">Upgrade</a> nötig.
<?php endif; ?></p>
<?php if ($bearbeitenMitglied !== null): ?>
<h3>Bearbeiten von <?php echo saas_html($bearbeitenMitglied['display_name']); ?></h3>
<form method="post" action="<?php echo htmlspecialchars($_SERVER["PHP_SELF"]); ?>">
<input type="hidden" name="aktion" value="bearbeitenspeichern">
<input type="hidden" name="mitgliedID" value="<?php echo $bearbeitenMitglied['participant_id']; ?>">
<?php echo app_csrf_field(); ?>
<label for="name">Name:</label>
<input type="text" name="name" id="name" value="<?php echo saas_html($bearbeitenMitglied['display_name']); ?>" required>
<label for="email">E-Mail:</label>
<input type="email" name="email" id="email" value="<?php echo saas_html($bearbeitenMitglied['email'] ?? ''); ?>" required>
<label for="paypalname">PayPal-Name:</label>
<input type="text" name="paypalname" id="paypalname" value="<?php echo saas_html($bearbeitenMitglied['paypal_name'] ?? ''); ?>">
<br>
<div class="form-check">
<input class="form-check-input" type="checkbox" name="aktiv" id="aktiv" <?php echo $bearbeitenMitglied['active'] ? 'checked' : ''; ?>>
<label class="form-check-label" for="aktiv">Aktiv:</label>
</div>
<button type="submit">Speichern</button>
</form>
<?php endif; ?>
<!-- Formular für das Anlegen von Mitgliedern -->
<h3>Neues Mitglied anlegen</h3>
<form method="post" action="<?php echo htmlspecialchars($_SERVER["PHP_SELF"]); ?>">
<input type="hidden" name="aktion" id="aktion" value="anlegen">
<?php echo app_csrf_field(); ?>
<label for="new-name">Name:</label>
<input type="text" name="name" id="new-name" required>
<label for="new-email">E-Mail:</label>
<input type="email" name="email" id="new-email" required>
<label for="new-paypalname">PayPal-Name:</label>
<input type="text" name="paypalname" id="new-paypalname">
<br>
<div class="form-check">
<input class="form-check-input" type="checkbox" name="aktiv" id="new-aktiv" checked>
<label class="form-check-label" for="new-aktiv">Aktiv</label>
</div>
<button type="submit">Mitglied anlegen</button>
</form>
<h3>Mitglieder per CSV importieren</h3>
<p>Lade zunächst die Vorlage herunter, trage eure Mitglieder ein (Name, E-Mail,
optional PayPal-Name und „ja/nein" für aktiv) und lade die Datei anschließend
wieder hoch. Bereits vorhandene E-Mail-Adressen werden übersprungen.</p>
<p><a class="button" href="mitglieder-vorlage.php">Vorlage herunterladen</a></p>
<form method="post" action="<?php echo htmlspecialchars($_SERVER["PHP_SELF"]); ?>" enctype="multipart/form-data">
<?php echo app_csrf_field(); ?>
<input type="hidden" name="aktion" value="csv_import">
<label for="mitglieder_csv">CSV-Datei mit Mitgliedern:</label>
<input type="file" name="mitglieder_csv" id="mitglieder_csv" accept=".csv" required>
<button type="submit">Mitglieder importieren</button>
</form>
<p>Name und E-Mail dienen der Kaffeeliste und Benachrichtigungen. Ein Login-Zugang
ist davon unabhängig und wird separat je Mitglied gewährt oder entzogen.</p>
<!-- Tabelle zur Anzeige und Bearbeitung von Mitgliedern -->
<table class="table table-striped">
<tr>
<th>Name</th>
<th>E-Mail</th>
<th>PayPal-Name</th>
<th>Aktiv</th>
<th>Zugang</th>
<th>Aktionen</th>
</tr>
<?php foreach ($mitglieder as $mitglied): ?>
<tr>
<td><?php echo saas_html($mitglied['display_name']); ?></td>
<td><?php echo saas_html($mitglied['email'] ?? ''); ?></td>
<td><?php echo saas_html($mitglied['paypal_name'] ?? ''); ?></td>
<td><?php echo $mitglied['active'] ? '1' : '0'; ?></td>
<td>
<?php if ($mitglied['membership_status'] === 'active'): ?>
<?php if ($mitglied['role'] === 'owner'): ?>
Inhaber
<?php else: ?>
<?php echo saas_html($rollenLabels[$mitglied['role']] ?? $mitglied['role']); ?>
<form method="post" action="<?php echo htmlspecialchars($_SERVER["PHP_SELF"]); ?>" style="display:inline">
<input type="hidden" name="aktion" value="zugang_gewaehren">
<input type="hidden" name="mitgliedID" value="<?php echo $mitglied['participant_id']; ?>">
<?php echo app_csrf_field(); ?>
<select name="rolle">
<?php foreach (saas_grantable_roles() as $rolle): ?>
<option value="<?php echo saas_html($rolle); ?>" <?php echo $rolle === $mitglied['role'] ? 'selected' : ''; ?>><?php echo saas_html($rollenLabels[$rolle] ?? $rolle); ?></option>
<?php endforeach; ?>
</select>
<button type="submit">Rolle ändern</button>
</form>
<form method="post" action="<?php echo htmlspecialchars($_SERVER["PHP_SELF"]); ?>" style="display:inline">
<input type="hidden" name="aktion" value="zugang_entziehen">
<input type="hidden" name="mitgliedID" value="<?php echo $mitglied['participant_id']; ?>">
<?php echo app_csrf_field(); ?>
<button type="submit">Zugang entziehen</button>
</form>
<?php endif; ?>
<?php else: ?>
<?php echo $mitglied['membership_status'] === 'revoked' ? 'Zugang entzogen' : 'Kein Zugang'; ?>
<?php if ($mitglied['email'] !== null && $mitglied['email'] !== ''): ?>
<form method="post" action="<?php echo htmlspecialchars($_SERVER["PHP_SELF"]); ?>" style="display:inline">
<input type="hidden" name="aktion" value="zugang_gewaehren">
<input type="hidden" name="mitgliedID" value="<?php echo $mitglied['participant_id']; ?>">
<?php echo app_csrf_field(); ?>
<select name="rolle">
<?php foreach (saas_grantable_roles() as $rolle): ?>
<option value="<?php echo saas_html($rolle); ?>"><?php echo saas_html($rollenLabels[$rolle] ?? $rolle); ?></option>
<?php endforeach; ?>
</select>
<button type="submit">Zugang gewähren</button>
</form>
<?php endif; ?>
<?php endif; ?>
</td>
<td>
<ul class="actions">
<li>
<form method="post" action="<?php echo htmlspecialchars($_SERVER["PHP_SELF"]); ?>">
<input type="hidden" name="aktion" value="bearbeiten">
<input type="hidden" name="mitgliedID" value="<?php echo $mitglied['participant_id']; ?>">
<?php echo app_csrf_field(); ?>
<button type="submit">Bearbeiten</button>
</form>
</li>
<li>
<?php if ($mitglied['active']): ?>
<form method="post" action="<?php echo htmlspecialchars($_SERVER["PHP_SELF"]); ?>">
<input type="hidden" name="aktion" value="deaktivieren">
<input type="hidden" name="mitgliedID" value="<?php echo $mitglied['participant_id']; ?>">
<?php echo app_csrf_field(); ?>
<button type="submit">Deaktivieren</button>
</form>
<?php else: ?>
<form method="post" action="<?php echo htmlspecialchars($_SERVER["PHP_SELF"]); ?>">
<input type="hidden" name="aktion" value="aktivieren">
<input type="hidden" name="mitgliedID" value="<?php echo $mitglied['participant_id']; ?>">
<?php echo app_csrf_field(); ?>
<button type="submit">Aktivieren</button>
</form>
<?php endif; ?>
</li>
<?php if ($mitglied['role'] !== 'owner'): ?>
<li>
<form method="post" action="<?php echo htmlspecialchars($_SERVER["PHP_SELF"]); ?>" onsubmit="return confirm('Name und E-Mail dieses Mitglieds unwiderruflich entfernen? Buchungen bleiben erhalten.');">
<input type="hidden" name="aktion" value="anonymisieren">
<input type="hidden" name="mitgliedID" value="<?php echo $mitglied['participant_id']; ?>">
<?php echo app_csrf_field(); ?>
<button type="submit">Anonymisieren</button>
</form>
</li>
<?php endif; ?>
</ul>
</td>
</tr>
<?php endforeach; ?>
</table>
<?php
}else{
echo "<h2>Sie haben keinen Zugang zu dieser Webseite</h2>";
}
?>
</div>
</section>
<?php include "footer.php"; ?>