diff --git a/app/src/main/java/com/ivy/wallet/domain/action/account/CalcAccBalanceAct.kt b/app/src/main/java/com/ivy/wallet/domain/action/account/CalcAccBalanceAct.kt index 13ee664184..a3710d093d 100644 --- a/app/src/main/java/com/ivy/wallet/domain/action/account/CalcAccBalanceAct.kt +++ b/app/src/main/java/com/ivy/wallet/domain/action/account/CalcAccBalanceAct.kt @@ -4,8 +4,8 @@ import arrow.core.nonEmptyListOf import com.ivy.fp.action.FPAction import com.ivy.fp.action.then import com.ivy.wallet.domain.data.core.Account -import com.ivy.wallet.domain.pure.account.AccountValueFunctions -import com.ivy.wallet.domain.pure.account.calcAccValues +import com.ivy.wallet.domain.pure.AccountValueFunctions +import com.ivy.wallet.domain.pure.calcValues import com.ivy.wallet.domain.pure.data.ClosedTimeRange import java.math.BigDecimal import javax.inject.Inject @@ -20,9 +20,9 @@ class CalcAccBalanceAct @Inject constructor( range = range ) } then accTrnsAct then { accTrns -> - calcAccValues( - accountId = account.id, - accountsTrns = accTrns, + calcValues( + transactions = accTrns, + arg = account.id, valueFunctions = nonEmptyListOf(AccountValueFunctions::balance) ).head } then { balance -> diff --git a/app/src/main/java/com/ivy/wallet/domain/action/transaction/AddDateDividersAct.kt b/app/src/main/java/com/ivy/wallet/domain/action/transaction/TrnsWithDateDividersAct.kt similarity index 75% rename from app/src/main/java/com/ivy/wallet/domain/action/transaction/AddDateDividersAct.kt rename to app/src/main/java/com/ivy/wallet/domain/action/transaction/TrnsWithDateDividersAct.kt index 1c657e29ed..845d3aa94b 100644 --- a/app/src/main/java/com/ivy/wallet/domain/action/transaction/AddDateDividersAct.kt +++ b/app/src/main/java/com/ivy/wallet/domain/action/transaction/TrnsWithDateDividersAct.kt @@ -6,18 +6,20 @@ import com.ivy.wallet.domain.action.ExchangeAct import com.ivy.wallet.domain.action.actInput import com.ivy.wallet.domain.data.TransactionHistoryItem import com.ivy.wallet.domain.data.core.Transaction -import com.ivy.wallet.domain.pure.wallet.withDateDividers +import com.ivy.wallet.domain.pure.transaction.transactionsWithDateDividers import com.ivy.wallet.io.persistence.dao.AccountDao import javax.inject.Inject -class AddDateDividersAct @Inject constructor( +class TrnsWithDateDividersAct @Inject constructor( private val accountDao: AccountDao, private val exchangeAct: ExchangeAct -) : FPAction>() { +) : FPAction>() { override suspend fun Input.compose(): suspend () -> List = suspend { - transactions.withDateDividers( + transactionsWithDateDividers( + transactions = transactions, baseCurrencyCode = baseCurrency, + getAccount = accountDao::findById then { it?.toDomain() }, exchange = ::actInput then exchangeAct ) diff --git a/app/src/main/java/com/ivy/wallet/domain/data/core/Transaction.kt b/app/src/main/java/com/ivy/wallet/domain/data/core/Transaction.kt index 19167e1365..de7b33d373 100644 --- a/app/src/main/java/com/ivy/wallet/domain/data/core/Transaction.kt +++ b/app/src/main/java/com/ivy/wallet/domain/data/core/Transaction.kt @@ -2,15 +2,16 @@ package com.ivy.wallet.domain.data.core import com.ivy.wallet.domain.data.TransactionHistoryItem import com.ivy.wallet.domain.data.TransactionType +import java.math.BigDecimal import java.time.LocalDateTime import java.util.* data class Transaction( val accountId: UUID, val type: TransactionType, - val amount: Double, + val amount: BigDecimal, val toAccountId: UUID? = null, - val toAmount: Double? = null, + val toAmount: BigDecimal, val title: String? = null, val description: String? = null, val dateTime: LocalDateTime? = null, @@ -25,24 +26,7 @@ data class Transaction( val loanId: UUID? = null, //This refers to the loan record id that is linked with a transaction - val loanRecordId:UUID? = null, - - val isSynced: Boolean = false, - val isDeleted: Boolean = false, + val loanRecordId: UUID? = null, val id: UUID = UUID.randomUUID() -) : TransactionHistoryItem { - - fun isIdenticalWith(transaction: Transaction?): Boolean { - if (transaction == null) return false - - //Set isSynced && isDeleted to false so they aren't accounted in the equals check - return this.copy( - isSynced = false, - isDeleted = false - ) == transaction.copy( - isSynced = false, - isDeleted = false - ) - } -} \ No newline at end of file +) : TransactionHistoryItem \ No newline at end of file diff --git a/app/src/main/java/com/ivy/wallet/domain/pure/account/AccountValueFunctions.kt b/app/src/main/java/com/ivy/wallet/domain/pure/AccValueFunctions.kt similarity index 71% rename from app/src/main/java/com/ivy/wallet/domain/pure/account/AccountValueFunctions.kt rename to app/src/main/java/com/ivy/wallet/domain/pure/AccValueFunctions.kt index 5f5dbb8648..66ff1176e0 100644 --- a/app/src/main/java/com/ivy/wallet/domain/pure/account/AccountValueFunctions.kt +++ b/app/src/main/java/com/ivy/wallet/domain/pure/AccValueFunctions.kt @@ -1,8 +1,8 @@ -package com.ivy.wallet.domain.pure.account +package com.ivy.wallet.domain.pure import com.ivy.wallet.domain.data.TransactionType +import com.ivy.wallet.domain.data.core.Transaction import com.ivy.wallet.domain.pure.core.ValueFunction -import com.ivy.wallet.domain.pure.data.FPTransaction import java.math.BigDecimal import java.util.* @@ -10,16 +10,16 @@ typealias AccountValueFunction = ValueFunction object AccountValueFunctions { fun balance( - fpTransaction: FPTransaction, + transaction: Transaction, accountId: UUID - ): BigDecimal = with(fpTransaction) { + ): BigDecimal = with(transaction) { if (this.accountId == accountId) { //Account's transactions when (type) { TransactionType.INCOME -> amount TransactionType.EXPENSE -> amount.negate() TransactionType.TRANSFER -> { - if (toAccountId.orNull() != accountId) { + if (toAccountId != accountId) { //transfer to another account amount.negate() } else { @@ -30,39 +30,39 @@ object AccountValueFunctions { } } else { //potential transfer to account? - toAccountId.orNull()?.takeIf { it == accountId } ?: return BigDecimal.ZERO + toAccountId?.takeIf { it == accountId } ?: return BigDecimal.ZERO toAmount } } fun income( - fpTransaction: FPTransaction, + transaction: Transaction, accountId: UUID - ): BigDecimal = with(fpTransaction) { + ): BigDecimal = with(transaction) { if (this.accountId == accountId && type == TransactionType.INCOME) amount else BigDecimal.ZERO } fun expense( - fpTransaction: FPTransaction, + transaction: Transaction, accountId: UUID - ): BigDecimal = with(fpTransaction) { + ): BigDecimal = with(transaction) { if (this.accountId == accountId && type == TransactionType.EXPENSE) amount else BigDecimal.ZERO } fun incomeCount( - fpTransaction: FPTransaction, + transaction: Transaction, accountId: UUID - ): BigDecimal = with(fpTransaction) { + ): BigDecimal = with(transaction) { if (this.accountId == accountId && type == TransactionType.INCOME) BigDecimal.ONE else BigDecimal.ZERO } fun expenseCount( - fpTransaction: FPTransaction, + transaction: Transaction, accountId: UUID - ): BigDecimal = with(fpTransaction) { + ): BigDecimal = with(transaction) { if (this.accountId == accountId && type == TransactionType.EXPENSE) BigDecimal.ONE else BigDecimal.ZERO } diff --git a/app/src/main/java/com/ivy/wallet/domain/pure/category/CategoryValueFunctions.kt b/app/src/main/java/com/ivy/wallet/domain/pure/CatValueFunctions.kt similarity index 61% rename from app/src/main/java/com/ivy/wallet/domain/pure/category/CategoryValueFunctions.kt rename to app/src/main/java/com/ivy/wallet/domain/pure/CatValueFunctions.kt index 342c48dd03..077926a1d8 100644 --- a/app/src/main/java/com/ivy/wallet/domain/pure/category/CategoryValueFunctions.kt +++ b/app/src/main/java/com/ivy/wallet/domain/pure/CatValueFunctions.kt @@ -1,13 +1,12 @@ -package com.ivy.wallet.domain.pure.category +package com.ivy.wallet.domain.pure import arrow.core.Option import arrow.core.toOption +import com.ivy.fp.SideEffect import com.ivy.wallet.domain.data.TransactionType import com.ivy.wallet.domain.data.core.Account +import com.ivy.wallet.domain.data.core.Transaction import com.ivy.wallet.domain.pure.core.SuspendValueFunction -import com.ivy.wallet.domain.pure.data.FPTransaction -import com.ivy.wallet.domain.pure.exchangeToBaseCurrency -import com.ivy.wallet.io.persistence.dao.ExchangeRateDao import java.math.BigDecimal import java.util.* @@ -16,17 +15,17 @@ typealias CategoryValueFunction = SuspendValueFunction, - val exchangeRateDao: ExchangeRateDao, - val baseCurrencyCode: String, + + @SideEffect + val exchangeToBaseCurrency: suspend (Option, BigDecimal) -> Option ) suspend fun balance( - fpTransaction: FPTransaction, + transaction: Transaction, arg: Argument, - ): BigDecimal = with(fpTransaction) { - if (this.categoryId.orNull() == arg.categoryId) { + ): BigDecimal = with(transaction) { + if (this.categoryId == arg.categoryId) { when (type) { TransactionType.INCOME -> amount.toBaseCurrencyOrZero(arg, accountId) TransactionType.EXPENSE -> amount.toBaseCurrencyOrZero(arg, accountId).negate() @@ -36,10 +35,10 @@ object CategoryValueFunctions { } suspend fun income( - fpTransaction: FPTransaction, + transaction: Transaction, arg: Argument, - ): BigDecimal = with(fpTransaction) { - if (this.categoryId.orNull() == arg.categoryId) { + ): BigDecimal = with(transaction) { + if (this.categoryId == arg.categoryId) { when (type) { TransactionType.INCOME -> amount.toBaseCurrencyOrZero(arg, accountId) else -> BigDecimal.ZERO @@ -48,10 +47,10 @@ object CategoryValueFunctions { } suspend fun expense( - fpTransaction: FPTransaction, + transaction: Transaction, arg: Argument, - ): BigDecimal = with(fpTransaction) { - if (this.categoryId.orNull() == arg.categoryId) { + ): BigDecimal = with(transaction) { + if (this.categoryId == arg.categoryId) { when (type) { TransactionType.EXPENSE -> amount.toBaseCurrencyOrZero(arg, accountId) else -> BigDecimal.ZERO @@ -60,10 +59,10 @@ object CategoryValueFunctions { } suspend fun incomeCount( - fpTransaction: FPTransaction, + transaction: Transaction, arg: Argument, - ): BigDecimal = with(fpTransaction) { - if (this.categoryId.orNull() == arg.categoryId) { + ): BigDecimal = with(transaction) { + if (this.categoryId == arg.categoryId) { when (type) { TransactionType.INCOME -> BigDecimal.ONE else -> BigDecimal.ZERO @@ -72,10 +71,10 @@ object CategoryValueFunctions { } suspend fun expenseCount( - fpTransaction: FPTransaction, + transaction: Transaction, arg: Argument, - ): BigDecimal = with(fpTransaction) { - if (this.categoryId.orNull() == arg.categoryId) { + ): BigDecimal = with(transaction) { + if (this.categoryId == arg.categoryId) { when (type) { TransactionType.EXPENSE -> BigDecimal.ONE else -> BigDecimal.ZERO @@ -84,24 +83,20 @@ object CategoryValueFunctions { } private suspend fun BigDecimal.toBaseCurrencyOrZero( - argument: Argument, + arg: Argument, accountId: UUID ): BigDecimal { return this.convertToBaseCurrency( - argument = argument, + arg = arg, accountId = accountId ).orNull() ?: BigDecimal.ZERO } private suspend fun BigDecimal.convertToBaseCurrency( accountId: UUID, - argument: Argument + arg: Argument ): Option { - return exchangeToBaseCurrency( - exchangeRateDao = argument.exchangeRateDao, - baseCurrencyCode = argument.baseCurrencyCode, - fromAmount = this, - fromCurrencyCode = argument.accounts.find { it.id == accountId }?.currency.toOption() - ) + val trnCurrency = arg.accounts.find { it.id == accountId }?.currency.toOption() + return arg.exchangeToBaseCurrency(trnCurrency, this) } } \ No newline at end of file diff --git a/app/src/main/java/com/ivy/wallet/domain/pure/IvyCore.kt b/app/src/main/java/com/ivy/wallet/domain/pure/IvyCore.kt index bca1206d53..e93dafd137 100644 --- a/app/src/main/java/com/ivy/wallet/domain/pure/IvyCore.kt +++ b/app/src/main/java/com/ivy/wallet/domain/pure/IvyCore.kt @@ -1,2 +1,21 @@ package com.ivy.wallet.domain.pure +import arrow.core.NonEmptyList +import com.ivy.fp.Pure +import com.ivy.wallet.domain.data.core.Transaction +import com.ivy.wallet.domain.pure.core.ValueFunction +import com.ivy.wallet.domain.pure.core.calculateValueFunctionsSum +import java.math.BigDecimal + +@Pure +fun calcValues( + transactions: List, + valueFunctions: NonEmptyList>, + arg: Arg +): NonEmptyList { + return calculateValueFunctionsSum( + valueFunctionArgument = arg, + transactions = transactions, + valueFunctions = valueFunctions + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/ivy/wallet/domain/pure/account/AccountCore.kt b/app/src/main/java/com/ivy/wallet/domain/pure/account/AccountCore.kt deleted file mode 100644 index 11f6cc50a2..0000000000 --- a/app/src/main/java/com/ivy/wallet/domain/pure/account/AccountCore.kt +++ /dev/null @@ -1,73 +0,0 @@ -package com.ivy.wallet.domain.pure.account - -import arrow.core.NonEmptyList -import com.ivy.fp.Pure -import com.ivy.fp.Total -import com.ivy.wallet.domain.data.core.Transaction -import com.ivy.wallet.domain.pure.core.calculateValueFunctionsSum -import com.ivy.wallet.domain.pure.data.ClosedTimeRange -import com.ivy.wallet.domain.pure.data.toFPTransaction -import com.ivy.wallet.io.persistence.dao.TransactionDao -import java.math.BigDecimal -import java.util.* - -@Total -suspend fun calcAccValues( - transactionDao: TransactionDao, - accountId: UUID, - range: ClosedTimeRange, - valueFunctions: NonEmptyList -): NonEmptyList { - return calcAccValues( - accountId = accountId, - retrieveAccountTransactions = { - transactionDao.findAllByAccountAndBetween( - accountId = accountId, - startDate = range.from, - endDate = range.to - ) - }, - retrieveToAccountTransfers = { - transactionDao.findAllToAccountAndBetween( - toAccountId = accountId, - startDate = range.from, - endDate = range.to - ) - }, - valueFunctions = valueFunctions - ) -} - -@Total -suspend fun calcAccValues( - accountId: UUID, - retrieveAccountTransactions: suspend (UUID) -> List, - retrieveToAccountTransfers: suspend (UUID) -> List, - valueFunctions: NonEmptyList -): NonEmptyList { - val accountTrns = retrieveAccountTransactions(accountId) - .plus(retrieveToAccountTransfers(accountId)) - .map { it.toFPTransaction() } - - return calculateValueFunctionsSum( - valueFunctionArgument = accountId, - transactions = accountTrns, - valueFunctions = valueFunctions - ) -} - -@Pure -fun calcAccValues( - accountId: UUID, - accountsTrns: List, - valueFunctions: NonEmptyList -): NonEmptyList { - val accountTrns = accountsTrns - .map { it.toFPTransaction() } - - return calculateValueFunctionsSum( - valueFunctionArgument = accountId, - transactions = accountTrns, - valueFunctions = valueFunctions - ) -} \ No newline at end of file diff --git a/app/src/main/java/com/ivy/wallet/domain/pure/account/AccountFunctions.kt b/app/src/main/java/com/ivy/wallet/domain/pure/account/AccountFunctions.kt deleted file mode 100644 index c5907b7b4a..0000000000 --- a/app/src/main/java/com/ivy/wallet/domain/pure/account/AccountFunctions.kt +++ /dev/null @@ -1,82 +0,0 @@ -package com.ivy.wallet.domain.pure.account - -import arrow.core.nonEmptyListOf -import com.ivy.wallet.domain.pure.data.ClosedTimeRange -import com.ivy.wallet.domain.pure.data.IncomeExpensePair -import com.ivy.wallet.io.persistence.dao.TransactionDao -import java.math.BigDecimal -import java.util.* - - -suspend fun calculateAccountBalance( - transactionDao: TransactionDao, - accountId: UUID, - range: ClosedTimeRange = ClosedTimeRange.allTimeIvy() -): BigDecimal { - return calcAccValues( - transactionDao = transactionDao, - accountId = accountId, - range = range, - valueFunctions = nonEmptyListOf( - AccountValueFunctions::balance - ) - ).head -} - -data class AccountStats( - val balance: BigDecimal, - val income: BigDecimal, - val expense: BigDecimal, - val incomeCount: Int, - val expenseCount: Int -) - -suspend fun calculateAccountStats( - transactionDao: TransactionDao, - accountId: UUID, - range: ClosedTimeRange = ClosedTimeRange.allTimeIvy() -): AccountStats { - val values = calcAccValues( - transactionDao = transactionDao, - accountId = accountId, - range = range, - valueFunctions = nonEmptyListOf( - AccountValueFunctions::balance, - AccountValueFunctions::income, - AccountValueFunctions::expense, - AccountValueFunctions::incomeCount, - AccountValueFunctions::expenseCount - ) - ) - - return AccountStats( - balance = values[0], - income = values[1], - expense = values[2], - incomeCount = values[3].toInt(), - expenseCount = values[4].toInt() - ) -} - -suspend fun calculateAccountIncomeExpense( - transactionDao: TransactionDao, - accountId: UUID, - range: ClosedTimeRange = ClosedTimeRange.allTimeIvy() -): IncomeExpensePair { - val values = calcAccValues( - transactionDao = transactionDao, - accountId = accountId, - range = range, - valueFunctions = nonEmptyListOf( - AccountValueFunctions::income, - AccountValueFunctions::expense, - ) - ) - - return IncomeExpensePair( - income = values[0], - expense = values[1], - ) -} - - diff --git a/app/src/main/java/com/ivy/wallet/domain/pure/category/CategoryCore.kt b/app/src/main/java/com/ivy/wallet/domain/pure/category/CategoryCore.kt deleted file mode 100644 index 4b34da7f27..0000000000 --- a/app/src/main/java/com/ivy/wallet/domain/pure/category/CategoryCore.kt +++ /dev/null @@ -1,101 +0,0 @@ -package com.ivy.wallet.domain.pure.category - -import arrow.core.NonEmptyList -import com.ivy.wallet.domain.data.core.Transaction -import com.ivy.wallet.domain.pure.core.calculateValueFunctionsSumSuspend -import com.ivy.wallet.domain.pure.data.ClosedTimeRange -import com.ivy.wallet.domain.pure.data.WalletDAOs -import com.ivy.wallet.domain.pure.data.toFPTransaction -import java.math.BigDecimal -import java.util.* - -suspend fun calculateCategoryValues( - walletDAOs: WalletDAOs, - baseCurrencyCode: String, - categoryId: UUID?, - range: ClosedTimeRange, - valueFunctions: NonEmptyList -): NonEmptyList { - return calculateCategoryValues( - argument = CategoryValueFunctions.Argument( - categoryId = categoryId, - accounts = walletDAOs.accountDao.findAll(), - exchangeRateDao = walletDAOs.exchangeRateDao, - baseCurrencyCode = baseCurrencyCode - ), - retrieveCategoryTransactions = { forCategoryId -> - if (forCategoryId == null) { - walletDAOs.transactionDao.findAllUnspecifiedAndBetween( - startDate = range.from, - endDate = range.to - ) - } else { - walletDAOs.transactionDao.findAllByCategoryAndBetween( - categoryId = forCategoryId, - startDate = range.from, - endDate = range.to - ) - } - - }, - valueFunctions = valueFunctions - ) -} - -suspend fun calculateCategoryValuesWithAccountFilters( - walletDAOs: WalletDAOs, - baseCurrencyCode: String, - categoryId: UUID?, - range: ClosedTimeRange, - accountIdFilterSet: Set, - valueFunctions: NonEmptyList -): NonEmptyList { - return calculateCategoryValues( - argument = CategoryValueFunctions.Argument( - categoryId = categoryId, - accounts = walletDAOs.accountDao.findAll(), - exchangeRateDao = walletDAOs.exchangeRateDao, - baseCurrencyCode = baseCurrencyCode - ), - retrieveCategoryTransactions = { forCategoryId -> - if (forCategoryId == null) { - walletDAOs.transactionDao.findAllUnspecifiedAndBetween( - startDate = range.from, - endDate = range.to - ).filter { - if (accountIdFilterSet.isNotEmpty()) - accountIdFilterSet.contains(it.accountId) - else - true - } - } else { - walletDAOs.transactionDao.findAllByCategoryAndBetween( - categoryId = forCategoryId, - startDate = range.from, - endDate = range.to - ).filter { - if (accountIdFilterSet.isNotEmpty()) - accountIdFilterSet.contains(it.accountId) - else - true - } - } - }, - valueFunctions = valueFunctions - ) -} - -suspend fun calculateCategoryValues( - argument: CategoryValueFunctions.Argument, - retrieveCategoryTransactions: suspend (UUID?) -> List, - valueFunctions: NonEmptyList -): NonEmptyList { - val categoryTrns = retrieveCategoryTransactions(argument.categoryId) - .map { it.toFPTransaction() } - - return calculateValueFunctionsSumSuspend( - valueFunctionArgument = argument, - transactions = categoryTrns, - valueFunctions = valueFunctions - ) -} \ No newline at end of file diff --git a/app/src/main/java/com/ivy/wallet/domain/pure/category/CategoryFunctions.kt b/app/src/main/java/com/ivy/wallet/domain/pure/category/CategoryFunctions.kt deleted file mode 100644 index c5a6e7c5ea..0000000000 --- a/app/src/main/java/com/ivy/wallet/domain/pure/category/CategoryFunctions.kt +++ /dev/null @@ -1,135 +0,0 @@ -package com.ivy.wallet.domain.pure.category - -import arrow.core.nonEmptyListOf -import com.ivy.wallet.domain.pure.data.ClosedTimeRange -import com.ivy.wallet.domain.pure.data.WalletDAOs -import java.math.BigDecimal -import java.util.* - -suspend fun calculateCategoryBalance( - walletDAOs: WalletDAOs, - baseCurrencyCode: String, - categoryId: UUID?, - range: ClosedTimeRange, -): BigDecimal { - return calculateCategoryValues( - walletDAOs = walletDAOs, - baseCurrencyCode = baseCurrencyCode, - categoryId = categoryId, - range = range, - valueFunctions = nonEmptyListOf( - CategoryValueFunctions::balance - ) - ).head -} - -data class CategoryStats( - val balance: BigDecimal, - val income: BigDecimal, - val expense: BigDecimal, - val incomeCount: BigDecimal, - val expenseCount: BigDecimal -) - -suspend fun calculateCategoryStats( - walletDAOs: WalletDAOs, - baseCurrencyCode: String, - categoryId: UUID?, - range: ClosedTimeRange, -): CategoryStats { - val values = calculateCategoryValues( - walletDAOs = walletDAOs, - baseCurrencyCode = baseCurrencyCode, - categoryId = categoryId, - range = range, - valueFunctions = nonEmptyListOf( - CategoryValueFunctions::income, - CategoryValueFunctions::expense, - CategoryValueFunctions::incomeCount, - CategoryValueFunctions::expenseCount - ) - ) - - val income = values[0] - val expense = values[1] - - return CategoryStats( - balance = income - expense, - income = income, - expense = expense, - incomeCount = values[2], - expenseCount = values[3] - ) -} - -suspend fun calculateCategoryIncome( - walletDAOs: WalletDAOs, - baseCurrencyCode: String, - categoryId: UUID?, - range: ClosedTimeRange, -): BigDecimal { - return calculateCategoryValues( - walletDAOs = walletDAOs, - baseCurrencyCode = baseCurrencyCode, - categoryId = categoryId, - range = range, - valueFunctions = nonEmptyListOf( - CategoryValueFunctions::income - ) - ).head -} - -suspend fun calculateCategoryIncomeWithAccountFilters( - walletDAOs: WalletDAOs, - baseCurrencyCode: String, - categoryId: UUID?, - accountIdFilterList: List, - range: ClosedTimeRange, -): BigDecimal { - return calculateCategoryValuesWithAccountFilters( - walletDAOs = walletDAOs, - baseCurrencyCode = baseCurrencyCode, - categoryId = categoryId, - range = range, - accountIdFilterSet = accountIdFilterList.toHashSet(), - valueFunctions = nonEmptyListOf( - CategoryValueFunctions::income - ) - ).head -} - -suspend fun calculateCategoryExpense( - walletDAOs: WalletDAOs, - baseCurrencyCode: String, - categoryId: UUID?, - range: ClosedTimeRange, -): BigDecimal { - return calculateCategoryValues( - walletDAOs = walletDAOs, - baseCurrencyCode = baseCurrencyCode, - categoryId = categoryId, - range = range, - valueFunctions = nonEmptyListOf( - CategoryValueFunctions::expense - ) - ).head -} - -suspend fun calculateCategoryExpenseWithAccountFilters( - walletDAOs: WalletDAOs, - baseCurrencyCode: String, - categoryId: UUID?, - accountIdList: List = emptyList(), - range: ClosedTimeRange, -): BigDecimal { - return calculateCategoryValuesWithAccountFilters( - walletDAOs = walletDAOs, - baseCurrencyCode = baseCurrencyCode, - categoryId = categoryId, - range = range, - accountIdFilterSet = accountIdList.toHashSet(), - valueFunctions = nonEmptyListOf( - CategoryValueFunctions::expense - ) - ).head -} \ No newline at end of file diff --git a/app/src/main/java/com/ivy/wallet/domain/pure/core/TransactionFunctions.kt b/app/src/main/java/com/ivy/wallet/domain/pure/core/TransactionFunctions.kt index ffa27aaa18..4ef158109e 100644 --- a/app/src/main/java/com/ivy/wallet/domain/pure/core/TransactionFunctions.kt +++ b/app/src/main/java/com/ivy/wallet/domain/pure/core/TransactionFunctions.kt @@ -2,12 +2,10 @@ package com.ivy.wallet.domain.pure.core import com.ivy.wallet.domain.data.TransactionType import com.ivy.wallet.domain.data.core.Transaction -import com.ivy.wallet.domain.pure.data.FPTransaction -import com.ivy.wallet.domain.pure.data.toFPTransaction import java.math.BigDecimal suspend fun sum( - transactions: List, + transactions: List, valueFunction: SuspendValueFunction, argument: A ): BigDecimal { @@ -16,18 +14,14 @@ suspend fun sum( } } -fun expenses(transactions: List): List { +fun expenses(transactions: List): List { return transactions.filter { it.type == TransactionType.EXPENSE } } -fun incomes(transactions: List): List { +fun incomes(transactions: List): List { return transactions.filter { it.type == TransactionType.INCOME } } -fun transfers(transactions: List): List { +fun transfers(transactions: List): List { return transactions.filter { it.type == TransactionType.TRANSFER } -} - -fun List.toFPTransactions(): List { - return this.map { it.toFPTransaction() } } \ No newline at end of file diff --git a/app/src/main/java/com/ivy/wallet/domain/pure/core/ValueFunction.kt b/app/src/main/java/com/ivy/wallet/domain/pure/core/ValueFunction.kt index a9d0267943..7c464e7d8b 100644 --- a/app/src/main/java/com/ivy/wallet/domain/pure/core/ValueFunction.kt +++ b/app/src/main/java/com/ivy/wallet/domain/pure/core/ValueFunction.kt @@ -1,15 +1,15 @@ package com.ivy.wallet.domain.pure.core import arrow.core.NonEmptyList -import com.ivy.wallet.domain.pure.data.FPTransaction +import com.ivy.wallet.domain.data.core.Transaction import java.math.BigDecimal -typealias ValueFunction = (FPTransaction, A) -> BigDecimal -typealias SuspendValueFunction = suspend (FPTransaction, A) -> BigDecimal +typealias ValueFunction = (Transaction, A) -> BigDecimal +typealias SuspendValueFunction = suspend (Transaction, A) -> BigDecimal internal tailrec fun calculateValueFunctionsSum( valueFunctionArgument: A, - transactions: List, + transactions: List, valueFunctions: NonEmptyList>, sum: NonEmptyList = nonEmptyListOfZeros(n = valueFunctions.size) ): NonEmptyList { @@ -29,7 +29,7 @@ internal tailrec fun calculateValueFunctionsSum( internal tailrec suspend fun calculateValueFunctionsSumSuspend( valueFunctionArgument: A, - transactions: List, + transactions: List, valueFunctions: NonEmptyList>, sum: NonEmptyList = nonEmptyListOfZeros(n = valueFunctions.size) ): NonEmptyList { diff --git a/app/src/main/java/com/ivy/wallet/domain/pure/data/FPTransaction.kt b/app/src/main/java/com/ivy/wallet/domain/pure/data/FPTransaction.kt deleted file mode 100644 index eafc522431..0000000000 --- a/app/src/main/java/com/ivy/wallet/domain/pure/data/FPTransaction.kt +++ /dev/null @@ -1,46 +0,0 @@ -package com.ivy.wallet.domain.pure.data - -import arrow.core.Option -import arrow.core.toOption -import com.ivy.wallet.domain.data.TransactionType -import com.ivy.wallet.domain.data.core.Transaction -import java.math.BigDecimal -import java.time.LocalDateTime -import java.util.* - -data class FPTransaction( - val id: UUID, - val type: TransactionType, - val accountId: UUID, - val categoryId: Option, - val amount: BigDecimal, - val toAccountId: Option, - val toAmount: BigDecimal, - val dateTime: Option, - - val loanId: Option, - val loanRecordId:Option, - - val title: Option, - val description: Option, - val dueDate: Option, - val recurringRuleId: Option -) - -fun Transaction.toFPTransaction(): FPTransaction = - FPTransaction( - id = id, - accountId = accountId, - type = type, - amount = amount.toBigDecimal(), - toAccountId = toAccountId.toOption(), - toAmount = toAmount?.toBigDecimal() ?: amount.toBigDecimal(), - title = title.toOption(), - description = description.toOption(), - dateTime = dateTime.toOption(), - categoryId = categoryId.toOption(), - dueDate = dueDate.toOption(), - recurringRuleId = recurringRuleId.toOption(), - loanId = loanId.toOption(), - loanRecordId = loanRecordId.toOption() - ) diff --git a/app/src/main/java/com/ivy/wallet/domain/pure/wallet/WalletTransactions.kt b/app/src/main/java/com/ivy/wallet/domain/pure/transaction/TrnDateDividers.kt similarity index 79% rename from app/src/main/java/com/ivy/wallet/domain/pure/wallet/WalletTransactions.kt rename to app/src/main/java/com/ivy/wallet/domain/pure/transaction/TrnDateDividers.kt index 67efc552f0..53956ae9d7 100644 --- a/app/src/main/java/com/ivy/wallet/domain/pure/wallet/WalletTransactions.kt +++ b/app/src/main/java/com/ivy/wallet/domain/pure/transaction/TrnDateDividers.kt @@ -1,4 +1,4 @@ -package com.ivy.wallet.domain.pure.wallet +package com.ivy.wallet.domain.pure.transaction import arrow.core.Option import arrow.core.toOption @@ -12,18 +12,14 @@ import com.ivy.wallet.domain.pure.ExchangeData import com.ivy.wallet.domain.pure.core.expenses import com.ivy.wallet.domain.pure.core.incomes import com.ivy.wallet.domain.pure.core.sum -import com.ivy.wallet.domain.pure.core.toFPTransactions -import com.ivy.wallet.domain.pure.data.FPTransaction import com.ivy.wallet.utils.convertUTCtoLocal import com.ivy.wallet.utils.toEpochSeconds import java.math.BigDecimal import java.util.* -//TODO: overdue(range) -//TODO: upcoming(range) - @Pure -suspend fun List.withDateDividers( +suspend fun transactionsWithDateDividers( + transactions: List, baseCurrencyCode: String, @SideEffect @@ -31,10 +27,9 @@ suspend fun List.withDateDividers( @SideEffect exchange: suspend (ExchangeData, BigDecimal) -> Option ): List { - val history = this - if (history.isEmpty()) return emptyList() + if (transactions.isEmpty()) return emptyList() - return history + return transactions .groupBy { it.dateTime?.convertUTCtoLocal()?.toLocalDate() } .filterKeys { it != null } .toSortedMap { date1, date2 -> @@ -48,19 +43,18 @@ suspend fun List.withDateDividers( exchange = exchange ) - val fpTransactions = transactionsForDate.toFPTransactions() listOf( TransactionHistoryDateDivider( date = date!!, income = sum( - incomes(fpTransactions), - ::amountInCurrency, + incomes(transactionsForDate), + ::exchangeInCurrency, arg ).toDouble(), expenses = sum( - expenses(fpTransactions), - ::amountInCurrency, + expenses(transactionsForDate), + ::exchangeInCurrency, arg ).toDouble() ), @@ -76,8 +70,8 @@ data class AmountInCurrencyArgument( val exchange: suspend (ExchangeData, BigDecimal) -> Option ) -suspend fun amountInCurrency( - fpTransaction: FPTransaction, +suspend fun exchangeInCurrency( + fpTransaction: Transaction, arg: AmountInCurrencyArgument ): BigDecimal { val fromCurrencyCode = arg.getAccount(fpTransaction.accountId)?.currency.toOption() diff --git a/app/src/main/java/com/ivy/wallet/domain/pure/wallet/WalletCore.kt b/app/src/main/java/com/ivy/wallet/domain/pure/wallet/WalletCore.kt deleted file mode 100644 index 6f3e90866f..0000000000 --- a/app/src/main/java/com/ivy/wallet/domain/pure/wallet/WalletCore.kt +++ /dev/null @@ -1,192 +0,0 @@ -package com.ivy.wallet.domain.pure.wallet - -import arrow.core.NonEmptyList -import arrow.core.Some -import arrow.core.toOption -import com.ivy.fp.SideEffect -import com.ivy.wallet.domain.data.core.Account -import com.ivy.wallet.domain.data.core.ExchangeRate -import com.ivy.wallet.domain.data.core.Transaction -import com.ivy.wallet.domain.pure.ExchangeData -import com.ivy.wallet.domain.pure.account.AccountValueFunction -import com.ivy.wallet.domain.pure.account.calcAccValues -import com.ivy.wallet.domain.pure.core.Uncertain -import com.ivy.wallet.domain.pure.core.mapIndexedNel -import com.ivy.wallet.domain.pure.core.nonEmptyListOfZeros -import com.ivy.wallet.domain.pure.data.* -import com.ivy.wallet.domain.pure.exchange -import com.ivy.wallet.utils.scopedIOThread -import kotlinx.coroutines.async -import kotlinx.coroutines.awaitAll -import java.math.BigDecimal -import java.util.* - -typealias UncertainWalletValues = Uncertain, NonEmptyList> -typealias AccountValuesPair = Pair> - -suspend fun calculateWalletValues( - walletDAOs: WalletDAOs, - baseCurrencyCode: String, - filterExcluded: Boolean = true, - range: ClosedTimeRange = ClosedTimeRange.allTimeIvy(), - valueFunctions: NonEmptyList, - - @SideEffect - getExchangeRate: suspend (baseCurrency: String, toCurrency: String) -> ExchangeRate?, -): UncertainWalletValues { - val uncertainWalletValues = walletDAOs.accountDao.findAll() - .filter { !filterExcluded || it.includeInBalance } - .map { account -> - Pair( - first = account.toFPAccount(baseCurrencyCode), - second = calcAccValues( - transactionDao = walletDAOs.transactionDao, - accountId = account.id, - range = range, - valueFunctions = valueFunctions - ) - ) - } - .convertValuesInBaseCurrency( - baseCurrency = baseCurrencyCode, - getExchangeRate = getExchangeRate - ) - - return sumUncertainWalletValues( - valueN = valueFunctions.size, - uncertainWalletValues = uncertainWalletValues - ) -} - -suspend fun sumAccountValuesInCurrency( - accountTrns: List>>, - baseCurrencyCode: String, - valueFunctions: NonEmptyList, - - @SideEffect - getExchangeRate: suspend (baseCurrency: String, toCurrency: String) -> ExchangeRate?, -): UncertainWalletValues { - val uncertainWalletValues = accountTrns.map { (account, trns) -> - Pair( - first = account.toFPAccount(baseCurrencyCode), - second = calcAccValues( - accountId = account.id, - accountsTrns = trns, - valueFunctions = valueFunctions - ) - ) - }.convertValuesInBaseCurrency( - baseCurrency = baseCurrencyCode, - getExchangeRate = getExchangeRate - ) - - return sumUncertainWalletValues( - valueN = valueFunctions.size, - uncertainWalletValues = uncertainWalletValues - ) -} - -suspend fun calculateWalletValuesWithAccountFilters( - walletDAOs: WalletDAOs, - baseCurrencyCode: String, - filterExcluded: Boolean = true, - accountIdFilterList: List, - range: ClosedTimeRange = ClosedTimeRange.allTimeIvy(), - valueFunctions: NonEmptyList, - - @SideEffect - getExchangeRate: suspend (baseCurrency: String, toCurrency: String) -> ExchangeRate?, -): UncertainWalletValues { - - val accounts = scopedIOThread { scope -> - if (accountIdFilterList.isNotEmpty()) - accountIdFilterList.map { accId -> - scope.async { - walletDAOs.accountDao.findById(accId) - } - }.awaitAll().filterNotNull() - else { - walletDAOs.accountDao.findAll() - } - } - - val uncertainWalletValues = accounts - .filter { !filterExcluded || it.includeInBalance } - .map { account -> - Pair( - first = account.toFPAccount(baseCurrencyCode), - second = calcAccValues( - transactionDao = walletDAOs.transactionDao, - accountId = account.id, - range = range, - valueFunctions = valueFunctions - ) - ) - } - .convertValuesInBaseCurrency( - baseCurrency = baseCurrencyCode, - getExchangeRate = getExchangeRate - ) - - return sumUncertainWalletValues( - valueN = valueFunctions.size, - uncertainWalletValues = uncertainWalletValues - ) -} - -private suspend fun Iterable.convertValuesInBaseCurrency( - baseCurrency: String, - - @SideEffect - getExchangeRate: suspend (baseCurrency: String, toCurrency: String) -> ExchangeRate?, -): List { - return this.map { (account, values) -> - val valuesInBaseCurrency = values.map { - exchange( - data = ExchangeData( - baseCurrency = baseCurrency, - fromCurrency = account.currencyCode.toOption(), - toCurrency = baseCurrency - ), - amount = it, - getExchangeRate = getExchangeRate - ) - } - val hasError = valuesInBaseCurrency.any { !it.isDefined() } - - Uncertain( - error = if (hasError) - listOf(CurrencyConvError(account = account)) else emptyList(), - value = if (!hasError) { - //if there is no error all values must be Some() - valuesInBaseCurrency.map { (it as Some).value } - } else nonEmptyListOfZeros(values.size) - ) - } -} - -private tailrec fun sumUncertainWalletValues( - valueN: Int, - uncertainWalletValues: List, - sum: UncertainWalletValues = Uncertain( - error = emptyList(), - value = nonEmptyListOfZeros(n = valueN) - ) -): UncertainWalletValues { - return if (uncertainWalletValues.isEmpty()) sum else { - val uncertainValues = uncertainWalletValues.first() - - sumUncertainWalletValues( - valueN = valueN, - uncertainWalletValues = uncertainWalletValues.drop(1), - sum = Uncertain( - error = sum.error.plus(uncertainValues.error), - value = if (uncertainValues.isCertain()) { - sum.value.mapIndexedNel { index, value -> - value.plus(uncertainValues.value[index]) - } - } else sum.value //no need to sum it, if it's uncertain (it'll be all ZEROs) - ) - ) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/ivy/wallet/domain/pure/wallet/WalletFunctions.kt b/app/src/main/java/com/ivy/wallet/domain/pure/wallet/WalletFunctions.kt deleted file mode 100644 index ffe736c8c2..0000000000 --- a/app/src/main/java/com/ivy/wallet/domain/pure/wallet/WalletFunctions.kt +++ /dev/null @@ -1,130 +0,0 @@ -package com.ivy.wallet.domain.pure.wallet - -import arrow.core.nonEmptyListOf -import com.ivy.wallet.domain.data.core.Settings -import com.ivy.wallet.domain.pure.account.AccountValueFunctions -import com.ivy.wallet.domain.pure.core.Uncertain -import com.ivy.wallet.domain.pure.data.ClosedTimeRange -import com.ivy.wallet.domain.pure.data.CurrencyConvError -import com.ivy.wallet.domain.pure.data.IncomeExpensePair -import com.ivy.wallet.domain.pure.data.WalletDAOs -import com.ivy.wallet.io.persistence.dao.SettingsDao -import java.math.BigDecimal -import java.util.* - -fun walletBufferDiff( - settings: Settings, - balance: BigDecimal -): BigDecimal { - return balance - settings.bufferAmount.toBigDecimal() -} - -@Deprecated("Side-effects must be handled by Actions") -suspend fun baseCurrencyCode( - settingsDao: SettingsDao -): String { - return settingsDao.findFirst().currency -} - -suspend fun calculateWalletIncomeWithAccountFilters( - walletDAOs: WalletDAOs, - baseCurrencyCode: String, - filterExcluded: Boolean = true, - accountIdFilterList: List, - range: ClosedTimeRange = ClosedTimeRange.allTimeIvy(), -): Uncertain, BigDecimal> { - val uncertainValues = calculateWalletValuesWithAccountFilters( - walletDAOs = walletDAOs, - baseCurrencyCode = baseCurrencyCode, - filterExcluded = filterExcluded, - accountIdFilterList = accountIdFilterList, - range = range, - valueFunctions = nonEmptyListOf( - AccountValueFunctions::income - ) - ) - - return Uncertain( - error = uncertainValues.error, - value = uncertainValues.value.head - ) -} - - -suspend fun calculateWalletExpenseWithAccountFilters( - walletDAOs: WalletDAOs, - baseCurrencyCode: String, - filterExcluded: Boolean = true, - accountIdFilterList: List, - range: ClosedTimeRange = ClosedTimeRange.allTimeIvy(), -): Uncertain, BigDecimal> { - val uncertainValues = calculateWalletValuesWithAccountFilters( - walletDAOs = walletDAOs, - baseCurrencyCode = baseCurrencyCode, - filterExcluded = filterExcluded, - accountIdFilterList = accountIdFilterList, - range = range, - valueFunctions = nonEmptyListOf( - AccountValueFunctions::expense - ) - ) - - return Uncertain( - error = uncertainValues.error, - value = uncertainValues.value.head - ) -} - -suspend fun calculateWalletIncomeExpense( - walletDAOs: WalletDAOs, - baseCurrencyCode: String, - filterExcluded: Boolean = true, - range: ClosedTimeRange, -): Uncertain, IncomeExpensePair> { - val uncertainValues = calculateWalletValues( - walletDAOs = walletDAOs, - baseCurrencyCode = baseCurrencyCode, - filterExcluded = filterExcluded, - range = range, - valueFunctions = nonEmptyListOf( - AccountValueFunctions::income, - AccountValueFunctions::expense - ) - ) - - return Uncertain( - error = uncertainValues.error, - value = IncomeExpensePair( - income = uncertainValues.value[0], - expense = uncertainValues.value[1] - ) - ) -} - -suspend fun calculateWalletIncomeExpenseCount( - walletDAOs: WalletDAOs, - baseCurrencyCode: String, - filterExcluded: Boolean = true, - range: ClosedTimeRange, -): Uncertain, Pair> { - val uncertainValues = calculateWalletValues( - walletDAOs = walletDAOs, - baseCurrencyCode = baseCurrencyCode, - filterExcluded = filterExcluded, - range = range, - valueFunctions = nonEmptyListOf( - AccountValueFunctions::incomeCount, - AccountValueFunctions::expenseCount - ) - ) - - return Uncertain( - error = uncertainValues.error, - value = Pair( - uncertainValues.value[0], uncertainValues.value[1] - ) - ) -} - -//TODO: upcomingIncomeExpense -//TODO: overdueIncomeExpense diff --git a/app/src/main/java/com/ivy/wallet/io/persistence/data/TransactionEntity.kt b/app/src/main/java/com/ivy/wallet/io/persistence/data/TransactionEntity.kt index 9a7eefee00..749a803622 100644 --- a/app/src/main/java/com/ivy/wallet/io/persistence/data/TransactionEntity.kt +++ b/app/src/main/java/com/ivy/wallet/io/persistence/data/TransactionEntity.kt @@ -39,9 +39,9 @@ data class TransactionEntity( fun toDomain(): Transaction = Transaction( accountId = accountId, type = type, - amount = amount, + amount = amount.toBigDecimal(), toAccountId = toAccountId, - toAmount = toAmount, + toAmount = toAmount?.toBigDecimal() ?: amount.toBigDecimal(), title = title, description = description, dateTime = dateTime, @@ -51,8 +51,6 @@ data class TransactionEntity( attachmentUrl = attachmentUrl, loanId = loanId, loanRecordId = loanRecordId, - isSynced = isSynced, - isDeleted = isDeleted, id = id ) diff --git a/app/src/main/java/com/ivy/wallet/ui/home/HomeViewModel.kt b/app/src/main/java/com/ivy/wallet/ui/home/HomeViewModel.kt index 9f70a0ce6b..1c82aab262 100644 --- a/app/src/main/java/com/ivy/wallet/ui/home/HomeViewModel.kt +++ b/app/src/main/java/com/ivy/wallet/ui/home/HomeViewModel.kt @@ -4,7 +4,7 @@ import androidx.lifecycle.viewModelScope import com.ivy.design.l0_system.Theme import com.ivy.design.navigation.Navigation import com.ivy.fp.viewmodel.IvyViewModel -import com.ivy.wallet.domain.action.transaction.AddDateDividersAct +import com.ivy.wallet.domain.action.transaction.TrnsWithDateDividersAct import com.ivy.wallet.domain.action.wallet.CalcOverdueAct import com.ivy.wallet.domain.action.wallet.CalcUpcomingAct import com.ivy.wallet.domain.action.wallet.CalcWalletBalanceAct @@ -49,7 +49,7 @@ class HomeViewModel @Inject constructor( private val calcAccountBalanceAct: CalcWalletBalanceAct, private val calcUpcomingAct: CalcUpcomingAct, private val calcOverdueAct: CalcOverdueAct, - private val historyWithDateDivAct: AddDateDividersAct, + private val historyWithDateDivAct: TrnsWithDateDividersAct, ) : IvyViewModel() { override val mutableState: MutableStateFlow = MutableStateFlow( HomeState.initial(ivyWalletCtx = ivyContext) @@ -154,7 +154,7 @@ class HomeViewModel @Inject constructor( updateState { it.copy( history = historyWithDateDivAct( - AddDateDividersAct.Input( + TrnsWithDateDividersAct.Input( timeRange = timeRange.toCloseTimeRange(), baseCurrencyCode = stateVal().baseCurrencyCode )