Steuerkram/Changes.md

864 lines
61 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Changelog
---
## 2026-07-10 – Fix: `update.sh` meldet Testfehler per Textsuche statt Exit-Code (Issue #124)
- **Niedrig:** `scripts/update.sh` prüfte den Erfolg der Test-Suite nicht über den Exit-Code von `tests/run.php`, sondern per `grep` nach den Wörtern „FEHLER“, „FAILED“, „fehlgeschlagen“ bzw. „failed“ im Test-Output. Diese Wörter kommen jedoch auch in Testdaten und erwarteten Fehlermeldungen bestandener Tests vor (z.B. Test 47 EventLog, Test 50 SMTP), sodass trotz „✓ Alle Tests bestanden“ eine falsche Warnung ausgegeben wurde. Zusätzlich war `TEST_EXIT=${PIPESTATUS[0]:-0}` wirkungslos, da `PIPESTATUS` sich nicht auf die vorangegangene Command-Substitution bezieht.
- **`scripts/update.sh`:** Exit-Code von `docker compose exec ... tests/run.php` wird nun direkt über `if TEST_OUTPUT=$(...); then ... else TEST_EXIT=$?; fi` erfasst (kompatibel mit `set -e`) und für die Erfolgsmeldung ausgewertet, statt den Output nach Schlüsselwörtern zu durchsuchen.
## 2026-07-10 – Fix: Session-Cookie ohne Secure-, HttpOnly- und SameSite-Attribute (Issue #121)
- **Mittel-Hoch:** `session_start()` wurde an acht Stellen (`src/Auth.php`, `src/View.php`, `src/Controllers/LoginController.php`) unkonfiguriert aufgerufen; `session.cookie_httponly`, `session.cookie_secure` und `session.cookie_samesite` liefen auf den PHP-Compiled-in-Defaults (aus/leer). Das Session-Cookie – Zugriff auf sämtliche Mandanten-/Steuerdaten – war damit per JavaScript auslesbar (volle Session-Übernahme bei jeder Art von XSS) und unterlag keiner `SameSite`-Beschränkung (CWE-614/CWE-1004).
- **`src/Auth.php`:** Neue zentrale Methode `Auth::startSession()` setzt vor jedem `session_start()` `session_set_cookie_params()` mit `httponly => true` und `samesite => 'Lax'`. `secure` wird anhand des Requests erkannt (`HTTPS`-Server-Variable bzw. `X-Forwarded-Proto: https` hinter einem TLS-terminierenden Reverse-Proxy), damit der Standard-Quickstart per HTTP ohne vorgeschalteten Proxy weiter funktioniert. Über die Umgebungsvariable `SESSION_COOKIE_SECURE` lässt sich das Verhalten explizit erzwingen (`1`) oder abschalten (`0`).
- **`src/View.php`, `src/Controllers/LoginController.php`:** Alle direkten `session_start()`-Aufrufe durch `Auth::startSession()` ersetzt, damit die Härtung an jeder Stelle greift.
- **`.env.example`:** `SESSION_COOKIE_SECURE` dokumentiert.
- **`tests/54_SessionCookieHardeningTest.php`:** Neuer Test prüft HttpOnly/SameSite, automatische Secure-Erkennung über HTTPS bzw. `X-Forwarded-Proto`, sowie den expliziten Override per `SESSION_COOKIE_SECURE`.
## 2026-07-10 – Fix: Kein Schutz gegen Brute-Force am Login (Issue #122)
- **Mittel:** `LoginController::authenticate()` prüfte Benutzername/Passwort ausschließlich per `password_verify()` – ohne Rate-Begrenzung, Lockout, Captcha oder Delay. Da `/login` als einzige Route bewusst ohne `Auth::require()` erreichbar ist und Passwörter nur eine Mindestlänge von 8 Zeichen benötigen, war ein Online-Brute-Force-/Credential-Stuffing-Angriff gegen einzelne Konten praktisch unlimitiert (CWE-307).
- **`migrations/086_login_throttle.sql`:** Neue Spalten `users.failed_attempts` und `users.locked_until`.
- **`src/Models/User.php`:** `registerFailedLogin()` zählt Fehlversuche pro Benutzer und sperrt das Konto ab 5 Fehlversuchen für ein mit jedem weiteren Fehlversuch steigendes Zeitfenster (`2^(Fehlversuche-5)` Minuten, gedeckelt auf 60 Minuten). `resetFailedLogins()` setzt den Zähler bei erfolgreichem Login zurück, `isLocked()` prüft die Sperre.
- **`src/Controllers/LoginController.php`:** `authenticate()` weist Login-Versuche für gesperrte Konten ab, bevor das Passwort geprüft wird, und registriert Fehlversuche bei falschem Passwort.
- **`tests/53_LoginThrottleTest.php`:** Neuer Test prüft Zählung der Fehlversuche, Sperre ab dem 5. Fehlversuch, steigendes Zeitfenster bei weiteren Fehlversuchen und Reset bei erfolgreichem Login.
## 2026-07-08 – Fix: SMTP-/Nextcloud-Zugangsdaten im Klartext gespeichert und im HTML sichtbar (Issue #119)
- **Hoch:** `mail_smtp_pass` (SMTP-Passwort) und `nextcloud_pass` (Nextcloud-App-Passwort) wurden unverschlüsselt in `company_settings` abgelegt, obwohl für vergleichbar sensible Felder (FinTS-PIN, Client-ID/Secret) bereits `Crypto::encrypt()`/`Crypto::decrypt()` (AES-256-GCM) etabliert ist. Zusätzlich schrieb `src/Views/settings/form.php` beide Klartext-Passwörter in das `value`-Attribut zurück, wodurch sie für jeden Betrachter des Seitenquelltexts sichtbar waren.
- **`src/Models/Setting.php`:** `get()` entschlüsselt `nextcloud_pass`/`mail_smtp_pass` nun über `Crypto::decrypt()` (Migrationsmodus: bestehende Klartextwerte in der DB werden unverändert durchgereicht), `update()` verschlüsselt beide Felder vor dem Speichern über `Crypto::encrypt()` – analog zum bestehenden Muster in `BankSource.php`.
- **`src/Controllers/SettingsController.php`:** `update()` behält bei leer gelassenem Passwortfeld das bisherige (entschlüsselte) Passwort bei, statt es zu überschreiben – analog zum bestehenden Muster in `BankController`.
- **`src/Views/settings/form.php`:** `value`-Attribute der beiden Passwortfelder entfernt; stattdessen Platzhalter „(gespeichert – leer lassen für unverändert)", wenn bereits ein Wert existiert.
- **`migrations/085_encrypt_settings_passwords.sql`:** `mail_smtp_pass` von `VARCHAR(255)` auf `TEXT` erweitert, damit der Verschlüsselungs-Overhead (Prefix + Base64) nicht abgeschnitten wird.
- **`tests/52_SettingsPasswordEncryptionTest.php`:** Neuer Test prüft, dass beide Felder verschlüsselt in der DB landen, über `Setting::get()` korrekt entschlüsselt werden und bei leerem Wert nicht verschlüsselt gespeichert werden.
## 2026-07-08 – Fix: SSRF über ungeprüftes fints_url-Feld bei Bankverbindungen (Issue #118)
- **Kritisch:** Beim Anlegen/Bearbeiten einer FinTS-Bankverbindung wurde die vom Benutzer eingegebene `fints_url` ungefiltert übernommen und später von `FinTsFetchService` direkt als Ziel-URL für eine echte HTTP-Verbindung verwendet – ein authentifizierter Benutzer konnte den Server so Requests gegen beliebige Ziele absetzen lassen (interne Dienste, `localhost`, Cloud-Metadata-Endpunkte, CWE-918).
- **Neu `src/Services/SsrfGuard.php`:** `assertPublicHttpsUrl()` erzwingt Schema `https://`, verbietet Userinfo in der URL und löst den Host auf, um sämtliche zugehörigen IPs gegen private/reservierte Bereiche zu prüfen (RFC1918, Loopback, Link-Local/Metadata via `FILTER_FLAG_NO_PRIV_RANGE`/`FILTER_FLAG_NO_RES_RANGE`); bei Verstoß wirft die Methode eine `RuntimeException`.
- **`src/Controllers/BankController.php`:** `direktStore()` und `direktUpdate()` validieren `fints_url` vor dem Speichern mit `SsrfGuard`; ungültige Ziele werden mit Fehlermeldung abgelehnt statt in der Datenbank zu landen.
- **`src/Services/FinTsFetchService.php`:** `run()` prüft die gespeicherte URL zusätzlich unmittelbar vor dem Verbindungsaufbau erneut (Schutz gegen DNS-Rebinding zwischen Speichern und Abruf).
- **`tests/51_SsrfGuardTest.php`:** Neuer Test prüft, dass `http://`, private/loopback/link-local IP-Literale, `localhost` und URLs mit Userinfo abgelehnt werden, während öffentliche `https://`-Ziele weiterhin akzeptiert werden.
---
## 2026-07-08 – Fix: SMTP-/E-Mail-Header-Injection über ungeprüfte Empfänger-/CC-Adressen (Issue #117)
- **Kritisch:** Empfänger- und CC-Adressen (Rechnungsversand, Test-Mail, Kundenstammdaten) wurden nur `trim()`t, aber nie auf gültiges Format oder enthaltene `\r`/`\n` geprüft. Da `SmtpMailer` das SMTP-Protokoll über einen rohen Socket implementiert, konnte eine Adresse mit eingebettetem CRLF zusätzliche `RCPT TO:`-Kommandos oder Mail-Header einschleusen (CWE-93, SMTP-/Header-Injection).
- **`src/Services/SmtpMailer.php`:** `send()` validiert jetzt jede Empfängeradresse mit `filter_var(..., FILTER_VALIDATE_EMAIL)`, bevor überhaupt eine SMTP-Verbindung aufgebaut wird; ungültige Adressen (inkl. CRLF-Payloads) werfen eine `RuntimeException`. Da alle Versandwege (`EmailService::sendInvoice()`, `sendTestMail()`) durch diese einzige Methode laufen, ist die Prüfung zentral wirksam.
- **`src/Controllers/CustomerController.php`:** Formularvalidierung prüft `email`, `email_cc1`, `email_cc2` zusätzlich mit `filter_var(FILTER_VALIDATE_EMAIL)`, damit gar nicht erst ungültige Adressen in der Kundendatenbank landen.
- **`src/Views/customers/form.php`:** Fehleranzeige für die drei E-Mail-Felder ergänzt.
- **`tests/50_EmailHeaderInjectionTest.php`:** Neuer Test prüft, dass CRLF-Injection-Payloads und ungültige Adressformate von `SmtpMailer::send()` abgelehnt werden.
---
## 2026-07-08 – Fix: Unauthentifizierter Direktzugriff auf Belege/Rechnungs-PDFs (Issue #116)
- **Kritisch:** Hochgeladene Belege und Rechnungs-PDFs lagen unter `public/static/uploads/...` im Web-Root und wurden von Apache direkt ausgeliefert, bevor der Request `index.php`/`Auth::require()` erreichte – ein Angreifer konnte Belege ohne Anmeldung per URL abrufen.
- **`src/Config.php`:** `upload_dir` zeigt jetzt auf `data/uploads` (außerhalb des Web-Roots, analog zum bereits per Apache gesperrten `/data`-Verzeichnis)
- **`src/Models/Beleg.php`:** Neue Methode `absolutePath()` als einzige Quelle für die Pfad-Auflösung; unterstützt weiterhin Bestandsdaten mit dem alten Präfix `static/uploads/` in `storage_path`
- **`src/Controllers/BelegController.php`, `InvoiceController.php`, `BankController.php`, `src/Services/NextcloudImportService.php`, `KontierungsvorschlagService.php`:** Alle Stellen, die Beleg-Dateien schreiben oder lesen, nutzen jetzt `Config::get('upload_dir')` bzw. `Beleg::absolutePath()` statt eigener `public/static/uploads`-Pfade
- **`docker-compose.yml`, `Dockerfile`, `docker-entrypoint.sh`:** Upload-Verzeichnis wird nach `/var/www/html/data/uploads` gemountet/angelegt (bestehender Host-Ordner `./uploads` bleibt unverändert)
---
## 2026-07-06 – Fix: Fehlermeldung beim Beleg-Löschen, falls Datei nicht löschbar ist (Issue #114)
- **`src/Models/Beleg.php`:** `delete()` nutzte ein ungeschütztes `unlink()`; schlug dieses auf dem Server mit „Permission denied" fehl, wurde eine PHP-Warning ausgegeben, die den nachfolgenden Redirect-Header zerstörte („headers already sent"). Der Datensatz wurde dabei trotzdem gelöscht, was zu Verwirrung führte. Fehler beim Datei-Löschen wird jetzt abgefangen und nur noch geloggt (`error_log`), die Löschung des Datensatzes läuft unverändert durch.
---
## 2026-07-02 – Feature: Zusätzliche CC-E-Mail-Adressen pro Kunde (Issue #111)
- **`migrations/084_customer_cc_emails.sql`:** Neue Spalten `email_cc1`, `email_cc2` in `customers`
- **`src/Models/Customer.php`:** `create()` und `update()` speichern die beiden CC-Adressen (Whitespace wird zu NULL normalisiert)
- **`src/Views/customers/form.php`:** Zwei neue Eingabefelder „E-Mail CC 1/2" unter der Haupt-E-Mail
- **`src/Controllers/InvoiceController.php`:** `show()` lädt den Kunden und übergibt ihn an die Detailansicht
- **`src/Views/invoices/show.php`:** CC-Felder im E-Mail-Sende-Modal werden mit den hinterlegten Kunden-CC-Adressen vorbelegt
---
## 2026-06-29 – Feature: Ereignisse-Log für Nextcloud-Import (Issue #109)
- **`migrations/083_system_events.sql`:** Neue Tabelle `system_events` (id, channel, message, created_at)
- **`src/Models/EventLog.php`:** Neues Model mit `add()` und `findByChannel()`
- **`src/Services/NextcloudImportService.php`:** Persistiert relevante Ereignisse (Import, Duplikat, Fehler) automatisch; stille Läufe ohne Dateiaktivität erzeugen keine Einträge; Zusammenfassung wird nur bei tatsächlicher Aktivität geloggt
- **`src/Controllers/EventController.php`:** Neuer Controller für `/ereignisse` und `/ereignisse/nextcloud-import`
- **`src/Views/ereignisse/nextcloud_import.php`:** Log-Ansicht mit Zeitstempel, Kanal und farbiger Meldung
- **`src/Views/layout/nav.php`:** Neuer Menüpunkt „Ereignisse" unter Verwaltung mit aufklappbarem Unterpunkt „Nextcloud-Import"
---
## 2026-06-29 – Feature: Lieferant in Belegliste inline bearbeiten (Issue #108)
- **`src/Views/belege/list.php`:** Lieferant-Spalte ist direkt in der Liste klickbar; zeigt ein Inline-Textfeld zur Eingabe, speichert mit Enter, bricht mit Escape ab; andere Spalten öffnen weiterhin die Vorschau
- **`src/Models/Beleg.php`:** Neue Methode `updateSupplier()` aktualisiert nur die `supplier_id` ohne andere Metadaten zu verändern
- **`src/Controllers/BelegController.php`:** Neue Methode `updateSupplier()` als AJAX-Endpunkt
- **`public/index.php`:** Route `POST /belege/{id}/supplier` registriert
---
## 2026-06-11 – Fix: Undefined-Array-Key-Warning bei Buchungsvorlagen ohne Standard-Lieferant
- **`src/Models/Buchungsvorlage.php`:** `create()`/`update()` prüften `$data['standard_lieferant']` direkt ohne Null-Coalescing, was bei fehlendem Schlüssel eine PHP-Warning auslöste
---
## 2026-06-11 – Feature: Liste verfügbarer Ollama-Modelle in den Einstellungen (Issue #103)
- **`LlmService`:** Neue Methode `getAvailableModels()` fragt `/api/tags` ab und liefert die Namen aller lokal verfügbaren Ollama-Modelle; neue Getter `getMode()`, `getModel()`, `getVisionModel()`
- **Einstellungsseite:** Im KI-Abschnitt wird bei erreichbarem Ollama-Provider die Liste der verfügbaren Modelle angezeigt, das konfigurierte Standard- und Vision-Modell sind markiert
## 2026-06-11 – Feature: CSV-Importformat aus „Neue Direktverbindung"-Dialog anlegen (Issue #102)
- **`bank/fints.php`:** Neue Auswahl „+ CSV-Importformat…" im Anbieter-Dropdown des „Neue Direktverbindung"-Dialogs; zeigt Felder für Trennzeichen, Anführungszeichen, Datumsformat, Dezimal-/Tausendertrennzeichen, Headerzeile und übersprungene Zeilen
- **`BankController::direktStore()`:** Behandelt `provider_id === '__csv__'` gesondert – legt einen `bank_sources`-Eintrag mit `type='csv'` und `provider_id=NULL` an (Standard-Spaltenzuordnung: Buchungstag=0, Wertstellung=1, Vorgang=2, Betrag=4) und leitet zur Feinabstimmung nach `/settings/bankimport` weiter
## 2026-06-11 – Fix: Direktabruf springt nach Abschluss zurück zu „Bank buchen" (Issue #101)
- **`src/Views/bank/fints.php`:** Nach erfolgreichem Direktabruf (FinTS) führt der „Schließen"-Button im Abruf-Log-Modal jetzt zu einem Redirect auf `/bank` ("Bank buchen"); bei Abbruch oder Fehler bleibt die Anzeige wie bisher auf `/bank/direkt`, damit der Abruf wiederholt werden kann
---
## 2026-06-08 – Fix: KI-Verbindungstest mit Diagnoseinformationen (Issue #55)
- **`LlmService`:** Neue Methode `testConnection()` gibt detaillierte Diagnose zurück (cURL-Fehlernummer/-meldung, HTTP-Code, Antwortzeit, verfügbare Modelle)
- **`LlmService`:** `probeOllama()` schreibt bei cURL-Fehlern jetzt einen Eintrag ins PHP-Fehlerlog (`docker compose logs web`)
- **Einstellungsseite:** Neuer Button „Verbindung testen" im KI-Abschnitt; zeigt bei Erfolg verfügbare Modelle, bei Fehler cURL-Fehler, URL und Firewall-Hinweis an
- **Route:** `POST /settings/llm-test` → `SettingsController::llmTest()`
- **`BelegController`:** Bei Verbindungsfehler wird jetzt `[]` statt `null` gespeichert (verhindert endlose Auto-Retry-Schleife); Fehlermeldung differenziert zwischen „Ollama nicht erreichbar" und „kein Vorschlag generiert"
- **Beleg-Detailseite:** Zeigt bei Verbindungsfehler spezifische Meldung „Ollama nicht erreichbar" mit Link zu den Einstellungen statt der generischen Warnung
## 2026-06-01 – Fix: EN16931-Validierungsfehler in ZUGFeRD-Rechnungen
- **[BR-FX-EN-04]** behoben: `ZugferdService` setzt jetzt BT-72 (Liefer-/Leistungsdatum) via `setDocumentSupplyChainEvent()`; das Datum wird aus dem Feld `leistungszeitraum` geparst (Format `DD.MM.YYYY` → direkt; Format `MM.YYYY` → letzter Tag des Monats; sonst Fallback auf Rechnungsdatum)
- **[PEPPOL-EN16931-R008]** behoben: Leere Strings in optionalen Adressfeldern (Straße, PLZ, Ort) werden als `null` übergeben, sodass keine leeren XML-Elemente entstehen
## 2026-05-28 – Feature: Buchungsvorlagen im Buchungsdialog
- **Datenbank:** 3 neue Tabellen `buchungsvorlagen`, `buchungsvorlage_felder`, `buchungsvorlage_regeln` (Migrationen 078–080); initiale Vorlage "Essensbeleg n. abzugsfähig (nur USt.)" per Migration 080 eingespielt
- **Buchungsdialog** (`/belege/{id}`): Neues Vorlagen-Badge (gelb) zwischen Bank-Badge und Buchungsformular; Auswahlbox mit aktiven Vorlagen; Vorlage auswählen öffnet modalen Dialog mit Felder-Gruppen und Live-USt-Berechnung
- **Modal:** Feldgruppe 1 (Metadaten: Lieferant, Rechnungsnr., Belegdatum, Brutto-Anzeige), Feldgruppe 2 (USt-Zeilen mit Netto↔Brutto-Live-Berechnung), Feldgruppe 3 (Zahlungskonto; 1200 vorausgewählt wenn Bankbuchung, sonst 1210)
- **Buchungslogik:** `VorlageBuchungsService` interpretiert JSON-Buchungsregeln und ruft `Booking::create()` direkt auf (keine automatischen Kindzeilen, da Konten explizit); Metafelder werden über `Beleg::updateMeta()` gespeichert
- **Pflege-UI:** `/einstellungen/buchungsvorlagen` – CRUD-Oberfläche für Vorlagen, Felder und Buchungsregeln; Navigationslink unter Einstellungen
- **Test 42:** `42_BuchungsvorlageTest.php` prüft CRUD, Buchungsausführung, korrekte Konten und `neq_zero`-Bedingung
## 2026-05-24 – Fix: EÜR-Zeilennummern auf amtliche Anlage EÜR 2025 korrigiert
- `migrations/075_euer_zeilen_korrektur.sql`: alle `euer_zeile`-Werte in `accounts` gemäß amtlicher Anlage EÜR 2025 neu zugeordnet (zuvor internes Schema ohne Bezug zum amtlichen Formular)
- `EuerCalculator.php`: Sonderbehandlung für nicht abziehbare BA von `euer_zeile='51'` auf `'74'` umgestellt (Zeile 74 = „Nicht abziehbare Beträge" der amtlichen Anlage EÜR)
- `accounts/form.php`: `$euerZeilen`-Array vollständig durch amtliche Zeilen 12–72 und 74 ersetzt; alle Beschriftungen entsprechen exakt dem Formulartext der Anlage EÜR 2025
## 2026-05-24 – Feature: EÜR Anlageliste CSV/PDF-Export
- EÜR-Ansicht: Kopfzeile der Anlageliste-Karte enthält jetzt rechtsbündig „Herunterladen: [CSV] [PDF]"
- CSV-Export: `GET /tax/euer/{year}/anlageliste/csv` – alle 9 Spalten mit UTF-8 BOM (Excel-kompatibel), Semikolon-getrennt, inkl. Summenzeile
- PDF-Export: `GET /tax/euer/{year}/anlageliste/pdf` – A4 Querformat, enge Spaltenbreiten sodass alle 9 Spalten auf eine Seite passen; neuer `PdfAnlagelisteService`
- EÜR-Karte: heller „PDF herunterladen"-Button entfernt; Topbar-Button umbenannt zu „PDF"
---
## 2026-05-24 – Feature: AFA-Buchung stornieren/löschen in AfA-Tabelle
- AfA-Tabelle im Asset-Edit-Modal: Gebuchte Zeilen zeigen neben dem grünen Haken einen Storno-Button (Undo-Pfeil, alle Modi) und im Testmodus zusätzlich einen Papierkorb-Button
- Storno legt eine Gegenbuchung (`buchungstyp=AfA`, `is_reversal=1`, negativer Betrag) an und setzt `afa_booking_id` auf NULL → der Pfeil-aus-Box-Button erscheint wieder
- Löschen (Testmodus) löscht die AFA-Buchung direkt und setzt `afa_booking_id` auf NULL
- Neuer Controller: `POST /assets/{id}/depreciation/{rowId}/reverse` und `…/delete`
- `AssetDepreciation::setBookingId()` akzeptiert jetzt `?int` (null erlaubt)
---
## 2026-05-24 – Feature: Buchungsjournal-Filter Buchungstyp + EÜR-Anlageliste
- Buchungsjournal: neues Filterfeld „Buchungstyp" (Einnahme / Ausgabe / AfA); vorbelegt mit „Alle Typen"
- Buchungsjournal: AfA-Buchungen (ohne Beleg) werden jetzt korrekt angezeigt (`LEFT JOIN belege`); Beleg-Zelle zeigt „–" wenn kein Beleg
- Buchungsjournal: Badge für Buchungstyp `AfA` in grau (badge-gray)
- EÜR-Ansicht: neue Tabelle „Anlagegüter {Jahr}" unterhalb der EÜR mit Spalten Anlage-ID, Bezeichnung, Anschaffungsdatum, Anschaffungskosten, Nutzungsdauer, AfA Vorjahre (kumulierte gebuchte AfA-Buchungen), Restbuchwert Vorjahr, AfA {Jahr} (gebuchte AfA des Jahres), Restbuchwert 31.12.{Jahr}; Summenzeile am Ende
- `Asset::anlageliste(int $year)` als neue Modell-Methode (single query mit Subqueries)
---
## 2026-05-24 – Feature: AfA-Tabelle anteilig nach Anschaffungsmonat
- Das erste Abschreibungsjahr wird anteilig nach Restmonaten berechnet: der Anschaffungsmonat zählt voll (Januar = 12 Monate, April = 9 Monate usw.)
- Das letzte Jahr erhält automatisch die verbleibenden Monate (12 − Monate des ersten Jahres); bei Januar-Kauf entfällt das extra letzte Jahr
- Die Cent-Option (`cent_im_ersten_jahr`) bleibt davon unberührt: der Cent-Rest geht weiterhin vollständig ins erste Jahr, ohne Aufteilung auf das letzte Jahr
- Sofortabschreibung (`nutzungsdauer = 0`) ist unverändert
- Neuer Test `41_AfaAnteiligTest` prüft April-, Oktober-, Juli-, Dezember- und Januar-Käufe sowie die Cent-Option mit anteiligem Jahr
---
## 2026-05-22 – Feature: „Abrufen ab"-Auswahl für Direktverbindungen
- Im Bearbeiten-Dialog jeder FinTS- bzw. API-Direktverbindung gibt es ein neues Dropdown **Abrufen ab** mit den Optionen *Ab letztem Abruf (00:00 Uhr)* (Default), *Letzte 7 Tage*, *Aktueller Monat (ab 01.)*, *Letzte 30 Tage*, *Ohne Datum (letzte 365 Tage)*
- Die Auswahl gilt nur für den nächsten Abruf: nach erfolgreichem Lauf setzt `BankSource::updateLastFetched()` `fetch_range` wieder auf `'last_fetched'` zurück
- Migration `072_bank_sources_fetch_range.sql` ergänzt die neue ENUM-Spalte (Default `'last_fetched'`, bestehende Verbindungen verhalten sich unverändert)
- Logik in `BankSource::computeStartDate()` zentralisiert; `FinTsFetchService` und `PayPalNvpFetchService` nutzen sie. Neuer Test `39_BankFetchRangeTest` deckt alle fünf Range-Werte plus Fallbacks ab
---
## 2026-05-22 – Fix: FinTS-Konto-Auswahl wird jetzt gespeichert + Bearbeiten-Dialog mit Checkboxen
- **Bug:** Beim ersten Abruf erschien das Konto-Auswahl-Modal, das Speichern blieb aber hängen. Ursache war PHP-Session-Locking: der SSE-Stream `/bank/direkt/{id}/run` hielt die Session-Datei während des `awaitAccountSelection()`-Polling-Loops gesperrt, der parallele POST `/bank/direkt/{id}/accounts` blockierte beim `session_start()` → Deadlock bis Timeout
- **Fix:** Im SSE-Endpoint `session_write_close()` direkt nach dem Auth-Check; entstört zugleich die analog gelagerte TAN-Eingabe
- **UI:** Im Bearbeiten-Dialog einer FinTS-Verbindung erscheinen die gespeicherten Konten jetzt als Checkbox-Liste (statt nur als Text). Abhaken entfernt den Identifier beim Speichern aus `account_filter`; alle abgehakt verhält sich wie der Reset-Toggle
- Filter-Berechnung als `BankSource::computeFilterFromPost()` extrahiert, neuer Test `38_BankSourceFilterFromPostTest` deckt Reset, Teilauswahl, leere Auswahl und Schutz gegen Unterschieben fremder Identifier ab
---
## 2026-05-22 – Feature: APP_KEY Status & Generator in den Einstellungen
- Neuer Settings-Bereich unter dem Betriebsmodus zeigt Status des Verschlüsselungsschlüssels (`APP_KEY`): **Gesetzt** (grün, 64 Hex-Zeichen valide), **Ungültig** (falsches Format) oder **Fehlt** (leer)
- Bei fehlendem oder ungültigem Schlüssel: Button „Neuen Schlüssel generieren" → POST `/settings/generate-app-key` liefert `bin2hex(random_bytes(32))`, Anzeige im Code-Block mit Copy-Button
- Schlüssel wird nicht persistiert (Container hat keinen Schreibzugriff auf `.env`); Anleitung im UI: kopieren → in `.env` eintragen → `docker compose restart web`
---
## 2026-05-22 – UI: Einheitliche Icon-Buttons für Bearbeiten/Ansehen/Löschen
- **Bleistift-Icon** (transparent, ohne Hintergrund – Stil der Direktverbindungs-Kacheln) ersetzt alle „Bearbeiten"- und „Öffnen"-Buttons in `accounts/list`, `customers/show`, `customers/list`, `suppliers/list`, `useradmin/list`, `assets/list`, `belege/list`, `invoices/list`, `invoices/show`
- **Auge-Icon** (Kästchen mit Auge) ersetzt die PDF-Buttons in `invoices/list` (PDF), `invoices/show` (PDF herunterladen) sowie den „Ansehen"-Button in `tax/ustva_index`, um „nur ansehen" visuell vom „bearbeiten" abzugrenzen
- **Papierkorb-Icon** (transparent, Danger-Farbe) ersetzt alle „Löschen"-Buttons in `accounts/list`, `invoices/list`, `useradmin/list`, `suppliers/list`, `assets/list`, `belege/detail`, `settings/bankimport`, `bank/fints` und `settings/form`
- **Plus-Icon** (rund, btn-primary, 1,8rem) ersetzt alle „Neu anlegen/erstellen/hochladen"-Buttons – Topbars (`bank/fints`, Kunden, Lieferanten, Rechnungen, Belege, Wirtschaftsgüter, Konten), Dashboard-Schnellzugriff, Card-Header in `customers/show`, Aktionsspalte in `customers/list` und „Position hinzufügen" im Rechnungsformular
- `bank/index.php`: Button „Öffnen" → „Aktivieren" (Status-Toggle, semantisch eindeutiger)
---
## 2026-05-21 – Feature: FinTS-Direktverbindung + Vereinheitlichung der Bank-Importquellen
- **FinTS-Abruf** für Comdirect, DKB und weitere Banken über `nemiah/php-fints` (PHP-Lib für HBCI/FinTS 3.0). Leseabruf mit PIN; TAN-pflichtige Aktionen werden mit klarer Fehlermeldung abgebrochen
- **Verschlüsselung sensibler Felder**: Neuer `Crypto`-Service (AES-256-GCM, Magic-Prefix `enc:v1:`) verschlüsselt PayPal-Signature und FinTS-PIN. Schlüssel aus `APP_KEY` in `.env` – im Backup mitsichern, Verlust = Verlust aller verschlüsselten Werte
- **Datenmodell-Fusion**: Neue Tabelle `bank_sources` ersetzt `bank_import_formats` + `bank_connections` mit `type`-Spalte (`csv`/`api`/`fints`). `bank_import_batches.source_id` löst `format_id`/`connection_id` ab (alte Spalten bleiben vorerst bestehen für rollback-Sicherheit)
- **Route-Umbenennung**: `/bank/fints*` → `/bank/direkt*` (semantisch korrekt – PayPal ist keine FinTS-Verbindung). Topbar in /bank vereinheitlicht zu einer „Importieren"-Schaltfläche
- **NEU/Edit-Modal** zeigt typabhängig API-Felder (PayPal: App-Name, Kennwort, Signature, Sandbox) oder FinTS-Felder (BLZ, Benutzerkennung, PIN, optional FinTS-URL und IBAN-Filter)
- **`FinTsFetchService`**: Login → SEPA-Konten abrufen → Statements seit `last_fetched_at` (00:00) → Buchungen mit Hash-basierter `external_id` (IBAN+Datum+Betrag+Verwendungszweck) deduplizieren
- Migrationen 068 (bank_sources + Backfill), 069 (default_fints_url auf bank_providers für Comdirect/DKB)
- `scripts/encrypt_bank_secrets.php` – einmaliges idempotentes Verschlüsseln der Klartext-Werte nach Backfill
- Neue Tests 36 (Crypto-Roundtrip) und 37 (BankSource CRUD über alle drei Typen)
---
## 2026-05-17 – Feature: Automatischer Format-Vorschlag beim Bank-Import
- **Format-Vorschlag**: Wenn eine hochgeladene CSV keinem bekannten Format eindeutig entspricht, analysiert `BankImportParser::suggestFormat()` die Datei automatisch (Headerzeilen-Erkennung bei skip_rows 0–10, Delimiter-Erkennung, Keyword-basiertes Spalten-Mapping)
- **Session-gestützter Vorschlags-Flow**: Klick auf „Formate bearbeiten" speichert den Vorschlag via AJAX in der Session und öffnet die Einstellungsseite
- **Vorschlags-Card**: Auf `/settings/bankimport` erscheint der Vorschlag oberhalb bestehender Formate als ausfüllbares Formular mit vorausgefüllten Feldern (skip_rows, Delimiter, Spalten-Mapping mit erkannten Headernamen)
- **Explizites Speichern**: Neues Format wird erst nach Klick auf „Speichern" in der DB angelegt (POST `/settings/bankimport/create`); Verwerfen löscht die Session-Suggestion
- Neue Routen: `POST /settings/bankimport/suggest`, `POST /settings/bankimport/create`, `POST /settings/bankimport/dismiss-suggestion`
- Neues Model: `BankImportFormat::create()`; neuer Test 35
---
## 2026-05-17 – Feature: Automatische Format-Erkennung beim Bank-Import
- **Format-Erkennung**: Hochgeladene Datei wird automatisch gegen alle gespeicherten Formate geprüft; bei eindeutigem Match wird sofort importiert
- **Vorschau-Panel**: Wenn kein Format passt, zeigt die Upload-Seite Überschriften-Zeile, erste Werte-Zeile und die Spalten-Zuordnung (Wertstellung, Buchungstext, Wert) für das gewählte Format an; wechselt live beim Umschalten der Formatauswahl
- **„Formate bearbeiten"-Button** oben rechts verlinkt direkt auf die Einstellungsseite für Bankimport-Formate
- Neuer AJAX-Endpunkt `POST /bank/detect-format` in `BankController`; neue Methoden `peekRows()` und `isFormatMatch()` in `BankImportParser`
---
## 2026-05-17 – Feature: DKB-Importformat für Bankbuchungen
- **Neues Format „DKB Bank"**: 4 Skip-Zeilen, Semikolon-getrennt, Anführungszeichen-eingeschlossen
- Buchungstext wird konditionell zusammengesetzt: bei negativem Betrag aus Zahlungsempfänger + Verwendungszweck, sonst aus Zahlungspflichtiger + Verwendungszweck
- Migration 054 ergänzt drei neue Nullable-Spalten in `bank_import_formats` (`col_zahlungspflichtiger`, `col_zahlungsempfaenger`, `col_verwendungszweck`)
---
## 2026-05-16 – Refactor: Code-Qualität, Sicherheit und Konsistenz
- **CSRF-Schutz**: `Auth::generateCsrfToken()` / `validateCsrfToken()` neu; Router validiert Token bei allen POST/PUT/DELETE-Requests; base.php injiziert Token in Formulare und fetch-Aufrufe via JS; login.php erhält hidden Input
- **Path Traversal** in `BelegController::serveFile()` abgesichert (`realpath()` + Prefix-Check gegen Upload-Basisverzeichnis)
- **`mime_content_type()`** (deprecated) durch `finfo_file(finfo_open(FILEINFO_MIME_TYPE), ...)` in `BelegController`, `SettingsController`, `BankController` und `ImageCropService` ersetzt
- **XSS** in `topbarActions`: `View::buildQuery()` (neuer Helper) statt rohem `http_build_query($_GET)`
- **`@`-Unterdrückung** in `SettingsController` entfernt (ersetzt durch `file_exists()`-Prüfung)
- **BaseController**: gemeinsame `renderView()`-Methode zentralisiert; alle 13 Controller erben davon; lokale Duplikate entfernt
- **DB-Transaktionen**: `Booking::create()` und `Booking::reverse()` laufen jetzt in atomaren Transaktionen; `Database::beginTransaction/commit/rollBack` unterstützt verschachtelte Aufrufe via Tiefenzähler (TestCase-Isolation bleibt intakt)
- **`src/Constants.php`**: Buchungstypen und Steuerkontonummern (SKR03) als benannte Konstanten; Magic Strings in `Booking.php` ersetzt
- **Dokumentation**: `docs/funktionen.md` – Abschnitt „Bank buchen" ergänzt; UStVA-Kennzahlen (81, 89, 66, 61, 83) korrigiert; Eigenanteil-Konten (4920/4925) korrigiert
- **Tests**: `32_BankTransactionTest` (linkBeleg, unlinkBeleg, recalcStatus, findDuplicates) und `33_BankImportParserTest` (CSV-Parsing, BOM, Tausendertrenner, Fehlerfall) neu
---
## 2026-05-15 – Feature: Bankbuchung auf mehrere Belege aufteilen
- Migration 053: Junction-Tabelle `bank_transaction_belege` (bank_tx_id, beleg_id, betrag); `bank_transactions.beleg_id` entfernt; Status-ENUM um `teilweise` erweitert; bestehende Verknüpfungen automatisch migriert
- Neuer Status `teilweise`: wird gesetzt wenn mind. ein Beleg verknüpft ist, aber noch Restbetrag offen bleibt
- Standardfilter in der Bankübersicht zeigt jetzt „Offen & Teilweise"
- `bank/show.php`: Sektion „Verknüpfte Belege" mit Betrag je Beleg und Entfernen-Button; Restsaldo-Anzeige; Betrag-Eingabefeld pro Beleg in der Offene-Belege-Liste
- Beim Verknüpfen wird der zugeordnete Betrag an die Buchungsmaske weitergegeben (`alloc_betrag`)
- `BankTransaction::linkBeleg()` schreibt jetzt in Junction-Tabelle mit Betrag; `recalcStatus()` setzt Status automatisch; `unlinkBeleg()` entfernt Verknüpfung; `getLinkedBelege()` / `getRemainingBetrag()` neu
- Neue Route `POST /bank/{id}/unlink`
---
## 2026-05-15 – Feature: Dublettenprüfung beim Bank-Import
- Nach dem Upload wird geprüft, welche Buchungen bereits in der DB vorhanden sind (Abgleich über Buchungstag, Wertstellung, Betrag, Buchungstext)
- Werden Dubletten gefunden, erscheint eine Prüfseite mit zwei Tabellen: erkannte Dubletten (per Checkbox auswählbar) und neue Buchungen (werden immer importiert)
- „Alle auswählen / abwählen"-Buttons + Gesamtzähler zeigen an, wie viele Buchungen importiert werden
- Erst nach Bestätigung wird importiert; ohne Auswahl wird der Import übersprungen
- Migration 052: UNIQUE-Constraint auf `bank_transactions` entfernt (UI-Prüfung ist die primäre Schutzschicht)
- `BankTransaction::findDuplicates()`: neue Methode mit Batch-Abfrage gegen die DB
- `BankController::doImport()`: gemeinsame Import-Logik extrahiert (wird von `upload()` und `uploadConfirm()` genutzt)
---
## 2026-05-15 – Fix: Bank-Import-Duplikate verhindern
- Migration 051: UNIQUE-Constraint `(buchungstag, wertstellung, wert, buchungstext(200))` auf `bank_transactions`; bestehende Duplikate wurden dabei bereinigt
- `BankController::upload()`: Import läuft jetzt in einer DB-Transaktion – bei Fehler wird alles zurückgerollt statt einen unvollständigen Batch zu hinterlassen
- `BankController::upload()`: Dateiname-Prüfung vor dem Import – erneuter Upload derselben Datei wird mit Fehlermeldung abgelehnt
- `BankTransaction::filenameExists()`: neue Hilfsmethode zur Duplikat-Prüfung anhand Dateiname
---
## 2026-05-15 – Fix: Netto-Neuberechnung bei Kategorie-Wechsel in Bank-Buchungsmaske
- IIFE-Wrapper für `updateAutoUst` in einen `DOMContentLoaded`-Listener verschoben, damit er erst nach der Funktionsdefinition (späteres `<script>`-Tag) gesetzt wird und nicht sofort von ihr überschrieben wird.
---
## 2026-05-14 – Feature: Bank buchen
- Navigation: neuer Eintrag „Bank buchen" unter Belege & Buchungen
- Migration 049: Tabellen `bank_import_formats`, `bank_import_batches`, `bank_transactions`; Comdirect-Format als erster Eintrag
- `BankImportParser`: Parst CSV/TXT (quoted, Semikolon-getrennt) und XLSX (via ZipArchive/SimpleXML, kein Composer-Paket)
- Liste `/bank`: Suche + Filter (Jahr, Monat, Einnahme/Ausgabe, Status); Standard-Filter: offen; Einnahmen grün, Ausgaben rot
- Upload `/bank/upload`: Drag-&-Drop-Zone für CSV/TXT/XLSX + Format-Auswahl
- Detail `/bank/{id}`: Bank-Zeile oben; Beleg-Upload; Liste offener Belege zum Verknüpfen
- Status-Aktionen: „Ignorieren" / „Wieder öffnen"; Verknüpfen eines Belegs setzt Status auf „gebucht"
---
## 2026-05-14 – Feature: UStVA Kz 83 = ust81 + ust89 − Kz66 − Kz61
- `UstvaCalculator`: Kz 83 wird nun als `ust81 + ust89 − Kz66 − Kz61` berechnet (nicht mehr kontobasiert via ustva_kz='83')
- Test 29: angepasst (7%-Buchung auf korrektes Konto 8300; delta83-Erwartung aktualisiert)
## 2026-05-14 – Feature: UStVA Kz 61 aus ust89 abgeleitet (ReverseCharge-Symmetrie)
- `UstvaCalculator`: Kz 61 = ust89 (nicht mehr kontobasiert via Konto 1574) – fachlich korrekt, da VSt ig. Erwerb = USt ig. Erwerb
- `Booking::reverse()`: Hauptstornobuchung überträgt jetzt `steuerart` der Originalbuchung (fehlte bisher, wurde durch Kz-61-Kontoberechnung kaschiert)
## 2026-05-14 – Feature: UStVA Kz 81/89 auf volle Euro abgerundet
- `UstvaCalculator`: Kz 81 und 89 werden mit `floor()` auf volle Euro abgerundet; USt wird auf dem gerundeten Wert berechnet
- Darstellung: Kz 81/89 zeigen "1.234,- €" statt "1.234,00 €" (in Tabelle und Zusammenfassung)
- USt-Felder (ust81, ust89) bleiben als Dezimalzahl unverändert
- Test 31: prüft floor-Verhalten für Kz 81 und 89
## 2026-05-14 – Refactor: UStVA auf 5-Kennzahlen-Schema vereinfacht
- Migration 048: Kontenrahmen bereinigt – nur noch `ustva_kz` ∈ {81, 83, 61, 66} aktiv; 1571/1576 → 66, 1574 → 61, 1771/1774 → 83, alle anderen auf NULL
- `UstvaCalculator`: Nur noch Kz 81, 89, 66, 61, 83 + zwei Anzeigefelder (ust81, ust89 = Kz × 0,19, kein XML)
- Kz 83 ist jetzt „Vorauszahlung / Überschuss" = `(USt-Konten) − Kz 66 − Kz 61`
- `ElsterXmlService`: Export nur Kz 81/89 (ganzzahlig) + 66/61/83 (2 Dez); Kz 83 nur bei positivem Wert
- View `ustva_show.php`: Neue Sektionsgliederung, Zusammenfassungsbox aktualisiert
- Tests 5, 7, 13, 18, 28, 29, 30 auf neues Schema angepasst
---
## 2026-05-10 – Feature: ig. Erwerb 19 % ReverseCharge über Kindzeilen
- Migration 047: ustva_kz='91' für Konto 1774; Kindzeilen (1574/1774) für bestehende ig_erwerb_19-Buchungen
- `Booking::create()`: steuerart=ig_erwerb_19 erzeugt zwei Kindzeilen – VSt auf 1574 (Kz 67), USt auf 1774 (Kz 91)
- `UstvaCalculator`: Kz 67/91 jetzt kontobasiert via 1574/1774; Kz 93/94 (ig_erwerb_7) entfernt
- Buchungsformular: Option „ig. Erwerb 7 %" entfernt
- Test 7 auf neue Logik angepasst; Test 30 neu
---
## 2026-05-10 – Feature: USt-Buchungszeilen für Ausgangsrechnungen
- Migration 046: Konten 1771 (USt 7 %) und 1776 (USt 19 %) erhalten `ustva_kz='84'`/'83'`; bestehende Einnahme-Buchungen mit USt erhalten nachträglich USt-Kindzeilen
- `Booking::create()`: Bei Einnahme-Buchungen mit 7/19 % und `steuerart=normal` wird analog zur VSt-Kindzeile eine USt-Zeile auf 1771/1776 angelegt
- `UstvaCalculator`: Kz 83/84 jetzt kontobasiert via 1771/1776 (`betrag_netto`), nicht mehr via `betrag_ust` der Erlöskonten; `KZ_ACCOUNT_MAP` aktualisiert
- `Invoice::cancel()`: Nur Hauptbuchungen (`parent_booking_id IS NULL`) werden direkt storniert; Kindzeilen werden von `Booking::reverse()` mitaufgehoben (verhindert Doppelstorno)
- Tests 10 und 27 an geänderte Buchungsstruktur angepasst; Test 29 neu
---
## 2026-05-05 – Feature: Stornorechnung (§ 14 UStG)
### Hinzugefügt
- `migrations/042_invoice_storno_of_id.sql`: Neue Spalte `storno_of_id` in `invoices` (FK auf Original)
- `Invoice::createCancellation()`: Legt Stornorechnung mit negierten Beträgen an, setzt Original auf „Storniert", erzeugt GoBD-konforme Stornobuchungen
- Storno-Button in Rechnungsübersicht (`list.php`) und Detailansicht (`show.php`)
- Querverweise Original ↔ Stornorechnung in der Detailansicht
- PDF-Anpassung: Titel „STORNORECHNUNG", Referenzzeile, kein Zahlungshinweis, angepasster Dateiname
- Test `27_StornorechnungTest.php`
---
## 2026-04-28 – Feature: E-Rechnung / ZUGFeRD EN16931
### Hinzugefügt
- `src/Services/ZugferdService.php`: Erzeugt ZUGFeRD-konformes EN16931-XML und bettet es in den PDF-String ein (via `horstoeko/zugferd`)
- `src/Services/PdfInvoiceService.php`: PDF wird nach der mPDF-Generierung durch `ZugferdService::attachToPdf()` geleitet; Ausgabe erfolgt jetzt manuell per Header/echo statt `mpdf->Output('D')`
- Alle Ausgangsrechnungs-PDFs sind ab sofort gültige E-Rechnungen (ZUGFeRD EN16931 / Factur-X)
---
## 2026-04-27 – Feature: Neues Rechnungsnummernformat (PPPYYYYMM-nn)
### Geändert
- `src/Models/Invoice.php`: `generateRechnungsNr()` erzeugt jetzt das Format `PPPYYYYMM-nn` (Prefix + Jahr + Monat + zweistellige Sequenz, die bei Bedarf wächst); Zähler wird monatlich zurückgesetzt
- `migrations/038_invoice_nr_month.sql`: Neue Spalte `invoice_nr_month` in `company_settings`
---
## 2026-04-26 – Fix: Eigenanteil-Buchung als Einnahme statt Ausgabe
### Geändert
- `src/Models/Booking.php`: `upsertKorrekturbuchung()` setzt `buchungstyp` jetzt korrekt auf `Einnahme` (war fälschlich `Ausgabe`)
---
## 2026-04-26 – Feature #57: Lineare Abschreibungstabelle für Anlagegüter
### Hinzugefügt
- `migrations/035_assets_abschreibung_flags.sql`: Neue Spalten `automatisch_berechnet` und `cent_im_ersten_jahr` in `assets`
- `migrations/036_asset_depreciation_schedule.sql`: Neue Tabelle `asset_depreciation_schedule` (Jahr, Abschreibungsbetrag, Restwert)
- `src/Models/AssetDepreciation.php`: Berechnung (linear, Sofortabschreibung, Cent-Option), Persistenz
- `tests/26_AbschreibungstabelleTest.php`: 22 Tests für alle AfA-Szenarien
### Geändert
- `src/Models/Asset.php`: `create()` und `update()` speichern `automatisch_berechnet` und `cent_im_ersten_jahr`
- `src/Controllers/AssetController.php`: Abschreibungstabelle wird nach Speichern automatisch generiert; neue Endpunkte `/assets/depreciation-preview` (AJAX-Vorschau) und `/assets/{id}/depreciation` (manuelle Speicherung)
- `src/Views/assets/form.php`: Nutzungsdauer-Dropdown (sofort, 1–10, 12, 13, 15, 20, 25, 50 Jahre + individuell), Cent-Option, „AfA-Tabelle"-Button, Modal mit Auto/Manuell-Toggle und editierbarer Tabelle
- `public/index.php`: Neue Routen für Depreciation-Preview und -Speicherung
---
## 2026-04-26 – KI-Diagnose: Erreichbarkeitsprüfung und Fehlerlog für Ollama
### Geändert
- `src/Services/LlmService.php`: Neue Methode `isReachable()` für Diagnosezwecke; Fehlerlog in `callOllama()` bei cURL-Fehler oder leerer Antwort
- `src/Views/settings/form.php`: Einstellungsseite prüft jetzt aktiv, ob der Ollama-Server erreichbar ist (nicht nur ob er konfiguriert ist); zeigt bei Verbindungsfehler einen konkreten Hinweis auf `OLLAMA_HOST=0.0.0.0:11434`
---
## 2026-04-26 – Leistungszeitraum als freies Textfeld
### Geändert
- `migrations/034_leistungszeitraum_text.sql`: Spalten `leistungszeitraum_von` (DATE) und `leistungszeitraum_bis` (DATE) wurden durch eine einzige Textspalte `leistungszeitraum` (VARCHAR 255) ersetzt; bestehende Datumswerte werden automatisch migriert
- `src/Views/invoices/form.php`: Zwei Datumsfelder durch ein Freitextfeld „Leistungszeitraum" ersetzt, Grid von 4 auf 3 Spalten angepasst
- `src/Models/Invoice.php`: INSERT/UPDATE verwenden jetzt `leistungszeitraum` statt `leistungszeitraum_von`/`leistungszeitraum_bis`
- `src/Controllers/InvoiceController.php`: Datums-Defaults für `leistungszeitraum_von`/`leistungszeitraum_bis` entfernt
- `src/Views/invoices/show.php`: Leistungszeitraum-Anzeige vereinfacht (kein Datumsformat, direkter Textausgabe)
- `src/Services/PdfInvoiceService.php`: Leistungszeitraum-Ausgabe vereinfacht
---
## 2026-04-15 – KI-Erkennung: automatischer Aufruf aus Belegansicht entfernt
### Geändert
- `src/Views/belege/detail.php`: KI-Analyse wird nicht mehr automatisch beim Öffnen eines Belegs ausgelöst; stattdessen wird ein Hinweis "KI-Analyse wird im Hintergrund durchgeführt …" mit einem "Jetzt generieren"-Button angezeigt, wenn noch kein Vorschlag vorhanden ist
- Hintergrund: Die Analyse läuft bereits beim Nextcloud-Import (`ki-batch.php`); ein doppelter Aufruf aus der UI war überflüssig
---
## 2026-04-12 – Anlagevermögen: Anzeige-Fix und Daten-Backfill
### Fix
- Buchungstabellen (Beleg-Detail, Journal, UStVA-Drill-down, EÜR-Drill-down): Anlage-Spalte zeigt jetzt auch die Bezeichnung als Fallback, wenn `anlage_nr` leer ist
- `migrations/032_anlage_nr_backfill.sql`: Anlage-Nummern für bestehende Anlagegüter ohne `anlage_nr` werden nachgeneriert
---
## 2026-04-12 – Anlagevermögen (Wirtschaftsgüter)
### Neu
- Navigation: neuer Abschnitt „Betriebsvermögen" mit Menüpunkt „Anlagevermögen"
- `migrations/028_assets_anlagevermoegen.sql`: neue Tabelle `assets` für Wirtschaftsgüter
- `src/Models/Asset.php`: CRUD-Model mit automatischer laufender Nummer und Geschäftsjahrzuordnung
- `src/Controllers/AssetController.php`: Liste, Anlegen, Bearbeiten, Löschen
- `src/Views/assets/list.php`: Anlagenspiegel-Tabelle mit Bleistift- und Papierkorb-Aktionen
- `src/Views/assets/form.php`: Formular für Wirtschaftsgut (Bezeichnung, Anschaffungsdatum, Kosten, Zugänge, Abgänge, AfA, Nutzungsdauer, Notizen)
- `tests/20_AnlagevermoegensTest.php`: 14 Tests für CRUD-Operationen
---
## 2026-04-12 – EÜR-Korrekturen: Gewerbesteuer + Bewirtungskonten ohne VSt
### Geändert
- `migrations/028_euer_4320_bewirtung_ohne_vst.sql`: Gewerbesteuer (4320) von Z. 52 auf Z. 51 (§ 4 Abs. 5b EStG, KZ 165 nicht abzugsfähig)
- `migrations/028_euer_4320_bewirtung_ohne_vst.sql`: Konten 4652 (Bewirtung 70 % ohne VSt, Z. 44) und 4657 (Bewirtung 30 % ohne VSt, Z. 51) ergänzt
- `docs/kontenrahmen.md`: Dokumentation entsprechend aktualisiert
---
## 2026-04-12 – Klasse-0-Konten als Anlagevermögen klassifiziert
### Geändert
- `docs/kontenrahmen.md`: Alle 10 Klasse-0-Konten (0027–0480) von `Zahlungskonto` auf `Anlagevermögen` korrigiert
- `migrations/027_anlagevermögen_kontenart.sql`: Migration war bereits vorhanden und setzt die DB-Änderung beim nächsten Container-Start um
---
## 2026-04-12 – Backup: smarte Rotation und Lock-Schutz
### Geändert
- `scripts/backup.sh`: Rotation auf 7 Tages- / 4 Wochen- / 12 Monats-Buckets umgestellt (statt pauschaler 90-Tage-Löschung)
- `scripts/backup.sh`: `flock`-Lock verhindert parallele Läufe bei Verzögerungen
- `scripts/backup.sh`: Pfade sind absolut (Script funktioniert auch aus Cron ohne Verzeichniswechsel)
---
## 2026-04-12 – Uploads: named Volume → Bind Mount
### Geändert
- `docker-compose.yml`: Upload-Volume von named Volume (`steuerkram_uploads`) auf Bind Mount (`./uploads/`) umgestellt – konsistent mit `./data/mariadb/`
- `scripts/migrate-uploads-to-bindmount.sh`: Einmalige Migration hinzugefügt
### Update auf Produktiv-System
**Vor** `docker compose up` einmalig ausführen:
```bash
./scripts/migrate-uploads-to-bindmount.sh
```
Das Script kopiert alle Dateien aus dem named Volume in `./uploads/`, ist idempotent und gibt am Ende den Befehl zum Löschen des alten Volumes aus.
---
## 2026-04-11 – Buchungsbasierte Vorsteuer Kz 61/62 in UStVA
### Geändert
- `UstvaCalculator`: Kz 61 und 62 werden nicht mehr über `accounts.ustva_kz` geroutet, sondern direkt aus `bookings.ust_satz` ermittelt (`buchungstyp='Ausgabe'`, `betrag_ust != 0`). Damit reicht ein einziges Aufwandskonto für beide USt-Sätze – separate VSt19-/VSt7-Konten sind nicht mehr erforderlich
- `fetchBookingsForKz`: Drill-down für Kz 61/62 nutzt jetzt ebenfalls buchungsbasierte Filterung
- `BERICHTE.md`: Routing-Logik für Kz 61/62 dokumentiert
---
## 2026-04-10 – Nextcloud-Belegimport via WebDAV
### Neu
- `NextcloudImportService`: importiert Belege aus einem Nextcloud-Ordner via WebDAV (PROPFIND → GET → MOVE/DELETE); Duplikat-Erkennung via SHA-256; JPEG/PNG werden nach dem Download automatisch zugeschnitten
- `scripts/nextcloud-import.php`: CLI-Einstiegspunkt für Cron-Betrieb
- Migration 023: Nextcloud-Zugangsdaten (`nextcloud_url`, `nextcloud_user`, `nextcloud_pass`, `nextcloud_folder`, `nextcloud_done`) in `company_settings`; Konfiguration über Einstellungen → Nextcloud-Belegimport in der UI
---
## 2026-04-10 – UStVA Vorsteuer-Kz-Aufteilung (Migration 022)
### Fix
- Migration 022: Vorsteuer-Konten von `ustva_kz='66'` auf `'61'` (VSt19) bzw. `'62'` (VSt7) aufgeteilt – Kz 61, 62 und 66 wurden zuvor stets als 0 ausgewiesen
- `UstvaCalculator`: zweite Abfrage für steuerart-basierte EU-Kennzahlen (ig. Lieferung → Kz 41, Ausfuhr → Kz 44, ig. Erwerb → Kz 89/91/93/94); verhindert Doppelzählung gegenüber kontobasierter Berechnung
- `fetchBookingsForKz`: UNION-Abfrage aus Konto-Kz und steuerart für korrekten Drill-down
- Tests 4, 5, 6, 9: Kontonummern auf tatsächliche SKR03-Nummern korrigiert (8000→8400, 1400→1200)
---
## 2026-04-10 – Upload-Limit auf 20 MB erhöht
### Geändert
- `php/uploads.ini`: `upload_max_filesize` und `post_max_size` auf 20 MB gesetzt (zuvor PHP-Standard 2 MB)
- `Dockerfile`: bindet `php/uploads.ini` als PHP-Konfiguration ein
---
## 2026-04-10 – Belegtext-Speicherung für spätere Volltextsuche
### Neu
- Migration 021: Spalte `beleg_text MEDIUMTEXT` in `belege` – speichert den per `pdftotext` oder Tesseract-OCR extrahierten Volltext
- `KontierungsvorschlagService::extractText()`: neue public Methode zur Textextraktion (PDF + Bild), unabhängig vom KI-Vorschlag nutzbar
- Beim Generieren des KI-Vorschlags wird `beleg_text` automatisch befüllt (einmalig, nur wenn noch leer)
- `Beleg::saveBelegText()`: neue Methode im Model
---
## 2026-04-10 – OCR-Schalter unabhängig vom KI-Schalter
### Neu
- Einstellungen: separater Schalter „OCR-Texterkennung" (`ocr_aktiv`) zum unabhängigen Ein-/Ausschalten der Tesseract-OCR bei Bildbelegen (Migration 020)
- KI-Schalter und OCR-Schalter arbeiten nun vollständig unabhängig voneinander
- `KontierungsvorschlagService::isOcrEnabled()`: neue Methode, prüft OCR-Einstellung
- Bildbeleg-OCR wird nur noch ausgeführt, wenn `ocr_aktiv = 1`; PDF-Textextraktion via `pdftotext` läuft unabhängig davon
---
## 2026-04-02 – Kontenrahmen: Dokumentation und finale Korrekturen
### Neu
- `Kontenrahmen.md`: Vollständige Dokumentation aller SKR03-Systemkonten mit UStVA-Kennzahlen und EÜR-Zeilennummern
- Verweis auf `Kontenrahmen.md` in `README.md` ergänzt
### Geändert (Navigation)
- Kontenrahmen (`/accounts`) als eingerückter Untermenüpunkt unter „Einstellungen" in der Seitenleiste
- Konten-Tabelle aus `/settings` entfernt (war redundant zu `/accounts`)
### Geändert (UStVA-Kennzahlen, Migration 017)
- KZ `61` → `66` für alle VSt19-Konten (normale Inlands-Vorsteuer, § 15 Abs. 1 Nr. 1 UStG)
- KZ `62` → `66` für alle VSt7-Konten
- KZ `35` → `48` für 8100/8105/8110 (steuerfreie Umsätze ohne VoSt-Abzug)
- KZ `35` → `43` für 8120 (Ausfuhrlieferungen § 4 Nr. 1a UStG)
### Geändert (EÜR-Zeilennummern, Migration 019)
- 8400/8570/8720: Z. 14 → Z. 16 (19 %-Erlöse netto; Z. 14 = Kleinunternehmer brutto)
- 8120/8125: Z. 16 → Z. 18 (Ausfuhr/ig. Lieferungen)
- 8950: Z. 18 → Z. 21 (nicht steuerbare Innenumsätze)
- 4672: Z. 43 → Z. 51 (nicht abziehbarer Anteil Unternehmerreisekosten, § 4 Abs. 5 EStG)
- 4288: Z. 52 → Z. 65 (häusl. Arbeitszimmer abziehbar, eigene Zeile seit EÜR 2023)
- 4287: Z. 52 → Z. 66 (Tagespauschale häusl. Wohnung, eigene Zeile seit EÜR 2023)
- 4856: Z. 49 → Z. 37 (Sonderabschreibungen § 7b EStG)
### Kanonischer Seed (Migration 018)
- `018_seed_kontenrahmen_skr03_final.sql`: Alle 146 Systemkonten mit bereits korrekten UStVA-KZ und EÜR-Zeilen; Neuinstallationen erhalten den vollständigen aktuellen Stand ohne weitere Korrekturen
---
## 2026-04-01 – Feature: EÜR-Zeile in Konten-Übersicht anzeigen
### Neu
- Spalte „EÜR-Zeile" in der Konten-Liste (/accounts) ergänzt
- Zeigt den Wert `euer_zeile` je Konto an (z.B. 14, 26, 51, 52 oder „–" wenn leer)
## 2026-04-01 – Feature: Bewirtungsbelege (Konten 4650/4651) mit automatischem Split
### Neu
- Buchungen auf Konto 4650 (Bewirtungskosten VSt. 19%) oder 4651 (Bewirtungskosten VSt. 7%) werden automatisch aufgeteilt: 70 % auf das gewählte Konto, 30 % auf das Abzugskonto (4654 bzw. 4656)
- Neue Felder `bewirtungsgrund` und `teilnehmerliste` in der `bookings`-Tabelle (Migration 016)
- Im Buchungsformular erscheinen die beiden Felder automatisch, wenn 4650/4651 als Kategorie gewählt wird
- Vorhandene Buchungen zeigen Bewirtungsgrund und Teilnehmerliste in der Buchungsübersicht an
## 2026-04-01 – Bugfix: Nicht abzugsfähige Kosten (4654/4656) separat in EÜR unter KZ 165
### Behoben
- Konten mit `euer_zeile = '51'` (4654, 4656, 4635, 4636, 4289) fließen nicht mehr in `total_ausgaben` und `gewinn` ein
- Sie erscheinen in einem eigenen Abschnitt „Nicht abzugsfähige Betriebsausgaben (KZ 165)" in Web und PDF
- Migration 015 setzt `euer_zeile = '51'` für Konto 4656 (fehlte bisher)
---
## 2026-03-30 – Bugfix: 30 % Eigenanteil fließt jetzt korrekt in UStVA KZ 81/83
### Behoben
- `Booking::upsertEigenanteil30`: statt zwei Buchungen (8922 Netto + 1776 USt-Korrektur) wird
nur noch eine einzige Buchung auf 8922 mit echtem `ust_satz` und `betrag_ust` erzeugt
- `upsertKorrekturbuchung`: neue Parameter `ustSatz` und `betragUst`; UPDATE und INSERT setzen
jetzt `betrag_ust` und `betrag_brutto` korrekt
- Vorher: `betrag_ust = 0` auf 8922 → KZ 83 blieb leer; 1776-Buchung hatte keine UStVA-Wirkung
---
## 2026-03-30 – Kontenrahmen-Reset: DATEV SKR03 2026 (IST-Versteuerung)
### Geändert
- Alle Buchungen und Belege gelöscht (Testdaten)
- Gesamter Kontenrahmen durch DATEV SKR03 2026 (gültig ab 2026-01-01) ersetzt
- Neue Spalte `euer_zeile` in Tabelle `accounts`: Zeilennummer der Anlage EÜR
- 145 Konten neu angelegt: Klassen 0, 1, 3, 4, 8 (ohne Klasse 9 / ohne Verbindlichkeiten)
- Verfahren: IST-Versteuerung; Konto 1390 „Verrechnungskonto Ist-Versteuerung" enthalten
- UStVA-Kennzeichen korrekt nach DATEV: 81/86 (Umsätze), 61/62 (Vorsteuer), 35/41 (steuerfrei)
- Migration: `013_reset_kontenrahmen_skr03_2026.sql`
---
## 2026-03-30 – KI-gestützte Belegerkennung (experimentell) + 30 % Eigenanteil Telefon/Internet
### Neu
- Eigenanteil von 30 % bei Buchungen auf Telefon- und Internetkosten-Konten (4920/4925) wählbar
- Experimentelle KI-Beleganalyse: erkennt Buchungstyp, Betrag, Konto und Lieferant aus PDF/Bild-Belegen
- Setzt lokales LLM (z. B. Ollama/Mistral) oder Remote-API-Schlüssel voraus
- Hoher Ressourcenbedarf bei lokaler Ausführung; Ergebnisqualität mit Mistral bisher mäßig
- Getestet mit `ollama run mistral`
### Betrifft
- `src/Services/KontierungsvorschlagService.php`
- `src/Services/LlmService.php`
- `src/Views/belege/detail.php`, `list.php`
- `Dockerfile`, `docker-compose.yml`
---
## 2026-03-30 – Fix: Lieferant-Eingabe in Metadaten-Leiste speichert korrekt
### Geändert
- `supplier-persistent-input`: Nach erfolgreichem AJAX-Speichern wird das Texteingabefeld durch den Inline-Trigger-Span ersetzt – visuelle Bestätigung, dass der Lieferant gespeichert wurde
- Fehlerbehandlung (`try/catch`) hinzugefügt: bei fehlgeschlagenem Speichern bleibt das Feld editierbar
- Das versteckte Metaformular (`supplier_name_input`) wird nach AJAX-Speichern synchronisiert, damit ein späteres Absenden des Metaformulars den Lieferanten nicht überschreibt
### Betrifft
- `src/Views/belege/detail.php`
---
## 2026-03-30 – Lieferant inline editierbar + KI-Lieferantenerkennung
### Geändert
- Lieferant in der Beleg-Info-Bar ist jetzt direkt inline editierbar (Klick auf ✎), wie Belegdatum und Rechnungsnr.
- KI-Kontierungsvorschlag extrahiert nun auch den Lieferantennamen aus dem Beleg
- Nach KI-Analyse: bekannter Lieferant wird automatisch eingetragen; unbekannter Lieferant öffnet das Lieferantenfeld sofort mit Fokus
- KI-Badge zeigt Lieferantenname an, unbekannte Lieferanten werden als „(neu – bitte prüfen)" markiert
- Validierung in `KontierungsvorschlagService` prüft `zahlungskonto_id` und `kategorie_konto_id` jetzt gegen die korrekte Kontenart statt gegen alle Konten
### Betrifft
- `src/Services/KontierungsvorschlagService.php`
- `src/Views/belege/detail.php`
- `public/static/js/app.js`
---
## 2026-03-29 – EÜR: Spaltenauswahl nach Kontenart statt Buchungstyp
### Geändert
- `EuerCalculator::calculate()` wertet `accounts.kontenart` aus statt `buchungstyp`
- `Betriebseinnahme` → Einnahmen-Spalte, `Betriebsausgabe` → Ausgaben-Spalte
- `Privat`, `Zahlungskonto`, `Verbindlichkeit` als Kategorie-Konto werden nicht in der EÜR ausgewiesen
- Vorzeichen von `betrag_netto` werden beibehalten (kein `abs()` mehr) – negative Beträge mindern die jeweilige Spalte
- Buchungen desselben Kontos werden unabhängig vom `buchungstyp` zu einem Eintrag zusammengefasst
### Betrifft
- `src/Services/EuerCalculator.php`
- `BERICHTE.md` (EÜR-Kapitel aktualisiert)
---
## 2026-03-29 – UStVA: kontonummernbasierte Kennzahlenberechnung
### Geändert
- `UstvaCalculator::calculate()` wertet ausschließlich `accounts.ustva_kz` des Kategorie-Kontos aus – `buchungstyp` und `steuerart` werden nicht mehr benötigt
- `UstvaCalculator::fetchBookingsForKz()` filtert über `k.ustva_kz IN (...)` statt über Buchungsfelder
- Kz 83 und 84 kommen jetzt direkt aus `betrag_ust` der Erlöskonten (statt `Kz81 × 19 %`)
- Kz 91 und 94 kommen direkt aus `betrag_ust` der ig.-Erwerb-Konten (statt abgeleitet)
### Betrifft
- `src/Services/UstvaCalculator.php`
- Migration `010_ustva_kz_konto_mapping.sql`
- `BERICHTE.md` (UStVA-Kapitel vollständig neu)
### Migration 010 – Kontenanpassungen
- Alle Betriebsausgabe-Konten mit `steuercode='VSt19'`: `ustva_kz` von `'66'` auf `'61'`
- Alle Betriebsausgabe-Konten mit `steuercode='VSt7'`: `ustva_kz` von `'66'` auf `'62'`
- Neue Konten: 3425 „Innergemeinschaftliche Erwerbe 19 %" (`ustva_kz='89'`), 3420 „Innergemeinschaftliche Erwerbe 7 %" (`ustva_kz='93'`)
---
## 2026-03-28 – 30% Eigenanteil-Kennzeichnung und Korrekturbuchungen
### Hinzugefügt
- Checkbox „30% Eigenanteil buchen" in der Buchungszeilen-Erfassung (zwischen Nettobetrag und Submit-Button)
- Neues Feld `eigenanteil_30 TINYINT(1)` in der `belege`-Tabelle (Migration `009_beleg_eigenanteil_30.sql`)
- Der Checkbox-Wert wird beim Speichern einer Buchung in die Beleg-Metadaten übernommen
- Bei gesetzter Checkbox werden automatisch zwei Korrekturbuchungen angelegt oder aktualisiert:
- 30% des Nettobetrags an Konto 8922 (nicht abzugsfähiger Eigenanteil), Zahlungskonto 1800
- 30% der errechneten USt an Konto 1776 (Vorsteuerkorrektur), Zahlungskonto 1800 – nur wenn USt-Satz > 0
- Im Testmodus: bestehende Korrekturbuchungen werden direkt überschrieben
- Im Produktivmodus: bestehende Korrekturbuchungen werden GoBD-konform storniert und neu angelegt
### Betrifft
- `src/Views/belege/detail.php`
- `src/Controllers/BookingController.php`
- `src/Models/Booking.php` (neue Methoden `upsertEigenanteil30`, `upsertKorrekturbuchung`)
- Migration: `009_beleg_eigenanteil_30.sql`
---
## 2026-03-27 – DATEV-konformer SKR03 Kontenrahmen (#11)
### Geändert
- **Kontenrahmen**: Bisherige ~80 Konten durch DATEV-konformen SKR03 ersetzt (Migration `008_skr03_vollstaendig.sql`)
- **Umfang**: 157 Systemkonten aus Klassen 0 (Anlagevermögen), 1 (Zahlungsmittel, Vorsteuer, USt, Privat), 2 (Eigenkapital), 4 (Betriebsausgaben), 8 (Betriebseinnahmen)
- **EÜR-Fokus**: Keine Forderungs- oder Verbindlichkeitskonten (nicht bilanzierend)
- **Kompatibilität**: Alte Konten bleiben mit `is_system = 0` erhalten – bestehende Buchungen sind nicht betroffen
### Betrifft
- Migration: `008_skr03_vollstaendig.sql`
---
## 2026-03-27 – Ausgangsrechnungen in Dashboard/EÜR/UStVA (#8)
### Behoben
- **Seed-Skript**: `scripts/seed-testdata.php` rief `Invoice::finalize()` auf, ohne anschließend Beleg und Buchungen anzulegen – Ausgangsrechnungen fehlten daher in allen Auswertungen
- **Ursache**: `Invoice::finalize()` (Model) setzt nur Status und Rechnungsnummer; die Buchungserstellung liegt in `InvoiceController::createBelegAndBookings()` und wurde im Seed nicht aufgerufen
- **Fix**: Seed-Skript legt nach `Invoice::finalize()` nun Beleg und zugehörige Buchungen an (analog zur Controller-Logik), sodass Ausgangsrechnungen korrekt in EÜR, UStVA und Dashboard erscheinen
### Betrifft
- Skript: `scripts/seed-testdata.php`
- Test: `tests/09_InvoiceToReportsTest.php` (neu – End-to-End-Test Ausgangsrechnung → EÜR/UStVA/Dashboard)
---
## 2026-03-26 – Lieferantenmanagement (#7)
### Neu
- **Lieferantenverwaltung**: Neue Seite `/suppliers` zum Anlegen, Bearbeiten und Löschen von Lieferanten
- **Lieferant am Beleg**: Im Metadaten-Formular der Belegdetailseite kann ein Lieferant per Dropdown/Freitexteingabe ausgewählt werden; unbekannte Namen werden beim Speichern automatisch angelegt
- **Datenbank**: Tabelle `suppliers` (id, name) und Fremdschlüssel `supplier_id` in `belege`
- **Navigation**: Neuer Menüpunkt "Lieferanten" im Bereich Belege & Buchungen
### Betrifft
- Migration: `007_suppliers.sql`
- Model: `Supplier.php` (neu), `Beleg.php` (JOIN + updateMeta)
- Controller: `SupplierController.php` (neu), `BelegController.php`
- Views: `suppliers/list.php`, `suppliers/form.php` (neu), `belege/detail.php`, `layout/nav.php`
- Routes: `public/index.php`
- Test: `08_LieferantTest.php`
---
## 2026-03-26 – EÜR-Buchungsmodell (Soll/Haben → Einnahme/Ausgabe)
### Geändert
- **Buchungsmodell**: Doppelte Buchführung (Soll-Konto/Haben-Konto) durch reines EÜR-Modell ersetzt – jede Buchung hat nun `buchungstyp` (Einnahme/Ausgabe), `kategorie_konto_id` (Was?) und `zahlungskonto_id` (Wie bezahlt?)
- **Kontenarten**: Umbenannt von Aktiv/Passiv/Aufwand/Ertrag/Neutral zu Betriebseinnahme/Betriebsausgabe/Zahlungskonto/Verbindlichkeit/Privat
- **EÜR-Berechnung**: Vereinfacht – gruppiert direkt nach `buchungstyp` statt komplexer Soll/Haben-Ableitung
- **UStVA-Berechnung**: Verwendet `buchungstyp` statt Kontenart-Ableitung für USt/VSt-Zuordnung
- **UI-Terminologie**: Alle Bezeichnungen in Views, Journal, Dashboard und Formularen von Soll/Haben auf Typ/Kategorie/Zahlungskonto umgestellt
- **CSV-Export**: Spalten an neues Modell angepasst (Typ, Kategorie-Kto., Zahlungskonto-Kto.)
### Betrifft
- Schema (`001_initial_schema.sql`), Seed (`002_seed_skr03.sql`)
- Models: `Booking.php`, `Account.php`
- Services: `EuerCalculator.php`, `UstvaCalculator.php`
- Controllers: `BookingController.php`, `AccountController.php`, `InvoiceController.php`
- Views: `dashboard.php`, `journal.php`, `detail.php`, `accounts/form.php`, `accounts/list.php`
- JavaScript: `app.js`
- Tests: 02, 04, 05 aktualisiert + neuer Test 06 (EÜR-Buchungsmodell)
---
## 2026-03-25 – Session-Authentifizierung & Benutzerverwaltung
### Features
- **Session-Authentifizierung**: HTTP Basic Auth durch cookie-basierte Session-Authentifizierung ersetzt; Sessions laufen nach 24 Stunden absolut ab
- **Benutzerverwaltung** (`/useradmin`): Benutzer anlegen, bearbeiten (Benutzername und Passwort), deaktivieren und löschen; Passwörter mit bcrypt gehasht
- **Ersteinrichtungsflow**: Beim ersten Start ohne Benutzer ermöglicht `/login` das Anlegen des ersten Administrator-Accounts
- **Abmelden-Button** in der Seitenleiste
### Geändert
- `AUTH_USER` und `AUTH_PASS` entfernt – Zugangsdaten werden ausschließlich in der Datenbank verwaltet
- Neue Datenbanktabelle `users` (Migration `005_users.sql`)
### Behoben
- Fehler in `BelegController` behoben
---
## 2026-03-25 – Sicherheit, UX-Verbesserungen & Bugfixes
### Sicherheit
- **Timing-sicherer Login**: Passwort- und Benutzernamevergleich auf `hash_equals()` umgestellt (verhindert Timing-Angriffe)
- **SVG-Upload gesperrt**: Logo-Upload akzeptiert nur noch PNG und JPEG – SVG wurde entfernt (XSS-Risiko)
- **CSV-Injection-Schutz**: Felder im Buchungsjournal-CSV-Export werden gegen Formula-Injection (`=`, `+`, `-`, `@`) abgesichert
### Features
- **Inline-Editing**: Belegdatum und Rechnungsnr. können direkt in der Beleg-Detailansicht per Klick bearbeitet werden (kein Seitenreload)
- **Belegart-Filter**: Neue Filteroption in der Belegliste zum Filtern nach Belegart (Eingangsrechnung, Ausgangsrechnung, Quittung etc.)
- **Favicon & App-Icon**: Favicon für Browser-Tabs, Apple Touch Icon und Web-App-Manifest eingebunden
### Robustheit
- **Datenbankfehler-Handling**: PDO-Exceptions in Invoice-, Booking- und BelegController werden separat abgefangen und geloggt statt als generische Fehler zu erscheinen
---
## 2026-03-24 – Initiales Release
### Features
- **Basis-System**: MVC-Framework, Routing, Authentifizierung, Datenbank-Migrations-System
- **Ausgangsrechnungen**: Erstellen, Verwalten und PDF-Export von Ausgangsrechnungen
- **EÜR-Vorbereitung**: Einnahmen-Überschuss-Rechnung als PDF-Export
- **UStVA-Vorbereitung**: Umsatzsteuer-Voranmeldung inkl. XML-Export für elster.de