import 'dart:convert'; import 'package:appwrite/models.dart'; import 'auftrag_status.dart'; import 'dokument_typ.dart'; import 'zahlungs_status.dart'; /// Ein Datensatz = dokumentierter Vorgang (Fotos, Unterschrift, PDF). /// /// `fotoUrls` und `unterschriftUrl` speichern Appwrite-**Datei-IDs** aus dem Bucket /// (oder ältere http-URLs aus früheren Versionen). /// /// In Appwrite liegen **alle** Felder (inkl. Fotos/Unterschrift/Kernfelder) in /// **einem** String `extendedJson` als JSON mit `v: 2`, plus `userId` — wegen /// des niedrigen Attribut-Limits in Appwrite 1.8.x. Ältere flache Dokumente /// und `v: 1`-JSON werden weiter gelesen. class Auftrag { Auftrag({ required this.id, required this.titel, required this.beschreibung, required this.kundenName, required this.kundenAdresse, required this.kundenEmail, required this.rechnungsnummer, required this.betragText, required this.fotoUrls, required this.unterschriftUrl, required this.createdAt, required this.status, this.dokumentTyp = DokumentTyp.rechnung, this.zahlungsStatus = ZahlungsStatus.offen, this.faelligAm, this.leistungsDatum, this.kleinunternehmer = false, this.reverseCharge = false, this.skontoText = '', this.ustIdKunde = '', this.ibanVerkaeufer = '', this.bicVerkaeufer = '', this.kontoinhaberVerkaeufer = '', }); final String id; final String titel; final String beschreibung; final String kundenName; final String kundenAdresse; final String kundenEmail; final String rechnungsnummer; final String betragText; final List fotoUrls; final String? unterschriftUrl; final DateTime? createdAt; final AuftragStatus status; final DokumentTyp dokumentTyp; final ZahlungsStatus zahlungsStatus; final DateTime? faelligAm; final DateTime? leistungsDatum; final bool kleinunternehmer; final bool reverseCharge; final String skontoText; final String ustIdKunde; final String ibanVerkaeufer; final String bicVerkaeufer; final String kontoinhaberVerkaeufer; bool get hasUnterschrift => unterschriftUrl != null && unterschriftUrl!.isNotEmpty; static bool _parseBool(dynamic v) { if (v == true) return true; if (v == false) return false; final s = v?.toString().trim().toLowerCase(); return s == '1' || s == 'true' || s == 'yes' || s == 'ja'; } static List _parseFotoListe(dynamic v) { if (v == null) return []; if (v is List) { return v.map((e) => e.toString()).where((s) => s.isNotEmpty).toList(); } final s = v.toString(); if (s.isEmpty) return []; return s.split(',').map((e) => e.trim()).where((e) => e.isNotEmpty).toList(); } static DateTime? _parseDate(dynamic v) { if (v == null) return null; final s = v.toString().trim(); if (s.isEmpty) return null; return DateTime.tryParse(s); } static Map? _parseExtendedJson(String? raw) { if (raw == null || raw.trim().isEmpty) return null; try { final o = jsonDecode(raw); if (o is Map) return o; if (o is Map) return Map.from(o); } catch (_) {} return null; } factory Auftrag.fromAppwriteDoc(Document doc) { final d = doc.data; final ext = _parseExtendedJson(d['extendedJson'] as String?); if (ext != null && ext['v'] == 2) { final betrag = ext['betragText'] as String? ?? ''; return Auftrag( id: doc.$id, titel: ext['titel'] as String? ?? '', beschreibung: ext['beschreibung'] as String? ?? '', kundenName: ext['kundenName'] as String? ?? '', kundenAdresse: ext['kundenAdresse'] as String? ?? '', kundenEmail: ext['kundenEmail'] as String? ?? '', rechnungsnummer: ext['rechnungsnummer'] as String? ?? '', betragText: betrag, fotoUrls: _parseFotoListe(ext['fotoUrls']), unterschriftUrl: ext['unterschriftUrl'] as String?, createdAt: DateTime.tryParse(doc.$createdAt), status: auftragStatusFromStorage( ext['status'] as String?, betragTextLegacy: betrag, ), dokumentTyp: dokumentTypFromStorage(ext['dokumentTyp'] as String?), zahlungsStatus: zahlungsStatusFromStorage(ext['zahlungsStatus'] as String?), faelligAm: _parseDate(ext['faelligAm']), leistungsDatum: _parseDate(ext['leistungsDatum']), kleinunternehmer: _parseBool(ext['kleinunternehmer']), reverseCharge: _parseBool(ext['reverseCharge']), skontoText: ext['skontoText'] as String? ?? '', ustIdKunde: ext['ustIdKunde'] as String? ?? '', ibanVerkaeufer: ext['ibanVerkaeufer'] as String? ?? '', bicVerkaeufer: ext['bicVerkaeufer'] as String? ?? '', kontoinhaberVerkaeufer: ext['kontoinhaberVerkaeufer'] as String? ?? '', ); } final betrag = d['betragText'] as String? ?? ''; DokumentTyp dokumentTyp; ZahlungsStatus zahlungsStatus; DateTime? faelligAm; DateTime? leistungsDatum; bool kleinunternehmer; bool reverseCharge; String skontoText; String ustIdKunde; String ibanVerkaeufer; String bicVerkaeufer; String kontoinhaberVerkaeufer; if (ext != null) { dokumentTyp = dokumentTypFromStorage(ext['dokumentTyp'] as String?); zahlungsStatus = zahlungsStatusFromStorage(ext['zahlungsStatus'] as String?); faelligAm = _parseDate(ext['faelligAm']); leistungsDatum = _parseDate(ext['leistungsDatum']); kleinunternehmer = _parseBool(ext['kleinunternehmer']); reverseCharge = _parseBool(ext['reverseCharge']); skontoText = ext['skontoText'] as String? ?? ''; ustIdKunde = ext['ustIdKunde'] as String? ?? ''; ibanVerkaeufer = ext['ibanVerkaeufer'] as String? ?? ''; bicVerkaeufer = ext['bicVerkaeufer'] as String? ?? ''; kontoinhaberVerkaeufer = ext['kontoinhaberVerkaeufer'] as String? ?? ''; } else { dokumentTyp = dokumentTypFromStorage(d['dokumentTyp'] as String?); zahlungsStatus = zahlungsStatusFromStorage(d['zahlungsStatus'] as String?); faelligAm = _parseDate(d['faelligAm']); leistungsDatum = _parseDate(d['leistungsDatum']); kleinunternehmer = _parseBool(d['kleinunternehmer']); reverseCharge = _parseBool(d['reverseCharge']); skontoText = d['skontoText'] as String? ?? ''; ustIdKunde = d['ustIdKunde'] as String? ?? ''; ibanVerkaeufer = d['ibanVerkaeufer'] as String? ?? ''; bicVerkaeufer = d['bicVerkaeufer'] as String? ?? ''; kontoinhaberVerkaeufer = d['kontoinhaberVerkaeufer'] as String? ?? ''; } return Auftrag( id: doc.$id, titel: d['titel'] as String? ?? '', beschreibung: d['beschreibung'] as String? ?? '', kundenName: d['kundenName'] as String? ?? '', kundenAdresse: d['kundenAdresse'] as String? ?? '', kundenEmail: d['kundenEmail'] as String? ?? '', rechnungsnummer: d['rechnungsnummer'] as String? ?? '', betragText: betrag, fotoUrls: _parseFotoListe(d['fotoUrls']), unterschriftUrl: d['unterschriftUrl'] as String?, createdAt: DateTime.tryParse(doc.$createdAt), status: auftragStatusFromStorage( d['status'] as String?, betragTextLegacy: betrag, ), dokumentTyp: dokumentTyp, zahlungsStatus: zahlungsStatus, faelligAm: faelligAm, leistungsDatum: leistungsDatum, kleinunternehmer: kleinunternehmer, reverseCharge: reverseCharge, skontoText: skontoText, ustIdKunde: ustIdKunde, ibanVerkaeufer: ibanVerkaeufer, bicVerkaeufer: bicVerkaeufer, kontoinhaberVerkaeufer: kontoinhaberVerkaeufer, ); } String _encodeAppwriteExtendedJson() { return jsonEncode({ 'v': 2, 'titel': titel, 'beschreibung': beschreibung, 'kundenName': kundenName, 'kundenAdresse': kundenAdresse, 'kundenEmail': kundenEmail, 'rechnungsnummer': rechnungsnummer, 'betragText': betragText, 'fotoUrls': fotoUrls, 'unterschriftUrl': unterschriftUrl, 'status': status.storageValue, 'dokumentTyp': dokumentTyp.storageValue, 'zahlungsStatus': zahlungsStatus.storageValue, 'faelligAm': faelligAm?.toIso8601String() ?? '', 'leistungsDatum': leistungsDatum?.toIso8601String() ?? '', 'kleinunternehmer': kleinunternehmer, 'reverseCharge': reverseCharge, 'skontoText': skontoText, 'ustIdKunde': ustIdKunde, 'ibanVerkaeufer': ibanVerkaeufer, 'bicVerkaeufer': bicVerkaeufer, 'kontoinhaberVerkaeufer': kontoinhaberVerkaeufer, }); } /// Payload für Appwrite `data` — nur [extendedJson]; [userId] setzt das Repo. Map toMap() { return { 'extendedJson': _encodeAppwriteExtendedJson(), }; } /// JSON-fähige Darstellung für Datenauskunft/Export (ohne Binärdateien). Map toExportMap() { return { 'id': id, 'titel': titel, 'beschreibung': beschreibung, 'kundenName': kundenName, 'kundenAdresse': kundenAdresse, 'kundenEmail': kundenEmail, 'rechnungsnummer': rechnungsnummer, 'betragText': betragText, 'status': status.storageValue, 'dokumentTyp': dokumentTyp.storageValue, 'zahlungsStatus': zahlungsStatus.storageValue, 'faelligAm': faelligAm?.toIso8601String(), 'leistungsDatum': leistungsDatum?.toIso8601String(), 'kleinunternehmer': kleinunternehmer, 'reverseCharge': reverseCharge, 'skontoText': skontoText, 'ustIdKunde': ustIdKunde, 'ibanVerkaeufer': ibanVerkaeufer, 'bicVerkaeufer': bicVerkaeufer, 'kontoinhaberVerkaeufer': kontoinhaberVerkaeufer, 'fotoDateiIds': fotoUrls, 'unterschriftDateiId': unterschriftUrl, 'createdAt': createdAt?.toIso8601String(), }; } Auftrag copyWith({ String? id, String? titel, String? beschreibung, String? kundenName, String? kundenAdresse, String? kundenEmail, String? rechnungsnummer, String? betragText, List? fotoUrls, String? unterschriftUrl, DateTime? createdAt, AuftragStatus? status, DokumentTyp? dokumentTyp, ZahlungsStatus? zahlungsStatus, DateTime? faelligAm, DateTime? leistungsDatum, bool? kleinunternehmer, bool? reverseCharge, String? skontoText, String? ustIdKunde, String? ibanVerkaeufer, String? bicVerkaeufer, String? kontoinhaberVerkaeufer, }) { return Auftrag( id: id ?? this.id, titel: titel ?? this.titel, beschreibung: beschreibung ?? this.beschreibung, kundenName: kundenName ?? this.kundenName, kundenAdresse: kundenAdresse ?? this.kundenAdresse, kundenEmail: kundenEmail ?? this.kundenEmail, rechnungsnummer: rechnungsnummer ?? this.rechnungsnummer, betragText: betragText ?? this.betragText, fotoUrls: fotoUrls ?? List.from(this.fotoUrls), unterschriftUrl: unterschriftUrl ?? this.unterschriftUrl, createdAt: createdAt ?? this.createdAt, status: status ?? this.status, dokumentTyp: dokumentTyp ?? this.dokumentTyp, zahlungsStatus: zahlungsStatus ?? this.zahlungsStatus, faelligAm: faelligAm ?? this.faelligAm, leistungsDatum: leistungsDatum ?? this.leistungsDatum, kleinunternehmer: kleinunternehmer ?? this.kleinunternehmer, reverseCharge: reverseCharge ?? this.reverseCharge, skontoText: skontoText ?? this.skontoText, ustIdKunde: ustIdKunde ?? this.ustIdKunde, ibanVerkaeufer: ibanVerkaeufer ?? this.ibanVerkaeufer, bicVerkaeufer: bicVerkaeufer ?? this.bicVerkaeufer, kontoinhaberVerkaeufer: kontoinhaberVerkaeufer ?? this.kontoinhaberVerkaeufer, ); } }