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 2c83ee9c..6ca59ea2 100644 --- a/fints4k/src/commonMain/kotlin/net/dankito/banking/fints/FinTsClient.kt +++ b/fints4k/src/commonMain/kotlin/net/dankito/banking/fints/FinTsClient.kt @@ -347,19 +347,18 @@ 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 -> if (initDialogResponse.successful == false) { - callback(GetTransactionsResponse(initDialogResponse)) + callback(GetTransactionsResponse(initDialogResponse, RetrievedAccountData.unsuccessfulList(account))) } else { mayGetBalance(parameter, account, dialogContext) { balanceResponse -> if (balanceResponse.successful == false && balanceResponse.couldCreateMessage == true) { // don't break here if required HKSAL message is not implemented closeDialog(dialogContext) - callback(GetTransactionsResponse(balanceResponse)) + callback(GetTransactionsResponse(balanceResponse, RetrievedAccountData.unsuccessfulList(account))) } else { getTransactionsAfterInitAndGetBalance(parameter, account, dialogContext, balanceResponse, callback) @@ -374,7 +373,8 @@ open class FinTsClient( val balance: Money? = balanceResponse.getFirstSegmentById(InstituteSegmentId.Balance)?.let { Money(it.balance, it.currency) } - val retrievedData = RetrievedAccountData(account, balance, listOf(), listOf()) + val bookedTransactions = mutableSetOf() + val unbookedTransactions = mutableSetOf() val message = messageBuilder.createGetTransactionsMessage(parameter, account, dialogContext) @@ -386,17 +386,19 @@ open class FinTsClient( response.getFirstSegmentById(InstituteSegmentId.AccountTransactionsMt940)?.let { transactionsSegment -> val (chunkTransaction, remainder) = mt940Parser.parseTransactionsChunk(remainingMt940String + transactionsSegment.bookedTransactionsString, account) - retrievedData.addBookedTransactions(chunkTransaction) + bookedTransactions.addAll(chunkTransaction) remainingMt940String = remainder - parameter.retrievedChunkListener?.invoke(retrievedData.bookedTransactions) + parameter.retrievedChunkListener?.invoke(bookedTransactions) } } getAndHandleResponseForMessage(message, dialogContext) { response -> closeDialog(dialogContext) - callback(GetTransactionsResponse(response, listOf(retrievedData), + val successful = response.successful && (parameter.alsoRetrieveBalance == false || balance != null) + + callback(GetTransactionsResponse(response, listOf(RetrievedAccountData(account, successful, balance, bookedTransactions, unbookedTransactions)), 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 index be9195ed..64036227 100644 --- a/fints4k/src/commonMain/kotlin/net/dankito/banking/fints/model/RetrievedAccountData.kt +++ b/fints4k/src/commonMain/kotlin/net/dankito/banking/fints/model/RetrievedAccountData.kt @@ -3,17 +3,21 @@ package net.dankito.banking.fints.model open class RetrievedAccountData( open val accountData: AccountData, + open val successfullyRetrievedData: Boolean, open val balance: Money?, open var bookedTransactions: Collection, - open var unbookedTransactions: List + open var unbookedTransactions: Collection ) { - open fun addBookedTransactions(transactions: List) { - val bookedTransactions = this.bookedTransactions.toMutableSet() // some banks like Postbank return some transactions multiple times -> remove these + companion object { - bookedTransactions.addAll(transactions) + fun unsuccessful(account: AccountData): RetrievedAccountData { + return RetrievedAccountData(account, false, null, listOf(), listOf()) + } + + fun unsuccessfulList(account: AccountData): List { + return listOf(unsuccessful(account)) + } - this.bookedTransactions = bookedTransactions } - } \ No newline at end of file diff --git a/ui/BankingAndroidApp/src/main/java/net/dankito/banking/ui/android/home/HomeFragment.kt b/ui/BankingAndroidApp/src/main/java/net/dankito/banking/ui/android/home/HomeFragment.kt index 5e601b0f..b654e094 100644 --- a/ui/BankingAndroidApp/src/main/java/net/dankito/banking/ui/android/home/HomeFragment.kt +++ b/ui/BankingAndroidApp/src/main/java/net/dankito/banking/ui/android/home/HomeFragment.kt @@ -10,7 +10,6 @@ import androidx.appcompat.app.AlertDialog import androidx.appcompat.widget.SearchView import androidx.fragment.app.Fragment import androidx.lifecycle.ViewModelProviders -import androidx.recyclerview.widget.DividerItemDecoration import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import kotlinx.android.synthetic.main.fragment_home.* @@ -161,7 +160,7 @@ class HomeFragment : Fragment() { presenter.addSelectedBankAccountsChangedListener { updateMenuItemsStateAndTransactionsToDisplay() } presenter.addRetrievedAccountTransactionsResponseListener { response -> - handleGetTransactionsResponse(response) + handleGetTransactionsResponseOffUiThread(response) } updateMenuItemsStateAndTransactionsToDisplay() @@ -181,19 +180,25 @@ class HomeFragment : Fragment() { presenter.updateSelectedBankAccountTransactionsAsync { } } - private fun handleGetTransactionsResponse(response: GetTransactionsResponse) { + private fun handleGetTransactionsResponseOffUiThread(response: GetTransactionsResponse) { context?.asActivity()?.let { activity -> activity.runOnUiThread { - if (response.isSuccessful) { - updateTransactionsToDisplayOnUiThread() - } - else if (response.userCancelledAction == false) { // if user cancelled entering TAN then don't show a error message - AlertDialog.Builder(activity) - .setMessage(activity.getString(R.string.fragment_home_could_not_retrieve_account_transactions, - response.bankAccount.displayName, response.errorToShowToUser)) - .setPositiveButton(android.R.string.ok) { dialog, _ -> dialog.dismiss() } - .show() - } + handleGetTransactionsResponseOnUiThread(activity, response) + } + } + } + + private fun handleGetTransactionsResponseOnUiThread(context: Context, response: GetTransactionsResponse) { + response.retrievedData.forEach { retrievedData -> + if (retrievedData.successfullyRetrievedData) { + updateTransactionsToDisplayOnUiThread() + } + else if (response.userCancelledAction == false) { // if user cancelled entering TAN then don't show a error message + AlertDialog.Builder(context) + .setMessage(context.getString(R.string.fragment_home_could_not_retrieve_account_transactions, + retrievedData.account.displayName, response.errorToShowToUser)) + .setPositiveButton(android.R.string.ok) { dialog, _ -> dialog.dismiss() } + .show() } } } diff --git a/ui/BankingJavaFxControls/src/main/kotlin/net/dankito/banking/ui/javafx/controls/AccountTransactionsView.kt b/ui/BankingJavaFxControls/src/main/kotlin/net/dankito/banking/ui/javafx/controls/AccountTransactionsView.kt index b80ec828..049a1236 100644 --- a/ui/BankingJavaFxControls/src/main/kotlin/net/dankito/banking/ui/javafx/controls/AccountTransactionsView.kt +++ b/ui/BankingJavaFxControls/src/main/kotlin/net/dankito/banking/ui/javafx/controls/AccountTransactionsView.kt @@ -143,13 +143,15 @@ open class AccountTransactionsView(private val presenter: BankingPresenter) : Vi } protected open fun handleGetTransactionsResponseOnUiThread(response: GetTransactionsResponse) { - if (response.isSuccessful) { - updateTransactionsToDisplay() - } - else if (response.userCancelledAction == false) { // if user cancelled entering TAN then don't show a error message - JavaFxDialogService().showErrorMessageOnUiThread( - String.format(messages["account.transactions.control.view.could.not.retrieve.account.transactions"], response.bankAccount.displayName, response.errorToShowToUser) - ) + response.retrievedData.forEach { retrievedData -> + if (retrievedData.successfullyRetrievedData) { + updateTransactionsToDisplay() + } + else if (response.userCancelledAction == false) { // if user cancelled entering TAN then don't show a error message + JavaFxDialogService().showErrorMessageOnUiThread( + String.format(messages["account.transactions.control.view.could.not.retrieve.account.transactions"], retrievedData.account.displayName, response.errorToShowToUser) + ) + } } } 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 index d840a8d9..7260b285 100644 --- 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 @@ -5,6 +5,7 @@ import net.dankito.utils.multiplatform.BigDecimal open class RetrievedAccountData( open val account: TypedBankAccount, + open val successfullyRetrievedData: Boolean, open val balance: BigDecimal?, open val bookedTransactions: Collection, open val unbookedTransactions: List 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 41546a20..dac9f86f 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,11 +1,9 @@ package net.dankito.banking.ui.model.responses import net.dankito.banking.ui.model.RetrievedAccountData -import net.dankito.banking.ui.model.TypedBankAccount open class GetTransactionsResponse( - open val bankAccount: TypedBankAccount, isSuccessful: Boolean, errorToShowToUser: String?, open val retrievedData: List = listOf(), 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 347b696c..9c3a04a8 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 @@ -161,9 +161,7 @@ open class BankingPresenter( persistAccountOffUiThread(account) response.retrievedData.forEach { retrievedData -> - retrievedAccountTransactions(GetTransactionsResponse(retrievedData.account, true, null, - listOf(retrievedData)), startDate, false - ) + retrievedAccountTransactions(GetTransactionsResponse(true, null, listOf(retrievedData)), startDate, false) } findIconForBankAsync(account) @@ -355,7 +353,7 @@ 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, listOf(RetrievedAccountData(bankAccount, null, accountTransactionsChunk, listOf())))) + callRetrievedAccountTransactionsResponseListener(GetTransactionsResponse(true, null, listOf(RetrievedAccountData(bankAccount, true, null, accountTransactionsChunk, listOf())))) } } } diff --git a/ui/fints4kBankingClient/src/commonMain/kotlin/net/dankito/banking/fints4kBankingClient.kt b/ui/fints4kBankingClient/src/commonMain/kotlin/net/dankito/banking/fints4kBankingClient.kt index d955b2d1..88064b97 100644 --- a/ui/fints4kBankingClient/src/commonMain/kotlin/net/dankito/banking/fints4kBankingClient.kt +++ b/ui/fints4kBankingClient/src/commonMain/kotlin/net/dankito/banking/fints4kBankingClient.kt @@ -78,7 +78,7 @@ open class fints4kBankingClient( override fun getTransactionsAsync(bankAccount: TypedBankAccount, parameter: GetTransactionsParameter, callback: (GetTransactionsResponse) -> Unit) { findAccountForBankAccount(bankAccount) { account, errorMessage -> if (account == null) { - callback(GetTransactionsResponse(bankAccount, false, errorMessage)) + callback(GetTransactionsResponse(false, errorMessage)) } else { val mappedParameter = GetTransactionsParameter(parameter.alsoRetrieveBalance, parameter.fromDate, 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 1b610684..f4474e83 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 @@ -39,7 +39,7 @@ open class fints4kModelMapper(protected val modelCreator: IModelCreator) { open fun mapResponse(bankAccount: TypedBankAccount, response: net.dankito.banking.fints.response.client.GetTransactionsResponse): GetTransactionsResponse { - return GetTransactionsResponse(bankAccount, response.isSuccessful, mapErrorToShowToUser(response), + return GetTransactionsResponse(response.isSuccessful, mapErrorToShowToUser(response), map(bankAccount.customer as TypedCustomer, response.retrievedData), response.userCancelledAction, response.tanRequiredButWeWereToldToAbortIfSo) } @@ -58,9 +58,10 @@ open class fints4kModelMapper(protected val modelCreator: IModelCreator) { return RetrievedAccountData( account, + retrievedData.successfullyRetrievedData, retrievedData.balance?.toBigDecimal(), mapTransactions(account, retrievedData.bookedTransactions), - listOf() + listOf() // TODO: map unbooked transactions ) } diff --git a/ui/hbci4jBankingClient/src/main/kotlin/net/dankito/banking/hbci4jBankingClient.kt b/ui/hbci4jBankingClient/src/main/kotlin/net/dankito/banking/hbci4jBankingClient.kt index 6575d113..e8ee2188 100644 --- a/ui/hbci4jBankingClient/src/main/kotlin/net/dankito/banking/hbci4jBankingClient.kt +++ b/ui/hbci4jBankingClient/src/main/kotlin/net/dankito/banking/hbci4jBankingClient.kt @@ -89,26 +89,28 @@ open class hbci4jBankingClient( } protected open fun tryToRetrieveAccountTransactionsForAddedAccounts(customer: TypedCustomer): AddAccountResponse { - val transactionsOfLast90DaysResponses = mutableListOf() - val balances = mutableMapOf() - val bookedTransactions = mutableMapOf>() - val unbookedTransactions = mutableMapOf>() + var supportsRetrievingTransactionsOfLast90DaysWithoutTan = false + var userCancelledAction = false - customer.accounts.forEach { bankAccount -> - if (bankAccount.supportsRetrievingAccountTransactions) { - val response = getTransactionsOfLast90Days(bankAccount) - transactionsOfLast90DaysResponses.add(response) + val retrievedData = customer.accounts.map { account -> + if (account.supportsRetrievingAccountTransactions) { + val response = getTransactionsOfLast90Days(account) + if (response.isSuccessful) { + supportsRetrievingTransactionsOfLast90DaysWithoutTan = true + } + if (response.userCancelledAction) { + userCancelledAction = true + } - bookedTransactions.put(bankAccount, response.bookedTransactions) - unbookedTransactions.put(bankAccount, response.unbookedTransactions) - balances.put(bankAccount, response.balance?.toBigDecimal() ?: BigDecimal.Zero) // TODO: really add BigDecimal.Zero if balance couldn't be retrieved? + response.retrievedData.first() + } + else { + RetrievedAccountData(account, false, null, listOf(), listOf()) } } - val supportsRetrievingTransactionsOfLast90DaysWithoutTan = transactionsOfLast90DaysResponses.firstOrNull { it.isSuccessful } != null - return AddAccountResponse(true, null, customer, supportsRetrievingTransactionsOfLast90DaysWithoutTan, - bookedTransactions, unbookedTransactions, balances) + retrievedData, userCancelledAction) } @@ -146,6 +148,7 @@ open class hbci4jBankingClient( protected open fun getTransactions(bankAccount: TypedBankAccount, parameter: GetTransactionsParameter): GetTransactionsResponse { val connection = connect() + val retrievingDataFailed = listOf(RetrievedAccountData(bankAccount, false, null, listOf(), listOf())) connection.handle?.let { handle -> try { @@ -153,8 +156,8 @@ open class hbci4jBankingClient( // Pruefen, ob die Kommunikation mit der Bank grundsaetzlich geklappt hat if (!status.isOK) { - log.error("Could not connect to bank ${credentials.bankCode} ${status.toString()}: ${status.errorString}") - return GetTransactionsResponse(bankAccount, false, "Could not connect to bank ${credentials.bankCode}: ${status.toString()}") + log.error("Could not connect to bank ${credentials.bankCode} $status: ${status.errorString}") + return GetTransactionsResponse(false, "Could not connect to bank ${credentials.bankCode}: $status", retrievingDataFailed) } // Auswertung des Saldo-Abrufs. @@ -163,7 +166,7 @@ open class hbci4jBankingClient( val balanceResult = nullableBalanceJob.jobResult as GVRSaldoReq if(balanceResult.isOK == false) { log.error("Could not fetch balance of bank account $bankAccount: $balanceResult", balanceResult.getJobStatus().exceptions) - return GetTransactionsResponse(bankAccount, false, "Could not fetch balance of bank account $bankAccount: $balanceResult") + return GetTransactionsResponse(false, "Could not fetch balance of bank account $bankAccount: $balanceResult", retrievingDataFailed) } balance = balanceResult.entries[0].ready.value.bigDecimalValue.toBigDecimal() @@ -177,15 +180,15 @@ open class hbci4jBankingClient( // Pruefen, ob der Abruf der Umsaetze geklappt hat if (result.isOK == false) { log.error("Could not get fetch account transactions of bank account $bankAccount: $result", result.getJobStatus().exceptions) - return GetTransactionsResponse(bankAccount, false, "Could not fetch account transactions of bank account $bankAccount: $result") + return GetTransactionsResponse(false, "Could not fetch account transactions of bank account $bankAccount: $result", retrievingDataFailed) } - return GetTransactionsResponse(bankAccount, true, null, accountTransactionMapper.mapAccountTransactions(bankAccount, result), - listOf(), balance?.toBigDecimal()) + return GetTransactionsResponse(true, null, listOf(RetrievedAccountData(bankAccount, true, balance.toBigDecimal(), + accountTransactionMapper.mapAccountTransactions(bankAccount, result), listOf()))) } catch(e: Exception) { log.error("Could not get accounting details for bank ${credentials.bankCode}", e) - return GetTransactionsResponse(bankAccount, false, e.getInnerExceptionMessage()) + return GetTransactionsResponse(false, e.getInnerExceptionMessage(), retrievingDataFailed) } finally { closeConnection(connection) @@ -194,7 +197,7 @@ open class hbci4jBankingClient( closeConnection(connection) - return GetTransactionsResponse(bankAccount, false, connection.error.getInnerExceptionMessage()) + return GetTransactionsResponse(false, connection.error.getInnerExceptionMessage(), retrievingDataFailed) } protected open fun executeJobsForGetAccountingEntries(handle: HBCIHandler, bankAccount: TypedBankAccount, parameter: GetTransactionsParameter): Triple {