Merge pull request 'Fix #51: Adressverwaltung in der Verwaltung, Typeahead im Zahlungsverkehr' (#53) from fds2610-bot/Finanzmanager:main into main
Reviewed-on: https://codeberg.org/fds2610/Finanzmanager/pulls/53
This commit is contained in:
commit
2f524c72da
12 changed files with 349 additions and 4 deletions
18
Changes.md
18
Changes.md
|
|
@ -1,5 +1,23 @@
|
|||
# Änderungen
|
||||
|
||||
## Adressverwaltung in der Verwaltung, Typeahead im Zahlungsverkehr (31.08.2026)
|
||||
|
||||
Das Adressbuch (bisher nur über die Verträge-Kachel erreichbar) bekommt in der Verwaltung
|
||||
eine eigene Kachel „Adressverwaltung“ (Feature #51) – es speist auch den Zahlungsverkehr,
|
||||
der mit Verträgen nichts zu tun hat. Die Adressliste unter `/vertraege/adressen` bleibt die
|
||||
gleiche Seite; zu jeder Adresse klappt jetzt ein `<details>`-Element ihre letzten Buchungen
|
||||
auf. Da die Buchung kein Fremdschlüsselfeld auf die Adresse führt, sucht
|
||||
`Buchung::letzteFuerEmpfaenger()` kontenübergreifend nach dem exakten Empfängernamen – die
|
||||
eindeutige Kombination aus `benutzer_id` und `name` in `adressen` (Migration 008) macht das
|
||||
zuverlässig.
|
||||
|
||||
Die Empfängerauswahl im Zahlungsverkehr-Formular ist jetzt ein Typeahead: `kombobox.js`
|
||||
(schon für die Kategorie im Kontoblatt im Einsatz) baut das `<select>` in ein Eingabefeld mit
|
||||
gefilterter Liste um, ohne dass App- oder Formular-Code etwas davon merken – Wert und
|
||||
Ändern-Ereignis bleiben identisch. Geprüft wurde außerdem, dass „Empfänger ins Adressbuch
|
||||
übernehmen“ beim Erfassen einer Überweisung tatsächlich eine Adresse anlegt
|
||||
(`ZahlungsverkehrController::anlegen()` → `Adresse::anlegen()`).
|
||||
|
||||
## Zahlungsverkehr: automatische Bankauswahl (31.08.2026)
|
||||
|
||||
Beim Erfassen einer Überweisung musste bisher neben dem Auftragskonto auch die
|
||||
|
|
|
|||
|
|
@ -62,6 +62,24 @@ Das Adressbuch speist die Empfängerauswahl im Zahlungsverkehr. **IBANs werden g
|
|||
Gerechnet wird stückweise mit `%` statt mit `bcmod`: Die BC-Erweiterung ist keine
|
||||
Standardausstattung, und ein Modulo über sieben Ziffern lange Blöcke kommt ohne sie aus.
|
||||
|
||||
**Adressverwaltung.** Eine eigene Kachel in der Verwaltung führt zur Adressliste
|
||||
(`/vertraege/adressen`, weiter über `VertraegeController` gepflegt) – neben, nicht nur
|
||||
innerhalb der Verträge-Kachel: Das Adressbuch bedient auch den Zahlungsverkehr, der mit
|
||||
Verträgen nichts zu tun hat. Zu jeder Adresse klappt ein `<details>`-Element ihre letzten
|
||||
Buchungen auf, kontenübergreifend nach dem exakten Empfängernamen gesucht
|
||||
(`Buchung::letzteFuerEmpfaenger()`) – die Buchung führt kein Fremdschlüsselfeld auf die
|
||||
Adresse, nur den Freitext-Empfänger.
|
||||
|
||||
**Typeahead im Zahlungsverkehr.** Die Empfängerauswahl (`<select data-adressauswahl>`) trägt
|
||||
zusätzlich `data-kombobox` und wird wie die Kategorie im Kontoblatt (`kombobox.js`, siehe
|
||||
`docs/`-Test 35) zu einem Eingabefeld mit gefilterter Liste. Die Auswahl füllt Name, IBAN und
|
||||
BIC; geändert werden darf danach alles.
|
||||
|
||||
**Der Empfänger landet wirklich im Adressbuch**, wenn beim Erfassen einer Überweisung
|
||||
„Empfänger ins Adressbuch übernehmen“ angekreuzt ist – `ZahlungsverkehrController::anlegen()`
|
||||
ruft dafür `Adresse::anlegen()` auf, nachdem der Zahlungsauftrag erfolgreich angelegt wurde;
|
||||
gibt es die Adresse schon, bleibt der Fehler von `Adresse::anlegen()` (Duplikat) ohne Wirkung.
|
||||
|
||||
## Verträge
|
||||
|
||||
Der Nutzen liegt in der **Kündigungsfrist**: Die Übersicht zeigt, bis wann gekündigt sein
|
||||
|
|
|
|||
|
|
@ -1133,6 +1133,9 @@ tr.zugeklappt { display: none; }
|
|||
.filterleiste .kombobox { max-width: 15rem; }
|
||||
.filterleiste .kombobox-eingabe { padding: .3rem .45rem; font-size: .9rem; }
|
||||
|
||||
.formular .kombobox { width: 100%; }
|
||||
.formular .kombobox-eingabe { padding: .5rem .6rem; }
|
||||
|
||||
.zuordnungsauswahl .kombobox { width: 100%; max-width: 13rem; }
|
||||
.zuordnungsauswahl .kombobox-eingabe {
|
||||
height: 1.35rem;
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ declare(strict_types=1);
|
|||
namespace Finanzmanager\Controllers;
|
||||
|
||||
use Finanzmanager\Models\Adresse;
|
||||
use Finanzmanager\Models\Buchung;
|
||||
use Finanzmanager\Models\Kategorie;
|
||||
use Finanzmanager\Models\Konto;
|
||||
use Finanzmanager\Models\Kredit;
|
||||
|
|
@ -96,11 +97,18 @@ class VertraegeController extends BaseController
|
|||
{
|
||||
$this->verlangeAnmeldung();
|
||||
|
||||
$suche = $this->query('suche');
|
||||
$suche = $this->query('suche');
|
||||
$adressen = Adresse::alle($suche);
|
||||
|
||||
// Auf einen Blick, was zuletzt mit dem Empfänger gebucht wurde – ohne
|
||||
// eigene Seite, denn die Liste bleibt kurz genug für den Direktabruf.
|
||||
foreach ($adressen as $nummer => $adresse) {
|
||||
$adressen[$nummer]['buchungen'] = Buchung::letzteFuerEmpfaenger((string)$adresse['name']);
|
||||
}
|
||||
|
||||
$this->render('vertraege/adressen.php', [
|
||||
'titel' => 'Adressen',
|
||||
'adressen' => Adresse::alle($suche),
|
||||
'adressen' => $adressen,
|
||||
'suche' => $suche,
|
||||
]);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ declare(strict_types=1);
|
|||
|
||||
namespace Finanzmanager\Controllers;
|
||||
|
||||
use Finanzmanager\Models\Adresse;
|
||||
use Finanzmanager\Models\Bankinstitut;
|
||||
use Finanzmanager\Models\Einstellung;
|
||||
use Finanzmanager\Models\Erinnerung;
|
||||
|
|
@ -44,6 +45,7 @@ class VerwaltungController extends BaseController
|
|||
'vertraege' => count(Vertrag::alle(true)),
|
||||
'fristen' => count(Vertrag::fristenLaufenAb(90)),
|
||||
'bargeld' => count(Kategorie::bargeldAuswahl()),
|
||||
'adressen' => Adresse::anzahl(),
|
||||
],
|
||||
'bargeldAktiv' => BargeldAbgleich::eingerichtet(),
|
||||
]);
|
||||
|
|
|
|||
|
|
@ -487,6 +487,32 @@ class Buchung
|
|||
return $anzahlen;
|
||||
}
|
||||
|
||||
/**
|
||||
* Die jüngsten Buchungen zu einem Empfängernamen, kontenübergreifend – für
|
||||
* die Adressverwaltung: Wer sich eine Adresse ansieht, will wissen, was
|
||||
* zuletzt mit ihr gebucht wurde. Der Empfänger ist ein Freitextfeld, daher
|
||||
* zählt der exakte Name; die Kollation der Spalte macht das unabhängig von
|
||||
* Groß-/Kleinschreibung.
|
||||
*
|
||||
* @return list<array<string,mixed>>
|
||||
*/
|
||||
public static function letzteFuerEmpfaenger(string $empfaenger, int $limit = 5): array
|
||||
{
|
||||
if ($empfaenger === '') {
|
||||
return [];
|
||||
}
|
||||
|
||||
return Database::fetchAll(
|
||||
'SELECT b.id, b.datum, b.betrag, b.buchungstext, k.name AS kontoname
|
||||
FROM buchungen b
|
||||
JOIN konten k ON k.id = b.konto_id
|
||||
WHERE b.benutzer_id = ? AND b.empfaenger = ?
|
||||
ORDER BY b.datum DESC, b.id DESC
|
||||
LIMIT ' . max(1, $limit),
|
||||
[Mandant::id(), $empfaenger]
|
||||
);
|
||||
}
|
||||
|
||||
/** Wie viele Buchungen hat das Konto (beide Sichten)? */
|
||||
public static function anzahlFuerKonto(int $kontoId): int
|
||||
{
|
||||
|
|
|
|||
|
|
@ -90,6 +90,37 @@ include __DIR__ . '/../layout/kopf.php';
|
|||
class="knopf klein gefahr">Löschen</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="6">
|
||||
<details>
|
||||
<summary>
|
||||
Letzte Buchungen
|
||||
<?= $adresse['buchungen'] !== [] ? '(' . count($adresse['buchungen']) . ')' : '' ?>
|
||||
</summary>
|
||||
<?php if ($adresse['buchungen'] === []): ?>
|
||||
<p class="platzhalter">Noch keine Buchung mit diesem Empfänger.</p>
|
||||
<?php else: ?>
|
||||
<table class="liste">
|
||||
<thead>
|
||||
<tr><th>Datum</th><th>Konto</th><th>Buchungstext</th><th class="rechts">Betrag</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($adresse['buchungen'] as $buchung): ?>
|
||||
<tr>
|
||||
<td><?= View::datum($buchung['datum']) ?></td>
|
||||
<td><?= View::h($buchung['kontoname']) ?></td>
|
||||
<td><?= View::h($buchung['buchungstext']) ?></td>
|
||||
<td class="rechts <?= View::betragKlasse($buchung['betrag']) ?>">
|
||||
<?= View::betrag($buchung['betrag']) ?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
<?php endif; ?>
|
||||
</details>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
|
|
|
|||
|
|
@ -101,6 +101,18 @@ include __DIR__ . '/../layout/kopf.php';
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="kachel">
|
||||
<header><h2>Adressverwaltung</h2></header>
|
||||
<p class="kennzahl"><?= (int)$zahlen['adressen'] ?></p>
|
||||
<p class="platzhalter">
|
||||
Das Adressbuch der Zahlungsempfänger – speist die Empfängerauswahl im
|
||||
Zahlungsverkehr und zeigt zu jeder Adresse die letzten Buchungen.
|
||||
</p>
|
||||
<div class="knopfleiste">
|
||||
<a href="/vertraege/adressen" class="knopf klein primaer">Adressen verwalten</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="kachel">
|
||||
<header><h2>Termine</h2></header>
|
||||
<p class="kennzahl"><?= (int)$zahlen['reihen'] ?></p>
|
||||
|
|
|
|||
|
|
@ -65,8 +65,11 @@ include __DIR__ . '/../layout/kopf.php';
|
|||
<h3>Empfänger</h3>
|
||||
|
||||
<?php if ($adressen !== []): ?>
|
||||
<label for="adresse">Aus dem Adressbuch</label>
|
||||
<select id="adresse" name="adresse" data-adressauswahl>
|
||||
<?php /* Kein „for“ auf dem Label: kombobox.js legt vor das <select> ein
|
||||
sichtbares Eingabefeld ohne eigene id, das aria-label trägt die
|
||||
Beschriftung für die Vorleser weiter. */ ?>
|
||||
<label>Aus dem Adressbuch</label>
|
||||
<select id="adresse" name="adresse" aria-label="Aus dem Adressbuch" data-adressauswahl data-kombobox>
|
||||
<option value="">– von Hand eingeben –</option>
|
||||
<?php foreach ($adressen as $adresse): ?>
|
||||
<option value="<?= (int)$adresse['id'] ?>"
|
||||
|
|
@ -156,4 +159,8 @@ include __DIR__ . '/../layout/kopf.php';
|
|||
</form>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($adressen !== []): ?>
|
||||
<script src="/static/kombobox.js" defer></script>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php include __DIR__ . '/../layout/fuss.php'; ?>
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ class NavigationTest extends TestCase
|
|||
'kategorien' => 0, 'einnahmen' => 0, 'ausgaben' => 0, 'klassen' => 0,
|
||||
'konten' => 0, 'kontengruppen' => 0, 'bankinstitute' => 0, 'bargeld' => 0,
|
||||
'reihen' => 3, 'erinnerungen' => 2, 'vertraege' => 7, 'fristen' => 1,
|
||||
'adressen' => 4,
|
||||
];
|
||||
|
||||
public function run(): void
|
||||
|
|
@ -23,6 +24,7 @@ class NavigationTest extends TestCase
|
|||
$this->verwaltungLeuchtetFuerBeide();
|
||||
$this->verwaltungsseiteFuehrtZuDenTerminen();
|
||||
$this->verwaltungsseiteFuehrtZuDenVertraegen();
|
||||
$this->verwaltungsseiteFuehrtZurAdressverwaltung();
|
||||
$this->alleKnoepfeGleichUndFussbuendig();
|
||||
}
|
||||
|
||||
|
|
@ -169,6 +171,32 @@ class NavigationTest extends TestCase
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fix #51: Eine eigene Kachel für die Adressverwaltung – nicht nur der
|
||||
* kleine Knopf innerhalb der Verträge-Kachel, denn das Adressbuch bedient
|
||||
* auch den Zahlungsverkehr, der nichts mit Verträgen zu tun hat.
|
||||
*/
|
||||
private function verwaltungsseiteFuehrtZurAdressverwaltung(): void
|
||||
{
|
||||
$kacheln = $this->kacheln();
|
||||
|
||||
$this->assert(str_contains($kacheln, '<h2>Adressverwaltung</h2>'), 'Die Kachel heißt Adressverwaltung');
|
||||
$this->assert(str_contains($kacheln, '>4<'), 'Die Kennzahl zeigt die Zahl der Adressen');
|
||||
$this->assert(
|
||||
(bool)preg_match('#href="/vertraege/adressen" class="knopf klein#', $kacheln),
|
||||
'Der Knopf zu den Adressen ist klein'
|
||||
);
|
||||
|
||||
$this->assert(
|
||||
strrpos($kacheln, '<h2>Adressverwaltung</h2>') > (int)strrpos($kacheln, '<h2>Online-Banking</h2>'),
|
||||
'Die Adressverwaltung steht hinter dem Online-Banking'
|
||||
);
|
||||
$this->assert(
|
||||
strrpos($kacheln, '<h2>Termine</h2>') > (int)strrpos($kacheln, '<h2>Adressverwaltung</h2>'),
|
||||
'Die Termine stehen hinter der Adressverwaltung'
|
||||
);
|
||||
}
|
||||
|
||||
private function stilblatt(): string
|
||||
{
|
||||
return (string)file_get_contents(dirname(__DIR__) . '/public/static/app.css');
|
||||
|
|
|
|||
154
tests/38_AdressverwaltungTest.php
Normal file
154
tests/38_AdressverwaltungTest.php
Normal file
|
|
@ -0,0 +1,154 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Finanzmanager\Models\Adresse;
|
||||
use Finanzmanager\Models\Buchung;
|
||||
use Finanzmanager\Models\Konto;
|
||||
|
||||
/**
|
||||
* Fix #51: Adressverwaltung.
|
||||
*
|
||||
* Drei Teile: die letzten Buchungen zu einer Adresse (kontenübergreifend, nach
|
||||
* dem Empfängernamen), die entsprechende Anzeige auf der Adressseite, und der
|
||||
* Typeahead im Zahlungsverkehr, der aus dem Adressbuch heraus Name, IBAN und
|
||||
* BIC übernimmt – umgebaut wie schon die Kategorie im Kontoblatt (Test 35),
|
||||
* mit demselben kombobox.js.
|
||||
*/
|
||||
class AdressverwaltungTest extends TestCase
|
||||
{
|
||||
protected string $testName = 'Test 38: Adressverwaltung';
|
||||
|
||||
private int $girokonto = 0;
|
||||
private int $sparkonto = 0;
|
||||
|
||||
public function run(): void
|
||||
{
|
||||
$this->mandantAnlegen();
|
||||
$this->stammdaten();
|
||||
|
||||
$this->letzteBuchungenNachEmpfaengerKontenuebergreifend();
|
||||
$this->limitUndReihenfolge();
|
||||
$this->ohneTrefferBleibtDieListeLeer();
|
||||
$this->adressenseiteZeigtDieLetztenBuchungen();
|
||||
$this->zahlungsempfaengerIstEinTypeahead();
|
||||
}
|
||||
|
||||
private function stammdaten(): void
|
||||
{
|
||||
$this->girokonto = Konto::anlegen([
|
||||
'name' => 'Adressen-Giro', 'kontoart' => 'bank', 'anfangssaldo' => 0, 'aktiv' => 1,
|
||||
]);
|
||||
$this->sparkonto = Konto::anlegen([
|
||||
'name' => 'Adressen-Sparbuch', 'kontoart' => 'sparkonto', 'anfangssaldo' => 0, 'aktiv' => 1,
|
||||
]);
|
||||
}
|
||||
|
||||
private function letzteBuchungenNachEmpfaengerKontenuebergreifend(): void
|
||||
{
|
||||
Buchung::anlegen([
|
||||
'konto_id' => $this->girokonto, 'datum' => '2026-01-10', 'betrag' => -20.0,
|
||||
'empfaenger' => 'Stadtwerke Musterstadt', 'buchungstext' => 'Strom Januar',
|
||||
]);
|
||||
Buchung::anlegen([
|
||||
'konto_id' => $this->sparkonto, 'datum' => '2026-02-10', 'betrag' => -21.0,
|
||||
'empfaenger' => 'Stadtwerke Musterstadt', 'buchungstext' => 'Strom Februar',
|
||||
]);
|
||||
Buchung::anlegen([
|
||||
'konto_id' => $this->girokonto, 'datum' => '2026-01-15', 'betrag' => -5.0,
|
||||
'empfaenger' => 'Anderer Empfänger', 'buchungstext' => 'Nichts damit zu tun',
|
||||
]);
|
||||
|
||||
$treffer = Buchung::letzteFuerEmpfaenger('Stadtwerke Musterstadt');
|
||||
|
||||
$this->assertEquals(2, count($treffer), 'Beide Konten liefern ihre Buchung zum Empfänger');
|
||||
$this->assertEquals('Adressen-Sparbuch', $treffer[0]['kontoname'], 'Das Sparbuch führt (jüngstes Datum zuerst)');
|
||||
$this->assertEquals('Adressen-Giro', $treffer[1]['kontoname'], 'Das Girokonto folgt');
|
||||
}
|
||||
|
||||
private function limitUndReihenfolge(): void
|
||||
{
|
||||
for ($i = 1; $i <= 7; $i++) {
|
||||
Buchung::anlegen([
|
||||
'konto_id' => $this->girokonto, 'datum' => sprintf('2026-03-%02d', $i), 'betrag' => -1.0 * $i,
|
||||
'empfaenger' => 'Vielbucher GmbH', 'buchungstext' => 'Rechnung ' . $i,
|
||||
]);
|
||||
}
|
||||
|
||||
$treffer = Buchung::letzteFuerEmpfaenger('Vielbucher GmbH', 3);
|
||||
|
||||
$this->assertEquals(3, count($treffer), 'Das Limit wird eingehalten');
|
||||
$this->assertEquals('2026-03-07', $treffer[0]['datum'], 'Die jüngste Buchung steht vorn');
|
||||
$this->assertEquals('2026-03-05', $treffer[2]['datum'], 'Danach bricht die Liste beim Limit ab');
|
||||
}
|
||||
|
||||
private function ohneTrefferBleibtDieListeLeer(): void
|
||||
{
|
||||
$this->assertEquals([], Buchung::letzteFuerEmpfaenger('Niemand hat je an diesen Namen gebucht'), 'Keine Treffer, keine Zeilen');
|
||||
$this->assertEquals([], Buchung::letzteFuerEmpfaenger(''), 'Ein leerer Empfänger liefert keine Zeilen statt aller Buchungen');
|
||||
}
|
||||
|
||||
private function adressenseiteZeigtDieLetztenBuchungen(): void
|
||||
{
|
||||
Adresse::anlegen(['name' => 'Stadtwerke Musterstadt', 'iban' => 'DE02120300000000202051']);
|
||||
Adresse::anlegen(['name' => 'Ganz ohne Buchung']);
|
||||
|
||||
$adressen = Adresse::alle();
|
||||
foreach ($adressen as $nummer => $adresse) {
|
||||
$adressen[$nummer]['buchungen'] = Buchung::letzteFuerEmpfaenger((string)$adresse['name']);
|
||||
}
|
||||
|
||||
$html = $this->adressenHtml($adressen);
|
||||
|
||||
$this->assert(str_contains($html, 'Strom Februar'), 'Die letzte Buchung zur Adresse erscheint');
|
||||
$this->assert(str_contains($html, 'Strom Januar'), 'Auch die ältere Buchung erscheint');
|
||||
$this->assert(str_contains($html, 'Noch keine Buchung mit diesem Empfänger.'), 'Eine Adresse ohne Buchung sagt das auch so');
|
||||
$this->assert(str_contains($html, '<details>'), 'Die Buchungen stehen hinter einem Klappelement');
|
||||
}
|
||||
|
||||
private function zahlungsempfaengerIstEinTypeahead(): void
|
||||
{
|
||||
$html = $this->zahlungsverkehrFormularHtml();
|
||||
|
||||
$this->assert(
|
||||
(bool)preg_match(
|
||||
'#<select id="adresse" name="adresse" aria-label="Aus dem Adressbuch" data-adressauswahl data-kombobox>#',
|
||||
$html
|
||||
),
|
||||
'Die Adressauswahl ist als Kombobox (Typeahead) markiert'
|
||||
);
|
||||
$this->assert(str_contains($html, '/static/kombobox.js'), 'kombobox.js wird eingebunden');
|
||||
}
|
||||
|
||||
/** @param list<array<string,mixed>> $adressen */
|
||||
private function adressenHtml(array $adressen): string
|
||||
{
|
||||
return $this->rendere('vertraege/adressen.php', [
|
||||
'adressen' => $adressen,
|
||||
'suche' => '',
|
||||
]);
|
||||
}
|
||||
|
||||
private function zahlungsverkehrFormularHtml(): string
|
||||
{
|
||||
return $this->rendere('zahlungsverkehr/formular.php', [
|
||||
'auftrag' => null,
|
||||
'konten' => [],
|
||||
'adressen' => Adresse::alle(),
|
||||
'kategorien' => [],
|
||||
'klassen' => [],
|
||||
]);
|
||||
}
|
||||
|
||||
/** @param array<string,mixed> $daten */
|
||||
private function rendere(string $view, array $daten): string
|
||||
{
|
||||
$daten += ['titel' => 'Test', 'flash' => [], 'benutzer' => null, 'csrfToken' => 'x', 'aktuellerPfad' => '/'];
|
||||
|
||||
extract($daten, EXTR_SKIP);
|
||||
|
||||
ob_start();
|
||||
include dirname(__DIR__) . '/src/Views/' . $view;
|
||||
return (string)ob_get_clean();
|
||||
}
|
||||
}
|
||||
|
|
@ -309,4 +309,42 @@ test.describe('Module der Phasen 4 bis 7', () => {
|
|||
// werden, bevor eine eingerichtet ist. Geprüft wird sie erst beim Senden.
|
||||
await expect(page.locator('.meldung-fehler')).toContainText('IBAN')
|
||||
})
|
||||
|
||||
test('Eine Überweisung übernimmt den Empfänger auf Wunsch ins Adressbuch', async ({ page }) => {
|
||||
const empfaenger = `E2E-Adressbuch ${Date.now()}`
|
||||
|
||||
await page.goto('/zahlungsverkehr/neu')
|
||||
await page.fill('#empfaenger', empfaenger)
|
||||
await page.fill('#iban', 'DE02120300000000202051')
|
||||
await page.fill('#betrag', '12,34')
|
||||
await page.check('input[name="adresse_merken"]')
|
||||
await page.click('button:has-text("Erfassen")')
|
||||
|
||||
await expect(page.locator('.meldung-erfolg')).toContainText('erfasst')
|
||||
|
||||
// Die Adresse landet wirklich im Adressbuch – nicht nur der Auftrag.
|
||||
await page.goto('/vertraege/adressen')
|
||||
await expect(page.locator('table.liste')).toContainText(empfaenger)
|
||||
})
|
||||
|
||||
test('Die Empfängerauswahl im Zahlungsverkehr ist ein Typeahead aus dem Adressbuch', async ({ page }) => {
|
||||
const empfaenger = `E2E-Typeahead ${Date.now()}`
|
||||
|
||||
// Erst eine Adresse anlegen, damit die Auswahl etwas zu bieten hat.
|
||||
await page.goto('/vertraege/adressen')
|
||||
await page.fill('#name', empfaenger)
|
||||
await page.fill('#iban', 'DE02120300000000202051')
|
||||
await page.click('button:has-text("Adresse anlegen")')
|
||||
await expect(page.locator('.meldung-erfolg')).toContainText('angelegt')
|
||||
|
||||
await page.goto('/zahlungsverkehr/neu')
|
||||
|
||||
// kombobox.js ersetzt das <select> optisch durch ein Eingabefeld.
|
||||
const eingabe = page.locator('.kombobox-eingabe')
|
||||
await eingabe.click()
|
||||
await eingabe.fill(empfaenger)
|
||||
await page.locator('.kombobox-option', { hasText: empfaenger }).click()
|
||||
|
||||
await expect(page.locator('#iban')).toHaveValue('DE02120300000000202051')
|
||||
})
|
||||
})
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue