feat: add iOS support and harden receipt scanning

This commit is contained in:
Timo
2026-08-20 23:48:26 +02:00
parent f5e06c32b0
commit cf1b799e1b
107 changed files with 11755 additions and 122 deletions

View File

@@ -0,0 +1,55 @@
import Foundation
/// `/api/export/{csv,excel,pdf}` Pro-only (403 `pro_required` for a
/// free-plan account, surfaced via `APIError.server`). Every route takes the
/// same request body and returns the raw file with its name in
/// `Content-Disposition`; present the returned `Data` via `UIActivityViewController`
/// (share sheet) or write it into a temp file and use `.fileExporter` /
/// `QLPreviewController` either is fine, this layer just gets you the bytes.
enum ExportAPI {
enum Format: String {
case csv, excel, pdf
var path: String { "/api/export/\(rawValue)" }
var fallbackExtension: String {
switch self {
case .csv: return "csv"
case .excel: return "xlsx"
case .pdf: return "pdf"
}
}
}
struct ExportRequestBody: Encodable {
let receipts: [Receipt]
let locale: String
// Only meaningful for the PDF export (date range on the cover page);
// harmless to omit for csv/excel, which ignore unknown fields.
var dateFrom: String?
var dateTo: String?
}
struct ExportResult {
let data: Data
let fileName: String
}
/// - Parameters:
/// - receipts: exactly the receipts to include the backend does not
/// look anything up server-side, it only formats what you send.
/// - locale: `"de"` or `"en"`; anything else falls back to German on
/// the backend, so just always pass one of the two.
static func export(
_ format: Format,
receipts: [Receipt],
locale: String = "de",
dateFrom: String? = nil,
dateTo: String? = nil
) async throws -> ExportResult {
let body = ExportRequestBody(receipts: receipts, locale: locale, dateFrom: dateFrom, dateTo: dateTo)
let request = try APIRequest.json(path: format.path, method: .post, body: body)
let (data, suggestedName) = try await APIClient.shared.sendForFile(request)
let fileName = suggestedName ?? "Belege_Export.\(format.fallbackExtension)"
return ExportResult(data: data, fileName: fileName)
}
}