Merge pull request 'Fix n.n.: Regelmäßige Buchungen – Buchen zum gewählten Datum' (#109) from fds2610-bot/Finanzmanager:main into main
Reviewed-on: https://codeberg.org/fds2610/Finanzmanager/pulls/109
This commit is contained in:
commit
18107959b9
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.
|
- 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`.
|
- 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)
|
## „+ 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.
|
- 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 () {
|
(function () {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const tabelle = document.querySelector('table.reihen');
|
const tabellen = document.querySelectorAll('table.reihen, table.faellig');
|
||||||
if (!tabelle) {
|
if (tabellen.length === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -59,6 +59,7 @@
|
||||||
anzeige.hidden = false;
|
anzeige.hidden = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tabellen.forEach(function (tabelle) {
|
||||||
tabelle.querySelectorAll('.feld').forEach(function (feld) {
|
tabelle.querySelectorAll('.feld').forEach(function (feld) {
|
||||||
const anzeige = feld.querySelector('.anzeige');
|
const anzeige = feld.querySelector('.anzeige');
|
||||||
const steuer = feld.querySelector('input, select');
|
const steuer = feld.querySelector('input, select');
|
||||||
|
|
@ -99,6 +100,7 @@
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
function geaendert(e) {
|
function geaendert(e) {
|
||||||
const zeile = e.target.closest('tr');
|
const zeile = e.target.closest('tr');
|
||||||
|
|
@ -107,6 +109,8 @@
|
||||||
knopf.hidden = false;
|
knopf.hidden = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
tabelle.addEventListener('input', geaendert);
|
tabellen.forEach(function (tabelle) {
|
||||||
tabelle.addEventListener('change', geaendert);
|
tabelle.addEventListener('input', geaendert);
|
||||||
|
tabelle.addEventListener('change', geaendert);
|
||||||
|
});
|
||||||
})();
|
})();
|
||||||
|
|
|
||||||
|
|
@ -169,7 +169,14 @@ class RegelmaessigeBuchung
|
||||||
throw new \RuntimeException('Die regelmäßige Buchung gibt es nicht.');
|
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'] !== ''
|
$betrag = array_key_exists('betrag', $abweichungen) && $abweichungen['betrag'] !== ''
|
||||||
? round((float)$abweichungen['betrag'], 2)
|
? round((float)$abweichungen['betrag'], 2)
|
||||||
: (float)$reihe['betrag'];
|
: (float)$reihe['betrag'];
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,13 @@ include __DIR__ . '/../layout/kopf.php';
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h3>Jetzt fällig</h3>
|
<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>
|
<thead>
|
||||||
<tr><th>Fällig am</th><th>Name</th><th>Konto</th><th class="rechts">Betrag</th>
|
<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>
|
<th>Art</th><th class="rechts">Aktion</th></tr>
|
||||||
|
|
@ -50,7 +56,14 @@ include __DIR__ . '/../layout/kopf.php';
|
||||||
<tbody>
|
<tbody>
|
||||||
<?php foreach ($faellig as $reihe): ?>
|
<?php foreach ($faellig as $reihe): ?>
|
||||||
<tr<?= (string)$reihe['naechste_faelligkeit'] < date('Y-m-d') ? ' class="ueberfaellig"' : '' ?>>
|
<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['name']) ?></td>
|
||||||
<td><?= View::h($reihe['kontoname']) ?></td>
|
<td><?= View::h($reihe['kontoname']) ?></td>
|
||||||
<td class="rechts <?= View::betragKlasse($reihe['betrag']) ?>">
|
<td class="rechts <?= View::betragKlasse($reihe['betrag']) ?>">
|
||||||
|
|
@ -62,10 +75,8 @@ include __DIR__ . '/../layout/kopf.php';
|
||||||
: '<span class="marke">auf Bestätigung</span>' ?>
|
: '<span class="marke">auf Bestätigung</span>' ?>
|
||||||
</td>
|
</td>
|
||||||
<td class="rechts nowrap">
|
<td class="rechts nowrap">
|
||||||
<form method="post" action="/termine/<?= (int)$reihe['id'] ?>/buchen" class="inline">
|
<button type="submit" form="buchen-<?= (int)$reihe['id'] ?>"
|
||||||
<input type="hidden" name="csrf_token" value="<?= View::h($csrfToken) ?>">
|
id="buchen-knopf-<?= (int)$reihe['id'] ?>" class="knopf klein primaer">Buchen</button>
|
||||||
<button type="submit" class="knopf klein primaer">Buchen</button>
|
|
||||||
</form>
|
|
||||||
<form method="post" action="/termine/<?= (int)$reihe['id'] ?>/ueberspringen" class="inline">
|
<form method="post" action="/termine/<?= (int)$reihe['id'] ?>/ueberspringen" class="inline">
|
||||||
<input type="hidden" name="csrf_token" value="<?= View::h($csrfToken) ?>">
|
<input type="hidden" name="csrf_token" value="<?= View::h($csrfToken) ?>">
|
||||||
<button type="submit" class="knopf klein">Überspringen</button>
|
<button type="submit" class="knopf klein">Überspringen</button>
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@ class RegelmaessigeLoeschenTest extends TestCase
|
||||||
$this->manuelleBuchungBleibtUnberuehrt();
|
$this->manuelleBuchungBleibtUnberuehrt();
|
||||||
$this->buchungstextWirdUebernommen();
|
$this->buchungstextWirdUebernommen();
|
||||||
$this->naechsterTerminLaesstSichSetzen();
|
$this->naechsterTerminLaesstSichSetzen();
|
||||||
|
$this->buchenMitAbweichendemDatum();
|
||||||
}
|
}
|
||||||
|
|
||||||
private function reihe(string $name, string $beginn, ?string $ende = null): int
|
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);
|
RegelmaessigeBuchung::speichern($id, ['naechste_faelligkeit' => 'Unsinn'] + $daten);
|
||||||
$this->assertEquals('2026-12-15', $this->faelligkeit($id), 'Ungültige Angaben werden ignoriert');
|
$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)
|
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 }) => {
|
test('Der Finanzkalender ist erreichbar und lässt sich einstellen', async ({ page }) => {
|
||||||
await page.goto('/termine/kalender')
|
await page.goto('/termine/kalender')
|
||||||
await expect(page.locator('h1')).toContainText('Finanzkalender')
|
await expect(page.locator('h1')).toContainText('Finanzkalender')
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue