Implemented mapping and displaying if an account type is supported or not

This commit is contained in:
dankito 2020-09-22 01:59:00 +02:00
parent a59b335316
commit 700e3e6da9
30 changed files with 145 additions and 101 deletions

View File

@ -9,7 +9,7 @@ However it's not a full implementation of FinTS standard but implements all comm
## Features ## Features
- Retrieving account information, balances and turnovers (Kontoumsätze und -saldo). - Retrieving account information, balances and turnovers (Kontoumsätze und -saldo).
- Transfer money and instant payments (SEPA Überweisungen und Echtzeitüberweisung). - Transfer money and instant payments (SEPA Überweisungen und Echtzeitüberweisung).
- Supports TAN procedures chipTAN manual, Flickercode, QrCode and Photo (Matrix code), pushTAN, smsTAN and appTAN. - Supports TAN methods chipTAN manual, Flickercode, QrCode and Photo (Matrix code), pushTAN, smsTAN and appTAN.
## Setup ## Setup
Not uploaded to Maven Central yet, will do this the next few days! Not uploaded to Maven Central yet, will do this the next few days!

View File

@ -42,6 +42,8 @@ open class FinTsClient(
) { ) {
companion object { companion object {
val SupportedAccountTypes = listOf(AccountType.Girokonto, AccountType.Festgeldkonto)
val FindAccountTransactionsStartRegex = Regex("^HIKAZ:\\d:\\d:\\d\\+@\\d+@", RegexOption.MULTILINE) val FindAccountTransactionsStartRegex = Regex("^HIKAZ:\\d:\\d:\\d\\+@\\d+@", RegexOption.MULTILINE)
val FindAccountTransactionsEndRegex = Regex("^-'", RegexOption.MULTILINE) val FindAccountTransactionsEndRegex = Regex("^-'", RegexOption.MULTILINE)

View File

@ -1,5 +1,6 @@
package net.dankito.banking.fints.model package net.dankito.banking.fints.model
import net.dankito.banking.fints.FinTsClient
import net.dankito.banking.fints.messages.datenelemente.abgeleiteteformate.Laenderkennzeichen import net.dankito.banking.fints.messages.datenelemente.abgeleiteteformate.Laenderkennzeichen
import net.dankito.banking.fints.response.segments.AccountType import net.dankito.banking.fints.response.segments.AccountType
import net.dankito.banking.fints.response.segments.JobParameters import net.dankito.banking.fints.response.segments.JobParameters
@ -27,6 +28,10 @@ open class AccountData(
protected open val _supportedFeatures = mutableSetOf<AccountFeature>() protected open val _supportedFeatures = mutableSetOf<AccountFeature>()
open val isAccountTypeSupported: Boolean
get() = FinTsClient.SupportedAccountTypes.contains(accountType)
open fun supportsFeature(feature: AccountFeature): Boolean { open fun supportsFeature(feature: AccountFeature): Boolean {
return _supportedFeatures.contains(feature) return _supportedFeatures.contains(feature)
} }

View File

@ -7,7 +7,7 @@ import net.dankito.banking.persistence.dao.*
import net.dankito.banking.persistence.model.* import net.dankito.banking.persistence.model.*
@Database(entities = [ Bank::class, BankAccount::class, AccountTransaction::class, TanProcedure::class, TanMedium::class ], @Database(entities = [ Bank::class, BankAccount::class, AccountTransaction::class, TanMethod::class, TanMedium::class ],
version = 1, exportSchema = false) version = 1, exportSchema = false)
@TypeConverters(net.dankito.banking.persistence.TypeConverters::class) @TypeConverters(net.dankito.banking.persistence.TypeConverters::class)
abstract class BankingDatabase : RoomDatabase() { abstract class BankingDatabase : RoomDatabase() {
@ -18,7 +18,7 @@ abstract class BankingDatabase : RoomDatabase() {
abstract fun accountTransactionDao(): AccountTransactionDao abstract fun accountTransactionDao(): AccountTransactionDao
abstract fun tanProcedureDao(): TanProcedureDao abstract fun tanMethodDao(): TanMethodDao
abstract fun tanMediumDao(): TanMediumDao abstract fun tanMediumDao(): TanMediumDao

View File

@ -34,7 +34,7 @@ open class RoomBankingPersistence(applicationContext: Context, password: String?
override fun saveOrUpdateAccount(customer: TypedCustomer, allCustomers: List<TypedCustomer>) { override fun saveOrUpdateAccount(customer: TypedCustomer, allCustomers: List<TypedCustomer>) {
(customer as? Bank)?.let { bank -> (customer as? Bank)?.let { bank ->
bank.selectedTanProcedureId = bank.selectedTanProcedure?.technicalId bank.selectedTanMethodId = bank.selectedTanMethod?.technicalId
db.bankDao().saveOrUpdate(bank) db.bankDao().saveOrUpdate(bank)
@ -43,19 +43,19 @@ open class RoomBankingPersistence(applicationContext: Context, password: String?
accounts.forEach { it.bankId = bank.id } accounts.forEach { it.bankId = bank.id }
db.bankAccountDao().saveOrUpdate(accounts) db.bankAccountDao().saveOrUpdate(accounts)
// TODO: in this way removed TAN procedures won't be deleted from DB and therefore still be visible to user // TODO: in this way removed TAN methods won't be deleted from DB and therefore still be visible to user
val tanProcedures = bank.supportedTanProcedures.filterIsInstance<TanProcedure>() val tanMethods = bank.supportedTanMethods.filterIsInstance<TanMethod>()
tanProcedures.forEach { tanProcedure -> tanMethods.forEach { tantanMethod ->
if (tanProcedure.bankId == BaseDao.ObjectNotInsertedId) { if (tantanMethod.bankId == BaseDao.ObjectNotInsertedId) {
tanProcedure.bankId = bank.id tantanMethod.bankId = bank.id
db.tanProcedureDao().insert(tanProcedure) db.tanMethodDao().insert(tantanMethod)
} }
else { else {
db.tanProcedureDao().update(tanProcedure) db.tanMethodDao().update(tantanMethod)
} }
} }
// TODO: in this way removed TAN procedures won't be deleted from DB and therefore still be visible to user // TODO: in this way removed TAN media won't be deleted from DB and therefore still be visible to user
val tanMedia = bank.tanMedia.map { tanMedium -> val tanMedia = bank.tanMedia.map { tanMedium ->
bank.tanMediumEntities.firstOrNull { it.id == tanMedium.technicalId } ?: map(bank, tanMedium) bank.tanMediumEntities.firstOrNull { it.id == tanMedium.technicalId } ?: map(bank, tanMedium)
} }
@ -70,7 +70,7 @@ open class RoomBankingPersistence(applicationContext: Context, password: String?
db.bankAccountDao().delete(bank.accounts.filterIsInstance<BankAccount>()) db.bankAccountDao().delete(bank.accounts.filterIsInstance<BankAccount>())
db.tanProcedureDao().delete(bank.supportedTanProcedures.filterIsInstance<TanProcedure>()) db.tanMethodDao().delete(bank.supportedTanMethods.filterIsInstance<TanMethod>())
db.tanMediumDao().delete(bank.tanMedia.filterIsInstance<TanMedium>()) db.tanMediumDao().delete(bank.tanMedia.filterIsInstance<TanMedium>())
db.bankDao().delete(bank) db.bankDao().delete(bank)
@ -84,7 +84,7 @@ open class RoomBankingPersistence(applicationContext: Context, password: String?
val transactions = db.accountTransactionDao().getAll() val transactions = db.accountTransactionDao().getAll()
val tanProcedures = db.tanProcedureDao().getAll() val tanMethods = db.tanMethodDao().getAll()
val tanMedia = db.tanMediumDao().getAll() val tanMedia = db.tanMediumDao().getAll()
@ -101,8 +101,8 @@ open class RoomBankingPersistence(applicationContext: Context, password: String?
} }
} }
bank.supportedTanProcedures = tanProcedures.filter { it.bankId == bank.id } bank.supportedTanMethods = tanMethods.filter { it.bankId == bank.id }
bank.selectedTanProcedure = bank.supportedTanProcedures.firstOrNull { it.technicalId == bank.selectedTanProcedureId } bank.selectedTanMethod = bank.supportedTanMethods.firstOrNull { it.technicalId == bank.selectedTanMethodId }
bank.tanMediumEntities = tanMedia.filter { it.bankId == bank.id } bank.tanMediumEntities = tanMedia.filter { it.bankId == bank.id }
bank.tanMedia = bank.tanMediumEntities.map { map(it) } bank.tanMedia = bank.tanMediumEntities.map { map(it) }

View File

@ -5,7 +5,7 @@ import net.dankito.banking.persistence.model.TanMediumType
import net.dankito.banking.ui.model.BankAccountType import net.dankito.banking.ui.model.BankAccountType
import net.dankito.banking.ui.model.tan.AllowedTanFormat import net.dankito.banking.ui.model.tan.AllowedTanFormat
import net.dankito.banking.ui.model.tan.TanMediumStatus import net.dankito.banking.ui.model.tan.TanMediumStatus
import net.dankito.banking.ui.model.tan.TanProcedureType import net.dankito.banking.ui.model.tan.TanMethodType
import net.dankito.utils.multiplatform.BigDecimal import net.dankito.utils.multiplatform.BigDecimal
import net.dankito.utils.multiplatform.Date import net.dankito.utils.multiplatform.Date
@ -46,13 +46,13 @@ open class TypeConverters {
@TypeConverter @TypeConverter
fun fromTanProcedureType(value: TanProcedureType): Int { fun fromTanMethodType(value: TanMethodType): Int {
return value.ordinal return value.ordinal
} }
@TypeConverter @TypeConverter
fun toTanProcedureType(value: Int): TanProcedureType { fun toTanMethodType(value: Int): TanMethodType {
return TanProcedureType.values().first { it.ordinal == value } return TanMethodType.values().first { it.ordinal == value }
} }

View File

@ -0,0 +1,14 @@
package net.dankito.banking.persistence.dao
import androidx.room.Dao
import androidx.room.Query
import net.dankito.banking.persistence.model.TanMethod
@Dao
interface TanMethodDao : BaseDao<TanMethod> {
@Query("SELECT * FROM TanMethod")
fun getAll(): List<TanMethod>
}

View File

@ -1,14 +0,0 @@
package net.dankito.banking.persistence.dao
import androidx.room.Dao
import androidx.room.Query
import net.dankito.banking.persistence.model.TanProcedure
@Dao
interface TanProcedureDao : BaseDao<TanProcedure> {
@Query("SELECT * FROM TanProcedure")
fun getAll(): List<TanProcedure>
}

View File

@ -7,7 +7,7 @@ import net.dankito.banking.persistence.dao.BaseDao
import net.dankito.banking.ui.model.TypedBankAccount import net.dankito.banking.ui.model.TypedBankAccount
import net.dankito.banking.ui.model.TypedCustomer import net.dankito.banking.ui.model.TypedCustomer
import net.dankito.banking.ui.model.tan.TanMedium import net.dankito.banking.ui.model.tan.TanMedium
import net.dankito.banking.ui.model.tan.TanProcedure import net.dankito.banking.ui.model.tan.TanMethod
@Entity @Entity
@ -25,11 +25,11 @@ open class Bank(
@Ignore @Ignore
override var accounts: List<TypedBankAccount> = listOf(), override var accounts: List<TypedBankAccount> = listOf(),
@Ignore @Ignore
override var supportedTanProcedures: List<TanProcedure> = listOf(), override var supportedTanMethods: List<TanMethod> = listOf(),
@Ignore @Ignore
override var selectedTanProcedure: TanProcedure? = null, override var selectedTanMethod: TanMethod? = null,
@Ignore @Ignore
override var tanMedia: List<TanMedium> = listOf(), override var tanMedia: List<TanMedium> = listOf(),
@ -49,7 +49,7 @@ open class Bank(
internal constructor() : this("", "", "", "", "", "", "") // for object deserializers internal constructor() : this("", "", "", "", "", "", "") // for object deserializers
open var selectedTanProcedureId: String? = null open var selectedTanMethodId: String? = null
@Ignore @Ignore
open var tanMediumEntities = listOf<net.dankito.banking.persistence.model.TanMedium>() open var tanMediumEntities = listOf<net.dankito.banking.persistence.model.TanMedium>()

View File

@ -58,6 +58,8 @@ open class BankAccount(
override var haveAllTransactionsBeenFetched: Boolean = false override var haveAllTransactionsBeenFetched: Boolean = false
override var isAccountTypeSupported: Boolean = true
override var userSetDisplayName: String? = null override var userSetDisplayName: String? = null

View File

@ -5,7 +5,7 @@ import net.dankito.banking.ui.model.TypedBankAccount
import net.dankito.banking.ui.model.TypedCustomer import net.dankito.banking.ui.model.TypedCustomer
import net.dankito.banking.ui.model.mapper.IModelCreator import net.dankito.banking.ui.model.mapper.IModelCreator
import net.dankito.banking.ui.model.tan.AllowedTanFormat import net.dankito.banking.ui.model.tan.AllowedTanFormat
import net.dankito.banking.ui.model.tan.TanProcedureType import net.dankito.banking.ui.model.tan.TanMethodType
import net.dankito.utils.multiplatform.BigDecimal import net.dankito.utils.multiplatform.BigDecimal
import net.dankito.utils.multiplatform.Date import net.dankito.utils.multiplatform.Date
@ -66,8 +66,8 @@ open class RoomModelCreator : IModelCreator {
} }
override fun createTanProcedure(displayName: String, type: TanProcedureType, bankInternalProcedureCode: String, maxTanInputLength: Int?, allowedTanFormat: AllowedTanFormat): net.dankito.banking.ui.model.tan.TanProcedure { override fun createTanMethod(displayName: String, type: TanMethodType, bankInternalMethodCode: String, maxTanInputLength: Int?, allowedTanFormat: AllowedTanFormat): net.dankito.banking.ui.model.tan.TanMethod {
return TanProcedure(displayName, type, bankInternalProcedureCode, maxTanInputLength, allowedTanFormat) return TanMethod(displayName, type, bankInternalMethodCode, maxTanInputLength, allowedTanFormat)
} }
} }

View File

@ -0,0 +1,29 @@
package net.dankito.banking.persistence.model
import androidx.room.Entity
import androidx.room.PrimaryKey
import net.dankito.banking.persistence.dao.BaseDao
import net.dankito.banking.ui.model.tan.AllowedTanFormat
import net.dankito.banking.ui.model.tan.TanMethod
import net.dankito.banking.ui.model.tan.TanMethodType
@Entity
open class TanMethod(
override var displayName: String,
override var type: TanMethodType,
override var bankInternalMethodCode: String,
override var maxTanInputLength: Int? = null,
override var allowedTanFormat: AllowedTanFormat = AllowedTanFormat.Alphanumeric
) : TanMethod(displayName, type, bankInternalMethodCode, maxTanInputLength, allowedTanFormat) {
internal constructor() : this("", TanMethodType.EnterTan, "") // for object deserializers
@PrimaryKey
open var id: String = technicalId
// Room doesn't allow me to add getters and setters -> have to map it manually
open var bankId: Long = BaseDao.ObjectNotInsertedId
}

View File

@ -1,29 +0,0 @@
package net.dankito.banking.persistence.model
import androidx.room.Entity
import androidx.room.PrimaryKey
import net.dankito.banking.persistence.dao.BaseDao
import net.dankito.banking.ui.model.tan.AllowedTanFormat
import net.dankito.banking.ui.model.tan.TanProcedure
import net.dankito.banking.ui.model.tan.TanProcedureType
@Entity
open class TanProcedure(
displayName: String,
type: TanProcedureType,
bankInternalProcedureCode: String,
maxTanInputLength: Int? = null,
allowedTanFormat: AllowedTanFormat = AllowedTanFormat.Alphanumeric
) : TanProcedure(displayName, type, bankInternalProcedureCode, maxTanInputLength, allowedTanFormat) {
internal constructor() : this("", TanProcedureType.EnterTan, "") // for object deserializers
@PrimaryKey
open var id: String = technicalId
// Room doesn't allow me to add getters and setters -> have to map it manually
open var bankId: Long = BaseDao.ObjectNotInsertedId
}

View File

@ -32,6 +32,7 @@ open class BankAccountEntity(
override var technicalId: String = UUID.random(), override var technicalId: String = UUID.random(),
override var userSetDisplayName: String? = null, override var userSetDisplayName: String? = null,
override var haveAllTransactionsBeenFetched: Boolean = false, override var haveAllTransactionsBeenFetched: Boolean = false,
override var isAccountTypeSupported: Boolean = true,
override var displayIndex: Int = 0 override var displayIndex: Int = 0
) : IBankAccount<AccountTransactionEntity> { ) : IBankAccount<AccountTransactionEntity> {

View File

@ -32,6 +32,13 @@ import javax.inject.Inject
class HomeFragment : Fragment() { class HomeFragment : Fragment() {
companion object {
val TransactionsCannotBeRetrievedStates = listOf(TransactionsRetrievalState.AccountTypeNotSupported, TransactionsRetrievalState.AccountDoesNotSupportFetchingTransactions)
}
private lateinit var homeViewModel: HomeViewModel private lateinit var homeViewModel: HomeViewModel
private lateinit var mnitmBalance: MenuItem private lateinit var mnitmBalance: MenuItem
@ -262,10 +269,11 @@ class HomeFragment : Fragment() {
rcyvwAccountTransactions.visibility = if (haveTransactionsBeenRetrieved) View.VISIBLE else View.GONE rcyvwAccountTransactions.visibility = if (haveTransactionsBeenRetrieved) View.VISIBLE else View.GONE
lytNoTransactionsFetched.visibility = if (haveTransactionsBeenRetrieved || noAccountsAddedYet) View.GONE else View.VISIBLE lytNoTransactionsFetched.visibility = if (haveTransactionsBeenRetrieved || noAccountsAddedYet) View.GONE else View.VISIBLE
btnRetrieveTransactions.visibility = if (transactionsRetrievalState == TransactionsRetrievalState.AccountDoesNotSupportFetchingTransactions) View.GONE else View.VISIBLE btnRetrieveTransactions.visibility = if (TransactionsCannotBeRetrievedStates.contains(transactionsRetrievalState)) View.GONE else View.VISIBLE
btnAddAccount.visibility = if (noAccountsAddedYet) View.VISIBLE else View.GONE btnAddAccount.visibility = if (noAccountsAddedYet) View.VISIBLE else View.GONE
val transactionsRetrievalStateMessageId = when (transactionsRetrievalState) { val transactionsRetrievalStateMessageId = when (transactionsRetrievalState) {
TransactionsRetrievalState.AccountTypeNotSupported -> R.string.fragment_home_transactions_retrieval_state_account_type_not_supported
TransactionsRetrievalState.AccountDoesNotSupportFetchingTransactions -> R.string.fragment_home_transactions_retrieval_state_account_does_not_support_retrieving_transactions TransactionsRetrievalState.AccountDoesNotSupportFetchingTransactions -> R.string.fragment_home_transactions_retrieval_state_account_does_not_support_retrieving_transactions
TransactionsRetrievalState.NoTransactionsInRetrievedPeriod -> R.string.fragment_home_transactions_retrieval_state_no_transactions_in_retrieved_period TransactionsRetrievalState.NoTransactionsInRetrievedPeriod -> R.string.fragment_home_transactions_retrieval_state_no_transactions_in_retrieved_period
TransactionsRetrievalState.NeverRetrievedTransactions -> R.string.fragment_home_transactions_retrieval_state_never_retrieved_transactions TransactionsRetrievalState.NeverRetrievedTransactions -> R.string.fragment_home_transactions_retrieval_state_never_retrieved_transactions

View File

@ -39,6 +39,7 @@
<string name="fragment_home_count_transactions">%d Umsätze</string> <string name="fragment_home_count_transactions">%d Umsätze</string>
<string name="fragment_home_could_not_retrieve_account_transactions">Kontoumsätze für \'%1$s\' konnten nicht empfangen werden.\n\nFehlermeldung Ihrer Bank:\n\n%2$s</string> <string name="fragment_home_could_not_retrieve_account_transactions">Kontoumsätze für \'%1$s\' konnten nicht empfangen werden.\n\nFehlermeldung Ihrer Bank:\n\n%2$s</string>
<string name="fragment_home_fetch_transactions">Umsätze abrufen</string> <string name="fragment_home_fetch_transactions">Umsätze abrufen</string>
<string name="fragment_home_transactions_retrieval_state_account_type_not_supported">Kontotyp wird von App nicht unterstützt</string>
<string name="fragment_home_transactions_retrieval_state_account_does_not_support_retrieving_transactions">Konto unterstützt Abrufen von Umsätzen nicht</string> <string name="fragment_home_transactions_retrieval_state_account_does_not_support_retrieving_transactions">Konto unterstützt Abrufen von Umsätzen nicht</string>
<string name="fragment_home_transactions_retrieval_state_never_retrieved_transactions">Noch keine Umsätze abgerufen</string> <string name="fragment_home_transactions_retrieval_state_never_retrieved_transactions">Noch keine Umsätze abgerufen</string>
<string name="fragment_home_transactions_retrieval_state_no_transactions_in_retrieved_period">Empfangener Zeitraum enthielt keine Umsätze</string> <string name="fragment_home_transactions_retrieval_state_no_transactions_in_retrieved_period">Empfangener Zeitraum enthielt keine Umsätze</string>

View File

@ -39,6 +39,7 @@
<string name="fragment_home_count_transactions">%d transactions</string> <string name="fragment_home_count_transactions">%d transactions</string>
<string name="fragment_home_could_not_retrieve_account_transactions">Could not retrieve account transactions for \'%1$s\'.\n\nError message from your bank:\n\n%2$s</string> <string name="fragment_home_could_not_retrieve_account_transactions">Could not retrieve account transactions for \'%1$s\'.\n\nError message from your bank:\n\n%2$s</string>
<string name="fragment_home_fetch_transactions">Fetch transactions</string> <string name="fragment_home_fetch_transactions">Fetch transactions</string>
<string name="fragment_home_transactions_retrieval_state_account_type_not_supported">Account type not supported by app</string>
<string name="fragment_home_transactions_retrieval_state_account_does_not_support_retrieving_transactions">Account does not support retrieving transactions</string> <string name="fragment_home_transactions_retrieval_state_account_does_not_support_retrieving_transactions">Account does not support retrieving transactions</string>
<string name="fragment_home_transactions_retrieval_state_never_retrieved_transactions">No transactions fetched yet</string> <string name="fragment_home_transactions_retrieval_state_never_retrieved_transactions">No transactions fetched yet</string>
<string name="fragment_home_transactions_retrieval_state_no_transactions_in_retrieved_period">There haven\'t been any transactions in retrieved period</string> <string name="fragment_home_transactions_retrieval_state_no_transactions_in_retrieved_period">There haven\'t been any transactions in retrieved period</string>

View File

@ -42,6 +42,8 @@ open class BankAccount @JvmOverloads constructor(
override var haveAllTransactionsBeenFetched: Boolean = false override var haveAllTransactionsBeenFetched: Boolean = false
override var isAccountTypeSupported: Boolean = true
override var userSetDisplayName: String? = null override var userSetDisplayName: String? = null

View File

@ -28,6 +28,7 @@ interface IBankAccount<TTransaction: IAccountTransaction> : OrderedDisplayable {
var unbookedTransactions: List<Any> var unbookedTransactions: List<Any>
var technicalId: String var technicalId: String
var haveAllTransactionsBeenFetched: Boolean var haveAllTransactionsBeenFetched: Boolean
var isAccountTypeSupported: Boolean
var userSetDisplayName: String? var userSetDisplayName: String?

View File

@ -3,6 +3,8 @@ package net.dankito.banking.ui.model
enum class TransactionsRetrievalState { enum class TransactionsRetrievalState {
AccountTypeNotSupported,
AccountDoesNotSupportFetchingTransactions, AccountDoesNotSupportFetchingTransactions,
NeverRetrievedTransactions, NeverRetrievedTransactions,

View File

@ -750,10 +750,18 @@ open class BankingPresenter(
return TransactionsRetrievalState.NeverRetrievedTransactions return TransactionsRetrievalState.NeverRetrievedTransactions
} }
return TransactionsRetrievalState.AccountDoesNotSupportFetchingTransactions if (states.contains(TransactionsRetrievalState.AccountDoesNotSupportFetchingTransactions)) {
return TransactionsRetrievalState.AccountDoesNotSupportFetchingTransactions
}
return TransactionsRetrievalState.AccountTypeNotSupported
} }
protected open fun getAccountTransactionRetrievalState(account: TypedBankAccount): TransactionsRetrievalState { protected open fun getAccountTransactionRetrievalState(account: TypedBankAccount): TransactionsRetrievalState {
if (account.isAccountTypeSupported == false) {
return TransactionsRetrievalState.AccountTypeNotSupported
}
if (account.supportsRetrievingAccountTransactions == false) { if (account.supportsRetrievingAccountTransactions == false) {
return TransactionsRetrievalState.AccountDoesNotSupportFetchingTransactions return TransactionsRetrievalState.AccountDoesNotSupportFetchingTransactions
} }

View File

@ -46,6 +46,7 @@
<attribute name="haveAllTransactionsBeenFetched" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/> <attribute name="haveAllTransactionsBeenFetched" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="iban" optional="YES" attributeType="String"/> <attribute name="iban" optional="YES" attributeType="String"/>
<attribute name="identifier" attributeType="String"/> <attribute name="identifier" attributeType="String"/>
<attribute name="isAccountTypeSupported" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
<attribute name="lastRetrievedTransactionsTimestamp" optional="YES" attributeType="Date" usesScalarValueType="NO"/> <attribute name="lastRetrievedTransactionsTimestamp" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="productName" optional="YES" attributeType="String"/> <attribute name="productName" optional="YES" attributeType="String"/>
<attribute name="subAccountNumber" optional="YES" attributeType="String"/> <attribute name="subAccountNumber" optional="YES" attributeType="String"/>
@ -89,7 +90,7 @@
</entity> </entity>
<elements> <elements>
<element name="PersistedAccountTransaction" positionX="-36" positionY="45" width="128" height="553"/> <element name="PersistedAccountTransaction" positionX="-36" positionY="45" width="128" height="553"/>
<element name="PersistedBankAccount" positionX="-54" positionY="63" width="128" height="343"/> <element name="PersistedBankAccount" positionX="-54" positionY="63" width="128" height="358"/>
<element name="PersistedCustomer" positionX="-63" positionY="-18" width="128" height="283"/> <element name="PersistedCustomer" positionX="-63" positionY="-18" width="128" height="283"/>
<element name="PersistedTanMedium" positionX="-45" positionY="144" width="128" height="28"/> <element name="PersistedTanMedium" positionX="-45" positionY="144" width="128" height="28"/>
<element name="PersistedTanMethod" positionX="-54" positionY="135" width="128" height="118"/> <element name="PersistedTanMethod" positionX="-54" positionY="135" width="128" height="118"/>

View File

@ -76,6 +76,7 @@
"Fetch all account transactions" = "Fetch earlier transactions (requires TAN)"; "Fetch all account transactions" = "Fetch earlier transactions (requires TAN)";
"Fetch transactions" = "Fetch transactions"; "Fetch transactions" = "Fetch transactions";
"Account type not supported by app" = "Account type not supported by app";
"Account does not support retrieving transactions" = "Account does not support retrieving transactions"; "Account does not support retrieving transactions" = "Account does not support retrieving transactions";
"No transactions fetched yet" = "No transactions fetched yet"; "No transactions fetched yet" = "No transactions fetched yet";
"There haven't been any transactions in retrieved period" = "There haven't been any transactions in retrieved period"; "There haven't been any transactions in retrieved period" = "There haven't been any transactions in retrieved period";

View File

@ -76,6 +76,7 @@
"Fetch all account transactions" = "Ältere Umsätze laden (erfordert TAN)"; "Fetch all account transactions" = "Ältere Umsätze laden (erfordert TAN)";
"Fetch transactions" = "Umsätze abrufen"; "Fetch transactions" = "Umsätze abrufen";
"Account type not supported by app" = "Kontotyp wird von App nicht unterstützt";
"Account does not support retrieving transactions" = "Konto unterstützt Abrufen von Umsätzen nicht"; "Account does not support retrieving transactions" = "Konto unterstützt Abrufen von Umsätzen nicht";
"No transactions fetched yet" = "Noch keine Umsätze abgerufen"; "No transactions fetched yet" = "Noch keine Umsätze abgerufen";
"There haven't been any transactions in retrieved period" = "Empfangener Zeitraum enthielt keine Umsätze"; "There haven't been any transactions in retrieved period" = "Empfangener Zeitraum enthielt keine Umsätze";

View File

@ -61,6 +61,7 @@ class Mapper {
let mapped = BankAccount(customer: customer, identifier: map(account.identifier), accountHolderName: map(account.accountHolderName), iban: account.iban, subAccountNumber: account.subAccountNumber, customerId: map(account.customerId), balance: map(account.balance), currency: map(account.currency), type: map(account.type), productName: account.productName, accountLimit: account.accountLimit, lastRetrievedTransactionsTimestamp: map(account.lastRetrievedTransactionsTimestamp), supportsRetrievingAccountTransactions: account.supportsRetrievingAccountTransactions, supportsRetrievingBalance: account.supportsRetrievingBalance, supportsTransferringMoney: account.supportsTransferringMoney, supportsInstantPaymentMoneyTransfer: account.supportsInstantPaymentMoneyTransfer, bookedTransactions: [], unbookedTransactions: []) let mapped = BankAccount(customer: customer, identifier: map(account.identifier), accountHolderName: map(account.accountHolderName), iban: account.iban, subAccountNumber: account.subAccountNumber, customerId: map(account.customerId), balance: map(account.balance), currency: map(account.currency), type: map(account.type), productName: account.productName, accountLimit: account.accountLimit, lastRetrievedTransactionsTimestamp: map(account.lastRetrievedTransactionsTimestamp), supportsRetrievingAccountTransactions: account.supportsRetrievingAccountTransactions, supportsRetrievingBalance: account.supportsRetrievingBalance, supportsTransferringMoney: account.supportsTransferringMoney, supportsInstantPaymentMoneyTransfer: account.supportsInstantPaymentMoneyTransfer, bookedTransactions: [], unbookedTransactions: [])
mapped.haveAllTransactionsBeenFetched = account.haveAllTransactionsBeenFetched mapped.haveAllTransactionsBeenFetched = account.haveAllTransactionsBeenFetched
mapped.isAccountTypeSupported = account.isAccountTypeSupported
mapped.userSetDisplayName = account.userSetDisplayName mapped.userSetDisplayName = account.userSetDisplayName
mapped.displayIndex = account.displayIndex mapped.displayIndex = account.displayIndex
@ -88,6 +89,7 @@ class Mapper {
mapped.balance = account.balance.decimal mapped.balance = account.balance.decimal
mapped.currency = account.currency mapped.currency = account.currency
mapped.type = map(account.type) mapped.type = map(account.type)
mapped.isAccountTypeSupported = account.isAccountTypeSupported
mapped.productName = account.productName mapped.productName = account.productName
mapped.accountLimit = account.accountLimit mapped.accountLimit = account.accountLimit
mapped.lastRetrievedTransactionsTimestamp = account.lastRetrievedTransactionsTimestamp?.date mapped.lastRetrievedTransactionsTimestamp = account.lastRetrievedTransactionsTimestamp?.date

View File

@ -183,7 +183,7 @@ struct AccountTransactionsDialog: View {
self.showTransactionsList = haveTransactionsBeenRetrievedForSelectedAccounts self.showTransactionsList = haveTransactionsBeenRetrievedForSelectedAccounts
self.noTransactionsFetchedMessage = getNoTransactionsFetchedMessage(transactionsRetrievalState) self.noTransactionsFetchedMessage = getNoTransactionsFetchedMessage(transactionsRetrievalState)
self.showFetchTransactionsButton = transactionsRetrievalState != .accountdoesnotsupportfetchingtransactions self.showFetchTransactionsButton = transactionsRetrievalState != .accountdoesnotsupportfetchingtransactions && transactionsRetrievalState != .accounttypenotsupported
} }
@ -248,9 +248,12 @@ struct AccountTransactionsDialog: View {
else if state == .notransactionsinretrievedperiod { else if state == .notransactionsinretrievedperiod {
return "There haven't been any transactions in retrieved period" return "There haven't been any transactions in retrieved period"
} }
else { else if state == .accountdoesnotsupportfetchingtransactions {
return "Account does not support retrieving transactions" return "Account does not support retrieving transactions"
} }
else {
return "Account type not supported by app"
}
} }
private func filterTransactions(_ query: String) { private func filterTransactions(_ query: String) {

View File

@ -19,6 +19,7 @@ struct BankAccountListItem : View {
AmountLabel(amount: account.balance) AmountLabel(amount: account.balance)
}.frame(height: 35) }.frame(height: 35)
} }
.disabled( !account.isAccountTypeSupported)
.contextMenu { .contextMenu {
Button(action: { self.navigateToBankAccountSettingsDialog() }) { Button(action: { self.navigateToBankAccountSettingsDialog() }) {
HStack { HStack {

View File

@ -119,6 +119,7 @@ open class fints4kModelMapper(protected val modelCreator: IModelCreator) {
account.currency = accountData.currency ?: "EUR" account.currency = accountData.currency ?: "EUR"
account.type = mapBankAccountType(accountData.accountType) account.type = mapBankAccountType(accountData.accountType)
account.isAccountTypeSupported = accountData.isAccountTypeSupported
account.productName = accountData.productName account.productName = accountData.productName
account.accountLimit = accountData.accountLimit account.accountLimit = accountData.accountLimit

View File

@ -83,9 +83,9 @@ open class HbciCallback(
/* TAN */ /* TAN */
// ADDED: Auswaehlen welches PinTan Verfahren verwendet werden soll // ADDED: Auswaehlen welches PinTan Verfahren verwendet werden soll
HBCICallback.NEED_PT_SECMECH -> selectTanProcedure(retData.toString())?.let { selectedTanProcedure -> HBCICallback.NEED_PT_SECMECH -> selectTanMethod(retData.toString())?.let { selectedTanMethod ->
customer.selectedTanProcedure = selectedTanProcedure customer.selectedTanMethod = selectedTanMethod
retData.replace(0, retData.length, selectedTanProcedure.bankInternalProcedureCode) retData.replace(0, retData.length, selectedTanMethod.bankInternalMethodCode)
} }
// chipTan or simple TAN request (iTAN, smsTAN, ...) // chipTan or simple TAN request (iTAN, smsTAN, ...)
@ -99,7 +99,7 @@ open class HbciCallback(
HBCICallback.NEED_PT_QRTAN -> { // use class QRCode to display QR code HBCICallback.NEED_PT_QRTAN -> { // use class QRCode to display QR code
val qrData = retData.toString() val qrData = retData.toString()
val qrCode = QRCode(qrData, msg) val qrCode = QRCode(qrData, msg)
val enterTanResult = callback.enterTan(customer, ImageTanChallenge(TanImage(qrCode.mimetype, qrCode.image), msg, customer.selectedTanProcedure!!)) val enterTanResult = callback.enterTan(customer, ImageTanChallenge(TanImage(qrCode.mimetype, qrCode.image), msg, customer.selectedTanMethod!!))
enterTanResult.enteredTan?.let { enteredTan -> enterTanResult.enteredTan?.let { enteredTan ->
retData.replace(0, retData.length, enteredTan) retData.replace(0, retData.length, enteredTan)
} }
@ -108,7 +108,7 @@ open class HbciCallback(
// photoTan // photoTan
HBCICallback.NEED_PT_PHOTOTAN -> { // use class MatrixCode to display photo HBCICallback.NEED_PT_PHOTOTAN -> { // use class MatrixCode to display photo
val matrixCode = MatrixCode(retData.toString()) val matrixCode = MatrixCode(retData.toString())
val enterTanResult = callback.enterTan(customer, ImageTanChallenge(TanImage(matrixCode.mimetype, matrixCode.image), msg, customer.selectedTanProcedure!!)) val enterTanResult = callback.enterTan(customer, ImageTanChallenge(TanImage(matrixCode.mimetype, matrixCode.image), msg, customer.selectedTanMethod!!))
enterTanResult.enteredTan?.let { enteredTan -> enterTanResult.enteredTan?.let { enteredTan ->
retData.replace(0, retData.length, enteredTan) retData.replace(0, retData.length, enteredTan)
} }
@ -183,14 +183,14 @@ open class HbciCallback(
// werden. // werden.
val enterTanResult = if (challengeHHD_UC.isNullOrEmpty()) { val enterTanResult = if (challengeHHD_UC.isNullOrEmpty()) {
callback.enterTan(customer, TanChallenge(messageToShowToUser, customer.selectedTanProcedure!!)) callback.enterTan(customer, TanChallenge(messageToShowToUser, customer.selectedTanMethod!!))
} }
else { else {
// for Sparkasse messageToShowToUser started with "chipTAN optisch\nTAN-Nummer\n\n" // for Sparkasse messageToShowToUser started with "chipTAN optisch\nTAN-Nummer\n\n"
val usefulMessage = messageToShowToUser.split("\n").last().trim() val usefulMessage = messageToShowToUser.split("\n").last().trim()
// val parsedDataSet = FlickerCode(challengeHHD_UC).render() // val parsedDataSet = FlickerCode(challengeHHD_UC).render()
callback.enterTan(customer, FlickerCodeTanChallenge(net.dankito.banking.ui.model.tan.FlickerCode("", challengeHHD_UC), usefulMessage, customer.selectedTanProcedure!!)) callback.enterTan(customer, FlickerCodeTanChallenge(net.dankito.banking.ui.model.tan.FlickerCode("", challengeHHD_UC), usefulMessage, customer.selectedTanMethod!!))
} }
return enterTanResult.enteredTan return enterTanResult.enteredTan
@ -198,15 +198,15 @@ open class HbciCallback(
open fun selectTanProcedure(supportedTanProceduresString: String): net.dankito.banking.ui.model.tan.TanProcedure? { open fun selectTanMethod(supportedTanMethodsString: String): net.dankito.banking.ui.model.tan.TanMethod? {
val supportedTanProcedures = mapper.mapTanProcedures(supportedTanProceduresString) val supportedTanMethods = mapper.mapTanMethods(supportedTanMethodsString)
customer.supportedTanProcedures = supportedTanProcedures customer.supportedTanMethods = supportedTanMethods
if (supportedTanProcedures.isNotEmpty()) { if (supportedTanMethods.isNotEmpty()) {
// select any procedure, user then can select her preferred one in EnterTanDialog; try not to select 'chipTAN manuell' // select any method, user then can select her preferred one in EnterTanDialog; try not to select 'chipTAN manuell'
return supportedTanProcedures.firstOrNull { it.displayName.contains("manuell", true) == false } return supportedTanMethods.firstOrNull { it.displayName.contains("manuell", true) == false }
?: supportedTanProcedures.firstOrNull() ?: supportedTanMethods.firstOrNull()
} }
return null return null

View File

@ -5,7 +5,7 @@ import net.dankito.banking.ui.model.mapper.IModelCreator
import net.dankito.utils.multiplatform.BigDecimal import net.dankito.utils.multiplatform.BigDecimal
import net.dankito.utils.multiplatform.toBigDecimal import net.dankito.utils.multiplatform.toBigDecimal
import net.dankito.banking.ui.model.parameters.TransferMoneyData import net.dankito.banking.ui.model.parameters.TransferMoneyData
import net.dankito.banking.ui.model.tan.TanProcedureType import net.dankito.banking.ui.model.tan.TanMethodType
import org.kapott.hbci.passport.HBCIPassport import org.kapott.hbci.passport.HBCIPassport
import org.kapott.hbci.structures.Konto import org.kapott.hbci.structures.Konto
import org.kapott.hbci.structures.Value import org.kapott.hbci.structures.Value
@ -55,6 +55,7 @@ open class hbci4jModelMapper(
result.currency = bankAccount.curr result.currency = bankAccount.curr
result.type = mapBankAccountType(bankAccount) result.type = mapBankAccountType(bankAccount)
result.isAccountTypeSupported = result.type == BankAccountType.Girokonto || result.type == BankAccountType.Festgeldkonto
result.accountLimit = bankAccount.limit?.value?.let { mapValue(it).toString() } result.accountLimit = bankAccount.limit?.value?.let { mapValue(it).toString() }
result.supportsRetrievingBalance = bankAccount.allowedGVs.contains("HKSAL") result.supportsRetrievingBalance = bankAccount.allowedGVs.contains("HKSAL")
@ -88,14 +89,14 @@ open class hbci4jModelMapper(
} }
open fun mapTanProcedures(tanProceduresString: String): List<net.dankito.banking.ui.model.tan.TanProcedure> { open fun mapTanMethods(tanMethodsString: String): List<net.dankito.banking.ui.model.tan.TanMethod> {
return tanProceduresString.split('|') return tanMethodsString.split('|')
.map { mapTanProcedure(it) } .map { mapTanMethod(it) }
.filterNotNull() .filterNotNull()
} }
open fun mapTanProcedure(tanProcedureString: String): net.dankito.banking.ui.model.tan.TanProcedure? { open fun mapTanMethod(tanMethodString: String): net.dankito.banking.ui.model.tan.TanMethod? {
val parts = tanProcedureString.split(':') val parts = tanMethodString.split(':')
if (parts.size > 1) { if (parts.size > 1) {
val code = parts[0] val code = parts[0]
@ -103,18 +104,18 @@ open class hbci4jModelMapper(
val displayNameLowerCase = displayName.toLowerCase() val displayNameLowerCase = displayName.toLowerCase()
return when { return when {
// TODO: implement all TAN procedures // TODO: implement all TAN methods
displayNameLowerCase.contains("chiptan") -> { displayNameLowerCase.contains("chiptan") -> {
if (displayNameLowerCase.contains("qr")) { if (displayNameLowerCase.contains("qr")) {
modelCreator.createTanProcedure(displayName, TanProcedureType.ChipTanQrCode, code) modelCreator.createTanMethod(displayName, TanMethodType.ChipTanQrCode, code)
} }
else { else {
modelCreator.createTanProcedure(displayName, TanProcedureType.ChipTanFlickercode, code) modelCreator.createTanMethod(displayName, TanMethodType.ChipTanFlickercode, code)
} }
} }
displayNameLowerCase.contains("sms") -> modelCreator.createTanProcedure(displayName, TanProcedureType.SmsTan, code) displayNameLowerCase.contains("sms") -> modelCreator.createTanMethod(displayName, TanMethodType.SmsTan, code)
displayNameLowerCase.contains("push") -> modelCreator.createTanProcedure(displayName, TanProcedureType.AppTan, code) displayNameLowerCase.contains("push") -> modelCreator.createTanMethod(displayName, TanMethodType.AppTan, code)
// we filter out iTAN and Einschritt-Verfahren as they are not permitted anymore according to PSD2 // we filter out iTAN and Einschritt-Verfahren as they are not permitted anymore according to PSD2
else -> null else -> null