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 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)
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
if (account.supportsRetrievingTransactionsOfLast90DaysWithoutTan == null &&
response.successful && transactions.bookedTransactionsString.isNotEmpty() && parameter.fromDate == null) {
tryGetTransactionsOfLast90DaysWithoutTan(bank, customer, account, true)
}
val bookedAndUnbookedTransactions = getTransactionsFromResponse(response, transactions, account)
return GetTransactionsResponse(response,
bookedAndUnbookedTransactions.first.sortedByDescending { it.bookingDate },
bookedAndUnbookedTransactions.second,
balance)
// just retrieved all transactions -> check if retrieving that ones of last 90 days is possible without entering TAN
if (account.supportsRetrievingTransactionsOfLast90DaysWithoutTan == null &&
response.successful && bookedTransactions.isNotEmpty() && parameter.fromDate == null) {
tryGetTransactionsOfLast90DaysWithoutTan(bank, customer, account, true)
}
return GetTransactionsResponse(response)
}
protected open fun getTransactionsFromResponse(response: Response, transactions: ReceivedAccountTransactions, account: AccountData): Pair<List<AccountTransaction>, List<Any>> {
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)
return GetTransactionsResponse(response,
bookedTransactions.sortedByDescending { it.bookingDate },
listOf(), // TODO: implement parsing MT942
balance)
}
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
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 (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.hasFollowUpMessageButCouldNotReceiveIt = handledResponse.followUpResponse == null
}
}
?: run { dialogContext.chunkedResponseHandler?.invoke(handledResponse) }
return handledResponse
}
@ -794,7 +795,7 @@ open class FinTsClient @JvmOverloads constructor(
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
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)
if (initDialogResponse.successful == false) {

View File

@ -1,6 +1,7 @@
package net.dankito.banking.fints.messages
import net.dankito.banking.fints.messages.segmente.Segment
import net.dankito.banking.fints.messages.segmente.implementierte.ZweiSchrittTanEinreichung
open class MessageBuilderResult(
@ -25,4 +26,10 @@ open class MessageBuilderResult(
open val getHighestAllowedVersion: Int?
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 response: Response? = null,
var didBankCloseDialog: Boolean = false,
var previousMessageInDialog: MessageBuilderResult? = null
var previousMessageInDialog: MessageBuilderResult? = null,
var chunkedResponseHandler: ((Response) -> Unit)? = null
) : MessageBaseData(bank, customer, product) {
companion object {

View File

@ -7,5 +7,6 @@ open class GetTransactionsParameter @JvmOverloads constructor(
val alsoRetrieveBalance: Boolean = true,
val fromDate: 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 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) }
}
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> {
try {
return statement.transactions.map { mapToAccountTransaction(statement, it, account) }

View File

@ -7,4 +7,6 @@ interface IMt940Parser {
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()
}
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> {
return mt940String.split(AccountStatementsSeparatorPattern)

View File

@ -1,12 +1,14 @@
package net.dankito.banking.ui.model.parameters
import net.dankito.banking.ui.model.AccountTransaction
import java.util.*
open class GetTransactionsParameter(
val alsoRetrieveBalance: Boolean = true,
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

View File

@ -268,7 +268,7 @@ open class BankingPresenter(
getClientForAccount(bankAccount.account)?.let { client ->
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)
@ -299,6 +299,14 @@ open class BankingPresenter(
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) {
response.bookedTransactions.forEach { entry ->

View File

@ -106,7 +106,8 @@ open class fints4kBankingClient(
callback(GetTransactionsResponse(false, "Cannot find account for ${bankAccount.identifier}")) // TODO: translate
}
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)