import Foundation import CoreData @objc(InvestmentSource) public class InvestmentSource: NSManagedObject, Identifiable { @nonobjc public class func fetchRequest() -> NSFetchRequest { return NSFetchRequest(entityName: "InvestmentSource") } @NSManaged public var id: UUID @NSManaged public var name: String @NSManaged public var notificationFrequency: String @NSManaged public var customFrequencyMonths: Int16 @NSManaged public var isActive: Bool @NSManaged public var createdAt: Date @NSManaged public var category: Category? @NSManaged public var snapshots: NSSet? public override func awakeFromInsert() { super.awakeFromInsert() id = UUID() createdAt = Date() isActive = true notificationFrequency = NotificationFrequency.monthly.rawValue customFrequencyMonths = 1 name = "" } } // MARK: - Notification Frequency enum NotificationFrequency: String, CaseIterable, Identifiable { case monthly = "monthly" case quarterly = "quarterly" case semiannual = "semiannual" case annual = "annual" case custom = "custom" case never = "never" var id: String { rawValue } var displayName: String { switch self { case .monthly: return "Monthly" case .quarterly: return "Quarterly" case .semiannual: return "Semi-Annual" case .annual: return "Annual" case .custom: return "Custom" case .never: return "Never" } } var months: Int { switch self { case .monthly: return 1 case .quarterly: return 3 case .semiannual: return 6 case .annual: return 12 case .custom, .never: return 0 } } } // MARK: - Computed Properties extension InvestmentSource { var snapshotsArray: [Snapshot] { let set = snapshots as? Set ?? [] return set.sorted { $0.date > $1.date } } var sortedSnapshotsByDateAscending: [Snapshot] { let set = snapshots as? Set ?? [] return set.sorted { $0.date < $1.date } } var latestSnapshot: Snapshot? { snapshotsArray.first } var latestValue: Decimal { latestSnapshot?.value?.decimalValue ?? Decimal.zero } var snapshotCount: Int { snapshots?.count ?? 0 } var frequency: NotificationFrequency { NotificationFrequency(rawValue: notificationFrequency) ?? .monthly } var nextReminderDate: Date? { guard frequency != .never else { return nil } let months = frequency == .custom ? Int(customFrequencyMonths) : frequency.months guard let lastSnapshot = latestSnapshot else { return Date() // Remind now if no snapshots } return Calendar.current.date(byAdding: .month, value: months, to: lastSnapshot.date) } var needsUpdate: Bool { guard let nextDate = nextReminderDate else { return false } return Date() >= nextDate } // MARK: - Performance Metrics var totalReturn: Decimal { guard let first = sortedSnapshotsByDateAscending.first, let last = snapshotsArray.first, let firstValue = first.value?.decimalValue, firstValue != Decimal.zero else { return Decimal.zero } let lastValue = last.value?.decimalValue ?? Decimal.zero return ((lastValue - firstValue) / firstValue) * 100 } var totalContributions: Decimal { snapshotsArray.reduce(Decimal.zero) { result, snapshot in result + (snapshot.contribution?.decimalValue ?? Decimal.zero) } } } // MARK: - Generated accessors for snapshots extension InvestmentSource { @objc(addSnapshotsObject:) @NSManaged public func addToSnapshots(_ value: Snapshot) @objc(removeSnapshotsObject:) @NSManaged public func removeFromSnapshots(_ value: Snapshot) @objc(addSnapshots:) @NSManaged public func addToSnapshots(_ values: NSSet) @objc(removeSnapshots:) @NSManaged public func removeFromSnapshots(_ values: NSSet) }