// lib/providers/receipt_provider.dart import 'package:flutter/material.dart'; import 'package:cashumit/providers/receipt_state.dart'; import 'package:cashumit/models/receipt_item.dart'; import 'package:cashumit/services/receipt_service.dart'; // Pastikan ReceiptService sudah dibuat import 'package:cashumit/models/firefly_account.dart'; // Untuk tipe akun import 'package:cashumit/models/local_receipt.dart'; import 'package:cashumit/services/local_receipt_service.dart'; import 'package:cashumit/services/account_cache_service.dart'; import 'package:cashumit/services/account_mirror_service.dart'; import 'dart:math'; class ReceiptProvider with ChangeNotifier { late ReceiptState _state; ReceiptState get state => _state; ReceiptProvider() { _state = ReceiptState(); } /// Initialize state and load credentials and accounts Future initialize() async { await loadCredentialsAndAccounts(); // Additional initialization can be added here if needed } /// Memuat kredensial dan akun Future loadCredentialsAndAccounts() async { final credentials = await ReceiptService.loadCredentials(); if (credentials == null) { // Jika tidak ada kredensial, kita tetap perlu memberitahu listener bahwa state berubah // (misalnya untuk menampilkan pesan error di UI) _state = _state.copyWith( fireflyUrl: null, accessToken: null, accounts: [], // Kosongkan akun juga ); notifyListeners(); return; } // Periksa apakah kredensial berubah final credentialsChanged = _state.fireflyUrl != credentials['url'] || _state.accessToken != credentials['token']; _state = _state.copyWith( fireflyUrl: credentials['url'], accessToken: credentials['token'], ); notifyListeners(); // Jika kredensial ada dan berubah, lanjutkan untuk memuat akun if (credentialsChanged) { await loadAccounts(); // Juga perbarui mirror akun dari server try { await ReceiptService.updateAccountMirror( baseUrl: _state.fireflyUrl!, accessToken: _state.accessToken!, ); } catch (e) { print('Gagal memperbarui mirror akun: $e'); } } else if (_state.accounts.isEmpty) { // Jika akun belum pernah dimuat, muat sekarang await loadAccounts(); } } /// Memuat daftar akun dengan prioritas: server > cache > fallback Future loadAccounts() async { if (_state.fireflyUrl == null || _state.accessToken == null) { // Jika kredensial tidak ada, coba ambil dari cache final cachedAccounts = await AccountCacheService.getCachedAccounts(); if (cachedAccounts.isNotEmpty) { _state = _state.copyWith(accounts: cachedAccounts); notifyListeners(); } return; } // Gunakan fungsi untuk mendapatkan akun dengan fallback mekanisme final allAccounts = await _getAccountsWithFallback(); _state = _state.copyWith(accounts: allAccounts); notifyListeners(); } /// Fungsi untuk mendapatkan akun dengan fallback mekanisme Future>> _getAccountsWithFallback() async { if (_state.fireflyUrl == null || _state.accessToken == null) { return []; } try { // Coba ambil dari server terlebih dahulu final serverAccounts = await ReceiptService.loadAccounts( baseUrl: _state.fireflyUrl!, accessToken: _state.accessToken!, ); // Simpan ke mirror jika berhasil ambil dari server await ReceiptService.saveAccountsToMirror(serverAccounts); return serverAccounts; } catch (serverError) { print('Gagal memuat akun dari server: $serverError'); // Jika gagal dari server, coba dari mirror try { final mirroredAccounts = await ReceiptService.getMirroredAccounts(); if (mirroredAccounts.isNotEmpty) { return mirroredAccounts; } } catch (mirrorError) { print('Gagal memuat dari mirror: $mirrorError'); } // Jika semua fallback gagal, kembalikan list kosong return []; } } /// Menambahkan item ke receipt void addItem(ReceiptItem item) { _state = _state.copyWith( items: [..._state.items, item], ); notifyListeners(); } /// Mengedit item di receipt void editItem(int index, ReceiptItem newItem) { if (index < 0 || index >= _state.items.length) return; final updatedItems = List.from(_state.items); updatedItems[index] = newItem; _state = _state.copyWith( items: updatedItems, ); notifyListeners(); } /// Menghapus item dari receipt void removeItem(int index) { if (index < 0 || index >= _state.items.length) return; final updatedItems = List.from(_state.items)..removeAt(index); _state = _state.copyWith( items: updatedItems, ); notifyListeners(); } /// Memilih akun sumber void selectSourceAccount(String id, String name) { _state = _state.copyWith( sourceAccountId: id, sourceAccountName: name, ); // Tidak menyimpan default account mapping lagi notifyListeners(); } /// Memilih akun tujuan void selectDestinationAccount(String id, String name) { _state = _state.copyWith( destinationAccountId: id, destinationAccountName: name, ); // Tidak menyimpan default account mapping lagi notifyListeners(); } /// Mencari ID akun berdasarkan nama dan tipe String? findAccountIdByName(String name, String expectedType) { return ReceiptService.findAccountIdByName( name: name, expectedType: expectedType, accounts: _state.accounts, ); } /// Mengirim transaksi (ini akan memanggil ReceiptService dan mungkin memperbarui state setelahnya) Future submitTransaction() async { if (_state.items.isEmpty) { throw Exception('Tidak ada item untuk dikirim'); } if (_state.fireflyUrl == null || _state.accessToken == null) { throw Exception('Kredensial Firefly III tidak ditemukan'); } // Pastikan akun sumber dan tujuan dipilih // Logika untuk mencari ID berdasarkan nama jika diperlukan bisa ditambahkan di sini // atau diharapkan UI sudah menangani ini sebelum memanggil submitTransaction if (_state.sourceAccountId == null || _state.destinationAccountId == null) { throw Exception('Akun sumber dan tujuan harus dipilih'); } final transactionId = await ReceiptService.submitTransaction( items: _state.items, transactionDate: _state.transactionDate, sourceAccountId: _state.sourceAccountId!, destinationAccountId: _state.destinationAccountId!, accounts: _state.accounts, baseUrl: _state.fireflyUrl!, accessToken: _state.accessToken!, ); return transactionId; } /// Simpan transaksi ke penyimpanan lokal (ini akan menyimpan transaksi lokal bukan langsung submit ke server) Future saveTransactionLocally() async { if (_state.items.isEmpty) { throw Exception('Tidak ada item untuk disimpan'); } if (_state.sourceAccountId == null || _state.destinationAccountId == null) { throw Exception('Akun sumber dan tujuan harus dipilih'); } // Buat ID unik untuk receipt lokal final receiptId = 'receipt_${DateTime.now().millisecondsSinceEpoch}_${Random().nextInt(1000)}'; // Generate transaction description final transactionDescription = _generateTransactionDescription(_state.items); // Buat objek LocalReceipt final localReceipt = LocalReceipt( id: receiptId, items: _state.items, sourceAccountId: _state.sourceAccountId, sourceAccountName: _state.sourceAccountName, destinationAccountId: _state.destinationAccountId, destinationAccountName: _state.destinationAccountName, transactionDate: _state.transactionDate, transactionDescription: transactionDescription, createdAt: DateTime.now(), ); // Simpan ke penyimpanan lokal await LocalReceiptService.saveReceipt(localReceipt); // Kosongkan items di state setelah disimpan _state = _state.copyWith( items: [], sourceAccountId: null, sourceAccountName: null, destinationAccountId: null, destinationAccountName: null, ); notifyListeners(); return receiptId; } /// Submit semua receipt lokal yang belum terkirim ke server Future> submitAllLocalReceipts() async { if (_state.fireflyUrl == null || _state.accessToken == null) { throw Exception('Kredensial Firefly III tidak ditemukan'); } // Submit semua receipt lokal yang belum terkirim final result = await LocalReceiptService.submitAllUnsubmittedReceipts( _state.fireflyUrl!, _state.accessToken!, ); notifyListeners(); // Update UI jika ada perubahan return result; } /// Generate transaction description from items 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(', '); } // Tambahkan metode lain sesuai kebutuhan, seperti untuk memperbarui transactionDate jika diperlukan }