// lib/services/receipt_service.dart import 'package:shared_preferences/shared_preferences.dart'; import 'package:cashumit/models/firefly_account.dart'; import 'package:cashumit/models/receipt_item.dart'; import 'package:cashumit/services/firefly_api_service.dart'; class ReceiptService { /// Memuat kredensial dari shared preferences. /// /// Mengembalikan Map dengan key 'url' dan 'token' jika kredensial ada dan valid, /// atau null jika tidak ditemukan atau tidak valid. static Future?> loadCredentials() async { final prefs = await SharedPreferences.getInstance(); final url = prefs.getString('firefly_url'); final token = prefs.getString('firefly_token'); if (url == null || token == null || url.isEmpty || token.isEmpty) { return null; } return {'url': url, 'token': token}; } /// Memuat daftar akun sumber (revenue) dan tujuan (asset) dari API. /// /// Mengembalikan daftar akun yang berisi akun revenue dan asset. /// Melempar exception jika terjadi kesalahan saat memuat akun. static Future>> loadAccounts({ required String baseUrl, required String accessToken, }) async { // Mengambil akun revenue final revenueAccounts = await FireflyApiService.fetchAccounts( baseUrl: baseUrl, accessToken: accessToken, type: 'revenue', ); // Mengambil akun asset final assetAccounts = await FireflyApiService.fetchAccounts( baseUrl: baseUrl, accessToken: accessToken, type: 'asset', ); // Menggabungkan akun revenue dan asset untuk dropdown final allAccounts = >[]; for (var account in revenueAccounts) { allAccounts.add({ 'id': account.id, 'name': account.name, 'type': account.type, }); } for (var account in assetAccounts) { allAccounts.add({ 'id': account.id, 'name': account.name, 'type': account.type, }); } return allAccounts; } /// Mencari ID akun berdasarkan nama dan tipe akun. static String? findAccountIdByName({ required String name, required String expectedType, required List> accounts, }) { if (name.isEmpty) return null; try { // Cari akun dengan nama yang cocok dan tipe yang diharapkan final account = accounts.firstWhere( (account) => account['name'].toString().toLowerCase() == name.toLowerCase() && account['type'] == expectedType, ); return account['id'] as String?; } catch (e) { // Jika tidak ditemukan, coba pencarian yang lebih fleksibel for (var account in accounts) { if (account['type'] == expectedType && account['name'] .toString() .toLowerCase() .contains(name.toLowerCase())) { return account['id'] as String?; } } // Jika masih tidak ditemukan, kembalikan null return null; } } /// Generates a transaction description based on item names static String generateTransactionDescription(List items) { if (items.isEmpty) { return 'Transaksi Struk Belanja'; } // Take the first 5 item descriptions final itemNames = items.take(5).map((item) => item.description).toList(); // If there are more than 5 items, append ', dll' to the last item if (items.length > 5) { itemNames[4] += ', dll'; } // Join the item names with ', ' return itemNames.join(', '); } /// Mengirim transaksi ke Firefly III. /// /// Sebelum mengirim transaksi, metode ini melakukan beberapa validasi: /// 1. Memastikan ada item yang akan dikirim /// 2. Memastikan akun sumber dan tujuan telah dipilih atau dimasukkan /// 3. Memastikan akun sumber dan tujuan berbeda /// 4. Memastikan akun sumber dan tujuan ada di daftar akun yang dimuat /// 5. Memastikan tipe akun sesuai (sumber: revenue, tujuan: asset) /// /// Mengembalikan transactionId jika berhasil, atau null jika gagal. /// Melempar exception jika ada error validasi. static Future submitTransaction({ required List items, required DateTime transactionDate, required String sourceAccountId, required String destinationAccountId, required List> accounts, // Daftar akun yang dimuat required String baseUrl, required String accessToken, }) async { if (items.isEmpty) { throw Exception('Tidak ada item untuk dikirim'); } // Validasi apakah akun benar-benar ada di Firefly III bool sourceAccountExists = false; bool destinationAccountExists = false; // Cari detail akun untuk validasi tipe akun Map? sourceAccountDetails; Map? destinationAccountDetails; for (var account in accounts) { if (account['id'].toString() == sourceAccountId) { sourceAccountExists = true; sourceAccountDetails = account; } if (account['id'].toString() == destinationAccountId) { destinationAccountExists = true; destinationAccountDetails = account; } } if (!sourceAccountExists) { throw Exception( 'Akun sumber tidak ditemukan di daftar akun yang dimuat. Klik "Muat Ulang Akun" dan coba lagi.'); } if (!destinationAccountExists) { throw Exception( 'Akun tujuan tidak ditemukan di daftar akun yang dimuat. Klik "Muat Ulang Akun" dan coba lagi.'); } // Validasi tipe akun (sumber harus revenue, tujuan harus asset) if (sourceAccountDetails != null && sourceAccountDetails['type'] != 'revenue') { // Tampilkan peringatan, tapi tidak menghentikan proses print( 'Peringatan: Akun sumber sebaiknya bertipe revenue, tetapi akun ini bertipe ${sourceAccountDetails['type']}.'); } if (destinationAccountDetails != null && destinationAccountDetails['type'] != 'asset') { // Tampilkan peringatan, tapi tidak menghentikan proses print( 'Peringatan: Akun tujuan sebaiknya bertipe asset, tetapi akun ini bertipe ${destinationAccountDetails['type']}.'); } if (sourceAccountId == destinationAccountId) { throw Exception('Akun sumber dan tujuan tidak boleh sama'); } final total = _calculateTotal(items); // Generate transaction description final transactionDescription = generateTransactionDescription(items); final transactionId = await FireflyApiService.submitDummyTransaction( baseUrl: baseUrl, accessToken: accessToken, sourceId: sourceAccountId, destinationId: destinationAccountId, type: 'deposit', description: transactionDescription, date: '${transactionDate.year}-${transactionDate.month.toString().padLeft(2, '0')}-${transactionDate.day.toString().padLeft(2, '0')}', amount: total.toStringAsFixed(2), ); return transactionId; } /// Menghitung total harga semua item static double _calculateTotal(List items) { return items.fold(0.0, (sum, item) => sum + item.total); } }