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:
@@ -564,6 +564,85 @@ function ledger_void_entry_by_legacy_id(PDO $pdo, int $tenantId, string $legacyT
|
||||
return $stmt->rowCount() > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Records a consumption entry straight into the ledger, without any legacy
|
||||
* kl_Kaffeeverbrauch row. Used for tenants that have no legacy shadow table
|
||||
* (every tenant except the migrated default tenant).
|
||||
*/
|
||||
function ledger_record_consumption(PDO $pdo, int $tenantId, int $participantId, int $marks, int $unitPriceCents, string $source, ?int $createdByUserId = null): int
|
||||
{
|
||||
$stmt = $pdo->prepare(
|
||||
"INSERT INTO ledger_entries
|
||||
(tenant_id, participant_id, type, amount_cents, marks_count, unit_price_cents, booked_at, source, created_by_user_id)
|
||||
SELECT ?, id, 'consumption', ?, ?, ?, NOW(), ?, ?
|
||||
FROM participants
|
||||
WHERE id = ? AND tenant_id = ?"
|
||||
);
|
||||
$stmt->execute([$tenantId, -($marks * $unitPriceCents), $marks, $unitPriceCents, $source, $createdByUserId, $participantId, $tenantId]);
|
||||
|
||||
if ($stmt->rowCount() !== 1) {
|
||||
throw new RuntimeException('Der Strich-Eintrag konnte nicht gespeichert werden.');
|
||||
}
|
||||
|
||||
return (int)$pdo->lastInsertId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Records a payment entry straight into the ledger, without any legacy
|
||||
* kl_Einzahlungen row. Used for tenants that have no legacy shadow table.
|
||||
*/
|
||||
function ledger_record_payment(PDO $pdo, int $tenantId, int $participantId, int $amountCents, string $source, ?int $createdByUserId = null): int
|
||||
{
|
||||
$stmt = $pdo->prepare(
|
||||
"INSERT INTO ledger_entries
|
||||
(tenant_id, participant_id, type, amount_cents, booked_at, source, created_by_user_id)
|
||||
SELECT ?, id, 'payment', ?, NOW(), ?, ?
|
||||
FROM participants
|
||||
WHERE id = ? AND tenant_id = ?"
|
||||
);
|
||||
$stmt->execute([$tenantId, $amountCents, $source, $createdByUserId, $participantId, $tenantId]);
|
||||
|
||||
if ($stmt->rowCount() !== 1) {
|
||||
throw new RuntimeException('Die Einzahlung konnte nicht gespeichert werden.');
|
||||
}
|
||||
|
||||
return (int)$pdo->lastInsertId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Ledger-native equivalent of the legacy "100-Tage-Liste" front/back split:
|
||||
* active participants whose summed marks within the trailing window are at
|
||||
* or above the ten-mark threshold ($atLeastTen), or below it. Used for
|
||||
* tenants without a legacy kl_Kaffeeverbrauch history to filter against.
|
||||
*
|
||||
* @return list<array{participant_id: int, tenant_id: int, legacy_mitarbeiter_id: ?int, display_name: string, email: ?string, email_norm: ?string, paypal_name: ?string, active: bool, total_payments_cents: int, total_costs_cents: int, total_marks: int, balance_cents: int, year_payments_cents: int, year_costs_cents: int, year_marks: int}>
|
||||
*/
|
||||
function ledger_fetch_participants_by_window_marks(PDO $pdo, int $tenantId, int $windowDays, bool $atLeastTen): array
|
||||
{
|
||||
$stmt = $pdo->prepare(
|
||||
"SELECT p.id
|
||||
FROM participants p
|
||||
LEFT JOIN ledger_entries le
|
||||
ON le.tenant_id = p.tenant_id
|
||||
AND le.participant_id = p.id
|
||||
AND le.type = 'consumption'
|
||||
AND le.voided_at IS NULL
|
||||
AND le.booked_at >= DATE_SUB(NOW(), INTERVAL ? DAY)
|
||||
WHERE p.tenant_id = ?
|
||||
AND p.active = 1
|
||||
GROUP BY p.id
|
||||
HAVING COALESCE(SUM(le.marks_count), 0) " . ($atLeastTen ? '>= 10' : '< 10')
|
||||
);
|
||||
$stmt->execute([$windowDays, $tenantId]);
|
||||
$ids = array_map('intval', $stmt->fetchAll(PDO::FETCH_COLUMN));
|
||||
|
||||
if ($ids === []) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return ledger_fetch_participant_summaries($pdo, $tenantId, ['participant_ids' => $ids]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Options:
|
||||
* - participant_id: int
|
||||
|
||||
+53
-8
@@ -247,18 +247,60 @@ gepatcht:
|
||||
auf `kaffeeliste.php`), Zugang entzogen, anschließender Login-Versuch
|
||||
korrekt mit „kein aktiver Mandant" abgelehnt.
|
||||
|
||||
## Fortsetzung: Sammelerfassung tenant-nativ
|
||||
|
||||
Umgesetzte Dateien:
|
||||
|
||||
```text
|
||||
stricheintragen.php (Picker + Schreibpfad neu)
|
||||
einzahlung.php (Picker + Schreibpfad neu)
|
||||
app/ledger.php (ledger_record_consumption, ledger_record_payment,
|
||||
ledger_fetch_participants_by_window_marks)
|
||||
```
|
||||
|
||||
`stricheintragen.php` und `einzahlung.php` lasen ihren Mitarbeiter-Picker
|
||||
bisher direkt aus der global unscoped `kl_Mitarbeiter`-Tabelle. Für jeden
|
||||
Mandanten außer dem Default-Mandanten war das faktisch nutzlos: Die Liste
|
||||
zeigte fremde (Default-Mandanten-)Namen an, und Schreiben schlug wegen des
|
||||
Tenant-Checks in `ledger_mirror_legacy_*` sicher, aber ohne verständliche
|
||||
Fehlermeldung fehl.
|
||||
|
||||
- Der Picker kommt jetzt aus `ledger_fetch_participant_summaries()`
|
||||
(tenant-scoped), Formularfelder verwenden `participant_id` statt
|
||||
`MitarbeiterID`.
|
||||
- Beim Speichern wird pro Teilnehmer entschieden: Hat der Teilnehmer eine
|
||||
`legacy_mitarbeiter_id` (Default-Mandant), läuft der Schreibpfad wie
|
||||
bisher über `kl_Kaffeeverbrauch`/`kl_Einzahlungen` plus Ledger-Spiegelung.
|
||||
Ohne Legacy-Verknüpfung (jeder andere Mandant) wird direkt über die neuen
|
||||
Funktionen `ledger_record_consumption()`/`ledger_record_payment()` ins
|
||||
Ledger gebucht, ohne Legacy-Tabellen zu berühren.
|
||||
- Die "Vorderseite"/"Rückseite"-Filter (100-Tage-Listen-Regel, `>= 10`
|
||||
beziehungsweise `< 10` Striche im Fenster) bleiben für den Default-
|
||||
Mandanten exakt auf der bisherigen Legacy-Logik (Anker: jüngstes Datum in
|
||||
`kl_Kaffeeverbrauch`). Andere Mandanten nutzen die neue, Ledger-native
|
||||
Fensterprüfung `ledger_fetch_participants_by_window_marks()` mit dem in
|
||||
`tenant_settings.sheet_window_days` konfigurierten Fenster.
|
||||
- Nebenbei zwei Legacy-Bugs behoben: In `einzahlung.php` verlinkten die
|
||||
Vorderseite/Rückseite/Alle-Buttons auf `?aktion=...`, ausgewertet wurde
|
||||
aber `$_GET['action']` – die Filter griffen also nie. Und in beiden
|
||||
Dateien blieb nach einem POST-Speichern `$sqlMitarbeiter` unbelegt
|
||||
(behoben bereits in der vorherigen Session, jetzt strukturell nicht mehr
|
||||
möglich, da die Anzeige nach dem Speichern regulär über denselben
|
||||
Picker-Code läuft).
|
||||
- Preis-pro-Strich-Vorbelegung kommt jetzt aus `tenant_settings.mark_price_cents`
|
||||
statt aus der mandantenunabhängigen `kl_config`-Tabelle. Das ist auch für
|
||||
den Default-Mandanten eine Korrektur: `kl_config` wird seit M3 nicht mehr
|
||||
über `mandant-einstellungen.php` gepflegt und kann veraltet sein.
|
||||
- Live gegen die Dev-Datenbank getestet: eigener isolierter Test-Mandant
|
||||
angelegt, Picker zeigt nur dessen Teilnehmer, Striche und Einzahlung rein
|
||||
Ledger-nativ gebucht (korrekter Saldo), Vorder-/Rückseiten-Schwelle bei
|
||||
10 Strichen korrekt ein-/aussortiert, Default-Mandant weiterhin mit
|
||||
Dual-Write geprüft. Alle Testdaten anschließend entfernt.
|
||||
|
||||
## Noch offen
|
||||
|
||||
- Eigene PayPal-/Zahlungsbereich als eigenständiger App-Screen (aktuell nur im
|
||||
Dashboard integriert).
|
||||
- `stricheintragen.php` und `einzahlung.php` lesen ihre Mitarbeiter-Picker
|
||||
weiterhin aus der global unscoped `kl_Mitarbeiter`-Tabelle. Für den
|
||||
Default-Mandanten funktioniert das unverändert; für jeden anderen Mandanten
|
||||
ist die Liste faktisch leer beziehungsweise zeigt (nur lesend, Schreiben
|
||||
schlägt dank Tenant-Scope in `ledger_mirror_legacy_*` sicher fehl) die
|
||||
Namen der Default-Mandanten-Mitglieder an. Das ist ein bestehendes,
|
||||
eigenständiges Scope-Thema für eine spätere Iteration, keine Regression
|
||||
dieser Session.
|
||||
- Export, Mail und Jahresprozesse bleiben M6-Themen.
|
||||
|
||||
## Aktueller Prüfstatus
|
||||
@@ -276,3 +318,6 @@ gepatcht:
|
||||
(korrekt escaped), Soft-Delete, Banner-Anzeige auf Mandant geprüft.
|
||||
- Live-Test Zugangsvergabe/-entzug: kompletter Flow von Einladung bis
|
||||
Login-Sperre nach Entzug erfolgreich geprüft (siehe oben).
|
||||
- Live-Test Sammelerfassung für Nicht-Default-Mandanten: eigener Test-Tenant,
|
||||
Striche/Einzahlung Ledger-nativ gebucht, Vorder-/Rückseiten-Fenster
|
||||
geprüft (siehe oben).
|
||||
|
||||
@@ -534,7 +534,14 @@ Schritte:
|
||||
die Revision erhalten.
|
||||
- Sammelerfassung (`stricheintragen.php`, `einzahlung.php`) tenant-sicher
|
||||
absichern und ans Ledger anbinden: erledigt. Beide Seiten hatten zuvor
|
||||
keine Zugriffskontrolle außer CSRF; das ist behoben.
|
||||
keine Zugriffskontrolle außer CSRF; das ist behoben. Zusätzlich lasen
|
||||
beide Seiten ihre Mitarbeiterliste aus der global unscoped
|
||||
`kl_Mitarbeiter`-Tabelle, was sie für jeden Mandanten außer dem
|
||||
Default-Mandanten unbrauchbar machte. Der Picker kommt jetzt aus
|
||||
`participants` (tenant-scoped); der Schreibpfad nutzt für den
|
||||
Default-Mandanten weiterhin Dual-Write nach `kl_Kaffeeverbrauch`/
|
||||
`kl_Einzahlungen`, für alle anderen Mandanten schreibt er direkt und
|
||||
ausschließlich ins Ledger.
|
||||
- Hinweise als tenant-spezifische Notices umsetzen: erledigt. Neue Tabelle
|
||||
`notices` mit Soft-Delete, `hinweise.php` und die Banner-Anzeige in
|
||||
`header.php` sind tenant-scoped umgestellt; `kl_hinweise` bleibt nur noch
|
||||
|
||||
+90
-61
@@ -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,19 +151,31 @@ $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>
|
||||
</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>
|
||||
</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>
|
||||
</li>
|
||||
@@ -168,14 +199,12 @@ $stmtMitarbeiter = sqlsrv_query($conn, $sqlMitarbeiter);
|
||||
<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>";
|
||||
|
||||
+104
-68
@@ -43,30 +43,102 @@ if (!$hasAccess) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Liefert die Mitarbeiter/Teilnehmerliste fuer die Vorder-/Rueckseiten- oder
|
||||
* Alle-Auswahl. Fuer den Default-Mandanten bleibt die Fensterlogik exakt wie
|
||||
* im Legacy-Bestand (Anker ist das juengste Datum in kl_Kaffeeverbrauch);
|
||||
* andere Mandanten haben keine Legacy-Historie und nutzen die
|
||||
* Ledger-native Fensterpruefung mit dem konfigurierten Listenfenster.
|
||||
*
|
||||
* @return list<array>
|
||||
*/
|
||||
function stricheintragen_fetch_participants(PDO $pdo, int $tenantId, string $action): array
|
||||
{
|
||||
$isDefault = ledger_is_default_tenant($pdo, $tenantId);
|
||||
|
||||
if ($isDefault && $action === 'vorderseite') {
|
||||
$stmt = $pdo->query(
|
||||
"SELECT M.MitarbeiterID
|
||||
FROM kl_Mitarbeiter M
|
||||
JOIN kl_Kaffeeverbrauch V ON M.MitarbeiterID = V.MitarbeiterID
|
||||
WHERE V.Datum >= DATE_SUB((SELECT MAX(Datum) FROM kl_Kaffeeverbrauch WHERE Datum < CURDATE()), INTERVAL 100 DAY)
|
||||
AND M.aktiv = 1
|
||||
GROUP BY M.MitarbeiterID
|
||||
HAVING SUM(V.AnzahlStriche) >= 10"
|
||||
);
|
||||
$legacyIds = array_map('intval', $stmt->fetchAll(PDO::FETCH_COLUMN));
|
||||
|
||||
return ledger_fetch_participant_summaries($pdo, $tenantId, ['legacy_mitarbeiter_ids' => $legacyIds]);
|
||||
}
|
||||
|
||||
if ($isDefault && $action === 'rueckseite') {
|
||||
$stmt = $pdo->query(
|
||||
"SELECT M.MitarbeiterID
|
||||
FROM kl_Mitarbeiter M
|
||||
LEFT JOIN kl_Kaffeeverbrauch V ON M.MitarbeiterID = V.MitarbeiterID AND V.Datum >= DATE_SUB((SELECT MAX(Datum) FROM kl_Kaffeeverbrauch), INTERVAL 100 DAY)
|
||||
WHERE M.aktiv = 1
|
||||
GROUP BY M.MitarbeiterID
|
||||
HAVING COALESCE(SUM(V.AnzahlStriche), 0) < 10"
|
||||
);
|
||||
$legacyIds = array_map('intval', $stmt->fetchAll(PDO::FETCH_COLUMN));
|
||||
|
||||
return ledger_fetch_participant_summaries($pdo, $tenantId, ['legacy_mitarbeiter_ids' => $legacyIds]);
|
||||
}
|
||||
|
||||
if (!$isDefault && ($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;
|
||||
|
||||
// Verarbeitung des Formulars, wenn es gesendet wurde
|
||||
if ($_SERVER["REQUEST_METHOD"] == "POST") {
|
||||
$hatGespeichert = true;
|
||||
$kostenproStrich = floatval($_POST["kostenproStrich"] ?? 0);
|
||||
$datum = date("Y-m-d H:i:s"); // Das aktuelle Datum verwenden
|
||||
$eingetragen = 0;
|
||||
$fehlgeschlagen = false;
|
||||
$unitPriceCents = (int)round($kostenproStrich * 100);
|
||||
$datum = date("Y-m-d H:i:s");
|
||||
|
||||
// Legacy-Verknuepfung je Teilnehmer vorab laden, damit pro Zeile
|
||||
// entschieden werden kann, ob (Default-Mandant) zusaetzlich in
|
||||
// kl_Kaffeeverbrauch 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_Kaffeeverbrauch (MitarbeiterID, AnzahlStriche, Kosten, KostenproStrich, Datum) VALUES (?, ?, ?, ?, ?)"
|
||||
);
|
||||
|
||||
foreach ($_POST["anzahlStriche"] ?? [] as $mitarbeiterID => $anzahlStriche) {
|
||||
$mitarbeiterID = (int)$mitarbeiterID;
|
||||
foreach ($_POST["anzahlStriche"] ?? [] as $participantId => $anzahlStriche) {
|
||||
$participantId = (int)$participantId;
|
||||
$anzahlStriche = (int)$anzahlStriche;
|
||||
if ($mitarbeiterID <= 0 || $anzahlStriche === 0) {
|
||||
if ($participantId <= 0 || $anzahlStriche === 0 || !array_key_exists($participantId, $legacyMap)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$kosten = $anzahlStriche * $kostenproStrich;
|
||||
$insert->execute([$mitarbeiterID, $anzahlStriche, $kosten, $kostenproStrich, $datum]);
|
||||
$legacyConsumptionId = (int)$pdo->lastInsertId();
|
||||
ledger_mirror_legacy_consumption($pdo, $tenantId, $legacyConsumptionId);
|
||||
$legacyMitarbeiterId = $legacyMap[$participantId];
|
||||
if ($legacyMitarbeiterId !== null) {
|
||||
$kosten = $anzahlStriche * $kostenproStrich;
|
||||
$insertLegacy->execute([$legacyMitarbeiterId, $anzahlStriche, $kosten, $kostenproStrich, $datum]);
|
||||
$legacyConsumptionId = (int)$pdo->lastInsertId();
|
||||
ledger_mirror_legacy_consumption($pdo, $tenantId, $legacyConsumptionId);
|
||||
} else {
|
||||
ledger_record_consumption($pdo, $tenantId, $participantId, $anzahlStriche, $unitPriceCents, 'manual_bulk');
|
||||
}
|
||||
$eingetragen++;
|
||||
}
|
||||
|
||||
@@ -78,55 +150,14 @@ if ($_SERVER["REQUEST_METHOD"] == "POST") {
|
||||
$fehlgeschlagen = true;
|
||||
}
|
||||
|
||||
if ($fehlgeschlagen) {
|
||||
echo "Die Einträge 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"){
|
||||
|
||||
$sqlMitarbeiter = "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, (SELECT MAX(Datum) FROM kl_Kaffeeverbrauch WHERE Datum < CAST(GETDATE() AS DATE))) AND M.aktiv = 1
|
||||
GROUP BY M.MitarbeiterID, M.Name, M.Email
|
||||
HAVING SUM(V.AnzahlStriche) >= 10
|
||||
ORDER BY Name;";
|
||||
|
||||
}elseif( $_GET["action"] == "rueckseite"){
|
||||
|
||||
$sqlMitarbeiter = "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, (SELECT MAX(Datum) FROM kl_Kaffeeverbrauch))
|
||||
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);
|
||||
|
||||
// Kosten pro Strich auslesen
|
||||
$sqlKostenproStrich = "SELECT KostenproStrich FROM kl_config ";
|
||||
$stmtKostenproStrich = sqlsrv_query($conn, $sqlKostenproStrich);
|
||||
$row = sqlsrv_fetch_array($stmtKostenproStrich, SQLSRV_FETCH_ASSOC);
|
||||
$KostenproStrichtemp = $row["KostenproStrich"];
|
||||
$KostenproStrich = number_format($KostenproStrichtemp, 2, '.', '');
|
||||
$mitarbeiter = stricheintragen_fetch_participants($pdo, $tenantId, $action);
|
||||
$settings = saas_fetch_tenant_settings($pdo, $tenantId);
|
||||
$kostenproStrichAnzeige = number_format(($settings['mark_price_cents'] ?? 20) / 100, 2, '.', '');
|
||||
|
||||
?>
|
||||
|
||||
@@ -140,6 +171,15 @@ $KostenproStrich = number_format($KostenproStrichtemp, 2, '.', '');
|
||||
<body>
|
||||
|
||||
<h2>Anzahl der Striche für alle Mitarbeiter</h2>
|
||||
|
||||
<?php if ($hatGespeichert): ?>
|
||||
<?php if ($fehlgeschlagen): ?>
|
||||
<div class="hint-box error"><p>Die Einträge 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="stricheintragen.php" method="get">
|
||||
@@ -166,7 +206,7 @@ $KostenproStrich = number_format($KostenproStrichtemp, 2, '.', '');
|
||||
<?php
|
||||
|
||||
echo "<label for='kostenproStrich'>Kosten pro Strich:</label>
|
||||
<input type='number' name='kostenproStrich' step='0.01' value='" . $KostenproStrich . "'><br>";
|
||||
<input type='number' name='kostenproStrich' step='0.01' value='" . saas_html($kostenproStrichAnzeige) . "'><br>";
|
||||
|
||||
echo "<table>";
|
||||
echo " <tr>
|
||||
@@ -174,17 +214,13 @@ $KostenproStrich = number_format($KostenproStrichtemp, 2, '.', '');
|
||||
<th>Anzahl Striche</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='anzahlStriche[$mitarbeiterID]'>$mitarbeiterName:</label></td>";
|
||||
echo "<td><input type='number' name='anzahlStriche[$mitarbeiterID]' ></td>";
|
||||
echo "<td><label for='anzahlStriche[$participantId]'>$name:</label></td>";
|
||||
echo "<td><input type='number' name='anzahlStriche[$participantId]' ></td>";
|
||||
echo "</tr>";
|
||||
|
||||
}
|
||||
echo "</table>";
|
||||
|
||||
|
||||
Reference in New Issue
Block a user