Feature: Anlage-Links auf Bearbeitungsmaske, Backfill anlage_nr, README

- Anlage-Spalte in allen Buchungstabellen verlinkt jetzt auf /assets/{id}/edit
- bk.asset_id in UstvaCalculator und EuerCalculator-Queries ergänzt
- Fallback-Anzeige: Bezeichnung wenn anlage_nr leer ist
- Migration 032: anlage_nr für bestehende Anlagegüter nachgenerieren
- README: Feature Anlagevermögen dokumentiert
This commit is contained in:
fds2610-bot 2026-04-12 18:51:54 +02:00
parent 2611d2b4b9
commit 49d145e251
9 changed files with 73 additions and 7 deletions

View file

@ -2,6 +2,14 @@
---
## 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

View file

@ -11,7 +11,8 @@ Self-hosted Buchhaltungs- und Steueranwendung für deutsche Freiberufler und Kle
- **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
- **Anlagevermögen** – Anlagenspiegel für Wirtschaftsgüter: automatische Anlagen-Nummerierung (konfiguriertes Präfix + Jahrgang + laufende Nummer), Verknüpfung mit Anlagevermögenskonten des SKR03; beim Buchen auf ein Anlagevermögenskonto öffnet sich automatisch ein Dialog zur Auswahl (neues Wirtschaftsgut anlegen oder bestehendes verknüpfen); Anlagennummer wird in allen Buchungsübersichten (Beleg-Detail, Journal, UStVA-Drill-down, EÜR-Drill-down) als direkter Link zur Bearbeitungsmaske des Anlageguts angezeigt
- **Einstellungen** – Firmendaten, Logo, ELSTER-Steuernummer, Nummernkreis-Präfixe (Belege, Rechnungen, Anlagen)
- **Benutzerverwaltung** – Benutzer anlegen, bearbeiten, deaktivieren und löschen; session-basierte Authentifizierung mit 24 h Timeout
## Tech Stack
@ -178,11 +179,37 @@ docker compose exec web php scripts/nextcloud-import.php
### Automatisch per Cron (auf dem Host)
Für den automatischen Abruf muss ein Cron-Eintrag auf dem Host angelegt werden. Dabei gibt es zwei Varianten:
**Option 1: Root-Crontab** (`sudo 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
```
Kein `sudo` im Befehl nötig. Nachteil: Root-Crons sind schwerer zu überblicken.
**Option 2: User-Crontab** (`crontab -e`) – empfohlen
Damit `docker compose exec` ohne `sudo` funktioniert, muss der User in der Docker-Gruppe sein:
```bash
sudo usermod -aG docker $USER
# Danach neu einloggen
```
Anschließend in der User-Crontab (ohne `sudo`):
```cron
*/15 * * * * docker compose -f /home/fds/projects/Steuerkram/docker-compose.yml exec -T web \
php scripts/nextcloud-import.php >> /var/log/steuerkram-import.log 2>&1
```
> **Hinweis:** Das `-T`-Flag ist bei Cron zwingend erforderlich – ohne TTY schlägt `docker compose exec` sonst still fehl.
>
> **Empfehlung:** User-Crontab + Docker-Gruppe. Das ist konsistent mit dem bestehenden Setup (alle anderen Jobs laufen ebenfalls in der User-Crontab).
Das Skript gibt beim Start eine Zusammenfassung aus (importiert / übersprungen / Fehler) und beendet sich mit Exit-Code 1 bei Fehlern.
## Datenpersistenz

View file

@ -0,0 +1,23 @@
-- Migration 032: Anlage-Nummern für bestehende Anlagegüter ohne Nummer nachgenerieren
-- Weist assets mit leerem anlage_nr Nummern im konfigurierten Format zu.
CREATE TEMPORARY TABLE IF NOT EXISTS _anlage_backfill_tmp AS
SELECT
a.id,
YEAR(COALESCE(a.anschaffungsdatum, NOW())) AS asset_year,
cs.anlage_nr_prefix AS prefix,
cs.anlage_nr_next + (ROW_NUMBER() OVER (ORDER BY a.id) - 1) AS seq_nr
FROM assets a
CROSS JOIN company_settings cs
WHERE a.anlage_nr = '' AND cs.id = 1;
UPDATE assets a
JOIN _anlage_backfill_tmp t ON a.id = t.id
SET a.anlage_nr = CONCAT(t.prefix, '-', t.asset_year, '-', LPAD(t.seq_nr, 4, '0'))
WHERE a.anlage_nr = '';
UPDATE company_settings
SET anlage_nr_next = anlage_nr_next + (SELECT COUNT(*) FROM _anlage_backfill_tmp)
WHERE id = 1 AND (SELECT COUNT(*) FROM _anlage_backfill_tmp) > 0;
DROP TEMPORARY TABLE IF EXISTS _anlage_backfill_tmp;

View file

@ -80,7 +80,7 @@ class EuerCalculator
return Database::fetchAll(
'SELECT bk.id, bk.buchungs_datum, bk.buchungstyp, bk.ust_satz,
bk.betrag_netto, bk.betrag_ust, bk.betrag_brutto, bk.buchungstext,
bk.is_reversal, bk.beleg_id, bl.beleg_nr,
bk.is_reversal, bk.beleg_id, bk.asset_id, bl.beleg_nr,
k.kontonummer AS kategorie_nr, k.bezeichnung AS kategorie_name,
z.kontonummer AS zahlungs_nr, z.bezeichnung AS zahlungs_name,
a.anlage_nr, a.bezeichnung AS anlage_bezeichnung

View file

@ -198,7 +198,7 @@ class UstvaCalculator
$select = 'SELECT bk.id, bk.buchungs_datum, bk.buchungstyp, bk.steuerart, bk.ust_satz,
bk.betrag_netto, bk.betrag_ust, bk.betrag_brutto, bk.buchungstext,
bk.is_reversal, bk.beleg_id, bl.beleg_nr,
bk.is_reversal, bk.beleg_id, bk.asset_id, bl.beleg_nr,
k.kontonummer AS kategorie_nr, k.bezeichnung AS kategorie_name,
z.kontonummer AS zahlungs_nr, z.bezeichnung AS zahlungs_name,
a.anlage_nr, a.bezeichnung AS anlage_bezeichnung

View file

@ -225,7 +225,9 @@ $suppliers = $suppliers ?? [];
</td>
<td class="td-mono text-sm">
<?php if (!empty($bk['anlage_nr'])): ?>
<a href="/assets" title="<?= $h($bk['anlage_bezeichnung']) ?>"><?= $h($bk['anlage_nr']) ?></a>
<a href="/assets/<?= (int)$bk['asset_id'] ?>/edit" title="<?= $h($bk['anlage_bezeichnung']) ?>"><?= $h($bk['anlage_nr']) ?></a>
<?php elseif (!empty($bk['anlage_bezeichnung'])): ?>
<a href="/assets/<?= (int)$bk['asset_id'] ?>/edit"><?= $h($bk['anlage_bezeichnung']) ?></a>
<?php else: ?>
<span class="text-muted">–</span>
<?php endif; ?>

View file

@ -87,7 +87,9 @@
</td>
<td class="td-mono text-sm">
<?php if (!empty($bk['anlage_nr'])): ?>
<a href="/assets" title="<?= $h($bk['anlage_bezeichnung']) ?>"><?= $h($bk['anlage_nr']) ?></a>
<a href="/assets/<?= (int)$bk['asset_id'] ?>/edit" title="<?= $h($bk['anlage_bezeichnung']) ?>"><?= $h($bk['anlage_nr']) ?></a>
<?php elseif (!empty($bk['anlage_bezeichnung'])): ?>
<a href="/assets/<?= (int)$bk['asset_id'] ?>/edit"><?= $h($bk['anlage_bezeichnung']) ?></a>
<?php else: ?>
<span class="text-muted">–</span>
<?php endif; ?>

View file

@ -65,7 +65,9 @@ $fmt = fn($v) => number_format((float)$v, 2, ',', '.') . ' €';
<td class="td-right td-mono"><?= $fmt($brutto) ?></td>
<td class="td-mono text-sm">
<?php if (!empty($b['anlage_nr'])): ?>
<a href="/assets" title="<?= $h($b['anlage_bezeichnung']) ?>"><?= $h($b['anlage_nr']) ?></a>
<a href="/assets/<?= (int)$b['asset_id'] ?>/edit" title="<?= $h($b['anlage_bezeichnung']) ?>"><?= $h($b['anlage_nr']) ?></a>
<?php elseif (!empty($b['anlage_bezeichnung'])): ?>
<a href="/assets/<?= (int)$b['asset_id'] ?>/edit"><?= $h($b['anlage_bezeichnung']) ?></a>
<?php else: ?>
<span class="text-muted">–</span>
<?php endif; ?>

View file

@ -84,7 +84,9 @@ $signedBetrag = fn(array $b, string $field): float =>
<td class="td-right td-mono"><?= $fmtKz($brutto) ?></td>
<td class="td-mono text-sm">
<?php if (!empty($b['anlage_nr'])): ?>
<a href="/assets" title="<?= $h($b['anlage_bezeichnung']) ?>"><?= $h($b['anlage_nr']) ?></a>
<a href="/assets/<?= (int)$b['asset_id'] ?>/edit" title="<?= $h($b['anlage_bezeichnung']) ?>"><?= $h($b['anlage_nr']) ?></a>
<?php elseif (!empty($b['anlage_bezeichnung'])): ?>
<a href="/assets/<?= (int)$b['asset_id'] ?>/edit"><?= $h($b['anlage_bezeichnung']) ?></a>
<?php else: ?>
<span class="text-muted">–</span>
<?php endif; ?>