Coverage Summary for Class: TransactionSplitGroup (com.georgeci.moneysurfer.domain.model)

Class Class, % Method, % Branch, % Line, % Instruction, %
TransactionSplitGroup 100% (1/1) 100% (3/3) 83.3% (5/6) 100% (8/8) 100% (77/77)


 package com.georgeci.moneysurfer.domain.model
 
 import com.georgeci.moneysurfer.domain.primitives.Money
 
 /**
  * One receipt: either an ordinary transaction on its own, or the legs it was split across.
  *
  * A group is what the transaction list, the recent-activity widget and search results render as a
  * single row — a payment the user made once should be one line, whatever number of categories it
  * was filed under. Everything that reasons about categories (budgets, monthly totals, spend
  * history, CSV) keeps working on the legs and never sees this type.
  */
 data class TransactionSplitGroup(val legs: List<Transaction>) {
 
     init {
         require(legs.isNotEmpty()) { "A split group needs at least one leg" }
     }
 
     /** The row that stands for the group: its id is what opening the group navigates to. */
     val primary: Transaction get() = legs.first()
 
     val isSplit: Boolean get() = legs.size > 1
 
     /** Magnitude of the whole receipt — the legs share a currency, so a plain sum is sound. */
     val total: Money = legs.fold(Money.zero()) { acc, leg -> acc + leg.money.abs() }
 
     /**
      * Distinct categories the receipt was filed under. Legs left uncategorized count once between
      * them, which is what the "N categories" badge should say for a half-filled split.
      */
     val categoryCount: Int = legs.map { it.categoryId }.distinct().size
 }
 
 /**
  * Collapses split legs into groups, preserving the order the rows arrived in: a group takes the
  * position of its topmost leg, so a list sorted newest-first stays sorted.
  *
  * Legs are matched across the whole list rather than only between neighbours — two legs of one
  * receipt sort adjacently in practice but nothing guarantees another row cannot land between them.
  */
 fun List<Transaction>.groupSplitLegs(): List<TransactionSplitGroup> {
     val bySplit = filter { it.splitId != null }.groupBy { it.splitId }
     val seen = mutableSetOf<Any>()
     return mapNotNull { transaction ->
         val splitId = transaction.splitId
             ?: return@mapNotNull TransactionSplitGroup(listOf(transaction))
         if (!seen.add(splitId)) return@mapNotNull null
         TransactionSplitGroup(bySplit.getValue(splitId))
     }
 }