24a6743ed2
Un plato aparecia en la lista de incumplimientos sin explicacion (captura del usuario: "Ensalada campera"): findViolations listaba los slots con isRuleOverridden, una marca puesta al asignar que quedaba obsoleta cuando el conflicto ya se habia resuelto. Y al reves, editar una regla despues de planificar no detectaba nada. - findViolations recorre el plan y reporta solo lo que incumple AHORA, siempre con sus razones (nunca una entrada vacia) - MealSlot.isRuleIgnored: "Ignorar" pasa a ser persistente, porque si no el aviso recalculado volveria enseguida; se resetea al cambiar o quitar el plato del hueco - El triangulo del calendario usa tambien el estado real - Campo en el payload KV (opcional, retrocompatible) y en el esquema CloudKit de Development - 3 tests: marca obsoleta no se lista, regla endurecida despues se detecta, y lo ignorado no reaparece Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013H6bXqGX1ygwib1Dm3n3UG
72 lines
2.4 KiB
Swift
72 lines
2.4 KiB
Swift
import Foundation
|
|
import SwiftData
|
|
|
|
// CloudKit-compatible (2.0): no unique constraints, inline defaults everywhere.
|
|
@Model
|
|
final class MealSlot {
|
|
var id: UUID = UUID()
|
|
var dayOfWeek: Int = 0 // 0=Monday, 6=Sunday
|
|
var mealType: String = MealType.dinner.rawValue
|
|
var dishId: UUID?
|
|
/// Optional companion dish ("ensalada + salmón"). Only meaningful while
|
|
/// `dishId` is set; cleared together with it.
|
|
var secondaryDishId: UUID?
|
|
var calendarEventId: String?
|
|
var isRuleOverridden: Bool = false
|
|
var isEatingOut: Bool = false
|
|
/// "Don't plan this meal": counts as resolved (like eating out) but shows
|
|
/// as intentionally blank — autocomplete leaves it alone.
|
|
var isSkipped: Bool = false
|
|
/// The user dismissed the rule warning for this assignment. Violations are
|
|
/// recomputed from the current plan, so without this the warning would
|
|
/// come straight back after being ignored.
|
|
var isRuleIgnored: Bool = false
|
|
|
|
var weekPlan: WeekPlan?
|
|
|
|
init(
|
|
id: UUID = UUID(),
|
|
dayOfWeek: Int,
|
|
mealType: String,
|
|
dishId: UUID? = nil,
|
|
secondaryDishId: UUID? = nil,
|
|
calendarEventId: String? = nil,
|
|
isRuleOverridden: Bool = false,
|
|
isEatingOut: Bool = false,
|
|
isSkipped: Bool = false,
|
|
isRuleIgnored: Bool = false
|
|
) {
|
|
self.id = id
|
|
self.dayOfWeek = dayOfWeek
|
|
self.mealType = mealType
|
|
self.dishId = dishId
|
|
self.secondaryDishId = secondaryDishId
|
|
self.calendarEventId = calendarEventId
|
|
self.isRuleOverridden = isRuleOverridden
|
|
self.isEatingOut = isEatingOut
|
|
self.isSkipped = isSkipped
|
|
self.isRuleIgnored = isRuleIgnored
|
|
}
|
|
|
|
var mealTypeEnum: MealType {
|
|
get { MealType(rawValue: mealType) ?? .dinner }
|
|
set { mealType = newValue.rawValue }
|
|
}
|
|
}
|
|
|
|
extension MealSlot {
|
|
static func preferredForDuplicateResolution(_ slots: [MealSlot]) -> MealSlot {
|
|
slots.sorted { lhs, rhs in
|
|
let lhsHasDish = lhs.dishId != nil
|
|
let rhsHasDish = rhs.dishId != nil
|
|
if lhsHasDish != rhsHasDish { return lhsHasDish }
|
|
|
|
let lhsHasEvent = lhs.calendarEventId != nil
|
|
let rhsHasEvent = rhs.calendarEventId != nil
|
|
if lhsHasEvent != rhsHasEvent { return lhsHasEvent }
|
|
|
|
return lhs.id.uuidString < rhs.id.uuidString
|
|
}.first ?? slots[0]
|
|
}
|
|
}
|