'Bank', 'kreditkarte' => 'Kreditkarte', 'bar' => 'Bar', 'sparkonto' => 'Tagesgeld/Sparkonto', 'vermoegen' => 'Vermögen', 'verbindlichkeit' => 'Verbindlichkeit', 'rente' => 'Rentenversicherung', 'sonstiges' => 'Sonstiges Vermögen', 'depot' => 'Depot', ]; /** * QIF-Kontotyp → Kontoart. "Oth E" und "Oth F" fasst der FinanzManager beim * Export zu sonstigem Vermögen zusammen, "Port" und "Invst" beide zum Depot. */ public const QIF_ARTEN = [ 'Bank' => 'bank', 'CCard' => 'kreditkarte', 'Cash' => 'bar', 'Oth T' => 'sparkonto', 'Oth A' => 'vermoegen', 'Oth L' => 'verbindlichkeit', 'Oth P' => 'rente', 'Oth E' => 'sonstiges', 'Oth F' => 'sonstiges', 'Port' => 'depot', 'Invst' => 'depot', ]; /** Konten, deren Saldo üblicherweise eine Schuld ausweist. */ public const SCHULDARTEN = ['kreditkarte', 'verbindlichkeit']; public static function artAusQif(string $qifTyp): string { $typ = trim($qifTyp); return self::QIF_ARTEN[$typ] ?? 'sonstiges'; } public static function bezeichnungDerArt(string $art): string { return self::ARTEN[$art] ?? $art; } /** @return list> */ public static function alle(bool $nurAktive = false): array { $sql = 'SELECT k.*, g.name AS gruppenname FROM konten k LEFT JOIN kontengruppen g ON g.id = k.kontengruppe_id WHERE k.benutzer_id = ?'; if ($nurAktive) { $sql .= ' AND k.aktiv = 1'; } return Database::fetchAll( $sql . ' ORDER BY g.sortierung IS NULL, g.sortierung, g.name, k.sortierung, k.name', [Mandant::id()] ); } public static function find(int $id): ?array { $zeile = Database::fetchOne( 'SELECT * FROM konten WHERE id = ? AND benutzer_id = ?', [$id, Mandant::id()] ); return $zeile === false ? null : $zeile; } public static function findeNachName(string $name): ?array { $zeile = Database::fetchOne( 'SELECT * FROM konten WHERE name = ? AND benutzer_id = ?', [trim($name), Mandant::id()] ); return $zeile === false ? null : $zeile; } /** * @param array $daten */ public static function anlegen(array $daten): int { $name = trim((string)($daten['name'] ?? '')); if ($name === '') { throw new \InvalidArgumentException('Das Konto braucht einen Namen.'); } if (self::findeNachName($name) !== null) { throw new \RuntimeException('Das Konto „' . $name . '“ gibt es bereits.'); } $felder = self::normalisiere($daten); Database::execute( 'INSERT INTO konten (benutzer_id, kontengruppe_id, name, kontoart, bankname, kontonummer, iban, bic, beschreibung, anfangssaldo, kreditlimit, waehrung, aktiv, sortierung) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)', [ Mandant::id(), $felder['kontengruppe_id'], $name, $felder['kontoart'], $felder['bankname'], $felder['kontonummer'], $felder['iban'], $felder['bic'], $felder['beschreibung'], $felder['anfangssaldo'], $felder['kreditlimit'], $felder['waehrung'], $felder['aktiv'], $felder['sortierung'], ] ); return Database::lastInsertId(); } /** * @param array $daten */ public static function speichern(int $id, array $daten): void { $name = trim((string)($daten['name'] ?? '')); if ($name === '') { throw new \InvalidArgumentException('Das Konto braucht einen Namen.'); } $andere = self::findeNachName($name); if ($andere !== null && (int)$andere['id'] !== $id) { throw new \RuntimeException('Das Konto „' . $name . '“ gibt es bereits.'); } $felder = self::normalisiere($daten); Database::execute( 'UPDATE konten SET kontengruppe_id = ?, name = ?, kontoart = ?, bankname = ?, kontonummer = ?, iban = ?, bic = ?, beschreibung = ?, anfangssaldo = ?, kreditlimit = ?, waehrung = ?, aktiv = ?, sortierung = ? WHERE id = ? AND benutzer_id = ?', [ $felder['kontengruppe_id'], $name, $felder['kontoart'], $felder['bankname'], $felder['kontonummer'], $felder['iban'], $felder['bic'], $felder['beschreibung'], $felder['anfangssaldo'], $felder['kreditlimit'], $felder['waehrung'], $felder['aktiv'], $felder['sortierung'], $id, Mandant::id(), ] ); } public static function loeschen(int $id): void { Database::execute('DELETE FROM konten WHERE id = ? AND benutzer_id = ?', [$id, Mandant::id()]); } public static function anzahl(): int { return (int)Database::fetchValue( 'SELECT COUNT(*) FROM konten WHERE benutzer_id = ?', [Mandant::id()], 0 ); } /** * Kontenübersicht: Konten nach Kontengruppe gebündelt, je Gruppe eine Summe * und darunter die Gesamtsumme. * * Die drei Saldospalten des FinanzManagers stehen schon jetzt in den Daten: * Online-Saldo (Stand des letzten Bankabrufs, Phase 5), aktueller Saldo * (gebuchte Umsätze bis heute) und erwarteter Saldo (einschließlich künftig * datierter Buchungen). Solange es keine Buchungen gibt, entsprechen alle * drei dem Anfangssaldo. * * @return array{gruppen: list>, summe: float} */ public static function uebersicht(bool $nurAktive = true): array { $gruppen = []; $summe = 0.0; foreach (self::alle($nurAktive) as $konto) { $konto = self::mitSalden($konto); $name = $konto['gruppenname'] ?? null; $name = ($name === null || $name === '') ? Kontengruppe::OHNE_GRUPPE : $name; if (!isset($gruppen[$name])) { $gruppen[$name] = ['name' => $name, 'konten' => [], 'summe' => 0.0]; } $gruppen[$name]['konten'][] = $konto; $gruppen[$name]['summe'] += $konto['saldo_erwartet']; $summe += $konto['saldo_erwartet']; } return ['gruppen' => array_values($gruppen), 'summe' => $summe]; } /** * Ergänzt die Saldospalten. Ab Phase 2 kommen hier die Buchungen dazu – * die Aufrufer bleiben davon unberührt. * * @param array $konto * @return array */ public static function mitSalden(array $konto): array { $anfang = (float)$konto['anfangssaldo']; $konto['saldo_online'] = null; $konto['saldo_aktuell'] = $anfang; $konto['saldo_erwartet'] = $anfang; return $konto; } /** * @param array $daten * @return array */ private static function normalisiere(array $daten): array { $art = (string)($daten['kontoart'] ?? 'bank'); if (!isset(self::ARTEN[$art])) { throw new \InvalidArgumentException('Unbekannte Kontoart: ' . $art); } $gruppe = $daten['kontengruppe_id'] ?? null; $gruppe = ($gruppe === null || $gruppe === '' || (int)$gruppe === 0) ? null : (int)$gruppe; $limit = $daten['kreditlimit'] ?? null; $limit = ($limit === null || $limit === '') ? null : (float)$limit; return [ 'kontengruppe_id' => $gruppe, 'kontoart' => $art, 'bankname' => trim((string)($daten['bankname'] ?? '')), 'kontonummer' => trim((string)($daten['kontonummer'] ?? '')), 'iban' => strtoupper(str_replace(' ', '', (string)($daten['iban'] ?? ''))), 'bic' => strtoupper(trim((string)($daten['bic'] ?? ''))), 'beschreibung' => trim((string)($daten['beschreibung'] ?? '')), 'anfangssaldo' => (float)($daten['anfangssaldo'] ?? 0), 'kreditlimit' => $limit, 'waehrung' => strtoupper(trim((string)($daten['waehrung'] ?? 'EUR'))) ?: 'EUR', 'aktiv' => !empty($daten['aktiv']) ? 1 : 0, 'sortierung' => (int)($daten['sortierung'] ?? 0), ]; } }