Files
TravelMate/lib/repositories/balance_repository.dart
Dayron 4edbd1cf34 feat: Add User and UserBalance models with serialization methods
feat: Implement BalanceRepository for group balance calculations

feat: Create ExpenseRepository for managing expenses

feat: Add services for handling expenses and storage operations

fix: Update import paths for models in repositories and services

refactor: Rename CountContent to AccountContent in HomePage

chore: Add StorageService for image upload and management
2025-10-21 16:02:58 +02:00

146 lines
4.6 KiB
Dart

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<GroupBalance> 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;
}
}
// Calculer les balances individuelles
List<UserBalance> _calculateUserBalances(List<Expense> expenses) {
final Map<String, Map<String, dynamic>> 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<Settlement> _calculateOptimalSettlements(List<UserBalance> balances) {
final settlements = <Settlement>[];
// 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;
}
}