diff --git a/SampleApplications/NativeApp/src/nativeMain/kotlin/util/CsvWriter.kt b/SampleApplications/NativeApp/src/nativeMain/kotlin/util/CsvWriter.kt index c0b43693..060d2656 100644 --- a/SampleApplications/NativeApp/src/nativeMain/kotlin/util/CsvWriter.kt +++ b/SampleApplications/NativeApp/src/nativeMain/kotlin/util/CsvWriter.kt @@ -41,7 +41,7 @@ open class CsvWriter { protected open suspend fun writeToFile(stream: AsyncStream, valueSeparator: String, customer: CustomerAccount, account: BankAccount, transaction: AccountTransaction) { val amount = if (valueSeparator == ";") transaction.amount.amount.string.replace('.', ',') else transaction.amount.amount.string.replace(',', '.') - stream.writeString(listOf(customer.bankName, account.identifier, transaction.valueDate, amount, transaction.amount.currency, ensureNotNull(transaction.bookingText), wrap(transaction.reference), + stream.writeString(listOf(customer.bankName, account.identifier, transaction.valueDate, amount, transaction.amount.currency, ensureNotNull(transaction.postingText), wrap(transaction.reference), ensureNotNull(transaction.otherPartyName), ensureNotNull(transaction.otherPartyBankCode), ensureNotNull(transaction.otherPartyAccountId)).joinToString(valueSeparator)) stream.writeString(NewLine) diff --git a/fints4k/src/commonMain/kotlin/net/codinux/banking/fints/FinTsJobExecutor.kt b/fints4k/src/commonMain/kotlin/net/codinux/banking/fints/FinTsJobExecutor.kt index 6ef0a7fe..7f7dce9b 100644 --- a/fints4k/src/commonMain/kotlin/net/codinux/banking/fints/FinTsJobExecutor.kt +++ b/fints4k/src/commonMain/kotlin/net/codinux/banking/fints/FinTsJobExecutor.kt @@ -227,7 +227,7 @@ open class FinTsJobExecutor( response.getFirstSegmentById(InstituteSegmentId.CreditCardTransactions)?.let { creditCardTransactionsSegment -> balance = Money(creditCardTransactionsSegment.balance.amount, creditCardTransactionsSegment.balance.currency ?: "EUR") - bookedTransactions.addAll(creditCardTransactionsSegment.transactions.map { AccountTransaction(parameter.account, it.amount, it.description, it.bookingDate, it.transactionDescriptionBase ?: "", null, null, "", it.valueDate) }) + bookedTransactions.addAll(creditCardTransactionsSegment.transactions.map { AccountTransaction(parameter.account, it.amount, it.description, it.bookingDate, it.valueDate, it.transactionDescriptionBase ?: "", null, null) }) } } diff --git a/fints4k/src/commonMain/kotlin/net/codinux/banking/fints/mapper/FinTsModelMapper.kt b/fints4k/src/commonMain/kotlin/net/codinux/banking/fints/mapper/FinTsModelMapper.kt index 897c66e6..de48333e 100644 --- a/fints4k/src/commonMain/kotlin/net/codinux/banking/fints/mapper/FinTsModelMapper.kt +++ b/fints4k/src/commonMain/kotlin/net/codinux/banking/fints/mapper/FinTsModelMapper.kt @@ -111,14 +111,23 @@ open class FinTsModelMapper { } open fun map(transaction: net.codinux.banking.fints.model.AccountTransaction): AccountTransaction { - return AccountTransaction(transaction.amount, transaction.unparsedReference, transaction.bookingDate, - transaction.otherPartyName, transaction.otherPartyBankCode, transaction.otherPartyAccountId, transaction.bookingText, transaction.valueDate, - transaction.statementNumber, transaction.sequenceNumber, transaction.openingBalance, transaction.closingBalance, - transaction.endToEndReference, transaction.customerReference, transaction.mandateReference, transaction.creditorIdentifier, transaction.originatorsIdentificationCode, + return AccountTransaction( + transaction.amount, transaction.unparsedReference, + transaction.bookingDate, transaction.valueDate, + transaction.otherPartyName, transaction.otherPartyBankCode, transaction.otherPartyAccountId, + + transaction.postingText, transaction.statementNumber, transaction.sheetNumber, + transaction.openingBalance, transaction.closingBalance, + + transaction.customerReference, transaction.bankReference, transaction.furtherInformation, + + transaction.endToEndReference, transaction.mandateReference, transaction.creditorIdentifier, transaction.originatorsIdentificationCode, transaction.compensationAmount, transaction.originalAmount, transaction.sepaReference, transaction.deviantOriginator, transaction.deviantRecipient, - transaction.referenceWithNoSpecialType, transaction.primaNotaNumber, transaction.textKeySupplement, - transaction.currencyType, transaction.bookingKey, transaction.referenceForTheAccountOwner, transaction.referenceOfTheAccountServicingInstitution, transaction.supplementaryDetails, - transaction.transactionReferenceNumber, transaction.relatedReferenceNumber) + transaction.referenceWithNoSpecialType, + + transaction.journalNumber, transaction.textKeyAddition, + transaction.orderReferenceNumber, transaction.referenceNumber + ) } diff --git a/fints4k/src/commonMain/kotlin/net/codinux/banking/fints/model/AccountTransaction.kt b/fints4k/src/commonMain/kotlin/net/codinux/banking/fints/model/AccountTransaction.kt index f2191bdd..90e4da32 100644 --- a/fints4k/src/commonMain/kotlin/net/codinux/banking/fints/model/AccountTransaction.kt +++ b/fints4k/src/commonMain/kotlin/net/codinux/banking/fints/model/AccountTransaction.kt @@ -7,51 +7,118 @@ import net.codinux.banking.fints.extensions.UnixEpochStart open class AccountTransaction( val account: AccountData, val amount: Money, - val isReversal: Boolean, val unparsedReference: String, + val bookingDate: LocalDate, - val otherPartyName: String?, - val otherPartyBankCode: String?, - val otherPartyAccountId: String?, - val bookingText: String?, val valueDate: LocalDate, + + /** + * Name des Überweisenden oder Zahlungsempfängers + */ + val otherPartyName: String?, + /** + * BIC des Überweisenden / Zahlungsempfängers + */ + val otherPartyBankCode: String?, + /** + * IBAN des Überweisenden oder Zahlungsempfängers + */ + val otherPartyAccountId: String?, + + /** + * Buchungstext, z. B. DAUERAUFTRAG, BARGELDAUSZAHLUNG, ONLINE-UEBERWEISUNG, FOLGELASTSCHRIFT, ... + */ + val postingText: String?, + /** + * Auszugsnummer + */ val statementNumber: Int, - val sequenceNumber: Int?, + /** + * Blattnummer + */ + val sheetNumber: Int?, + val openingBalance: Money?, val closingBalance: Money?, - val endToEndReference: String?, + /** + * Kundenreferenz. + */ val customerReference: String?, + /** + * Bankreferenz + */ + val bankReference: String?, + /** + * Währungsart und Umsatzbetrag in Ursprungswährung + */ + val furtherInformation: String?, + + + /* Remittance information */ + + val endToEndReference: String?, val mandateReference: String?, val creditorIdentifier: String?, val originatorsIdentificationCode: String?, + /** + * Summe aus Auslagenersatz und Bearbeitungsprovision bei einer nationalen Rücklastschrift + * sowie optionalem Zinsausgleich. + */ val compensationAmount: String?, + /** + * Betrag der ursprünglichen Lastschrift + */ val originalAmount: String?, val sepaReference: String?, + /** + * Abweichender Überweisender oder Zahlungsempfänger + */ val deviantOriginator: String?, + /** + * Abweichender Zahlungsempfänger oder Zahlungspflichtiger + */ val deviantRecipient: String?, val referenceWithNoSpecialType: String?, - val primaNotaNumber: String?, - val textKeySupplement: String?, - val currencyType: String?, - val bookingKey: String, - val referenceForTheAccountOwner: String, - val referenceOfTheAccountServicingInstitution: String?, - val supplementaryDetails: String?, + /** + * Primanoten-Nr. + */ + val journalNumber: String?, + /** + * Bei R-Transaktionen siehe Tabelle der + * SEPA-Rückgabecodes, bei SEPALastschriften siehe optionale Belegung + * bei GVC 104 und GVC 105 (GVC = Geschäftsvorfallcode) + */ + val textKeyAddition: String?, - val transactionReferenceNumber: String, - val relatedReferenceNumber: String? + /** + * Referenznummer, die vom Sender als eindeutige Kennung für die Nachricht vergeben wurde + * (z.B. als Referenz auf stornierte Nachrichten). + */ + val orderReferenceNumber: String?, + /** + * Bezugsreferenz + */ + val referenceNumber: String?, + + /** + * Storno, ob die Buchung storniert wurde(?). + * Aus: + * „RC“ = Storno Haben + * „RD“ = Storno Soll + */ + val isReversal: Boolean ) { // for object deserializers - internal constructor() : this(AccountData(), Money(Amount.Zero, ""), "", UnixEpochStart, null, null, null, null, UnixEpochStart) + internal constructor() : this(AccountData(), Money(Amount.Zero, ""), "", UnixEpochStart, UnixEpochStart, null, null, null, null) - constructor(account: AccountData, amount: Money, unparsedReference: String, bookingDate: LocalDate, otherPartyName: String?, otherPartyBankCode: String?, otherPartyAccountId: String?, bookingText: String?, valueDate: LocalDate) - : this(account, amount, false, unparsedReference, bookingDate, otherPartyName, otherPartyBankCode, otherPartyAccountId, bookingText, valueDate, + constructor(account: AccountData, amount: Money, unparsedReference: String, bookingDate: LocalDate, valueDate: LocalDate, otherPartyName: String?, otherPartyBankCode: String?, otherPartyAccountId: String?, postingText: String? = null) + : this(account, amount, unparsedReference, bookingDate, valueDate, otherPartyName, otherPartyBankCode, otherPartyAccountId, postingText, 0, null, null, null, - null, null, null, null, null, null, null, null, null, null, null, null, null, - null, "", "", null, null, "", null) + null, null, null, null, null, null, null, null, null, null, null, null, + "", null, null, "", null, false) open val showOtherPartyName: Boolean @@ -72,7 +139,7 @@ open class AccountTransaction( if (otherPartyName != other.otherPartyName) return false if (otherPartyBankCode != other.otherPartyBankCode) return false if (otherPartyAccountId != other.otherPartyAccountId) return false - if (bookingText != other.bookingText) return false + if (postingText != other.postingText) return false if (valueDate != other.valueDate) return false return true @@ -86,7 +153,7 @@ open class AccountTransaction( result = 31 * result + (otherPartyName?.hashCode() ?: 0) result = 31 * result + (otherPartyBankCode?.hashCode() ?: 0) result = 31 * result + (otherPartyAccountId?.hashCode() ?: 0) - result = 31 * result + (bookingText?.hashCode() ?: 0) + result = 31 * result + (postingText?.hashCode() ?: 0) result = 31 * result + valueDate.hashCode() return result } diff --git a/fints4k/src/commonMain/kotlin/net/codinux/banking/fints/transactions/Mt940AccountTransactionsParser.kt b/fints4k/src/commonMain/kotlin/net/codinux/banking/fints/transactions/Mt940AccountTransactionsParser.kt index ae587147..66cb5558 100644 --- a/fints4k/src/commonMain/kotlin/net/codinux/banking/fints/transactions/Mt940AccountTransactionsParser.kt +++ b/fints4k/src/commonMain/kotlin/net/codinux/banking/fints/transactions/Mt940AccountTransactionsParser.kt @@ -44,24 +44,33 @@ open class Mt940AccountTransactionsParser( protected open fun mapToAccountTransaction(statement: AccountStatement, transaction: Transaction, account: AccountData): AccountTransaction { val currency = statement.closingBalance.currency + // may parse postingKey to postingText (Buchungstext) according to table in 8.2.3 Buchungsschlüssel (Feld 61), S. 654 ff. + return AccountTransaction( account, Money(mapAmount(transaction.statementLine), currency), - transaction.statementLine.isReversal, transaction.information?.unparsedReference ?: "", + transaction.statementLine.bookingDate ?: statement.closingBalance.bookingDate, + transaction.statementLine.valueDate, + transaction.information?.otherPartyName, transaction.information?.otherPartyBankCode, transaction.information?.otherPartyAccountId, + transaction.information?.postingText, - transaction.statementLine.valueDate, statement.statementNumber, statement.sheetNumber, - Money(mapAmount(statement.openingBalance), currency), // TODO: that's not true, these are the opening and closing balance of - Money(mapAmount(statement.closingBalance), currency), // all transactions of this day, not this specific transaction's ones + + Money(mapAmount(statement.openingBalance), currency), + Money(mapAmount(statement.closingBalance), currency), + + // :60: customer reference: Wenn „KREF+“ eingestellt ist, dann erfolgt die Angabe der Referenznummer in Tag :86: . + transaction.information?.customerReference ?: transaction.statementLine.customerReference, + transaction.statementLine.bankReference, + transaction.statementLine.furtherInformationOriginalAmountAndCharges, transaction.information?.endToEndReference, - transaction.information?.customerReference, transaction.information?.mandateReference, transaction.information?.creditorIdentifier, transaction.information?.originatorsIdentificationCode, @@ -74,14 +83,10 @@ open class Mt940AccountTransactionsParser( transaction.information?.journalNumber, transaction.information?.textKeyAddition, - transaction.statementLine.currencyType, - transaction.statementLine.postingKey, - transaction.statementLine.customerReference, - transaction.statementLine.bankReference, - transaction.statementLine.furtherInformationOriginalAmountAndCharges, - statement.orderReferenceNumber, - statement.referenceNumber + statement.referenceNumber, + + transaction.statementLine.isReversal, ) } diff --git a/fints4k/src/commonMain/kotlin/net/codinux/banking/fints/transactions/mt940/Mt940Parser.kt b/fints4k/src/commonMain/kotlin/net/codinux/banking/fints/transactions/mt940/Mt940Parser.kt index 617e06e1..bbb64723 100644 --- a/fints4k/src/commonMain/kotlin/net/codinux/banking/fints/transactions/mt940/Mt940Parser.kt +++ b/fints4k/src/commonMain/kotlin/net/codinux/banking/fints/transactions/mt940/Mt940Parser.kt @@ -273,7 +273,7 @@ open class Mt940Parser( val postingKey = fieldValue.substring(postingKeyStart, postingKeyStart + 3) // TODO: parse codes, p. 178 val customerAndBankReference = fieldValue.substring(postingKeyStart + 3).split("//") - val customerReference = customerAndBankReference[0] + val customerReference = customerAndBankReference[0].takeIf { it != "NONREF" } /** * The content of this subfield is the account servicing institution's own reference for the transaction. @@ -281,14 +281,14 @@ open class Mt940Parser( * reference may be identical to subfield 7, Reference for the Account Owner. If this is * the case, Reference of the Account Servicing Institution, subfield 8 may be omitted. */ - var bankReference = if (customerAndBankReference.size > 1) customerAndBankReference[1] else customerReference // TODO: or use null? + var bankReference = if (customerAndBankReference.size > 1) customerAndBankReference[1] else null var furtherInformation: String? = null - val bankReferenceAndSupplementaryDetails = bankReference.split("\n") - if (bankReferenceAndSupplementaryDetails.size > 1) { - bankReference = bankReferenceAndSupplementaryDetails[0].trim() + if (bankReference != null && bankReference.contains('\n')) { + val bankReferenceAndFurtherInformation = bankReference.split("\n") + bankReference = bankReferenceAndFurtherInformation[0].trim() // TODO: parse /OCMT/ and /CHGS/, see page 518 - furtherInformation = bankReferenceAndSupplementaryDetails[1].trim() + furtherInformation = bankReferenceAndFurtherInformation[1].trim() } return StatementLine(!!!isDebit, isCancellation, valueDate, bookingDate, null, amount, postingKey, diff --git a/fints4k/src/commonMain/kotlin/net/codinux/banking/fints/transactions/mt940/model/StatementLine.kt b/fints4k/src/commonMain/kotlin/net/codinux/banking/fints/transactions/mt940/model/StatementLine.kt index c5f2ee60..9ed88c9a 100644 --- a/fints4k/src/commonMain/kotlin/net/codinux/banking/fints/transactions/mt940/model/StatementLine.kt +++ b/fints4k/src/commonMain/kotlin/net/codinux/banking/fints/transactions/mt940/model/StatementLine.kt @@ -58,17 +58,11 @@ open class StatementLine( val postingKey: String, /** - * Kundenreferenz. Bei - * Nichtbelegung wird - * „NONREF“ eingestellt, - * zum Beispiel bei Schecknummer - * Wenn „KREF+“ eingestellt - * ist, dann erfolgt die - * Angabe der - * Referenznummer in Tag - * :86: . + * Kundenreferenz. + * Bei Nichtbelegung wird „NONREF“ eingestellt, zum Beispiel bei Schecknummer + * Wenn „KREF+“ eingestellt ist, dann erfolgt die Angabe der Referenznummer in Tag :86: . */ - val customerReference: String, + val customerReference: String?, /** * Bankreferenz diff --git a/fints4k/src/commonMain/kotlin/net/dankito/banking/client/model/AccountTransaction.kt b/fints4k/src/commonMain/kotlin/net/dankito/banking/client/model/AccountTransaction.kt index 2e152127..efd84781 100644 --- a/fints4k/src/commonMain/kotlin/net/dankito/banking/client/model/AccountTransaction.kt +++ b/fints4k/src/commonMain/kotlin/net/dankito/banking/client/model/AccountTransaction.kt @@ -11,19 +11,26 @@ import net.codinux.banking.fints.extensions.UnixEpochStart open class AccountTransaction( val amount: Money, // TODO: if we decide to stick with Money, create own type, don't use that one from fints.model (or move over from) val unparsedReference: String, // alternative names: purpose, reason + val bookingDate: LocalDate, + val valueDate: LocalDate, + val otherPartyName: String?, val otherPartyBankCode: String?, val otherPartyAccountId: String?, - val bookingText: String?, - val valueDate: LocalDate, + + val postingText: String?, val statementNumber: Int, - val sequenceNumber: Int?, + val sheetNumber: Int?, + val openingBalance: Money?, val closingBalance: Money?, - val endToEndReference: String?, val customerReference: String?, + val bankReference: String?, + val furtherInformation: String?, + + val endToEndReference: String?, val mandateReference: String?, val creditorIdentifier: String?, val originatorsIdentificationCode: String?, @@ -36,24 +43,18 @@ open class AccountTransaction( val primaNotaNumber: String?, val textKeySupplement: String?, - val currencyType: String?, - val bookingKey: String, - val referenceForTheAccountOwner: String, - val referenceOfTheAccountServicingInstitution: String?, - val supplementaryDetails: String?, - - val transactionReferenceNumber: String, - val relatedReferenceNumber: String? + val orderReferenceNumber: String?, + val referenceNumber: String? ) { // for object deserializers - internal constructor() : this(Money(Amount.Zero, ""), "", UnixEpochStart, null, null, null, null, UnixEpochStart) + internal constructor() : this(Money(Amount.Zero, ""), "", UnixEpochStart, UnixEpochStart, null, null, null, null) - constructor(amount: Money, unparsedReference: String, bookingDate: LocalDate, otherPartyName: String?, otherPartyBankCode: String?, otherPartyAccountId: String?, bookingText: String?, valueDate: LocalDate) - : this(amount, unparsedReference, bookingDate, otherPartyName, otherPartyBankCode, otherPartyAccountId, bookingText, valueDate, + constructor(amount: Money, unparsedReference: String, bookingDate: LocalDate, valueDate: LocalDate, otherPartyName: String?, otherPartyBankCode: String?, otherPartyAccountId: String?, postingText: String?) + : this(amount, unparsedReference, bookingDate, valueDate, otherPartyName, otherPartyBankCode, otherPartyAccountId, postingText, 0, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, - null, "", "", null, null, "", null) + null, null, null, null) open val showOtherPartyName: Boolean @@ -73,7 +74,7 @@ open class AccountTransaction( if (otherPartyName != other.otherPartyName) return false if (otherPartyBankCode != other.otherPartyBankCode) return false if (otherPartyAccountId != other.otherPartyAccountId) return false - if (bookingText != other.bookingText) return false + if (postingText != other.postingText) return false if (valueDate != other.valueDate) return false return true @@ -86,7 +87,7 @@ open class AccountTransaction( result = 31 * result + (otherPartyName?.hashCode() ?: 0) result = 31 * result + (otherPartyBankCode?.hashCode() ?: 0) result = 31 * result + (otherPartyAccountId?.hashCode() ?: 0) - result = 31 * result + (bookingText?.hashCode() ?: 0) + result = 31 * result + (postingText?.hashCode() ?: 0) result = 31 * result + valueDate.hashCode() return result } diff --git a/fints4k/src/commonTest/kotlin/net/codinux/banking/fints/transactions/Mt940ParserTest.kt b/fints4k/src/commonTest/kotlin/net/codinux/banking/fints/transactions/Mt940ParserTest.kt index 53820a60..83813c13 100644 --- a/fints4k/src/commonTest/kotlin/net/codinux/banking/fints/transactions/Mt940ParserTest.kt +++ b/fints4k/src/commonTest/kotlin/net/codinux/banking/fints/transactions/Mt940ParserTest.kt @@ -3,7 +3,7 @@ package net.codinux.banking.fints.transactions import net.codinux.banking.fints.FinTsTestBase import net.codinux.banking.fints.transactions.mt940.Mt940Parser import net.codinux.banking.fints.transactions.mt940.model.Balance -import net.codinux.banking.fints.transactions.mt940.model.InformationToAccountOwner +import net.codinux.banking.fints.transactions.mt940.model.RemittanceInformationField import net.codinux.banking.fints.transactions.mt940.model.StatementLine import kotlinx.datetime.LocalDate import net.codinux.banking.fints.extensions.* @@ -90,7 +90,7 @@ class Mt940ParserTest : FinTsTestBase() { assertEquals(BankCode, statement.bankCodeBicOrIban) assertEquals(CustomerId, statement.accountIdentifier) assertEquals(0, statement.statementNumber) - assertNull(statement.sequenceNumber) + assertNull(statement.sheetNumber) assertBalance(statement.openingBalance, true, bookingDate, Amount("0,00")) assertBalance(statement.closingBalance, isCredit, bookingDate, amount) @@ -306,7 +306,7 @@ class Mt940ParserTest : FinTsTestBase() { assertSize(1, result.first().transactions) result.first().transactions[0].information?.apply { - assertEquals("BASISLASTSCHRIFT", bookingText) + assertEquals("BASISLASTSCHRIFT", postingText) assertEquals("TUBDDEDD", otherPartyBankCode) assertEquals("DE87300308801234567890", otherPartyAccountId) assertEquals("6MKL2OT30QENNLIU", endToEndReference) @@ -362,7 +362,7 @@ class Mt940ParserTest : FinTsTestBase() { assertEquals(amount, statementLine.amount) } - private fun assertTransactionDetails(details: InformationToAccountOwner?, otherPartyName: String, + private fun assertTransactionDetails(details: RemittanceInformationField?, otherPartyName: String, otherPartyBankCode: String, otherPartyAccountId: String) { assertNotNull(details)