Die Wertpapiermaske (Dialog im Kontoblatt und Seite ohne JavaScript) sowie „Depotbuchung erfassen“ bieten eine Klasse an. Im Kontoblatt ist sie mit der bisherigen Klasse der Buchung vorbelegt und gilt für die Buchung samt aller Splitzeilen der Nebenkosten; bei der Depotbuchung steht sie an der erzeugten Geldbuchung. Unbekannte Klassen werden abgewiesen. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
202 lines
7.4 KiB
PHP
202 lines
7.4 KiB
PHP
<?php
|
||
|
||
declare(strict_types=1);
|
||
|
||
use Finanzmanager\Models\Buchung;
|
||
use Finanzmanager\Models\Depotbuchung;
|
||
use Finanzmanager\Models\Einstellung;
|
||
use Finanzmanager\Models\Kategorie;
|
||
use Finanzmanager\Models\Klasse;
|
||
use Finanzmanager\Models\Konto;
|
||
use Finanzmanager\Models\Wertpapier;
|
||
use Finanzmanager\Services\Wertpapiergeschaeft;
|
||
use Finanzmanager\Services\Wertpapierkosten;
|
||
|
||
/**
|
||
* Fix #125: Beim Buchen von Wertpapieren lässt sich eine Klasse wählen – im
|
||
* Wertpapiergeschäft aus dem Kontoblatt wie in der Depotbuchung mit eigener
|
||
* Geldbuchung.
|
||
*/
|
||
class WertpapierKlasseTest extends TestCase
|
||
{
|
||
protected string $testName = 'Test 62: Klasse beim Buchen von Wertpapieren';
|
||
|
||
private int $giro = 0;
|
||
private int $depot = 0;
|
||
private int $klasse = 0;
|
||
private int $andereKlasse = 0;
|
||
|
||
public function run(): void
|
||
{
|
||
$this->mandantAnlegen();
|
||
|
||
$this->giro = Konto::anlegen([
|
||
'name' => 'KL Giro', 'kontoart' => 'bank', 'anfangssaldo' => 10000, 'aktiv' => 1,
|
||
]);
|
||
$this->depot = Konto::anlegen([
|
||
'name' => 'KL Depot', 'kontoart' => 'depot', 'anfangssaldo' => 0, 'aktiv' => 1,
|
||
]);
|
||
$this->klasse = Klasse::anlegen('KL Altersvorsorge');
|
||
$this->andereKlasse = Klasse::anlegen('KL Haushalt');
|
||
|
||
Einstellung::set(
|
||
Wertpapierkosten::schluessel('bankprovision'),
|
||
(string)Kategorie::anlegen(['name' => 'KL Bankprovision', 'art' => 'ausgabe']),
|
||
'wertpapiere'
|
||
);
|
||
|
||
$this->geschaeftOhneKosten();
|
||
$this->geschaeftMitKosten();
|
||
$this->leereAuswahlLoeschtDieKlasse();
|
||
$this->ohneAngabeBleibtDieKlasse();
|
||
$this->unbekannteKlasse();
|
||
$this->depotbuchungMitGeldbuchung();
|
||
}
|
||
|
||
private function buchung(float $betrag, string $datum, ?int $klasse = null): int
|
||
{
|
||
return Buchung::anlegen([
|
||
'konto_id' => $this->giro,
|
||
'datum' => $datum,
|
||
'betrag' => $betrag,
|
||
'klasse_id' => $klasse,
|
||
'quelle' => 'banking',
|
||
]);
|
||
}
|
||
|
||
private function geschaeftOhneKosten(): void
|
||
{
|
||
$id = $this->buchung(-500.0, '2026-09-01');
|
||
|
||
Wertpapiergeschaeft::buchen(Buchung::find($id), [
|
||
'depot_id' => $this->depot,
|
||
'wertpapier_name' => 'KL Ohne Kosten AG',
|
||
'stueck' => 5,
|
||
'klasse_id' => $this->klasse,
|
||
]);
|
||
|
||
$buchung = Buchung::find($id);
|
||
$this->assertEquals($this->depot, (int)$buchung['gegenkonto_id'], 'Die Umbuchung ins Depot entsteht');
|
||
$this->assertEquals($this->klasse, (int)$buchung['klasse_id'], 'Die gewählte Klasse steht an der Buchung');
|
||
}
|
||
|
||
private function geschaeftMitKosten(): void
|
||
{
|
||
$id = $this->buchung(-255.0, '2026-09-02');
|
||
|
||
Wertpapiergeschaeft::buchen(Buchung::find($id), [
|
||
'depot_id' => $this->depot,
|
||
'wertpapier_name' => 'KL Mit Kosten AG',
|
||
'stueck' => 2,
|
||
'kosten' => ['bankprovision' => 5.0],
|
||
'klasse_id' => $this->klasse,
|
||
]);
|
||
|
||
$buchung = Buchung::find($id);
|
||
$this->assertEquals($this->klasse, (int)$buchung['klasse_id'], 'Auch die Splitbuchung trägt die Klasse');
|
||
|
||
$splits = Buchung::splits($id);
|
||
$this->assertEquals(2, count($splits), 'Depot und Bankprovision');
|
||
foreach ($splits as $nr => $zeile) {
|
||
$this->assertEquals($this->klasse, (int)$zeile['klasse_id'], 'Splitzeile ' . $nr . ' trägt die Klasse');
|
||
}
|
||
|
||
$anzeige = Buchung::splitKlasse($splits, (int)$buchung['klasse_id']);
|
||
$this->assert(!$anzeige['gemischt'], 'Das Kontoblatt zeigt keine gemischten Klassen');
|
||
$this->assertEquals($this->klasse, $anzeige['klasse_id'], 'Sondern die eine gewählte Klasse');
|
||
}
|
||
|
||
/** Die Maske ist mit der Klasse der Buchung vorbelegt – leer heißt: keine. */
|
||
private function leereAuswahlLoeschtDieKlasse(): void
|
||
{
|
||
$id = $this->buchung(-100.0, '2026-09-03', $this->andereKlasse);
|
||
|
||
Wertpapiergeschaeft::buchen(Buchung::find($id), [
|
||
'depot_id' => $this->depot,
|
||
'wertpapier_name' => 'KL Leer AG',
|
||
'stueck' => 1,
|
||
'klasse_id' => null,
|
||
]);
|
||
|
||
$this->assertNull(Buchung::find($id)['klasse_id'], 'Die leere Auswahl entfernt die Klasse');
|
||
}
|
||
|
||
private function ohneAngabeBleibtDieKlasse(): void
|
||
{
|
||
$id = $this->buchung(-110.0, '2026-09-04', $this->andereKlasse);
|
||
|
||
Wertpapiergeschaeft::buchen(Buchung::find($id), [
|
||
'depot_id' => $this->depot,
|
||
'wertpapier_name' => 'KL Ohne Angabe AG',
|
||
'stueck' => 1,
|
||
'kosten' => ['bankprovision' => 10.0],
|
||
]);
|
||
|
||
$this->assertEquals(
|
||
$this->andereKlasse,
|
||
(int)Buchung::find($id)['klasse_id'],
|
||
'Ohne Angabe bleibt die Klasse der Buchung'
|
||
);
|
||
foreach (Buchung::splits($id) as $zeile) {
|
||
$this->assertEquals($this->andereKlasse, (int)$zeile['klasse_id'], 'Und die Splitzeilen übernehmen sie');
|
||
}
|
||
}
|
||
|
||
private function unbekannteKlasse(): void
|
||
{
|
||
$id = $this->buchung(-120.0, '2026-09-05');
|
||
|
||
$abgewiesen = false;
|
||
try {
|
||
Wertpapiergeschaeft::buchen(Buchung::find($id), [
|
||
'depot_id' => $this->depot,
|
||
'wertpapier_name' => 'KL Unbekannt AG',
|
||
'stueck' => 1,
|
||
'klasse_id' => 999999999,
|
||
]);
|
||
} catch (\InvalidArgumentException $e) {
|
||
$abgewiesen = true;
|
||
}
|
||
$this->assert($abgewiesen, 'Eine unbekannte Klasse wird abgewiesen');
|
||
$this->assertNull(Depotbuchung::findeZuBuchung($id), 'Und es entsteht kein Geschäft');
|
||
}
|
||
|
||
/** Die Depotbuchung unter Wertpapiere legt die Geldbuchung selbst an. */
|
||
private function depotbuchungMitGeldbuchung(): void
|
||
{
|
||
$papier = Wertpapier::findenOderAnlegen(['name' => 'KL Depot AG', 'isin' => 'DE000KL00001']);
|
||
|
||
$depotbuchungId = Depotbuchung::anlegen([
|
||
'konto_id' => $this->depot,
|
||
'wertpapier_id' => $papier,
|
||
'datum' => '2026-09-06',
|
||
'vorgang' => 'kauf',
|
||
'stueck' => 10,
|
||
'kurs' => 20,
|
||
'betrag' => 200,
|
||
'verrechnungskonto_id' => $this->giro,
|
||
'klasse_id' => $this->klasse,
|
||
]);
|
||
|
||
$geldbuchung = Buchung::find((int)Depotbuchung::find($depotbuchungId)['buchung_id']);
|
||
$this->assertNotEmpty($geldbuchung, 'Die Geldbuchung entsteht');
|
||
$this->assertEquals($this->klasse, (int)$geldbuchung['klasse_id'], 'Mit der gewählten Klasse');
|
||
|
||
$abgewiesen = false;
|
||
try {
|
||
Depotbuchung::anlegen([
|
||
'konto_id' => $this->depot,
|
||
'wertpapier_id' => $papier,
|
||
'datum' => '2026-09-07',
|
||
'vorgang' => 'kauf',
|
||
'stueck' => 1,
|
||
'betrag' => 20,
|
||
'verrechnungskonto_id' => $this->giro,
|
||
'klasse_id' => 999999999,
|
||
]);
|
||
} catch (\InvalidArgumentException $e) {
|
||
$abgewiesen = true;
|
||
}
|
||
$this->assert($abgewiesen, 'Auch hier wird eine unbekannte Klasse abgewiesen');
|
||
}
|
||
}
|