Implemented saving CustomerAccounts to db
This commit is contained in:
parent
6e6eb91e74
commit
8a7226661f
|
@ -83,6 +83,8 @@ kotlin {
|
|||
|
||||
commonTest.dependencies {
|
||||
implementation(libs.kotlin.test)
|
||||
|
||||
implementation(libs.coroutines.test)
|
||||
}
|
||||
|
||||
androidMain.dependencies {
|
||||
|
|
|
@ -1,10 +1,17 @@
|
|||
package net.codinux.banking.dataaccess
|
||||
|
||||
import net.codinux.banking.client.model.AccountTransaction
|
||||
import net.codinux.banking.client.model.CustomerAccount
|
||||
import net.codinux.banking.dataaccess.entities.AccountTransactionEntity
|
||||
import net.codinux.banking.dataaccess.entities.UserAccountEntity
|
||||
|
||||
interface BankingRepository {
|
||||
|
||||
fun getAllUserAccounts(): List<UserAccountEntity>
|
||||
|
||||
suspend fun persistUserAccount(userAccount: CustomerAccount): Long
|
||||
|
||||
|
||||
fun getAllAccountTransactions(): List<AccountTransactionEntity>
|
||||
|
||||
suspend fun persistAccountTransactions(transactions: Collection<AccountTransaction>)
|
||||
|
|
|
@ -1,17 +1,31 @@
|
|||
package net.codinux.banking.dataaccess
|
||||
|
||||
import net.codinux.banking.client.model.AccountTransaction
|
||||
import net.codinux.banking.client.model.CustomerAccount
|
||||
import net.codinux.banking.dataaccess.entities.AccountTransactionEntity
|
||||
import net.codinux.banking.dataaccess.entities.UserAccountEntity
|
||||
|
||||
class InMemoryBankingRepository(
|
||||
transactions: Collection<AccountTransaction>
|
||||
userAccounts: Collection<CustomerAccount> = emptyList(),
|
||||
transactions: Collection<AccountTransaction> = emptyList()
|
||||
) : BankingRepository {
|
||||
|
||||
private var nextId = 0L // TODO: make thread-safe
|
||||
|
||||
private val userAccounts = userAccounts.map { map(it) }.toMutableList()
|
||||
|
||||
private val transactions = transactions.map { map(it) }.toMutableList()
|
||||
|
||||
|
||||
override fun getAllUserAccounts(): List<UserAccountEntity> = userAccounts.toList()
|
||||
|
||||
override suspend fun persistUserAccount(userAccount: CustomerAccount): Long {
|
||||
val entity = map(userAccount)
|
||||
this.userAccounts.add(entity)
|
||||
return entity.id
|
||||
}
|
||||
|
||||
|
||||
override fun getAllAccountTransactions(): List<AccountTransactionEntity> = transactions.toList()
|
||||
|
||||
override suspend fun persistAccountTransactions(transactions: Collection<AccountTransaction>) {
|
||||
|
@ -19,6 +33,13 @@ class InMemoryBankingRepository(
|
|||
}
|
||||
|
||||
|
||||
private fun map(account: CustomerAccount) = UserAccountEntity(
|
||||
nextId++,
|
||||
account.bankCode, account.loginName, account.password, account.bankName, account.bic, account.customerName, account.userId,
|
||||
emptyList(), account.selectedTanMethodId, emptyList(), account.selectedTanMediumName, emptyList(),
|
||||
account.bankingGroup, account.iconUrl, account.wrongCredentialsEntered, account.userSetDisplayName, account.displayIndex
|
||||
)
|
||||
|
||||
private fun map(transaction: AccountTransaction) = AccountTransactionEntity(
|
||||
nextId++,
|
||||
transaction.amount, transaction.currency, transaction.reference,
|
||||
|
|
|
@ -4,7 +4,10 @@ import app.cash.sqldelight.db.SqlDriver
|
|||
import kotlinx.datetime.LocalDate
|
||||
import net.codinux.banking.client.model.AccountTransaction
|
||||
import net.codinux.banking.client.model.Amount
|
||||
import net.codinux.banking.client.model.BankingGroup
|
||||
import net.codinux.banking.client.model.CustomerAccount
|
||||
import net.codinux.banking.dataaccess.entities.AccountTransactionEntity
|
||||
import net.codinux.banking.dataaccess.entities.UserAccountEntity
|
||||
|
||||
class SqliteBankingRepository(
|
||||
sqlDriver: SqlDriver
|
||||
|
@ -12,9 +15,27 @@ class SqliteBankingRepository(
|
|||
|
||||
private val database = BankmeisterDb(sqlDriver)
|
||||
|
||||
private val userAccountQueries = database.userAccountQueries
|
||||
|
||||
private val accountTransactionQueries = database.accountTransactionQueries
|
||||
|
||||
|
||||
override fun getAllUserAccounts(): List<UserAccountEntity> {
|
||||
|
||||
return userAccountQueries.selectAllUserAccounts { id, bankCode, loginName, password, bankName, bic, customerName, userId, selectedTanMethodId, selectedTanMediumName, bankingGroup, iconUrl, wrongCredentialsEntered, userSetDisplayName, displayIndex ->
|
||||
UserAccountEntity(id, bankCode, loginName, password, bankName, bic, customerName, userId, emptyList(), selectedTanMethodId, emptyList(), selectedTanMediumName, emptyList(),
|
||||
bankingGroup?.let { BankingGroup.valueOf(it) }, iconUrl, wrongCredentialsEntered, userSetDisplayName, displayIndex.toInt())
|
||||
}.executeAsList()
|
||||
}
|
||||
|
||||
override suspend fun persistUserAccount(userAccount: CustomerAccount): Long {
|
||||
return userAccountQueries.insertUserAccount(userAccount.bankCode, userAccount.loginName, userAccount.password, userAccount.bankName, userAccount.bic,
|
||||
userAccount.customerName, userAccount.userId, userAccount.selectedTanMethodId, userAccount.selectedTanMediumName,
|
||||
userAccount.bankingGroup?.name, userAccount.iconUrl, userAccount.wrongCredentialsEntered, userAccount.userSetDisplayName, userAccount.displayIndex.toLong()
|
||||
).executeAsOne()
|
||||
}
|
||||
|
||||
|
||||
override fun getAllAccountTransactions(): List<AccountTransactionEntity> {
|
||||
return accountTransactionQueries.selectAllTransactions { id, amount, currency, reference, bookingDate, valueDate, otherPartyName, otherPartyBankCode, otherPartyAccountId, bookingText, userSetDisplayName, notes, information, statementNumber, sequenceNumber, openingBalance, closingBalance, endToEndReference, customerReference, mandateReference, creditorIdentifier, originatorsIdentificationCode, compensationAmount, originalAmount, sepaReference, deviantOriginator, deviantRecipient, referenceWithNoSpecialType, primaNotaNumber, textKeySupplement, currencyType, bookingKey, referenceForTheAccountOwner, referenceOfTheAccountServicingInstitution, supplementaryDetails, transactionReferenceNumber, relatedReferenceNumber ->
|
||||
AccountTransactionEntity(
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
package net.codinux.banking.dataaccess.entities
|
||||
|
||||
import net.codinux.banking.client.model.BankAccount
|
||||
import net.codinux.banking.client.model.BankingGroup
|
||||
import net.codinux.banking.client.model.CustomerAccount
|
||||
import net.codinux.banking.client.model.tan.TanMedium
|
||||
import net.codinux.banking.client.model.tan.TanMethod
|
||||
|
||||
class UserAccountEntity(
|
||||
val id: Long,
|
||||
|
||||
bankCode: String,
|
||||
loginName: String,
|
||||
password: String?,
|
||||
|
||||
bankName: String,
|
||||
bic: String,
|
||||
|
||||
customerName: String,
|
||||
userId: String = loginName,
|
||||
|
||||
accounts: List<BankAccount> = emptyList(),
|
||||
|
||||
selectedTanMethodId: String? = null,
|
||||
tanMethods: List<TanMethod> = listOf(),
|
||||
|
||||
selectedTanMediumName: String? = null,
|
||||
tanMedia: List<TanMedium> = listOf(),
|
||||
|
||||
bankingGroup: BankingGroup? = null,
|
||||
iconUrl: String? = null,
|
||||
|
||||
wrongCredentialsEntered: Boolean = false,
|
||||
|
||||
userSetDisplayName: String? = null,
|
||||
displayIndex: Int = 0
|
||||
) : CustomerAccount(bankCode, loginName, password, bankName, bic, customerName, userId, accounts, selectedTanMethodId, tanMethods, selectedTanMediumName, tanMedia, bankingGroup, iconUrl) {
|
||||
|
||||
init {
|
||||
this.wrongCredentialsEntered = wrongCredentialsEntered
|
||||
this.userSetDisplayName = userSetDisplayName
|
||||
this.displayIndex = displayIndex
|
||||
}
|
||||
|
||||
|
||||
constructor(id: Long, user: CustomerAccount) : this(
|
||||
id,
|
||||
user.bankCode, user.loginName, user.password, user.bankName, user.bic, user.customerName, user.userId,
|
||||
emptyList(), user.selectedTanMethodId, emptyList(), user.selectedTanMediumName, emptyList(),
|
||||
user.bankingGroup, user.iconUrl, user.wrongCredentialsEntered, user.userSetDisplayName, user.displayIndex
|
||||
)
|
||||
|
||||
}
|
|
@ -21,10 +21,10 @@ object DI {
|
|||
val bankFinder = BankFinder()
|
||||
|
||||
|
||||
|
||||
var bankingRepository: BankingRepository = InMemoryBankingRepository(emptyList())
|
||||
|
||||
val bankingService by lazy {
|
||||
BankingService(uiState, bankingRepository, bankFinder) }
|
||||
val bankingService by lazy { BankingService(uiState, bankingRepository, bankFinder) }
|
||||
|
||||
|
||||
fun setRepository(sqlDriver: SqlDriver) = setRepository(SqliteBankingRepository(sqlDriver))
|
||||
|
|
|
@ -11,6 +11,7 @@ import net.codinux.banking.client.model.options.RetrieveTransactions
|
|||
import net.codinux.banking.client.model.request.GetAccountDataRequest
|
||||
import net.codinux.banking.client.model.response.*
|
||||
import net.codinux.banking.dataaccess.BankingRepository
|
||||
import net.codinux.banking.dataaccess.entities.UserAccountEntity
|
||||
import net.codinux.banking.fints.config.FinTsClientConfiguration
|
||||
import net.codinux.banking.fints.config.FinTsClientOptions
|
||||
import net.codinux.banking.ui.model.BankInfo
|
||||
|
@ -39,9 +40,11 @@ class BankingService(
|
|||
|
||||
suspend fun init() {
|
||||
try {
|
||||
uiState.userAccounts.value = bankingRepository.getAllUserAccounts()
|
||||
|
||||
uiState.transactions.value = bankingRepository.getAllAccountTransactions()
|
||||
} catch (e: Throwable) {
|
||||
log.error(e) { "Could not read all account transactions from repository" }
|
||||
log.error(e) { "Could not read all user accounts and account transactions from repository" }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -69,11 +72,8 @@ class BankingService(
|
|||
}
|
||||
|
||||
private suspend fun handleSuccessfulGetAccountDataResponse(response: GetAccountDataResponse) {
|
||||
// TODO: save customer
|
||||
|
||||
val transactions = uiState.transactions.value.toMutableList()
|
||||
transactions.addAll(response.bookedTransactions)
|
||||
|
||||
uiState.transactions.value = transactions.sortedByDescending { it.valueDate }
|
||||
|
||||
try {
|
||||
|
@ -83,6 +83,18 @@ class BankingService(
|
|||
} catch (e: Throwable) {
|
||||
log.error(e) { "Could not save account transactions ${response.bookedTransactions}" }
|
||||
}
|
||||
|
||||
try {
|
||||
val newUserAccountId = bankingRepository.persistUserAccount(response.customer)
|
||||
|
||||
log.info { "Saved user account ${response.customer}" }
|
||||
|
||||
val userAccounts = uiState.userAccounts.value.toMutableList()
|
||||
userAccounts.add(UserAccountEntity(newUserAccountId, response.customer))
|
||||
uiState.userAccounts.value = userAccounts
|
||||
} catch (e: Throwable) {
|
||||
log.error(e) { "Could not save user account ${response.customer}" }
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleUnsuccessfulBankingClientResponse(action: BankingClientAction, response: Response<*>) {
|
||||
|
|
|
@ -3,6 +3,7 @@ package net.codinux.banking.ui.state
|
|||
import androidx.lifecycle.ViewModel
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import net.codinux.banking.client.model.AccountTransaction
|
||||
import net.codinux.banking.dataaccess.entities.UserAccountEntity
|
||||
import net.codinux.banking.ui.model.TanChallengeReceived
|
||||
import net.codinux.banking.ui.model.error.ApplicationError
|
||||
import net.codinux.banking.ui.model.error.BankingClientError
|
||||
|
@ -10,6 +11,8 @@ import net.codinux.banking.ui.model.error.ErroneousAction
|
|||
|
||||
class UiState : ViewModel() {
|
||||
|
||||
val userAccounts = MutableStateFlow<List<UserAccountEntity>>(emptyList())
|
||||
|
||||
val transactions = MutableStateFlow<List<AccountTransaction>>(emptyList())
|
||||
|
||||
val applicationErrorOccurred = MutableStateFlow<ApplicationError?>(null)
|
||||
|
|
|
@ -0,0 +1,66 @@
|
|||
import kotlin.Boolean;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS UserAccount (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
|
||||
bankCode TEXT NOT NULL,
|
||||
loginName TEXT NOT NULL,
|
||||
password TEXT,
|
||||
|
||||
bankName TEXT NOT NULL,
|
||||
bic TEXT NOT NULL,
|
||||
|
||||
customerName TEXT NOT NULL,
|
||||
userId TEXT NOT NULL,
|
||||
|
||||
selectedTanMethodId TEXT,
|
||||
|
||||
selectedTanMediumName TEXT,
|
||||
|
||||
bankingGroup TEXT,
|
||||
iconUrl TEXT,
|
||||
|
||||
wrongCredentialsEntered INTEGER AS Boolean NOT NULL,
|
||||
|
||||
userSetDisplayName TEXT,
|
||||
displayIndex INTEGER NOT NULL
|
||||
);
|
||||
|
||||
|
||||
insertUserAccount {
|
||||
INSERT INTO UserAccount(
|
||||
bankCode, loginName, password,
|
||||
|
||||
bankName, bic,
|
||||
|
||||
customerName, userId,
|
||||
|
||||
selectedTanMethodId,
|
||||
|
||||
selectedTanMediumName,
|
||||
|
||||
bankingGroup, iconUrl,
|
||||
|
||||
wrongCredentialsEntered,
|
||||
userSetDisplayName, displayIndex
|
||||
)
|
||||
VALUES(
|
||||
?, ?, ?,
|
||||
?, ?,
|
||||
?, ?,
|
||||
?,
|
||||
|
||||
?,
|
||||
|
||||
?, ?,
|
||||
?,
|
||||
?, ?
|
||||
);
|
||||
|
||||
SELECT last_insert_rowid();
|
||||
}
|
||||
|
||||
|
||||
selectAllUserAccounts:
|
||||
SELECT UserAccount.*
|
||||
FROM UserAccount;
|
|
@ -39,7 +39,10 @@ fun main() = application {
|
|||
@Preview
|
||||
@Composable
|
||||
fun AppPreview() {
|
||||
DI.setRepository(InMemoryBankingRepository(listOf(AccountTransaction(Amount("12.34"), "EUR", "Lohn", LocalDate(2024, 7, 5), LocalDate(2024, 6, 15), "Dein Boss"))))
|
||||
DI.setRepository(InMemoryBankingRepository(
|
||||
emptyList(),
|
||||
listOf(AccountTransaction(Amount("12.34"), "EUR", "Lohn", LocalDate(2024, 7, 5), LocalDate(2024, 6, 15), "Dein Boss"))
|
||||
))
|
||||
|
||||
App()
|
||||
}
|
|
@ -2,10 +2,12 @@ package net.codinux.banking.dataaccess
|
|||
|
||||
import app.cash.sqldelight.async.coroutines.synchronous
|
||||
import app.cash.sqldelight.driver.jdbc.sqlite.JdbcSqliteDriver
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import kotlinx.datetime.LocalDate
|
||||
import net.codinux.banking.client.model.AccountTransaction
|
||||
import net.codinux.banking.client.model.Amount
|
||||
import net.codinux.banking.client.model.BankingGroup
|
||||
import net.codinux.banking.client.model.CustomerAccount
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertNotNull
|
||||
|
@ -20,7 +22,39 @@ class SqliteBankingRepositoryTest {
|
|||
|
||||
|
||||
@Test
|
||||
fun saveTransaction() = runBlocking {
|
||||
fun saveUserAccount() = runTest {
|
||||
val userAccount = CustomerAccount("12345678", "SupiDupiUser", "geheim", "Abzock-Bank", "ABCDDEBBXXX", "Herr Maier", bankingGroup = BankingGroup.DKB).apply {
|
||||
wrongCredentialsEntered = true
|
||||
displayIndex = 99
|
||||
}
|
||||
|
||||
underTest.persistUserAccount(userAccount)
|
||||
|
||||
val result = underTest.getAllUserAccounts()
|
||||
|
||||
assertEquals(1, result.size)
|
||||
|
||||
val persisted = result.first()
|
||||
assertNotNull(persisted.id)
|
||||
|
||||
assertEquals(userAccount.bankCode, persisted.bankCode)
|
||||
assertEquals(userAccount.loginName, persisted.loginName)
|
||||
assertEquals(userAccount.password, persisted.password)
|
||||
|
||||
assertEquals(userAccount.bankName, persisted.bankName)
|
||||
assertEquals(userAccount.bic, persisted.bic)
|
||||
|
||||
assertEquals(userAccount.customerName, persisted.customerName)
|
||||
assertEquals(userAccount.userId, persisted.userId)
|
||||
|
||||
assertEquals(userAccount.bankingGroup, persisted.bankingGroup)
|
||||
|
||||
assertEquals(userAccount.wrongCredentialsEntered, persisted.wrongCredentialsEntered)
|
||||
assertEquals(userAccount.displayIndex, persisted.displayIndex)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun saveTransaction() = runTest {
|
||||
val transaction = AccountTransaction(Amount("12.45"), "EUR", "Lohn", LocalDate(2024, 5, 7), LocalDate(2024, 6, 15), "Dein Boss")
|
||||
|
||||
underTest.persistAccountTransactions(listOf(transaction))
|
||||
|
|
|
@ -33,6 +33,7 @@ banking-client-model = { group = "net.codinux.banking.client", name = "banking-c
|
|||
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" }
|
||||
coroutines-test = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-test", version.ref = "kotlinx-coroutines" }
|
||||
kotlinx-serializable = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version.ref = "kotlinx-serializable" }
|
||||
|
||||
klf = { group = "net.codinux.log", name = "klf", version.ref = "klf" }
|
||||
|
|
Loading…
Reference in New Issue