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:
@@ -53,25 +53,21 @@ class GoalRepository: ObservableObject {
|
||||
|
||||
// MARK: - Create
|
||||
|
||||
/// Check if a goal with the given name already exists in the account (case-insensitive)
|
||||
func goalExists(name: String, in account: Account?) -> Bool {
|
||||
let trimmed = name.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
let normalized = trimmed.lowercased()
|
||||
return goals.contains { goal in
|
||||
let nameMatches = goal.name.trimmingCharacters(in: .whitespacesAndNewlines).lowercased() == normalized
|
||||
let accountMatches = goal.account?.id == account?.id
|
||||
return nameMatches && accountMatches
|
||||
}
|
||||
/// Check if a goal with the given name already exists in the same scope
|
||||
/// (account + category), case-insensitive
|
||||
func goalExists(name: String, in account: Account?, category: Category? = nil) -> Bool {
|
||||
findGoal(byName: name, in: account, category: category) != nil
|
||||
}
|
||||
|
||||
/// Find an existing goal by name (case-insensitive) within the same account
|
||||
func findGoal(byName name: String, in account: Account?) -> Goal? {
|
||||
/// Find an existing goal by name (case-insensitive) within the same scope
|
||||
func findGoal(byName name: String, in account: Account?, category: Category? = nil) -> Goal? {
|
||||
let trimmed = name.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
let normalized = trimmed.lowercased()
|
||||
return goals.first { goal in
|
||||
let nameMatches = goal.name.trimmingCharacters(in: .whitespacesAndNewlines).lowercased() == normalized
|
||||
let accountMatches = goal.account?.id == account?.id
|
||||
return nameMatches && accountMatches
|
||||
let categoryMatches = goal.category?.id == category?.id
|
||||
return nameMatches && accountMatches && categoryMatches
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,10 +76,11 @@ class GoalRepository: ObservableObject {
|
||||
name: String,
|
||||
targetAmount: Decimal,
|
||||
targetDate: Date? = nil,
|
||||
account: Account?
|
||||
account: Account?,
|
||||
category: Category? = nil
|
||||
) -> Goal {
|
||||
// Check if goal with same name already exists in this account
|
||||
if let existing = findGoal(byName: name, in: account) {
|
||||
// Check if goal with same name already exists in this scope
|
||||
if let existing = findGoal(byName: name, in: account, category: category) {
|
||||
return existing
|
||||
}
|
||||
|
||||
@@ -92,6 +89,7 @@ class GoalRepository: ObservableObject {
|
||||
goal.targetAmount = NSDecimalNumber(decimal: targetAmount)
|
||||
goal.targetDate = targetDate
|
||||
goal.account = account
|
||||
goal.category = category
|
||||
save()
|
||||
return goal
|
||||
}
|
||||
@@ -104,7 +102,9 @@ class GoalRepository: ObservableObject {
|
||||
targetAmount: Decimal? = nil,
|
||||
targetDate: Date? = nil,
|
||||
clearTargetDate: Bool = false,
|
||||
isActive: Bool? = nil
|
||||
isActive: Bool? = nil,
|
||||
category: Category? = nil,
|
||||
clearCategory: Bool = false
|
||||
) {
|
||||
if let name = name {
|
||||
goal.name = name
|
||||
@@ -120,6 +120,11 @@ class GoalRepository: ObservableObject {
|
||||
if let isActive = isActive {
|
||||
goal.isActive = isActive
|
||||
}
|
||||
if clearCategory {
|
||||
goal.category = nil
|
||||
} else if let category = category {
|
||||
goal.category = category
|
||||
}
|
||||
save()
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user