diff --git a/app/src/main/java/dev/achmad/ledgerr/di/DomainModule.kt b/app/src/main/java/dev/achmad/ledgerr/di/DomainModule.kt index 7166688..0231b97 100644 --- a/app/src/main/java/dev/achmad/ledgerr/di/DomainModule.kt +++ b/app/src/main/java/dev/achmad/ledgerr/di/DomainModule.kt @@ -1,9 +1,15 @@ 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.GetCategories import dev.achmad.ledgerr.domain.category.interactor.SeedDefaultCategories 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.GetExpenseSummary 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.ProcessDueRecurringExpenses 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 val domainModule = module { @@ -33,4 +41,15 @@ val domainModule = module { factory { UpsertRecurringExpense(get()) } factory { DeleteRecurringExpense(get()) } factory { ProcessDueRecurringExpenses(get()) } + + factory(named("bri")) { ImportBRIBankStatement(androidContext()) } + factory(named("jago")) { ImportJagoBankStatement(androidContext()) } + factory(named("bni")) { ImportBNIBankStatement(androidContext()) } + factory> { + listOf(get(named("bri")), get(named("jago")), get(named("bni"))) + } + + factory { ExportExpensesToCsv(get(), get(), androidContext()) } + + factory { ClearAllData(get()) } } diff --git a/app/src/main/java/dev/achmad/ledgerr/domain/bankstatement/interactor/BankStatementImporter.kt b/app/src/main/java/dev/achmad/ledgerr/domain/bankstatement/interactor/BankStatementImporter.kt new file mode 100644 index 0000000..a42e39f --- /dev/null +++ b/app/src/main/java/dev/achmad/ledgerr/domain/bankstatement/interactor/BankStatementImporter.kt @@ -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> +} diff --git a/app/src/main/java/dev/achmad/ledgerr/domain/bankstatement/interactor/ImportBNIBankStatement.kt b/app/src/main/java/dev/achmad/ledgerr/domain/bankstatement/interactor/ImportBNIBankStatement.kt new file mode 100644 index 0000000..3cca2bb --- /dev/null +++ b/app/src/main/java/dev/achmad/ledgerr/domain/bankstatement/interactor/ImportBNIBankStatement.kt @@ -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> = + Result.failure(NotImplementedError("BNI import not yet implemented")) +} diff --git a/app/src/main/java/dev/achmad/ledgerr/domain/bankstatement/interactor/ImportBRIBankStatement.kt b/app/src/main/java/dev/achmad/ledgerr/domain/bankstatement/interactor/ImportBRIBankStatement.kt new file mode 100644 index 0000000..4354370 --- /dev/null +++ b/app/src/main/java/dev/achmad/ledgerr/domain/bankstatement/interactor/ImportBRIBankStatement.kt @@ -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> = + Result.failure(NotImplementedError("BRI import not yet implemented")) +} diff --git a/app/src/main/java/dev/achmad/ledgerr/domain/bankstatement/interactor/ImportJagoBankStatement.kt b/app/src/main/java/dev/achmad/ledgerr/domain/bankstatement/interactor/ImportJagoBankStatement.kt new file mode 100644 index 0000000..24b4e7b --- /dev/null +++ b/app/src/main/java/dev/achmad/ledgerr/domain/bankstatement/interactor/ImportJagoBankStatement.kt @@ -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> = + Result.failure(NotImplementedError("Jago import not yet implemented")) +} diff --git a/app/src/main/java/dev/achmad/ledgerr/domain/data/interactor/ClearAllData.kt b/app/src/main/java/dev/achmad/ledgerr/domain/data/interactor/ClearAllData.kt new file mode 100644 index 0000000..7f07706 --- /dev/null +++ b/app/src/main/java/dev/achmad/ledgerr/domain/data/interactor/ClearAllData.kt @@ -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() + } + } +} diff --git a/app/src/main/java/dev/achmad/ledgerr/domain/export/interactor/ExportExpensesToCsv.kt b/app/src/main/java/dev/achmad/ledgerr/domain/export/interactor/ExportExpensesToCsv.kt new file mode 100644 index 0000000..f8c7bfd --- /dev/null +++ b/app/src/main/java/dev/achmad/ledgerr/domain/export/interactor/ExportExpensesToCsv.kt @@ -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 = 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") + } + } + } +} diff --git a/app/src/main/java/dev/achmad/ledgerr/ui/base/MainApplication.kt b/app/src/main/java/dev/achmad/ledgerr/ui/base/MainApplication.kt index 6adc97a..47e439b 100644 --- a/app/src/main/java/dev/achmad/ledgerr/ui/base/MainApplication.kt +++ b/app/src/main/java/dev/achmad/ledgerr/ui/base/MainApplication.kt @@ -1,6 +1,7 @@ package dev.achmad.ledgerr.ui.base import android.app.Application +import com.tom_roush.pdfbox.android.PDFBoxResourceLoader import dev.achmad.ledgerr.di.coreModule import dev.achmad.ledgerr.di.dataModule import dev.achmad.ledgerr.di.domainModule @@ -17,6 +18,7 @@ class MainApplication : Application() { override fun onCreate() { super.onCreate() + PDFBoxResourceLoader.init(this) startKoin { androidLogger() androidContext(this@MainApplication)