Implemented parsing each received MT940 part immediately and passing it on to UI

This commit is contained in:
dankito 2020-05-21 00:29:53 +02:00
parent 56308687fb
commit e749a88c83
11 changed files with 80 additions and 32 deletions

View File

@ -307,39 +307,35 @@ open class FinTsClient @JvmOverloads constructor(
val message = messageBuilder.createGetTransactionsMessage(parameter, account, dialogContext) val message = messageBuilder.createGetTransactionsMessage(parameter, account, dialogContext)
val bookedTransactions = mutableListOf<AccountTransaction>()
var remainingMt940String = ""
dialogContext.chunkedResponseHandler = { response ->
response.getFirstSegmentById<ReceivedAccountTransactions>(InstituteSegmentId.AccountTransactionsMt940)?.let { transactionsSegment ->
val (chunkTransaction, remainder) = mt940Parser.parseTransactionsChunk(remainingMt940String + transactionsSegment.bookedTransactionsString, account)
bookedTransactions.addAll(chunkTransaction)
remainingMt940String = remainder
parameter.retrievedChunkListener?.invoke(bookedTransactions)
}
}
val response = getAndHandleResponseForMessage(message, dialogContext) val response = getAndHandleResponseForMessage(message, dialogContext)
closeDialog(dialogContext) closeDialog(dialogContext)
response.getFirstSegmentById<ReceivedAccountTransactions>(InstituteSegmentId.AccountTransactionsMt940)?.let { transactions -> // just retrieved all transactions -> check if retrieving that ones of last 90 days is possible without entering TAN
// just retrieved all transactions -> check if retrieving that ones of last 90 days is possible without entering TAN if (account.supportsRetrievingTransactionsOfLast90DaysWithoutTan == null &&
if (account.supportsRetrievingTransactionsOfLast90DaysWithoutTan == null && response.successful && bookedTransactions.isNotEmpty() && parameter.fromDate == null) {
response.successful && transactions.bookedTransactionsString.isNotEmpty() && parameter.fromDate == null) { tryGetTransactionsOfLast90DaysWithoutTan(bank, customer, account, true)
tryGetTransactionsOfLast90DaysWithoutTan(bank, customer, account, true)
}
val bookedAndUnbookedTransactions = getTransactionsFromResponse(response, transactions, account)
return GetTransactionsResponse(response,
bookedAndUnbookedTransactions.first.sortedByDescending { it.bookingDate },
bookedAndUnbookedTransactions.second,
balance)
} }
return GetTransactionsResponse(response) return GetTransactionsResponse(response,
} bookedTransactions.sortedByDescending { it.bookingDate },
listOf(), // TODO: implement parsing MT942
protected open fun getTransactionsFromResponse(response: Response, transactions: ReceivedAccountTransactions, account: AccountData): Pair<List<AccountTransaction>, List<Any>> { balance)
val bookedTransactionsString = StringBuilder()
val unbookedTransactionsString = StringBuilder()
getTransactionsFromResponse(response, transactions, bookedTransactionsString, unbookedTransactionsString)
val bookedTransactions = mt940Parser.parseTransactions(bookedTransactionsString.toString(), account)
val unbookedTransactions = listOf<Any>() // TODO: implement parsing MT942
return Pair(bookedTransactions, unbookedTransactions)
} }
protected open fun getTransactionsFromResponse(response: Response, transactions: ReceivedAccountTransactions, protected open fun getTransactionsFromResponse(response: Response, transactions: ReceivedAccountTransactions,
@ -561,11 +557,16 @@ open class FinTsClient @JvmOverloads constructor(
// if there's a Aufsetzpunkt (continuationId) set, then response is not complete yet, there's more information to fetch by sending this Aufsetzpunkt // if there's a Aufsetzpunkt (continuationId) set, then response is not complete yet, there's more information to fetch by sending this Aufsetzpunkt
handledResponse.aufsetzpunkt?.let { continuationId -> handledResponse.aufsetzpunkt?.let { continuationId ->
if (handledResponse.followUpResponse == null) { // for re-sent messages followUpResponse is already set and dialog already closed -> would be overwritten with an error response that dialog is closed if (handledResponse.followUpResponse == null) { // for re-sent messages followUpResponse is already set and dialog already closed -> would be overwritten with an error response that dialog is closed
if (message.isSendEnteredTanMessage() == false) { // for sending TAN no follow up message can be created -> filter out, otherwise chunkedResponseHandler would get called twice for same response
dialogContext.chunkedResponseHandler?.invoke(handledResponse)
}
handledResponse.followUpResponse = getFollowUpMessageForContinuationId(handledResponse, continuationId, message, dialogContext) handledResponse.followUpResponse = getFollowUpMessageForContinuationId(handledResponse, continuationId, message, dialogContext)
handledResponse.hasFollowUpMessageButCouldNotReceiveIt = handledResponse.followUpResponse == null handledResponse.hasFollowUpMessageButCouldNotReceiveIt = handledResponse.followUpResponse == null
} }
} }
?: run { dialogContext.chunkedResponseHandler?.invoke(handledResponse) }
return handledResponse return handledResponse
} }
@ -794,7 +795,7 @@ open class FinTsClient @JvmOverloads constructor(
protected open fun resendMessageInNewDialog(lastCreatedMessage: MessageBuilderResult?, previousDialogContext: DialogContext): Response { protected open fun resendMessageInNewDialog(lastCreatedMessage: MessageBuilderResult?, previousDialogContext: DialogContext): Response {
lastCreatedMessage?.let { // do not use previousDialogContext.currentMessage as this may is previous dialog's dialog close message lastCreatedMessage?.let { // do not use previousDialogContext.currentMessage as this may is previous dialog's dialog close message
val newDialogContext = DialogContext(previousDialogContext.bank, previousDialogContext.customer, previousDialogContext.product) val newDialogContext = DialogContext(previousDialogContext.bank, previousDialogContext.customer, previousDialogContext.product, chunkedResponseHandler = previousDialogContext.chunkedResponseHandler)
val initDialogResponse = initDialog(newDialogContext) val initDialogResponse = initDialog(newDialogContext)
if (initDialogResponse.successful == false) { if (initDialogResponse.successful == false) {

View File

@ -1,6 +1,7 @@
package net.dankito.banking.fints.messages package net.dankito.banking.fints.messages
import net.dankito.banking.fints.messages.segmente.Segment import net.dankito.banking.fints.messages.segmente.Segment
import net.dankito.banking.fints.messages.segmente.implementierte.ZweiSchrittTanEinreichung
open class MessageBuilderResult( open class MessageBuilderResult(
@ -25,4 +26,10 @@ open class MessageBuilderResult(
open val getHighestAllowedVersion: Int? open val getHighestAllowedVersion: Int?
get() = allowedVersions.max() get() = allowedVersions.max()
open fun isSendEnteredTanMessage(): Boolean {
// contains only a ZweiSchrittTanEinreichung segment
return messageBodySegments.size == 1
&& messageBodySegments.first() is ZweiSchrittTanEinreichung
}
} }

View File

@ -12,7 +12,8 @@ open class DialogContext(
var dialogId: String = InitialDialogId, var dialogId: String = InitialDialogId,
var response: Response? = null, var response: Response? = null,
var didBankCloseDialog: Boolean = false, var didBankCloseDialog: Boolean = false,
var previousMessageInDialog: MessageBuilderResult? = null var previousMessageInDialog: MessageBuilderResult? = null,
var chunkedResponseHandler: ((Response) -> Unit)? = null
) : MessageBaseData(bank, customer, product) { ) : MessageBaseData(bank, customer, product) {
companion object { companion object {

View File

@ -7,5 +7,6 @@ open class GetTransactionsParameter @JvmOverloads constructor(
val alsoRetrieveBalance: Boolean = true, val alsoRetrieveBalance: Boolean = true,
val fromDate: Date? = null, val fromDate: Date? = null,
val toDate: Date? = null, val toDate: Date? = null,
val maxCountEntries: Int? = null val maxCountEntries: Int? = null,
val retrievedChunkListener: ((List<AccountTransaction>) -> Unit)? = null
) )

View File

@ -8,4 +8,6 @@ interface IAccountTransactionsParser {
fun parseTransactions(transactionsString: String, account: AccountData): List<AccountTransaction> fun parseTransactions(transactionsString: String, account: AccountData): List<AccountTransaction>
fun parseTransactionsChunk(transactionsChunk: String, account: AccountData): Pair<List<AccountTransaction>, String>
} }

View File

@ -27,6 +27,12 @@ open class Mt940AccountTransactionsParser @JvmOverloads constructor(
return accountStatements.flatMap { mapToAccountTransactions(it, account) } return accountStatements.flatMap { mapToAccountTransactions(it, account) }
} }
override fun parseTransactionsChunk(transactionsChunk: String, account: AccountData): Pair<List<AccountTransaction>, String> {
val (accountStatements, remainder) = mt940Parser.parseTransactionsChunk(transactionsChunk)
return Pair(accountStatements.flatMap { mapToAccountTransactions(it, account) }, remainder)
}
protected open fun mapToAccountTransactions(statement: AccountStatement, account: AccountData): List<AccountTransaction> { protected open fun mapToAccountTransactions(statement: AccountStatement, account: AccountData): List<AccountTransaction> {
try { try {
return statement.transactions.map { mapToAccountTransaction(statement, it, account) } return statement.transactions.map { mapToAccountTransaction(statement, it, account) }

View File

@ -7,4 +7,6 @@ interface IMt940Parser {
fun parseMt940String(mt940String: String): List<AccountStatement> fun parseMt940String(mt940String: String): List<AccountStatement>
fun parseTransactionsChunk(mt940Chunk: String): Pair<List<AccountStatement>, String>
} }

View File

@ -73,6 +73,23 @@ open class Mt940Parser : IMt940Parser {
return listOf() return listOf()
} }
override fun parseTransactionsChunk(mt940Chunk: String): Pair<List<AccountStatement>, String> {
try {
val singleAccountStatementsStrings = splitIntoSingleAccountStatements(mt940Chunk)
val transactions = singleAccountStatementsStrings.mapNotNull { parseAccountStatement(it) }
val remainder = if (singleAccountStatementsStrings.size == transactions.size + 1) singleAccountStatementsStrings.last()
else ""
return Pair(transactions, remainder)
} catch (e: Exception) {
log.error("Could not parse account statements from MT940 string:\n$mt940Chunk", e)
}
return Pair(listOf(), "")
}
protected open fun splitIntoSingleAccountStatements(mt940String: String): List<String> { protected open fun splitIntoSingleAccountStatements(mt940String: String): List<String> {
return mt940String.split(AccountStatementsSeparatorPattern) return mt940String.split(AccountStatementsSeparatorPattern)

View File

@ -1,12 +1,14 @@
package net.dankito.banking.ui.model.parameters package net.dankito.banking.ui.model.parameters
import net.dankito.banking.ui.model.AccountTransaction
import java.util.* import java.util.*
open class GetTransactionsParameter( open class GetTransactionsParameter(
val alsoRetrieveBalance: Boolean = true, val alsoRetrieveBalance: Boolean = true,
val fromDate: Date? = null, val fromDate: Date? = null,
val toDate: Date? = null val toDate: Date? = null,
val retrievedChunkListener: ((List<AccountTransaction>) -> Unit)? = null
) { ) {
constructor() : this(true, null, null) // for Java constructor() : this(true, null, null) // for Java

View File

@ -268,7 +268,7 @@ open class BankingPresenter(
getClientForAccount(bankAccount.account)?.let { client -> getClientForAccount(bankAccount.account)?.let { client ->
val startDate = Date() val startDate = Date()
client.getTransactionsAsync(bankAccount, net.dankito.banking.ui.model.parameters.GetTransactionsParameter(true, fromDate)) { response -> client.getTransactionsAsync(bankAccount, net.dankito.banking.ui.model.parameters.GetTransactionsParameter(true, fromDate, null, { receivedAccountsTransactionChunk(bankAccount, it) } )) { response ->
retrievedAccountTransactions(bankAccount, startDate, response) retrievedAccountTransactions(bankAccount, startDate, response)
@ -299,6 +299,14 @@ open class BankingPresenter(
callRetrievedAccountTransactionsResponseListener(bankAccount, response) callRetrievedAccountTransactionsResponseListener(bankAccount, response)
} }
protected open fun receivedAccountsTransactionChunk(bankAccount: BankAccount, accountTransactionsChunk: List<AccountTransaction>) {
if (accountTransactionsChunk.isNotEmpty()) {
bankAccount.addBookedTransactions(accountTransactionsChunk)
callRetrievedAccountTransactionsResponseListener(bankAccount, GetTransactionsResponse(true, null, mapOf(bankAccount to accountTransactionsChunk)))
}
}
protected open fun updateAccountTransactionsAndBalances(bankAccount: BankAccount, response: GetTransactionsResponse) { protected open fun updateAccountTransactionsAndBalances(bankAccount: BankAccount, response: GetTransactionsResponse) {
response.bookedTransactions.forEach { entry -> response.bookedTransactions.forEach { entry ->

View File

@ -106,7 +106,8 @@ open class fints4kBankingClient(
callback(GetTransactionsResponse(false, "Cannot find account for ${bankAccount.identifier}")) // TODO: translate callback(GetTransactionsResponse(false, "Cannot find account for ${bankAccount.identifier}")) // TODO: translate
} }
else { else {
client.getTransactionsAsync(net.dankito.banking.fints.model.GetTransactionsParameter(parameter.alsoRetrieveBalance, parameter.fromDate, parameter.toDate), account) { response -> client.getTransactionsAsync(net.dankito.banking.fints.model.GetTransactionsParameter(parameter.alsoRetrieveBalance, parameter.fromDate, parameter.toDate, null,
{ parameter.retrievedChunkListener?.invoke(mapper.mapTransactions(bankAccount, it)) } ), account) { response ->
val mappedResponse = mapper.mapResponse(bankAccount, response) val mappedResponse = mapper.mapResponse(bankAccount, response)