- Updated Group, Trip, User, and other model classes to include comprehensive documentation for better understanding and maintainability. - Improved error handling and logging in services, including AuthService, ErrorService, and StorageService. - Added validation and business logic explanations in ExpenseService and TripService. - Refactored method comments to follow a consistent format across the codebase. - Translated error messages and comments from French to English for consistency.
114 lines
3.8 KiB
Dart
114 lines
3.8 KiB
Dart
import 'dart:io';
|
|
import '../models/expense.dart';
|
|
import '../repositories/expense_repository.dart';
|
|
import 'error_service.dart';
|
|
import 'storage_service.dart';
|
|
|
|
/// Service for managing expense operations with business logic validation.
|
|
///
|
|
/// This service provides high-level expense management functionality including
|
|
/// validation, receipt image uploading, and coordination with the expense repository.
|
|
/// It acts as a business logic layer between the UI and data persistence.
|
|
class ExpenseService {
|
|
/// Repository for expense data operations.
|
|
final ExpenseRepository _expenseRepository;
|
|
|
|
/// Service for error handling and logging.
|
|
final ErrorService _errorService;
|
|
|
|
/// Service for handling file uploads (receipts).
|
|
final StorageService _storageService;
|
|
|
|
/// Creates a new [ExpenseService] with required dependencies.
|
|
///
|
|
/// [expenseRepository] is required for data operations.
|
|
/// [errorService] and [storageService] have default implementations if not provided.
|
|
ExpenseService({
|
|
required ExpenseRepository expenseRepository,
|
|
ErrorService? errorService,
|
|
StorageService? storageService,
|
|
}) : _expenseRepository = expenseRepository,
|
|
_errorService = errorService ?? ErrorService(),
|
|
_storageService = storageService ?? StorageService();
|
|
|
|
/// Creates an expense with validation and optional receipt image upload.
|
|
///
|
|
/// Validates the expense data, uploads receipt image if provided, and
|
|
/// creates the expense record in the database.
|
|
///
|
|
/// [expense] - The expense data to create
|
|
/// [receiptImage] - Optional receipt image file to upload
|
|
///
|
|
/// Returns the ID of the created expense.
|
|
/// Throws exceptions if validation fails or creation errors occur.
|
|
Future<String> createExpenseWithValidation(Expense expense, File? receiptImage) async {
|
|
try {
|
|
// Business logic validation
|
|
_validateExpenseData(expense);
|
|
|
|
// Upload receipt image if provided
|
|
String? receiptUrl;
|
|
if (receiptImage != null) {
|
|
receiptUrl = await _storageService.uploadReceiptImage(
|
|
expense.groupId,
|
|
receiptImage,
|
|
);
|
|
}
|
|
|
|
// Créer l'expense avec l'URL du reçu
|
|
final expenseWithReceipt = expense.copyWith(receiptUrl: receiptUrl);
|
|
|
|
return await _expenseRepository.createExpense(expenseWithReceipt);
|
|
} catch (e) {
|
|
_errorService.logError('ExpenseService', 'Erreur création expense: $e');
|
|
rethrow;
|
|
}
|
|
}
|
|
|
|
// Mise à jour avec validation
|
|
Future<void> updateExpenseWithValidation(Expense expense, File? newReceiptImage) async {
|
|
try {
|
|
_validateExpenseData(expense);
|
|
|
|
// Gérer le nouvel upload si nécessaire
|
|
String? receiptUrl = expense.receiptUrl;
|
|
if (newReceiptImage != null) {
|
|
receiptUrl = await _storageService.uploadReceiptImage(
|
|
expense.groupId,
|
|
newReceiptImage,
|
|
);
|
|
}
|
|
|
|
final expenseWithReceipt = expense.copyWith(
|
|
receiptUrl: receiptUrl,
|
|
editedAt: DateTime.now(),
|
|
isEdited: true,
|
|
);
|
|
|
|
await _expenseRepository.updateExpense(expenseWithReceipt);
|
|
} catch (e) {
|
|
_errorService.logError('ExpenseService', 'Erreur update expense: $e');
|
|
rethrow;
|
|
}
|
|
}
|
|
|
|
// Validation des données
|
|
void _validateExpenseData(Expense expense) {
|
|
if (expense.description.trim().isEmpty) {
|
|
throw Exception('La description est requise');
|
|
}
|
|
|
|
if (expense.amount <= 0) {
|
|
throw Exception('Le montant doit être positif');
|
|
}
|
|
|
|
if (expense.splits.isEmpty) {
|
|
throw Exception('Au moins un participant est requis');
|
|
}
|
|
|
|
final totalSplits = expense.splits.fold(0.0, (sum, split) => sum + split.amount);
|
|
if ((totalSplits - expense.amountInEur).abs() > 0.01) {
|
|
throw Exception('La somme des répartitions ne correspond pas au montant total');
|
|
}
|
|
}
|
|
} |