diff --git a/ui/BankingAndroidApp/src/main/java/net/dankito/banking/ui/android/dialogs/AddAccountDialog.kt b/ui/BankingAndroidApp/src/main/java/net/dankito/banking/ui/android/dialogs/AddAccountDialog.kt
index efc9e3fa..3786f087 100644
--- a/ui/BankingAndroidApp/src/main/java/net/dankito/banking/ui/android/dialogs/AddAccountDialog.kt
+++ b/ui/BankingAndroidApp/src/main/java/net/dankito/banking/ui/android/dialogs/AddAccountDialog.kt
@@ -169,7 +169,7 @@ open class AddAccountDialog : DialogFragment() {
}
protected open fun retrieveAccountTransactionsAndDismiss(response: AddAccountResponse, messageDialog: DialogInterface) {
- presenter.fetchAccountTransactionsAsync(response.customer) { }
+ presenter.fetchAllAccountTransactionsAsync(response.customer) { }
messageDialog.dismiss()
}
diff --git a/ui/BankingJavaFxControls/src/main/kotlin/net/dankito/banking/ui/javafx/dialogs/AddAccountDialog.kt b/ui/BankingJavaFxControls/src/main/kotlin/net/dankito/banking/ui/javafx/dialogs/AddAccountDialog.kt
index 34cd6955..e9a929f4 100755
--- a/ui/BankingJavaFxControls/src/main/kotlin/net/dankito/banking/ui/javafx/dialogs/AddAccountDialog.kt
+++ b/ui/BankingJavaFxControls/src/main/kotlin/net/dankito/banking/ui/javafx/dialogs/AddAccountDialog.kt
@@ -272,7 +272,7 @@ open class AddAccountDialog(protected val presenter: BankingPresenter) : Window(
val userSelection = dialogService.showDialog(Alert.AlertType.CONFIRMATION, message, null, currentStage, ButtonType.YES, ButtonType.NO)
when (userSelection) {
- ButtonType.YES -> presenter.fetchAccountTransactionsAsync(response.customer) { }
+ ButtonType.YES -> presenter.fetchAllAccountTransactionsAsync(response.customer) { }
else -> { } // nothing to do then, simply close dialog
}
diff --git a/ui/BankingUiCommon/src/commonMain/kotlin/net/dankito/banking/ui/model/BankAccount.kt b/ui/BankingUiCommon/src/commonMain/kotlin/net/dankito/banking/ui/model/BankAccount.kt
index 54bb34c5..90d90057 100644
--- a/ui/BankingUiCommon/src/commonMain/kotlin/net/dankito/banking/ui/model/BankAccount.kt
+++ b/ui/BankingUiCommon/src/commonMain/kotlin/net/dankito/banking/ui/model/BankAccount.kt
@@ -40,6 +40,9 @@ open class BankAccount @JvmOverloads constructor(
open var technicalId: String = UUID.random()
+ open var haveAllTransactionsBeenFetched: Boolean = false
+
+
open var userSetDisplayName: String? = null
open val displayName: String
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 2c3b4648..2db2f69b 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
@@ -167,10 +167,11 @@ open class BankingPresenter(
if (response.supportsRetrievingTransactionsOfLast90DaysWithoutTan) {
response.bookedTransactionsOfLast90Days.keys.forEach { bankAccount ->
- retrievedAccountTransactions(startDate, GetTransactionsResponse(bankAccount, true, null,
+ retrievedAccountTransactions(GetTransactionsResponse(bankAccount, true, null,
response.bookedTransactionsOfLast90Days[bankAccount] ?: listOf(),
- response.unbookedTransactionsOfLast90Days[bankAccount] ?: listOf(),
- response.balances[bankAccount])
+ response.unbookedTransactionsOfLast90Days[bankAccount] ?: listOf(),
+ response.balances[bankAccount]),
+ startDate, false
)
}
}
@@ -257,18 +258,18 @@ open class BankingPresenter(
}
- open fun fetchAccountTransactionsAsync(customer: Customer,
- callback: (GetTransactionsResponse) -> Unit) {
+ open fun fetchAllAccountTransactionsAsync(customer: Customer,
+ callback: (GetTransactionsResponse) -> Unit) {
customer.accounts.forEach { bankAccount ->
if (bankAccount.supportsRetrievingAccountTransactions) {
- fetchAccountTransactionsAsync(bankAccount, callback) // TODO: use a synchronous version of fetchAccountTransactions() so that all bank accounts get handled serially
+ fetchAllAccountTransactionsAsync(bankAccount, callback) // TODO: use a synchronous version of fetchAccountTransactions() so that all bank accounts get handled serially
}
}
}
- open fun fetchAccountTransactionsAsync(bankAccount: BankAccount,
- callback: (GetTransactionsResponse) -> Unit) {
+ open fun fetchAllAccountTransactionsAsync(bankAccount: BankAccount,
+ callback: (GetTransactionsResponse) -> Unit) {
fetchAccountTransactionsAsync(bankAccount, null, false, callback)
}
@@ -282,7 +283,7 @@ open class BankingPresenter(
client.getTransactionsAsync(bankAccount, GetTransactionsParameter(true, fromDate, null, abortIfTanIsRequired, { receivedAccountsTransactionChunk(bankAccount, it) } )) { response ->
if (response.tanRequiredButWeWereToldToAbortIfSo == false) { // don't call retrievedAccountTransactions() if aborted due to TAN required but we told client to abort if so
- retrievedAccountTransactions(startDate, response)
+ retrievedAccountTransactions(response, startDate, fromDate == null)
}
callback(response)
@@ -326,10 +327,14 @@ open class BankingPresenter(
fetchAccountTransactionsAsync(bankAccount, fromDate, abortIfTanIsRequired, callback)
}
- protected open fun retrievedAccountTransactions(startDate: Date, response: GetTransactionsResponse) {
+ protected open fun retrievedAccountTransactions(response: GetTransactionsResponse, startDate: Date, didFetchAllTransactions: Boolean) {
if (response.isSuccessful) {
response.bankAccount.lastRetrievedTransactionsTimestamp = startDate
+ if (didFetchAllTransactions) {
+ response.bankAccount.haveAllTransactionsBeenFetched = true
+ }
+
updateAccountTransactionsAndBalances(response)
}
diff --git a/ui/BankingiOSApp/BankingiOSApp/BankingiOSApp.xcdatamodeld/BankingiOSApp.xcdatamodel/contents b/ui/BankingiOSApp/BankingiOSApp/BankingiOSApp.xcdatamodeld/BankingiOSApp.xcdatamodel/contents
index 88fe6d96..3abd187c 100644
--- a/ui/BankingiOSApp/BankingiOSApp/BankingiOSApp.xcdatamodeld/BankingiOSApp.xcdatamodel/contents
+++ b/ui/BankingiOSApp/BankingiOSApp/BankingiOSApp.xcdatamodeld/BankingiOSApp.xcdatamodel/contents
@@ -42,6 +42,7 @@
+
@@ -78,7 +79,7 @@
-
+
diff --git a/ui/BankingiOSApp/BankingiOSApp/Base.lproj/Localizable.strings b/ui/BankingiOSApp/BankingiOSApp/Base.lproj/Localizable.strings
index ebc90b20..5f6f7f94 100644
--- a/ui/BankingiOSApp/BankingiOSApp/Base.lproj/Localizable.strings
+++ b/ui/BankingiOSApp/BankingiOSApp/Base.lproj/Localizable.strings
@@ -45,8 +45,15 @@
/* AccountTransactionsDialog */
+"Fetch all account transactions" = "Alle Umsätze holen";
"Transfer money to %@" = "Transfer money to %@";
+"Could not fetch latest transactions" = "Could not fetch latest transactions";
+"Could not fetch latest transactions for %@. Error message from your bank: %@." = "Could not fetch latest transactions for %@.\nError message from your bank:\n%@.";
+
+"Could not fetch all transactions" = "Could not fetch all transactions";
+"Could not fetch all transactions for %@. Error message from your bank: %@." = "Could not fetch all transactions for %@.\nError message from your bank:\n%@.";
+
/* New action sheet */
diff --git a/ui/BankingiOSApp/BankingiOSApp/de.lproj/Localizable.strings b/ui/BankingiOSApp/BankingiOSApp/de.lproj/Localizable.strings
index 5e9891f7..94a76dd4 100644
--- a/ui/BankingiOSApp/BankingiOSApp/de.lproj/Localizable.strings
+++ b/ui/BankingiOSApp/BankingiOSApp/de.lproj/Localizable.strings
@@ -45,8 +45,15 @@
/* AccountTransactionsDialog */
+"Fetch all account transactions" = "Alle Umsätze holen";
"Transfer money to %@" = "Neue Überweisung an %@";
+"Could not fetch latest transactions" = "Umsätze konnte nicht aktualisiert werden";
+"Could not fetch latest transactions for %@. Error message from your bank: %@." = "Die Umsätze für %@ konnten nicht aktualisiert werden.\nFehlermeldung Ihrer Bank:\n%@.";
+
+"Could not fetch all transactions" = "Es konnte nicht all Umsätze geholt werden";
+"Could not fetch all transactions for %@. Error message from your bank: %@." = "Für %@ konnten nicht alle Umsätze geholt werden.\nFehlermeldung Ihrer Bank:\n%@.";
+
/* New action sheet */
diff --git a/ui/BankingiOSApp/BankingiOSApp/persistence/Mapper.swift b/ui/BankingiOSApp/BankingiOSApp/persistence/Mapper.swift
index 58e31db7..b27fe50e 100644
--- a/ui/BankingiOSApp/BankingiOSApp/persistence/Mapper.swift
+++ b/ui/BankingiOSApp/BankingiOSApp/persistence/Mapper.swift
@@ -63,6 +63,8 @@ class Mapper {
func map(_ customer: Customer, _ account: PersistedBankAccount) -> BankAccount {
let mapped = BankAccount(customer: customer, identifier: map(account.identifier), accountHolderName: map(account.accountHolderName), iban: account.iban, subAccountNumber: account.subAccountNumber, customerId: map(account.customerId), balance: map(account.balance), currency: map(account.currency), type: map(account.type), productName: account.productName, accountLimit: account.accountLimit, lastRetrievedTransactionsTimestamp: map(account.lastRetrievedTransactionsTimestamp), supportsRetrievingAccountTransactions: account.supportsRetrievingAccountTransactions, supportsRetrievingBalance: account.supportsRetrievingBalance, supportsTransferringMoney: account.supportsTransferringMoney, supportsInstantPaymentMoneyTransfer: account.supportsInstantPaymentMoneyTransfer, bookedTransactions: [], unbookedTransactions: [])
+ mapped.haveAllTransactionsBeenFetched = account.haveAllTransactionsBeenFetched
+
mapped.userSetDisplayName = account.userSetDisplayName
mapped.bookedTransactions = map(mapped, account.transactions as? Set)
@@ -96,6 +98,8 @@ class Mapper {
mapped.supportsTransferringMoney = account.supportsTransferringMoney
mapped.supportsInstantPaymentMoneyTransfer = account.supportsInstantPaymentMoneyTransfer
+ mapped.haveAllTransactionsBeenFetched = account.haveAllTransactionsBeenFetched
+
mapped.userSetDisplayName = account.userSetDisplayName
mapped.transactions = NSSet(array: map(mapped, account.bookedTransactions, context))
diff --git a/ui/BankingiOSApp/BankingiOSApp/ui/views/AccountTransactionsDialog.swift b/ui/BankingiOSApp/BankingiOSApp/ui/views/AccountTransactionsDialog.swift
index 936b1787..7cc70172 100644
--- a/ui/BankingiOSApp/BankingiOSApp/ui/views/AccountTransactionsDialog.swift
+++ b/ui/BankingiOSApp/BankingiOSApp/ui/views/AccountTransactionsDialog.swift
@@ -13,6 +13,13 @@ struct AccountTransactionsDialog: View {
private let areMoreThanOneBanksTransactionsDisplayed: Bool
+ @State private var haveAllTransactionsBeenFetched: Bool
+
+ @State private var showFetchAllTransactionsOverlay: Bool
+
+ @State private var accountsForWhichNotAllTransactionsHaveBeenFetched: [BankAccount]
+
+
@State private var filteredTransactions: [AccountTransaction]
@State private var balanceOfFilteredTransactions: CommonBigDecimal
@@ -29,28 +36,33 @@ struct AccountTransactionsDialog: View {
}
+ @State private var errorMessage: Message? = nil
+
+
@Inject private var presenter: BankingPresenterSwift
init(allBanks: [Customer]) {
- self.init(title: "All accounts", transactions: allBanks.flatMap { $0.accounts }.flatMap { $0.bookedTransactions }, balance: allBanks.sumBalances())
+ let allAccounts = allBanks.flatMap { $0.accounts }
+
+ self.init("All accounts", allAccounts.flatMap { $0.bookedTransactions }, allBanks.sumBalances(), allAccounts.filter { $0.haveAllTransactionsBeenFetched == false })
presenter.selectedAllBankAccounts()
}
init(bank: Customer) {
- self.init(title: bank.displayName, transactions: bank.accounts.flatMap { $0.bookedTransactions }, balance: bank.balance)
+ self.init(bank.displayName, bank.accounts.flatMap { $0.bookedTransactions }, bank.balance, bank.accounts.filter { $0.haveAllTransactionsBeenFetched == false })
presenter.selectedAccount(customer: bank)
}
init(account: BankAccount) {
- self.init(title: account.displayName, transactions: account.bookedTransactions, balance: account.balance)
+ self.init(account.displayName, account.bookedTransactions, account.balance, account.haveAllTransactionsBeenFetched ? [] : [account])
presenter.selectedBankAccount(bankAccount: account)
}
- fileprivate init(title: String, transactions: [AccountTransaction], balance: CommonBigDecimal) {
+ fileprivate init(_ title: String, _ transactions: [AccountTransaction], _ balance: CommonBigDecimal, _ accountsForWhichNotAllTransactionsHaveBeenFetched: [BankAccount] = []) {
self.title = title
self.allTransactions = transactions
@@ -60,6 +72,10 @@ struct AccountTransactionsDialog: View {
self._balanceOfFilteredTransactions = State(initialValue: balance)
self.areMoreThanOneBanksTransactionsDisplayed = Set(allTransactions.compactMap { $0.bankAccount }.compactMap { $0.customer }).count > 1
+
+ _accountsForWhichNotAllTransactionsHaveBeenFetched = State(initialValue: accountsForWhichNotAllTransactionsHaveBeenFetched)
+ _haveAllTransactionsBeenFetched = State(initialValue: accountsForWhichNotAllTransactionsHaveBeenFetched.isEmpty)
+ _showFetchAllTransactionsOverlay = State(initialValue: accountsForWhichNotAllTransactionsHaveBeenFetched.isNotEmpty)
}
@@ -77,26 +93,86 @@ struct AccountTransactionsDialog: View {
}
.padding(.horizontal)
- List(filteredTransactions.sorted(by: { $0.valueDate.date > $1.valueDate.date } ), id: \.technicalId) { transaction in
- AccountTransactionListItem(transaction, self.areMoreThanOneBanksTransactionsDisplayed)
+ Spacer()
+
+ List {
+ ForEach(filteredTransactions.sorted(by: { $0.valueDate.date > $1.valueDate.date } ), id: \.technicalId) { transaction in
+ AccountTransactionListItem(transaction, self.areMoreThanOneBanksTransactionsDisplayed)
+ }
+
+ if haveAllTransactionsBeenFetched == false {
+ Spacer()
+
+ HStack(alignment: .center) {
+ Spacer()
+
+ Button("Fetch all account transactions") {
+ self.fetchAllTransactions(self.accountsForWhichNotAllTransactionsHaveBeenFetched)
+ }
+
+ Spacer()
+ }
+ .frame(height: 35)
+ }
+ }
+
+ Spacer()
+
+ if showFetchAllTransactionsOverlay {
+ HStack(alignment: .center) {
+ Button("x") {
+ self.showFetchAllTransactionsOverlay = false
+ }
+
+ Spacer()
+
+ Button("Fetch all account transactions") {
+ self.fetchAllTransactions(self.accountsForWhichNotAllTransactionsHaveBeenFetched)
+ }
+
+ Spacer()
+ }
+ .frame(height: 35)
+ .padding(.top, 8)
+ .padding(.horizontal, 6)
+ .background(Color(UIColor.systemGroupedBackground))
}
}
+ .alert(item: $errorMessage) { message in
+ Alert(title: message.title, message: message.message, dismissButton: message.primaryButton)
+ }
.showNavigationBarTitle(LocalizedStringKey(title))
- .navigationBarItems(trailing: UpdateButton { _ in self.retrieveTransactions() })
+ .navigationBarItems(trailing: UpdateButton { _ in self.updateTransactions() })
}
- private func retrieveTransactions() {
+ private func updateTransactions() {
presenter.updateSelectedBankAccountTransactionsAsync { response in
if response.isSuccessful {
self.filterTransactions(self.searchText)
}
else if response.userCancelledAction == false {
- // TODO: show updating transactions failed message
+ self.errorMessage = Message(title: Text("Could not fetch latest transactions"), message: Text("Could not fetch latest transactions for \(response.bankAccount.displayName). Error message from your bank: \(response.errorToShowToUser ?? "")."))
}
}
}
+ private func fetchAllTransactions(_ accounts: [BankAccount]) {
+ accounts.forEach { account in
+ presenter.fetchAllAccountTransactionsAsync(bankAccount: account, callback: self.handleGetAllTransactionsResult)
+ }
+ }
+
+ private func handleGetAllTransactionsResult(_ response: GetTransactionsResponse) {
+ self.accountsForWhichNotAllTransactionsHaveBeenFetched = self.accountsForWhichNotAllTransactionsHaveBeenFetched.filter { $0.haveAllTransactionsBeenFetched == false }
+ self.haveAllTransactionsBeenFetched = self.accountsForWhichNotAllTransactionsHaveBeenFetched.isEmpty
+ self.showFetchAllTransactionsOverlay = self.accountsForWhichNotAllTransactionsHaveBeenFetched.isNotEmpty
+
+ if response.isSuccessful == false {
+ self.errorMessage = Message(title: Text("Could not fetch all transactions"), message: Text("Could not fetch all transactions for \(response.bankAccount.displayName). Error message from your bank: \(response.errorToShowToUser ?? "")."))
+ }
+ }
+
private func filterTransactions(_ query: String) {
self.filteredTransactions = presenter.searchSelectedAccountTransactions(query: query)
@@ -107,9 +183,9 @@ struct AccountTransactionsDialog: View {
struct AccountTransactionsDialog_Previews: PreviewProvider {
static var previews: some View {
- AccountTransactionsDialog(title: previewBanks[0].displayName, transactions: [
+ AccountTransactionsDialog(previewBanks[0].displayName, [
AccountTransaction(bankAccount: previewBanks[0].accounts[0], amount: CommonBigDecimal(double: 1234.56), currency: "€", unparsedUsage: "Usage", bookingDate: CommonDate(year: 2020, month: 5, day: 7), otherPartyName: "Marieke Musterfrau", otherPartyBankCode: nil, otherPartyAccountId: nil, bookingText: "SEPA Ueberweisung", valueDate: CommonDate(year: 2020, month: 5, day: 7))
- ],
- balance: CommonBigDecimal(double: 84.12))
+ ],
+ CommonBigDecimal(double: 84.12))
}
}