fix(#3): make ProcessDueRecurringExpenses atomic via withTransaction
Address review: insert + advance pair must run in one DB transaction to prevent duplicate expenses if the process is killed mid-loop.
This commit is contained in:
@@ -22,5 +22,5 @@ val domainModule = module {
|
|||||||
factory { GetRecurringExpenses(get()) }
|
factory { GetRecurringExpenses(get()) }
|
||||||
factory { UpsertRecurringExpense(get()) }
|
factory { UpsertRecurringExpense(get()) }
|
||||||
factory { DeleteRecurringExpense(get()) }
|
factory { DeleteRecurringExpense(get()) }
|
||||||
factory { ProcessDueRecurringExpenses(get(), get()) }
|
factory { ProcessDueRecurringExpenses(get()) }
|
||||||
}
|
}
|
||||||
|
|||||||
+27
-25
@@ -1,36 +1,38 @@
|
|||||||
package dev.achmad.ledgerr.domain.recurring.interactor
|
package dev.achmad.ledgerr.domain.recurring.interactor
|
||||||
|
|
||||||
import dev.achmad.ledgerr.data.local.dao.ExpenseDao
|
import androidx.room.withTransaction
|
||||||
import dev.achmad.ledgerr.data.local.dao.RecurringExpenseDao
|
import dev.achmad.ledgerr.data.local.AppDatabase
|
||||||
import dev.achmad.ledgerr.data.local.mapper.toEntity
|
import dev.achmad.ledgerr.data.local.mapper.toEntity
|
||||||
import dev.achmad.ledgerr.data.local.mapper.toModel
|
import dev.achmad.ledgerr.data.local.mapper.toModel
|
||||||
import dev.achmad.ledgerr.domain.expense.model.Expense
|
import dev.achmad.ledgerr.domain.expense.model.Expense
|
||||||
import java.time.LocalDate
|
import java.time.LocalDate
|
||||||
|
|
||||||
class ProcessDueRecurringExpenses(
|
class ProcessDueRecurringExpenses(
|
||||||
private val recurringDao: RecurringExpenseDao,
|
private val database: AppDatabase,
|
||||||
private val expenseDao: ExpenseDao,
|
|
||||||
) {
|
) {
|
||||||
suspend fun await(today: LocalDate = LocalDate.now()): List<Expense> {
|
suspend fun await(today: LocalDate = LocalDate.now()): List<Expense> =
|
||||||
val dueTemplates = recurringDao.getDue(today.toEpochDay())
|
database.withTransaction {
|
||||||
if (dueTemplates.isEmpty()) return emptyList()
|
val recurringDao = database.recurringExpenseDao()
|
||||||
val created = mutableListOf<Expense>()
|
val expenseDao = database.expenseDao()
|
||||||
for (templateEntity in dueTemplates) {
|
val dueTemplates = recurringDao.getDue(today.toEpochDay())
|
||||||
val template = templateEntity.toModel()
|
if (dueTemplates.isEmpty()) return@withTransaction emptyList()
|
||||||
val expense = Expense(
|
val created = mutableListOf<Expense>()
|
||||||
amount = template.amount,
|
for (templateEntity in dueTemplates) {
|
||||||
categoryId = template.categoryId,
|
val template = templateEntity.toModel()
|
||||||
date = template.nextDueDate,
|
val expense = Expense(
|
||||||
note = template.note,
|
amount = template.amount,
|
||||||
recurringExpenseId = template.id,
|
categoryId = template.categoryId,
|
||||||
)
|
date = template.nextDueDate,
|
||||||
val newId = expenseDao.insert(expense.toEntity())
|
note = template.note,
|
||||||
created += expense.copy(id = newId)
|
recurringExpenseId = template.id,
|
||||||
val advanced = template.copy(
|
)
|
||||||
nextDueDate = template.interval.advance(template.nextDueDate),
|
val newId = expenseDao.insert(expense.toEntity())
|
||||||
)
|
created += expense.copy(id = newId)
|
||||||
recurringDao.update(advanced.toEntity())
|
val advanced = template.copy(
|
||||||
|
nextDueDate = template.interval.advance(template.nextDueDate),
|
||||||
|
)
|
||||||
|
recurringDao.update(advanced.toEntity())
|
||||||
|
}
|
||||||
|
created
|
||||||
}
|
}
|
||||||
return created
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user