Compare commits
4 Commits
c5b7967ce1
...
fd7a3bc747
Author | SHA1 | Date |
---|---|---|
dankito | fd7a3bc747 | |
dankito | 825dc7c8b9 | |
dankito | 54940742f7 | |
dankito | 5187e34797 |
|
@ -36,8 +36,8 @@ interface BankingClient {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convenience wrapper around [getAccountDataAsync].
|
* Convenience wrapper around [getAccountDataAsync].
|
||||||
* Updates account's transactions beginning from [BankAccount.lastTransactionsRetrievalTime].
|
* Updates account's transactions beginning from [BankAccount.lastAccountUpdateTime].
|
||||||
* This may requires TAN if [BankAccount.lastTransactionsRetrievalTime] is older than 90 days.
|
* This may requires TAN if [BankAccount.lastAccountUpdateTime] is older than 90 days.
|
||||||
*
|
*
|
||||||
* Optionally specify which [accounts] should be updated. If not specified all accounts will be updated.
|
* Optionally specify which [accounts] should be updated. If not specified all accounts will be updated.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -37,8 +37,8 @@ interface BankingClientForUser {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convenience wrapper around [getAccountDataAsync].
|
* Convenience wrapper around [getAccountDataAsync].
|
||||||
* Updates account's transactions beginning from [BankAccount.lastTransactionsRetrievalTime].
|
* Updates account's transactions beginning from [BankAccount.lastAccountUpdateTime].
|
||||||
* This may requires TAN if [BankAccount.lastTransactionsRetrievalTime] is older than 90 days.
|
* This may requires TAN if [BankAccount.lastAccountUpdateTime] is older than 90 days.
|
||||||
*/
|
*/
|
||||||
suspend fun updateAccountTransactionsAsync(accounts: List<BankAccount>? = null): Response<List<GetTransactionsResponse>>
|
suspend fun updateAccountTransactionsAsync(accounts: List<BankAccount>? = null): Response<List<GetTransactionsResponse>>
|
||||||
|
|
||||||
|
|
|
@ -79,6 +79,8 @@ kotlin {
|
||||||
|
|
||||||
val kotlinxDateTimeVersion: String by project
|
val kotlinxDateTimeVersion: String by project
|
||||||
val jsJodaTimeZoneVersion: String by project
|
val jsJodaTimeZoneVersion: String by project
|
||||||
|
val ionspinBigNumVersion: String by project
|
||||||
|
|
||||||
|
|
||||||
sourceSets {
|
sourceSets {
|
||||||
commonMain {
|
commonMain {
|
||||||
|
@ -102,12 +104,26 @@ kotlin {
|
||||||
jsMain {
|
jsMain {
|
||||||
dependencies {
|
dependencies {
|
||||||
api(npm("@js-joda/timezone", jsJodaTimeZoneVersion))
|
api(npm("@js-joda/timezone", jsJodaTimeZoneVersion))
|
||||||
|
|
||||||
|
implementation(npm("big.js", "6.0.3"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
jsTest { }
|
jsTest { }
|
||||||
|
|
||||||
nativeMain { }
|
nativeMain { }
|
||||||
nativeTest { }
|
nativeTest { }
|
||||||
|
|
||||||
|
linuxMain {
|
||||||
|
dependencies {
|
||||||
|
implementation("com.ionspin.kotlin:bignum:$ionspinBigNumVersion")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mingwMain {
|
||||||
|
dependencies {
|
||||||
|
implementation("com.ionspin.kotlin:bignum:$ionspinBigNumVersion")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
package net.codinux.banking.client.model
|
||||||
|
|
||||||
|
import platform.Foundation.NSDecimalNumber
|
||||||
|
import net.codinux.banking.client.model.config.NoArgConstructor
|
||||||
|
|
||||||
|
@NoArgConstructor
|
||||||
|
actual class Amount actual constructor(amount: String) : NSDecimalNumber(string = amount) {
|
||||||
|
|
||||||
|
actual companion object {
|
||||||
|
actual val Zero = Amount("0.00")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
actual operator fun plus(other: Amount): Amount =
|
||||||
|
Amount(this.decimalNumberByAdding(other).stringValue)
|
||||||
|
|
||||||
|
actual operator fun minus(other: Amount): Amount =
|
||||||
|
Amount(this.decimalNumberBySubtracting(other).stringValue)
|
||||||
|
|
||||||
|
actual operator fun times(other: Amount): Amount =
|
||||||
|
Amount(this.decimalNumberByMultiplyingBy(other).stringValue)
|
||||||
|
|
||||||
|
actual operator fun div(other: Amount): Amount =
|
||||||
|
Amount(this.decimalNumberByDividingBy(other).stringValue)
|
||||||
|
|
||||||
|
|
||||||
|
actual override fun toString(): String = this.stringValue
|
||||||
|
|
||||||
|
}
|
|
@ -1,18 +1,31 @@
|
||||||
package net.codinux.banking.client.model
|
package net.codinux.banking.client.model
|
||||||
|
|
||||||
import net.codinux.banking.client.model.config.NoArgConstructor
|
import net.codinux.banking.client.model.config.NoArgConstructor
|
||||||
import kotlin.jvm.JvmInline
|
|
||||||
|
|
||||||
@JvmInline
|
|
||||||
|
internal const val DecimalPrecision = 20 // 20 to match Big.js's behavior
|
||||||
|
|
||||||
|
|
||||||
|
fun Amount.toFloat() =
|
||||||
|
this.toString().toFloat()
|
||||||
|
|
||||||
|
fun Amount.toDouble() =
|
||||||
|
this.toString().toDouble()
|
||||||
|
|
||||||
|
|
||||||
@NoArgConstructor
|
@NoArgConstructor
|
||||||
value class Amount(val amount: String = "0") {
|
expect class Amount(amount: String = "0") {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val Zero = Amount("0")
|
val Zero: Amount
|
||||||
|
|
||||||
fun fromString(amount: String): Amount = Amount(amount)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
override fun toString() = amount
|
operator fun plus(other: Amount): Amount
|
||||||
|
operator fun minus(other: Amount): Amount
|
||||||
|
operator fun times(other: Amount): Amount
|
||||||
|
operator fun div(other: Amount): Amount
|
||||||
|
|
||||||
|
override fun toString(): String
|
||||||
|
|
||||||
}
|
}
|
|
@ -3,6 +3,7 @@ package net.codinux.banking.client.model
|
||||||
import kotlinx.datetime.*
|
import kotlinx.datetime.*
|
||||||
import net.codinux.banking.client.model.config.JsonIgnore
|
import net.codinux.banking.client.model.config.JsonIgnore
|
||||||
import net.codinux.banking.client.model.config.NoArgConstructor
|
import net.codinux.banking.client.model.config.NoArgConstructor
|
||||||
|
import net.codinux.banking.client.model.securitiesaccount.Holding
|
||||||
|
|
||||||
@Suppress("RUNTIME_ANNOTATION_NOT_SUPPORTED")
|
@Suppress("RUNTIME_ANNOTATION_NOT_SUPPORTED")
|
||||||
@NoArgConstructor
|
@NoArgConstructor
|
||||||
|
@ -23,11 +24,12 @@ open class BankAccount(
|
||||||
var balance: Amount = Amount.Zero, // TODO: add a BigDecimal library
|
var balance: Amount = Amount.Zero, // TODO: add a BigDecimal library
|
||||||
|
|
||||||
val serverTransactionsRetentionDays: Int? = null,
|
val serverTransactionsRetentionDays: Int? = null,
|
||||||
open var lastTransactionsRetrievalTime: Instant? = null,
|
open var lastAccountUpdateTime: Instant? = null,
|
||||||
var retrievedTransactionsFrom: LocalDate? = null,
|
var retrievedTransactionsFrom: LocalDate? = null,
|
||||||
|
|
||||||
open val bookedTransactions: MutableList<AccountTransaction> = mutableListOf(),
|
open val bookedTransactions: MutableList<AccountTransaction> = mutableListOf(),
|
||||||
open val prebookedTransactions: MutableList<PrebookedAccountTransaction> = mutableListOf(),
|
open val prebookedTransactions: MutableList<PrebookedAccountTransaction> = mutableListOf(),
|
||||||
|
open val holdings: List<Holding> = emptyList(),
|
||||||
|
|
||||||
var userSetDisplayName: String? = null,
|
var userSetDisplayName: String? = null,
|
||||||
var displayIndex: Int = 0,
|
var displayIndex: Int = 0,
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
package net.codinux.banking.client.model.extensions
|
||||||
|
|
||||||
|
import kotlinx.datetime.DateTimeUnit
|
||||||
|
import kotlinx.datetime.LocalDate
|
||||||
|
import kotlinx.datetime.TimeZone
|
||||||
|
import kotlinx.datetime.minus
|
||||||
|
|
||||||
|
|
||||||
|
fun LocalDate.minusDays(days: Int): LocalDate {
|
||||||
|
return this.minus(days, DateTimeUnit.DAY)
|
||||||
|
}
|
||||||
|
|
||||||
|
val TimeZone.Companion.EuropeBerlin: TimeZone
|
||||||
|
get() = TimeZone.of("Europe/Berlin")
|
|
@ -7,6 +7,7 @@ import net.codinux.banking.client.model.Amount
|
||||||
import net.codinux.banking.client.model.BankAccount
|
import net.codinux.banking.client.model.BankAccount
|
||||||
import net.codinux.banking.client.model.PrebookedAccountTransaction
|
import net.codinux.banking.client.model.PrebookedAccountTransaction
|
||||||
import net.codinux.banking.client.model.config.NoArgConstructor
|
import net.codinux.banking.client.model.config.NoArgConstructor
|
||||||
|
import net.codinux.banking.client.model.securitiesaccount.Holding
|
||||||
|
|
||||||
@NoArgConstructor
|
@NoArgConstructor
|
||||||
open class GetTransactionsResponse(
|
open class GetTransactionsResponse(
|
||||||
|
@ -14,6 +15,7 @@ open class GetTransactionsResponse(
|
||||||
val balance: Amount? = null,
|
val balance: Amount? = null,
|
||||||
val bookedTransactions: List<AccountTransaction>,
|
val bookedTransactions: List<AccountTransaction>,
|
||||||
val prebookedTransactions: List<PrebookedAccountTransaction>,
|
val prebookedTransactions: List<PrebookedAccountTransaction>,
|
||||||
|
val holdings: List<Holding> = emptyList(),
|
||||||
val transactionsRetrievalTime: Instant,
|
val transactionsRetrievalTime: Instant,
|
||||||
val retrievedTransactionsFrom: LocalDate? = null,
|
val retrievedTransactionsFrom: LocalDate? = null,
|
||||||
val retrievedTransactionsTo: LocalDate? = null
|
val retrievedTransactionsTo: LocalDate? = null
|
||||||
|
|
|
@ -0,0 +1,49 @@
|
||||||
|
package net.codinux.banking.client.model.securitiesaccount
|
||||||
|
|
||||||
|
import kotlinx.datetime.Instant
|
||||||
|
import kotlinx.datetime.LocalDate
|
||||||
|
import net.codinux.banking.client.model.Amount
|
||||||
|
import net.codinux.banking.client.model.config.NoArgConstructor
|
||||||
|
|
||||||
|
@NoArgConstructor
|
||||||
|
open class Holding(
|
||||||
|
val name: String,
|
||||||
|
|
||||||
|
val isin: String? = null,
|
||||||
|
val wkn: String? = null,
|
||||||
|
|
||||||
|
val quantity: Int? = null,
|
||||||
|
val currency: String? = null,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gesamter Kurswert aller Einheiten des Wertpapiers
|
||||||
|
*/
|
||||||
|
val totalBalance: Amount? = null,
|
||||||
|
/**
|
||||||
|
* Aktueller Kurswert einer einzelnen Einheit des Wertpapiers
|
||||||
|
*/
|
||||||
|
val marketValue: Amount? = null,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Änderung in Prozent Aktueller Kurswert gegenüber Einstandspreis.
|
||||||
|
*/
|
||||||
|
val performancePercentage: Float? = null,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gesamter Einstandspreis (Kaufpreis)
|
||||||
|
*/
|
||||||
|
val totalCostPrice: Amount? = null,
|
||||||
|
/**
|
||||||
|
* (Durchschnittlicher) Einstandspreis/-kurs einer Einheit des Wertpapiers
|
||||||
|
*/
|
||||||
|
val averageCostPrice: Amount? = null,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Zeitpunkt zu dem der Kurswert bestimmt wurde
|
||||||
|
*/
|
||||||
|
val pricingTime: Instant? = null,
|
||||||
|
|
||||||
|
val buyingDate: LocalDate? = null,
|
||||||
|
) {
|
||||||
|
override fun toString() = "$name $totalBalance $currency"
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
package net.codinux.banking.client.model
|
||||||
|
|
||||||
|
import kotlin.test.Test
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
|
class AmountTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun add() {
|
||||||
|
val result = Amount("0.1") + Amount("0.2")
|
||||||
|
|
||||||
|
assertEquals(Amount("0.3"), result)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun minus() {
|
||||||
|
val result = Amount("0.1") - Amount("0.2")
|
||||||
|
|
||||||
|
assertEquals(Amount("-0.1"), result)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun multiply() {
|
||||||
|
val result = Amount("0.1") * Amount("0.2")
|
||||||
|
|
||||||
|
assertEquals(Amount("0.02"), result)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun divide() {
|
||||||
|
val result = Amount("1") / Amount("3")
|
||||||
|
|
||||||
|
assertEquals(Amount("0.33333333333333333333"), result)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,51 @@
|
||||||
|
package net.codinux.banking.client.model
|
||||||
|
|
||||||
|
import net.codinux.banking.client.model.config.NoArgConstructor
|
||||||
|
|
||||||
|
@JsModule("big.js")
|
||||||
|
@JsNonModule
|
||||||
|
open external class Big(value: String) {
|
||||||
|
fun plus(other: Big): Big
|
||||||
|
fun minus(other: Big): Big
|
||||||
|
fun times(other: Big): Big
|
||||||
|
fun div(other: Big): Big
|
||||||
|
override fun toString(): String
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@NoArgConstructor
|
||||||
|
actual class Amount actual constructor(amount: String): Big(amount) {
|
||||||
|
|
||||||
|
actual companion object {
|
||||||
|
actual val Zero = Amount("0.00")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
actual operator fun plus(other: Amount): Amount {
|
||||||
|
return Amount(super.plus(other).toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
actual operator fun minus(other: Amount): Amount {
|
||||||
|
return Amount(super.minus(other).toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
actual operator fun times(other: Amount): Amount {
|
||||||
|
return Amount(super.times(other).toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
actual operator fun div(other: Amount): Amount {
|
||||||
|
return Amount(super.div(other).toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
return other is Amount && this.toString() == other.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
return super.hashCode()
|
||||||
|
}
|
||||||
|
|
||||||
|
actual override fun toString(): String = super.toString()
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
package net.codinux.banking.client.model
|
||||||
|
|
||||||
|
import net.codinux.banking.client.model.config.NoArgConstructor
|
||||||
|
import java.math.BigDecimal
|
||||||
|
import java.math.RoundingMode
|
||||||
|
|
||||||
|
@NoArgConstructor
|
||||||
|
actual class Amount actual constructor(amount: String) : BigDecimal(amount) {
|
||||||
|
|
||||||
|
actual companion object {
|
||||||
|
actual val Zero = Amount("0.00")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
actual operator fun plus(other: Amount): Amount =
|
||||||
|
Amount(this.add(other).toString())
|
||||||
|
|
||||||
|
actual operator fun minus(other: Amount): Amount =
|
||||||
|
Amount(this.subtract(other).toString())
|
||||||
|
|
||||||
|
actual operator fun times(other: Amount): Amount =
|
||||||
|
Amount(this.multiply(other).toString())
|
||||||
|
|
||||||
|
actual operator fun div(other: Amount): Amount =
|
||||||
|
// without RoundingMode a java.lang.ArithmeticException: Non-terminating decimal expansion; no exact representable decimal result. will be thrown
|
||||||
|
Amount(this.divide(other, DecimalPrecision, RoundingMode.HALF_UP).toString()) // 20 to match Big.js's behaviour
|
||||||
|
|
||||||
|
|
||||||
|
/* why are these methods required when deriving from BigDecimal? */
|
||||||
|
|
||||||
|
override fun toByte(): Byte {
|
||||||
|
1 + 1
|
||||||
|
return 0 // will never be called; where is this method coming from?
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toShort(): Short {
|
||||||
|
return 0 // will never be called; where is this method coming from?
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,47 @@
|
||||||
|
package net.codinux.banking.client.model
|
||||||
|
|
||||||
|
import com.ionspin.kotlin.bignum.decimal.BigDecimal
|
||||||
|
import com.ionspin.kotlin.bignum.decimal.DecimalMode
|
||||||
|
import com.ionspin.kotlin.bignum.decimal.RoundingMode
|
||||||
|
import net.codinux.banking.client.model.config.NoArgConstructor
|
||||||
|
|
||||||
|
@NoArgConstructor
|
||||||
|
actual class Amount actual constructor(amount: String) {
|
||||||
|
|
||||||
|
actual companion object {
|
||||||
|
actual val Zero: Amount = Amount("0.00")
|
||||||
|
|
||||||
|
private val decimalMode = DecimalMode(DecimalPrecision.toLong(), RoundingMode.ROUND_HALF_CEILING)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
internal val amount: BigDecimal = BigDecimal.parseString(amount)
|
||||||
|
|
||||||
|
actual operator fun plus(other: Amount): Amount {
|
||||||
|
return Amount(amount.add(other.amount).toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
actual operator fun minus(other: Amount): Amount {
|
||||||
|
return Amount(amount.subtract(other.amount).toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
actual operator fun times(other: Amount): Amount {
|
||||||
|
return Amount(amount.multiply(other.amount).toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
actual operator fun div(other: Amount): Amount {
|
||||||
|
return Amount(amount.divide(other.amount, decimalMode).toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
return other is Amount && this.amount == other.amount
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
return amount.hashCode()
|
||||||
|
}
|
||||||
|
|
||||||
|
actual override fun toString(): String = amount.toPlainString()
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,47 @@
|
||||||
|
package net.codinux.banking.client.model
|
||||||
|
|
||||||
|
import com.ionspin.kotlin.bignum.decimal.BigDecimal
|
||||||
|
import com.ionspin.kotlin.bignum.decimal.DecimalMode
|
||||||
|
import com.ionspin.kotlin.bignum.decimal.RoundingMode
|
||||||
|
import net.codinux.banking.client.model.config.NoArgConstructor
|
||||||
|
|
||||||
|
@NoArgConstructor
|
||||||
|
actual class Amount actual constructor(amount: String) {
|
||||||
|
|
||||||
|
actual companion object {
|
||||||
|
actual val Zero: Amount = Amount("0.00")
|
||||||
|
|
||||||
|
private val decimalMode = DecimalMode(DecimalPrecision.toLong(), RoundingMode.ROUND_HALF_CEILING)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
internal val amount: BigDecimal = BigDecimal.parseString(amount)
|
||||||
|
|
||||||
|
actual operator fun plus(other: Amount): Amount {
|
||||||
|
return Amount(amount.add(other.amount).toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
actual operator fun minus(other: Amount): Amount {
|
||||||
|
return Amount(amount.subtract(other.amount).toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
actual operator fun times(other: Amount): Amount {
|
||||||
|
return Amount(amount.multiply(other.amount).toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
actual operator fun div(other: Amount): Amount {
|
||||||
|
return Amount(amount.divide(other.amount, decimalMode).toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
return other is Amount && this.amount == other.amount
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
return amount.hashCode()
|
||||||
|
}
|
||||||
|
|
||||||
|
actual override fun toString(): String = amount.toPlainString()
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,51 @@
|
||||||
|
package net.codinux.banking.client.model
|
||||||
|
|
||||||
|
import net.codinux.banking.client.model.config.NoArgConstructor
|
||||||
|
|
||||||
|
@JsModule("big.js")
|
||||||
|
@kotlin.js.JsNonModule
|
||||||
|
open external class Big(value: String) {
|
||||||
|
fun plus(other: Big): Big
|
||||||
|
fun minus(other: Big): Big
|
||||||
|
fun times(other: Big): Big
|
||||||
|
fun div(other: Big): Big
|
||||||
|
override fun toString(): String
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@NoArgConstructor
|
||||||
|
actual class Amount actual constructor(amount: String): Big(amount) {
|
||||||
|
|
||||||
|
actual companion object {
|
||||||
|
actual val Zero = Amount("0.00")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
actual operator fun plus(other: Amount): Amount {
|
||||||
|
return Amount(super.plus(other).toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
actual operator fun minus(other: Amount): Amount {
|
||||||
|
return Amount(super.minus(other).toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
actual operator fun times(other: Amount): Amount {
|
||||||
|
return Amount(super.times(other).toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
actual operator fun div(other: Amount): Amount {
|
||||||
|
return Amount(super.div(other).toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
return other is Amount && this.toString() == other.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
return super.hashCode()
|
||||||
|
}
|
||||||
|
|
||||||
|
actual override fun toString(): String = super.toString()
|
||||||
|
|
||||||
|
}
|
|
@ -10,7 +10,7 @@ plugins {
|
||||||
|
|
||||||
|
|
||||||
kotlin {
|
kotlin {
|
||||||
jvmToolchain(8)
|
jvmToolchain(11)
|
||||||
|
|
||||||
jvm {
|
jvm {
|
||||||
withJava()
|
withJava()
|
||||||
|
@ -77,7 +77,7 @@ kotlin {
|
||||||
dependencies {
|
dependencies {
|
||||||
api(project(":BankingClient"))
|
api(project(":BankingClient"))
|
||||||
|
|
||||||
api("net.codinux.banking:fints4k:1.0.0-Alpha-13-SNAPSHOT")
|
implementation("net.codinux.banking:fints4k:1.0.0-Alpha-13-SNAPSHOT")
|
||||||
|
|
||||||
api("org.jetbrains.kotlinx:kotlinx-datetime:$kotlinxDateTimeVersion")
|
api("org.jetbrains.kotlinx:kotlinx-datetime:$kotlinxDateTimeVersion")
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,9 @@ open class FinTs4kBankingClient(
|
||||||
|
|
||||||
constructor(callback: BankingClientCallback) : this(FinTsClientConfiguration(), callback)
|
constructor(callback: BankingClientCallback) : this(FinTsClientConfiguration(), callback)
|
||||||
|
|
||||||
|
constructor(options: FinTsClientOptions, callback: BankingClientCallback)
|
||||||
|
: this(FinTsClientConfiguration(net.codinux.banking.fints.config.FinTsClientOptions(options.collectMessageLog, false, options.removeSensitiveDataFromMessageLog, options.closeDialogs, options.version, options.productName)), callback)
|
||||||
|
|
||||||
|
|
||||||
protected open val mapper = FinTs4kMapper()
|
protected open val mapper = FinTs4kMapper()
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
package net.codinux.banking.client.fints4k
|
package net.codinux.banking.client.fints4k
|
||||||
|
|
||||||
import kotlinx.datetime.Clock
|
import kotlinx.datetime.Clock
|
||||||
|
import kotlinx.datetime.Instant
|
||||||
import kotlinx.datetime.TimeZone
|
import kotlinx.datetime.TimeZone
|
||||||
import kotlinx.datetime.toLocalDateTime
|
import kotlinx.datetime.toLocalDateTime
|
||||||
import net.codinux.banking.client.model.*
|
import net.codinux.banking.client.model.*
|
||||||
import net.codinux.banking.client.model.AccountTransaction
|
import net.codinux.banking.client.model.AccountTransaction
|
||||||
import net.codinux.banking.client.model.Amount
|
import net.codinux.banking.client.model.Amount
|
||||||
|
import net.codinux.banking.client.model.extensions.EuropeBerlin
|
||||||
import net.codinux.banking.client.model.tan.*
|
import net.codinux.banking.client.model.tan.*
|
||||||
import net.codinux.banking.client.model.options.GetAccountDataOptions
|
import net.codinux.banking.client.model.options.GetAccountDataOptions
|
||||||
import net.codinux.banking.client.model.request.GetAccountDataRequest
|
import net.codinux.banking.client.model.request.GetAccountDataRequest
|
||||||
|
@ -16,7 +18,6 @@ import net.codinux.banking.client.model.tan.TanChallenge
|
||||||
import net.codinux.banking.client.model.tan.TanImage
|
import net.codinux.banking.client.model.tan.TanImage
|
||||||
import net.codinux.banking.client.model.tan.TanMethod
|
import net.codinux.banking.client.model.tan.TanMethod
|
||||||
import net.codinux.banking.client.model.tan.TanMethodType
|
import net.codinux.banking.client.model.tan.TanMethodType
|
||||||
import net.codinux.banking.fints.extensions.EuropeBerlin
|
|
||||||
import net.dankito.banking.client.model.BankAccountIdentifierImpl
|
import net.dankito.banking.client.model.BankAccountIdentifierImpl
|
||||||
import net.dankito.banking.client.model.parameter.GetAccountDataParameter
|
import net.dankito.banking.client.model.parameter.GetAccountDataParameter
|
||||||
import net.dankito.banking.client.model.parameter.RetrieveTransactions
|
import net.dankito.banking.client.model.parameter.RetrieveTransactions
|
||||||
|
@ -24,6 +25,7 @@ import net.dankito.banking.client.model.response.ErrorCode
|
||||||
import net.codinux.banking.fints.mapper.FinTsModelMapper
|
import net.codinux.banking.fints.mapper.FinTsModelMapper
|
||||||
import net.codinux.banking.fints.messages.datenelemente.implementierte.signatur.Sicherheitsfunktion
|
import net.codinux.banking.fints.messages.datenelemente.implementierte.signatur.Sicherheitsfunktion
|
||||||
import net.codinux.banking.fints.model.*
|
import net.codinux.banking.fints.model.*
|
||||||
|
import net.codinux.banking.fints.transactions.swift.model.Holding
|
||||||
import net.codinux.banking.fints.messages.datenelemente.implementierte.tan.TanMedium
|
import net.codinux.banking.fints.messages.datenelemente.implementierte.tan.TanMedium
|
||||||
import net.codinux.banking.fints.messages.datenelemente.implementierte.tan.MobilePhoneTanMedium
|
import net.codinux.banking.fints.messages.datenelemente.implementierte.tan.MobilePhoneTanMedium
|
||||||
import net.codinux.banking.fints.messages.datenelemente.implementierte.tan.TanGeneratorTanMedium
|
import net.codinux.banking.fints.messages.datenelemente.implementierte.tan.TanGeneratorTanMedium
|
||||||
|
@ -60,7 +62,7 @@ open class FinTs4kMapper {
|
||||||
val defaults = GetAccountDataOptions()
|
val defaults = GetAccountDataOptions()
|
||||||
|
|
||||||
val accountIdentifier = BankAccountIdentifierImpl(account.identifier, account.subAccountNumber, account.iban)
|
val accountIdentifier = BankAccountIdentifierImpl(account.identifier, account.subAccountNumber, account.iban)
|
||||||
val from = account.lastTransactionsRetrievalTime?.toLocalDateTime(TimeZone.EuropeBerlin)?.date // TODO: in case lastTransactionsUpdateTime is not set, this would retrieve all transactions (and require a TAN im most cases)
|
val from = account.lastAccountUpdateTime?.toLocalDateTime(TimeZone.EuropeBerlin)?.date // TODO: in case lastTransactionsUpdateTime is not set, this would retrieve all transactions (and require a TAN im most cases)
|
||||||
val retrieveTransactions = if (from != null) RetrieveTransactions.AccordingToRetrieveFromAndTo else RetrieveTransactions.valueOf(defaults.retrieveTransactions.name)
|
val retrieveTransactions = if (from != null) RetrieveTransactions.AccordingToRetrieveFromAndTo else RetrieveTransactions.valueOf(defaults.retrieveTransactions.name)
|
||||||
// val preferredTanMethods = listOf(mapTanMethodType(user.selectedTanMethod.type)) // TODO: currently we aren't saving TanMethods in database, re-enable as soon as TanMethods get saved
|
// val preferredTanMethods = listOf(mapTanMethodType(user.selectedTanMethod.type)) // TODO: currently we aren't saving TanMethods in database, re-enable as soon as TanMethods get saved
|
||||||
val preferredTanMethods = defaults.preferredTanMethods?.map { mapTanMethodType(it) }
|
val preferredTanMethods = defaults.preferredTanMethods?.map { mapTanMethodType(it) }
|
||||||
|
@ -103,16 +105,17 @@ open class FinTs4kMapper {
|
||||||
val finTsBankAccount = user?.accounts?.firstOrNull { it.identifier == account.identifier && it.subAccountNumber == account.subAccountNumber }
|
val finTsBankAccount = user?.accounts?.firstOrNull { it.identifier == account.identifier && it.subAccountNumber == account.subAccountNumber }
|
||||||
|
|
||||||
if (getAccountDataResponse.successful && user != null && finTsBankAccount != null) {
|
if (getAccountDataResponse.successful && user != null && finTsBankAccount != null) {
|
||||||
if (finTsBankAccount.lastTransactionsRetrievalTime != null) {
|
if (finTsBankAccount.lastAccountUpdateTime != null) {
|
||||||
account.lastTransactionsRetrievalTime = finTsBankAccount.lastTransactionsRetrievalTime
|
account.lastAccountUpdateTime = finTsBankAccount.lastAccountUpdateTime
|
||||||
}
|
}
|
||||||
if (account.retrievedTransactionsFrom == null || (finTsBankAccount.retrievedTransactionsFrom != null
|
if (account.retrievedTransactionsFrom == null || (finTsBankAccount.retrievedTransactionsFrom != null
|
||||||
&& account.retrievedTransactionsFrom!! < finTsBankAccount.retrievedTransactionsFrom!!)) {
|
&& account.retrievedTransactionsFrom!! < finTsBankAccount.retrievedTransactionsFrom!!)) {
|
||||||
account.retrievedTransactionsFrom = finTsBankAccount.retrievedTransactionsFrom
|
account.retrievedTransactionsFrom = finTsBankAccount.retrievedTransactionsFrom
|
||||||
}
|
}
|
||||||
|
|
||||||
Response.success(GetTransactionsResponse(account, mapAmount(finTsBankAccount.balance), mapBookedTransactions(finTsBankAccount), emptyList(),
|
Response.success(GetTransactionsResponse(account, mapMoney(finTsBankAccount.balance), mapBookedTransactions(finTsBankAccount), emptyList(),
|
||||||
finTsBankAccount.lastTransactionsRetrievalTime ?: Clock.System.now(), param.retrieveTransactionsFrom, param.retrieveTransactionsTo))
|
mapHoldings(finTsBankAccount.statementOfHoldings, finTsBankAccount.currency, finTsBankAccount.lastAccountUpdateTime),
|
||||||
|
finTsBankAccount.lastAccountUpdateTime ?: Clock.System.now(), param.retrieveTransactionsFrom, param.retrieveTransactionsTo))
|
||||||
} else {
|
} else {
|
||||||
mapError(getAccountDataResponse)
|
mapError(getAccountDataResponse)
|
||||||
}
|
}
|
||||||
|
@ -156,10 +159,11 @@ open class FinTs4kMapper {
|
||||||
account.identifier, account.subAccountNumber, account.iban, account.productName, account.accountHolderName,
|
account.identifier, account.subAccountNumber, account.iban, account.productName, account.accountHolderName,
|
||||||
mapAccountType(account.type), account.currency, account.accountLimit,
|
mapAccountType(account.type), account.currency, account.accountLimit,
|
||||||
account.isAccountTypeSupportedByApplication, mapFeatures(account),
|
account.isAccountTypeSupportedByApplication, mapFeatures(account),
|
||||||
mapAmount(account.balance),
|
mapMoney(account.balance),
|
||||||
account.serverTransactionsRetentionDays,
|
account.serverTransactionsRetentionDays,
|
||||||
account.lastTransactionsRetrievalTime, account.retrievedTransactionsFrom,
|
account.lastAccountUpdateTime, account.retrievedTransactionsFrom,
|
||||||
bookedTransactions = mapBookedTransactions(account).toMutableList()
|
bookedTransactions = mapBookedTransactions(account).toMutableList(),
|
||||||
|
holdings = mapHoldings(account.statementOfHoldings, account.currency, account.lastAccountUpdateTime)
|
||||||
)
|
)
|
||||||
|
|
||||||
protected open fun mapAccountType(type: net.dankito.banking.client.model.BankAccountType): BankAccountType =
|
protected open fun mapAccountType(type: net.dankito.banking.client.model.BankAccountType): BankAccountType =
|
||||||
|
@ -185,12 +189,12 @@ open class FinTs4kMapper {
|
||||||
account.bookedTransactions.map { mapTransaction(it) }
|
account.bookedTransactions.map { mapTransaction(it) }
|
||||||
|
|
||||||
protected open fun mapTransaction(transaction: net.dankito.banking.client.model.AccountTransaction): AccountTransaction = AccountTransaction(
|
protected open fun mapTransaction(transaction: net.dankito.banking.client.model.AccountTransaction): AccountTransaction = AccountTransaction(
|
||||||
mapAmount(transaction.amount), transaction.amount.currency.code, transaction.reference,
|
mapMoney(transaction.amount), transaction.amount.currency.code, transaction.reference,
|
||||||
transaction.bookingDate, transaction.valueDate,
|
transaction.bookingDate, transaction.valueDate,
|
||||||
transaction.otherPartyName, transaction.otherPartyBankId, transaction.otherPartyAccountId,
|
transaction.otherPartyName, transaction.otherPartyBankId, transaction.otherPartyAccountId,
|
||||||
|
|
||||||
transaction.postingText,
|
transaction.postingText,
|
||||||
mapNullableAmount(transaction.openingBalance), mapNullableAmount(transaction.closingBalance),
|
mapNullableMoney(transaction.openingBalance), mapNullableMoney(transaction.closingBalance),
|
||||||
|
|
||||||
transaction.statementNumber, transaction.sheetNumber,
|
transaction.statementNumber, transaction.sheetNumber,
|
||||||
|
|
||||||
|
@ -209,9 +213,77 @@ open class FinTs4kMapper {
|
||||||
transaction.isReversal
|
transaction.isReversal
|
||||||
)
|
)
|
||||||
|
|
||||||
protected open fun mapNullableAmount(amount: Money?) = amount?.let { mapAmount(it) }
|
protected open fun mapHoldings(statements: List<net.codinux.banking.fints.transactions.swift.model.StatementOfHoldings>, accountCurrency: String, lastAccountUpdateTime: Instant? = null) =
|
||||||
|
statements.flatMap { mapHoldings(it, accountCurrency, lastAccountUpdateTime) }
|
||||||
|
|
||||||
protected open fun mapAmount(amount: Money) = Amount.fromString(amount.amount.string.replace(',', '.'))
|
protected open fun mapHoldings(statement: net.codinux.banking.fints.transactions.swift.model.StatementOfHoldings, accountCurrency: String, lastAccountUpdateTime: Instant? = null): List<net.codinux.banking.client.model.securitiesaccount.Holding> {
|
||||||
|
|
||||||
|
val totalBalance = mapNullableAmount(statement.totalBalance)
|
||||||
|
val currency = statement.currency ?: accountCurrency
|
||||||
|
val statementDate: Instant? = /* statement.statementDate ?: statement.preparationDate ?: */ lastAccountUpdateTime // TODO
|
||||||
|
|
||||||
|
return statement.holdings.map { mapHolding(it, currency, statementDate, if (statement.holdings.size == 1) totalBalance else null) }
|
||||||
|
}
|
||||||
|
|
||||||
|
protected open fun mapHolding(holding: Holding, accountCurrency: String, statementDate: Instant?, totalBalance: Amount? = null) = net.codinux.banking.client.model.securitiesaccount.Holding(
|
||||||
|
holding.name, holding.isin, holding.wkn,
|
||||||
|
|
||||||
|
holding.quantity, holding.currency ?: accountCurrency,
|
||||||
|
|
||||||
|
getTotalBalance(holding), mapNullableAmount(holding.marketValue),
|
||||||
|
calculatePerformance(holding),
|
||||||
|
getTotalCostPrice(holding), mapNullableAmount(holding.averageCostPrice),
|
||||||
|
|
||||||
|
holding.pricingTime ?: statementDate, holding.buyingDate
|
||||||
|
)
|
||||||
|
|
||||||
|
// visible for testing
|
||||||
|
internal fun getTotalBalance(holding: Holding): Amount? {
|
||||||
|
return if (holding.totalBalance != null) {
|
||||||
|
mapNullableAmount(holding.totalBalance)
|
||||||
|
} else if (holding.quantity != null && holding.marketValue != null) {
|
||||||
|
Amount((holding.quantity!! * holding.marketValue.toString().toDouble()).toString())
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// visible for testing
|
||||||
|
internal fun getTotalCostPrice(holding: Holding): Amount? {
|
||||||
|
return if (holding.totalCostPrice != null) {
|
||||||
|
mapNullableAmount(holding.totalCostPrice)
|
||||||
|
} else if (holding.quantity != null && holding.averageCostPrice != null) {
|
||||||
|
Amount((holding.quantity!! * holding.averageCostPrice.toString().toDouble()).toString())
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// visible for testing
|
||||||
|
internal fun calculatePerformance(holding: Holding): Float? {
|
||||||
|
val totalBalance = getTotalBalance(holding)
|
||||||
|
val totalCostPrice = getTotalCostPrice(holding)
|
||||||
|
|
||||||
|
if (totalBalance != null && totalCostPrice != null) {
|
||||||
|
return ((totalBalance - totalCostPrice) / totalCostPrice).toFloat() * 100
|
||||||
|
}
|
||||||
|
|
||||||
|
val marketValue = mapNullableAmount(holding.marketValue)
|
||||||
|
val costPrice = mapNullableAmount(holding.averageCostPrice)
|
||||||
|
if (marketValue != null && costPrice != null) {
|
||||||
|
return ((marketValue - costPrice) / costPrice).toFloat() * 100
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
protected open fun mapNullableMoney(amount: Money?) = amount?.let { mapMoney(it) }
|
||||||
|
|
||||||
|
protected open fun mapMoney(amount: Money) = Amount(amount.amount.string.replace(',', '.'))
|
||||||
|
|
||||||
|
protected open fun mapNullableAmount(amount: net.codinux.banking.fints.model.Amount?) = amount?.let { mapAmount(it) }
|
||||||
|
|
||||||
|
protected open fun mapAmount(amount: net.codinux.banking.fints.model.Amount) = Amount(amount.string.replace(',', '.'))
|
||||||
|
|
||||||
|
|
||||||
open fun mapTanChallenge(challenge: net.codinux.banking.fints.model.TanChallenge): TanChallenge {
|
open fun mapTanChallenge(challenge: net.codinux.banking.fints.model.TanChallenge): TanChallenge {
|
||||||
|
@ -318,7 +390,7 @@ open class FinTs4kMapper {
|
||||||
mapError(response)
|
mapError(response)
|
||||||
}
|
}
|
||||||
|
|
||||||
open fun mapToMoney(amount: Amount, currency: String): Money = Money(amount.amount, currency)
|
open fun mapToMoney(amount: Amount, currency: String): Money = Money(amount.toString(), currency)
|
||||||
|
|
||||||
|
|
||||||
protected open fun <T> mapError(response: net.dankito.banking.client.model.response.FinTsClientResponse): Response<T> {
|
protected open fun <T> mapError(response: net.dankito.banking.client.model.response.FinTsClientResponse): Response<T> {
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
package net.codinux.banking.client.fints4k
|
||||||
|
|
||||||
|
data class FinTsClientOptions(
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If FinTS messages sent to and received from bank servers and errors should be collected.
|
||||||
|
*
|
||||||
|
* Set to false by default.
|
||||||
|
*/
|
||||||
|
val collectMessageLog: Boolean = false,
|
||||||
|
|
||||||
|
// /**
|
||||||
|
// * If set to true then [net.codinux.banking.fints.callback.FinTsClientCallback.messageLogAdded] get fired when a
|
||||||
|
// * FinTS message get sent to bank server, a FinTS message is received from bank server or an error occurred.
|
||||||
|
// *
|
||||||
|
// * Defaults to false.
|
||||||
|
// */
|
||||||
|
// val fireCallbackOnMessageLogs: Boolean = false,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If sensitive data like user name, password, login name should be removed from FinTS messages before being logged.
|
||||||
|
*
|
||||||
|
* Defaults to true.
|
||||||
|
*/
|
||||||
|
val removeSensitiveDataFromMessageLog: Boolean = true,
|
||||||
|
|
||||||
|
val closeDialogs: Boolean = true,
|
||||||
|
|
||||||
|
val version: String = "1.0.0", // TODO: get version dynamically
|
||||||
|
val productName: String = "15E53C26816138699C7B6A3E8"
|
||||||
|
)
|
|
@ -0,0 +1,53 @@
|
||||||
|
package net.codinux.banking.client.fints4k
|
||||||
|
|
||||||
|
import net.codinux.banking.client.model.Amount
|
||||||
|
import kotlin.test.Test
|
||||||
|
import net.codinux.banking.fints.transactions.swift.model.Holding
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
|
class FinTs4kMapperTest {
|
||||||
|
|
||||||
|
private val underTest = FinTs4kMapper()
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun getTotalBalance_TotalBalanceIsNull_CalculateByQuantityAndMarketValue() {
|
||||||
|
val holding = Holding("", null, null, null, 4, null, null, null, fints4kAmount("13.33"))
|
||||||
|
|
||||||
|
val result = underTest.getTotalBalance(holding)
|
||||||
|
|
||||||
|
assertEquals(Amount("53.32"), result)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun getTotalCostPrice_TotalCostPriceIsNull_CalculateByQuantityAndAverageCostPrice() {
|
||||||
|
val holding = Holding("", null, null, null, 47, fints4kAmount("16.828"), null)
|
||||||
|
|
||||||
|
val result = underTest.getTotalCostPrice(holding)
|
||||||
|
|
||||||
|
assertEquals(Amount("790.9159999999999"), result)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun calculatePerformance_ByTotalBalanceAndTotalCostPrice() {
|
||||||
|
val holding = Holding("", null, null, null, null, null, fints4kAmount("20217.12"), null, null, null, fints4kAmount("19027.04"))
|
||||||
|
|
||||||
|
val result = underTest.calculatePerformance(holding)
|
||||||
|
|
||||||
|
assertEquals(6.2546773f, result!!, 0.000001f) // for JS the result has too many decimal places, so add a tolerance
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun calculatePerformance_ByMarketValueAndAverageCostPrice() {
|
||||||
|
val holding = Holding("", null, null, null, null, fints4kAmount("16.828"), null, null, fints4kAmount("16.75"), null, null)
|
||||||
|
|
||||||
|
val result = underTest.calculatePerformance(holding)
|
||||||
|
|
||||||
|
assertEquals(-0.4635132f, result!!, 0.0000001f) // for JS the result has too many decimal places, so add a tolerance
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun fints4kAmount(amount: String) =
|
||||||
|
net.codinux.banking.fints.model.Amount(amount)
|
||||||
|
|
||||||
|
}
|
|
@ -115,12 +115,12 @@ response.error?.let{ error ->
|
||||||
|
|
||||||
### Update Account Transactions
|
### Update Account Transactions
|
||||||
|
|
||||||
The data model saves when it retrieved account transactions the last time (in `BankAccount.lastTransactionsRetrievalTime`).
|
The data model saves when it retrieved account transactions the last time (in `BankAccount.lastAccountUpdateTime`).
|
||||||
So you only need to call `FinTs4kBankingClient.updateAccountTransactions()` to retrieve all transactions starting from
|
So you only need to call `FinTs4kBankingClient.updateAccountTransactions()` to retrieve all transactions starting from
|
||||||
`BankAccount.lastTransactionsRetrievalTime`.
|
`BankAccount.lastAccountUpdateTime`.
|
||||||
|
|
||||||
But as we can only specify from which day on account transactions should be retrieved, response may contain some transactions
|
But as we can only specify from which day on account transactions should be retrieved, response may contain some transactions
|
||||||
from the day of `lastTransactionsRetrievalTime` that we already have locally. To filter out these you can use
|
from the day of `lastAccountUpdateTime` that we already have locally. To filter out these you can use
|
||||||
`BankingModelService().findNewTransactions(retrieveTransactions, existingTransactions)`:
|
`BankingModelService().findNewTransactions(retrieveTransactions, existingTransactions)`:
|
||||||
|
|
||||||
```kotlin
|
```kotlin
|
||||||
|
|
|
@ -15,7 +15,7 @@ import net.codinux.banking.client.model.response.Response
|
||||||
import net.codinux.banking.client.model.tan.EnterTanResult
|
import net.codinux.banking.client.model.tan.EnterTanResult
|
||||||
import net.codinux.banking.client.service.BankingModelService
|
import net.codinux.banking.client.service.BankingModelService
|
||||||
import net.codinux.banking.client.updateAccountTransactions
|
import net.codinux.banking.client.updateAccountTransactions
|
||||||
import net.codinux.banking.fints.extensions.minusDays
|
import net.codinux.banking.client.model.extensions.minusDays
|
||||||
|
|
||||||
fun main() {
|
fun main() {
|
||||||
val showUsage = ShowUsage()
|
val showUsage = ShowUsage()
|
||||||
|
|
|
@ -6,3 +6,6 @@ kotlinVersion=2.0.10
|
||||||
kotlinxDateTimeVersion=0.5.0
|
kotlinxDateTimeVersion=0.5.0
|
||||||
jsJodaTimeZoneVersion=2.3.0
|
jsJodaTimeZoneVersion=2.3.0
|
||||||
coroutinesVersion=1.8.1
|
coroutinesVersion=1.8.1
|
||||||
|
|
||||||
|
# 0.3.10 uses Kotlin 2.0.0
|
||||||
|
ionspinBigNumVersion=0.3.9
|
||||||
|
|
|
@ -556,6 +556,11 @@ batch@0.6.1:
|
||||||
resolved "https://registry.yarnpkg.com/batch/-/batch-0.6.1.tgz#dc34314f4e679318093fc760272525f94bf25c16"
|
resolved "https://registry.yarnpkg.com/batch/-/batch-0.6.1.tgz#dc34314f4e679318093fc760272525f94bf25c16"
|
||||||
integrity sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==
|
integrity sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==
|
||||||
|
|
||||||
|
big.js@6.0.3:
|
||||||
|
version "6.0.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/big.js/-/big.js-6.0.3.tgz#8b4d99ac7023668e0e465d3f78c23b8ac29ad381"
|
||||||
|
integrity sha512-n6yn1FyVL1EW2DBAr4jlU/kObhRzmr+NNRESl65VIOT8WBJj/Kezpx2zFdhJUqYI6qrtTW7moCStYL5VxeVdPA==
|
||||||
|
|
||||||
binary-extensions@^2.0.0:
|
binary-extensions@^2.0.0:
|
||||||
version "2.3.0"
|
version "2.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.3.0.tgz#f6e14a97858d327252200242d4ccfe522c445522"
|
resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.3.0.tgz#f6e14a97858d327252200242d4ccfe522c445522"
|
||||||
|
|
Loading…
Reference in New Issue