feat(#2): implement expense interactors
This commit is contained in:
@@ -1,7 +1,9 @@
|
||||
package dev.achmad.ledgerr.data.local.mapper
|
||||
|
||||
import dev.achmad.ledgerr.data.local.dao.ExpenseWithCategoryRow
|
||||
import dev.achmad.ledgerr.data.local.entity.ExpenseEntity
|
||||
import dev.achmad.ledgerr.domain.expense.model.Expense
|
||||
import dev.achmad.ledgerr.domain.expense.model.ExpenseWithCategory
|
||||
|
||||
fun ExpenseEntity.toModel(): Expense = Expense(
|
||||
id = id,
|
||||
@@ -22,3 +24,8 @@ fun Expense.toEntity(): ExpenseEntity = ExpenseEntity(
|
||||
recurringExpenseId = recurringExpenseId,
|
||||
createdAt = createdAt,
|
||||
)
|
||||
|
||||
fun ExpenseWithCategoryRow.toModel(): ExpenseWithCategory = ExpenseWithCategory(
|
||||
expense = expense.toModel(),
|
||||
category = category.toModel(),
|
||||
)
|
||||
|
||||
@@ -4,7 +4,12 @@ import dev.achmad.ledgerr.domain.category.interactor.DeleteCategory
|
||||
import dev.achmad.ledgerr.domain.category.interactor.GetCategories
|
||||
import dev.achmad.ledgerr.domain.category.interactor.SeedDefaultCategories
|
||||
import dev.achmad.ledgerr.domain.category.interactor.UpsertCategory
|
||||
import dev.achmad.ledgerr.domain.expense.interactor.DeleteExpense
|
||||
import dev.achmad.ledgerr.domain.expense.interactor.GetExpenseSummary
|
||||
import dev.achmad.ledgerr.domain.expense.interactor.GetExpenses
|
||||
import dev.achmad.ledgerr.domain.expense.interactor.InsertExpenses
|
||||
import dev.achmad.ledgerr.domain.expense.interactor.ReassignExpenseCategory
|
||||
import dev.achmad.ledgerr.domain.expense.interactor.UpsertExpense
|
||||
import org.koin.dsl.module
|
||||
|
||||
val domainModule = module {
|
||||
@@ -13,5 +18,10 @@ val domainModule = module {
|
||||
factory { DeleteCategory(get(), get(), get()) }
|
||||
factory { SeedDefaultCategories(get()) }
|
||||
|
||||
factory { GetExpenses(get(), get()) }
|
||||
factory { UpsertExpense(get()) }
|
||||
factory { InsertExpenses(get()) }
|
||||
factory { DeleteExpense(get()) }
|
||||
factory { ReassignExpenseCategory(get()) }
|
||||
factory { GetExpenseSummary(get(), get()) }
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
package dev.achmad.ledgerr.domain.expense.interactor
|
||||
|
||||
import dev.achmad.ledgerr.data.local.dao.ExpenseDao
|
||||
|
||||
class DeleteExpense(
|
||||
private val dao: ExpenseDao,
|
||||
) {
|
||||
suspend fun await(id: Long) {
|
||||
dao.deleteById(id)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package dev.achmad.ledgerr.domain.expense.interactor
|
||||
|
||||
import dev.achmad.ledgerr.data.local.dao.CategoryDao
|
||||
import dev.achmad.ledgerr.data.local.dao.ExpenseDao
|
||||
import dev.achmad.ledgerr.data.local.mapper.toModel
|
||||
import dev.achmad.ledgerr.domain.expense.model.DateRange
|
||||
import dev.achmad.ledgerr.domain.expense.model.ExpenseSummary
|
||||
|
||||
class GetExpenseSummary(
|
||||
private val dao: ExpenseDao,
|
||||
private val categoryDao: CategoryDao,
|
||||
) {
|
||||
suspend fun await(range: DateRange): ExpenseSummary {
|
||||
val expenses = dao.getByDateRange(
|
||||
startDay = range.start.toEpochDay(),
|
||||
endDay = range.end.toEpochDay(),
|
||||
)
|
||||
val totalAmount = expenses.sumOf { it.amount }
|
||||
val categoryMap = categoryDao.getAll().associate { it.id to it.toModel() }
|
||||
val byCategory = expenses
|
||||
.groupBy { it.categoryId }
|
||||
.mapNotNull { (categoryId, group) ->
|
||||
val category = categoryMap[categoryId] ?: return@mapNotNull null
|
||||
category to group.sumOf { it.amount }
|
||||
}
|
||||
.sortedByDescending { it.second }
|
||||
return ExpenseSummary(
|
||||
totalAmount = totalAmount,
|
||||
byCategory = byCategory,
|
||||
period = range,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package dev.achmad.ledgerr.domain.expense.interactor
|
||||
|
||||
import dev.achmad.ledgerr.data.local.dao.CategoryDao
|
||||
import dev.achmad.ledgerr.data.local.dao.ExpenseDao
|
||||
import dev.achmad.ledgerr.data.local.mapper.toModel
|
||||
import dev.achmad.ledgerr.domain.expense.model.DateRange
|
||||
import dev.achmad.ledgerr.domain.expense.model.ExpenseWithCategory
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
class GetExpenses(
|
||||
private val dao: ExpenseDao,
|
||||
private val categoryDao: CategoryDao,
|
||||
) {
|
||||
fun subscribeAll(): Flow<List<ExpenseWithCategory>> =
|
||||
dao.subscribeAll().map { rows -> rows.map { it.toModel() } }
|
||||
|
||||
fun subscribeByDateRange(range: DateRange): Flow<List<ExpenseWithCategory>> =
|
||||
dao.subscribeByDateRange(
|
||||
startDay = range.start.toEpochDay(),
|
||||
endDay = range.end.toEpochDay(),
|
||||
).map { rows -> rows.map { it.toModel() } }
|
||||
|
||||
suspend fun awaitOne(id: Long): ExpenseWithCategory? {
|
||||
val expense = dao.getById(id) ?: return null
|
||||
val category = categoryDao.getById(expense.categoryId) ?: return null
|
||||
return ExpenseWithCategory(
|
||||
expense = expense.toModel(),
|
||||
category = category.toModel(),
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun awaitAll(
|
||||
query: String = "",
|
||||
range: DateRange? = null,
|
||||
): List<ExpenseWithCategory> {
|
||||
val categoryMap = categoryDao.getAll().associate { it.id to it.toModel() }
|
||||
val expenses = dao.search(
|
||||
rangeStart = range?.start?.toEpochDay(),
|
||||
rangeEnd = range?.end?.toEpochDay(),
|
||||
)
|
||||
return expenses
|
||||
.filter { entity ->
|
||||
if (query.isBlank()) return@filter true
|
||||
val note = entity.note.orEmpty()
|
||||
val amountStr = entity.amount.toString()
|
||||
val categoryName = categoryMap[entity.categoryId]?.name.orEmpty()
|
||||
note.contains(query, ignoreCase = true) ||
|
||||
amountStr.contains(query, ignoreCase = true) ||
|
||||
categoryName.contains(query, ignoreCase = true)
|
||||
}
|
||||
.mapNotNull { entity ->
|
||||
val category = categoryMap[entity.categoryId] ?: return@mapNotNull null
|
||||
ExpenseWithCategory(
|
||||
expense = entity.toModel(),
|
||||
category = category,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package dev.achmad.ledgerr.domain.expense.interactor
|
||||
|
||||
import dev.achmad.ledgerr.data.local.dao.ExpenseDao
|
||||
import dev.achmad.ledgerr.data.local.mapper.toEntity
|
||||
import dev.achmad.ledgerr.domain.expense.model.Expense
|
||||
|
||||
class InsertExpenses(
|
||||
private val dao: ExpenseDao,
|
||||
) {
|
||||
suspend fun awaitAll(expenses: List<Expense>) {
|
||||
if (expenses.isEmpty()) return
|
||||
dao.insertAll(expenses.map { it.toEntity() })
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package dev.achmad.ledgerr.domain.expense.interactor
|
||||
|
||||
import dev.achmad.ledgerr.data.local.dao.ExpenseDao
|
||||
import dev.achmad.ledgerr.data.local.mapper.toEntity
|
||||
import dev.achmad.ledgerr.domain.expense.model.Expense
|
||||
|
||||
class UpsertExpense(
|
||||
private val dao: ExpenseDao,
|
||||
) {
|
||||
suspend fun await(expense: Expense): Long {
|
||||
if (expense.id == 0L) {
|
||||
return dao.insert(expense.toEntity())
|
||||
}
|
||||
dao.update(expense.toEntity())
|
||||
return expense.id
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user