Finanzmanager/tests/22_DepotbuchungenLoeschenTest.php
fds2610-bot 4d62529e2c Wertpapiere: Depotbuchungen sammelweise löschen
Nach einem Import, der schiefging, war das Löschen Zeile für Zeile mühsam. Die
Liste der Depotbuchungen räumt jetzt so auf 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 nennt sie
ebenfalls. Ohne JavaScript funktioniert alles weiter – dann ist der Knopf offen
und der Controller 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. Zusammengesetzt wird
er im Controller neu, damit kein fremdes Ziel eingeschleust werden kann. Das
gilt jetzt auch für das „Löschen“ je Zeile, das bisher zur ungefilterten Liste
zurücksprang.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-12 14:27:51 +02:00

177 lines
7.3 KiB
PHP
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.

<?php
declare(strict_types=1);
use Finanzmanager\Models\Buchung;
use Finanzmanager\Models\Depotbuchung;
use Finanzmanager\Models\Konto;
use Finanzmanager\Models\Wertpapier;
use Finanzmanager\Services\Depot;
/**
* Sammellöschen in der Liste der Depotbuchungen.
*
* Geprüft wird, was hinter dem Knopf *Auswahl löschen* passiert: Jede gewählte
* Depotbuchung geht samt ihrer Geldbuchung, und Bestand, realisierter Gewinn
* und Erträge stehen danach wieder auf dem Stand, den sie ohne die Buchungen
* hätten – der Bestand wird gerechnet, nicht fortgeschrieben.
*/
class DepotbuchungenLoeschenTest extends TestCase
{
protected string $testName = 'Test 22: Depotbuchungen sammelweise löschen';
private int $depot = 0;
private int $verrechnung = 0;
private int $papier = 0;
public function run(): void
{
$this->mandantAnlegen();
$this->stammdaten();
$this->auswahlNimmtDieGeldbuchungenMit();
$this->bestandGibtDieLoeschungWiederHer();
$this->unbekannteNummernBleibenFolgenlos();
}
private function stammdaten(): void
{
$this->depot = Konto::anlegen([
'name' => 'Sammeldepot', 'kontoart' => 'depot', 'anfangssaldo' => 0, 'aktiv' => 1,
]);
$this->verrechnung = Konto::anlegen([
'name' => 'Sammelverrechnungskonto', 'kontoart' => 'bank',
'anfangssaldo' => 10000, 'aktiv' => 1,
]);
$this->papier = Wertpapier::findenOderAnlegen([
'name' => 'Sammelaktie AG', 'isin' => 'DE0007654321', 'art' => 'aktie',
]);
}
/**
* Zwei Käufe, ein Verkauf, eine Dividende – wie eine Auswahl in der Liste.
*
* @return list<int>
*/
private function vierBuchungen(): array
{
return [
Depotbuchung::anlegen([
'konto_id' => $this->depot, 'wertpapier_id' => $this->papier,
'datum' => '2026-01-15', 'vorgang' => 'kauf',
'stueck' => 10, 'kurs' => 100, 'betrag' => 1000, 'gebuehren' => 10,
'verrechnungskonto_id' => $this->verrechnung,
]),
Depotbuchung::anlegen([
'konto_id' => $this->depot, 'wertpapier_id' => $this->papier,
'datum' => '2026-02-15', 'vorgang' => 'kauf',
'stueck' => 10, 'kurs' => 120, 'betrag' => 1200, 'gebuehren' => 10,
'verrechnungskonto_id' => $this->verrechnung,
]),
Depotbuchung::anlegen([
'konto_id' => $this->depot, 'wertpapier_id' => $this->papier,
'datum' => '2026-03-15', 'vorgang' => 'verkauf',
'stueck' => 5, 'kurs' => 150, 'betrag' => 750,
'gebuehren' => 15, 'steuern' => 20,
'verrechnungskonto_id' => $this->verrechnung,
]),
Depotbuchung::anlegen([
'konto_id' => $this->depot, 'wertpapier_id' => $this->papier,
'datum' => '2026-04-01', 'vorgang' => 'dividende',
'betrag' => 50, 'steuern' => 13.19,
'verrechnungskonto_id' => $this->verrechnung,
]),
];
}
/** So arbeitet der Controller die Auswahl ab: Nummer für Nummer. */
private function auswahlLoeschen(array $ids): int
{
$anzahl = 0;
foreach ($ids as $id) {
if (Depotbuchung::find((int)$id) === null) {
continue;
}
Depotbuchung::loeschen((int)$id);
$anzahl++;
}
return $anzahl;
}
private function auswahlNimmtDieGeldbuchungenMit(): void
{
$ids = $this->vierBuchungen();
$geldIds = [];
foreach ($ids as $id) {
$geldIds[] = (int)Depotbuchung::find($id)['buchung_id'];
}
$this->assertEquals(4, count(array_filter($geldIds)), 'Jede Depotbuchung hat ihre Geldbuchung');
// Die beiden Käufe auswählen – der Rest bleibt stehen.
$this->assertEquals(2, $this->auswahlLoeschen([$ids[0], $ids[1]]), 'Zwei Buchungen gelöscht');
$this->assertNull(Depotbuchung::find($ids[0]), 'Der erste Kauf ist weg');
$this->assertNull(Depotbuchung::find($ids[1]), 'Der zweite Kauf auch');
$this->assertNotEmpty(Depotbuchung::find($ids[2]), 'Der Verkauf steht noch');
$this->assertNotEmpty(Depotbuchung::find($ids[3]), 'Die Dividende ebenso');
$this->assertNull(Buchung::find($geldIds[0]), 'Die Geldbuchung des ersten Kaufs ging mit');
$this->assertNull(Buchung::find($geldIds[1]), 'Die des zweiten Kaufs ebenso');
$this->assertNotEmpty(Buchung::find($geldIds[2]), 'Die des Verkaufs blieb unberührt');
// 10000 − 1010 − 1210 + 715 + 36,81; nach dem Löschen der Käufe fehlen
// deren Belastungen wieder.
$this->assertEquals(
10000.0 + 715.0 + 36.81,
Konto::saldo($this->verrechnung, '2026-12-31'),
'Das Verrechnungskonto trägt nur noch Verkauf und Dividende'
);
// Aufräumen für den nächsten Abschnitt.
$this->auswahlLoeschen([$ids[2], $ids[3]]);
}
private function bestandGibtDieLoeschungWiederHer(): void
{
Wertpapier::kursSetzen($this->papier, '2026-06-30', 130.0, 'test');
$leer = Depot::bestand($this->depot, '2026-12-31');
$this->assertEquals(0, count($leer['positionen']), 'Vorher ist das Depot leer');
$ids = $this->vierBuchungen();
$voll = Depot::bestand($this->depot, '2026-12-31');
$this->assertEquals(15.0, $voll['positionen'][0]['stueck'], 'Fünfzehn Stück nach zwei Käufen und einem Verkauf');
$this->assertGreaterThan(0.0, $voll['realisiert'], 'Der Verkauf hat etwas realisiert');
$this->assertEquals(36.81, $voll['ertraege'], 'Die Dividende zählt als Ertrag');
// Verkauf und Dividende zurücknehmen: Der Bestand steht wieder auf den
// beiden Käufen, realisierter Gewinn und Erträge fallen auf null.
$this->assertEquals(2, $this->auswahlLoeschen([$ids[2], $ids[3]]), 'Zwei Buchungen gelöscht');
$nachher = Depot::bestand($this->depot, '2026-12-31');
$this->assertEquals(20.0, $nachher['positionen'][0]['stueck'], 'Die verkauften Stücke sind zurück');
$this->assertEquals(2220.0, $nachher['positionen'][0]['einstand'], 'Und der volle Einstand beider Käufe');
$this->assertEquals(2600.0, $nachher['wert'], 'Zwanzig Stück zu 130 €');
$this->assertEquals(0.0, $nachher['realisiert'], 'Ohne Verkauf ist nichts realisiert');
$this->assertEquals(0.0, $nachher['ertraege'], 'Ohne Dividende gibt es keinen Ertrag');
$veraeusserungen = Depot::veraeusserungen('2026-01-01', '2026-12-31', $this->depot);
$this->assertEquals(0, count($veraeusserungen['geschaefte']), 'Kein Veräußerungsgeschäft mehr');
$this->assertEquals(0.0, $veraeusserungen['gewinn'], 'Und kein Gewinn in der Auswertung');
$this->auswahlLoeschen([$ids[0], $ids[1]]);
}
private function unbekannteNummernBleibenFolgenlos(): void
{
$id = $this->vierBuchungen()[0];
// Eine Nummer, die es nicht (mehr) gibt, wird übergangen – der Rest der
// Auswahl verschwindet trotzdem.
$this->assertEquals(1, $this->auswahlLoeschen([999999, $id]), 'Nur die vorhandene Buchung zählt');
$this->assertNull(Depotbuchung::find($id), 'Und sie ist gelöscht');
}
}