Goals por categoría: ámbito de categoría en objetivos (build 88) #46
Un Goal ya podía acotarse por cuenta; ahora también por categoría de fuente, de modo que se puede fijar "100.000 € en Cash" y que el progreso cuente solo las fuentes de esa categoría. - CoreData: relación Goal.category ↔ Category.goals (opcional, Nullify) - Goal.includes(_:)/relevantSources(from:)/scopeKey centralizan el filtro de ámbito (cuenta Y categoría) que antes estaba duplicado en GoalsViewModel, AddSourceView y NotificationService - GoalEditorView: sección "Ámbito" con selector de categoría - Chip de categoría en la lista de Goals y en la tarjeta del Dashboard - ETA del Dashboard para goals con ámbito: usa la proyección del propio goal en vez de la evolución global de la cartera (que daría "objetivo alcanzado" en cuanto la cartera total superara el importe) - GoalRepository e ImportService deduplican por (nombre, cuenta, categoría) - Export/Import de goals incluye "category" - Nuevas claves localizadas en los 7 idiomas Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YcDn5ccuRFV83q7xWWokBT
This commit is contained in:
@@ -635,6 +635,29 @@ class DashboardViewModel: ObservableObject {
|
||||
let value: Decimal
|
||||
}
|
||||
|
||||
/// ETA text for a goal scoped to an account and/or a category. The dashboard
|
||||
/// evolution covers the whole (selected) portfolio, so a scoped goal — e.g.
|
||||
/// "100k in Cash" — must be projected over its own sources instead; the
|
||||
/// caller passes that projection in.
|
||||
func goalEtaText(for goal: Goal, currentValue: Decimal, projection: GoalProjection.Result) -> String? {
|
||||
if currentValue >= goal.targetDecimal {
|
||||
return "Goal reached. Keep it steady."
|
||||
}
|
||||
guard let months = projection.monthsToReach else {
|
||||
return "Keep going. Consistency pays off."
|
||||
}
|
||||
if months == 0 {
|
||||
return "Almost there. One more check-in."
|
||||
}
|
||||
if months >= 72 {
|
||||
return "Long journey, strong discipline. You're building momentum."
|
||||
}
|
||||
if let projectedDate = projection.projectedDate {
|
||||
return "Estimated: \(projectedDate.monthYearString)"
|
||||
}
|
||||
return "Estimated: \(months) months"
|
||||
}
|
||||
|
||||
func goalEtaText(for goal: Goal, currentValue: Decimal) -> String? {
|
||||
let target = goal.targetDecimal
|
||||
let lastValue = evolutionData.last?.value ?? currentValue
|
||||
|
||||
Reference in New Issue
Block a user