Applied adjusted values from MT 940 to AccountTransaction

This commit is contained in:
dankito 2024-09-05 19:14:23 +02:00
parent 47e2b851b9
commit bf76de4f23
9 changed files with 158 additions and 82 deletions

View File

@ -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)

View File

@ -227,7 +227,7 @@ open class FinTsJobExecutor(
response.getFirstSegmentById<ReceivedCreditCardTransactionsAndBalance>(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) })
}
}

View File

@ -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
)
}

View File

@ -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
}

View File

@ -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,
)
}

View File

@ -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,

View File

@ -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

View File

@ -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
}

View File

@ -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)