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

View File

@ -1,15 +1,12 @@
package net.dankito.banking.fints.response.client package net.dankito.banking.fints.response.client
import net.dankito.banking.fints.model.AccountTransaction import net.dankito.banking.fints.model.RetrievedAccountData
import net.dankito.banking.fints.model.Money
import net.dankito.banking.fints.response.Response import net.dankito.banking.fints.response.Response
open class GetTransactionsResponse( open class GetTransactionsResponse(
response: Response, response: Response,
val bookedTransactions: List<AccountTransaction> = listOf(), val retrievedData: List<RetrievedAccountData> = listOf(),
val unbookedTransactions: List<Any> = listOf(),
val balance: Money? = null,
/** /**
* This value is only set if [GetTransactionsParameter.maxCountEntries] was set to tell caller if maxCountEntries parameter has been evaluated or not * 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?, errorToShowToUser: String?,
val customer: TypedCustomer, val customer: TypedCustomer,
val supportsRetrievingTransactionsOfLast90DaysWithoutTan: Boolean = false, val supportsRetrievingTransactionsOfLast90DaysWithoutTan: Boolean = false,
val bookedTransactionsOfLast90Days: Map<TypedBankAccount, List<IAccountTransaction>> = mapOf(), val retrievedData: List<RetrievedAccountData> = listOf(),
val unbookedTransactionsOfLast90Days: Map<TypedBankAccount, List<Any>> = mapOf(),
val balances: Map<TypedBankAccount, BigDecimal> = mapOf(),
userCancelledAction: Boolean = false userCancelledAction: Boolean = false
) ) : BankingClientResponse(isSuccessful, errorToShowToUser, userCancelledAction) {
: BankingClientResponse(isSuccessful, errorToShowToUser, userCancelledAction) {
override fun toString(): String { override fun toString(): String {
return customer.toString() + " " + super.toString() return customer.toString() + " " + super.toString()

View File

@ -1,7 +1,6 @@
package net.dankito.banking.ui.model.responses package net.dankito.banking.ui.model.responses
import net.dankito.utils.multiplatform.BigDecimal import net.dankito.banking.ui.model.RetrievedAccountData
import net.dankito.banking.ui.model.IAccountTransaction
import net.dankito.banking.ui.model.TypedBankAccount import net.dankito.banking.ui.model.TypedBankAccount
@ -9,10 +8,7 @@ open class GetTransactionsResponse(
val bankAccount: TypedBankAccount, val bankAccount: TypedBankAccount,
isSuccessful: Boolean, isSuccessful: Boolean,
errorToShowToUser: String?, errorToShowToUser: String?,
val bookedTransactions: List<IAccountTransaction> = listOf(), val retrievedData: List<RetrievedAccountData> = listOf(),
val unbookedTransactions: List<Any> = listOf(),
val balance: BigDecimal? = null,
userCancelledAction: Boolean = false, userCancelledAction: Boolean = false,
val tanRequiredButWeWereToldToAbortIfSo: 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) persistAccountOffUiThread(account)
if (response.supportsRetrievingTransactionsOfLast90DaysWithoutTan) { response.retrievedData.forEach { retrievedData ->
response.bookedTransactionsOfLast90Days.keys.forEach { bankAccount -> retrievedAccountTransactions(GetTransactionsResponse(retrievedData.account, true, null,
retrievedAccountTransactions(GetTransactionsResponse(bankAccount, true, null, listOf(retrievedData)), startDate, false
response.bookedTransactionsOfLast90Days[bankAccount] ?: listOf(),
response.unbookedTransactionsOfLast90Days[bankAccount] ?: listOf(),
response.balances[bankAccount]),
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) findIconForBankAsync(account)
} }
@ -346,13 +336,15 @@ open class BankingPresenter(
protected open fun retrievedAccountTransactions(response: GetTransactionsResponse, startDate: Date, didFetchAllTransactions: Boolean) { protected open fun retrievedAccountTransactions(response: GetTransactionsResponse, startDate: Date, didFetchAllTransactions: Boolean) {
if (response.isSuccessful) { if (response.isSuccessful) {
response.bankAccount.lastRetrievedTransactionsTimestamp = startDate response.retrievedData.forEach { retrievedData ->
retrievedData.account.lastRetrievedTransactionsTimestamp = startDate
if (didFetchAllTransactions) { if (didFetchAllTransactions) {
response.bankAccount.haveAllTransactionsBeenFetched = true retrievedData.account.haveAllTransactionsBeenFetched = true
} }
updateAccountTransactionsAndBalances(response) updateAccountTransactionsAndBalances(retrievedData)
}
} }
callRetrievedAccountTransactionsResponseListener(response) callRetrievedAccountTransactionsResponseListener(response)
@ -363,22 +355,20 @@ open class BankingPresenter(
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(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) { protected open fun updateAccountTransactionsAndBalances(retrievedData: RetrievedAccountData) {
val bankAccount = response.bankAccount updateAccountTransactions(retrievedData.account, retrievedData.bookedTransactions, retrievedData.unbookedTransactions)
updateAccountTransactions(bankAccount, response.bookedTransactions, response.unbookedTransactions) retrievedData.balance?.let {
updateBalance(retrievedData.account, it)
response.balance?.let {
updateBalance(bankAccount, 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 knownAccountTransactions = bankAccount.bookedTransactions.map { it.transactionIdentifier }
val newBookedTransactions = bookedTransactions.filterNot { knownAccountTransactions.contains(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.client.FinTsClientResponse
import net.dankito.banking.fints.response.segments.AccountType import net.dankito.banking.fints.response.segments.AccountType
import net.dankito.banking.ui.model.mapper.IModelCreator import net.dankito.banking.ui.model.mapper.IModelCreator
import net.dankito.utils.multiplatform.log.LoggerFactory
open class fints4kModelMapper(protected val modelCreator: IModelCreator) { open class fints4kModelMapper(protected val modelCreator: IModelCreator) {
companion object {
private val log = LoggerFactory.getLogger(fints4kModelMapper::class)
}
open fun mapResponse(response: FinTsClientResponse): BankingClientResponse { open fun mapResponse(response: FinTsClientResponse): BankingClientResponse {
return BankingClientResponse(response.isSuccessful, mapErrorToShowToUser(response), response.userCancelledAction) return BankingClientResponse(response.isSuccessful, mapErrorToShowToUser(response), response.userCancelledAction)
} }
open fun mapResponse(customer: TypedCustomer, response: net.dankito.banking.fints.response.client.AddAccountResponse): AddAccountResponse { 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), return AddAccountResponse(response.isSuccessful, mapErrorToShowToUser(response),
customer, response.supportsRetrievingTransactionsOfLast90DaysWithoutTan, customer, response.supportsRetrievingTransactionsOfLast90DaysWithoutTan,
mappedBookedTransactions, map(customer, response.retrievedData),
mapOf(), // TODO: map unbooked transactions
balances,
response.userCancelledAction) response.userCancelledAction)
} }
open fun mapResponse(bankAccount: TypedBankAccount, response: net.dankito.banking.fints.response.client.GetTransactionsResponse): GetTransactionsResponse { open fun mapResponse(bankAccount: TypedBankAccount, response: net.dankito.banking.fints.response.client.GetTransactionsResponse): GetTransactionsResponse {
return GetTransactionsResponse(bankAccount, response.isSuccessful, mapErrorToShowToUser(response), return GetTransactionsResponse(bankAccount, response.isSuccessful, mapErrorToShowToUser(response),
mapTransactions(bankAccount, response.bookedTransactions), map(bankAccount.customer as TypedCustomer, response.retrievedData),
listOf(), // TODO: map unbooked transactions
response.balance?.toBigDecimal(),
response.userCancelledAction, response.tanRequiredButWeWereToldToAbortIfSo) 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? { open fun mapErrorToShowToUser(response: FinTsClientResponse): String? {
val errorMessage = response.errorMessage val errorMessage = response.errorMessage