M5: Sammelerfassung (Striche/Einzahlungen) tenant-nativ machen

stricheintragen.php und einzahlung.php lasen ihren Mitarbeiter-Picker
bisher direkt aus der global unscoped kl_Mitarbeiter-Tabelle. Fuer jeden
Mandanten ausser dem Default-Mandanten zeigte das fremde Namen und
Schreiben schlug (sicher, aber unverstaendlich) am Tenant-Check in
ledger_mirror_legacy_* fehl.

- Picker kommt jetzt aus participants (tenant-scoped), Formularfelder
  nutzen participant_id statt MitarbeiterID.
- Schreibpfad pro Teilnehmer: mit legacy_mitarbeiter_id (Default-Mandant)
  weiterhin Dual-Write nach kl_Kaffeeverbrauch/kl_Einzahlungen plus
  Ledger-Spiegelung; ohne Legacy-Verknuepfung (jeder andere Mandant) direkt
  ueber neue ledger_record_consumption()/ledger_record_payment().
- Vorderseite/Rueckseite-Filter (100-Tage-Regel) bleiben fuer den
  Default-Mandanten exakt auf der bisherigen Legacy-Logik; andere
  Mandanten nutzen die neue ledger_fetch_participants_by_window_marks()
  mit tenant_settings.sheet_window_days.
- Nebenbei behoben: einzahlung.php verlinkte auf ?aktion=... statt
  ?action=..., wodurch die Vorderseite/Rueckseite-Buttons nie griffen.
  Preis-pro-Strich-Vorbelegung kommt jetzt aus tenant_settings statt der
  seit M3 nicht mehr gepflegten kl_config-Tabelle.
- Live getestet: isolierter Test-Mandant, Picker zeigt nur eigene
  Teilnehmer, Buchungen rein Ledger-nativ mit korrektem Saldo, 10-Striche-
  Schwelle korrekt sortiert, Default-Mandant-Dual-Write weiterhin gruen.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-15 00:57:51 +02:00
