Steuerkram/CLAUDE.md

7.2 KiB
Raw Blame History

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, kein die() 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().

./scripts/test.sh                                                         # alle Tests ausführen
docker compose exec -u www-data web ./scripts/container/run-tests.sh      # Tests + automatische Issue-Erstellung bei Fehlern
docker compose exec -u www-data web ./scripts/container/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

  1. Datei tests/NN_MeinFeatureTest.php anlegen (NN = nächste freie Nummer)
  2. Klasse MeinFeatureTest extends TestCase
  3. protected string $testName = 'Test NN: Beschreibung';
  4. Methode public function run(): void mit Assertions implementieren
  5. Transaktions-Isolation ist automatisch (setUp/tearDown in TestCase)
  6. Dateien und Verzeichnisse immer über TestCase anlegen – 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/container/e2e-user.php   # einmalig: Testnutzer anlegen
./scripts/e2e.sh                                              # alle E2E-Tests
docker compose exec web php scripts/container/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/intern/migrate.php erledigt das automatisch beim Container-Start
  • Jede Migration muss idempotent sein oder mit IF NOT EXISTS / ON DUPLICATE KEY abgesichert werden
# Nächste freie Nummer ermitteln:
ls migrations/ | tail -1

# Migration manuell testen (ohne Neustart):
docker compose exec web php scripts/intern/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.