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
This commit is contained in:
Dayron
2025-10-21 16:02:58 +02:00
parent 62eb434548
commit 4edbd1cf34
60 changed files with 1973 additions and 342 deletions

205
lib/models/expense.dart Normal file
View File

@@ -0,0 +1,205 @@
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import 'expense_split.dart';
enum ExpenseCurrency {
eur('', 'EUR'),
usd('\$', 'USD'),
gbp('£', 'GBP');
const ExpenseCurrency(this.symbol, this.code);
final String symbol;
final String code;
}
enum ExpenseCategory {
restaurant('Restaurant', Icons.restaurant),
transport('Transport', Icons.directions_car),
accommodation('Hébergement', Icons.hotel),
entertainment('Loisirs', Icons.local_activity),
shopping('Shopping', Icons.shopping_bag),
other('Autre', Icons.category);
const ExpenseCategory(this.displayName, this.icon);
final String displayName;
final IconData icon;
}
class Expense extends Equatable {
final String id;
final String groupId;
final String description;
final double amount;
final ExpenseCurrency currency;
final double amountInEur; // Montant converti en EUR
final ExpenseCategory category;
final String paidById;
final String paidByName;
final DateTime date;
final DateTime createdAt;
final DateTime? editedAt;
final bool isEdited;
final bool isArchived;
final String? receiptUrl;
final List<ExpenseSplit> splits;
const Expense({
required this.id,
required this.groupId,
required this.description,
required this.amount,
required this.currency,
required this.amountInEur,
required this.category,
required this.paidById,
required this.paidByName,
required this.date,
required this.createdAt,
this.editedAt,
this.isEdited = false,
this.isArchived = false,
this.receiptUrl,
required this.splits,
});
factory Expense.fromMap(Map<String, dynamic> map, String id) {
return Expense(
id: id,
groupId: map['groupId'] ?? '',
description: map['description'] ?? '',
amount: (map['amount'] as num?)?.toDouble() ?? 0.0,
currency: ExpenseCurrency.values.firstWhere(
(c) => c.code == map['currency'],
orElse: () => ExpenseCurrency.eur,
),
amountInEur: (map['amountInEur'] as num?)?.toDouble() ?? 0.0,
category: ExpenseCategory.values.firstWhere(
(c) => c.name == map['category'],
orElse: () => ExpenseCategory.other,
),
paidById: map['paidById'] ?? '',
paidByName: map['paidByName'] ?? '',
date: _parseDateTime(map['date']),
createdAt: _parseDateTime(map['createdAt']),
editedAt: map['editedAt'] != null ? _parseDateTime(map['editedAt']) : null,
isEdited: map['isEdited'] ?? false,
isArchived: map['isArchived'] ?? false,
receiptUrl: map['receiptUrl'],
splits: (map['splits'] as List?)
?.map((s) => ExpenseSplit.fromMap(s))
.toList() ?? [],
);
}
Map<String, dynamic> toMap() {
return {
'groupId': groupId,
'description': description,
'amount': amount,
'currency': currency.code,
'amountInEur': amountInEur,
'category': category.name,
'paidById': paidById,
'paidByName': paidByName,
'date': Timestamp.fromDate(date),
'createdAt': Timestamp.fromDate(createdAt),
'editedAt': editedAt != null ? Timestamp.fromDate(editedAt!) : null,
'isEdited': isEdited,
'isArchived': isArchived,
'receiptUrl': receiptUrl,
'splits': splits.map((s) => s.toMap()).toList(),
};
}
static DateTime _parseDateTime(dynamic value) {
if (value is Timestamp) return value.toDate();
if (value is String) return DateTime.parse(value);
if (value is DateTime) return value;
return DateTime.now();
}
Expense copyWith({
String? id,
String? groupId,
String? description,
double? amount,
ExpenseCurrency? currency,
double? amountInEur,
ExpenseCategory? category,
String? paidById,
String? paidByName,
DateTime? date,
DateTime? createdAt,
DateTime? editedAt,
bool? isEdited,
bool? isArchived,
String? receiptUrl,
List<ExpenseSplit>? splits,
}) {
return Expense(
id: id ?? this.id,
groupId: groupId ?? this.groupId,
description: description ?? this.description,
amount: amount ?? this.amount,
currency: currency ?? this.currency,
amountInEur: amountInEur ?? this.amountInEur,
category: category ?? this.category,
paidById: paidById ?? this.paidById,
paidByName: paidByName ?? this.paidByName,
date: date ?? this.date,
createdAt: createdAt ?? this.createdAt,
editedAt: editedAt ?? this.editedAt,
isEdited: isEdited ?? this.isEdited,
isArchived: isArchived ?? this.isArchived,
receiptUrl: receiptUrl ?? this.receiptUrl,
splits: splits ?? this.splits,
);
}
Expense copyWithEdit({
String? description,
double? amount,
ExpenseCurrency? currency,
double? amountInEur,
ExpenseCategory? category,
List<ExpenseSplit>? splits,
String? receiptUrl,
}) {
return copyWith(
description: description,
amount: amount,
currency: currency,
amountInEur: amountInEur,
category: category,
splits: splits,
receiptUrl: receiptUrl,
editedAt: DateTime.now(),
isEdited: true,
);
}
// Marquer comme archivé
Expense copyWithArchived() {
return copyWith(
isArchived: true,
);
}
// Ajouter/mettre à jour l'URL du reçu
Expense copyWithReceipt(String receiptUrl) {
return copyWith(
receiptUrl: receiptUrl,
);
}
// Mettre à jour les splits
Expense copyWithSplits(List<ExpenseSplit> newSplits) {
return copyWith(
splits: newSplits,
);
}
@override
List<Object?> get props => [id];
}