Implemented requesting and parsing securities account balance
This commit is contained in:
parent
95e60b2706
commit
a42de32260
|
@ -202,6 +202,29 @@ open class FinTsJobExecutor(
|
|||
var balance: Money? = balanceResponse?.getFirstSegmentById<BalanceSegment>(InstituteSegmentId.Balance)?.let {
|
||||
Money(it.balance, it.currency)
|
||||
}
|
||||
|
||||
// TODO: for larger portfolios there can be a Aufsetzpunkt, but for balances we currently do not support sending multiple messages
|
||||
val statementOfHoldings = balanceResponse?.getFirstSegmentById<SecuritiesAccountBalanceSegment>(InstituteSegmentId.SecuritiesAccountBalance)?.let {
|
||||
val statementOfHoldings = it.statementOfHoldings
|
||||
val statementOfHolding = statementOfHoldings.firstOrNull { it.totalBalance != null }
|
||||
if (statementOfHolding != null) {
|
||||
balance = Money(statementOfHolding.totalBalance!!, statementOfHolding.currency ?: Currency.DefaultCurrencyCode)
|
||||
}
|
||||
statementOfHoldings
|
||||
} ?: emptyList()
|
||||
|
||||
if (parameter.account.supportsRetrievingAccountTransactions == false) {
|
||||
if (balanceResponse == null) {
|
||||
return GetAccountTransactionsResponse(context, BankResponse(false, "Balance could not be retrieved"), RetrievedAccountData.unsuccessful(parameter.account))
|
||||
} else {
|
||||
val successful = balance != null || balanceResponse.tanRequiredButWeWereToldToAbortIfSo
|
||||
val retrievedData = RetrievedAccountData(parameter.account, successful, balance, emptyList(), emptyList(), statementOfHoldings, Instant.nowExt(), null, null, balanceResponse?.internalError)
|
||||
|
||||
return GetAccountTransactionsResponse(context, balanceResponse, retrievedData)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
val bookedTransactions = mutableSetOf<AccountTransaction>()
|
||||
val unbookedTransactions = mutableSetOf<Any>()
|
||||
|
||||
|
@ -235,11 +258,12 @@ open class FinTsJobExecutor(
|
|||
closeDialog(context)
|
||||
|
||||
val successful = response.tanRequiredButWeWereToldToAbortIfSo
|
||||
|| (response.successful && (parameter.alsoRetrieveBalance == false || balance != null))
|
||||
|| (response.successful && (parameter.alsoRetrieveBalance == false || balance != null))
|
||||
|| (parameter.account.supportsRetrievingAccountTransactions == false && balance != null)
|
||||
val fromDate = parameter.fromDate
|
||||
?: parameter.account.serverTransactionsRetentionDays?.let { LocalDate.todayAtSystemDefaultTimeZone().minusDays(it) }
|
||||
?: bookedTransactions.minByOrNull { it.valueDate }?.valueDate
|
||||
val retrievedData = RetrievedAccountData(parameter.account, successful, balance, bookedTransactions, unbookedTransactions, startTime, fromDate, parameter.toDate ?: LocalDate.todayAtEuropeBerlin(), response.internalError)
|
||||
val retrievedData = RetrievedAccountData(parameter.account, successful, balance, bookedTransactions, unbookedTransactions, statementOfHoldings, startTime, fromDate, parameter.toDate ?: LocalDate.todayAtEuropeBerlin(), response.internalError)
|
||||
|
||||
return GetAccountTransactionsResponse(context, response, retrievedData,
|
||||
if (parameter.maxCountEntries != null) parameter.isSettingMaxCountEntriesAllowedByBank else null)
|
||||
|
|
|
@ -103,6 +103,8 @@ open class FinTsModelMapper {
|
|||
addAll(map(accountTransactionsResponse))
|
||||
}
|
||||
}
|
||||
|
||||
bankAccount.statementOfHoldings = accountTransactionsResponse.statementOfHoldings
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -13,6 +13,7 @@ import net.codinux.banking.fints.messages.segmente.Synchronisierung
|
|||
import net.codinux.banking.fints.messages.segmente.id.CustomerSegmentId
|
||||
import net.codinux.banking.fints.messages.segmente.id.ISegmentId
|
||||
import net.codinux.banking.fints.messages.segmente.implementierte.*
|
||||
import net.codinux.banking.fints.messages.segmente.implementierte.depot.Depotaufstellung
|
||||
import net.codinux.banking.fints.messages.segmente.implementierte.sepa.SepaBankTransferBase
|
||||
import net.codinux.banking.fints.messages.segmente.implementierte.tan.TanGeneratorListeAnzeigen
|
||||
import net.codinux.banking.fints.messages.segmente.implementierte.tan.TanGeneratorTanMediumAnOderUmmelden
|
||||
|
@ -242,17 +243,38 @@ open class MessageBuilder(protected val utils: FinTsUtils = FinTsUtils()) {
|
|||
return createSignedMessageBuilderResult(context, MessageType.GetBalance, segments)
|
||||
}
|
||||
|
||||
val securitiesAccountResult = supportsGetSecuritiesAccountBalance(account)
|
||||
|
||||
if (securitiesAccountResult.isJobVersionSupported) {
|
||||
return createGetSecuritiesAccountBalanceMessage(context, result, account)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
protected open fun createGetSecuritiesAccountBalanceMessage(context: JobContext, result: MessageBuilderResult,
|
||||
account: AccountData): MessageBuilderResult {
|
||||
|
||||
val segments = mutableListOf<Segment>(Depotaufstellung(SignedMessagePayloadFirstSegmentNumber, account))
|
||||
|
||||
addTanSegmentIfRequired(context, CustomerSegmentId.SecuritiesAccountBalance, segments, SignedMessagePayloadFirstSegmentNumber + 1)
|
||||
|
||||
return createSignedMessageBuilderResult(context, MessageType.GetSecuritiesAccountBalance, segments)
|
||||
}
|
||||
|
||||
open fun supportsGetBalance(account: AccountData): Boolean {
|
||||
return supportsGetBalanceMessage(account).isJobVersionSupported
|
||||
|| supportsGetSecuritiesAccountBalance(account).isJobVersionSupported
|
||||
}
|
||||
|
||||
protected open fun supportsGetBalanceMessage(account: AccountData): MessageBuilderResult {
|
||||
return getSupportedVersionsOfJobForAccount(CustomerSegmentId.Balance, account, listOf(5, 6, 7, 8))
|
||||
}
|
||||
|
||||
protected open fun supportsGetSecuritiesAccountBalance(account: AccountData): MessageBuilderResult {
|
||||
return getSupportedVersionsOfJobForAccount(CustomerSegmentId.SecuritiesAccountBalance, account, listOf(6))
|
||||
}
|
||||
|
||||
|
||||
open fun createGetTanMediaListMessage(context: JobContext,
|
||||
tanMediaKind: TanMedienArtVersion = TanMedienArtVersion.Alle,
|
||||
|
|
|
@ -27,6 +27,11 @@ enum class CustomerSegmentId(override val id: String) : ISegmentId {
|
|||
|
||||
SepaRealTimeTransfer("HKIPZ"),
|
||||
|
||||
SepaAccountInfoParameters("HKSPA") // not implemented, retrieved automatically with UPD
|
||||
SepaAccountInfoParameters("HKSPA"), // not implemented, retrieved automatically with UPD
|
||||
|
||||
|
||||
/* Wertpapierdepot */
|
||||
|
||||
SecuritiesAccountBalance("HKWPD")
|
||||
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
package net.codinux.banking.fints.messages.segmente.implementierte.depot
|
||||
|
||||
import net.codinux.banking.fints.messages.Existenzstatus
|
||||
import net.codinux.banking.fints.messages.datenelemente.implementierte.Aufsetzpunkt
|
||||
import net.codinux.banking.fints.messages.datenelemente.implementierte.account.MaximaleAnzahlEintraege
|
||||
import net.codinux.banking.fints.messages.datenelementgruppen.implementierte.Segmentkopf
|
||||
import net.codinux.banking.fints.messages.datenelementgruppen.implementierte.account.Kontoverbindung
|
||||
import net.codinux.banking.fints.messages.segmente.Segment
|
||||
import net.codinux.banking.fints.messages.segmente.id.CustomerSegmentId
|
||||
import net.codinux.banking.fints.model.AccountData
|
||||
|
||||
/**
|
||||
* Nr. Name Version Typ Format Länge Status Anzahl Restriktionen
|
||||
1 Segmentkopf 1 DEG M 1
|
||||
2 Depot 3 DEG ktv # M 1
|
||||
3 Währung der Depotaufstellung 1 DE cur # C 1 O: „Währung der Depotaufstellung wählbar“ (BPD) = „J“; N: sonst
|
||||
4 Kursqualität 2 DE code 1 C 1 1,2 O: „Kursqualität wählbar“ (BPD) = „J“; N: sonst
|
||||
5 Maximale Anzahl Einträge 1 DE num ..4 C 1 >0 O: „Eingabe Anzahl Einträge erlaubt“ (BPD) = „J“; N: sonst
|
||||
6 Aufsetzpunkt 1 DE an ..35 C 1 M: vom Institut wurde ein Aufsetzpunkt rückgemeldet N: sonst
|
||||
*/
|
||||
class Depotaufstellung(
|
||||
segmentNumber: Int,
|
||||
account: AccountData,
|
||||
// parameter: GetAccountTransactionsParameter
|
||||
): Segment(listOf(
|
||||
Segmentkopf(CustomerSegmentId.SecuritiesAccountBalance, 6, segmentNumber),
|
||||
Kontoverbindung(account),
|
||||
// TODO:
|
||||
// 3. Währung der Depotaufstellung
|
||||
// 4. Kursqualität
|
||||
// 5. Maximale Anzahl Einträge
|
||||
// 6. Aufsetzpunkt
|
||||
|
||||
// MaximaleAnzahlEintraege(parameter), // TODO: this is wrong, it only works for HKKAZ
|
||||
MaximaleAnzahlEintraege(null, Existenzstatus.Optional),
|
||||
Aufsetzpunkt(null, Existenzstatus.Optional) // will be set dynamically, see MessageBuilder.rebuildMessageWithContinuationId(); M: vom Institut wurde ein Aufsetzpunkt rückgemeldet. N: sonst
|
||||
))
|
|
@ -25,6 +25,8 @@ enum class MessageType {
|
|||
|
||||
GetCreditCardTransactions,
|
||||
|
||||
GetSecuritiesAccountBalance,
|
||||
|
||||
TransferMoney
|
||||
|
||||
}
|
|
@ -2,6 +2,7 @@ package net.codinux.banking.fints.model
|
|||
|
||||
import kotlinx.datetime.Instant
|
||||
import kotlinx.datetime.LocalDate
|
||||
import net.codinux.banking.fints.transactions.swift.model.StatementOfHoldings
|
||||
|
||||
|
||||
open class RetrievedAccountData(
|
||||
|
@ -10,6 +11,7 @@ open class RetrievedAccountData(
|
|||
open val balance: Money?,
|
||||
open var bookedTransactions: Collection<AccountTransaction>,
|
||||
open var unbookedTransactions: Collection<Any>,
|
||||
open var statementOfHoldings: List<StatementOfHoldings>,
|
||||
open val retrievalTime: Instant,
|
||||
open val retrievedTransactionsFrom: LocalDate?,
|
||||
open val retrievedTransactionsTo: LocalDate?,
|
||||
|
@ -19,7 +21,7 @@ open class RetrievedAccountData(
|
|||
companion object {
|
||||
|
||||
fun unsuccessful(account: AccountData): RetrievedAccountData {
|
||||
return RetrievedAccountData(account, false, null, listOf(), listOf(), Instant.DISTANT_PAST, null, null)
|
||||
return RetrievedAccountData(account, false, null, listOf(), listOf(), listOf(), Instant.DISTANT_PAST, null, null)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -43,6 +43,11 @@ enum class InstituteSegmentId(override val id: String) : ISegmentId {
|
|||
|
||||
CreditCardTransactions("DIKKU"),
|
||||
|
||||
CreditCardTransactionsParameters(CreditCardTransactions.id + "S")
|
||||
CreditCardTransactionsParameters(CreditCardTransactions.id + "S"),
|
||||
|
||||
|
||||
/* Wertpapierdepot */
|
||||
|
||||
SecuritiesAccountBalance("HIWPD")
|
||||
|
||||
}
|
|
@ -24,11 +24,13 @@ import net.codinux.banking.fints.model.Money
|
|||
import net.codinux.banking.fints.response.segments.*
|
||||
import net.codinux.banking.fints.util.MessageUtils
|
||||
import net.codinux.banking.fints.extensions.getAllExceptionMessagesJoined
|
||||
import net.codinux.banking.fints.transactions.swift.Mt535Parser
|
||||
|
||||
|
||||
open class ResponseParser(
|
||||
protected open val messageUtils: MessageUtils = MessageUtils(),
|
||||
open var logAppender: IMessageLogAppender? = null
|
||||
open var logAppender: IMessageLogAppender? = null,
|
||||
open var mt535Parser: Mt535Parser = Mt535Parser(logAppender)
|
||||
) {
|
||||
|
||||
companion object {
|
||||
|
@ -116,6 +118,7 @@ open class ResponseParser(
|
|||
InstituteSegmentId.ChangeTanMediaParameters.id -> parseChangeTanMediaParameters(segment, segmentId, dataElementGroups)
|
||||
|
||||
InstituteSegmentId.Balance.id -> parseBalanceSegment(segment, dataElementGroups)
|
||||
InstituteSegmentId.SecuritiesAccountBalance.id -> parseSecuritiesAccountBalanceSegment(segment, dataElementGroups)
|
||||
|
||||
InstituteSegmentId.AccountTransactionsMt940.id -> parseMt940AccountTransactions(segment, dataElementGroups)
|
||||
InstituteSegmentId.AccountTransactionsMt940Parameters.id -> parseMt940AccountTransactionsParameters(segment, segmentId, dataElementGroups)
|
||||
|
@ -690,6 +693,17 @@ open class ResponseParser(
|
|||
)
|
||||
}
|
||||
|
||||
protected open fun parseSecuritiesAccountBalanceSegment(segment: String, dataElementGroups: List<String>): SecuritiesAccountBalanceSegment {
|
||||
// 1 Segmentkopf 1 DEG M 1
|
||||
// 2 Depotaufstellung 1 DE bin .. M 1
|
||||
|
||||
val balancesMt535String = extractBinaryData(dataElementGroups[1])
|
||||
// TODO: for larger portfolios there can be a Aufsetzpunkt, but for balances we currently do not support sending multiple messages
|
||||
val statementOfHoldings = mt535Parser.parseMt535String(balancesMt535String)
|
||||
|
||||
return SecuritiesAccountBalanceSegment(statementOfHoldings, segment)
|
||||
}
|
||||
|
||||
protected open fun parseBalanceToNullIfZeroOrNotSet(dataElementGroup: String): Balance? {
|
||||
if (dataElementGroup.isEmpty()) {
|
||||
return null
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
package net.codinux.banking.fints.response.segments
|
||||
|
||||
import net.codinux.banking.fints.transactions.swift.model.StatementOfHoldings
|
||||
|
||||
class SecuritiesAccountBalanceSegment(
|
||||
val statementOfHoldings: List<StatementOfHoldings>,
|
||||
segmentString: String
|
||||
)
|
||||
: ReceivedSegment(segmentString)
|
|
@ -2,8 +2,10 @@ package net.codinux.banking.fints.transactions.swift.model
|
|||
|
||||
import kotlinx.datetime.Instant
|
||||
import kotlinx.datetime.LocalDate
|
||||
import kotlinx.serialization.Serializable
|
||||
import net.codinux.banking.fints.model.Amount
|
||||
|
||||
@Serializable
|
||||
data class Holding(
|
||||
val name: String,
|
||||
val isin: String?,
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package net.codinux.banking.fints.transactions.swift.model
|
||||
|
||||
import kotlinx.datetime.LocalDate
|
||||
import kotlinx.serialization.Serializable
|
||||
import net.codinux.banking.fints.model.Amount
|
||||
|
||||
/**
|
||||
|
@ -8,6 +9,7 @@ import net.codinux.banking.fints.model.Amount
|
|||
* „Statement of Holdings“; basiert auf SWIFT „Standards Release Guide“
|
||||
* (letzte berücksichtigte Änderung SRG 1998)
|
||||
*/
|
||||
@Serializable
|
||||
data class StatementOfHoldings(
|
||||
val bankCode: String,
|
||||
val accountIdentifier: String,
|
||||
|
@ -15,7 +17,7 @@ data class StatementOfHoldings(
|
|||
val holdings: List<Holding>,
|
||||
|
||||
val totalBalance: Amount? = null,
|
||||
val totalBalanceCurrency: String? = null,
|
||||
val currency: String? = null,
|
||||
|
||||
/**
|
||||
* The page number is actually mandatory, but to be prepared for surprises like for [statementDate] i added error
|
||||
|
|
|
@ -5,6 +5,7 @@ import kotlinx.datetime.LocalDate
|
|||
import kotlinx.serialization.Serializable
|
||||
import net.codinux.banking.fints.model.Currency
|
||||
import net.codinux.banking.fints.model.Money
|
||||
import net.codinux.banking.fints.transactions.swift.model.StatementOfHoldings
|
||||
|
||||
|
||||
@Serializable
|
||||
|
@ -40,6 +41,8 @@ open class BankAccount(
|
|||
|
||||
open var bookedTransactions: List<AccountTransaction> = listOf()
|
||||
|
||||
open var statementOfHoldings: List<StatementOfHoldings> = emptyList()
|
||||
|
||||
|
||||
override fun toString(): String {
|
||||
return "$productName ($identifier)"
|
||||
|
|
|
@ -8,7 +8,6 @@ import net.codinux.banking.fints.transactions.swift.model.ContinuationIndicator
|
|||
import net.codinux.banking.fints.transactions.swift.model.Holding
|
||||
import net.codinux.banking.fints.transactions.swift.model.StatementOfHoldings
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertNotNull
|
||||
|
||||
class Mt535ParserTest {
|
||||
|
||||
|
@ -37,7 +36,7 @@ class Mt535ParserTest {
|
|||
assertEquals("1234567", statement.accountIdentifier)
|
||||
|
||||
assertEquals("17026,37", statement.totalBalance?.string)
|
||||
assertEquals("EUR", statement.totalBalanceCurrency)
|
||||
assertEquals("EUR", statement.currency)
|
||||
|
||||
assertEquals(1, statement.pageNumber)
|
||||
assertEquals(ContinuationIndicator.SinglePage, statement.continuationIndicator)
|
||||
|
@ -61,7 +60,7 @@ class Mt535ParserTest {
|
|||
assertEquals(accountId, statement.accountIdentifier)
|
||||
|
||||
assertEquals(totalBalance, statement.totalBalance?.string)
|
||||
assertEquals(totalBalanceCurrency, statement.totalBalanceCurrency)
|
||||
assertEquals(totalBalanceCurrency, statement.currency)
|
||||
|
||||
assertEquals(pageNumber, statement.pageNumber)
|
||||
assertEquals(continuationIndicator, statement.continuationIndicator)
|
||||
|
|
Loading…
Reference in New Issue