106 lines
3.2 KiB
Dart
106 lines
3.2 KiB
Dart
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})';
|
|
} |