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>
177 lines
7.3 KiB
PHP
177 lines
7.3 KiB
PHP
<?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');
|
||
}
|
||
}
|