Implement recurring interactors #3

Closed
opened 2026-06-28 08:44:01 +00:00 by admin · 0 comments
Owner

Overview

Implements the recurring feature — 4 interactors covering list, upsert, delete, and the due-date processor. Depends on #1.

Prerequisites

Issue #1 must be merged first. This gives you:

  • AppDatabase + RecurringExpenseDao + ExpenseDao already wired
  • RecurringInterval, RecurringExpense, RecurringExpenseWithCategory domain models already in place

What to do

1. Define signatures + TODOs

Create these 4 files under domain/recurring/interactor/:

  • GetRecurringExpenses.ktsubscribeAll(), awaitOne(id)
  • UpsertRecurringExpense.ktawait(recurring): Long
  • DeleteRecurringExpense.ktawait(id)
  • ProcessDueRecurringExpenses.ktawait(today: LocalDate = LocalDate.now()): List<Expense>

2. Implement all 4 interactors (replace TODOs)

Refer to docs/03-function-todos.md for exact per-method behavior. Key points:

  • GetRecurringExpenses.subscribeAll(): use recurringExpenseDao.subscribeAll() (returns RecurringExpenseWithCategoryRow) and map each row via toModel() for both nested types.

  • UpsertRecurringExpense.await: if id == 0 insert, else update.

  • DeleteRecurringExpense.await: deletes template by id. Do NOT delete expense instances previously created from this template — they retain their recurringExpenseId value.

  • ProcessDueRecurringExpenses.await:

    1. Query recurringExpenseDao.getDue(today.toEpochDay()) — returns templates where isActive = true AND nextDueDate <= today.
    2. For each, build an Expense (no id, recurringExpenseId = template.id, date = template.nextDueDate, note = template.note, categoryId = template.categoryId, amount = template.amount).
    3. Insert via expenseDao.upsert(expense) (or insertAll in a batch — your choice).
    4. Advance: template.nextDueDate = template.interval.advance(template.nextDueDate). Upsert the updated template.
    5. Collect created expenses, return the list.

    Edge case (per doc 03): if the app has not been opened for multiple intervals, advance nextDueDate by one interval per call. The next app open processes again if still overdue. This avoids flooding the expense list with back-filled entries.

3. Add to DomainModule

factory { GetRecurringExpenses(get(), get()) }
factory { UpsertRecurringExpense(get()) }
factory { DeleteRecurringExpense(get()) }
factory { ProcessDueRecurringExpenses(get(), get()) }

4. Verify compilation

Run ./gradlew assembleDebug. Must succeed.

Acceptance

  • All 4 recurring interactors implemented
  • DomainModule has the 4 new factories
  • ./gradlew assembleDebug succeeds

Implementation rule

Per AGENTS.md — do not start implementation without explicit user sign-off on this issue. When working, check for related issues in the remote repo first.

## Overview Implements the `recurring` feature — 4 interactors covering list, upsert, delete, and the due-date processor. Depends on #1. ## Prerequisites Issue #1 must be merged first. This gives you: - `AppDatabase` + `RecurringExpenseDao` + `ExpenseDao` already wired - `RecurringInterval`, `RecurringExpense`, `RecurringExpenseWithCategory` domain models already in place ## What to do ### 1. Define signatures + TODOs Create these 4 files under `domain/recurring/interactor/`: - `GetRecurringExpenses.kt` — `subscribeAll()`, `awaitOne(id)` - `UpsertRecurringExpense.kt` — `await(recurring): Long` - `DeleteRecurringExpense.kt` — `await(id)` - `ProcessDueRecurringExpenses.kt` — `await(today: LocalDate = LocalDate.now()): List<Expense>` ### 2. Implement all 4 interactors (replace TODOs) Refer to `docs/03-function-todos.md` for exact per-method behavior. Key points: - `GetRecurringExpenses.subscribeAll()`: use `recurringExpenseDao.subscribeAll()` (returns `RecurringExpenseWithCategoryRow`) and map each row via `toModel()` for both nested types. - `UpsertRecurringExpense.await`: if `id == 0` insert, else update. - `DeleteRecurringExpense.await`: deletes template by id. **Do NOT** delete expense instances previously created from this template — they retain their `recurringExpenseId` value. - `ProcessDueRecurringExpenses.await`: 1. Query `recurringExpenseDao.getDue(today.toEpochDay())` — returns templates where `isActive = true AND nextDueDate <= today`. 2. For each, build an `Expense` (no id, `recurringExpenseId = template.id`, `date = template.nextDueDate`, `note = template.note`, `categoryId = template.categoryId`, `amount = template.amount`). 3. Insert via `expenseDao.upsert(expense)` (or `insertAll` in a batch — your choice). 4. Advance: `template.nextDueDate = template.interval.advance(template.nextDueDate)`. Upsert the updated template. 5. Collect created expenses, return the list. Edge case (per doc 03): if the app has not been opened for multiple intervals, advance `nextDueDate` by **one** interval per call. The next app open processes again if still overdue. This avoids flooding the expense list with back-filled entries. ### 3. Add to `DomainModule` ```kotlin factory { GetRecurringExpenses(get(), get()) } factory { UpsertRecurringExpense(get()) } factory { DeleteRecurringExpense(get()) } factory { ProcessDueRecurringExpenses(get(), get()) } ``` ### 4. Verify compilation Run `./gradlew assembleDebug`. Must succeed. ## Acceptance - [ ] All 4 recurring interactors implemented - [ ] `DomainModule` has the 4 new factories - [ ] `./gradlew assembleDebug` succeeds ## Implementation rule Per `AGENTS.md` — do not start implementation without explicit user sign-off on this issue. When working, check for related issues in the remote repo first.
admin closed this issue 2026-06-28 09:56:39 +00:00
Sign in to join this conversation.
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: admin/ledgerr#3