Merged Customer and Bank

This commit is contained in:
dankito 2020-06-24 20:54:11 +02:00
parent 603b8ff4b7
commit 647a385f2a
14 changed files with 56 additions and 84 deletions

View File

@ -81,7 +81,7 @@ open class LuceneBankingPersistence(
override fun deleteAccount(customer: Customer, allCustomers: List<Customer>) { override fun deleteAccount(customer: Customer, allCustomers: List<Customer>) {
try { try {
deleteAccountTransactions(customer.bankAccounts) deleteAccountTransactions(customer.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 $customer", e)
} }

View File

@ -51,7 +51,7 @@ open class AccountTransactionAdapter(protected val presenter: BankingPresenter)
viewHolder.txtvwAmount.text = presenter.formatAmount(item.amount) viewHolder.txtvwAmount.text = presenter.formatAmount(item.amount)
viewHolder.txtvwAmount.setTextColorToColorResource(if (item.amount >= BigDecimal.ZERO) R.color.positiveAmount else R.color.negativeAmount) viewHolder.txtvwAmount.setTextColorToColorResource(if (item.amount >= BigDecimal.ZERO) R.color.positiveAmount else R.color.negativeAmount)
val iconUrl = item.bankAccount.customer.bank.iconUrl val iconUrl = item.bankAccount.customer.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

@ -32,7 +32,7 @@ open class BankAccountsAdapter(bankAccounts: List<BankAccount>) : ListAdapter<Ba
protected open fun setIcon(bankAccount: BankAccount, imgBankIcon: ImageView) { protected open fun setIcon(bankAccount: BankAccount, imgBankIcon: ImageView) {
try { try {
val iconUrl = bankAccount.customer.bank.iconUrl val iconUrl = bankAccount.customer.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

@ -148,11 +148,11 @@ open class DrawerView(
.withSecondaryIcon(GoogleMaterial.Icon.gmd_delete) .withSecondaryIcon(GoogleMaterial.Icon.gmd_delete)
.withSecondaryIconColor(activity, R.color.primaryTextColor_Dark) .withSecondaryIconColor(activity, R.color.primaryTextColor_Dark)
.withOnSecondaryIconClickedListener { closeDrawerAndEditAccount(customer) } .withOnSecondaryIconClickedListener { closeDrawerAndEditAccount(customer) }
.withIcon(customer.bank.iconUrl ?: "") .withIcon(customer.iconUrl ?: "")
.withSelected(presenter.isSingleSelectedAccount(customer)) .withSelected(presenter.isSingleSelectedAccount(customer))
.withOnDrawerItemClickListener { _, _, _ -> itemClicked { presenter.selectedAccount(customer) } } .withOnDrawerItemClickListener { _, _, _ -> itemClicked { presenter.selectedAccount(customer) } }
if (customer.bank.iconUrl == null) { if (customer.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)
} }
@ -161,7 +161,7 @@ open class DrawerView(
} }
private fun createBankAccountsDrawerItems(customer: Customer): List<IDrawerItem<*>> { private fun createBankAccountsDrawerItems(customer: Customer): List<IDrawerItem<*>> {
return customer.bankAccounts.map { bankAccount -> return customer.accounts.map { bankAccount ->
SecondaryDrawerItem() SecondaryDrawerItem()
.withName(bankAccount.displayName) .withName(bankAccount.displayName)
.withLevel(BankAccountLevel) .withLevel(BankAccountLevel)

View File

@ -257,7 +257,7 @@ open class AddAccountDialog(protected val presenter: BankingPresenter) : Window(
val account = response.customer val account = response.customer
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.bank.bankCode, account.customerId, response.errorToShowToUser) account.bankCode, account.customerId, response.errorToShowToUser)
isEnteredCredentialsResultVisible.value = true isEnteredCredentialsResultVisible.value = true
} }

View File

@ -114,7 +114,7 @@ open class TransferMoneyDialog @JvmOverloads constructor(
cellFormat { cellFormat {
text = it.displayNameIncludingBankName text = it.displayNameIncludingBankName
it.customer.bank.iconUrl?.let { iconUrl -> it.customer.iconUrl?.let { iconUrl ->
graphic = ImageView(iconUrl)?.apply { graphic = ImageView(iconUrl)?.apply {
this.fitHeight = BankIconSize this.fitHeight = BankIconSize
this.fitWidth = BankIconSize this.fitWidth = BankIconSize

View File

@ -17,13 +17,13 @@ open class AccountsAccountTreeItem(val customer: Customer) : AccountsTreeItemBas
graphic = createIconImageView() graphic = createIconImageView()
customer.bankAccounts.forEach { bankAccount -> customer.accounts.forEach { bankAccount ->
children.add(AccountsBankAccountTreeItem(bankAccount)) children.add(AccountsBankAccountTreeItem(bankAccount))
} }
} }
protected open fun createIconImageView(): Node? { protected open fun createIconImageView(): Node? {
customer.bank.iconUrl?.let { customer.iconUrl?.let {
val iconImageView = ImageView(it) val iconImageView = ImageView(it)
iconImageView.fitHeight = IconSize iconImageView.fitHeight = IconSize

View File

@ -1,24 +0,0 @@
package net.dankito.banking.ui.model
open class Bank @JvmOverloads constructor(
var name: String,
val bankCode: String,
var bic: String,
var finTsServerAddress: String,
var iconUrl: String? = null
) {
internal constructor() : this("", "", "", "") // for object deserializers
val displayName: String
get() = name
override fun toString(): String {
return "$name ($bankCode)"
}
}

View File

@ -46,7 +46,7 @@ open class BankAccount @JvmOverloads constructor(
} }
open val displayNameIncludingBankName: String open val displayNameIncludingBankName: String
get() = "${customer.bank.name} ${displayName}" get() = "${customer.bankName} ${displayName}"
open var bookedTransactions: List<AccountTransaction> = bookedAccountTransactions open var bookedTransactions: List<AccountTransaction> = bookedAccountTransactions

View File

@ -11,16 +11,20 @@ import java.util.*
@JsonIdentityInfo(property = "id", generator = ObjectIdGenerators.PropertyGenerator::class) // to avoid stack overflow due to circular references @JsonIdentityInfo(property = "id", generator = ObjectIdGenerators.PropertyGenerator::class) // to avoid stack overflow due to circular references
open class Customer( open class Customer(
val bank: Bank, val bankCode: String,
val customerId: String, val customerId: String,
var password: String, var password: String,
var name: String, var finTsServerAddress: String,
var bankName: String,
var bic: String,
var customerName: String,
var userId: String = customerId, var userId: String = customerId,
var bankAccounts: List<BankAccount> = listOf() var iconUrl: String? = null,
var accounts: List<BankAccount> = listOf()
) { ) {
internal constructor() : this(Bank(), "", "", "") // for object deserializers internal constructor() : this("", "", "", "", "", "", "") // for object deserializers
var id: String = UUID.randomUUID().toString() var id: String = UUID.randomUUID().toString()
@ -38,17 +42,17 @@ open class Customer(
val displayName: String val displayName: String
get() = bank.name get() = bankName
val balance: BigDecimal val balance: BigDecimal
get() = bankAccounts.map { it.balance }.fold(BigDecimal.ZERO) { acc, e -> acc + e } get() = accounts.map { it.balance }.fold(BigDecimal.ZERO) { acc, e -> acc + e }
val transactions: List<AccountTransaction> val transactions: List<AccountTransaction>
get() = bankAccounts.flatMap { it.bookedTransactions } get() = accounts.flatMap { it.bookedTransactions }
override fun toString(): String { override fun toString(): String {
return "$name ($customerId)" return "$customerName ($customerId)"
} }
} }

View File

@ -126,8 +126,7 @@ open class BankingPresenter(
val deserializedAccounts = persister.readPersistedAccounts() val deserializedAccounts = persister.readPersistedAccounts()
deserializedAccounts.forEach { customer -> deserializedAccounts.forEach { customer ->
val bank = customer.bank val bankInfo = BankInfo(customer.bankName, customer.bankCode, customer.bic, "", "", "", customer.finTsServerAddress, "FinTS V3.0", null)
val bankInfo = BankInfo(bank.name, bank.bankCode, bank.bic, "", "", "", bank.finTsServerAddress, "FinTS V3.0", null)
val newClient = bankingClientCreator.createClient(bankInfo, customer.customerId, customer.password, val newClient = bankingClientCreator.createClient(bankInfo, customer.customerId, customer.password,
dataFolder, threadPool, callback) dataFolder, threadPool, callback)
@ -198,29 +197,27 @@ open class BankingPresenter(
} }
protected open fun findIconForBank(customer: Customer) { protected open fun findIconForBank(customer: Customer) {
val bank = customer.bank
try { try {
bankIconFinder.findIconForBank(bank.name)?.let { bankIconUrl -> bankIconFinder.findIconForBank(customer.bankName)?.let { bankIconUrl ->
val bankIconFile = saveBankIconToDisk(bank, bankIconUrl) val bankIconFile = saveBankIconToDisk(customer, bankIconUrl)
bank.iconUrl = "file://" + bankIconFile.absolutePath // without 'file://' Android will not find it customer.iconUrl = "file://" + bankIconFile.absolutePath // without 'file://' Android will not find it
persistAccount(customer) persistAccount(customer)
callAccountsChangedListeners() callAccountsChangedListeners()
} }
} catch (e: Exception) { } catch (e: Exception) {
log.error("Could not get icon for bank $bank", e) log.error("Could not get icon for bank ${customer.bankName}", e)
} }
} }
protected open fun saveBankIconToDisk(bank: Bank, bankIconUrl: String): File { protected open fun saveBankIconToDisk(customer: Customer, 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, bank.bankCode + if (extension != null) (".$extension") else "") val bankIconFile = File(bankIconsDir, customer.bankCode + if (extension != null) (".$extension") else "")
URL(bankIconUrl).openConnection().getInputStream().buffered().use { iconInputStream -> URL(bankIconUrl).openConnection().getInputStream().buffered().use { iconInputStream ->
FileOutputStream(bankIconFile).use { fileOutputStream -> FileOutputStream(bankIconFile).use { fileOutputStream ->
@ -252,7 +249,7 @@ open class BankingPresenter(
open fun deleteAccount(customer: Customer) { open fun deleteAccount(customer: Customer) {
val wasSelected = isSingleSelectedAccount(customer) or // either account or one of its bank accounts is currently selected val wasSelected = isSingleSelectedAccount(customer) or // either account or one of its bank accounts is currently selected
(customer.bankAccounts.firstOrNull { isSingleSelectedBankAccount(it) } != null) (customer.accounts.firstOrNull { isSingleSelectedBankAccount(it) } != null)
bankingClientsForAccounts.remove(customer) bankingClientsForAccounts.remove(customer)
@ -269,7 +266,7 @@ open class BankingPresenter(
open fun fetchAccountTransactionsAsync(customer: Customer, open fun fetchAccountTransactionsAsync(customer: Customer,
callback: (GetTransactionsResponse) -> Unit) { callback: (GetTransactionsResponse) -> Unit) {
customer.bankAccounts.forEach { bankAccount -> customer.accounts.forEach { bankAccount ->
if (bankAccount.supportsRetrievingAccountTransactions) { if (bankAccount.supportsRetrievingAccountTransactions) {
fetchAccountTransactionsAsync(bankAccount, callback) // TODO: use a synchronous version of fetchAccountTransactions() so that all bank accounts get handled serially fetchAccountTransactionsAsync(bankAccount, callback) // TODO: use a synchronous version of fetchAccountTransactions() so that all bank accounts get handled serially
} }
@ -309,7 +306,7 @@ open class BankingPresenter(
protected open fun updateAccountsTransactionsAsync(abortIfTanIsRequired: Boolean = false, callback: (GetTransactionsResponse) -> Unit) { protected open fun updateAccountsTransactionsAsync(abortIfTanIsRequired: Boolean = false, callback: (GetTransactionsResponse) -> Unit) {
bankingClientsForAccounts.keys.forEach { account -> bankingClientsForAccounts.keys.forEach { account ->
account.bankAccounts.forEach { bankAccount -> account.accounts.forEach { bankAccount ->
if (bankAccount.supportsRetrievingAccountTransactions) { if (bankAccount.supportsRetrievingAccountTransactions) {
updateBankAccountTransactionsAsync(bankAccount, abortIfTanIsRequired, callback) updateBankAccountTransactionsAsync(bankAccount, abortIfTanIsRequired, callback)
} }
@ -491,7 +488,7 @@ open class BankingPresenter(
} }
return logEntries.map { entry -> return logEntries.map { entry ->
MessageLogEntryDateFormat.format(entry.time) + " " + entry.customer.bank.bankCode + " " + entry.message MessageLogEntryDateFormat.format(entry.time) + " " + entry.customer.bankCode + " " + entry.message
} }
} }
@ -546,7 +543,7 @@ open class BankingPresenter(
open fun selectedAccount(customer: Customer) { open fun selectedAccount(customer: Customer) {
selectedAccountType = SelectedAccountType.SingleAccount selectedAccountType = SelectedAccountType.SingleAccount
setSelectedBankAccounts(customer.bankAccounts) setSelectedBankAccounts(customer.accounts)
} }
open fun selectedBankAccount(bankAccount: BankAccount) { open fun selectedBankAccount(bankAccount: BankAccount) {
@ -566,7 +563,7 @@ open class BankingPresenter(
get() = bankingClientsForAccounts.keys.toList() get() = bankingClientsForAccounts.keys.toList()
open val bankAccounts: List<BankAccount> open val bankAccounts: List<BankAccount>
get() = customers.flatMap { it.bankAccounts } get() = customers.flatMap { it.accounts }
open val allTransactions: List<AccountTransaction> open val allTransactions: List<AccountTransaction>
get() = getAccountTransactionsForBankAccounts(bankAccounts) get() = getAccountTransactionsForBankAccounts(bankAccounts)

View File

@ -67,16 +67,11 @@ open class fints4kModelMapper {
} }
open fun mapBank(bank: BankData): Bank {
return Bank(bank.name, bank.bankCode, bank.bic, bank.finTs3ServerAddress)
}
open fun mapCustomer(customer: CustomerData, bank: BankData): Customer { open fun mapCustomer(customer: CustomerData, bank: BankData): Customer {
val mappedBank = mapBank(bank) val mappedCustomer = Customer(bank.bankCode, customer.customerId, customer.pin,
bank.finTs3ServerAddress, bank.name, bank.bic, customer.name, customer.userId)
val mappedCustomer = Customer(mappedBank, customer.customerId, customer.pin, customer.name, customer.userId) mappedCustomer.accounts = mapBankAccounts(mappedCustomer, customer.accounts)
mappedCustomer.bankAccounts = mapBankAccounts(mappedCustomer, customer.accounts)
updateTanMediaAndProcedures(mappedCustomer, customer) updateTanMediaAndProcedures(mappedCustomer, customer)
@ -166,7 +161,7 @@ open class fints4kModelMapper {
} }
open fun findMatchingBankAccount(customer: Customer, accountData: AccountData): BankAccount? { open fun findMatchingBankAccount(customer: Customer, accountData: AccountData): BankAccount? {
return customer.bankAccounts.firstOrNull { it.identifier == accountData.accountIdentifier } return customer.accounts.firstOrNull { it.identifier == accountData.accountIdentifier }
} }
open fun findMatchingBankAccount(accounts: List<AccountData>, accountData: AccountData): AccountData? { open fun findMatchingBankAccount(accounts: List<AccountData>, accountData: AccountData): AccountData? {

View File

@ -53,9 +53,8 @@ open class hbci4jBankingClient(
protected val credentials = AccountCredentials(bankInfo.bankCode, customerId, pin) protected val credentials = AccountCredentials(bankInfo.bankCode, customerId, pin)
protected var bank = Bank(bankInfo.name, bankInfo.bankCode, bankInfo.bic, bankInfo.pinTanAddress ?: "") protected var customer = Customer(bankInfo.bankCode, customerId, pin,
bankInfo.pinTanAddress ?: "", bankInfo.name, bankInfo.bic, "")
protected var account = Customer(bank, customerId, pin, "")
protected val mapper = hbci4jModelMapper() protected val mapper = hbci4jModelMapper()
@ -81,16 +80,16 @@ 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(false, "Keine Konten ermittelbar", account) // TODO: translate return AddAccountResponse(false, "Keine Konten ermittelbar", customer) // TODO: translate
} }
this.account.bankAccounts = mapper.mapBankAccounts(account, accounts, passport) this.customer.accounts = mapper.mapBankAccounts(customer, accounts, passport)
return tryToRetrieveAccountTransactionsForAddedAccounts(account) return tryToRetrieveAccountTransactionsForAddedAccounts(customer)
} }
} }
return AddAccountResponse(false, null, account, error = connection.error) return AddAccountResponse(false, null, customer, error = connection.error)
} }
protected open fun tryToRetrieveAccountTransactionsForAddedAccounts(customer: Customer): AddAccountResponse { protected open fun tryToRetrieveAccountTransactionsForAddedAccounts(customer: Customer): AddAccountResponse {
@ -99,7 +98,7 @@ open class hbci4jBankingClient(
val bookedTransactions = mutableMapOf<BankAccount, List<AccountTransaction>>() val bookedTransactions = mutableMapOf<BankAccount, List<AccountTransaction>>()
val unbookedTransactions = mutableMapOf<BankAccount, List<Any>>() val unbookedTransactions = mutableMapOf<BankAccount, List<Any>>()
customer.bankAccounts.forEach { bankAccount -> customer.accounts.forEach { bankAccount ->
if (bankAccount.supportsRetrievingAccountTransactions) { if (bankAccount.supportsRetrievingAccountTransactions) {
val response = getTransactionsOfLast90Days(bankAccount) val response = getTransactionsOfLast90Days(bankAccount)
transactionsOfLast90DaysResponses.add(response) transactionsOfLast90DaysResponses.add(response)
@ -203,7 +202,7 @@ open class hbci4jBankingClient(
} }
protected open fun executeJobsForGetAccountingEntries(handle: HBCIHandler, bankAccount: BankAccount, parameter: GetTransactionsParameter): Triple<HBCIJob?, HBCIJob, HBCIExecStatus> { protected open fun executeJobsForGetAccountingEntries(handle: HBCIHandler, bankAccount: BankAccount, parameter: GetTransactionsParameter): Triple<HBCIJob?, HBCIJob, HBCIExecStatus> {
val konto = mapper.mapToKonto(bank, bankAccount) val konto = mapper.mapToKonto(bankAccount)
// 1. Auftrag fuer das Abrufen des Saldos erzeugen // 1. Auftrag fuer das Abrufen des Saldos erzeugen
var balanceJob: HBCIJob? = null var balanceJob: HBCIJob? = null
@ -265,7 +264,7 @@ open class hbci4jBankingClient(
// TODO: implement instant payment // TODO: implement instant payment
val transferCashJob = handle.newJob("UebSEPA") val transferCashJob = handle.newJob("UebSEPA")
val source = mapper.mapToKonto(bank, bankAccount) val source = mapper.mapToKonto(bankAccount)
val destination = mapper.mapToKonto(data) val destination = mapper.mapToKonto(data)
val amount = Value(data.amount, "EUR") val amount = Value(data.amount, "EUR")
@ -292,7 +291,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, account, mapper, callback)) HBCIUtils.init(props, HbciCallback(credentials, customer, 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

@ -1,7 +1,6 @@
package net.dankito.banking.util package net.dankito.banking.util
import net.dankito.banking.ui.model.Customer import net.dankito.banking.ui.model.Customer
import net.dankito.banking.ui.model.Bank
import net.dankito.banking.ui.model.BankAccount import net.dankito.banking.ui.model.BankAccount
import net.dankito.banking.ui.model.BankAccountType import net.dankito.banking.ui.model.BankAccountType
import net.dankito.banking.ui.model.parameters.TransferMoneyData import net.dankito.banking.ui.model.parameters.TransferMoneyData
@ -14,12 +13,14 @@ import java.math.BigDecimal
open class hbci4jModelMapper { open class hbci4jModelMapper {
open fun mapToKonto(bank: Bank, bankAccount: BankAccount): Konto { open fun mapToKonto(bankAccount: BankAccount): Konto {
val konto = Konto("DE", bank.bankCode, bankAccount.identifier, bankAccount.subAccountNumber) val customer = bankAccount.customer
konto.name = bank.name val konto = Konto("DE", customer.bankCode, bankAccount.identifier, bankAccount.subAccountNumber)
konto.name = customer.bankName
konto.iban = bankAccount.iban konto.iban = bankAccount.iban
konto.bic = bank.bic konto.bic = customer.bic
return konto return konto
} }