Implement bankstatement, export, and data interactors #4

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

Overview

Implements three independent features that all need a Context for IO:

  1. bankstatementBankStatementImporter interface + 3 stubs (BRI / Jago / BNI)
  2. exportExportExpensesToCsv interactor
  3. dataClearAllData interactor (wipe-all-data action used by SettingsScreen)

Also adds PDFBoxResourceLoader.init(this) to MainApplication.

Depends on #1.

Prerequisites

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

  • AppDatabase already wired
  • Expense, DateRange, ExpenseWithCategory models
  • ExpenseDao, CategoryDao registered

What to do

1. bankstatement — interface + 3 stubs

Create domain/bankstatement/interactor/BankStatementImporter.kt:

interface BankStatementImporter {
    val bankName: String
    suspend fun await(pdfUri: Uri): Result<List<PendingImportExpense>>
}

Create 3 stub implementations, all returning Result.failure(NotImplementedError("… import not yet implemented")):

  • ImportBRIBankStatement.ktbankName = "BRI", takes Context
  • ImportJagoBankStatement.ktbankName = "Jago", takes Context
  • ImportBNIBankStatement.ktbankName = "BNI", takes Context

2. export — CSV exporter

Create domain/export/interactor/ExportExpensesToCsv.kt:

  • Constructor: (expenseDao: ExpenseDao, categoryDao: CategoryDao, context: Context)
  • await(range: DateRange, outputUri: Uri): Result<Unit>
  • Implementation (per doc 03):
    1. Fetch expenses in range: expenseDao.getByDateRange(range.start.toEpochDay(), range.end.toEpochDay()).
    2. Fetch all categories: categoryDao.getAll(). Build a Map<Long, Category>.
    3. Open context.contentResolver.openOutputStream(outputUri). Wrap with Okio BufferedSink (use sink(outputStream).buffer()).
    4. Write UTF-8 BOM: 0xEF, 0xBB, 0xBF (use sink.writeUtf8("\uFEFF") or write the three bytes directly).
    5. Write header: Date,Category,Amount,Note\n.
    6. For each expense: expense.date.toString(),category.name,expense.amount,"${expense.note.orEmpty()}"\n. ISO 8601 dates come for free from LocalDate.toString() (yyyy-MM-dd). Double-quote notes to handle embedded commas/newlines.
    7. Close sink. Return Result.success(Unit) or Result.failure(e) on any exception.

3. data — wipe-all-data

Create domain/data/interactor/ClearAllData.kt:

  • Constructor: (database: AppDatabase)
  • await():
    • Use database.withTransaction { database.clearAllTables() } (import androidx.room.withTransaction).
    • No need to re-seed categories here — the SettingsScreen ScreenModel will call inject<SeedDefaultCategories>().await() afterwards.

4. Update MainApplication

Add the PDFBox init call to the existing MainApplication.onCreate():

import com.tom_roush.pdfbox.android.PDFBoxResourceLoader

// in onCreate, before startKoin:
PDFBoxResourceLoader.init(this)

5. Wire DomainModule

// bankstatement
factory<BankStatementImporter>(named("bri"))  { ImportBRIBankStatement(androidContext()) }
factory<BankStatementImporter>(named("jago")) { ImportJagoBankStatement(androidContext()) }
factory<BankStatementImporter>(named("bni"))  { ImportBNIBankStatement(androidContext()) }
factory<List<BankStatementImporter>> {
    listOf(get(named("bri")), get(named("jago")), get(named("bni")))
}

// export
factory { ExportExpensesToCsv(get(), get(), androidContext()) }

// data
factory { ClearAllData(get()) }

6. Verify compilation

Run ./gradlew assembleDebug. Must succeed.

