feat(#3): implement recurring interactors

This commit is contained in:
Achmad Setyabudi Susilo
2026-06-28 16:33:41 +07:00
parent 179f5fe2f8
commit 6a11284212
5 changed files with 100 additions and 0 deletions
@@ -5,6 +5,10 @@ import dev.achmad.ledgerr.domain.category.interactor.GetCategories
import dev.achmad.ledgerr.domain.category.interactor.SeedDefaultCategories import dev.achmad.ledgerr.domain.category.interactor.SeedDefaultCategories
import dev.achmad.ledgerr.domain.category.interactor.UpsertCategory import dev.achmad.ledgerr.domain.category.interactor.UpsertCategory
import dev.achmad.ledgerr.domain.expense.interactor.ReassignExpenseCategory import dev.achmad.ledgerr.domain.expense.interactor.ReassignExpenseCategory
import dev.achmad.ledgerr.domain.recurring.interactor.DeleteRecurringExpense
import dev.achmad.ledgerr.domain.recurring.interactor.GetRecurringExpenses
import dev.achmad.ledgerr.domain.recurring.interactor.ProcessDueRecurringExpenses
import dev.achmad.ledgerr.domain.recurring.interactor.UpsertRecurringExpense
import org.koin.dsl.module import org.koin.dsl.module
val domainModule = module { val domainModule = module {
@@ -14,4 +18,9 @@ val domainModule = module {
factory { SeedDefaultCategories(get()) } factory { SeedDefaultCategories(get()) }
factory { ReassignExpenseCategory(get()) } factory { ReassignExpenseCategory(get()) }
factory { GetRecurringExpenses(get()) }
factory { UpsertRecurringExpense(get()) }
factory { DeleteRecurringExpense(get()) }
factory { ProcessDueRecurringExpenses(get(), get()) }
} }
@@ -0,0 +1,11 @@
package dev.achmad.ledgerr.domain.recurring.interactor
import dev.achmad.ledgerr.data.local.dao.RecurringExpenseDao
class DeleteRecurringExpense(
private val dao: RecurringExpenseDao,
) {
suspend fun await(id: Long) {
dao.deleteById(id)
}
}
@@ -0,0 +1,25 @@
package dev.achmad.ledgerr.domain.recurring.interactor
import dev.achmad.ledgerr.data.local.dao.RecurringExpenseDao
import dev.achmad.ledgerr.data.local.mapper.toModel
import dev.achmad.ledgerr.domain.recurring.model.RecurringExpense
import dev.achmad.ledgerr.domain.recurring.model.RecurringExpenseWithCategory
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
class GetRecurringExpenses(
private val recurringDao: RecurringExpenseDao,
) {
fun subscribeAll(): Flow<List<RecurringExpenseWithCategory>> =
recurringDao.subscribeAll().map { rows ->
rows.map { row ->
RecurringExpenseWithCategory(
recurring = row.recurring.toModel(),
category = row.category.toModel(),
)
}
}
suspend fun awaitOne(id: Long): RecurringExpense? =
recurringDao.getById(id)?.toModel()
}
@@ -0,0 +1,36 @@
package dev.achmad.ledgerr.domain.recurring.interactor
import dev.achmad.ledgerr.data.local.dao.ExpenseDao
import dev.achmad.ledgerr.data.local.dao.RecurringExpenseDao
import dev.achmad.ledgerr.data.local.mapper.toEntity
import dev.achmad.ledgerr.data.local.mapper.toModel
import dev.achmad.ledgerr.domain.expense.model.Expense
import java.time.LocalDate
class ProcessDueRecurringExpenses(
private val recurringDao: RecurringExpenseDao,
private val expenseDao: ExpenseDao,
) {
suspend fun await(today: LocalDate = LocalDate.now()): List<Expense> {
val dueTemplates = recurringDao.getDue(today.toEpochDay())
if (dueTemplates.isEmpty()) return emptyList()
val created = mutableListOf<Expense>()
for (templateEntity in dueTemplates) {
val template = templateEntity.toModel()
val expense = Expense(
amount = template.amount,
categoryId = template.categoryId,
date = template.nextDueDate,
note = template.note,
recurringExpenseId = template.id,
)
val newId = expenseDao.insert(expense.toEntity())
created += expense.copy(id = newId)
val advanced = template.copy(
nextDueDate = template.interval.advance(template.nextDueDate),
)
recurringDao.update(advanced.toEntity())
}
return created
}
}
@@ -0,0 +1,19 @@
package dev.achmad.ledgerr.domain.recurring.interactor
import dev.achmad.ledgerr.data.local.dao.RecurringExpenseDao
import dev.achmad.ledgerr.data.local.mapper.toEntity
import dev.achmad.ledgerr.domain.recurring.model.RecurringExpense
class UpsertRecurringExpense(
private val dao: RecurringExpenseDao,
) {
suspend fun await(recurring: RecurringExpense): Long {
val entity = recurring.toEntity()
return if (recurring.id == 0L) {
dao.insert(entity)
} else {
dao.update(entity)
recurring.id
}
}
}