7.1 KiB
Steuerkram – Entwicklungsregeln
Tech Stack
PHP 8.3+, MySQL, PSR-4 (Namespace Steuerkram\), Composer, kein Framework.
MVC: src/Controllers/, src/Models/, src/Services/, src/Views/.
Coding-Regeln
- Kein
var_dump, keindie()in Produktionscode. - Neue Libraries nur nach expliziter Rückfrage einbinden.
- Bestehenden Code-Stil beibehalten (declare strict_types, 4 Spaces).
- SQL ausschließlich über
Database::fetchAll/fetchOne/execute()mit Prepared Statements. - Keine neuen
style="-Attribute außer für Laufzeitwerte (siehe unten).
CSS-Klassen statt Inline-Styles (Issue #160)
public/static/css/app.css ist das zentrale Stylesheet. Wiederkehrende Muster
gehören dorthin, nicht als style="..." in die Views – sonst ist weder eine
CSP ohne unsafe-inline noch Theming möglich, und tests/97_InlineStyleBudgetTest.php
schlägt fehl, sobald das Budget überschritten wird.
Regel: Inline-Styles nur, wenn der Wert tatsächlich zur Laufzeit aus PHP kommt (Kontofarbe, Logo-Maße aus den Einstellungen o. ä.) – und auch dann bevorzugt als CSS-Variable statt vollständiger Deklaration:
<!-- statt -->
<span style="background:<?= $h($k['farbe']) ?>;color:<?= $h($textfarbe) ?>">
<!-- besser -->
<span class="konto-badge" style="--badge-bg: <?= $h($k['farbe']) ?>">
Wichtige vorhandene Klassen (Auswahl, siehe app.css für die vollständige Liste):
| Klasse | Zweck |
|---|---|
.modal-overlay (+ .open, --scroll, --stacked) |
Dialog-Hintergrund, Toggle über .open-Klasse oder direktes style.display |
.modal / .modal-card (+ -invoice, --lg) |
Dialog-Box in zwei etablierten Größen/Stilen |
.modal-card-header / .modal-card-close |
Titelzeile mit Schließen-Button im Dialog |
.icon-btn |
Rahmenloser Icon-Button (SVG-Icon, --icon-color für Laufzeitfarbe) |
.btn-round-icon |
Runder Icon-Button (z. B. „Position hinzufügen“) |
.row-clickable + data-row-href |
Klickbare Tabellenzeile (zentrale Delegation in app.js), data-no-row-click nimmt einzelne Zellen aus |
.alert / .alert-warning / .alert-info |
Hinweisboxen |
.text-muted, .text-sm, .text-danger, .nowrap, .min-w-0, .m-0, .w-full, .hidden |
Utilities für einzelne Eigenschaften |
Inline-Handler (onclick=, onchange=, oninput=) sind in src/Views nicht
mehr erlaubt (Test 97 prüft das). Statt dessen: Element bekommt eine id
oder ein data-action-Attribut, die Verdrahtung passiert per
addEventListener() im zugehörigen Seiten-Skript oder – bei generischen,
seitenübergreifenden Mustern (z. B. data-modal-open/data-modal-close,
data-row-href) – zentral in app.js.
Tests
Regel: Jedes neue Feature bekommt einen eigenen Test.
Tests liegen in tests/ und folgen dem Namensschema NN_FunktionsnameTest.php.
Jede Testklasse erweitert TestCase und implementiert run().
docker compose exec -u www-data web php tests/run.php # alle Tests ausführen
./scripts/run-tests.sh # Tests + automatische Issue-Erstellung bei Fehlern
./scripts/run-tests.sh 42 # Tests; bei Fehlern Kommentar in Issue #42
Immer -u www-data: ohne die Option laufen die Tests als root und legen
unterhalb von data/ Dateien an, die Apache/PHP später nicht mehr beschreiben
kann – ein echter Beleg-Upload scheitert dann mit HTTP 500.
Neuen Test erstellen
- Datei
tests/NN_MeinFeatureTest.phpanlegen (NN = nächste freie Nummer) - Klasse
MeinFeatureTest extends TestCase protected string $testName = 'Test NN: Beschreibung';- Methode
public function run(): voidmit Assertions implementieren - Transaktions-Isolation ist automatisch (setUp/tearDown in TestCase)
- Dateien und Verzeichnisse immer über
TestCaseanlegen – der Rollback greift nur in der Datenbank, nicht im Dateisystem:
$pfad = $this->tempPath('.csv'); // Pfad in /tmp, wird aufgeräumt
$dir = $this->makeTempDir($basis . '/2026/BLG-...'); // Verzeichnis anlegen + aufräumen
$this->registerTempPath($schonVorhandenerPfad); // nachträglich anmelden
Bleibt trotzdem etwas liegen, meldet tests/run.php das am Ende des Laufs.
Assertion-Methoden
$this->assert(bool, 'Beschreibung');
$this->assertEquals($erwartet, $ist, 'Beschreibung');
$this->assertGreaterThan($min, $wert, 'Beschreibung');
$this->assertNotEmpty($wert, 'Beschreibung');
E2E-Tests (Browser)
Die PHP-Tests erreichen Routing, Views und Formular-JavaScript nicht. Dafür gibt
es Playwright-Tests in tests/e2e/, die im Container des e2e-runner laufen
(auf dem Host wird nichts installiert). Details: tests/e2e/README.md.
docker compose exec web php scripts/e2e-user.php # einmalig: Testnutzer anlegen
tests/e2e/run.sh # alle E2E-Tests
docker compose exec web php scripts/e2e-cleanup.php --force # Reste entfernen
Fehlt Docker, der Runner oder das Image, wird mit Exit 0 übersprungen.
Welche Ebene wofür: Rechen- und Model-Logik gehört in die PHP-Suite (schneller,
isoliert). In tests/e2e/ gehört nur, was einen Browser braucht: Formularabläufe,
Redirects, gerenderte Views, JavaScript-Umrechnungen, DOM-XSS.
Dokumentation aktuell halten
| Datei | Wann aktualisieren |
|---|---|
Changes.md |
Bei jedem Feature, Bugfix oder strukturellen Änderung (nicht bei reinen Test- oder Doku-Commits) |
README.md |
Wenn sich der Funktionsumfang oder der Tech Stack grundlegend ändert |
docs/funktionen.md |
Wenn ein neues Feature hinzukommt oder ein bestehendes wesentlich geändert wird |
docs/berechnungslogik.md |
Bei jeder Änderung an EuerCalculator.php oder UstvaCalculator.php (Zuordnungsregeln, neue Kennzahlen, geänderte Bedingungen) |
docs/anlagevermögen.md |
Bei Änderungen am Anlagevermögens-Workflow oder den Asset-Modellen |
Datenbankmigrationen
Schemaänderungen (neue Spalten, Tabellen, Kontenanpassungen) kommen ausschließlich als nummerierte Migrations-Datei in migrations/:
- Schema:
NNN_kurze_beschreibung.sql(NNN = nächste freie dreistellige Nummer, z.B.011_...) - Migrationen werden nie manuell eingespielt –
scripts/migrate.phperledigt das automatisch beim Container-Start - Jede Migration muss idempotent sein oder mit
IF NOT EXISTS/ON DUPLICATE KEYabgesichert werden
# Nächste freie Nummer ermitteln:
ls migrations/ | tail -1
# Migration manuell testen (ohne Neustart):
docker compose exec web php scripts/migrate.php
Geplante Aufgaben (Cron)
Alle planmäßigen Aufgaben (z.B. Ideenagent, Automatisierungen) laufen lokal über die System-Crontab des Nutzers. Für außerplanmäßige Ausführung das jeweilige Skript direkt aufrufen:
crontab -l # alle geplanten Jobs anzeigen
/home/fds/projects/Steuerkram/.claude/ideenagent-run.sh # Ideenagent manuell starten
/home/fds/projects/Steuerkram/.claude/automate-run.sh # Automatisierung manuell starten
Remote Triggers (claude.ai) oder Claude Code Cron-Jobs werden nicht verwendet.
Commits
Fix #NR: Kurze Beschreibung der Änderung
Keine Co-Authored-By-Zeile. Keine langen Commit-Messages.