Finanzmanager/src/Controllers/BudgetController.php
fds2610-bot 20b2406219 Budgetplaner: drei Ansichten und Verteilen über das Zahnrad
Abgelaufene Monate werden nicht mehr zusammengefasst – alle zwölf Monate
stehen einzeln da und bleiben beplanbar. Statt Verlauf und alle Monate
gibt es Alles, Nur Budget und Nur IST; die Budgetspalte ist überall dort
editierbar, wo sie steht.

Das Zahnrad vor jedem Feld verteilt dessen Wert auf das ganze oder das
restliche Jahr. Es arbeitet auf den Eingabefeldern statt auf der
Datenbank: Das Ergebnis ist vor dem Speichern zu sehen und geht mit
demselben Knopf weg wie jede andere Änderung.

Nebenbei: app.js war doppelt eingebunden, jeder Handler lief zweimal.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 08:22:35 +02:00

227 lines
7 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
declare(strict_types=1);
namespace Finanzmanager\Controllers;
use Finanzmanager\Models\Budget;
use Finanzmanager\Services\Budgetplaner;
use Finanzmanager\View;
/**
* Der Budgetplaner: Kategoriezeilen mal zwölf Monatsspalten, dazu die
* Budgetverwaltung (anlegen, kopieren, aktivieren).
*
* Die Monatswerte werden nicht einzeln gespeichert, sondern die ganze Tabelle
* auf einmal – ein Formular um die Tabelle herum, ein POST. Das hält die
* Eingabe zusammen, wenn mehrere Zeilen geändert wurden, und lässt das
* Zahnrad einer Zelle ihren Wert vor dem Speichern über die Zeile verteilen.
*/
class BudgetController extends BaseController
{
public function planer(): void
{
$this->verlangeAnmeldung();
$budget = $this->gewaehltesBudget();
if ($budget === null) {
$this->render('planung/budget-leer.php', ['titel' => 'Budgetplaner']);
return;
}
$this->render('planung/budgetplaner.php', [
'titel' => 'Budgetplaner',
'budget' => $budget,
'budgets' => Budget::alle(),
'tabelle' => Budgetplaner::tabelle($budget),
'ansicht' => self::ansicht($this->query('ansicht')),
'laufenderMonat' => Budgetplaner::laufenderMonat((int)$budget['jahr']),
'nurGeplante' => $this->query('nur_geplante') === '1',
]);
}
/**
* Speichert die eingegebenen Monatswerte. Ein leeres Feld löscht die
* Position („nicht geplant“), 0 bleibt eine ausdrückliche Null.
*
* @param array<string,string> $params
*/
public function positionenSpeichern(array $params): void
{
$this->verlangeAnmeldung();
$id = (int)$params['id'];
$budget = Budget::find($id);
if ($budget === null) {
View::redirect('/planung/budget', 'Das Budget gibt es nicht.', 'fehler');
return;
}
$werte = $_POST['betrag'] ?? [];
$geaendert = 0;
if (is_array($werte)) {
foreach ($werte as $kategorieId => $monate) {
if (!is_array($monate)) {
continue;
}
foreach ($monate as $monat => $eingabe) {
$betrag = self::betrag((string)$eingabe);
Budget::positionSetzen($id, (int)$kategorieId, (int)$monat, $betrag);
$geaendert++;
}
}
}
View::redirect(
$this->zurueckZumPlaner($id),
$geaendert > 0 ? 'Budget gespeichert.' : 'Nichts zu speichern.'
);
}
public function verwaltung(): void
{
$this->verlangeAnmeldung();
$this->render('planung/budget-verwaltung.php', [
'titel' => 'Budgets',
'budgets' => Budget::alle(),
'jahr' => (int)date('Y'),
]);
}
public function anlegen(): void
{
$this->verlangeAnmeldung();
try {
$id = Budget::anlegen(
$this->post('name'),
(int)$this->post('jahr', date('Y')),
$this->post('beschreibung')
);
} catch (\Throwable $e) {
View::redirect('/planung/budget/verwaltung', $e->getMessage(), 'fehler');
return;
}
View::redirect('/planung/budget?budget=' . $id, 'Budget angelegt.');
}
/** @param array<string,string> $params */
public function bearbeiten(array $params): void
{
$this->verlangeAnmeldung();
$budget = Budget::find((int)$params['id']);
if ($budget === null) {
View::redirect('/planung/budget/verwaltung', 'Das Budget gibt es nicht.', 'fehler');
return;
}
$this->render('planung/budget-formular.php', [
'titel' => 'Budget bearbeiten',
'budget' => $budget,
]);
}
/** @param array<string,string> $params */
public function speichern(array $params): void
{
$this->verlangeAnmeldung();
try {
Budget::speichern(
(int)$params['id'],
$this->post('name'),
(int)$this->post('jahr', date('Y')),
$this->post('beschreibung')
);
} catch (\Throwable $e) {
View::redirect('/planung/budget/verwaltung', $e->getMessage(), 'fehler');
return;
}
View::redirect('/planung/budget/verwaltung', 'Budget gespeichert.');
}
public function kopieren(): void
{
$this->verlangeAnmeldung();
try {
$neu = Budget::kopieren(
(int)$this->post('vorlage'),
$this->post('name'),
(int)$this->post('jahr', date('Y'))
);
} catch (\Throwable $e) {
View::redirect('/planung/budget/verwaltung', $e->getMessage(), 'fehler');
return;
}
View::redirect('/planung/budget?budget=' . $neu, 'Budget kopiert – die Werte stehen im neuen Jahr.');
}
/** @param array<string,string> $params */
public function aktivieren(array $params): void
{
$this->verlangeAnmeldung();
Budget::aktivieren((int)$params['id']);
View::redirect('/planung/budget/verwaltung', 'Budget ist jetzt das aktive.');
}
/** @param array<string,string> $params */
public function loeschen(array $params): void
{
$this->verlangeAnmeldung();
Budget::loeschen((int)$params['id']);
View::redirect('/planung/budget/verwaltung', 'Budget gelöscht.');
}
/**
* Eingabe aus dem Planer: leeres Feld heißt „nicht geplant“ (null), sonst
* derselbe Zahlenparser wie in der Kontenmaske.
*/
public static function betrag(string $eingabe): ?float
{
return trim($eingabe) === '' ? null : KontenController::zahl($eingabe);
}
/**
* Die drei Ansichten des Planers: alle drei Wertspalten je Monat, nur die
* Budgetspalte oder nur die Ist-Spalte. Unbekanntes fällt auf „alles“
* zurück, damit eine alte Lesezeichen-URL nicht ins Leere zeigt.
*/
public static function ansicht(string $gewaehlt): string
{
return in_array($gewaehlt, ['budget', 'ist'], true) ? $gewaehlt : 'alles';
}
/** @return array<string,mixed>|null */
private function gewaehltesBudget(): ?array
{
$gewaehlt = (int)$this->query('budget');
if ($gewaehlt > 0) {
$budget = Budget::find($gewaehlt);
if ($budget !== null) {
return $budget;
}
}
return Budget::aktives();
}
/** Nach dem Speichern zurück in dieselbe Ansicht, nicht auf das aktive Budget. */
private function zurueckZumPlaner(int $id): string
{
$pfad = '/planung/budget?budget=' . $id . '&ansicht=' . self::ansicht($this->post('ansicht'));
if ($this->post('nur_geplante') === '1') {
$pfad .= '&nur_geplante=1';
}
return $pfad;
}
}