Clarified that getTransactionsAsync() only retrieves the transactions of one account; also fixed data model in regard to this

This commit is contained in:
dankito 2021-11-13 15:17:06 +01:00
parent e90579a1b7
commit e87adc8499
24 changed files with 182 additions and 122 deletions

View File

@ -64,7 +64,7 @@ open class FinTsClient @JvmOverloads constructor(
jobExecutor.retrieveBasicDataLikeUsersTanMethods(context, parameter.preferredTanMethods, parameter.preferredTanMedium) { newUserInfoResponse -> jobExecutor.retrieveBasicDataLikeUsersTanMethods(context, parameter.preferredTanMethods, parameter.preferredTanMedium) { newUserInfoResponse ->
if (newUserInfoResponse.successful == false) { // bank parameter (FinTS server address, ...) already seem to be wrong if (newUserInfoResponse.successful == false) { // bank parameter (FinTS server address, ...) already seem to be wrong
callback(AddAccountResponse(context, newUserInfoResponse, bank)) callback(AddAccountResponse(context, newUserInfoResponse))
return@retrieveBasicDataLikeUsersTanMethods return@retrieveBasicDataLikeUsersTanMethods
} }
@ -82,7 +82,7 @@ open class FinTsClient @JvmOverloads constructor(
jobExecutor.getAccounts(context) { getAccountsResponse -> jobExecutor.getAccounts(context) { getAccountsResponse ->
if (getAccountsResponse.successful == false) { if (getAccountsResponse.successful == false) {
callback(AddAccountResponse(context, getAccountsResponse, context.bank)) callback(AddAccountResponse(context, getAccountsResponse))
return@getAccounts return@getAccounts
} }
@ -92,8 +92,7 @@ open class FinTsClient @JvmOverloads constructor(
addAccountGetAccountBalancesAndTransactions(context, getAccountsResponse, callback) addAccountGetAccountBalancesAndTransactions(context, getAccountsResponse, callback)
} }
else { else {
val retrievedAccountData = context.bank.accounts.associateBy( { it }, { RetrievedAccountData.balanceAndTransactionsNotRequestedByUser(it) } ) addAccountDone(context, getAccountsResponse, listOf(), callback)
addAccountDone(context, getAccountsResponse, retrievedAccountData, callback)
} }
} }
} }
@ -102,38 +101,34 @@ open class FinTsClient @JvmOverloads constructor(
callback: (AddAccountResponse) -> Unit) { callback: (AddAccountResponse) -> Unit) {
val bank = context.bank val bank = context.bank
val retrievedAccountData = bank.accounts.associateBy( { it }, { RetrievedAccountData.unsuccessful(it) } ).toMutableMap() val retrievedTransactionsResponses = mutableListOf<GetAccountTransactionsResponse>()
val accountsSupportingRetrievingTransactions = bank.accounts.filter { it.supportsRetrievingBalance || it.supportsRetrievingAccountTransactions } val accountsSupportingRetrievingTransactions = bank.accounts.filter { it.supportsRetrievingBalance || it.supportsRetrievingAccountTransactions }
val countAccountsSupportingRetrievingTransactions = accountsSupportingRetrievingTransactions.size val countAccountsSupportingRetrievingTransactions = accountsSupportingRetrievingTransactions.size
var countRetrievedAccounts = 0 var countRetrievedAccounts = 0
if (countAccountsSupportingRetrievingTransactions == 0) { if (countAccountsSupportingRetrievingTransactions == 0) {
addAccountDone(context, getAccountsResponse, retrievedAccountData, callback) addAccountDone(context, getAccountsResponse, retrievedTransactionsResponses, callback)
return // no necessary just to make it clearer that code below doesn't get called return // not necessary just to make it clearer that code below doesn't get called
} }
accountsSupportingRetrievingTransactions.forEach { account -> accountsSupportingRetrievingTransactions.forEach { account ->
tryGetTransactionsOfLast90DaysWithoutTan(bank, account) { response -> tryGetAccountTransactionsOfLast90DaysWithoutTan(bank, account) { response ->
retrievedAccountData.put(account, response.retrievedData.first()) retrievedTransactionsResponses.add(response)
if (response.internalError != null) { // TODO: errors from response get lost! User then only sees "null" as error message
//getAccountsResponse.errorMessage = response.errorMessage
}
countRetrievedAccounts++ countRetrievedAccounts++
if (countRetrievedAccounts == countAccountsSupportingRetrievingTransactions) { if (countRetrievedAccounts == countAccountsSupportingRetrievingTransactions) {
addAccountDone(context, getAccountsResponse, retrievedAccountData, callback) addAccountDone(context, getAccountsResponse, retrievedTransactionsResponses, callback)
} }
} }
} }
} }
protected open fun addAccountDone(context: JobContext, getAccountsResponse: BankResponse, protected open fun addAccountDone(context: JobContext, getAccountsResponse: BankResponse,
retrievedAccountData: Map<AccountData, RetrievedAccountData>, retrievedTransactionsResponses: List<GetAccountTransactionsResponse>,
callback: (AddAccountResponse) -> Unit) { callback: (AddAccountResponse) -> Unit) {
callback(AddAccountResponse(context, getAccountsResponse, context.bank, retrievedAccountData.values.toList())) callback(AddAccountResponse(context, getAccountsResponse, retrievedTransactionsResponses))
} }
@ -143,18 +138,20 @@ open class FinTsClient @JvmOverloads constructor(
* *
* Check if bank supports this. * Check if bank supports this.
*/ */
open fun tryGetTransactionsOfLast90DaysWithoutTan(bank: BankData, account: AccountData, callback: (GetTransactionsResponse) -> Unit) { open fun tryGetAccountTransactionsOfLast90DaysWithoutTan(bank: BankData, account: AccountData, callback: (GetAccountTransactionsResponse) -> Unit) {
val ninetyDaysAgo = Date.today.addDays(-90) getAccountTransactionsAsync(createGetAccountTransactionsOfLast90DaysParameter(bank, account), callback)
getTransactionsAsync(GetTransactionsParameter(account, account.supportsRetrievingBalance, ninetyDaysAgo, abortIfTanIsRequired = true), bank) { response ->
callback(response)
}
} }
open fun getTransactionsAsync(parameter: GetTransactionsParameter, bank: BankData, callback: (GetTransactionsResponse) -> Unit) { protected open fun createGetAccountTransactionsOfLast90DaysParameter(bank: BankData, account: AccountData): GetAccountTransactionsParameter {
val ninetyDaysAgo = Date.today.addDays(-90)
val context = JobContext(JobContextType.GetTransactions, this.callback, product, bank, parameter.account) return GetAccountTransactionsParameter(bank, account, account.supportsRetrievingBalance, ninetyDaysAgo, abortIfTanIsRequired = true)
}
open fun getAccountTransactionsAsync(parameter: GetAccountTransactionsParameter, callback: (GetAccountTransactionsResponse) -> Unit) {
val context = JobContext(JobContextType.GetTransactions, this.callback, product, parameter.bank, parameter.account)
jobExecutor.getTransactionsAsync(context, parameter, callback) jobExecutor.getTransactionsAsync(context, parameter, callback)
} }

View File

@ -6,9 +6,7 @@ import net.dankito.banking.fints.model.*
import net.dankito.banking.fints.model.mapper.ModelMapper import net.dankito.banking.fints.model.mapper.ModelMapper
import net.dankito.banking.fints.response.client.AddAccountResponse import net.dankito.banking.fints.response.client.AddAccountResponse
import net.dankito.banking.fints.response.client.FinTsClientResponse import net.dankito.banking.fints.response.client.FinTsClientResponse
import net.dankito.banking.fints.response.client.GetTransactionsResponse import net.dankito.banking.fints.response.client.GetAccountTransactionsResponse
import net.dankito.banking.fints.transactions.IAccountTransactionsParser
import net.dankito.banking.fints.transactions.Mt940AccountTransactionsParser
import net.dankito.banking.fints.util.IBase64Service import net.dankito.banking.fints.util.IBase64Service
import net.dankito.banking.fints.util.PureKotlinBase64Service import net.dankito.banking.fints.util.PureKotlinBase64Service
import net.dankito.banking.fints.util.TanMethodSelector import net.dankito.banking.fints.util.TanMethodSelector
@ -50,8 +48,8 @@ open class FinTsClientForCustomer(
} }
open fun getTransactionsAsync(parameter: GetTransactionsParameter, callback: (GetTransactionsResponse) -> Unit) { open fun getAccountTransactionsAsync(parameter: GetAccountTransactionsParameter, callback: (GetAccountTransactionsResponse) -> Unit) {
client.getTransactionsAsync(parameter, bank, callback) client.getAccountTransactionsAsync(parameter, callback)
} }

