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

Class Class, % Method, % Branch, % Line, % Instruction, %
GetBudgetTransactionsUseCaseKt 100% (1/1) 100% (1/1) 100% (3/3) 100% (12/12)


 package com.georgeci.moneysurfer.domain.usecase
 
 import com.georgeci.moneysurfer.domain.model.Budget
 import com.georgeci.moneysurfer.domain.model.CategorizedTransaction
 import com.georgeci.moneysurfer.domain.model.transactionsIn
 import com.georgeci.moneysurfer.domain.primitives.ClockUseCase
 import com.georgeci.moneysurfer.domain.repositories.TransactionRepository
 import com.georgeci.moneysurfer.domain.repositories.WorkspaceRepository
 import com.georgeci.moneysurfer.domain.util.BudgetPeriodWindow
 import com.georgeci.moneysurfer.domain.util.TransactionPeriodWindow
 import com.georgeci.moneysurfer.domain.util.windowContaining
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.emitAll
 import kotlinx.coroutines.flow.flow
 import kotlinx.coroutines.flow.map
 import kotlinx.datetime.DateTimeUnit
 import kotlinx.datetime.TimeZone
 import kotlinx.datetime.minus
 import kotlinx.datetime.toLocalDateTime
 import org.koin.core.annotation.Single
 
 /**
  * A budget window holds one period of spend, so this bound is far above any realistic count —
  * it exists only because the query is paged, and cuts off the oldest rows if it is ever hit.
  */
 private const val WINDOW_TRANSACTION_LIMIT = 1000
 
 /**
  * The transactions that make up a budget's spend in its current window — the same filter
  * [GetBudgetProgressUseCase] sums, so the list always adds up to the number above it.
  */
 @Single
 class GetBudgetTransactionsUseCase(
     private val transactionRepository: TransactionRepository,
     private val workspaceRepository: WorkspaceRepository,
     private val clock: ClockUseCase,
 ) {
 
     operator fun invoke(
         budget: Budget,
         timeZone: TimeZone = TimeZone.currentSystemDefault(),
     ): Flow<List<CategorizedTransaction>> = flow {
         val baseCurrency = workspaceRepository.getById(budget.workspaceId)?.baseCurrency
         val window = budget.windowContaining(clock.now().toLocalDateTime(timeZone).date)
         emitAll(
             transactionRepository.getCategorizedWindow(
                 accountId = null,
                 window = window.asTransactionPeriod(),
                 limit = WINDOW_TRANSACTION_LIMIT,
             ).map { categorized ->
                 // The date range is already applied by the query; the rest of the budget filter
                 // (workspace, type, status, transfers, categories, currency) is not.
                 val matching = budget.transactionsIn(
                     transactions = categorized.map { it.transaction },
                     window = window,
                     baseCurrency = baseCurrency,
                 ).map { it.id }.toSet()
                 categorized.filter { it.transaction.id in matching }
             },
         )
     }
 }
 
 /**
  * The same span as a transaction-query window: a budget window is half-open, the query one is
  * closed, so the last day moves in by one.
  */
 private fun BudgetPeriodWindow.asTransactionPeriod(): TransactionPeriodWindow =
     TransactionPeriodWindow(
         from = startInclusive,
         to = endExclusive.minus(1, DateTimeUnit.DAY),
     )