Primera build enviada

This commit is contained in:
2026-01-19 14:40:43 +01:00
parent c6be398e5a
commit b03d35194f
36 changed files with 1641 additions and 561 deletions
+53 -6
View File
@@ -179,6 +179,55 @@ class CoreDataStack: ObservableObject {
private init() {}
// MARK: - Cleanup Duplicates
/// Removes duplicate objects that have the same UUID, keeping only the oldest one.
/// This fixes data corruption from race conditions during object creation.
func cleanupDuplicateObjects() {
let context = viewContext
context.performAndWait {
// Clean up duplicate Goals
cleanupDuplicates(entityName: "Goal", idKey: "id", context: context)
// Clean up duplicate Accounts (already handled in AccountRepository but added here for safety)
cleanupDuplicates(entityName: "Account", idKey: "id", context: context)
// Clean up duplicate InvestmentSources
cleanupDuplicates(entityName: "InvestmentSource", idKey: "id", context: context)
// Clean up duplicate Categories
cleanupDuplicates(entityName: "Category", idKey: "id", context: context)
if context.hasChanges {
try? context.save()
}
}
}
private func cleanupDuplicates(entityName: String, idKey: String, context: NSManagedObjectContext) {
let request = NSFetchRequest<NSManagedObject>(entityName: entityName)
request.sortDescriptors = [NSSortDescriptor(key: "createdAt", ascending: true)]
guard let objects = try? context.fetch(request) else { return }
var seenIds = Set<UUID>()
var objectsToDelete: [NSManagedObject] = []
for object in objects {
guard let objectId = object.value(forKey: idKey) as? UUID else { continue }
if seenIds.contains(objectId) {
objectsToDelete.append(object)
} else {
seenIds.insert(objectId)
}
}
if !objectsToDelete.isEmpty {
print("Cleaning up \(objectsToDelete.count) duplicate \(entityName) objects")
for object in objectsToDelete {
context.delete(object)
}
}
}
// MARK: - Save Context
func save() {
@@ -232,12 +281,10 @@ class CoreDataStack: ObservableObject {
/// Forces SQLite to checkpoint the WAL file, ensuring all changes are written to the main database file.
/// This is necessary for the widget to see changes, as it opens the database read-only.
private func checkpointWAL() {
if viewContext.hasChanges {
try? viewContext.save()
}
let coordinator = persistentContainer.persistentStoreCoordinator
coordinator.performAndWait {
viewContext.performAndWait {
if viewContext.hasChanges {
try? viewContext.save()
}
viewContext.refreshAllObjects()
}
}