> */ public static function alle(bool $nurAktive = false): array { $sql = 'SELECT v.*, a.name AS adressname, k.pfad AS kategorie_pfad, r.name AS buchungsname FROM vertraege v LEFT JOIN adressen a ON a.id = v.adresse_id LEFT JOIN kategorien k ON k.id = v.kategorie_id LEFT JOIN regelmaessige_buchungen r ON r.id = v.regelmaessige_id WHERE v.benutzer_id = ?'; if ($nurAktive) { $sql .= ' AND v.aktiv = 1'; } return Database::fetchAll($sql . ' ORDER BY v.ende IS NULL, v.ende, v.name', [Mandant::id()]); } public static function find(int $id): ?array { $zeile = Database::fetchOne( 'SELECT * FROM vertraege WHERE id = ? AND benutzer_id = ?', [$id, Mandant::id()] ); return $zeile === false ? null : $zeile; } /** @param array $daten */ public static function anlegen(array $daten): int { $f = self::normalisiere($daten); Database::execute( 'INSERT INTO vertraege (benutzer_id, name, partner, adresse_id, vertragsnummer, kategorie_id, betrag, intervall, beginn, ende, kuendigungsfrist_tage, regelmaessige_id, notiz, aktiv) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)', [ Mandant::id(), $f['name'], $f['partner'], $f['adresse_id'], $f['vertragsnummer'], $f['kategorie_id'], $f['betrag'], $f['intervall'], $f['beginn'], $f['ende'], $f['kuendigungsfrist_tage'], $f['regelmaessige_id'], $f['notiz'], $f['aktiv'], ] ); return Database::lastInsertId(); } /** @param array $daten */ public static function speichern(int $id, array $daten): void { $f = self::normalisiere($daten); Database::execute( 'UPDATE vertraege SET name = ?, partner = ?, adresse_id = ?, vertragsnummer = ?, kategorie_id = ?, betrag = ?, intervall = ?, beginn = ?, ende = ?, kuendigungsfrist_tage = ?, regelmaessige_id = ?, notiz = ?, aktiv = ? WHERE id = ? AND benutzer_id = ?', [ $f['name'], $f['partner'], $f['adresse_id'], $f['vertragsnummer'], $f['kategorie_id'], $f['betrag'], $f['intervall'], $f['beginn'], $f['ende'], $f['kuendigungsfrist_tage'], $f['regelmaessige_id'], $f['notiz'], $f['aktiv'], $id, Mandant::id(), ] ); } public static function loeschen(int $id): void { Database::execute('DELETE FROM vertraege WHERE id = ? AND benutzer_id = ?', [$id, Mandant::id()]); } /** * Der letzte Tag, an dem noch gekündigt werden kann: Vertragsende minus * Frist. Ohne Ende oder ohne Frist gibt es keinen. */ public static function kuendigenBis(array $vertrag): ?string { $ende = (string)($vertrag['ende'] ?? ''); $tage = (int)($vertrag['kuendigungsfrist_tage'] ?? 0); if ($ende === '' || $tage <= 0) { return null; } return (new \DateTimeImmutable($ende))->modify('-' . $tage . ' days')->format('Y-m-d'); } /** * Verträge, deren Kündigungsfrist innerhalb der nächsten Tage abläuft – * genau die, die man leicht verpasst. * * @return list> */ public static function fristenLaufenAb(int $tage = 60, ?string $heute = null): array { $heute = $heute ?? date('Y-m-d'); $grenze = (new \DateTimeImmutable($heute))->modify('+' . $tage . ' days')->format('Y-m-d'); $treffer = []; foreach (self::alle(true) as $vertrag) { $stichtag = self::kuendigenBis($vertrag); if ($stichtag === null || $stichtag < $heute || $stichtag > $grenze) { continue; } $vertrag['kuendigen_bis'] = $stichtag; $treffer[] = $vertrag; } usort($treffer, static fn(array $a, array $b): int => strcmp($a['kuendigen_bis'], $b['kuendigen_bis'])); return $treffer; } /** Was der Vertrag im Jahr kostet – zum Vergleich verschieden getakteter Verträge. */ public static function jahresbetrag(array $vertrag): float { $betrag = (float)($vertrag['betrag'] ?? 0); return round($betrag * match ((string)($vertrag['intervall'] ?? 'monatlich')) { 'taeglich' => 365, 'woechentlich' => 52, 'zweiwoechentlich' => 26, 'monatlich' => 12, 'zweimonatlich' => 6, 'vierteljaehrlich' => 4, 'halbjaehrlich' => 2, default => 1, }, 2); } /** * @param array $daten * @return array */ private static function normalisiere(array $daten): array { $name = trim((string)($daten['name'] ?? '')); if ($name === '') { throw new \InvalidArgumentException('Der Vertrag braucht einen Namen.'); } $datum = static function (mixed $wert): ?string { $wert = trim((string)$wert); return Buchung::istDatum($wert) ? $wert : null; }; $id = static fn(mixed $wert): ?int => ($wert === null || $wert === '' || (int)$wert === 0) ? null : (int)$wert; $intervall = (string)($daten['intervall'] ?? 'monatlich'); return [ 'name' => mb_substr($name, 0, 255), 'partner' => mb_substr(trim((string)($daten['partner'] ?? '')), 0, 255), 'adresse_id' => $id($daten['adresse_id'] ?? null), 'vertragsnummer' => mb_substr(trim((string)($daten['vertragsnummer'] ?? '')), 0, 64), 'kategorie_id' => $id($daten['kategorie_id'] ?? null), 'betrag' => round((float)($daten['betrag'] ?? 0), 2), 'intervall' => array_key_exists($intervall, RegelmaessigeBuchung::INTERVALLE) ? $intervall : 'monatlich', 'beginn' => $datum($daten['beginn'] ?? ''), 'ende' => $datum($daten['ende'] ?? ''), 'kuendigungsfrist_tage' => max(0, min(999, (int)($daten['kuendigungsfrist_tage'] ?? 0))), 'regelmaessige_id' => $id($daten['regelmaessige_id'] ?? null), 'notiz' => trim((string)($daten['notiz'] ?? '')), 'aktiv' => array_key_exists('aktiv', $daten) && empty($daten['aktiv']) ? 0 : 1, ]; } }