Coverage Summary for Class: GetDailySpendSeriesUseCase (com.georgeci.moneysurfer.domain.usecase)

Class Method, % Branch, % Line, % Instruction, %
GetDailySpendSeriesUseCase 80% (4/5) 100% (8/8) 95.2% (20/21) 73.7% (28/38)
GetDailySpendSeriesUseCase$baseCurrency-DBbEM4U$$inlined$map$1 0% (0/2)
GetDailySpendSeriesUseCase$baseCurrency-DBbEM4U$$inlined$map$1$1
GetDailySpendSeriesUseCase$baseCurrency-DBbEM4U$$inlined$map$1$2 0% (0/1)
GetDailySpendSeriesUseCase$baseCurrency-DBbEM4U$$inlined$map$1$2$1
GetDailySpendSeriesUseCase$invoke$$inlined$flatMapLatest$1 0% (0/1)
GetDailySpendSeriesUseCase$invoke$lambda$0$$inlined$flatMapLatest$1 0% (0/1)
GetDailySpendSeriesUseCase$invoke$lambda$0$0$$inlined$map$1 0% (0/2)
GetDailySpendSeriesUseCase$invoke$lambda$0$0$$inlined$map$1$1
GetDailySpendSeriesUseCase$invoke$lambda$0$0$$inlined$map$1$2 0% (0/1)
GetDailySpendSeriesUseCase$invoke$lambda$0$0$$inlined$map$1$2$1
Total 30.8% (4/13) 100% (8/8) 95.2% (20/21) 73.7% (28/38)


 package com.georgeci.moneysurfer.domain.usecase
 
 import com.georgeci.moneysurfer.domain.auth.SessionPointers
 import com.georgeci.moneysurfer.domain.model.DailySpendSeries
 import com.georgeci.moneysurfer.domain.model.SpendScope
 import com.georgeci.moneysurfer.domain.model.dailySpendSeries
 import com.georgeci.moneysurfer.domain.model.dailySpendSeriesWindow
 import com.georgeci.moneysurfer.domain.primitives.ClockUseCase
 import com.georgeci.moneysurfer.domain.primitives.CurrencyCode
 import com.georgeci.moneysurfer.domain.primitives.WorkspaceId
 import com.georgeci.moneysurfer.domain.repositories.SpendAnalyticsRepository
 import com.georgeci.moneysurfer.domain.repositories.WorkspaceRepository
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.map
 import kotlinx.datetime.TimeZone
 import kotlinx.datetime.toLocalDateTime
 import org.koin.core.annotation.Single
 
 /**
  * The active workspace's last week of daily spend, or null while nothing backs it — nobody signed
  * in, no workspace selected, or a workspace row the device has not pulled yet.
  *
  * One `GROUP BY` answers both halves of what the burn rate needs: the days the chart draws and the
  * month-to-date its projection starts from. See
  * [com.georgeci.moneysurfer.domain.model.dailySpendSeriesWindow] for why that is one window.
  *
  * "Today" is read from the clock once per workspace-and-currency emission, not per row, so a session
  * left open across midnight keeps yesterday's window until something re-subscribes. That matches
  * every other date-scoped query here ([GetCategorySpendHistoryUseCase] resolves its months the same
  * way), and a dashboard is re-collected on every return to the screen.
  */
 @Single
 @OptIn(ExperimentalCoroutinesApi::class)
 class GetDailySpendSeriesUseCase(
     private val spendAnalytics: SpendAnalyticsRepository,
     private val workspaceRepository: WorkspaceRepository,
     private val session: SessionPointers,
     private val clock: ClockUseCase,
 ) {
 
     operator fun invoke(timeZone: TimeZone = TimeZone.currentSystemDefault()): Flow<DailySpendSeries?> =
         session.currentWorkspaceId.flatMapLatest { workspaceId ->
             workspaceId ?: return@flatMapLatest flowOf(null)
             val today = clock.now().toLocalDateTime(timeZone).date
 
             baseCurrency(workspaceId).flatMapLatest { currency ->
                 currency ?: return@flatMapLatest flowOf(null)
                 val scope = SpendScope(
                     workspaceId = workspaceId,
                     baseCurrency = currency,
                     window = dailySpendSeriesWindow(today),
                 )
                 spendAnalytics.daily(scope)
                     .map { points -> dailySpendSeries(points, today, currency, workspaceId) }
             }
         }
 
     /**
      * The workspace base currency, live — the same reason [GetCategorySpendHistoryUseCase] observes
      * it rather than reading it once: the query *filters* on it, so a device that has not pulled the
      * workspace row yet resolves null, and a null would otherwise latch an empty chart on screen for
      * as long as the caller stayed subscribed.
      *
      * Null is reported as "no series" rather than queried with, because a spend figure the app
      * cannot name a currency for is not a figure the widget can print.
      */
     private fun baseCurrency(workspaceId: WorkspaceId): Flow<CurrencyCode?> =
         workspaceRepository.getAll()
             .map { workspaces -> workspaces.firstOrNull { it.id == workspaceId }?.baseCurrency }
             .distinctUntilChanged()
 }