import '../../../../core/utils/date_time_utils.dart'; import 'pantry_category.dart'; /// Expiration bucket used by the pantry list to render warning badges. enum ExpirationStatus { /// The item has no expiration date recorded. none, /// Comfortably far from expiring. fresh, /// Within [PantryItem.expirationWarningDays] of expiring. expiringSoon, /// Already past its expiration date. expired, } /// Immutable on-hand inventory record for one ingredient. /// /// Pure Dart (no framework imports) so the entity can be shared by the delta /// use case, the drift-backed repository, and the Riverpod presentation /// layer without leaking infrastructure concerns into the domain. class PantryItem { const PantryItem({ required this.id, required this.name, required this.quantityOnHand, required this.unit, required this.category, this.expirationDate, required this.lastUpdated, }); /// Days ahead of today that trigger an `expiringSoon` warning badge. static const int expirationWarningDays = 3; /// UUID primary key assigned when the row is first created. final String id; /// Canonical, lowercase ingredient key (see /// `value_objects/ingredient_name.dart`) used for fuzzy recipe matching, /// e.g. `chicken breast`. final String name; /// Quantity currently on hand, expressed in [unit]. final double quantityOnHand; /// Canonical unit key (e.g. `g`, `oz`, `count`). final String unit; /// Grocery aisle the item belongs to. final PantryCategory category; /// Best-by date; `null` when the item does not expire. final DateTime? expirationDate; /// When the row was last written (drives change tracking). final DateTime lastUpdated; /// Warning bucket for [now] (defaults to the current instant). ExpirationStatus expirationStatus({DateTime? now}) { final date = expirationDate; if (date == null) return ExpirationStatus.none; final today = startOfDay(now ?? DateTime.now()); final expiry = startOfDay(date); if (expiry.isBefore(today)) return ExpirationStatus.expired; if (expiry.difference(today).inDays <= expirationWarningDays) { return ExpirationStatus.expiringSoon; } return ExpirationStatus.fresh; } /// Whole days until expiration (0 = today, negative = overdue); `null` /// when no expiration date is recorded. int? daysUntilExpiration({DateTime? now}) { final date = expirationDate; if (date == null) return null; return startOfDay(date) .difference(startOfDay(now ?? DateTime.now())) .inDays; } PantryItem copyWith({ String? id, String? name, double? quantityOnHand, String? unit, PantryCategory? category, DateTime? expirationDate, DateTime? lastUpdated, }) => PantryItem( id: id ?? this.id, name: name ?? this.name, quantityOnHand: quantityOnHand ?? this.quantityOnHand, unit: unit ?? this.unit, category: category ?? this.category, expirationDate: expirationDate ?? this.expirationDate, lastUpdated: lastUpdated ?? this.lastUpdated, ); @override String toString() => 'PantryItem($id, "$name", $quantityOnHand $unit, ${category.persistenceKey})'; }