Collapse PlanEntry customization into a materialized ingredient snapshot
PlanCustomization / IngredientCustomization / AddedIngredient disappear; PlanEntry now carries List<PlanIngredient> directly. Substitutions, exclusions, amount overrides, product picks, and added ingredients are all just whatever ends up in the list. Recipe edits no longer mutate historic plan entries — load-bearing once consumption tracking lands. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -4,50 +4,34 @@ import kotlinx.datetime.LocalDate
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* Per-ingredient overrides applied to a planned meal. All fields are optional;
|
||||
* an entry is only stored in [PlanCustomization.overrides] when at least one
|
||||
* field is non-default. The repository prunes default-only entries on write.
|
||||
* One ingredient line on a planned meal. Self-contained — substitutions,
|
||||
* exclusions, amount overrides, and product picks are not modeled as
|
||||
* overlays on the recipe; they're just whatever ends up in this list.
|
||||
*
|
||||
* The four fields cover PLAN-07 through PLAN-11 (substitutions, exclusions,
|
||||
* amount overrides, product picks) collapsed onto a single per-ingredient row
|
||||
* so they can compose — substituting almonds for walnuts AND overriding the
|
||||
* amount AND pinning a brand all land on the same record.
|
||||
* A swap (butter → margarine) is just a `PlanIngredient` with the swapped
|
||||
* `ingredientId`. A removal is absence from the list. An amount tweak is
|
||||
* the [quantity] field. A pinned brand is [productPick].
|
||||
*/
|
||||
@Serializable
|
||||
public data class IngredientCustomization(
|
||||
public val substituteWith: IngredientId? = null,
|
||||
public val excluded: Boolean = false,
|
||||
public val amountOverride: Quantity? = null,
|
||||
public val productPick: ProductId? = null,
|
||||
)
|
||||
|
||||
/**
|
||||
* An ingredient injected into a planned meal that isn't part of the original
|
||||
* recipe (PLAN-10). [productPick] optionally pins a specific [Product].
|
||||
*/
|
||||
@Serializable
|
||||
public data class AddedIngredient(
|
||||
public data class PlanIngredient(
|
||||
public val ingredientId: IngredientId,
|
||||
public val quantity: Quantity,
|
||||
public val productPick: ProductId? = null,
|
||||
)
|
||||
|
||||
/**
|
||||
* All customizations attached to a single [PlanEntry]. Defaults are empty —
|
||||
* a Phase 6 entry without customizations stays cheap.
|
||||
*/
|
||||
@Serializable
|
||||
public data class PlanCustomization(
|
||||
public val overrides: Map<IngredientId, IngredientCustomization> = emptyMap(),
|
||||
public val added: List<AddedIngredient> = emptyList(),
|
||||
)
|
||||
|
||||
/**
|
||||
* One planned meal: a recipe at a date/slot pair. Identity is the UUID
|
||||
* One planned meal: a recipe at a date/slot pair, with a fully-materialized
|
||||
* snapshot of the ingredients as the user planned them. Identity is the UUID
|
||||
* [PlanEntryId] — never the composite `(date, slot)` — so concurrent edits
|
||||
* across devices can survive a delete + recreate race without colliding on a
|
||||
* natural key (PLAN-14).
|
||||
*
|
||||
* [recipeId] is provenance only: it powers the "open recipe" link, the "cook
|
||||
* again" action, and analytics. The recipe's current ingredient list is not
|
||||
* consulted at read time — [ingredients] is the source of truth for this
|
||||
* meal. This way, editing a recipe never mutates historic plan entries
|
||||
* (load-bearing once consumption tracking lands in a later phase).
|
||||
*
|
||||
* "Skipped slot" (PLAN-12) is modeled as absence — no [PlanEntry] row exists
|
||||
* for that `(date, slotId)`. No sentinel field, no sealed sibling.
|
||||
*/
|
||||
@@ -59,5 +43,5 @@ public data class PlanEntry(
|
||||
public val slotId: MealSlotId,
|
||||
public val recipeId: RecipeId,
|
||||
public val servings: Double,
|
||||
public val customization: PlanCustomization = PlanCustomization(),
|
||||
public val ingredients: List<PlanIngredient>,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user