Files
TravelMate/lib/models/expense.dart

290 lines
7.7 KiB
Dart

import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import 'expense_split.dart';
/// Enumeration of supported currencies for expenses.
///
/// Each currency includes both a display symbol and standard currency code.
enum ExpenseCurrency {
/// Euro currency
eur('', 'EUR'),
/// US Dollar currency
usd('\$', 'USD'),
/// British Pound currency
gbp('£', 'GBP');
const ExpenseCurrency(this.symbol, this.code);
/// Currency symbol for display (e.g., €, $, £)
final String symbol;
/// Standard currency code (e.g., EUR, USD, GBP)
final String code;
}
/// Enumeration of expense categories with display names and icons.
///
/// Provides predefined categories for organizing travel expenses.
enum ExpenseCategory {
/// Restaurant and food expenses
restaurant('Restaurant', Icons.restaurant),
/// Transportation expenses
transport('Transport', Icons.directions_car),
/// Accommodation and lodging expenses
accommodation('Accommodation', Icons.hotel),
/// Entertainment and activity expenses
entertainment('Entertainment', Icons.local_activity),
/// Shopping expenses
shopping('Shopping', Icons.shopping_bag),
/// Other miscellaneous expenses
other('Other', Icons.category);
const ExpenseCategory(this.displayName, this.icon);
/// Human-readable display name for the category
final String displayName;
/// Icon representing the category
final IconData icon;
}
/// Model representing a travel expense.
///
/// This class encapsulates all information about an expense including
/// amount, currency, category, who paid, how it's split among participants,
/// and receipt information. It extends [Equatable] for value comparison.
class Expense extends Equatable {
/// Unique identifier for the expense
final String id;
/// ID of the group this expense belongs to
final String groupId;
/// Description of the expense
final String description;
/// Amount of the expense in the original currency
final double amount;
/// Currency of the expense
final ExpenseCurrency currency;
/// Amount converted to EUR for standardized calculations
final double amountInEur;
/// Category of the expense
final ExpenseCategory category;
/// ID of the user who paid for this expense
final String paidById;
/// Name of the user who paid for this expense
final String paidByName;
/// Date when the expense occurred
final DateTime date;
/// Timestamp when the expense was created
final DateTime createdAt;
/// Timestamp when the expense was last edited (null if never edited)
final DateTime? editedAt;
/// Whether this expense has been edited after creation
final bool isEdited;
/// Whether this expense has been archived
final bool isArchived;
/// URL to the receipt image (optional)
final String? receiptUrl;
/// List of expense splits showing how the cost is divided
final List<ExpenseSplit> splits;
/// Creates a new [Expense] instance.
///
/// All parameters except [editedAt] and [receiptUrl] are required.
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,
groupId,
description,
amount,
currency,
amountInEur,
category,
paidById,
paidByName,
date,
createdAt,
editedAt,
isEdited,
isArchived,
receiptUrl,
splits,
];
}