glp-companion/lib/features/pantry/domain/entities/pantry_item.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})';
}