Extracted RetrievedAccountData, makes code way better readable

This commit is contained in:
dankito 2020-09-19 01:41:02 +02:00
parent 58748579b4
commit 7cb19d6f7d
9 changed files with 103 additions and 92 deletions

View File

@ -283,8 +283,9 @@ open class FinTsClient(
protected open fun addAccountGetAccountBalancesAndTransactions(bank: BankData, newUserInfoResponse: AddAccountResponse,
didOverwriteUserUnselectedTanProcedure: Boolean, originalAreWeThatGentleToCloseDialogs: Boolean,
callback: (AddAccountResponse) -> Unit) {
val transactionsOfLast90DaysResponses = mutableListOf<GetTransactionsResponse>()
val balances = mutableMapOf<AccountData, Money>()
// TODO: or add a default RetrievedAccountData instance for each account?
val retrievedAccountData = mutableListOf<RetrievedAccountData>()
var successfullyReceivedAccountTransactionsResponse = false
val accountSupportingRetrievingTransactions = bank.accounts.filter { it.supportsFeature(AccountFeature.RetrieveBalance) || it.supportsFeature(AccountFeature.RetrieveAccountTransactions) }
val countAccountSupportingRetrievingTransactions = accountSupportingRetrievingTransactions.size
@ -292,18 +293,21 @@ open class FinTsClient(
if (countAccountSupportingRetrievingTransactions == 0) {
addAccountAfterRetrievingTransactions(bank, newUserInfoResponse, didOverwriteUserUnselectedTanProcedure,
originalAreWeThatGentleToCloseDialogs, transactionsOfLast90DaysResponses, balances, callback)
originalAreWeThatGentleToCloseDialogs, retrievedAccountData, successfullyReceivedAccountTransactionsResponse, callback)
}
accountSupportingRetrievingTransactions.forEach { account ->
tryGetTransactionsOfLast90DaysWithoutTan(bank, account) { response ->
transactionsOfLast90DaysResponses.add(response)
response.balance?.let { balances.put(account, it) }
retrievedAccountData.addAll(response.retrievedData)
if (response.isSuccessful) {
successfullyReceivedAccountTransactionsResponse = true
}
countRetrievedAccounts++
if (countRetrievedAccounts == countAccountSupportingRetrievingTransactions) {
addAccountAfterRetrievingTransactions(bank, newUserInfoResponse, didOverwriteUserUnselectedTanProcedure, originalAreWeThatGentleToCloseDialogs,
transactionsOfLast90DaysResponses, balances, callback)
retrievedAccountData, successfullyReceivedAccountTransactionsResponse, callback)
}
}
}
@ -311,21 +315,16 @@ open class FinTsClient(
protected open fun addAccountAfterRetrievingTransactions(bank: BankData, newUserInfoResponse: AddAccountResponse,
didOverwriteUserUnselectedTanProcedure: Boolean, originalAreWeThatGentleToCloseDialogs: Boolean,
transactionsOfLast90DaysResponses: MutableList<GetTransactionsResponse>,
balances: MutableMap<AccountData, Money>, callback: (AddAccountResponse) -> Unit) {
retrievedAccountData: List<RetrievedAccountData>, successfullyReceivedAccountTransactionsResponse: Boolean,
callback: (AddAccountResponse) -> Unit) {
if (didOverwriteUserUnselectedTanProcedure) {
bank.resetSelectedTanProcedure()
}
areWeThatGentleToCloseDialogs = originalAreWeThatGentleToCloseDialogs
val supportsRetrievingTransactionsOfLast90DaysWithoutTan = transactionsOfLast90DaysResponses.firstOrNull { it.isSuccessful } != null
val unbookedTransactions = transactionsOfLast90DaysResponses.flatMap { it.unbookedTransactions }
val bookedTransactions = transactionsOfLast90DaysResponses.flatMap { it.bookedTransactions }
// TODO: to evaluate if adding account has been successful also check if count accounts > 0
callback(AddAccountResponse(newUserInfoResponse.toResponse(), bank, supportsRetrievingTransactionsOfLast90DaysWithoutTan,
bookedTransactions, unbookedTransactions, balances))
callback(AddAccountResponse(newUserInfoResponse.toResponse(), bank, successfullyReceivedAccountTransactionsResponse, retrievedAccountData))
}
@ -348,6 +347,7 @@ open class FinTsClient(
open fun getTransactionsAsync(parameter: GetTransactionsParameter, bank: BankData,
account: AccountData, callback: (GetTransactionsResponse) -> Unit) {
// TODO: or add default RetrievedAccountData if an error occurs?
val dialogContext = DialogContext(bank, product)
initDialog(dialogContext) { initDialogResponse ->
@ -374,10 +374,10 @@ open class FinTsClient(
val balance: Money? = balanceResponse.getFirstSegmentById<BalanceSegment>(InstituteSegmentId.Balance)?.let {
Money(it.balance, it.currency)
}
val retrievedData = RetrievedAccountData(account, balance, listOf(), listOf())
val message = messageBuilder.createGetTransactionsMessage(parameter, account, dialogContext)
val bookedTransactions = mutableSetOf<AccountTransaction>() // some banks like Postbank return some transactions multiple times -> remove these
var remainingMt940String = ""
dialogContext.abortIfTanIsRequired = parameter.abortIfTanIsRequired
@ -386,24 +386,19 @@ open class FinTsClient(
response.getFirstSegmentById<ReceivedAccountTransactions>(InstituteSegmentId.AccountTransactionsMt940)?.let { transactionsSegment ->
val (chunkTransaction, remainder) = mt940Parser.parseTransactionsChunk(remainingMt940String + transactionsSegment.bookedTransactionsString, account)
bookedTransactions.addAll(chunkTransaction)
retrievedData.addBookedTransactions(chunkTransaction)
remainingMt940String = remainder
parameter.retrievedChunkListener?.invoke(bookedTransactions)
parameter.retrievedChunkListener?.invoke(retrievedData.bookedTransactions)
}
}
getAndHandleResponseForMessage(message, dialogContext) { response ->
closeDialog(dialogContext)
callback(GetTransactionsResponse(
response,
bookedTransactions.toList(),
listOf(), // TODO: implement parsing MT942
balance,
callback(GetTransactionsResponse(response, listOf(retrievedData),
if (parameter.maxCountEntries != null) parameter.isSettingMaxCountEntriesAllowedByBank else null
)
)
))
}
}

View File

@ -0,0 +1,19 @@
package net.dankito.banking.fints.model
open class RetrievedAccountData(
val accountData: AccountData,
val balance: Money?,
var bookedTransactions: Collection<AccountTransaction>,
var unbookedTransactions: List<Any>
) {
open fun addBookedTransactions(transactions: List<AccountTransaction>) {
val bookedTransactions = this.bookedTransactions.toMutableList() // some banks like Postbank return some transactions multiple times -> remove these
bookedTransactions.addAll(transactions)
this.bookedTransactions = bookedTransactions
}
}

View File

@ -8,9 +8,5 @@ open class AddAccountResponse(
response: Response,
val bank: BankData,
val supportsRetrievingTransactionsOfLast90DaysWithoutTan: Boolean = false,
bookedTransactionsOfLast90Days: List<AccountTransaction> = listOf(),
unbookedTransactionsOfLast90Days: List<Any> = listOf(),
val balances: Map<AccountData, Money> = mapOf()
)
: GetTransactionsResponse(response, bookedTransactionsOfLast90Days, unbookedTransactionsOfLast90Days,
Money(Amount.Zero, balances.values.firstOrNull()?.currency?.code ?: "EUR")) // TODO: sum balances
retrievedData: List<RetrievedAccountData> = listOf()
) : GetTransactionsResponse(response, retrievedData)

View File

@ -1,15 +1,12 @@
package net.dankito.banking.fints.response.client
import net.dankito.banking.fints.model.AccountTransaction
import net.dankito.banking.fints.model.Money
import net.dankito.banking.fints.model.RetrievedAccountData
import net.dankito.banking.fints.response.Response
open class GetTransactionsResponse(
response: Response,
val bookedTransactions: List<AccountTransaction> = listOf(),
val unbookedTransactions: List<Any> = listOf(),
val balance: Money? = null,
val retrievedData: List<RetrievedAccountData> = listOf(),
/**
* This value is only set if [GetTransactionsParameter.maxCountEntries] was set to tell caller if maxCountEntries parameter has been evaluated or not
*/

View File

@ -0,0 +1,11 @@
package net.dankito.banking.ui.model
import net.dankito.utils.multiplatform.BigDecimal
open class RetrievedAccountData(
val account: TypedBankAccount,
val balance: BigDecimal?,
var bookedTransactions: Collection<IAccountTransaction>,
var unbookedTransactions: List<Any>
)

View File

@ -9,12 +9,9 @@ open class AddAccountResponse(
errorToShowToUser: String?,
val customer: TypedCustomer,
val supportsRetrievingTransactionsOfLast90DaysWithoutTan: Boolean = false,
val bookedTransactionsOfLast90Days: Map<TypedBankAccount, List<IAccountTransaction>> = mapOf(),
val unbookedTransactionsOfLast90Days: Map<TypedBankAccount, List<Any>> = mapOf(),
val balances: Map<TypedBankAccount, BigDecimal> = mapOf(),
val retrievedData: List<RetrievedAccountData> = listOf(),
userCancelledAction: Boolean = false
)
: BankingClientResponse(isSuccessful, errorToShowToUser, userCancelledAction) {
) : BankingClientResponse(isSuccessful, errorToShowToUser, userCancelledAction) {
override fun toString(): String {
return customer.toString() + " " + super.toString()

View File

@ -1,7 +1,6 @@
package net.dankito.banking.ui.model.responses
import net.dankito.utils.multiplatform.BigDecimal
import net.dankito.banking.ui.model.IAccountTransaction
import net.dankito.banking.ui.model.RetrievedAccountData
import net.dankito.banking.ui.model.TypedBankAccount
@ -9,10 +8,7 @@ open class GetTransactionsResponse(
val bankAccount: TypedBankAccount,
isSuccessful: Boolean,
errorToShowToUser: String?,
val bookedTransactions: List<IAccountTransaction> = listOf(),
val unbookedTransactions: List<Any> = listOf(),
val balance: BigDecimal? = null,
val retrievedData: List<RetrievedAccountData> = listOf(),
userCancelledAction: Boolean = false,
val tanRequiredButWeWereToldToAbortIfSo: Boolean = false
)
: BankingClientResponse(isSuccessful, errorToShowToUser, userCancelledAction)
) : BankingClientResponse(isSuccessful, errorToShowToUser, userCancelledAction)

View File

@ -160,21 +160,11 @@ open class BankingPresenter(
persistAccountOffUiThread(account)
if (response.supportsRetrievingTransactionsOfLast90DaysWithoutTan) {
response.bookedTransactionsOfLast90Days.keys.forEach { bankAccount ->
retrievedAccountTransactions(GetTransactionsResponse(bankAccount, true, null,
response.bookedTransactionsOfLast90Days[bankAccount] ?: listOf(),
response.unbookedTransactionsOfLast90Days[bankAccount] ?: listOf(),
response.balances[bankAccount]),
startDate, false
response.retrievedData.forEach { retrievedData ->
retrievedAccountTransactions(GetTransactionsResponse(retrievedData.account, true, null,
listOf(retrievedData)), startDate, false
)
}
}
else { // TODO: find a better way to update balances if transactions couldn't be retrieved
response.balances.keys.forEach { bankAccount ->
updateBalance(bankAccount, response.balances[bankAccount]!!)
}
}
findIconForBankAsync(account)
}
@ -346,13 +336,15 @@ open class BankingPresenter(
protected open fun retrievedAccountTransactions(response: GetTransactionsResponse, startDate: Date, didFetchAllTransactions: Boolean) {
if (response.isSuccessful) {
response.bankAccount.lastRetrievedTransactionsTimestamp = startDate
response.retrievedData.forEach { retrievedData ->
retrievedData.account.lastRetrievedTransactionsTimestamp = startDate
if (didFetchAllTransactions) {
response.bankAccount.haveAllTransactionsBeenFetched = true
retrievedData.account.haveAllTransactionsBeenFetched = true
}
updateAccountTransactionsAndBalances(response)
updateAccountTransactionsAndBalances(retrievedData)
}
}
callRetrievedAccountTransactionsResponseListener(response)
@ -363,22 +355,20 @@ open class BankingPresenter(
asyncRunner.runAsync { // don't block retrieving next chunk by blocked saving to db / json
updateAccountTransactions(bankAccount, accountTransactionsChunk)
callRetrievedAccountTransactionsResponseListener(GetTransactionsResponse(bankAccount, true, null, accountTransactionsChunk))
callRetrievedAccountTransactionsResponseListener(GetTransactionsResponse(bankAccount, true, null, listOf(RetrievedAccountData(bankAccount, null, accountTransactionsChunk, listOf()))))
}
}
}
protected open fun updateAccountTransactionsAndBalances(response: GetTransactionsResponse) {
val bankAccount = response.bankAccount
protected open fun updateAccountTransactionsAndBalances(retrievedData: RetrievedAccountData) {
updateAccountTransactions(retrievedData.account, retrievedData.bookedTransactions, retrievedData.unbookedTransactions)
updateAccountTransactions(bankAccount, response.bookedTransactions, response.unbookedTransactions)
response.balance?.let {
updateBalance(bankAccount, it)
retrievedData.balance?.let {
updateBalance(retrievedData.account, it)
}
}
protected open fun updateAccountTransactions(bankAccount: TypedBankAccount, bookedTransactions: List<IAccountTransaction>, unbookedTransactions: List<Any>? = null) {
protected open fun updateAccountTransactions(bankAccount: TypedBankAccount, bookedTransactions: Collection<IAccountTransaction>, unbookedTransactions: List<Any>? = null) {
val knownAccountTransactions = bankAccount.bookedTransactions.map { it.transactionIdentifier }
val newBookedTransactions = bookedTransactions.filterNot { knownAccountTransactions.contains(it.transactionIdentifier) }

View File

@ -15,45 +15,55 @@ import net.dankito.banking.fints.model.BankData
import net.dankito.banking.fints.response.client.FinTsClientResponse
import net.dankito.banking.fints.response.segments.AccountType
import net.dankito.banking.ui.model.mapper.IModelCreator
import net.dankito.utils.multiplatform.log.LoggerFactory
open class fints4kModelMapper(protected val modelCreator: IModelCreator) {
companion object {
private val log = LoggerFactory.getLogger(fints4kModelMapper::class)
}
open fun mapResponse(response: FinTsClientResponse): BankingClientResponse {
return BankingClientResponse(response.isSuccessful, mapErrorToShowToUser(response), response.userCancelledAction)
}
open fun mapResponse(customer: TypedCustomer, response: net.dankito.banking.fints.response.client.AddAccountResponse): AddAccountResponse {
val balances = response.balances.mapKeys { findMatchingBankAccount(customer, it.key) }.filter { it.key != null }
.mapValues { it.value.toBigDecimal() } as Map<TypedBankAccount, BigDecimal>
val bookedTransactions = response.bookedTransactions.associateBy { it.account }
val mappedBookedTransactions = mutableMapOf<TypedBankAccount, List<IAccountTransaction>>()
bookedTransactions.keys.forEach { accountData ->
findMatchingBankAccount(customer, accountData)?.let { bankAccount ->
mappedBookedTransactions.put(bankAccount, mapTransactions(bankAccount, response.bookedTransactions))
}
}
return AddAccountResponse(response.isSuccessful, mapErrorToShowToUser(response),
customer, response.supportsRetrievingTransactionsOfLast90DaysWithoutTan,
mappedBookedTransactions,
mapOf(), // TODO: map unbooked transactions
balances,
map(customer, response.retrievedData),
response.userCancelledAction)
}
open fun mapResponse(bankAccount: TypedBankAccount, response: net.dankito.banking.fints.response.client.GetTransactionsResponse): GetTransactionsResponse {
return GetTransactionsResponse(bankAccount, response.isSuccessful, mapErrorToShowToUser(response),
mapTransactions(bankAccount, response.bookedTransactions),
listOf(), // TODO: map unbooked transactions
response.balance?.toBigDecimal(),
map(bankAccount.customer as TypedCustomer, response.retrievedData),
response.userCancelledAction, response.tanRequiredButWeWereToldToAbortIfSo)
}
open fun map(customer: TypedCustomer, retrievedData: List<net.dankito.banking.fints.model.RetrievedAccountData>): List<RetrievedAccountData> {
return retrievedData.mapNotNull { map(customer, it) }
}
open fun map(customer: TypedCustomer, retrievedData: net.dankito.banking.fints.model.RetrievedAccountData): RetrievedAccountData? {
val account = findMatchingBankAccount(customer, retrievedData.accountData)
if (account == null) {
log.error("No matching account found for ${retrievedData.accountData}. Has there an account been added we didn't map yet?")
return null
}
return RetrievedAccountData(
account,
retrievedData.balance?.toBigDecimal(),
mapTransactions(account, retrievedData.bookedTransactions),
listOf()
)
}
open fun mapErrorToShowToUser(response: FinTsClientResponse): String? {
val errorMessage = response.errorMessage