Compare commits
16 Commits
41586b0148
...
3c3734d565
Author | SHA1 | Date |
---|---|---|
dankito | 3c3734d565 | |
dankito | af99608dd6 | |
dankito | a11c862ecc | |
dankito | f6dfada5bf | |
dankito | b4ea02bbd8 | |
dankito | 2d472d1683 | |
dankito | de36a403df | |
dankito | debae8b7ca | |
dankito | 09a1a41c20 | |
dankito | 75ccc648c5 | |
dankito | 34f2fca126 | |
dankito | e883713eba | |
dankito | 4cd727b5c0 | |
dankito | 96c8cf59cd | |
dankito | d7a9acbe56 | |
dankito | 9330b72726 |
|
@ -137,10 +137,10 @@ android {
|
|||
sourceSets["main"].resources.srcDirs("src/commonMain/resources")
|
||||
|
||||
defaultConfig {
|
||||
applicationId = "net.codinux.banking.ui"
|
||||
applicationId = "net.codinux.banking.android" // the appId of the old Bankmeister app to be able to use the old PlayStore entry
|
||||
minSdk = libs.versions.android.minSdk.get().toInt()
|
||||
targetSdk = libs.versions.android.targetSdk.get().toInt()
|
||||
versionCode = 1
|
||||
versionCode = 10
|
||||
versionName = "1.0.0-Alpha-12"
|
||||
}
|
||||
packaging {
|
||||
|
@ -148,18 +148,26 @@ android {
|
|||
excludes += "/META-INF/{AL2.0,LGPL2.1}"
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
getByName("release") {
|
||||
named("debug") {
|
||||
applicationIdSuffix = ".develop"
|
||||
}
|
||||
|
||||
named("release") {
|
||||
isMinifyEnabled = false
|
||||
}
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_11
|
||||
targetCompatibility = JavaVersion.VERSION_11
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
compose = true
|
||||
}
|
||||
|
||||
dependencies {
|
||||
debugImplementation(compose.uiTooling)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">Bankmeister Dev</string>
|
||||
</resources>
|
|
@ -13,8 +13,8 @@ import net.codinux.banking.ui.model.TanChallengeReceived
|
|||
@Composable
|
||||
fun EnterTanDialogPreview_EnterTan() {
|
||||
val tanMethods = listOf(TanMethod("Zeig mich an", TanMethodType.AppTan, "902"))
|
||||
val user = 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 bank = BankViewInfo("12345678", "SupiDupiNutzer", "Abzockbank", BankingGroup.Postbank)
|
||||
val tanChallenge = TanChallenge(TanChallengeType.EnterTan, ActionRequiringTan.GetAccountInfo, "Geben Sie die TAN ein", tanMethods.first().identifier, tanMethods, bank = bank)
|
||||
|
||||
EnterTanDialog(TanChallengeReceived(tanChallenge) { }) { }
|
||||
}
|
||||
|
@ -27,9 +27,9 @@ fun EnterTanDialogPreview_TanImage() {
|
|||
val tanMethod = TanMethod("photoTAN-Verfahren", TanMethodType.photoTan, "902", 6, AllowedTanFormat.Numeric)
|
||||
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) { }) { }
|
||||
}
|
||||
|
@ -50,10 +50,10 @@ fun EnterTanDialogPreview_WithMultipleTanMedia() { // shows that dialog is reall
|
|||
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 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) { }) { }
|
||||
}
|
||||
|
@ -62,8 +62,8 @@ fun EnterTanDialogPreview_WithMultipleTanMedia() { // shows that dialog is reall
|
|||
@Composable
|
||||
fun EnterTanDialogPreview_Flickercode() {
|
||||
val tanMethods = listOf(TanMethod("chipTAN Flickercode", TanMethodType.ChipTanFlickercode, "902"))
|
||||
val user = 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 bank = BankViewInfo("12345678", "SupiDupiNutzer", "Abzockbank", BankingGroup.Postbank)
|
||||
val tanChallenge = TanChallenge(TanChallengeType.Flickercode, ActionRequiringTan.GetAccountInfo, "Geben Sie die TAN ein", tanMethods.first().identifier, tanMethods, bank = bank, flickerCode = FlickerCode("", ""))
|
||||
|
||||
EnterTanDialog(TanChallengeReceived(tanChallenge) { }) { }
|
||||
}
|
File diff suppressed because one or more lines are too long
|
@ -1,19 +1,30 @@
|
|||
package net.codinux.banking.dataaccess
|
||||
|
||||
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.dataaccess.entities.AccountTransactionEntity
|
||||
import net.codinux.banking.dataaccess.entities.BankAccountEntity
|
||||
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.settings.AppSettings
|
||||
import net.codinux.banking.ui.settings.UiSettings
|
||||
|
||||
interface BankingRepository {
|
||||
|
||||
fun getAllUsers(): List<UserEntity>
|
||||
fun getAppSettings(): AppSettings?
|
||||
|
||||
suspend fun persistUser(user: User): UserEntity
|
||||
suspend fun saveAppSettings(settings: AppSettings)
|
||||
|
||||
fun getUiSettings(settings: UiSettings)
|
||||
|
||||
suspend fun saveUiSettings(settings: UiSettings)
|
||||
|
||||
|
||||
fun getAllBanks(): List<BankAccessEntity>
|
||||
|
||||
suspend fun persistBank(bank: BankAccess): BankAccessEntity
|
||||
|
||||
suspend fun persistTransactions(bankAccount: BankAccountEntity, transactions: List<AccountTransaction>): List<AccountTransactionEntity>
|
||||
|
||||
|
@ -29,7 +40,7 @@ interface BankingRepository {
|
|||
|
||||
fun getAllAccountTransactions(): List<AccountTransactionEntity>
|
||||
|
||||
fun getAllTransactionsOfUser(user: UserEntity): List<AccountTransactionEntity>
|
||||
fun getAllTransactionsForBank(bank: BankAccessEntity): List<AccountTransactionEntity>
|
||||
|
||||
fun getTransactionById(transactionId: Long): AccountTransactionEntity?
|
||||
|
||||
|
|
|
@ -1,31 +1,51 @@
|
|||
package net.codinux.banking.dataaccess
|
||||
|
||||
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.dataaccess.entities.AccountTransactionEntity
|
||||
import net.codinux.banking.dataaccess.entities.BankAccountEntity
|
||||
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.settings.AppSettings
|
||||
import net.codinux.banking.ui.settings.UiSettings
|
||||
|
||||
class InMemoryBankingRepository(
|
||||
users: Collection<User> = emptyList(),
|
||||
transactions: Collection<AccountTransaction> = emptyList()
|
||||
banks: Collection<BankAccess> = emptyList(),
|
||||
transactions: Collection<AccountTransaction> = emptyList(),
|
||||
private var appSettings: AppSettings = AppSettings()
|
||||
) : BankingRepository {
|
||||
|
||||
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 lateinit var uiSettings: UiSettings
|
||||
|
||||
override fun getAllUsers(): List<UserEntity> = users.toList()
|
||||
|
||||
override suspend fun persistUser(user: User): UserEntity {
|
||||
val entity = map(user) // TODO: may fix someday and add also BankAccounts and their id
|
||||
this.users.add(entity)
|
||||
override fun getAppSettings(): AppSettings? = appSettings
|
||||
|
||||
override suspend fun saveAppSettings(settings: AppSettings) {
|
||||
this.appSettings = settings
|
||||
}
|
||||
|
||||
override fun getUiSettings(settings: UiSettings) {
|
||||
this.uiSettings = settings
|
||||
}
|
||||
|
||||
override suspend fun saveUiSettings(settings: UiSettings) {
|
||||
this.uiSettings = settings
|
||||
}
|
||||
|
||||
|
||||
override fun getAllBanks(): List<BankAccessEntity> = banks.toList()
|
||||
|
||||
override suspend fun persistBank(bank: BankAccess): BankAccessEntity {
|
||||
val entity = map(bank) // TODO: may fix someday and add also BankAccounts and their id
|
||||
this.banks.add(entity)
|
||||
return entity
|
||||
}
|
||||
|
||||
|
@ -50,27 +70,27 @@ class InMemoryBankingRepository(
|
|||
|
||||
override fun getAllAccountTransactions(): List<AccountTransactionEntity> = transactions.toList()
|
||||
|
||||
override fun getAllTransactionsOfUser(user: UserEntity): List<AccountTransactionEntity> =
|
||||
getAllAccountTransactions().filter { it.userId == user.id }
|
||||
override fun getAllTransactionsForBank(bank: BankAccessEntity): List<AccountTransactionEntity> =
|
||||
getAllAccountTransactions().filter { it.bankId == bank.id }
|
||||
|
||||
override fun getTransactionById(transactionId: Long): AccountTransactionEntity? =
|
||||
getAllAccountTransactions().firstOrNull { it.id == transactionId }
|
||||
|
||||
|
||||
private fun map(account: User) = UserEntity(
|
||||
private fun map(bank: BankAccess) = BankAccessEntity(
|
||||
nextId++,
|
||||
account.bankCode, account.loginName, account.password, account.bankName, account.bic, account.customerName, account.userId,
|
||||
bank.domesticBankCode, bank.loginName, bank.password, bank.bankName, bank.bic, bank.customerName, bank.userId,
|
||||
// TODO: may fix someday and also add BankAccounts
|
||||
emptyList(), account.selectedTanMethodIdentifier, emptyList(), account.selectedTanMediumIdentifier, emptyList(),
|
||||
account.bankingGroup, account.serverAddress,
|
||||
account.userSetDisplayName, account.displayIndex,
|
||||
account.iconUrl, account.wrongCredentialsEntered,
|
||||
emptyList(), bank.selectedTanMethodIdentifier, mutableListOf(), bank.selectedTanMediumIdentifier, mutableListOf(),
|
||||
bank.bankingGroup, bank.serverAddress, bank.countryCode,
|
||||
bank.userSetDisplayName, bank.displayIndex,
|
||||
bank.iconUrl, bank.wrongCredentialsEntered,
|
||||
)
|
||||
|
||||
// TODO: someday may fix and get userId and bankAccountId
|
||||
private fun map(transaction: AccountTransaction, userId: Long = nextId++, bankAccountId: Long = nextId++) = AccountTransactionEntity(
|
||||
// TODO: someday may fix and get bankId and accountId
|
||||
private fun map(transaction: AccountTransaction, bankId: Long = nextId++, accountId: Long = nextId++) = AccountTransactionEntity(
|
||||
nextId++,
|
||||
userId, bankAccountId,
|
||||
bankId, accountId,
|
||||
transaction.amount, transaction.currency, transaction.reference,
|
||||
transaction.bookingDate, transaction.valueDate,
|
||||
transaction.otherPartyName, transaction.otherPartyBankId, transaction.otherPartyAccountId,
|
||||
|
|
|
@ -8,6 +8,10 @@ import net.codinux.banking.client.model.securitiesaccount.Holding
|
|||
import net.codinux.banking.client.model.tan.*
|
||||
import net.codinux.banking.dataaccess.entities.*
|
||||
import net.codinux.banking.ui.model.AccountTransactionViewModel
|
||||
import net.codinux.banking.ui.model.TransactionsGrouping
|
||||
import net.codinux.banking.ui.model.settings.AppAuthenticationMethod
|
||||
import net.codinux.banking.ui.model.settings.AppSettings
|
||||
import net.codinux.banking.ui.settings.UiSettings
|
||||
import net.codinux.log.logger
|
||||
import kotlin.enums.EnumEntries
|
||||
import kotlin.js.JsName
|
||||
|
@ -19,53 +23,90 @@ open class SqliteBankingRepository(
|
|||
|
||||
private val database = BankmeisterDb(sqlDriver)
|
||||
|
||||
private val userQueries = database.userQueries
|
||||
private val settingsQueries = database.settingsQueries
|
||||
|
||||
private val bankQueries = database.bankQueries
|
||||
|
||||
private val accountTransactionQueries = database.accountTransactionQueries
|
||||
|
||||
private val log by logger()
|
||||
|
||||
|
||||
override fun getAllUsers(): List<UserEntity> {
|
||||
val bankAccounts = getAllBankAccounts().groupBy { it.userId }
|
||||
val tanMethods = getAllTanMethods().groupBy { it.userId }
|
||||
val tanMedia = getAllTanMedia().groupBy { it.userId }
|
||||
val holdings = getAllHoldings().groupBy { it.bankAccountId }
|
||||
override fun getAppSettings(): AppSettings? =
|
||||
settingsQueries.getAppSettings { _,
|
||||
authenticationMethod, hashedPassword, updateAccountsOnAppStart, updateAccountsIfNoUpdatedForHours,
|
||||
sideMenuWidth,
|
||||
windowPositionX, windowPositionY, windowWidth, windowHeight,
|
||||
windowState
|
||||
->
|
||||
AppSettings(mapToEnum(authenticationMethod, AppAuthenticationMethod.entries), hashedPassword, updateAccountsOnAppStart, mapToInt(updateAccountsIfNoUpdatedForHours))
|
||||
}.executeAsOneOrNull()
|
||||
|
||||
return userQueries.selectAllUsers { 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(),
|
||||
bankingGroup?.let { BankingGroup.valueOf(it) }, serverAddress, userSetDisplayName, displayIndex.toInt(), iconUrl, wrongCredentialsEntered)
|
||||
override suspend fun saveAppSettings(settings: AppSettings) {
|
||||
settingsQueries.upsertAppSettings(
|
||||
mapEnum(settings.authenticationMethod), settings.hashedPassword, settings.updateAccountsOnAppStart, mapInt(settings.updateAccountsIfNoUpdatedForHours),
|
||||
0,
|
||||
0, 0, 0, 0,
|
||||
null
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
override fun getUiSettings(settings: UiSettings) {
|
||||
settingsQueries.getUiSettings { _, transactionsGrouping, showBalance, showBankIcons, showColoredAmounts, showTransactionsInAlternatingColors ->
|
||||
settings.transactionsGrouping.value = mapToEnum(transactionsGrouping, TransactionsGrouping.entries)
|
||||
settings.showBalance.value = showBalance
|
||||
settings.showBankIcons.value = showBankIcons
|
||||
settings.showColoredAmounts.value = showColoredAmounts
|
||||
settings.showTransactionsInAlternatingColors.value = showTransactionsInAlternatingColors
|
||||
}.executeAsOneOrNull()
|
||||
}
|
||||
|
||||
override suspend fun saveUiSettings(settings: UiSettings) {
|
||||
settingsQueries.upsertUiSettings(mapEnum(settings.transactionsGrouping.value), settings.showBalance.value, settings.showBankIcons.value, settings.showColoredAmounts.value, settings.showTransactionsInAlternatingColors.value)
|
||||
}
|
||||
|
||||
|
||||
override fun getAllBanks(): List<BankAccessEntity> {
|
||||
val bankAccounts = getAllBankAccounts().groupBy { it.bankId }
|
||||
val tanMethods = getAllTanMethods().groupBy { it.bankId }.mapValues { it.value.toMutableList() }
|
||||
val tanMedia = getAllTanMedia().groupBy { it.bankId }.mapValues { it.value.toMutableList() }
|
||||
val holdings = getAllHoldings().groupBy { it.accountId }
|
||||
|
||||
return bankQueries.getAllBanks { id, domesticBankCode, loginName, password, bankName, bic, customerName, userId, selectedTanMethodIdentifier, selectedTanMediumIdentifier, bankingGroup, serverAddress, countryCode, userSetDisplayName, clientData, displayIndex, iconUrl, wrongCredentialsEntered ->
|
||||
BankAccessEntity(id, domesticBankCode, loginName, password, bankName, bic, customerName, userId, getAccountsOfBank(id, bankAccounts, holdings), selectedTanMethodIdentifier, tanMethods[id] ?: mutableListOf(), selectedTanMediumIdentifier, tanMedia[id] ?: mutableListOf(),
|
||||
bankingGroup?.let { BankingGroup.valueOf(it) }, serverAddress, countryCode, userSetDisplayName, displayIndex.toInt(), iconUrl, wrongCredentialsEntered)
|
||||
}.executeAsList()
|
||||
}
|
||||
|
||||
protected open fun getAccountsOfUser(userId: Long, bankAccounts: Map<Long, List<BankAccountEntity>>, holdings: Map<Long, List<HoldingEntity>>): List<BankAccountEntity> {
|
||||
return bankAccounts[userId].orEmpty().onEach {
|
||||
it.holdings = holdings[it.id].orEmpty()
|
||||
protected open fun getAccountsOfBank(bankId: Long, bankAccounts: Map<Long, List<BankAccountEntity>>, holdings: Map<Long, List<HoldingEntity>>): List<BankAccountEntity> {
|
||||
return bankAccounts[bankId].orEmpty().onEach {
|
||||
it.addHoldings(holdings[it.id].orEmpty())
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun persistUser(user: User): UserEntity {
|
||||
return userQueries.transactionWithResult {
|
||||
userQueries.insertUser(user.bankCode, user.loginName, user.password, user.bankName, user.bic,
|
||||
user.customerName, user.userId, user.selectedTanMethodIdentifier, user.selectedTanMediumIdentifier,
|
||||
user.bankingGroup?.name, user.serverAddress, null, user.userSetDisplayName, user.displayIndex.toLong(), user.iconUrl, user.wrongCredentialsEntered
|
||||
override suspend fun persistBank(bank: BankAccess): BankAccessEntity {
|
||||
return bankQueries.transactionWithResult {
|
||||
bankQueries.insertBank(bank.domesticBankCode, bank.loginName, bank.password, bank.bankName, bank.bic,
|
||||
bank.customerName, bank.userId, bank.selectedTanMethodIdentifier, bank.selectedTanMediumIdentifier,
|
||||
bank.bankingGroup?.name, bank.serverAddress, bank.countryCode, 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 tanMedia = persistTanMedia(userId, user.tanMedia)
|
||||
val tanMethods = persistTanMethods(bankId, bank.tanMethods)
|
||||
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.getAllBankAccounts { id, bankId, identifier, subAccountNumber, iban, productName, accountHolderName, type, currency, accountLimit, isAccountTypeSupportedByApplication, features, balance, serverTransactionsRetentionDays, lastAccountUpdateTime, retrievedTransactionsFrom, userSetDisplayName, displayIndex, hideAccount, includeInAutomaticAccountsUpdate ->
|
||||
BankAccountEntity(
|
||||
id, userId,
|
||||
id, bankId,
|
||||
|
||||
identifier, subAccountNumber, iban, productName,
|
||||
|
||||
|
@ -79,22 +120,22 @@ open class SqliteBankingRepository(
|
|||
mapToInt(serverTransactionsRetentionDays),
|
||||
mapToInstant(lastAccountUpdateTime), mapToDate(retrievedTransactionsFrom),
|
||||
|
||||
mutableListOf(), mutableListOf(), emptyList(),
|
||||
mutableListOf(), mutableListOf(), mutableListOf(),
|
||||
|
||||
userSetDisplayName, mapToInt(displayIndex),
|
||||
hideAccount, includeInAutomaticAccountsUpdate
|
||||
)
|
||||
}.executeAsList()
|
||||
|
||||
private suspend fun persistBankAccounts(userId: Long, bankAccounts: Collection<BankAccount>): List<BankAccountEntity> =
|
||||
bankAccounts.map { persistBankAccount(userId, it) }
|
||||
private suspend fun persistBankAccounts(bankId: Long, bankAccounts: Collection<BankAccount>): List<BankAccountEntity> =
|
||||
bankAccounts.map { persistBankAccount(bankId, it) }
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
userQueries.insertBankAccount(
|
||||
userId,
|
||||
private suspend fun persistBankAccount(bankId: Long, account: BankAccount): BankAccountEntity {
|
||||
bankQueries.insertBankAccount(
|
||||
bankId,
|
||||
account.identifier, account.accountHolderName, mapEnum(account.type),
|
||||
account.iban, account.subAccountNumber, account.productName, account.currency, account.accountLimit,
|
||||
|
||||
|
@ -111,49 +152,53 @@ open class SqliteBankingRepository(
|
|||
val accountId = getLastInsertedId()
|
||||
|
||||
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.getAllTanMethods { id, bankId, displayName, type, identifier, maxTanInputLength, allowedTanFormat, userSetDisplayName ->
|
||||
TanMethodEntity(
|
||||
id,
|
||||
userId,
|
||||
bankId,
|
||||
|
||||
displayName,
|
||||
mapToEnum(type, TanMethodType.entries),
|
||||
identifier,
|
||||
mapToInt(maxTanInputLength),
|
||||
mapToEnum(allowedTanFormat, AllowedTanFormat.entries)
|
||||
mapToEnum(allowedTanFormat, AllowedTanFormat.entries),
|
||||
|
||||
userSetDisplayName
|
||||
)
|
||||
}.executeAsList()
|
||||
|
||||
private suspend fun persistTanMethods(userId: Long, tanMethods: List<TanMethod>): List<TanMethodEntity> =
|
||||
tanMethods.map { persistTanMethod(userId, it) }
|
||||
private suspend fun persistTanMethods(bankId: Long, tanMethods: List<TanMethod>): List<TanMethodEntity> =
|
||||
tanMethods.map { persistTanMethod(bankId, it) }
|
||||
|
||||
private suspend fun persistTanMethod(userId: Long, tanMethod: TanMethod): TanMethodEntity {
|
||||
userQueries.insertTanMethod(
|
||||
userId,
|
||||
private suspend fun persistTanMethod(bankId: Long, tanMethod: TanMethod): TanMethodEntity {
|
||||
bankQueries.insertTanMethod(
|
||||
bankId,
|
||||
|
||||
tanMethod.displayName,
|
||||
mapEnum(tanMethod.type),
|
||||
tanMethod.identifier,
|
||||
mapInt(tanMethod.maxTanInputLength),
|
||||
mapEnum(tanMethod.allowedTanFormat)
|
||||
mapEnum(tanMethod.allowedTanFormat),
|
||||
|
||||
tanMethod.userSetDisplayName
|
||||
)
|
||||
|
||||
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.getAllTanMedia { id, bankId, type, mediumName, status, phoneNumber, concealedPhoneNumber, cardNumber, cardSequenceNumber, cardType, validFrom, validTo, userSetDisplayName ->
|
||||
val mobilePhone = if (phoneNumber != null || concealedPhoneNumber != null) {
|
||||
MobilePhoneTanMedium(phoneNumber, concealedPhoneNumber)
|
||||
} else {
|
||||
|
@ -168,23 +213,25 @@ open class SqliteBankingRepository(
|
|||
|
||||
TanMediumEntity(
|
||||
id,
|
||||
userId,
|
||||
bankId,
|
||||
|
||||
mapToEnum(type, TanMediumType.entries),
|
||||
mediumName,
|
||||
mapToEnum(status, TanMediumStatus.entries),
|
||||
|
||||
tanGenerator,
|
||||
mobilePhone
|
||||
mobilePhone,
|
||||
|
||||
userSetDisplayName
|
||||
)
|
||||
}.executeAsList()
|
||||
|
||||
private suspend fun persistTanMedia(userId: Long, tanMedia: List<TanMedium>): List<TanMediumEntity> =
|
||||
tanMedia.map { persistTanMedium(userId, it) }
|
||||
private suspend fun persistTanMedia(bankId: Long, tanMedia: List<TanMedium>): List<TanMediumEntity> =
|
||||
tanMedia.map { persistTanMedium(bankId, it) }
|
||||
|
||||
private suspend fun persistTanMedium(userId: Long, medium: TanMedium): TanMediumEntity {
|
||||
userQueries.insertTanMedium(
|
||||
userId,
|
||||
private suspend fun persistTanMedium(bankId: Long, medium: TanMedium): TanMediumEntity {
|
||||
bankQueries.insertTanMedium(
|
||||
bankId,
|
||||
|
||||
mapEnum(medium.type),
|
||||
medium.mediumName,
|
||||
|
@ -197,31 +244,33 @@ open class SqliteBankingRepository(
|
|||
medium.tanGenerator?.cardSequenceNumber,
|
||||
mapInt(medium.tanGenerator?.cardType),
|
||||
mapDate(medium.tanGenerator?.validFrom),
|
||||
mapDate(medium.tanGenerator?.validTo)
|
||||
mapDate(medium.tanGenerator?.validTo),
|
||||
|
||||
medium.userSetDisplayName
|
||||
)
|
||||
|
||||
val tanMediumId = getLastInsertedId()
|
||||
|
||||
return TanMediumEntity(tanMediumId, userId, medium)
|
||||
return TanMediumEntity(tanMediumId, bankId, medium)
|
||||
}
|
||||
|
||||
|
||||
protected open fun getAllHoldings(): List<HoldingEntity> =
|
||||
accountTransactionQueries.selectAllHoldings { id, userId, 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))
|
||||
accountTransactionQueries.selectAllHoldings { id, bankId, accountId, name, isin, wkn, quantity, currency, totalBalance, marketValue, performancePercentage, totalCostPrice, averageCostPrice, pricingTime, buyingDate ->
|
||||
HoldingEntity(id, bankId, accountId, name, isin, wkn, mapToInt(quantity), currency, mapToAmount(totalBalance), mapToAmount(marketValue), performancePercentage?.toFloat(), mapToAmount(totalCostPrice), mapToAmount(averageCostPrice), mapToInstant(pricingTime), mapToDate(buyingDate))
|
||||
}.executeAsList()
|
||||
|
||||
override suspend fun persistHoldings(bankAccount: BankAccountEntity, holdings: List<Holding>): List<HoldingEntity> =
|
||||
accountTransactionQueries.transactionWithResult {
|
||||
holdings.map { persistHolding(bankAccount.userId, bankAccount.id, it) }
|
||||
holdings.map { persistHolding(bankAccount.bankId, bankAccount.id, it) }
|
||||
}
|
||||
|
||||
/**
|
||||
* 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, accountId: Long, holding: Holding): HoldingEntity {
|
||||
accountTransactionQueries.insertHolding(
|
||||
userId, bankAccountId,
|
||||
bankId, accountId,
|
||||
|
||||
holding.name, holding.isin, holding.wkn,
|
||||
|
||||
|
@ -234,7 +283,7 @@ open class SqliteBankingRepository(
|
|||
mapInstant(holding.pricingTime), mapDate(holding.buyingDate)
|
||||
)
|
||||
|
||||
return HoldingEntity(getLastInsertedId(), userId, bankAccountId, holding)
|
||||
return HoldingEntity(getLastInsertedId(), bankId, accountId, holding)
|
||||
}
|
||||
|
||||
override suspend fun updateHoldings(holdings: List<HoldingEntity>) {
|
||||
|
@ -266,16 +315,16 @@ open class SqliteBankingRepository(
|
|||
|
||||
|
||||
override fun getAllAccountTransactionsAsViewModel(): List<AccountTransactionViewModel> =
|
||||
accountTransactionQueries.selectAllTransactionsAsViewModel { id, userId, bankAccountId, amount, currency, reference, valueDate, otherPartyName, postingText, userSetDisplayName, category ->
|
||||
AccountTransactionViewModel(id, userId, bankAccountId, mapToAmount(amount), currency, reference, mapToDate(valueDate), otherPartyName, postingText, userSetDisplayName, category)
|
||||
accountTransactionQueries.getAllTransactionsAsViewModel { id, bankId, accountId, amount, currency, reference, valueDate, otherPartyName, postingText, userSetDisplayName, userSetOtherPartyName ->
|
||||
AccountTransactionViewModel(id, bankId, accountId, mapToAmount(amount), currency, reference, mapToDate(valueDate), otherPartyName, postingText, userSetDisplayName, userSetOtherPartyName)
|
||||
}.executeAsList()
|
||||
|
||||
override fun getAllAccountTransactions(): List<AccountTransactionEntity> {
|
||||
return accountTransactionQueries.selectAllTransactions(::mapTransaction).executeAsList()
|
||||
return accountTransactionQueries.getAllTransactions(::mapTransaction).executeAsList()
|
||||
}
|
||||
|
||||
override fun getAllTransactionsOfUser(user: UserEntity): List<AccountTransactionEntity> {
|
||||
return accountTransactionQueries.selectAllTransactionsOfUser(user.id, ::mapTransaction).executeAsList()
|
||||
override fun getAllTransactionsForBank(bank: BankAccessEntity): List<AccountTransactionEntity> {
|
||||
return accountTransactionQueries.getAllTransactionsForBank(bank.id, ::mapTransaction).executeAsList()
|
||||
}
|
||||
|
||||
override fun getTransactionById(transactionId: Long): AccountTransactionEntity? =
|
||||
|
@ -285,7 +334,7 @@ open class SqliteBankingRepository(
|
|||
override suspend fun persistTransactions(bankAccount: BankAccountEntity, transactions: List<AccountTransaction>): List<AccountTransactionEntity> {
|
||||
return accountTransactionQueries.transactionWithResult {
|
||||
transactions.map { transaction ->
|
||||
persistTransaction(bankAccount.userId, bankAccount.id, transaction)
|
||||
persistTransaction(bankAccount.bankId, bankAccount.id, transaction)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -293,9 +342,9 @@ open class SqliteBankingRepository(
|
|||
/**
|
||||
* 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, accountId: Long, transaction: AccountTransaction): AccountTransactionEntity {
|
||||
accountTransactionQueries.insertTransaction(
|
||||
userId, bankAccountId,
|
||||
bankId, accountId,
|
||||
|
||||
mapAmount(transaction.amount), transaction.currency, transaction.reference,
|
||||
mapDate(transaction.bookingDate), mapDate(transaction.valueDate),
|
||||
|
@ -305,7 +354,8 @@ open class SqliteBankingRepository(
|
|||
|
||||
mapAmount(transaction.openingBalance), mapAmount(transaction.closingBalance),
|
||||
|
||||
transaction.userSetDisplayName, transaction.category, transaction.notes,
|
||||
transaction.userSetReference, transaction.userSetReference,
|
||||
transaction.category, transaction.notes,
|
||||
|
||||
transaction.statementNumber?.toLong(), transaction.sheetNumber?.toLong(),
|
||||
|
||||
|
@ -326,16 +376,16 @@ open class SqliteBankingRepository(
|
|||
transaction.isReversal
|
||||
)
|
||||
|
||||
return AccountTransactionEntity(getLastInsertedId(), userId, bankAccountId, transaction)
|
||||
return AccountTransactionEntity(getLastInsertedId(), bankId, accountId, transaction)
|
||||
}
|
||||
|
||||
|
||||
private fun getLastInsertedId(): Long =
|
||||
userQueries.getLastInsertedId().executeAsOne()
|
||||
bankQueries.getLastInsertedId().executeAsOne()
|
||||
|
||||
|
||||
private fun mapTransaction(
|
||||
id: Long, userId: Long, bankAccountId: Long,
|
||||
id: Long, bankId: Long, accountId: Long,
|
||||
|
||||
amount: String, currency: String, reference: String?,
|
||||
bookingDate: String, valueDate: String,
|
||||
|
@ -345,7 +395,8 @@ open class SqliteBankingRepository(
|
|||
|
||||
openingBalance: String?, closingBalance: String?,
|
||||
|
||||
userSetDisplayName: String?, category: String?, notes: String?,
|
||||
userSetDisplayName: String?, userSetReference: String?,
|
||||
category: String?, notes: String?,
|
||||
|
||||
statementNumber: Long?, sheetNumber: Long?,
|
||||
|
||||
|
@ -366,7 +417,7 @@ open class SqliteBankingRepository(
|
|||
isReversal: Boolean
|
||||
): AccountTransactionEntity = AccountTransactionEntity(
|
||||
id,
|
||||
userId, bankAccountId,
|
||||
bankId, accountId,
|
||||
|
||||
Amount(amount), currency, reference,
|
||||
mapToDate(bookingDate), mapToDate(valueDate),
|
||||
|
@ -375,7 +426,8 @@ open class SqliteBankingRepository(
|
|||
|
||||
mapToAmount(openingBalance), mapToAmount(closingBalance),
|
||||
|
||||
userSetDisplayName, category, notes,
|
||||
userSetDisplayName, userSetReference,
|
||||
category, notes,
|
||||
|
||||
statementNumber?.toInt(), sheetNumber?.toInt(),
|
||||
|
||||
|
|
|
@ -6,8 +6,8 @@ import net.codinux.banking.client.model.Amount
|
|||
|
||||
class AccountTransactionEntity(
|
||||
val id: Long,
|
||||
val userId: Long,
|
||||
val bankAccountId: Long,
|
||||
val bankId: Long,
|
||||
val accountId: Long,
|
||||
|
||||
amount: Amount,
|
||||
currency: String,
|
||||
|
@ -25,7 +25,8 @@ class AccountTransactionEntity(
|
|||
openingBalance: Amount? = null,
|
||||
closingBalance: Amount? = null,
|
||||
|
||||
userSetDisplayName: String? = null,
|
||||
userSetReference: String? = null,
|
||||
userSetOtherPartyName: String? = null,
|
||||
category: String? = null,
|
||||
notes: String? = null,
|
||||
|
||||
|
@ -80,10 +81,10 @@ class AccountTransactionEntity(
|
|||
|
||||
isReversal,
|
||||
|
||||
userSetDisplayName, category, notes
|
||||
userSetReference, userSetOtherPartyName, category, notes
|
||||
) {
|
||||
constructor(id: Long, userId: Long, bankAccountId: Long, transaction: AccountTransaction) : this(
|
||||
id, userId, bankAccountId,
|
||||
constructor(id: Long, bankId: Long, accountId: Long, transaction: AccountTransaction) : this(
|
||||
id, bankId, accountId,
|
||||
transaction.amount, transaction.currency, transaction.reference,
|
||||
transaction.bookingDate, transaction.valueDate,
|
||||
|
||||
|
@ -93,7 +94,8 @@ class AccountTransactionEntity(
|
|||
|
||||
transaction.openingBalance, transaction.closingBalance,
|
||||
|
||||
transaction.userSetDisplayName, transaction.category, transaction.notes,
|
||||
transaction.userSetReference, transaction.userSetOtherPartyName,
|
||||
transaction.category, transaction.notes,
|
||||
|
||||
transaction.statementNumber, transaction.sheetNumber,
|
||||
|
||||
|
@ -116,7 +118,7 @@ class AccountTransactionEntity(
|
|||
|
||||
|
||||
override val identifier: String by lazy {
|
||||
"$userId ${super.identifier}"
|
||||
"$bankId ${super.identifier}"
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
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.tan.TanMedium
|
||||
|
||||
class BankAccessEntity(
|
||||
val id: Long,
|
||||
|
||||
domesticBankCode: String,
|
||||
loginName: String,
|
||||
password: String?,
|
||||
|
||||
bankName: String,
|
||||
bic: String?,
|
||||
|
||||
customerName: String,
|
||||
userId: String? = null,
|
||||
|
||||
override val accounts: List<BankAccountEntity> = emptyList(),
|
||||
|
||||
selectedTanMethodIdentifier: String? = null,
|
||||
override val tanMethods: MutableList<TanMethodEntity> = mutableListOf(),
|
||||
|
||||
selectedTanMediumIdentifier: String? = null,
|
||||
override val tanMedia: MutableList<TanMediumEntity> = mutableListOf(),
|
||||
|
||||
bankingGroup: BankingGroup? = null,
|
||||
serverAddress: String? = null,
|
||||
countryCode: String = "de",
|
||||
|
||||
userSetDisplayName: String? = null,
|
||||
displayIndex: Int = 0,
|
||||
|
||||
iconUrl: String? = null,
|
||||
wrongCredentialsEntered: Boolean = false
|
||||
) : BankAccess(domesticBankCode, loginName, password, bankName, bic, customerName, userId, accounts, selectedTanMethodIdentifier, tanMethods, selectedTanMediumIdentifier, tanMedia, bankingGroup, serverAddress, countryCode) {
|
||||
|
||||
init {
|
||||
this.userSetDisplayName = userSetDisplayName
|
||||
this.displayIndex = displayIndex
|
||||
|
||||
this.iconUrl = iconUrl
|
||||
this.wrongCredentialsEntered = wrongCredentialsEntered
|
||||
}
|
||||
|
||||
|
||||
constructor(id: Long, bank: BankAccess, bankAccounts: List<BankAccountEntity>, tanMethods: List<TanMethodEntity>, tanMedia: List<TanMediumEntity>) : this(
|
||||
id,
|
||||
bank.domesticBankCode, bank.loginName, bank.password, bank.bankName, bank.bic, bank.customerName, bank.userId,
|
||||
bankAccounts,
|
||||
bank.selectedTanMethodIdentifier, tanMethods.toMutableList(), bank.selectedTanMediumIdentifier, tanMedia.toMutableList(),
|
||||
bank.bankingGroup, bank.serverAddress, bank.countryCode,
|
||||
bank.userSetDisplayName, bank.displayIndex,
|
||||
bank.iconUrl, bank.wrongCredentialsEntered,
|
||||
)
|
||||
|
||||
}
|
|
@ -3,11 +3,11 @@ package net.codinux.banking.dataaccess.entities
|
|||
import kotlinx.datetime.Instant
|
||||
import kotlinx.datetime.LocalDate
|
||||
import net.codinux.banking.client.model.*
|
||||
import net.codinux.banking.client.model.securitiesaccount.Holding
|
||||
import kotlin.jvm.JvmName
|
||||
|
||||
class BankAccountEntity(
|
||||
val id: Long,
|
||||
val userId: Long,
|
||||
val bankId: Long,
|
||||
|
||||
identifier: String,
|
||||
subAccountNumber: String? = null,
|
||||
|
@ -28,9 +28,9 @@ class BankAccountEntity(
|
|||
lastAccountUpdateTime: Instant? = null,
|
||||
retrievedTransactionsFrom: LocalDate? = null,
|
||||
|
||||
bookedTransactions: MutableList<AccountTransactionEntity> = mutableListOf(),
|
||||
override val bookedTransactions: MutableList<AccountTransactionEntity> = mutableListOf(),
|
||||
prebookedTransactions: MutableList<PrebookedAccountTransaction> = mutableListOf(),
|
||||
override var holdings: List<HoldingEntity> = emptyList(),
|
||||
override val holdings: MutableList<HoldingEntity> = mutableListOf(),
|
||||
|
||||
userSetDisplayName: String? = null,
|
||||
displayIndex: Int = 0,
|
||||
|
@ -48,14 +48,14 @@ class BankAccountEntity(
|
|||
|
||||
serverTransactionsRetentionDays, lastAccountUpdateTime, retrievedTransactionsFrom,
|
||||
|
||||
bookedTransactions as MutableList<AccountTransaction>, prebookedTransactions,
|
||||
bookedTransactions, prebookedTransactions,
|
||||
holdings,
|
||||
|
||||
userSetDisplayName, displayIndex,
|
||||
hideAccount, includeInAutomaticAccountsUpdate
|
||||
) {
|
||||
constructor(id: Long, userId: Long, account: BankAccount, transactions: List<AccountTransactionEntity> = emptyList(), holdings: List<HoldingEntity> = emptyList()) : this(
|
||||
id, userId,
|
||||
constructor(id: Long, bankId: Long, account: BankAccount, transactions: List<AccountTransactionEntity> = emptyList(), holdings: List<HoldingEntity> = emptyList()) : this(
|
||||
id, bankId,
|
||||
account.identifier, account.subAccountNumber, account.iban, account.productName,
|
||||
|
||||
account.accountHolderName, account.type,
|
||||
|
@ -68,12 +68,16 @@ class BankAccountEntity(
|
|||
account.serverTransactionsRetentionDays,
|
||||
account.lastAccountUpdateTime, account.retrievedTransactionsFrom,
|
||||
|
||||
transactions.toMutableList(), mutableListOf(), holdings,
|
||||
transactions.toMutableList(), mutableListOf(), holdings.toMutableList(),
|
||||
|
||||
account.userSetDisplayName, account.displayIndex,
|
||||
account.hideAccount, account.includeInAutomaticAccountsUpdate
|
||||
)
|
||||
|
||||
val bookedTransactionsEntities: MutableList<AccountTransactionEntity> = bookedTransactions
|
||||
|
||||
@JvmName("addHoldingsEntities")
|
||||
fun addHoldings(holdings: List<HoldingEntity>) {
|
||||
this.holdings.addAll(holdings)
|
||||
}
|
||||
|
||||
}
|
|
@ -7,8 +7,8 @@ import net.codinux.banking.client.model.securitiesaccount.Holding
|
|||
|
||||
class HoldingEntity(
|
||||
val id: Long,
|
||||
val userId: Long,
|
||||
val bankAccountId: Long,
|
||||
val bankId: Long,
|
||||
val accountId: Long,
|
||||
|
||||
name: String,
|
||||
|
||||
|
@ -30,8 +30,8 @@ class HoldingEntity(
|
|||
buyingDate: LocalDate? = null
|
||||
) : Holding(name, isin, wkn, quantity, currency, totalBalance, marketValue, performancePercentage, totalCostPrice, averageCostPrice, pricingTime, buyingDate) {
|
||||
|
||||
constructor(id: Long, userId: Long, bankAccountId: Long, holding: Holding) : this(
|
||||
id, userId, bankAccountId,
|
||||
constructor(id: Long, bankId: Long, accountId: Long, holding: Holding) : this(
|
||||
id, bankId, accountId,
|
||||
|
||||
holding.name, holding.isin, holding.wkn,
|
||||
|
||||
|
|
|
@ -4,17 +4,19 @@ import net.codinux.banking.client.model.tan.*
|
|||
|
||||
class TanMediumEntity(
|
||||
val id: Long,
|
||||
val userId: Long,
|
||||
val bankId: Long,
|
||||
|
||||
type: TanMediumType,
|
||||
mediumName: String?,
|
||||
status: TanMediumStatus,
|
||||
|
||||
tanGenerator: TanGeneratorTanMedium? = null,
|
||||
mobilePhone: MobilePhoneTanMedium? = null
|
||||
) : TanMedium(type, mediumName, status, tanGenerator, mobilePhone) {
|
||||
mobilePhone: MobilePhoneTanMedium? = null,
|
||||
|
||||
constructor(id: Long, userId: Long, medium: TanMedium)
|
||||
: this(id, userId, medium.type, medium.mediumName, medium.status, medium.tanGenerator, medium.mobilePhone)
|
||||
userSetDisplayName: String? = null
|
||||
) : TanMedium(type, mediumName, status, tanGenerator, mobilePhone, userSetDisplayName) {
|
||||
|
||||
constructor(id: Long, bankId: Long, medium: TanMedium)
|
||||
: this(id, bankId, medium.type, medium.mediumName, medium.status, medium.tanGenerator, medium.mobilePhone, medium.userSetDisplayName)
|
||||
|
||||
}
|
|
@ -6,16 +6,18 @@ import net.codinux.banking.client.model.tan.TanMethodType
|
|||
|
||||
class TanMethodEntity(
|
||||
val id: Long,
|
||||
val userId: Long,
|
||||
val bankId: Long,
|
||||
|
||||
displayName: String,
|
||||
type: TanMethodType,
|
||||
identifier: String,
|
||||
maxTanInputLength: Int? = null,
|
||||
allowedTanFormat: AllowedTanFormat = AllowedTanFormat.Alphanumeric
|
||||
) : TanMethod(displayName, type, identifier, maxTanInputLength, allowedTanFormat) {
|
||||
allowedTanFormat: AllowedTanFormat = AllowedTanFormat.Alphanumeric,
|
||||
|
||||
constructor(id: Long, userId: Long, tanMethod: TanMethod)
|
||||
: this(id, userId, tanMethod.displayName, tanMethod.type, tanMethod.identifier, tanMethod.maxTanInputLength, tanMethod.allowedTanFormat)
|
||||
userSetDisplayName: String? = null,
|
||||
) : TanMethod(displayName, type, identifier, maxTanInputLength, allowedTanFormat, userSetDisplayName) {
|
||||
|
||||
constructor(id: Long, bankId: Long, tanMethod: TanMethod)
|
||||
: this(id, bankId, tanMethod.displayName, tanMethod.type, tanMethod.identifier, tanMethod.maxTanInputLength, tanMethod.allowedTanFormat, tanMethod.userSetDisplayName)
|
||||
|
||||
}
|
|
@ -1,58 +0,0 @@
|
|||
package net.codinux.banking.dataaccess.entities
|
||||
|
||||
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.TanMethod
|
||||
|
||||
class UserEntity(
|
||||
val id: Long,
|
||||
|
||||
bankCode: String,
|
||||
loginName: String,
|
||||
password: String?,
|
||||
|
||||
bankName: String,
|
||||
bic: String,
|
||||
|
||||
customerName: String,
|
||||
userId: String? = null,
|
||||
|
||||
override val accounts: List<BankAccountEntity> = emptyList(),
|
||||
|
||||
selectedTanMethodIdentifier: String? = null,
|
||||
override val tanMethods: List<TanMethodEntity> = listOf(),
|
||||
|
||||
selectedTanMediumIdentifier: String? = null,
|
||||
tanMedia: List<TanMedium> = listOf(),
|
||||
|
||||
bankingGroup: BankingGroup? = null,
|
||||
serverAddress: String? = null,
|
||||
|
||||
userSetDisplayName: String? = null,
|
||||
displayIndex: Int = 0,
|
||||
|
||||
iconUrl: String? = null,
|
||||
wrongCredentialsEntered: Boolean = false
|
||||
) : User(bankCode, loginName, password, bankName, bic, customerName, userId, accounts, selectedTanMethodIdentifier, tanMethods, selectedTanMediumIdentifier, tanMedia, bankingGroup, serverAddress) {
|
||||
|
||||
init {
|
||||
this.userSetDisplayName = userSetDisplayName
|
||||
this.displayIndex = displayIndex
|
||||
|
||||
this.iconUrl = iconUrl
|
||||
this.wrongCredentialsEntered = wrongCredentialsEntered
|
||||
}
|
||||
|
||||
|
||||
constructor(id: Long, user: User, bankAccounts: List<BankAccountEntity>, tanMethods: List<TanMethodEntity>, tanMedia: List<TanMediumEntity>) : this(
|
||||
id,
|
||||
user.bankCode, user.loginName, user.password, user.bankName, user.bic, user.customerName, user.userId,
|
||||
bankAccounts,
|
||||
user.selectedTanMethodIdentifier, tanMethods, user.selectedTanMediumIdentifier, tanMedia,
|
||||
user.bankingGroup, user.serverAddress,
|
||||
user.userSetDisplayName, user.displayIndex,
|
||||
user.iconUrl, user.wrongCredentialsEntered,
|
||||
)
|
||||
|
||||
}
|
|
@ -29,7 +29,7 @@ private val IconWidth = 48.dp
|
|||
|
||||
@Composable
|
||||
fun BottomBar(showMenuDrawer: Boolean = true) {
|
||||
val users by uiState.users.collectAsState()
|
||||
val banks by uiState.banks.collectAsState()
|
||||
|
||||
val transactionsFilter by uiState.transactionsFilter.collectAsState()
|
||||
|
||||
|
@ -65,7 +65,7 @@ fun BottomBar(showMenuDrawer: Boolean = true) {
|
|||
} else if (selectedAccount.bankAccount != null) {
|
||||
selectedAccount.bankAccount.displayName
|
||||
} else {
|
||||
selectedAccount.user.displayName
|
||||
selectedAccount.bank.displayName
|
||||
}
|
||||
|
||||
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) {
|
||||
Row(Modifier.fillMaxHeight().widthIn(IconWidth, IconWidth), verticalAlignment = Alignment.CenterVertically) {
|
||||
IconButton({ showSearchbar = true }, Modifier.width(IconWidth)) {
|
||||
|
|
|
@ -2,7 +2,6 @@ package net.codinux.banking.ui.appskeleton
|
|||
|
||||
import androidx.compose.foundation.*
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material.Divider
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.icons.Icons
|
||||
|
@ -55,7 +54,7 @@ private val VerticalSpacing = 8.dp
|
|||
fun SideMenuContent() {
|
||||
val drawerState = uiState.drawerState.collectAsState().value
|
||||
|
||||
val accounts = uiState.users.collectAsState().value
|
||||
val accounts = uiState.banks.collectAsState().value
|
||||
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
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.ui.config.DI
|
||||
import net.codinux.banking.ui.model.BankInfo
|
||||
|
@ -21,8 +21,8 @@ private val bankIconService = DI.bankIconService
|
|||
private val DefaultIconModifier: Modifier = Modifier.size(16.dp)
|
||||
|
||||
@Composable
|
||||
fun BankIcon(user: User?, modifier: Modifier = Modifier, iconModifier: Modifier = DefaultIconModifier, fallbackIcon: ImageVector? = null, fallbackIconTintColor: Color? = null) {
|
||||
val iconUrl by remember(user?.bic) { mutableStateOf(user?.let { bankIconService.findIconForBank(it) }) }
|
||||
fun BankIcon(bank: BankAccess?, modifier: Modifier = Modifier, iconModifier: Modifier = DefaultIconModifier, fallbackIcon: ImageVector? = null, fallbackIconTintColor: Color? = null) {
|
||||
val iconUrl by remember(bank?.bic) { mutableStateOf(bank?.let { bankIconService.findIconForBank(it) }) }
|
||||
|
||||
BankIcon(iconUrl, modifier, iconModifier, fallbackIcon = fallbackIcon, fallbackIconTintColor = fallbackIconTintColor)
|
||||
}
|
||||
|
@ -37,8 +37,8 @@ fun BankIcon(bank: BankInfo, modifier: Modifier = Modifier, iconModifier: Modifi
|
|||
}
|
||||
|
||||
@Composable
|
||||
fun BankIcon(user: BankViewInfo?, modifier: Modifier = Modifier, iconModifier: Modifier = DefaultIconModifier, fallbackIcon: ImageVector? = null) {
|
||||
val iconUrl = user?.let { bankIconService.findIconForBank(it.bankName, null, it.bankingGroup) }
|
||||
fun BankIcon(bank: BankViewInfo?, modifier: Modifier = Modifier, iconModifier: Modifier = DefaultIconModifier, fallbackIcon: ImageVector? = null) {
|
||||
val iconUrl = bank?.let { bankIconService.findIconForBank(it.bankName, null, it.bankingGroup) }
|
||||
|
||||
BankIcon(iconUrl, modifier, iconModifier, fallbackIcon = fallbackIcon)
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ import androidx.compose.ui.graphics.Color
|
|||
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.UserEntity
|
||||
import net.codinux.banking.dataaccess.entities.BankAccessEntity
|
||||
import net.codinux.banking.ui.config.DI
|
||||
|
||||
private val uiState = DI.uiState
|
||||
|
@ -27,9 +27,9 @@ fun BanksList(
|
|||
textColor: Color = Color.White,
|
||||
itemModifier: Modifier = Modifier.height(48.dp).widthIn(min = 300.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) {
|
||||
|
@ -37,16 +37,16 @@ fun BanksList(
|
|||
accountSelected?.invoke(null, null)
|
||||
}
|
||||
|
||||
users.value.sortedBy { it.displayIndex }.forEach { user ->
|
||||
banks.value.sortedBy { it.displayIndex }.forEach { bank ->
|
||||
Spacer(Modifier.fillMaxWidth().height(12.dp))
|
||||
|
||||
NavigationMenuItem(itemModifier, user.displayName, textColor, iconSize, IconTextSpacing, itemHorizontalPadding, user, fallbackIcon = defaultBankIcon) {
|
||||
accountSelected?.invoke(user, null)
|
||||
NavigationMenuItem(itemModifier, bank.displayName, textColor, iconSize, IconTextSpacing, itemHorizontalPadding, bank, fallbackIcon = defaultBankIcon) {
|
||||
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) {
|
||||
accountSelected?.invoke(user, account)
|
||||
accountSelected?.invoke(bank, account)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ import androidx.compose.foundation.layout.Row
|
|||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.ContentAlpha
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
|
@ -19,7 +20,7 @@ import androidx.compose.ui.text.style.TextOverflow
|
|||
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.UserEntity
|
||||
import net.codinux.banking.dataaccess.entities.BankAccessEntity
|
||||
import net.codinux.banking.ui.config.Colors
|
||||
import net.codinux.banking.ui.config.DI
|
||||
|
||||
|
@ -37,7 +38,7 @@ fun NavigationMenuItem(
|
|||
iconSize: Dp = 24.dp,
|
||||
iconTextSpacing: Dp = 24.dp,
|
||||
horizontalPadding: Dp = 8.dp,
|
||||
user: UserEntity? = null,
|
||||
bank: BankAccessEntity? = null,
|
||||
bankAccount: BankAccountEntity? = null,
|
||||
fallbackIcon: ImageVector? = null,
|
||||
icon: (@Composable () -> Unit)? = null,
|
||||
|
@ -49,12 +50,26 @@ fun NavigationMenuItem(
|
|||
|
||||
val showColoredAmounts by DI.uiSettings.showColoredAmounts.collectAsState()
|
||||
|
||||
val isUnsupportedAccountType = bankAccount?.isAccountTypeSupportedByApplication == false
|
||||
|
||||
val effectiveText = if (isUnsupportedAccountType) "$text (nicht unterstützt)" else text
|
||||
|
||||
val effectiveTextColor = if (isUnsupportedAccountType) textColor.copy(ContentAlpha.disabled) else textColor
|
||||
|
||||
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = modifier
|
||||
.clickable { onClick?.invoke() }
|
||||
modifier = modifier.let {
|
||||
if (isUnsupportedAccountType == false) {
|
||||
it.clickable {
|
||||
onClick?.invoke()
|
||||
}
|
||||
} else {
|
||||
it
|
||||
}
|
||||
}
|
||||
.let {
|
||||
if (user != null && filterService.isSelected(user, transactionsFilter)
|
||||
if (bank != null && filterService.isSelected(bank, transactionsFilter)
|
||||
|| bankAccount != null && filterService.isSelected(bankAccount, transactionsFilter)) {
|
||||
it.background(Colors.AccentAsSelectionBackground, shape = RoundedCornerShape(4.dp))
|
||||
} else {
|
||||
|
@ -68,17 +83,17 @@ fun NavigationMenuItem(
|
|||
icon()
|
||||
}
|
||||
} 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 = effectiveTextColor)
|
||||
}
|
||||
|
||||
Text(text, color = textColor, modifier = Modifier.weight(1f), maxLines = 1, overflow = TextOverflow.Ellipsis)
|
||||
Text(effectiveText, color = effectiveTextColor, modifier = Modifier.weight(1f), maxLines = 1, overflow = TextOverflow.Ellipsis)
|
||||
|
||||
val balance = if (showBalance == false) {
|
||||
val balance = if (showBalance == false || isUnsupportedAccountType) {
|
||||
null
|
||||
} else if (bankAccount != null) {
|
||||
bankAccount.balance
|
||||
} else if (user != null) {
|
||||
calculator.calculateBalanceOfUser(user)
|
||||
} else if (bank != null) {
|
||||
calculator.calculateBalanceOfBankAccess(bank)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
|
|
@ -75,7 +75,7 @@ fun StateHandler(uiState: UiState, snackbarHostState: SnackbarHostState) {
|
|||
}
|
||||
|
||||
snackbarHostState.showSnackbar(
|
||||
message = "$messagePrefix für ${event.user.displayName} ${event.account.displayName}",
|
||||
message = "$messagePrefix für ${event.bank.displayName} ${event.account.displayName}",
|
||||
actionLabel = actionLabel,
|
||||
duration = SnackbarDuration.Long
|
||||
)
|
||||
|
|
|
@ -23,7 +23,7 @@ fun UiSettings(modifier: Modifier, textColor: Color = Color.Unspecified) {
|
|||
|
||||
val transactionsGrouping by uiSettings.transactionsGrouping.collectAsState()
|
||||
|
||||
val zebraStripes by uiSettings.zebraStripes.collectAsState()
|
||||
val showTransactionsInAlternatingColors by uiSettings.showTransactionsInAlternatingColors.collectAsState()
|
||||
|
||||
val showBankIcons by uiSettings.showBankIcons.collectAsState()
|
||||
|
||||
|
@ -33,7 +33,7 @@ fun UiSettings(modifier: Modifier, textColor: Color = Color.Unspecified) {
|
|||
Column(modifier) {
|
||||
BooleanOption("Kontostand anzeigen", showBalance, textColor = textColor) { uiSettings.showBalance.value = it }
|
||||
|
||||
BooleanOption("Zebra Stripes", zebraStripes, textColor = textColor) { uiSettings.zebraStripes.value = it }
|
||||
BooleanOption("Umsätze in alternierenden Farben anzeigen", showTransactionsInAlternatingColors, textColor = textColor) { uiSettings.showTransactionsInAlternatingColors.value = it }
|
||||
|
||||
BooleanOption("Bank Icons anzeigen", showBankIcons, textColor = textColor) { uiSettings.showBankIcons.value = it }
|
||||
|
||||
|
|
|
@ -14,9 +14,10 @@ import androidx.compose.ui.unit.dp
|
|||
import androidx.compose.ui.unit.sp
|
||||
import net.codinux.banking.client.model.Amount
|
||||
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.DI
|
||||
import net.codinux.banking.ui.config.Style
|
||||
import net.codinux.banking.ui.forms.RoundedCornersCard
|
||||
import net.codinux.banking.ui.model.AccountTransactionViewModel
|
||||
import net.codinux.banking.ui.model.TransactionsGrouping
|
||||
|
@ -31,7 +32,7 @@ fun GroupedTransactionsListItems(
|
|||
modifier: Modifier,
|
||||
transactionsToDisplay: List<AccountTransactionViewModel>,
|
||||
holdingsToDisplay: List<Holding>,
|
||||
usersById: Map<Long, UserEntity>,
|
||||
banksById: Map<Long, BankAccessEntity>,
|
||||
transactionsGrouping: TransactionsGrouping
|
||||
) {
|
||||
val groupingService = remember { TransactionsGroupingService() }
|
||||
|
@ -49,6 +50,7 @@ fun GroupedTransactionsListItems(
|
|||
Column(Modifier.fillMaxWidth().padding(top = 8.dp, bottom = 16.dp)) {
|
||||
Text(
|
||||
text = "Depotwerte",
|
||||
color = Style.ListItemHeaderTextColor,
|
||||
fontSize = 16.sp,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
modifier = Modifier.padding(bottom = 2.dp),
|
||||
|
@ -77,6 +79,7 @@ fun GroupedTransactionsListItems(
|
|||
Column(Modifier.fillMaxWidth()) {
|
||||
Text(
|
||||
text = DI.formatUtil.formatGroupingDate(groupingDate, transactionsGrouping),
|
||||
color = Style.ListItemHeaderTextColor,
|
||||
fontSize = 16.sp,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
modifier = Modifier.padding(top = 8.dp, bottom = 2.dp),
|
||||
|
@ -90,7 +93,7 @@ fun GroupedTransactionsListItems(
|
|||
Column(Modifier.background(Color.White)) { // LazyColumn inside LazyColumn is not allowed
|
||||
monthTransactions.forEachIndexed { index, transaction ->
|
||||
key(transaction.id) {
|
||||
TransactionListItem(usersById[transaction.userId], transaction, index, monthTransactions.size)
|
||||
TransactionListItem(banksById[transaction.bankId], transaction, index, monthTransactions.size)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,7 +9,6 @@ import androidx.compose.runtime.getValue
|
|||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import net.codinux.banking.client.model.Amount
|
||||
|
@ -32,11 +31,11 @@ fun HoldingListItem(holding: Holding, isOddItem: Boolean = false, isNotLastItem:
|
|||
// TODO: also regard showBalance?
|
||||
val showColoredAmounts by uiSettings.showColoredAmounts.collectAsState()
|
||||
|
||||
val zebraStripes by uiSettings.zebraStripes.collectAsState()
|
||||
val showTransactionsInAlternatingColors by uiSettings.showTransactionsInAlternatingColors.collectAsState()
|
||||
|
||||
val showBankIcons by uiSettings.showBankIcons.collectAsState()
|
||||
|
||||
val backgroundColor = if (zebraStripes && isOddItem) Colors.ZebraStripesColor else Color.White
|
||||
val backgroundColor = if (showTransactionsInAlternatingColors && isOddItem) Colors.ZebraStripesColor else Color.White
|
||||
|
||||
val currency = holding.currency ?: fallbackCurrency
|
||||
|
||||
|
@ -49,6 +48,7 @@ fun HoldingListItem(holding: Holding, isOddItem: Boolean = false, isNotLastItem:
|
|||
|
||||
Text(
|
||||
holding.name,
|
||||
color = Style.ListItemHeaderTextColor,
|
||||
fontWeight = Style.ListItemHeaderWeight,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
|
|
|
@ -9,12 +9,11 @@ import androidx.compose.ui.Alignment
|
|||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
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.unit.DpOffset
|
||||
import androidx.compose.ui.unit.dp
|
||||
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.text.ItemDivider
|
||||
import net.codinux.banking.ui.config.Colors
|
||||
|
@ -28,14 +27,14 @@ private val uiSettings = DI.uiSettings
|
|||
private val formatUtil = DI.formatUtil
|
||||
|
||||
@Composable
|
||||
fun TransactionListItem(user: User?, transaction: AccountTransactionViewModel, itemIndex: Int, countItems: Int) {
|
||||
val zebraStripes by uiSettings.zebraStripes.collectAsState()
|
||||
fun TransactionListItem(bank: BankAccess?, transaction: AccountTransactionViewModel, itemIndex: Int, countItems: Int) {
|
||||
val showTransactionsInAlternatingColors by uiSettings.showTransactionsInAlternatingColors.collectAsState()
|
||||
|
||||
val showBankIcons by uiSettings.showBankIcons.collectAsState()
|
||||
|
||||
val showColoredAmounts by uiSettings.showColoredAmounts.collectAsState()
|
||||
|
||||
val backgroundColor = if (zebraStripes && itemIndex % 2 == 1) Colors.ZebraStripesColor else Color.White
|
||||
val backgroundColor = if (showTransactionsInAlternatingColors && itemIndex % 2 == 1) Colors.ZebraStripesColor else Color.White
|
||||
|
||||
val bottomPadding = 56.dp
|
||||
|
||||
|
@ -51,7 +50,7 @@ fun TransactionListItem(user: User?, transaction: AccountTransactionViewModel, i
|
|||
val transactionEntity = DI.bankingService.getTransaction(transaction.id)
|
||||
|
||||
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.accountId } },
|
||||
transaction.otherPartyName,
|
||||
transactionEntity?.otherPartyBankId,
|
||||
transactionEntity?.otherPartyAccountId,
|
||||
|
@ -79,12 +78,13 @@ fun TransactionListItem(user: User?, transaction: AccountTransactionViewModel, i
|
|||
Column(Modifier.weight(1f)) {
|
||||
Row {
|
||||
if (showBankIcons) {
|
||||
BankIcon(user, Modifier.padding(end = 6.dp))
|
||||
BankIcon(bank, Modifier.padding(end = 6.dp))
|
||||
}
|
||||
|
||||
Text(
|
||||
text = transaction.otherPartyName ?: transaction.postingText ?: "",
|
||||
Modifier.fillMaxWidth(),
|
||||
color = Style.ListItemHeaderTextColor,
|
||||
fontWeight = Style.ListItemHeaderWeight,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
|
|
|
@ -25,9 +25,9 @@ private val formatUtil = DI.formatUtil
|
|||
|
||||
@Composable
|
||||
fun TransactionsList(uiState: UiState, uiSettings: UiSettings, isMobile: Boolean = true) {
|
||||
val users by uiState.users.collectAsState()
|
||||
val usersById by remember(users) {
|
||||
derivedStateOf { users.associateBy { it.id } }
|
||||
val banks by uiState.banks.collectAsState()
|
||||
val banksById by remember(banks) {
|
||||
derivedStateOf { banks.associateBy { it.id } }
|
||||
}
|
||||
|
||||
val transactionsFilter by uiState.transactionsFilter.collectAsState()
|
||||
|
@ -59,13 +59,13 @@ fun TransactionsList(uiState: UiState, uiSettings: UiSettings, isMobile: Boolean
|
|||
Spacer(Modifier.weight(1f))
|
||||
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
||||
if (transactionsGrouping != TransactionsGrouping.None) {
|
||||
GroupedTransactionsListItems(transactionsListModifier, transactionsToDisplay, holdingsToDisplay, usersById, transactionsGrouping)
|
||||
GroupedTransactionsListItems(transactionsListModifier, transactionsToDisplay, holdingsToDisplay, banksById, transactionsGrouping)
|
||||
} else {
|
||||
LazyColumn(transactionsListModifier, contentPadding = PaddingValues(top = 8.dp, bottom = 16.dp)) {
|
||||
itemsIndexed(holdingsToDisplay) { index, holding ->
|
||||
|
@ -76,7 +76,7 @@ fun TransactionsList(uiState: UiState, uiSettings: UiSettings, isMobile: Boolean
|
|||
|
||||
itemsIndexed(transactionsToDisplay) { index, transaction ->
|
||||
key(transaction.id) {
|
||||
TransactionListItem(usersById[transaction.userId], transaction, index, transactionsToDisplay.size)
|
||||
TransactionListItem(banksById[transaction.bankId], transaction, index, transactionsToDisplay.size)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,6 +39,8 @@ object Colors {
|
|||
|
||||
val Zinc200 = Color(228, 228, 231)
|
||||
|
||||
val Zinc500 = Color(0xFF71717a)
|
||||
|
||||
val Zinc700 = Color(63, 63, 70)
|
||||
|
||||
|
||||
|
|
|
@ -36,7 +36,7 @@ object DI {
|
|||
|
||||
var bankingRepository: BankingRepository = InMemoryBankingRepository(emptyList())
|
||||
|
||||
val bankingService by lazy { BankingService(uiState, bankingRepository, bankFinder) }
|
||||
val bankingService by lazy { BankingService(uiState, uiSettings, bankingRepository, bankFinder) }
|
||||
|
||||
|
||||
fun setRepository(sqlDriver: SqlDriver) = setRepository(SqliteBankingRepository(sqlDriver))
|
||||
|
|
|
@ -13,6 +13,9 @@ object Style {
|
|||
|
||||
val HeaderFontWeight: FontWeight = FontWeight.Bold
|
||||
|
||||
|
||||
val ListItemHeaderTextColor: Color = Colors.Zinc500
|
||||
|
||||
val ListItemHeaderWeight = FontWeight.Medium // couldn't believe it, the FontWeights look different on Desktop and Android
|
||||
|
||||
|
||||
|
|
|
@ -136,7 +136,7 @@ fun AddAccountDialog(
|
|||
}
|
||||
|
||||
Row(Modifier.fillMaxWidth().padding(top = 6.dp)) {
|
||||
Text(bank.bankCode, color = textColor)
|
||||
Text(bank.domesticBankCode, color = textColor)
|
||||
|
||||
Text("${bank.postalCode} ${bank.city}", Modifier.weight(1f).padding(start = 8.dp), color = if (supportsFinTs) Color.Gray else textColor)
|
||||
}
|
||||
|
|
|
@ -94,9 +94,9 @@ fun EnterTanDialog(tanChallengeReceived: TanChallengeReceived, onDismiss: () ->
|
|||
Column(Modifier.fillMaxWidth()) {
|
||||
Column(Modifier.fillMaxWidth()) {
|
||||
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(
|
||||
"TAN benötigt ${Internationalization.getTextForActionRequiringTan(challenge.forAction)}",
|
||||
|
|
|
@ -38,11 +38,11 @@ fun TransferMoneyDialog(
|
|||
data: ShowTransferMoneyDialogData,
|
||||
onDismiss: () -> Unit,
|
||||
) {
|
||||
val users = uiState.users.value
|
||||
val accountsToUser = users.sortedBy { it.displayIndex }
|
||||
.flatMap { user -> user.accounts.sortedBy { it.displayIndex }.map { it to user } }.toMap()
|
||||
val banks = uiState.banks.value
|
||||
val accountsToBank = banks.sortedBy { it.displayIndex }
|
||||
.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 }
|
||||
|
||||
if (accountsSupportingTransferringMoney.isEmpty()) {
|
||||
|
@ -98,7 +98,7 @@ fun TransferMoneyDialog(
|
|||
|
||||
transferMoneyJob = coroutineScope.launch(Dispatchers.IOorDefault) {
|
||||
val successful = bankingService.transferMoney(
|
||||
accountsToUser[senderAccount]!!, senderAccount,
|
||||
accountsToBank[senderAccount]!!, senderAccount,
|
||||
recipientName, recipientAccountIdentifier,
|
||||
Amount(amount), // TODO: verify entered amount is valid
|
||||
"EUR", // TODO: add input field for currency
|
||||
|
@ -132,13 +132,13 @@ fun TransferMoneyDialog(
|
|||
Select(
|
||||
"Konto",
|
||||
accountsSupportingTransferringMoney, senderAccount, { senderAccount = it },
|
||||
{ account -> "${accountsToUser[account]?.displayName} ${account.displayName}" },
|
||||
leadingIcon = { BankIcon(accountsToUser[senderAccount]) }
|
||||
{ account -> "${accountsToBank[account]?.displayName} ${account.displayName}" },
|
||||
leadingIcon = { BankIcon(accountsToBank[senderAccount]) }
|
||||
) { account ->
|
||||
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}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,8 +7,8 @@ import net.codinux.banking.dataaccess.entities.AccountTransactionEntity
|
|||
|
||||
data class AccountTransactionViewModel(
|
||||
val id: Long,
|
||||
val userId: Long,
|
||||
val bankAccountId: Long,
|
||||
val bankId: Long,
|
||||
val accountId: Long,
|
||||
|
||||
val amount: Amount,
|
||||
val currency: String,
|
||||
|
@ -17,11 +17,11 @@ data class AccountTransactionViewModel(
|
|||
val otherPartyName: String? = null,
|
||||
|
||||
val postingText: String? = null,
|
||||
val userSetDisplayName: String? = null,
|
||||
val category: String? = null
|
||||
val userSetReference: String? = null,
|
||||
val userSetOtherPartyName: String? = null
|
||||
) {
|
||||
constructor(entity: AccountTransactionEntity) : this(entity.id, entity.userId, entity.bankAccountId, entity)
|
||||
constructor(entity: AccountTransactionEntity) : this(entity.id, entity.bankId, entity.accountId, entity)
|
||||
|
||||
constructor(id: Long, userId: Long, bankAccountId: Long, transaction: AccountTransaction)
|
||||
: this(id, userId, bankAccountId, transaction.amount, transaction.currency, transaction.reference, transaction.valueDate, transaction.otherPartyName, transaction.postingText)
|
||||
constructor(id: Long, bankId: Long, accountId: Long, transaction: AccountTransaction)
|
||||
: this(id, bankId, accountId, transaction.amount, transaction.currency, transaction.reference, transaction.valueDate, transaction.otherPartyName, transaction.postingText, transaction.userSetReference, transaction.userSetOtherPartyName)
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ package net.codinux.banking.ui.model
|
|||
|
||||
import androidx.compose.runtime.*
|
||||
import net.codinux.banking.dataaccess.entities.BankAccountEntity
|
||||
import net.codinux.banking.dataaccess.entities.UserEntity
|
||||
import net.codinux.banking.dataaccess.entities.BankAccessEntity
|
||||
|
||||
class AccountTransactionsFilter {
|
||||
|
||||
|
@ -19,7 +19,7 @@ class AccountTransactionsFilter {
|
|||
val selectedAccount: BankAccountFilter?
|
||||
get() = selectedAccounts.value.firstOrNull()
|
||||
|
||||
fun selectedAccountChanged(user: UserEntity?, bankAccount: BankAccountEntity?) {
|
||||
fun selectedAccountChanged(user: BankAccessEntity?, bankAccount: BankAccountEntity?) {
|
||||
selectedAccounts.value = if (user == null) {
|
||||
emptyList()
|
||||
} else {
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
package net.codinux.banking.ui.model
|
||||
|
||||
import net.codinux.banking.dataaccess.entities.BankAccountEntity
|
||||
import net.codinux.banking.dataaccess.entities.UserEntity
|
||||
import net.codinux.banking.dataaccess.entities.BankAccessEntity
|
||||
|
||||
data class BankAccountFilter(
|
||||
val user: UserEntity,
|
||||
val bank: BankAccessEntity,
|
||||
val bankAccount: BankAccountEntity? = null
|
||||
)
|
|
@ -6,7 +6,7 @@ import kotlinx.serialization.Serializable
|
|||
@Serializable
|
||||
class BankInfo(
|
||||
val name: String,
|
||||
val bankCode: String,
|
||||
val domesticBankCode: String,
|
||||
val bic: String = "",
|
||||
val postalCode: String,
|
||||
val city: String,
|
||||
|
@ -23,5 +23,5 @@ class BankInfo(
|
|||
get() = pinTanVersion == "FinTS V3.0"
|
||||
|
||||
|
||||
override fun toString() = "$bankCode $name $city"
|
||||
override fun toString() = "$domesticBankCode $name $city"
|
||||
}
|
|
@ -1,12 +1,12 @@
|
|||
package net.codinux.banking.ui.model.events
|
||||
|
||||
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.ui.model.AccountTransactionViewModel
|
||||
|
||||
data class AccountTransactionsRetrievedEvent(
|
||||
val user: User,
|
||||
val bank: BankAccess,
|
||||
val account: BankAccount,
|
||||
val newTransactions: List<AccountTransactionViewModel>,
|
||||
val updatedHoldings: List<Holding> = emptyList()
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
package net.codinux.banking.ui.model.settings
|
||||
|
||||
enum class AppAuthenticationMethod {
|
||||
None,
|
||||
Password,
|
||||
Biometric
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
package net.codinux.banking.ui.model.settings
|
||||
|
||||
class AppSettings(
|
||||
var authenticationMethod: AppAuthenticationMethod = AppAuthenticationMethod.None,
|
||||
var hashedPassword: String? = null,
|
||||
|
||||
var updateAccountsOnAppStart: Boolean = false,
|
||||
var updateAccountsIfNoUpdatedForHours: Int = 6
|
||||
) {
|
||||
override fun toString() = "$authenticationMethod"
|
||||
}
|
|
@ -2,7 +2,7 @@ package net.codinux.banking.ui.service
|
|||
|
||||
import net.codinux.banking.dataaccess.entities.BankAccountEntity
|
||||
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.AccountTransactionsFilter
|
||||
import net.codinux.banking.ui.model.BankAccountFilter
|
||||
|
@ -38,9 +38,9 @@ class AccountTransactionsFilterService {
|
|||
private fun matchesFilter(transaction: AccountTransactionViewModel, accountsFilter: List<BankAccountFilter>): Boolean =
|
||||
accountsFilter.any { (user, bankAccount) ->
|
||||
if (bankAccount != null) {
|
||||
transaction.bankAccountId == bankAccount.id
|
||||
transaction.accountId == bankAccount.id
|
||||
} else {
|
||||
transaction.userId == user.id
|
||||
transaction.bankId == user.id
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -71,9 +71,9 @@ class AccountTransactionsFilterService {
|
|||
private fun matchesFilter(holding: HoldingEntity, filter: List<BankAccountFilter>): Boolean =
|
||||
filter.any { (user, bankAccount) ->
|
||||
if (bankAccount != null) {
|
||||
holding.bankAccountId == bankAccount.id
|
||||
holding.accountId == bankAccount.id
|
||||
} else {
|
||||
holding.userId == user.id
|
||||
holding.bankId == user.id
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -83,14 +83,14 @@ class AccountTransactionsFilterService {
|
|||
|| holding.wkn?.contains(searchTerm, true) == true
|
||||
|
||||
|
||||
fun isSelected(user: UserEntity, transactionsFilter: AccountTransactionsFilter): Boolean {
|
||||
fun isSelected(user: BankAccessEntity, transactionsFilter: AccountTransactionsFilter): Boolean {
|
||||
if (transactionsFilter.showAllAccounts) {
|
||||
return false
|
||||
}
|
||||
|
||||
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 {
|
||||
|
|
|
@ -36,7 +36,7 @@ class BankFinder {
|
|||
return getBankList(maxItems)
|
||||
}
|
||||
|
||||
return getBankList().asSequence().filter { it.bankCode.startsWith(query) }
|
||||
return getBankList().asSequence().filter { it.domesticBankCode.startsWith(query) }
|
||||
.max(maxItems)
|
||||
}
|
||||
|
||||
|
@ -69,7 +69,7 @@ class BankFinder {
|
|||
|
||||
val bankCode = iban.substring(4) // first two letters are the country code, third and fourth char are the checksum, bank code starts at 5th char
|
||||
|
||||
val result = getBankList().asSequence().filter { it.bankCode.startsWith(bankCode) }.max(2)
|
||||
val result = getBankList().asSequence().filter { it.domesticBankCode.startsWith(bankCode) }.max(2)
|
||||
|
||||
return if (result.size > 1) { // non unique result, but should actually never happen for BICs
|
||||
null
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
package net.codinux.banking.ui.service
|
||||
|
||||
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
|
||||
|
||||
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) {
|
||||
BankingGroup.Sparkasse -> "https://sparkasse.de/favicon-32x32.png"
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
package net.codinux.banking.ui.service
|
||||
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import bankmeister.composeapp.generated.resources.Res
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.datetime.LocalDate
|
||||
import net.codinux.banking.client.SimpleBankingClientCallback
|
||||
import net.codinux.banking.client.fints4k.FinTs4kBankingClient
|
||||
|
@ -22,6 +24,8 @@ import net.codinux.banking.ui.model.BankInfo
|
|||
import net.codinux.banking.ui.model.error.*
|
||||
import net.codinux.banking.ui.model.events.AccountTransactionsRetrievedEvent
|
||||
import net.codinux.banking.ui.model.events.TransferredMoneyEvent
|
||||
import net.codinux.banking.ui.model.settings.AppSettings
|
||||
import net.codinux.banking.ui.settings.UiSettings
|
||||
import net.codinux.banking.ui.state.UiState
|
||||
import net.codinux.csv.reader.CsvReader
|
||||
import net.codinux.log.logger
|
||||
|
@ -30,6 +34,7 @@ import org.jetbrains.compose.resources.ExperimentalResourceApi
|
|||
@OptIn(ExperimentalResourceApi::class)
|
||||
class BankingService(
|
||||
private val uiState: UiState,
|
||||
private val uiSettings: UiSettings,
|
||||
private val bankingRepository: BankingRepository,
|
||||
private val bankFinder: BankFinder
|
||||
) {
|
||||
|
@ -45,21 +50,40 @@ class BankingService(
|
|||
|
||||
suspend fun init() {
|
||||
try {
|
||||
uiState.users.value = getAllUsers()
|
||||
var appSettings = getAppSettings()
|
||||
if (appSettings == null) {
|
||||
appSettings = AppSettings()
|
||||
saveAppSettings(appSettings)
|
||||
}
|
||||
uiState.appSettings.value = appSettings
|
||||
|
||||
bankingRepository.getUiSettings(uiSettings)
|
||||
|
||||
updateOnChanges(uiSettings)
|
||||
|
||||
|
||||
uiState.banks.value = getAllBanks()
|
||||
|
||||
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) {
|
||||
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 getAppSettings() = bankingRepository.getAppSettings()
|
||||
|
||||
suspend fun saveAppSettings(settings: AppSettings) = bankingRepository.saveAppSettings(settings)
|
||||
|
||||
suspend fun saveUiSettings(settings: UiSettings) = bankingRepository.saveUiSettings(settings)
|
||||
|
||||
|
||||
fun getAllBanks() = bankingRepository.getAllBanks()
|
||||
|
||||
fun getAllAccountTransactions() = bankingRepository.getAllAccountTransactions()
|
||||
|
||||
fun getAllTransactionsOfUser(user: UserEntity) = bankingRepository.getAllTransactionsOfUser(user)
|
||||
fun getAllTransactionsForBank(bank: BankAccessEntity) = bankingRepository.getAllTransactionsForBank(bank)
|
||||
|
||||
fun getAllAccountTransactionsAsViewModel() = bankingRepository.getAllAccountTransactionsAsViewModel()
|
||||
|
||||
|
@ -72,7 +96,7 @@ class BankingService(
|
|||
suspend fun addAccount(bank: BankInfo, loginName: String, password: String, retrieveAllTransactions: Boolean = false): Boolean {
|
||||
try {
|
||||
val retrieveTransactions = if (retrieveAllTransactions) RetrieveTransactions.All else RetrieveTransactions.OfLast90Days
|
||||
val response = client.getAccountDataAsync(GetAccountDataRequest(bank.bankCode, loginName, password, GetAccountDataOptions(retrieveTransactions), mapBankInfo(bank)))
|
||||
val response = client.getAccountDataAsync(GetAccountDataRequest(bank.domesticBankCode, loginName, password, GetAccountDataOptions(retrieveTransactions), mapBankInfo(bank)))
|
||||
|
||||
if (response.type == ResponseType.Success && response.data != null) {
|
||||
handleSuccessfulGetAccountDataResponse(response.data!!)
|
||||
|
@ -101,21 +125,21 @@ class BankingService(
|
|||
|
||||
private suspend fun handleSuccessfulGetAccountDataResponse(response: GetAccountDataResponse) {
|
||||
try {
|
||||
val newUser = response.user
|
||||
newUser.displayIndex = uiState.users.value.size
|
||||
val newBank = response.bank
|
||||
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.bookedTransactions }.size} transactions" }
|
||||
|
||||
val users = uiState.users.value.toMutableList()
|
||||
users.add(newUserEntity)
|
||||
uiState.users.value = users
|
||||
val banks = uiState.banks.value.toMutableList()
|
||||
banks.add(newBankEntity)
|
||||
uiState.banks.value = banks
|
||||
|
||||
updateTransactionsInUi(newUserEntity.accounts.flatMap { it.bookedTransactionsEntities })
|
||||
updateHoldingsInUi(newUserEntity.accounts.flatMap { it.holdings }, emptyList())
|
||||
updateTransactionsInUi(newBankEntity.accounts.flatMap { it.bookedTransactions })
|
||||
updateHoldingsInUi(newBankEntity.accounts.flatMap { it.holdings }, emptyList())
|
||||
} catch (e: Throwable) {
|
||||
log.error(e) { "Could not save user account ${response.user}" }
|
||||
log.error(e) { "Could not save bank ${response.bank}" }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -123,45 +147,45 @@ class BankingService(
|
|||
suspend fun updateAccountTransactions() {
|
||||
val selectedAccount = uiState.transactionsFilter.value.selectedAccount
|
||||
if (selectedAccount != null) {
|
||||
updateAccountTransactions(selectedAccount.user, selectedAccount.bankAccount)
|
||||
updateAccountTransactions(selectedAccount.bank, selectedAccount.bankAccount)
|
||||
} else {
|
||||
uiState.users.value.forEach { user ->
|
||||
updateAccountTransactions(user)
|
||||
uiState.banks.value.forEach { bank ->
|
||||
updateAccountTransactions(bank)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun updateAccountTransactions(user: UserEntity, bankAccount: BankAccountEntity? = null) {
|
||||
private suspend fun updateAccountTransactions(bank: BankAccessEntity, bankAccount: BankAccountEntity? = null) {
|
||||
withContext(Dispatchers.IOorDefault) {
|
||||
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) {
|
||||
handleSuccessfulUpdateAccountTransactionsResponse(user, response.data!!)
|
||||
handleSuccessfulUpdateAccountTransactionsResponse(bank, response.data!!)
|
||||
} else {
|
||||
handleUnsuccessfulBankingClientResponse(BankingClientAction.UpdateAccountTransactions, response)
|
||||
}
|
||||
} 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 {
|
||||
// TODO: when user gets updated by BankingClient, also update user in database
|
||||
// val newUser = response.user
|
||||
// TODO: when bank gets updated by BankingClient, also update bank in database
|
||||
// val newUser = response.bank
|
||||
// val newUserEntity = bankingRepository.persistUser(newUser)
|
||||
//
|
||||
// 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 ->
|
||||
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
|
||||
|
||||
val existingAccountTransactions = userTransactions.filter { it.bankAccountId == account.id }
|
||||
val existingAccountTransactions = transactionsForBank.filter { it.accountId == account.id }
|
||||
|
||||
val newTransactions = modelService.findNewTransactions(response.bookedTransactions, existingAccountTransactions)
|
||||
|
||||
|
@ -181,18 +205,16 @@ class BankingService(
|
|||
bankingRepository.updateHoldings(updateHoldings(updatedExistingHoldings, updatedRetrievedHoldings))
|
||||
bankingRepository.deleteHoldings(deletedHoldings)
|
||||
|
||||
account.holdings = account.holdings.toMutableList().apply {
|
||||
addAll(persistedNewHoldings)
|
||||
removeAll(deletedHoldings)
|
||||
}
|
||||
account.holdings.removeAll(deletedHoldings)
|
||||
account.addHoldings(persistedNewHoldings)
|
||||
|
||||
updateHoldingsInUi(persistedNewHoldings, deletedHoldings)
|
||||
|
||||
val transactionsViewModel = updateTransactionsInUi(newTransactionsEntities)
|
||||
uiState.dispatchNewTransactionsRetrieved(AccountTransactionsRetrievedEvent(user, account, transactionsViewModel, response.holdings))
|
||||
uiState.dispatchNewTransactionsRetrieved(AccountTransactionsRetrievedEvent(bank, account, transactionsViewModel, response.holdings))
|
||||
}
|
||||
} 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 +257,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,
|
||||
paymentReference: String? = null, instantTransfer: Boolean = false, recipientBankIdentifier: String? = null): Boolean {
|
||||
val response = client.transferMoneyAsync(TransferMoneyRequestForUser(
|
||||
user.bankCode, user.loginName, user.password!!,
|
||||
bank.domesticBankCode, bank.loginName, bank.password!!,
|
||||
BankAccountIdentifier(account.identifier, account.subAccountNumber, account.iban), // TODO: use BankingClient's one
|
||||
recipientName, recipientAccountIdentifier, recipientBankIdentifier,
|
||||
amount, "EUR",
|
||||
|
@ -251,7 +273,7 @@ class BankingService(
|
|||
} else if (response.type == ResponseType.Success) {
|
||||
uiState.dispatchTransferredMoney(TransferredMoneyEvent(recipientName, amount, currency))
|
||||
|
||||
updateAccountTransactions(user, account)
|
||||
updateAccountTransactions(bank, account)
|
||||
}
|
||||
|
||||
return response.type == ResponseType.Success
|
||||
|
@ -269,6 +291,24 @@ class BankingService(
|
|||
}
|
||||
|
||||
|
||||
private suspend fun updateOnChanges(uiSettings: UiSettings) {
|
||||
updateOnChanges(uiSettings, uiSettings.transactionsGrouping)
|
||||
|
||||
updateOnChanges(uiSettings, uiSettings.showBalance)
|
||||
updateOnChanges(uiSettings, uiSettings.showBankIcons)
|
||||
updateOnChanges(uiSettings, uiSettings.showColoredAmounts)
|
||||
updateOnChanges(uiSettings, uiSettings.showTransactionsInAlternatingColors)
|
||||
}
|
||||
|
||||
private suspend fun updateOnChanges(uiSettings: UiSettings, state: MutableStateFlow<*>) {
|
||||
uiSettings.viewModelScope.launch(Dispatchers.Unconfined) {
|
||||
state.collect {
|
||||
saveUiSettings(uiSettings)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private suspend fun readTransactionsFromCsv(): List<AccountTransaction> {
|
||||
val csv = Res.readBytes("files/transactions.csv").decodeToString()
|
||||
val csvReader = CsvReader(hasHeaderRow = true, reuseRowInstance = true, skipEmptyRows = true).read(csv)
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package net.codinux.banking.ui.service
|
||||
|
||||
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.AccountTransactionsFilter
|
||||
|
||||
|
@ -10,8 +10,8 @@ class CalculatorService {
|
|||
fun sumTransactions(transactions: Collection<AccountTransactionViewModel>): Amount =
|
||||
transactions.map { it.amount }.sum()
|
||||
|
||||
fun calculateBalanceOfUser(user: User): Amount =
|
||||
sumAmounts(user.accounts.map { it.balance })
|
||||
fun calculateBalanceOfBankAccess(bank: BankAccess): Amount =
|
||||
sumAmounts(bank.accounts.map { it.balance })
|
||||
|
||||
fun sumAmounts(amounts: Collection<Amount>): Amount =
|
||||
amounts.sum()
|
||||
|
@ -22,9 +22,9 @@ class CalculatorService {
|
|||
fun sumExpenses(transactions: Collection<AccountTransactionViewModel>): Amount =
|
||||
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) {
|
||||
return sumAmounts(users.flatMap { it.accounts.map { it.balance } })
|
||||
return sumAmounts(banks.flatMap { it.accounts.map { it.balance } })
|
||||
}
|
||||
|
||||
val selectedAccount = filter.selectedAccount
|
||||
|
@ -33,7 +33,7 @@ class CalculatorService {
|
|||
if (selectedAccount.bankAccount != null) {
|
||||
selectedAccount.bankAccount.balance
|
||||
} else {
|
||||
calculateBalanceOfUser(selectedAccount.user)
|
||||
calculateBalanceOfBankAccess(selectedAccount.bank)
|
||||
}
|
||||
} else {
|
||||
sumTransactions(transactions)
|
||||
|
|
|
@ -10,7 +10,7 @@ class UiSettings : ViewModel() {
|
|||
|
||||
val transactionsGrouping = MutableStateFlow(TransactionsGrouping.Month)
|
||||
|
||||
val zebraStripes = MutableStateFlow(true)
|
||||
val showTransactionsInAlternatingColors = MutableStateFlow(true)
|
||||
|
||||
val showBankIcons = MutableStateFlow(true)
|
||||
|
||||
|
|
|
@ -8,17 +8,21 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
|||
import net.codinux.banking.client.model.tan.EnterTanResult
|
||||
import net.codinux.banking.client.model.tan.TanChallenge
|
||||
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.error.ApplicationError
|
||||
import net.codinux.banking.ui.model.error.BankingClientError
|
||||
import net.codinux.banking.ui.model.error.ErroneousAction
|
||||
import net.codinux.banking.ui.model.events.AccountTransactionsRetrievedEvent
|
||||
import net.codinux.banking.ui.model.events.TransferredMoneyEvent
|
||||
import net.codinux.banking.ui.model.settings.AppSettings
|
||||
|
||||
class UiState : ViewModel() {
|
||||
|
||||
val users = MutableStateFlow<List<UserEntity>>(emptyList())
|
||||
val appSettings = MutableStateFlow(AppSettings())
|
||||
|
||||
|
||||
val banks = MutableStateFlow<List<BankAccessEntity>>(emptyList())
|
||||
|
||||
val transactions = MutableStateFlow<List<AccountTransactionViewModel>>(emptyList())
|
||||
|
||||
|
|
|
@ -3,8 +3,8 @@ import kotlin.Boolean;
|
|||
CREATE TABLE IF NOT EXISTS AccountTransaction (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
|
||||
userId INTEGER NOT NULL,
|
||||
bankAccountId INTEGER NOT NULL,
|
||||
bankId INTEGER NOT NULL,
|
||||
accountId INTEGER NOT NULL,
|
||||
|
||||
amount TEXT NOT NULL,
|
||||
currency TEXT NOT NULL,
|
||||
|
@ -22,7 +22,8 @@ CREATE TABLE IF NOT EXISTS AccountTransaction (
|
|||
openingBalance TEXT,
|
||||
closingBalance TEXT,
|
||||
|
||||
userSetDisplayName TEXT,
|
||||
userSetReference TEXT,
|
||||
userSetOtherPartyName TEXT,
|
||||
category TEXT,
|
||||
notes TEXT,
|
||||
|
||||
|
@ -53,10 +54,16 @@ CREATE TABLE IF NOT EXISTS AccountTransaction (
|
|||
isReversal INTEGER AS Boolean NOT NULL
|
||||
);
|
||||
|
||||
CREATE INDEX idx_AccountTransaction_bankId
|
||||
ON AccountTransaction (bankId);
|
||||
|
||||
CREATE INDEX idx_AccountTransaction_accountId
|
||||
ON AccountTransaction (accountId);
|
||||
|
||||
|
||||
insertTransaction:
|
||||
INSERT INTO AccountTransaction(
|
||||
userId, bankAccountId,
|
||||
bankId, accountId,
|
||||
|
||||
amount, currency, reference,
|
||||
bookingDate, valueDate,
|
||||
|
@ -66,7 +73,8 @@ INSERT INTO AccountTransaction(
|
|||
|
||||
openingBalance, closingBalance,
|
||||
|
||||
userSetDisplayName, category, notes,
|
||||
userSetReference, userSetOtherPartyName,
|
||||
category, notes,
|
||||
|
||||
statementNumber, sheetNumber,
|
||||
|
||||
|
@ -96,7 +104,8 @@ VALUES(
|
|||
|
||||
?, ?,
|
||||
|
||||
?, ?, ?,
|
||||
?, ?,
|
||||
?, ?,
|
||||
|
||||
?, ?,
|
||||
|
||||
|
@ -118,18 +127,18 @@ VALUES(
|
|||
);
|
||||
|
||||
|
||||
selectAllTransactions:
|
||||
getAllTransactions:
|
||||
SELECT AccountTransaction.*
|
||||
FROM AccountTransaction;
|
||||
|
||||
selectAllTransactionsAsViewModel:
|
||||
SELECT id, userId, bankAccountId, amount, currency, reference, valueDate, otherPartyName, postingText, userSetDisplayName, category
|
||||
getAllTransactionsAsViewModel:
|
||||
SELECT id, bankId, accountId, amount, currency, reference, valueDate, otherPartyName, postingText, userSetReference, userSetOtherPartyName
|
||||
FROM AccountTransaction;
|
||||
|
||||
|
||||
selectAllTransactionsOfUser:
|
||||
getAllTransactionsForBank:
|
||||
SELECT AccountTransaction.*
|
||||
FROM AccountTransaction WHERE userId = ?;
|
||||
FROM AccountTransaction WHERE bankId = ?;
|
||||
|
||||
getTransactionWithId:
|
||||
SELECT AccountTransaction.*
|
||||
|
@ -141,8 +150,8 @@ FROM AccountTransaction WHERE id = ?;
|
|||
CREATE TABLE IF NOT EXISTS Holding (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
|
||||
userId INTEGER NOT NULL,
|
||||
bankAccountId INTEGER NOT NULL,
|
||||
bankId INTEGER NOT NULL,
|
||||
accountId INTEGER NOT NULL,
|
||||
|
||||
name TEXT NOT NULL,
|
||||
isin TEXT,
|
||||
|
@ -163,10 +172,16 @@ CREATE TABLE IF NOT EXISTS Holding (
|
|||
buyingDate TEXT
|
||||
);
|
||||
|
||||
CREATE INDEX idx_Holding_bankId
|
||||
ON Holding (bankId);
|
||||
|
||||
CREATE INDEX idx_Holding_accountId
|
||||
ON Holding (accountId);
|
||||
|
||||
|
||||
insertHolding:
|
||||
INSERT INTO Holding(
|
||||
userId, bankAccountId,
|
||||
bankId, accountId,
|
||||
|
||||
name, isin, wkn,
|
||||
|
||||
|
|
|
@ -1,24 +1,25 @@
|
|||
import kotlin.Boolean;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS User (
|
||||
CREATE TABLE IF NOT EXISTS BankAccess (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
|
||||
bankCode TEXT NOT NULL,
|
||||
domesticBankCode TEXT NOT NULL,
|
||||
loginName TEXT NOT NULL,
|
||||
password TEXT,
|
||||
|
||||
bankName TEXT NOT NULL,
|
||||
bic TEXT NOT NULL,
|
||||
bic TEXT,
|
||||
|
||||
customerName TEXT NOT NULL,
|
||||
userId TEXT,
|
||||
|
||||
selectedTanMethodId TEXT,
|
||||
selectedTanMethodIdentifier TEXT,
|
||||
|
||||
selectedTanMediumName TEXT,
|
||||
selectedTanMediumIdentifier TEXT,
|
||||
|
||||
bankingGroup TEXT,
|
||||
serverAddress TEXT,
|
||||
countryCode TEXT NOT NULL,
|
||||
|
||||
clientData TEXT,
|
||||
|
||||
|
@ -31,20 +32,21 @@ CREATE TABLE IF NOT EXISTS User (
|
|||
);
|
||||
|
||||
|
||||
insertUser:
|
||||
INSERT INTO User(
|
||||
bankCode, loginName, password,
|
||||
insertBank:
|
||||
INSERT INTO BankAccess(
|
||||
domesticBankCode, loginName, password,
|
||||
|
||||
bankName, bic,
|
||||
|
||||
customerName, userId,
|
||||
|
||||
selectedTanMethodId,
|
||||
selectedTanMethodIdentifier,
|
||||
|
||||
selectedTanMediumName,
|
||||
selectedTanMediumIdentifier,
|
||||
|
||||
bankingGroup,
|
||||
serverAddress,
|
||||
countryCode,
|
||||
|
||||
clientData,
|
||||
|
||||
|
@ -61,7 +63,7 @@ VALUES(
|
|||
|
||||
?,
|
||||
|
||||
?, ?,
|
||||
?, ?, ?,
|
||||
|
||||
?,
|
||||
|
||||
|
@ -72,16 +74,16 @@ VALUES(
|
|||
);
|
||||
|
||||
|
||||
selectAllUsers:
|
||||
SELECT User.*
|
||||
FROM User;
|
||||
getAllBanks:
|
||||
SELECT BankAccess.*
|
||||
FROM BankAccess;
|
||||
|
||||
|
||||
|
||||
CREATE TABLE IF NOT EXISTS BankAccount (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
|
||||
userId INTEGER NOT NULL,
|
||||
bankId INTEGER NOT NULL,
|
||||
|
||||
identifier TEXT NOT NULL,
|
||||
subAccountNumber TEXT,
|
||||
|
@ -109,10 +111,13 @@ CREATE TABLE IF NOT EXISTS BankAccount (
|
|||
includeInAutomaticAccountsUpdate INTEGER AS Boolean NOT NULL
|
||||
);
|
||||
|
||||
CREATE INDEX idx_BankAccount_bankId
|
||||
ON BankAccount (bankId);
|
||||
|
||||
|
||||
insertBankAccount:
|
||||
INSERT INTO BankAccount(
|
||||
userId,
|
||||
bankId,
|
||||
|
||||
identifier, accountHolderName, type,
|
||||
iban, subAccountNumber, productName, currency, accountLimit,
|
||||
|
@ -145,7 +150,7 @@ VALUES(
|
|||
);
|
||||
|
||||
|
||||
selectAllBankAccounts:
|
||||
getAllBankAccounts:
|
||||
SELECT BankAccount.*
|
||||
FROM BankAccount;
|
||||
|
||||
|
@ -154,25 +159,32 @@ FROM BankAccount;
|
|||
CREATE TABLE IF NOT EXISTS TanMethod (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
|
||||
userId INTEGER NOT NULL,
|
||||
bankId INTEGER NOT NULL,
|
||||
|
||||
displayName TEXT NOT NULL,
|
||||
type TEXT NOT NULL,
|
||||
identifier TEXT NOT NULL,
|
||||
maxTanInputLength INTEGER ,
|
||||
allowedTanFormat TEXT NOT NULL
|
||||
allowedTanFormat TEXT NOT NULL,
|
||||
|
||||
userSetDisplayName TEXT
|
||||
);
|
||||
|
||||
CREATE INDEX idx_TanMethod_bankId
|
||||
ON TanMethod (bankId);
|
||||
|
||||
|
||||
insertTanMethod:
|
||||
INSERT INTO TanMethod(
|
||||
userId,
|
||||
bankId,
|
||||
|
||||
displayName,
|
||||
type,
|
||||
identifier,
|
||||
maxTanInputLength,
|
||||
allowedTanFormat
|
||||
allowedTanFormat,
|
||||
|
||||
userSetDisplayName
|
||||
)
|
||||
VALUES (
|
||||
?,
|
||||
|
@ -181,11 +193,13 @@ VALUES (
|
|||
?,
|
||||
?,
|
||||
?,
|
||||
?,
|
||||
|
||||
?
|
||||
);
|
||||
|
||||
|
||||
selectAllTanMethods:
|
||||
getAllTanMethods:
|
||||
SELECT TanMethod.*
|
||||
FROM TanMethod;
|
||||
|
||||
|
@ -194,7 +208,7 @@ FROM TanMethod;
|
|||
CREATE TABLE IF NOT EXISTS TanMedium (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
|
||||
userId INTEGER NOT NULL,
|
||||
bankId INTEGER NOT NULL,
|
||||
|
||||
type TEXT NOT NULL,
|
||||
mediumName TEXT,
|
||||
|
@ -209,13 +223,18 @@ CREATE TABLE IF NOT EXISTS TanMedium (
|
|||
cardSequenceNumber TEXT,
|
||||
cardType INTEGER,
|
||||
validFrom TEXT,
|
||||
validTo TEXT
|
||||
validTo TEXT,
|
||||
|
||||
userSetDisplayName TEXT
|
||||
);
|
||||
|
||||
CREATE INDEX idx_TanMedium_bankId
|
||||
ON TanMedium (bankId);
|
||||
|
||||
|
||||
insertTanMedium:
|
||||
INSERT INTO TanMedium(
|
||||
userId,
|
||||
bankId,
|
||||
|
||||
type,
|
||||
mediumName,
|
||||
|
@ -228,7 +247,9 @@ INSERT INTO TanMedium(
|
|||
cardSequenceNumber,
|
||||
cardType,
|
||||
validFrom,
|
||||
validTo
|
||||
validTo,
|
||||
|
||||
userSetDisplayName
|
||||
)
|
||||
VALUES (
|
||||
?,
|
||||
|
@ -244,11 +265,13 @@ VALUES (
|
|||
?,
|
||||
?,
|
||||
?,
|
||||
?,
|
||||
|
||||
?
|
||||
);
|
||||
|
||||
|
||||
selectAllTanMedia:
|
||||
getAllTanMedia:
|
||||
SELECT TanMedium.*
|
||||
FROM TanMedium;
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
import kotlin.Boolean;
|
||||
|
||||
|
||||
CREATE TABLE IF NOT EXISTS AppSettings (
|
||||
id INTEGER PRIMARY KEY,
|
||||
|
||||
authenticationMethod TEXT NOT NULL,
|
||||
hashedPassword TEXT,
|
||||
|
||||
updateAccountsOnAppStart INTEGER AS Boolean NOT NULL,
|
||||
updateAccountsIfNoUpdatedForHours INTEGER NOT NULL,
|
||||
|
||||
sideMenuWidth INTEGER NOT NULL,
|
||||
|
||||
windowPositionX INTEGER NOT NULL,
|
||||
windowPositionY INTEGER NOT NULL,
|
||||
|
||||
windowWidth INTEGER NOT NULL,
|
||||
windowHeight INTEGER NOT NULL,
|
||||
|
||||
windowState TEXT
|
||||
);
|
||||
|
||||
|
||||
getAppSettings:
|
||||
SELECT * FROM AppSettings WHERE id = 1;
|
||||
|
||||
upsertAppSettings:
|
||||
INSERT OR REPLACE INTO AppSettings(
|
||||
id,
|
||||
|
||||
authenticationMethod, hashedPassword, updateAccountsOnAppStart, updateAccountsIfNoUpdatedForHours,
|
||||
|
||||
sideMenuWidth,
|
||||
|
||||
windowPositionX, windowPositionY, windowWidth, windowHeight,
|
||||
|
||||
windowState
|
||||
)
|
||||
VALUES (
|
||||
1,
|
||||
|
||||
?, ?, ?, ?,
|
||||
|
||||
?,
|
||||
|
||||
?, ?,
|
||||
?, ?,
|
||||
|
||||
?
|
||||
);
|
||||
|
||||
|
||||
|
||||
CREATE TABLE IF NOT EXISTS UiSettings (
|
||||
id INTEGER PRIMARY KEY,
|
||||
|
||||
transactionsGrouping TEXT NOT NULL,
|
||||
|
||||
showBalance INTEGER AS Boolean NOT NULL,
|
||||
showBankIcons INTEGER AS Boolean NOT NULL,
|
||||
showColoredAmounts INTEGER AS Boolean NOT NULL,
|
||||
showTransactionsInAlternatingColors INTEGER AS Boolean NOT NULL
|
||||
);
|
||||
|
||||
|
||||
getUiSettings:
|
||||
SELECT * FROM UiSettings WHERE id = 1;
|
||||
|
||||
upsertUiSettings:
|
||||
INSERT OR REPLACE INTO UiSettings(id, transactionsGrouping, showBalance, showBankIcons, showColoredAmounts, showTransactionsInAlternatingColors)
|
||||
VALUES (1, ?, ?, ?, ?, ?);
|
|
@ -13,8 +13,8 @@ import net.codinux.banking.ui.model.TanChallengeReceived
|
|||
@Composable
|
||||
fun EnterTanDialogPreview_EnterTan() {
|
||||
val tanMethods = listOf(TanMethod("Zeig mich an", TanMethodType.AppTan, "902"))
|
||||
val user = 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 bank = BankViewInfo("12345678", "SupiDupiNutzer", "Abzockbank", BankingGroup.Postbank)
|
||||
val tanChallenge = TanChallenge(TanChallengeType.EnterTan, ActionRequiringTan.GetAccountInfo, "Geben Sie die TAN ein", tanMethods.first().identifier, tanMethods, bank = bank)
|
||||
|
||||
EnterTanDialog(TanChallengeReceived(tanChallenge) { }) { }
|
||||
}
|
||||
|
@ -27,9 +27,9 @@ fun EnterTanDialogPreview_TanImage() {
|
|||
val tanMethod = TanMethod("photoTAN-Verfahren", TanMethodType.photoTan, "902", 6, AllowedTanFormat.Numeric)
|
||||
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) { }) { }
|
||||
}
|
||||
|
@ -50,10 +50,10 @@ fun EnterTanDialogPreview_WithMultipleTanMedia() { // shows that dialog is reall
|
|||
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 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) { }) { }
|
||||
}
|
|
@ -17,8 +17,8 @@ class SqliteBankingRepositoryTest {
|
|||
}
|
||||
|
||||
private val underTest = object : SqliteBankingRepository(sqlDriver) {
|
||||
override public suspend fun persistTransaction(userId: Long, bankAccountId: Long, transaction: AccountTransaction): AccountTransactionEntity =
|
||||
super.persistTransaction(userId, bankAccountId, transaction)
|
||||
override public suspend fun persistTransaction(bankId: Long, accountId: Long, transaction: AccountTransaction): AccountTransactionEntity =
|
||||
super.persistTransaction(bankId, accountId, transaction)
|
||||
}
|
||||
|
||||
|
||||
|
@ -27,35 +27,35 @@ class SqliteBankingRepositoryTest {
|
|||
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)
|
||||
)
|
||||
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
|
||||
displayIndex = 99
|
||||
}
|
||||
|
||||
val persisted = underTest.persistUser(user)
|
||||
val persisted = underTest.persistBank(bank)
|
||||
|
||||
assertNotNull(persisted.id)
|
||||
|
||||
assertEquals(user.bankCode, persisted.bankCode)
|
||||
assertEquals(user.loginName, persisted.loginName)
|
||||
assertEquals(user.password, persisted.password)
|
||||
assertEquals(bank.domesticBankCode, persisted.domesticBankCode)
|
||||
assertEquals(bank.loginName, persisted.loginName)
|
||||
assertEquals(bank.password, persisted.password)
|
||||
|
||||
assertEquals(user.bankName, persisted.bankName)
|
||||
assertEquals(user.bic, persisted.bic)
|
||||
assertEquals(bank.bankName, persisted.bankName)
|
||||
assertEquals(bank.bic, persisted.bic)
|
||||
|
||||
assertEquals(user.customerName, persisted.customerName)
|
||||
assertEquals(user.userId, persisted.userId)
|
||||
assertEquals(bank.customerName, persisted.customerName)
|
||||
assertEquals(bank.userId, persisted.userId)
|
||||
|
||||
assertEquals(user.bankingGroup, persisted.bankingGroup)
|
||||
assertEquals(bank.bankingGroup, persisted.bankingGroup)
|
||||
|
||||
assertEquals(user.wrongCredentialsEntered, persisted.wrongCredentialsEntered)
|
||||
assertEquals(user.displayIndex, persisted.displayIndex)
|
||||
assertEquals(bank.wrongCredentialsEntered, persisted.wrongCredentialsEntered)
|
||||
assertEquals(bank.displayIndex, persisted.displayIndex)
|
||||
|
||||
assertEquals(1, persisted.accounts.size)
|
||||
|
||||
val persistedBankAccount = persisted.accounts.first()
|
||||
assertNotNull(persistedBankAccount.id)
|
||||
assertEquals(persisted.id, persistedBankAccount.userId)
|
||||
assertEquals(persisted.id, persistedBankAccount.bankId)
|
||||
|
||||
assertEquals(bankAccounts.first().identifier, persistedBankAccount.identifier)
|
||||
assertEquals(bankAccounts.first().accountHolderName, persistedBankAccount.accountHolderName)
|
||||
|
|
|
@ -14,7 +14,7 @@ sqlDelight = "2.0.2"
|
|||
|
||||
agp = "8.5.2"
|
||||
android-compileSdk = "34"
|
||||
android-minSdk = "24"
|
||||
android-minSdk = "23"
|
||||
android-targetSdk = "34"
|
||||
androidx-activityCompose = "1.9.1"
|
||||
androidx-appcompat = "1.7.0"
|
||||
|
|
Loading…
Reference in New Issue