From 094d66914890bec211f9183db8d20cc85563ec22 Mon Sep 17 00:00:00 2001 From: dankito Date: Sun, 20 Sep 2020 19:02:01 +0200 Subject: [PATCH] Implemented evaluating count days for which bank stores transactions -> don't show fetch all transactions overlay if bank server doesn't store additional transactions --- .../utils/multiplatform/CommonExtensions.kt | 9 +++++ .../net/dankito/utils/multiplatform/Date.kt | 10 ++++++ .../dankito/utils/multiplatform/BigDecimal.kt | 7 +--- .../net/dankito/utils/multiplatform/Date.kt | 16 ++++++++- .../dankito/utils/multiplatform/Extensions.kt | 10 ++++++ .../net/dankito/utils/multiplatform/Date.kt | 24 ++++++++++++++ .../net/dankito/banking/fints/FinTsClient.kt | 10 ++++++ .../dankito/banking/fints/model/BankData.kt | 2 ++ .../dankito/banking/persistence/model/Bank.kt | 4 +++ .../persistence/model/CustomerEntity.kt | 1 + .../net/dankito/banking/ui/model/Customer.kt | 3 ++ .../net/dankito/banking/ui/model/ICustomer.kt | 3 ++ .../banking/ui/presenter/BankingPresenter.kt | 33 ++++++++++++++----- .../BankingiOSApp.xcdatamodel/contents | 5 +-- .../BankingiOSApp/persistence/Mapper.swift | 19 +++++++++++ .../banking/mapper/fints4kModelMapper.kt | 1 + 16 files changed, 139 insertions(+), 18 deletions(-) diff --git a/common/src/commonMain/kotlin/net/dankito/utils/multiplatform/CommonExtensions.kt b/common/src/commonMain/kotlin/net/dankito/utils/multiplatform/CommonExtensions.kt index 6453e456..4a6f98f1 100644 --- a/common/src/commonMain/kotlin/net/dankito/utils/multiplatform/CommonExtensions.kt +++ b/common/src/commonMain/kotlin/net/dankito/utils/multiplatform/CommonExtensions.kt @@ -8,6 +8,15 @@ val Char.isUpperCase: Boolean get() = isLowerCase == false +fun Date.isBefore(other: Date): Boolean { + return compareTo(other) < 0 +} + +fun Date.isBeforeOrEquals(other: Date): Boolean { + return compareTo(other) <= 0 +} + + fun Throwable.getInnerExceptionMessage(maxDepth: Int = 3): String { return this.getInnerException(maxDepth).message ?: "" } diff --git a/common/src/commonMain/kotlin/net/dankito/utils/multiplatform/Date.kt b/common/src/commonMain/kotlin/net/dankito/utils/multiplatform/Date.kt index 0b318d79..9b1db026 100644 --- a/common/src/commonMain/kotlin/net/dankito/utils/multiplatform/Date.kt +++ b/common/src/commonMain/kotlin/net/dankito/utils/multiplatform/Date.kt @@ -12,6 +12,13 @@ fun Date.format(pattern: String): String { expect class Date(millisSinceEpoch: Long) { + companion object { + + val today: Date + + } + + constructor() constructor(year: Int, month: Int, day: Int) @@ -29,4 +36,7 @@ expect class Date(millisSinceEpoch: Long) { fun day(): Int + + fun compareTo(other: Date): Int + } \ No newline at end of file diff --git a/common/src/iosMain/kotlin/net/dankito/utils/multiplatform/BigDecimal.kt b/common/src/iosMain/kotlin/net/dankito/utils/multiplatform/BigDecimal.kt index 09cb00c6..af932728 100644 --- a/common/src/iosMain/kotlin/net/dankito/utils/multiplatform/BigDecimal.kt +++ b/common/src/iosMain/kotlin/net/dankito/utils/multiplatform/BigDecimal.kt @@ -44,12 +44,7 @@ actual class BigDecimal(val decimal: NSDecimalNumber) : Comparable { override fun compareTo(other: BigDecimal): Int { - return when (decimal.compare(other.decimal)) { - NSOrderedSame -> 0 - NSOrderedAscending -> -1 - NSOrderedDescending -> 1 - else -> 0 - } + return decimal.compare(other.decimal).toCompareToResult() } override fun equals(other: Any?): Boolean { diff --git a/common/src/iosMain/kotlin/net/dankito/utils/multiplatform/Date.kt b/common/src/iosMain/kotlin/net/dankito/utils/multiplatform/Date.kt index 25ba7b9a..73a332c6 100644 --- a/common/src/iosMain/kotlin/net/dankito/utils/multiplatform/Date.kt +++ b/common/src/iosMain/kotlin/net/dankito/utils/multiplatform/Date.kt @@ -18,7 +18,7 @@ fun NSDate.toDate(): Date { actual class Date(val date: NSDate) { // cannot subclass NSDate as it's a class cluster - companion object { + actual companion object { val DiffBetweenEpochTimeAndReferenceDate = (NSDate.timeIntervalSinceReferenceDate - NSTimeIntervalSince1970).toMillis() @@ -32,6 +32,14 @@ actual class Date(val date: NSDate) { // cannot subclass NSDate as it's a class return NSCalendar.currentCalendar.dateFromComponents(dateComponents) !! } + + actual val today: Date + get() { + val now = Date() + + return Date(from(now.year(), now.monthInt(), now.day())) + } + } @@ -68,6 +76,12 @@ actual class Date(val date: NSDate) { // cannot subclass NSDate as it's a class } + + actual fun compareTo(other: Date): Int { + return date.compare(other.date).toCompareToResult() + } + + override fun equals(other: Any?): Boolean { if (this === other) return true if (other !is Date) return false diff --git a/common/src/iosMain/kotlin/net/dankito/utils/multiplatform/Extensions.kt b/common/src/iosMain/kotlin/net/dankito/utils/multiplatform/Extensions.kt index a514c5a8..4943184b 100644 --- a/common/src/iosMain/kotlin/net/dankito/utils/multiplatform/Extensions.kt +++ b/common/src/iosMain/kotlin/net/dankito/utils/multiplatform/Extensions.kt @@ -28,6 +28,16 @@ fun NSDictionary.getStringOrEmpty(key: String): String { } +fun NSComparisonResult.toCompareToResult(): Int { + return when (this) { + NSOrderedSame -> 0 + NSOrderedAscending -> -1 + NSOrderedDescending -> 1 + else -> 0 + } +} + + fun String.toNSData(): NSData { return this.encodeToByteArray().toNSData() } diff --git a/common/src/jvmMain/kotlin/net/dankito/utils/multiplatform/Date.kt b/common/src/jvmMain/kotlin/net/dankito/utils/multiplatform/Date.kt index 4105a958..bfa121f3 100644 --- a/common/src/jvmMain/kotlin/net/dankito/utils/multiplatform/Date.kt +++ b/common/src/jvmMain/kotlin/net/dankito/utils/multiplatform/Date.kt @@ -3,11 +3,30 @@ package net.dankito.utils.multiplatform import com.fasterxml.jackson.databind.annotation.JsonDeserialize import net.dankito.utils.multiplatform.serialization.DateDeserializer import java.text.DateFormat +import java.util.* +import java.util.Date @JsonDeserialize(using = DateDeserializer::class) actual class Date actual constructor(millisSinceEpoch: Long) : java.util.Date(millisSinceEpoch) { + actual companion object { + + actual val today: net.dankito.utils.multiplatform.Date + get() { + val today = Calendar.getInstance() + + today.set(Calendar.HOUR_OF_DAY, 0) + today.set(Calendar.MINUTE, 0) + today.set(Calendar.SECOND, 0) + today.set(Calendar.MILLISECOND, 0) + + return net.dankito.utils.multiplatform.Date(today.time.time) + } + + } + + actual constructor() : this(System.currentTimeMillis()) actual constructor(year: Int, month: Int, day: Int) : this(java.util.Date(year - 1900, month - 1, day).time) @@ -41,4 +60,9 @@ actual class Date actual constructor(millisSinceEpoch: Long) : java.util.Date(mi return dateStringString.toInt() } + + actual fun compareTo(other: net.dankito.utils.multiplatform.Date): Int { + return super.compareTo(other) + } + } \ No newline at end of file diff --git a/fints4k/src/commonMain/kotlin/net/dankito/banking/fints/FinTsClient.kt b/fints4k/src/commonMain/kotlin/net/dankito/banking/fints/FinTsClient.kt index 898ab7a5..c222151d 100644 --- a/fints4k/src/commonMain/kotlin/net/dankito/banking/fints/FinTsClient.kt +++ b/fints4k/src/commonMain/kotlin/net/dankito/banking/fints/FinTsClient.kt @@ -997,6 +997,16 @@ open class FinTsClient( } } + response.receivedSegments.filterIsInstance().firstOrNull()?.let { retrieveTransactionsParameters -> + bank.countDaysForWhichTransactionsAreKept = retrieveTransactionsParameters.countDaysForWhichTransactionsAreKept + } + + response.getFirstSegmentById(InstituteSegmentId.SepaAccountInfo)?.let { sepaAccountInfo -> + sepaAccountInfo.account.bic?.let { + bank.bic = it // TODO: really set BIC on bank then? + } + } + response.getFirstSegmentById(InstituteSegmentId.ChangeTanMediaParameters)?.let { parameters -> bank.changeTanMediumParameters = parameters } diff --git a/fints4k/src/commonMain/kotlin/net/dankito/banking/fints/model/BankData.kt b/fints4k/src/commonMain/kotlin/net/dankito/banking/fints/model/BankData.kt index ecef08dd..d56c0278 100644 --- a/fints4k/src/commonMain/kotlin/net/dankito/banking/fints/model/BankData.kt +++ b/fints4k/src/commonMain/kotlin/net/dankito/banking/fints/model/BankData.kt @@ -42,6 +42,8 @@ open class BankData( */ open var countMaxJobsPerMessage: Int = 0, + open var countDaysForWhichTransactionsAreKept: Int? = null, + open var supportedHbciVersions: List = listOf(), open var supportedJobs: List = listOf() ) { diff --git a/persistence/database/RoomBankingPersistence/src/main/java/net/dankito/banking/persistence/model/Bank.kt b/persistence/database/RoomBankingPersistence/src/main/java/net/dankito/banking/persistence/model/Bank.kt index 4aabb281..6bce7740 100644 --- a/persistence/database/RoomBankingPersistence/src/main/java/net/dankito/banking/persistence/model/Bank.kt +++ b/persistence/database/RoomBankingPersistence/src/main/java/net/dankito/banking/persistence/model/Bank.kt @@ -33,6 +33,10 @@ open class Bank( @Ignore override var tanMedia: List = listOf(), + + override var countDaysForWhichTransactionsAreKept: Int? = null, + + @PrimaryKey(autoGenerate = true) open var id: Long = BaseDao.IdNotSet, diff --git a/persistence/json/BankingPersistenceJson/src/main/kotlin/net/dankito/banking/persistence/model/CustomerEntity.kt b/persistence/json/BankingPersistenceJson/src/main/kotlin/net/dankito/banking/persistence/model/CustomerEntity.kt index 2bda1e34..06fad9f9 100644 --- a/persistence/json/BankingPersistenceJson/src/main/kotlin/net/dankito/banking/persistence/model/CustomerEntity.kt +++ b/persistence/json/BankingPersistenceJson/src/main/kotlin/net/dankito/banking/persistence/model/CustomerEntity.kt @@ -23,6 +23,7 @@ open class CustomerEntity( override var supportedTanProcedures: List = listOf(), override var selectedTanProcedure: TanProcedure? = null, override var tanMedia: List = listOf(), + override var countDaysForWhichTransactionsAreKept: Int? = null, override var technicalId: String = UUID.randomUUID().toString(), override var userSetDisplayName: String? = null, override var displayIndex: Int = 0 diff --git a/ui/BankingUiCommon/src/commonMain/kotlin/net/dankito/banking/ui/model/Customer.kt b/ui/BankingUiCommon/src/commonMain/kotlin/net/dankito/banking/ui/model/Customer.kt index a6441b9d..44f46698 100644 --- a/ui/BankingUiCommon/src/commonMain/kotlin/net/dankito/banking/ui/model/Customer.kt +++ b/ui/BankingUiCommon/src/commonMain/kotlin/net/dankito/banking/ui/model/Customer.kt @@ -37,6 +37,9 @@ open class Customer( override var tanMedia: List = listOf() + override var countDaysForWhichTransactionsAreKept: Int? = null + + override var userSetDisplayName: String? = null override var displayIndex: Int = 0 diff --git a/ui/BankingUiCommon/src/commonMain/kotlin/net/dankito/banking/ui/model/ICustomer.kt b/ui/BankingUiCommon/src/commonMain/kotlin/net/dankito/banking/ui/model/ICustomer.kt index 475d9c6f..76284623 100644 --- a/ui/BankingUiCommon/src/commonMain/kotlin/net/dankito/banking/ui/model/ICustomer.kt +++ b/ui/BankingUiCommon/src/commonMain/kotlin/net/dankito/banking/ui/model/ICustomer.kt @@ -54,6 +54,9 @@ interface ICustomer, TAccountTransac get() = tanMedia.sortedByDescending { it.status == TanMediumStatus.Used } + var countDaysForWhichTransactionsAreKept: Int? + + val stringRepresentation: String get() = "$bankName $customerId" diff --git a/ui/BankingUiCommon/src/commonMain/kotlin/net/dankito/banking/ui/presenter/BankingPresenter.kt b/ui/BankingUiCommon/src/commonMain/kotlin/net/dankito/banking/ui/presenter/BankingPresenter.kt index 883adc5d..20b2a415 100644 --- a/ui/BankingUiCommon/src/commonMain/kotlin/net/dankito/banking/ui/presenter/BankingPresenter.kt +++ b/ui/BankingUiCommon/src/commonMain/kotlin/net/dankito/banking/ui/presenter/BankingPresenter.kt @@ -51,7 +51,7 @@ open class BankingPresenter( val ChipTanTanProcedures = listOf(TanProcedureType.ChipTanManuell, TanProcedureType.ChipTanFlickercode, TanProcedureType.ChipTanUsb, TanProcedureType.ChipTanQrCode, TanProcedureType.ChipTanPhotoTanMatrixCode) - protected const val OneDayMillis = 24 * 60 * 60 * 1000 + protected const val OneDayMillis = 24 * 60 * 60 * 1000L protected val MessageLogEntryDateFormatter = DateFormatter("yyyy.MM.dd HH:mm:ss.SSS") @@ -335,10 +335,11 @@ open class BankingPresenter( protected open fun retrievedAccountTransactions(response: GetTransactionsResponse, startDate: Date, didFetchAllTransactions: Boolean) { if (response.successful) { response.retrievedData.forEach { retrievedData -> - retrievedData.account.lastRetrievedTransactionsTimestamp = startDate + val account = retrievedData.account + account.lastRetrievedTransactionsTimestamp = startDate - if (didFetchAllTransactions) { - retrievedData.account.haveAllTransactionsBeenFetched = true + if (didFetchAllTransactions || didFetchAllTransactionsStoredOnBankServer(account, retrievedData.bookedTransactions)) { + account.haveAllTransactionsBeenFetched = true } updateAccountTransactionsAndBalances(retrievedData) @@ -348,6 +349,20 @@ open class BankingPresenter( callRetrievedAccountTransactionsResponseListener(response) } + protected open fun didFetchAllTransactionsStoredOnBankServer(account: IBankAccount, fetchedTransactions: Collection): Boolean { + account.customer.countDaysForWhichTransactionsAreKept?.let { countDaysForWhichTransactionsAreKept -> + val firstAccountTransactions = if (account.bookedTransactions.isNotEmpty()) account.bookedTransactions else fetchedTransactions + + firstAccountTransactions.map { it.valueDate }.minBy { it.millisSinceEpoch }?.let { dateOfFirstRetrievedTransaction -> + val dayOfFirstTransactionStoredOnBankServer = Date(Date.today.millisSinceEpoch - countDaysForWhichTransactionsAreKept * OneDayMillis) + + return dateOfFirstRetrievedTransaction.isBeforeOrEquals(dayOfFirstTransactionStoredOnBankServer) + } + } + + return false + } + protected open fun receivedAccountsTransactionChunk(bankAccount: TypedBankAccount, accountTransactionsChunk: List) { if (accountTransactionsChunk.isNotEmpty()) { asyncRunner.runAsync { // don't block retrieving next chunk by blocked saving to db / json @@ -366,17 +381,17 @@ open class BankingPresenter( } } - protected open fun updateAccountTransactions(bankAccount: TypedBankAccount, bookedTransactions: Collection, unbookedTransactions: List? = null) { - val knownAccountTransactions = bankAccount.bookedTransactions.map { it.transactionIdentifier } + protected open fun updateAccountTransactions(account: TypedBankAccount, bookedTransactions: Collection, unbookedTransactions: List? = null) { + val knownAccountTransactions = account.bookedTransactions.map { it.transactionIdentifier } val newBookedTransactions = bookedTransactions.filterNot { knownAccountTransactions.contains(it.transactionIdentifier) } - bankAccount.addBookedTransactions(newBookedTransactions) + account.addBookedTransactions(newBookedTransactions) unbookedTransactions?.let { - bankAccount.addUnbookedTransactions(unbookedTransactions) + account.addUnbookedTransactions(unbookedTransactions) } - persistAccountTransactionsOffUiThread(bankAccount, newBookedTransactions) + persistAccountTransactionsOffUiThread(account, newBookedTransactions) } protected open fun updateBalance(bankAccount: TypedBankAccount, balance: BigDecimal) { diff --git a/ui/BankingiOSApp/BankingiOSApp/BankingiOSApp.xcdatamodeld/BankingiOSApp.xcdatamodel/contents b/ui/BankingiOSApp/BankingiOSApp/BankingiOSApp.xcdatamodeld/BankingiOSApp.xcdatamodel/contents index 2aae05c4..99befae4 100644 --- a/ui/BankingiOSApp/BankingiOSApp/BankingiOSApp.xcdatamodeld/BankingiOSApp.xcdatamodel/contents +++ b/ui/BankingiOSApp/BankingiOSApp/BankingiOSApp.xcdatamodeld/BankingiOSApp.xcdatamodel/contents @@ -1,5 +1,5 @@ - + @@ -62,6 +62,7 @@ + @@ -90,7 +91,7 @@ - + \ No newline at end of file diff --git a/ui/BankingiOSApp/BankingiOSApp/persistence/Mapper.swift b/ui/BankingiOSApp/BankingiOSApp/persistence/Mapper.swift index c5c8cfec..3e5c336c 100644 --- a/ui/BankingiOSApp/BankingiOSApp/persistence/Mapper.swift +++ b/ui/BankingiOSApp/BankingiOSApp/persistence/Mapper.swift @@ -7,6 +7,8 @@ class Mapper { func map(_ customer: PersistedCustomer) -> ICustomer { let mapped = Customer(bankCode: map(customer.bankCode), customerId: map(customer.customerId), password: map(customer.password), finTsServerAddress: map(customer.finTsServerAddress), bankName: map(customer.bankName), bic: map(customer.bic), customerName: map(customer.customerName), userId: map(customer.userId), iconUrl: customer.iconUrl, accounts: []) + + mapped.countDaysForWhichTransactionsAreKept = mapToInt(customer.countDaysForWhichTransactionsAreKept) mapped.userSetDisplayName = customer.userSetDisplayName mapped.displayIndex = customer.displayIndex @@ -35,6 +37,7 @@ class Mapper { mapped.customerName = customer.customerName mapped.userId = customer.userId mapped.iconUrl = customer.iconUrl + mapped.countDaysForWhichTransactionsAreKept = mapFromInt(customer.countDaysForWhichTransactionsAreKept) mapped.userSetDisplayName = customer.userSetDisplayName mapped.displayIndex = customer.displayIndex @@ -357,6 +360,22 @@ class Mapper { return nil } + func mapToInt(_ int: NSNumber?) -> KotlinInt? { + if let int = int { + return KotlinInt(int: int.int32Value) + } + + return nil + } + + func mapFromInt(_ int: KotlinInt?) -> NSNumber? { + if let int = map(int) { + return NSNumber(value: int) + } + + return nil + } + func map(_ string: String?) -> String { return string ?? "" } diff --git a/ui/fints4kBankingClient/src/commonMain/kotlin/net/dankito/banking/mapper/fints4kModelMapper.kt b/ui/fints4kBankingClient/src/commonMain/kotlin/net/dankito/banking/mapper/fints4kModelMapper.kt index 5939f098..ffaae806 100644 --- a/ui/fints4kBankingClient/src/commonMain/kotlin/net/dankito/banking/mapper/fints4kModelMapper.kt +++ b/ui/fints4kBankingClient/src/commonMain/kotlin/net/dankito/banking/mapper/fints4kModelMapper.kt @@ -76,6 +76,7 @@ open class fints4kModelMapper(protected val modelCreator: IModelCreator) { customer.bankName = bank.bankName customer.bic = bank.bic customer.customerName = bank.customerName + customer.countDaysForWhichTransactionsAreKept = bank.countDaysForWhichTransactionsAreKept customer.userId = bank.userId customer.accounts = mapBankAccounts(customer, bank.accounts)