Renamed Customer to BankData

This commit is contained in:
dankito 2020-09-22 06:06:11 +02:00
parent df1ce4fd3a
commit 60c93dedfb
84 changed files with 810 additions and 811 deletions

View File

@ -45,22 +45,22 @@ open class LuceneBankingPersistence(
protected val fields = FieldBuilder() protected val fields = FieldBuilder()
override fun saveOrUpdateAccountTransactions(bankAccount: TypedBankAccount, transactions: List<IAccountTransaction>) { override fun saveOrUpdateAccountTransactions(account: TypedBankAccount, transactions: List<IAccountTransaction>) {
val writer = getWriter() val writer = getWriter()
transactions.forEach { transaction -> transactions.forEach { transaction ->
writer.updateDocumentForNonNullFields( writer.updateDocumentForNonNullFields(
IdFieldName, transaction.technicalId, IdFieldName, transaction.technicalId,
*createFieldsForAccountTransaction(bankAccount, transaction).toTypedArray() *createFieldsForAccountTransaction(account, transaction).toTypedArray()
) )
} }
writer.flushChangesToDisk() writer.flushChangesToDisk()
} }
protected open fun createFieldsForAccountTransaction(bankAccount: TypedBankAccount, transaction: IAccountTransaction): List<IndexableField?> { protected open fun createFieldsForAccountTransaction(account: TypedBankAccount, transaction: IAccountTransaction): List<IndexableField?> {
return listOf( return listOf(
fields.keywordField(BankAccountIdFieldName, bankAccount.technicalId), fields.keywordField(BankAccountIdFieldName, account.technicalId),
fields.nullableFullTextSearchField(OtherPartyNameFieldName, transaction.otherPartyName, true), fields.nullableFullTextSearchField(OtherPartyNameFieldName, transaction.otherPartyName, true),
fields.fullTextSearchField(UsageFieldName, transaction.usage, true), fields.fullTextSearchField(UsageFieldName, transaction.usage, true),
fields.nullableFullTextSearchField(BookingTextFieldName, transaction.bookingText, true), fields.nullableFullTextSearchField(BookingTextFieldName, transaction.bookingText, true),
@ -77,21 +77,21 @@ open class LuceneBankingPersistence(
} }
override fun deleteAccount(customer: TypedCustomer, allCustomers: List<TypedCustomer>) { override fun deleteBank(bank: TypedBankData, allBanks: List<TypedBankData>) {
try { try {
deleteAccountTransactions(customer.accounts) deleteAccountTransactions(bank.accounts)
} catch (e: Exception) { } catch (e: Exception) {
log.error("Could not delete account transactions of account $customer", e) log.error("Could not delete account transactions of account $bank", e)
} }
super.deleteAccount(customer, allCustomers) super.deleteBank(bank, allBanks)
} }
protected open fun deleteAccountTransactions(bankAccounts: List<TypedBankAccount>) { protected open fun deleteAccountTransactions(accounts: List<TypedBankAccount>) {
val writer = getWriter() val writer = getWriter()
val bankAccountIds = bankAccounts.map { it.technicalId } val accountIds = accounts.map { it.technicalId }
writer.deleteDocumentsAndFlushChangesToDisk(BankAccountIdFieldName, *bankAccountIds.toTypedArray()) writer.deleteDocumentsAndFlushChangesToDisk(BankAccountIdFieldName, *accountIds.toTypedArray())
} }

View File

@ -1,7 +1,7 @@
package net.dankito.banking.search package net.dankito.banking.search
import net.dankito.banking.persistence.LuceneBankingPersistence import net.dankito.banking.persistence.LuceneBankingPersistence
import net.dankito.banking.ui.model.Customer import net.dankito.banking.ui.model.BankData
import net.dankito.banking.ui.model.AccountTransaction import net.dankito.banking.ui.model.AccountTransaction
import net.dankito.banking.ui.model.BankAccount import net.dankito.banking.ui.model.BankAccount
import net.dankito.utils.io.FileUtils import net.dankito.utils.io.FileUtils
@ -37,7 +37,7 @@ class LuceneRemitteeSearcherTest {
private val Amount = BigDecimal.valueOf(123.45) private val Amount = BigDecimal.valueOf(123.45)
private val bankAccountMock = BankAccount(mock(Customer::class.java), "", "", null, null, "") private val bankAccountMock = BankAccount(mock(BankData::class.java), "", "", null, null, "")
private val dateFormat = SimpleDateFormat("dd.MM.yyyy") private val dateFormat = SimpleDateFormat("dd.MM.yyyy")

View File

@ -9,7 +9,7 @@ import net.dankito.banking.search.IRemitteeSearcher
import net.dankito.banking.search.Remittee import net.dankito.banking.search.Remittee
import net.dankito.banking.ui.model.IAccountTransaction import net.dankito.banking.ui.model.IAccountTransaction
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.TypedBankData
import net.dankito.banking.ui.model.tan.MobilePhoneTanMedium import net.dankito.banking.ui.model.tan.MobilePhoneTanMedium
import net.dankito.banking.ui.model.tan.TanGeneratorTanMedium import net.dankito.banking.ui.model.tan.TanGeneratorTanMedium
import net.dankito.banking.util.persistence.doSaveUrlToFile import net.dankito.banking.util.persistence.doSaveUrlToFile
@ -32,8 +32,8 @@ open class RoomBankingPersistence(applicationContext: Context, password: String?
} }
override fun saveOrUpdateAccount(customer: TypedCustomer, allCustomers: List<TypedCustomer>) { override fun saveOrUpdateBank(bank: TypedBankData, allBanks: List<TypedBankData>) {
(customer as? Bank)?.let { bank -> (bank as? Bank)?.let { bank ->
bank.selectedTanMethodId = bank.selectedTanMethod?.technicalId bank.selectedTanMethodId = bank.selectedTanMethod?.technicalId
db.bankDao().saveOrUpdate(bank) db.bankDao().saveOrUpdate(bank)
@ -64,8 +64,8 @@ open class RoomBankingPersistence(applicationContext: Context, password: String?
} }
} }
override fun deleteAccount(customer: TypedCustomer, allCustomers: List<TypedCustomer>) { override fun deleteBank(bank: TypedBankData, allBanks: List<TypedBankData>) {
(customer as? Bank)?.let { bank -> (bank as? Bank)?.let { bank ->
db.accountTransactionDao().delete(bank.accounts.flatMap { it.bookedTransactions }.filterIsInstance<AccountTransaction>()) db.accountTransactionDao().delete(bank.accounts.flatMap { it.bookedTransactions }.filterIsInstance<AccountTransaction>())
db.bankAccountDao().delete(bank.accounts.filterIsInstance<BankAccount>()) db.bankAccountDao().delete(bank.accounts.filterIsInstance<BankAccount>())
@ -77,7 +77,7 @@ open class RoomBankingPersistence(applicationContext: Context, password: String?
} }
} }
override fun readPersistedAccounts(): List<TypedCustomer> { override fun readPersistedBanks(): List<TypedBankData> {
val banks = db.bankDao().getAll() val banks = db.bankDao().getAll()
val accounts = db.bankAccountDao().getAll() val accounts = db.bankAccountDao().getAll()
@ -92,12 +92,12 @@ open class RoomBankingPersistence(applicationContext: Context, password: String?
bank.accounts = accounts.filter { it.bankId == bank.id } bank.accounts = accounts.filter { it.bankId == bank.id }
bank.accounts.filterIsInstance<BankAccount>().forEach { account -> bank.accounts.filterIsInstance<BankAccount>().forEach { account ->
account.customer = bank account.bank = bank
account.bookedTransactions = transactions.filter { it.bankAccountId == account.id } account.bookedTransactions = transactions.filter { it.accountId == account.id }
account.bookedTransactions.filterIsInstance<AccountTransaction>().forEach { transaction -> account.bookedTransactions.filterIsInstance<AccountTransaction>().forEach { transaction ->
transaction.bankAccount = account transaction.account = account
} }
} }
@ -111,12 +111,12 @@ open class RoomBankingPersistence(applicationContext: Context, password: String?
return banks return banks
} }
override fun saveOrUpdateAccountTransactions(bankAccount: TypedBankAccount, transactions: List<IAccountTransaction>) { override fun saveOrUpdateAccountTransactions(account: TypedBankAccount, transactions: List<IAccountTransaction>) {
val accountId = (bankAccount as? BankAccount)?.id ?: bankAccount.technicalId.toLong() val accountId = (account as? BankAccount)?.id ?: account.technicalId.toLong()
val mappedTransactions = transactions.filterIsInstance<AccountTransaction>() val mappedTransactions = transactions.filterIsInstance<AccountTransaction>()
mappedTransactions.forEach { it.bankAccountId = accountId } mappedTransactions.forEach { it.accountId = accountId }
db.accountTransactionDao().saveOrUpdate(mappedTransactions) db.accountTransactionDao().saveOrUpdate(mappedTransactions)
} }

View File

@ -11,7 +11,7 @@ import net.dankito.utils.multiplatform.*
@Entity @Entity
open class AccountTransaction( open class AccountTransaction(
@Ignore @Ignore
override var bankAccount: BankAccount, override var account: BankAccount,
override var amount: BigDecimal, override var amount: BigDecimal,
override var currency: String, override var currency: String,
@ -56,15 +56,15 @@ open class AccountTransaction(
/* convenience constructors for languages not supporting default values */ /* convenience constructors for languages not supporting default values */
constructor(bankAccount: BankAccount, otherPartyName: String?, unparsedUsage: String, amount: BigDecimal, valueDate: Date, bookingText: String?) constructor(account: BankAccount, otherPartyName: String?, unparsedUsage: String, amount: BigDecimal, valueDate: Date, bookingText: String?)
: this(bankAccount, amount, "EUR", unparsedUsage, valueDate, : this(account, amount, "EUR", unparsedUsage, valueDate,
otherPartyName, null, null, bookingText, valueDate) otherPartyName, null, null, bookingText, valueDate)
constructor(bankAccount: BankAccount, amount: BigDecimal, currency: String, unparsedUsage: String, bookingDate: Date, constructor(account: BankAccount, amount: BigDecimal, currency: String, unparsedUsage: String, bookingDate: Date,
otherPartyName: String?, otherPartyBankCode: String?, otherPartyAccountId: String?, otherPartyName: String?, otherPartyBankCode: String?, otherPartyAccountId: String?,
bookingText: String?, valueDate: Date) bookingText: String?, valueDate: Date)
: this(bankAccount, amount, currency, unparsedUsage, bookingDate, : this(account, amount, currency, unparsedUsage, bookingDate,
otherPartyName, otherPartyBankCode, otherPartyAccountId, bookingText, valueDate, otherPartyName, otherPartyBankCode, otherPartyAccountId, bookingText, valueDate,
0, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, "", "", null, null, "", null) 0, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, "", "", null, null, "", null)
@ -75,7 +75,7 @@ open class AccountTransaction(
override var technicalId: String = buildTransactionIdentifier() override var technicalId: String = buildTransactionIdentifier()
// Room doesn't allow me to add getters and setters -> have to map it manually // Room doesn't allow me to add getters and setters -> have to map it manually
open var bankAccountId: Long = BaseDao.ObjectNotInsertedId open var accountId: Long = BaseDao.ObjectNotInsertedId
override fun equals(other: Any?): Boolean { override fun equals(other: Any?): Boolean {

View File

@ -5,7 +5,7 @@ import androidx.room.Ignore
import androidx.room.PrimaryKey import androidx.room.PrimaryKey
import net.dankito.banking.persistence.dao.BaseDao 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.TypedBankData
import net.dankito.banking.ui.model.tan.TanMedium import net.dankito.banking.ui.model.tan.TanMedium
import net.dankito.banking.ui.model.tan.TanMethod import net.dankito.banking.ui.model.tan.TanMethod
@ -44,7 +44,7 @@ open class Bank(
override var userSetDisplayName: String? = null, override var userSetDisplayName: String? = null,
override var displayIndex: Int = 0 override var displayIndex: Int = 0
) : TypedCustomer { ) : TypedBankData {
internal constructor() : this("", "", "", "", "", "", "") // for object deserializers internal constructor() : this("", "", "", "", "", "", "") // for object deserializers

View File

@ -13,7 +13,7 @@ import net.dankito.utils.multiplatform.UUID
@Entity @Entity
open class BankAccount( open class BankAccount(
@Ignore @Ignore
override var customer: TypedCustomer, override var bank: TypedBankData,
override var identifier: String, override var identifier: String,
override var accountHolderName: String, override var accountHolderName: String,
override var iban: String?, override var iban: String?,
@ -42,10 +42,10 @@ open class BankAccount(
/* convenience constructors for languages not supporting default values */ /* convenience constructors for languages not supporting default values */
constructor(customer: TypedCustomer, productName: String?, identifier: String) : this(customer, productName, identifier, BankAccountType.Girokonto) constructor(bank: TypedBankData, productName: String?, identifier: String) : this(bank, productName, identifier, BankAccountType.Girokonto)
constructor(customer: TypedCustomer, productName: String?, identifier: String, type: BankAccountType = BankAccountType.Girokonto, balance: BigDecimal = BigDecimal.Zero) constructor(bank: TypedBankData, productName: String?, identifier: String, type: BankAccountType = BankAccountType.Girokonto, balance: BigDecimal = BigDecimal.Zero)
: this(customer, identifier, "", null, null, "", balance, "EUR", type, productName) : this(bank, identifier, "", null, null, "", balance, "EUR", type, productName)
@PrimaryKey(autoGenerate = true) @PrimaryKey(autoGenerate = true)

View File

@ -2,7 +2,7 @@ package net.dankito.banking.persistence.model
import net.dankito.banking.ui.model.IAccountTransaction import net.dankito.banking.ui.model.IAccountTransaction
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.TypedBankData
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.TanMethodType import net.dankito.banking.ui.model.tan.TanMethodType
@ -12,18 +12,18 @@ import net.dankito.utils.multiplatform.Date
open class RoomModelCreator : IModelCreator { open class RoomModelCreator : IModelCreator {
override fun createCustomer(bankCode: String, customerId: String, password: String, finTsServerAddress: String, bankName: String, override fun createBank(bankCode: String, customerId: String, password: String, finTsServerAddress: String, bankName: String,
bic: String, customerName: String, userId: String, iconUrl: String?): TypedCustomer { bic: String, customerName: String, userId: String, iconUrl: String?): TypedBankData {
return Bank(bankCode, customerId, password, finTsServerAddress, bankName, bic, customerName, userId, iconUrl) return Bank(bankCode, customerId, password, finTsServerAddress, bankName, bic, customerName, userId, iconUrl)
} }
override fun createBankAccount(customer: TypedCustomer, productName: String?, identifier: String): TypedBankAccount { override fun createAccount(bank: TypedBankData, productName: String?, identifier: String): TypedBankAccount {
return BankAccount(customer, productName, identifier) return BankAccount(bank, productName, identifier)
} }
override fun createTransaction( override fun createTransaction(
bankAccount: TypedBankAccount, account: TypedBankAccount,
amount: BigDecimal, amount: BigDecimal,
currency: String, currency: String,
unparsedUsage: String, unparsedUsage: String,
@ -58,7 +58,7 @@ open class RoomModelCreator : IModelCreator {
transactionReferenceNumber: String, transactionReferenceNumber: String,
relatedReferenceNumber: String? relatedReferenceNumber: String?
): IAccountTransaction { ): IAccountTransaction {
return AccountTransaction(bankAccount as BankAccount, amount, currency, unparsedUsage, bookingDate, otherPartyName, otherPartyBankCode, otherPartyAccountId, return AccountTransaction(account as BankAccount, amount, currency, unparsedUsage, bookingDate, otherPartyName, otherPartyBankCode, otherPartyAccountId,
bookingText, valueDate, statementNumber, sequenceNumber, openingBalance, closingBalance, endToEndReference, customerReference, mandateReference, bookingText, valueDate, statementNumber, sequenceNumber, openingBalance, closingBalance, endToEndReference, customerReference, mandateReference,
creditorIdentifier, originatorsIdentificationCode, compensationAmount, originalAmount, sepaUsage, deviantOriginator, deviantRecipient, creditorIdentifier, originatorsIdentificationCode, compensationAmount, originalAmount, sepaUsage, deviantOriginator, deviantRecipient,
usageWithNoSpecialType, primaNotaNumber, textKeySupplement, currencyType, bookingKey, referenceForTheAccountOwner, usageWithNoSpecialType, primaNotaNumber, textKeySupplement, currencyType, bookingKey, referenceForTheAccountOwner,

View File

@ -1,6 +1,6 @@
package net.dankito.banking.persistence package net.dankito.banking.persistence
import net.dankito.banking.persistence.model.CustomerEntity import net.dankito.banking.persistence.model.BankDataEntity
import net.dankito.banking.ui.model.* import net.dankito.banking.ui.model.*
import net.dankito.utils.multiplatform.File import net.dankito.utils.multiplatform.File
import net.dankito.banking.util.ISerializer import net.dankito.banking.util.ISerializer
@ -17,27 +17,27 @@ open class BankingPersistenceJson(
} }
override fun saveOrUpdateAccount(customer: TypedCustomer, allCustomers: List<TypedCustomer>) { override fun saveOrUpdateBank(bank: TypedBankData, allBanks: List<TypedBankData>) {
saveAllCustomers(allCustomers) saveAllBanks(allBanks)
} }
override fun deleteAccount(customer: TypedCustomer, allCustomers: List<TypedCustomer>) { override fun deleteBank(bank: TypedBankData, allBanks: List<TypedBankData>) {
saveAllCustomers(allCustomers) saveAllBanks(allBanks)
} }
override fun readPersistedAccounts(): List<TypedCustomer> { override fun readPersistedBanks(): List<TypedBankData> {
return serializer.deserializeListOr(jsonFile, CustomerEntity::class).map { it as TypedCustomer } return serializer.deserializeListOr(jsonFile, BankDataEntity::class).map { it as TypedBankData }
} }
override fun saveOrUpdateAccountTransactions(bankAccount: TypedBankAccount, transactions: List<IAccountTransaction>) { override fun saveOrUpdateAccountTransactions(account: TypedBankAccount, transactions: List<IAccountTransaction>) {
// done when called saveOrUpdateAccount() // done when called saveOrUpdateAccount()
// TODO: or also call saveAllCustomers()? // TODO: or also call saveAllBanks()?
} }
protected open fun saveAllCustomers(allCustomers: List<TypedCustomer>) { protected open fun saveAllBanks(allBanks: List<TypedBankData>) {
serializer.serializeObject(allCustomers, jsonFile) serializer.serializeObject(allBanks, jsonFile)
} }

View File

@ -2,10 +2,10 @@ package net.dankito.banking.persistence.mapper
import net.dankito.banking.persistence.model.AccountTransactionEntity import net.dankito.banking.persistence.model.AccountTransactionEntity
import net.dankito.banking.persistence.model.BankAccountEntity import net.dankito.banking.persistence.model.BankAccountEntity
import net.dankito.banking.persistence.model.CustomerEntity import net.dankito.banking.persistence.model.BankDataEntity
import net.dankito.banking.ui.model.IAccountTransaction import net.dankito.banking.ui.model.IAccountTransaction
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.TypedBankData
import net.dankito.banking.ui.model.mapper.IModelCreator 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.Date import net.dankito.utils.multiplatform.Date
@ -13,19 +13,19 @@ import net.dankito.utils.multiplatform.Date
open class EntitiesModelCreator : IModelCreator { open class EntitiesModelCreator : IModelCreator {
override fun createCustomer(bankCode: String, customerId: String, password: String, finTsServerAddress: String, bankName: String, bic: String, override fun createBank(bankCode: String, customerId: String, password: String, finTsServerAddress: String, bankName: String, bic: String,
customerName: String, userId: String, iconUrl: String?): TypedCustomer { customerName: String, userId: String, iconUrl: String?): TypedBankData {
return CustomerEntity(bankCode, customerId, password, finTsServerAddress, bankName, bic, customerName, userId, iconUrl) as TypedCustomer return BankDataEntity(bankCode, customerId, password, finTsServerAddress, bankName, bic, customerName, userId, iconUrl) as TypedBankData
} }
override fun createBankAccount(customer: TypedCustomer, productName: String?, identifier: String): TypedBankAccount { override fun createAccount(bank: TypedBankData, productName: String?, identifier: String): TypedBankAccount {
return BankAccountEntity(customer as CustomerEntity, identifier, "", null, null, "", productName = productName) as TypedBankAccount return BankAccountEntity(bank as BankDataEntity, identifier, "", null, null, "", productName = productName) as TypedBankAccount
} }
override fun createTransaction( override fun createTransaction(
bankAccount: TypedBankAccount, account: TypedBankAccount,
amount: BigDecimal, amount: BigDecimal,
currency: String, currency: String,
unparsedUsage: String, unparsedUsage: String,
@ -61,7 +61,7 @@ open class EntitiesModelCreator : IModelCreator {
relatedReferenceNumber: String? relatedReferenceNumber: String?
) : IAccountTransaction { ) : IAccountTransaction {
return AccountTransactionEntity(bankAccount as BankAccountEntity, amount, currency, unparsedUsage, bookingDate, return AccountTransactionEntity(account as BankAccountEntity, amount, currency, unparsedUsage, bookingDate,
otherPartyName, otherPartyBankCode, otherPartyAccountId, bookingText, valueDate, statementNumber, sequenceNumber, otherPartyName, otherPartyBankCode, otherPartyAccountId, bookingText, valueDate, statementNumber, sequenceNumber,
openingBalance, closingBalance, endToEndReference, customerReference, mandateReference, creditorIdentifier, openingBalance, closingBalance, endToEndReference, customerReference, mandateReference, creditorIdentifier,
originatorsIdentificationCode, compensationAmount, originalAmount, sepaUsage, deviantOriginator, deviantRecipient, originatorsIdentificationCode, compensationAmount, originalAmount, sepaUsage, deviantOriginator, deviantRecipient,

View File

@ -11,7 +11,7 @@ import net.dankito.utils.multiplatform.UUID
@JsonIdentityInfo(property = "technicalId", generator = ObjectIdGenerators.PropertyGenerator::class) // to avoid stack overflow due to circular references @JsonIdentityInfo(property = "technicalId", generator = ObjectIdGenerators.PropertyGenerator::class) // to avoid stack overflow due to circular references
// had to define all properties as 'var' 'cause MapStruct cannot handle vals // had to define all properties as 'var' 'cause MapStruct cannot handle vals
open class AccountTransactionEntity( open class AccountTransactionEntity(
override var bankAccount: BankAccountEntity, override var account: BankAccountEntity,
override var amount: BigDecimal, override var amount: BigDecimal,
override var currency: String, override var currency: String,
override var unparsedUsage: String, override var unparsedUsage: String,
@ -56,8 +56,8 @@ open class AccountTransactionEntity(
-1, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, "", "", null, -1, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, "", "", null,
null, "", null) null, "", null)
constructor(bankAccount: BankAccountEntity, otherPartyName: String?, unparsedUsage: String, amount: BigDecimal, valueDate: Date, bookingText: String?) constructor(account: BankAccountEntity, otherPartyName: String?, unparsedUsage: String, amount: BigDecimal, valueDate: Date, bookingText: String?)
: this(bankAccount, amount, "EUR", unparsedUsage, valueDate, otherPartyName, null, null, bookingText, valueDate, 0, null, null, null, : this(account, amount, "EUR", unparsedUsage, valueDate, otherPartyName, null, null, bookingText, valueDate, 0, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, "", "", null, null, "", null) null, "", "", null, null, "", null)

View File

@ -11,7 +11,7 @@ import net.dankito.utils.multiplatform.UUID
@JsonIdentityInfo(property = "technicalId", generator = ObjectIdGenerators.PropertyGenerator::class) // to avoid stack overflow due to circular references @JsonIdentityInfo(property = "technicalId", generator = ObjectIdGenerators.PropertyGenerator::class) // to avoid stack overflow due to circular references
// had to define all properties as 'var' 'cause MapStruct cannot handle vals (and cannot use Pozo's mapstruct-kotlin as SerializableBankAccountBuilder would fail with @Context) // had to define all properties as 'var' 'cause MapStruct cannot handle vals (and cannot use Pozo's mapstruct-kotlin as SerializableBankAccountBuilder would fail with @Context)
open class BankAccountEntity( open class BankAccountEntity(
override var customer: CustomerEntity, override var bank: BankDataEntity,
override var identifier: String, override var identifier: String,
override var accountHolderName: String, override var accountHolderName: String,
override var iban: String?, override var iban: String?,
@ -38,7 +38,7 @@ open class BankAccountEntity(
) : IBankAccount<AccountTransactionEntity> { ) : IBankAccount<AccountTransactionEntity> {
internal constructor() : this(CustomerEntity(), "", "", null, null, "") // for object deserializers internal constructor() : this(BankDataEntity(), "", "", null, null, "") // for object deserializers
override fun toString(): String { override fun toString(): String {

View File

@ -1,7 +1,7 @@
package net.dankito.banking.persistence.model package net.dankito.banking.persistence.model
import com.fasterxml.jackson.annotation.* import com.fasterxml.jackson.annotation.*
import net.dankito.banking.ui.model.ICustomer import net.dankito.banking.ui.model.IBankData
import net.dankito.banking.ui.model.tan.TanMedium import net.dankito.banking.ui.model.tan.TanMedium
import net.dankito.banking.ui.model.tan.TanMethod import net.dankito.banking.ui.model.tan.TanMethod
import java.util.* import java.util.*
@ -9,7 +9,7 @@ import java.util.*
@JsonIdentityInfo(property = "technicalId", generator = ObjectIdGenerators.PropertyGenerator::class) // to avoid stack overflow due to circular references @JsonIdentityInfo(property = "technicalId", generator = ObjectIdGenerators.PropertyGenerator::class) // to avoid stack overflow due to circular references
// had to define all properties as 'var' 'cause MapStruct cannot handle vals (and cannot use Pozo's mapstruct-kotlin as SerializableCustomerBuilder would fail with @Context) // had to define all properties as 'var' 'cause MapStruct cannot handle vals (and cannot use Pozo's mapstruct-kotlin as SerializableCustomerBuilder would fail with @Context)
open class CustomerEntity( open class BankDataEntity(
override var bankCode: String, override var bankCode: String,
override var customerId: String, override var customerId: String,
override var password: String, override var password: String,
@ -27,7 +27,7 @@ open class CustomerEntity(
override var technicalId: String = UUID.randomUUID().toString(), override var technicalId: String = UUID.randomUUID().toString(),
override var userSetDisplayName: String? = null, override var userSetDisplayName: String? = null,
override var displayIndex: Int = 0 override var displayIndex: Int = 0
) : ICustomer<BankAccountEntity, AccountTransactionEntity> { ) : IBankData<BankAccountEntity, AccountTransactionEntity> {
internal constructor() : this("", "", "", "", "", "", "") // for object deserializers internal constructor() : this("", "", "", "", "", "", "") // for object deserializers

View File

@ -2,7 +2,7 @@ package net.dankito.banking.persistence
import net.dankito.banking.persistence.model.AccountTransactionEntity import net.dankito.banking.persistence.model.AccountTransactionEntity
import net.dankito.banking.persistence.model.BankAccountEntity import net.dankito.banking.persistence.model.BankAccountEntity
import net.dankito.banking.persistence.model.CustomerEntity import net.dankito.banking.persistence.model.BankDataEntity
import net.dankito.banking.ui.model.* import net.dankito.banking.ui.model.*
import net.dankito.banking.util.JacksonJsonSerializer import net.dankito.banking.util.JacksonJsonSerializer
import net.dankito.utils.multiplatform.BigDecimal import net.dankito.utils.multiplatform.BigDecimal
@ -57,96 +57,96 @@ class BankingPersistenceJsonTest {
@Test @Test
fun saveOrUpdateAccount() { fun saveOrUpdateBank() {
// given // given
val customers = listOf( val banks = listOf(
createCustomer(2), createBank(2),
createCustomer(3) createBank(3)
) )
// when // when
underTest.saveOrUpdateAccount(customers.first() as TypedCustomer, customers.map { it as TypedCustomer }) underTest.saveOrUpdateBank(banks.first() as TypedBankData, banks.map { it as TypedBankData })
// then // then
val result = serializer.deserializeListOr(file, CustomerEntity::class) val result = serializer.deserializeListOr(file, BankDataEntity::class)
assertCustomersEqual(result, customers) assertBanksEqual(result, banks)
} }
@Test @Test
fun saveOrUpdateAccountWithBankAccountsAndTransactions() { fun saveOrUpdateBankWithAccountsAndTransactions() {
// given // given
val customer = createCustomer(2) val bank = createBank(2)
// when // when
underTest.saveOrUpdateAccount(customer as TypedCustomer, listOf(customer).map { it as TypedCustomer }) underTest.saveOrUpdateBank(bank as TypedBankData, listOf(bank).map { it as TypedBankData })
// then // then
val result = serializer.deserializeListOr(file, CustomerEntity::class) val result = serializer.deserializeListOr(file, BankDataEntity::class)
assertCustomersEqual(result, listOf(customer) as List<CustomerEntity>) assertBanksEqual(result, listOf(bank) as List<BankDataEntity>)
} }
@Test @Test
fun readPersistedAccounts() { fun readPersistedBanks() {
// given // given
val customers = listOf( val banks = listOf(
createCustomer(2), createBank(2),
createCustomer(3) createBank(3)
) )
serializer.serializeObject(customers, file) serializer.serializeObject(banks, file)
// when // when
val result = underTest.readPersistedAccounts() val result = underTest.readPersistedBanks()
// then // then
assertCustomersEqual(customers, result as List<CustomerEntity>) assertBanksEqual(banks, result as List<BankDataEntity>)
} }
private fun createCustomer(countBankAccounts: Int = 0, customerId: String = CustomerId): CustomerEntity { private fun createBank(countAccounts: Int = 0, customerId: String = CustomerId): BankDataEntity {
val result = CustomerEntity(BankCode, customerId, Password, FinTsServerAddress, BankName, Bic, CustomerName, UserId, IconUrl) val result = BankDataEntity(BankCode, customerId, Password, FinTsServerAddress, BankName, Bic, CustomerName, UserId, IconUrl)
result.accounts = createBankAccounts(countBankAccounts, result) result.accounts = createAccounts(countAccounts, result)
return result return result
} }
private fun createBankAccounts(count: Int, customer: CustomerEntity): List<BankAccountEntity> { private fun createAccounts(count: Int, customer: BankDataEntity): List<BankAccountEntity> {
val random = Random(System.nanoTime()) val random = Random(System.nanoTime())
return IntRange(1, count).map { accountIndex -> return IntRange(1, count).map { accountIndex ->
createBankAccount("Account_$accountIndex", customer, random.nextInt(2, 50)) createAccount("Account_$accountIndex", customer, random.nextInt(2, 50))
} }
} }
private fun createBankAccount(productName: String, customer: CustomerEntity, countTransactions: Int = 0): BankAccountEntity { private fun createAccount(productName: String, customer: BankDataEntity, countTransactions: Int = 0): BankAccountEntity {
val result = BankAccountEntity(customer, customer.customerId, "AccountHolder", "DE00" + customer.bankCode + customer.customerId, null, val result = BankAccountEntity(customer, customer.customerId, "AccountHolder", "DE00" + customer.bankCode + customer.customerId, null,
customer.customerId, BigDecimal(84.25), productName = productName) customer.customerId, BigDecimal(84.25), productName = productName)
result.bookedTransactions = createAccountTransactions(countTransactions, result) result.bookedTransactions = createTransactions(countTransactions, result)
return result return result
} }
private fun createAccountTransactions(countTransactions: Int, account: BankAccountEntity): List<AccountTransactionEntity> { private fun createTransactions(countTransactions: Int, account: BankAccountEntity): List<AccountTransactionEntity> {
return IntRange(1, countTransactions).map { transactionIndex -> return IntRange(1, countTransactions).map { transactionIndex ->
createAccountTransaction(transactionIndex, account) createTransaction(transactionIndex, account)
} }
} }
private fun createAccountTransaction(transactionIndex: Int, account: BankAccountEntity): AccountTransactionEntity { private fun createTransaction(transactionIndex: Int, account: BankAccountEntity): AccountTransactionEntity {
return AccountTransactionEntity(account, "OtherParty_$transactionIndex", "Usage_$transactionIndex", BigDecimal(transactionIndex.toDouble()), createDate(), null) return AccountTransactionEntity(account, "OtherParty_$transactionIndex", "Usage_$transactionIndex", BigDecimal(transactionIndex.toDouble()), createDate(), null)
} }
@ -155,37 +155,37 @@ class BankingPersistenceJsonTest {
} }
private fun assertCustomersEqual(deserializedCustomers: List<CustomerEntity>, customers: List<CustomerEntity>) { private fun assertBanksEqual(deserializedBanks: List<BankDataEntity>, banks: List<BankDataEntity>) {
assertThat(deserializedCustomers.size).isEqualTo(customers.size) assertThat(deserializedBanks.size).isEqualTo(banks.size)
deserializedCustomers.forEach { deserializedCustomer -> deserializedBanks.forEach { deserializedBanks ->
val customer = customers.firstOrNull { it.technicalId == deserializedCustomer.technicalId } val bank = banks.firstOrNull { it.technicalId == deserializedBanks.technicalId }
if (customer == null) { if (bank == null) {
Assert.fail("Could not find matching customer for deserialized customer $deserializedCustomer. customers = $customers") Assert.fail("Could not find matching bank for deserialized bank $deserializedBanks. banks = $banks")
} }
else { else {
assertCustomersEqual(deserializedCustomer, customer) assertBanksEqual(deserializedBanks, bank)
} }
} }
} }
private fun assertCustomersEqual(deserializedCustomer: CustomerEntity, customer: CustomerEntity) { private fun assertBanksEqual(deserializedBank: BankDataEntity, bank: BankDataEntity) {
assertThat(deserializedCustomer.bankCode).isEqualTo(customer.bankCode) assertThat(deserializedBank.bankCode).isEqualTo(bank.bankCode)
assertThat(deserializedCustomer.customerId).isEqualTo(customer.customerId) assertThat(deserializedBank.customerId).isEqualTo(bank.customerId)
assertThat(deserializedCustomer.password).isEqualTo(customer.password) assertThat(deserializedBank.password).isEqualTo(bank.password)
assertThat(deserializedCustomer.finTsServerAddress).isEqualTo(customer.finTsServerAddress) assertThat(deserializedBank.finTsServerAddress).isEqualTo(bank.finTsServerAddress)
assertThat(deserializedCustomer.bankName).isEqualTo(customer.bankName) assertThat(deserializedBank.bankName).isEqualTo(bank.bankName)
assertThat(deserializedCustomer.bic).isEqualTo(customer.bic) assertThat(deserializedBank.bic).isEqualTo(bank.bic)
assertThat(deserializedCustomer.customerName).isEqualTo(customer.customerName) assertThat(deserializedBank.customerName).isEqualTo(bank.customerName)
assertThat(deserializedCustomer.userId).isEqualTo(customer.userId) assertThat(deserializedBank.userId).isEqualTo(bank.userId)
assertThat(deserializedCustomer.iconUrl).isEqualTo(customer.iconUrl) assertThat(deserializedBank.iconUrl).isEqualTo(bank.iconUrl)
assertBankAccountsEqual(deserializedCustomer.accounts, customer.accounts) assertAccountsEqual(deserializedBank.accounts, bank.accounts)
} }
private fun assertBankAccountsEqual(deserializedAccounts: List<BankAccountEntity>, accounts: List<BankAccountEntity>) { private fun assertAccountsEqual(deserializedAccounts: List<BankAccountEntity>, accounts: List<BankAccountEntity>) {
assertThat(deserializedAccounts.size).isEqualTo(accounts.size) assertThat(deserializedAccounts.size).isEqualTo(accounts.size)
deserializedAccounts.forEach { deserializedAccount -> deserializedAccounts.forEach { deserializedAccount ->
@ -195,14 +195,14 @@ class BankingPersistenceJsonTest {
Assert.fail("Could not find matching account for deserialized account $deserializedAccount. accounts = $accounts") Assert.fail("Could not find matching account for deserialized account $deserializedAccount. accounts = $accounts")
} }
else { else {
assertBankAccountsEqual(deserializedAccount, account) assertAccountsEqual(deserializedAccount, account)
} }
} }
} }
private fun assertBankAccountsEqual(deserializedAccount: BankAccountEntity, account: BankAccountEntity) { private fun assertAccountsEqual(deserializedAccount: BankAccountEntity, account: BankAccountEntity) {
// to check if MapStruct created reference correctly // to check if MapStruct created reference correctly
assertThat(deserializedAccount.customer.technicalId).isEqualTo(account.customer.technicalId) assertThat(deserializedAccount.bank.technicalId).isEqualTo(account.bank.technicalId)
assertThat(deserializedAccount.identifier).isEqualTo(account.identifier) assertThat(deserializedAccount.identifier).isEqualTo(account.identifier)
assertThat(deserializedAccount.iban).isEqualTo(account.iban) assertThat(deserializedAccount.iban).isEqualTo(account.iban)
@ -210,10 +210,10 @@ class BankingPersistenceJsonTest {
assertThat(deserializedAccount.balance).isEqualTo(account.balance) assertThat(deserializedAccount.balance).isEqualTo(account.balance)
assertThat(deserializedAccount.productName).isEqualTo(account.productName) assertThat(deserializedAccount.productName).isEqualTo(account.productName)
assertAccountTransactionsEqual(deserializedAccount.bookedTransactions, account.bookedTransactions) assertTransactionsEqual(deserializedAccount.bookedTransactions, account.bookedTransactions)
} }
private fun assertAccountTransactionsEqual(deserializedTransactions: List<AccountTransactionEntity>, transactions: List<AccountTransactionEntity>) { private fun assertTransactionsEqual(deserializedTransactions: List<AccountTransactionEntity>, transactions: List<AccountTransactionEntity>) {
assertThat(deserializedTransactions.size).isEqualTo(transactions.size) assertThat(deserializedTransactions.size).isEqualTo(transactions.size)
deserializedTransactions.forEach { deserializedTransaction -> deserializedTransactions.forEach { deserializedTransaction ->
@ -223,14 +223,14 @@ class BankingPersistenceJsonTest {
Assert.fail("Could not find matching transaction for deserialized transaction $deserializedTransaction. transactions = $transactions") Assert.fail("Could not find matching transaction for deserialized transaction $deserializedTransaction. transactions = $transactions")
} }
else { else {
assertAccountTransactionsEqual(deserializedTransaction, transaction) assertTransactionsEqual(deserializedTransaction, transaction)
} }
} }
} }
private fun assertAccountTransactionsEqual(deserializedTransaction: AccountTransactionEntity, transaction: AccountTransactionEntity) { private fun assertTransactionsEqual(deserializedTransaction: AccountTransactionEntity, transaction: AccountTransactionEntity) {
// to check if MapStruct created reference correctly // to check if MapStruct created reference correctly
assertThat(deserializedTransaction.bankAccount.technicalId).isEqualTo(transaction.bankAccount.technicalId) assertThat(deserializedTransaction.account.technicalId).isEqualTo(transaction.account.technicalId)
assertThat(deserializedTransaction.otherPartyName).isEqualTo(transaction.otherPartyName) assertThat(deserializedTransaction.otherPartyName).isEqualTo(transaction.otherPartyName)
assertThat(deserializedTransaction.unparsedUsage).isEqualTo(transaction.unparsedUsage) assertThat(deserializedTransaction.unparsedUsage).isEqualTo(transaction.unparsedUsage)

View File

@ -3,7 +3,7 @@ package net.dankito.banking.ui.android
import net.dankito.banking.ui.android.util.CurrentActivityTracker import net.dankito.banking.ui.android.util.CurrentActivityTracker
import net.dankito.banking.ui.IRouter import net.dankito.banking.ui.IRouter
import net.dankito.banking.ui.android.dialogs.* import net.dankito.banking.ui.android.dialogs.*
import net.dankito.banking.ui.model.TypedCustomer import net.dankito.banking.ui.model.TypedBankData
import net.dankito.banking.ui.model.parameters.TransferMoneyData import net.dankito.banking.ui.model.parameters.TransferMoneyData
import net.dankito.banking.ui.model.tan.EnterTanGeneratorAtcResult import net.dankito.banking.ui.model.tan.EnterTanGeneratorAtcResult
import net.dankito.banking.ui.model.tan.EnterTanResult import net.dankito.banking.ui.model.tan.EnterTanResult
@ -20,10 +20,10 @@ open class RouterAndroid(protected val activityTracker: CurrentActivityTracker)
} }
} }
override fun getTanFromUserFromNonUiThread(customer: TypedCustomer, tanChallenge: TanChallenge, presenter: BankingPresenter, callback: (EnterTanResult) -> Unit) { override fun getTanFromUserFromNonUiThread(bank: TypedBankData, tanChallenge: TanChallenge, presenter: BankingPresenter, callback: (EnterTanResult) -> Unit) {
activityTracker.currentOrNextActivity { activity -> activityTracker.currentOrNextActivity { activity ->
activity.runOnUiThread { activity.runOnUiThread {
EnterTanDialog().show(customer, tanChallenge, activity, false) { result -> EnterTanDialog().show(bank, tanChallenge, activity, false) { result ->
callback(result) callback(result)
} }
} }

View File

@ -44,7 +44,7 @@ open class AccountTransactionAdapter(protected val presenter: BankingPresenter)
viewHolder.txtvwAmount.showAmount(presenter, item.amount) viewHolder.txtvwAmount.showAmount(presenter, item.amount)
val iconUrl = item.bankAccount.customer.iconUrl val iconUrl = item.account.bank.iconUrl
if (iconUrl != null && presenter.areAllAccountSelected) { if (iconUrl != null && presenter.areAllAccountSelected) {
viewHolder.imgvwBankIcon.visibility = View.VISIBLE viewHolder.imgvwBankIcon.visibility = View.VISIBLE
viewHolder.imgvwBankIcon.setImageURI(Uri.parse(iconUrl)) viewHolder.imgvwBankIcon.setImageURI(Uri.parse(iconUrl))

View File

@ -12,7 +12,7 @@ import net.dankito.banking.ui.model.TypedBankAccount
import net.dankito.utils.android.ui.adapter.ListAdapter import net.dankito.utils.android.ui.adapter.ListAdapter
open class BankAccountsAdapter(bankAccounts: List<TypedBankAccount>) : ListAdapter<TypedBankAccount>(bankAccounts) { open class BankAccountsAdapter(accounts: List<TypedBankAccount>) : ListAdapter<TypedBankAccount>(accounts) {
override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View? { override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View? {
@ -30,9 +30,9 @@ open class BankAccountsAdapter(bankAccounts: List<TypedBankAccount>) : ListAdapt
return view return view
} }
protected open fun setIcon(bankAccount: TypedBankAccount, imgBankIcon: ImageView) { protected open fun setIcon(account: TypedBankAccount, imgBankIcon: ImageView) {
try { try {
val iconUrl = bankAccount.customer.iconUrl val iconUrl = account.bank.iconUrl
imgBankIcon.visibility = if (iconUrl == null) View.GONE else View.VISIBLE imgBankIcon.visibility = if (iconUrl == null) View.GONE else View.VISIBLE
imgBankIcon.setImageURI(Uri.parse(iconUrl)) imgBankIcon.setImageURI(Uri.parse(iconUrl))
} catch (e: Exception) { } catch (e: Exception) {

View File

@ -3,13 +3,13 @@ package net.dankito.banking.ui.android.alerts
import android.content.Context import android.content.Context
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import net.dankito.banking.ui.android.R import net.dankito.banking.ui.android.R
import net.dankito.banking.ui.model.TypedCustomer import net.dankito.banking.ui.model.TypedBankData
import net.dankito.banking.ui.presenter.BankingPresenter import net.dankito.banking.ui.presenter.BankingPresenter
open class AskDeleteAccountAlert { open class AskDeleteAccountAlert {
open fun show(bank: TypedCustomer, presenter: BankingPresenter, context: Context, accountDeleted: (() -> Unit)? = null) { open fun show(bank: TypedBankData, presenter: BankingPresenter, context: Context, accountDeleted: (() -> Unit)? = null) {
AlertDialog.Builder(context) AlertDialog.Builder(context)
.setTitle(context.getString(R.string.alert_ask_delete_account_title, bank.displayName)) .setTitle(context.getString(R.string.alert_ask_delete_account_title, bank.displayName))
.setMessage(context.getString(R.string.alert_ask_delete_account_message)) .setMessage(context.getString(R.string.alert_ask_delete_account_message))

View File

@ -21,7 +21,7 @@ import net.dankito.banking.ui.android.adapter.TanMediumAdapter
import net.dankito.banking.ui.android.adapter.TanMethodsAdapter import net.dankito.banking.ui.android.adapter.TanMethodsAdapter
import net.dankito.banking.ui.android.di.BankingComponent import net.dankito.banking.ui.android.di.BankingComponent
import net.dankito.banking.ui.android.listener.ListItemSelectedListener import net.dankito.banking.ui.android.listener.ListItemSelectedListener
import net.dankito.banking.ui.model.TypedCustomer import net.dankito.banking.ui.model.TypedBankData
import net.dankito.banking.ui.model.responses.BankingClientResponse import net.dankito.banking.ui.model.responses.BankingClientResponse
import net.dankito.banking.ui.model.tan.* import net.dankito.banking.ui.model.tan.*
import net.dankito.banking.ui.presenter.BankingPresenter import net.dankito.banking.ui.presenter.BankingPresenter
@ -41,7 +41,7 @@ open class EnterTanDialog : DialogFragment() {
} }
protected lateinit var customer: TypedCustomer protected lateinit var bank: TypedBankData
protected lateinit var tanChallenge: TanChallenge protected lateinit var tanChallenge: TanChallenge
@ -59,10 +59,10 @@ open class EnterTanDialog : DialogFragment() {
} }
open fun show(customer: TypedCustomer, tanChallenge: TanChallenge, activity: AppCompatActivity, open fun show(bank: TypedBankData, tanChallenge: TanChallenge, activity: AppCompatActivity,
fullscreen: Boolean = false, tanEnteredCallback: (EnterTanResult) -> Unit) { fullscreen: Boolean = false, tanEnteredCallback: (EnterTanResult) -> Unit) {
this.customer = customer this.bank = bank
this.tanChallenge = tanChallenge this.tanChallenge = tanChallenge
this.tanEnteredCallback = tanEnteredCallback this.tanEnteredCallback = tanEnteredCallback
@ -97,13 +97,13 @@ open class EnterTanDialog : DialogFragment() {
protected open fun setupSelectTanMethodView(rootView: View) { protected open fun setupSelectTanMethodView(rootView: View) {
val adapter = TanMethodsAdapter() val adapter = TanMethodsAdapter()
val tanMethodsWithoutUnsupported = customer.supportedTanMethods.filterNot { it.type == TanMethodType.ChipTanUsb } // USB tan generators are not supported on Android val tanMethodsWithoutUnsupported = bank.supportedTanMethods.filterNot { it.type == TanMethodType.ChipTanUsb } // USB tan generators are not supported on Android
adapter.setItems(tanMethodsWithoutUnsupported) adapter.setItems(tanMethodsWithoutUnsupported)
rootView.findViewById<Spinner>(R.id.spnTanMethods)?.let { spinner -> rootView.findViewById<Spinner>(R.id.spnTanMethods)?.let { spinner ->
spinner.adapter = adapter spinner.adapter = adapter
val selectedTanMethod = customer.selectedTanMethod val selectedTanMethod = bank.selectedTanMethod
?: tanMethodsWithoutUnsupported.firstOrNull { it.type != TanMethodType.ChipTanManuell && it.type != TanMethodType.ChipTanUsb } ?: tanMethodsWithoutUnsupported.firstOrNull { it.type != TanMethodType.ChipTanManuell && it.type != TanMethodType.ChipTanUsb }
?: tanMethodsWithoutUnsupported.firstOrNull() ?: tanMethodsWithoutUnsupported.firstOrNull()
selectedTanMethod?.let { spinner.setSelection(adapter.getItems().indexOf(selectedTanMethod)) } selectedTanMethod?.let { spinner.setSelection(adapter.getItems().indexOf(selectedTanMethod)) }
@ -120,12 +120,12 @@ open class EnterTanDialog : DialogFragment() {
} }
protected open fun setupSelectTanMediumView(rootView: View) { protected open fun setupSelectTanMediumView(rootView: View) {
val tanMediaForTanMethod = presenter.getTanMediaForTanMethod(customer, tanChallenge.tanMethod) val tanMediaForTanMethod = presenter.getTanMediaForTanMethod(bank, tanChallenge.tanMethod)
if (tanMediaForTanMethod.size > 1) { if (tanMediaForTanMethod.size > 1) {
rootView.lytTanMedium.visibility = View.VISIBLE rootView.lytTanMedium.visibility = View.VISIBLE
tanMediumAdapter.setItems(customer.tanMediaSorted) tanMediumAdapter.setItems(bank.tanMediaSorted)
rootView.spnTanMedium.adapter = tanMediumAdapter rootView.spnTanMedium.adapter = tanMediumAdapter
rootView.spnTanMedium.onItemSelectedListener = ListItemSelectedListener(tanMediumAdapter) { selectedTanMedium -> rootView.spnTanMedium.onItemSelectedListener = ListItemSelectedListener(tanMediumAdapter) { selectedTanMedium ->

View File

@ -51,7 +51,7 @@ open class SendMessageLogDialog : DialogFragment() {
} }
protected open fun setupUI(rootView: View) { protected open fun setupUI(rootView: View) {
val messageLog = presenter.getMessageLogForAccounts(presenter.customers).joinToString("\r\n\r\n") val messageLog = presenter.getMessageLogForAccounts(presenter.allBanks).joinToString("\r\n\r\n")
if (messageLog.isBlank()) { if (messageLog.isBlank()) {
rootView.txtvwInfoNoMessageLogEntriesYet.visibility = View.VISIBLE rootView.txtvwInfoNoMessageLogEntriesYet.visibility = View.VISIBLE

View File

@ -53,7 +53,7 @@ open class TransferMoneyDialog : DialogFragment() {
} }
protected lateinit var bankAccount: TypedBankAccount protected lateinit var account: TypedBankAccount
protected var preselectedValues: TransferMoneyData? = null protected var preselectedValues: TransferMoneyData? = null
@ -108,16 +108,16 @@ open class TransferMoneyDialog : DialogFragment() {
} }
protected open fun setupUI(rootView: View) { protected open fun setupUI(rootView: View) {
val allBankAccountsSupportingTransferringMoney = presenter.bankAccountsSupportingTransferringMoney val accountsSupportingTransferringMoney = presenter.accountsSupportingTransferringMoney
bankAccount = preselectedValues?.account ?: allBankAccountsSupportingTransferringMoney.first() account = preselectedValues?.account ?: accountsSupportingTransferringMoney.first()
if (allBankAccountsSupportingTransferringMoney.size > 1) { if (accountsSupportingTransferringMoney.size > 1) {
rootView.lytSelectBankAccount.visibility = View.VISIBLE rootView.lytSelectBankAccount.visibility = View.VISIBLE
val adapter = BankAccountsAdapter(allBankAccountsSupportingTransferringMoney) val adapter = BankAccountsAdapter(accountsSupportingTransferringMoney)
rootView.spnBankAccounts.adapter = adapter rootView.spnBankAccounts.adapter = adapter
rootView.spnBankAccounts.onItemSelectedListener = ListItemSelectedListener(adapter) { selectedBankAccount -> rootView.spnBankAccounts.onItemSelectedListener = ListItemSelectedListener(adapter) { selectedBankAccount ->
this.bankAccount = selectedBankAccount this.account = selectedBankAccount
setInstantPaymentControlsVisibility(rootView) setInstantPaymentControlsVisibility(rootView)
} }
preselectedValues?.account?.let { rootView.spnBankAccounts.setSelection(adapter.getItems().indexOf(it)) } preselectedValues?.account?.let { rootView.spnBankAccounts.setSelection(adapter.getItems().indexOf(it)) }
@ -184,7 +184,7 @@ open class TransferMoneyDialog : DialogFragment() {
protected open fun setInstantPaymentControlsVisibility(rootView: View) { protected open fun setInstantPaymentControlsVisibility(rootView: View) {
rootView.lytInstantPayment.visibility = rootView.lytInstantPayment.visibility =
if (bankAccount.supportsInstantPaymentMoneyTransfer) { if (account.supportsInstantPaymentMoneyTransfer) {
View.VISIBLE View.VISIBLE
} }
else { else {
@ -295,7 +295,7 @@ open class TransferMoneyDialog : DialogFragment() {
protected open fun transferMoney() { protected open fun transferMoney() {
getEnteredAmount()?.let { amount -> // should only come at this stage when a valid amount has been entered getEnteredAmount()?.let { amount -> // should only come at this stage when a valid amount has been entered
val data = TransferMoneyData( val data = TransferMoneyData(
bankAccount, account,
inputValidator.convertToAllowedSepaCharacters(edtxtRemitteeName.text.toString()), inputValidator.convertToAllowedSepaCharacters(edtxtRemitteeName.text.toString()),
edtxtRemitteeIban.text.toString().replace(" ", ""), edtxtRemitteeIban.text.toString().replace(" ", ""),
remitteeBic?.replace(" ", "") ?: "", // should always be != null at this point remitteeBic?.replace(" ", "") ?: "", // should always be != null at this point

View File

@ -13,7 +13,7 @@ import net.dankito.banking.ui.android.alerts.AskDeleteAccountAlert
import net.dankito.banking.ui.android.alerts.AskDismissChangesAlert import net.dankito.banking.ui.android.alerts.AskDismissChangesAlert
import net.dankito.banking.ui.android.di.BankingComponent import net.dankito.banking.ui.android.di.BankingComponent
import net.dankito.banking.ui.android.views.FormEditText import net.dankito.banking.ui.android.views.FormEditText
import net.dankito.banking.ui.model.TypedCustomer import net.dankito.banking.ui.model.TypedBankData
import net.dankito.banking.ui.presenter.BankingPresenter import net.dankito.banking.ui.presenter.BankingPresenter
import javax.inject.Inject import javax.inject.Inject
@ -25,7 +25,7 @@ open class BankSettingsDialog : DialogFragment() {
} }
protected lateinit var bank: TypedCustomer protected lateinit var bank: TypedBankData
@Inject @Inject
@ -38,7 +38,7 @@ open class BankSettingsDialog : DialogFragment() {
fun show(bank: TypedCustomer, activity: AppCompatActivity, fullscreen: Boolean = false) { fun show(bank: TypedBankData, activity: AppCompatActivity, fullscreen: Boolean = false) {
this.bank = bank this.bank = bank
val style = if (fullscreen) R.style.FullscreenDialogWithStatusBar else R.style.FloatingDialog val style = if (fullscreen) R.style.FullscreenDialogWithStatusBar else R.style.FloatingDialog
@ -108,7 +108,7 @@ open class BankSettingsDialog : DialogFragment() {
bank.customerId = edtxtCustomerId.text bank.customerId = edtxtCustomerId.text
bank.password = edtxtPassword.text bank.password = edtxtPassword.text
presenter.accountUpdated(bank) presenter.bankUpdated(bank)
} }
protected open fun askToDismissChanges() { protected open fun askToDismissChanges() {

View File

@ -170,8 +170,8 @@ class HomeFragment : Fragment() {
private fun initLogicAfterUiInitialized() { private fun initLogicAfterUiInitialized() {
presenter.addAccountsChangedListener { updateMenuItemsStateAndTransactionsToDisplay() } // on account addition or deletion may menu items' state changes presenter.addBanksChangedListener { updateMenuItemsStateAndTransactionsToDisplay() } // on account addition or deletion may menu items' state changes
presenter.addSelectedBankAccountsChangedListener { updateMenuItemsStateAndTransactionsToDisplay() } presenter.addSelectedAccountsChangedListener { updateMenuItemsStateAndTransactionsToDisplay() }
presenter.addRetrievedAccountTransactionsResponseListener { response -> presenter.addRetrievedAccountTransactionsResponseListener { response ->
handleGetTransactionsResponseOffUiThread(response) handleGetTransactionsResponseOffUiThread(response)
@ -183,15 +183,15 @@ class HomeFragment : Fragment() {
private fun updateMenuItemsStateAndTransactionsToDisplay() { private fun updateMenuItemsStateAndTransactionsToDisplay() {
context?.asActivity()?.runOnUiThread { context?.asActivity()?.runOnUiThread {
mnitmSearchTransactions.isVisible = presenter.doSelectedBankAccountsSupportRetrievingAccountTransactions mnitmSearchTransactions.isVisible = presenter.doSelectedAccountsSupportRetrievingTransactions
mnitmUpdateTransactions.isVisible = presenter.doSelectedBankAccountsSupportRetrievingAccountTransactions mnitmUpdateTransactions.isVisible = presenter.doSelectedAccountsSupportRetrievingTransactions
updateTransactionsToDisplayOnUiThread() updateTransactionsToDisplayOnUiThread()
} }
} }
private fun updateAccountsTransactions() { private fun updateAccountsTransactions() {
presenter.updateSelectedBankAccountTransactionsAsync { } presenter.updateSelectedAccountsTransactionsAsync { }
} }
private fun handleGetTransactionsResponseOffUiThread(response: GetTransactionsResponse) { private fun handleGetTransactionsResponseOffUiThread(response: GetTransactionsResponse) {
@ -249,14 +249,14 @@ class HomeFragment : Fragment() {
private fun updateTransactionsToDisplayOnUiThread() { private fun updateTransactionsToDisplayOnUiThread() {
transactionAdapter.items = presenter.searchSelectedAccountTransactions(appliedTransactionsFilter) transactionAdapter.items = presenter.searchSelectedAccountTransactions(appliedTransactionsFilter)
mnitmBalance.title = presenter.formatAmount(presenter.balanceOfSelectedBankAccounts) mnitmBalance.title = presenter.formatAmount(presenter.balanceOfSelectedAccounts)
mnitmBalance.isVisible = presenter.doSelectedBankAccountsSupportRetrievingBalance mnitmBalance.isVisible = presenter.doSelectedAccountsSupportRetrievingBalance
lytTransactionsSummary.visibility = if (presenter.doSelectedBankAccountsSupportRetrievingBalance) View.VISIBLE else View.GONE lytTransactionsSummary.visibility = if (presenter.doSelectedAccountsSupportRetrievingBalance) View.VISIBLE else View.GONE
txtCountTransactions.text = context?.getString(R.string.fragment_home_count_transactions, transactionAdapter.items.size) txtCountTransactions.text = context?.getString(R.string.fragment_home_count_transactions, transactionAdapter.items.size)
val sumOfDisplayedTransactions = if (appliedTransactionsFilter.isBlank()) presenter.balanceOfSelectedBankAccounts val sumOfDisplayedTransactions = if (appliedTransactionsFilter.isBlank()) presenter.balanceOfSelectedAccounts
else transactionAdapter.items.map { it.amount }.sum() else transactionAdapter.items.map { it.amount }.sum()
txtTransactionsBalance.showAmount(presenter, sumOfDisplayedTransactions) txtTransactionsBalance.showAmount(presenter, sumOfDisplayedTransactions)
@ -266,9 +266,9 @@ class HomeFragment : Fragment() {
} }
private fun setRecyclerViewAndNoTransactionsFetchedView() { private fun setRecyclerViewAndNoTransactionsFetchedView() {
val transactionsRetrievalState = presenter.selectedBankAccountsTransactionRetrievalState val transactionsRetrievalState = presenter.selectedAccountsTransactionRetrievalState
val haveTransactionsBeenRetrieved = transactionsRetrievalState == TransactionsRetrievalState.RetrievedTransactions val haveTransactionsBeenRetrieved = transactionsRetrievalState == TransactionsRetrievalState.RetrievedTransactions
val noAccountsAddedYet = presenter.customers.isEmpty() val noAccountsAddedYet = presenter.allBanks.isEmpty()
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
@ -280,7 +280,7 @@ class HomeFragment : Fragment() {
TransactionsRetrievalState.AccountTypeNotSupported -> R.string.fragment_home_transactions_retrieval_state_account_type_not_supported 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 -> { TransactionsRetrievalState.NoTransactionsInRetrievedPeriod -> {
val account = presenter.selectedBankAccounts.first() val account = presenter.selectedAccounts.first()
account.retrievedTransactionsFromOn?.let { messageArgs.add(RetrievedTransactionsPeriodDateFormat.format(it)) } account.retrievedTransactionsFromOn?.let { messageArgs.add(RetrievedTransactionsPeriodDateFormat.format(it)) }
account.retrievedTransactionsUpTo?.let { messageArgs.add(RetrievedTransactionsPeriodDateFormat.format(it)) } account.retrievedTransactionsUpTo?.let { messageArgs.add(RetrievedTransactionsPeriodDateFormat.format(it)) }
R.string.fragment_home_transactions_retrieval_state_no_transactions_in_retrieved_period R.string.fragment_home_transactions_retrieval_state_no_transactions_in_retrieved_period
@ -292,12 +292,12 @@ class HomeFragment : Fragment() {
} }
private fun setFetchAllTransactionsView() { private fun setFetchAllTransactionsView() {
accountsForWhichNotAllTransactionsHaveBeenFetched = presenter.selectedBankAccountsForWhichNotAllTransactionsHaveBeenFetched accountsForWhichNotAllTransactionsHaveBeenFetched = presenter.selectedAccountsForWhichNotAllTransactionsHaveBeenFetched
var floatingActionMenuBottomMarginResourceId = R.dimen.fab_margin_bottom_without_toolbar var floatingActionMenuBottomMarginResourceId = R.dimen.fab_margin_bottom_without_toolbar
if (doNotShowFetchAllTransactionsOverlay || accountsForWhichNotAllTransactionsHaveBeenFetched.isEmpty() if (doNotShowFetchAllTransactionsOverlay || accountsForWhichNotAllTransactionsHaveBeenFetched.isEmpty()
|| presenter.selectedBankAccountsTransactionRetrievalState != TransactionsRetrievalState.RetrievedTransactions) { || presenter.selectedAccountsTransactionRetrievalState != TransactionsRetrievalState.RetrievedTransactions) {
lytFetchAllTransactionsOverlay.visibility = View.GONE lytFetchAllTransactionsOverlay.visibility = View.GONE
} }
else { else {
@ -312,9 +312,9 @@ class HomeFragment : Fragment() {
private fun fetchTransactions() { private fun fetchTransactions() {
presenter.selectedBankAccounts.forEach { account -> presenter.selectedAccounts.forEach { account ->
if (account.haveAllTransactionsBeenFetched) { if (account.haveAllTransactionsBeenFetched) {
presenter.updateBankAccountTransactionsAsync(account) presenter.updateAccountTransactionsAsync(account)
} }
else { else {
presenter.fetchAllAccountTransactionsAsync(account) presenter.fetchAllAccountTransactionsAsync(account)

View File

@ -20,7 +20,7 @@ import com.mikepenz.materialdrawer.widget.MaterialDrawerSliderView
import net.dankito.banking.ui.android.R import net.dankito.banking.ui.android.R
import net.dankito.banking.ui.android.dialogs.settings.BankSettingsDialog import net.dankito.banking.ui.android.dialogs.settings.BankSettingsDialog
import net.dankito.banking.ui.android.extensions.withIcon import net.dankito.banking.ui.android.extensions.withIcon
import net.dankito.banking.ui.model.TypedCustomer import net.dankito.banking.ui.model.TypedBankData
import net.dankito.banking.ui.presenter.BankingPresenter import net.dankito.banking.ui.presenter.BankingPresenter
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
@ -32,9 +32,9 @@ open class DrawerView(
) { ) {
companion object { companion object {
private const val AccountLevel = 2 private const val BankLevel = 2
private const val BankAccountLevel = 7 private const val AccountLevel = 7
private const val AccountsSectionHeaderId = 1000L private const val AccountsSectionHeaderId = 1000L
private const val AllAccountsId = 1001L private const val AllAccountsId = 1001L
@ -60,11 +60,11 @@ open class DrawerView(
setDefaultDrawerItems() setDefaultDrawerItems()
presenter.addAccountsChangedListener { presenter.addBanksChangedListener {
activity.runOnUiThread { updateDrawerItems() } activity.runOnUiThread { updateDrawerItems() }
} }
presenter.addSelectedBankAccountsChangedListener { presenter.addSelectedAccountsChangedListener {
activity.runOnUiThread { updateDrawerItems() } activity.runOnUiThread { updateDrawerItems() }
} }
@ -83,16 +83,16 @@ open class DrawerView(
PrimaryDrawerItem() PrimaryDrawerItem()
.withName(R.string.drawer_menu_all_bank_accounts_title) .withName(R.string.drawer_menu_all_bank_accounts_title)
.withIdentifier(AllAccountsId) .withIdentifier(AllAccountsId)
.withLevel(AccountLevel) .withLevel(BankLevel)
.withSelected(true) .withSelected(true)
.withIcon(activity, GoogleMaterial.Icon.gmd_account_balance, R.color.primaryTextColor_Dark) .withIcon(activity, GoogleMaterial.Icon.gmd_account_balance, R.color.primaryTextColor_Dark)
.withOnDrawerItemClickListener { _, _, _ -> itemClicked { presenter.selectedAllBankAccounts() } } .withOnDrawerItemClickListener { _, _, _ -> itemClicked { presenter.selectedAllAccounts() } }
, ,
PrimaryDrawerItem() PrimaryDrawerItem()
.withName(R.string.add_account) .withName(R.string.add_account)
.withIdentifier(AddAccountId) .withIdentifier(AddAccountId)
.withLevel(AccountLevel) .withLevel(BankLevel)
.withIcon(activity, GoogleMaterial.Icon.gmd_add, R.color.primaryTextColor_Dark) .withIcon(activity, GoogleMaterial.Icon.gmd_add, R.color.primaryTextColor_Dark)
.withSelectable(false) .withSelectable(false)
.withOnDrawerItemClickListener { _, _, _ -> itemClicked { presenter.showAddAccountDialog() } } .withOnDrawerItemClickListener { _, _, _ -> itemClicked { presenter.showAddAccountDialog() } }
@ -129,29 +129,29 @@ open class DrawerView(
} }
private fun createAccountsDrawerItems(): List<IDrawerItem<*>> { private fun createAccountsDrawerItems(): List<IDrawerItem<*>> {
return presenter.customers.map { account -> return presenter.allBanks.map { account ->
val accountItem = createAccountDrawerItem(account) val accountItem = createAccountDrawerItem(account)
val bankAccountsItems = createBankAccountsDrawerItems(account).toMutableList() val accountsItems = createBankAccountsDrawerItems(account).toMutableList()
bankAccountsItems.add(0, accountItem) accountsItems.add(0, accountItem)
return@map bankAccountsItems return@map accountsItems
}.flatten() }.flatten()
} }
private fun createAccountDrawerItem(customer: TypedCustomer): IDrawerItem<*> { private fun createAccountDrawerItem(bank: TypedBankData): IDrawerItem<*> {
val accountItem = AccountDrawerItem() val accountItem = AccountDrawerItem()
.withName(customer.displayName) .withName(bank.displayName)
.withLevel(AccountLevel) .withLevel(BankLevel)
.withSecondaryIcon(R.drawable.ic_baseline_settings_24) .withSecondaryIcon(R.drawable.ic_baseline_settings_24)
.withSecondaryIconColor(activity, R.color.primaryTextColor_Dark) .withSecondaryIconColor(activity, R.color.primaryTextColor_Dark)
.withOnSecondaryIconClickedListener { closeDrawerAndEditAccount(customer) } .withOnSecondaryIconClickedListener { closeDrawerAndEditAccount(bank) }
.withIcon(customer.iconUrl ?: "") .withIcon(bank.iconUrl ?: "")
.withSelected(presenter.isSingleSelectedAccount(customer)) .withSelected(presenter.isSingleSelectedBank(bank))
.withOnDrawerItemClickListener { _, _, _ -> itemClicked { presenter.selectedAccount(customer) } } .withOnDrawerItemClickListener { _, _, _ -> itemClicked { presenter.selectedBank(bank) } }
if (customer.iconUrl == null) { if (bank.iconUrl == null) {
accountItem.withIcon(activity, FontAwesome.Icon.faw_piggy_bank, R.color.primaryTextColor_Dark) accountItem.withIcon(activity, FontAwesome.Icon.faw_piggy_bank, R.color.primaryTextColor_Dark)
} }
@ -159,13 +159,13 @@ open class DrawerView(
return accountItem return accountItem
} }
private fun createBankAccountsDrawerItems(customer: TypedCustomer): List<IDrawerItem<*>> { private fun createBankAccountsDrawerItems(bank: TypedBankData): List<IDrawerItem<*>> {
return customer.accounts.map { bankAccount -> return bank.accounts.map { account ->
SecondaryDrawerItem() SecondaryDrawerItem()
.withName(bankAccount.displayName) .withName(account.displayName)
.withLevel(BankAccountLevel) .withLevel(AccountLevel)
.withSelected(presenter.isSingleSelectedBankAccount(bankAccount)) .withSelected(presenter.isSingleSelectedAccount(account))
.withOnDrawerItemClickListener { _, _, _ -> itemClicked { presenter.selectedBankAccount(bankAccount) } } .withOnDrawerItemClickListener { _, _, _ -> itemClicked { presenter.selectedAccount(account) } }
} }
} }
@ -175,14 +175,14 @@ open class DrawerView(
return false return false
} }
private fun closeDrawerAndEditAccount(customer: TypedCustomer) { private fun closeDrawerAndEditAccount(bank: TypedBankData) {
closeDrawer() closeDrawer()
editAccount(customer) editAccount(bank)
} }
private fun editAccount(customer: TypedCustomer) { private fun editAccount(bank: TypedBankData) {
BankSettingsDialog().show(customer, activity, true) BankSettingsDialog().show(bank, activity, true)
} }
private fun showAppVersion(navigationHeaderView: View?) { private fun showAppVersion(navigationHeaderView: View?) {

View File

@ -35,7 +35,7 @@ open class MainActivityFloatingActionMenuButton(
init { init {
setupButtons(floatingActionMenu) setupButtons(floatingActionMenu)
presenter.addAccountsChangedListener { presenter.addBanksChangedListener {
fabTransferMoney.context.asActivity()?.runOnUiThread { fabTransferMoney.context.asActivity()?.runOnUiThread {
checkIfThereAreAccountsThatCanTransferMoney() checkIfThereAreAccountsThatCanTransferMoney()
} }
@ -65,9 +65,9 @@ open class MainActivityFloatingActionMenuButton(
protected open fun checkIfThereAreAccountsThatCanTransferMoney() { protected open fun checkIfThereAreAccountsThatCanTransferMoney() {
fabTransferMoney.isEnabled = presenter.hasBankAccountsSupportTransferringMoney fabTransferMoney.isEnabled = presenter.hasAccountsSupportTransferringMoney
fabTransferMoneyFromPdf.isEnabled = presenter.hasBankAccountsSupportTransferringMoney fabTransferMoneyFromPdf.isEnabled = presenter.hasAccountsSupportTransferringMoney
} }

View File

@ -23,7 +23,7 @@ open class MainMenuBar(protected val presenter: BankingPresenter) : View() {
init { init {
presenter.addAccountsChangedListener { presenter.addBanksChangedListener {
runLater { runLater {
checkIfThereAreAccountsThatCanTransferMoney() checkIfThereAreAccountsThatCanTransferMoney()
} }
@ -68,7 +68,7 @@ open class MainMenuBar(protected val presenter: BankingPresenter) : View() {
protected open fun checkIfThereAreAccountsThatCanTransferMoney() { protected open fun checkIfThereAreAccountsThatCanTransferMoney() {
areAccountsThatCanTransferMoneyAdded.value = presenter.hasBankAccountsSupportTransferringMoney areAccountsThatCanTransferMoneyAdded.value = presenter.hasAccountsSupportTransferringMoney
} }
protected open fun transferMoneyWithDataFromPdf() { protected open fun transferMoneyWithDataFromPdf() {

View File

@ -4,7 +4,7 @@ import net.dankito.banking.ui.IRouter
import net.dankito.banking.ui.javafx.dialogs.AddAccountDialog import net.dankito.banking.ui.javafx.dialogs.AddAccountDialog
import net.dankito.banking.ui.javafx.dialogs.cashtransfer.TransferMoneyDialog import net.dankito.banking.ui.javafx.dialogs.cashtransfer.TransferMoneyDialog
import net.dankito.banking.ui.javafx.dialogs.tan.EnterTanDialog import net.dankito.banking.ui.javafx.dialogs.tan.EnterTanDialog
import net.dankito.banking.ui.model.TypedCustomer import net.dankito.banking.ui.model.TypedBankData
import net.dankito.banking.ui.model.parameters.TransferMoneyData import net.dankito.banking.ui.model.parameters.TransferMoneyData
import net.dankito.banking.ui.model.tan.EnterTanGeneratorAtcResult import net.dankito.banking.ui.model.tan.EnterTanGeneratorAtcResult
import net.dankito.banking.ui.model.tan.EnterTanResult import net.dankito.banking.ui.model.tan.EnterTanResult
@ -22,9 +22,9 @@ open class RouterJavaFx : IRouter {
AddAccountDialog(presenter).show(messages["add.account.dialog.title"]) AddAccountDialog(presenter).show(messages["add.account.dialog.title"])
} }
override fun getTanFromUserFromNonUiThread(customer: TypedCustomer, tanChallenge: TanChallenge, presenter: BankingPresenter, callback: (EnterTanResult) -> Unit) { override fun getTanFromUserFromNonUiThread(bank: TypedBankData, tanChallenge: TanChallenge, presenter: BankingPresenter, callback: (EnterTanResult) -> Unit) {
FX.runAndWait { FX.runAndWait {
EnterTanDialog(customer, tanChallenge, presenter) { result -> EnterTanDialog(bank, tanChallenge, presenter) { result ->
callback(result) callback(result)
}.show(messages["enter.tan.dialog.title"]) }.show(messages["enter.tan.dialog.title"])
} }

View File

@ -109,8 +109,8 @@ open class AccountTransactionsControlView(
protected open fun initLogic() { protected open fun initLogic() {
presenter.addAccountsChangedListener { runLater { accountsChanged() } } presenter.addBanksChangedListener { runLater { accountsChanged() } }
presenter.addSelectedBankAccountsChangedListener { selectedBankAccountsChanged() } presenter.addSelectedAccountsChangedListener { selectedBankAccountsChanged() }
checkIfSupportsTransferringMoneyOnUiThread() checkIfSupportsTransferringMoneyOnUiThread()
checkIfSupportsRetrievingAccountTransactionsOnUiThread() checkIfSupportsRetrievingAccountTransactionsOnUiThread()
@ -130,13 +130,13 @@ open class AccountTransactionsControlView(
protected open fun checkIfSupportsTransferringMoneyOnUiThread() { protected open fun checkIfSupportsTransferringMoneyOnUiThread() {
supportsTransferringMoney.value = presenter.hasBankAccountsSupportTransferringMoney supportsTransferringMoney.value = presenter.hasAccountsSupportTransferringMoney
} }
protected open fun checkIfSupportsRetrievingAccountTransactionsOnUiThread() { protected open fun checkIfSupportsRetrievingAccountTransactionsOnUiThread() {
supportsRetrievingBalance.value = presenter.doSelectedBankAccountsSupportRetrievingBalance supportsRetrievingBalance.value = presenter.doSelectedAccountsSupportRetrievingBalance
supportsRetrievingAccountTransactions.value = presenter.doSelectedBankAccountsSupportRetrievingAccountTransactions supportsRetrievingAccountTransactions.value = presenter.doSelectedAccountsSupportRetrievingTransactions
} }
protected open fun updateAccountTransactions(processingIndicatorButton: ProcessingIndicatorButton) { protected open fun updateAccountTransactions(processingIndicatorButton: ProcessingIndicatorButton) {

View File

@ -31,7 +31,7 @@ open class AccountTransactionsView(private val presenter: BankingPresenter) : Vi
init { init {
presenter.addSelectedBankAccountsChangedListener { handleSelectedBankAccountsChanged(it) } presenter.addSelectedAccountsChangedListener { handleSelectedBankAccountsChanged(it) }
presenter.addRetrievedAccountTransactionsResponseListener { response -> presenter.addRetrievedAccountTransactionsResponseListener { response ->
handleGetTransactionsResponseOffUiThread(response) handleGetTransactionsResponseOffUiThread(response)
@ -39,7 +39,7 @@ open class AccountTransactionsView(private val presenter: BankingPresenter) : Vi
transactionsFilter.addListener { _, _, newValue -> updateTransactionsToDisplay(newValue) } transactionsFilter.addListener { _, _, newValue -> updateTransactionsToDisplay(newValue) }
handleSelectedBankAccountsChanged(presenter.selectedBankAccounts) // so that isAccountSelected and transactionsToDisplay get set handleSelectedBankAccountsChanged(presenter.selectedAccounts) // so that isAccountSelected and transactionsToDisplay get set
} }
@ -135,7 +135,7 @@ open class AccountTransactionsView(private val presenter: BankingPresenter) : Vi
transactionsToDisplay.setAll(presenter.searchSelectedAccountTransactions(filter)) transactionsToDisplay.setAll(presenter.searchSelectedAccountTransactions(filter))
// TODO: if transactions are filtered calculate and show balance of displayed transactions? // TODO: if transactions are filtered calculate and show balance of displayed transactions?
balance.value = presenter.formatAmount(presenter.balanceOfSelectedBankAccounts) balance.value = presenter.formatAmount(presenter.balanceOfSelectedAccounts)
} }
protected open fun handleGetTransactionsResponseOffUiThread(response: GetTransactionsResponse) { protected open fun handleGetTransactionsResponseOffUiThread(response: GetTransactionsResponse) {

View File

@ -10,14 +10,14 @@ import javafx.scene.input.KeyCode
import net.dankito.banking.ui.javafx.dialogs.JavaFxDialogService import net.dankito.banking.ui.javafx.dialogs.JavaFxDialogService
import net.dankito.banking.ui.javafx.model.AccountsAccountTreeItem import net.dankito.banking.ui.javafx.model.AccountsAccountTreeItem
import net.dankito.banking.ui.javafx.model.AccountsRootTreeItem import net.dankito.banking.ui.javafx.model.AccountsRootTreeItem
import net.dankito.banking.ui.model.TypedCustomer import net.dankito.banking.ui.model.TypedBankData
import net.dankito.banking.ui.presenter.BankingPresenter import net.dankito.banking.ui.presenter.BankingPresenter
import tornadofx.* import tornadofx.*
import tornadofx.FX.Companion.messages import tornadofx.FX.Companion.messages
open class AccountsTreeView(customers: ObservableList<TypedCustomer>, protected val presenter: BankingPresenter) open class AccountsTreeView(banks: ObservableList<TypedBankData>, protected val presenter: BankingPresenter)
: TreeView<String>(AccountsRootTreeItem(customers)) { : TreeView<String>(AccountsRootTreeItem(banks)) {
protected var currentMenu: ContextMenu? = null protected var currentMenu: ContextMenu? = null
@ -64,7 +64,7 @@ open class AccountsTreeView(customers: ObservableList<TypedCustomer>, protected
} }
protected open fun askIfAccountShouldBeDeleted(treeItem: AccountsAccountTreeItem) { protected open fun askIfAccountShouldBeDeleted(treeItem: AccountsAccountTreeItem) {
val account = treeItem.customer val account = treeItem.bank
val selectedButton = JavaFxDialogService().showDialog( val selectedButton = JavaFxDialogService().showDialog(
Alert.AlertType.WARNING, Alert.AlertType.WARNING,

View File

@ -15,11 +15,11 @@ import tornadofx.*
open class AccountsView(protected val presenter: BankingPresenter) : View() { open class AccountsView(protected val presenter: BankingPresenter) : View() {
protected val accounts = FXCollections.observableArrayList(presenter.customers) protected val accounts = FXCollections.observableArrayList(presenter.allBanks)
init { init {
presenter.addAccountsChangedListener { presenter.addBanksChangedListener {
runLater { runLater {
accounts.setAll(it) accounts.setAll(it)
} }
@ -69,9 +69,9 @@ open class AccountsView(protected val presenter: BankingPresenter) : View() {
protected open fun selectedBankAccountChanged(accountTreeItem: TreeItem<String>?) { protected open fun selectedBankAccountChanged(accountTreeItem: TreeItem<String>?) {
accountTreeItem?.let { accountTreeItem?.let {
when (accountTreeItem) { when (accountTreeItem) {
is AccountsBankAccountTreeItem -> presenter.selectedBankAccount(accountTreeItem.bankAccount) is AccountsBankAccountTreeItem -> presenter.selectedAccount(accountTreeItem.account)
is AccountsAccountTreeItem -> presenter.selectedAccount(accountTreeItem.customer) is AccountsAccountTreeItem -> presenter.selectedBank(accountTreeItem.bank)
else -> presenter.selectedAllBankAccounts() else -> presenter.selectedAllAccounts()
} }
} }
} }

View File

@ -256,7 +256,7 @@ open class AddAccountDialog(protected val presenter: BankingPresenter) : Window(
handleSuccessfullyAddedAccountResultOnUiThread(response) handleSuccessfullyAddedAccountResultOnUiThread(response)
} }
else { else {
val account = response.customer val account = response.bank
checkEnteredCredentialsResult.value = String.format(messages["add.account.dialog.error.could.not.add.account"], checkEnteredCredentialsResult.value = String.format(messages["add.account.dialog.error.could.not.add.account"],
account.bankCode, account.customerId, response.errorToShowToUser) account.bankCode, account.customerId, response.errorToShowToUser)
@ -273,7 +273,7 @@ open class AddAccountDialog(protected val presenter: BankingPresenter) : Window(
val userSelection = dialogService.showDialog(Alert.AlertType.CONFIRMATION, message, null, currentStage, ButtonType.YES, ButtonType.NO) val userSelection = dialogService.showDialog(Alert.AlertType.CONFIRMATION, message, null, currentStage, ButtonType.YES, ButtonType.NO)
when (userSelection) { when (userSelection) {
ButtonType.YES -> presenter.fetchAllAccountTransactionsAsync(response.customer) { } ButtonType.YES -> presenter.fetchAllAccountTransactionsAsync(response.bank) { }
else -> { } // nothing to do then, simply close dialog else -> { } // nothing to do then, simply close dialog
} }

View File

@ -47,7 +47,7 @@ open class TransferMoneyDialog @JvmOverloads constructor(
} }
protected val bankAccountsSupportingTransferringMoney = FXCollections.observableArrayList(presenter.bankAccounts.filter { it.supportsTransferringMoney }) protected val bankAccountsSupportingTransferringMoney = FXCollections.observableArrayList(presenter.allAccounts.filter { it.supportsTransferringMoney })
protected val selectedBankAccount = SimpleObjectProperty<TypedBankAccount>(preselectedValues?.account ?: bankAccountsSupportingTransferringMoney.firstOrNull()) protected val selectedBankAccount = SimpleObjectProperty<TypedBankAccount>(preselectedValues?.account ?: bankAccountsSupportingTransferringMoney.firstOrNull())
@ -114,7 +114,7 @@ open class TransferMoneyDialog @JvmOverloads constructor(
cellFormat { cellFormat {
text = it.displayName text = it.displayName
it.customer.iconUrl?.let { iconUrl -> it.bank.iconUrl?.let { iconUrl ->
graphic = ImageView(iconUrl)?.apply { graphic = ImageView(iconUrl)?.apply {
this.fitHeight = BankIconSize this.fitHeight = BankIconSize
this.fitWidth = BankIconSize this.fitWidth = BankIconSize
@ -346,10 +346,10 @@ open class TransferMoneyDialog @JvmOverloads constructor(
protected open fun transferMoney() { protected open fun transferMoney() {
remitteeBank.value?.let { remitteeBank.value?.let {
val bankAccount = selectedBankAccount.value val account = selectedBankAccount.value
val data = TransferMoneyData( val data = TransferMoneyData(
bankAccount, account,
inputValidator.convertToAllowedSepaCharacters(remitteeName.value), inputValidator.convertToAllowedSepaCharacters(remitteeName.value),
remitteeIban.value.replace(" ", ""), remitteeIban.value.replace(" ", ""),
remitteeBic.value.replace(" ", ""), remitteeBic.value.replace(" ", ""),
@ -360,14 +360,14 @@ open class TransferMoneyDialog @JvmOverloads constructor(
presenter.transferMoneyAsync(data) { presenter.transferMoneyAsync(data) {
runLater { runLater {
handleTransferMoneyResultOnUiThread(bankAccount, data, it) handleTransferMoneyResultOnUiThread(account, data, it)
} }
} }
} }
} }
protected open fun handleTransferMoneyResultOnUiThread(bankAccount: TypedBankAccount, transferData: TransferMoneyData, response: BankingClientResponse) { protected open fun handleTransferMoneyResultOnUiThread(account: TypedBankAccount, transferData: TransferMoneyData, response: BankingClientResponse) {
val currency = bankAccount.currency val currency = account.currency
if (response.successful) { if (response.successful) {
dialogService.showInfoMessage(String.format(messages["transfer.money.dialog.message.transfer.cash.success"], dialogService.showInfoMessage(String.format(messages["transfer.money.dialog.message.transfer.cash.success"],

View File

@ -10,7 +10,7 @@ import javafx.scene.text.FontWeight
import net.dankito.banking.ui.javafx.dialogs.tan.controls.ChipTanFlickerCodeView import net.dankito.banking.ui.javafx.dialogs.tan.controls.ChipTanFlickerCodeView
import net.dankito.banking.ui.javafx.dialogs.JavaFxDialogService import net.dankito.banking.ui.javafx.dialogs.JavaFxDialogService
import net.dankito.banking.ui.javafx.dialogs.tan.controls.TanImageView import net.dankito.banking.ui.javafx.dialogs.tan.controls.TanImageView
import net.dankito.banking.ui.model.TypedCustomer import net.dankito.banking.ui.model.TypedBankData
import net.dankito.banking.ui.model.responses.BankingClientResponse import net.dankito.banking.ui.model.responses.BankingClientResponse
import net.dankito.banking.ui.model.tan.* import net.dankito.banking.ui.model.tan.*
import net.dankito.banking.ui.presenter.BankingPresenter import net.dankito.banking.ui.presenter.BankingPresenter
@ -20,7 +20,7 @@ import tornadofx.*
open class EnterTanDialog( open class EnterTanDialog(
protected val customer: TypedCustomer, protected val bank: TypedBankData,
protected val challenge: TanChallenge, protected val challenge: TanChallenge,
protected val presenter: BankingPresenter, protected val presenter: BankingPresenter,
protected val tanEnteredCallback: (EnterTanResult) -> Unit protected val tanEnteredCallback: (EnterTanResult) -> Unit
@ -41,11 +41,11 @@ open class EnterTanDialog(
protected var tanImageView: TanImageView? = null protected var tanImageView: TanImageView? = null
protected val tanMethodsWithoutUnsupported = customer.supportedTanMethods.filterNot { it.type == TanMethodType.ChipTanUsb } // USB tan generators are not supported protected val tanMethodsWithoutUnsupported = bank.supportedTanMethods.filterNot { it.type == TanMethodType.ChipTanUsb } // USB tan generators are not supported
protected val selectedTanMethod = SimpleObjectProperty<TanMethod>(customer.selectedTanMethod ?: tanMethodsWithoutUnsupported.firstOrNull { it.displayName.contains("manuell", true) == false } ?: tanMethodsWithoutUnsupported.firstOrNull()) protected val selectedTanMethod = SimpleObjectProperty<TanMethod>(bank.selectedTanMethod ?: tanMethodsWithoutUnsupported.firstOrNull { it.displayName.contains("manuell", true) == false } ?: tanMethodsWithoutUnsupported.firstOrNull())
protected val selectedTanMedium = SimpleObjectProperty<TanMedium>(customer.tanMediaSorted.firstOrNull()) protected val selectedTanMedium = SimpleObjectProperty<TanMedium>(bank.tanMediaSorted.firstOrNull())
protected val enteredTan = SimpleStringProperty("") protected val enteredTan = SimpleStringProperty("")
@ -84,13 +84,13 @@ open class EnterTanDialog(
} }
} }
if (customer.tanMediaSorted.isNotEmpty()) { if (bank.tanMediaSorted.isNotEmpty()) {
field(messages["enter.tan.dialog.select.tan.medium"]) { field(messages["enter.tan.dialog.select.tan.medium"]) {
label.apply { label.apply {
font = Font.font(font.family, FontWeight.BLACK, font.size) font = Font.font(font.family, FontWeight.BLACK, font.size)
} }
combobox(selectedTanMedium, customer.tanMediaSorted) { combobox(selectedTanMedium, bank.tanMediaSorted) {
cellFormat { cellFormat {
text = it.displayName text = it.displayName
} }

View File

@ -2,10 +2,10 @@ package net.dankito.banking.ui.javafx.model
import javafx.scene.Node import javafx.scene.Node
import javafx.scene.image.ImageView import javafx.scene.image.ImageView
import net.dankito.banking.ui.model.TypedCustomer import net.dankito.banking.ui.model.TypedBankData
open class AccountsAccountTreeItem(val customer: TypedCustomer) : AccountsTreeItemBase(customer.displayName) { open class AccountsAccountTreeItem(val bank: TypedBankData) : AccountsTreeItemBase(bank.displayName) {
companion object { companion object {
private const val IconSize = 16.0 private const val IconSize = 16.0
@ -17,13 +17,13 @@ open class AccountsAccountTreeItem(val customer: TypedCustomer) : AccountsTreeIt
graphic = createIconImageView() graphic = createIconImageView()
customer.accounts.forEach { bankAccount -> bank.accounts.forEach { account ->
children.add(AccountsBankAccountTreeItem(bankAccount)) children.add(AccountsBankAccountTreeItem(account))
} }
} }
protected open fun createIconImageView(): Node? { protected open fun createIconImageView(): Node? {
customer.iconUrl?.let { bank.iconUrl?.let {
val iconImageView = ImageView(it) val iconImageView = ImageView(it)
iconImageView.fitHeight = IconSize iconImageView.fitHeight = IconSize

View File

@ -3,4 +3,4 @@ package net.dankito.banking.ui.javafx.model
import net.dankito.banking.ui.model.TypedBankAccount import net.dankito.banking.ui.model.TypedBankAccount
open class AccountsBankAccountTreeItem(val bankAccount: TypedBankAccount) : AccountsTreeItemBase(bankAccount.displayName) open class AccountsBankAccountTreeItem(val account: TypedBankAccount) : AccountsTreeItemBase(account.displayName)

View File

@ -2,26 +2,26 @@ package net.dankito.banking.ui.javafx.model
import javafx.collections.ListChangeListener import javafx.collections.ListChangeListener
import javafx.collections.ObservableList import javafx.collections.ObservableList
import net.dankito.banking.ui.model.TypedCustomer import net.dankito.banking.ui.model.TypedBankData
import tornadofx.FX.Companion.messages import tornadofx.FX.Companion.messages
import tornadofx.get import tornadofx.get
import tornadofx.runLater import tornadofx.runLater
open class AccountsRootTreeItem(customers: ObservableList<TypedCustomer>) : AccountsTreeItemBase(messages["accounts.view.all.accounts"]) { open class AccountsRootTreeItem(banks: ObservableList<TypedBankData>) : AccountsTreeItemBase(messages["accounts.view.all.accounts"]) {
init { init {
setAccounts(customers) setBanks(banks)
customers.addListener(ListChangeListener { banks.addListener(ListChangeListener {
runLater { setAccounts(customers) } runLater { setBanks(banks) }
}) })
} }
protected open fun setAccounts(customers: List<TypedCustomer>) { protected open fun setBanks(banks: List<TypedBankData>) {
isExpanded = customers.isNotEmpty() isExpanded = banks.isNotEmpty()
children.setAll(customers.map { AccountsAccountTreeItem(it) }) children.setAll(banks.map { AccountsAccountTreeItem(it) })
} }
} }

View File

@ -6,14 +6,14 @@ import net.dankito.utils.multiplatform.File
interface IBankingPersistence { interface IBankingPersistence {
fun saveOrUpdateAccount(customer: TypedCustomer, allCustomers: List<TypedCustomer>) fun saveOrUpdateBank(bank: TypedBankData, allBanks: List<TypedBankData>)
fun deleteAccount(customer: TypedCustomer, allCustomers: List<TypedCustomer>) fun deleteBank(bank: TypedBankData, allBanks: List<TypedBankData>)
fun readPersistedAccounts(): List<TypedCustomer> fun readPersistedBanks(): List<TypedBankData>
fun saveOrUpdateAccountTransactions(bankAccount: TypedBankAccount, transactions: List<IAccountTransaction>) fun saveOrUpdateAccountTransactions(account: TypedBankAccount, transactions: List<IAccountTransaction>)
fun saveUrlToFile(url: String, file: File) fun saveUrlToFile(url: String, file: File)

View File

@ -6,20 +6,20 @@ import net.dankito.utils.multiplatform.File
open class NoOpBankingPersistence : IBankingPersistence { open class NoOpBankingPersistence : IBankingPersistence {
override fun saveOrUpdateAccount(customer: TypedCustomer, allCustomers: List<TypedCustomer>) { override fun saveOrUpdateBank(bank: TypedBankData, allBanks: List<TypedBankData>) {
} }
override fun deleteAccount(customer: TypedCustomer, allCustomers: List<TypedCustomer>) { override fun deleteBank(bank: TypedBankData, allBanks: List<TypedBankData>) {
} }
override fun readPersistedAccounts(): List<TypedCustomer> { override fun readPersistedBanks(): List<TypedBankData> {
return listOf() return listOf()
} }
override fun saveOrUpdateAccountTransactions(bankAccount: TypedBankAccount, transactions: List<IAccountTransaction>) { override fun saveOrUpdateAccountTransactions(account: TypedBankAccount, transactions: List<IAccountTransaction>) {
} }

View File

@ -1,6 +1,6 @@
package net.dankito.banking.ui package net.dankito.banking.ui
import net.dankito.banking.ui.model.TypedCustomer import net.dankito.banking.ui.model.TypedBankData
import net.dankito.banking.ui.model.tan.EnterTanGeneratorAtcResult import net.dankito.banking.ui.model.tan.EnterTanGeneratorAtcResult
import net.dankito.banking.ui.model.tan.EnterTanResult import net.dankito.banking.ui.model.tan.EnterTanResult
import net.dankito.banking.ui.model.tan.TanChallenge import net.dankito.banking.ui.model.tan.TanChallenge
@ -9,7 +9,7 @@ import net.dankito.banking.ui.model.tan.TanGeneratorTanMedium
interface BankingClientCallback { interface BankingClientCallback {
fun enterTan(customer: TypedCustomer, tanChallenge: TanChallenge, callback: (EnterTanResult) -> Unit) fun enterTan(bank: TypedBankData, tanChallenge: TanChallenge, callback: (EnterTanResult) -> Unit)
/** /**
* This method gets called for chipTan TAN generators when the bank asks the customer to synchronize her/his TAN generator. * This method gets called for chipTan TAN generators when the bank asks the customer to synchronize her/his TAN generator.

View File

@ -23,8 +23,8 @@ interface IBankingClient {
fun transferMoneyAsync(data: TransferMoneyData, callback: (BankingClientResponse) -> Unit) fun transferMoneyAsync(data: TransferMoneyData, callback: (BankingClientResponse) -> Unit)
fun dataChanged(customer: TypedCustomer) fun dataChanged(bank: TypedBankData)
fun deletedAccount(customer: TypedCustomer, wasLastAccountWithThisCredentials: Boolean) fun deletedBank(bank: TypedBankData, wasLastAccountWithThisCredentials: Boolean)
} }

View File

@ -1,14 +1,14 @@
package net.dankito.banking.ui package net.dankito.banking.ui
import net.dankito.utils.multiplatform.File import net.dankito.utils.multiplatform.File
import net.dankito.banking.ui.model.TypedCustomer import net.dankito.banking.ui.model.TypedBankData
import net.dankito.banking.util.IAsyncRunner import net.dankito.banking.util.IAsyncRunner
interface IBankingClientCreator { interface IBankingClientCreator {
fun createClient( fun createClient(
customer: TypedCustomer, bank: TypedBankData,
dataFolder: File, dataFolder: File,
asyncRunner: IAsyncRunner, asyncRunner: IAsyncRunner,
callback: BankingClientCallback callback: BankingClientCallback

View File

@ -1,6 +1,6 @@
package net.dankito.banking.ui package net.dankito.banking.ui
import net.dankito.banking.ui.model.TypedCustomer import net.dankito.banking.ui.model.TypedBankData
import net.dankito.banking.ui.model.parameters.TransferMoneyData import net.dankito.banking.ui.model.parameters.TransferMoneyData
import net.dankito.banking.ui.model.tan.EnterTanGeneratorAtcResult import net.dankito.banking.ui.model.tan.EnterTanGeneratorAtcResult
import net.dankito.banking.ui.model.tan.EnterTanResult import net.dankito.banking.ui.model.tan.EnterTanResult
@ -13,7 +13,7 @@ interface IRouter {
fun showAddAccountDialog(presenter: BankingPresenter) fun showAddAccountDialog(presenter: BankingPresenter)
fun getTanFromUserFromNonUiThread(customer: TypedCustomer, tanChallenge: TanChallenge, presenter: BankingPresenter, callback: (EnterTanResult) -> Unit) fun getTanFromUserFromNonUiThread(bank: TypedBankData, tanChallenge: TanChallenge, presenter: BankingPresenter, callback: (EnterTanResult) -> Unit)
fun getAtcFromUserFromNonUiThread(tanMedium: TanGeneratorTanMedium, callback: (EnterTanGeneratorAtcResult) -> Unit) fun getAtcFromUserFromNonUiThread(tanMedium: TanGeneratorTanMedium, callback: (EnterTanGeneratorAtcResult) -> Unit)

View File

@ -2,12 +2,10 @@ package net.dankito.banking.ui.model
import net.dankito.utils.multiplatform.BigDecimal import net.dankito.utils.multiplatform.BigDecimal
import net.dankito.utils.multiplatform.Date import net.dankito.utils.multiplatform.Date
import net.dankito.utils.multiplatform.DateFormatStyle
import net.dankito.utils.multiplatform.DateFormatter
open class AccountTransaction( open class AccountTransaction(
override val bankAccount: TypedBankAccount, override val account: TypedBankAccount,
override val amount: BigDecimal, override val amount: BigDecimal,
override val currency: String, override val currency: String,
override val unparsedUsage: String, override val unparsedUsage: String,
@ -51,15 +49,15 @@ open class AccountTransaction(
/* convenience constructors for languages not supporting default values */ /* convenience constructors for languages not supporting default values */
constructor(bankAccount: BankAccount, otherPartyName: String?, unparsedUsage: String, amount: BigDecimal, valueDate: Date, bookingText: String?) constructor(account: BankAccount, otherPartyName: String?, unparsedUsage: String, amount: BigDecimal, valueDate: Date, bookingText: String?)
: this(bankAccount, amount, "EUR", unparsedUsage, valueDate, : this(account, amount, "EUR", unparsedUsage, valueDate,
otherPartyName, null, null, bookingText, valueDate) otherPartyName, null, null, bookingText, valueDate)
constructor(bankAccount: BankAccount, amount: BigDecimal, currency: String, unparsedUsage: String, bookingDate: Date, constructor(account: BankAccount, amount: BigDecimal, currency: String, unparsedUsage: String, bookingDate: Date,
otherPartyName: String?, otherPartyBankCode: String?, otherPartyAccountId: String?, otherPartyName: String?, otherPartyBankCode: String?, otherPartyAccountId: String?,
bookingText: String?, valueDate: Date) bookingText: String?, valueDate: Date)
: this(bankAccount, amount, currency, unparsedUsage, bookingDate, : this(account, amount, currency, unparsedUsage, bookingDate,
otherPartyName, otherPartyBankCode, otherPartyAccountId, bookingText, valueDate, otherPartyName, otherPartyBankCode, otherPartyAccountId, bookingText, valueDate,
0, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, "", "", null, null, "", null) 0, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, "", "", null, null, "", null)

View File

@ -7,7 +7,7 @@ import kotlin.jvm.JvmOverloads
open class BankAccount @JvmOverloads constructor( open class BankAccount @JvmOverloads constructor(
override val customer: TypedCustomer, override val bank: TypedBankData,
override var identifier: String, override var identifier: String,
override var accountHolderName: String, override var accountHolderName: String,
override var iban: String?, override var iban: String?,
@ -28,14 +28,14 @@ open class BankAccount @JvmOverloads constructor(
override var unbookedTransactions: List<Any> = listOf() override var unbookedTransactions: List<Any> = listOf()
) : TypedBankAccount { ) : TypedBankAccount {
internal constructor() : this(Customer(), null, "") // for object deserializers internal constructor() : this(BankData(), null, "") // for object deserializers
/* convenience constructors for languages not supporting default values */ /* convenience constructors for languages not supporting default values */
constructor(customer: TypedCustomer, productName: String?, identifier: String) : this(customer, productName, identifier, BankAccountType.Girokonto) constructor(bank: TypedBankData, productName: String?, identifier: String) : this(bank, productName, identifier, BankAccountType.Girokonto)
constructor(customer: TypedCustomer, productName: String?, identifier: String, type: BankAccountType = BankAccountType.Girokonto, balance: BigDecimal = BigDecimal.Zero) constructor(bank: TypedBankData, productName: String?, identifier: String, type: BankAccountType = BankAccountType.Girokonto, balance: BigDecimal = BigDecimal.Zero)
: this(customer, identifier, "", null, null, "", balance, "EUR", type, productName) : this(bank, identifier, "", null, null, "", balance, "EUR", type, productName)
override var technicalId: String = UUID.random() override var technicalId: String = UUID.random()

View File

@ -5,7 +5,7 @@ import net.dankito.banking.ui.model.tan.TanMethod
import net.dankito.utils.multiplatform.UUID import net.dankito.utils.multiplatform.UUID
open class Customer( open class BankData(
override var bankCode: String, override var bankCode: String,
override var customerId: String, override var customerId: String,
override var password: String, override var password: String,
@ -16,7 +16,7 @@ open class Customer(
override var userId: String = customerId, override var userId: String = customerId,
override var iconUrl: String? = null, override var iconUrl: String? = null,
override var accounts: List<TypedBankAccount> = listOf() override var accounts: List<TypedBankAccount> = listOf()
) : TypedCustomer { ) : TypedBankData {
internal constructor() : this("", "", "", "", "", "", "") // for object deserializers internal constructor() : this("", "", "", "", "", "", "") // for object deserializers

View File

@ -13,7 +13,7 @@ interface IAccountTransaction {
} }
val bankAccount: IBankAccount<*> val account: IBankAccount<*>
val amount: BigDecimal val amount: BigDecimal
val currency: String val currency: String
val unparsedUsage: String val unparsedUsage: String
@ -62,15 +62,15 @@ interface IAccountTransaction {
get() = otherPartyName.isNullOrBlank() == false /* && type != "ENTGELTABSCHLUSS" && type != "AUSZAHLUNG" */ // TODO get() = otherPartyName.isNullOrBlank() == false /* && type != "ENTGELTABSCHLUSS" && type != "AUSZAHLUNG" */ // TODO
val canCreateMoneyTransferFrom: Boolean val canCreateMoneyTransferFrom: Boolean
get() = otherPartyAccountId != null && bankAccount.supportsTransferringMoney get() = otherPartyAccountId != null && account.supportsTransferringMoney
val usage: String val usage: String
get() = sepaUsage ?: unparsedUsage get() = sepaUsage ?: unparsedUsage
fun buildTransactionIdentifier() : String { fun buildTransactionIdentifier() : String {
if (bankAccount != null) { if (account != null) {
return "${bankAccount.technicalId} ${IdDateFormat.format(bookingDate)} ${IdDateFormat.format(valueDate)} $amount $currency $unparsedUsage $otherPartyName $otherPartyBankCode $otherPartyAccountId" return "${account.technicalId} ${IdDateFormat.format(bookingDate)} ${IdDateFormat.format(valueDate)} $amount $currency $unparsedUsage $otherPartyName $otherPartyBankCode $otherPartyAccountId"
} }
else { // happens for derived classes during initialization. These have to set technicalId after initialization by themselves else { // happens for derived classes during initialization. These have to set technicalId after initialization by themselves
return "<uninitialized_bank_acccount> ${IdDateFormat.format(bookingDate)} ${IdDateFormat.format(valueDate)} $amount $currency $unparsedUsage $otherPartyName $otherPartyBankCode $otherPartyAccountId" return "<uninitialized_bank_acccount> ${IdDateFormat.format(bookingDate)} ${IdDateFormat.format(valueDate)} $amount $currency $unparsedUsage $otherPartyName $otherPartyBankCode $otherPartyAccountId"
@ -83,7 +83,7 @@ interface IAccountTransaction {
if (this === other) return true if (this === other) return true
if (other !is IAccountTransaction) return false if (other !is IAccountTransaction) return false
if (bankAccount != other.bankAccount) return false if (account != other.account) return false
if (amount != other.amount) return false if (amount != other.amount) return false
if (currency != other.currency) return false if (currency != other.currency) return false
if (unparsedUsage != other.unparsedUsage) return false if (unparsedUsage != other.unparsedUsage) return false
@ -98,7 +98,7 @@ interface IAccountTransaction {
} }
fun calculateHashCode(): Int { fun calculateHashCode(): Int {
var result = bankAccount.hashCode() var result = account.hashCode()
result = 31 * result + amount.hashCode() result = 31 * result + amount.hashCode()
result = 31 * result + currency.hashCode() result = 31 * result + currency.hashCode()
result = 31 * result + unparsedUsage.hashCode() result = 31 * result + unparsedUsage.hashCode()

View File

@ -8,7 +8,7 @@ typealias TypedBankAccount = IBankAccount<IAccountTransaction>
interface IBankAccount<TTransaction: IAccountTransaction> : OrderedDisplayable { interface IBankAccount<TTransaction: IAccountTransaction> : OrderedDisplayable {
val customer: ICustomer<*, *> val bank: IBankData<*, *>
var identifier: String var identifier: String
var accountHolderName: String var accountHolderName: String
var iban: String? var iban: String?

View File

@ -8,10 +8,10 @@ import net.dankito.utils.multiplatform.BigDecimal
import net.dankito.utils.multiplatform.sum import net.dankito.utils.multiplatform.sum
typealias TypedCustomer = ICustomer<IBankAccount<IAccountTransaction>, IAccountTransaction> typealias TypedBankData = IBankData<IBankAccount<IAccountTransaction>, IAccountTransaction>
interface ICustomer<TAccount: IBankAccount<TAccountTransaction>, TAccountTransaction: IAccountTransaction> : OrderedDisplayable { interface IBankData<TAccount: IBankAccount<TAccountTransaction>, TAccountTransaction: IAccountTransaction> : OrderedDisplayable {
var bankCode: String var bankCode: String
var customerId: String var customerId: String

View File

@ -6,7 +6,7 @@ import net.dankito.utils.multiplatform.Date
open class MessageLogEntry( open class MessageLogEntry(
val message: String, val message: String,
val time: Date, val time: Date,
val customer: TypedCustomer val bank: TypedBankData
) { ) {
override fun toString(): String { override fun toString(): String {

View File

@ -5,8 +5,8 @@ enum class SelectedAccountType {
AllAccounts, AllAccounts,
SingleAccount, SingleBank,
SingleBankAccount SingleAccount
} }

View File

@ -7,19 +7,19 @@ import net.dankito.utils.multiplatform.Date
open class DefaultModelCreator : IModelCreator { open class DefaultModelCreator : IModelCreator {
override fun createCustomer(bankCode: String, customerId: String, password: String, finTsServerAddress: String, bankName: String, bic: String, override fun createBank(bankCode: String, customerId: String, password: String, finTsServerAddress: String, bankName: String, bic: String,
customerName: String, userId: String, iconUrl: String?): TypedCustomer { customerName: String, userId: String, iconUrl: String?): TypedBankData {
return Customer(bankCode, customerId, password, finTsServerAddress, bankName, bic, customerName, userId, iconUrl) return BankData(bankCode, customerId, password, finTsServerAddress, bankName, bic, customerName, userId, iconUrl)
} }
override fun createBankAccount(customer: TypedCustomer, productName: String?, identifier: String): TypedBankAccount { override fun createAccount(bank: TypedBankData, productName: String?, identifier: String): TypedBankAccount {
return BankAccount(customer, productName, identifier) return BankAccount(bank, productName, identifier)
} }
override fun createTransaction( override fun createTransaction(
bankAccount: TypedBankAccount, account: TypedBankAccount,
amount: BigDecimal, amount: BigDecimal,
currency: String, currency: String,
unparsedUsage: String, unparsedUsage: String,
@ -55,7 +55,7 @@ open class DefaultModelCreator : IModelCreator {
relatedReferenceNumber: String? relatedReferenceNumber: String?
) : IAccountTransaction { ) : IAccountTransaction {
return AccountTransaction(bankAccount, amount, currency, unparsedUsage, bookingDate, return AccountTransaction(account, amount, currency, unparsedUsage, bookingDate,
otherPartyName, otherPartyBankCode, otherPartyAccountId, bookingText, valueDate, statementNumber, sequenceNumber, otherPartyName, otherPartyBankCode, otherPartyAccountId, bookingText, valueDate, statementNumber, sequenceNumber,
openingBalance, closingBalance, endToEndReference, customerReference, mandateReference, creditorIdentifier, openingBalance, closingBalance, endToEndReference, customerReference, mandateReference, creditorIdentifier,
originatorsIdentificationCode, compensationAmount, originalAmount, sepaUsage, deviantOriginator, deviantRecipient, originatorsIdentificationCode, compensationAmount, originalAmount, sepaUsage, deviantOriginator, deviantRecipient,

View File

@ -8,15 +8,15 @@ import net.dankito.utils.multiplatform.Date
interface IModelCreator { interface IModelCreator {
fun createCustomer(bankCode: String, customerId: String, password: String, finTsServerAddress: String, bankName: String, bic: String, fun createBank(bankCode: String, customerId: String, password: String, finTsServerAddress: String, bankName: String, bic: String,
customerName: String = "", userId: String = customerId, iconUrl: String? = null): TypedCustomer customerName: String = "", userId: String = customerId, iconUrl: String? = null): TypedBankData
fun createBankAccount(customer: TypedCustomer, productName: String?, identifier: String) : TypedBankAccount fun createAccount(bank: TypedBankData, productName: String?, identifier: String) : TypedBankAccount
fun createTransaction( fun createTransaction(
bankAccount: TypedBankAccount, account: TypedBankAccount,
amount: BigDecimal, amount: BigDecimal,
currency: String, currency: String,
unparsedUsage: String, unparsedUsage: String,

View File

@ -18,7 +18,7 @@ open class TransferMoneyData(
fun fromAccountTransactionWithoutAmountAndUsage(transaction: IAccountTransaction): TransferMoneyData { fun fromAccountTransactionWithoutAmountAndUsage(transaction: IAccountTransaction): TransferMoneyData {
return TransferMoneyData( return TransferMoneyData(
transaction.bankAccount as TypedBankAccount, transaction.account as TypedBankAccount,
transaction.otherPartyName ?: "", transaction.otherPartyName ?: "",
transaction.otherPartyAccountId ?: "", transaction.otherPartyAccountId ?: "",
transaction.otherPartyBankCode ?: "", transaction.otherPartyBankCode ?: "",
@ -29,7 +29,7 @@ open class TransferMoneyData(
fun fromAccountTransaction(transaction: IAccountTransaction): TransferMoneyData { fun fromAccountTransaction(transaction: IAccountTransaction): TransferMoneyData {
return TransferMoneyData( return TransferMoneyData(
transaction.bankAccount as TypedBankAccount, transaction.account as TypedBankAccount,
transaction.otherPartyName ?: "", transaction.otherPartyName ?: "",
transaction.otherPartyAccountId ?: "", transaction.otherPartyAccountId ?: "",
transaction.otherPartyBankCode ?: "", transaction.otherPartyBankCode ?: "",

View File

@ -4,24 +4,24 @@ import net.dankito.banking.ui.model.*
open class AddAccountResponse( open class AddAccountResponse(
open val customer: TypedCustomer, open val bank: TypedBankData,
retrievedData: List<RetrievedAccountData> = listOf(), retrievedData: List<RetrievedAccountData> = listOf(),
errorToShowToUser: String?, errorToShowToUser: String?,
userCancelledAction: Boolean = false userCancelledAction: Boolean = false
) : GetTransactionsResponse(retrievedData, errorToShowToUser, userCancelledAction) { ) : GetTransactionsResponse(retrievedData, errorToShowToUser, userCancelledAction) {
constructor(customer: TypedCustomer, errorToShowToUser: String?) : this(customer, listOf(), errorToShowToUser) constructor(bank: TypedBankData, errorToShowToUser: String?) : this(bank, listOf(), errorToShowToUser)
override val successful: Boolean override val successful: Boolean
get() = super.successful && customer.accounts.isNotEmpty() get() = super.successful && bank.accounts.isNotEmpty()
open val supportsRetrievingTransactionsOfLast90DaysWithoutTan: Boolean open val supportsRetrievingTransactionsOfLast90DaysWithoutTan: Boolean
get() = retrievedData.isNotEmpty() && retrievedData.any { it.successfullyRetrievedData } get() = retrievedData.isNotEmpty() && retrievedData.any { it.successfullyRetrievedData }
override fun toString(): String { override fun toString(): String {
return customer.toString() + " " + super.toString() return bank.toString() + " " + super.toString()
} }
} }

View File

@ -59,31 +59,31 @@ open class BankingPresenter(
} }
protected val bankingClientsForAccounts = mutableMapOf<TypedCustomer, IBankingClient>() protected val bankingClientsForBanks = mutableMapOf<TypedBankData, IBankingClient>()
protected var selectedBankAccountsField = mutableListOf<TypedBankAccount>() protected var _selectedAccounts = mutableListOf<TypedBankAccount>()
protected var selectedAccountType = SelectedAccountType.AllAccounts protected var selectedAccountType = SelectedAccountType.AllAccounts
protected var saveAccountOnNextEnterTanInvocation = false protected var saveAccountOnNextEnterTanInvocation = false
protected val accountsChangedListeners = mutableListOf<(List<TypedCustomer>) -> Unit>() protected val banksChangedListeners = mutableListOf<(List<TypedBankData>) -> Unit>()
protected val retrievedAccountTransactionsResponseListeners = mutableListOf<(GetTransactionsResponse) -> Unit>() protected val retrievedAccountTransactionsResponseListeners = mutableListOf<(GetTransactionsResponse) -> Unit>()
protected val selectedBankAccountsChangedListeners = mutableListOf<(List<TypedBankAccount>) -> Unit>() protected val selectedAccountsChangedListeners = mutableListOf<(List<TypedBankAccount>) -> Unit>()
protected val callback: BankingClientCallback = object : BankingClientCallback { protected val callback: BankingClientCallback = object : BankingClientCallback {
override fun enterTan(customer: TypedCustomer, tanChallenge: TanChallenge, callback: (EnterTanResult) -> Unit) { override fun enterTan(bank: TypedBankData, tanChallenge: TanChallenge, callback: (EnterTanResult) -> Unit) {
if (saveAccountOnNextEnterTanInvocation) { if (saveAccountOnNextEnterTanInvocation) {
persistAccountOffUiThread(customer) persistBankOffUiThread(bank)
saveAccountOnNextEnterTanInvocation = false saveAccountOnNextEnterTanInvocation = false
} }
router.getTanFromUserFromNonUiThread(customer, tanChallenge, this@BankingPresenter) { result -> router.getTanFromUserFromNonUiThread(bank, tanChallenge, this@BankingPresenter) { result ->
if (result.changeTanMethodTo != null || result.changeTanMediumTo != null) { // then either selected TAN medium or method will change -> save account on next call to enterTan() as then changes will be visible if (result.changeTanMethodTo != null || result.changeTanMediumTo != null) { // then either selected TAN medium or method will change -> save account on next call to enterTan() as then changes will be visible
saveAccountOnNextEnterTanInvocation = true saveAccountOnNextEnterTanInvocation = true
} }
@ -104,7 +104,7 @@ open class BankingPresenter(
init { init {
asyncRunner.runAsync { asyncRunner.runAsync {
readAppSettings() readAppSettings()
readPersistedAccounts() readPersistedBanks()
updateAccountsTransactionsIfNoTanIsRequiredAsync() updateAccountsTransactionsIfNoTanIsRequiredAsync()
} }
@ -116,81 +116,81 @@ open class BankingPresenter(
} }
protected open fun readPersistedAccounts() { protected open fun readPersistedBanks() {
try { try {
val deserializedAccounts = persister.readPersistedAccounts() val deserializedBanks = persister.readPersistedBanks()
deserializedAccounts.forEach { customer -> deserializedBanks.forEach { bank ->
val newClient = bankingClientCreator.createClient(customer, dataFolder, asyncRunner, callback) val newClient = bankingClientCreator.createClient(bank, dataFolder, asyncRunner, callback)
addClientForAccount(customer, newClient) addClientForBank(bank, newClient)
customer.accounts.forEach { account -> bank.accounts.forEach { account ->
if (account.haveAllTransactionsBeenFetched == false && didFetchAllTransactionsStoredOnBankServer(account, listOf())) { if (account.haveAllTransactionsBeenFetched == false && didFetchAllTransactionsStoredOnBankServer(account, listOf())) {
account.haveAllTransactionsBeenFetched = true // no need to save account, just delays app start-up, as even if account doesn't get saved during app run, haveAllTransactionsBeenFetched gets restored on next app run account.haveAllTransactionsBeenFetched = true // no need to save account, just delays app start-up, as even if account doesn't get saved during app run, haveAllTransactionsBeenFetched gets restored on next app run
} }
} }
} }
callAccountsChangedListeners() callBanksChangedListeners()
selectedAllBankAccounts() // TODO: save last selected bank account(s) selectedAllAccounts() // TODO: save last selected bank account(s)
} catch (e: Exception) { } catch (e: Exception) {
log.error(e) { "Could not deserialize persisted accounts with persister $persister" } log.error(e) { "Could not deserialize persisted banks with persister $persister" }
} }
} }
protected open fun addClientForAccount(customer: TypedCustomer, client: IBankingClient) { protected open fun addClientForBank(bank: TypedBankData, client: IBankingClient) {
bankingClientsForAccounts.put(customer, client) bankingClientsForBanks.put(bank, client)
} }
// TODO: move BankInfo out of fints4k // TODO: move BankInfo out of fints4k
open fun addAccountAsync(bankInfo: BankInfo, customerId: String, password: String, callback: (AddAccountResponse) -> Unit) { open fun addAccountAsync(bankInfo: BankInfo, customerId: String, password: String, callback: (AddAccountResponse) -> Unit) {
val customer = modelCreator.createCustomer(bankInfo.bankCode, customerId, password, bankInfo.pinTanAddress ?: "", bankInfo.name, bankInfo.bic, "") val bank = modelCreator.createBank(bankInfo.bankCode, customerId, password, bankInfo.pinTanAddress ?: "", bankInfo.name, bankInfo.bic, "")
val newClient = bankingClientCreator.createClient(customer, dataFolder, asyncRunner, this.callback) val newClient = bankingClientCreator.createClient(bank, dataFolder, asyncRunner, this.callback)
val startDate = Date() val startDate = Date()
newClient.addAccountAsync { response -> newClient.addAccountAsync { response ->
if (response.successful) { if (response.successful) {
val account = response.customer val bank = response.bank
account.displayIndex = customers.size bank.displayIndex = allBanks.size
addClientForAccount(account, newClient) addClientForBank(bank, newClient)
selectedAccount(account) selectedBank(bank)
callAccountsChangedListeners() callBanksChangedListeners()
persistAccountOffUiThread(account) persistBankOffUiThread(bank)
response.retrievedData.forEach { retrievedData -> response.retrievedData.forEach { retrievedData ->
retrievedAccountTransactions(GetTransactionsResponse(retrievedData), startDate, false) retrievedAccountTransactions(GetTransactionsResponse(retrievedData), startDate, false)
} }
findIconForBankAsync(account) findIconForBankAsync(bank)
} }
callback(response) callback(response)
} }
} }
protected open fun findIconForBankAsync(customer: TypedCustomer) { protected open fun findIconForBankAsync(bank: TypedBankData) {
bankIconFinder.findIconForBankAsync(customer.bankName) { bankIconUrl -> bankIconFinder.findIconForBankAsync(bank.bankName) { bankIconUrl ->
bankIconUrl?.let { bankIconUrl?.let {
try { try {
handleFindIconForBankResult(customer, bankIconUrl) handleFindIconForBankResult(bank, bankIconUrl)
} catch (e: Exception) { } catch (e: Exception) {
log.error(e) { "Could not get icon for bank ${customer.bankName}" } log.error(e) { "Could not get icon for bank ${bank.bankName}" }
} }
} }
} }
} }
protected open fun handleFindIconForBankResult(customer: TypedCustomer, bankIconUrl: String) { protected open fun handleFindIconForBankResult(bank: TypedBankData, bankIconUrl: String) {
val bankIconFile = saveBankIconToDisk(customer, bankIconUrl) val bankIconFile = saveBankIconToDisk(bank, bankIconUrl)
var iconFilePath = bankIconFile.getAbsolutePath() var iconFilePath = bankIconFile.getAbsolutePath()
@ -198,19 +198,19 @@ open class BankingPresenter(
iconFilePath = "file://" + iconFilePath // without 'file://' Android will not find it iconFilePath = "file://" + iconFilePath // without 'file://' Android will not find it
} }
customer.iconUrl = iconFilePath bank.iconUrl = iconFilePath
persistAccountOffUiThread(customer) persistBankOffUiThread(bank)
callAccountsChangedListeners() callBanksChangedListeners()
} }
protected open fun saveBankIconToDisk(customer: TypedCustomer, bankIconUrl: String): File { protected open fun saveBankIconToDisk(bank: TypedBankData, bankIconUrl: String): File {
val bankIconsDir = File(dataFolder, "bank_icons") val bankIconsDir = File(dataFolder, "bank_icons")
bankIconsDir.mkdirs() bankIconsDir.mkdirs()
val extension = getIconFileExtension(bankIconUrl) val extension = getIconFileExtension(bankIconUrl)
val bankIconFile = File(bankIconsDir, customer.bankCode + if (extension != null) (".$extension") else "") val bankIconFile = File(bankIconsDir, bank.bankCode + if (extension != null) (".$extension") else "")
persister.saveUrlToFile(bankIconUrl, bankIconFile) persister.saveUrlToFile(bankIconUrl, bankIconFile)
@ -236,45 +236,45 @@ open class BankingPresenter(
} }
open fun deleteAccount(customer: TypedCustomer) { open fun deleteAccount(bank: TypedBankData) {
asyncRunner.runAsync { asyncRunner.runAsync {
deleteAccountOffUiThread(customer) deleteAccountOffUiThread(bank)
} }
} }
protected open fun deleteAccountOffUiThread(customer: TypedCustomer) { protected open fun deleteAccountOffUiThread(bank: TypedBankData) {
val wasSelected = isSingleSelectedAccount(customer) or // either account or one of its bank accounts is currently selected val wasSelected = isSingleSelectedBank(bank) or // either account or one of its bank accounts is currently selected
(customer.accounts.firstOrNull { isSingleSelectedBankAccount(it) } != null) (bank.accounts.firstOrNull { isSingleSelectedAccount(it) } != null)
val client = bankingClientsForAccounts.remove(customer) val client = bankingClientsForBanks.remove(bank)
val displayIndex = customer.displayIndex val displayIndex = bank.displayIndex
persister.deleteAccount(customer, customers) persister.deleteBank(bank, allBanks)
val sortedBanks = customers.sortedByDisplayIndex() val sortedBanks = allBanks.sortedByDisplayIndex()
for (i in IntRange(displayIndex, sortedBanks.size - 1)) { for (i in IntRange(displayIndex, sortedBanks.size - 1)) {
val bank = sortedBanks[i] val bank = sortedBanks[i]
bank.displayIndex = i bank.displayIndex = i
accountDisplayIndexUpdated(bank) bankDisplayIndexUpdated(bank)
} }
client?.deletedAccount(customer, customers.firstOrNull { it.customerId == customer.customerId && it.bankCode == customer.bankCode} == null) client?.deletedBank(bank, allBanks.firstOrNull { it.customerId == bank.customerId && it.bankCode == bank.bankCode} == null)
callAccountsChangedListeners() callBanksChangedListeners()
if (wasSelected || areAllAccountSelected) { // to update displayed account transactions as transactions of yet deleted accounts have to be removed if (wasSelected || areAllAccountSelected) { // to update displayed account transactions as transactions of yet deleted accounts have to be removed
selectedAllBankAccounts() selectedAllAccounts()
} }
} }
open fun fetchAllAccountTransactionsAsync(customer: TypedCustomer, open fun fetchAllAccountTransactionsAsync(bank: TypedBankData,
callback: ((GetTransactionsResponse) -> Unit)? = null) { callback: ((GetTransactionsResponse) -> Unit)? = null) {
customer.accounts.forEach { bankAccount -> bank.accounts.forEach { account ->
if (bankAccount.supportsRetrievingAccountTransactions) { if (account.supportsRetrievingAccountTransactions) {
fetchAllAccountTransactionsAsync(bankAccount, callback) // TODO: use a synchronous version of fetchAccountTransactions() so that all bank accounts get handled serially fetchAllAccountTransactionsAsync(account, callback) // TODO: use a synchronous version of fetchAccountTransactions() so that all bank accounts get handled serially
} }
} }
} }
@ -288,10 +288,10 @@ open class BankingPresenter(
open fun fetchAccountTransactionsAsync(account: TypedBankAccount, fromDate: Date?, abortIfTanIsRequired: Boolean = false, open fun fetchAccountTransactionsAsync(account: TypedBankAccount, fromDate: Date?, abortIfTanIsRequired: Boolean = false,
callback: ((GetTransactionsResponse) -> Unit)? = null) { callback: ((GetTransactionsResponse) -> Unit)? = null) {
getBankingClientForAccount(account.customer)?.let { client -> getBankingClientForBank(account.bank)?.let { client ->
val startDate = Date() val startDate = Date()
client.getTransactionsAsync(GetTransactionsParameter(account,true, fromDate, null, abortIfTanIsRequired, { receivedAccountsTransactionChunk(account, it) } )) { response -> client.getTransactionsAsync(GetTransactionsParameter(account,true, fromDate, null, abortIfTanIsRequired, { receivedAccountTransactionChunk(account, it) } )) { response ->
if (response.tanRequiredButWeWereToldToAbortIfSo == false) { // don't call retrievedAccountTransactions() if aborted due to TAN required but we told client to abort if so if (response.tanRequiredButWeWereToldToAbortIfSo == false) { // don't call retrievedAccountTransactions() if aborted due to TAN required but we told client to abort if so
retrievedAccountTransactions(response, startDate, fromDate == null) retrievedAccountTransactions(response, startDate, fromDate == null)
@ -310,32 +310,32 @@ open class BankingPresenter(
updateAccountsTransactionsAsync(true) { } updateAccountsTransactionsAsync(true) { }
} }
open fun updateSelectedBankAccountTransactionsAsync(callback: (GetTransactionsResponse) -> Unit) { open fun updateSelectedAccountsTransactionsAsync(callback: (GetTransactionsResponse) -> Unit) {
updateBanksAccountsTransactionsAsync(selectedBankAccounts, false, callback) updateAccountsTransactionsAsync(selectedAccounts, false, callback)
} }
protected open fun updateAccountsTransactionsAsync(abortIfTanIsRequired: Boolean = false, callback: (GetTransactionsResponse) -> Unit) { protected open fun updateAccountsTransactionsAsync(abortIfTanIsRequired: Boolean = false, callback: (GetTransactionsResponse) -> Unit) {
bankingClientsForAccounts.keys.forEach { account -> bankingClientsForBanks.keys.forEach { account ->
account.accounts.forEach { bankAccount -> account.accounts.forEach { account ->
if (bankAccount.supportsRetrievingAccountTransactions) { if (account.supportsRetrievingAccountTransactions) {
updateBankAccountTransactionsAsync(bankAccount, abortIfTanIsRequired, callback) updateAccountTransactionsAsync(account, abortIfTanIsRequired, callback)
} }
} }
} }
} }
protected open fun updateBanksAccountsTransactionsAsync(accounts: List<TypedBankAccount>, abortIfTanIsRequired: Boolean = false, callback: (GetTransactionsResponse) -> Unit) { protected open fun updateAccountsTransactionsAsync(accounts: List<TypedBankAccount>, abortIfTanIsRequired: Boolean = false, callback: (GetTransactionsResponse) -> Unit) {
accounts.forEach { bankAccount -> accounts.forEach { account ->
if (bankAccount.supportsRetrievingAccountTransactions) { if (account.supportsRetrievingAccountTransactions) {
updateBankAccountTransactionsAsync(bankAccount, abortIfTanIsRequired, callback) updateAccountTransactionsAsync(account, abortIfTanIsRequired, callback)
} }
} }
} }
open fun updateBankAccountTransactionsAsync(bankAccount: TypedBankAccount, abortIfTanIsRequired: Boolean = false, callback: ((GetTransactionsResponse) -> Unit)? = null) { open fun updateAccountTransactionsAsync(account: TypedBankAccount, abortIfTanIsRequired: Boolean = false, callback: ((GetTransactionsResponse) -> Unit)? = null) {
val fromDate = bankAccount.retrievedTransactionsUpTo?.let { Date(it.millisSinceEpoch - OneDayMillis) } // one day before last received transactions val fromDate = account.retrievedTransactionsUpTo?.let { Date(it.millisSinceEpoch - OneDayMillis) } // one day before last received transactions
fetchAccountTransactionsAsync(bankAccount, fromDate, abortIfTanIsRequired, callback) fetchAccountTransactionsAsync(account, fromDate, abortIfTanIsRequired, callback)
} }
protected open fun retrievedAccountTransactions(response: GetTransactionsResponse, startDate: Date, didFetchAllTransactions: Boolean) { protected open fun retrievedAccountTransactions(response: GetTransactionsResponse, startDate: Date, didFetchAllTransactions: Boolean) {
@ -359,7 +359,7 @@ open class BankingPresenter(
} }
protected open fun didFetchAllTransactionsStoredOnBankServer(account: IBankAccount<IAccountTransaction>, fetchedTransactions: Collection<IAccountTransaction>): Boolean { protected open fun didFetchAllTransactionsStoredOnBankServer(account: IBankAccount<IAccountTransaction>, fetchedTransactions: Collection<IAccountTransaction>): Boolean {
account.customer.countDaysForWhichTransactionsAreKept?.let { countDaysForWhichTransactionsAreKept -> account.bank.countDaysForWhichTransactionsAreKept?.let { countDaysForWhichTransactionsAreKept ->
(account.retrievedTransactionsFromOn ?: getDateOfFirstRetrievedTransaction(account.bookedTransactions) ?: getDateOfFirstRetrievedTransaction(fetchedTransactions))?.let { retrievedTransactionsFromOn -> (account.retrievedTransactionsFromOn ?: getDateOfFirstRetrievedTransaction(account.bookedTransactions) ?: getDateOfFirstRetrievedTransaction(fetchedTransactions))?.let { retrievedTransactionsFromOn ->
val dayOfFirstTransactionStoredOnBankServer = Date(Date.today.millisSinceEpoch - countDaysForWhichTransactionsAreKept * OneDayMillis) val dayOfFirstTransactionStoredOnBankServer = Date(Date.today.millisSinceEpoch - countDaysForWhichTransactionsAreKept * OneDayMillis)
@ -374,12 +374,12 @@ open class BankingPresenter(
return transactions.map { it.valueDate }.minBy { it.millisSinceEpoch } return transactions.map { it.valueDate }.minBy { it.millisSinceEpoch }
} }
protected open fun receivedAccountsTransactionChunk(bankAccount: TypedBankAccount, accountTransactionsChunk: List<IAccountTransaction>) { protected open fun receivedAccountTransactionChunk(account: TypedBankAccount, transactionsChunk: List<IAccountTransaction>) {
if (accountTransactionsChunk.isNotEmpty()) { if (transactionsChunk.isNotEmpty()) {
asyncRunner.runAsync { // don't block retrieving next chunk by blocked saving to db / json asyncRunner.runAsync { // don't block retrieving next chunk by blocked saving to db / json
updateAccountTransactions(bankAccount, accountTransactionsChunk) updateAccountTransactions(account, transactionsChunk)
callRetrievedAccountTransactionsResponseListener(GetTransactionsResponse(RetrievedAccountData(bankAccount, true, null, accountTransactionsChunk, listOf(), null, null))) callRetrievedAccountTransactionsResponseListener(GetTransactionsResponse(RetrievedAccountData(account, true, null, transactionsChunk, listOf(), null, null)))
} }
} }
} }
@ -405,10 +405,10 @@ open class BankingPresenter(
persistAccountTransactionsOffUiThread(account, newBookedTransactions) persistAccountTransactionsOffUiThread(account, newBookedTransactions)
} }
protected open fun updateBalance(bankAccount: TypedBankAccount, balance: BigDecimal) { protected open fun updateBalance(account: TypedBankAccount, balance: BigDecimal) {
bankAccount.balance = balance account.balance = balance
persistAccountOffUiThread(bankAccount.customer) persistBankOffUiThread(account.bank)
} }
@ -417,60 +417,60 @@ open class BankingPresenter(
} }
open fun allAccountsUpdated() { open fun allBanksUpdated() {
customers.forEach { account -> allBanks.forEach { bank ->
accountDisplayIndexUpdated(account) bankDisplayIndexUpdated(bank)
} }
} }
open fun accountDisplayIndexUpdated(account: TypedCustomer) { open fun bankDisplayIndexUpdated(bank: TypedBankData) {
persistAccountAsync(account) persistBankAsync(bank)
callAccountsChangedListeners() callBanksChangedListeners()
} }
open fun accountUpdated(bank: TypedCustomer) { open fun bankUpdated(bank: TypedBankData) {
persistAccountAsync(bank) persistBankAsync(bank)
callAccountsChangedListeners() callBanksChangedListeners()
getBankingClientForAccount(bank)?.dataChanged(bank) getBankingClientForBank(bank)?.dataChanged(bank)
} }
open fun accountUpdated(account: TypedBankAccount) { open fun accountUpdated(account: TypedBankAccount) {
persistAccountAsync(account.customer) persistBankAsync(account.bank)
callAccountsChangedListeners() callBanksChangedListeners()
} }
protected open fun persistAccountAsync(customer: ICustomer<*, *>) { protected open fun persistBankAsync(bank: IBankData<*, *>) {
asyncRunner.runAsync { asyncRunner.runAsync {
persistAccountOffUiThread(customer) persistBankOffUiThread(bank)
} }
} }
/** /**
* Ensure that this method only gets called off UI thread (at least for Android Room db) as otherwise it may blocks UI thread. * Ensure that this method only gets called off UI thread (at least for Android Room db) as otherwise it may blocks UI thread.
*/ */
protected open fun persistAccountOffUiThread(customer: ICustomer<*, *>) { protected open fun persistBankOffUiThread(bank: IBankData<*, *>) {
persister.saveOrUpdateAccount(customer as TypedCustomer, customers) persister.saveOrUpdateBank(bank as TypedBankData, allBanks)
} }
/** /**
* Ensure that this method only gets called off UI thread (at least for Android Room db) as otherwise it may blocks UI thread. * Ensure that this method only gets called off UI thread (at least for Android Room db) as otherwise it may blocks UI thread.
*/ */
protected open fun persistAccountTransactionsOffUiThread(bankAccount: TypedBankAccount, bookedTransactions: List<IAccountTransaction>) { protected open fun persistAccountTransactionsOffUiThread(account: TypedBankAccount, bookedTransactions: List<IAccountTransaction>) {
persister.saveOrUpdateAccountTransactions(bankAccount, bookedTransactions) persister.saveOrUpdateAccountTransactions(account, bookedTransactions)
} }
open fun transferMoneyAsync(data: TransferMoneyData, callback: (BankingClientResponse) -> Unit) { open fun transferMoneyAsync(data: TransferMoneyData, callback: (BankingClientResponse) -> Unit) {
val account = data.account val account = data.account
getBankingClientForAccount(account.customer)?.let { client -> getBankingClientForBank(account.bank)?.let { client ->
client.transferMoneyAsync(data) { response -> client.transferMoneyAsync(data) { response ->
if (response.successful) { if (response.successful) {
updateBankAccountTransactionsAsync(account, true) updateAccountTransactionsAsync(account, true)
} }
callback(response) callback(response)
@ -492,7 +492,7 @@ open class BankingPresenter(
if (invoiceData.potentialTotalAmount != null || invoiceData.potentialIban != null) { // if at least an amount or IBAN could get extracted if (invoiceData.potentialTotalAmount != null || invoiceData.potentialIban != null) { // if at least an amount or IBAN could get extracted
val transferMoneyData = TransferMoneyData( val transferMoneyData = TransferMoneyData(
bankAccounts.first(), "", allAccounts.first(), "",
invoiceData.potentialIban ?: "", invoiceData.potentialIban ?: "",
invoiceData.potentialBic ?: "", invoiceData.potentialBic ?: "",
invoiceData.potentialTotalAmount ?: BigDecimal.Zero, "") invoiceData.potentialTotalAmount ?: BigDecimal.Zero, "")
@ -586,7 +586,7 @@ open class BankingPresenter(
open fun searchSelectedAccountTransactions(query: String): List<IAccountTransaction> { open fun searchSelectedAccountTransactions(query: String): List<IAccountTransaction> {
return searchAccountTransactions(query, selectedBankAccountsAccountTransactions) return searchAccountTransactions(query, selectedAccountsTransactions)
} }
open fun searchAccountTransactions(query: String, transactions: List<IAccountTransaction>): List<IAccountTransaction> { open fun searchAccountTransactions(query: String, transactions: List<IAccountTransaction>): List<IAccountTransaction> {
@ -604,13 +604,13 @@ open class BankingPresenter(
} }
open fun getMessageLogForAccounts(customers: List<TypedCustomer>): List<String> { open fun getMessageLogForAccounts(banks: List<TypedBankData>): List<String> {
val logEntries = customers.flatMap { val logEntries = banks.flatMap {
getBankingClientForAccount(it)?.messageLogWithoutSensitiveData ?: listOf() getBankingClientForBank(it)?.messageLogWithoutSensitiveData ?: listOf()
} }
return logEntries.map { entry -> return logEntries.map { entry ->
MessageLogEntryDateFormatter.format(entry.time) + " " + entry.customer.bankCode + " " + entry.message MessageLogEntryDateFormatter.format(entry.time) + " " + entry.bank.bankCode + " " + entry.message
} }
} }
@ -628,122 +628,122 @@ open class BankingPresenter(
} }
protected open fun getBankingClientForAccount(customer: ICustomer<*, *>): IBankingClient? { protected open fun getBankingClientForBank(bank: IBankData<*, *>): IBankingClient? {
return bankingClientsForAccounts.get(customer as TypedCustomer) return bankingClientsForBanks.get(bank as TypedBankData)
} }
open val selectedBankAccounts: List<TypedBankAccount> open val selectedAccounts: List<TypedBankAccount>
get() = ArrayList(selectedBankAccountsField) get() = ArrayList(_selectedAccounts)
open val selectedBankAccountsAccountTransactions: List<IAccountTransaction> open val selectedAccountsTransactions: List<IAccountTransaction>
get() = getAccountTransactionsForBankAccounts(selectedBankAccounts) get() = getTransactionsForAccounts(selectedAccounts)
open val balanceOfSelectedBankAccounts: BigDecimal open val balanceOfSelectedAccounts: BigDecimal
get() = sumBalance(selectedBankAccounts.map { it.balance }) get() = sumBalance(selectedAccounts.map { it.balance })
open val selectedBankAccountsForWhichNotAllTransactionsHaveBeenFetched: List<TypedBankAccount> open val selectedAccountsForWhichNotAllTransactionsHaveBeenFetched: List<TypedBankAccount>
get() = selectedBankAccounts.filter { it.haveAllTransactionsBeenFetched == false } get() = selectedAccounts.filter { it.haveAllTransactionsBeenFetched == false }
open val selectedBankAccountsTransactionRetrievalState: TransactionsRetrievalState open val selectedAccountsTransactionRetrievalState: TransactionsRetrievalState
get() = getAccountsTransactionRetrievalState(selectedBankAccounts) get() = getAccountsTransactionRetrievalState(selectedAccounts)
open val areAllAccountSelected: Boolean open val areAllAccountSelected: Boolean
get() = selectedAccountType == SelectedAccountType.AllAccounts get() = selectedAccountType == SelectedAccountType.AllAccounts
open fun isSingleSelectedAccount(customer: TypedCustomer): Boolean { open fun isSingleSelectedBank(bank: TypedBankData): Boolean {
return selectedAccountType == SelectedAccountType.SingleBank
&& _selectedAccounts.map { it.bank }.toSet().containsExactly(bank)
}
open fun isSingleSelectedAccount(account: TypedBankAccount): Boolean {
return selectedAccountType == SelectedAccountType.SingleAccount return selectedAccountType == SelectedAccountType.SingleAccount
&& selectedBankAccountsField.map { it.customer }.toSet().containsExactly(customer) && _selectedAccounts.containsExactly(account)
} }
open fun isSingleSelectedBankAccount(bankAccount: TypedBankAccount): Boolean { open fun selectedAllAccounts() {
return selectedAccountType == SelectedAccountType.SingleBankAccount
&& selectedBankAccountsField.containsExactly(bankAccount)
}
open fun selectedAllBankAccounts() {
selectedAccountType = SelectedAccountType.AllAccounts selectedAccountType = SelectedAccountType.AllAccounts
setSelectedBankAccounts(bankAccounts) setSelectedAccounts(allAccounts)
} }
open fun selectedAccount(customer: TypedCustomer) { open fun selectedBank(bank: TypedBankData) {
selectedAccountType = SelectedAccountType.SingleBank
setSelectedAccounts(bank.accounts)
}
open fun selectedAccount(account: TypedBankAccount) {
selectedAccountType = SelectedAccountType.SingleAccount selectedAccountType = SelectedAccountType.SingleAccount
setSelectedBankAccounts(customer.accounts) setSelectedAccounts(listOf(account))
} }
open fun selectedBankAccount(bankAccount: TypedBankAccount) { protected open fun setSelectedAccounts(accounts: List<TypedBankAccount>) {
selectedAccountType = SelectedAccountType.SingleBankAccount this._selectedAccounts = ArrayList(accounts) // make a copy
setSelectedBankAccounts(listOf(bankAccount)) callSelectedAccountsChangedListeners(_selectedAccounts)
}
protected open fun setSelectedBankAccounts(bankAccounts: List<TypedBankAccount>) {
this.selectedBankAccountsField = ArrayList(bankAccounts) // make a copy
callSelectedBankAccountsChangedListeners(selectedBankAccountsField)
} }
open val customers: List<TypedCustomer> open val allBanks: List<TypedBankData>
get() = bankingClientsForAccounts.keys.toList() get() = bankingClientsForBanks.keys.toList()
open val bankAccounts: List<TypedBankAccount> open val allAccounts: List<TypedBankAccount>
get() = customers.flatMap { it.accounts } get() = allBanks.flatMap { it.accounts }
open val allTransactions: List<IAccountTransaction> open val allTransactions: List<IAccountTransaction>
get() = getAccountTransactionsForBankAccounts(bankAccounts) get() = getTransactionsForAccounts(allAccounts)
open val balanceOfAllAccounts: BigDecimal open val balanceOfAllAccounts: BigDecimal
get() = getBalanceForAccounts(customers) get() = getBalanceForBanks(allBanks)
open val bankAccountsSupportingRetrievingAccountTransactions: List<TypedBankAccount> open val accountsSupportingRetrievingAccountTransactions: List<TypedBankAccount>
get() = bankAccounts.filter { it.supportsRetrievingAccountTransactions } get() = allAccounts.filter { it.supportsRetrievingAccountTransactions }
open val hasBankAccountsSupportingRetrievingAccountTransactions: Boolean open val hasAccountsSupportingRetrievingTransactions: Boolean
get() = doBankAccountsSupportRetrievingAccountTransactions(bankAccounts) get() = doAccountsSupportRetrievingTransactions(allAccounts)
open val doSelectedBankAccountsSupportRetrievingAccountTransactions: Boolean open val doSelectedAccountsSupportRetrievingTransactions: Boolean
get() = doBankAccountsSupportRetrievingAccountTransactions(selectedBankAccounts) get() = doAccountsSupportRetrievingTransactions(selectedAccounts)
open fun doBankAccountsSupportRetrievingAccountTransactions(bankAccounts: List<TypedBankAccount>): Boolean { open fun doAccountsSupportRetrievingTransactions(accounts: List<TypedBankAccount>): Boolean {
return bankAccounts.firstOrNull { it.supportsRetrievingAccountTransactions } != null return accounts.firstOrNull { it.supportsRetrievingAccountTransactions } != null
} }
open val bankAccountsSupportingRetrievingBalance: List<TypedBankAccount> open val accountsSupportingRetrievingBalance: List<TypedBankAccount>
get() = bankAccounts.filter { it.supportsRetrievingBalance } get() = allAccounts.filter { it.supportsRetrievingBalance }
open val hasBankAccountsSupportingRetrievingBalance: Boolean open val hasAccountsSupportingRetrievingBalance: Boolean
get() = doBankAccountsSupportRetrievingBalance(bankAccounts) get() = doAccountsSupportRetrievingBalance(allAccounts)
open val doSelectedBankAccountsSupportRetrievingBalance: Boolean open val doSelectedAccountsSupportRetrievingBalance: Boolean
get() = doBankAccountsSupportRetrievingBalance(selectedBankAccounts) get() = doAccountsSupportRetrievingBalance(selectedAccounts)
open fun doBankAccountsSupportRetrievingBalance(bankAccounts: List<TypedBankAccount>): Boolean { open fun doAccountsSupportRetrievingBalance(accounts: List<TypedBankAccount>): Boolean {
return bankAccounts.firstOrNull { it.supportsRetrievingBalance } != null return accounts.firstOrNull { it.supportsRetrievingBalance } != null
} }
open val bankAccountsSupportingTransferringMoney: List<TypedBankAccount> open val accountsSupportingTransferringMoney: List<TypedBankAccount>
get() = bankAccounts.filter { it.supportsTransferringMoney } get() = allAccounts.filter { it.supportsTransferringMoney }
open val hasBankAccountsSupportTransferringMoney: Boolean open val hasAccountsSupportTransferringMoney: Boolean
get() = doBankAccountsSupportTransferringMoney(bankAccounts) get() = doAccountsSupportTransferringMoney(allAccounts)
open val doSelectedBankAccountsSupportTransferringMoney: Boolean open val doSelectedAccountsSupportTransferringMoney: Boolean
get() = doBankAccountsSupportTransferringMoney(selectedBankAccounts) get() = doAccountsSupportTransferringMoney(selectedAccounts)
open fun doBankAccountsSupportTransferringMoney(bankAccounts: List<TypedBankAccount>): Boolean { open fun doAccountsSupportTransferringMoney(account: List<TypedBankAccount>): Boolean {
return bankAccounts.firstOrNull { it.supportsTransferringMoney } != null return account.firstOrNull { it.supportsTransferringMoney } != null
} }
protected open fun getAccountTransactionsForBankAccounts(bankAccounts: Collection<TypedBankAccount>): List<IAccountTransaction> { protected open fun getTransactionsForAccounts(accounts: Collection<TypedBankAccount>): List<IAccountTransaction> {
return bankAccounts.flatMap { it.bookedTransactions }.sortedByDescending { it.valueDate.millisSinceEpoch } // TODO: someday add unbooked transactions return accounts.flatMap { it.bookedTransactions }.sortedByDescending { it.valueDate.millisSinceEpoch } // TODO: someday add unbooked transactions
} }
protected open fun getAccountsTransactionRetrievalState(accounts: List<TypedBankAccount>): TransactionsRetrievalState { protected open fun getAccountsTransactionRetrievalState(accounts: List<TypedBankAccount>): TransactionsRetrievalState {
@ -788,8 +788,8 @@ open class BankingPresenter(
return TransactionsRetrievalState.NeverRetrievedTransactions return TransactionsRetrievalState.NeverRetrievedTransactions
} }
protected open fun getBalanceForAccounts(customers: Collection<TypedCustomer>): BigDecimal { protected open fun getBalanceForBanks(banks: Collection<TypedBankData>): BigDecimal {
return customers.map { it.balance }.sum() return banks.map { it.balance }.sum()
} }
protected open fun sumBalance(singleBalances: Collection<BigDecimal>): BigDecimal { protected open fun sumBalance(singleBalances: Collection<BigDecimal>): BigDecimal {
@ -797,7 +797,7 @@ open class BankingPresenter(
} }
open fun getTanMediaForTanMethod(bank: TypedCustomer, tanMethod: TanMethod): List<TanMedium> { open fun getTanMediaForTanMethod(bank: TypedBankData, tanMethod: TanMethod): List<TanMedium> {
if (ChipTanTanMethods.contains(tanMethod.type)) { if (ChipTanTanMethods.contains(tanMethod.type)) {
return bank.tanMediaSorted.filterIsInstance<TanGeneratorTanMedium>() return bank.tanMediaSorted.filterIsInstance<TanGeneratorTanMedium>()
} }
@ -839,19 +839,19 @@ open class BankingPresenter(
} }
open fun addAccountsChangedListener(listener: (List<TypedCustomer>) -> Unit): Boolean { open fun addBanksChangedListener(listener: (List<TypedBankData>) -> Unit): Boolean {
return accountsChangedListeners.add(listener) return banksChangedListeners.add(listener)
} }
open fun removeAccountsChangedListener(listener: (List<TypedCustomer>) -> Unit): Boolean { open fun removeBanksChangedListener(listener: (List<TypedBankData>) -> Unit): Boolean {
return accountsChangedListeners.add(listener) return banksChangedListeners.add(listener)
} }
protected open fun callAccountsChangedListeners() { protected open fun callBanksChangedListeners() {
val accounts = ArrayList(this.customers) val banks = ArrayList(this.allBanks)
ArrayList(accountsChangedListeners).forEach { ArrayList(banksChangedListeners).forEach {
it(accounts) // TODO: use RxJava for this it(banks) // TODO: use RxJava for this
} }
} }
@ -871,19 +871,19 @@ open class BankingPresenter(
} }
open fun addSelectedBankAccountsChangedListener(listener: (List<TypedBankAccount>) -> Unit): Boolean { open fun addSelectedAccountsChangedListener(listener: (List<TypedBankAccount>) -> Unit): Boolean {
return selectedBankAccountsChangedListeners.add(listener) return selectedAccountsChangedListeners.add(listener)
} }
open fun removeSelectedBankAccountsChangedListener(listener: (List<TypedBankAccount>) -> Unit): Boolean { open fun removeSelectedAccountsChangedListener(listener: (List<TypedBankAccount>) -> Unit): Boolean {
return selectedBankAccountsChangedListeners.add(listener) return selectedAccountsChangedListeners.add(listener)
} }
protected open fun callSelectedBankAccountsChangedListeners(selectedBankAccounts: List<TypedBankAccount>) { protected open fun callSelectedAccountsChangedListeners(selectedAccounts: List<TypedBankAccount>) {
val selectedBankAccounts = this.selectedBankAccounts val accounts = ArrayList(selectedAccounts)
ArrayList(selectedBankAccountsChangedListeners).forEach { ArrayList(selectedAccountsChangedListeners).forEach {
it(selectedBankAccounts) // TODO: use RxJava for this it(accounts) // TODO: use RxJava for this
} }
} }

View File

@ -57,10 +57,10 @@
<attribute name="supportsTransferringMoney" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/> <attribute name="supportsTransferringMoney" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="type" attributeType="String"/> <attribute name="type" attributeType="String"/>
<attribute name="userSetDisplayName" optional="YES" attributeType="String"/> <attribute name="userSetDisplayName" optional="YES" attributeType="String"/>
<relationship name="customer" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="PersistedCustomer" inverseName="accounts" inverseEntity="PersistedCustomer"/> <relationship name="bank" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="PersistedBankData" inverseName="accounts" inverseEntity="PersistedBankData"/>
<relationship name="transactions" toMany="YES" deletionRule="Cascade" destinationEntity="PersistedAccountTransaction" inverseName="account" inverseEntity="PersistedAccountTransaction"/> <relationship name="transactions" toMany="YES" deletionRule="Cascade" destinationEntity="PersistedAccountTransaction" inverseName="account" inverseEntity="PersistedAccountTransaction"/>
</entity> </entity>
<entity name="PersistedCustomer" representedClassName="PersistedCustomer" syncable="YES" codeGenerationType="class"> <entity name="PersistedBankData" representedClassName="PersistedBankData" syncable="YES" codeGenerationType="class">
<attribute name="bankCode" attributeType="String"/> <attribute name="bankCode" attributeType="String"/>
<attribute name="bankName" attributeType="String"/> <attribute name="bankName" attributeType="String"/>
<attribute name="bic" attributeType="String"/> <attribute name="bic" attributeType="String"/>
@ -74,7 +74,7 @@
<attribute name="selectedTanMethodCode" optional="YES" attributeType="String"/> <attribute name="selectedTanMethodCode" optional="YES" attributeType="String"/>
<attribute name="userId" attributeType="String"/> <attribute name="userId" attributeType="String"/>
<attribute name="userSetDisplayName" optional="YES" attributeType="String"/> <attribute name="userSetDisplayName" optional="YES" attributeType="String"/>
<relationship name="accounts" toMany="YES" deletionRule="Cascade" ordered="YES" destinationEntity="PersistedBankAccount" inverseName="customer" inverseEntity="PersistedBankAccount"/> <relationship name="accounts" toMany="YES" deletionRule="Cascade" ordered="YES" destinationEntity="PersistedBankAccount" inverseName="bank" inverseEntity="PersistedBankAccount"/>
<relationship name="supportedTanMethods" toMany="YES" deletionRule="Cascade" ordered="YES" destinationEntity="PersistedTanMethod"/> <relationship name="supportedTanMethods" toMany="YES" deletionRule="Cascade" ordered="YES" destinationEntity="PersistedTanMethod"/>
<relationship name="tanMedia" optional="YES" toMany="YES" deletionRule="Cascade" ordered="YES" destinationEntity="PersistedTanMedium"/> <relationship name="tanMedia" optional="YES" toMany="YES" deletionRule="Cascade" ordered="YES" destinationEntity="PersistedTanMedium"/>
</entity> </entity>
@ -92,7 +92,7 @@
<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="373"/> <element name="PersistedBankAccount" positionX="-54" positionY="63" width="128" height="373"/>
<element name="PersistedCustomer" positionX="-63" positionY="-18" width="128" height="283"/> <element name="PersistedBankData" 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"/>
</elements> </elements>

View File

@ -15,20 +15,20 @@ let previewImageTanChallenge = ImageTanChallenge(image: TanImage(mimeType: "imag
let previewFlickerCodeTanChallenge = FlickerCodeTanChallenge(flickerCode: FlickerCode(challengeHHD_UC: "", parsedDataSet: "", decodingError: nil), messageToShowToUser: "", tanMethod: previewTanMethods[0]) let previewFlickerCodeTanChallenge = FlickerCodeTanChallenge(flickerCode: FlickerCode(challengeHHD_UC: "", parsedDataSet: "", decodingError: nil), messageToShowToUser: "", tanMethod: previewTanMethods[0])
func createPreviewBanks() -> [ICustomer] { func createPreviewBanks() -> [IBankData] {
let bank1 = Customer(bankCode: "", customerId: "", password: "", finTsServerAddress: "", bankName: "Abzockbank", bic: "", customerName: "Marieke Musterfrau", userId: "", iconUrl: "", accounts: []) let bank1 = BankData(bankCode: "", customerId: "", password: "", finTsServerAddress: "", bankName: "Abzockbank", bic: "", customerName: "Marieke Musterfrau", userId: "", iconUrl: "", accounts: [])
bank1.accounts = [ bank1.accounts = [
BankAccount(customer: bank1, productName: "Girokonto", identifier: "1234567890"), BankAccount(bank: bank1, productName: "Girokonto", identifier: "1234567890"),
BankAccount(customer: bank1, productName: "Tagesgeld Minus", identifier: "0987654321") BankAccount(bank: bank1, productName: "Tagesgeld Minus", identifier: "0987654321")
] ]
let bank2 = Customer(bankCode: "", customerId: "", password: "", finTsServerAddress: "", bankName: "Kundenverarschebank", bic: "", customerName: "Marieke Musterfrau", userId: "", iconUrl: "", accounts: []) let bank2 = BankData(bankCode: "", customerId: "", password: "", finTsServerAddress: "", bankName: "Kundenverarschebank", bic: "", customerName: "Marieke Musterfrau", userId: "", iconUrl: "", accounts: [])
bank2.accounts = [ bank2.accounts = [
BankAccount(customer: bank2, productName: "Girokonto", identifier: "1234567890") BankAccount(bank: bank2, productName: "Girokonto", identifier: "1234567890")
] ]
return [ bank1, bank2 ] return [ bank1, bank2 ]

View File

@ -70,7 +70,7 @@ extension KeychainPasswordItem {
} }
extension Customer : Identifiable { extension BankData : Identifiable {
public var id: String { technicalId } public var id: String { technicalId }
@ -89,11 +89,11 @@ extension AccountTransaction : Identifiable {
} }
func ==(lhs: ICustomer, rhs: ICustomer) -> Bool { func ==(lhs: IBankData, rhs: IBankData) -> Bool {
return lhs.technicalId == rhs.technicalId return lhs.technicalId == rhs.technicalId
} }
func !=(lhs: ICustomer, rhs: ICustomer) -> Bool { func !=(lhs: IBankData, rhs: IBankData) -> Bool {
return lhs.technicalId != rhs.technicalId return lhs.technicalId != rhs.technicalId
} }
@ -114,7 +114,7 @@ func !=(lhs: IAccountTransaction, rhs: IAccountTransaction) -> Bool {
} }
extension Array where Element == ICustomer { extension Array where Element == IBankData {
func sumBalances() -> CommonBigDecimal { func sumBalances() -> CommonBigDecimal {
return CommonBigDecimal(decimal_: self.map { $0.balance.decimal }.sum()) return CommonBigDecimal(decimal_: self.map { $0.balance.decimal }.sum())

View File

@ -6,8 +6,8 @@ class AppData : ObservableObject {
@Inject private var presenter: BankingPresenterSwift @Inject private var presenter: BankingPresenterSwift
@Published var banks: [ICustomer] = [] @Published var banks: [IBankData] = []
@Published var banksSorted: [ICustomer] = [] @Published var banksSorted: [IBankData] = []
@Published var hasAtLeastOneAccountBeenAdded: Bool = false @Published var hasAtLeastOneAccountBeenAdded: Bool = false
@ -17,14 +17,14 @@ class AppData : ObservableObject {
init() { init() {
setFieldsForBanks() setFieldsForBanks()
presenter.addAccountsChangedListener { banks in presenter.addBanksChangedListener { banks in
self.setFieldsForBanks() self.setFieldsForBanks()
} }
} }
private func setFieldsForBanks() { private func setFieldsForBanks() {
self.banks = presenter.customers self.banks = presenter.allBanks
self.banksSorted = banks.sortedByDisplayIndex() self.banksSorted = banks.sortedByDisplayIndex()
hasAtLeastOneAccountBeenAdded = banks.isNotEmpty hasAtLeastOneAccountBeenAdded = banks.isNotEmpty

View File

@ -19,74 +19,74 @@ class CoreDataBankingPersistence: IBankingPersistence, IRemitteeSearcher {
} }
func saveOrUpdateAccount(customer: ICustomer, allCustomers: [ICustomer]) { func saveOrUpdateBank(bank: IBankData, allBanks: [IBankData]) {
do { do {
let mapped = mapper.map(customer, context) let mapped = mapper.map(bank, context)
if customer.technicalId.isCoreDataId == false { // an unpersisted bank (but check should not be necessary) if bank.technicalId.isCoreDataId == false { // an unpersisted bank (but check should not be necessary)
context.insert(mapped) context.insert(mapped)
} }
try context.save() try context.save()
setIds(customer, mapped) setIds(bank, mapped)
} catch { } catch {
NSLog("Could not save Customer \(customer): \(error)") NSLog("Could not save bank \(bank): \(error)")
} }
} }
private func setIds(_ customer: ICustomer, _ mappedCustomer: PersistedCustomer) { private func setIds(_ bank: IBankData, _ mappedBank: PersistedBankData) {
customer.technicalId = mappedCustomer.objectIDAsString bank.technicalId = mappedBank.objectIDAsString
for account in customer.accounts { for account in bank.accounts {
if let mappedAccount = mappedCustomer.accounts?.first { ($0 as! PersistedBankAccount).identifier == account.identifier} as? PersistedBankAccount { if let mappedAccount = mappedBank.accounts?.first { ($0 as! PersistedBankAccount).identifier == account.identifier} as? PersistedBankAccount {
account.technicalId = mappedAccount.objectIDAsString account.technicalId = mappedAccount.objectIDAsString
} }
} }
for tanMethod in customer.supportedTanMethods { for tanMethod in bank.supportedTanMethods {
if let mappedTanMethod = mappedCustomer.supportedTanMethods?.first { ($0 as! PersistedTanMethod).bankInternalMethodCode == tanMethod.bankInternalMethodCode } as? PersistedTanMethod { if let mappedTanMethod = mappedBank.supportedTanMethods?.first { ($0 as! PersistedTanMethod).bankInternalMethodCode == tanMethod.bankInternalMethodCode } as? PersistedTanMethod {
tanMethod.technicalId = mappedTanMethod.objectIDAsString tanMethod.technicalId = mappedTanMethod.objectIDAsString
} }
} }
for tanMedium in customer.tanMedia { for tanMedium in bank.tanMedia {
if let mappedTanMedium = mappedCustomer.tanMedia?.first { ($0 as! PersistedTanMedium).displayName == tanMedium.displayName } as? PersistedTanMedium { if let mappedTanMedium = mappedBank.tanMedia?.first { ($0 as! PersistedTanMedium).displayName == tanMedium.displayName } as? PersistedTanMedium {
tanMedium.technicalId = mappedTanMedium.objectIDAsString tanMedium.technicalId = mappedTanMedium.objectIDAsString
} }
} }
} }
func readPersistedAccounts_() -> [ICustomer] { func readPersistedBanks_() -> [IBankData] {
var customers: [PersistedCustomer] = [] var banks: [PersistedBankData] = []
do { do {
let request: NSFetchRequest<PersistedCustomer> = PersistedCustomer.fetchRequest() let request: NSFetchRequest<PersistedBankData> = PersistedBankData.fetchRequest()
request.returnsObjectsAsFaults = false request.returnsObjectsAsFaults = false
try customers = context.fetch(request) try banks = context.fetch(request)
} catch { } catch {
NSLog("Could not request Customers: \(error)") NSLog("Could not request banks: \(error)")
} }
return customers.map( { mapper.map($0) } ) return banks.map( { mapper.map($0) } )
} }
func deleteAccount(customer: ICustomer, allCustomers: [ICustomer]) { func deleteBank(bank: IBankData, allBanks: [IBankData]) {
do { do {
let mapped = mapper.map(customer, context) let mapped = mapper.map(bank, context)
context.delete(mapped) context.delete(mapped)
try context.save() try context.save()
} catch { } catch {
NSLog("Could not delete Customer \(customer): \(error)") NSLog("Could not delete Bank \(bank): \(error)")
} }
} }
func saveOrUpdateAccountTransactions(bankAccount: IBankAccount, transactions: [IAccountTransaction]) { func saveOrUpdateAccountTransactions(account: IBankAccount, transactions: [IAccountTransaction]) {
if let persistedAccount = context.objectByID(bankAccount.technicalId) as? PersistedBankAccount { if let persistedAccount = context.objectByID(account.technicalId) as? PersistedBankAccount {
for transaction in transactions { for transaction in transactions {
if transaction.technicalId.isCoreDataId == false { // TODO: or also update already persisted transactions? if transaction.technicalId.isCoreDataId == false { // TODO: or also update already persisted transactions?
do { do {
@ -96,7 +96,7 @@ class CoreDataBankingPersistence: IBankingPersistence, IRemitteeSearcher {
transaction.technicalId = mappedTransaction.objectIDAsString transaction.technicalId = mappedTransaction.objectIDAsString
} catch { } catch {
NSLog("Could not save transaction \(transaction.buildTransactionIdentifier()) of account \(bankAccount.displayName): \(error)") NSLog("Could not save transaction \(transaction.buildTransactionIdentifier()) of account \(account.displayName): \(error)")
} }
} }
} }
@ -137,7 +137,7 @@ class CoreDataBankingPersistence: IBankingPersistence, IRemitteeSearcher {
try transactions = context.fetch(request) try transactions = context.fetch(request)
} catch { } catch {
NSLog("Could not request Customers: \(error)") NSLog("Could not request banks: \(error)")
} }
let remittees = transactions let remittees = transactions
@ -152,13 +152,13 @@ class CoreDataBankingPersistence: IBankingPersistence, IRemitteeSearcher {
func deleteAll() { func deleteAll() {
do { do {
let request = NSFetchRequest<NSFetchRequestResult>(entityName: "PersistedCustomer") let request = NSFetchRequest<NSFetchRequestResult>(entityName: "PersistedBankData")
let deleteAll = NSBatchDeleteRequest(fetchRequest: request) let deleteAll = NSBatchDeleteRequest(fetchRequest: request)
try context.execute(deleteAll) try context.execute(deleteAll)
} catch { } catch {
NSLog("Could not delete all Customers: \(error)") NSLog("Could not delete all banks: \(error)")
} }
} }

View File

@ -6,15 +6,15 @@ class EnterTanState : Identifiable {
let id: Foundation.UUID = UUID() let id: Foundation.UUID = UUID()
let customer: ICustomer let bank: IBankData
let tanChallenge: TanChallenge let tanChallenge: TanChallenge
let callback: (EnterTanResult) -> Void let callback: (EnterTanResult) -> Void
init(_ customer: ICustomer, _ tanChallenge: TanChallenge, _ callback: @escaping (EnterTanResult) -> Void) { init(_ bank: IBankData, _ tanChallenge: TanChallenge, _ callback: @escaping (EnterTanResult) -> Void) {
self.customer = customer self.bank = bank
self.tanChallenge = tanChallenge self.tanChallenge = tanChallenge
self.callback = callback self.callback = callback
} }

View File

@ -5,60 +5,60 @@ import BankingUiSwift
class Mapper { class Mapper {
func map(_ customer: PersistedCustomer) -> ICustomer { func map(_ bank: PersistedBankData) -> IBankData {
let mapped = Customer(bankCode: map(customer.bankCode), customerId: map(customer.customerId), password: map(customer.password), finTsServerAddress: map(customer.finTsServerAddress), bankName: map(customer.bankName), bic: map(customer.bic), customerName: map(customer.customerName), userId: map(customer.userId), iconUrl: customer.iconUrl, accounts: []) let mapped = BankData(bankCode: map(bank.bankCode), customerId: map(bank.customerId), password: map(bank.password), finTsServerAddress: map(bank.finTsServerAddress), bankName: map(bank.bankName), bic: map(bank.bic), customerName: map(bank.customerName), userId: map(bank.userId), iconUrl: bank.iconUrl, accounts: [])
mapped.countDaysForWhichTransactionsAreKept = mapToInt(customer.countDaysForWhichTransactionsAreKept) mapped.countDaysForWhichTransactionsAreKept = mapToInt(bank.countDaysForWhichTransactionsAreKept)
mapped.userSetDisplayName = customer.userSetDisplayName mapped.userSetDisplayName = bank.userSetDisplayName
mapped.displayIndex = customer.displayIndex mapped.displayIndex = bank.displayIndex
mapped.accounts = map(mapped, customer.accounts?.array as? [PersistedBankAccount]) mapped.accounts = map(mapped, bank.accounts?.array as? [PersistedBankAccount])
mapped.supportedTanMethods = map(customer.supportedTanMethods?.array as? [PersistedTanMethod]) mapped.supportedTanMethods = map(bank.supportedTanMethods?.array as? [PersistedTanMethod])
mapped.selectedTanMethod = mapped.supportedTanMethods.first(where: { $0.bankInternalMethodCode == customer.selectedTanMethodCode }) mapped.selectedTanMethod = mapped.supportedTanMethods.first(where: { $0.bankInternalMethodCode == bank.selectedTanMethodCode })
mapped.tanMedia = map(customer.tanMedia?.array as? [PersistedTanMedium]) mapped.tanMedia = map(bank.tanMedia?.array as? [PersistedTanMedium])
mapped.technicalId = customer.objectIDAsString mapped.technicalId = bank.objectIDAsString
return mapped return mapped
} }
func map(_ customer: ICustomer, _ context: NSManagedObjectContext) -> PersistedCustomer { func map(_ bank: IBankData, _ context: NSManagedObjectContext) -> PersistedBankData {
let mapped = context.objectByID(customer.technicalId) ?? PersistedCustomer(context: context) let mapped = context.objectByID(bank.technicalId) ?? PersistedBankData(context: context)
mapped.bankCode = customer.bankCode mapped.bankCode = bank.bankCode
mapped.customerId = customer.customerId mapped.customerId = bank.customerId
mapped.password = customer.password mapped.password = bank.password
mapped.finTsServerAddress = customer.finTsServerAddress mapped.finTsServerAddress = bank.finTsServerAddress
mapped.bankName = customer.bankName mapped.bankName = bank.bankName
mapped.bic = customer.bic mapped.bic = bank.bic
mapped.customerName = customer.customerName mapped.customerName = bank.customerName
mapped.userId = customer.userId mapped.userId = bank.userId
mapped.iconUrl = customer.iconUrl mapped.iconUrl = bank.iconUrl
mapped.countDaysForWhichTransactionsAreKept = mapFromInt(customer.countDaysForWhichTransactionsAreKept) mapped.countDaysForWhichTransactionsAreKept = mapFromInt(bank.countDaysForWhichTransactionsAreKept)
mapped.userSetDisplayName = customer.userSetDisplayName mapped.userSetDisplayName = bank.userSetDisplayName
mapped.displayIndex = customer.displayIndex mapped.displayIndex = bank.displayIndex
mapped.accounts = NSOrderedSet(array: map(mapped, customer.accounts, context)) mapped.accounts = NSOrderedSet(array: map(mapped, bank.accounts, context))
mapped.supportedTanMethods = NSOrderedSet(array: map(customer.supportedTanMethods, context)) mapped.supportedTanMethods = NSOrderedSet(array: map(bank.supportedTanMethods, context))
mapped.selectedTanMethodCode = customer.selectedTanMethod?.bankInternalMethodCode mapped.selectedTanMethodCode = bank.selectedTanMethod?.bankInternalMethodCode
mapped.tanMedia = NSOrderedSet(array: map(customer.tanMedia, context)) mapped.tanMedia = NSOrderedSet(array: map(bank.tanMedia, context))
return mapped return mapped
} }
func map(_ customer: ICustomer, _ accounts: [PersistedBankAccount]?) -> [IBankAccount] { func map(_ bank: IBankData, _ accounts: [PersistedBankAccount]?) -> [IBankAccount] {
return accounts?.map( { map(customer, $0) } ) ?? [] return accounts?.map( { map(bank, $0) } ) ?? []
} }
func map(_ customer: ICustomer, _ account: PersistedBankAccount) -> IBankAccount { func map(_ bank: IBankData, _ account: PersistedBankAccount) -> IBankAccount {
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, retrievedTransactionsFromOn: map(account.retrievedTransactionsFromOn), retrievedTransactionsUpTo: map(account.retrievedTransactionsUpTo), supportsRetrievingAccountTransactions: account.supportsRetrievingAccountTransactions, supportsRetrievingBalance: account.supportsRetrievingBalance, supportsTransferringMoney: account.supportsTransferringMoney, supportsInstantPaymentMoneyTransfer: account.supportsInstantPaymentMoneyTransfer, bookedTransactions: [], unbookedTransactions: []) let mapped = BankAccount(bank: bank, 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, retrievedTransactionsFromOn: map(account.retrievedTransactionsFromOn), retrievedTransactionsUpTo: map(account.retrievedTransactionsUpTo), 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.isAccountTypeSupported = account.isAccountTypeSupported
@ -73,14 +73,14 @@ class Mapper {
return mapped return mapped
} }
func map(_ customer: PersistedCustomer, _ accounts: [IBankAccount], _ context: NSManagedObjectContext) -> [PersistedBankAccount] { func map(_ bank: PersistedBankData, _ accounts: [IBankAccount], _ context: NSManagedObjectContext) -> [PersistedBankAccount] {
return accounts.map( { map(customer, $0, context) } ) return accounts.map( { map(bank, $0, context) } )
} }
func map(_ customer: PersistedCustomer, _ account: IBankAccount, _ context: NSManagedObjectContext) -> PersistedBankAccount { func map(_ bank: PersistedBankData, _ account: IBankAccount, _ context: NSManagedObjectContext) -> PersistedBankAccount {
let mapped = context.objectByID(account.technicalId) ?? PersistedBankAccount(context: context) let mapped = context.objectByID(account.technicalId) ?? PersistedBankAccount(context: context)
mapped.customer = customer mapped.bank = bank
mapped.identifier = account.identifier mapped.identifier = account.identifier
mapped.accountHolderName = account.accountHolderName mapped.accountHolderName = account.accountHolderName
mapped.iban = account.iban mapped.iban = account.iban
@ -147,7 +147,7 @@ class Mapper {
} }
func map(_ account: IBankAccount, _ transaction: PersistedAccountTransaction) -> IAccountTransaction { func map(_ account: IBankAccount, _ transaction: PersistedAccountTransaction) -> IAccountTransaction {
let mapped = AccountTransaction(bankAccount: account, amount: map(transaction.amount), currency: map(transaction.currency), unparsedUsage: map(transaction.unparsedUsage), bookingDate: map(transaction.bookingDate), otherPartyName: transaction.otherPartyName, otherPartyBankCode: transaction.otherPartyBankCode, otherPartyAccountId: transaction.otherPartyAccountId, bookingText: transaction.bookingText, valueDate: map(transaction.valueDate), statementNumber: Int32(transaction.statementNumber), sequenceNumber: map(transaction.sequenceNumber), openingBalance: map(transaction.openingBalance), closingBalance: map(transaction.closingBalance), endToEndReference: transaction.endToEndReference, customerReference: transaction.customerReference, mandateReference: transaction.mandateReference, creditorIdentifier: transaction.creditorIdentifier, originatorsIdentificationCode: transaction.originatorsIdentificationCode, compensationAmount: transaction.compensationAmount, originalAmount: transaction.originalAmount, sepaUsage: transaction.sepaUsage, deviantOriginator: transaction.deviantOriginator, deviantRecipient: transaction.deviantRecipient, usageWithNoSpecialType: transaction.usageWithNoSpecialType, primaNotaNumber: transaction.primaNotaNumber, textKeySupplement: transaction.textKeySupplement, currencyType: transaction.currencyType, bookingKey: map(transaction.bookingKey), referenceForTheAccountOwner: map(transaction.referenceForTheAccountOwner), referenceOfTheAccountServicingInstitution: transaction.referenceOfTheAccountServicingInstitution, supplementaryDetails: transaction.supplementaryDetails, transactionReferenceNumber: map(transaction.transactionReferenceNumber), relatedReferenceNumber: transaction.relatedReferenceNumber) let mapped = AccountTransaction(account: account, amount: map(transaction.amount), currency: map(transaction.currency), unparsedUsage: map(transaction.unparsedUsage), bookingDate: map(transaction.bookingDate), otherPartyName: transaction.otherPartyName, otherPartyBankCode: transaction.otherPartyBankCode, otherPartyAccountId: transaction.otherPartyAccountId, bookingText: transaction.bookingText, valueDate: map(transaction.valueDate), statementNumber: Int32(transaction.statementNumber), sequenceNumber: map(transaction.sequenceNumber), openingBalance: map(transaction.openingBalance), closingBalance: map(transaction.closingBalance), endToEndReference: transaction.endToEndReference, customerReference: transaction.customerReference, mandateReference: transaction.mandateReference, creditorIdentifier: transaction.creditorIdentifier, originatorsIdentificationCode: transaction.originatorsIdentificationCode, compensationAmount: transaction.compensationAmount, originalAmount: transaction.originalAmount, sepaUsage: transaction.sepaUsage, deviantOriginator: transaction.deviantOriginator, deviantRecipient: transaction.deviantRecipient, usageWithNoSpecialType: transaction.usageWithNoSpecialType, primaNotaNumber: transaction.primaNotaNumber, textKeySupplement: transaction.textKeySupplement, currencyType: transaction.currencyType, bookingKey: map(transaction.bookingKey), referenceForTheAccountOwner: map(transaction.referenceForTheAccountOwner), referenceOfTheAccountServicingInstitution: transaction.referenceOfTheAccountServicingInstitution, supplementaryDetails: transaction.supplementaryDetails, transactionReferenceNumber: map(transaction.transactionReferenceNumber), relatedReferenceNumber: transaction.relatedReferenceNumber)
mapped.technicalId = transaction.objectIDAsString mapped.technicalId = transaction.objectIDAsString

View File

@ -11,7 +11,7 @@ extension Message {
secondaryButton: .cancel()) secondaryButton: .cancel())
} }
static func createAskUserToDeleteAccountMessage(_ bank: ICustomer, _ deleteAccount: @escaping (ICustomer) -> Void) -> Message { static func createAskUserToDeleteAccountMessage(_ bank: IBankData, _ deleteAccount: @escaping (IBankData) -> Void) -> Message {
return Message(title: Text("Really delete account '\(bank.displayName)'?"), return Message(title: Text("Really delete account '\(bank.displayName)'?"),
message: Text("All data for this account will be permanently deleted locally."), message: Text("All data for this account will be permanently deleted locally."),
primaryButton: .destructive(Text("Delete"), action: { deleteAccount(bank) } ), primaryButton: .destructive(Text("Delete"), action: { deleteAccount(bank) } ),

View File

@ -9,8 +9,8 @@ class SwiftUiRouter : IRouter {
} }
func getTanFromUserFromNonUiThread(customer: ICustomer, tanChallenge: TanChallenge, presenter: BankingPresenter, callback: @escaping (EnterTanResult) -> Void) { func getTanFromUserFromNonUiThread(bank: IBankData, tanChallenge: TanChallenge, presenter: BankingPresenter, callback: @escaping (EnterTanResult) -> Void) {
let enterTanState = EnterTanState(customer, tanChallenge, callback) let enterTanState = EnterTanState(bank, tanChallenge, callback)
SceneDelegate.navigateToView(EnterTanDialog(enterTanState)) SceneDelegate.navigateToView(EnterTanDialog(enterTanState))
} }

View File

@ -53,22 +53,22 @@ struct AccountTransactionsDialog: View {
@Inject private var presenter: BankingPresenterSwift @Inject private var presenter: BankingPresenterSwift
init(allBanks: [ICustomer]) { init(allBanks: [IBankData]) {
self.init("All accounts", true) self.init("All accounts", true)
presenter.selectedAllBankAccounts() presenter.selectedAllAccounts()
} }
init(bank: ICustomer) { init(bank: IBankData) {
self.init(bank.displayName, false) self.init(bank.displayName, false)
presenter.selectedAccount(customer: bank) presenter.selectedBank(bank: bank)
} }
init(account: IBankAccount) { init(account: IBankAccount) {
self.init(account.displayName, false) self.init(account.displayName, false)
presenter.selectedBankAccount(bankAccount: account) presenter.selectedAccount(account: account)
} }
fileprivate init(_ title: String, _ showBankIcons: Bool) { fileprivate init(_ title: String, _ showBankIcons: Bool) {
@ -168,7 +168,7 @@ struct AccountTransactionsDialog: View {
private func setInitialValues() { private func setInitialValues() {
self.balanceOfAllTransactions = self.presenter.balanceOfSelectedBankAccounts self.balanceOfAllTransactions = self.presenter.balanceOfSelectedAccounts
self.filterTransactions("") self.filterTransactions("")
@ -176,10 +176,10 @@ struct AccountTransactionsDialog: View {
} }
private func setTransactionsView() { private func setTransactionsView() {
let transactionsRetrievalState = presenter.selectedBankAccountsTransactionRetrievalState let transactionsRetrievalState = presenter.selectedAccountsTransactionRetrievalState
self.haveTransactionsBeenRetrievedForSelectedAccounts = transactionsRetrievalState == .retrievedtransactions self.haveTransactionsBeenRetrievedForSelectedAccounts = transactionsRetrievalState == .retrievedtransactions
self.accountsForWhichNotAllTransactionsHaveBeenFetched = presenter.selectedBankAccountsForWhichNotAllTransactionsHaveBeenFetched self.accountsForWhichNotAllTransactionsHaveBeenFetched = presenter.selectedAccountsForWhichNotAllTransactionsHaveBeenFetched
self.haveAllTransactionsBeenFetched = self.accountsForWhichNotAllTransactionsHaveBeenFetched.isEmpty self.haveAllTransactionsBeenFetched = self.accountsForWhichNotAllTransactionsHaveBeenFetched.isEmpty
self.showFetchAllTransactionsOverlay = shouldShowFetchAllTransactionsOverlay && haveTransactionsBeenRetrievedForSelectedAccounts self.showFetchAllTransactionsOverlay = shouldShowFetchAllTransactionsOverlay && haveTransactionsBeenRetrievedForSelectedAccounts
@ -199,10 +199,10 @@ struct AccountTransactionsDialog: View {
private func updateTransactions(_ executingDone: @escaping () -> Void) { private func updateTransactions(_ executingDone: @escaping () -> Void) {
presenter.updateSelectedBankAccountTransactionsAsync { response in presenter.updateSelectedAccountsTransactionsAsync { response in
executingDone() executingDone()
self.balanceOfAllTransactions = self.presenter.balanceOfSelectedBankAccounts self.balanceOfAllTransactions = self.presenter.balanceOfSelectedAccounts
if response.successful { if response.successful {
self.filterTransactions(self.searchText) self.filterTransactions(self.searchText)
@ -216,9 +216,9 @@ struct AccountTransactionsDialog: View {
} }
private func fetchTransactions() { private func fetchTransactions() {
for account in presenter.selectedBankAccounts { for account in presenter.selectedAccounts {
if account.haveAllTransactionsBeenFetched { if account.haveAllTransactionsBeenFetched {
presenter.updateBankAccountTransactionsAsync(bankAccount: account, abortIfTanIsRequired: false, callback: self.handleGetTransactionsResult) presenter.updateAccountTransactionsAsync(account: account, abortIfTanIsRequired: false, callback: self.handleGetTransactionsResult)
} }
else { else {
presenter.fetchAllAccountTransactionsAsync(account: account, callback: self.handleGetTransactionsResult) presenter.fetchAllAccountTransactionsAsync(account: account, callback: self.handleGetTransactionsResult)
@ -250,7 +250,7 @@ struct AccountTransactionsDialog: View {
return "No transactions fetched yet" return "No transactions fetched yet"
} }
else if state == .notransactionsinretrievedperiod { else if state == .notransactionsinretrievedperiod {
let account = presenter.selectedBankAccounts.first! let account = presenter.selectedAccounts.first!
return "There haven't been any transactions in retrieved period from \(mapDate(account.retrievedTransactionsFromOn)) - \(mapDate(account.retrievedTransactionsUpTo))" return "There haven't been any transactions in retrieved period from \(mapDate(account.retrievedTransactionsFromOn)) - \(mapDate(account.retrievedTransactionsUpTo))"
} }
else if state == .accountdoesnotsupportfetchingtransactions { else if state == .accountdoesnotsupportfetchingtransactions {

View File

@ -99,7 +99,7 @@ struct AddAccountDialog: View {
self.closeDialog() self.closeDialog()
let authenticationService = AuthenticationService() let authenticationService = AuthenticationService()
if self.presenter.customers.count == 1 && authenticationService.authenticationType == .unset { if self.presenter.allBanks.count == 1 && authenticationService.authenticationType == .unset {
authenticationService.setAuthenticationType(.none) authenticationService.setAuthenticationType(.none)
UIAlert("Secure data?", "Secure data with?", UIAlert("Secure data?", "Secure data with?",

View File

@ -10,7 +10,7 @@ struct BankSettingsDialog: View {
@Inject private var presenter: BankingPresenterSwift @Inject private var presenter: BankingPresenterSwift
private let bank: ICustomer private let bank: IBankData
@State private var displayName: String @State private var displayName: String
@ -32,7 +32,7 @@ struct BankSettingsDialog: View {
} }
init(_ bank: ICustomer) { init(_ bank: IBankData) {
self.bank = bank self.bank = bank
_displayName = State(initialValue: bank.displayName) _displayName = State(initialValue: bank.displayName)
@ -102,7 +102,7 @@ struct BankSettingsDialog: View {
func reorderAccounts(from source: IndexSet, to destination: Int) { func reorderAccounts(from source: IndexSet, to destination: Int) {
accountsSorted = accountsSorted.reorder(from: source, to: destination) accountsSorted = accountsSorted.reorder(from: source, to: destination)
presenter.accountDisplayIndexUpdated(account: bank) presenter.bankDisplayIndexUpdated(bank: bank)
} }
@ -110,8 +110,8 @@ struct BankSettingsDialog: View {
self.askUserToDeleteAccountOrSaveChangesMessage = Message.createAskUserToDeleteAccountMessage(bank, self.deleteAccount) self.askUserToDeleteAccountOrSaveChangesMessage = Message.createAskUserToDeleteAccountMessage(bank, self.deleteAccount)
} }
func deleteAccount(bank: ICustomer) { func deleteAccount(bank: IBankData) {
presenter.deleteAccount(customer: bank) presenter.deleteAccount(bank: bank)
closeDialog() closeDialog()
} }
@ -135,7 +135,7 @@ struct BankSettingsDialog: View {
bank.selectedTanMethod = selectedTanMethod bank.selectedTanMethod = selectedTanMethod
presenter.accountUpdated(bank: bank) presenter.bankUpdated(bank: bank)
} }
closeDialog() closeDialog()

View File

@ -11,7 +11,7 @@ struct EnterTanDialog: View {
private var tanChallenge: TanChallenge private var tanChallenge: TanChallenge
private var customer: ICustomer private var bank: IBankData
private var customersTanMedia: [TanMedium] = [] private var customersTanMedia: [TanMedium] = []
@ -50,9 +50,9 @@ struct EnterTanDialog: View {
self.state = state self.state = state
self.tanChallenge = state.tanChallenge self.tanChallenge = state.tanChallenge
self.customer = state.customer self.bank = state.bank
self.customersTanMedia = customer.tanMediaSorted self.customersTanMedia = bank.tanMediaSorted
self.showSelectTanMediumView = self.customersTanMedia.count > 1 // TODO: use isOpticalTanMethod && tanMedia.count > 1 self.showSelectTanMediumView = self.customersTanMedia.count > 1 // TODO: use isOpticalTanMethod && tanMedia.count > 1
@ -73,7 +73,7 @@ struct EnterTanDialog: View {
var body: some View { var body: some View {
Form { Form {
Section { Section {
TanMethodPicker(customer, state.tanChallenge.tanMethod) { selectedTanMethod in TanMethodPicker(bank, state.tanChallenge.tanMethod) { selectedTanMethod in
self.selectedTanMethodChanged(selectedTanMethod) self.selectedTanMethodChanged(selectedTanMethod)
} }
@ -215,7 +215,7 @@ struct EnterTanDialog: View {
struct EnterTanDialog_Previews: PreviewProvider { struct EnterTanDialog_Previews: PreviewProvider {
static var previews: some View { static var previews: some View {
let customer = Customer(bankCode: "", customerId: "", password: "", finTsServerAddress: "") let customer = BankData(bankCode: "", customerId: "", password: "", finTsServerAddress: "")
customer.supportedTanMethods = previewTanMethods customer.supportedTanMethods = previewTanMethods
customer.tanMedia = previewTanMedia customer.tanMedia = previewTanMedia

View File

@ -58,7 +58,7 @@ struct SettingsDialog: View {
data.banksDisplayIndexChanged() data.banksDisplayIndexChanged()
presenter.allAccountsUpdated() presenter.allBanksUpdated()
} }
func deleteBanks(at offsets: IndexSet) { func deleteBanks(at offsets: IndexSet) {
@ -68,14 +68,14 @@ struct SettingsDialog: View {
} }
} }
func askUserToDeleteAccount(_ bankToDelete: ICustomer) { func askUserToDeleteAccount(_ bankToDelete: IBankData) {
self.askToDeleteAccountMessage = Message.createAskUserToDeleteAccountMessage(bankToDelete, self.deleteAccountWithSecurityChecks) self.askToDeleteAccountMessage = Message.createAskUserToDeleteAccountMessage(bankToDelete, self.deleteAccountWithSecurityChecks)
} }
func deleteAccountWithSecurityChecks(_ bankToDelete: ICustomer) { func deleteAccountWithSecurityChecks(_ bankToDelete: IBankData) {
// don't know why but when deleting last bank application crashes if we don't delete bank async // don't know why but when deleting last bank application crashes if we don't delete bank async
DispatchQueue.main.async { DispatchQueue.main.async {
if self.presenter.customers.count == 1 { if self.presenter.allBanks.count == 1 {
self.editMode?.wrappedValue = .inactive self.editMode?.wrappedValue = .inactive
DispatchQueue.main.asyncAfter(deadline: .now() + 1) { DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
@ -88,8 +88,8 @@ struct SettingsDialog: View {
} }
} }
private func deleteAccount(_ bankToDelete: ICustomer) { private func deleteAccount(_ bankToDelete: IBankData) {
self.presenter.deleteAccount(customer: bankToDelete) self.presenter.deleteAccount(bank: bankToDelete)
} }

View File

@ -71,7 +71,7 @@ struct TransferMoneyDialog: View {
init() { init() {
self.accountsSupportingTransferringMoney = self.presenter.bankAccounts.filter({ $0.supportsTransferringMoney }) self.accountsSupportingTransferringMoney = self.presenter.accountsSupportingTransferringMoney
self.showAccounts = self.accountsSupportingTransferringMoney.count > 1 self.showAccounts = self.accountsSupportingTransferringMoney.count > 1
} }

View File

@ -13,12 +13,12 @@ struct IconedTitleView: View {
private var titleFont: Font? private var titleFont: Font?
init(_ bank: ICustomer, titleFont: Font? = nil) { init(_ bank: IBankData, titleFont: Font? = nil) {
self.init(accountTitle: bank.displayName, iconUrl: bank.iconUrl, defaultIconName: Styles.AccountFallbackIcon, titleFont: titleFont) self.init(accountTitle: bank.displayName, iconUrl: bank.iconUrl, defaultIconName: Styles.AccountFallbackIcon, titleFont: titleFont)
} }
init(_ account: IBankAccount, titleFont: Font? = nil) { init(_ account: IBankAccount, titleFont: Font? = nil) {
self.init(accountTitle: account.displayName, iconUrl: account.customer.iconUrl, defaultIconName: Styles.AccountFallbackIcon, titleFont: titleFont) self.init(accountTitle: account.displayName, iconUrl: account.bank.iconUrl, defaultIconName: Styles.AccountFallbackIcon, titleFont: titleFont)
} }
init(accountTitle: String, iconUrl: String?, defaultIconName: String, titleFont: Font? = nil) { init(accountTitle: String, iconUrl: String?, defaultIconName: String, titleFont: Font? = nil) {

View File

@ -47,7 +47,7 @@ struct AccountTransactionListItem: View {
VStack(alignment: .trailing) { VStack(alignment: .trailing) {
if areMoreThanOneBanksTransactionsDisplayed { if areMoreThanOneBanksTransactionsDisplayed {
IconView(iconUrl: transaction.bankAccount.customer.iconUrl, defaultIconName: Styles.AccountFallbackIcon) IconView(iconUrl: transaction.account.bank.iconUrl, defaultIconName: Styles.AccountFallbackIcon)
Spacer() Spacer()
} }
@ -98,6 +98,6 @@ struct AccountTransactionListItem: View {
struct AccountTransactionListItem_Previews: PreviewProvider { struct AccountTransactionListItem_Previews: PreviewProvider {
static var previews: some View { static var previews: some View {
AccountTransactionListItem(AccountTransaction(bankAccount: previewBanks[0].accounts[0] as! BankAccount, otherPartyName: "Marieke Musterfrau", unparsedUsage: "Vielen Dank für Ihre Mühen", amount: CommonBigDecimal(double: 1234.56), valueDate: CommonDate(year: 2020, month: .march, day_: 27), bookingText: "SEPA Überweisung"), false) AccountTransactionListItem(AccountTransaction(account: previewBanks[0].accounts[0] as! BankAccount, otherPartyName: "Marieke Musterfrau", unparsedUsage: "Vielen Dank für Ihre Mühen", amount: CommonBigDecimal(double: 1234.56), valueDate: CommonDate(year: 2020, month: .march, day_: 27), bookingText: "SEPA Überweisung"), false)
} }
} }

View File

@ -4,7 +4,7 @@ import BankingUiSwift
struct AllBanksListItem: View { struct AllBanksListItem: View {
let banks: [ICustomer] let banks: [IBankData]
@State private var navigateToAccountTransactionsDialog = false @State private var navigateToAccountTransactionsDialog = false

View File

@ -4,7 +4,7 @@ import BankingUiSwift
struct BankListItem : View { struct BankListItem : View {
let bank: ICustomer let bank: IBankData
@State private var navigateToAccountTransactionsDialog = false @State private var navigateToAccountTransactionsDialog = false
@ -70,8 +70,8 @@ struct BankListItem : View {
).show() ).show()
} }
private func deleteAccount(_ bank: ICustomer) { private func deleteAccount(_ bank: IBankData) {
presenter.deleteAccount(customer: bank) presenter.deleteAccount(bank: bank)
} }
} }

View File

@ -4,7 +4,7 @@ import BankingUiSwift
struct TanMethodPicker: View { struct TanMethodPicker: View {
private let bank: ICustomer private let bank: IBankData
private let selectedTanMethodChanged: (TanMethod) -> Void private let selectedTanMethodChanged: (TanMethod) -> Void
@ -27,7 +27,7 @@ struct TanMethodPicker: View {
} }
init(_ bank: ICustomer, _ initiallySelectedTanMethod: TanMethod? = nil, selectedTanMethodChanged: @escaping (TanMethod) -> Void) { init(_ bank: IBankData, _ initiallySelectedTanMethod: TanMethod? = nil, selectedTanMethodChanged: @escaping (TanMethod) -> Void) {
self.bank = bank self.bank = bank
self.selectedTanMethodChanged = selectedTanMethodChanged self.selectedTanMethodChanged = selectedTanMethodChanged

View File

@ -16,6 +16,7 @@ import net.dankito.banking.fints.util.PureKotlinBase64Service
import net.dankito.banking.fints.webclient.IWebClient import net.dankito.banking.fints.webclient.IWebClient
import net.dankito.banking.fints.webclient.KtorWebClient import net.dankito.banking.fints.webclient.KtorWebClient
import net.dankito.banking.extensions.toMoney import net.dankito.banking.extensions.toMoney
import net.dankito.banking.fints.model.BankData
import net.dankito.banking.fints.response.client.FinTsClientResponse import net.dankito.banking.fints.response.client.FinTsClientResponse
import net.dankito.banking.ui.model.* import net.dankito.banking.ui.model.*
import net.dankito.banking.ui.model.MessageLogEntry import net.dankito.banking.ui.model.MessageLogEntry
@ -26,7 +27,7 @@ import net.dankito.utils.multiplatform.log.LoggerFactory
open class fints4kBankingClient( open class fints4kBankingClient(
protected val customer: TypedCustomer, protected val bank: TypedBankData,
protected val modelCreator: IModelCreator, protected val modelCreator: IModelCreator,
protected val dataFolder: File, protected val dataFolder: File,
protected val serializer: ISerializer, protected val serializer: ISerializer,
@ -48,14 +49,14 @@ open class fints4kBankingClient(
protected var didTryToGetAccountDataFromBank = false protected var didTryToGetAccountDataFromBank = false
protected val bank = restoreDataOrMapFromUiModel(customer) protected val fintsBank = restoreDataOrMapFromUiModel(bank)
protected open val client = FinTsClientForCustomer(bank, createFinTsClientCallback(callback), webClient, base64Service) protected open val client = FinTsClientForCustomer(fintsBank, createFinTsClientCallback(callback), webClient, base64Service)
override val messageLogWithoutSensitiveData: List<MessageLogEntry> override val messageLogWithoutSensitiveData: List<MessageLogEntry>
get() = client.messageLogWithoutSensitiveData.map { MessageLogEntry(it.message, it.time, customer) } get() = client.messageLogWithoutSensitiveData.map { MessageLogEntry(it.message, it.time, bank) }
override fun addAccountAsync(callback: (AddAccountResponse) -> Unit) { override fun addAccountAsync(callback: (AddAccountResponse) -> Unit) {
@ -66,8 +67,8 @@ open class fints4kBankingClient(
protected open fun handleAddAccountResponse(response: net.dankito.banking.fints.response.client.AddAccountResponse, protected open fun handleAddAccountResponse(response: net.dankito.banking.fints.response.client.AddAccountResponse,
callback: (AddAccountResponse) -> Unit) { callback: (AddAccountResponse) -> Unit) {
mapper.mapBank(customer, bank) mapper.mapBank(bank, fintsBank)
val mappedResponse = mapper.mapResponse(customer, response) val mappedResponse = mapper.mapResponse(bank, response)
saveData() saveData()
@ -76,33 +77,33 @@ open class fints4kBankingClient(
override fun getTransactionsAsync(parameter: GetTransactionsParameter, callback: (GetTransactionsResponse) -> Unit) { override fun getTransactionsAsync(parameter: GetTransactionsParameter, callback: (GetTransactionsResponse) -> Unit) {
val bankAccount = parameter.account val account = parameter.account
findAccountForBankAccount(bankAccount) { account, errorMessage -> findAccountForAccount(account) { accountData, errorMessage ->
if (account == null) { if (accountData == null) {
callback(GetTransactionsResponse(bankAccount, errorMessage ?: "")) callback(GetTransactionsResponse(account, errorMessage ?: ""))
} }
else { else {
val mappedParameter = GetTransactionsParameter(account, parameter.alsoRetrieveBalance, parameter.fromDate, val mappedParameter = GetTransactionsParameter(accountData, parameter.alsoRetrieveBalance, parameter.fromDate,
parameter.toDate, null, parameter.abortIfTanIsRequired) { parameter.toDate, null, parameter.abortIfTanIsRequired) {
parameter.retrievedChunkListener?.invoke(mapper.mapTransactions(bankAccount, it)) parameter.retrievedChunkListener?.invoke(mapper.mapTransactions(account, it))
} }
doGetTransactionsAsync(mappedParameter, bankAccount, callback) doGetTransactionsAsync(mappedParameter, account, callback)
} }
} }
} }
protected open fun doGetTransactionsAsync(parameter: net.dankito.banking.fints.model.GetTransactionsParameter, protected open fun doGetTransactionsAsync(parameter: net.dankito.banking.fints.model.GetTransactionsParameter,
bankAccount: TypedBankAccount, callback: (GetTransactionsResponse) -> Unit) { account: TypedBankAccount, callback: (GetTransactionsResponse) -> Unit) {
client.getTransactionsAsync(parameter) { response -> client.getTransactionsAsync(parameter) { response ->
handleGetTransactionsResponse(bankAccount, response, callback) handleGetTransactionsResponse(account, response, callback)
} }
} }
protected open fun handleGetTransactionsResponse(bankAccount: TypedBankAccount, response: net.dankito.banking.fints.response.client.GetTransactionsResponse, protected open fun handleGetTransactionsResponse(account: TypedBankAccount, response: net.dankito.banking.fints.response.client.GetTransactionsResponse,
callback: (GetTransactionsResponse) -> Unit) { callback: (GetTransactionsResponse) -> Unit) {
val mappedResponse = mapper.mapResponse(bankAccount, response) val mappedResponse = mapper.mapResponse(account, response)
saveData() saveData()
@ -111,7 +112,7 @@ open class fints4kBankingClient(
override fun transferMoneyAsync(data: TransferMoneyData, callback: (BankingClientResponse) -> Unit) { override fun transferMoneyAsync(data: TransferMoneyData, callback: (BankingClientResponse) -> Unit) {
findAccountForBankAccount(data.account) { account, errorMessage -> findAccountForAccount(data.account) { account, errorMessage ->
if (account == null) { if (account == null) {
callback(BankingClientResponse(false, errorMessage)) callback(BankingClientResponse(false, errorMessage))
} }
@ -136,19 +137,19 @@ open class fints4kBankingClient(
} }
override fun dataChanged(customer: TypedCustomer) { override fun dataChanged(bank: TypedBankData) {
mapper.mapChangesFromUiToClientModel(customer, bank) mapper.mapChangesFromUiToClientModel(bank, this.fintsBank)
} }
override fun deletedAccount(customer: TypedCustomer, wasLastAccountWithThisCredentials: Boolean) { override fun deletedBank(bank: TypedBankData, wasLastAccountWithThisCredentials: Boolean) {
if (wasLastAccountWithThisCredentials) { if (wasLastAccountWithThisCredentials) {
getFints4kClientDataFile(customer).delete() getFints4kClientDataFile(bank).delete()
} }
} }
protected open fun findAccountForBankAccount(bankAccount: TypedBankAccount, findAccountResult: (AccountData?, error: String?) -> Unit) { protected open fun findAccountForAccount(account: TypedBankAccount, findAccountResult: (AccountData?, error: String?) -> Unit) {
val mappedAccount = mapper.findAccountForBankAccount(bank, bankAccount) val mappedAccount = mapper.findMatchingAccount(fintsBank, account)
if (mappedAccount != null) { if (mappedAccount != null) {
findAccountResult(mappedAccount, null) findAccountResult(mappedAccount, null)
@ -157,37 +158,37 @@ open class fints4kBankingClient(
addAccountAsync { response -> addAccountAsync { response ->
didTryToGetAccountDataFromBank = !!! response.successful didTryToGetAccountDataFromBank = !!! response.successful
findAccountResult(mapper.findAccountForBankAccount(bank, bankAccount), findAccountResult(mapper.findMatchingAccount(fintsBank, account),
response.errorToShowToUser) response.errorToShowToUser)
} }
} }
else { else {
findAccountResult(null, "Cannot find account for ${bankAccount.identifier}") // TODO: translate findAccountResult(null, "Cannot find account for ${account.identifier}") // TODO: translate
} }
} }
protected open fun restoreDataOrMapFromUiModel(customer: TypedCustomer): BankData { protected open fun restoreDataOrMapFromUiModel(bank: TypedBankData): BankData {
if (isNewAccount(customer)) { if (isNewAccount(bank)) {
return mapToBankData(customer) return mapToBankData(bank)
} }
return restoreData(customer) ?: mapToBankData(customer) return restoreData(bank) ?: mapToBankData(bank)
} }
protected open fun isNewAccount(customer: TypedCustomer): Boolean { protected open fun isNewAccount(bank: TypedBankData): Boolean {
return customer.accounts.isEmpty() return bank.accounts.isEmpty()
} }
protected open fun mapToBankData(customer: TypedCustomer): BankData { protected open fun mapToBankData(bank: TypedBankData): BankData {
return BankData(customer.bankCode, customer.customerId, customer.password, customer.finTsServerAddress, customer.bic, customer.bankName) return BankData(bank.bankCode, bank.customerId, bank.password, bank.finTsServerAddress, bank.bic, bank.bankName)
} }
protected open fun restoreData(customer: TypedCustomer): BankData? { protected open fun restoreData(bank: TypedBankData): BankData? {
try { try {
return serializer.deserializeObject(getFints4kClientDataFile(customer), BankData::class) return serializer.deserializeObject(getFints4kClientDataFile(bank), BankData::class)
} catch (e: Exception) { } catch (e: Exception) {
log.warn(e) { "Could not deserialize bank data of $customer" } log.warn(e) { "Could not deserialize bank data of $bank" }
} }
return null return null
@ -195,16 +196,16 @@ open class fints4kBankingClient(
protected open fun saveData() { protected open fun saveData() {
try { try {
val clientDataFile = getFints4kClientDataFile(bank.bankCode, bank.customerId) val clientDataFile = getFints4kClientDataFile(fintsBank.bankCode, fintsBank.customerId)
serializer.serializeObject(bank, clientDataFile) serializer.serializeObject(fintsBank, clientDataFile)
} catch (e: Exception) { } catch (e: Exception) {
log.error("Could not save customer data for $bank", e) log.error("Could not save bank data for $fintsBank", e)
} }
} }
protected open fun getFints4kClientDataFile(customer: TypedCustomer): File { protected open fun getFints4kClientDataFile(bank: TypedBankData): File {
return getFints4kClientDataFile(customer.bankCode, customer.customerId) return getFints4kClientDataFile(bank.bankCode, bank.customerId)
} }
protected open fun getFints4kClientDataFile(bankCode: String, customerId: String): File { protected open fun getFints4kClientDataFile(bankCode: String, customerId: String): File {
@ -240,15 +241,15 @@ open class fints4kBankingClient(
} }
protected open fun handleEnterTan(bank: BankData, tanChallenge: TanChallenge, enterTanCallback: (EnterTanResult) -> Unit, clientCallback: BankingClientCallback) { protected open fun handleEnterTan(bank: BankData, tanChallenge: TanChallenge, enterTanCallback: (EnterTanResult) -> Unit, clientCallback: BankingClientCallback) {
mapper.updateTanMediaAndMethods(this@fints4kBankingClient.customer, bank) mapper.updateTanMediaAndMethods(this@fints4kBankingClient.bank, bank)
clientCallback.enterTan(this@fints4kBankingClient.customer, mapper.mapTanChallenge(tanChallenge)) { result -> clientCallback.enterTan(this@fints4kBankingClient.bank, mapper.mapTanChallenge(tanChallenge)) { result ->
enterTanCallback(mapper.mapEnterTanResult(result, bank)) enterTanCallback(mapper.mapEnterTanResult(result, bank))
} }
} }
protected open fun handleEnterTanGeneratorAtc(bank: BankData, tanMedium: TanGeneratorTanMedium, enterAtcCallback: (EnterTanGeneratorAtcResult) -> Unit, clientCallback: BankingClientCallback) { protected open fun handleEnterTanGeneratorAtc(bank: BankData, tanMedium: TanGeneratorTanMedium, enterAtcCallback: (EnterTanGeneratorAtcResult) -> Unit, clientCallback: BankingClientCallback) {
mapper.updateTanMediaAndMethods(this@fints4kBankingClient.customer, bank) mapper.updateTanMediaAndMethods(this@fints4kBankingClient.bank, bank)
clientCallback.enterTanGeneratorAtc(mapper.mapTanMedium(tanMedium)) { result -> clientCallback.enterTanGeneratorAtc(mapper.mapTanMedium(tanMedium)) { result ->
enterAtcCallback(mapper.mapEnterTanGeneratorAtcResult(result)) enterAtcCallback(mapper.mapEnterTanGeneratorAtcResult(result))

View File

@ -5,7 +5,7 @@ import net.dankito.banking.ui.IBankingClient
import net.dankito.banking.ui.IBankingClientCreator import net.dankito.banking.ui.IBankingClientCreator
import net.dankito.banking.fints.webclient.IWebClient import net.dankito.banking.fints.webclient.IWebClient
import net.dankito.banking.fints.webclient.KtorWebClient import net.dankito.banking.fints.webclient.KtorWebClient
import net.dankito.banking.ui.model.TypedCustomer import net.dankito.banking.ui.model.TypedBankData
import net.dankito.banking.ui.model.mapper.IModelCreator import net.dankito.banking.ui.model.mapper.IModelCreator
import net.dankito.banking.util.IAsyncRunner import net.dankito.banking.util.IAsyncRunner
import net.dankito.banking.util.ISerializer import net.dankito.banking.util.ISerializer
@ -19,13 +19,13 @@ open class fints4kBankingClientCreator(
) : IBankingClientCreator { ) : IBankingClientCreator {
override fun createClient( override fun createClient(
customer: TypedCustomer, bank: TypedBankData,
dataFolder: File, dataFolder: File,
asyncRunner: IAsyncRunner, asyncRunner: IAsyncRunner,
callback: BankingClientCallback callback: BankingClientCallback
): IBankingClient { ): IBankingClient {
return fints4kBankingClient(customer, modelCreator, dataFolder, serializer, webClient, callback = callback) return fints4kBankingClient(bank, modelCreator, dataFolder, serializer, webClient, callback = callback)
} }
} }

View File

@ -28,23 +28,23 @@ open class fints4kModelMapper(protected val modelCreator: IModelCreator) {
return BankingClientResponse(response.successful, mapErrorToShowToUser(response), response.userCancelledAction) return BankingClientResponse(response.successful, mapErrorToShowToUser(response), response.userCancelledAction)
} }
open fun mapResponse(customer: TypedCustomer, response: net.dankito.banking.fints.response.client.AddAccountResponse): AddAccountResponse { open fun mapResponse(bank: TypedBankData, response: net.dankito.banking.fints.response.client.AddAccountResponse): AddAccountResponse {
return AddAccountResponse(customer, map(customer, response.retrievedData), mapErrorToShowToUser(response), response.userCancelledAction) return AddAccountResponse(bank, map(bank, response.retrievedData), mapErrorToShowToUser(response), response.userCancelledAction)
} }
open fun mapResponse(bankAccount: TypedBankAccount, response: net.dankito.banking.fints.response.client.GetTransactionsResponse): GetTransactionsResponse { open fun mapResponse(account: TypedBankAccount, response: net.dankito.banking.fints.response.client.GetTransactionsResponse): GetTransactionsResponse {
return GetTransactionsResponse(map(bankAccount.customer as TypedCustomer, response.retrievedData), return GetTransactionsResponse(map(account.bank as TypedBankData, response.retrievedData),
mapErrorToShowToUser(response), response.userCancelledAction, response.tanRequiredButWeWereToldToAbortIfSo) mapErrorToShowToUser(response), response.userCancelledAction, response.tanRequiredButWeWereToldToAbortIfSo)
} }
open fun map(customer: TypedCustomer, retrievedData: List<net.dankito.banking.fints.model.RetrievedAccountData>): List<RetrievedAccountData> { open fun map(bank: TypedBankData, retrievedData: List<net.dankito.banking.fints.model.RetrievedAccountData>): List<RetrievedAccountData> {
return retrievedData.mapNotNull { map(customer, it) } return retrievedData.mapNotNull { map(bank, it) }
} }
open fun map(customer: TypedCustomer, retrievedData: net.dankito.banking.fints.model.RetrievedAccountData): RetrievedAccountData? { open fun map(bank: TypedBankData, retrievedData: net.dankito.banking.fints.model.RetrievedAccountData): RetrievedAccountData? {
val account = findMatchingBankAccount(customer, retrievedData.accountData) val account = findMatchingAccount(bank, retrievedData.accountData)
if (account == null) { if (account == null) {
log.error("No matching account found for ${retrievedData.accountData}. Has there an account been added we didn't map yet?") log.error("No matching account found for ${retrievedData.accountData}. Has there an account been added we didn't map yet?")
@ -70,41 +70,41 @@ open class fints4kModelMapper(protected val modelCreator: IModelCreator) {
} }
open fun mapBank(customer: TypedCustomer, bank: BankData) { open fun mapBank(bank: TypedBankData, fintsBank: BankData) {
customer.bankCode = bank.bankCode bank.bankCode = fintsBank.bankCode
customer.customerId = bank.customerId bank.customerId = fintsBank.customerId
customer.password = bank.pin bank.password = fintsBank.pin
customer.finTsServerAddress = bank.finTs3ServerAddress bank.finTsServerAddress = fintsBank.finTs3ServerAddress
customer.bankName = bank.bankName bank.bankName = fintsBank.bankName
customer.bic = bank.bic bank.bic = fintsBank.bic
customer.customerName = bank.customerName bank.customerName = fintsBank.customerName
customer.countDaysForWhichTransactionsAreKept = bank.countDaysForWhichTransactionsAreKept bank.countDaysForWhichTransactionsAreKept = fintsBank.countDaysForWhichTransactionsAreKept
customer.userId = bank.userId bank.userId = fintsBank.userId
customer.accounts = mapBankAccounts(customer, bank.accounts) bank.accounts = mapAccounts(bank, fintsBank.accounts)
updateTanMediaAndMethods(customer, bank) updateTanMediaAndMethods(bank, fintsBank)
} }
/** /**
* In UI only customerId, password, (bankCode,) and selected TAN method can be set * In UI only customerId, password, (bankCode,) and selected TAN method can be set
*/ */
open fun mapChangesFromUiToClientModel(customer: TypedCustomer, bank: BankData) { open fun mapChangesFromUiToClientModel(bank: TypedBankData, fintsBank: BankData) {
bank.customerId = customer.customerId fintsBank.customerId = bank.customerId
bank.pin = customer.password fintsBank.pin = bank.password
bank.bankCode = customer.bankCode fintsBank.bankCode = bank.bankCode
bank.selectedTanMethod = findTanMethod(bank, customer.selectedTanMethod) ?: bank.selectedTanMethod fintsBank.selectedTanMethod = findTanMethod(fintsBank, bank.selectedTanMethod) ?: fintsBank.selectedTanMethod
} }
open fun mapBankAccounts(customer: TypedCustomer, accountData: List<AccountData>): List<TypedBankAccount> { open fun mapAccounts(bank: TypedBankData, accountData: List<AccountData>): List<TypedBankAccount> {
return accountData.mapIndexed { index, account -> return accountData.mapIndexed { index, account ->
val mappedAccount = customer.accounts.firstOrNull { it.identifier == account.accountIdentifier } val mappedAccount = bank.accounts.firstOrNull { it.identifier == account.accountIdentifier }
?: modelCreator.createBankAccount(customer, account.productName, account.accountIdentifier) ?: modelCreator.createAccount(bank, account.productName, account.accountIdentifier)
mapBankAccount(mappedAccount, account) mapAccount(mappedAccount, account)
mappedAccount.displayIndex = index mappedAccount.displayIndex = index
@ -112,7 +112,7 @@ open class fints4kModelMapper(protected val modelCreator: IModelCreator) {
} }
} }
open fun mapBankAccount(account: TypedBankAccount, accountData: AccountData) { open fun mapAccount(account: TypedBankAccount, accountData: AccountData) {
account.identifier = accountData.accountIdentifier account.identifier = accountData.accountIdentifier
account.accountHolderName = accountData.accountHolderName account.accountHolderName = accountData.accountHolderName
account.iban = accountData.iban account.iban = accountData.iban
@ -147,22 +147,22 @@ open class fints4kModelMapper(protected val modelCreator: IModelCreator) {
} }
// TODO: move to a fints4k internal mapper // TODO: move to a fints4k internal mapper
open fun updateBankAccounts(bank: BankData, updatedAccounts: List<AccountData>) { open fun updateAccounts(bank: BankData, updatedAccounts: List<AccountData>) {
val accounts = bank.accounts val accounts = bank.accounts
updatedAccounts.forEach { updatedAccount -> updatedAccounts.forEach { updatedAccount ->
val matchingExistingAccount = findMatchingBankAccount(accounts, updatedAccount) val matchingExistingAccount = findMatchingAccount(accounts, updatedAccount)
if (matchingExistingAccount == null) { if (matchingExistingAccount == null) {
bank.addAccount(updatedAccount) bank.addAccount(updatedAccount)
} }
else { else {
updateBankAccount(matchingExistingAccount, updatedAccount) updateAccount(matchingExistingAccount, updatedAccount)
} }
} }
bank.accounts.forEach { account -> bank.accounts.forEach { account ->
val updatedAccount = findMatchingBankAccount(updatedAccounts, account) val updatedAccount = findMatchingAccount(updatedAccounts, account)
if (updatedAccount == null) { if (updatedAccount == null) {
bank.removeAccount(account) bank.removeAccount(account)
@ -170,7 +170,7 @@ open class fints4kModelMapper(protected val modelCreator: IModelCreator) {
} }
} }
open fun updateBankAccount(account: AccountData, updatedAccount: AccountData) { open fun updateAccount(account: AccountData, updatedAccount: AccountData) {
account.allowedJobs = updatedAccount.allowedJobs account.allowedJobs = updatedAccount.allowedJobs
AccountFeature.values().forEach { feature -> AccountFeature.values().forEach { feature ->
@ -178,26 +178,26 @@ open class fints4kModelMapper(protected val modelCreator: IModelCreator) {
} }
} }
open fun findAccountForBankAccount(bank: BankData, bankAccount: TypedBankAccount): AccountData? { open fun findMatchingAccount(bank: BankData, account: TypedBankAccount): AccountData? {
return bank.accounts.firstOrNull { bankAccount.identifier == it.accountIdentifier } return bank.accounts.firstOrNull { account.identifier == it.accountIdentifier }
} }
open fun findMatchingBankAccount(customer: TypedCustomer, accountData: AccountData): TypedBankAccount? { open fun findMatchingAccount(bank: TypedBankData, accountData: AccountData): TypedBankAccount? {
return customer.accounts.firstOrNull { it.identifier == accountData.accountIdentifier } return bank.accounts.firstOrNull { it.identifier == accountData.accountIdentifier }
} }
open fun findMatchingBankAccount(accounts: List<AccountData>, accountData: AccountData): AccountData? { open fun findMatchingAccount(accounts: List<AccountData>, accountData: AccountData): AccountData? {
return accounts.firstOrNull { it.accountIdentifier == accountData.accountIdentifier } return accounts.firstOrNull { it.accountIdentifier == accountData.accountIdentifier }
} }
open fun mapTransactions(bankAccount: TypedBankAccount, transactions: Collection<net.dankito.banking.fints.model.AccountTransaction>): List<IAccountTransaction> { open fun mapTransactions(account: TypedBankAccount, transactions: Collection<net.dankito.banking.fints.model.AccountTransaction>): List<IAccountTransaction> {
return transactions.map { mapTransaction(bankAccount, it) } return transactions.map { mapTransaction(account, it) }
} }
open fun mapTransaction(bankAccount: TypedBankAccount, transaction: net.dankito.banking.fints.model.AccountTransaction): IAccountTransaction { open fun mapTransaction(account: TypedBankAccount, transaction: net.dankito.banking.fints.model.AccountTransaction): IAccountTransaction {
return modelCreator.createTransaction( return modelCreator.createTransaction(
bankAccount, account,
transaction.amount.toBigDecimal(), transaction.amount.toBigDecimal(),
transaction.amount.currency.code, transaction.amount.currency.code,
transaction.unparsedUsage, transaction.unparsedUsage,
@ -238,20 +238,20 @@ open class fints4kModelMapper(protected val modelCreator: IModelCreator) {
} }
open fun updateTanMediaAndMethods(account: TypedCustomer, bank: BankData) { open fun updateTanMediaAndMethods(bank: TypedBankData, fintsBank: BankData) {
account.supportedTanMethods = bank.tanMethodsAvailableForUser.map { tanMethod -> bank.supportedTanMethods = fintsBank.tanMethodsAvailableForUser.map { tanMethod ->
findMappedTanMethod(account, tanMethod) ?: mapTanMethod(tanMethod) findMappedTanMethod(bank, tanMethod) ?: mapTanMethod(tanMethod)
} }
if (bank.isTanMethodSelected) { if (fintsBank.isTanMethodSelected) {
account.selectedTanMethod = findMappedTanMethod(account, bank.selectedTanMethod) bank.selectedTanMethod = findMappedTanMethod(bank, fintsBank.selectedTanMethod)
} }
else { else {
account.selectedTanMethod = null bank.selectedTanMethod = null
} }
account.tanMedia = bank.tanMedia.map { tanMedium -> bank.tanMedia = fintsBank.tanMedia.map { tanMedium ->
findMappedTanMedium(account, tanMedium) ?: mapTanMedium(tanMedium) findMappedTanMedium(bank, tanMedium) ?: mapTanMedium(tanMedium)
} }
} }
@ -292,8 +292,8 @@ open class fints4kModelMapper(protected val modelCreator: IModelCreator) {
} }
} }
protected open fun findMappedTanMethod(customer: TypedCustomer, tanMethod: net.dankito.banking.fints.model.TanMethod): TanMethod? { protected open fun findMappedTanMethod(bank: TypedBankData, tanMethod: net.dankito.banking.fints.model.TanMethod): TanMethod? {
return customer.supportedTanMethods.firstOrNull { it.bankInternalMethodCode == tanMethod.securityFunction.code } return bank.supportedTanMethods.firstOrNull { it.bankInternalMethodCode == tanMethod.securityFunction.code }
} }
protected open fun findTanMethod(bank: BankData, tanMethod: TanMethod?): net.dankito.banking.fints.model.TanMethod? { protected open fun findTanMethod(bank: BankData, tanMethod: TanMethod?): net.dankito.banking.fints.model.TanMethod? {
@ -304,8 +304,8 @@ open class fints4kModelMapper(protected val modelCreator: IModelCreator) {
return bank.tanMethodsAvailableForUser.firstOrNull { it.securityFunction.code == tanMethod.bankInternalMethodCode } return bank.tanMethodsAvailableForUser.firstOrNull { it.securityFunction.code == tanMethod.bankInternalMethodCode }
} }
protected open fun findMappedTanMedium(customer: TypedCustomer, tanMedium: net.dankito.banking.fints.messages.datenelemente.implementierte.tan.TanMedium): TanMedium? { protected open fun findMappedTanMedium(bank: TypedBankData, tanMedium: net.dankito.banking.fints.messages.datenelemente.implementierte.tan.TanMedium): TanMedium? {
return customer.tanMedia.firstOrNull { doesMatchTanMedium(tanMedium, it) } return bank.tanMedia.firstOrNull { doesMatchTanMedium(tanMedium, it) }
} }
protected open fun doesMatchTanMedium(fintsTanMedium: net.dankito.banking.fints.messages.datenelemente.implementierte.tan.TanMedium, tanMedium: TanMedium): Boolean { protected open fun doesMatchTanMedium(fintsTanMedium: net.dankito.banking.fints.messages.datenelemente.implementierte.tan.TanMedium, tanMedium: TanMedium): Boolean {

View File

@ -2,7 +2,7 @@ package net.dankito.banking
import net.dankito.banking.model.AccountCredentials import net.dankito.banking.model.AccountCredentials
import net.dankito.banking.ui.BankingClientCallback import net.dankito.banking.ui.BankingClientCallback
import net.dankito.banking.ui.model.TypedCustomer import net.dankito.banking.ui.model.TypedBankData
import net.dankito.banking.ui.model.tan.FlickerCodeTanChallenge import net.dankito.banking.ui.model.tan.FlickerCodeTanChallenge
import net.dankito.banking.ui.model.tan.ImageTanChallenge import net.dankito.banking.ui.model.tan.ImageTanChallenge
import net.dankito.banking.ui.model.tan.TanChallenge import net.dankito.banking.ui.model.tan.TanChallenge
@ -23,7 +23,7 @@ import org.slf4j.LoggerFactory
*/ */
open class HbciCallback( open class HbciCallback(
protected val credentials: AccountCredentials, protected val credentials: AccountCredentials,
protected val customer: TypedCustomer, protected val bank: TypedBankData,
protected val mapper: hbci4jModelMapper, protected val mapper: hbci4jModelMapper,
protected val callback: BankingClientCallback protected val callback: BankingClientCallback
) : AbstractHBCICallback() { ) : AbstractHBCICallback() {
@ -84,13 +84,13 @@ open class HbciCallback(
// ADDED: Auswaehlen welches PinTan Verfahren verwendet werden soll // ADDED: Auswaehlen welches PinTan Verfahren verwendet werden soll
HBCICallback.NEED_PT_SECMECH -> selectTanMethod(retData.toString())?.let { selectedTanMethod -> HBCICallback.NEED_PT_SECMECH -> selectTanMethod(retData.toString())?.let { selectedTanMethod ->
customer.selectedTanMethod = selectedTanMethod bank.selectedTanMethod = selectedTanMethod
retData.replace(0, retData.length, selectedTanMethod.bankInternalMethodCode) retData.replace(0, retData.length, selectedTanMethod.bankInternalMethodCode)
} }
// chipTan or simple TAN request (iTAN, smsTAN, ...) // chipTan or simple TAN request (iTAN, smsTAN, ...)
HBCICallback.NEED_PT_TAN -> { HBCICallback.NEED_PT_TAN -> {
getTanFromUser(customer, msg, retData.toString())?.let { enteredTan -> getTanFromUser(bank, msg, retData.toString())?.let { enteredTan ->
retData.replace(0, retData.length, enteredTan) retData.replace(0, retData.length, enteredTan)
} }
} }
@ -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.selectedTanMethod!!)) val enterTanResult = callback.enterTan(bank, ImageTanChallenge(TanImage(qrCode.mimetype, qrCode.image), msg, bank.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.selectedTanMethod!!)) val enterTanResult = callback.enterTan(bank, ImageTanChallenge(TanImage(matrixCode.mimetype, matrixCode.image), msg, bank.selectedTanMethod!!))
enterTanResult.enteredTan?.let { enteredTan -> enterTanResult.enteredTan?.let { enteredTan ->
retData.replace(0, retData.length, enteredTan) retData.replace(0, retData.length, enteredTan)
} }
@ -171,7 +171,7 @@ open class HbciCallback(
} }
open fun getTanFromUser(customer: TypedCustomer, messageToShowToUser: String, challengeHHD_UC: String): String? { open fun getTanFromUser(bank: TypedBankData, messageToShowToUser: String, challengeHHD_UC: String): String? {
// Wenn per "retData" Daten uebergeben wurden, dann enthalten diese // Wenn per "retData" Daten uebergeben wurden, dann enthalten diese
// den fuer chipTAN optisch zu verwendenden Flickercode. // den fuer chipTAN optisch zu verwendenden Flickercode.
// Falls nicht, ist es eine TAN-Abfrage, fuer die keine weiteren // Falls nicht, ist es eine TAN-Abfrage, fuer die keine weiteren
@ -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.selectedTanMethod!!)) callback.enterTan(bank, TanChallenge(messageToShowToUser, bank.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.selectedTanMethod!!)) callback.enterTan(bank, FlickerCodeTanChallenge(net.dankito.banking.ui.model.tan.FlickerCode("", challengeHHD_UC), usefulMessage, bank.selectedTanMethod!!))
} }
return enterTanResult.enteredTan return enterTanResult.enteredTan
@ -201,7 +201,7 @@ open class HbciCallback(
open fun selectTanMethod(supportedTanMethodsString: String): net.dankito.banking.ui.model.tan.TanMethod? { open fun selectTanMethod(supportedTanMethodsString: String): net.dankito.banking.ui.model.tan.TanMethod? {
val supportedTanMethods = mapper.mapTanMethods(supportedTanMethodsString) val supportedTanMethods = mapper.mapTanMethods(supportedTanMethodsString)
customer.supportedTanMethods = supportedTanMethods bank.supportedTanMethods = supportedTanMethods
if (supportedTanMethods.isNotEmpty()) { if (supportedTanMethods.isNotEmpty()) {
// select any method, 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'

View File

@ -33,7 +33,7 @@ import java.util.*
open class hbci4jBankingClient( open class hbci4jBankingClient(
protected val customer: TypedCustomer, protected val bank: TypedBankData,
modelCreator: IModelCreator, modelCreator: IModelCreator,
protected val dataFolder: File, protected val dataFolder: File,
protected val asyncRunner: IAsyncRunner = ThreadPoolAsyncRunner(ThreadPool()), protected val asyncRunner: IAsyncRunner = ThreadPoolAsyncRunner(ThreadPool()),
@ -50,7 +50,7 @@ open class hbci4jBankingClient(
} }
protected val credentials = AccountCredentials(customer) protected val credentials = AccountCredentials(bank)
protected val mapper = hbci4jModelMapper(modelCreator) protected val mapper = hbci4jModelMapper(modelCreator)
@ -76,22 +76,22 @@ open class hbci4jBankingClient(
val accounts = passport.accounts val accounts = passport.accounts
if (accounts == null || accounts.size == 0) { if (accounts == null || accounts.size == 0) {
log.error("Keine Konten ermittelbar") log.error("Keine Konten ermittelbar")
return AddAccountResponse(customer, "Keine Konten ermittelbar") // TODO: translate return AddAccountResponse(bank, "Keine Konten ermittelbar") // TODO: translate
} }
this.customer.accounts = mapper.mapBankAccounts(customer, accounts, passport) this.bank.accounts = mapper.mapAccounts(bank, accounts, passport)
return tryToRetrieveAccountTransactionsForAddedAccounts(customer) return tryToRetrieveAccountTransactionsForAddedAccounts(bank)
} }
} }
return AddAccountResponse(customer, connection.error?.getInnerExceptionMessage() ?: "Could not connect") return AddAccountResponse(bank, connection.error?.getInnerExceptionMessage() ?: "Could not connect")
} }
protected open fun tryToRetrieveAccountTransactionsForAddedAccounts(customer: TypedCustomer): AddAccountResponse { protected open fun tryToRetrieveAccountTransactionsForAddedAccounts(bank: TypedBankData): AddAccountResponse {
var userCancelledAction = false var userCancelledAction = false
val retrievedData = customer.accounts.map { account -> val retrievedData = bank.accounts.map { account ->
if (account.supportsRetrievingAccountTransactions) { if (account.supportsRetrievingAccountTransactions) {
val response = getTransactionsOfLast90Days(account) val response = getTransactionsOfLast90Days(account)
@ -106,34 +106,34 @@ open class hbci4jBankingClient(
} }
} }
return AddAccountResponse(customer, retrievedData, null, userCancelledAction) return AddAccountResponse(bank, retrievedData, null, userCancelledAction)
} }
/** /**
* According to PSD2 for the accounting entries of the last 90 days the two-factor authorization does not have to * According to PSD2 for the account transactions of the last 90 days the two-factor authorization does not have to
* be applied. It depends on the bank if they request a second factor or not. * be applied. It depends on the bank if they request a second factor or not.
* *
* So we simply try to retrieve at accounting entries of the last 90 days and see if a second factor is required * So we simply try to retrieve at account transactions of the last 90 days and see if a second factor is required
* or not. * or not.
*/ */
open fun getTransactionsOfLast90DaysAsync(bankAccount: TypedBankAccount, callback: (GetTransactionsResponse) -> Unit) { open fun getTransactionsOfLast90DaysAsync(account: TypedBankAccount, callback: (GetTransactionsResponse) -> Unit) {
asyncRunner.runAsync { asyncRunner.runAsync {
callback(getTransactionsOfLast90Days(bankAccount)) callback(getTransactionsOfLast90Days(account))
} }
} }
/** /**
* According to PSD2 for the accounting entries of the last 90 days the two-factor authorization does not have to * According to PSD2 for the account transactions of the last 90 days the two-factor authorization does not have to
* be applied. It depends on the bank if they request a second factor or not. * be applied. It depends on the bank if they request a second factor or not.
* *
* So we simply try to retrieve at accounting entries of the last 90 days and see if a second factor is required * So we simply try to retrieve at account transactions of the last 90 days and see if a second factor is required
* or not. * or not.
*/ */
open fun getTransactionsOfLast90Days(bankAccount: TypedBankAccount): GetTransactionsResponse { open fun getTransactionsOfLast90Days(account: TypedBankAccount): GetTransactionsResponse {
val ninetyDaysAgo = Date(Date.today.time - NinetyDaysInMilliseconds) val ninetyDaysAgo = Date(Date.today.time - NinetyDaysInMilliseconds)
return getTransactions(GetTransactionsParameter(bankAccount, bankAccount.supportsRetrievingBalance, ninetyDaysAgo)) // TODO: implement abortIfTanIsRequired return getTransactions(GetTransactionsParameter(account, account.supportsRetrievingBalance, ninetyDaysAgo)) // TODO: implement abortIfTanIsRequired
} }
override fun getTransactionsAsync(parameter: GetTransactionsParameter, callback: (GetTransactionsResponse) -> Unit) { override fun getTransactionsAsync(parameter: GetTransactionsParameter, callback: (GetTransactionsResponse) -> Unit) {
@ -148,7 +148,7 @@ open class hbci4jBankingClient(
connection.handle?.let { handle -> connection.handle?.let { handle ->
try { try {
val (nullableBalanceJob, accountTransactionsJob, status) = executeJobsForGetAccountingEntries(handle, parameter) val (nullableBalanceJob, accountTransactionsJob, status) = executeJobsForGetAccountTransactions(handle, parameter)
// Pruefen, ob die Kommunikation mit der Bank grundsaetzlich geklappt hat // Pruefen, ob die Kommunikation mit der Bank grundsaetzlich geklappt hat
if (!status.isOK) { if (!status.isOK) {
@ -180,10 +180,10 @@ open class hbci4jBankingClient(
} }
return GetTransactionsResponse(RetrievedAccountData(account, true, balance.toBigDecimal(), return GetTransactionsResponse(RetrievedAccountData(account, true, balance.toBigDecimal(),
accountTransactionMapper.mapAccountTransactions(account, result), listOf(), parameter.fromDate, parameter.toDate)) accountTransactionMapper.mapTransactions(account, result), listOf(), parameter.fromDate, parameter.toDate))
} }
catch(e: Exception) { catch(e: Exception) {
log.error("Could not get accounting details for bank ${credentials.bankCode}", e) log.error("Could not get account transactions for bank ${credentials.bankCode}", e)
return GetTransactionsResponse(account, e.getInnerExceptionMessage()) return GetTransactionsResponse(account, e.getInnerExceptionMessage())
} }
finally { finally {
@ -196,7 +196,7 @@ open class hbci4jBankingClient(
return GetTransactionsResponse(account, connection.error?.getInnerExceptionMessage() ?: "Could not connect") return GetTransactionsResponse(account, connection.error?.getInnerExceptionMessage() ?: "Could not connect")
} }
protected open fun executeJobsForGetAccountingEntries(handle: HBCIHandler, parameter: GetTransactionsParameter): Triple<HBCIJob?, HBCIJob, HBCIExecStatus> { protected open fun executeJobsForGetAccountTransactions(handle: HBCIHandler, parameter: GetTransactionsParameter): Triple<HBCIJob?, HBCIJob, HBCIExecStatus> {
val konto = mapper.mapToKonto(parameter.account) val konto = mapper.mapToKonto(parameter.account)
// 1. Auftrag fuer das Abrufen des Saldos erzeugen // 1. Auftrag fuer das Abrufen des Saldos erzeugen
@ -272,17 +272,17 @@ open class hbci4jBankingClient(
} }
override fun dataChanged(customer: TypedCustomer) { override fun dataChanged(bank: TypedBankData) {
if (customer.bankCode != credentials.bankCode || customer.customerId != credentials.customerId || customer.password != credentials.password) { if (bank.bankCode != credentials.bankCode || bank.customerId != credentials.customerId || bank.password != credentials.password) {
getPassportFile(credentials).delete() getPassportFile(credentials).delete()
} }
credentials.bankCode = customer.bankCode credentials.bankCode = bank.bankCode
credentials.customerId = customer.customerId credentials.customerId = bank.customerId
credentials.password = customer.password credentials.password = bank.password
} }
override fun deletedAccount(customer: TypedCustomer, wasLastAccountWithThisCredentials: Boolean) { override fun deletedBank(bank: TypedBankData, wasLastAccountWithThisCredentials: Boolean) {
getPassportFile(credentials).delete() getPassportFile(credentials).delete()
} }
@ -296,7 +296,7 @@ open class hbci4jBankingClient(
// In "props" koennen optional Kernel-Parameter abgelegt werden, die in der Klasse // In "props" koennen optional Kernel-Parameter abgelegt werden, die in der Klasse
// org.kapott.hbci.manager.HBCIUtils (oben im Javadoc) beschrieben sind. // org.kapott.hbci.manager.HBCIUtils (oben im Javadoc) beschrieben sind.
val props = Properties() val props = Properties()
HBCIUtils.init(props, HbciCallback(credentials, customer, mapper, callback)) HBCIUtils.init(props, HbciCallback(credentials, bank, mapper, callback))
// In der Passport-Datei speichert HBCI4Java die Daten des Bankzugangs (Bankparameterdaten, Benutzer-Parameter, etc.). // In der Passport-Datei speichert HBCI4Java die Daten des Bankzugangs (Bankparameterdaten, Benutzer-Parameter, etc.).
// Die Datei kann problemlos geloescht werden. Sie wird beim naechsten mal automatisch neu erzeugt, // Die Datei kann problemlos geloescht werden. Sie wird beim naechsten mal automatisch neu erzeugt,

View File

@ -3,7 +3,7 @@ package net.dankito.banking
import net.dankito.banking.ui.BankingClientCallback import net.dankito.banking.ui.BankingClientCallback
import net.dankito.banking.ui.IBankingClient import net.dankito.banking.ui.IBankingClient
import net.dankito.banking.ui.IBankingClientCreator import net.dankito.banking.ui.IBankingClientCreator
import net.dankito.banking.ui.model.TypedCustomer import net.dankito.banking.ui.model.TypedBankData
import net.dankito.banking.ui.model.mapper.IModelCreator import net.dankito.banking.ui.model.mapper.IModelCreator
import net.dankito.banking.util.IAsyncRunner import net.dankito.banking.util.IAsyncRunner
import net.dankito.utils.multiplatform.File import net.dankito.utils.multiplatform.File
@ -14,13 +14,13 @@ open class hbci4jBankingClientCreator(
) : IBankingClientCreator { ) : IBankingClientCreator {
override fun createClient( override fun createClient(
customer: TypedCustomer, bank: TypedBankData,
dataFolder: File, dataFolder: File,
asyncRunner: IAsyncRunner, asyncRunner: IAsyncRunner,
callback: BankingClientCallback callback: BankingClientCallback
): IBankingClient { ): IBankingClient {
return hbci4jBankingClient(customer, modelCreator, dataFolder, asyncRunner, callback) return hbci4jBankingClient(bank, modelCreator, dataFolder, asyncRunner, callback)
} }
} }

View File

@ -1,6 +1,6 @@
package net.dankito.banking.model package net.dankito.banking.model
import net.dankito.banking.ui.model.TypedCustomer import net.dankito.banking.ui.model.TypedBankData
open class AccountCredentials( open class AccountCredentials(
@ -9,6 +9,6 @@ open class AccountCredentials(
var password: String var password: String
) { ) {
constructor(bank: TypedCustomer) : this(bank.bankCode, bank.customerId, bank.password) constructor(bank: TypedBankData) : this(bank.bankCode, bank.customerId, bank.password)
} }

View File

@ -28,26 +28,26 @@ open class AccountTransactionMapper(
} }
open fun mapAccountTransactions(bankAccount: TypedBankAccount, result: GVRKUms): List<IAccountTransaction> { open fun mapTransactions(account: TypedBankAccount, result: GVRKUms): List<IAccountTransaction> {
val entries = mutableListOf<IAccountTransaction>() val entries = mutableListOf<IAccountTransaction>()
result.dataPerDay.forEach { btag -> result.dataPerDay.forEach { btag ->
btag.lines.forEach { transaction -> btag.lines.forEach { transaction ->
entries.add(mapAccountingEntry(bankAccount, btag, transaction)) entries.add(mapTransaction(account, btag, transaction))
} }
} }
log.debug("Retrieved ${result.flatData.size} accounting entries") log.debug("Retrieved ${result.flatData.size} account transactions")
return entries return entries
} }
protected open fun mapAccountingEntry(bankAccount: TypedBankAccount, btag: GVRKUms.BTag, transaction: GVRKUms.UmsLine): IAccountTransaction { protected open fun mapTransaction(account: TypedBankAccount, btag: GVRKUms.BTag, transaction: GVRKUms.UmsLine): IAccountTransaction {
val unparsedUsage = transaction.usage.joinToString("") val unparsedUsage = transaction.usage.joinToString("")
val parsedUsage = Mt940Parser().getUsageParts(unparsedUsage) val parsedUsage = Mt940Parser().getUsageParts(unparsedUsage)
val statementAndMaySequenceNumber = btag.counter.split('/') val statementAndMaySequenceNumber = btag.counter.split('/')
return modelCreator.createTransaction(bankAccount, return modelCreator.createTransaction(account,
mapValue(transaction.value), transaction.value.curr, unparsedUsage, transaction.bdate.toDate(), mapValue(transaction.value), transaction.value.curr, unparsedUsage, transaction.bdate.toDate(),
transaction.other.name + (transaction.other.name2 ?: ""), transaction.other.name + (transaction.other.name2 ?: ""),
transaction.other.bic ?: transaction.other.blz, transaction.other.bic ?: transaction.other.blz,

View File

@ -15,14 +15,14 @@ open class hbci4jModelMapper(
protected val modelCreator: IModelCreator protected val modelCreator: IModelCreator
) { ) {
open fun mapToKonto(bankAccount: TypedBankAccount): Konto { open fun mapToKonto(account: TypedBankAccount): Konto {
val customer = bankAccount.customer val bank = account.bank
val konto = Konto("DE", customer.bankCode, bankAccount.identifier, bankAccount.subAccountNumber) val konto = Konto("DE", bank.bankCode, account.identifier, account.subAccountNumber)
konto.name = customer.bankName konto.name = bank.bankName
konto.iban = bankAccount.iban konto.iban = account.iban
konto.bic = customer.bic konto.bic = bank.bic
return konto return konto
} }
@ -42,32 +42,32 @@ open class hbci4jModelMapper(
} }
open fun mapBankAccounts(customer: TypedCustomer, bankAccounts: Array<out Konto>, passport: HBCIPassport): List<TypedBankAccount> { open fun mapAccounts(bank: TypedBankData, accounts: Array<out Konto>, passport: HBCIPassport): List<TypedBankAccount> {
return bankAccounts.map { bankAccount -> return accounts.map { account ->
val iban = if (bankAccount.iban.isNullOrBlank() == false) bankAccount.iban else passport.upd.getProperty("KInfo.iban") ?: "" val iban = if (account.iban.isNullOrBlank() == false) account.iban else passport.upd.getProperty("KInfo.iban") ?: ""
val result = modelCreator.createBankAccount(customer, bankAccount.number, val result = modelCreator.createAccount(bank, account.number,
if (bankAccount.name2.isNullOrBlank() == false) bankAccount.name + " " + bankAccount.name2 else bankAccount.name) if (account.name2.isNullOrBlank() == false) account.name + " " + account.name2 else account.name)
result.iban = iban result.iban = iban
result.subAccountNumber = bankAccount.subnumber result.subAccountNumber = account.subnumber
result.customerId = bankAccount.customerid result.customerId = account.customerid
result.currency = bankAccount.curr result.currency = account.curr
result.type = mapBankAccountType(bankAccount) result.type = mapBankAccountType(account)
result.isAccountTypeSupported = result.type == BankAccountType.Girokonto || result.type == BankAccountType.Festgeldkonto result.isAccountTypeSupported = result.type == BankAccountType.Girokonto || result.type == BankAccountType.Festgeldkonto
result.accountLimit = bankAccount.limit?.value?.let { mapValue(it).toString() } result.accountLimit = account.limit?.value?.let { mapValue(it).toString() }
result.supportsRetrievingBalance = bankAccount.allowedGVs.contains("HKSAL") result.supportsRetrievingBalance = account.allowedGVs.contains("HKSAL")
result.supportsRetrievingAccountTransactions = bankAccount.allowedGVs.contains("HKKAZ") result.supportsRetrievingAccountTransactions = account.allowedGVs.contains("HKKAZ")
result.supportsRetrievingBalance = bankAccount.allowedGVs.contains("HKCCS") result.supportsRetrievingBalance = account.allowedGVs.contains("HKCCS")
result result
} }
} }
open fun mapBankAccountType(bankAccount: Konto): BankAccountType { open fun mapBankAccountType(account: Konto): BankAccountType {
val type = bankAccount.acctype val type = account.acctype
return when { return when {
type.length == 1 -> BankAccountType.Girokonto type.length == 1 -> BankAccountType.Girokonto