// lib/models/report_transaction.dart class ReportTransaction { final String id; DateTime date; String description; double amount; final String direction; // 'pemasukan' atau 'pengeluaran' final String counterpartyName; // Nama akun lawan atau keterangan List tags; // Metadata Firefly III untuk keperluan sinkronisasi/edit String? transactionGroupId; String? transactionJournalId; // File attachments lokal untuk keperluan SPJ/Laporan String? fotoBarangPath; String? fotoBarangName; String? invoicePath; String? invoiceName; String? invoiceType; // 'pdf' atau 'image' String? invoiceSnapshotPath; // Path gambar PNG hasil render halaman PDF invoice ReportTransaction({ required this.id, required this.date, required this.description, required this.amount, required this.direction, required this.counterpartyName, this.tags = const [], this.transactionGroupId, this.transactionJournalId, this.fotoBarangPath, this.fotoBarangName, this.invoicePath, this.invoiceName, this.invoiceType, this.invoiceSnapshotPath, }); bool get isPengeluaran => direction == 'pengeluaran'; bool get isPemasukan => direction == 'pemasukan'; bool get hasFotoBarang => fotoBarangPath != null && fotoBarangPath!.isNotEmpty; bool get hasInvoice => invoicePath != null && invoicePath!.isNotEmpty; bool get hasInvoiceSnapshot => invoiceSnapshotPath != null && invoiceSnapshotPath!.isNotEmpty; /// Pengeluaran wajib foto barang & invoice. Pemasukan selalu dianggap lengkap. bool get isComplete => !isPengeluaran || (hasFotoBarang && hasInvoice); /// Status badge attachment untuk pengeluaran: 'complete', 'partial', atau 'missing' String get attachmentStatus { if (!isPengeluaran) return 'not_required'; if (hasFotoBarang && hasInvoice) return 'complete'; if (hasFotoBarang || hasInvoice) return 'partial'; return 'missing'; } ReportTransaction copyWith({ String? id, DateTime? date, String? description, double? amount, String? direction, String? counterpartyName, List? tags, String? transactionGroupId, String? transactionJournalId, String? fotoBarangPath, String? fotoBarangName, String? invoicePath, String? invoiceName, String? invoiceType, String? invoiceSnapshotPath, }) { return ReportTransaction( id: id ?? this.id, date: date ?? this.date, description: description ?? this.description, amount: amount ?? this.amount, direction: direction ?? this.direction, counterpartyName: counterpartyName ?? this.counterpartyName, tags: tags ?? List.from(this.tags), transactionGroupId: transactionGroupId ?? this.transactionGroupId, transactionJournalId: transactionJournalId ?? this.transactionJournalId, fotoBarangPath: fotoBarangPath ?? this.fotoBarangPath, fotoBarangName: fotoBarangName ?? this.fotoBarangName, invoicePath: invoicePath ?? this.invoicePath, invoiceName: invoiceName ?? this.invoiceName, invoiceType: invoiceType ?? this.invoiceType, invoiceSnapshotPath: invoiceSnapshotPath ?? this.invoiceSnapshotPath, ); } factory ReportTransaction.fromJson(Map json) { List parsedTags = []; if (json['tags'] is List) { parsedTags = (json['tags'] as List).map((e) => e.toString()).toList(); } return ReportTransaction( id: json['id']?.toString() ?? '', date: DateTime.tryParse(json['date']?.toString() ?? '') ?? DateTime.now(), description: json['description']?.toString() ?? '-', amount: (json['amount'] is num) ? (json['amount'] as num).toDouble() : double.tryParse(json['amount']?.toString() ?? '0') ?? 0.0, direction: json['direction']?.toString() ?? 'pengeluaran', counterpartyName: json['counterpartyName']?.toString() ?? '-', tags: parsedTags, transactionGroupId: json['transactionGroupId']?.toString(), transactionJournalId: json['transactionJournalId']?.toString(), fotoBarangPath: json['fotoBarangPath']?.toString(), fotoBarangName: json['fotoBarangName']?.toString(), invoicePath: json['invoicePath']?.toString(), invoiceName: json['invoiceName']?.toString(), invoiceType: json['invoiceType']?.toString(), invoiceSnapshotPath: json['invoiceSnapshotPath']?.toString(), ); } Map toJson() { return { 'id': id, 'date': date.toIso8601String(), 'description': description, 'amount': amount, 'direction': direction, 'counterpartyName': counterpartyName, 'tags': tags, 'transactionGroupId': transactionGroupId, 'transactionJournalId': transactionJournalId, 'fotoBarangPath': fotoBarangPath, 'fotoBarangName': fotoBarangName, 'invoicePath': invoicePath, 'invoiceName': invoiceName, 'invoiceType': invoiceType, 'invoiceSnapshotPath': invoiceSnapshotPath, }; } }