import '../models/group_balance.dart'; import '../models/expense.dart'; import '../services/error_service.dart'; import 'expense_repository.dart'; import '../models/user_balance.dart'; import '../models/settlement.dart'; class BalanceRepository { final ExpenseRepository _expenseRepository; final ErrorService _errorService; BalanceRepository({ required ExpenseRepository expenseRepository, ErrorService? errorService, }) : _expenseRepository = expenseRepository, _errorService = errorService ?? ErrorService(); // Calculer la balance d'un groupe Future calculateGroupBalance(String groupId) async { try { // Pour l'instant, on utilise une liste statique des dépenses // En production, vous récupéreriez depuis le stream final expenses = await _expenseRepository .getExpensesStream(groupId) .first; final userBalances = _calculateUserBalances(expenses); final settlements = _calculateOptimalSettlements(userBalances); final totalExpenses = expenses .where((e) => !e.isArchived) .fold(0.0, (sum, e) => sum + e.amountInEur); return GroupBalance( groupId: groupId, userBalances: userBalances, settlements: settlements, totalExpenses: totalExpenses, calculatedAt: DateTime.now(), ); } catch (e) { _errorService.logError('BalanceRepository', 'Erreur calcul balance: $e'); rethrow; } } Future> calculateGroupUserBalances(String groupId) async { try { final expenses = await _expenseRepository .getExpensesStream(groupId) .first; return _calculateUserBalances(expenses); } catch (e) { _errorService.logError('BalanceRepository', 'Erreur calcul user balances: $e'); rethrow; } } // Calculer les balances individuelles List _calculateUserBalances(List expenses) { final Map> userBalanceMap = {}; // Initialiser les utilisateurs for (final expense in expenses) { if (expense.isArchived) continue; // Ajouter le payeur userBalanceMap.putIfAbsent(expense.paidById, () => { 'name': expense.paidByName, 'paid': 0.0, 'owed': 0.0, }); // Ajouter les participants for (final split in expense.splits) { userBalanceMap.putIfAbsent(split.userId, () => { 'name': split.userName, 'paid': 0.0, 'owed': 0.0, }); } } // Calculer les montants for (final expense in expenses) { if (expense.isArchived) continue; // Ajouter au montant payé userBalanceMap[expense.paidById]!['paid'] += expense.amountInEur; // Ajouter aux montants dus for (final split in expense.splits) { userBalanceMap[split.userId]!['owed'] += split.amount; } } // Convertir en liste de UserBalance return userBalanceMap.entries.map((entry) { final userId = entry.key; final data = entry.value; final paid = data['paid'] as double; final owed = data['owed'] as double; return UserBalance( userId: userId, userName: data['name'] as String, totalPaid: paid, totalOwed: owed, balance: paid - owed, ); }).toList(); } // Algorithme d'optimisation des règlements List _calculateOptimalSettlements(List balances) { final settlements = []; // Séparer les créditeurs et débiteurs final creditors = balances.where((b) => b.shouldReceive).toList(); final debtors = balances.where((b) => b.shouldPay).toList(); // Trier par montant (plus gros montants en premier) creditors.sort((a, b) => b.balance.compareTo(a.balance)); debtors.sort((a, b) => a.balance.compareTo(b.balance)); // Créer des copies mutables des montants final creditorsRemaining = Map.fromEntries( creditors.map((c) => MapEntry(c.userId, c.balance)) ); final debtorsRemaining = Map.fromEntries( debtors.map((d) => MapEntry(d.userId, -d.balance)) ); // Algorithme glouton pour minimiser le nombre de transactions for (final creditor in creditors) { for (final debtor in debtors) { final creditAmount = creditorsRemaining[creditor.userId] ?? 0; final debtAmount = debtorsRemaining[debtor.userId] ?? 0; if (creditAmount <= 0.01 || debtAmount <= 0.01) continue; final settlementAmount = [creditAmount, debtAmount].reduce((a, b) => a < b ? a : b); settlements.add(Settlement( fromUserId: debtor.userId, fromUserName: debtor.userName, toUserId: creditor.userId, toUserName: creditor.userName, amount: settlementAmount, )); creditorsRemaining[creditor.userId] = creditAmount - settlementAmount; debtorsRemaining[debtor.userId] = debtAmount - settlementAmount; } } return settlements; } }