fix(#2): address PR review — use row mapper, require id==0, drop ignoreCase on amount, doc drift, orphan-category note

This commit is contained in:
Achmad Setyabudi Susilo
2026-06-28 16:52:42 +07:00
parent 46f882b3c3
commit 63bfe2a6b5
4 changed files with 13 additions and 6 deletions
@@ -17,6 +17,9 @@ class GetExpenseSummary(
) )
val totalAmount = expenses.sumOf { it.amount } val totalAmount = expenses.sumOf { it.amount }
val categoryMap = categoryDao.getAll().associate { it.id to it.toModel() } val categoryMap = categoryDao.getAll().associate { it.id to it.toModel() }
// ForeignKey.RESTRICT keeps categories intact, but be defensive: if a
// category was deleted out-of-band, drop its group from byCategory
// rather than crashing. totalAmount still includes the orphan rows.
val byCategory = expenses val byCategory = expenses
.groupBy { it.categoryId } .groupBy { it.categoryId }
.mapNotNull { (categoryId, group) -> .mapNotNull { (categoryId, group) ->
@@ -2,6 +2,7 @@ package dev.achmad.ledgerr.domain.expense.interactor
import dev.achmad.ledgerr.data.local.dao.CategoryDao import dev.achmad.ledgerr.data.local.dao.CategoryDao
import dev.achmad.ledgerr.data.local.dao.ExpenseDao import dev.achmad.ledgerr.data.local.dao.ExpenseDao
import dev.achmad.ledgerr.data.local.dao.ExpenseWithCategoryRow
import dev.achmad.ledgerr.data.local.mapper.toModel import dev.achmad.ledgerr.data.local.mapper.toModel
import dev.achmad.ledgerr.domain.expense.model.DateRange import dev.achmad.ledgerr.domain.expense.model.DateRange
import dev.achmad.ledgerr.domain.expense.model.ExpenseWithCategory import dev.achmad.ledgerr.domain.expense.model.ExpenseWithCategory
@@ -24,10 +25,10 @@ class GetExpenses(
suspend fun awaitOne(id: Long): ExpenseWithCategory? { suspend fun awaitOne(id: Long): ExpenseWithCategory? {
val expense = dao.getById(id) ?: return null val expense = dao.getById(id) ?: return null
val category = categoryDao.getById(expense.categoryId) ?: return null val category = categoryDao.getById(expense.categoryId) ?: return null
return ExpenseWithCategory( return ExpenseWithCategoryRow(
expense = expense.toModel(), expense = expense,
category = category.toModel(), category = category,
) ).toModel()
} }
suspend fun awaitAll( suspend fun awaitAll(
@@ -46,7 +47,7 @@ class GetExpenses(
val amountStr = entity.amount.toString() val amountStr = entity.amount.toString()
val categoryName = categoryMap[entity.categoryId]?.name.orEmpty() val categoryName = categoryMap[entity.categoryId]?.name.orEmpty()
note.contains(query, ignoreCase = true) || note.contains(query, ignoreCase = true) ||
amountStr.contains(query, ignoreCase = true) || amountStr.contains(query) ||
categoryName.contains(query, ignoreCase = true) categoryName.contains(query, ignoreCase = true)
} }
.mapNotNull { entity -> .mapNotNull { entity ->
@@ -9,6 +9,9 @@ class InsertExpenses(
) { ) {
suspend fun awaitAll(expenses: List<Expense>) { suspend fun awaitAll(expenses: List<Expense>) {
if (expenses.isEmpty()) return if (expenses.isEmpty()) return
require(expenses.all { it.id == 0L }) {
"InsertExpenses.awaitAll requires all Expense.id == 0L (got ${expenses.filter { it.id != 0L }.map { it.id }})"
}
dao.insertAll(expenses.map { it.toEntity() }) dao.insertAll(expenses.map { it.toEntity() })
} }
} }
+1 -1
View File
@@ -23,7 +23,7 @@ One-shot search. If `query` is blank and `range` is null, returns all expenses (
## expense / UpsertExpense ## expense / UpsertExpense
### `await(expense: Expense): Long` ### `await(expense: Expense): Long`
If `expense.id == 0L`: insert and return generated id. Otherwise: update by id and return the existing id. Uses Room `@Upsert`. If `expense.id == 0L`: insert and return generated id. Otherwise: update by id and return the existing id. Routes through separate `dao.insert` / `dao.update` (the DAO does not expose a `@Upsert` method).
--- ---