Implement bankstatement, export, and data interactors (#4) #16
@@ -1,9 +1,15 @@
|
|||||||
package dev.achmad.ledgerr.di
|
package dev.achmad.ledgerr.di
|
||||||
|
|
||||||
|
import dev.achmad.ledgerr.domain.bankstatement.interactor.BankStatementImporter
|
||||||
|
import dev.achmad.ledgerr.domain.bankstatement.interactor.ImportBNIBankStatement
|
||||||
|
import dev.achmad.ledgerr.domain.bankstatement.interactor.ImportBRIBankStatement
|
||||||
|
import dev.achmad.ledgerr.domain.bankstatement.interactor.ImportJagoBankStatement
|
||||||
import dev.achmad.ledgerr.domain.category.interactor.DeleteCategory
|
import dev.achmad.ledgerr.domain.category.interactor.DeleteCategory
|
||||||
import dev.achmad.ledgerr.domain.category.interactor.GetCategories
|
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.data.interactor.ClearAllData
|
||||||
|
import dev.achmad.ledgerr.domain.export.interactor.ExportExpensesToCsv
|
||||||
import dev.achmad.ledgerr.domain.expense.interactor.DeleteExpense
|
import dev.achmad.ledgerr.domain.expense.interactor.DeleteExpense
|
||||||
import dev.achmad.ledgerr.domain.expense.interactor.GetExpenseSummary
|
import dev.achmad.ledgerr.domain.expense.interactor.GetExpenseSummary
|
||||||
import dev.achmad.ledgerr.domain.expense.interactor.GetExpenses
|
import dev.achmad.ledgerr.domain.expense.interactor.GetExpenses
|
||||||
@@ -14,6 +20,8 @@ import dev.achmad.ledgerr.domain.recurring.interactor.DeleteRecurringExpense
|
|||||||
import dev.achmad.ledgerr.domain.recurring.interactor.GetRecurringExpenses
|
import dev.achmad.ledgerr.domain.recurring.interactor.GetRecurringExpenses
|
||||||
import dev.achmad.ledgerr.domain.recurring.interactor.ProcessDueRecurringExpenses
|
import dev.achmad.ledgerr.domain.recurring.interactor.ProcessDueRecurringExpenses
|
||||||
import dev.achmad.ledgerr.domain.recurring.interactor.UpsertRecurringExpense
|
import dev.achmad.ledgerr.domain.recurring.interactor.UpsertRecurringExpense
|
||||||
|
import org.koin.android.ext.koin.androidContext
|
||||||
|
import org.koin.core.qualifier.named
|
||||||
import org.koin.dsl.module
|
import org.koin.dsl.module
|
||||||
|
|
||||||
val domainModule = module {
|
val domainModule = module {
|
||||||
@@ -33,4 +41,15 @@ val domainModule = module {
|
|||||||
factory { UpsertRecurringExpense(get()) }
|
factory { UpsertRecurringExpense(get()) }
|
||||||
factory { DeleteRecurringExpense(get()) }
|
factory { DeleteRecurringExpense(get()) }
|
||||||
factory { ProcessDueRecurringExpenses(get()) }
|
factory { ProcessDueRecurringExpenses(get()) }
|
||||||
|
|
||||||
|
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")))
|
||||||
|
}
|
||||||
|
|
||||||
|
factory { ExportExpensesToCsv(get(), get(), androidContext()) }
|
||||||
|
|
||||||
|
factory { ClearAllData(get()) }
|
||||||
}
|
}
|
||||||
|
|||||||
+9
@@ -0,0 +1,9 @@
|
|||||||
|
package dev.achmad.ledgerr.domain.bankstatement.interactor
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
|
import dev.achmad.ledgerr.domain.bankstatement.model.PendingImportExpense
|
||||||
|
|
||||||
|
interface BankStatementImporter {
|
||||||
|
val bankName: String
|
||||||
|
suspend fun await(pdfUri: Uri): Result<List<PendingImportExpense>>
|
||||||
|
}
|
||||||
+13
@@ -0,0 +1,13 @@
|
|||||||
|
package dev.achmad.ledgerr.domain.bankstatement.interactor
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.net.Uri
|
||||||
|
import dev.achmad.ledgerr.domain.bankstatement.model.PendingImportExpense
|
||||||
|
|
||||||
|
class ImportBNIBankStatement(
|
||||||
|
@Suppress("unused") private val context: Context,
|
||||||
|
) : BankStatementImporter {
|
||||||
|
override val bankName: String = "BNI"
|
||||||
|
override suspend fun await(pdfUri: Uri): Result<List<PendingImportExpense>> =
|
||||||
|
Result.failure(NotImplementedError("BNI import not yet implemented"))
|
||||||
|
}
|
||||||
+13
@@ -0,0 +1,13 @@
|
|||||||
|
package dev.achmad.ledgerr.domain.bankstatement.interactor
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.net.Uri
|
||||||
|
import dev.achmad.ledgerr.domain.bankstatement.model.PendingImportExpense
|
||||||
|
|
||||||
|
class ImportBRIBankStatement(
|
||||||
|
@Suppress("unused") private val context: Context,
|
||||||
|
) : BankStatementImporter {
|
||||||
|
override val bankName: String = "BRI"
|
||||||
|
override suspend fun await(pdfUri: Uri): Result<List<PendingImportExpense>> =
|
||||||
|
Result.failure(NotImplementedError("BRI import not yet implemented"))
|
||||||
|
}
|
||||||
+13
@@ -0,0 +1,13 @@
|
|||||||
|
package dev.achmad.ledgerr.domain.bankstatement.interactor
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.net.Uri
|
||||||
|
import dev.achmad.ledgerr.domain.bankstatement.model.PendingImportExpense
|
||||||
|
|
||||||
|
class ImportJagoBankStatement(
|
||||||
|
@Suppress("unused") private val context: Context,
|
||||||
|
) : BankStatementImporter {
|
||||||
|
override val bankName: String = "Jago"
|
||||||
|
override suspend fun await(pdfUri: Uri): Result<List<PendingImportExpense>> =
|
||||||
|
Result.failure(NotImplementedError("Jago import not yet implemented"))
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package dev.achmad.ledgerr.domain.data.interactor
|
||||||
|
|
||||||
|
import androidx.room.withTransaction
|
||||||
|
import dev.achmad.ledgerr.data.local.AppDatabase
|
||||||
|
|
||||||
|
class ClearAllData(
|
||||||
|
private val database: AppDatabase,
|
||||||
|
) {
|
||||||
|
suspend fun await() {
|
||||||
|
database.withTransaction {
|
||||||
|
database.clearAllTables()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package dev.achmad.ledgerr.domain.export.interactor
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.net.Uri
|
||||||
|
import dev.achmad.ledgerr.data.local.dao.CategoryDao
|
||||||
|
import dev.achmad.ledgerr.data.local.dao.ExpenseDao
|
||||||
|
import dev.achmad.ledgerr.data.local.mapper.toModel
|
||||||
|
import dev.achmad.ledgerr.domain.expense.model.DateRange
|
||||||
|
import okio.buffer
|
||||||
|
import okio.sink
|
||||||
|
|
||||||
|
class ExportExpensesToCsv(
|
||||||
|
private val expenseDao: ExpenseDao,
|
||||||
|
private val categoryDao: CategoryDao,
|
||||||
|
private val context: Context,
|
||||||
|
) {
|
||||||
|
suspend fun await(range: DateRange, outputUri: Uri): Result<Unit> = runCatching {
|
||||||
|
val expenses = expenseDao.getByDateRange(
|
||||||
|
startDay = range.start.toEpochDay(),
|
||||||
|
endDay = range.end.toEpochDay(),
|
||||||
|
).map { it.toModel() }
|
||||||
|
val categoryMap = categoryDao.getAll().associate { entity ->
|
||||||
|
entity.id to entity.toModel()
|
||||||
|
}
|
||||||
|
val outputStream = context.contentResolver.openOutputStream(outputUri)
|
||||||
|
?: error("Failed to open output stream for $outputUri")
|
||||||
|
outputStream.sink().buffer().use { sink ->
|
||||||
|
sink.writeUtf8("\uFEFF")
|
||||||
|
sink.writeUtf8("Date,Category,Amount,Note\n")
|
||||||
|
expenses.forEach { expense ->
|
||||||
|
val categoryName = categoryMap[expense.categoryId]?.name.orEmpty()
|
||||||
|
val note = expense.note.orEmpty()
|
||||||
|
sink.writeUtf8("${expense.date},$categoryName,${expense.amount},\"$note\"\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package dev.achmad.ledgerr.ui.base
|
package dev.achmad.ledgerr.ui.base
|
||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
|
import com.tom_roush.pdfbox.android.PDFBoxResourceLoader
|
||||||
import dev.achmad.ledgerr.di.coreModule
|
import dev.achmad.ledgerr.di.coreModule
|
||||||
import dev.achmad.ledgerr.di.dataModule
|
import dev.achmad.ledgerr.di.dataModule
|
||||||
import dev.achmad.ledgerr.di.domainModule
|
import dev.achmad.ledgerr.di.domainModule
|
||||||
@@ -17,6 +18,7 @@ class MainApplication : Application() {
|
|||||||
|
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
|
PDFBoxResourceLoader.init(this)
|
||||||
startKoin {
|
startKoin {
|
||||||
androidLogger()
|
androidLogger()
|
||||||
androidContext(this@MainApplication)
|
androidContext(this@MainApplication)
|
||||||
|
|||||||
Reference in New Issue
Block a user