> */ public static function alle(): array { $ziele = Database::fetchAll( 'SELECT s.*, k.name AS kontoname FROM sparziele s LEFT JOIN konten k ON k.id = s.konto_id WHERE s.benutzer_id = ? ORDER BY s.erreicht, s.zieldatum IS NULL, s.zieldatum, s.name', [Mandant::id()] ); foreach ($ziele as $nummer => $ziel) { $ziele[$nummer] = self::mitStand($ziel); } return $ziele; } public static function find(int $id): ?array { $zeile = Database::fetchOne( 'SELECT * FROM sparziele WHERE id = ? AND benutzer_id = ?', [$id, Mandant::id()] ); return $zeile === false ? null : self::mitStand($zeile); } /** * Ergänzt Stand, Fortschritt und die Rate, die bis zum Ziel nötig wäre. * * @param array $ziel * @return array */ public static function mitStand(array $ziel, ?string $heute = null): array { $heute = $heute ?? date('Y-m-d'); $ziel['stand'] = $ziel['konto_id'] === null ? 0.0 : round(Konto::saldo((int)$ziel['konto_id'], $heute) - (float)$ziel['startbetrag'], 2); $zielbetrag = (float)$ziel['zielbetrag']; $ziel['fehlt'] = round(max(0.0, $zielbetrag - $ziel['stand']), 2); $ziel['fortschritt'] = $zielbetrag > 0.0 ? max(0.0, min(1.0, $ziel['stand'] / $zielbetrag)) : 0.0; // Wie viel monatlich noch nötig wäre, um den Termin zu halten. $ziel['noetige_rate'] = null; $zieldatum = (string)($ziel['zieldatum'] ?? ''); if ($zieldatum !== '' && $zieldatum > $heute && $ziel['fehlt'] > 0.0) { $monate = self::monateBis($heute, $zieldatum); $ziel['noetige_rate'] = $monate > 0 ? round($ziel['fehlt'] / $monate, 2) : $ziel['fehlt']; } return $ziel; } /** @param array $daten */ public static function anlegen(array $daten): int { $f = self::normalisiere($daten); Database::execute( 'INSERT INTO sparziele (benutzer_id, name, konto_id, zielbetrag, startbetrag, zieldatum, monatsrate, notiz) VALUES (?, ?, ?, ?, ?, ?, ?, ?)', [ Mandant::id(), $f['name'], $f['konto_id'], $f['zielbetrag'], $f['startbetrag'], $f['zieldatum'], $f['monatsrate'], $f['notiz'], ] ); return Database::lastInsertId(); } /** @param array $daten */ public static function speichern(int $id, array $daten): void { $f = self::normalisiere($daten); Database::execute( 'UPDATE sparziele SET name = ?, konto_id = ?, zielbetrag = ?, startbetrag = ?, zieldatum = ?, monatsrate = ?, notiz = ?, erreicht = ? WHERE id = ? AND benutzer_id = ?', [ $f['name'], $f['konto_id'], $f['zielbetrag'], $f['startbetrag'], $f['zieldatum'], $f['monatsrate'], $f['notiz'], $f['erreicht'], $id, Mandant::id(), ] ); } public static function loeschen(int $id): void { Database::execute('DELETE FROM sparziele WHERE id = ? AND benutzer_id = ?', [$id, Mandant::id()]); } /** Volle Monate zwischen zwei Daten, mindestens einer. */ private static function monateBis(string $von, string $bis): int { $a = new \DateTimeImmutable(substr($von, 0, 7) . '-01'); $b = new \DateTimeImmutable(substr($bis, 0, 7) . '-01'); $differenz = $a->diff($b); return max(1, $differenz->y * 12 + $differenz->m); } /** * @param array $daten * @return array */ private static function normalisiere(array $daten): array { $name = trim((string)($daten['name'] ?? '')); if ($name === '') { throw new \InvalidArgumentException('Das Sparziel braucht einen Namen.'); } $zielbetrag = round((float)($daten['zielbetrag'] ?? 0), 2); if ($zielbetrag <= 0.0) { throw new \InvalidArgumentException('Der Zielbetrag muss größer als null sein.'); } $zieldatum = trim((string)($daten['zieldatum'] ?? '')); $rate = $daten['monatsrate'] ?? null; return [ 'name' => mb_substr($name, 0, 255), 'konto_id' => ($daten['konto_id'] ?? null) ?: null, 'zielbetrag' => $zielbetrag, 'startbetrag' => round((float)($daten['startbetrag'] ?? 0), 2), 'zieldatum' => Buchung::istDatum($zieldatum) ? $zieldatum : null, 'monatsrate' => ($rate === null || $rate === '') ? null : round((float)$rate, 2), 'notiz' => trim((string)($daten['notiz'] ?? '')), 'erreicht' => !empty($daten['erreicht']) ? 1 : 0, ]; } }