View File

@ -10,10 +10,7 @@ import net.dankito.banking.fints.model.*
import net.dankito.banking.fints.model.mapper.ModelMapper import net.dankito.banking.fints.model.mapper.ModelMapper
import net.dankito.banking.fints.response.BankResponse import net.dankito.banking.fints.response.BankResponse
import net.dankito.banking.fints.response.InstituteSegmentId import net.dankito.banking.fints.response.InstituteSegmentId
import net.dankito.banking.fints.response.client.FinTsClientResponse import net.dankito.banking.fints.response.client.*
import net.dankito.banking.fints.response.client.GetTanMediaListResponse
import net.dankito.banking.fints.response.client.GetTransactionsResponse
import net.dankito.banking.fints.response.client.GetUserTanMethodsResponse
import net.dankito.banking.fints.response.segments.* import net.dankito.banking.fints.response.segments.*
import net.dankito.banking.fints.tan.FlickerCodeDecoder import net.dankito.banking.fints.tan.FlickerCodeDecoder
import net.dankito.banking.fints.tan.TanImageDecoder import net.dankito.banking.fints.tan.TanImageDecoder
@ -178,14 +175,14 @@ open class FinTsJobExecutor(
} }
open fun getTransactionsAsync(context: JobContext, parameter: GetTransactionsParameter, callback: (GetTransactionsResponse) -> Unit) { open fun getTransactionsAsync(context: JobContext, parameter: GetAccountTransactionsParameter, callback: (GetAccountTransactionsResponse) -> Unit) {
val dialogContext = context.startNewDialog() val dialogContext = context.startNewDialog()
initDialogWithStrongCustomerAuthentication(context) { initDialogResponse -> initDialogWithStrongCustomerAuthentication(context) { initDialogResponse ->
if (initDialogResponse.successful == false) { if (initDialogResponse.successful == false) {
callback(GetTransactionsResponse(context, initDialogResponse, RetrievedAccountData.unsuccessfulList(parameter.account))) callback(GetAccountTransactionsResponse(context, initDialogResponse, RetrievedAccountData.unsuccessful(parameter.account)))
} }
else { else {
// we now retrieved the fresh account information from FinTS server, use that one // we now retrieved the fresh account information from FinTS server, use that one
@ -193,7 +190,7 @@ open class FinTsJobExecutor(
mayGetBalance(context, parameter) { balanceResponse -> mayGetBalance(context, parameter) { balanceResponse ->
if (dialogContext.didBankCloseDialog) { if (dialogContext.didBankCloseDialog) {
callback(GetTransactionsResponse(context, balanceResponse ?: initDialogResponse, RetrievedAccountData.unsuccessfulList(parameter.account))) callback(GetAccountTransactionsResponse(context, balanceResponse ?: initDialogResponse, RetrievedAccountData.unsuccessful(parameter.account)))
} }
else { else {
getTransactionsAfterInitAndGetBalance(context, parameter, balanceResponse, callback) getTransactionsAfterInitAndGetBalance(context, parameter, balanceResponse, callback)
@ -207,8 +204,8 @@ open class FinTsJobExecutor(
return context.bank.accounts.firstOrNull { it.accountIdentifier == account.accountIdentifier } ?: account return context.bank.accounts.firstOrNull { it.accountIdentifier == account.accountIdentifier } ?: account
} }
protected open fun getTransactionsAfterInitAndGetBalance(context: JobContext, parameter: GetTransactionsParameter, protected open fun getTransactionsAfterInitAndGetBalance(context: JobContext, parameter: GetAccountTransactionsParameter,
balanceResponse: BankResponse?, callback: (GetTransactionsResponse) -> Unit) { balanceResponse: BankResponse?, callback: (GetAccountTransactionsResponse) -> Unit) {
var balance: Money? = balanceResponse?.getFirstSegmentById<BalanceSegment>(InstituteSegmentId.Balance)?.let { var balance: Money? = balanceResponse?.getFirstSegmentById<BalanceSegment>(InstituteSegmentId.Balance)?.let {
Money(it.balance, it.currency) Money(it.balance, it.currency)
} }
@ -248,15 +245,12 @@ open class FinTsJobExecutor(
?: bookedTransactions.map { it.valueDate }.sortedBy { it.millisSinceEpoch }.firstOrNull() ?: bookedTransactions.map { it.valueDate }.sortedBy { it.millisSinceEpoch }.firstOrNull()
val retrievedData = RetrievedAccountData(parameter.account, successful, balance, bookedTransactions, unbookedTransactions, fromDate, parameter.toDate ?: Date.today, response.internalError) val retrievedData = RetrievedAccountData(parameter.account, successful, balance, bookedTransactions, unbookedTransactions, fromDate, parameter.toDate ?: Date.today, response.internalError)
callback( callback(GetAccountTransactionsResponse(context, response, retrievedData,
GetTransactionsResponse(context, response, listOf(retrievedData), if (parameter.maxCountEntries != null) parameter.isSettingMaxCountEntriesAllowedByBank else null))
if (parameter.maxCountEntries != null) parameter.isSettingMaxCountEntriesAllowedByBank else null
)
)
} }
} }
protected open fun mayGetBalance(context: JobContext, parameter: GetTransactionsParameter, callback: (BankResponse?) -> Unit) { protected open fun mayGetBalance(context: JobContext, parameter: GetAccountTransactionsParameter, callback: (BankResponse?) -> Unit) {
if (parameter.alsoRetrieveBalance && parameter.account.supportsRetrievingBalance) { if (parameter.alsoRetrieveBalance && parameter.account.supportsRetrievingBalance) {
val message = messageBuilder.createGetBalanceMessage(context, parameter.account) val message = messageBuilder.createGetBalanceMessage(context, parameter.account)

View File

@ -155,7 +155,7 @@ open class MessageBuilder(protected val generator: ISegmentNumberGenerator = Seg
} }
open fun createGetTransactionsMessage(context: JobContext, parameter: GetTransactionsParameter): MessageBuilderResult { open fun createGetTransactionsMessage(context: JobContext, parameter: GetAccountTransactionsParameter): MessageBuilderResult {
val result = supportsGetTransactionsMt940(parameter.account) val result = supportsGetTransactionsMt940(parameter.account)
@ -173,7 +173,7 @@ open class MessageBuilder(protected val generator: ISegmentNumberGenerator = Seg
} }
protected open fun createGetTransactionsMessageMt940(context: JobContext, result: MessageBuilderResult, protected open fun createGetTransactionsMessageMt940(context: JobContext, result: MessageBuilderResult,
parameter: GetTransactionsParameter): MessageBuilderResult { parameter: GetAccountTransactionsParameter): MessageBuilderResult {
if (parameter.maxCountEntries != null) { if (parameter.maxCountEntries != null) {
parameter.isSettingMaxCountEntriesAllowedByBank = determineIsSettingMaxCountEntriesAllowed(context.bank, InstituteSegmentId.AccountTransactionsMt940Parameters, listOf(5, 6, 7)) parameter.isSettingMaxCountEntriesAllowedByBank = determineIsSettingMaxCountEntriesAllowed(context.bank, InstituteSegmentId.AccountTransactionsMt940Parameters, listOf(5, 6, 7))
@ -197,7 +197,7 @@ open class MessageBuilder(protected val generator: ISegmentNumberGenerator = Seg
} }
protected open fun createGetCreditCardTransactionsMessage(context: JobContext, result: MessageBuilderResult, protected open fun createGetCreditCardTransactionsMessage(context: JobContext, result: MessageBuilderResult,
parameter: GetTransactionsParameter): MessageBuilderResult { parameter: GetAccountTransactionsParameter): MessageBuilderResult {
val segments = mutableListOf<Segment>(KreditkartenUmsaetze(generator.resetSegmentNumber(2), parameter)) val segments = mutableListOf<Segment>(KreditkartenUmsaetze(generator.resetSegmentNumber(2), parameter))

View File

@ -2,7 +2,7 @@ package net.dankito.banking.fints.messages.datenelemente.implementierte.account
import net.dankito.banking.fints.messages.Existenzstatus import net.dankito.banking.fints.messages.Existenzstatus
import net.dankito.banking.fints.messages.datenelemente.basisformate.NumerischesDatenelement import net.dankito.banking.fints.messages.datenelemente.basisformate.NumerischesDatenelement
import net.dankito.banking.fints.model.GetTransactionsParameter import net.dankito.banking.fints.model.GetAccountTransactionsParameter
/** /**
@ -11,6 +11,6 @@ import net.dankito.banking.fints.model.GetTransactionsParameter
*/ */
open class MaximaleAnzahlEintraege(maxAmount: Int?, existenzstatus: Existenzstatus) : NumerischesDatenelement(maxAmount, 4, existenzstatus) { open class MaximaleAnzahlEintraege(maxAmount: Int?, existenzstatus: Existenzstatus) : NumerischesDatenelement(maxAmount, 4, existenzstatus) {
constructor(parameter: GetTransactionsParameter) : this(parameter.maxCountEntriesIfSettingItIsAllowed, if (parameter.isSettingMaxCountEntriesAllowedByBank) Existenzstatus.Optional else Existenzstatus.NotAllowed) // > 0. O: „Eingabe Anzahl Einträge erlaubt“ (BPD) = „J“. N: sonst constructor(parameter: GetAccountTransactionsParameter) : this(parameter.maxCountEntriesIfSettingItIsAllowed, if (parameter.isSettingMaxCountEntriesAllowedByBank) Existenzstatus.Optional else Existenzstatus.NotAllowed) // > 0. O: „Eingabe Anzahl Einträge erlaubt“ (BPD) = „J“. N: sonst
} }

View File

@ -9,7 +9,7 @@ import net.dankito.banking.fints.messages.datenelementgruppen.Datenelementgruppe
import net.dankito.banking.fints.messages.datenelementgruppen.implementierte.Segmentkopf import net.dankito.banking.fints.messages.datenelementgruppen.implementierte.Segmentkopf
import net.dankito.banking.fints.messages.segmente.Segment import net.dankito.banking.fints.messages.segmente.Segment
import net.dankito.banking.fints.messages.segmente.id.CustomerSegmentId import net.dankito.banking.fints.messages.segmente.id.CustomerSegmentId
import net.dankito.banking.fints.model.GetTransactionsParameter import net.dankito.banking.fints.model.GetAccountTransactionsParameter
/** /**
@ -26,7 +26,7 @@ abstract class KontoumsaetzeZeitraumMt940Base(
segmentVersion: Int, segmentVersion: Int,
segmentNumber: Int, segmentNumber: Int,
account: Datenelementgruppe, account: Datenelementgruppe,
parameter: GetTransactionsParameter parameter: GetAccountTransactionsParameter
) )
: Segment(listOf( : Segment(listOf(
Segmentkopf(CustomerSegmentId.AccountTransactionsMt940, segmentVersion, segmentNumber), Segmentkopf(CustomerSegmentId.AccountTransactionsMt940, segmentVersion, segmentNumber),

View File

@ -1,8 +1,7 @@
package net.dankito.banking.fints.messages.segmente.implementierte.umsaetze package net.dankito.banking.fints.messages.segmente.implementierte.umsaetze
import net.dankito.banking.fints.messages.datenelementgruppen.implementierte.account.Kontoverbindung import net.dankito.banking.fints.messages.datenelementgruppen.implementierte.account.Kontoverbindung
import net.dankito.banking.fints.model.AccountData import net.dankito.banking.fints.model.GetAccountTransactionsParameter
import net.dankito.banking.fints.model.GetTransactionsParameter
/** /**
@ -17,5 +16,5 @@ import net.dankito.banking.fints.model.GetTransactionsParameter
*/ */
open class KontoumsaetzeZeitraumMt940Version5( open class KontoumsaetzeZeitraumMt940Version5(
segmentNumber: Int, segmentNumber: Int,
parameter: GetTransactionsParameter parameter: GetAccountTransactionsParameter
) : KontoumsaetzeZeitraumMt940Base(5, segmentNumber, Kontoverbindung(parameter.account), parameter) ) : KontoumsaetzeZeitraumMt940Base(5, segmentNumber, Kontoverbindung(parameter.account), parameter)

View File

@ -1,8 +1,7 @@
package net.dankito.banking.fints.messages.segmente.implementierte.umsaetze package net.dankito.banking.fints.messages.segmente.implementierte.umsaetze
import net.dankito.banking.fints.messages.datenelementgruppen.implementierte.account.Kontoverbindung import net.dankito.banking.fints.messages.datenelementgruppen.implementierte.account.Kontoverbindung
import net.dankito.banking.fints.model.AccountData import net.dankito.banking.fints.model.GetAccountTransactionsParameter
import net.dankito.banking.fints.model.GetTransactionsParameter
/** /**
@ -17,6 +16,6 @@ import net.dankito.banking.fints.model.GetTransactionsParameter
*/ */
open class KontoumsaetzeZeitraumMt940Version6( open class KontoumsaetzeZeitraumMt940Version6(
segmentNumber: Int, segmentNumber: Int,
parameter: GetTransactionsParameter parameter: GetAccountTransactionsParameter
) : KontoumsaetzeZeitraumMt940Base(6, segmentNumber, Kontoverbindung(parameter.account), parameter) ) : KontoumsaetzeZeitraumMt940Base(6, segmentNumber, Kontoverbindung(parameter.account), parameter)

View File

@ -1,9 +1,8 @@
package net.dankito.banking.fints.messages.segmente.implementierte.umsaetze package net.dankito.banking.fints.messages.segmente.implementierte.umsaetze
import net.dankito.banking.fints.messages.datenelementgruppen.implementierte.account.KontoverbindungInternational import net.dankito.banking.fints.messages.datenelementgruppen.implementierte.account.KontoverbindungInternational
import net.dankito.banking.fints.model.AccountData
import net.dankito.banking.fints.model.BankData import net.dankito.banking.fints.model.BankData
import net.dankito.banking.fints.model.GetTransactionsParameter import net.dankito.banking.fints.model.GetAccountTransactionsParameter
/** /**
@ -18,6 +17,6 @@ import net.dankito.banking.fints.model.GetTransactionsParameter
*/ */
open class KontoumsaetzeZeitraumMt940Version7( open class KontoumsaetzeZeitraumMt940Version7(
segmentNumber: Int, segmentNumber: Int,
parameter: GetTransactionsParameter, parameter: GetAccountTransactionsParameter,
bank: BankData bank: BankData
) : KontoumsaetzeZeitraumMt940Base(7, segmentNumber, KontoverbindungInternational(parameter.account, bank), parameter) ) : KontoumsaetzeZeitraumMt940Base(7, segmentNumber, KontoverbindungInternational(parameter.account, bank), parameter)

View File

@ -9,12 +9,12 @@ import net.dankito.banking.fints.messages.datenelementgruppen.implementierte.Seg
import net.dankito.banking.fints.messages.datenelementgruppen.implementierte.account.Kontoverbindung import net.dankito.banking.fints.messages.datenelementgruppen.implementierte.account.Kontoverbindung
import net.dankito.banking.fints.messages.segmente.Segment import net.dankito.banking.fints.messages.segmente.Segment
import net.dankito.banking.fints.messages.segmente.id.CustomerSegmentId import net.dankito.banking.fints.messages.segmente.id.CustomerSegmentId
import net.dankito.banking.fints.model.GetTransactionsParameter import net.dankito.banking.fints.model.GetAccountTransactionsParameter
open class KreditkartenUmsaetze( open class KreditkartenUmsaetze(
segmentNumber: Int, segmentNumber: Int,
parameter: GetTransactionsParameter parameter: GetAccountTransactionsParameter
) : Segment(listOf( ) : Segment(listOf(
Segmentkopf(CustomerSegmentId.CreditCardTransactions, 2, segmentNumber), Segmentkopf(CustomerSegmentId.CreditCardTransactions, 2, segmentNumber),
Kontoverbindung(parameter.account), Kontoverbindung(parameter.account),

View File

@ -0,0 +1,30 @@
package net.dankito.banking.fints.model
import net.dankito.utils.multiplatform.Date
import kotlin.jvm.JvmOverloads
open class GetAccountTransactionsParameter @JvmOverloads constructor(
bank: BankData,
account: AccountData,
alsoRetrieveBalance: Boolean = true,
fromDate: Date? = null,
toDate: Date? = null,
/**
* Be aware this is by far not supported by all banks.
* And it depends on the actual job if setting maxCountEntries is supported or not.
*/
maxCountEntries: Int? = null,
abortIfTanIsRequired: Boolean = false,
retrievedChunkListener: ((Collection<AccountTransaction>) -> Unit)? = null
) : GetTransactionsParameter(bank, alsoRetrieveBalance, fromDate, toDate, maxCountEntries, abortIfTanIsRequired, retrievedChunkListener) {
open var account: AccountData = account
internal set
constructor(parameter: GetTransactionsParameter, account: AccountData) : this(parameter.bank, account, parameter.alsoRetrieveBalance,
parameter.fromDate, parameter.toDate, parameter.maxCountEntries, parameter.abortIfTanIsRequired, parameter.retrievedChunkListener)
}

View File

@ -5,7 +5,7 @@ import kotlin.jvm.JvmOverloads
open class GetTransactionsParameter @JvmOverloads constructor( open class GetTransactionsParameter @JvmOverloads constructor(
account: AccountData, open val bank: BankData,
open val alsoRetrieveBalance: Boolean = true, open val alsoRetrieveBalance: Boolean = true,
open val fromDate: Date? = null, open val fromDate: Date? = null,
open val toDate: Date? = null, open val toDate: Date? = null,
@ -20,9 +20,6 @@ open class GetTransactionsParameter @JvmOverloads constructor(
open val retrievedChunkListener: ((Collection<AccountTransaction>) -> Unit)? = null open val retrievedChunkListener: ((Collection<AccountTransaction>) -> Unit)? = null
) { ) {
open var account: AccountData = account
internal set
internal open var isSettingMaxCountEntriesAllowedByBank = false internal open var isSettingMaxCountEntriesAllowedByBank = false
internal open val maxCountEntriesIfSettingItIsAllowed: Int? internal open val maxCountEntriesIfSettingItIsAllowed: Int?

View File

@ -16,18 +16,10 @@ open class RetrievedAccountData(
companion object { companion object {
fun balanceAndTransactionsNotRequestedByUser(account: AccountData): RetrievedAccountData {
return RetrievedAccountData(account, true, null, listOf(), listOf(), null, null)
}
fun unsuccessful(account: AccountData): RetrievedAccountData { fun unsuccessful(account: AccountData): RetrievedAccountData {
return RetrievedAccountData(account, false, null, listOf(), listOf(), null, null) return RetrievedAccountData(account, false, null, listOf(), listOf(), null, null)
} }
fun unsuccessfulList(account: AccountData): List<RetrievedAccountData> {
return listOf(unsuccessful(account))
}
} }

View File

@ -6,12 +6,34 @@ import net.dankito.banking.fints.response.BankResponse
open class AddAccountResponse( open class AddAccountResponse(
context: JobContext, context: JobContext,
response: BankResponse, getAccountsResponse: BankResponse,
open val bank: BankData, open val retrievedTransactionsResponses: List<GetAccountTransactionsResponse> = listOf()
retrievedData: List<RetrievedAccountData> = listOf() ) : FinTsClientResponse(context, getAccountsResponse) {
) : GetTransactionsResponse(context, response, retrievedData) {
open val bank: BankData = context.bank
override val successful: Boolean override val successful: Boolean
get() = super.successful && bank.accounts.isNotEmpty() get() = super.successful && bank.accounts.isNotEmpty()
&& bank.accounts.size == retrievedTransactionsResponses.size
&& retrievedTransactionsResponses.none { it.noTanMethodSelected }
&& retrievedTransactionsResponses.none { it.isPinLocked }
&& retrievedTransactionsResponses.none { it.wrongCredentialsEntered }
&& retrievedTransactionsResponses.none { it.internalError != null }
&& retrievedTransactionsResponses.none { it.errorMessagesFromBank.isNotEmpty() }
override val internalError: String?
get() = super.internalError
?: retrievedTransactionsResponses.mapNotNull { it.internalError }.joinToString("\r\n")
.ifBlank { null } // if mapNotNull { it.internalError } results in an empty list, then joinToString() results in an empty string -> return null then
override val errorMessagesFromBank: List<String>
get() {
val allMessages = super.errorMessagesFromBank.toMutableList()
allMessages.addAll(retrievedTransactionsResponses.flatMap { it.errorMessagesFromBank })
return allMessages
}
open val retrievedData: List<RetrievedAccountData>
get() = retrievedTransactionsResponses.mapNotNull { it.retrievedData }
} }

View File

@ -0,0 +1,31 @@
package net.dankito.banking.fints.response.client
import net.dankito.banking.fints.model.JobContext
import net.dankito.banking.fints.model.RetrievedAccountData
import net.dankito.banking.fints.response.BankResponse
open class GetAccountTransactionsResponse(
context: JobContext,
response: BankResponse,
open val retrievedData: RetrievedAccountData?,
/**
* This value is only set if [GetTransactionsParameter.maxCountEntries] was set to tell caller if maxCountEntries parameter has been evaluated or not
*/
open var isSettingMaxCountEntriesAllowedByBank: Boolean? = null
) : FinTsClientResponse(context, response) {
override val successful: Boolean
get() = super.successful
&& retrievedData?.successfullyRetrievedData == true
override val internalError: String?
get() = super.internalError
?: retrievedData?.errorMessage
override fun toString(): String {
return super.toString() + ": Retrieved data: $retrievedData"
}
}

View File

@ -1,29 +1,35 @@
package net.dankito.banking.fints.response.client package net.dankito.banking.fints.response.client
import net.dankito.banking.fints.model.JobContext
import net.dankito.banking.fints.model.RetrievedAccountData import net.dankito.banking.fints.model.RetrievedAccountData
import net.dankito.banking.fints.response.BankResponse
open class GetTransactionsResponse( open class GetTransactionsResponse(
context: JobContext, open val retrievedResponses: List<GetAccountTransactionsResponse>,
response: BankResponse, errorMessage: String? = null
open val retrievedData: List<RetrievedAccountData> = listOf(), ) : FinTsClientResponse(isSuccessful(retrievedResponses), retrievedResponses.any { it.noTanMethodSelected },
/** retrievedResponses.any { it.isStrongAuthenticationRequired }, retrievedResponses.map { it.tanRequired }.firstOrNull(),
* This value is only set if [GetTransactionsParameter.maxCountEntries] was set to tell caller if maxCountEntries parameter has been evaluated or not retrievedResponses.flatMap { it.messageLogWithoutSensitiveData },
*/ errorMessage ?: retrievedResponses.mapNotNull { it.internalError }.joinToString("\r\n"),
open var isSettingMaxCountEntriesAllowedByBank: Boolean? = null retrievedResponses.flatMap { it.errorMessagesFromBank }, retrievedResponses.any { it.isPinLocked },
) : FinTsClientResponse(context, response) { retrievedResponses.any { it.wrongCredentialsEntered }, retrievedResponses.any { it.userCancelledAction },
retrievedResponses.any { it.tanRequiredButWeWereToldToAbortIfSo },
retrievedResponses.any { it.isJobAllowed }, retrievedResponses.any { it.isJobAllowed },
retrievedResponses.flatMap { it.allowedVersions }.toSet().toList(),
retrievedResponses.flatMap { it.supportedVersions }.toSet().toList()
) {
override val successful: Boolean companion object {
get() = super.successful
&& retrievedData.isNotEmpty()
&& retrievedData.none { it.account.supportsRetrievingAccountTransactions && it.successfullyRetrievedData == false }
// TODO: remove again if then in AddAccountResponse errors get displayed that should or extract getRetrievingTransactionsError() and override in AddAccountResponse fun isSuccessful(retrievedResponses: List<GetAccountTransactionsResponse>): Boolean {
override val internalError: String? return retrievedResponses.isNotEmpty() &&
get() = super.internalError retrievedResponses.none { it.retrievedData?.account?.supportsRetrievingAccountTransactions == true && it.retrievedData?.successfullyRetrievedData == false }
?: retrievedData.mapNotNull { it.errorMessage }.firstOrNull() }
}
open val retrievedData: List<RetrievedAccountData>
get() = retrievedResponses.mapNotNull { it.retrievedData }
override fun toString(): String { override fun toString(): String {

View File

@ -132,7 +132,7 @@ class MessageBuilderTest : FinTsTestBase() {
val context = createContext() val context = createContext()
// when // when
val result = underTest.createGetTransactionsMessage(context, GetTransactionsParameter(Account)) val result = underTest.createGetTransactionsMessage(context, GetAccountTransactionsParameter(Bank, Account))
// then // then
expect(result.isJobAllowed).toBe(false) expect(result.isJobAllowed).toBe(false)
@ -151,7 +151,7 @@ class MessageBuilderTest : FinTsTestBase() {
val context = createContext() val context = createContext()
// when // when
val result = underTest.createGetTransactionsMessage(context, GetTransactionsParameter(account)) val result = underTest.createGetTransactionsMessage(context, GetAccountTransactionsParameter(Bank, account))
// then // then
expect(result.isJobAllowed).toBe(true) expect(result.isJobAllowed).toBe(true)
@ -175,7 +175,7 @@ class MessageBuilderTest : FinTsTestBase() {
val maxCountEntries = 99 val maxCountEntries = 99
// when // when
val result = underTest.createGetTransactionsMessage(context, GetTransactionsParameter(account, false, fromDate, toDate, maxCountEntries)) val result = underTest.createGetTransactionsMessage(context, GetAccountTransactionsParameter(Bank, account, false, fromDate, toDate, maxCountEntries))
// then // then
expect(result.createdMessage).notToBeNull() expect(result.createdMessage).notToBeNull()
@ -210,7 +210,7 @@ class MessageBuilderTest : FinTsTestBase() {
// when // when
val result = underTest.createGetTransactionsMessage(context, // TODO: test Aufsetzpunkt / continuationId val result = underTest.createGetTransactionsMessage(context, // TODO: test Aufsetzpunkt / continuationId
GetTransactionsParameter(account, false, fromDate, toDate, maxCountEntries, false)) GetAccountTransactionsParameter(Bank, account, false, fromDate, toDate, maxCountEntries, false))
// then // then
expect(result.createdMessage).notToBeNull() expect(result.createdMessage).notToBeNull()

View File

@ -15,10 +15,7 @@ import net.dankito.banking.fints.messages.datenelemente.implementierte.tan.TanMe
import net.dankito.banking.fints.messages.datenelemente.implementierte.tan.TanMediumKlasse import net.dankito.banking.fints.messages.datenelemente.implementierte.tan.TanMediumKlasse
import net.dankito.banking.fints.messages.segmente.id.CustomerSegmentId import net.dankito.banking.fints.messages.segmente.id.CustomerSegmentId
import net.dankito.banking.fints.model.* import net.dankito.banking.fints.model.*
import net.dankito.banking.fints.response.client.AddAccountResponse import net.dankito.banking.fints.response.client.*
import net.dankito.banking.fints.response.client.FinTsClientResponse
import net.dankito.banking.fints.response.client.GetTanMediaListResponse
import net.dankito.banking.fints.response.client.GetTransactionsResponse
import net.dankito.utils.multiplatform.Date import net.dankito.utils.multiplatform.Date
import net.dankito.utils.multiplatform.DateFormatter import net.dankito.utils.multiplatform.DateFormatter
import net.dankito.utils.multiplatform.UUID import net.dankito.utils.multiplatform.UUID
@ -144,7 +141,7 @@ open class FinTsClientTestBase {
fun getTransactions() { fun getTransactions() {
// given // given
val response = AtomicReference<GetTransactionsResponse>() val response = AtomicReference<GetAccountTransactionsResponse>()
val countDownLatch = CountDownLatch(1) val countDownLatch = CountDownLatch(1)
underTest.addAccountAsync(Bank.toAddAccountParameter(false)) { // retrieve basic data, e.g. accounts underTest.addAccountAsync(Bank.toAddAccountParameter(false)) { // retrieve basic data, e.g. accounts
@ -154,7 +151,7 @@ open class FinTsClientTestBase {
// when // when
// some banks support retrieving account transactions of last 90 days without TAN // some banks support retrieving account transactions of last 90 days without TAN
underTest.tryGetTransactionsOfLast90DaysWithoutTan(Bank, account!!) { underTest.tryGetAccountTransactionsOfLast90DaysWithoutTan(Bank, account!!) {
response.set(it) response.set(it)
countDownLatch.countDown() countDownLatch.countDown()
} }

View File

@ -45,7 +45,7 @@ open class TestAccessBankingClient(
} }
} }
override fun getTransactionsAsync(parameter: GetTransactionsParameter, callback: (GetTransactionsResponse) -> Unit) { override fun getAccountTransactionsAsync(parameter: GetTransactionsParameter, callback: (GetTransactionsResponse) -> Unit) {
asyncRunner.runAsync { asyncRunner.runAsync {
callback(GetTransactionsResponse(createRetrievedAccountData(parameter.account))) callback(GetTransactionsResponse(createRetrievedAccountData(parameter.account)))
} }

View File

@ -15,10 +15,7 @@ interface IBankingClient {
fun addAccountAsync(callback: (AddAccountResponse) -> Unit) fun addAccountAsync(callback: (AddAccountResponse) -> Unit)
fun getTransactionsAsync( fun getAccountTransactionsAsync(parameter: GetTransactionsParameter, callback: (GetTransactionsResponse) -> Unit)
parameter: GetTransactionsParameter,
callback: (GetTransactionsResponse) -> Unit
)
fun transferMoneyAsync(data: TransferMoneyData, callback: (BankingClientResponse) -> Unit) fun transferMoneyAsync(data: TransferMoneyData, callback: (BankingClientResponse) -> Unit)

View File

@ -371,7 +371,7 @@ open class BankingPresenter(
getBankingClientForBank(account.bank)?.let { client -> getBankingClientForBank(account.bank)?.let { client ->
val startDate = Date() val startDate = Date()
client.getTransactionsAsync(GetTransactionsParameter(account,true, fromDate, null, abortIfTanIsRequired, { receivedAccountTransactionChunk(account, it) } )) { response -> client.getAccountTransactionsAsync(GetTransactionsParameter(account,true, fromDate, null, abortIfTanIsRequired, { receivedAccountTransactionChunk(account, it) } )) { response ->
if (response.tanRequiredButWeWereToldToAbortIfSo == false) { // don't call retrievedAccountTransactions() if aborted due to TAN required but we told client to abort if so if (response.tanRequiredButWeWereToldToAbortIfSo == false) { // don't call retrievedAccountTransactions() if aborted due to TAN required but we told client to abort if so
retrievedAccountTransactions(response, startDate, fromDate == null) retrievedAccountTransactions(response, startDate, fromDate == null)

View File

@ -84,7 +84,8 @@ open class fints4kBankingClient(
} }
override fun getTransactionsAsync(parameter: GetTransactionsParameter, callback: (GetTransactionsResponse) -> Unit) { // we currently leave the data model of the UI layer untouched as this may changes soon anyway
override fun getAccountTransactionsAsync(parameter: GetTransactionsParameter, callback: (GetTransactionsResponse) -> Unit) {
val account = parameter.account val account = parameter.account
findAccountForAccount(account) { accountData, response -> findAccountForAccount(account) { accountData, response ->
@ -97,25 +98,26 @@ open class fints4kBankingClient(
} }
} }
else { else {
val mappedParameter = GetTransactionsParameter(accountData, parameter.alsoRetrieveBalance, parameter.fromDate, val mappedParameter = GetAccountTransactionsParameter(fintsBank, accountData, parameter.alsoRetrieveBalance, parameter.fromDate,
parameter.toDate, null, parameter.abortIfTanIsRequired) { parameter.toDate, null, parameter.abortIfTanIsRequired) {
parameter.retrievedChunkListener?.invoke(mapper.mapTransactions(account, it)) parameter.retrievedChunkListener?.invoke(mapper.mapTransactions(account, it))
} }
doGetTransactionsAsync(mappedParameter, account, callback) doGetAccountTransactionsAsync(mappedParameter, account, callback)
} }
} }
} }
protected open fun doGetTransactionsAsync(parameter: net.dankito.banking.fints.model.GetTransactionsParameter, protected open fun doGetAccountTransactionsAsync(parameter: net.dankito.banking.fints.model.GetAccountTransactionsParameter,
account: TypedBankAccount, callback: (GetTransactionsResponse) -> Unit) { account: TypedBankAccount, callback: (GetTransactionsResponse) -> Unit) {
client.getTransactionsAsync(parameter) { response -> client.getAccountTransactionsAsync(parameter) { response ->
handleGetTransactionsResponse(account, response, callback) handleGetTransactionsResponse(account, response, callback)
} }
} }
protected open fun handleGetTransactionsResponse(account: TypedBankAccount, response: net.dankito.banking.fints.response.client.GetTransactionsResponse, protected open fun handleGetTransactionsResponse(account: TypedBankAccount, response: net.dankito.banking.fints.response.client.GetAccountTransactionsResponse,
callback: (GetTransactionsResponse) -> Unit) { callback: (GetTransactionsResponse) -> Unit) {
// we currently leave the data model of the UI layer untouched as this may changes soon anyway
val mappedResponse = mapper.mapResponse(account, response) val mappedResponse = mapper.mapResponse(account, response)
saveData(response) saveData(response)

View File

@ -32,9 +32,9 @@ open class fints4kModelMapper(protected val modelCreator: IModelCreator) {
return AddAccountResponse(bank, map(bank, response.retrievedData), response.errorMessage, response.didBankReturnError, response.wrongCredentialsEntered, response.userCancelledAction) return AddAccountResponse(bank, map(bank, response.retrievedData), response.errorMessage, response.didBankReturnError, response.wrongCredentialsEntered, response.userCancelledAction)
} }
open fun mapResponse(account: TypedBankAccount, response: net.dankito.banking.fints.response.client.GetTransactionsResponse): GetTransactionsResponse { open fun mapResponse(account: TypedBankAccount, response: net.dankito.banking.fints.response.client.GetAccountTransactionsResponse): GetTransactionsResponse {
return GetTransactionsResponse(map(account.bank as TypedBankData, response.retrievedData), return GetTransactionsResponse(response.retrievedData?.let { map(account.bank as TypedBankData, it)?.let { listOf(it) } } ?: listOf(),
response.errorMessage, response.didBankReturnError, response.wrongCredentialsEntered, response.userCancelledAction, response.tanRequiredButWeWereToldToAbortIfSo) response.errorMessage, response.didBankReturnError, response.wrongCredentialsEntered, response.userCancelledAction, response.tanRequiredButWeWereToldToAbortIfSo)
} }

View File

@ -136,7 +136,7 @@ open class hbci4jBankingClient(
return getTransactions(GetTransactionsParameter(account, account.supportsRetrievingBalance, ninetyDaysAgo)) // TODO: implement abortIfTanIsRequired return getTransactions(GetTransactionsParameter(account, account.supportsRetrievingBalance, ninetyDaysAgo)) // TODO: implement abortIfTanIsRequired
} }
override fun getTransactionsAsync(parameter: GetTransactionsParameter, callback: (GetTransactionsResponse) -> Unit) { override fun getAccountTransactionsAsync(parameter: GetTransactionsParameter, callback: (GetTransactionsResponse) -> Unit) {
asyncRunner.runAsync { asyncRunner.runAsync {
callback(getTransactions(parameter)) callback(getTransactions(parameter))
} }