Implemented evaluating count days for which bank stores transactions -> don't show fetch all transactions overlay if bank server doesn't store additional transactions

This commit is contained in:
dankito 2020-09-20 19:02:01 +02:00
parent 1f9fd48e80
commit 094d669148
16 changed files with 139 additions and 18 deletions

View File

@ -8,6 +8,15 @@ val Char.isUpperCase: Boolean
get() = isLowerCase == false 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 { fun Throwable.getInnerExceptionMessage(maxDepth: Int = 3): String {
return this.getInnerException(maxDepth).message ?: "" return this.getInnerException(maxDepth).message ?: ""
} }

View File

@ -12,6 +12,13 @@ fun Date.format(pattern: String): String {
expect class Date(millisSinceEpoch: Long) { expect class Date(millisSinceEpoch: Long) {
companion object {
val today: Date
}
constructor() constructor()
constructor(year: Int, month: Int, day: Int) constructor(year: Int, month: Int, day: Int)
@ -29,4 +36,7 @@ expect class Date(millisSinceEpoch: Long) {
fun day(): Int fun day(): Int
fun compareTo(other: Date): Int
} }

View File

@ -44,12 +44,7 @@ actual class BigDecimal(val decimal: NSDecimalNumber) : Comparable<BigDecimal> {
override fun compareTo(other: BigDecimal): Int { override fun compareTo(other: BigDecimal): Int {
return when (decimal.compare(other.decimal)) { return decimal.compare(other.decimal).toCompareToResult()
NSOrderedSame -> 0
NSOrderedAscending -> -1
NSOrderedDescending -> 1
else -> 0
}
} }
override fun equals(other: Any?): Boolean { override fun equals(other: Any?): Boolean {

View File

@ -18,7 +18,7 @@ fun NSDate.toDate(): Date {
actual class Date(val date: NSDate) { // cannot subclass NSDate as it's a class cluster 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() 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) !! 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 { override fun equals(other: Any?): Boolean {
if (this === other) return true if (this === other) return true
if (other !is Date) return false if (other !is Date) return false

View File

@ -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 { fun String.toNSData(): NSData {
return this.encodeToByteArray().toNSData() return this.encodeToByteArray().toNSData()
} }

View File

@ -3,11 +3,30 @@ package net.dankito.utils.multiplatform
import com.fasterxml.jackson.databind.annotation.JsonDeserialize import com.fasterxml.jackson.databind.annotation.JsonDeserialize
import net.dankito.utils.multiplatform.serialization.DateDeserializer import net.dankito.utils.multiplatform.serialization.DateDeserializer
import java.text.DateFormat import java.text.DateFormat
import java.util.*
import java.util.Date
@JsonDeserialize(using = DateDeserializer::class) @JsonDeserialize(using = DateDeserializer::class)
actual class Date actual constructor(millisSinceEpoch: Long) : java.util.Date(millisSinceEpoch) { 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() : this(System.currentTimeMillis())
actual constructor(year: Int, month: Int, day: Int) : this(java.util.Date(year - 1900, month - 1, day).time) 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() return dateStringString.toInt()
} }
actual fun compareTo(other: net.dankito.utils.multiplatform.Date): Int {
return super.compareTo(other)
}
} }

View File

@ -997,6 +997,16 @@ open class FinTsClient(
} }
} }
response.receivedSegments.filterIsInstance<RetrieveAccountTransactionsInMt940Parameters>().firstOrNull()?.let { retrieveTransactionsParameters ->
bank.countDaysForWhichTransactionsAreKept = retrieveTransactionsParameters.countDaysForWhichTransactionsAreKept
}
response.getFirstSegmentById<SepaAccountInfo>(InstituteSegmentId.SepaAccountInfo)?.let { sepaAccountInfo ->
sepaAccountInfo.account.bic?.let {
bank.bic = it // TODO: really set BIC on bank then?
}
}
response.getFirstSegmentById<ChangeTanMediaParameters>(InstituteSegmentId.ChangeTanMediaParameters)?.let { parameters -> response.getFirstSegmentById<ChangeTanMediaParameters>(InstituteSegmentId.ChangeTanMediaParameters)?.let { parameters ->
bank.changeTanMediumParameters = parameters bank.changeTanMediumParameters = parameters
} }

View File

@ -42,6 +42,8 @@ open class BankData(
*/ */
open var countMaxJobsPerMessage: Int = 0, open var countMaxJobsPerMessage: Int = 0,
open var countDaysForWhichTransactionsAreKept: Int? = null,
open var supportedHbciVersions: List<HbciVersion> = listOf(), open var supportedHbciVersions: List<HbciVersion> = listOf(),
open var supportedJobs: List<JobParameters> = listOf() open var supportedJobs: List<JobParameters> = listOf()
) { ) {

View File

@ -33,6 +33,10 @@ open class Bank(
@Ignore @Ignore
override var tanMedia: List<TanMedium> = listOf(), override var tanMedia: List<TanMedium> = listOf(),
override var countDaysForWhichTransactionsAreKept: Int? = null,
@PrimaryKey(autoGenerate = true) @PrimaryKey(autoGenerate = true)
open var id: Long = BaseDao.IdNotSet, open var id: Long = BaseDao.IdNotSet,

View File

@ -23,6 +23,7 @@ open class CustomerEntity(
override var supportedTanProcedures: List<TanProcedure> = listOf(), override var supportedTanProcedures: List<TanProcedure> = listOf(),
override var selectedTanProcedure: TanProcedure? = null, override var selectedTanProcedure: TanProcedure? = null,
override var tanMedia: List<TanMedium> = listOf(), override var tanMedia: List<TanMedium> = listOf(),
override var countDaysForWhichTransactionsAreKept: Int? = null,
override var technicalId: String = UUID.randomUUID().toString(), override var technicalId: String = UUID.randomUUID().toString(),
override var userSetDisplayName: String? = null, override var userSetDisplayName: String? = null,
override var displayIndex: Int = 0 override var displayIndex: Int = 0

View File

@ -37,6 +37,9 @@ open class Customer(
override var tanMedia: List<TanMedium> = listOf() override var tanMedia: List<TanMedium> = listOf()
override var countDaysForWhichTransactionsAreKept: Int? = null
override var userSetDisplayName: String? = null override var userSetDisplayName: String? = null
override var displayIndex: Int = 0 override var displayIndex: Int = 0

View File

@ -54,6 +54,9 @@ interface ICustomer<TAccount: IBankAccount<TAccountTransaction>, TAccountTransac
get() = tanMedia.sortedByDescending { it.status == TanMediumStatus.Used } get() = tanMedia.sortedByDescending { it.status == TanMediumStatus.Used }
var countDaysForWhichTransactionsAreKept: Int?
val stringRepresentation: String val stringRepresentation: String
get() = "$bankName $customerId" get() = "$bankName $customerId"

View File

@ -51,7 +51,7 @@ open class BankingPresenter(
val ChipTanTanProcedures = listOf(TanProcedureType.ChipTanManuell, TanProcedureType.ChipTanFlickercode, TanProcedureType.ChipTanUsb, val ChipTanTanProcedures = listOf(TanProcedureType.ChipTanManuell, TanProcedureType.ChipTanFlickercode, TanProcedureType.ChipTanUsb,
TanProcedureType.ChipTanQrCode, TanProcedureType.ChipTanPhotoTanMatrixCode) 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") 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) { protected open fun retrievedAccountTransactions(response: GetTransactionsResponse, startDate: Date, didFetchAllTransactions: Boolean) {
if (response.successful) { if (response.successful) {
response.retrievedData.forEach { retrievedData -> response.retrievedData.forEach { retrievedData ->
retrievedData.account.lastRetrievedTransactionsTimestamp = startDate val account = retrievedData.account
account.lastRetrievedTransactionsTimestamp = startDate
if (didFetchAllTransactions) { if (didFetchAllTransactions || didFetchAllTransactionsStoredOnBankServer(account, retrievedData.bookedTransactions)) {
retrievedData.account.haveAllTransactionsBeenFetched = true account.haveAllTransactionsBeenFetched = true
} }
updateAccountTransactionsAndBalances(retrievedData) updateAccountTransactionsAndBalances(retrievedData)
@ -348,6 +349,20 @@ open class BankingPresenter(
callRetrievedAccountTransactionsResponseListener(response) callRetrievedAccountTransactionsResponseListener(response)
} }
protected open fun didFetchAllTransactionsStoredOnBankServer(account: IBankAccount<IAccountTransaction>, fetchedTransactions: Collection<IAccountTransaction>): 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<IAccountTransaction>) { protected open fun receivedAccountsTransactionChunk(bankAccount: TypedBankAccount, accountTransactionsChunk: List<IAccountTransaction>) {
if (accountTransactionsChunk.isNotEmpty()) { if (accountTransactionsChunk.isNotEmpty()) {
asyncRunner.runAsync { // don't block retrieving next chunk by blocked saving to db / json 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<IAccountTransaction>, unbookedTransactions: List<Any>? = null) { protected open fun updateAccountTransactions(account: TypedBankAccount, bookedTransactions: Collection<IAccountTransaction>, unbookedTransactions: List<Any>? = null) {
val knownAccountTransactions = bankAccount.bookedTransactions.map { it.transactionIdentifier } val knownAccountTransactions = account.bookedTransactions.map { it.transactionIdentifier }
val newBookedTransactions = bookedTransactions.filterNot { knownAccountTransactions.contains(it.transactionIdentifier) } val newBookedTransactions = bookedTransactions.filterNot { knownAccountTransactions.contains(it.transactionIdentifier) }
bankAccount.addBookedTransactions(newBookedTransactions) account.addBookedTransactions(newBookedTransactions)
unbookedTransactions?.let { unbookedTransactions?.let {
bankAccount.addUnbookedTransactions(unbookedTransactions) account.addUnbookedTransactions(unbookedTransactions)
} }
persistAccountTransactionsOffUiThread(bankAccount, newBookedTransactions) persistAccountTransactionsOffUiThread(account, newBookedTransactions)
} }
protected open fun updateBalance(bankAccount: TypedBankAccount, balance: BigDecimal) { protected open fun updateBalance(bankAccount: TypedBankAccount, balance: BigDecimal) {

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="16119" systemVersion="19E287" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier=""> <model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="17192" systemVersion="19G2021" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
<entity name="PersistedAccountTransaction" representedClassName="PersistedAccountTransaction" syncable="YES" codeGenerationType="class"> <entity name="PersistedAccountTransaction" representedClassName="PersistedAccountTransaction" syncable="YES" codeGenerationType="class">
<attribute name="amount" attributeType="Decimal" defaultValueString="0.0"/> <attribute name="amount" attributeType="Decimal" defaultValueString="0.0"/>
<attribute name="bookingDate" attributeType="Date" usesScalarValueType="NO"/> <attribute name="bookingDate" attributeType="Date" usesScalarValueType="NO"/>
@ -62,6 +62,7 @@
<attribute name="bankCode" attributeType="String"/> <attribute name="bankCode" attributeType="String"/>
<attribute name="bankName" attributeType="String"/> <attribute name="bankName" attributeType="String"/>
<attribute name="bic" attributeType="String"/> <attribute name="bic" attributeType="String"/>
<attribute name="countDaysForWhichTransactionsAreKept" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="NO"/>
<attribute name="customerId" attributeType="String"/> <attribute name="customerId" attributeType="String"/>
<attribute name="customerName" attributeType="String"/> <attribute name="customerName" attributeType="String"/>
<attribute name="displayIndex" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/> <attribute name="displayIndex" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
@ -90,7 +91,7 @@
<element name="PersistedAccountTransaction" positionX="-36" positionY="45" width="128" height="553"/> <element name="PersistedAccountTransaction" positionX="-36" positionY="45" width="128" height="553"/>
<element name="PersistedBankAccount" positionX="-54" positionY="63" width="128" height="343"/> <element name="PersistedBankAccount" positionX="-54" positionY="63" width="128" height="343"/>
<element name="PersistedCustomer" positionX="-63" positionY="-18" width="128" height="28"/> <element name="PersistedCustomer" positionX="-63" positionY="-18" width="128" height="28"/>
<element name="PersistedTanProcedure" positionX="-54" positionY="135" width="128" height="118"/>
<element name="PersistedTanMedium" positionX="-45" positionY="144" width="128" height="28"/> <element name="PersistedTanMedium" positionX="-45" positionY="144" width="128" height="28"/>
<element name="PersistedTanProcedure" positionX="-54" positionY="135" width="128" height="118"/>
</elements> </elements>
</model> </model>

View File

@ -8,6 +8,8 @@ class Mapper {
func map(_ customer: PersistedCustomer) -> ICustomer { 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: []) 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.userSetDisplayName = customer.userSetDisplayName
mapped.displayIndex = customer.displayIndex mapped.displayIndex = customer.displayIndex
@ -35,6 +37,7 @@ class Mapper {
mapped.customerName = customer.customerName mapped.customerName = customer.customerName
mapped.userId = customer.userId mapped.userId = customer.userId
mapped.iconUrl = customer.iconUrl mapped.iconUrl = customer.iconUrl
mapped.countDaysForWhichTransactionsAreKept = mapFromInt(customer.countDaysForWhichTransactionsAreKept)
mapped.userSetDisplayName = customer.userSetDisplayName mapped.userSetDisplayName = customer.userSetDisplayName
mapped.displayIndex = customer.displayIndex mapped.displayIndex = customer.displayIndex
@ -357,6 +360,22 @@ class Mapper {
return nil 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 { func map(_ string: String?) -> String {
return string ?? "" return string ?? ""
} }

View File

@ -76,6 +76,7 @@ open class fints4kModelMapper(protected val modelCreator: IModelCreator) {
customer.bankName = bank.bankName customer.bankName = bank.bankName
customer.bic = bank.bic customer.bic = bank.bic
customer.customerName = bank.customerName customer.customerName = bank.customerName
customer.countDaysForWhichTransactionsAreKept = bank.countDaysForWhichTransactionsAreKept
customer.userId = bank.userId customer.userId = bank.userId
customer.accounts = mapBankAccounts(customer, bank.accounts) customer.accounts = mapBankAccounts(customer, bank.accounts)