import Foundation // MARK: - Schlagwörter /// `KeywordOut` – ein Wort, an dem die Erkennung eine Kategorie festmacht. /// /// Nicht zu verwechseln mit `Schlagwort` (einem Tag): Ein Tag klebt an einer /// Buchung, ein Schlagwort hängt an einer **Kategorie** und wirkt nur auf /// künftige Importe. struct Erkennungswort: Codable, Sendable, Identifiable, Hashable { let id: Int let categoryId: Int let word: String /// Nur wenn der Betrag mitentscheiden soll – „Migros" über 200 ist /// Haushalt, darunter Lebensmittel. let amountCents: Int? private enum CodingKeys: String, CodingKey { case id, word case categoryId = "category_id" case amountCents = "amount_cents" } } /// `FlaggedKeywordOut` – ein Wort, das oft wieder korrigiert wird. /// /// Ab fünf Anwendungen und mindestens einem Drittel Korrekturen meldet das /// Backend es zur Prüfung. Behalten oder entfernen entscheidet der Mensch. struct MarkiertesWort: Codable, Sendable, Identifiable, Hashable { let id: Int let categoryId: Int? let categoryName: String let word: String let appliedCount: Int let overriddenCount: Int private enum CodingKeys: String, CodingKey { case id, word case categoryId = "category_id" case categoryName = "category_name" case appliedCount = "applied_count" case overriddenCount = "overridden_count" } } /// `KeywordCreate` struct ErkennungswortAnlegen: Encodable, Sendable { let word: String var amountCents: Int? private enum CodingKeys: String, CodingKey { case word case amountCents = "amount_cents" } } // MARK: - Kategorien schreiben /// `CategoryCreate` struct KategorieAnlegen: Encodable, Sendable { let groupId: Int let name: String var icon = "more-horizontal" var color = "#6b7480" /// Zählt in keiner Auswertung mit – etwa durchlaufende Posten. var excludedFromStats = false /// Eine Ausgabe, die kein Verlust ist: Sparen. var spendingIsGood = false private enum CodingKeys: String, CodingKey { case name, icon, color case groupId = "group_id" case excludedFromStats = "excluded_from_stats" case spendingIsGood = "spending_is_good" } } /// `CategoryUpdate` – nur die gesetzten Felder gehen mit. struct KategorieAendern: Encodable, Sendable { var name: String? var groupId: Int? var icon: String? var color: String? var isArchived: Bool? var excludedFromStats: Bool? var spendingIsGood: Bool? private enum CodingKeys: String, CodingKey { case name, icon, color case groupId = "group_id" case isArchived = "is_archived" case excludedFromStats = "excluded_from_stats" case spendingIsGood = "spending_is_good" } } /// `CategoryGroupCreate` struct GruppeAnlegen: Encodable, Sendable { let name: String var kind: Kategorieart = .expense var icon = "list" var color = "#6b7480" } /// `CategoryGroupUpdate` struct GruppeAendern: Encodable, Sendable { var name: String? var icon: String? var color: String? } // MARK: - Tags schreiben /// `TagCreate` struct TagAnlegen: Encodable, Sendable { let name: String var color = "#6b7480" } /// `TagUpdate` struct TagAendern: Encodable, Sendable { var name: String? var color: String? } // MARK: - Zweiter Faktor /// `TwoFactorStatusOut` struct ZweiterFaktorStand: Codable, Sendable, Hashable { let enabled: Bool let recoveryCodesLeft: Int private enum CodingKeys: String, CodingKey { case enabled case recoveryCodesLeft = "recovery_codes_left" } } /// `TwoFactorSetupOut` – das noch **nicht** aktive Geheimnis. /// /// Gültig wird es erst, wenn ein Code daraus geprüft wurde. Wer die /// Einrichtung abbricht, lässt einfach ein unbenutztes Geheimnis stehen. struct ZweiterFaktorEinrichtung: Codable, Sendable, Hashable { let secret: String let otpauthUri: String /// Der QR-Code als `data:`-URI. Auf dem Telefon selten gebraucht – dort /// führt der Weg über `otpauthUri` direkt in die Authenticator-App. let qrPng: String private enum CodingKeys: String, CodingKey { case secret case otpauthUri = "otpauth_uri" case qrPng = "qr_png" } } /// `RecoveryCodesOut` – einmal zu sehen, danach nie wieder. struct Rettungscodes: Codable, Sendable, Hashable { let codes: [String] } struct ZweiterFaktorCode: Encodable, Sendable { let code: String } // MARK: - Bank /// `BankConnectionOut` – eine bestehende Anbindung. /// /// Hergestellt wird sie am Rechner: Die Bank leitet fest auf /// `{base_url}/bank/rueckkehr` zurück. Abrufen geht von hier. struct Bankverbindung: Codable, Sendable, Identifiable, Hashable { let id: Int let aspspName: String let aspspCountry: String let validUntil: Zeitpunkt? let lastSyncAt: Zeitpunkt? /// Takt des automatischen Abrufs in Minuten, 0 = aus. let pollMinutes: Int let lastError: String? let expiresSoon: Bool private enum CodingKeys: String, CodingKey { case id case aspspName = "aspsp_name" case aspspCountry = "aspsp_country" case validUntil = "valid_until" case lastSyncAt = "last_sync_at" case pollMinutes = "poll_minutes" case lastError = "last_error" case expiresSoon = "expires_soon" } } /// Was ein Abruf zurückmeldet. struct Abrufergebnis: Codable, Sendable, Hashable { let geholt: Int let neu: Int let gebucht: Int? let vorgemerkt: Int? let doppelt: Int? let uebertraege: Int? /// Die Verbindung hat kein zugeordnetes Konto – dann wurde nichts geholt. let ohneZuordnung: Bool? private enum CodingKeys: String, CodingKey { case geholt, neu, gebucht, vorgemerkt, doppelt, uebertraege case ohneZuordnung = "ohne_zuordnung" } /// Ein Satz für die Rückmeldung. var meldung: String { if ohneZuordnung == true { return String(localized: "bank.ohneZuordnung") } return String(format: String(localized: "bank.abrufErgebnis"), geholt, neu) } }