From 7cb19d6f7d521527a2a07aa7d0313291f627feb3 Mon Sep 17 00:00:00 2001 From: dankito Date: Sat, 19 Sep 2020 01:41:02 +0200 Subject: [PATCH] Extracted RetrievedAccountData, makes code way better readable --- .../net/dankito/banking/fints/FinTsClient.kt | 45 +++++++++---------- .../fints/model/RetrievedAccountData.kt | 19 ++++++++ .../response/client/AddAccountResponse.kt | 8 +--- .../client/GetTransactionsResponse.kt | 7 +-- .../banking/ui/model/RetrievedAccountData.kt | 11 +++++ .../ui/model/responses/AddAccountResponse.kt | 7 +-- .../responses/GetTransactionsResponse.kt | 10 ++--- .../banking/ui/presenter/BankingPresenter.kt | 44 +++++++----------- .../banking/mapper/fints4kModelMapper.kt | 44 +++++++++++------- 9 files changed, 103 insertions(+), 92 deletions(-) create mode 100644 fints4k/src/commonMain/kotlin/net/dankito/banking/fints/model/RetrievedAccountData.kt create mode 100644 ui/BankingUiCommon/src/commonMain/kotlin/net/dankito/banking/ui/model/RetrievedAccountData.kt diff --git a/fints4k/src/commonMain/kotlin/net/dankito/banking/fints/FinTsClient.kt b/fints4k/src/commonMain/kotlin/net/dankito/banking/fints/FinTsClient.kt index eecdab95..2c83ee9c 100644 --- a/fints4k/src/commonMain/kotlin/net/dankito/banking/fints/FinTsClient.kt +++ b/fints4k/src/commonMain/kotlin/net/dankito/banking/fints/FinTsClient.kt @@ -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() - val balances = mutableMapOf() + // TODO: or add a default RetrievedAccountData instance for each account? + val retrievedAccountData = mutableListOf() + 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, - balances: MutableMap, callback: (AddAccountResponse) -> Unit) { + retrievedAccountData: List, 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(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() // 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(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, - if (parameter.maxCountEntries != null) parameter.isSettingMaxCountEntriesAllowedByBank else null - ) - ) + callback(GetTransactionsResponse(response, listOf(retrievedData), + if (parameter.maxCountEntries != null) parameter.isSettingMaxCountEntriesAllowedByBank else null + )) } } diff --git a/fints4k/src/commonMain/kotlin/net/dankito/banking/fints/model/RetrievedAccountData.kt b/fints4k/src/commonMain/kotlin/net/dankito/banking/fints/model/RetrievedAccountData.kt new file mode 100644 index 00000000..81e04ede --- /dev/null +++ b/fints4k/src/commonMain/kotlin/net/dankito/banking/fints/model/RetrievedAccountData.kt @@ -0,0 +1,19 @@ +package net.dankito.banking.fints.model + + +open class RetrievedAccountData( + val accountData: AccountData, + val balance: Money?, + var bookedTransactions: Collection, + var unbookedTransactions: List +) { + + open fun addBookedTransactions(transactions: List) { + val bookedTransactions = this.bookedTransactions.toMutableList() // some banks like Postbank return some transactions multiple times -> remove these + + bookedTransactions.addAll(transactions) + + this.bookedTransactions = bookedTransactions + } + +} \ No newline at end of file diff --git a/fints4k/src/commonMain/kotlin/net/dankito/banking/fints/response/client/AddAccountResponse.kt b/fints4k/src/commonMain/kotlin/net/dankito/banking/fints/response/client/AddAccountResponse.kt index cb8bbfa0..773b9c50 100644 --- a/fints4k/src/commonMain/kotlin/net/dankito/banking/fints/response/client/AddAccountResponse.kt +++ b/fints4k/src/commonMain/kotlin/net/dankito/banking/fints/response/client/AddAccountResponse.kt @@ -8,9 +8,5 @@ open class AddAccountResponse( response: Response, val bank: BankData, val supportsRetrievingTransactionsOfLast90DaysWithoutTan: Boolean = false, - bookedTransactionsOfLast90Days: List = listOf(), - unbookedTransactionsOfLast90Days: List = listOf(), - val balances: Map = mapOf() -) - : GetTransactionsResponse(response, bookedTransactionsOfLast90Days, unbookedTransactionsOfLast90Days, - Money(Amount.Zero, balances.values.firstOrNull()?.currency?.code ?: "EUR")) // TODO: sum balances \ No newline at end of file + retrievedData: List = listOf() +) : GetTransactionsResponse(response, retrievedData) \ No newline at end of file diff --git a/fints4k/src/commonMain/kotlin/net/dankito/banking/fints/response/client/GetTransactionsResponse.kt b/fints4k/src/commonMain/kotlin/net/dankito/banking/fints/response/client/GetTransactionsResponse.kt index 0ecf133a..67128a9e 100644 --- a/fints4k/src/commonMain/kotlin/net/dankito/banking/fints/response/client/GetTransactionsResponse.kt +++ b/fints4k/src/commonMain/kotlin/net/dankito/banking/fints/response/client/GetTransactionsResponse.kt @@ -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 = listOf(), - val unbookedTransactions: List = listOf(), - val balance: Money? = null, + val retrievedData: List = listOf(), /** * This value is only set if [GetTransactionsParameter.maxCountEntries] was set to tell caller if maxCountEntries parameter has been evaluated or not */ diff --git a/ui/BankingUiCommon/src/commonMain/kotlin/net/dankito/banking/ui/model/RetrievedAccountData.kt b/ui/BankingUiCommon/src/commonMain/kotlin/net/dankito/banking/ui/model/RetrievedAccountData.kt new file mode 100644 index 00000000..73b466a0 --- /dev/null +++ b/ui/BankingUiCommon/src/commonMain/kotlin/net/dankito/banking/ui/model/RetrievedAccountData.kt @@ -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, + var unbookedTransactions: List +) \ No newline at end of file diff --git a/ui/BankingUiCommon/src/commonMain/kotlin/net/dankito/banking/ui/model/responses/AddAccountResponse.kt b/ui/BankingUiCommon/src/commonMain/kotlin/net/dankito/banking/ui/model/responses/AddAccountResponse.kt index 8962823c..f98f99a7 100644 --- a/ui/BankingUiCommon/src/commonMain/kotlin/net/dankito/banking/ui/model/responses/AddAccountResponse.kt +++ b/ui/BankingUiCommon/src/commonMain/kotlin/net/dankito/banking/ui/model/responses/AddAccountResponse.kt @@ -9,12 +9,9 @@ open class AddAccountResponse( errorToShowToUser: String?, val customer: TypedCustomer, val supportsRetrievingTransactionsOfLast90DaysWithoutTan: Boolean = false, - val bookedTransactionsOfLast90Days: Map> = mapOf(), - val unbookedTransactionsOfLast90Days: Map> = mapOf(), - val balances: Map = mapOf(), + val retrievedData: List = listOf(), userCancelledAction: Boolean = false -) - : BankingClientResponse(isSuccessful, errorToShowToUser, userCancelledAction) { +) : BankingClientResponse(isSuccessful, errorToShowToUser, userCancelledAction) { override fun toString(): String { return customer.toString() + " " + super.toString() diff --git a/ui/BankingUiCommon/src/commonMain/kotlin/net/dankito/banking/ui/model/responses/GetTransactionsResponse.kt b/ui/BankingUiCommon/src/commonMain/kotlin/net/dankito/banking/ui/model/responses/GetTransactionsResponse.kt index 2ea46a37..472e7145 100644 --- a/ui/BankingUiCommon/src/commonMain/kotlin/net/dankito/banking/ui/model/responses/GetTransactionsResponse.kt +++ b/ui/BankingUiCommon/src/commonMain/kotlin/net/dankito/banking/ui/model/responses/GetTransactionsResponse.kt @@ -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 = listOf(), - val unbookedTransactions: List = listOf(), - val balance: BigDecimal? = null, + val retrievedData: List = listOf(), userCancelledAction: Boolean = false, val tanRequiredButWeWereToldToAbortIfSo: Boolean = false -) - : BankingClientResponse(isSuccessful, errorToShowToUser, userCancelledAction) +) : BankingClientResponse(isSuccessful, errorToShowToUser, userCancelledAction) diff --git a/ui/BankingUiCommon/src/commonMain/kotlin/net/dankito/banking/ui/presenter/BankingPresenter.kt b/ui/BankingUiCommon/src/commonMain/kotlin/net/dankito/banking/ui/presenter/BankingPresenter.kt index 4db84d99..347b696c 100644 --- a/ui/BankingUiCommon/src/commonMain/kotlin/net/dankito/banking/ui/presenter/BankingPresenter.kt +++ b/ui/BankingUiCommon/src/commonMain/kotlin/net/dankito/banking/ui/presenter/BankingPresenter.kt @@ -160,20 +160,10 @@ 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 - ) - } - } - 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]!!) - } + response.retrievedData.forEach { retrievedData -> + retrievedAccountTransactions(GetTransactionsResponse(retrievedData.account, true, null, + listOf(retrievedData)), startDate, false + ) } 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 + if (didFetchAllTransactions) { + retrievedData.account.haveAllTransactionsBeenFetched = true + } + + updateAccountTransactionsAndBalances(retrievedData) } - - updateAccountTransactionsAndBalances(response) } 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, unbookedTransactions: List? = null) { + protected open fun updateAccountTransactions(bankAccount: TypedBankAccount, bookedTransactions: Collection, unbookedTransactions: List? = null) { val knownAccountTransactions = bankAccount.bookedTransactions.map { it.transactionIdentifier } val newBookedTransactions = bookedTransactions.filterNot { knownAccountTransactions.contains(it.transactionIdentifier) } diff --git a/ui/fints4kBankingClient/src/commonMain/kotlin/net/dankito/banking/mapper/fints4kModelMapper.kt b/ui/fints4kBankingClient/src/commonMain/kotlin/net/dankito/banking/mapper/fints4kModelMapper.kt index 69c8a2e9..1b610684 100644 --- a/ui/fints4kBankingClient/src/commonMain/kotlin/net/dankito/banking/mapper/fints4kModelMapper.kt +++ b/ui/fints4kBankingClient/src/commonMain/kotlin/net/dankito/banking/mapper/fints4kModelMapper.kt @@ -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 - - val bookedTransactions = response.bookedTransactions.associateBy { it.account } - val mappedBookedTransactions = mutableMapOf>() - - 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): List { + 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