Fix n.n.: Regelmäßige Buchungen – Buchen zum gewählten Datum
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
176282d3a3
commit
1fc78abc11
6 changed files with 78 additions and 11 deletions
|
|
@ -11,6 +11,10 @@
|
|||
- Neue Buchungen ohne Nummer erhalten beim Anlegen die nächste laufende Nummer ihres Kontos (höchste rein numerische Nummer + 1, `Buchung::naechsteNummer()`). Vorgegebene Nummern (QIF, CSV, Formular) bleiben, bestehende Buchungen werden nicht verändert; die interne ID bleibt verborgen.
|
||||
- Test: `53_BuchungsnummerTest.php`.
|
||||
|
||||
## Regelmäßige Buchungen: Buchen zum gewählten Datum (01.10.2026)
|
||||
|
||||
- „Buchen“ in „Jetzt fällig“ schlug mit einer Fehlermeldung fehl, weil das Formular ein leeres Datum schickte. Jetzt ist „Fällig am“ per Klick editierbar; „Buchen“ bucht zu diesem Datum. Die Änderung gilt nur für diese Buchung und verändert die Reihe nicht. Ein leeres Datum fällt auf den Termin zurück, ein ungültiges wird abgewiesen.
|
||||
|
||||
## „+ Neu“ öffnet das Formular als Dialog (01.10.2026)
|
||||
|
||||
- Das Formular zur Neuanlage stand in elf Ansichten unter der Liste. Jetzt öffnet der Knopf „+ Neu“ es als modalen Dialog (`app.js`, `.neudialog`); nach dem Speichern erscheint die Liste wieder, mit dem neuen Eintrag. Betroffen: Klassen, Buchungsvorlagen, Regelmäßige Buchungen, Erinnerungen, Verträge, Adressen, Sparziele, Kredite, Abgleichregeln, Budgets, Berichtsgruppen.
|
||||
|
|
|
|||
|
|
@ -5,8 +5,8 @@
|
|||
(function () {
|
||||
'use strict';
|
||||
|
||||
const tabelle = document.querySelector('table.reihen');
|
||||
if (!tabelle) {
|
||||
const tabellen = document.querySelectorAll('table.reihen, table.faellig');
|
||||
if (tabellen.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -59,6 +59,7 @@
|
|||
anzeige.hidden = false;
|
||||
}
|
||||
|
||||
tabellen.forEach(function (tabelle) {
|
||||
tabelle.querySelectorAll('.feld').forEach(function (feld) {
|
||||
const anzeige = feld.querySelector('.anzeige');
|
||||
const steuer = feld.querySelector('input, select');
|
||||
|
|
@ -99,6 +100,7 @@
|
|||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function geaendert(e) {
|
||||
const zeile = e.target.closest('tr');
|
||||
|
|
@ -107,6 +109,8 @@
|
|||
knopf.hidden = false;
|
||||
}
|
||||
}
|
||||
tabellen.forEach(function (tabelle) {
|
||||
tabelle.addEventListener('input', geaendert);
|
||||
tabelle.addEventListener('change', geaendert);
|
||||
});
|
||||
})();
|
||||
|
|
|
|||
|
|
@ -169,7 +169,14 @@ class RegelmaessigeBuchung
|
|||
throw new \RuntimeException('Die regelmäßige Buchung gibt es nicht.');
|
||||
}
|
||||
|
||||
$datum = (string)($abweichungen['datum'] ?? $reihe['naechste_faelligkeit'] ?? date('Y-m-d'));
|
||||
$datum = trim((string)($abweichungen['datum'] ?? ''));
|
||||
if ($datum === '') {
|
||||
$datum = (string)($reihe['naechste_faelligkeit'] ?? date('Y-m-d'));
|
||||
}
|
||||
$pruef = \DateTimeImmutable::createFromFormat('!Y-m-d', $datum);
|
||||
if ($pruef === false || $pruef->format('Y-m-d') !== $datum) {
|
||||
throw new \InvalidArgumentException('Ungültiges Datum.');
|
||||
}
|
||||
$betrag = array_key_exists('betrag', $abweichungen) && $abweichungen['betrag'] !== ''
|
||||
? round((float)$abweichungen['betrag'], 2)
|
||||
: (float)$reihe['betrag'];
|
||||
|
|
|
|||
|
|
@ -42,7 +42,13 @@ include __DIR__ . '/../layout/kopf.php';
|
|||
</div>
|
||||
|
||||
<h3>Jetzt fällig</h3>
|
||||
<table class="liste">
|
||||
<?php foreach ($faellig as $reihe): ?>
|
||||
<form id="buchen-<?= (int)$reihe['id'] ?>" method="post"
|
||||
action="/termine/<?= (int)$reihe['id'] ?>/buchen">
|
||||
<input type="hidden" name="csrf_token" value="<?= View::h($csrfToken) ?>">
|
||||
</form>
|
||||
<?php endforeach; ?>
|
||||
<table class="liste faellig">
|
||||
<thead>
|
||||
<tr><th>Fällig am</th><th>Name</th><th>Konto</th><th class="rechts">Betrag</th>
|
||||
<th>Art</th><th class="rechts">Aktion</th></tr>
|
||||
|
|
@ -50,7 +56,14 @@ include __DIR__ . '/../layout/kopf.php';
|
|||
<tbody>
|
||||
<?php foreach ($faellig as $reihe): ?>
|
||||
<tr<?= (string)$reihe['naechste_faelligkeit'] < date('Y-m-d') ? ' class="ueberfaellig"' : '' ?>>
|
||||
<td><?= View::h(View::datum($reihe['naechste_faelligkeit'])) ?></td>
|
||||
<td>
|
||||
<div class="feld" data-datum>
|
||||
<span class="anzeige"><?= View::h(View::datum($reihe['naechste_faelligkeit'])) ?></span>
|
||||
<input type="date" form="buchen-<?= (int)$reihe['id'] ?>" name="datum" hidden
|
||||
id="buchen-datum-<?= (int)$reihe['id'] ?>"
|
||||
value="<?= View::h((string)$reihe['naechste_faelligkeit']) ?>">
|
||||
</div>
|
||||
</td>
|
||||
<td><?= View::h($reihe['name']) ?></td>
|
||||
<td><?= View::h($reihe['kontoname']) ?></td>
|
||||
<td class="rechts <?= View::betragKlasse($reihe['betrag']) ?>">
|
||||
|
|
@ -62,10 +75,8 @@ include __DIR__ . '/../layout/kopf.php';
|
|||
: '<span class="marke">auf Bestätigung</span>' ?>
|
||||
</td>
|
||||
<td class="rechts nowrap">
|
||||
<form method="post" action="/termine/<?= (int)$reihe['id'] ?>/buchen" class="inline">
|
||||
<input type="hidden" name="csrf_token" value="<?= View::h($csrfToken) ?>">
|
||||
<button type="submit" class="knopf klein primaer">Buchen</button>
|
||||
</form>
|
||||
<button type="submit" form="buchen-<?= (int)$reihe['id'] ?>"
|
||||
id="buchen-knopf-<?= (int)$reihe['id'] ?>" class="knopf klein primaer">Buchen</button>
|
||||
<form method="post" action="/termine/<?= (int)$reihe['id'] ?>/ueberspringen" class="inline">
|
||||
<input type="hidden" name="csrf_token" value="<?= View::h($csrfToken) ?>">
|
||||
<button type="submit" class="knopf klein">Überspringen</button>
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ class RegelmaessigeLoeschenTest extends TestCase
|
|||
$this->manuelleBuchungBleibtUnberuehrt();
|
||||
$this->buchungstextWirdUebernommen();
|
||||
$this->naechsterTerminLaesstSichSetzen();
|
||||
$this->buchenMitAbweichendemDatum();
|
||||
}
|
||||
|
||||
private function reihe(string $name, string $beginn, ?string $ende = null): int
|
||||
|
|
@ -125,4 +126,25 @@ class RegelmaessigeLoeschenTest extends TestCase
|
|||
RegelmaessigeBuchung::speichern($id, ['naechste_faelligkeit' => 'Unsinn'] + $daten);
|
||||
$this->assertEquals('2026-12-15', $this->faelligkeit($id), 'Ungültige Angaben werden ignoriert');
|
||||
}
|
||||
|
||||
private function buchenMitAbweichendemDatum(): void
|
||||
{
|
||||
$id = $this->reihe('Datumsbuchung', '2026-10-01');
|
||||
$b1 = RegelmaessigeBuchung::buchen($id, ['datum' => '', 'betrag' => '']);
|
||||
$this->assertEquals('2026-10-01', (string)Buchung::find($b1)['datum'], 'Leeres Datum bucht zum Termin');
|
||||
|
||||
$id = $this->reihe('Datumsbuchung 2', '2026-10-01');
|
||||
$termin = $this->faelligkeit($id);
|
||||
$b2 = RegelmaessigeBuchung::buchen($id, ['datum' => '2026-10-05']);
|
||||
$this->assertEquals('2026-10-05', (string)Buchung::find($b2)['datum'], 'Das gewählte Datum ist das Buchungsdatum');
|
||||
$this->assertEquals($termin, (string)Buchung::find($b2)['faelligkeit'], 'Der Termin der Reihe bleibt der gespeicherte');
|
||||
|
||||
$fehler = false;
|
||||
try {
|
||||
RegelmaessigeBuchung::buchen($id, ['datum' => '31.02.2026']);
|
||||
} catch (\InvalidArgumentException) {
|
||||
$fehler = true;
|
||||
}
|
||||
$this->assert($fehler, 'Ein ungültiges Datum wird abgewiesen');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -339,6 +339,25 @@ test.describe('Module der Phasen 4 bis 7', () => {
|
|||
await expect(page.locator('table.reihen tr', { hasText: 'E2E-Reihe' })).toHaveCount(0)
|
||||
})
|
||||
|
||||
test('Fälliges Datum lässt sich vor dem Buchen per Klick ändern', async ({ page }) => {
|
||||
await page.goto('/termine')
|
||||
await page.click('#neu-knopf')
|
||||
await page.fill('#name', 'E2E-Fällig')
|
||||
await page.fill('#neu_betrag', '-1,00')
|
||||
await page.click('form[action="/termine/neu"] button.primaer')
|
||||
|
||||
const zeile = page.locator('table.faellig tr', { hasText: 'E2E-Fällig' })
|
||||
const datum = zeile.locator('input[name="datum"]')
|
||||
await expect(datum).toBeHidden()
|
||||
await zeile.locator('.feld[data-datum] .anzeige').click()
|
||||
await expect(datum).toBeVisible()
|
||||
await expect(zeile.locator('button:has-text("Buchen")')).toBeVisible()
|
||||
|
||||
page.once('dialog', (dialog) => dialog.accept())
|
||||
await page.locator('table.reihen tr', { hasText: 'E2E-Fällig' }).locator('button:has-text("Löschen")').click()
|
||||
await expect(page.locator('table.reihen tr', { hasText: 'E2E-Fällig' })).toHaveCount(0)
|
||||
})
|
||||
|
||||
test('Der Finanzkalender ist erreichbar und lässt sich einstellen', async ({ page }) => {
|
||||
await page.goto('/termine/kalender')
|
||||
await expect(page.locator('h1')).toContainText('Finanzkalender')
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue