cashumit/lib/services/receipt_service.dart

215 lines
7.0 KiB
Dart

// 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<Map<String, String>?> 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<List<Map<String, dynamic>>> 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 = <Map<String, dynamic>>[];
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<Map<String, dynamic>> 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<ReceiptItem> 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<String?> submitTransaction({
required List<ReceiptItem> items,
required DateTime transactionDate,
required String sourceAccountId,
required String destinationAccountId,
required List<Map<String, dynamic>> 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<String, dynamic>? sourceAccountDetails;
Map<String, dynamic>? 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<ReceiptItem> items) {
return items.fold(0.0, (sum, item) => sum + item.total);
}
}