Funktions-Schalter je Mandant (app/features.php, tenant_features): der Betreiber schaltet FAQ, Selbsteintrag, PDF, PayPal-Eingang, CSV-Import, Mailversand, Jahresabschluss, Datenexport und eigenes Design pro Mandant frei. Gesperrte Funktionen verschwinden aus Menue und Schaltflaechen, ihre Seiten weisen Aufrufe und POSTs ab. Das Back-Office ist jetzt fuer Platform-Admins im Menue verlinkt statt nur per URL erreichbar. Eigenes Design je Mandant: Akzentfarbe und Logo in den Mandant- Einstellungen, eingebettet ueber app/branding.php; das Logo liegt geschuetzt in var/tenant_logos und wird nur ueber tenant-logo-anzeigen.php an den eigenen Mandanten ausgeliefert. Weniger Startinformationen: Startpaket neuer Mandanten auf zwei Beispielfragen gekuerzt, Anleitung von ~1400 auf ~750 Woerter gestrafft und um Abschnitte zu gesperrten Funktionen bereinigt. Vorder-/Rueckseite erst bei mehr als 50 Personen (vorher schon ab 50). Die Schwelle liegt jetzt gemeinsam in app/ledger.php und gilt auch fuer die Vorder-/Rueckseiten-Auswahl beim Erfassen, wo sie bisher unabhaengig von der Teamgroesse angeboten wurde. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
264 lines
7.9 KiB
PHP
264 lines
7.9 KiB
PHP
<?php
|
|
|
|
ob_start();
|
|
|
|
include "functions.php";
|
|
require_once __DIR__ . "/app/ledger.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', 'treasurer'], $saasUser)) {
|
|
$tenantId = (int)$saasUser['tenant_id'];
|
|
$hasAccess = true;
|
|
}
|
|
|
|
if (!$hasAccess) {
|
|
echo "<h2>Kein Zugriff</h2>";
|
|
include "footer.php";
|
|
exit;
|
|
}
|
|
|
|
/**
|
|
* Liefert die Teilnehmerliste fuer die Vorder-/Rueckseiten- oder
|
|
* Alle-Auswahl. Vorderseite sind die Vieltrinker (>= 10 Striche im
|
|
* Listenfenster), Rueckseite der Rest.
|
|
*
|
|
* @return list<array>
|
|
*/
|
|
function stricheintragen_fetch_participants(PDO $pdo, int $tenantId, string $action): array
|
|
{
|
|
if ($action === 'vorderseite' || $action === 'rueckseite') {
|
|
$settings = saas_fetch_tenant_settings($pdo, $tenantId);
|
|
$windowDays = $settings['sheet_window_days'] ?? 100;
|
|
|
|
return ledger_fetch_participants_by_window_marks($pdo, $tenantId, $windowDays, $action === 'vorderseite');
|
|
}
|
|
|
|
return ledger_fetch_participant_summaries($pdo, $tenantId, ['active_only' => true]);
|
|
}
|
|
|
|
$eingetragen = 0;
|
|
$fehlgeschlagen = false;
|
|
$hatGespeichert = false;
|
|
$erfolgsmeldung = null;
|
|
$validierungsFehler = [];
|
|
$eingaben = [];
|
|
$kostenproStrichEingabe = null;
|
|
$action = (string)($_GET['action'] ?? 'alle');
|
|
if (!in_array($action, ['vorderseite', 'rueckseite', 'alle'], true)) {
|
|
$action = 'alle';
|
|
}
|
|
|
|
if (isset($_SESSION['flash_stricheintragen']) && is_array($_SESSION['flash_stricheintragen'])) {
|
|
$flash = $_SESSION['flash_stricheintragen'];
|
|
unset($_SESSION['flash_stricheintragen']);
|
|
if (($flash['type'] ?? '') === 'success') {
|
|
$erfolgsmeldung = sprintf('%d Einträge erfolgreich hinzugefügt.', (int)($flash['count'] ?? 0));
|
|
}
|
|
}
|
|
|
|
const STRICHE_MAX_PRO_PERSON = 50;
|
|
const STRICHE_MAX_PREIS_CENTS = 10000;
|
|
|
|
// Verarbeitung des Formulars, wenn es gesendet wurde
|
|
if ($_SERVER["REQUEST_METHOD"] == "POST") {
|
|
$hatGespeichert = true;
|
|
$action = (string)($_POST['liste_action'] ?? 'alle');
|
|
if (!in_array($action, ['vorderseite', 'rueckseite', 'alle'], true)) {
|
|
$action = 'alle';
|
|
}
|
|
$kostenproStrichRaw = trim((string)($_POST["kostenproStrich"] ?? ''));
|
|
$kostenproStrichEingabe = $kostenproStrichRaw;
|
|
$unitPriceCents = saas_parse_money_cents($kostenproStrichRaw);
|
|
|
|
if ($unitPriceCents === null || $unitPriceCents <= 0) {
|
|
$validierungsFehler[] = 'Bitte gültige Kosten pro Strich angeben.';
|
|
} elseif ($unitPriceCents > STRICHE_MAX_PREIS_CENTS) {
|
|
$validierungsFehler[] = 'Die Kosten pro Strich überschreiten die Plausibilitätsgrenze von 100,00 €. Bitte prüfen.';
|
|
}
|
|
|
|
// Teilnehmer des Mandanten - dient der Zugehoerigkeitspruefung, damit ueber
|
|
// das Formular keine fremden IDs bebucht werden koennen.
|
|
$eigeneTeilnehmer = [];
|
|
$stmt = $pdo->prepare('SELECT id, display_name FROM participants WHERE tenant_id = ?');
|
|
$stmt->execute([$tenantId]);
|
|
foreach ($stmt->fetchAll() as $row) {
|
|
$eigeneTeilnehmer[(int)$row['id']] = (string)$row['display_name'];
|
|
}
|
|
|
|
$zuBuchen = [];
|
|
foreach ($_POST["anzahlStriche"] ?? [] as $participantId => $anzahlStriche) {
|
|
$participantId = (int)$participantId;
|
|
if ($participantId <= 0 || !array_key_exists($participantId, $eigeneTeilnehmer)) {
|
|
continue;
|
|
}
|
|
|
|
$rohStriche = trim((string)$anzahlStriche);
|
|
$eingaben[$participantId] = $rohStriche;
|
|
if ($rohStriche === '' || $rohStriche === '0') {
|
|
continue;
|
|
}
|
|
|
|
$name = $eigeneTeilnehmer[$participantId];
|
|
if (!preg_match('/^[0-9]+$/', $rohStriche)) {
|
|
$validierungsFehler[] = sprintf('%s: Bitte eine ganze positive Strich-Anzahl eintragen.', $name);
|
|
continue;
|
|
}
|
|
|
|
$anzahlStriche = (int)$rohStriche;
|
|
if ($anzahlStriche > STRICHE_MAX_PRO_PERSON) {
|
|
$validierungsFehler[] = sprintf(
|
|
'%s: %d Striche überschreiten die Plausibilitätsgrenze von %d pro Buchung.',
|
|
$name,
|
|
$anzahlStriche,
|
|
STRICHE_MAX_PRO_PERSON
|
|
);
|
|
continue;
|
|
}
|
|
|
|
$zuBuchen[$participantId] = $anzahlStriche;
|
|
}
|
|
|
|
if ($validierungsFehler === [] && $zuBuchen === []) {
|
|
$validierungsFehler[] = 'Bitte mindestens eine Strich-Anzahl eintragen.';
|
|
}
|
|
|
|
if ($validierungsFehler === []) {
|
|
try {
|
|
$pdo->beginTransaction();
|
|
|
|
foreach ($zuBuchen as $participantId => $anzahlStriche) {
|
|
ledger_record_consumption($pdo, $tenantId, $participantId, $anzahlStriche, (int)$unitPriceCents, 'manual_bulk');
|
|
$eingetragen++;
|
|
}
|
|
|
|
$pdo->commit();
|
|
$_SESSION['flash_stricheintragen'] = ['type' => 'success', 'count' => $eingetragen];
|
|
header('Location: stricheintragen.php?action=' . urlencode($action));
|
|
exit;
|
|
} catch (Throwable $e) {
|
|
if ($pdo->inTransaction()) {
|
|
$pdo->rollBack();
|
|
}
|
|
$fehlgeschlagen = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Vorder-/Rueckseite nur anbieten, wenn der Ausdruck auch wirklich zwei Seiten
|
|
// hat - bei kleinen Teams gibt es nur ein Blatt und die Aufteilung waere eine
|
|
// Auswahl ohne Entsprechung auf dem Papier. Muss vor dem Laden der Liste
|
|
// stehen, damit ein alter Link auf "vorderseite" nicht doch eine halbe Liste
|
|
// zeigt.
|
|
$alleAktiven = ledger_fetch_participant_summaries($pdo, $tenantId, ['active_only' => true]);
|
|
$zweiseitig = ledger_uses_two_sided_sheet(count($alleAktiven));
|
|
if (!$zweiseitig) {
|
|
$action = 'alle';
|
|
}
|
|
|
|
$mitarbeiter = $action === 'alle'
|
|
? $alleAktiven
|
|
: stricheintragen_fetch_participants($pdo, $tenantId, $action);
|
|
$settings = saas_fetch_tenant_settings($pdo, $tenantId);
|
|
$kostenproStrichAnzeige = $kostenproStrichEingabe !== null
|
|
? $kostenproStrichEingabe
|
|
: number_format(($settings['mark_price_cents'] ?? 20) / 100, 2, '.', '');
|
|
|
|
?>
|
|
|
|
<h2>Anzahl der Striche für alle Mitarbeiter</h2>
|
|
|
|
<?php if ($hatGespeichert): ?>
|
|
<?php if ($validierungsFehler !== []): ?>
|
|
<div class="hint-box error">
|
|
<ul>
|
|
<?php foreach ($validierungsFehler as $fehler): ?>
|
|
<li><?php echo saas_html($fehler); ?></li>
|
|
<?php endforeach; ?>
|
|
</ul>
|
|
</div>
|
|
<?php elseif ($fehlgeschlagen): ?>
|
|
<div class="hint-box error"><p>Die Einträge konnten nicht gespeichert werden.</p></div>
|
|
<?php endif; ?>
|
|
<?php endif; ?>
|
|
<?php if ($erfolgsmeldung !== null): ?>
|
|
<div class="hint-box success"><p><?php echo saas_html($erfolgsmeldung); ?></p></div>
|
|
<?php endif; ?>
|
|
|
|
<ul class="actions">
|
|
<?php if ($zweiseitig): ?>
|
|
<li>
|
|
<form action="stricheintragen.php" method="get">
|
|
<input type="hidden" name="action" value="vorderseite">
|
|
<button type="submit">Vorderseite</button>
|
|
</form>
|
|
</li>
|
|
<li>
|
|
<form action="stricheintragen.php" method="get">
|
|
<input type="hidden" name="action" value="rueckseite">
|
|
<button type="submit">Rückseite</button>
|
|
</form>
|
|
</li>
|
|
<?php endif; ?>
|
|
<li>
|
|
<form action="stricheintragen.php" method="get">
|
|
<input type="hidden" name="action" value="alle">
|
|
<button type="submit">Alle</button>
|
|
</form>
|
|
</li>
|
|
</ul>
|
|
|
|
<form method="post" action="<?php echo htmlspecialchars($_SERVER["PHP_SELF"]); ?>">
|
|
<?php echo app_csrf_field(); ?>
|
|
<input type="hidden" name="liste_action" value="<?php echo saas_html($action); ?>">
|
|
<?php
|
|
|
|
// Kein <br> noetig - das Feld ist ein Blockelement mit eigenem Abstand.
|
|
echo "<label for='kostenproStrich'>Kosten pro Strich:</label>
|
|
<input type='number' name='kostenproStrich' step='0.01' min='0.01' value='" . saas_html($kostenproStrichAnzeige) . "'>";
|
|
|
|
echo "<table>";
|
|
echo " <tr>
|
|
<th>Mitarbeiter</th>
|
|
<th>Anzahl Striche</th>
|
|
</tr>";
|
|
|
|
foreach ($mitarbeiter as $teilnehmer) {
|
|
$participantId = $teilnehmer['participant_id'];
|
|
$name = saas_html($teilnehmer['display_name']);
|
|
echo "<tr>";
|
|
echo "<td><label for='anzahlStriche[$participantId]'>$name:</label></td>";
|
|
echo "<td><input type='number' name='anzahlStriche[$participantId]' min='0' max='" . STRICHE_MAX_PRO_PERSON . "' step='1' value='" . saas_html($eingaben[$participantId] ?? '') . "'></td>";
|
|
echo "</tr>";
|
|
}
|
|
echo "</table>";
|
|
|
|
?>
|
|
|
|
<button type="submit">Eintragen</button>
|
|
</form>
|
|
|
|
</div>
|
|
</section>
|
|
|
|
<?php include "footer.php"; ?>
|