InvestmentTrackerApp/PortfolioJournal/Repositories/InvestmentSourceRepository....

212 lines
6.0 KiB
Swift

import Foundation
import CoreData
import Combine
class InvestmentSourceRepository: ObservableObject {
private let context: NSManagedObjectContext
@Published private(set) var sources: [InvestmentSource] = []
init(context: NSManagedObjectContext = CoreDataStack.shared.viewContext) {
self.context = context
fetchSources()
setupNotificationObserver()
}
private func setupNotificationObserver() {
NotificationCenter.default.addObserver(
self,
selector: #selector(contextDidChange),
name: .NSManagedObjectContextObjectsDidChange,
object: context
)
}
@objc private func contextDidChange(_ notification: Notification) {
guard isRelevantChange(notification) else { return }
fetchSources()
}
// MARK: - Fetch
func fetchSources(account: Account? = nil) {
context.perform { [weak self] in
guard let self else { return }
let request: NSFetchRequest<InvestmentSource> = InvestmentSource.fetchRequest()
if let account = account {
request.predicate = NSPredicate(format: "account == %@", account)
}
request.sortDescriptors = [
NSSortDescriptor(keyPath: \InvestmentSource.name, ascending: true)
]
do {
self.sources = try self.context.fetch(request)
} catch {
print("Failed to fetch sources: \(error)")
self.sources = []
}
}
}
func fetchSource(by id: UUID) -> InvestmentSource? {
let request: NSFetchRequest<InvestmentSource> = InvestmentSource.fetchRequest()
request.predicate = NSPredicate(format: "id == %@", id as CVarArg)
request.fetchLimit = 1
return try? context.fetch(request).first
}
func fetchSources(for category: Category) -> [InvestmentSource] {
let request: NSFetchRequest<InvestmentSource> = InvestmentSource.fetchRequest()
request.predicate = NSPredicate(format: "category == %@", category)
request.sortDescriptors = [
NSSortDescriptor(keyPath: \InvestmentSource.name, ascending: true)
]
return (try? context.fetch(request)) ?? []
}
func fetchActiveSources() -> [InvestmentSource] {
sources.filter { $0.isActive }
}
func fetchSourcesNeedingUpdate(for account: Account? = nil) -> [InvestmentSource] {
let filtered = account == nil ? sources : sources.filter { $0.account?.id == account?.id }
return filtered.filter { $0.needsUpdate }
}
// MARK: - Create
@discardableResult
func createSource(
name: String,
category: Category,
notificationFrequency: NotificationFrequency = .monthly,
customFrequencyMonths: Int = 1,
account: Account? = nil
) -> InvestmentSource {
let source = InvestmentSource(context: context)
source.name = name
source.category = category
source.notificationFrequency = notificationFrequency.rawValue
source.customFrequencyMonths = Int16(customFrequencyMonths)
source.account = account
save()
return source
}
// MARK: - Update
func updateSource(
_ source: InvestmentSource,
name: String? = nil,
category: Category? = nil,
notificationFrequency: NotificationFrequency? = nil,
customFrequencyMonths: Int? = nil,
isActive: Bool? = nil,
account: Account? = nil
) {
if let name = name {
source.name = name
}
if let category = category {
source.category = category
}
if let frequency = notificationFrequency {
source.notificationFrequency = frequency.rawValue
}
if let customMonths = customFrequencyMonths {
source.customFrequencyMonths = Int16(customMonths)
}
if let isActive = isActive {
source.isActive = isActive
}
if let account = account {
source.account = account
}
save()
}
func toggleActive(_ source: InvestmentSource) {
source.isActive.toggle()
save()
}
// MARK: - Delete
func deleteSource(_ source: InvestmentSource) {
context.delete(source)
save()
}
func deleteSource(at offsets: IndexSet) {
for index in offsets {
guard index < sources.count else { continue }
context.delete(sources[index])
}
save()
}
// MARK: - Statistics
var sourceCount: Int {
sources.count
}
var activeSourceCount: Int {
sources.filter { $0.isActive }.count
}
var totalValue: Decimal {
sources.reduce(Decimal.zero) { $0 + $1.latestValue }
}
func topSources(limit: Int = 5) -> [InvestmentSource] {
sources
.sorted { $0.latestValue > $1.latestValue }
.prefix(limit)
.map { $0 }
}
// MARK: - Freemium Check
var canAddMoreSources: Bool {
// This will be checked against FreemiumValidator
true
}
// MARK: - Save
private func save() {
guard context.hasChanges else { return }
do {
try context.save()
fetchSources()
CoreDataStack.shared.refreshWidgetData()
} catch {
print("Failed to save context: \(error)")
}
}
private func isRelevantChange(_ notification: Notification) -> Bool {
guard let info = notification.userInfo else { return false }
let keys: [String] = [
NSInsertedObjectsKey,
NSUpdatedObjectsKey,
NSDeletedObjectsKey,
NSRefreshedObjectsKey
]
for key in keys {
if let objects = info[key] as? Set<NSManagedObject> {
if objects.contains(where: { $0 is InvestmentSource }) {
return true
}
}
}
return false
}
}