diff --git a/.env.example b/.env.example index 9c0fad0..7dcf14c 100644 --- a/.env.example +++ b/.env.example @@ -4,9 +4,6 @@ DB_NAME=steuerkram DB_USER=steuerkram DB_PASS=steuerkram -# App -APP_SECRET=change-this-to-a-random-string - # KI-Kontierungsvorschlag (optional) # Wenn nichts konfiguriert wird, sucht die App automatisch nach einem lokalen Ollama auf localhost:11434. # diff --git a/CLAUDE.md b/CLAUDE.md index 7ee6bb9..9930a72 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -42,8 +42,10 @@ $this->assertNotEmpty($wert, 'Beschreibung'); | 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, Tech Stack oder die Installationsanleitung ändert | -| `BERICHTE.md` | Bei jeder Änderung an `EuerCalculator.php` oder `UstvaCalculator.php` (Zuordnungsregeln, neue Kennzahlen, geänderte Bedingungen) | +| `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 diff --git a/README.md b/README.md index 63cbf1f..98a04a9 100644 --- a/README.md +++ b/README.md @@ -1,396 +1,62 @@ # Steuerkram -Self-hosted Buchhaltungs- und Steueranwendung für deutsche Freiberufler und Kleinunternehmer. Entwickelt als datensouveräne Alternative zu SaaS-Diensten. +Self-hosted Buchhaltungs- und Steueranwendung für deutsche Freiberufler und Kleinunternehmer. Entwickelt als datensouveräne Alternative zu SaaS-Diensten – mit Unterstützung von [Claude Code](https://claude.ai/code). + +![PHP](https://img.shields.io/badge/PHP-777BB4?logo=php&logoColor=white) +![MariaDB](https://img.shields.io/badge/MariaDB-003545?logo=mariadb&logoColor=white) +![Docker](https://img.shields.io/badge/Docker-2496ED?logo=docker&logoColor=white) +[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE.md) + +--- ## Funktionsumfang -- **Kontenrahmen SKR03** – DATEV-konformer SKR03 mit 157 Systemkonten (Klassen 0, 1, 2, 4, 8); zugeschnitten auf EÜR-Buchhalter ohne Bilanzierung (CRUD) -- **Belegverwaltung** – Upload von PDF, PNG, TIFF und ZUGFeRD-XML; SHA-256 Integritätsprüfung; automatischer Weißrand-Zuschnitt bei JPEG/PNG via GD -- **Buchungen** – EÜR-Buchungsmodell (Einnahme/Ausgabe) mit Kategorie- und Zahlungskonto; GoBD-konforme Trigger verhindern nachträgliche Manipulation -- **Kundenverwaltung** – CRUD -- **Ausgangsrechnungen** – DIN 5008 Layout, PDF-Export via mPDF -- **UStVA** – Kennzahlen-Berechnung aus Buchungen, ELSTER XML-Export -- **EÜR** – Jahresauswertung mit PDF-Export -- **Einstellungen** – Firmendaten, Logo, ELSTER-Steuernummer -- **Benutzerverwaltung** – Benutzer anlegen, bearbeiten, deaktivieren und löschen; session-basierte Authentifizierung mit 24 h Timeout - -## Tech Stack - -Dieses Projekt wurden mit Unterstützung von [Claude Code](https://claude.ai/code) entwickelt. - -![PHP](https://img.shields.io/badge/PHP-777BB4?logo=php&logoColor=white) -![HTML](https://img.shields.io/badge/HTML-E34F26?logo=html5&logoColor=white) -![CSS](https://img.shields.io/badge/CSS-1572B6?logo=css3&logoColor=white) -![JavaScript](https://img.shields.io/badge/JavaScript-F7DF1E?logo=javascript&logoColor=black) - -| Schicht | Technologie | +| Bereich | Beschreibung | |---|---| -| Webserver | Apache 2.4 | -| Backend | PHP 8.3 (MVC, kein Framework) | -| Datenbank | MariaDB 11 | -| PDF-Generierung | mPDF 8.x | -| Frontend | HTML, CSS, JavaScript (keine Bibliotheken) | -| Deployment | Docker / Docker Compose | +| **Kontenrahmen SKR03** | DATEV-konform, 157 Konten, mit UStVA-Kennzahlen und EÜR-Zeilennummern | +| **Belegverwaltung** | PDF, PNG, TIFF, ZUGFeRD-XML; SHA-256 Integritätsprüfung; optionale KI-Kontierung | +| **Buchungen** | EÜR-Modell (Einnahme/Ausgabe); GoBD-konforme Trigger; Stornobuchungen; Journal | +| **Anlagevermögen** | Anlagenspiegel; automatische Anlagen-Nummerierung; Buchungsworkflow mit Asset-Verknüpfung | +| **Ausgangsrechnungen** | DIN 5008, PDF-Export via mPDF | +| **UStVA** | Kennzahlen-Berechnung, ELSTER XML-Export, Drill-down | +| **EÜR** | Jahresauswertung, PDF-Export, Drill-down | +| **Kundenverwaltung** | CRUD | +| **Einstellungen** | Firmendaten, Logo, ELSTER-Steuernummer, Nummernkreise | +| **Benutzerverwaltung** | Mehrbenutzer, Session-Auth mit 24 h Timeout | -## Voraussetzungen +--- -- [Docker](https://docs.docker.com/get-docker/) ≥ 24 -- [Docker Compose](https://docs.docker.com/compose/install/) ≥ 2.20 (in neueren Docker-Versionen enthalten) -- Git - -## Installation - -### Automatische Installation - -Das Script `install.sh` übernimmt die gesamte Installation: es erkennt die Linux-Distribution, installiert git, curl und Docker, klont das Repository in das gewünschte Verzeichnis und legt die `.env`-Datei an. - -Das Script muss **vor** dem Klonen des Repositories heruntergeladen werden – es erledigt das Klonen selbst: +## Schnellstart ```bash curl -fsSL https://codeberg.org/fds2610/Steuerkram/raw/branch/main/install.sh -o install.sh sudo bash install.sh -``` - -> Das Script muss als root ausgeführt werden, da es Pakete installiert und ggf. den Docker-Dienst aktiviert. Mit `sudo bash install.sh` funktioniert das ohne dauerhaft als root angemeldet zu sein. - -Nach der Installation die `.env`-Datei im gewählten Installationsverzeichnis anpassen (siehe Hinweis am Ende des Scripts) und anschließend die Container starten: - -```bash -cd /opt/Steuerkram # oder das gewählte Verzeichnis +cd /opt/Steuerkram docker compose up -d ``` ---- - -### Manuelle Installation - -#### 1. Repository klonen - -```bash -git clone steuerkram -cd steuerkram -``` - -#### 2. Konfiguration anpassen - -Kopiere `.env.example` nach `.env` und passe die Werte an: - -```bash -cp .env.example .env -``` - -```dotenv -DB_PASS=sicheres-passwort # Datenbankpasswort – in docker-compose.yml spiegeln -APP_SECRET=zufaelliger-string # Zufälligen langen String eintragen (für Sessions) -``` - -Außerdem in `docker-compose.yml` das `MARIADB_PASSWORD` im `db`-Service auf denselben Wert wie `DB_PASS` setzen. - -#### 3. Container starten - -```bash -docker compose up -d -``` - -Beim ersten Start werden automatisch: -- die Datenbank initialisiert (`migrations/001_initial_schema.sql`) -- der SKR03-Kontenrahmen eingespielt (`migrations/002_seed_skr03.sql`) -- das Docker-Image mit PHP-Abhängigkeiten (mPDF) gebaut - -#### 4. Anwendung aufrufen - -``` -http://localhost:8080 -``` - -Beim ersten Aufruf erscheint die **Ersteinrichtung**: dort den ersten Benutzer-Account anlegen. Danach ist die Anwendung passwortgeschützt. - -Weitere Benutzer können nach dem Login unter **Benutzer** in der Seitenleiste verwaltet werden. - -#### 5. KI-Beleganalyse einrichten (optional) - -Die KI-gestützte Belegerkennung ist standardmäßig deaktiviert. Sie kann unter **Einstellungen → KI-Kontierungsvorschlag** aktiviert werden, sobald ein LLM erreichbar ist. - -**Option A – Lokales Ollama** (kein API-Schlüssel nötig, aber hoher Ressourcenbedarf): - -```bash -ollama pull mistral # oder llama3.2 etc. -``` - -In `.env` ist keine weitere Konfiguration nötig – die App erkennt Ollama auf `localhost:11434` automatisch. Modell und URL können optional überschrieben werden: - -```dotenv -OLLAMA_URL=http://localhost:11434 -OLLAMA_MODEL=mistral -``` - -**Option B – Remote-API** (OpenAI-kompatibel, z. B. OpenAI, OpenRouter, LM Studio): - -```dotenv -LLM_API_URL=https://api.openai.com/v1 -LLM_API_KEY=sk-... -LLM_MODEL=gpt-4o-mini -``` - -> **Hinweis:** Die Ergebnisqualität hängt stark vom gewählten Modell ab. Tests mit `mistral` (lokal via Ollama) lieferten bisher mäßige Ergebnisse. - -#### 6. Firmendaten hinterlegen - -Nach dem ersten Login unter **Einstellungen** Firmendaten, Logo und ELSTER-Steuernummer eintragen. Diese Angaben erscheinen auf Ausgangsrechnungen und im EÜR-Export. - -## Port ändern - -Der Standard-Port ist `8080`. Um ihn zu ändern, passe in `docker-compose.yml` die `ports`-Zeile an: - -```yaml -ports: - - "9090:80" # Beispiel: Port 9090 auf dem Host -``` - -## Upload-Limit anpassen - -Die maximale Dateigröße für Beleganhänge ist auf **20 MB** voreingestellt (`php/uploads.ini`). Um das Limit zu ändern, die Werte in dieser Datei anpassen und den Container neu bauen: - -```ini -upload_max_filesize = 20M -post_max_size = 20M -``` - -```bash -docker compose up -d --build -``` - -## Belege automatisch aus Nextcloud importieren - -Belege können automatisch aus einem Nextcloud-Ordner via WebDAV importiert werden. Das CLI-Skript `scripts/nextcloud-import.php` liest alle unterstützten Dateien (PDF, JPEG, PNG, TIFF, XML) aus dem konfigurierten Ordner, legt sie als Belege an und verschiebt sie anschließend in einen Erledigte-Ordner (oder löscht sie, wenn kein Ziel-Ordner konfiguriert ist). - -### Konfiguration - -Die Zugangsdaten werden in der Steuerkram-UI gepflegt: **Einstellungen → Nextcloud-Belegimport**. - -Empfehlung: Ein Nextcloud-App-Passwort verwenden (Nextcloud → Einstellungen → Sicherheit → App-Passwörter). - -### Manueller Aufruf - -```bash -docker compose exec web php scripts/nextcloud-import.php -``` - -### Automatisch per Cron (auf dem Host) - -```cron -*/15 * * * * docker compose -f /opt/steuerkram/docker-compose.yml exec -T web \ - php scripts/nextcloud-import.php >> /var/log/steuerkram-import.log 2>&1 -``` - -Das Skript gibt beim Start eine Zusammenfassung aus (importiert / übersprungen / Fehler) und beendet sich mit Exit-Code 1 bei Fehlern. - -## Datenpersistenz - -| Pfad | Inhalt | -|---|---| -| `./data/mariadb/` | MariaDB-Datenbankdateien | -| Docker Volume `uploads` | Hochgeladene Belege | - -Beide Pfade liegen außerhalb des Containers und bleiben bei `docker compose down` erhalten. - -## Systemd-Service einrichten - -Damit Steuerkram automatisch beim Systemstart startet und als Dienst verwaltet werden kann, liegt im Repository die Datei `steuerkram.service`. - -#### 1. Service-Datei installieren - -```bash -sudo cp steuerkram.service /etc/systemd/system/steuerkram.service -``` - -Die Datei setzt `WorkingDirectory=/opt/Steuerkram` voraus. Falls das Repository in ein anderes Verzeichnis geklont wurde, den Pfad in der Service-Datei anpassen: - -```bash -sudo nano /etc/systemd/system/steuerkram.service -# WorkingDirectory= auf das tatsächliche Installationsverzeichnis setzen -``` - -#### 2. Service aktivieren und starten - -```bash -sudo systemctl daemon-reload -sudo systemctl enable steuerkram # automatisch beim Systemstart -sudo systemctl start steuerkram -``` - -#### 3. Status prüfen - -```bash -sudo systemctl status steuerkram -journalctl -u steuerkram -f # Live-Log inkl. Migrations-Ausgabe -``` - -#### Wichtige Service-Befehle - -| Befehl | Wirkung | -|---|---| -| `sudo systemctl start steuerkram` | Container starten | -| `sudo systemctl stop steuerkram` | Container stoppen | -| `sudo systemctl restart steuerkram` | Neustart (z.B. nach Update) | -| `sudo systemctl status steuerkram` | Aktueller Status | - -## Backup - -```bash -bash scripts/backup.sh -``` - -Das Skript erstellt ein Dump der Datenbank sowie ein Archiv der Uploads im Verzeichnis `backups/`. - -## Update - -Seit dem 11.04.2026 bin ich mit meiner Software für mich selber produktiv. Ab diesem Zeitpunkt sollten keine breaking changes mehr vorkommen. - -Datenbankmigrationen werden **automatisch** beim nächsten Container-Start eingespielt – kein manueller Eingriff nötig. Das Script `scripts/migrate.php` prüft beim Start, welche Migrations-Dateien noch nicht angewendet wurden, und führt sie der Reihe nach aus. - -### Vollständige Update-Anleitung - -Die folgenden Schritte sichern einen vollständigen und sicheren Update-Ablauf. Alle Befehle im Steuerkram-Verzeichnis ausführen. - -#### Schritt 1 – Datenbank sichern - -Vor jedem Update ein Backup erstellen: - -```bash -bash scripts/backup.sh -``` - -Das Backup landet in `backups/`. Alternativ manuell: - -```bash -docker compose exec db mariadb-dump -u steuerkram -psteuerkram steuerkram \ - > ~/steuerkram_backup_$(date +%Y%m%d_%H%M).sql -``` - -#### Schritt 2 – Aktuellen Code holen - -```bash -git pull -``` - -Prüfen, ob neue Migrations-Dateien enthalten sind: - -```bash -git log --oneline -5 -``` - -#### Schritt 3 – Container neu bauen und starten - -```bash -docker compose up -d --build -``` - -Dieser Befehl baut das PHP-Image neu (falls sich `Dockerfile` oder `composer.json` geändert haben) und startet alle Container. Der Web-Container führt beim Start automatisch `scripts/migrate.php` aus. - -#### Schritt 4 – Migrationen prüfen - -```bash -docker compose logs web | grep migrate -``` - -Erwartete Ausgabe bei erfolgreicher Migration: - -``` -[migrate] Starte Migrations-Prüfung... -[migrate] Führe aus: NNN_beschreibung.sql -[migrate] N Migration(en) erfolgreich angewendet. -``` - -Oder wenn alles bereits aktuell ist: - -``` -[migrate] Datenbank ist aktuell – keine Migrationen notwendig. -``` - -Falls die Ausgabe einen Fehler enthält, das Backup aus Schritt 1 einspielen: - -```bash -docker compose exec -T db mariadb -u steuerkram -psteuerkram steuerkram \ - < ~/steuerkram_backup_YYYYMMDD_HHMM.sql -``` - -#### Schritt 5 – Anwendung prüfen - -```bash -docker compose ps # alle Container müssen "running" sein -docker compose logs web # keine PHP-Fehler oder Exceptions -``` - -Anschließend im Browser unter `http://localhost:8080` (oder dem konfigurierten Port) einloggen und stichprobenartig Belege und Buchungen prüfen. +Anwendung aufrufen: `http://localhost:8080` --- -#### Ohne Systemd-Service (Kurzform) +## Dokumentation -```bash -bash scripts/backup.sh -git pull -docker compose up -d --build -docker compose logs web | grep migrate -``` +| Dokument | Inhalt | +|---|---| +| [Installation](docs/installation.md) | Docker-Setup, Systemd-Service, Port, Upload-Limit | +| [Konfiguration](docs/konfiguration.md) | Firmendaten, Nummernkreise, KI-Beleganalyse, Nextcloud-Import | +| [Funktionen](docs/funktionen.md) | Alle Features im Detail | +| [Anlagevermögen](docs/anlagevermögen.md) | Anlagenspiegel, Buchungsworkflow, Nummernkreis | +| [Update & Backup](docs/update-backup.md) | Update-Anleitung, Backup, Datenpersistenz | +| [Automatisierung](docs/automatisierung.md) | Automatisierungsagent, Ideenagent, Support | +| [Datenbankstruktur](docs/datenbankstruktur.md) | Tabellen, Spalten, Fremdschlüssel, GoBD-Mechanismen | +| [Berechnungslogik](docs/berechnungslogik.md) | UStVA-Kennzahlen und EÜR-Zuordnungsregeln | +| [Kontenrahmen](docs/kontenrahmen.md) | Vollständige SKR03-Kontenliste | -#### Mit Systemd-Service (Kurzform) - -```bash -bash scripts/backup.sh -git pull -docker compose build -sudo systemctl restart steuerkram -journalctl -u steuerkram | grep migrate -``` +--- ## GoBD-Hinweise - Gebuchte Sätze können nicht geändert oder gelöscht werden (MariaDB `BEFORE UPDATE/DELETE` Trigger). - Korrekturen erfolgen ausschließlich über Stornobuchungen. - Jeder hochgeladene Beleg erhält einen SHA-256 Hash zur Integritätsprüfung. - -## Kontenrahmen SKR03 - -Der enthaltene Kontenrahmen basiert auf dem DATEV SKR03 2026, zugeschnitten auf EÜR-Buchhalter (IST-Versteuerung). Alle Konten sind mit geprüften UStVA-Kennzahlen (ELSTER) und EÜR-Zeilennummern (Anlage EÜR 2024) hinterlegt. - -Vollständige Dokumentation: [`Kontenrahmen.md`](Kontenrahmen.md) - -## Berechnungslogik UStVA und EÜR - -Detaillierte Informationen darüber, welche Buchungsattribute und Konten in welche Positionen der UStVA (ELSTER-Kennzahlen) bzw. der EÜR einfließen, sind in [`BERICHTE.md`](BERICHTE.md) dokumentiert. - -## Support - -Issues können im [Codeberg-Repo](https://codeberg.org/fds2610/Steuerkram/issues) gemeldet werden. Mit dem Label **`automate`** werden sie automatisch verarbeitet. - -### Automatisierter Agent - -Ein stündlich per Cron gestarteter Agent (Claude CLI) verarbeitet Issues mit dem Label `automate` nacheinander – mit Lockfile-Schutz gegen Parallelläufe. Mehrere offene Tickets werden in einer Sitzung sequenziell abgearbeitet. - -#### Ablauf je Ticket - -1. Issue-Body und alle Kommentare laden (inkl. Diskussionsergebnisse früherer Rückfragen) -2. Relevante Codebase-Dateien lesen -3. **Klarheitscheck**: Sind die Anforderungen eindeutig? Falls nicht → Rückfragen als Kommentar posten, `automate`-Label entfernen, warten -4. Analyse mit Claude Opus (Ursache, Lösungsplan, Risiken) -5. Lösung implementieren -6. Tests ausführen (`php tests/run.php`), Fehler beheben -7. Commit & Push: `Fix #NR: Kurzbeschreibung` -8. Kommentar mit Zusammenfassung im Issue posten -9. Labels aktualisieren, Issue offen lassen - -#### Label-Ausgänge - -| Ergebnis | `automate` | gesetztes Label | -|---|---|---| -| Erfolgreich umgesetzt | entfernt | `testing` | -| Unklar / Rückfrage offen | entfernt | *(keins)* | -| Zu riskant / abgelehnt | entfernt | `abgelehnt` | - -## Lizenz - -[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) - -Dieses Projekt steht unter der MIT-Lizenz – siehe [LICENSE.md](LICENSE.md) für Details. diff --git a/docs/anlagevermögen.md b/docs/anlagevermögen.md new file mode 100644 index 0000000..e196242 --- /dev/null +++ b/docs/anlagevermögen.md @@ -0,0 +1,69 @@ +# Anlagevermögen + +## Überblick + +Der Bereich **Betriebsvermögen → Anlagevermögen** führt einen Anlagenspiegel für abnutzbare Wirtschaftsgüter. Jedes Wirtschaftsgut ist mit einem Anlagevermögenskonto des SKR03 verknüpft und erhält eine eindeutige Anlagen-Nummer. + +--- + +## Anlagen-Nummernkreis + +Anlagen-Nummern werden automatisch im Format `{Präfix}-{Jahr}-{laufende Nummer}` vergeben, z.B. `ANL-2026-0001`. + +Der Präfix ist unter **Einstellungen → Nummernkreise → Anlagen-Präfix** konfigurierbar (Standard: `ANL`). + +--- + +## Anlagenspiegel + +Die Liste unter **Anlagevermögen** zeigt alle erfassten Wirtschaftsgüter mit folgenden Spalten: + +| Spalte | Beschreibung | +|---|---| +| Nr. | Anlagen-Nummer (automatisch vergeben) | +| Anschaffungsdatum | Datum der Anschaffung | +| Anlageart | Verknüpftes SKR03-Anlagevermögenskonto | +| Wirtschaftsgut | Bezeichnung | +| Anschaffungskosten | Nettobetrag der Anschaffung | +| Zugänge | Nachträgliche Anschaffungskosten | +| Abgänge | Wertminderungen durch Verkauf/Abgang | +| Abschreibungen | AfA des laufenden Wirtschaftsjahres | + +Über das Bleistift-Symbol gelangt man zur Bearbeitungsmaske, über das Papierkorb-Symbol kann ein Wirtschaftsgut gelöscht werden (nur wenn keine Buchungen verknüpft sind). + +--- + +## Buchungsworkflow + +Wird beim Erfassen einer Buchung ein **Anlagevermögenskonto** als Kategorie gewählt, öffnet sich automatisch ein Dialog: + +**Option 1 – Neues Wirtschaftsgut anlegen** +Das System legt automatisch ein neues Anlagegut an: +- Bezeichnung ← Buchungstext +- Anschaffungsdatum ← Buchungsdatum +- Anschaffungskosten ← Nettobetrag + +**Option 2 – Bestehendes Wirtschaftsgut auswählen** +Es wird die Liste der Wirtschaftsgüter angezeigt, die mit dem gewählten Anlagevermögenskonto verknüpft sind. Nach der Auswahl wird die Buchung mit dem vorhandenen Anlagegut verknüpft. + +In beiden Fällen wird die `asset_id` in der Buchungszeile gespeichert. + +--- + +## Anzeige in Buchungstabellen + +In allen Buchungsübersichten (Beleg-Detail, Journal, UStVA-Drill-down, EÜR-Drill-down) erscheint eine **Anlage**-Spalte. Sie zeigt die Anlagen-Nummer als direkten Link zur Bearbeitungsmaske des Wirtschaftsguts. Falls für ein älteres Wirtschaftsgut keine Anlagen-Nummer hinterlegt ist, wird stattdessen die Bezeichnung angezeigt. + +--- + +## Anlagen-Stammdaten bearbeiten + +Folgende Felder können nachträglich angepasst werden: + +- Bezeichnung +- Anlageart (SKR03-Konto) +- Anschaffungsdatum und -kosten +- Zugänge und Abgänge +- Abschreibung laufendes Jahr +- Nutzungsdauer (in Jahren) +- Notizen diff --git a/docs/automatisierung.md b/docs/automatisierung.md new file mode 100644 index 0000000..995543c --- /dev/null +++ b/docs/automatisierung.md @@ -0,0 +1,50 @@ +# Automatisierung & Support + +## Automatisierungsagent + +Issues mit dem Label **`automate`** im [Codeberg-Repo](https://codeberg.org/fds2610/Steuerkram/issues) werden automatisch durch einen stündlich per Cron gestarteten Agenten (Claude CLI) verarbeitet. Mehrere offene Tickets werden in einer Sitzung sequenziell abgearbeitet. Ein Lockfile verhindert parallele Läufe. + +### Ablauf je Ticket + +1. Issue-Body und alle Kommentare laden (inkl. Diskussionsergebnisse früherer Rückfragen) +2. Relevante Codebase-Dateien lesen +3. **Klarheitscheck**: Sind die Anforderungen eindeutig? Falls nicht → Rückfragen als Kommentar posten, `automate`-Label entfernen, warten +4. Analyse (Ursache, Lösungsplan, Risiken) +5. Lösung implementieren +6. Tests ausführen (`php tests/run.php`), Fehler beheben +7. Commit & Push: `Fix #NR: Kurzbeschreibung` +8. Kommentar mit Zusammenfassung im Issue posten +9. Labels aktualisieren, Issue offen lassen + +### Label-Ausgänge + +| Ergebnis | `automate` | gesetztes Label | +|---|---|---| +| Erfolgreich umgesetzt | entfernt | `testing` | +| Unklar / Rückfrage offen | entfernt | *(keins)* | +| Zu riskant / abgelehnt | entfernt | `abgelehnt` | + +### Manuell starten + +```bash +/home/fds/projects/Steuerkram/.claude/automate-run.sh +``` + +--- + +## Ideenagent + +Ein separater Agent sammelt und bewertet Ideen zur Weiterentwicklung. Er läuft ebenfalls per Cron. + +```bash +/home/fds/projects/Steuerkram/.claude/ideenagent-run.sh +``` + +--- + +## Support + +Issues können im [Codeberg-Repo](https://codeberg.org/fds2610/Steuerkram/issues) gemeldet werden. + +- Mit dem Label **`automate`** werden sie automatisch verarbeitet (siehe oben). +- Ohne Label landen sie in der normalen Issue-Queue. diff --git a/BERICHTE.md b/docs/berechnungslogik.md similarity index 100% rename from BERICHTE.md rename to docs/berechnungslogik.md diff --git a/docs/datenbankstruktur.md b/docs/datenbankstruktur.md new file mode 100644 index 0000000..b823d68 --- /dev/null +++ b/docs/datenbankstruktur.md @@ -0,0 +1,284 @@ +# Datenbankstruktur + +MariaDB 11, Engine InnoDB, Zeichensatz `utf8mb4_unicode_ci`. Das Schema wird durch nummerierte Migrations-Dateien in `migrations/` verwaltet und beim Container-Start automatisch eingespielt. + +--- + +## Übersicht + +| Tabelle | Zweck | +|---|---| +| `fiscal_years` | Geschäftsjahre | +| `accounts` | Kontenrahmen SKR03 | +| `company_settings` | Firmendaten und Einstellungen (Singleton) | +| `customers` | Kundenstammdaten | +| `belege` | Belegdokumente | +| `bookings` | Buchungszeilen (EÜR-Journal) | +| `invoices` | Ausgangsrechnungen | +| `invoice_lines` | Rechnungspositionen | +| `users` | Benutzerkonten | +| `ustva_submissions` | UStVA-Einreichungen | +| `audit_log` | GoBD-Audit-Log | +| `schema_migrations` | Protokoll angewendeter Migrationen | + +--- + +## Tabellen im Detail + +### `fiscal_years` – Geschäftsjahre + +| Spalte | Typ | Beschreibung | +|---|---|---| +| `id` | INT UNSIGNED PK | | +| `year` | SMALLINT UNSIGNED UNIQUE | Jahreszahl (z.B. 2026) | +| `start_date` | DATE | Beginn des Geschäftsjahres | +| `end_date` | DATE | Ende des Geschäftsjahres | +| `locked` | TINYINT(1) | `1` = Jahr abgeschlossen, keine neuen Buchungen möglich | +| `created_at` | DATETIME | | + +--- + +### `accounts` – Kontenrahmen SKR03 + +| Spalte | Typ | Beschreibung | +|---|---|---| +| `id` | INT UNSIGNED PK | | +| `kontonummer` | VARCHAR(10) UNIQUE | DATEV-Kontonummer | +| `bezeichnung` | VARCHAR(255) | Kontobezeichnung | +| `kontenart` | ENUM | `Betriebseinnahme`, `Betriebsausgabe`, `Anlagevermögen`, `Zahlungskonto`, `Verbindlichkeit`, `Privat` | +| `beschreibung` | TEXT | Erläuterungstext | +| `steuercode` | VARCHAR(20) | `USt19`, `USt7`, `VSt19`, `VSt7` oder leer | +| `ustva_kz` | VARCHAR(10) | ELSTER-Kennzahl (z.B. `81`, `35`) | +| `euer_zeile` | VARCHAR(5) | Zeilennummer Anlage EÜR (z.B. `14`, `51`) | +| `is_system` | TINYINT(1) | `1` = SKR03-Standardkonto | +| `aktiv` | TINYINT(1) | `0` = deaktiviert | +| `created_at` / `updated_at` | DATETIME | | + +--- + +### `company_settings` – Firmendaten (Singleton, id=1) + +| Spalte | Typ | Beschreibung | +|---|---|---| +| `id` | INT UNSIGNED PK | Immer `1` | +| `firma` | VARCHAR(255) | Firmenname | +| `inhaber` | VARCHAR(255) | Inhabername | +| `strasse`, `plz`, `ort` | VARCHAR | Adresse | +| `telefon`, `email`, `website` | VARCHAR | Kontaktdaten | +| `ust_id` | VARCHAR(20) | Umsatzsteuer-ID (DE...) | +| `steuernummer` | VARCHAR(30) | Steuernummer beim Finanzamt | +| `finanzamt` | VARCHAR(255) | Zuständiges Finanzamt | +| `iban`, `bic`, `bank_name` | VARCHAR | Bankverbindung | +| `logo_path` | VARCHAR(500) | Pfad zum Logo-PNG | +| `ustva_steuernummer` | VARCHAR(20) | ELSTER-Format: `FFFBBBBBBUUUUP` | +| `ustva_bundesland` | VARCHAR(2) | Bundesland-Kürzel (z.B. `BY`) | +| `invoice_nr_prefix` | VARCHAR(10) | Präfix Rechnungsnummer (Standard: `AR`) | +| `invoice_nr_next` | INT UNSIGNED | Nächste Rechnungsnummer | +| `invoice_nr_year` | SMALLINT UNSIGNED | Jahresstand des Rechnungszählers | +| `beleg_nr_prefix` | VARCHAR(10) | Präfix Belegnummer (Standard: `BLG`) | +| `beleg_nr_next` | INT UNSIGNED | Nächste Belegnummer | +| `anlage_nr_prefix` | VARCHAR(10) | Präfix Anlagen-Nummer (Standard: `ANL`) | +| `anlage_nr_next` | INT UNSIGNED | Nächste Anlagen-Nummer | +| `payment_days_default` | INT UNSIGNED | Standard-Zahlungsziel in Tagen | +| `ki_vorschlag_aktiv` | TINYINT(1) | KI-Kontierungsvorschlag aktiviert | +| `nextcloud_url` | VARCHAR(500) | WebDAV-URL der Nextcloud-Instanz | +| `nextcloud_user` | VARCHAR(255) | Nextcloud-Benutzername | +| `nextcloud_pass` | VARCHAR(500) | Nextcloud-Passwort / App-Passwort | +| `nextcloud_folder` | VARCHAR(500) | Import-Quellordner (Standard: `/Belege/Import`) | +| `nextcloud_done` | VARCHAR(500) | Zielordner nach erfolgreichem Import | +| `updated_at` | DATETIME | | + +--- + +### `customers` – Kundenstammdaten + +| Spalte | Typ | Beschreibung | +|---|---|---| +| `id` | INT UNSIGNED PK | | +| `kundennummer` | VARCHAR(20) UNIQUE | | +| `firma` | VARCHAR(255) | Firmenname (optional) | +| `anrede`, `vorname`, `nachname` | VARCHAR | Ansprechpartner | +| `strasse`, `plz`, `ort`, `land` | VARCHAR | Adresse | +| `email`, `telefon` | VARCHAR | Kontakt | +| `ust_id` | VARCHAR(20) | USt-ID des Kunden (B2B) | +| `steuernummer` | VARCHAR(30) | | +| `payment_days` | INT UNSIGNED | Zahlungsziel in Tagen | +| `notes` | TEXT | Interne Notizen | +| `archived` | TINYINT(1) | `1` = archiviert | +| `created_at` / `updated_at` | DATETIME | | + +--- + +### `belege` – Belegdokumente + +| Spalte | Typ | Beschreibung | +|---|---|---| +| `id` | INT UNSIGNED PK | | +| `beleg_nr` | VARCHAR(30) UNIQUE | Automatisch vergeben (z.B. `BLG-2026-0001`) | +| `filename` | VARCHAR(500) | Originaldateiname | +| `storage_path` | VARCHAR(500) | Pfad unter `public/static/uploads/` | +| `mime_type` | VARCHAR(100) | MIME-Typ der Datei | +| `file_hash` | VARCHAR(64) | SHA-256 für GoBD-Integritätsprüfung | +| `beleg_datum` | DATE | Datum auf dem Dokument | +| `rechnungs_nr` | VARCHAR(100) | Rechnungsnummer des Lieferanten | +| `betrag_brutto` | DECIMAL(12,2) | Bruttobetrag laut Dokument | +| `belegtyp` | ENUM | `Eingangsrechnung`, `Ausgangsrechnung`, `Quittung`, `Kontoauszug`, `Sonstiges` | +| `notes` | TEXT | Interne Notizen | +| `bewirtungsgrund` | TEXT | Anlass der Bewirtung (§ 4 Abs. 5 Nr. 2 EStG) | +| `teilnehmerliste` | TEXT | Teilnehmer der Bewirtung | +| `eigenanteil_30` | TINYINT(1) | `1` = 30%-Eigenanteil beim Buchen markiert | +| `ki_vorschlag` | JSON | KI-Kontierungsvorschlag | +| `ki_vorschlag_at` | TIMESTAMP | Zeitpunkt der KI-Analyse | +| `beleg_text` | MEDIUMTEXT | Extrahierter OCR-Text | +| `locked` | TINYINT(1) | GoBD: mit Geschäftsjahr gesperrt | +| `fiscal_year_id` | INT UNSIGNED FK | → `fiscal_years.id` | +| `created_at` / `updated_at` | DATETIME | | + +--- + +### `bookings` – Buchungszeilen (EÜR-Journal) + +GoBD-gesichert: MariaDB-Trigger verhindern Änderungen und Löschungen gesperrter Buchungen. + +| Spalte | Typ | Beschreibung | +|---|---|---| +| `id` | INT UNSIGNED PK | | +| `buchungs_nr` | VARCHAR(30) UNIQUE | Automatisch vergeben | +| `beleg_id` | INT UNSIGNED FK | → `belege.id` | +| `asset_id` | INT UNSIGNED FK | → `assets.id` (nur bei Anlagevermögen-Buchungen) | +| `buchungs_datum` | DATE | | +| `buchungstyp` | ENUM | `Einnahme`, `Ausgabe` | +| `kategorie_konto_id` | INT UNSIGNED FK | → `accounts.id` (Was?) | +| `zahlungskonto_id` | INT UNSIGNED FK | → `accounts.id` (Wie bezahlt?) | +| `betrag_netto` | DECIMAL(12,2) | | +| `ust_satz` | DECIMAL(5,2) | Steuersatz in % (0, 7, 19) | +| `steuerart` | ENUM | `normal`, `ig_lieferung`, `export`, `ig_erwerb_19`, `ig_erwerb_7` | +| `betrag_ust` | DECIMAL(12,2) | | +| `betrag_brutto` | DECIMAL(12,2) | | +| `buchungstext` | VARCHAR(500) | | +| `is_reversal` | TINYINT(1) | `1` = Stornobuchung | +| `reversed_by` | INT UNSIGNED | ID der zugehörigen Stornobuchung | +| `locked` | TINYINT(1) | GoBD: unveränderlich | +| `fiscal_year_id` | INT UNSIGNED FK | → `fiscal_years.id` | +| `created_at` | DATETIME | | + +> **Hinweis:** Die Spalte `asset_id` existiert nur nach Migration 030 (Feature-Branch `Betriebsvermögen`). + +--- + +### `assets` – Anlagegüter (Wirtschaftsgüter) + +> Diese Tabelle existiert nur nach Migration 028 (Feature-Branch `Betriebsvermögen`). + +| Spalte | Typ | Beschreibung | +|---|---|---| +| `id` | INT UNSIGNED PK | | +| `lfd_nr` | INT UNSIGNED | Laufende Nummer | +| `anlage_nr` | VARCHAR(30) | Anlagen-Nummer (z.B. `ANL-2026-0001`) | +| `bezeichnung` | VARCHAR(255) | Bezeichnung des Wirtschaftsguts | +| `account_id` | INT UNSIGNED FK | → `accounts.id` (Anlageart) | +| `anschaffungsdatum` | DATE | | +| `anschaffungskosten` | DECIMAL(12,2) | Netto-Anschaffungskosten | +| `zugaenge` | DECIMAL(12,2) | Nachträgliche Anschaffungskosten | +| `abgaenge` | DECIMAL(12,2) | Wertminderungen | +| `abschreibung_jahr` | DECIMAL(12,2) | AfA laufendes Wirtschaftsjahr | +| `nutzungsdauer` | INT UNSIGNED | Nutzungsdauer in Jahren | +| `notizen` | TEXT | | +| `fiscal_year_id` | INT UNSIGNED FK | → `fiscal_years.id` | +| `created_at` / `updated_at` | DATETIME | | + +--- + +### `invoices` – Ausgangsrechnungen + +| Spalte | Typ | Beschreibung | +|---|---|---| +| `id` | INT UNSIGNED PK | | +| `rechnungs_nr` | VARCHAR(30) UNIQUE | `NULL` solange Entwurf | +| `customer_id` | INT UNSIGNED FK | → `customers.id` | +| `betreff` | VARCHAR(500) | | +| `leistungszeitraum_von` / `_bis` | DATE | | +| `rechnungs_datum` | DATE | | +| `faellig_datum` | DATE | | +| `status` | ENUM | `Entwurf`, `Gesendet`, `Bezahlt`, `Storniert` | +| `betrag_netto` / `_ust` / `_brutto` | DECIMAL(12,2) | Summenspalten | +| `beleg_id` | INT UNSIGNED FK | → `belege.id` | +| `notes` | TEXT | | +| `fiscal_year_id` | INT UNSIGNED FK | → `fiscal_years.id` | +| `created_at` / `updated_at` | DATETIME | | + +--- + +### `invoice_lines` – Rechnungspositionen + +| Spalte | Typ | Beschreibung | +|---|---|---| +| `id` | INT UNSIGNED PK | | +| `invoice_id` | INT UNSIGNED FK | → `invoices.id` (CASCADE DELETE) | +| `position` | SMALLINT UNSIGNED | Positionsnummer | +| `bezeichnung` | VARCHAR(500) | | +| `menge` | DECIMAL(10,3) | | +| `einheit` | VARCHAR(20) | z.B. `Stk.`, `h` | +| `einzelpreis` | DECIMAL(12,2) | Netto-Einzelpreis | +| `ust_satz` | DECIMAL(5,2) | | +| `betrag_netto` / `_ust` / `_brutto` | DECIMAL(12,2) | | + +--- + +### `users` – Benutzerkonten + +| Spalte | Typ | Beschreibung | +|---|---|---| +| `id` | INT UNSIGNED PK | | +| `username` | VARCHAR(64) UNIQUE | | +| `password_hash` | VARCHAR(255) | bcrypt | +| `active` | TINYINT(1) | `0` = deaktiviert | +| `created_at` | DATETIME | | + +--- + +### `ustva_submissions` – UStVA-Einreichungen + +| Spalte | Typ | Beschreibung | +|---|---|---| +| `id` | INT UNSIGNED PK | | +| `year` | SMALLINT UNSIGNED | Meldejahr | +| `month` | TINYINT UNSIGNED | Meldemonat (1–12) | +| `xml_content` | MEDIUMTEXT | Generiertes ELSTER-XML | +| `field_values` | JSON | Kennzahlen als JSON | +| `submitted_at` | DATETIME | `NULL` = noch nicht eingereicht | +| `status` | ENUM | `Erstellt`, `Eingereicht` | +| `created_at` | DATETIME | | + +UNIQUE KEY auf `(year, month)`. + +--- + +### `audit_log` – GoBD-Audit-Log + +| Spalte | Typ | Beschreibung | +|---|---|---| +| `id` | BIGINT UNSIGNED PK | | +| `timestamp` | DATETIME | | +| `table_name` | VARCHAR(50) | Betroffene Tabelle | +| `record_id` | INT UNSIGNED | Betroffener Datensatz | +| `action` | ENUM | `INSERT`, `UPDATE`, `DELETE`, `LOCK` | +| `old_values` | JSON | Werte vor der Änderung | +| `new_values` | JSON | Werte nach der Änderung | +| `ip_address` | VARCHAR(45) | | +| `user_agent` | VARCHAR(500) | | + +--- + +## GoBD-Mechanismen + +- **Trigger `bookings_before_update` / `bookings_before_delete`**: Jeder Versuch, eine gesperrte Buchung (`locked = 1`) zu ändern oder zu löschen, wird mit einem SQL-Fehler abgewiesen. +- **`file_hash` in `belege`**: SHA-256-Prüfsumme jedes Belegdokuments; erlaubt nachträgliche Integritätsprüfung. +- **`audit_log`**: Protokolliert alle Datenbankänderungen mit alten und neuen Werten. +- **Stornobuchungen** statt Löschungen: Korrekturen erzeugen eine Gegenbuchung, die ursprüngliche Buchung bleibt erhalten. + +--- + +## Migrationen + +Schemaänderungen kommen ausschließlich als nummerierte SQL-Dateien in `migrations/` und werden vom Script `scripts/migrate.php` beim Container-Start automatisch eingespielt. Die Tabelle `schema_migrations` protokolliert, welche Dateien bereits angewendet wurden. diff --git a/docs/funktionen.md b/docs/funktionen.md new file mode 100644 index 0000000..35e30ca --- /dev/null +++ b/docs/funktionen.md @@ -0,0 +1,91 @@ +# Funktionen + +## Kontenrahmen SKR03 + +DATEV-konformer SKR03 2026 mit 157 Systemkonten (Klassen 0, 1, 2, 4, 8), zugeschnitten auf EÜR-Buchhalter ohne Bilanzierungspflicht. Alle Konten sind mit geprüften UStVA-Kennzahlen (ELSTER) und EÜR-Zeilennummern hinterlegt. + +Vollständige Kontenliste: [kontenrahmen.md](kontenrahmen.md) + +--- + +## Belegverwaltung + +- Upload von PDF, PNG, JPEG, TIFF und ZUGFeRD-XML +- SHA-256 Integritätsprüfung je Datei +- Automatischer Weißrand-Zuschnitt bei JPEG/PNG via GD +- KI-gestützter Kontierungsvorschlag (optional, siehe [konfiguration.md](konfiguration.md)) +- Nextcloud-Import via WebDAV (optional) +- Belege können nicht gelöscht werden, solange Buchungen darauf verweisen (GoBD) + +--- + +## Buchungen + +EÜR-Buchungsmodell (Einnahme/Ausgabe) mit Kategorie- und Zahlungskonto. + +- **Steuerarten** für Auslandsumsätze: Inland, steuerfreie ig. Lieferung (Kz 41), steuerfreie Ausfuhr (Kz 44), ig. Erwerb 19 % (Kz 89/91), ig. Erwerb 7 % (Kz 93/94) +- **Bewirtungsausgaben**: automatischer Split 70 %/30 % bei Konten 4650, 4651, 4652 +- **Eigenanteil** (30 %) bei Telefon/Internet (Konto 4940): optional zubuchbar +- **Stornobuchungen**: GoBD-konforme Gegenbuchung statt Löschung +- **GoBD-Schutz**: MariaDB `BEFORE UPDATE/DELETE`-Trigger verhindern nachträgliche Manipulation gebuchter Sätze +- **Journal**: gefilterte Übersicht aller Buchungen mit CSV-Export + +--- + +## Anlagevermögen + +Anlagenspiegel für abnutzbare Wirtschaftsgüter – siehe [anlagevermögen.md](anlagevermögen.md). + +--- + +## Kundenverwaltung + +CRUD für Kundenstammdaten. Kunden werden beim Erstellen von Ausgangsrechnungen ausgewählt. + +--- + +## Ausgangsrechnungen + +- DIN 5008-konformes Layout +- PDF-Export via mPDF +- Automatische Rechnungsnummer (konfiguriertes Präfix + Jahrgang + laufende Nummer) +- Direkte Verknüpfung mit Belegverwaltung + +--- + +## UStVA (Umsatzsteuervoranmeldung) + +- Monatliche Kennzahlen-Berechnung aus Buchungen (Kz 81, 83, 86, 84, 35, 41, 44, 61, 62, 67, 66, 69) +- ELSTER XML-Export +- Drill-down: Klick auf eine Kennzahl zeigt alle zugehörigen Buchungszeilen + +Zuordnungsregeln: [berechnungslogik.md](berechnungslogik.md) + +--- + +## EÜR (Einnahmen-Überschuss-Rechnung) + +- Jahresauswertung: Einnahmen, Ausgaben, Gewinn je Kategorie-Konto +- Nicht abzugsfähige Betriebsausgaben (§ 4 Abs. 5 EStG, EÜR-Zeile 51) in separatem Abschnitt +- PDF-Export +- Drill-down: Klick auf ein Konto zeigt alle zugehörigen Buchungszeilen + +Zuordnungsregeln: [berechnungslogik.md](berechnungslogik.md) + +--- + +## Einstellungen + +- Firmendaten und Logo (erscheinen auf Rechnungen und im EÜR/UStVA-Export) +- ELSTER-Steuernummer +- Nummernkreis-Präfixe für Belege, Rechnungen und Anlagen +- KI-Kontierungsvorschlag (Ollama oder OpenAI-kompatible API) +- Nextcloud-Belegimport (WebDAV-Zugangsdaten) + +--- + +## Benutzerverwaltung + +- Benutzer anlegen, bearbeiten, deaktivieren und löschen +- Session-basierte Authentifizierung mit 24 h Timeout +- Erste Benutzeranlage über Ersteinrichtungs-Dialog beim ersten Aufruf diff --git a/docs/installation.md b/docs/installation.md new file mode 100644 index 0000000..f4555c9 --- /dev/null +++ b/docs/installation.md @@ -0,0 +1,139 @@ +# Installation + +## Voraussetzungen + +- [Docker](https://docs.docker.com/get-docker/) ≥ 24 +- [Docker Compose](https://docs.docker.com/compose/install/) ≥ 2.20 (in neueren Docker-Versionen enthalten) +- Git + +--- + +## Automatische Installation + +Das Script `install.sh` übernimmt die gesamte Installation: es erkennt die Linux-Distribution, installiert git, curl und Docker, klont das Repository in das gewünschte Verzeichnis und legt die `.env`-Datei an. + +Das Script muss **vor** dem Klonen des Repositories heruntergeladen werden – es erledigt das Klonen selbst: + +```bash +curl -fsSL https://codeberg.org/fds2610/Steuerkram/raw/branch/main/install.sh -o install.sh +sudo bash install.sh +``` + +> Das Script muss als root ausgeführt werden, da es Pakete installiert und ggf. den Docker-Dienst aktiviert. + +Nach der Installation die `.env`-Datei im gewählten Installationsverzeichnis anpassen und die Container starten: + +```bash +cd /opt/Steuerkram # oder das gewählte Verzeichnis +docker compose up -d +``` + +--- + +## Manuelle Installation + +### 1. Repository klonen + +```bash +git clone steuerkram +cd steuerkram +``` + +### 2. Konfiguration anpassen + +```bash +cp .env.example .env +``` + +```dotenv +DB_PASS=sicheres-passwort # Datenbankpasswort – in docker-compose.yml spiegeln +``` + +Außerdem in `docker-compose.yml` das `MARIADB_PASSWORD` im `db`-Service auf denselben Wert wie `DB_PASS` setzen. + +### 3. Container starten + +```bash +docker compose up -d +``` + +Beim ersten Start werden automatisch: +- die Datenbank initialisiert +- der SKR03-Kontenrahmen eingespielt +- das Docker-Image mit PHP-Abhängigkeiten (mPDF) gebaut + +### 4. Anwendung aufrufen + +``` +http://localhost:8080 +``` + +Beim ersten Aufruf erscheint die **Ersteinrichtung**: dort den ersten Benutzer-Account anlegen. Danach ist die Anwendung passwortgeschützt. + +--- + +## Port ändern + +Der Standard-Port ist `8080`. Um ihn zu ändern, die `ports`-Zeile in `docker-compose.yml` anpassen: + +```yaml +ports: + - "9090:80" # Beispiel: Port 9090 auf dem Host +``` + +--- + +## Upload-Limit anpassen + +Die maximale Dateigröße für Beleganhänge ist auf **20 MB** voreingestellt (`php/uploads.ini`). Um das Limit zu ändern: + +```ini +upload_max_filesize = 20M +post_max_size = 20M +``` + +```bash +docker compose up -d --build +``` + +--- + +## Systemd-Service einrichten + +Damit Steuerkram automatisch beim Systemstart startet, liegt im Repository die Datei `steuerkram.service`. + +### 1. Service-Datei installieren + +```bash +sudo cp steuerkram.service /etc/systemd/system/steuerkram.service +``` + +Die Datei setzt `WorkingDirectory=/opt/Steuerkram` voraus. Falls das Repository woanders liegt, den Pfad anpassen: + +```bash +sudo nano /etc/systemd/system/steuerkram.service +``` + +### 2. Service aktivieren und starten + +```bash +sudo systemctl daemon-reload +sudo systemctl enable steuerkram +sudo systemctl start steuerkram +``` + +### 3. Status prüfen + +```bash +sudo systemctl status steuerkram +journalctl -u steuerkram -f # Live-Log inkl. Migrations-Ausgabe +``` + +### Wichtige Befehle + +| Befehl | Wirkung | +|---|---| +| `sudo systemctl start steuerkram` | Container starten | +| `sudo systemctl stop steuerkram` | Container stoppen | +| `sudo systemctl restart steuerkram` | Neustart (z.B. nach Update) | +| `sudo systemctl status steuerkram` | Aktueller Status | diff --git a/docs/konfiguration.md b/docs/konfiguration.md new file mode 100644 index 0000000..d73d895 --- /dev/null +++ b/docs/konfiguration.md @@ -0,0 +1,88 @@ +# Konfiguration + +## Firmendaten hinterlegen + +Nach dem ersten Login unter **Einstellungen** Firmendaten, Logo und ELSTER-Steuernummer eintragen. Diese Angaben erscheinen auf Ausgangsrechnungen und im EÜR/UStVA-Export. + +--- + +## Nummernkreise + +Unter **Einstellungen → Nummernkreise** können Präfixe für die automatisch vergebenen Nummern konfiguriert werden: + +| Nummernkreis | Standard | Beispiel | +|---|---|---| +| Belege | `BLG` | `BLG-2026-0001` | +| Ausgangsrechnungen | `RE` | `RE-2026-0001` | +| Anlagen | `ANL` | `ANL-2026-0001` | + +Das Format ist immer `{Präfix}-{Jahr}-{laufende Nummer}`. Die laufende Nummer wird automatisch hochgezählt und kann nicht zurückgesetzt werden (GoBD). + +--- + +## KI-Beleganalyse einrichten + +Die KI-gestützte Belegerkennung ist standardmäßig deaktiviert. Sie kann unter **Einstellungen → KI-Kontierungsvorschlag** aktiviert werden, sobald ein LLM erreichbar ist. + +### Option A – Lokales Ollama + +Kein API-Schlüssel nötig, aber hoher Ressourcenbedarf: + +```bash +ollama pull mistral # oder llama3.2 etc. +``` + +In `.env` ist keine weitere Konfiguration nötig – die App erkennt Ollama auf `localhost:11434` automatisch. Modell und URL können optional überschrieben werden: + +```dotenv +OLLAMA_URL=http://localhost:11434 +OLLAMA_MODEL=mistral +``` + +### Option B – Remote-API (OpenAI-kompatibel) + +z.B. OpenAI, OpenRouter, LM Studio: + +```dotenv +LLM_API_URL=https://api.openai.com/v1 +LLM_API_KEY=sk-... +LLM_MODEL=gpt-4o-mini +``` + +> **Hinweis:** Die Ergebnisqualität hängt stark vom gewählten Modell ab. Tests mit `mistral` (lokal via Ollama) lieferten bisher mäßige Ergebnisse. + +--- + +## Nextcloud-Import einrichten + +Belege können automatisch aus einem Nextcloud-Ordner via WebDAV importiert werden. Das CLI-Skript `scripts/nextcloud-import.php` liest alle unterstützten Dateien (PDF, JPEG, PNG, TIFF, XML) aus dem konfigurierten Ordner, legt sie als Belege an und verschiebt sie anschließend in einen Erledigte-Ordner. + +### Zugangsdaten konfigurieren + +Die Zugangsdaten werden in der Steuerkram-UI gepflegt: **Einstellungen → Nextcloud-Belegimport**. + +Empfehlung: Ein Nextcloud-App-Passwort verwenden (Nextcloud → Einstellungen → Sicherheit → App-Passwörter). + +### Manueller Aufruf + +```bash +docker compose exec web php scripts/nextcloud-import.php +``` + +### Automatisch per Cron + +Cron-Eintrag auf dem Host anlegen (`crontab -e`): + +```cron +*/15 * * * * docker compose -f /opt/Steuerkram/docker-compose.yml exec -T web \ + php scripts/nextcloud-import.php >> /var/log/steuerkram-import.log 2>&1 +``` + +> Das `-T`-Flag ist bei Cron zwingend erforderlich – ohne TTY schlägt `docker compose exec` sonst still fehl. + +Damit `docker compose exec` ohne `sudo` funktioniert, muss der User in der Docker-Gruppe sein: + +```bash +sudo usermod -aG docker $USER +# Danach neu einloggen +``` diff --git a/Kontenrahmen.md b/docs/kontenrahmen.md similarity index 100% rename from Kontenrahmen.md rename to docs/kontenrahmen.md diff --git a/docs/update-backup.md b/docs/update-backup.md new file mode 100644 index 0000000..3d7078a --- /dev/null +++ b/docs/update-backup.md @@ -0,0 +1,106 @@ +# Update & Backup + +## Backup + +```bash +bash scripts/backup.sh +``` + +Das Skript erstellt einen Dump der Datenbank sowie ein Archiv der Uploads im Verzeichnis `backups/`. Die Rotation erfolgt nach folgendem Schema: + +- 7 Tages-Backups +- 4 Wochen-Backups +- 12 Monats-Backups + +Ein `flock`-Lock verhindert parallele Läufe bei Verzögerungen. + +### Manuelles Backup + +```bash +docker compose exec db mariadb-dump -u steuerkram -psteuerkram steuerkram \ + > ~/steuerkram_backup_$(date +%Y%m%d_%H%M).sql +``` + +--- + +## Datenpersistenz + +| Pfad | Inhalt | +|---|---| +| `./data/mariadb/` | MariaDB-Datenbankdateien | +| Docker Volume `uploads` | Hochgeladene Belege | + +Beide Pfade liegen außerhalb des Containers und bleiben bei `docker compose down` erhalten. + +--- + +## Update + +Datenbankmigrationen werden **automatisch** beim nächsten Container-Start eingespielt. Das Script `scripts/migrate.php` prüft beim Start, welche Migrations-Dateien noch nicht angewendet wurden, und führt sie der Reihe nach aus. + +### Vollständige Update-Anleitung + +#### Schritt 1 – Datenbank sichern + +```bash +bash scripts/backup.sh +``` + +#### Schritt 2 – Aktuellen Code holen + +```bash +git pull +``` + +#### Schritt 3 – Container neu bauen und starten + +```bash +docker compose up -d --build +``` + +Dieser Befehl baut das PHP-Image neu (falls sich `Dockerfile` oder `composer.json` geändert haben) und startet alle Container. Der Web-Container führt beim Start automatisch `scripts/migrate.php` aus. + +#### Schritt 4 – Migrationen prüfen + +```bash +docker compose logs web | grep migrate +``` + +Erwartete Ausgabe: + +``` +[migrate] Starte Migrations-Prüfung... +[migrate] Führe aus: NNN_beschreibung.sql +[migrate] N Migration(en) erfolgreich angewendet. +``` + +Falls ein Fehler auftritt, das Backup aus Schritt 1 einspielen: + +```bash +docker compose exec -T db mariadb -u steuerkram -psteuerkram steuerkram \ + < ~/steuerkram_backup_YYYYMMDD_HHMM.sql +``` + +#### Schritt 5 – Anwendung prüfen + +```bash +docker compose ps # alle Container müssen "running" sein +docker compose logs web # keine PHP-Fehler oder Exceptions +``` + +--- + +### Kurzform ohne Systemd + +```bash +bash scripts/backup.sh && git pull && docker compose up -d --build +docker compose logs web | grep migrate +``` + +### Kurzform mit Systemd + +```bash +bash scripts/backup.sh && git pull && docker compose build +sudo systemctl restart steuerkram +journalctl -u steuerkram | grep migrate +``` diff --git a/src/Config.php b/src/Config.php index 42020c7..d6c5c76 100644 --- a/src/Config.php +++ b/src/Config.php @@ -15,7 +15,6 @@ class Config 'db_name' => $_ENV['DB_NAME'] ?? getenv('DB_NAME') ?: 'steuerkram', 'db_user' => $_ENV['DB_USER'] ?? getenv('DB_USER') ?: 'steuerkram', 'db_pass' => $_ENV['DB_PASS'] ?? getenv('DB_PASS') ?: 'steuerkram', - 'app_secret' => $_ENV['APP_SECRET'] ?? getenv('APP_SECRET') ?: 'dev-secret', 'upload_dir' => dirname(__DIR__) . '/public/static/uploads', 'base_url' => '', ]; diff --git a/tests/01_BelegUploadTest.php b/tests/01_BelegUploadTest.php index 8a88d53..3f5f294 100644 --- a/tests/01_BelegUploadTest.php +++ b/tests/01_BelegUploadTest.php @@ -18,17 +18,16 @@ class BelegUploadTest extends TestCase $datum = date('Y-m-d'); $id = \Steuerkram\Models\Beleg::create([ - 'beleg_nr' => $belegNr, - 'filename' => 'test-rechnung.pdf', - 'storage_path' => 'static/uploads/test/test-rechnung.pdf', - 'mime_type' => 'application/pdf', - 'file_hash' => hash('sha256', $belegNr), - 'beleg_datum' => $datum, - 'eingangs_datum' => $datum, - 'rechnungs_nr' => 'RE-TEST-001', - 'betrag_brutto' => 119.00, - 'belegtyp' => 'Eingangsrechnung', - 'notes' => 'Automatisch erstellt (Test)', + 'beleg_nr' => $belegNr, + 'filename' => 'test-rechnung.pdf', + 'storage_path' => 'static/uploads/test/test-rechnung.pdf', + 'mime_type' => 'application/pdf', + 'file_hash' => hash('sha256', $belegNr), + 'beleg_datum' => $datum, + 'rechnungs_nr' => 'RE-TEST-001', + 'betrag_brutto' => 119.00, + 'belegtyp' => 'Eingangsrechnung', + 'notes' => 'Automatisch erstellt (Test)', ]); $this->assertGreaterThan(0, $id, 'Beleg wurde in der DB angelegt (ID > 0)'); @@ -51,7 +50,8 @@ class BelegUploadTest extends TestCase $this->assert($found, 'Beleg erscheint in Beleg::all() (Belegübersicht)'); $this->assertGreaterThan(0, $result['total'], 'Belegübersicht enthält mindestens 1 Eintrag'); - // Betrag korrekt gespeichert - $this->assertEquals(119.00, (float)($beleg['betrag_brutto'] ?? 0), 'Betrag brutto korrekt gespeichert'); + // Betrag korrekt gespeichert (direkt aus belege, nicht die berechnete Buchungssumme) + $raw = \Steuerkram\Database::fetchOne('SELECT betrag_brutto FROM belege WHERE id = ?', [$id]); + $this->assertEquals(119.00, (float)($raw['betrag_brutto'] ?? 0), 'Betrag brutto korrekt gespeichert'); } } diff --git a/tests/11_KontierungsvorschlagTest.php b/tests/11_KontierungsvorschlagTest.php index 3b0df11..0d44082 100644 --- a/tests/11_KontierungsvorschlagTest.php +++ b/tests/11_KontierungsvorschlagTest.php @@ -50,9 +50,9 @@ class KontierungsvorschlagTest extends TestCase private function createTestBeleg(): int { Database::execute( - "INSERT INTO belege (beleg_nr, filename, storage_path, mime_type, file_hash, eingangs_datum, belegtyp) + "INSERT INTO belege (beleg_nr, filename, storage_path, mime_type, file_hash, belegtyp) VALUES ('TEST-KI-0001', 'test.pdf', 'static/uploads/test/test.pdf', 'application/pdf', - SHA2('test', 256), CURDATE(), 'Eingangsrechnung')" + SHA2('test', 256), 'Eingangsrechnung')" ); return (int)Database::lastInsertId(); } diff --git a/tests/16_BewirtungssplitTest.php b/tests/16_BewirtungssplitTest.php index 6d3bbcd..172aa6f 100644 --- a/tests/16_BewirtungssplitTest.php +++ b/tests/16_BewirtungssplitTest.php @@ -31,15 +31,18 @@ class BewirtungssplitTest extends TestCase // ── Test 1: Split bei Konto 4650 ───────────────────────────── $beleg1 = \Steuerkram\Models\Beleg::create([ - 'beleg_nr' => 'TEST-BEW-4650-' . uniqid(), - 'filename' => 'test.pdf', - 'storage_path' => '', - 'mime_type' => 'application/pdf', - 'file_hash' => hash('sha256', 'bew4650' . uniqid()), - 'beleg_datum' => $datum, - 'eingangs_datum' => $datum, - 'belegtyp' => 'Eingangsrechnung', + 'beleg_nr' => 'TEST-BEW-4650-' . uniqid(), + 'filename' => 'test.pdf', + 'storage_path' => '', + 'mime_type' => 'application/pdf', + 'file_hash' => hash('sha256', 'bew4650' . uniqid()), + 'beleg_datum' => $datum, + 'belegtyp' => 'Eingangsrechnung', ]); + \Steuerkram\Database::execute( + 'UPDATE belege SET bewirtungsgrund = ?, teilnehmerliste = ? WHERE id = ?', + ['Kundengespräch', 'Max Mustermann, Erika Mustermann', $beleg1] + ); // Bewirtungssplit direkt über Booking::create + zweite Buchung simulieren // (wie BookingController::bucheBewirtungssplit() es tut) @@ -57,8 +60,6 @@ class BewirtungssplitTest extends TestCase 'betrag_netto' => $netto70, 'ust_satz' => $ustSatz, 'buchungstext' => 'Bewirtung Test 70%', - 'bewirtungsgrund' => 'Kundengespräch', - 'teilnehmerliste' => 'Max Mustermann, Erika Mustermann', ]); $id1b = \Steuerkram\Models\Booking::create([ @@ -70,8 +71,6 @@ class BewirtungssplitTest extends TestCase 'betrag_netto' => $netto30, 'ust_satz' => $ustSatz, 'buchungstext' => 'Bewirtung Test 30%', - 'bewirtungsgrund' => 'Kundengespräch', - 'teilnehmerliste' => 'Max Mustermann, Erika Mustermann', ]); $b1a = \Steuerkram\Models\Booking::find($id1a); @@ -95,20 +94,20 @@ class BewirtungssplitTest extends TestCase 'Gesamt-Vorsteuer korrekt (100% des Nettobetrags)' ); - // Bewirtungsfelder gespeichert - $this->assertEquals('Kundengespräch', $b1a['bewirtungsgrund'], 'Bewirtungsgrund gespeichert'); - $this->assertEquals('Max Mustermann, Erika Mustermann', $b1a['teilnehmerliste'], 'Teilnehmerliste gespeichert'); + // Bewirtungsfelder auf dem Beleg gespeichert (seit Migration 024 in belege, nicht bookings) + $belegDaten = \Steuerkram\Models\Beleg::find($beleg1); + $this->assertEquals('Kundengespräch', $belegDaten['bewirtungsgrund'], 'Bewirtungsgrund gespeichert'); + $this->assertEquals('Max Mustermann, Erika Mustermann', $belegDaten['teilnehmerliste'], 'Teilnehmerliste gespeichert'); // ── Test 2: Split bei Konto 4651 ───────────────────────────── $beleg2 = \Steuerkram\Models\Beleg::create([ - 'beleg_nr' => 'TEST-BEW-4651-' . uniqid(), - 'filename' => 'test2.pdf', - 'storage_path' => '', - 'mime_type' => 'application/pdf', - 'file_hash' => hash('sha256', 'bew4651' . uniqid()), - 'beleg_datum' => $datum, - 'eingangs_datum' => $datum, - 'belegtyp' => 'Eingangsrechnung', + 'beleg_nr' => 'TEST-BEW-4651-' . uniqid(), + 'filename' => 'test2.pdf', + 'storage_path' => '', + 'mime_type' => 'application/pdf', + 'file_hash' => hash('sha256', 'bew4651' . uniqid()), + 'beleg_datum' => $datum, + 'belegtyp' => 'Eingangsrechnung', ]); $id2a = \Steuerkram\Models\Booking::create([ @@ -149,14 +148,13 @@ class BewirtungssplitTest extends TestCase $bankCheck = $this->findAccountId('4600'); if ($bankCheck) { $beleg3 = \Steuerkram\Models\Beleg::create([ - 'beleg_nr' => 'TEST-BEW-NORMAL-' . uniqid(), - 'filename' => 'test3.pdf', - 'storage_path' => '', - 'mime_type' => 'application/pdf', - 'file_hash' => hash('sha256', 'normal' . uniqid()), - 'beleg_datum' => $datum, - 'eingangs_datum' => $datum, - 'belegtyp' => 'Eingangsrechnung', + 'beleg_nr' => 'TEST-BEW-NORMAL-' . uniqid(), + 'filename' => 'test3.pdf', + 'storage_path' => '', + 'mime_type' => 'application/pdf', + 'file_hash' => hash('sha256', 'normal' . uniqid()), + 'beleg_datum' => $datum, + 'belegtyp' => 'Eingangsrechnung', ]); $idNormal = \Steuerkram\Models\Booking::create([ diff --git a/tests/17_AutomatikkontenUstTest.php b/tests/17_AutomatikkontenUstTest.php index 8a7077b..9cdf35c 100644 --- a/tests/17_AutomatikkontenUstTest.php +++ b/tests/17_AutomatikkontenUstTest.php @@ -37,7 +37,7 @@ class AutomatikkontenUstTest extends TestCase $mapping = ['VSt19' => 19, 'USt19' => 19, 'VSt7' => 7, 'USt7' => 7]; $kontenMitSteuercode = \Steuerkram\Database::fetchAll( - "SELECT kontonummer, steuercode FROM accounts WHERE steuercode IS NOT NULL LIMIT 20" + "SELECT kontonummer, steuercode FROM accounts WHERE steuercode IS NOT NULL AND steuercode != '' LIMIT 20" ); $this->assertNotEmpty($kontenMitSteuercode, 'Konten mit Steuercode vorhanden'); @@ -60,14 +60,13 @@ class AutomatikkontenUstTest extends TestCase } $belegId = \Steuerkram\Models\Beleg::create([ - 'beleg_nr' => 'TEST-AUTOUST-' . uniqid(), - 'filename' => 'test.pdf', - 'storage_path' => '', - 'mime_type' => 'application/pdf', - 'file_hash' => hash('sha256', 'autoust' . uniqid()), - 'beleg_datum' => $datum, - 'eingangs_datum' => $datum, - 'belegtyp' => 'Eingangsrechnung', + 'beleg_nr' => 'TEST-AUTOUST-' . uniqid(), + 'filename' => 'test.pdf', + 'storage_path' => '', + 'mime_type' => 'application/pdf', + 'file_hash' => hash('sha256', 'autoust' . uniqid()), + 'beleg_datum' => $datum, + 'belegtyp' => 'Eingangsrechnung', ]); $bookingId = \Steuerkram\Models\Booking::create([