diff --git a/Changes.md b/Changes.md index 21f56e5..ad6f6e4 100644 --- a/Changes.md +++ b/Changes.md @@ -1,5 +1,28 @@ # Änderungen +## Wertpapiere: Depotbuchungen sammelweise löschen (12.08.2026) + +Die Liste der Depotbuchungen räumt jetzt so auf wie das Kontoblatt – nach einem Import, +der schiefging, war das Löschen Zeile für Zeile mühsam. + +**Auswahl löschen** (`Views/wertpapiere/buchungen.php`, `WertpapiereController`, +`public/index.php`) +- Ankreuzfeld je Zeile, das Feld in der Titelzeile wählt alle angezeigten Buchungen. Der + Knopf steht oben in der Aktionsleiste, nennt die Anzahl und bleibt ohne Auswahl + gesperrt; die Rückfrage nennt sie ebenfalls. Ohne JavaScript funktioniert alles weiter: + Dann ist der Knopf offen und `POST /wertpapiere/buchungen/loeschen` weist eine leere + Auswahl ab. +- Mit jeder Depotbuchung geht ihre Geldbuchung – wie beim Löschen einer einzelnen. +- **Am Bestand ist nichts nachzuziehen.** Er wird bei jedem Aufruf aus den Depotbuchungen + gerechnet und gibt die Löschung von selbst wieder her, ebenso realisierter Gewinn, + Veräußerungen und Erträge. +- Der eingestellte Filter (Depot, Wertpapier, Vorgang) hängt am Ziel des Formulars, + deshalb steht danach wieder dieselbe Ansicht da. Das gilt jetzt auch für das *Löschen* + je Zeile, das bisher zur ungefilterten Liste zurücksprang. + +`tests/22_DepotbuchungenLoeschenTest.php` prüft Geldseite, Bestand und Auswertungen nach +dem Löschen; `tests/e2e/specs/module.spec.js` den Ablauf im Browser. + ## Wertpapiere: Depotbestand aus einer CSV-Datei übernehmen (12.08.2026) Ein Depot, das nie im FinanzManager geführt wurde, lässt sich jetzt in einem Zug diff --git a/docs/wertpapiere.md b/docs/wertpapiere.md index 15d1a41..1abb791 100644 --- a/docs/wertpapiere.md +++ b/docs/wertpapiere.md @@ -149,6 +149,24 @@ Der Ablauf ist zweistufig: Der Upload zeigt erst eine Vorschau mit allen Positio Einstand, Wert und dem Status je Zeile; erst *Jetzt übernehmen* schreibt. Die Datei bleibt im Importverzeichnis liegen. +## Depotbuchungen löschen + +Die Liste der Depotbuchungen löscht einzeln – *Löschen* je Zeile – oder mehrere auf +einmal, genau wie das Kontoblatt: Ankreuzfeld je Zeile, das Feld in der Titelzeile wählt +alle angezeigten, *Auswahl löschen* in der Aktionsleiste schickt sie an +`POST /wertpapiere/buchungen/loeschen`. Der Knopf nennt die Anzahl und bleibt ohne +Auswahl gesperrt; die Rückfrage kommt vor dem Absenden. Ohne JavaScript funktioniert +alles weiter – dann ist der Knopf offen und der Controller weist eine leere Auswahl ab. + +Der eingestellte Filter hängt am Ziel beider Formulare, deshalb steht danach wieder +dieselbe Ansicht da. Zusammengesetzt wird er im Controller neu, damit kein fremdes Ziel +eingeschleust werden kann. + +Mit jeder Depotbuchung geht ihre Geldbuchung (siehe *Zwei Seiten je Geschäft*). Am +Bestand ist nichts nachzuziehen: Er wird bei jedem Aufruf aus den Depotbuchungen +gerechnet und gibt die Löschung von selbst wieder her – ebenso realisierter Gewinn, +Veräußerungen und Erträge. Das Wertpapier selbst bleibt in den Stammdaten stehen. + ## Kontenübersicht Depots erscheinen dort mit ihrem **bewerteten Bestand**, nicht mit einem Buchungssaldo. Der @@ -166,6 +184,11 @@ Bestandsänderung; fehlende Kurse; Veräußerungsrechnung über die Historie hin QIF-Import eines echten Ausschnitts (Kauf, Dividende, Verkauf) inklusive Idempotenz; Löschen samt Geldbuchung. +`tests/22_DepotbuchungenLoeschenTest.php` – das Sammellöschen: die Geldbuchungen gehen +mit, das Verrechnungskonto stimmt danach wieder, Bestand, Einstand, realisierter Gewinn, +Erträge und die Veräußerungsauswertung geben die Löschung her, unbekannte Nummern in der +Auswahl bleiben folgenlos. Den Ablauf im Browser prüft `tests/e2e/specs/module.spec.js`. + `tests/21_DepotbestandImportTest.php` – der CSV-Bestandsimport an einem Ausschnitt des echten DKB-Exports: Vorschau ohne Nebenwirkung, Einstand und Wert aus beiden Kursen, Bruchstückzahlen, Assetklasse → Art, hinterlegte Bewertungskurse, Idempotenz beim zweiten diff --git a/public/index.php b/public/index.php index 5c8916b..9f20bf2 100644 --- a/public/index.php +++ b/public/index.php @@ -240,6 +240,8 @@ $router->get('/wertpapiere/auswertung', fn() => (new WertpapiereController())->a $router->get('/wertpapiere/buchungen', fn() => (new WertpapiereController())->buchungen()); $router->get('/wertpapiere/buchungen/neu', fn() => (new WertpapiereController())->neu()); $router->post('/wertpapiere/buchungen/neu', fn() => (new WertpapiereController())->anlegen()); +$router->post('/wertpapiere/buchungen/loeschen', + fn() => (new WertpapiereController())->auswahlLoeschen()); $router->post('/wertpapiere/buchungen/{id}/loeschen', fn(array $p) => (new WertpapiereController())->loeschen($p)); diff --git a/src/Controllers/WertpapiereController.php b/src/Controllers/WertpapiereController.php index dba3940..cc8db9f 100644 --- a/src/Controllers/WertpapiereController.php +++ b/src/Controllers/WertpapiereController.php @@ -128,7 +128,65 @@ class WertpapiereController extends BaseController Depotbuchung::loeschen((int)$params['id']); - View::redirect('/wertpapiere/buchungen', 'Depotbuchung gelöscht – samt ihrer Geldbuchung.'); + View::redirect($this->buchungenPfad(), 'Depotbuchung gelöscht – samt ihrer Geldbuchung.'); + } + + /** + * Sammellöschen aus der Liste der Depotbuchungen – wie im Kontoblatt. + * + * Der Bestand wird bei jedem Aufruf aus den Depotbuchungen gerechnet; er + * gibt die Löschung deshalb von selbst wieder her, ebenso Veräußerungen und + * Erträge. Fortgeschrieben wird nichts. + */ + public function auswahlLoeschen(): void + { + $this->verlangeAnmeldung(); + + $ziel = $this->buchungenPfad(); + $ids = $this->postIds('buchungen'); + if ($ids === []) { + View::redirect($ziel, 'Keine Depotbuchung ausgewählt.', 'warnung'); + return; + } + + $anzahl = 0; + foreach ($ids as $id) { + // Fremde oder längst gelöschte Nummern still übergehen – der Rest + // der Auswahl soll trotzdem verschwinden. + if (Depotbuchung::find($id) === null) { + continue; + } + + try { + Depotbuchung::loeschen($id); + } catch (\Throwable $e) { + View::redirect($ziel, $e->getMessage(), 'fehler'); + return; + } + $anzahl++; + } + + View::redirect($ziel, $anzahl === 1 + ? 'Depotbuchung gelöscht – samt ihrer Geldbuchung.' + : $anzahl . ' Depotbuchungen gelöscht – samt ihrer Geldbuchungen.'); + } + + /** + * Zurück in die Liste, mit demselben Filter. + * + * Die Werte kommen aus der Abfragezeichenfolge des Formularziels und werden + * hier neu zusammengesetzt statt übernommen: So kann kein fremdes Ziel + * eingeschleust werden. + */ + private function buchungenPfad(): string + { + $query = array_filter([ + 'depot' => $this->query('depot'), + 'wertpapier' => $this->query('wertpapier'), + 'vorgang' => $this->query('vorgang'), + ], static fn(string $wert): bool => $wert !== ''); + + return '/wertpapiere/buchungen' . ($query === [] ? '' : '?' . http_build_query($query)); } // ── Bestandsimport aus einer CSV-Datei ─────────────────────────────────── diff --git a/src/Views/wertpapiere/buchungen.php b/src/Views/wertpapiere/buchungen.php index f2aa4d0..5ca0a3c 100644 --- a/src/Views/wertpapiere/buchungen.php +++ b/src/Views/wertpapiere/buchungen.php @@ -11,6 +11,18 @@ use Finanzmanager\View; * @var string $csrfToken */ +// Nach dem Löschen soll dieselbe Ansicht wieder erscheinen – der Filter hängt +// deshalb am Ziel der Löschformulare. +$filterPfad = static function () use ($filter): string { + $query = array_filter([ + 'depot' => (string)($filter['konto_id'] ?? ''), + 'wertpapier' => (string)($filter['wertpapier_id'] ?? ''), + 'vorgang' => (string)($filter['vorgang'] ?? ''), + ], static fn(string $wert): bool => $wert !== ''); + + return $query === [] ? '' : '?' . http_build_query($query); +}; + include __DIR__ . '/../layout/kopf.php'; ?> @@ -53,6 +65,20 @@ include __DIR__ . '/../layout/kopf.php'; + + +
+ Depotbuchung erfassen @@ -62,6 +88,10 @@ include __DIR__ . '/../layout/kopf.php';| + + | Datum | Vorgang | Wertpapier | Depot | Stück | Kurs | Betrag | Kosten | @@ -71,6 +101,11 @@ include __DIR__ . '/../layout/kopf.php';
|---|---|---|---|---|---|---|---|---|
| + + | = View::h(View::datum($buchung['datum'])) ?> | = View::h(Depotbuchung::VORGAENGE[$buchung['vorgang']] ?? $buchung['vorgang']) ?> @@ -108,7 +143,8 @@ include __DIR__ . '/../layout/kopf.php'; | - |