initial version
This commit is contained in:
@@ -0,0 +1,128 @@
|
||||
import Foundation
|
||||
import Combine
|
||||
import CoreData
|
||||
|
||||
@MainActor
|
||||
class MonthlyCheckInViewModel: ObservableObject {
|
||||
@Published var sourcesNeedingUpdate: [InvestmentSource] = []
|
||||
@Published var sources: [InvestmentSource] = []
|
||||
@Published var monthlySummary: MonthlySummary = .empty
|
||||
@Published var recentNotes: [Snapshot] = []
|
||||
@Published var isLoading = false
|
||||
@Published var selectedAccount: Account?
|
||||
@Published var showAllAccounts = true
|
||||
@Published var selectedRange: DateRange = .thisMonth
|
||||
@Published var checkInStats: MonthlyCheckInStats = .empty
|
||||
|
||||
private let sourceRepository: InvestmentSourceRepository
|
||||
private let snapshotRepository: SnapshotRepository
|
||||
private let calculationService: CalculationService
|
||||
private var cancellables = Set<AnyCancellable>()
|
||||
|
||||
@MainActor
|
||||
init(
|
||||
sourceRepository: InvestmentSourceRepository? = nil,
|
||||
snapshotRepository: SnapshotRepository? = nil,
|
||||
calculationService: CalculationService? = nil
|
||||
) {
|
||||
let context = CoreDataStack.shared.viewContext
|
||||
self.sourceRepository = sourceRepository ?? InvestmentSourceRepository(context: context)
|
||||
self.snapshotRepository = snapshotRepository ?? SnapshotRepository(context: context)
|
||||
self.calculationService = calculationService ?? .shared
|
||||
|
||||
setupObservers()
|
||||
refresh()
|
||||
}
|
||||
|
||||
private func setupObservers() {
|
||||
sourceRepository.$sources
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] _ in
|
||||
self?.refresh()
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
|
||||
NotificationCenter.default.publisher(for: .NSManagedObjectContextObjectsDidChange)
|
||||
.debounce(for: .milliseconds(500), scheduler: DispatchQueue.main)
|
||||
.sink { [weak self] _ in
|
||||
self?.refresh()
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
}
|
||||
|
||||
func refresh() {
|
||||
isLoading = true
|
||||
let filtered = filteredSources()
|
||||
let snapshots = filteredSnapshots(for: filtered)
|
||||
|
||||
let accountFilter = showAllAccounts ? nil : selectedAccount
|
||||
sourcesNeedingUpdate = sourceRepository.fetchSourcesNeedingUpdate(for: accountFilter)
|
||||
sources = filtered
|
||||
|
||||
monthlySummary = calculationService.calculateMonthlySummary(
|
||||
sources: filtered,
|
||||
snapshots: snapshots,
|
||||
range: selectedRange
|
||||
)
|
||||
|
||||
checkInStats = MonthlyCheckInStore.stats(referenceDate: selectedRange.end)
|
||||
|
||||
recentNotes = snapshots
|
||||
.filter { ($0.notes?.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty == false)
|
||||
&& selectedRange.contains($0.date) }
|
||||
.sorted { $0.date > $1.date }
|
||||
.prefix(5)
|
||||
.map { $0 }
|
||||
|
||||
isLoading = false
|
||||
}
|
||||
|
||||
func duplicatePreviousMonthSnapshots(referenceDate: Date) {
|
||||
let targetRange = DateRange.month(containing: referenceDate)
|
||||
let previousRange = DateRange.month(containing: referenceDate.adding(months: -1))
|
||||
let sources = filteredSources()
|
||||
guard !sources.isEmpty else { return }
|
||||
|
||||
let targetSnapshots = snapshotRepository.fetchSnapshots(
|
||||
from: targetRange.start,
|
||||
to: targetRange.end
|
||||
)
|
||||
let targetSourceIds = Set(targetSnapshots.compactMap { $0.source?.id })
|
||||
|
||||
let now = Date()
|
||||
let targetDate = targetRange.contains(now) ? now : targetRange.end
|
||||
|
||||
for source in sources {
|
||||
let sourceId = source.id
|
||||
if targetSourceIds.contains(sourceId) { continue }
|
||||
|
||||
let previousSnapshots = snapshotRepository.fetchSnapshots(for: source)
|
||||
guard let previousSnapshot = previousSnapshots.first(where: { previousRange.contains($0.date) }) else {
|
||||
continue
|
||||
}
|
||||
|
||||
snapshotRepository.createSnapshot(
|
||||
for: source,
|
||||
date: targetDate,
|
||||
value: previousSnapshot.decimalValue,
|
||||
contribution: previousSnapshot.contribution?.decimalValue,
|
||||
notes: previousSnapshot.notes
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private func filteredSources() -> [InvestmentSource] {
|
||||
if showAllAccounts || selectedAccount == nil {
|
||||
return sourceRepository.sources
|
||||
}
|
||||
return sourceRepository.sources.filter { $0.account?.id == selectedAccount?.id }
|
||||
}
|
||||
|
||||
private func filteredSnapshots(for sources: [InvestmentSource]) -> [Snapshot] {
|
||||
let sourceIds = Set(sources.compactMap { $0.id })
|
||||
return snapshotRepository.fetchAllSnapshots().filter { snapshot in
|
||||
let sourceId = snapshot.source?.id
|
||||
return sourceId.map(sourceIds.contains) ?? false
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user