Implemented fetching all transactions

This commit is contained in:
dankito 2020-08-09 15:13:48 +02:00
parent bab012a9eb
commit b44161a45f
9 changed files with 128 additions and 25 deletions

View File

@ -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()
}

View File

@ -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
}

View File

@ -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

View File

@ -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.balances[bankAccount]),
startDate, false
)
}
}
@ -257,17 +258,17 @@ open class BankingPresenter(
}
open fun fetchAccountTransactionsAsync(customer: Customer,
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,
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)
}

View File

@ -42,6 +42,7 @@
<attribute name="balance" attributeType="Decimal" defaultValueString="0.0"/>
<attribute name="currency" attributeType="String"/>
<attribute name="customerId" attributeType="String"/>
<attribute name="haveAllTransactionsBeenFetched" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="iban" optional="YES" attributeType="String"/>
<attribute name="identifier" attributeType="String"/>
<attribute name="lastRetrievedTransactionsTimestamp" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
@ -78,7 +79,7 @@
</entity>
<elements>
<element name="PersistedAccountTransaction" positionX="-36" positionY="45" width="128" height="553"/>
<element name="PersistedBankAccount" positionX="-54" positionY="63" width="128" height="328"/>
<element name="PersistedBankAccount" positionX="-54" positionY="63" width="128" height="343"/>
<element name="PersistedCustomer" positionX="-63" positionY="-18" width="128" height="253"/>
<element name="PersistedTanProcedure" positionX="-54" positionY="135" width="128" height="88"/>
</elements>

View File

@ -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 */

View File

@ -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 */

View File

@ -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<PersistedAccountTransaction>)
@ -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))

View File

@ -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
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))
}
}