M0 Einrichtung
This commit is contained in:
@@ -0,0 +1,3 @@
|
|||||||
|
.env.local
|
||||||
|
.local/
|
||||||
|
uploads/
|
||||||
+8
-4
@@ -1,5 +1,9 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
if ((getenv('APP_DB_DRIVER') ?: '') === 'mysql' && !function_exists('sqlsrv_connect')) {
|
||||||
|
require_once __DIR__ . '/lib/sqlsrv_mysql_compat.php';
|
||||||
|
}
|
||||||
|
|
||||||
### ZUGANGSDATEN AD
|
### ZUGANGSDATEN AD
|
||||||
$aduser = '';
|
$aduser = '';
|
||||||
$adpassword = '';
|
$adpassword = '';
|
||||||
@@ -23,11 +27,11 @@ $zugriffsalleAbwesenheitgroup = '';
|
|||||||
$zugriffsAdminsgroup = '';
|
$zugriffsAdminsgroup = '';
|
||||||
|
|
||||||
// Verbindung zur Datenbank herstellen (ersetze die Platzhalter durch deine Daten)
|
// Verbindung zur Datenbank herstellen (ersetze die Platzhalter durch deine Daten)
|
||||||
$serverName = "";
|
$serverName = getenv('DB_HOST') ?: "";
|
||||||
$connectionOptions = array(
|
$connectionOptions = array(
|
||||||
"Database" => "",
|
"Database" => getenv('DB_NAME') ?: "",
|
||||||
"Uid" => "",
|
"Uid" => getenv('DB_USER') ?: "",
|
||||||
"PWD" => "",
|
"PWD" => getenv('DB_PASS') ?: "",
|
||||||
"TrustServerCertificate"=>true
|
"TrustServerCertificate"=>true
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,79 @@
|
|||||||
|
CREATE TABLE IF NOT EXISTS kl_Mitarbeiter (
|
||||||
|
MitarbeiterID INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
Name VARCHAR(255) NOT NULL,
|
||||||
|
Email VARCHAR(255) NOT NULL,
|
||||||
|
paypalname VARCHAR(255) NULL,
|
||||||
|
aktiv TINYINT(1) NOT NULL DEFAULT 1,
|
||||||
|
admin TINYINT(1) NOT NULL DEFAULT 0,
|
||||||
|
UNIQUE KEY uq_kl_mitarbeiter_email (Email)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS kl_config (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
KostenproStrich DECIMAL(10,2) NOT NULL DEFAULT 0.20,
|
||||||
|
paypaluse TINYINT(1) NOT NULL DEFAULT 0,
|
||||||
|
paypallink VARCHAR(1000) NOT NULL DEFAULT '',
|
||||||
|
strichperweb TINYINT(1) NOT NULL DEFAULT 1
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS kl_Einzahlungen (
|
||||||
|
EinzahlungsID INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
MitarbeiterID INT NOT NULL,
|
||||||
|
Betrag DECIMAL(10,2) NOT NULL,
|
||||||
|
Datum DATETIME NOT NULL,
|
||||||
|
KEY idx_kl_einzahlungen_mitarbeiter_datum (MitarbeiterID, Datum),
|
||||||
|
CONSTRAINT fk_kl_einzahlungen_mitarbeiter
|
||||||
|
FOREIGN KEY (MitarbeiterID) REFERENCES kl_Mitarbeiter(MitarbeiterID)
|
||||||
|
ON DELETE CASCADE
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS kl_Kaffeeverbrauch (
|
||||||
|
VerbrauchID INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
MitarbeiterID INT NOT NULL,
|
||||||
|
AnzahlStriche INT NOT NULL,
|
||||||
|
Kosten DECIMAL(10,2) NOT NULL,
|
||||||
|
KostenproStrich DECIMAL(10,2) NOT NULL,
|
||||||
|
Datum DATETIME NOT NULL,
|
||||||
|
Eintragsart INT NULL,
|
||||||
|
KEY idx_kl_verbrauch_mitarbeiter_datum (MitarbeiterID, Datum),
|
||||||
|
CONSTRAINT fk_kl_verbrauch_mitarbeiter
|
||||||
|
FOREIGN KEY (MitarbeiterID) REFERENCES kl_Mitarbeiter(MitarbeiterID)
|
||||||
|
ON DELETE CASCADE
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS kl_hinweise (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
nachricht TEXT NOT NULL,
|
||||||
|
gueltig_bis DATETIME NOT NULL,
|
||||||
|
KEY idx_kl_hinweise_gueltig_bis (gueltig_bis)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS CoffeeSurveyVotedEmails (
|
||||||
|
EmailNorm VARCHAR(255) PRIMARY KEY,
|
||||||
|
CreatedAt DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS CoffeeSurveyResponses (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
Q1_ListEase INT NOT NULL,
|
||||||
|
Q2_ListSatisfaction INT NOT NULL,
|
||||||
|
Q3_CoffeeQuality INT NOT NULL,
|
||||||
|
Q4_WebsiteSatisfaction INT NOT NULL,
|
||||||
|
Q5_Drinks TEXT NOT NULL,
|
||||||
|
Q5_DrinksOther TEXT NULL,
|
||||||
|
Q6_NewVarieties TEXT NULL,
|
||||||
|
Q7_ListProblem VARCHAR(100) NOT NULL,
|
||||||
|
Q7_ListProblemOther TEXT NULL,
|
||||||
|
Q8_Improvements TEXT NOT NULL,
|
||||||
|
Q8_ImprovementsOther TEXT NULL,
|
||||||
|
Q9_BetterIdeas TEXT NULL,
|
||||||
|
CreatedAt DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
INSERT INTO kl_config (id, KostenproStrich, paypaluse, paypallink, strichperweb)
|
||||||
|
VALUES (1, 0.20, 0, '', 1)
|
||||||
|
ON DUPLICATE KEY UPDATE
|
||||||
|
KostenproStrich = VALUES(KostenproStrich),
|
||||||
|
paypaluse = VALUES(paypaluse),
|
||||||
|
strichperweb = VALUES(strichperweb);
|
||||||
|
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
# Lokaler MySQL-Dev-Start
|
||||||
|
|
||||||
|
Die Legacy-App kann fuer Entwicklung gegen MySQL laufen. Dafuer gibt es eine
|
||||||
|
Kompatibilitaetsschicht, die die verwendeten `sqlsrv_*`-Funktionen im Dev-Modus
|
||||||
|
auf PDO/MySQL abbildet.
|
||||||
|
|
||||||
|
## Voraussetzungen
|
||||||
|
|
||||||
|
- PHP CLI mit `pdo_mysql`.
|
||||||
|
- MySQL-Testdatenbank.
|
||||||
|
- Keine Produktivdaten und keine Produktivpasswoerter im Repository.
|
||||||
|
|
||||||
|
In dieser VS-Code-Server-Umgebung wurde PHP lokal unter `.local/` entpackt,
|
||||||
|
weil `sudo` nicht passwordless verfuegbar war. `.local/` wird von Git ignoriert.
|
||||||
|
|
||||||
|
## Umgebungsvariablen
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export APP_DB_DRIVER=mysql
|
||||||
|
export DB_HOST="example.mysql.host"
|
||||||
|
export DB_PORT="3306"
|
||||||
|
export DB_NAME="example_database"
|
||||||
|
export DB_USER="example_user"
|
||||||
|
export DB_PASS="example_password"
|
||||||
|
export DEV_AUTH_EMAIL="admin@test.local"
|
||||||
|
export DEV_AUTH_NAME="Test Admin"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Testdatenbank initialisieren
|
||||||
|
|
||||||
|
```bash
|
||||||
|
php scripts/init-mysql-dev.php
|
||||||
|
```
|
||||||
|
|
||||||
|
In der aktuellen lokalen Umgebung:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
LD_LIBRARY_PATH="$PWD/.local/php/usr/lib/x86_64-linux-gnu" \
|
||||||
|
"$PWD/.local/php/usr/bin/php8.3" \
|
||||||
|
-c "$PWD/.local/php-dev.ini" \
|
||||||
|
scripts/init-mysql-dev.php
|
||||||
|
```
|
||||||
|
|
||||||
|
## Server starten
|
||||||
|
|
||||||
|
```bash
|
||||||
|
HOST=127.0.0.1 PORT=8080 scripts/run-dev-server.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Danach ist die App unter `http://127.0.0.1:8080/` erreichbar. In VS Code kann
|
||||||
|
der Port ueber die Ports-Ansicht weitergeleitet werden.
|
||||||
|
|
||||||
|
## Hinweise
|
||||||
|
|
||||||
|
- Der Dev-Login umgeht AD/LDAP und nutzt `DEV_AUTH_EMAIL`.
|
||||||
|
- Die Tabellen werden aus `database/mysql-dev-schema.sql` angelegt.
|
||||||
|
- Der Dev-Modus ist nur fuer lokale Tests gedacht, nicht fuer Produktion.
|
||||||
|
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
# M0 Baseline
|
||||||
|
|
||||||
|
Stand: 2026-07-11
|
||||||
|
|
||||||
|
M0 dokumentiert den aktuellen Legacy-Bestand, bevor die SaaS-Umstrukturierung
|
||||||
|
beginnt. Ziel ist eine belastbare Ausgangsbasis fuer Migration, Sicherheit,
|
||||||
|
Design-Erhalt und fachliche Vergleichstests.
|
||||||
|
|
||||||
|
## Status
|
||||||
|
|
||||||
|
| Bereich | Status | Ergebnis |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| Code-Inventar | begonnen | Siehe `code-inventory.md` |
|
||||||
|
| Prozesslandkarte | begonnen | In `code-inventory.md` enthalten |
|
||||||
|
| Sicherheitsbaseline | begonnen | Siehe `security-baseline.md` |
|
||||||
|
| DB-Schema-Export | vorbereitet | Siehe `schema-export.sql` |
|
||||||
|
| Golden-Master-Auswertungen | vorbereitet | Siehe `golden-master-queries.sql` |
|
||||||
|
| Design-/Screenshot-Referenz | vorbereitet | Siehe `screenshot-checklist.md` |
|
||||||
|
|
||||||
|
## Lokal abgeschlossen
|
||||||
|
|
||||||
|
- Legacy-Einstiegspunkte und Kernseiten identifiziert.
|
||||||
|
- Fachliche Tabellen aus SQL-Strings abgeleitet.
|
||||||
|
- Schreibende Aktionen und kritische Datenfluesse markiert.
|
||||||
|
- Sicherheitsrisiken fuer M0 inventarisiert.
|
||||||
|
- SQL-Vorlagen fuer Schema- und Golden-Master-Export angelegt.
|
||||||
|
- Screenshot-Checkliste fuer Design-Erhalt angelegt.
|
||||||
|
|
||||||
|
## Lokale Einschraenkungen
|
||||||
|
|
||||||
|
- `php` ist in dieser Umgebung nicht im PATH, daher kein lokaler Syntaxcheck und
|
||||||
|
kein lokaler App-Start.
|
||||||
|
- `sqlcmd` ist in dieser Umgebung nicht im PATH, daher kein direkter
|
||||||
|
SQL-Server-Export aus der Shell.
|
||||||
|
- Die aktuelle M0-Auswertung ist deshalb statisch aus dem Code abgeleitet.
|
||||||
|
|
||||||
|
## Blockiert bis Input vorliegt
|
||||||
|
|
||||||
|
Diese Punkte kann ich ohne externe Informationen nicht abschliessen:
|
||||||
|
|
||||||
|
1. Live- oder Staging-DB-Schema exportieren.
|
||||||
|
2. Tabellen, Spalten, Indizes, Constraints und Rowcounts verifizieren.
|
||||||
|
3. Golden-Master-Ergebnisse gegen echte Daten berechnen.
|
||||||
|
4. Screenshots der echten App-Zustaende aufnehmen, falls die App lokal nicht
|
||||||
|
lauffaehig ist.
|
||||||
|
5. Produktivumgebung dokumentieren: PHP-Version, Webserver, SQL-Server-Version,
|
||||||
|
Auth-Setup, Cron/Job-Ausfuehrung.
|
||||||
|
|
||||||
|
## Was ich von dir brauche
|
||||||
|
|
||||||
|
Bitte stelle eines der folgenden Pakete bereit:
|
||||||
|
|
||||||
|
- Idealerweise: einen anonymisierten SQL-Server-Schemaexport plus Rowcounts.
|
||||||
|
- Alternativ: Zugriffsdaten zu einer Test-/Staging-DB, nicht zu Produktion.
|
||||||
|
- Alternativ: die Ergebnisse aus `schema-export.sql` als CSV/Excel/Markdown.
|
||||||
|
|
||||||
|
Zusaetzlich hilfreich:
|
||||||
|
|
||||||
|
- Ein erreichbarer Test-Link zur bestehenden App oder Screenshots der Seiten aus
|
||||||
|
`screenshot-checklist.md`.
|
||||||
|
- Beispiel-Accounts oder Rollen fuer Admin und normales Mitglied, nur fuer eine
|
||||||
|
Testumgebung.
|
||||||
|
- Info, ob hart codierte Zugangsdaten aus Legacy-Skripten bereits rotiert wurden.
|
||||||
|
- Entscheidung, ob M1/M2 nativ in PHP oder mit Framework vorbereitet werden
|
||||||
|
sollen.
|
||||||
|
|
||||||
|
Bitte keine Produktivpasswoerter oder echten personenbezogenen Exportdaten in
|
||||||
|
das Repository legen.
|
||||||
@@ -0,0 +1,192 @@
|
|||||||
|
# M0 Code- und Prozessinventar
|
||||||
|
|
||||||
|
Stand: 2026-07-11
|
||||||
|
|
||||||
|
Dieses Inventar basiert auf statischer Sichtung des Legacy-Codes im Repository.
|
||||||
|
Das echte Datenbankschema muss noch mit `schema-export.sql` verifiziert werden.
|
||||||
|
|
||||||
|
## App-Struktur
|
||||||
|
|
||||||
|
| Datei | Rolle | Wichtige Beobachtung |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `config.php` | AD-/DB-Konfiguration | Erstellt globale SQL-Server-Verbindung `$conn` |
|
||||||
|
| `functions.php` | Auth-/User-Helper | Liest `$_SERVER['AUTH_USER']`, inkludiert LDAP, prueft `kl_Mitarbeiter` |
|
||||||
|
| `functionsLDAP.php` | LDAP-Mailauflösung | Ermittelt `$mailadress` aus AD |
|
||||||
|
| `header.php` | HTML-Head und Hinweisbanner | Laedt `assets/css/main.css`, liest `kl_hinweise` |
|
||||||
|
| `footer.php` | App-Sidebar und JS | Enthaelt die sichtbare Navigation und Admin-Menues |
|
||||||
|
| `nav.php` | Navigation | Aktuell praktisch leer |
|
||||||
|
| `headerline.php` | Headerfragment | Enthaelt NUL-Zeichen, fachlich unklar |
|
||||||
|
| `assets/css/main.css` | Hauptdesign | HTML5-UP Editorial, gruener Akzent, Tabellen, Buttons, Sidebar |
|
||||||
|
| `DataTables/` | Tabellen-Assets | Wird teilweise eingebunden, aber nicht konsequent initialisiert |
|
||||||
|
| `PHPMailer/` | Mailversand | Fuer Rundmails/Jahresauswertung genutzt |
|
||||||
|
| `TCPDF/` | PDF-Export | Fuer Kaffeelisten-Export genutzt |
|
||||||
|
|
||||||
|
## Fachliche Kernseiten
|
||||||
|
|
||||||
|
| Datei | Prozess | Tabellen |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `index.php` | Persoenliches Dashboard, Saldo, Jahreswerte, eigene Striche, PayPal-Links | `kl_Mitarbeiter`, `kl_Einzahlungen`, `kl_Kaffeeverbrauch`, `kl_config` |
|
||||||
|
| `kaffeeliste.php` | Admin-Gesamtuebersicht mit Salden | `kl_Mitarbeiter`, `kl_Einzahlungen`, `kl_Kaffeeverbrauch` |
|
||||||
|
| `stricheintragen.php` | Sammelerfassung von Strichen | `kl_Mitarbeiter`, `kl_Kaffeeverbrauch`, `kl_config` |
|
||||||
|
| `einzahlung.php` | Sammelerfassung von Einzahlungen | `kl_Mitarbeiter`, `kl_Einzahlungen`, `kl_Kaffeeverbrauch` |
|
||||||
|
| `mitarbeiterverwalten.php` | Mitglieder anlegen, bearbeiten, aktivieren, deaktivieren | `kl_Mitarbeiter` |
|
||||||
|
| `namenanpassen.php` | Anzeigenamen aendern | `kl_Mitarbeiter` |
|
||||||
|
| `letzteneintraege.php` | Letzte Buchungen anzeigen und loeschen | `kl_Einzahlungen`, `kl_Kaffeeverbrauch`, `kl_Mitarbeiter` |
|
||||||
|
| `csvupload.php` | Zahlungs-CSV importieren | `kl_Mitarbeiter`, `kl_Einzahlungen` |
|
||||||
|
| `exportKaffeeliste.php` | PDF-/Listenexport | `kl_Mitarbeiter`, `kl_Kaffeeverbrauch`, `kl_Einzahlungen`, `kl_config` |
|
||||||
|
| `mailausgebe.php` | Mailausgabe/Adressliste | `kl_Mitarbeiter` |
|
||||||
|
| `mailversenden.php` | Massenmail mit aktuellem Stand | `kl_Mitarbeiter`, `kl_Einzahlungen`, `kl_Kaffeeverbrauch` |
|
||||||
|
| `hinweise.php` | Hinweise pflegen | `kl_hinweise` |
|
||||||
|
| `faq.php` | FAQ | Keine fachlichen Schreibzugriffe |
|
||||||
|
| `jahresauswertung.php` | Jahresgutschriften und Mailversand | `kl_Mitarbeiter`, `kl_Kaffeeverbrauch`, `kl_Einzahlungen` |
|
||||||
|
| `umfrage.php` | Umfrage | `CoffeeSurveyVotedEmails`, `CoffeeSurveyResponses` |
|
||||||
|
| `umfrageergebnisse.php` | Umfrageauswertung | `CoffeeSurveyVotedEmails`, `CoffeeSurveyResponses` |
|
||||||
|
|
||||||
|
## Abgeleitete Tabellen und Spalten
|
||||||
|
|
||||||
|
Diese Spalten sind aus dem Code abgeleitet und muessen mit dem echten DB-Schema
|
||||||
|
abgeglichen werden.
|
||||||
|
|
||||||
|
| Tabelle | Abgeleitete Spalten |
|
||||||
|
| --- | --- |
|
||||||
|
| `kl_Mitarbeiter` | `MitarbeiterID`, `Name`, `Email`, `aktiv`, `admin`, `paypalname` |
|
||||||
|
| `kl_Einzahlungen` | `EinzahlungsID`, `MitarbeiterID`, `Betrag`, `Datum` |
|
||||||
|
| `kl_Kaffeeverbrauch` | `VerbrauchID`, `MitarbeiterID`, `AnzahlStriche`, `Kosten`, `KostenproStrich`, `Datum`, `Eintragsart` |
|
||||||
|
| `kl_config` | `KostenproStrich`, `paypaluse`, `paypallink`, `strichperweb` |
|
||||||
|
| `kl_hinweise` | `id`, `nachricht`, `gueltig_bis` |
|
||||||
|
| `CoffeeSurveyVotedEmails` | `EmailNorm` |
|
||||||
|
| `CoffeeSurveyResponses` | diverse `Q*_`-Antwortspalten, siehe Umfragecode |
|
||||||
|
|
||||||
|
## Prozesslandkarte
|
||||||
|
|
||||||
|
### Authentifizierung
|
||||||
|
|
||||||
|
1. Seite inkludiert `functions.php`.
|
||||||
|
2. `functions.php` liest `$_SERVER['AUTH_USER']`.
|
||||||
|
3. `functionsLDAP.php` ermittelt Mailadresse aus LDAP.
|
||||||
|
4. Zugriff wird ueber `kl_Mitarbeiter.Email`, `aktiv` und `admin` geprueft.
|
||||||
|
|
||||||
|
M0-Feststellung:
|
||||||
|
Login-Konto, Kaffee-Teilnehmer und Rolle sind im Legacy-Modell gekoppelt. Fuer
|
||||||
|
SaaS muessen `users`, `participants` und `tenant_memberships` getrennt werden.
|
||||||
|
|
||||||
|
### Persoenliches Dashboard
|
||||||
|
|
||||||
|
Quelle: `index.php`.
|
||||||
|
|
||||||
|
Berechnungen:
|
||||||
|
|
||||||
|
- Mitarbeiter per E-Mail suchen.
|
||||||
|
- Gesamteinzahlung: Summe `kl_Einzahlungen.Betrag`.
|
||||||
|
- Gesamtausgabe: Summe `kl_Kaffeeverbrauch.Kosten`.
|
||||||
|
- Gesamtstriche: Summe `kl_Kaffeeverbrauch.AnzahlStriche`.
|
||||||
|
- Aktueller Stand: Einzahlungen minus Ausgaben.
|
||||||
|
- Jahreswerte: dieselben Summen fuer aktuelles Jahr.
|
||||||
|
- Bei aktivem `strichperweb`: eigener Strich-Eintrag.
|
||||||
|
- Bei aktivem `paypaluse`: PayPal-Links anzeigen.
|
||||||
|
|
||||||
|
### Sammelerfassung Striche
|
||||||
|
|
||||||
|
Quelle: `stricheintragen.php`.
|
||||||
|
|
||||||
|
Berechnungen:
|
||||||
|
|
||||||
|
- Preis pro Strich aus `kl_config.KostenproStrich`.
|
||||||
|
- Pro Mitarbeiter eine Anzahl erfassen.
|
||||||
|
- Kosten = Anzahl Striche mal Kosten pro Strich.
|
||||||
|
- Neue Zeile in `kl_Kaffeeverbrauch`.
|
||||||
|
- Filter `vorderseite`/`rueckseite` anhand der letzten 100 Tage und mindestens
|
||||||
|
10 Strichen.
|
||||||
|
|
||||||
|
### Sammelerfassung Einzahlungen
|
||||||
|
|
||||||
|
Quelle: `einzahlung.php`.
|
||||||
|
|
||||||
|
Berechnungen:
|
||||||
|
|
||||||
|
- Pro Mitarbeiter Betrag erfassen.
|
||||||
|
- Neue Zeile in `kl_Einzahlungen`.
|
||||||
|
- Filter `vorderseite`/`rueckseite` anhand der letzten 100 Tage und mindestens
|
||||||
|
10 Strichen.
|
||||||
|
|
||||||
|
### Mitgliederverwaltung
|
||||||
|
|
||||||
|
Quelle: `mitarbeiterverwalten.php`.
|
||||||
|
|
||||||
|
Aktionen:
|
||||||
|
|
||||||
|
- Mitglied anlegen.
|
||||||
|
- Name/E-Mail/aktiv/admin bearbeiten.
|
||||||
|
- Mitglied aktivieren/deaktivieren.
|
||||||
|
|
||||||
|
M0-Feststellung:
|
||||||
|
Das `admin`-Flag ist fachlich eine Rolleninformation und sollte in der SaaS-App
|
||||||
|
in `tenant_memberships.role` wandern.
|
||||||
|
|
||||||
|
### Korrektur letzter Eintraege
|
||||||
|
|
||||||
|
Quelle: `letzteneintraege.php`.
|
||||||
|
|
||||||
|
Aktionen:
|
||||||
|
|
||||||
|
- Letzte 100 Einzahlungen anzeigen.
|
||||||
|
- Einzahlung hart loeschen.
|
||||||
|
- Letzte 100 Strich-Eintraege anzeigen.
|
||||||
|
- Strich-Eintrag hart loeschen.
|
||||||
|
|
||||||
|
M0-Feststellung:
|
||||||
|
Finanznahe Korrekturen sollten im Zielmodell nicht hart loeschen, sondern als
|
||||||
|
Storno/Reversal mit Audit-Trail gespeichert werden.
|
||||||
|
|
||||||
|
### CSV-Import
|
||||||
|
|
||||||
|
Quelle: `csvupload.php`.
|
||||||
|
|
||||||
|
Ablauf:
|
||||||
|
|
||||||
|
- Datei hochladen.
|
||||||
|
- CSV mit Komma-Trennung lesen.
|
||||||
|
- Name aus Spalte 3, Betrag aus Spalte 7, Datum aus Spalte 0.
|
||||||
|
- Mitarbeiter per Name oder `paypalname` finden.
|
||||||
|
- Dublette anhand Mitarbeiter, Betrag und Datumstag pruefen.
|
||||||
|
- Einzahlung einfuegen.
|
||||||
|
- Ergebnis in Erfolg/Fehler klassifizieren.
|
||||||
|
|
||||||
|
### Jahresauswertung
|
||||||
|
|
||||||
|
Quelle: `jahresauswertung.php`.
|
||||||
|
|
||||||
|
Ablauf:
|
||||||
|
|
||||||
|
- Gesamtstriche des aktuellen Jahres je aktivem Mitarbeiter berechnen.
|
||||||
|
- Konstante Anzahl neuer Striche/Gutschrift proportional verteilen.
|
||||||
|
- Einzahlung je Mitarbeiter einfuegen.
|
||||||
|
- Mail an Mitarbeiter senden.
|
||||||
|
|
||||||
|
M0-Feststellung:
|
||||||
|
Das ist ein Job/Skript mit direktem Schreibzugriff und Mailversand. Fuer SaaS
|
||||||
|
braucht es Dry-Run, Audit, Tenant-Scope und expliziten Operator.
|
||||||
|
|
||||||
|
## Offene Schemafragen
|
||||||
|
|
||||||
|
- Gibt es Primary Keys, Foreign Keys und Indizes fuer die `kl_`-Tabellen?
|
||||||
|
- Sind Datentypen fuer Geldwerte `decimal`, `money`, `float` oder etwas anderes?
|
||||||
|
- Gibt es historische Spalten, die im Code nicht sichtbar sind?
|
||||||
|
- Gibt es Trigger oder Stored Procedures?
|
||||||
|
- Gibt es weitere Jobs ausser `jahresauswertung.php`?
|
||||||
|
- Sind Umfragetabellen produktiv relevant oder Archiv/Randmodul?
|
||||||
|
|
||||||
|
## Technische Legacy-Inkonsistenzen
|
||||||
|
|
||||||
|
- `csvupload.php` sucht in `getMitarbeiterID` nach `Name = ? or paypalname = ?`,
|
||||||
|
uebergibt aber nur einen Parameter. Das sollte vor der Uebernahme des
|
||||||
|
CSV-Flows verifiziert werden.
|
||||||
|
- `einzahlung.php` prueft auf `$_GET["action"]`, die Buttons erzeugen aber
|
||||||
|
URLs mit `aktion=...`. Der Vorderseite-/Rueckseite-Filter kann dadurch
|
||||||
|
abweichend vom erwarteten Verhalten laufen.
|
||||||
|
- `exportKaffeeliste.php` referenziert `tcpdf/tcpdf.php`, waehrend der Ordner im
|
||||||
|
Repository `TCPDF/` heisst. Auf case-sensitiven Dateisystemen kann das
|
||||||
|
brechen.
|
||||||
|
- `headerline.php` ist fachlich leer beziehungsweise mit NUL-Zeichen gefuellt.
|
||||||
|
Vor der neuen App-Shell sollte geklaert werden, ob die Datei entfernt werden
|
||||||
|
kann.
|
||||||
@@ -0,0 +1,185 @@
|
|||||||
|
/*
|
||||||
|
M0 Golden-Master Queries
|
||||||
|
|
||||||
|
Ziel:
|
||||||
|
- Fachliche Legacy-Ergebnisse exportieren, damit Migration und neue SaaS-App
|
||||||
|
gegen denselben Stand verglichen werden koennen.
|
||||||
|
|
||||||
|
Ausfuehrung:
|
||||||
|
- In der Legacy-Datenbank ausfuehren.
|
||||||
|
- Ergebnisse je Query als CSV speichern.
|
||||||
|
- Keine personenbezogenen Exporte ins Repo committen.
|
||||||
|
*/
|
||||||
|
|
||||||
|
DECLARE @CurrentYear int = YEAR(GETDATE());
|
||||||
|
|
||||||
|
-- 1. Teilnehmer-Basisdaten
|
||||||
|
SELECT
|
||||||
|
m.MitarbeiterID,
|
||||||
|
m.Name,
|
||||||
|
m.Email,
|
||||||
|
m.aktiv,
|
||||||
|
m.admin
|
||||||
|
FROM kl_Mitarbeiter m
|
||||||
|
ORDER BY m.Name;
|
||||||
|
|
||||||
|
-- 2. Saldo je Teilnehmer
|
||||||
|
WITH payments AS (
|
||||||
|
SELECT
|
||||||
|
MitarbeiterID,
|
||||||
|
SUM(Betrag) AS total_payments
|
||||||
|
FROM kl_Einzahlungen
|
||||||
|
GROUP BY MitarbeiterID
|
||||||
|
),
|
||||||
|
consumption AS (
|
||||||
|
SELECT
|
||||||
|
MitarbeiterID,
|
||||||
|
SUM(Kosten) AS total_costs,
|
||||||
|
SUM(AnzahlStriche) AS total_marks
|
||||||
|
FROM kl_Kaffeeverbrauch
|
||||||
|
GROUP BY MitarbeiterID
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
m.MitarbeiterID,
|
||||||
|
m.Name,
|
||||||
|
m.Email,
|
||||||
|
COALESCE(p.total_payments, 0) AS total_payments,
|
||||||
|
COALESCE(c.total_costs, 0) AS total_costs,
|
||||||
|
COALESCE(c.total_marks, 0) AS total_marks,
|
||||||
|
COALESCE(p.total_payments, 0) - COALESCE(c.total_costs, 0) AS balance
|
||||||
|
FROM kl_Mitarbeiter m
|
||||||
|
LEFT JOIN payments p ON p.MitarbeiterID = m.MitarbeiterID
|
||||||
|
LEFT JOIN consumption c ON c.MitarbeiterID = m.MitarbeiterID
|
||||||
|
ORDER BY m.Name;
|
||||||
|
|
||||||
|
-- 3. Jahreswerte je Teilnehmer
|
||||||
|
WITH year_payments AS (
|
||||||
|
SELECT
|
||||||
|
MitarbeiterID,
|
||||||
|
SUM(Betrag) AS year_payments
|
||||||
|
FROM kl_Einzahlungen
|
||||||
|
WHERE YEAR(Datum) = @CurrentYear
|
||||||
|
GROUP BY MitarbeiterID
|
||||||
|
),
|
||||||
|
year_consumption AS (
|
||||||
|
SELECT
|
||||||
|
MitarbeiterID,
|
||||||
|
SUM(Kosten) AS year_costs,
|
||||||
|
SUM(AnzahlStriche) AS year_marks
|
||||||
|
FROM kl_Kaffeeverbrauch
|
||||||
|
WHERE YEAR(Datum) = @CurrentYear
|
||||||
|
GROUP BY MitarbeiterID
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
m.MitarbeiterID,
|
||||||
|
m.Name,
|
||||||
|
m.Email,
|
||||||
|
COALESCE(yp.year_payments, 0) AS year_payments,
|
||||||
|
COALESCE(yc.year_costs, 0) AS year_costs,
|
||||||
|
COALESCE(yc.year_marks, 0) AS year_marks
|
||||||
|
FROM kl_Mitarbeiter m
|
||||||
|
LEFT JOIN year_payments yp ON yp.MitarbeiterID = m.MitarbeiterID
|
||||||
|
LEFT JOIN year_consumption yc ON yc.MitarbeiterID = m.MitarbeiterID
|
||||||
|
ORDER BY m.Name;
|
||||||
|
|
||||||
|
-- 4. Letzte Einzahlungen je Teilnehmer
|
||||||
|
SELECT
|
||||||
|
e.EinzahlungsID,
|
||||||
|
e.MitarbeiterID,
|
||||||
|
m.Name,
|
||||||
|
m.Email,
|
||||||
|
e.Betrag,
|
||||||
|
e.Datum
|
||||||
|
FROM kl_Einzahlungen e
|
||||||
|
JOIN kl_Mitarbeiter m ON m.MitarbeiterID = e.MitarbeiterID
|
||||||
|
ORDER BY e.Datum DESC, e.EinzahlungsID DESC;
|
||||||
|
|
||||||
|
-- 5. Letzte Strich-/Verbrauchseintraege je Teilnehmer
|
||||||
|
SELECT
|
||||||
|
v.VerbrauchID,
|
||||||
|
v.MitarbeiterID,
|
||||||
|
m.Name,
|
||||||
|
m.Email,
|
||||||
|
v.AnzahlStriche,
|
||||||
|
v.Kosten,
|
||||||
|
v.KostenproStrich,
|
||||||
|
v.Datum,
|
||||||
|
v.Eintragsart
|
||||||
|
FROM kl_Kaffeeverbrauch v
|
||||||
|
JOIN kl_Mitarbeiter m ON m.MitarbeiterID = v.MitarbeiterID
|
||||||
|
ORDER BY v.Datum DESC, v.VerbrauchID DESC;
|
||||||
|
|
||||||
|
-- 6. Aktuelle Konfiguration
|
||||||
|
SELECT
|
||||||
|
*
|
||||||
|
FROM kl_config;
|
||||||
|
|
||||||
|
-- 7. Aktuelle und historische Hinweise
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
nachricht,
|
||||||
|
gueltig_bis
|
||||||
|
FROM kl_hinweise
|
||||||
|
ORDER BY gueltig_bis DESC;
|
||||||
|
|
||||||
|
-- 8. 100-Tage-Vorderseite analog Legacy-Stricherfassung
|
||||||
|
DECLARE @ReferenceDate date = (
|
||||||
|
SELECT MAX(CAST(Datum AS date))
|
||||||
|
FROM kl_Kaffeeverbrauch
|
||||||
|
WHERE Datum < CAST(GETDATE() AS date)
|
||||||
|
);
|
||||||
|
|
||||||
|
SELECT
|
||||||
|
m.MitarbeiterID,
|
||||||
|
m.Name,
|
||||||
|
m.Email,
|
||||||
|
SUM(v.AnzahlStriche) AS marks_in_window
|
||||||
|
FROM kl_Mitarbeiter m
|
||||||
|
JOIN kl_Kaffeeverbrauch v ON m.MitarbeiterID = v.MitarbeiterID
|
||||||
|
WHERE v.Datum >= DATEADD(DAY, -100, @ReferenceDate)
|
||||||
|
AND m.aktiv = 1
|
||||||
|
GROUP BY m.MitarbeiterID, m.Name, m.Email
|
||||||
|
HAVING SUM(v.AnzahlStriche) >= 10
|
||||||
|
ORDER BY m.Name;
|
||||||
|
|
||||||
|
-- 9. 100-Tage-Rueckseite analog Legacy-Stricherfassung
|
||||||
|
SELECT
|
||||||
|
m.MitarbeiterID,
|
||||||
|
m.Name,
|
||||||
|
m.Email,
|
||||||
|
COALESCE(SUM(v.AnzahlStriche), 0) AS marks_in_window
|
||||||
|
FROM kl_Mitarbeiter m
|
||||||
|
LEFT JOIN kl_Kaffeeverbrauch v
|
||||||
|
ON m.MitarbeiterID = v.MitarbeiterID
|
||||||
|
AND v.Datum >= DATEADD(DAY, -100, @ReferenceDate)
|
||||||
|
WHERE m.aktiv = 1
|
||||||
|
GROUP BY m.MitarbeiterID, m.Name, m.Email
|
||||||
|
HAVING COALESCE(SUM(v.AnzahlStriche), 0) < 10
|
||||||
|
ORDER BY m.Name;
|
||||||
|
|
||||||
|
-- 10. CSV-Import-Dublettenbasis
|
||||||
|
SELECT
|
||||||
|
e.MitarbeiterID,
|
||||||
|
m.Name,
|
||||||
|
e.Betrag,
|
||||||
|
CONVERT(varchar, e.Datum, 23) AS booking_day,
|
||||||
|
COUNT(*) AS duplicates_per_day
|
||||||
|
FROM kl_Einzahlungen e
|
||||||
|
JOIN kl_Mitarbeiter m ON m.MitarbeiterID = e.MitarbeiterID
|
||||||
|
GROUP BY e.MitarbeiterID, m.Name, e.Betrag, CONVERT(varchar, e.Datum, 23)
|
||||||
|
HAVING COUNT(*) > 1
|
||||||
|
ORDER BY booking_day DESC, m.Name;
|
||||||
|
|
||||||
|
-- 11. Jahresauswertung: Gesamtstriche aktuelles Jahr
|
||||||
|
SELECT
|
||||||
|
m.MitarbeiterID,
|
||||||
|
m.Name,
|
||||||
|
m.Email,
|
||||||
|
SUM(v.AnzahlStriche) AS GesamtStriche
|
||||||
|
FROM kl_Kaffeeverbrauch v
|
||||||
|
JOIN kl_Mitarbeiter m ON v.MitarbeiterID = m.MitarbeiterID
|
||||||
|
WHERE YEAR(v.Datum) = @CurrentYear
|
||||||
|
AND m.aktiv = 1
|
||||||
|
GROUP BY m.MitarbeiterID, m.Name, m.Email
|
||||||
|
ORDER BY GesamtStriche DESC, m.Name;
|
||||||
|
|
||||||
@@ -0,0 +1,152 @@
|
|||||||
|
/*
|
||||||
|
M0 SQL Server Schema Export
|
||||||
|
|
||||||
|
Ziel:
|
||||||
|
- Tabellen, Spalten, Defaults, Primary Keys, Foreign Keys, Indizes und Rowcounts
|
||||||
|
aus der Legacy-Datenbank exportieren.
|
||||||
|
|
||||||
|
Hinweise:
|
||||||
|
- In der Ziel-Datenbank ausfuehren.
|
||||||
|
- Ergebnisse als CSV/Excel speichern und nicht mit Produktiv-Secrets ins Repo
|
||||||
|
legen.
|
||||||
|
- Falls nur Kaffeelisten-Tabellen exportiert werden sollen, den WHERE-Block bei
|
||||||
|
Bedarf auf kl_% und CoffeeSurvey% begrenzen.
|
||||||
|
*/
|
||||||
|
|
||||||
|
-- 1. Tabellen und Spalten
|
||||||
|
SELECT
|
||||||
|
s.name AS schema_name,
|
||||||
|
t.name AS table_name,
|
||||||
|
c.column_id,
|
||||||
|
c.name AS column_name,
|
||||||
|
ty.name AS data_type,
|
||||||
|
c.max_length,
|
||||||
|
c.precision,
|
||||||
|
c.scale,
|
||||||
|
c.is_nullable,
|
||||||
|
c.is_identity,
|
||||||
|
dc.definition AS default_definition,
|
||||||
|
ep.value AS column_description
|
||||||
|
FROM sys.tables t
|
||||||
|
JOIN sys.schemas s ON s.schema_id = t.schema_id
|
||||||
|
JOIN sys.columns c ON c.object_id = t.object_id
|
||||||
|
JOIN sys.types ty ON ty.user_type_id = c.user_type_id
|
||||||
|
LEFT JOIN sys.default_constraints dc
|
||||||
|
ON dc.parent_object_id = t.object_id
|
||||||
|
AND dc.parent_column_id = c.column_id
|
||||||
|
LEFT JOIN sys.extended_properties ep
|
||||||
|
ON ep.major_id = t.object_id
|
||||||
|
AND ep.minor_id = c.column_id
|
||||||
|
AND ep.name = 'MS_Description'
|
||||||
|
WHERE t.is_ms_shipped = 0
|
||||||
|
ORDER BY s.name, t.name, c.column_id;
|
||||||
|
|
||||||
|
-- 2. Primary Keys und Unique Constraints
|
||||||
|
SELECT
|
||||||
|
s.name AS schema_name,
|
||||||
|
t.name AS table_name,
|
||||||
|
kc.name AS constraint_name,
|
||||||
|
kc.type_desc,
|
||||||
|
ic.key_ordinal,
|
||||||
|
c.name AS column_name
|
||||||
|
FROM sys.key_constraints kc
|
||||||
|
JOIN sys.tables t ON t.object_id = kc.parent_object_id
|
||||||
|
JOIN sys.schemas s ON s.schema_id = t.schema_id
|
||||||
|
JOIN sys.index_columns ic
|
||||||
|
ON ic.object_id = kc.parent_object_id
|
||||||
|
AND ic.index_id = kc.unique_index_id
|
||||||
|
JOIN sys.columns c
|
||||||
|
ON c.object_id = ic.object_id
|
||||||
|
AND c.column_id = ic.column_id
|
||||||
|
ORDER BY s.name, t.name, kc.name, ic.key_ordinal;
|
||||||
|
|
||||||
|
-- 3. Foreign Keys
|
||||||
|
SELECT
|
||||||
|
sch_parent.name AS parent_schema,
|
||||||
|
parent_t.name AS parent_table,
|
||||||
|
parent_c.name AS parent_column,
|
||||||
|
fk.name AS foreign_key_name,
|
||||||
|
sch_ref.name AS referenced_schema,
|
||||||
|
ref_t.name AS referenced_table,
|
||||||
|
ref_c.name AS referenced_column,
|
||||||
|
fk.delete_referential_action_desc,
|
||||||
|
fk.update_referential_action_desc
|
||||||
|
FROM sys.foreign_keys fk
|
||||||
|
JOIN sys.foreign_key_columns fkc ON fkc.constraint_object_id = fk.object_id
|
||||||
|
JOIN sys.tables parent_t ON parent_t.object_id = fkc.parent_object_id
|
||||||
|
JOIN sys.schemas sch_parent ON sch_parent.schema_id = parent_t.schema_id
|
||||||
|
JOIN sys.columns parent_c
|
||||||
|
ON parent_c.object_id = fkc.parent_object_id
|
||||||
|
AND parent_c.column_id = fkc.parent_column_id
|
||||||
|
JOIN sys.tables ref_t ON ref_t.object_id = fkc.referenced_object_id
|
||||||
|
JOIN sys.schemas sch_ref ON sch_ref.schema_id = ref_t.schema_id
|
||||||
|
JOIN sys.columns ref_c
|
||||||
|
ON ref_c.object_id = fkc.referenced_object_id
|
||||||
|
AND ref_c.column_id = fkc.referenced_column_id
|
||||||
|
ORDER BY parent_schema, parent_table, foreign_key_name;
|
||||||
|
|
||||||
|
-- 4. Indizes
|
||||||
|
SELECT
|
||||||
|
s.name AS schema_name,
|
||||||
|
t.name AS table_name,
|
||||||
|
i.name AS index_name,
|
||||||
|
i.type_desc,
|
||||||
|
i.is_unique,
|
||||||
|
i.is_primary_key,
|
||||||
|
i.is_unique_constraint,
|
||||||
|
ic.key_ordinal,
|
||||||
|
ic.is_included_column,
|
||||||
|
c.name AS column_name
|
||||||
|
FROM sys.indexes i
|
||||||
|
JOIN sys.tables t ON t.object_id = i.object_id
|
||||||
|
JOIN sys.schemas s ON s.schema_id = t.schema_id
|
||||||
|
JOIN sys.index_columns ic
|
||||||
|
ON ic.object_id = i.object_id
|
||||||
|
AND ic.index_id = i.index_id
|
||||||
|
JOIN sys.columns c
|
||||||
|
ON c.object_id = ic.object_id
|
||||||
|
AND c.column_id = ic.column_id
|
||||||
|
WHERE t.is_ms_shipped = 0
|
||||||
|
AND i.name IS NOT NULL
|
||||||
|
ORDER BY s.name, t.name, i.name, ic.key_ordinal, ic.index_column_id;
|
||||||
|
|
||||||
|
-- 5. Rowcounts
|
||||||
|
SELECT
|
||||||
|
s.name AS schema_name,
|
||||||
|
t.name AS table_name,
|
||||||
|
SUM(p.row_count) AS row_count
|
||||||
|
FROM sys.tables t
|
||||||
|
JOIN sys.schemas s ON s.schema_id = t.schema_id
|
||||||
|
JOIN sys.dm_db_partition_stats p ON p.object_id = t.object_id
|
||||||
|
WHERE t.is_ms_shipped = 0
|
||||||
|
AND p.index_id IN (0, 1)
|
||||||
|
GROUP BY s.name, t.name
|
||||||
|
ORDER BY s.name, t.name;
|
||||||
|
|
||||||
|
-- 6. Trigger
|
||||||
|
SELECT
|
||||||
|
s.name AS schema_name,
|
||||||
|
t.name AS table_name,
|
||||||
|
tr.name AS trigger_name,
|
||||||
|
tr.is_disabled,
|
||||||
|
OBJECT_DEFINITION(tr.object_id) AS trigger_definition
|
||||||
|
FROM sys.triggers tr
|
||||||
|
JOIN sys.tables t ON t.object_id = tr.parent_id
|
||||||
|
JOIN sys.schemas s ON s.schema_id = t.schema_id
|
||||||
|
ORDER BY s.name, t.name, tr.name;
|
||||||
|
|
||||||
|
-- 7. Stored Procedures und Funktionen mit Kaffeelisten-Bezug
|
||||||
|
SELECT
|
||||||
|
s.name AS schema_name,
|
||||||
|
o.name AS object_name,
|
||||||
|
o.type_desc,
|
||||||
|
OBJECT_DEFINITION(o.object_id) AS object_definition
|
||||||
|
FROM sys.objects o
|
||||||
|
JOIN sys.schemas s ON s.schema_id = o.schema_id
|
||||||
|
WHERE o.type IN ('P', 'FN', 'IF', 'TF')
|
||||||
|
AND (
|
||||||
|
OBJECT_DEFINITION(o.object_id) LIKE '%kl_%'
|
||||||
|
OR OBJECT_DEFINITION(o.object_id) LIKE '%CoffeeSurvey%'
|
||||||
|
)
|
||||||
|
ORDER BY s.name, o.name;
|
||||||
|
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
# M0 Screenshot- und Designreferenz
|
||||||
|
|
||||||
|
Stand: 2026-07-11
|
||||||
|
|
||||||
|
Diese Checkliste friert die bestehende Optik ein, damit die SaaS-App vertraut
|
||||||
|
bleibt. Wenn die Legacy-App lokal nicht laeuft, koennen Screenshots aus der
|
||||||
|
aktuellen Umgebung geliefert werden.
|
||||||
|
|
||||||
|
## Allgemeine Vorgaben
|
||||||
|
|
||||||
|
- Je Seite Desktop mit ca. 1440 px Breite aufnehmen.
|
||||||
|
- Je Kernseite mobile Ansicht mit ca. 390 px Breite aufnehmen.
|
||||||
|
- Je relevanter Rolle aufnehmen: normales Mitglied und Admin.
|
||||||
|
- Keine echten personenbezogenen Daten veroeffentlichen.
|
||||||
|
- Dateinamen nach Muster `m0-<rolle>-<seite>-<viewport>.png`.
|
||||||
|
|
||||||
|
## Kernseiten
|
||||||
|
|
||||||
|
| Prioritaet | Seite | Rolle | Zweck |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| Hoch | `index.php` | Mitglied | Dashboard, Saldo, Jahreswerte, eigene Striche, PayPal |
|
||||||
|
| Hoch | `index.php` | Admin | Dashboard mit Admin-Sidebar |
|
||||||
|
| Hoch | `kaffeeliste.php` | Admin | Gesamtuebersicht und Aktionsleiste |
|
||||||
|
| Hoch | `stricheintragen.php` | Admin | Sammelerfassung Striche |
|
||||||
|
| Hoch | `einzahlung.php` | Admin | Sammelerfassung Einzahlungen |
|
||||||
|
| Hoch | `mitarbeiterverwalten.php` | Admin | Mitgliederliste und Formular |
|
||||||
|
| Hoch | `letzteneintraege.php` | Admin | Korrekturansicht mit Loeschaktionen |
|
||||||
|
| Mittel | `csvupload.php` | Admin | CSV-Upload-Formular und Ergebniszustand |
|
||||||
|
| Mittel | `hinweise.php` | Admin | Hinweise anlegen und Liste |
|
||||||
|
| Mittel | `faq.php` | Mitglied | FAQ-Inhalte und Typografie |
|
||||||
|
| Mittel | `namenanpassen.php` | Mitglied | Formular fuer Anzeigenamen |
|
||||||
|
| Niedrig | `teilnehmerauswertung.php` | Admin | Detailauswertung pro Teilnehmer |
|
||||||
|
| Niedrig | `mailausgebe.php` | Admin | Mail-/Adressausgabe |
|
||||||
|
| Niedrig | `umfrage.php` | Mitglied | Randmodul, falls SaaS-relevant |
|
||||||
|
| Niedrig | `umfrageergebnisse.php` | Admin | Randmodul, falls SaaS-relevant |
|
||||||
|
|
||||||
|
## Zustaende
|
||||||
|
|
||||||
|
Diese UI-Zustaende sollten, wenn moeglich, separat gesichert werden:
|
||||||
|
|
||||||
|
- Positiver Kontostand.
|
||||||
|
- Negativer Kontostand.
|
||||||
|
- Nullsaldo.
|
||||||
|
- Nutzer ohne Zugriff.
|
||||||
|
- Erfolgreiche Stricherfassung.
|
||||||
|
- Erfolgreiche Einzahlung.
|
||||||
|
- CSV-Import mit Erfolg, Dublette und unbekanntem Nutzer.
|
||||||
|
- Hinweisbanner aktiv.
|
||||||
|
- Leere Tabellen oder keine Daten.
|
||||||
|
- Mobile Sidebar geoeffnet und geschlossen.
|
||||||
|
|
||||||
|
## Designmerkmale, die erhalten bleiben sollen
|
||||||
|
|
||||||
|
- Gruener Akzent fuer Links, Buttons und Linien.
|
||||||
|
- Roboto-Slab-Ueberschriften.
|
||||||
|
- Open-Sans-Fliesstext.
|
||||||
|
- Weisse Hauptflaeche.
|
||||||
|
- Graue Sidebar in der App.
|
||||||
|
- Tabellen als zentrale Datenansicht.
|
||||||
|
- Schlichte Formularfelder und Buttons.
|
||||||
|
- Ruhige, arbeitsorientierte Darstellung.
|
||||||
|
|
||||||
|
## Ablage
|
||||||
|
|
||||||
|
Empfohlene lokale Ablage, falls Screenshots ins Repo sollen:
|
||||||
|
|
||||||
|
```text
|
||||||
|
docs/m0/screenshots/
|
||||||
|
```
|
||||||
|
|
||||||
|
Vor dem Commit sollten Screenshots anonymisiert sein. Wenn echte Namen,
|
||||||
|
E-Mails, PayPal-Daten oder Salden sichtbar sind, besser ausserhalb des Repos
|
||||||
|
ablegen und nur als Referenz verwenden.
|
||||||
|
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
# M0 Sicherheitsbaseline
|
||||||
|
|
||||||
|
Stand: 2026-07-11
|
||||||
|
|
||||||
|
Diese Baseline ist eine statische Erstbewertung. Sie ersetzt kein Penetration
|
||||||
|
Testing, markiert aber die wichtigsten Risiken fuer die SaaS-Umstrukturierung.
|
||||||
|
|
||||||
|
## Kritische Sofortthemen
|
||||||
|
|
||||||
|
| Risiko | Quelle | Auswirkung | Empfehlung |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| Hart codierte DB-Zugangsdaten | `jahresauswertung.php` Zeilen 4-8 | Secret-Leak, direkte Produktiv-DB-Gefahr | Zugangsdaten rotieren, Skript deaktivieren oder auf Env-Konfiguration umstellen |
|
||||||
|
| Schreibende Seiten ohne eigene Rollenpruefung | `stricheintragen.php`, `einzahlung.php`, `mailversenden.php`, `exportKaffeeliste.php` | Direkter URL-Aufruf kann Aktionen erlauben | Jede Seite serverseitig mit `requireRole` absichern |
|
||||||
|
| Keine CSRF-Token | nahezu alle POST-/Delete-Formulare | Ungewollte Buchungen, Loeschungen, Imports | CSRF fuer alle schreibenden Aktionen |
|
||||||
|
| Harte Deletes fuer Buchungen | `letzteneintraege.php` | Audit-Historie und Revisionsfaehigkeit gehen verloren | Storno-/Reversal-Modell statt Delete |
|
||||||
|
| Upload in Webroot | `csvupload.php` Zeilen 131-138 | Dateiablage kann missbraucht werden | Upload ausserhalb Webroot, Dateityp/MIME/Name pruefen |
|
||||||
|
| Kein Tenant-Scope | alle fachlichen Queries | Zentrales SaaS-Leak-Risiko | `tenant_id` verpflichtend und Query-Schicht testen |
|
||||||
|
|
||||||
|
## Weitere Befunde
|
||||||
|
|
||||||
|
| Befund | Quelle | Empfehlung |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| Direkte SQL-String-Verkettung mit Mailadresse | `functions.php` Zeilen 14, 25, 39, 55 | Parameterisierte Queries |
|
||||||
|
| LDAP-Filter ohne Escaping | `functionsLDAP.php` LDAP-Suchen nach `samaccountname` | LDAP-Filter escapen |
|
||||||
|
| Datumsformat `Y-d-m H:i:s` | `index.php`, `stricheintragen.php`, `einzahlung.php`, `hinweise.php` | ISO/DB-kompatibel `Y-m-d H:i:s` oder DB-Zeit verwenden |
|
||||||
|
| Ausgabe von Namen/E-Mails teils unescaped | mehrere Tabellen, z.B. Mitgliederverwaltung | `htmlspecialchars` zentral erzwingen |
|
||||||
|
| Fehlerausgabe mit `sqlsrv_errors()` an Nutzer | mehrere Dateien | Logging intern, neutrale Fehlermeldung extern |
|
||||||
|
| CSV-Mitarbeitersuche mit mutmasslich falscher Parameteranzahl | `csvupload.php` `getMitarbeiterID` | Query pruefen und mit Tests abdecken |
|
||||||
|
| Basis-Auth-Beispiel mit Platzhalter-Passwort | `umfrageergebnisse.php` Kommentarblock | Entfernen oder echte Auth-Middleware nutzen |
|
||||||
|
| App-Navigation ist in `footer.php` | Layoutstruktur | Trennung in App-Shell und Public-Shell |
|
||||||
|
| `headerline.php` enthaelt NUL-Zeichen | `headerline.php` | Datei pruefen/entfernen, wenn ungenutzt |
|
||||||
|
| Massenmail ohne Versandlog | `mailversenden.php`, `jahresauswertung.php` | Job-Modell mit Dry-Run, Audit, Rate-Limit |
|
||||||
|
|
||||||
|
## Rollen- und Zugriffsrisiken
|
||||||
|
|
||||||
|
Aktuell gilt:
|
||||||
|
|
||||||
|
- Sichtbare Admin-Navigation wird in `footer.php` per `checkKaffeelisteAdmin`
|
||||||
|
gesteuert.
|
||||||
|
- Einige Admin-Zielseiten haben eigene Checks.
|
||||||
|
- Einige schreibende oder sensible Dateien verlassen sich nicht durchgehend auf
|
||||||
|
eine eigene Rollenpruefung.
|
||||||
|
|
||||||
|
Fuer SaaS gilt:
|
||||||
|
|
||||||
|
- Menue-Ausblendung ist keine Berechtigungspruefung.
|
||||||
|
- Jede Route braucht serverseitige Auth- und Rollenpruefung.
|
||||||
|
- Jede fachliche Route braucht Tenant-Kontext.
|
||||||
|
- IDs aus Requests muessen zum aktuellen Tenant gehoeren.
|
||||||
|
|
||||||
|
## Empfohlene Reihenfolge fuer Sicherheitsarbeit
|
||||||
|
|
||||||
|
1. Secrets rotieren und aus dem Code entfernen.
|
||||||
|
2. Legacy-Schreibseiten bis zum Umbau hinter explizite Admin-Pruefung setzen.
|
||||||
|
3. CSRF-Schutz in der neuen App-Basis einplanen.
|
||||||
|
4. Finanzdaten im Zielmodell nur noch stornieren, nicht loeschen.
|
||||||
|
5. Uploads ausserhalb des Webroots modellieren.
|
||||||
|
6. Einheitliche Escape-/View-Helfer einfuehren.
|
||||||
|
7. Tenant-Isolation mit Tests gegen zwei Tenants absichern.
|
||||||
|
|
||||||
|
## Nicht im Repo speichern
|
||||||
|
|
||||||
|
- Produktivpasswoerter.
|
||||||
|
- Echte Nutzerlisten.
|
||||||
|
- Bank-/PayPal-Exportdaten mit Personenbezug.
|
||||||
|
- AD-/LDAP-Service-Account-Daten.
|
||||||
|
- Vollstaendige Produktiv-Dumps.
|
||||||
@@ -296,6 +296,15 @@ Uebersicht:
|
|||||||
Ziel:
|
Ziel:
|
||||||
Den aktuellen Zustand verlaesslich dokumentieren, bevor umgebaut wird.
|
Den aktuellen Zustand verlaesslich dokumentieren, bevor umgebaut wird.
|
||||||
|
|
||||||
|
Arbeitsartefakte:
|
||||||
|
|
||||||
|
- `docs/m0/README.md`
|
||||||
|
- `docs/m0/code-inventory.md`
|
||||||
|
- `docs/m0/security-baseline.md`
|
||||||
|
- `docs/m0/schema-export.sql`
|
||||||
|
- `docs/m0/golden-master-queries.sql`
|
||||||
|
- `docs/m0/screenshot-checklist.md`
|
||||||
|
|
||||||
Schritte:
|
Schritte:
|
||||||
|
|
||||||
- Live-DB-Schema exportieren.
|
- Live-DB-Schema exportieren.
|
||||||
|
|||||||
+2
-2
@@ -28,7 +28,7 @@ if ($_SERVER["REQUEST_METHOD"] == "POST" ) {
|
|||||||
$anzahlBetrag = floatval($anzahlBetrag);
|
$anzahlBetrag = floatval($anzahlBetrag);
|
||||||
|
|
||||||
if($anzahlBetrag != 0){
|
if($anzahlBetrag != 0){
|
||||||
$datum = date("Y-d-m H:i:s"); // Das aktuelle Datum verwenden
|
$datum = date("Y-m-d H:i:s"); // Das aktuelle Datum verwenden
|
||||||
|
|
||||||
// SQL-Abfrage zum Einfügen der Daten
|
// SQL-Abfrage zum Einfügen der Daten
|
||||||
$sql = "INSERT INTO kl_Einzahlungen (MitarbeiterID, Betrag, Datum) VALUES (?, ?, ?)";
|
$sql = "INSERT INTO kl_Einzahlungen (MitarbeiterID, Betrag, Datum) VALUES (?, ?, ?)";
|
||||||
@@ -149,4 +149,4 @@ $stmtMitarbeiter = sqlsrv_query($conn, $sqlMitarbeiter);
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<?php include "footer.php"; ?>
|
<?php include "footer.php"; ?>
|
||||||
|
|||||||
+12
-6
@@ -2,12 +2,18 @@
|
|||||||
|
|
||||||
include_once "config.php";
|
include_once "config.php";
|
||||||
|
|
||||||
$kennungtemp = $_SERVER['AUTH_USER'];
|
$devAuthEmail = getenv('DEV_AUTH_EMAIL') ?: '';
|
||||||
$teile = explode("\\", $kennungtemp);
|
if ($devAuthEmail !== '') {
|
||||||
$kennung = $teile[1];
|
$mailadress = strtolower(trim($devAuthEmail));
|
||||||
|
$kennung = $mailadress;
|
||||||
|
} else {
|
||||||
|
$kennungtemp = $_SERVER['AUTH_USER'];
|
||||||
|
$teile = explode("\\", $kennungtemp);
|
||||||
|
$kennung = $teile[1];
|
||||||
|
|
||||||
|
|
||||||
include_once "functionsLDAP.php";
|
include_once "functionsLDAP.php";
|
||||||
|
}
|
||||||
|
|
||||||
function getUserName($conn, $mail){
|
function getUserName($conn, $mail){
|
||||||
|
|
||||||
@@ -66,4 +72,4 @@ function checkKaffeelisteAdmin($conn, $mail)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
?>
|
?>
|
||||||
|
|||||||
Binary file not shown.
+2
-2
@@ -35,7 +35,7 @@ if(checkKaffeelisteAdmin($conn, $mailadress)){
|
|||||||
$dt = DateTime::createFromFormat('Y-m-d\TH:i', $gueltig_bis);
|
$dt = DateTime::createFromFormat('Y-m-d\TH:i', $gueltig_bis);
|
||||||
|
|
||||||
if ($dt) {
|
if ($dt) {
|
||||||
$gueltig_bis_sql = $dt->format('Y-d-m H:i:s'); // z.B. "2025-03-09 14:00:00"
|
$gueltig_bis_sql = $dt->format('Y-m-d H:i:s'); // z.B. "2025-09-03 14:00:00"
|
||||||
} else {
|
} else {
|
||||||
die("Ungültiges Datumsformat");
|
die("Ungültiges Datumsformat");
|
||||||
}
|
}
|
||||||
@@ -92,4 +92,4 @@ if(checkKaffeelisteAdmin($conn, $mailadress)){
|
|||||||
|
|
||||||
<?php include "footer.php";
|
<?php include "footer.php";
|
||||||
|
|
||||||
?>
|
?>
|
||||||
|
|||||||
@@ -166,7 +166,7 @@ if(checkKaffeelisteAccess($conn, $mailadress)){
|
|||||||
$anzahlStriche = (int)$anzahlStriche;
|
$anzahlStriche = (int)$anzahlStriche;
|
||||||
$kostenproStrich = floatval($KostenproStrichtemp);
|
$kostenproStrich = floatval($KostenproStrichtemp);
|
||||||
$kosten = floatval($anzahlStriche * $KostenproStrichtemp);
|
$kosten = floatval($anzahlStriche * $KostenproStrichtemp);
|
||||||
$datum = date("Y-d-m H:i:s"); // Das aktuelle Datum verwenden
|
$datum = date("Y-m-d H:i:s"); // Das aktuelle Datum verwenden
|
||||||
if($anzahlStriche != 0){
|
if($anzahlStriche != 0){
|
||||||
// SQL-Abfrage zum Einfügen der Daten
|
// SQL-Abfrage zum Einfügen der Daten
|
||||||
$sql = "INSERT INTO kl_Kaffeeverbrauch (MitarbeiterID, AnzahlStriche, Kosten, KostenproStrich, Datum, Eintragsart) VALUES (?, ?, ?, ?, ?, 2)";
|
$sql = "INSERT INTO kl_Kaffeeverbrauch (MitarbeiterID, AnzahlStriche, Kosten, KostenproStrich, Datum, Eintragsart) VALUES (?, ?, ?, ?, ?, 2)";
|
||||||
@@ -238,4 +238,4 @@ if(checkKaffeelisteAccess($conn, $mailadress)){
|
|||||||
|
|
||||||
<?php include "footer.php";
|
<?php include "footer.php";
|
||||||
|
|
||||||
?>
|
?>
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
<?php
|
<?php
|
||||||
// Einstellungen
|
// Einstellungen
|
||||||
// Verbindung zur Datenbank herstellen (ersetze die Platzhalter durch deine Daten)
|
// Verbindung zur Datenbank herstellen (ersetze die Platzhalter durch deine Daten)
|
||||||
$serverName = "sql-nds-autoclient-prod.aoknds.aok";
|
$serverName = "server";
|
||||||
$connectionOptions = array(
|
$connectionOptions = array(
|
||||||
"Database" => "Automatisierungsclient",
|
"Database" => "database",
|
||||||
"Uid" => "ac_admin",
|
"Uid" => "user",
|
||||||
"PWD" => "allacc3ssPW",
|
"PWD" => "password",
|
||||||
"TrustServerCertificate"=>true
|
"TrustServerCertificate"=>true
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,245 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
if (!defined('SQLSRV_FETCH_ASSOC')) {
|
||||||
|
define('SQLSRV_FETCH_ASSOC', 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!defined('SQLSRV_FETCH_NUMERIC')) {
|
||||||
|
define('SQLSRV_FETCH_NUMERIC', 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
final class MysqlSqlsrvConnection
|
||||||
|
{
|
||||||
|
public PDO $pdo;
|
||||||
|
public ?array $lastError = null;
|
||||||
|
|
||||||
|
public function __construct(PDO $pdo)
|
||||||
|
{
|
||||||
|
$this->pdo = $pdo;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final class MysqlSqlsrvStatement
|
||||||
|
{
|
||||||
|
/** @var array<int, array<string, mixed>> */
|
||||||
|
public array $rows;
|
||||||
|
public int $index = 0;
|
||||||
|
public int $affectedRows = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<int, array<string, mixed>> $rows
|
||||||
|
*/
|
||||||
|
public function __construct(array $rows = [], int $affectedRows = 0)
|
||||||
|
{
|
||||||
|
$this->rows = $rows;
|
||||||
|
$this->affectedRows = $affectedRows;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function sqlsrv_connect($serverName, array $connectionOptions = [])
|
||||||
|
{
|
||||||
|
$database = getenv('DB_NAME') ?: ($connectionOptions['Database'] ?? '');
|
||||||
|
$user = getenv('DB_USER') ?: ($connectionOptions['Uid'] ?? '');
|
||||||
|
$password = getenv('DB_PASS') ?: ($connectionOptions['PWD'] ?? '');
|
||||||
|
$host = getenv('DB_HOST') ?: (string)$serverName;
|
||||||
|
$port = getenv('DB_PORT') ?: '3306';
|
||||||
|
|
||||||
|
try {
|
||||||
|
$dsn = sprintf('mysql:host=%s;port=%s;dbname=%s;charset=utf8mb4', $host, $port, $database);
|
||||||
|
$pdo = new PDO($dsn, $user, $password, [
|
||||||
|
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||||
|
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||||
|
PDO::ATTR_EMULATE_PREPARES => true,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return new MysqlSqlsrvConnection($pdo);
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
$GLOBALS['__sqlsrv_mysql_last_error'] = [[
|
||||||
|
'SQLSTATE' => $e->getCode(),
|
||||||
|
'message' => $e->getMessage(),
|
||||||
|
]];
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function sqlsrv_query($conn, string $sql, array $params = [], array $options = [])
|
||||||
|
{
|
||||||
|
if (!$conn instanceof MysqlSqlsrvConnection) {
|
||||||
|
$GLOBALS['__sqlsrv_mysql_last_error'] = [[
|
||||||
|
'SQLSTATE' => 'HY000',
|
||||||
|
'message' => 'Invalid MySQL compatibility connection.',
|
||||||
|
]];
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$translatedSql = sqlsrv_mysql_translate_query($sql);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$stmt = $conn->pdo->prepare($translatedSql);
|
||||||
|
$stmt->execute(sqlsrv_mysql_normalize_params($translatedSql, $params));
|
||||||
|
|
||||||
|
if ($stmt->columnCount() > 0) {
|
||||||
|
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
foreach ($rows as &$row) {
|
||||||
|
$row = sqlsrv_mysql_normalize_row($row);
|
||||||
|
}
|
||||||
|
unset($row);
|
||||||
|
|
||||||
|
return new MysqlSqlsrvStatement($rows, $stmt->rowCount());
|
||||||
|
}
|
||||||
|
|
||||||
|
return new MysqlSqlsrvStatement([], $stmt->rowCount());
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
$conn->lastError = [[
|
||||||
|
'SQLSTATE' => $e->getCode(),
|
||||||
|
'message' => $e->getMessage(),
|
||||||
|
'sql' => $translatedSql,
|
||||||
|
]];
|
||||||
|
$GLOBALS['__sqlsrv_mysql_last_error'] = $conn->lastError;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function sqlsrv_fetch_array($stmt, int $fetchType = SQLSRV_FETCH_ASSOC)
|
||||||
|
{
|
||||||
|
if (!$stmt instanceof MysqlSqlsrvStatement) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!array_key_exists($stmt->index, $stmt->rows)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$row = $stmt->rows[$stmt->index++];
|
||||||
|
|
||||||
|
if ($fetchType === SQLSRV_FETCH_NUMERIC) {
|
||||||
|
return array_values($row);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $row;
|
||||||
|
}
|
||||||
|
|
||||||
|
function sqlsrv_num_rows($stmt): int
|
||||||
|
{
|
||||||
|
if (!$stmt instanceof MysqlSqlsrvStatement) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return count($stmt->rows);
|
||||||
|
}
|
||||||
|
|
||||||
|
function sqlsrv_errors()
|
||||||
|
{
|
||||||
|
return $GLOBALS['__sqlsrv_mysql_last_error'] ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function sqlsrv_close($conn): bool
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function sqlsrv_begin_transaction($conn): bool
|
||||||
|
{
|
||||||
|
return $conn instanceof MysqlSqlsrvConnection && $conn->pdo->beginTransaction();
|
||||||
|
}
|
||||||
|
|
||||||
|
function sqlsrv_commit($conn): bool
|
||||||
|
{
|
||||||
|
return $conn instanceof MysqlSqlsrvConnection && $conn->pdo->commit();
|
||||||
|
}
|
||||||
|
|
||||||
|
function sqlsrv_rollback($conn): bool
|
||||||
|
{
|
||||||
|
return $conn instanceof MysqlSqlsrvConnection && $conn->pdo->rollBack();
|
||||||
|
}
|
||||||
|
|
||||||
|
function sqlsrv_mysql_translate_query(string $sql): string
|
||||||
|
{
|
||||||
|
$query = str_replace(["\r\n", "\r"], "\n", $sql);
|
||||||
|
$query = preg_replace('/\bdbo\./i', '', $query);
|
||||||
|
$query = preg_replace('/\[([A-Za-z0-9_]+)\]/', '`$1`', $query);
|
||||||
|
|
||||||
|
$query = preg_replace('/FORMAT\(([^,]+),\s*\'yyyy\'\)\s*=\s*FORMAT\(GETDATE\(\),\s*\'yyyy\'\)/i', 'YEAR($1) = YEAR(NOW())', $query);
|
||||||
|
$query = preg_replace('/CAST\(GETDATE\(\)\s+AS\s+DATE\)/i', 'CURDATE()', $query);
|
||||||
|
$query = preg_replace('/GETDATE\(\)/i', 'NOW()', $query);
|
||||||
|
$query = preg_replace('/SYSDATETIME\(\)/i', 'NOW()', $query);
|
||||||
|
$query = preg_replace('/CONVERT\(VARCHAR,\s*([^)]+),\s*23\)/i', 'DATE($1)', $query);
|
||||||
|
|
||||||
|
$query = preg_replace(
|
||||||
|
'/DATEADD\(DAY,\s*(-?\d+),\s*\(SELECT\s+MAX\(Datum\)\s+FROM\s+kl_Kaffeeverbrauch\s+WHERE\s+Datum\s+<\s+CURDATE\(\)\)\)/i',
|
||||||
|
'DATE_ADD((SELECT MAX(Datum) FROM kl_Kaffeeverbrauch WHERE Datum < CURDATE()), INTERVAL $1 DAY)',
|
||||||
|
$query
|
||||||
|
);
|
||||||
|
$query = preg_replace(
|
||||||
|
'/DATEADD\(DAY,\s*(-?\d+),\s*\(SELECT\s+MAX\(Datum\)\s+FROM\s+kl_Kaffeeverbrauch\s*\)\)/i',
|
||||||
|
'DATE_ADD((SELECT MAX(Datum) FROM kl_Kaffeeverbrauch), INTERVAL $1 DAY)',
|
||||||
|
$query
|
||||||
|
);
|
||||||
|
$query = preg_replace('/DATEADD\(DAY,\s*(-?\d+),\s*NOW\(\)\)/i', 'DATE_ADD(NOW(), INTERVAL $1 DAY)', $query);
|
||||||
|
|
||||||
|
$limit = null;
|
||||||
|
$query = preg_replace_callback('/^\s*SELECT\s+TOP\s+\(?\s*(\d+)\s*\)?\s+/i', function (array $matches) use (&$limit): string {
|
||||||
|
$limit = (int)$matches[1];
|
||||||
|
return 'SELECT ';
|
||||||
|
}, $query);
|
||||||
|
|
||||||
|
if ($limit !== null && !preg_match('/\bLIMIT\s+\d+\s*$/i', trim($query))) {
|
||||||
|
$query = rtrim($query, " \t\n\r\0\x0B;") . ' LIMIT ' . $limit;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $query;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PDO is stricter than the SQL Server driver used by the legacy app. Some
|
||||||
|
* legacy calls pass unused parameters, and one CSV lookup passes one value for
|
||||||
|
* two equivalent placeholders. Normalize this only in the dev adapter.
|
||||||
|
*
|
||||||
|
* @param array<int, mixed> $params
|
||||||
|
* @return array<int, mixed>
|
||||||
|
*/
|
||||||
|
function sqlsrv_mysql_normalize_params(string $sql, array $params): array
|
||||||
|
{
|
||||||
|
$normalized = array_values($params);
|
||||||
|
$placeholderCount = substr_count($sql, '?');
|
||||||
|
|
||||||
|
if ($placeholderCount === 0) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count($normalized) > $placeholderCount) {
|
||||||
|
return array_slice($normalized, 0, $placeholderCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count($normalized) < $placeholderCount && count($normalized) > 0) {
|
||||||
|
$last = $normalized[count($normalized) - 1];
|
||||||
|
while (count($normalized) < $placeholderCount) {
|
||||||
|
$normalized[] = $last;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $normalized;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<string, mixed> $row
|
||||||
|
* @return array<string, mixed>
|
||||||
|
*/
|
||||||
|
function sqlsrv_mysql_normalize_row(array $row): array
|
||||||
|
{
|
||||||
|
foreach ($row as $key => $value) {
|
||||||
|
if ($value === null || $value instanceof DateTimeInterface) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_string($value) && preg_match('/(datum|date|gueltig_bis|valid_)/i', (string)$key)) {
|
||||||
|
try {
|
||||||
|
$row[$key] = new DateTime($value);
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
$row[$key] = $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $row;
|
||||||
|
}
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
$required = ['DB_HOST', 'DB_NAME', 'DB_USER', 'DB_PASS', 'DEV_AUTH_EMAIL'];
|
||||||
|
foreach ($required as $name) {
|
||||||
|
if ((getenv($name) ?: '') === '') {
|
||||||
|
fwrite(STDERR, "Missing environment variable: {$name}\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$dsn = sprintf(
|
||||||
|
'mysql:host=%s;port=%s;dbname=%s;charset=utf8mb4',
|
||||||
|
getenv('DB_HOST'),
|
||||||
|
getenv('DB_PORT') ?: '3306',
|
||||||
|
getenv('DB_NAME')
|
||||||
|
);
|
||||||
|
|
||||||
|
$pdo = new PDO($dsn, getenv('DB_USER'), getenv('DB_PASS'), [
|
||||||
|
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||||
|
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$schema = file_get_contents(__DIR__ . '/../database/mysql-dev-schema.sql');
|
||||||
|
if ($schema === false) {
|
||||||
|
fwrite(STDERR, "Could not read schema file.\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
$pdo->exec($schema);
|
||||||
|
|
||||||
|
$email = strtolower(trim((string)getenv('DEV_AUTH_EMAIL')));
|
||||||
|
$name = trim((string)(getenv('DEV_AUTH_NAME') ?: 'Test Admin'));
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare(
|
||||||
|
'INSERT INTO kl_Mitarbeiter (Name, Email, aktiv, admin)
|
||||||
|
VALUES (?, ?, 1, 1)
|
||||||
|
ON DUPLICATE KEY UPDATE Name = VALUES(Name), aktiv = 1, admin = 1'
|
||||||
|
);
|
||||||
|
$stmt->execute([$name, $email]);
|
||||||
|
|
||||||
|
$memberId = (int)$pdo->query('SELECT MitarbeiterID FROM kl_Mitarbeiter WHERE Email = ' . $pdo->quote($email))->fetchColumn();
|
||||||
|
|
||||||
|
$paymentCount = (int)$pdo->query('SELECT COUNT(*) FROM kl_Einzahlungen WHERE MitarbeiterID = ' . $memberId)->fetchColumn();
|
||||||
|
if ($paymentCount === 0) {
|
||||||
|
$stmt = $pdo->prepare('INSERT INTO kl_Einzahlungen (MitarbeiterID, Betrag, Datum) VALUES (?, ?, NOW())');
|
||||||
|
$stmt->execute([$memberId, 10.00]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$consumptionCount = (int)$pdo->query('SELECT COUNT(*) FROM kl_Kaffeeverbrauch WHERE MitarbeiterID = ' . $memberId)->fetchColumn();
|
||||||
|
if ($consumptionCount === 0) {
|
||||||
|
$stmt = $pdo->prepare(
|
||||||
|
'INSERT INTO kl_Kaffeeverbrauch (MitarbeiterID, AnzahlStriche, Kosten, KostenproStrich, Datum, Eintragsart)
|
||||||
|
VALUES (?, ?, ?, ?, NOW(), ?)'
|
||||||
|
);
|
||||||
|
$stmt->execute([$memberId, 3, 0.60, 0.20, 2]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$noticeCount = (int)$pdo->query('SELECT COUNT(*) FROM kl_hinweise')->fetchColumn();
|
||||||
|
if ($noticeCount === 0) {
|
||||||
|
$stmt = $pdo->prepare('INSERT INTO kl_hinweise (nachricht, gueltig_bis) VALUES (?, DATE_ADD(NOW(), INTERVAL 30 DAY))');
|
||||||
|
$stmt->execute(['Dev-Hinweis: MySQL-Testumgebung aktiv.']);
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "MySQL dev schema is ready for {$email}.\n";
|
||||||
|
|
||||||
Executable
+15
@@ -0,0 +1,15 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
|
PHP_BIN="${PHP_BIN:-$ROOT_DIR/.local/php/usr/bin/php8.3}"
|
||||||
|
PHP_INI="${PHP_INI:-$ROOT_DIR/.local/php-dev.ini}"
|
||||||
|
PHP_LIB_DIR="$ROOT_DIR/.local/php/usr/lib/x86_64-linux-gnu"
|
||||||
|
|
||||||
|
export LD_LIBRARY_PATH="${LD_LIBRARY_PATH:-$PHP_LIB_DIR}"
|
||||||
|
export APP_DB_DRIVER="${APP_DB_DRIVER:-mysql}"
|
||||||
|
export APP_ENV="${APP_ENV:-dev}"
|
||||||
|
export DEV_AUTH_EMAIL="${DEV_AUTH_EMAIL:-admin@test.local}"
|
||||||
|
export DEV_AUTH_NAME="${DEV_AUTH_NAME:-Test Admin}"
|
||||||
|
|
||||||
|
exec "$PHP_BIN" -c "$PHP_INI" -S "${HOST:-127.0.0.1}:${PORT:-8080}" -t "$ROOT_DIR"
|
||||||
+2
-2
@@ -31,7 +31,7 @@ if ($_SERVER["REQUEST_METHOD"] == "POST") {
|
|||||||
$anzahlStriche = (int)$anzahlStriche;
|
$anzahlStriche = (int)$anzahlStriche;
|
||||||
$kostenproStrich = floatval($kostenproStrich);
|
$kostenproStrich = floatval($kostenproStrich);
|
||||||
$kosten = floatval($anzahlStriche * $kostenproStrich);
|
$kosten = floatval($anzahlStriche * $kostenproStrich);
|
||||||
$datum = date("Y-d-m H:i:s"); // Das aktuelle Datum verwenden
|
$datum = date("Y-m-d H:i:s"); // Das aktuelle Datum verwenden
|
||||||
if($anzahlStriche != 0){
|
if($anzahlStriche != 0){
|
||||||
// SQL-Abfrage zum Einfügen der Daten
|
// SQL-Abfrage zum Einfügen der Daten
|
||||||
$sql = "INSERT INTO kl_Kaffeeverbrauch (MitarbeiterID, AnzahlStriche, Kosten, KostenproStrich, Datum) VALUES (?, ?, ?, ?, ?)";
|
$sql = "INSERT INTO kl_Kaffeeverbrauch (MitarbeiterID, AnzahlStriche, Kosten, KostenproStrich, Datum) VALUES (?, ?, ?, ?, ?)";
|
||||||
@@ -158,4 +158,4 @@ $KostenproStrich = number_format($KostenproStrichtemp, 2, '.', '');
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<?php include "footer.php"; ?>
|
<?php include "footer.php"; ?>
|
||||||
|
|||||||
Reference in New Issue
Block a user