diff --git a/app/src/main/java/dev/achmad/ledgerr/data/local/mapper/ExpenseMapper.kt b/app/src/main/java/dev/achmad/ledgerr/data/local/mapper/ExpenseMapper.kt index 59cae18..cb98f0e 100644 --- a/app/src/main/java/dev/achmad/ledgerr/data/local/mapper/ExpenseMapper.kt +++ b/app/src/main/java/dev/achmad/ledgerr/data/local/mapper/ExpenseMapper.kt @@ -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(), +) diff --git a/app/src/main/java/dev/achmad/ledgerr/di/DomainModule.kt b/app/src/main/java/dev/achmad/ledgerr/di/DomainModule.kt index 56481b8..756a20a 100644 --- a/app/src/main/java/dev/achmad/ledgerr/di/DomainModule.kt +++ b/app/src/main/java/dev/achmad/ledgerr/di/DomainModule.kt @@ -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()) } } diff --git a/app/src/main/java/dev/achmad/ledgerr/domain/expense/interactor/DeleteExpense.kt b/app/src/main/java/dev/achmad/ledgerr/domain/expense/interactor/DeleteExpense.kt new file mode 100644 index 0000000..7e6a648 --- /dev/null +++ b/app/src/main/java/dev/achmad/ledgerr/domain/expense/interactor/DeleteExpense.kt @@ -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) + } +} diff --git a/app/src/main/java/dev/achmad/ledgerr/domain/expense/interactor/GetExpenseSummary.kt b/app/src/main/java/dev/achmad/ledgerr/domain/expense/interactor/GetExpenseSummary.kt new file mode 100644 index 0000000..fb22cd7 --- /dev/null +++ b/app/src/main/java/dev/achmad/ledgerr/domain/expense/interactor/GetExpenseSummary.kt @@ -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, + ) + } +} diff --git a/app/src/main/java/dev/achmad/ledgerr/domain/expense/interactor/GetExpenses.kt b/app/src/main/java/dev/achmad/ledgerr/domain/expense/interactor/GetExpenses.kt new file mode 100644 index 0000000..0781b5f --- /dev/null +++ b/app/src/main/java/dev/achmad/ledgerr/domain/expense/interactor/GetExpenses.kt @@ -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> = + dao.subscribeAll().map { rows -> rows.map { it.toModel() } } + + fun subscribeByDateRange(range: DateRange): Flow> = + 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 { + 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, + ) + } + } +} diff --git a/app/src/main/java/dev/achmad/ledgerr/domain/expense/interactor/InsertExpenses.kt b/app/src/main/java/dev/achmad/ledgerr/domain/expense/interactor/InsertExpenses.kt new file mode 100644 index 0000000..3c82eaa --- /dev/null +++ b/app/src/main/java/dev/achmad/ledgerr/domain/expense/interactor/InsertExpenses.kt @@ -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) { + if (expenses.isEmpty()) return + dao.insertAll(expenses.map { it.toEntity() }) + } +} diff --git a/app/src/main/java/dev/achmad/ledgerr/domain/expense/interactor/UpsertExpense.kt b/app/src/main/java/dev/achmad/ledgerr/domain/expense/interactor/UpsertExpense.kt new file mode 100644 index 0000000..dfa22a7 --- /dev/null +++ b/app/src/main/java/dev/achmad/ledgerr/domain/expense/interactor/UpsertExpense.kt @@ -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 + } +}