Implemented retrieving transactions of last 90 days when adding an account; switched from wasmJs to js as Ktor currently doesn't support wasmJs (due to CORS web sites cannot request banks servers directly anyway)

This commit is contained in:
dankito 2024-08-26 16:54:41 +02:00
parent 9ee82e9a5b
commit 2869544a1c
6 changed files with 75 additions and 6 deletions

View File

@ -15,8 +15,7 @@ plugins {
kotlin {
@OptIn(ExperimentalWasmDsl::class)
wasmJs {
js {
moduleName = "composeApp"
browser {
val projectDirPath = project.projectDir.path
@ -58,6 +57,7 @@ kotlin {
commonMain.dependencies {
implementation(libs.banking.client.model)
implementation(libs.fints4k.banking.client)
implementation(libs.kcsv)
implementation(libs.klf)

View File

@ -6,12 +6,16 @@ import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Close
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.window.Dialog
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import net.codinux.banking.ui.forms.*
import net.codinux.banking.ui.model.BankInfo
import net.codinux.banking.ui.service.Colors
@ -32,6 +36,11 @@ fun AddAccountDialog(
derivedStateOf { selectedBank != null && loginName.length > 3 && password.length > 3 }
}
var isAddingAccount by remember { mutableStateOf(false) }
val coroutineScope = rememberCoroutineScope()
Dialog(onDismissRequest = onDismiss) {
RoundedCornersCard {
Column(Modifier.background(Color.White).padding(8.dp)) {
@ -93,10 +102,35 @@ fun AddAccountDialog(
Spacer(Modifier.width(8.dp))
TextButton(
onClick = onDismiss,
enabled = isRequiredDataEntered
modifier = Modifier.width(150.dp),
enabled = isRequiredDataEntered && isAddingAccount == false,
onClick = {
selectedBank?.let {
isAddingAccount = true
coroutineScope.launch { // TODO: launch on Dispatchers.IO where it is available
val errorMessage = DI.bankingService.addAccount(selectedBank!!, loginName, password)
withContext(Dispatchers.Main) {
isAddingAccount = false
if (errorMessage == null) {
onDismiss()
} else {
// TODO: show error Message
}
}
}
}
}
) {
Text("Hinzufügen")
Row(verticalAlignment = Alignment.CenterVertically) {
if (isAddingAccount) {
CircularProgressIndicator(Modifier.padding(end = 6.dp))
}
Text("Hinzufügen")
}
}
}
}

View File

@ -2,8 +2,12 @@ package net.codinux.banking.ui.service
import bankmeister.composeapp.generated.resources.Res
import kotlinx.datetime.LocalDate
import net.codinux.banking.client.SimpleBankingClientCallback
import net.codinux.banking.client.fints4k.FinTs4kBankingClientForCustomer
import net.codinux.banking.client.model.AccountTransaction
import net.codinux.banking.client.model.Amount
import net.codinux.banking.fints.config.FinTsClientConfiguration
import net.codinux.banking.fints.config.FinTsClientOptions
import net.codinux.banking.ui.model.BankInfo
import net.codinux.csv.reader.CsvReader
import net.codinux.log.logger
@ -33,6 +37,29 @@ class BankingService(
return transactions
}
suspend fun addAccount(bank: BankInfo, loginName: String, password: String): String? {
try {
val config = FinTsClientConfiguration(FinTsClientOptions(true))
val client = FinTs4kBankingClientForCustomer(bank.bankCode, loginName, password, config, SimpleBankingClientCallback { tanChallenge, callback ->
// TODO: show EnterTanDialog
})
val response = client.getAccountDataAsync()
response.data?.let { accountData ->
if (cachedTransactions == null) {
cachedTransactions = accountData.bookedTransactions
} else {
cachedTransactions = (cachedTransactions!! + accountData.bookedTransactions).sortedByDescending { it.valueDate }
}
}
return response.error?.internalError ?: response.error?.errorMessagesFromBank?.joinToString("\n")
} catch (e: Throwable) {
log.error(e) { "Could not add account for ${bank.name} $loginName" }
return e.message
}
}
private suspend fun readTransactionsFromCsv(): List<AccountTransaction> {
val csv = Res.readBytes("files/transactions.csv").decodeToString()
val csvReader = CsvReader(hasHeaderRow = true, reuseRowInstance = true, skipEmptyRows = true).read(csv)

View File

@ -8,4 +8,7 @@ android.nonTransitiveRClass=true
android.useAndroidX=true
#Kotlin Multiplatform
kotlin.mpp.enableCInteropCommonization=true
kotlin.mpp.enableCInteropCommonization=true
# Compose Multiplatform Web
org.jetbrains.compose.experimental.jscanvas.enabled=true

View File

@ -26,6 +26,7 @@ junit = "4.13.2"
[libraries]
banking-client-model = { group = "net.codinux.banking.client", name = "banking-client-model", version.ref = "banking-client" }
fints4k-banking-client = { group = "net.codinux.banking.client", name = "fints4k-banking-client", version.ref = "banking-client" }
kcsv = { group = "net.codinux.csv", name = "kcsv", version.ref = "kcsv" }
klf = { group = "net.codinux.log", name = "klf", version.ref = "klf" }

View File

@ -25,6 +25,10 @@ dependencyResolutionManagement {
}
}
mavenCentral()
mavenLocal()
maven {
setUrl("https://maven.dankito.net/api/packages/codinux/maven")
}
}
}