Acceptance

  • BankStatementImporter interface defined
  • 3 bank importer stubs return NotImplementedError failure
  • ExportExpensesToCsv writes UTF-8 BOM CSV
  • ClearAllData wipes all tables in a transaction
  • MainApplication calls PDFBoxResourceLoader.init(this)
  • DomainModule has the new factories (incl. the List<BankStatementImporter> aggregate)
  • ./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 three independent features that all need a `Context` for IO: 1. **`bankstatement`** — `BankStatementImporter` interface + 3 stubs (BRI / Jago / BNI) 2. **`export`** — `ExportExpensesToCsv` interactor 3. **`data`** — `ClearAllData` interactor (wipe-all-data action used by `SettingsScreen`) Also adds `PDFBoxResourceLoader.init(this)` to `MainApplication`. Depends on #1. ## Prerequisites Issue #1 must be merged first. This gives you: - `AppDatabase` already wired - `Expense`, `DateRange`, `ExpenseWithCategory` models - `ExpenseDao`, `CategoryDao` registered ## What to do ### 1. `bankstatement` — interface + 3 stubs Create `domain/bankstatement/interactor/BankStatementImporter.kt`: ```kotlin interface BankStatementImporter { val bankName: String suspend fun await(pdfUri: Uri): Result<List<PendingImportExpense>> } ``` Create 3 stub implementations, all returning `Result.failure(NotImplementedError("… import not yet implemented"))`: - `ImportBRIBankStatement.kt` — `bankName = "BRI"`, takes `Context` - `ImportJagoBankStatement.kt` — `bankName = "Jago"`, takes `Context` - `ImportBNIBankStatement.kt` — `bankName = "BNI"`, takes `Context` ### 2. `export` — CSV exporter Create `domain/export/interactor/ExportExpensesToCsv.kt`: - Constructor: `(expenseDao: ExpenseDao, categoryDao: CategoryDao, context: Context)` - `await(range: DateRange, outputUri: Uri): Result<Unit>` - Implementation (per doc 03): 1. Fetch expenses in range: `expenseDao.getByDateRange(range.start.toEpochDay(), range.end.toEpochDay())`. 2. Fetch all categories: `categoryDao.getAll()`. Build a `Map<Long, Category>`. 3. Open `context.contentResolver.openOutputStream(outputUri)`. Wrap with Okio `BufferedSink` (use `sink(outputStream).buffer()`). 4. Write UTF-8 BOM: `0xEF, 0xBB, 0xBF` (use `sink.writeUtf8("\uFEFF")` or write the three bytes directly). 5. Write header: `Date,Category,Amount,Note\n`. 6. For each expense: `expense.date.toString(),category.name,expense.amount,"${expense.note.orEmpty()}"\n`. ISO 8601 dates come for free from `LocalDate.toString()` (`yyyy-MM-dd`). Double-quote notes to handle embedded commas/newlines. 7. Close sink. Return `Result.success(Unit)` or `Result.failure(e)` on any exception. ### 3. `data` — wipe-all-data Create `domain/data/interactor/ClearAllData.kt`: - Constructor: `(database: AppDatabase)` - `await()`: - Use `database.withTransaction { database.clearAllTables() }` (import `androidx.room.withTransaction`). - No need to re-seed categories here — the `SettingsScreen` ScreenModel will call `inject<SeedDefaultCategories>().await()` afterwards. ### 4. Update `MainApplication` Add the PDFBox init call to the existing `MainApplication.onCreate()`: ```kotlin import com.tom_roush.pdfbox.android.PDFBoxResourceLoader // in onCreate, before startKoin: PDFBoxResourceLoader.init(this) ``` ### 5. Wire `DomainModule` ```kotlin // bankstatement factory<BankStatementImporter>(named("bri")) { ImportBRIBankStatement(androidContext()) } factory<BankStatementImporter>(named("jago")) { ImportJagoBankStatement(androidContext()) } factory<BankStatementImporter>(named("bni")) { ImportBNIBankStatement(androidContext()) } factory<List<BankStatementImporter>> { listOf(get(named("bri")), get(named("jago")), get(named("bni"))) } // export factory { ExportExpensesToCsv(get(), get(), androidContext()) } // data factory { ClearAllData(get()) } ``` ### 6. Verify compilation Run `./gradlew assembleDebug`. Must succeed. ## Acceptance - [ ] `BankStatementImporter` interface defined - [ ] 3 bank importer stubs return `NotImplementedError` failure - [ ] `ExportExpensesToCsv` writes UTF-8 BOM CSV - [ ] `ClearAllData` wipes all tables in a transaction - [ ] `MainApplication` calls `PDFBoxResourceLoader.init(this)` - [ ] `DomainModule` has the new factories (incl. the `List<BankStatementImporter>` aggregate) - [ ] `./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 10:42:01 +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#4