Updated to model changes: User has been renamed to BankAccess and bic is now nullable

This commit is contained in:
dankito 2024-09-16 17:08:43 +02:00
parent 41586b0148
commit 9330b72726
27 changed files with 220 additions and 223 deletions

View File

@ -13,8 +13,8 @@ import net.codinux.banking.ui.model.TanChallengeReceived
@Composable @Composable
fun EnterTanDialogPreview_EnterTan() { fun EnterTanDialogPreview_EnterTan() {
val tanMethods = listOf(TanMethod("Zeig mich an", TanMethodType.AppTan, "902")) val tanMethods = listOf(TanMethod("Zeig mich an", TanMethodType.AppTan, "902"))
val user = BankViewInfo("12345678", "SupiDupiNutzer", "Abzockbank", BankingGroup.Postbank) val bank = BankViewInfo("12345678", "SupiDupiNutzer", "Abzockbank", BankingGroup.Postbank)
val tanChallenge = TanChallenge(TanChallengeType.EnterTan, ActionRequiringTan.GetAccountInfo, "Geben Sie die TAN ein", tanMethods.first().identifier, tanMethods, user = user) val tanChallenge = TanChallenge(TanChallengeType.EnterTan, ActionRequiringTan.GetAccountInfo, "Geben Sie die TAN ein", tanMethods.first().identifier, tanMethods, bank = bank)
EnterTanDialog(TanChallengeReceived(tanChallenge) { }) { } EnterTanDialog(TanChallengeReceived(tanChallenge) { }) { }
} }
@ -27,9 +27,9 @@ fun EnterTanDialogPreview_TanImage() {
val tanMethod = TanMethod("photoTAN-Verfahren", TanMethodType.photoTan, "902", 6, AllowedTanFormat.Numeric) val tanMethod = TanMethod("photoTAN-Verfahren", TanMethodType.photoTan, "902", 6, AllowedTanFormat.Numeric)
val tanImage = TanImage("image/png", tanImageBytes) val tanImage = TanImage("image/png", tanImageBytes)
val user = BankViewInfo("10010010", "Ihr krasser Login Name", "Phantasie Bank", BankingGroup.Comdirect) val bank = BankViewInfo("10010010", "Ihr krasser Login Name", "Phantasie Bank", BankingGroup.Comdirect)
val tanChallenge = TanChallenge(TanChallengeType.Image, ActionRequiringTan.GetAccountInfo, "Geben Sie die TAN ein", tanMethod.identifier, listOf(tanMethod), null, emptyList(), tanImage, null, user) val tanChallenge = TanChallenge(TanChallengeType.Image, ActionRequiringTan.GetAccountInfo, "Geben Sie die TAN ein", tanMethod.identifier, listOf(tanMethod), null, emptyList(), tanImage, null, bank)
EnterTanDialog(TanChallengeReceived(tanChallenge) { }) { } EnterTanDialog(TanChallengeReceived(tanChallenge) { }) { }
} }
@ -50,10 +50,10 @@ fun EnterTanDialogPreview_WithMultipleTanMedia() { // shows that dialog is reall
TanMedium(TanMediumType.TanGenerator, "SparkassenCard (Debitkarte)", TanMediumStatus.Used, TanGeneratorTanMedium("5432109876")) TanMedium(TanMediumType.TanGenerator, "SparkassenCard (Debitkarte)", TanMediumStatus.Used, TanGeneratorTanMedium("5432109876"))
) )
val user = BankViewInfo("10010010", "Ihr krasser Login Name", "Eine ganz gewöhnliche Sparkasse", BankingGroup.Sparkasse) val bank = BankViewInfo("10010010", "Ihr krasser Login Name", "Eine ganz gewöhnliche Sparkasse", BankingGroup.Sparkasse)
val account = BankAccountViewInfo("12345678", null, BankAccountType.CheckingAccount, null, "Giro Konto") val account = BankAccountViewInfo("12345678", null, BankAccountType.CheckingAccount, null, "Giro Konto")
val tanChallenge = TanChallenge(TanChallengeType.Image, ActionRequiringTan.GetTransactions, "Sie möchten eine \"Umsatzabfrage\" freigeben: Bitte bestätigen Sie den \"Startcode 80061030\" mit der Taste \"OK\".", "913", tanMethods, "SparkassenCard (Debitkarte)", tanMedia, tanImage, null, user, account) val tanChallenge = TanChallenge(TanChallengeType.Image, ActionRequiringTan.GetTransactions, "Sie möchten eine \"Umsatzabfrage\" freigeben: Bitte bestätigen Sie den \"Startcode 80061030\" mit der Taste \"OK\".", "913", tanMethods, "SparkassenCard (Debitkarte)", tanMedia, tanImage, null, bank, account)
EnterTanDialog(TanChallengeReceived(tanChallenge) { }) { } EnterTanDialog(TanChallengeReceived(tanChallenge) { }) { }
} }
@ -62,8 +62,8 @@ fun EnterTanDialogPreview_WithMultipleTanMedia() { // shows that dialog is reall
@Composable @Composable
fun EnterTanDialogPreview_Flickercode() { fun EnterTanDialogPreview_Flickercode() {
val tanMethods = listOf(TanMethod("chipTAN Flickercode", TanMethodType.ChipTanFlickercode, "902")) val tanMethods = listOf(TanMethod("chipTAN Flickercode", TanMethodType.ChipTanFlickercode, "902"))
val user = BankViewInfo("12345678", "SupiDupiNutzer", "Abzockbank", BankingGroup.Postbank) val bank = BankViewInfo("12345678", "SupiDupiNutzer", "Abzockbank", BankingGroup.Postbank)
val tanChallenge = TanChallenge(TanChallengeType.Flickercode, ActionRequiringTan.GetAccountInfo, "Geben Sie die TAN ein", tanMethods.first().identifier, tanMethods, user = user, flickerCode = FlickerCode("", "")) val tanChallenge = TanChallenge(TanChallengeType.Flickercode, ActionRequiringTan.GetAccountInfo, "Geben Sie die TAN ein", tanMethods.first().identifier, tanMethods, bank = bank, flickerCode = FlickerCode("", ""))
EnterTanDialog(TanChallengeReceived(tanChallenge) { }) { } EnterTanDialog(TanChallengeReceived(tanChallenge) { }) { }
} }

View File

@ -1,19 +1,19 @@
package net.codinux.banking.dataaccess package net.codinux.banking.dataaccess
import net.codinux.banking.client.model.AccountTransaction import net.codinux.banking.client.model.AccountTransaction
import net.codinux.banking.client.model.User import net.codinux.banking.client.model.BankAccess
import net.codinux.banking.client.model.securitiesaccount.Holding import net.codinux.banking.client.model.securitiesaccount.Holding
import net.codinux.banking.dataaccess.entities.AccountTransactionEntity import net.codinux.banking.dataaccess.entities.AccountTransactionEntity
import net.codinux.banking.dataaccess.entities.BankAccountEntity import net.codinux.banking.dataaccess.entities.BankAccountEntity
import net.codinux.banking.dataaccess.entities.HoldingEntity import net.codinux.banking.dataaccess.entities.HoldingEntity
import net.codinux.banking.dataaccess.entities.UserEntity import net.codinux.banking.dataaccess.entities.BankAccessEntity
import net.codinux.banking.ui.model.AccountTransactionViewModel import net.codinux.banking.ui.model.AccountTransactionViewModel
interface BankingRepository { interface BankingRepository {
fun getAllUsers(): List<UserEntity> fun getAllBanks(): List<BankAccessEntity>
suspend fun persistUser(user: User): UserEntity suspend fun persistBank(bank: BankAccess): BankAccessEntity
suspend fun persistTransactions(bankAccount: BankAccountEntity, transactions: List<AccountTransaction>): List<AccountTransactionEntity> suspend fun persistTransactions(bankAccount: BankAccountEntity, transactions: List<AccountTransaction>): List<AccountTransactionEntity>
@ -29,7 +29,7 @@ interface BankingRepository {
fun getAllAccountTransactions(): List<AccountTransactionEntity> fun getAllAccountTransactions(): List<AccountTransactionEntity>
fun getAllTransactionsOfUser(user: UserEntity): List<AccountTransactionEntity> fun getAllTransactionsForBank(bank: BankAccessEntity): List<AccountTransactionEntity>
fun getTransactionById(transactionId: Long): AccountTransactionEntity? fun getTransactionById(transactionId: Long): AccountTransactionEntity?

View File

@ -1,31 +1,31 @@
package net.codinux.banking.dataaccess package net.codinux.banking.dataaccess
import net.codinux.banking.client.model.AccountTransaction import net.codinux.banking.client.model.AccountTransaction
import net.codinux.banking.client.model.User import net.codinux.banking.client.model.BankAccess
import net.codinux.banking.client.model.securitiesaccount.Holding import net.codinux.banking.client.model.securitiesaccount.Holding
import net.codinux.banking.dataaccess.entities.AccountTransactionEntity import net.codinux.banking.dataaccess.entities.AccountTransactionEntity
import net.codinux.banking.dataaccess.entities.BankAccountEntity import net.codinux.banking.dataaccess.entities.BankAccountEntity
import net.codinux.banking.dataaccess.entities.HoldingEntity import net.codinux.banking.dataaccess.entities.HoldingEntity
import net.codinux.banking.dataaccess.entities.UserEntity import net.codinux.banking.dataaccess.entities.BankAccessEntity
import net.codinux.banking.ui.model.AccountTransactionViewModel import net.codinux.banking.ui.model.AccountTransactionViewModel
class InMemoryBankingRepository( class InMemoryBankingRepository(
users: Collection<User> = emptyList(), banks: Collection<BankAccess> = emptyList(),
transactions: Collection<AccountTransaction> = emptyList() transactions: Collection<AccountTransaction> = emptyList()
) : BankingRepository { ) : BankingRepository {
private var nextId = 0L // TODO: make thread-safe private var nextId = 0L // TODO: make thread-safe
private val users = users.map { map(it) }.toMutableList() private val banks = banks.map { map(it) }.toMutableList()
private val transactions = transactions.map { map(it) }.toMutableList() private val transactions = transactions.map { map(it) }.toMutableList()
override fun getAllUsers(): List<UserEntity> = users.toList() override fun getAllBanks(): List<BankAccessEntity> = banks.toList()
override suspend fun persistUser(user: User): UserEntity { override suspend fun persistBank(bank: BankAccess): BankAccessEntity {
val entity = map(user) // TODO: may fix someday and add also BankAccounts and their id val entity = map(bank) // TODO: may fix someday and add also BankAccounts and their id
this.users.add(entity) this.banks.add(entity)
return entity return entity
} }
@ -50,27 +50,27 @@ class InMemoryBankingRepository(
override fun getAllAccountTransactions(): List<AccountTransactionEntity> = transactions.toList() override fun getAllAccountTransactions(): List<AccountTransactionEntity> = transactions.toList()
override fun getAllTransactionsOfUser(user: UserEntity): List<AccountTransactionEntity> = override fun getAllTransactionsForBank(bank: BankAccessEntity): List<AccountTransactionEntity> =
getAllAccountTransactions().filter { it.userId == user.id } getAllAccountTransactions().filter { it.userId == bank.id }
override fun getTransactionById(transactionId: Long): AccountTransactionEntity? = override fun getTransactionById(transactionId: Long): AccountTransactionEntity? =
getAllAccountTransactions().firstOrNull { it.id == transactionId } getAllAccountTransactions().firstOrNull { it.id == transactionId }
private fun map(account: User) = UserEntity( private fun map(bank: BankAccess) = BankAccessEntity(
nextId++, nextId++,
account.bankCode, account.loginName, account.password, account.bankName, account.bic, account.customerName, account.userId, bank.bankCode, bank.loginName, bank.password, bank.bankName, bank.bic, bank.customerName, bank.userId,
// TODO: may fix someday and also add BankAccounts // TODO: may fix someday and also add BankAccounts
emptyList(), account.selectedTanMethodIdentifier, emptyList(), account.selectedTanMediumIdentifier, emptyList(), emptyList(), bank.selectedTanMethodIdentifier, emptyList(), bank.selectedTanMediumIdentifier, emptyList(),
account.bankingGroup, account.serverAddress, bank.bankingGroup, bank.serverAddress,
account.userSetDisplayName, account.displayIndex, bank.userSetDisplayName, bank.displayIndex,
account.iconUrl, account.wrongCredentialsEntered, bank.iconUrl, bank.wrongCredentialsEntered,
) )
// TODO: someday may fix and get userId and bankAccountId // TODO: someday may fix and get bankId and bankAccountId
private fun map(transaction: AccountTransaction, userId: Long = nextId++, bankAccountId: Long = nextId++) = AccountTransactionEntity( private fun map(transaction: AccountTransaction, bankId: Long = nextId++, bankAccountId: Long = nextId++) = AccountTransactionEntity(
nextId++, nextId++,
userId, bankAccountId, bankId, bankAccountId,
transaction.amount, transaction.currency, transaction.reference, transaction.amount, transaction.currency, transaction.reference,
transaction.bookingDate, transaction.valueDate, transaction.bookingDate, transaction.valueDate,
transaction.otherPartyName, transaction.otherPartyBankId, transaction.otherPartyAccountId, transaction.otherPartyName, transaction.otherPartyBankId, transaction.otherPartyAccountId,

View File

@ -19,53 +19,53 @@ open class SqliteBankingRepository(
private val database = BankmeisterDb(sqlDriver) private val database = BankmeisterDb(sqlDriver)
private val userQueries = database.userQueries private val bankQueries = database.userQueries
private val accountTransactionQueries = database.accountTransactionQueries private val accountTransactionQueries = database.accountTransactionQueries
private val log by logger() private val log by logger()
override fun getAllUsers(): List<UserEntity> { override fun getAllBanks(): List<BankAccessEntity> {
val bankAccounts = getAllBankAccounts().groupBy { it.userId } val bankAccounts = getAllBankAccounts().groupBy { it.userId }
val tanMethods = getAllTanMethods().groupBy { it.userId } val tanMethods = getAllTanMethods().groupBy { it.userId }
val tanMedia = getAllTanMedia().groupBy { it.userId } val tanMedia = getAllTanMedia().groupBy { it.userId }
val holdings = getAllHoldings().groupBy { it.bankAccountId } val holdings = getAllHoldings().groupBy { it.bankAccountId }
return userQueries.selectAllUsers { id, bankCode, loginName, password, bankName, bic, customerName, userId, selectedTanMethodIdentifier, selectedTanMediumIdentifier, bankingGroup, serverAddress, userSetDisplayName, clientData, displayIndex, iconUrl, wrongCredentialsEntered -> return bankQueries.selectAllBanks { id, bankCode, loginName, password, bankName, bic, customerName, userId, selectedTanMethodIdentifier, selectedTanMediumIdentifier, bankingGroup, serverAddress, userSetDisplayName, clientData, displayIndex, iconUrl, wrongCredentialsEntered ->
UserEntity(id, bankCode, loginName, password, bankName, bic, customerName, userId, getAccountsOfUser(id, bankAccounts, holdings), selectedTanMethodIdentifier, tanMethods[id] ?: emptyList(), selectedTanMediumIdentifier, tanMedia[id] ?: emptyList(), BankAccessEntity(id, bankCode, loginName, password, bankName, bic, customerName, userId, getAccountsOfBank(id, bankAccounts, holdings), selectedTanMethodIdentifier, tanMethods[id] ?: emptyList(), selectedTanMediumIdentifier, tanMedia[id] ?: emptyList(),
bankingGroup?.let { BankingGroup.valueOf(it) }, serverAddress, userSetDisplayName, displayIndex.toInt(), iconUrl, wrongCredentialsEntered) bankingGroup?.let { BankingGroup.valueOf(it) }, serverAddress, userSetDisplayName, displayIndex.toInt(), iconUrl, wrongCredentialsEntered)
}.executeAsList() }.executeAsList()
} }
protected open fun getAccountsOfUser(userId: Long, bankAccounts: Map<Long, List<BankAccountEntity>>, holdings: Map<Long, List<HoldingEntity>>): List<BankAccountEntity> { protected open fun getAccountsOfBank(bankId: Long, bankAccounts: Map<Long, List<BankAccountEntity>>, holdings: Map<Long, List<HoldingEntity>>): List<BankAccountEntity> {
return bankAccounts[userId].orEmpty().onEach { return bankAccounts[bankId].orEmpty().onEach {
it.holdings = holdings[it.id].orEmpty() it.holdings = holdings[it.id].orEmpty()
} }
} }
override suspend fun persistUser(user: User): UserEntity { override suspend fun persistBank(bank: BankAccess): BankAccessEntity {
return userQueries.transactionWithResult { return bankQueries.transactionWithResult {
userQueries.insertUser(user.bankCode, user.loginName, user.password, user.bankName, user.bic, bankQueries.insertBank(bank.bankCode, bank.loginName, bank.password, bank.bankName, bank.bic,
user.customerName, user.userId, user.selectedTanMethodIdentifier, user.selectedTanMediumIdentifier, bank.customerName, bank.userId, bank.selectedTanMethodIdentifier, bank.selectedTanMediumIdentifier,
user.bankingGroup?.name, user.serverAddress, null, user.userSetDisplayName, user.displayIndex.toLong(), user.iconUrl, user.wrongCredentialsEntered bank.bankingGroup?.name, bank.serverAddress, null, bank.userSetDisplayName, bank.displayIndex.toLong(), bank.iconUrl, bank.wrongCredentialsEntered
) )
val userId = getLastInsertedId() // getLastInsertedId() / last_insert_rowid() has to be called in a transaction with the insert operation, otherwise it will not work val bankId = getLastInsertedId() // getLastInsertedId() / last_insert_rowid() has to be called in a transaction with the insert operation, otherwise it will not work
val bankAccounts = persistBankAccounts(userId, user.accounts) val bankAccounts = persistBankAccounts(bankId, bank.accounts)
val tanMethods = persistTanMethods(userId, user.tanMethods) val tanMethods = persistTanMethods(bankId, bank.tanMethods)
val tanMedia = persistTanMedia(userId, user.tanMedia) val tanMedia = persistTanMedia(bankId, bank.tanMedia)
UserEntity(userId, user, bankAccounts, tanMethods, tanMedia) BankAccessEntity(bankId, bank, bankAccounts, tanMethods, tanMedia)
} }
} }
fun getAllBankAccounts(): List<BankAccountEntity> = userQueries.selectAllBankAccounts { id, userId, identifier, subAccountNumber, iban, productName, accountHolderName, type, currency, accountLimit, isAccountTypeSupportedByApplication, features, balance, serverTransactionsRetentionDays, lastAccountUpdateTime, retrievedTransactionsFrom, userSetDisplayName, displayIndex, hideAccount, includeInAutomaticAccountsUpdate -> fun getAllBankAccounts(): List<BankAccountEntity> = bankQueries.selectAllBankAccounts { id, bankId, identifier, subAccountNumber, iban, productName, accountHolderName, type, currency, accountLimit, isAccountTypeSupportedByApplication, features, balance, serverTransactionsRetentionDays, lastAccountUpdateTime, retrievedTransactionsFrom, userSetDisplayName, displayIndex, hideAccount, includeInAutomaticAccountsUpdate ->
BankAccountEntity( BankAccountEntity(
id, userId, id, bankId,
identifier, subAccountNumber, iban, productName, identifier, subAccountNumber, iban, productName,
@ -86,15 +86,15 @@ open class SqliteBankingRepository(
) )
}.executeAsList() }.executeAsList()
private suspend fun persistBankAccounts(userId: Long, bankAccounts: Collection<BankAccount>): List<BankAccountEntity> = private suspend fun persistBankAccounts(bankId: Long, bankAccounts: Collection<BankAccount>): List<BankAccountEntity> =
bankAccounts.map { persistBankAccount(userId, it) } bankAccounts.map { persistBankAccount(bankId, it) }
/** /**
* Has to be executed in a transaction in order that getting persisted BankAccount's id works~ * Has to be executed in a transaction in order that getting persisted BankAccount's id works~
*/ */
private suspend fun persistBankAccount(userId: Long, account: BankAccount): BankAccountEntity { private suspend fun persistBankAccount(bankId: Long, account: BankAccount): BankAccountEntity {
userQueries.insertBankAccount( bankQueries.insertBankAccount(
userId, bankId,
account.identifier, account.accountHolderName, mapEnum(account.type), account.identifier, account.accountHolderName, mapEnum(account.type),
account.iban, account.subAccountNumber, account.productName, account.currency, account.accountLimit, account.iban, account.subAccountNumber, account.productName, account.currency, account.accountLimit,
@ -111,19 +111,19 @@ open class SqliteBankingRepository(
val accountId = getLastInsertedId() val accountId = getLastInsertedId()
val accountTransactionEntities = account.bookedTransactions.map { transaction -> val accountTransactionEntities = account.bookedTransactions.map { transaction ->
persistTransaction(userId, accountId, transaction) persistTransaction(bankId, accountId, transaction)
} }
val holdings = account.holdings.map { holding -> persistHolding(userId, accountId, holding) } val holdings = account.holdings.map { holding -> persistHolding(bankId, accountId, holding) }
return BankAccountEntity(accountId, userId, account, accountTransactionEntities, holdings) return BankAccountEntity(accountId, bankId, account, accountTransactionEntities, holdings)
} }
private fun getAllTanMethods(): List<TanMethodEntity> = userQueries.selectAllTanMethods { id, userId, displayName, type, identifier, maxTanInputLength, allowedTanFormat -> private fun getAllTanMethods(): List<TanMethodEntity> = bankQueries.selectAllTanMethods { id, bankId, displayName, type, identifier, maxTanInputLength, allowedTanFormat ->
TanMethodEntity( TanMethodEntity(
id, id,
userId, bankId,
displayName, displayName,
mapToEnum(type, TanMethodType.entries), mapToEnum(type, TanMethodType.entries),
@ -133,12 +133,12 @@ open class SqliteBankingRepository(
) )
}.executeAsList() }.executeAsList()
private suspend fun persistTanMethods(userId: Long, tanMethods: List<TanMethod>): List<TanMethodEntity> = private suspend fun persistTanMethods(bankId: Long, tanMethods: List<TanMethod>): List<TanMethodEntity> =
tanMethods.map { persistTanMethod(userId, it) } tanMethods.map { persistTanMethod(bankId, it) }
private suspend fun persistTanMethod(userId: Long, tanMethod: TanMethod): TanMethodEntity { private suspend fun persistTanMethod(bankId: Long, tanMethod: TanMethod): TanMethodEntity {
userQueries.insertTanMethod( bankQueries.insertTanMethod(
userId, bankId,
tanMethod.displayName, tanMethod.displayName,
mapEnum(tanMethod.type), mapEnum(tanMethod.type),
@ -149,11 +149,11 @@ open class SqliteBankingRepository(
val tanMethodId = getLastInsertedId() val tanMethodId = getLastInsertedId()
return TanMethodEntity(tanMethodId, userId, tanMethod) return TanMethodEntity(tanMethodId, bankId, tanMethod)
} }
private fun getAllTanMedia(): List<TanMediumEntity> = userQueries.selectAllTanMedia { id, userId, type, mediumName, status, phoneNumber, concealedPhoneNumber, cardNumber, cardSequenceNumber, cardType, validFrom, validTo -> private fun getAllTanMedia(): List<TanMediumEntity> = bankQueries.selectAllTanMedia { id, bankId, type, mediumName, status, phoneNumber, concealedPhoneNumber, cardNumber, cardSequenceNumber, cardType, validFrom, validTo ->
val mobilePhone = if (phoneNumber != null || concealedPhoneNumber != null) { val mobilePhone = if (phoneNumber != null || concealedPhoneNumber != null) {
MobilePhoneTanMedium(phoneNumber, concealedPhoneNumber) MobilePhoneTanMedium(phoneNumber, concealedPhoneNumber)
} else { } else {
@ -168,7 +168,7 @@ open class SqliteBankingRepository(
TanMediumEntity( TanMediumEntity(
id, id,
userId, bankId,
mapToEnum(type, TanMediumType.entries), mapToEnum(type, TanMediumType.entries),
mediumName, mediumName,
@ -179,12 +179,12 @@ open class SqliteBankingRepository(
) )
}.executeAsList() }.executeAsList()
private suspend fun persistTanMedia(userId: Long, tanMedia: List<TanMedium>): List<TanMediumEntity> = private suspend fun persistTanMedia(bankId: Long, tanMedia: List<TanMedium>): List<TanMediumEntity> =
tanMedia.map { persistTanMedium(userId, it) } tanMedia.map { persistTanMedium(bankId, it) }
private suspend fun persistTanMedium(userId: Long, medium: TanMedium): TanMediumEntity { private suspend fun persistTanMedium(bankId: Long, medium: TanMedium): TanMediumEntity {
userQueries.insertTanMedium( bankQueries.insertTanMedium(
userId, bankId,
mapEnum(medium.type), mapEnum(medium.type),
medium.mediumName, medium.mediumName,
@ -202,13 +202,13 @@ open class SqliteBankingRepository(
val tanMediumId = getLastInsertedId() val tanMediumId = getLastInsertedId()
return TanMediumEntity(tanMediumId, userId, medium) return TanMediumEntity(tanMediumId, bankId, medium)
} }
protected open fun getAllHoldings(): List<HoldingEntity> = protected open fun getAllHoldings(): List<HoldingEntity> =
accountTransactionQueries.selectAllHoldings { id, userId, bankAccountId, name, isin, wkn, quantity, currency, totalBalance, marketValue, performancePercentage, totalCostPrice, averageCostPrice, pricingTime, buyingDate -> accountTransactionQueries.selectAllHoldings { id, bankId, bankAccountId, name, isin, wkn, quantity, currency, totalBalance, marketValue, performancePercentage, totalCostPrice, averageCostPrice, pricingTime, buyingDate ->
HoldingEntity(id, userId, bankAccountId, name, isin, wkn, mapToInt(quantity), currency, mapToAmount(totalBalance), mapToAmount(marketValue), performancePercentage?.toFloat(), mapToAmount(totalCostPrice), mapToAmount(averageCostPrice), mapToInstant(pricingTime), mapToDate(buyingDate)) HoldingEntity(id, bankId, bankAccountId, name, isin, wkn, mapToInt(quantity), currency, mapToAmount(totalBalance), mapToAmount(marketValue), performancePercentage?.toFloat(), mapToAmount(totalCostPrice), mapToAmount(averageCostPrice), mapToInstant(pricingTime), mapToDate(buyingDate))
}.executeAsList() }.executeAsList()
override suspend fun persistHoldings(bankAccount: BankAccountEntity, holdings: List<Holding>): List<HoldingEntity> = override suspend fun persistHoldings(bankAccount: BankAccountEntity, holdings: List<Holding>): List<HoldingEntity> =
@ -219,9 +219,9 @@ open class SqliteBankingRepository(
/** /**
* Has to be executed in a transaction in order that getting persisted Holding's id works~ * Has to be executed in a transaction in order that getting persisted Holding's id works~
*/ */
protected open suspend fun persistHolding(userId: Long, bankAccountId: Long, holding: Holding): HoldingEntity { protected open suspend fun persistHolding(bankId: Long, bankAccountId: Long, holding: Holding): HoldingEntity {
accountTransactionQueries.insertHolding( accountTransactionQueries.insertHolding(
userId, bankAccountId, bankId, bankAccountId,
holding.name, holding.isin, holding.wkn, holding.name, holding.isin, holding.wkn,
@ -234,7 +234,7 @@ open class SqliteBankingRepository(
mapInstant(holding.pricingTime), mapDate(holding.buyingDate) mapInstant(holding.pricingTime), mapDate(holding.buyingDate)
) )
return HoldingEntity(getLastInsertedId(), userId, bankAccountId, holding) return HoldingEntity(getLastInsertedId(), bankId, bankAccountId, holding)
} }
override suspend fun updateHoldings(holdings: List<HoldingEntity>) { override suspend fun updateHoldings(holdings: List<HoldingEntity>) {
@ -266,16 +266,16 @@ open class SqliteBankingRepository(
override fun getAllAccountTransactionsAsViewModel(): List<AccountTransactionViewModel> = override fun getAllAccountTransactionsAsViewModel(): List<AccountTransactionViewModel> =
accountTransactionQueries.selectAllTransactionsAsViewModel { id, userId, bankAccountId, amount, currency, reference, valueDate, otherPartyName, postingText, userSetDisplayName, category -> accountTransactionQueries.selectAllTransactionsAsViewModel { id, bankId, bankAccountId, amount, currency, reference, valueDate, otherPartyName, postingText, userSetDisplayName, category ->
AccountTransactionViewModel(id, userId, bankAccountId, mapToAmount(amount), currency, reference, mapToDate(valueDate), otherPartyName, postingText, userSetDisplayName, category) AccountTransactionViewModel(id, bankId, bankAccountId, mapToAmount(amount), currency, reference, mapToDate(valueDate), otherPartyName, postingText, userSetDisplayName, category)
}.executeAsList() }.executeAsList()
override fun getAllAccountTransactions(): List<AccountTransactionEntity> { override fun getAllAccountTransactions(): List<AccountTransactionEntity> {
return accountTransactionQueries.selectAllTransactions(::mapTransaction).executeAsList() return accountTransactionQueries.selectAllTransactions(::mapTransaction).executeAsList()
} }
override fun getAllTransactionsOfUser(user: UserEntity): List<AccountTransactionEntity> { override fun getAllTransactionsForBank(bank: BankAccessEntity): List<AccountTransactionEntity> {
return accountTransactionQueries.selectAllTransactionsOfUser(user.id, ::mapTransaction).executeAsList() return accountTransactionQueries.selectAllTransactionsOfUser(bank.id, ::mapTransaction).executeAsList()
} }
override fun getTransactionById(transactionId: Long): AccountTransactionEntity? = override fun getTransactionById(transactionId: Long): AccountTransactionEntity? =
@ -293,9 +293,9 @@ open class SqliteBankingRepository(
/** /**
* Has to be executed in a transaction in order that getting persisted AccountTransaction's id works~ * Has to be executed in a transaction in order that getting persisted AccountTransaction's id works~
*/ */
protected open suspend fun persistTransaction(userId: Long, bankAccountId: Long, transaction: AccountTransaction): AccountTransactionEntity { protected open suspend fun persistTransaction(bankId: Long, bankAccountId: Long, transaction: AccountTransaction): AccountTransactionEntity {
accountTransactionQueries.insertTransaction( accountTransactionQueries.insertTransaction(
userId, bankAccountId, bankId, bankAccountId,
mapAmount(transaction.amount), transaction.currency, transaction.reference, mapAmount(transaction.amount), transaction.currency, transaction.reference,
mapDate(transaction.bookingDate), mapDate(transaction.valueDate), mapDate(transaction.bookingDate), mapDate(transaction.valueDate),
@ -326,16 +326,16 @@ open class SqliteBankingRepository(
transaction.isReversal transaction.isReversal
) )
return AccountTransactionEntity(getLastInsertedId(), userId, bankAccountId, transaction) return AccountTransactionEntity(getLastInsertedId(), bankId, bankAccountId, transaction)
} }
private fun getLastInsertedId(): Long = private fun getLastInsertedId(): Long =
userQueries.getLastInsertedId().executeAsOne() bankQueries.getLastInsertedId().executeAsOne()
private fun mapTransaction( private fun mapTransaction(
id: Long, userId: Long, bankAccountId: Long, id: Long, bankId: Long, bankAccountId: Long,
amount: String, currency: String, reference: String?, amount: String, currency: String, reference: String?,
bookingDate: String, valueDate: String, bookingDate: String, valueDate: String,
@ -366,7 +366,7 @@ open class SqliteBankingRepository(
isReversal: Boolean isReversal: Boolean
): AccountTransactionEntity = AccountTransactionEntity( ): AccountTransactionEntity = AccountTransactionEntity(
id, id,
userId, bankAccountId, bankId, bankAccountId,
Amount(amount), currency, reference, Amount(amount), currency, reference,
mapToDate(bookingDate), mapToDate(valueDate), mapToDate(bookingDate), mapToDate(valueDate),

View File

@ -1,11 +1,10 @@
package net.codinux.banking.dataaccess.entities package net.codinux.banking.dataaccess.entities
import net.codinux.banking.client.model.BankAccess
import net.codinux.banking.client.model.BankingGroup import net.codinux.banking.client.model.BankingGroup
import net.codinux.banking.client.model.User
import net.codinux.banking.client.model.tan.TanMedium import net.codinux.banking.client.model.tan.TanMedium
import net.codinux.banking.client.model.tan.TanMethod
class UserEntity( class BankAccessEntity(
val id: Long, val id: Long,
bankCode: String, bankCode: String,
@ -13,7 +12,7 @@ class UserEntity(
password: String?, password: String?,
bankName: String, bankName: String,
bic: String, bic: String?,
customerName: String, customerName: String,
userId: String? = null, userId: String? = null,
@ -34,7 +33,7 @@ class UserEntity(
iconUrl: String? = null, iconUrl: String? = null,
wrongCredentialsEntered: Boolean = false wrongCredentialsEntered: Boolean = false
) : User(bankCode, loginName, password, bankName, bic, customerName, userId, accounts, selectedTanMethodIdentifier, tanMethods, selectedTanMediumIdentifier, tanMedia, bankingGroup, serverAddress) { ) : BankAccess(bankCode, loginName, password, bankName, bic, customerName, userId, accounts, selectedTanMethodIdentifier, tanMethods, selectedTanMediumIdentifier, tanMedia, bankingGroup, serverAddress) {
init { init {
this.userSetDisplayName = userSetDisplayName this.userSetDisplayName = userSetDisplayName
@ -45,14 +44,14 @@ class UserEntity(
} }
constructor(id: Long, user: User, bankAccounts: List<BankAccountEntity>, tanMethods: List<TanMethodEntity>, tanMedia: List<TanMediumEntity>) : this( constructor(id: Long, bank: BankAccess, bankAccounts: List<BankAccountEntity>, tanMethods: List<TanMethodEntity>, tanMedia: List<TanMediumEntity>) : this(
id, id,
user.bankCode, user.loginName, user.password, user.bankName, user.bic, user.customerName, user.userId, bank.bankCode, bank.loginName, bank.password, bank.bankName, bank.bic, bank.customerName, bank.userId,
bankAccounts, bankAccounts,
user.selectedTanMethodIdentifier, tanMethods, user.selectedTanMediumIdentifier, tanMedia, bank.selectedTanMethodIdentifier, tanMethods, bank.selectedTanMediumIdentifier, tanMedia,
user.bankingGroup, user.serverAddress, bank.bankingGroup, bank.serverAddress,
user.userSetDisplayName, user.displayIndex, bank.userSetDisplayName, bank.displayIndex,
user.iconUrl, user.wrongCredentialsEntered, bank.iconUrl, bank.wrongCredentialsEntered,
) )
} }

View File

@ -29,7 +29,7 @@ private val IconWidth = 48.dp
@Composable @Composable
fun BottomBar(showMenuDrawer: Boolean = true) { fun BottomBar(showMenuDrawer: Boolean = true) {
val users by uiState.users.collectAsState() val banks by uiState.banks.collectAsState()
val transactionsFilter by uiState.transactionsFilter.collectAsState() val transactionsFilter by uiState.transactionsFilter.collectAsState()
@ -65,7 +65,7 @@ fun BottomBar(showMenuDrawer: Boolean = true) {
} else if (selectedAccount.bankAccount != null) { } else if (selectedAccount.bankAccount != null) {
selectedAccount.bankAccount.displayName selectedAccount.bankAccount.displayName
} else { } else {
selectedAccount.user.displayName selectedAccount.bank.displayName
} }
Text(title, color = color, maxLines = 1, overflow = TextOverflow.Ellipsis) Text(title, color = color, maxLines = 1, overflow = TextOverflow.Ellipsis)
@ -122,7 +122,7 @@ fun BottomBar(showMenuDrawer: Boolean = true) {
} }
if (users.isNotEmpty()) { if (banks.isNotEmpty()) {
if (showSearchbar == false) { if (showSearchbar == false) {
Row(Modifier.fillMaxHeight().widthIn(IconWidth, IconWidth), verticalAlignment = Alignment.CenterVertically) { Row(Modifier.fillMaxHeight().widthIn(IconWidth, IconWidth), verticalAlignment = Alignment.CenterVertically) {
IconButton({ showSearchbar = true }, Modifier.width(IconWidth)) { IconButton({ showSearchbar = true }, Modifier.width(IconWidth)) {

View File

@ -2,7 +2,6 @@ package net.codinux.banking.ui.appskeleton
import androidx.compose.foundation.* import androidx.compose.foundation.*
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.material.Divider
import androidx.compose.material.Icon import androidx.compose.material.Icon
import androidx.compose.material.Text import androidx.compose.material.Text
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
@ -55,7 +54,7 @@ private val VerticalSpacing = 8.dp
fun SideMenuContent() { fun SideMenuContent() {
val drawerState = uiState.drawerState.collectAsState().value val drawerState = uiState.drawerState.collectAsState().value
val accounts = uiState.users.collectAsState().value val accounts = uiState.banks.collectAsState().value
val coroutineScope = rememberCoroutineScope() val coroutineScope = rememberCoroutineScope()

View File

@ -10,7 +10,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import net.codinux.banking.client.model.User import net.codinux.banking.client.model.BankAccess
import net.codinux.banking.client.model.BankViewInfo import net.codinux.banking.client.model.BankViewInfo
import net.codinux.banking.ui.config.DI import net.codinux.banking.ui.config.DI
import net.codinux.banking.ui.model.BankInfo import net.codinux.banking.ui.model.BankInfo
@ -21,8 +21,8 @@ private val bankIconService = DI.bankIconService
private val DefaultIconModifier: Modifier = Modifier.size(16.dp) private val DefaultIconModifier: Modifier = Modifier.size(16.dp)
@Composable @Composable
fun BankIcon(user: User?, modifier: Modifier = Modifier, iconModifier: Modifier = DefaultIconModifier, fallbackIcon: ImageVector? = null, fallbackIconTintColor: Color? = null) { fun BankIcon(bank: BankAccess?, modifier: Modifier = Modifier, iconModifier: Modifier = DefaultIconModifier, fallbackIcon: ImageVector? = null, fallbackIconTintColor: Color? = null) {
val iconUrl by remember(user?.bic) { mutableStateOf(user?.let { bankIconService.findIconForBank(it) }) } val iconUrl by remember(bank?.bic) { mutableStateOf(bank?.let { bankIconService.findIconForBank(it) }) }
BankIcon(iconUrl, modifier, iconModifier, fallbackIcon = fallbackIcon, fallbackIconTintColor = fallbackIconTintColor) BankIcon(iconUrl, modifier, iconModifier, fallbackIcon = fallbackIcon, fallbackIconTintColor = fallbackIconTintColor)
} }
@ -37,8 +37,8 @@ fun BankIcon(bank: BankInfo, modifier: Modifier = Modifier, iconModifier: Modifi
} }
@Composable @Composable
fun BankIcon(user: BankViewInfo?, modifier: Modifier = Modifier, iconModifier: Modifier = DefaultIconModifier, fallbackIcon: ImageVector? = null) { fun BankIcon(bank: BankViewInfo?, modifier: Modifier = Modifier, iconModifier: Modifier = DefaultIconModifier, fallbackIcon: ImageVector? = null) {
val iconUrl = user?.let { bankIconService.findIconForBank(it.bankName, null, it.bankingGroup) } val iconUrl = bank?.let { bankIconService.findIconForBank(it.bankName, null, it.bankingGroup) }
BankIcon(iconUrl, modifier, iconModifier, fallbackIcon = fallbackIcon) BankIcon(iconUrl, modifier, iconModifier, fallbackIcon = fallbackIcon)
} }

View File

@ -11,7 +11,7 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import net.codinux.banking.dataaccess.entities.BankAccountEntity import net.codinux.banking.dataaccess.entities.BankAccountEntity
import net.codinux.banking.dataaccess.entities.UserEntity import net.codinux.banking.dataaccess.entities.BankAccessEntity
import net.codinux.banking.ui.config.DI import net.codinux.banking.ui.config.DI
private val uiState = DI.uiState private val uiState = DI.uiState
@ -27,9 +27,9 @@ fun BanksList(
textColor: Color = Color.White, textColor: Color = Color.White,
itemModifier: Modifier = Modifier.height(48.dp).widthIn(min = 300.dp), itemModifier: Modifier = Modifier.height(48.dp).widthIn(min = 300.dp),
itemHorizontalPadding: Dp = 8.dp, itemHorizontalPadding: Dp = 8.dp,
accountSelected: ((UserEntity?, BankAccountEntity?) -> Unit)? = null accountSelected: ((BankAccessEntity?, BankAccountEntity?) -> Unit)? = null
) { ) {
val users = uiState.users.collectAsState() val banks = uiState.banks.collectAsState()
Column(modifier) { Column(modifier) {
@ -37,16 +37,16 @@ fun BanksList(
accountSelected?.invoke(null, null) accountSelected?.invoke(null, null)
} }
users.value.sortedBy { it.displayIndex }.forEach { user -> banks.value.sortedBy { it.displayIndex }.forEach { bank ->
Spacer(Modifier.fillMaxWidth().height(12.dp)) Spacer(Modifier.fillMaxWidth().height(12.dp))
NavigationMenuItem(itemModifier, user.displayName, textColor, iconSize, IconTextSpacing, itemHorizontalPadding, user, fallbackIcon = defaultBankIcon) { NavigationMenuItem(itemModifier, bank.displayName, textColor, iconSize, IconTextSpacing, itemHorizontalPadding, bank, fallbackIcon = defaultBankIcon) {
accountSelected?.invoke(user, null) accountSelected?.invoke(bank, null)
} }
user.accounts.sortedBy { it.displayIndex }.forEach { account -> bank.accounts.sortedBy { it.displayIndex }.forEach { account ->
NavigationMenuItem(itemModifier, account.displayName, textColor, iconSize, IconTextSpacing, itemHorizontalPadding, bankAccount = account) { NavigationMenuItem(itemModifier, account.displayName, textColor, iconSize, IconTextSpacing, itemHorizontalPadding, bankAccount = account) {
accountSelected?.invoke(user, account) accountSelected?.invoke(bank, account)
} }
} }
} }

View File

@ -19,7 +19,7 @@ import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import net.codinux.banking.dataaccess.entities.BankAccountEntity import net.codinux.banking.dataaccess.entities.BankAccountEntity
import net.codinux.banking.dataaccess.entities.UserEntity import net.codinux.banking.dataaccess.entities.BankAccessEntity
import net.codinux.banking.ui.config.Colors import net.codinux.banking.ui.config.Colors
import net.codinux.banking.ui.config.DI import net.codinux.banking.ui.config.DI
@ -37,7 +37,7 @@ fun NavigationMenuItem(
iconSize: Dp = 24.dp, iconSize: Dp = 24.dp,
iconTextSpacing: Dp = 24.dp, iconTextSpacing: Dp = 24.dp,
horizontalPadding: Dp = 8.dp, horizontalPadding: Dp = 8.dp,
user: UserEntity? = null, bank: BankAccessEntity? = null,
bankAccount: BankAccountEntity? = null, bankAccount: BankAccountEntity? = null,
fallbackIcon: ImageVector? = null, fallbackIcon: ImageVector? = null,
icon: (@Composable () -> Unit)? = null, icon: (@Composable () -> Unit)? = null,
@ -54,7 +54,7 @@ fun NavigationMenuItem(
modifier = modifier modifier = modifier
.clickable { onClick?.invoke() } .clickable { onClick?.invoke() }
.let { .let {
if (user != null && filterService.isSelected(user, transactionsFilter) if (bank != null && filterService.isSelected(bank, transactionsFilter)
|| bankAccount != null && filterService.isSelected(bankAccount, transactionsFilter)) { || bankAccount != null && filterService.isSelected(bankAccount, transactionsFilter)) {
it.background(Colors.AccentAsSelectionBackground, shape = RoundedCornerShape(4.dp)) it.background(Colors.AccentAsSelectionBackground, shape = RoundedCornerShape(4.dp))
} else { } else {
@ -68,7 +68,7 @@ fun NavigationMenuItem(
icon() icon()
} }
} else { } else {
BankIcon(user, Modifier.padding(end = iconTextSpacing), Modifier.size(iconSize), fallbackIcon = fallbackIcon, fallbackIconTintColor = textColor) BankIcon(bank, Modifier.padding(end = iconTextSpacing), Modifier.size(iconSize), fallbackIcon = fallbackIcon, fallbackIconTintColor = textColor)
} }
Text(text, color = textColor, modifier = Modifier.weight(1f), maxLines = 1, overflow = TextOverflow.Ellipsis) Text(text, color = textColor, modifier = Modifier.weight(1f), maxLines = 1, overflow = TextOverflow.Ellipsis)
@ -77,8 +77,8 @@ fun NavigationMenuItem(
null null
} else if (bankAccount != null) { } else if (bankAccount != null) {
bankAccount.balance bankAccount.balance
} else if (user != null) { } else if (bank != null) {
calculator.calculateBalanceOfUser(user) calculator.calculateBalanceOfBankAccess(bank)
} else { } else {
null null
} }

View File

@ -75,7 +75,7 @@ fun StateHandler(uiState: UiState, snackbarHostState: SnackbarHostState) {
} }
snackbarHostState.showSnackbar( snackbarHostState.showSnackbar(
message = "$messagePrefix für ${event.user.displayName} ${event.account.displayName}", message = "$messagePrefix für ${event.bank.displayName} ${event.account.displayName}",
actionLabel = actionLabel, actionLabel = actionLabel,
duration = SnackbarDuration.Long duration = SnackbarDuration.Long
) )

View File

@ -14,7 +14,7 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import net.codinux.banking.client.model.Amount import net.codinux.banking.client.model.Amount
import net.codinux.banking.client.model.securitiesaccount.Holding import net.codinux.banking.client.model.securitiesaccount.Holding
import net.codinux.banking.dataaccess.entities.UserEntity import net.codinux.banking.dataaccess.entities.BankAccessEntity
import net.codinux.banking.ui.config.Colors import net.codinux.banking.ui.config.Colors
import net.codinux.banking.ui.config.DI import net.codinux.banking.ui.config.DI
import net.codinux.banking.ui.forms.RoundedCornersCard import net.codinux.banking.ui.forms.RoundedCornersCard
@ -31,7 +31,7 @@ fun GroupedTransactionsListItems(
modifier: Modifier, modifier: Modifier,
transactionsToDisplay: List<AccountTransactionViewModel>, transactionsToDisplay: List<AccountTransactionViewModel>,
holdingsToDisplay: List<Holding>, holdingsToDisplay: List<Holding>,
usersById: Map<Long, UserEntity>, banksById: Map<Long, BankAccessEntity>,
transactionsGrouping: TransactionsGrouping transactionsGrouping: TransactionsGrouping
) { ) {
val groupingService = remember { TransactionsGroupingService() } val groupingService = remember { TransactionsGroupingService() }
@ -90,7 +90,7 @@ fun GroupedTransactionsListItems(
Column(Modifier.background(Color.White)) { // LazyColumn inside LazyColumn is not allowed Column(Modifier.background(Color.White)) { // LazyColumn inside LazyColumn is not allowed
monthTransactions.forEachIndexed { index, transaction -> monthTransactions.forEachIndexed { index, transaction ->
key(transaction.id) { key(transaction.id) {
TransactionListItem(usersById[transaction.userId], transaction, index, monthTransactions.size) TransactionListItem(banksById[transaction.userId], transaction, index, monthTransactions.size)
} }
} }
} }

View File

@ -9,12 +9,11 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.DpOffset import androidx.compose.ui.unit.DpOffset
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import net.codinux.banking.client.model.User import net.codinux.banking.client.model.BankAccess
import net.codinux.banking.ui.composables.BankIcon import net.codinux.banking.ui.composables.BankIcon
import net.codinux.banking.ui.composables.text.ItemDivider import net.codinux.banking.ui.composables.text.ItemDivider
import net.codinux.banking.ui.config.Colors import net.codinux.banking.ui.config.Colors
@ -28,7 +27,7 @@ private val uiSettings = DI.uiSettings
private val formatUtil = DI.formatUtil private val formatUtil = DI.formatUtil
@Composable @Composable
fun TransactionListItem(user: User?, transaction: AccountTransactionViewModel, itemIndex: Int, countItems: Int) { fun TransactionListItem(bank: BankAccess?, transaction: AccountTransactionViewModel, itemIndex: Int, countItems: Int) {
val zebraStripes by uiSettings.zebraStripes.collectAsState() val zebraStripes by uiSettings.zebraStripes.collectAsState()
val showBankIcons by uiSettings.showBankIcons.collectAsState() val showBankIcons by uiSettings.showBankIcons.collectAsState()
@ -51,7 +50,7 @@ fun TransactionListItem(user: User?, transaction: AccountTransactionViewModel, i
val transactionEntity = DI.bankingService.getTransaction(transaction.id) val transactionEntity = DI.bankingService.getTransaction(transaction.id)
DI.uiState.showTransferMoneyDialogData.value = ShowTransferMoneyDialogData( DI.uiState.showTransferMoneyDialogData.value = ShowTransferMoneyDialogData(
DI.uiState.users.value.firstNotNullOf { it.accounts.firstOrNull { it.id == transaction.bankAccountId } }, DI.uiState.banks.value.firstNotNullOf { it.accounts.firstOrNull { it.id == transaction.bankAccountId } },
transaction.otherPartyName, transaction.otherPartyName,
transactionEntity?.otherPartyBankId, transactionEntity?.otherPartyBankId,
transactionEntity?.otherPartyAccountId, transactionEntity?.otherPartyAccountId,
@ -79,7 +78,7 @@ fun TransactionListItem(user: User?, transaction: AccountTransactionViewModel, i
Column(Modifier.weight(1f)) { Column(Modifier.weight(1f)) {
Row { Row {
if (showBankIcons) { if (showBankIcons) {
BankIcon(user, Modifier.padding(end = 6.dp)) BankIcon(bank, Modifier.padding(end = 6.dp))
} }
Text( Text(

View File

@ -25,9 +25,9 @@ private val formatUtil = DI.formatUtil
@Composable @Composable
fun TransactionsList(uiState: UiState, uiSettings: UiSettings, isMobile: Boolean = true) { fun TransactionsList(uiState: UiState, uiSettings: UiSettings, isMobile: Boolean = true) {
val users by uiState.users.collectAsState() val banks by uiState.banks.collectAsState()
val usersById by remember(users) { val banksById by remember(banks) {
derivedStateOf { users.associateBy { it.id } } derivedStateOf { banks.associateBy { it.id } }
} }
val transactionsFilter by uiState.transactionsFilter.collectAsState() val transactionsFilter by uiState.transactionsFilter.collectAsState()
@ -59,13 +59,13 @@ fun TransactionsList(uiState: UiState, uiSettings: UiSettings, isMobile: Boolean
Spacer(Modifier.weight(1f)) Spacer(Modifier.weight(1f))
if (showBalance) { if (showBalance) {
val balance = calculator.calculateBalanceOfDisplayedTransactions(transactionsToDisplay, users, transactionsFilter) val balance = calculator.calculateBalanceOfDisplayedTransactions(transactionsToDisplay, banks, transactionsFilter)
Text(formatUtil.formatAmount(balance, "EUR"), color = formatUtil.getColorForAmount(balance, showColoredAmounts)) Text(formatUtil.formatAmount(balance, "EUR"), color = formatUtil.getColorForAmount(balance, showColoredAmounts))
} }
} }
if (transactionsGrouping != TransactionsGrouping.None) { if (transactionsGrouping != TransactionsGrouping.None) {
GroupedTransactionsListItems(transactionsListModifier, transactionsToDisplay, holdingsToDisplay, usersById, transactionsGrouping) GroupedTransactionsListItems(transactionsListModifier, transactionsToDisplay, holdingsToDisplay, banksById, transactionsGrouping)
} else { } else {
LazyColumn(transactionsListModifier, contentPadding = PaddingValues(top = 8.dp, bottom = 16.dp)) { LazyColumn(transactionsListModifier, contentPadding = PaddingValues(top = 8.dp, bottom = 16.dp)) {
itemsIndexed(holdingsToDisplay) { index, holding -> itemsIndexed(holdingsToDisplay) { index, holding ->
@ -76,7 +76,7 @@ fun TransactionsList(uiState: UiState, uiSettings: UiSettings, isMobile: Boolean
itemsIndexed(transactionsToDisplay) { index, transaction -> itemsIndexed(transactionsToDisplay) { index, transaction ->
key(transaction.id) { key(transaction.id) {
TransactionListItem(usersById[transaction.userId], transaction, index, transactionsToDisplay.size) TransactionListItem(banksById[transaction.userId], transaction, index, transactionsToDisplay.size)
} }
} }
} }

View File

@ -94,9 +94,9 @@ fun EnterTanDialog(tanChallengeReceived: TanChallengeReceived, onDismiss: () ->
Column(Modifier.fillMaxWidth()) { Column(Modifier.fillMaxWidth()) {
Column(Modifier.fillMaxWidth()) { Column(Modifier.fillMaxWidth()) {
Row { Row {
BankIcon(challenge.user, Modifier.padding(end = 6.dp)) BankIcon(challenge.bank, Modifier.padding(end = 6.dp))
Text("${challenge.user.bankName}, Nutzer ${challenge.user.loginName}${challenge.account?.let { ", Konto ${it.productName ?: it.identifier}" } ?: ""}") Text("${challenge.bank.bankName}, Nutzer ${challenge.bank.loginName}${challenge.account?.let { ", Konto ${it.productName ?: it.identifier}" } ?: ""}")
} }
Text( Text(
"TAN benötigt ${Internationalization.getTextForActionRequiringTan(challenge.forAction)}", "TAN benötigt ${Internationalization.getTextForActionRequiringTan(challenge.forAction)}",

View File

@ -38,11 +38,11 @@ fun TransferMoneyDialog(
data: ShowTransferMoneyDialogData, data: ShowTransferMoneyDialogData,
onDismiss: () -> Unit, onDismiss: () -> Unit,
) { ) {
val users = uiState.users.value val banks = uiState.banks.value
val accountsToUser = users.sortedBy { it.displayIndex } val accountsToBank = banks.sortedBy { it.displayIndex }
.flatMap { user -> user.accounts.sortedBy { it.displayIndex }.map { it to user } }.toMap() .flatMap { bank -> bank.accounts.sortedBy { it.displayIndex }.map { it to bank } }.toMap()
val accountsSupportingTransferringMoney = users.flatMap { it.accounts } val accountsSupportingTransferringMoney = banks.flatMap { it.accounts }
.filter { it.supportsMoneyTransfer } .filter { it.supportsMoneyTransfer }
if (accountsSupportingTransferringMoney.isEmpty()) { if (accountsSupportingTransferringMoney.isEmpty()) {
@ -98,7 +98,7 @@ fun TransferMoneyDialog(
transferMoneyJob = coroutineScope.launch(Dispatchers.IOorDefault) { transferMoneyJob = coroutineScope.launch(Dispatchers.IOorDefault) {
val successful = bankingService.transferMoney( val successful = bankingService.transferMoney(
accountsToUser[senderAccount]!!, senderAccount, accountsToBank[senderAccount]!!, senderAccount,
recipientName, recipientAccountIdentifier, recipientName, recipientAccountIdentifier,
Amount(amount), // TODO: verify entered amount is valid Amount(amount), // TODO: verify entered amount is valid
"EUR", // TODO: add input field for currency "EUR", // TODO: add input field for currency
@ -132,13 +132,13 @@ fun TransferMoneyDialog(
Select( Select(
"Konto", "Konto",
accountsSupportingTransferringMoney, senderAccount, { senderAccount = it }, accountsSupportingTransferringMoney, senderAccount, { senderAccount = it },
{ account -> "${accountsToUser[account]?.displayName} ${account.displayName}" }, { account -> "${accountsToBank[account]?.displayName} ${account.displayName}" },
leadingIcon = { BankIcon(accountsToUser[senderAccount]) } leadingIcon = { BankIcon(accountsToBank[senderAccount]) }
) { account -> ) { account ->
Row(Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) { Row(Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) {
BankIcon(accountsToUser[account], Modifier.padding(end = 6.dp)) BankIcon(accountsToBank[account], Modifier.padding(end = 6.dp))
Text("${accountsToUser[account]?.displayName} ${account.displayName}") Text("${accountsToBank[account]?.displayName} ${account.displayName}")
} }
} }
} }

View File

@ -2,7 +2,7 @@ package net.codinux.banking.ui.model
import androidx.compose.runtime.* import androidx.compose.runtime.*
import net.codinux.banking.dataaccess.entities.BankAccountEntity import net.codinux.banking.dataaccess.entities.BankAccountEntity
import net.codinux.banking.dataaccess.entities.UserEntity import net.codinux.banking.dataaccess.entities.BankAccessEntity
class AccountTransactionsFilter { class AccountTransactionsFilter {
@ -19,7 +19,7 @@ class AccountTransactionsFilter {
val selectedAccount: BankAccountFilter? val selectedAccount: BankAccountFilter?
get() = selectedAccounts.value.firstOrNull() get() = selectedAccounts.value.firstOrNull()
fun selectedAccountChanged(user: UserEntity?, bankAccount: BankAccountEntity?) { fun selectedAccountChanged(user: BankAccessEntity?, bankAccount: BankAccountEntity?) {
selectedAccounts.value = if (user == null) { selectedAccounts.value = if (user == null) {
emptyList() emptyList()
} else { } else {

View File

@ -1,9 +1,9 @@
package net.codinux.banking.ui.model package net.codinux.banking.ui.model
import net.codinux.banking.dataaccess.entities.BankAccountEntity import net.codinux.banking.dataaccess.entities.BankAccountEntity
import net.codinux.banking.dataaccess.entities.UserEntity import net.codinux.banking.dataaccess.entities.BankAccessEntity
data class BankAccountFilter( data class BankAccountFilter(
val user: UserEntity, val bank: BankAccessEntity,
val bankAccount: BankAccountEntity? = null val bankAccount: BankAccountEntity? = null
) )

View File

@ -1,12 +1,12 @@
package net.codinux.banking.ui.model.events package net.codinux.banking.ui.model.events
import net.codinux.banking.client.model.BankAccount import net.codinux.banking.client.model.BankAccount
import net.codinux.banking.client.model.User import net.codinux.banking.client.model.BankAccess
import net.codinux.banking.client.model.securitiesaccount.Holding import net.codinux.banking.client.model.securitiesaccount.Holding
import net.codinux.banking.ui.model.AccountTransactionViewModel import net.codinux.banking.ui.model.AccountTransactionViewModel
data class AccountTransactionsRetrievedEvent( data class AccountTransactionsRetrievedEvent(
val user: User, val bank: BankAccess,
val account: BankAccount, val account: BankAccount,
val newTransactions: List<AccountTransactionViewModel>, val newTransactions: List<AccountTransactionViewModel>,
val updatedHoldings: List<Holding> = emptyList() val updatedHoldings: List<Holding> = emptyList()

View File

@ -2,7 +2,7 @@ package net.codinux.banking.ui.service
import net.codinux.banking.dataaccess.entities.BankAccountEntity import net.codinux.banking.dataaccess.entities.BankAccountEntity
import net.codinux.banking.dataaccess.entities.HoldingEntity import net.codinux.banking.dataaccess.entities.HoldingEntity
import net.codinux.banking.dataaccess.entities.UserEntity import net.codinux.banking.dataaccess.entities.BankAccessEntity
import net.codinux.banking.ui.model.AccountTransactionViewModel import net.codinux.banking.ui.model.AccountTransactionViewModel
import net.codinux.banking.ui.model.AccountTransactionsFilter import net.codinux.banking.ui.model.AccountTransactionsFilter
import net.codinux.banking.ui.model.BankAccountFilter import net.codinux.banking.ui.model.BankAccountFilter
@ -83,14 +83,14 @@ class AccountTransactionsFilterService {
|| holding.wkn?.contains(searchTerm, true) == true || holding.wkn?.contains(searchTerm, true) == true
fun isSelected(user: UserEntity, transactionsFilter: AccountTransactionsFilter): Boolean { fun isSelected(user: BankAccessEntity, transactionsFilter: AccountTransactionsFilter): Boolean {
if (transactionsFilter.showAllAccounts) { if (transactionsFilter.showAllAccounts) {
return false return false
} }
val filter = transactionsFilter.selectedAccount val filter = transactionsFilter.selectedAccount
return filter?.user == user && filter.bankAccount == null return filter?.bank == user && filter.bankAccount == null
} }
fun isSelected(bankAccount: BankAccountEntity, transactionsFilter: AccountTransactionsFilter): Boolean { fun isSelected(bankAccount: BankAccountEntity, transactionsFilter: AccountTransactionsFilter): Boolean {

View File

@ -1,11 +1,11 @@
package net.codinux.banking.ui.service package net.codinux.banking.ui.service
import net.codinux.banking.client.model.BankingGroup import net.codinux.banking.client.model.BankingGroup
import net.codinux.banking.client.model.User import net.codinux.banking.client.model.BankAccess
class BankIconService { // TODO: extract to a common library class BankIconService { // TODO: extract to a common library
fun findIconForBank(user: User) = findIconForBank(user.bankName, user.bic, user.bankingGroup) fun findIconForBank(bank: BankAccess) = findIconForBank(bank.bankName, bank.bic, bank.bankingGroup)
fun findIconForBank(bankName: String, bic: String? = null, bankingGroup: BankingGroup? = null): String? = when (bankingGroup) { fun findIconForBank(bankName: String, bic: String? = null, bankingGroup: BankingGroup? = null): String? = when (bankingGroup) {
BankingGroup.Sparkasse -> "https://sparkasse.de/favicon-32x32.png" BankingGroup.Sparkasse -> "https://sparkasse.de/favicon-32x32.png"

View File

@ -45,21 +45,21 @@ class BankingService(
suspend fun init() { suspend fun init() {
try { try {
uiState.users.value = getAllUsers() uiState.banks.value = getAllBanks()
uiState.transactions.value = getAllAccountTransactionsAsViewModel() uiState.transactions.value = getAllAccountTransactionsAsViewModel()
uiState.holdings.value = uiState.users.value.flatMap { it.accounts }.flatMap { it.holdings } uiState.holdings.value = uiState.banks.value.flatMap { it.accounts }.flatMap { it.holdings }
} catch (e: Throwable) { } catch (e: Throwable) {
log.error(e) { "Could not read all user accounts and account transactions from repository" } log.error(e) { "Could not read all banks and account transactions from repository" }
} }
} }
fun getAllUsers() = bankingRepository.getAllUsers() fun getAllBanks() = bankingRepository.getAllBanks()
fun getAllAccountTransactions() = bankingRepository.getAllAccountTransactions() fun getAllAccountTransactions() = bankingRepository.getAllAccountTransactions()
fun getAllTransactionsOfUser(user: UserEntity) = bankingRepository.getAllTransactionsOfUser(user) fun getAllTransactionsForBank(bank: BankAccessEntity) = bankingRepository.getAllTransactionsForBank(bank)
fun getAllAccountTransactionsAsViewModel() = bankingRepository.getAllAccountTransactionsAsViewModel() fun getAllAccountTransactionsAsViewModel() = bankingRepository.getAllAccountTransactionsAsViewModel()
@ -101,21 +101,21 @@ class BankingService(
private suspend fun handleSuccessfulGetAccountDataResponse(response: GetAccountDataResponse) { private suspend fun handleSuccessfulGetAccountDataResponse(response: GetAccountDataResponse) {
try { try {
val newUser = response.user val newBank = response.bank
newUser.displayIndex = uiState.users.value.size newBank.displayIndex = uiState.banks.value.size
val newUserEntity = bankingRepository.persistUser(newUser) val newBankEntity = bankingRepository.persistBank(newBank)
log.info { "Saved user account $newUserEntity with ${newUserEntity.accounts.flatMap { it.bookedTransactionsEntities }.size} transactions" } log.info { "Saved bank $newBankEntity with ${newBankEntity.accounts.flatMap { it.bookedTransactionsEntities }.size} transactions" }
val users = uiState.users.value.toMutableList() val banks = uiState.banks.value.toMutableList()
users.add(newUserEntity) banks.add(newBankEntity)
uiState.users.value = users uiState.banks.value = banks
updateTransactionsInUi(newUserEntity.accounts.flatMap { it.bookedTransactionsEntities }) updateTransactionsInUi(newBankEntity.accounts.flatMap { it.bookedTransactionsEntities })
updateHoldingsInUi(newUserEntity.accounts.flatMap { it.holdings }, emptyList()) updateHoldingsInUi(newBankEntity.accounts.flatMap { it.holdings }, emptyList())
} catch (e: Throwable) { } catch (e: Throwable) {
log.error(e) { "Could not save user account ${response.user}" } log.error(e) { "Could not save bank ${response.bank}" }
} }
} }
@ -123,45 +123,45 @@ class BankingService(
suspend fun updateAccountTransactions() { suspend fun updateAccountTransactions() {
val selectedAccount = uiState.transactionsFilter.value.selectedAccount val selectedAccount = uiState.transactionsFilter.value.selectedAccount
if (selectedAccount != null) { if (selectedAccount != null) {
updateAccountTransactions(selectedAccount.user, selectedAccount.bankAccount) updateAccountTransactions(selectedAccount.bank, selectedAccount.bankAccount)
} else { } else {
uiState.users.value.forEach { user -> uiState.banks.value.forEach { bank ->
updateAccountTransactions(user) updateAccountTransactions(bank)
} }
} }
} }
private suspend fun updateAccountTransactions(user: UserEntity, bankAccount: BankAccountEntity? = null) { private suspend fun updateAccountTransactions(bank: BankAccessEntity, bankAccount: BankAccountEntity? = null) {
withContext(Dispatchers.IOorDefault) { withContext(Dispatchers.IOorDefault) {
try { try {
val response = client.updateAccountTransactionsAsync(user, bankAccount?.let { listOf(it) }) val response = client.updateAccountTransactionsAsync(bank, bankAccount?.let { listOf(it) })
if (response.type == ResponseType.Success && response.data != null) { if (response.type == ResponseType.Success && response.data != null) {
handleSuccessfulUpdateAccountTransactionsResponse(user, response.data!!) handleSuccessfulUpdateAccountTransactionsResponse(bank, response.data!!)
} else { } else {
handleUnsuccessfulBankingClientResponse(BankingClientAction.UpdateAccountTransactions, response) handleUnsuccessfulBankingClientResponse(BankingClientAction.UpdateAccountTransactions, response)
} }
} catch (e: Throwable) { } catch (e: Throwable) {
log.error(e) { "Could not update account transactions for $user" } log.error(e) { "Could not update account transactions for $bank" }
} }
} }
} }
private suspend fun handleSuccessfulUpdateAccountTransactionsResponse(user: UserEntity, responses: List<GetTransactionsResponse>) { private suspend fun handleSuccessfulUpdateAccountTransactionsResponse(bank: BankAccessEntity, responses: List<GetTransactionsResponse>) {
try { try {
// TODO: when user gets updated by BankingClient, also update user in database // TODO: when bank gets updated by BankingClient, also update bank in database
// val newUser = response.user // val newUser = response.bank
// val newUserEntity = bankingRepository.persistUser(newUser) // val newUserEntity = bankingRepository.persistUser(newUser)
// //
// log.info { "Saved user account $newUserEntity with ${newUserEntity.accounts.flatMap { it.bookedTransactionsEntities }.size} transactions" } // log.info { "Saved user account $newUserEntity with ${newUserEntity.accounts.flatMap { it.bookedTransactionsEntities }.size} transactions" }
val userTransactions = getAllTransactionsOfUser(user) val transactionsForBank = getAllTransactionsForBank(bank)
responses.forEach { response -> responses.forEach { response ->
val account = (response.account as? BankAccountEntity) ?: user.accounts.first { it.identifier == response.account.identifier && it.subAccountNumber == response.account.subAccountNumber } val account = (response.account as? BankAccountEntity) ?: bank.accounts.first { it.identifier == response.account.identifier && it.subAccountNumber == response.account.subAccountNumber }
// TODO: update BankAccount and may updated Transactions in database // TODO: update BankAccount and may updated Transactions in database
val existingAccountTransactions = userTransactions.filter { it.bankAccountId == account.id } val existingAccountTransactions = transactionsForBank.filter { it.bankAccountId == account.id }
val newTransactions = modelService.findNewTransactions(response.bookedTransactions, existingAccountTransactions) val newTransactions = modelService.findNewTransactions(response.bookedTransactions, existingAccountTransactions)
@ -189,10 +189,10 @@ class BankingService(
updateHoldingsInUi(persistedNewHoldings, deletedHoldings) updateHoldingsInUi(persistedNewHoldings, deletedHoldings)
val transactionsViewModel = updateTransactionsInUi(newTransactionsEntities) val transactionsViewModel = updateTransactionsInUi(newTransactionsEntities)
uiState.dispatchNewTransactionsRetrieved(AccountTransactionsRetrievedEvent(user, account, transactionsViewModel, response.holdings)) uiState.dispatchNewTransactionsRetrieved(AccountTransactionsRetrievedEvent(bank, account, transactionsViewModel, response.holdings))
} }
} catch (e: Throwable) { } catch (e: Throwable) {
log.error(e) { "Could not save updated account transactions for user $user" } log.error(e) { "Could not save updated account transactions for bank $bank" }
} }
} }
@ -235,11 +235,11 @@ class BankingService(
} }
suspend fun transferMoney(user: UserEntity, account: BankAccountEntity, suspend fun transferMoney(bank: BankAccessEntity, account: BankAccountEntity,
recipientName: String, recipientAccountIdentifier: String, amount: Amount, currency: String, recipientName: String, recipientAccountIdentifier: String, amount: Amount, currency: String,
paymentReference: String? = null, instantTransfer: Boolean = false, recipientBankIdentifier: String? = null): Boolean { paymentReference: String? = null, instantTransfer: Boolean = false, recipientBankIdentifier: String? = null): Boolean {
val response = client.transferMoneyAsync(TransferMoneyRequestForUser( val response = client.transferMoneyAsync(TransferMoneyRequestForUser(
user.bankCode, user.loginName, user.password!!, bank.bankCode, bank.loginName, bank.password!!,
BankAccountIdentifier(account.identifier, account.subAccountNumber, account.iban), // TODO: use BankingClient's one BankAccountIdentifier(account.identifier, account.subAccountNumber, account.iban), // TODO: use BankingClient's one
recipientName, recipientAccountIdentifier, recipientBankIdentifier, recipientName, recipientAccountIdentifier, recipientBankIdentifier,
amount, "EUR", amount, "EUR",
@ -251,7 +251,7 @@ class BankingService(
} else if (response.type == ResponseType.Success) { } else if (response.type == ResponseType.Success) {
uiState.dispatchTransferredMoney(TransferredMoneyEvent(recipientName, amount, currency)) uiState.dispatchTransferredMoney(TransferredMoneyEvent(recipientName, amount, currency))
updateAccountTransactions(user, account) updateAccountTransactions(bank, account)
} }
return response.type == ResponseType.Success return response.type == ResponseType.Success

View File

@ -1,7 +1,7 @@
package net.codinux.banking.ui.service package net.codinux.banking.ui.service
import net.codinux.banking.client.model.* import net.codinux.banking.client.model.*
import net.codinux.banking.dataaccess.entities.UserEntity import net.codinux.banking.dataaccess.entities.BankAccessEntity
import net.codinux.banking.ui.model.AccountTransactionViewModel import net.codinux.banking.ui.model.AccountTransactionViewModel
import net.codinux.banking.ui.model.AccountTransactionsFilter import net.codinux.banking.ui.model.AccountTransactionsFilter
@ -10,8 +10,8 @@ class CalculatorService {
fun sumTransactions(transactions: Collection<AccountTransactionViewModel>): Amount = fun sumTransactions(transactions: Collection<AccountTransactionViewModel>): Amount =
transactions.map { it.amount }.sum() transactions.map { it.amount }.sum()
fun calculateBalanceOfUser(user: User): Amount = fun calculateBalanceOfBankAccess(bank: BankAccess): Amount =
sumAmounts(user.accounts.map { it.balance }) sumAmounts(bank.accounts.map { it.balance })
fun sumAmounts(amounts: Collection<Amount>): Amount = fun sumAmounts(amounts: Collection<Amount>): Amount =
amounts.sum() amounts.sum()
@ -22,9 +22,9 @@ class CalculatorService {
fun sumExpenses(transactions: Collection<AccountTransactionViewModel>): Amount = fun sumExpenses(transactions: Collection<AccountTransactionViewModel>): Amount =
sumAmounts(transactions.map { it.amount }.filter { it.isNegative }) sumAmounts(transactions.map { it.amount }.filter { it.isNegative })
fun calculateBalanceOfDisplayedTransactions(transactions: Collection<AccountTransactionViewModel>, users: Collection<UserEntity>, filter: AccountTransactionsFilter): Amount { fun calculateBalanceOfDisplayedTransactions(transactions: Collection<AccountTransactionViewModel>, banks: Collection<BankAccessEntity>, filter: AccountTransactionsFilter): Amount {
if (filter.noFiltersApplied) { if (filter.noFiltersApplied) {
return sumAmounts(users.flatMap { it.accounts.map { it.balance } }) return sumAmounts(banks.flatMap { it.accounts.map { it.balance } })
} }
val selectedAccount = filter.selectedAccount val selectedAccount = filter.selectedAccount
@ -33,7 +33,7 @@ class CalculatorService {
if (selectedAccount.bankAccount != null) { if (selectedAccount.bankAccount != null) {
selectedAccount.bankAccount.balance selectedAccount.bankAccount.balance
} else { } else {
calculateBalanceOfUser(selectedAccount.user) calculateBalanceOfBankAccess(selectedAccount.bank)
} }
} else { } else {
sumTransactions(transactions) sumTransactions(transactions)

View File

@ -8,7 +8,7 @@ import kotlinx.coroutines.flow.MutableStateFlow
import net.codinux.banking.client.model.tan.EnterTanResult import net.codinux.banking.client.model.tan.EnterTanResult
import net.codinux.banking.client.model.tan.TanChallenge import net.codinux.banking.client.model.tan.TanChallenge
import net.codinux.banking.dataaccess.entities.HoldingEntity import net.codinux.banking.dataaccess.entities.HoldingEntity
import net.codinux.banking.dataaccess.entities.UserEntity import net.codinux.banking.dataaccess.entities.BankAccessEntity
import net.codinux.banking.ui.model.* import net.codinux.banking.ui.model.*
import net.codinux.banking.ui.model.error.ApplicationError import net.codinux.banking.ui.model.error.ApplicationError
import net.codinux.banking.ui.model.error.BankingClientError import net.codinux.banking.ui.model.error.BankingClientError
@ -18,7 +18,7 @@ import net.codinux.banking.ui.model.events.TransferredMoneyEvent
class UiState : ViewModel() { class UiState : ViewModel() {
val users = MutableStateFlow<List<UserEntity>>(emptyList()) val banks = MutableStateFlow<List<BankAccessEntity>>(emptyList())
val transactions = MutableStateFlow<List<AccountTransactionViewModel>>(emptyList()) val transactions = MutableStateFlow<List<AccountTransactionViewModel>>(emptyList())

View File

@ -8,7 +8,7 @@ CREATE TABLE IF NOT EXISTS User (
password TEXT, password TEXT,
bankName TEXT NOT NULL, bankName TEXT NOT NULL,
bic TEXT NOT NULL, bic TEXT,
customerName TEXT NOT NULL, customerName TEXT NOT NULL,
userId TEXT, userId TEXT,
@ -31,7 +31,7 @@ CREATE TABLE IF NOT EXISTS User (
); );
insertUser: insertBank:
INSERT INTO User( INSERT INTO User(
bankCode, loginName, password, bankCode, loginName, password,
@ -72,7 +72,7 @@ VALUES(
); );
selectAllUsers: selectAllBanks:
SELECT User.* SELECT User.*
FROM User; FROM User;

View File

@ -13,8 +13,8 @@ import net.codinux.banking.ui.model.TanChallengeReceived
@Composable @Composable
fun EnterTanDialogPreview_EnterTan() { fun EnterTanDialogPreview_EnterTan() {
val tanMethods = listOf(TanMethod("Zeig mich an", TanMethodType.AppTan, "902")) val tanMethods = listOf(TanMethod("Zeig mich an", TanMethodType.AppTan, "902"))
val user = BankViewInfo("12345678", "SupiDupiNutzer", "Abzockbank", BankingGroup.Postbank) val bank = BankViewInfo("12345678", "SupiDupiNutzer", "Abzockbank", BankingGroup.Postbank)
val tanChallenge = TanChallenge(TanChallengeType.EnterTan, ActionRequiringTan.GetAccountInfo, "Geben Sie die TAN ein", tanMethods.first().identifier, tanMethods, user = user) val tanChallenge = TanChallenge(TanChallengeType.EnterTan, ActionRequiringTan.GetAccountInfo, "Geben Sie die TAN ein", tanMethods.first().identifier, tanMethods, bank = bank)
EnterTanDialog(TanChallengeReceived(tanChallenge) { }) { } EnterTanDialog(TanChallengeReceived(tanChallenge) { }) { }
} }
@ -27,9 +27,9 @@ fun EnterTanDialogPreview_TanImage() {
val tanMethod = TanMethod("photoTAN-Verfahren", TanMethodType.photoTan, "902", 6, AllowedTanFormat.Numeric) val tanMethod = TanMethod("photoTAN-Verfahren", TanMethodType.photoTan, "902", 6, AllowedTanFormat.Numeric)
val tanImage = TanImage("image/png", tanImageBytes) val tanImage = TanImage("image/png", tanImageBytes)
val user = BankViewInfo("10010010", "Ihr krasser Login Name", "Phantasie Bank", BankingGroup.Comdirect) val bank = BankViewInfo("10010010", "Ihr krasser Login Name", "Phantasie Bank", BankingGroup.Comdirect)
val tanChallenge = TanChallenge(TanChallengeType.Image, ActionRequiringTan.GetAccountInfo, "Geben Sie die TAN ein", tanMethod.identifier, listOf(tanMethod), null, emptyList(), tanImage, null, user) val tanChallenge = TanChallenge(TanChallengeType.Image, ActionRequiringTan.GetAccountInfo, "Geben Sie die TAN ein", tanMethod.identifier, listOf(tanMethod), null, emptyList(), tanImage, null, bank)
EnterTanDialog(TanChallengeReceived(tanChallenge) { }) { } EnterTanDialog(TanChallengeReceived(tanChallenge) { }) { }
} }
@ -50,10 +50,10 @@ fun EnterTanDialogPreview_WithMultipleTanMedia() { // shows that dialog is reall
TanMedium(TanMediumType.TanGenerator, "SparkassenCard (Debitkarte)", TanMediumStatus.Used, TanGeneratorTanMedium("5432109876")) TanMedium(TanMediumType.TanGenerator, "SparkassenCard (Debitkarte)", TanMediumStatus.Used, TanGeneratorTanMedium("5432109876"))
) )
val user = BankViewInfo("10010010", "Ihr krasser Login Name", "Eine ganz gewöhnliche Sparkasse", BankingGroup.Sparkasse) val bank = BankViewInfo("10010010", "Ihr krasser Login Name", "Eine ganz gewöhnliche Sparkasse", BankingGroup.Sparkasse)
val account = BankAccountViewInfo("12345678", null, BankAccountType.CheckingAccount, null, "Giro Konto") val account = BankAccountViewInfo("12345678", null, BankAccountType.CheckingAccount, null, "Giro Konto")
val tanChallenge = TanChallenge(TanChallengeType.Image, ActionRequiringTan.GetTransactions, "Sie möchten eine \"Umsatzabfrage\" freigeben: Bitte bestätigen Sie den \"Startcode 80061030\" mit der Taste \"OK\".", "913", tanMethods, "SparkassenCard (Debitkarte)", tanMedia, tanImage, null, user, account) val tanChallenge = TanChallenge(TanChallengeType.Image, ActionRequiringTan.GetTransactions, "Sie möchten eine \"Umsatzabfrage\" freigeben: Bitte bestätigen Sie den \"Startcode 80061030\" mit der Taste \"OK\".", "913", tanMethods, "SparkassenCard (Debitkarte)", tanMedia, tanImage, null, bank, account)
EnterTanDialog(TanChallengeReceived(tanChallenge) { }) { } EnterTanDialog(TanChallengeReceived(tanChallenge) { }) { }
} }

View File

@ -27,29 +27,29 @@ class SqliteBankingRepositoryTest {
val bankAccounts = listOf( val bankAccounts = listOf(
BankAccount("12345", null, null, null, "Monika Tester", BankAccountType.CheckingAccount, balance = Amount("12.34"), retrievedTransactionsFrom = LocalDate(2024, 5, 7), features = setOf(BankAccountFeatures.RetrieveBalance, BankAccountFeatures.InstantTransfer), serverTransactionsRetentionDays = 320) BankAccount("12345", null, null, null, "Monika Tester", BankAccountType.CheckingAccount, balance = Amount("12.34"), retrievedTransactionsFrom = LocalDate(2024, 5, 7), features = setOf(BankAccountFeatures.RetrieveBalance, BankAccountFeatures.InstantTransfer), serverTransactionsRetentionDays = 320)
) )
val user = User("12345678", "SupiDupiUser", "geheim", "Abzock-Bank", "ABCDDEBBXXX", "Monika Tester", accounts = bankAccounts, bankingGroup = BankingGroup.DKB, serverAddress = "").apply { val bank = BankAccess("12345678", "SupiDupiUser", "geheim", "Abzock-Bank", "ABCDDEBBXXX", "Monika Tester", accounts = bankAccounts, bankingGroup = BankingGroup.DKB, serverAddress = "").apply {
wrongCredentialsEntered = true wrongCredentialsEntered = true
displayIndex = 99 displayIndex = 99
} }
val persisted = underTest.persistUser(user) val persisted = underTest.persistBank(bank)
assertNotNull(persisted.id) assertNotNull(persisted.id)
assertEquals(user.bankCode, persisted.bankCode) assertEquals(bank.domesticBankCode, persisted.domesticBankCode)
assertEquals(user.loginName, persisted.loginName) assertEquals(bank.loginName, persisted.loginName)
assertEquals(user.password, persisted.password) assertEquals(bank.password, persisted.password)
assertEquals(user.bankName, persisted.bankName) assertEquals(bank.bankName, persisted.bankName)
assertEquals(user.bic, persisted.bic) assertEquals(bank.bic, persisted.bic)
assertEquals(user.customerName, persisted.customerName) assertEquals(bank.customerName, persisted.customerName)
assertEquals(user.userId, persisted.userId) assertEquals(bank.userId, persisted.userId)
assertEquals(user.bankingGroup, persisted.bankingGroup) assertEquals(bank.bankingGroup, persisted.bankingGroup)
assertEquals(user.wrongCredentialsEntered, persisted.wrongCredentialsEntered) assertEquals(bank.wrongCredentialsEntered, persisted.wrongCredentialsEntered)
assertEquals(user.displayIndex, persisted.displayIndex) assertEquals(bank.displayIndex, persisted.displayIndex)
assertEquals(1, persisted.accounts.size) assertEquals(1, persisted.accounts.size)