Decided against parsing Mt942 creationTime and smallest amount (should rarely be used and may only cause parsing errors)
This commit is contained in:
parent
90a7543641
commit
7ddeb88475
|
@ -1,6 +1,5 @@
|
||||||
package net.codinux.banking.fints.transactions.mt940
|
package net.codinux.banking.fints.transactions.mt940
|
||||||
|
|
||||||
import kotlinx.datetime.Instant
|
|
||||||
import net.codinux.banking.fints.log.IMessageLogAppender
|
import net.codinux.banking.fints.log.IMessageLogAppender
|
||||||
import net.codinux.banking.fints.transactions.mt940.model.AmountAndCurrency
|
import net.codinux.banking.fints.transactions.mt940.model.AmountAndCurrency
|
||||||
import net.codinux.banking.fints.transactions.mt940.model.InterimAccountStatement
|
import net.codinux.banking.fints.transactions.mt940.model.InterimAccountStatement
|
||||||
|
@ -42,9 +41,12 @@ open class Mt942Parser(
|
||||||
transactions: List<Transaction>,
|
transactions: List<Transaction>,
|
||||||
fieldsByCode: List<Pair<String, String>>
|
fieldsByCode: List<Pair<String, String>>
|
||||||
): InterimAccountStatement {
|
): InterimAccountStatement {
|
||||||
val smallestAmounts = fieldsByCode.filter { it.first.startsWith(SmallestAmountCode) } // should we parse it? i see no use in it
|
// also decided against parsing smallest amounts, i don't think they ever going to be used
|
||||||
.mapIndexed { index, field -> parseAmountAndCurrency(field.second, index == 0) }
|
// val smallestAmounts = fieldsByCode.filter { it.first.startsWith(SmallestAmountCode) } // should we parse it? i see no use in it
|
||||||
val creationTime = fieldsByCode.first { it.first == CreationTimeCode || it.first.startsWith(CreationTimeStartCode) } // should we parse it? i see no use in it
|
// .mapIndexed { index, field -> parseAmountAndCurrency(field.second, index == 0) }
|
||||||
|
|
||||||
|
// decided against parsing creation time as there are so many non specification confirm time formats that parsing is likely to fail 'cause of this unused value
|
||||||
|
// val creationTime = parseDateTime(fieldsByCode.first { it.first == CreationTimeCode || it.first.startsWith(CreationTimeStartCode) }.second)
|
||||||
|
|
||||||
val numberAndTotalOfDebitPostings = fieldsByCode.firstOrNull { it.first.equals(AmountOfDebitPostingsCode) }
|
val numberAndTotalOfDebitPostings = fieldsByCode.firstOrNull { it.first.equals(AmountOfDebitPostingsCode) }
|
||||||
?.let { parseNumberAndTotalOfPostings(it.second) }
|
?.let { parseNumberAndTotalOfPostings(it.second) }
|
||||||
|
@ -55,9 +57,6 @@ open class Mt942Parser(
|
||||||
orderReferenceNumber, referenceNumber,
|
orderReferenceNumber, referenceNumber,
|
||||||
bankCodeBicOrIban, accountIdentifier,
|
bankCodeBicOrIban, accountIdentifier,
|
||||||
statementNumber, sheetNumber,
|
statementNumber, sheetNumber,
|
||||||
smallestAmounts.first(),
|
|
||||||
if (smallestAmounts.size > 1) smallestAmounts[1] else null,
|
|
||||||
Instant.DISTANT_PAST,
|
|
||||||
transactions,
|
transactions,
|
||||||
numberAndTotalOfDebitPostings,
|
numberAndTotalOfDebitPostings,
|
||||||
numberAndTotalOfCreditPostings
|
numberAndTotalOfCreditPostings
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package net.codinux.banking.fints.transactions.mt940
|
package net.codinux.banking.fints.transactions.mt940
|
||||||
|
|
||||||
import kotlinx.datetime.LocalDate
|
import kotlinx.datetime.*
|
||||||
import kotlinx.datetime.Month
|
import net.codinux.banking.fints.extensions.EuropeBerlin
|
||||||
import net.codinux.log.logger
|
import net.codinux.log.logger
|
||||||
import net.codinux.banking.fints.log.IMessageLogAppender
|
import net.codinux.banking.fints.log.IMessageLogAppender
|
||||||
import net.codinux.banking.fints.model.Amount
|
import net.codinux.banking.fints.model.Amount
|
||||||
|
@ -469,7 +469,7 @@ abstract class Mt94xParserBase<T: AccountStatementCommon>(
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
protected open fun parseMt940Date(dateString: String): LocalDate {
|
open fun parseMt940Date(dateString: String): LocalDate {
|
||||||
// TODO: this should be necessary anymore, isn't it?
|
// TODO: this should be necessary anymore, isn't it?
|
||||||
|
|
||||||
// SimpleDateFormat is not thread-safe. Before adding another library i decided to parse
|
// SimpleDateFormat is not thread-safe. Before adding another library i decided to parse
|
||||||
|
@ -526,6 +526,30 @@ abstract class Mt94xParserBase<T: AccountStatementCommon>(
|
||||||
return bookingDate
|
return bookingDate
|
||||||
}
|
}
|
||||||
|
|
||||||
|
open fun parseTime(timeString: String): LocalTime {
|
||||||
|
val hour = timeString.substring(0, 2).toInt()
|
||||||
|
val minute = timeString.substring(2, 4).toInt()
|
||||||
|
|
||||||
|
return LocalTime(hour, minute)
|
||||||
|
}
|
||||||
|
|
||||||
|
open fun parseDateTime(dateTimeString: String): Instant {
|
||||||
|
val date = parseMt940Date(dateTimeString.substring(0, 6))
|
||||||
|
|
||||||
|
val time = parseTime(dateTimeString.substring(6, 10))
|
||||||
|
|
||||||
|
val dateTime = LocalDateTime(date, time)
|
||||||
|
|
||||||
|
return if (dateTimeString.length == 15) { // actually mandatory, but by far not always stated: the time zone
|
||||||
|
val plus = dateTimeString[10] == '+'
|
||||||
|
val timeDifference = parseTime(dateTimeString.substring(11))
|
||||||
|
|
||||||
|
dateTime.toInstant(UtcOffset(if (plus) timeDifference.hour else timeDifference.hour * -1, timeDifference.minute))
|
||||||
|
} else { // we then assume the server states the DateTime in FinTS's default time zone, Europe/Berlin
|
||||||
|
dateTime.toInstant(TimeZone.EuropeBerlin)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected open fun parseAmount(amountString: String): Amount {
|
protected open fun parseAmount(amountString: String): Amount {
|
||||||
return Amount(amountString)
|
return Amount(amountString)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
package net.codinux.banking.fints.transactions.mt940.model
|
package net.codinux.banking.fints.transactions.mt940.model
|
||||||
|
|
||||||
import kotlinx.datetime.Instant
|
|
||||||
|
|
||||||
open class InterimAccountStatement(
|
open class InterimAccountStatement(
|
||||||
orderReferenceNumber: String,
|
orderReferenceNumber: String,
|
||||||
referenceNumber: String?,
|
referenceNumber: String?,
|
||||||
|
@ -12,11 +10,12 @@ open class InterimAccountStatement(
|
||||||
statementNumber: Int,
|
statementNumber: Int,
|
||||||
sheetNumber: Int?,
|
sheetNumber: Int?,
|
||||||
|
|
||||||
val smallestAmountOfReportedTransactions: AmountAndCurrency,
|
// decided against parsing them, see Mt942Parser
|
||||||
|
// val smallestAmountOfReportedTransactions: AmountAndCurrency,
|
||||||
val smallestAmountOfReportedCreditTransactions: AmountAndCurrency? = null,
|
//
|
||||||
|
// val smallestAmountOfReportedCreditTransactions: AmountAndCurrency? = null,
|
||||||
val creationTime: Instant,
|
//
|
||||||
|
// val creationTime: Instant,
|
||||||
|
|
||||||
transactions: List<Transaction>,
|
transactions: List<Transaction>,
|
||||||
|
|
||||||
|
@ -27,11 +26,11 @@ open class InterimAccountStatement(
|
||||||
) : AccountStatementCommon(orderReferenceNumber, referenceNumber, bankCodeBicOrIban, accountIdentifier, statementNumber, sheetNumber, transactions) {
|
) : AccountStatementCommon(orderReferenceNumber, referenceNumber, bankCodeBicOrIban, accountIdentifier, statementNumber, sheetNumber, transactions) {
|
||||||
|
|
||||||
// for object deserializers
|
// for object deserializers
|
||||||
private constructor() : this("", "", "", null, 0, null, AmountAndCurrency(), null, Instant.DISTANT_PAST, listOf())
|
private constructor() : this("", "", "", null, 0, null, listOf())
|
||||||
|
|
||||||
|
|
||||||
override fun toString(): String {
|
override fun toString(): String {
|
||||||
return "$smallestAmountOfReportedTransactions ${super.toString()}"
|
return "${amountAndTotalOfDebitPostings?.amount} ${super.toString()}"
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -103,13 +103,13 @@ class Mt942ParserTest {
|
||||||
assertEquals(5, statement.statementNumber)
|
assertEquals(5, statement.statementNumber)
|
||||||
assertEquals(1, statement.sheetNumber)
|
assertEquals(1, statement.sheetNumber)
|
||||||
|
|
||||||
assertEquals("20,50", statement.smallestAmountOfReportedTransactions.amount)
|
// assertEquals("20,50", statement.smallestAmountOfReportedTransactions.amount)
|
||||||
assertEquals("EUR", statement.smallestAmountOfReportedTransactions.currency)
|
// assertEquals("EUR", statement.smallestAmountOfReportedTransactions.currency)
|
||||||
assertEquals(false, statement.smallestAmountOfReportedTransactions.isCredit)
|
// assertEquals(false, statement.smallestAmountOfReportedTransactions.isCredit)
|
||||||
|
//
|
||||||
assertEquals("155,34", statement.smallestAmountOfReportedCreditTransactions?.amount)
|
// assertEquals("155,34", statement.smallestAmountOfReportedCreditTransactions?.amount)
|
||||||
assertEquals("EUR", statement.smallestAmountOfReportedCreditTransactions?.currency)
|
// assertEquals("EUR", statement.smallestAmountOfReportedCreditTransactions?.currency)
|
||||||
assertEquals(true, statement.smallestAmountOfReportedCreditTransactions?.isCredit)
|
// assertEquals(true, statement.smallestAmountOfReportedCreditTransactions?.isCredit)
|
||||||
|
|
||||||
assertEquals(1, statement.amountAndTotalOfDebitPostings?.numberOfPostings)
|
assertEquals(1, statement.amountAndTotalOfDebitPostings?.numberOfPostings)
|
||||||
assertEquals("20,50", statement.amountAndTotalOfDebitPostings?.amount)
|
assertEquals("20,50", statement.amountAndTotalOfDebitPostings?.amount)
|
||||||
|
@ -167,10 +167,10 @@ class Mt942ParserTest {
|
||||||
assertEquals(0, statement.statementNumber)
|
assertEquals(0, statement.statementNumber)
|
||||||
assertEquals(1, statement.sheetNumber)
|
assertEquals(1, statement.sheetNumber)
|
||||||
|
|
||||||
assertEquals("60,77", statement.smallestAmountOfReportedTransactions.amount)
|
// assertEquals("60,77", statement.smallestAmountOfReportedTransactions.amount)
|
||||||
assertEquals("EUR", statement.smallestAmountOfReportedTransactions.currency)
|
// assertEquals("EUR", statement.smallestAmountOfReportedTransactions.currency)
|
||||||
assertEquals(false, statement.smallestAmountOfReportedTransactions.isCredit)
|
// assertEquals(false, statement.smallestAmountOfReportedTransactions.isCredit)
|
||||||
assertNull(statement.smallestAmountOfReportedCreditTransactions)
|
// assertNull(statement.smallestAmountOfReportedCreditTransactions)
|
||||||
|
|
||||||
assertEquals(1, statement.amountAndTotalOfDebitPostings?.numberOfPostings)
|
assertEquals(1, statement.amountAndTotalOfDebitPostings?.numberOfPostings)
|
||||||
assertEquals("60,77", statement.amountAndTotalOfDebitPostings?.amount)
|
assertEquals("60,77", statement.amountAndTotalOfDebitPostings?.amount)
|
||||||
|
@ -199,8 +199,8 @@ class Mt942ParserTest {
|
||||||
assertEquals(0, statement.statementNumber)
|
assertEquals(0, statement.statementNumber)
|
||||||
assertEquals(1, statement.sheetNumber)
|
assertEquals(1, statement.sheetNumber)
|
||||||
|
|
||||||
assertEquals("0,", statement.smallestAmountOfReportedTransactions.amount)
|
// assertEquals("0,", statement.smallestAmountOfReportedTransactions.amount)
|
||||||
assertEquals("EUR", statement.smallestAmountOfReportedTransactions.currency)
|
// assertEquals("EUR", statement.smallestAmountOfReportedTransactions.currency)
|
||||||
|
|
||||||
assertSize(0, statement.transactions)
|
assertSize(0, statement.transactions)
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,59 @@
|
||||||
|
package net.codinux.banking.fints.transactions
|
||||||
|
|
||||||
|
import kotlinx.datetime.LocalDateTime
|
||||||
|
import kotlinx.datetime.TimeZone
|
||||||
|
import kotlinx.datetime.toLocalDateTime
|
||||||
|
import net.codinux.banking.fints.extensions.EuropeBerlin
|
||||||
|
import net.codinux.banking.fints.test.assertEquals
|
||||||
|
import net.codinux.banking.fints.transactions.mt940.Mt94xParserBase
|
||||||
|
import net.codinux.banking.fints.transactions.mt940.model.AccountStatement
|
||||||
|
import net.codinux.banking.fints.transactions.mt940.model.Transaction
|
||||||
|
import kotlin.test.Test
|
||||||
|
|
||||||
|
class Mt94xParserBaseTest {
|
||||||
|
|
||||||
|
private val underTest = object : Mt94xParserBase<AccountStatement>() {
|
||||||
|
override fun createAccountStatement(orderReferenceNumber: String, referenceNumber: String?, bankCodeBicOrIban: String, accountIdentifier: String?, statementNumber: Int, sheetNumber: Int?, transactions: List<Transaction>, fieldsByCode: List<Pair<String, String>>): AccountStatement {
|
||||||
|
throw IllegalStateException("We are testing base functionality, not parsing (Interim)AccountStatements")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun parseDateTimeWithTimeZoneUtc() {
|
||||||
|
val result = underTest.parseDateTime("1311130945+0000")
|
||||||
|
|
||||||
|
val resultAtEuropeBerlin = result.toLocalDateTime(TimeZone.EuropeBerlin)
|
||||||
|
|
||||||
|
assertEquals(LocalDateTime(2013, 11, 13, 10, 45), resultAtEuropeBerlin)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun parseDateTimeWithTimeZoneEuropeBerlin() {
|
||||||
|
val result = underTest.parseDateTime("2408210742+0200")
|
||||||
|
|
||||||
|
val resultAtEuropeBerlin = result.toLocalDateTime(TimeZone.EuropeBerlin)
|
||||||
|
|
||||||
|
assertEquals(LocalDateTime(2024, 8, 21, 7, 42), resultAtEuropeBerlin)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun parseDateTimeWithoutTimeZone() { // actually the time zone is mandatory, but by far not all banks add it
|
||||||
|
val result = underTest.parseDateTime("2408232156")
|
||||||
|
|
||||||
|
val resultAtEuropeBerlin = result.toLocalDateTime(TimeZone.EuropeBerlin)
|
||||||
|
|
||||||
|
assertEquals(LocalDateTime(2024, 8, 23, 21, 56), resultAtEuropeBerlin)
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Test
|
||||||
|
// fun parseDateTimeStartingWithCharacter() {
|
||||||
|
// // really don't know where's the 'C' at the start is coming from, but this is an example from DFÜ-Abkommen PDF, p. 674
|
||||||
|
// val result = underTest.parseDateTime("C1311130945+0000")
|
||||||
|
//
|
||||||
|
// val resultAtEuropeBerlin = result.toLocalDateTime(TimeZone.EuropeBerlin)
|
||||||
|
//
|
||||||
|
// assertEquals(LocalDateTime(2024, 8, 23, 21, 56), resultAtEuropeBerlin)
|
||||||
|
// }
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue