Implemented saving BankAccounts to dbCommented functionality
This commit is contained in:
parent
d75e8705ea
commit
55aad5242a
|
@ -121,7 +121,7 @@ sqldelight {
|
|||
databases {
|
||||
create("BankmeisterDb") {
|
||||
packageName.set("net.codinux.banking.dataaccess")
|
||||
generateAsync.set(true)
|
||||
generateAsync = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,12 +2,13 @@ package net.codinux.banking.dataaccess
|
|||
|
||||
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.client.model.*
|
||||
import net.codinux.banking.dataaccess.entities.AccountTransactionEntity
|
||||
import net.codinux.banking.dataaccess.entities.BankAccountEntity
|
||||
import net.codinux.banking.dataaccess.entities.UserAccountEntity
|
||||
import kotlin.enums.EnumEntries
|
||||
import kotlin.js.JsName
|
||||
import kotlin.jvm.JvmName
|
||||
|
||||
class SqliteBankingRepository(
|
||||
sqlDriver: SqlDriver
|
||||
|
@ -21,18 +22,67 @@ class SqliteBankingRepository(
|
|||
|
||||
|
||||
override fun getAllUserAccounts(): List<UserAccountEntity> {
|
||||
val bankAccounts = getAllBankAccounts().groupBy { it.userAccountId }
|
||||
|
||||
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(),
|
||||
UserAccountEntity(id, bankCode, loginName, password, bankName, bic, customerName, userId, bankAccounts[id] ?: 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,
|
||||
return userAccountQueries.transactionWithResult {
|
||||
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()
|
||||
)
|
||||
|
||||
val userAccountId = getLastInsertedId() // getLastInsertedId() / last_insert_rowid() has to be called in a transaction with the insert operation, otherwise it will not work
|
||||
|
||||
persistBankAccounts(userAccountId, userAccount.accounts)
|
||||
|
||||
userAccountId
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
fun getAllBankAccounts(): List<BankAccountEntity> = userAccountQueries.selectAllBankAccounts { id, userAccountId, identifier, accountHolderName, type, iban, subAccountNumber, productName, currency, accountLimit, isAccountTypeSupportedByApplication, features, balance, retrievedTransactionsFrom, retrievedTransactionsTo, countDaysForWhichTransactionsAreKept, userSetDisplayName, displayIndex, hideAccount, includeInAutomaticAccountsUpdate ->
|
||||
BankAccountEntity(
|
||||
id, userAccountId,
|
||||
identifier, accountHolderName, BankAccountType.valueOf(type),
|
||||
iban, subAccountNumber, productName, currency, accountLimit,
|
||||
|
||||
isAccountTypeSupportedByApplication, mapEnumSet(features, BankAccountFeatures.entries),
|
||||
mapToAmount(balance), mapToDate(retrievedTransactionsFrom), mapToDate(retrievedTransactionsTo),
|
||||
mapToInt(countDaysForWhichTransactionsAreKept),
|
||||
|
||||
userSetDisplayName, mapToInt(displayIndex),
|
||||
hideAccount, includeInAutomaticAccountsUpdate
|
||||
)
|
||||
}.executeAsList()
|
||||
|
||||
suspend fun persistBankAccounts(userAccountId: Long, bankAccounts: Collection<BankAccount>): Map<Long, BankAccount> =
|
||||
bankAccounts.associate { persistBankAccount(userAccountId, it) }
|
||||
|
||||
private suspend fun persistBankAccount(userAccountId: Long, account: BankAccount): Pair<Long, BankAccount> {
|
||||
userAccountQueries.insertBankAccount(
|
||||
userAccountId,
|
||||
account.identifier, account.accountHolderName, mapEnum(account.type),
|
||||
account.iban, account.subAccountNumber, account.productName, account.currency, account.accountLimit,
|
||||
|
||||
account.isAccountTypeSupportedByApplication, mapEnumCollectionToString(account.features),
|
||||
|
||||
mapAmount(account.balance),
|
||||
mapDate(account.retrievedTransactionsFrom), mapDate(account.retrievedTransactionsTo),
|
||||
|
||||
mapInt(account.countDaysForWhichTransactionsAreKept),
|
||||
|
||||
account.userSetDisplayName, mapInt(account.displayIndex),
|
||||
account.hideAccount, account.includeInAutomaticAccountsUpdate
|
||||
)
|
||||
|
||||
return Pair(getLastInsertedId(), account)
|
||||
}
|
||||
|
||||
|
||||
|
@ -77,7 +127,7 @@ class SqliteBankingRepository(
|
|||
|
||||
private suspend fun saveAccountTransaction(transaction: AccountTransaction) {
|
||||
accountTransactionQueries.insertTransaction(
|
||||
transaction.amount.amount, transaction.currency, transaction.reference,
|
||||
mapAmount(transaction.amount), transaction.currency, transaction.reference,
|
||||
mapDate(transaction.bookingDate), mapDate(transaction.valueDate),
|
||||
transaction.otherPartyName, transaction.otherPartyBankCode, transaction.otherPartyAccountId,
|
||||
transaction.bookingText,
|
||||
|
@ -86,7 +136,7 @@ class SqliteBankingRepository(
|
|||
|
||||
transaction.information,
|
||||
transaction.statementNumber?.toLong(), transaction.sequenceNumber?.toLong(),
|
||||
transaction.openingBalance?.amount, transaction.closingBalance?.amount,
|
||||
mapAmount(transaction.openingBalance), mapAmount(transaction.closingBalance),
|
||||
|
||||
transaction.endToEndReference, transaction.customerReference, transaction.mandateReference,
|
||||
transaction.creditorIdentifier, transaction.originatorsIdentificationCode,
|
||||
|
@ -104,10 +154,59 @@ class SqliteBankingRepository(
|
|||
}
|
||||
|
||||
|
||||
private fun mapToAmount(serializedAmount: String?): Amount? = serializedAmount?.let { Amount(it) }
|
||||
private fun getLastInsertedId(): Long =
|
||||
userAccountQueries.getLastInsertedId().executeAsOne()
|
||||
|
||||
|
||||
@JvmName("mapAmount")
|
||||
@JsName("mapAmount")
|
||||
private fun mapAmount(amount: Amount?): String? =
|
||||
amount?.let { mapAmount(it) }
|
||||
|
||||
private fun mapAmount(amount: Amount): String = amount.amount
|
||||
|
||||
private fun mapToAmount(serializedAmount: String?): Amount? =
|
||||
serializedAmount?.let { mapToAmount(it) }
|
||||
|
||||
private fun mapToAmount(serializedAmount: String): Amount = Amount(serializedAmount)
|
||||
|
||||
@JvmName("mapDateNullable")
|
||||
@JsName("mapDateNullable")
|
||||
private fun mapDate(date: LocalDate?): String? =
|
||||
date?.let { mapDate(it) }
|
||||
|
||||
private fun mapDate(date: LocalDate): String = date.toString()
|
||||
|
||||
@JvmName("mapToDateNullable")
|
||||
@JsName("mapToDateNullable")
|
||||
private fun mapToDate(serializedDate: String?): LocalDate? =
|
||||
serializedDate?.let { mapToDate(it) }
|
||||
|
||||
private fun mapToDate(serializedDate: String): LocalDate = LocalDate.parse(serializedDate)
|
||||
|
||||
private fun <E : Enum<E>> mapEnum(enum: Enum<E>): String = enum.name
|
||||
|
||||
private fun <E : Enum<E>> mapToEnum(enumName: String, values: EnumEntries<E>): E =
|
||||
values.first { it.name == enumName }
|
||||
|
||||
private fun <E : Enum<E>> mapEnumCollectionToString(enums: Collection<E>): String =
|
||||
enums.joinToString(",") { it.name }
|
||||
|
||||
private fun <E : Enum<E>> mapEnumSet(enumsString: String, values: EnumEntries<E>): Set<E> =
|
||||
enumsString.split(',').map { mapToEnum(it, values) }.toSet()
|
||||
|
||||
@JvmName("mapIntNullable")
|
||||
@JsName("mapIntNullable")
|
||||
private fun mapInt(int: Int?): Long? =
|
||||
int?.let { mapInt(it) }
|
||||
|
||||
private fun mapInt(int: Int): Long = int.toLong()
|
||||
|
||||
@JvmName("mapToIntNullable")
|
||||
@JsName("mapToIntNullable")
|
||||
private fun mapToInt(int: Long?): Int? =
|
||||
int?.let { mapToInt(it) }
|
||||
|
||||
private fun mapToInt(int: Long): Int = int.toInt()
|
||||
|
||||
}
|
|
@ -21,6 +21,7 @@ class AccountTransactionEntity(
|
|||
bookingText: String? = null,
|
||||
|
||||
userSetDisplayName: String? = null,
|
||||
val category: String? = null, // TODO: add to AccountTransaction
|
||||
notes: String? = null,
|
||||
information: String? = null,
|
||||
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
package net.codinux.banking.dataaccess.entities
|
||||
|
||||
import kotlinx.datetime.LocalDate
|
||||
import net.codinux.banking.client.model.*
|
||||
|
||||
class BankAccountEntity(
|
||||
val id: Long,
|
||||
val userAccountId: Long,
|
||||
|
||||
identifier: String,
|
||||
accountHolderName: String,
|
||||
type: BankAccountType = BankAccountType.Other,
|
||||
iban: String? = null,
|
||||
subAccountNumber: String? = null,
|
||||
productName: String? = null,
|
||||
currency: String = "EUR",
|
||||
accountLimit: String? = null,
|
||||
|
||||
isAccountTypeSupportedByApplication: Boolean = true,
|
||||
features: Set<BankAccountFeatures> = emptySet(),
|
||||
|
||||
balance: Amount = Amount.Zero, // TODO: add a BigDecimal library
|
||||
retrievedTransactionsFrom: LocalDate? = null,
|
||||
retrievedTransactionsTo: LocalDate? = null,
|
||||
|
||||
countDaysForWhichTransactionsAreKept: Int? = null,
|
||||
|
||||
// bookedTransactions: MutableList<AccountTransaction> = mutableListOf(),
|
||||
// unbookedTransactions: MutableList<UnbookedAccountTransaction> = mutableListOf(),
|
||||
|
||||
userSetDisplayName: String? = null,
|
||||
displayIndex: Int = 0,
|
||||
|
||||
hideAccount: Boolean = false,
|
||||
includeInAutomaticAccountsUpdate: Boolean = true
|
||||
) : BankAccount(
|
||||
identifier, accountHolderName, type, iban, subAccountNumber, productName, currency, accountLimit,
|
||||
isAccountTypeSupportedByApplication, features, balance,
|
||||
retrievedTransactionsFrom, retrievedTransactionsTo, false, countDaysForWhichTransactionsAreKept,
|
||||
mutableListOf(), mutableListOf(),
|
||||
userSetDisplayName, displayIndex, hideAccount, includeInAutomaticAccountsUpdate
|
||||
)
|
|
@ -1,6 +1,5 @@
|
|||
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
|
||||
|
@ -19,7 +18,7 @@ class UserAccountEntity(
|
|||
customerName: String,
|
||||
userId: String = loginName,
|
||||
|
||||
accounts: List<BankAccount> = emptyList(),
|
||||
accounts: List<BankAccountEntity> = emptyList(), // TODO: make accounts open
|
||||
|
||||
selectedTanMethodId: String? = null,
|
||||
tanMethods: List<TanMethod> = listOf(),
|
||||
|
@ -50,4 +49,7 @@ class UserAccountEntity(
|
|||
user.bankingGroup, user.iconUrl, user.wrongCredentialsEntered, user.userSetDisplayName, user.displayIndex
|
||||
)
|
||||
|
||||
|
||||
val accountEntities: List<BankAccountEntity> = accounts
|
||||
|
||||
}
|
|
@ -27,8 +27,8 @@ CREATE TABLE IF NOT EXISTS UserAccount (
|
|||
);
|
||||
|
||||
|
||||
insertUserAccount {
|
||||
INSERT INTO UserAccount(
|
||||
insertUserAccount:
|
||||
INSERT INTO UserAccount(
|
||||
bankCode, loginName, password,
|
||||
|
||||
bankName, bic,
|
||||
|
@ -43,8 +43,8 @@ insertUserAccount {
|
|||
|
||||
wrongCredentialsEntered,
|
||||
userSetDisplayName, displayIndex
|
||||
)
|
||||
VALUES(
|
||||
)
|
||||
VALUES(
|
||||
?, ?, ?,
|
||||
?, ?,
|
||||
?, ?,
|
||||
|
@ -55,12 +55,86 @@ insertUserAccount {
|
|||
?, ?,
|
||||
?,
|
||||
?, ?
|
||||
);
|
||||
|
||||
SELECT last_insert_rowid();
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
selectAllUserAccounts:
|
||||
SELECT UserAccount.*
|
||||
FROM UserAccount;
|
||||
|
||||
|
||||
|
||||
CREATE TABLE IF NOT EXISTS BankAccount (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
|
||||
userAccountId INTEGER NOT NULL,
|
||||
|
||||
identifier TEXT NOT NULL,
|
||||
accountHolderName TEXT NOT NULL,
|
||||
type TEXT NOT NULL,
|
||||
iban TEXT,
|
||||
subAccountNumber TEXT,
|
||||
productName TEXT,
|
||||
currency TEXT NOT NULL,
|
||||
accountLimit TEXT,
|
||||
|
||||
isAccountTypeSupportedByApplication INTEGER AS Boolean NOT NULL,
|
||||
features TEXT NOT NULL,
|
||||
|
||||
balance TEXT NOT NULL,
|
||||
retrievedTransactionsFrom TEXT,
|
||||
retrievedTransactionsTo TEXT,
|
||||
|
||||
countDaysForWhichTransactionsAreKept INTEGER,
|
||||
|
||||
userSetDisplayName TEXT,
|
||||
displayIndex INTEGER NOT NULL,
|
||||
|
||||
hideAccount INTEGER AS Boolean NOT NULL,
|
||||
includeInAutomaticAccountsUpdate INTEGER AS Boolean NOT NULL
|
||||
);
|
||||
|
||||
|
||||
insertBankAccount:
|
||||
INSERT INTO BankAccount(
|
||||
userAccountId,
|
||||
|
||||
identifier, accountHolderName, type,
|
||||
iban, subAccountNumber, productName, currency, accountLimit,
|
||||
|
||||
isAccountTypeSupportedByApplication, features,
|
||||
|
||||
balance, retrievedTransactionsFrom, retrievedTransactionsTo,
|
||||
|
||||
countDaysForWhichTransactionsAreKept,
|
||||
|
||||
userSetDisplayName, displayIndex,
|
||||
|
||||
hideAccount, includeInAutomaticAccountsUpdate
|
||||
)
|
||||
VALUES(
|
||||
?,
|
||||
|
||||
?, ?, ?,
|
||||
?, ?, ?, ?, ?,
|
||||
|
||||
?, ?,
|
||||
|
||||
?, ?, ?,
|
||||
|
||||
?,
|
||||
|
||||
?, ?,
|
||||
|
||||
?, ?
|
||||
);
|
||||
|
||||
|
||||
selectAllBankAccounts:
|
||||
SELECT BankAccount.*
|
||||
FROM BankAccount;
|
||||
|
||||
|
||||
-- TODO: find a better place for this cross-cutting concern:
|
||||
getLastInsertedId:
|
||||
SELECT last_insert_rowid();
|
||||
|
|
|
@ -4,10 +4,7 @@ import app.cash.sqldelight.async.coroutines.synchronous
|
|||
import app.cash.sqldelight.driver.jdbc.sqlite.JdbcSqliteDriver
|
||||
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 net.codinux.banking.client.model.*
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertNotNull
|
||||
|
@ -23,7 +20,10 @@ class SqliteBankingRepositoryTest {
|
|||
|
||||
@Test
|
||||
fun saveUserAccount() = runTest {
|
||||
val userAccount = CustomerAccount("12345678", "SupiDupiUser", "geheim", "Abzock-Bank", "ABCDDEBBXXX", "Herr Maier", bankingGroup = BankingGroup.DKB).apply {
|
||||
val bankAccounts = listOf(
|
||||
BankAccount("12345", "Monika Tester", BankAccountType.CheckingAccount, balance = Amount("12.34"), retrievedTransactionsTo = LocalDate(2024, 5, 7), features = setOf(BankAccountFeatures.RetrieveBalance, BankAccountFeatures.InstantPayment), countDaysForWhichTransactionsAreKept = 320)
|
||||
)
|
||||
val userAccount = CustomerAccount("12345678", "SupiDupiUser", "geheim", "Abzock-Bank", "ABCDDEBBXXX", "Monika Tester", accounts = bankAccounts, bankingGroup = BankingGroup.DKB).apply {
|
||||
wrongCredentialsEntered = true
|
||||
displayIndex = 99
|
||||
}
|
||||
|
@ -51,6 +51,26 @@ class SqliteBankingRepositoryTest {
|
|||
|
||||
assertEquals(userAccount.wrongCredentialsEntered, persisted.wrongCredentialsEntered)
|
||||
assertEquals(userAccount.displayIndex, persisted.displayIndex)
|
||||
|
||||
assertEquals(1, persisted.accountEntities.size)
|
||||
|
||||
val persistedBankAccount = persisted.accountEntities.first()
|
||||
assertNotNull(persistedBankAccount.id)
|
||||
assertEquals(persisted.id, persistedBankAccount.userAccountId)
|
||||
|
||||
assertEquals(bankAccounts.first().identifier, persistedBankAccount.identifier)
|
||||
assertEquals(bankAccounts.first().accountHolderName, persistedBankAccount.accountHolderName)
|
||||
assertEquals(bankAccounts.first().type, persistedBankAccount.type)
|
||||
|
||||
assertEquals(bankAccounts.first().balance, persistedBankAccount.balance)
|
||||
assertEquals(bankAccounts.first().retrievedTransactionsFrom, persistedBankAccount.retrievedTransactionsFrom)
|
||||
assertEquals(bankAccounts.first().retrievedTransactionsTo, persistedBankAccount.retrievedTransactionsTo)
|
||||
|
||||
assertEquals(bankAccounts.first().features, persistedBankAccount.features)
|
||||
|
||||
assertEquals(bankAccounts.first().countDaysForWhichTransactionsAreKept, persistedBankAccount.countDaysForWhichTransactionsAreKept)
|
||||
assertEquals(bankAccounts.first().hideAccount, persistedBankAccount.hideAccount)
|
||||
assertEquals(bankAccounts.first().includeInAutomaticAccountsUpdate, persistedBankAccount.includeInAutomaticAccountsUpdate)
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
Loading…
Reference in New Issue