co-authored by Claude Sonnet 5
parent aeb687f42e
commit 4d5f32ae6d
5 changed files with 346 additions and 150 deletions
+96 -67
View File
@@ -42,28 +42,85 @@ if (!$hasAccess) {
exit;
}
/**
* Liefert die Mitarbeiter/Teilnehmerliste fuer die Vorder-/Rueckseiten- oder
* Alle-Auswahl. Siehe stricheintragen.php fuer dieselbe Logik: Default-
* Mandant nutzt die Legacy-Historie, andere Mandanten die Ledger-native
* Fensterpruefung mit dem konfigurierten Listenfenster.
*
* @return list<array>
*/
function einzahlung_fetch_participants(PDO $pdo, int $tenantId, string $action): array
{
$isDefault = ledger_is_default_tenant($pdo, $tenantId);
if ($action === 'vorderseite' || $action === 'rueckseite') {
if ($isDefault) {
$vergleich = $action === 'vorderseite' ? '>= 10' : '< 10';
$join = $action === 'vorderseite' ? 'JOIN' : 'LEFT JOIN';
$stmt = $pdo->query(
"SELECT M.MitarbeiterID
FROM kl_Mitarbeiter M
{$join} kl_Kaffeeverbrauch V ON M.MitarbeiterID = V.MitarbeiterID AND V.Datum >= DATE_SUB(NOW(), INTERVAL 100 DAY)
WHERE M.aktiv = 1
GROUP BY M.MitarbeiterID
HAVING COALESCE(SUM(V.AnzahlStriche), 0) {$vergleich}"
);
$legacyIds = array_map('intval', $stmt->fetchAll(PDO::FETCH_COLUMN));
return ledger_fetch_participant_summaries($pdo, $tenantId, ['legacy_mitarbeiter_ids' => $legacyIds]);
}
$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;
// Verarbeitung des Formulars, wenn es gesendet wurde
if ($_SERVER["REQUEST_METHOD"] == "POST" ) {
$hatGespeichert = true;
$datum = date("Y-m-d H:i:s"); // Das aktuelle Datum verwenden
$eingetragen = 0;
$fehlgeschlagen = false;
// Legacy-Verknuepfung je Teilnehmer vorab laden, damit pro Zeile
// entschieden werden kann, ob (Default-Mandant) zusaetzlich in
// kl_Einzahlungen geschrieben oder (jeder andere Mandant) direkt ins
// Ledger gebucht wird.
$legacyMap = [];
$stmt = $pdo->prepare('SELECT id, legacy_mitarbeiter_id FROM participants WHERE tenant_id = ?');
$stmt->execute([$tenantId]);
foreach ($stmt->fetchAll() as $row) {
$legacyMap[(int)$row['id']] = $row['legacy_mitarbeiter_id'] !== null ? (int)$row['legacy_mitarbeiter_id'] : null;
}
try {
$pdo->beginTransaction();
$insert = $pdo->prepare(
$insertLegacy = $pdo->prepare(
"INSERT INTO kl_Einzahlungen (MitarbeiterID, Betrag, Datum) VALUES (?, ?, ?)"
);
foreach ($_POST["anzahlBetrag"] ?? [] as $mitarbeiterID => $anzahlBetrag) {
$mitarbeiterID = (int)$mitarbeiterID;
foreach ($_POST["anzahlBetrag"] ?? [] as $participantId => $anzahlBetrag) {
$participantId = (int)$participantId;
$anzahlBetrag = floatval($anzahlBetrag);
if ($mitarbeiterID <= 0 || $anzahlBetrag == 0.0) {
if ($participantId <= 0 || $anzahlBetrag == 0.0 || !array_key_exists($participantId, $legacyMap)) {
continue;
}
$insert->execute([$mitarbeiterID, $anzahlBetrag, $datum]);
$legacyPaymentId = (int)$pdo->lastInsertId();
ledger_mirror_legacy_payment($pdo, $tenantId, $legacyPaymentId);
$legacyMitarbeiterId = $legacyMap[$participantId];
if ($legacyMitarbeiterId !== null) {
$insertLegacy->execute([$legacyMitarbeiterId, $anzahlBetrag, $datum]);
$legacyPaymentId = (int)$pdo->lastInsertId();
ledger_mirror_legacy_payment($pdo, $tenantId, $legacyPaymentId);
} else {
ledger_record_payment($pdo, $tenantId, $participantId, (int)round($anzahlBetrag * 100), 'manual_bulk');
}
$eingetragen++;
}
@@ -75,51 +132,13 @@ if ($_SERVER["REQUEST_METHOD"] == "POST" ) {
$fehlgeschlagen = true;
}
if ($fehlgeschlagen) {
echo "Die Einzahlungen konnten nicht gespeichert werden.";
} else {
echo $eingetragen . " Einträge erfolgreich hinzugefügt.";
}
// Mitarbeiter aus der Datenbank abrufen und nach Namen sortieren
$sqlMitarbeiter = "SELECT MitarbeiterID, Name FROM kl_Mitarbeiter WHERE aktiv = 1 ORDER BY Name";
}elseif($_SERVER["REQUEST_METHOD"] == "GET" AND isset($_GET["action"])){
if($_GET["action"] == "vorderseite"){
$sqlMitglieder = "SELECT M.MitarbeiterID, M.Name, M.Email
FROM kl_Mitarbeiter M
JOIN kl_Kaffeeverbrauch V ON M.MitarbeiterID = V.MitarbeiterID
WHERE V.Datum >= DATEADD(DAY, -100, GETDATE()) AND M.aktiv = 1
GROUP BY M.MitarbeiterID, M.Name, M.Email
HAVING SUM(V.AnzahlStriche) >= 10
ORDER BY Name;";
}elseif( $_GET["action"] == "rueckseite"){
$sqlMitglieder = "SELECT M.MitarbeiterID, M.Name, M.Email
FROM kl_Mitarbeiter M
LEFT JOIN kl_Kaffeeverbrauch V ON M.MitarbeiterID = V.MitarbeiterID AND V.Datum >= DATEADD(DAY, -100, GETDATE())
WHERE M.aktiv = 1
GROUP BY M.MitarbeiterID, M.Name, M.Email
HAVING COALESCE(SUM(V.AnzahlStriche), 0) < 10
ORDER BY M.Name;";
}elseif($_GET["action"] == "alle"){
// Mitarbeiter aus der Datenbank abrufen und nach Namen sortieren
$sqlMitarbeiter = "SELECT MitarbeiterID, Name FROM kl_Mitarbeiter WHERE aktiv = 1 ORDER BY Name";
}
}else{
// Mitarbeiter aus der Datenbank abrufen und nach Namen sortieren
$sqlMitarbeiter = "SELECT MitarbeiterID, Name FROM kl_Mitarbeiter WHERE aktiv = 1 ORDER BY Name";
$action = 'alle';
} else {
$action = (string)($_GET['action'] ?? 'alle');
}
$stmtMitarbeiter = sqlsrv_query($conn, $sqlMitarbeiter);
$mitarbeiter = einzahlung_fetch_participants($pdo, $tenantId, $action);
// Schließe die Verbindung zur Datenbank
#sqlsrv_close($conn);
?>
<!DOCTYPE html>
@@ -132,50 +151,60 @@ $stmtMitarbeiter = sqlsrv_query($conn, $sqlMitarbeiter);
<body>
<h2>Einzahlungen für alle Mitarbeiter</h2>
<?php if ($hatGespeichert): ?>
<?php if ($fehlgeschlagen): ?>
<div class="hint-box error"><p>Die Einzahlungen konnten nicht gespeichert werden.</p></div>
<?php else: ?>
<div class="hint-box success"><p><?php echo (int)$eingetragen; ?> Einträge erfolgreich hinzugefügt.</p></div>
<?php endif; ?>
<?php endif; ?>
<ul class="actions">
<li>
<form action="einzahlung.php?aktion=vorderseite" method="get">
<form action="einzahlung.php" method="get">
<input type="hidden" name="action" value="vorderseite">
<button type="submit">Vorderseite</button>
</form>
</form>
</li>
<li>
<form action="einzahlung.php?aktion=rueckseite" method="get">
<form action="einzahlung.php" method="get">
<input type="hidden" name="action" value="rueckseite">
<button type="submit">Rückseite</button>
</form>
</form>
</li>
<li>
<form action="einzahlung.php?aktion=alle" method="get">
<form action="einzahlung.php" method="get">
<input type="hidden" name="action" value="alle">
<button type="submit">Alle</button>
</form>
</form>
</li>
<li>
<form action="letzteneintraege.php" method="get">
<button type="submit">Letzten Einträge</button>
</form>
</form>
</li>
<li>
<form action="csvupload.php" method="get">
<button type="submit">CSV Upload</button>
</form>
</form>
</li>
</ul>
<form method="post" action="<?php echo htmlspecialchars($_SERVER["PHP_SELF"]); ?>">
<?php echo app_csrf_field(); ?>
<?php
echo "<table>";
echo " <tr>
<th>Mitarbeiter</th>
<th>Betrag</th>
</tr>";
// Iteriere über alle Mitarbeiter
while ($row = sqlsrv_fetch_array($stmtMitarbeiter, SQLSRV_FETCH_ASSOC)) {
$mitarbeiterID = $row["MitarbeiterID"];
$mitarbeiterName = $row["Name"];
foreach ($mitarbeiter as $teilnehmer) {
$participantId = $teilnehmer['participant_id'];
$name = saas_html($teilnehmer['display_name']);
echo "<tr>";
// Formularfeld für die Anzahl der Striche
echo "<td><label for='anzahlBetrag[$mitarbeiterID]'>$mitarbeiterName </label></td>";
echo "<td><input type='number' name='anzahlBetrag[$mitarbeiterID]' step='0.01'></td>";
echo "<td><label for='anzahlBetrag[$participantId]'>$name </label></td>";
echo "<td><input type='number' name='anzahlBetrag[$participantId]' step='0.01'></td>";
echo "</tr>";
}
echo "</table>";