import Foundation /// Mirrors `ProcessedReceipt` (src/lib/schema/receipt.ts) exactly — this is /// the shape returned by `GET /api/receipts` and accepted by /// `POST /api/receipts`. Keep this file and that Zod schema in sync; when the /// web schema gains a field, add it here too rather than silently dropping it /// on decode (optional fields decode fine as `nil` when absent, so older /// receipts synced before a schema change won't crash the app). struct Receipt: Codable, Identifiable, Equatable { let id: String var projectId: String? let imageHash: String let originalFileName: String let fileSizeBytes: Int var previewUrl: String? let createdAt: String let updatedAt: String var status: ReceiptStatus var merchant: Merchant var date: ReceiptDate var documentType: DocumentType var receiptNumber: String? var currency: String var totalAmount: AmountWithConfidence var netAmount: Double? var tipAmount: Double? var taxBreakdown: [TaxBreakdownItem] var lineItems: [LineItem] var suggestedCategory: ReceiptCategory var paymentMethod: PaymentMethod? var hospitality: Hospitality? var validation: Validation /// Total actually paid: `totalAmount.value + tipAmount`. Mirrors /// `grossWithTip()` on the backend — tip is deliberately excluded from /// `totalAmount` there because VAT is never charged on it. var grossWithTip: Double { ((totalAmount.value + (tipAmount ?? 0)) * 100).rounded() / 100 } struct Merchant: Codable, Equatable { var name: String var address: String? var taxId: String? var confidence: Double } struct ReceiptDate: Codable, Equatable { var isoDate: String var time: String? var confidence: Double } struct AmountWithConfidence: Codable, Equatable { var value: Double var confidence: Double } struct TaxBreakdownItem: Codable, Equatable, Identifiable { var ratePercent: Double var taxAmount: Double var netAmount: Double? var id: String { "\(ratePercent)-\(taxAmount)" } } struct LineItem: Codable, Equatable, Identifiable { var id = UUID() var description: String var quantity: Double var price: Double var unitPrice: Double? var taxRate: Double? enum CodingKeys: String, CodingKey { case description, quantity, price, unitPrice, taxRate } } struct Hospitality: Codable, Equatable { var occasion: String? var participants: String? } struct Validation: Codable, Equatable { var isMathValid: Bool var isDuplicateSuspected: Bool var needsUserReview: Bool var reviewField: String var reviewReason: String? var issues: [Issue]? var userConfirmed: Bool? struct Issue: Codable, Equatable { var field: String var severity: String var message: String } } enum ReceiptStatus: String, Codable { case pending, processing, ready, needsReview = "needs_review", error } enum DocumentType: String, Codable, CaseIterable { case kassenbon = "KASSENBON" case rechnung = "RECHNUNG" case tankbeleg = "TANKBELEG" case bewirtungsbeleg = "BEWIRTUNGSBELEG" case parkticket = "PARKTICKET" case sonstiges = "SONSTIGES" } enum PaymentMethod: String, Codable, CaseIterable { case bar = "BAR" case ecKarte = "EC_KARTE" case kreditkarte = "KREDITKARTE" case ueberweisung = "UEBERWEISUNG" case paypal = "PAYPAL" case applePay = "APPLE_PAY" case googlePay = "GOOGLE_PAY" case sonstige = "SONSTIGE" } enum ReceiptCategory: String, Codable, CaseIterable { case bewirtung = "Bewirtung" case reisekosten = "Reisekosten & Hotel" case tanken = "Tanken & KFZ" case buerobedarf = "Bürobedarf & IT" case verpflegungsmehraufwand = "Verpflegungsmehraufwand" case material = "Material & Einkauf" case sonstiges = "Sonstiges" } } /// `GET /api/receipts` response envelope. struct ReceiptListResponse: Decodable { let success: Bool let receipts: [Receipt] let count: Int } /// `POST /api/receipts` response envelope (batch upsert). struct ReceiptSyncResponse: Decodable { let success: Bool let syncedCount: Int? let message: String? }