Added supportsRetrievingAccountTransactions, supportsRetrievingBalance, supportsTransferringMoney to BankAccount and BankData. Enabling features retrieving account transactions and transferring money now only if a bank account supports this.

This commit is contained in:
dankl 2020-01-25 16:58:43 +01:00 committed by dankito
parent d7c8d19da7
commit 438772a6a2
12 changed files with 143 additions and 47 deletions

View File

@ -15,6 +15,9 @@ open class BankAccount @JvmOverloads constructor(
var balance: BigDecimal = BigDecimal.ZERO,
var currency: String = "EUR",
var type: BankAccountType = BankAccountType.Giro,
var supportsRetrievingAccountTransactions: Boolean = false,
var supportsRetrievingBalance: Boolean = false,
var supportsTransferringMoney: Boolean = false,
bookedAccountTransactions: List<AccountTransaction> = listOf()
) {
@ -35,6 +38,7 @@ open class BankAccount @JvmOverloads constructor(
val displayNameIncludingBankName: String
get() = "${account.bank.name} ${displayName}"
var bookedTransactions: List<AccountTransaction> = bookedAccountTransactions
protected set

View File

@ -153,9 +153,11 @@ open class MainWindowPresenter(
callback: (GetTransactionsResponse) -> Unit) {
account.bankAccounts.forEach { bankAccount ->
if (bankAccount.supportsRetrievingAccountTransactions) {
getAccountTransactionsAsync(bankAccount, callback) // TODO: use a synchronous version of getAccountTransactions() so that all bank accounts get handled serially
}
}
}
open fun getAccountTransactionsAsync(bankAccount: BankAccount,
callback: (GetTransactionsResponse) -> Unit) {
@ -179,6 +181,7 @@ open class MainWindowPresenter(
open fun updateAccountsTransactionsAsync(callback: (GetTransactionsResponse) -> Unit) {
clientsForAccounts.keys.forEach { account ->
account.bankAccounts.forEach { bankAccount ->
if (bankAccount.supportsRetrievingAccountTransactions) {
val today = Date() // TODO: still don't know where this bug is coming from that bank returns a transaction dated at end of year
val lastRetrievedTransactionDate = bankAccount.bookedTransactions.firstOrNull { it.bookingDate <= today }?.bookingDate
val fromDate = lastRetrievedTransactionDate?.let { Date(it.time - OneDayMillis) } // one day before last received transaction
@ -187,6 +190,7 @@ open class MainWindowPresenter(
}
}
}
}
protected open fun retrievedAccountTransactions(bankAccount: BankAccount, response: GetTransactionsResponse) {
if (response.isSuccessful) {
@ -349,6 +353,48 @@ open class MainWindowPresenter(
get() = getBalanceForAccounts(accounts)
open val bankAccountsSupportingRetrievingAccountTransactions: List<BankAccount>
get() = bankAccounts.filter { it.supportsRetrievingAccountTransactions }
open val hasBankAccountsSupportingRetrievingAccountTransactions: Boolean
get() = doBankAccountsSupportRetrievingAccountTransactions(bankAccounts)
open val doSelectedBankAccountsSupportRetrievingAccountTransactions: Boolean
get() = doBankAccountsSupportRetrievingAccountTransactions(selectedBankAccounts)
open fun doBankAccountsSupportRetrievingAccountTransactions(bankAccounts: List<BankAccount>): Boolean {
return bankAccounts.firstOrNull { it.supportsRetrievingAccountTransactions } != null
}
open val bankAccountsSupportingRetrievingBalance: List<BankAccount>
get() = bankAccounts.filter { it.supportsRetrievingBalance }
open val hasBankAccountsSupportingRetrievingBalance: Boolean
get() = doBankAccountsSupportRetrievingBalance(bankAccounts)
open val doSelectedBankAccountsSupportRetrievingBalance: Boolean
get() = doBankAccountsSupportRetrievingBalance(selectedBankAccounts)
open fun doBankAccountsSupportRetrievingBalance(bankAccounts: List<BankAccount>): Boolean {
return bankAccounts.firstOrNull { it.supportsRetrievingBalance } != null
}
open val bankAccountsSupportingTransferringMoney: List<BankAccount>
get() = bankAccounts.filter { it.supportsTransferringMoney }
open val hasBankAccountsSupportTransferringMoney: Boolean
get() = doBankAccountsSupportTransferringMoney(bankAccounts)
open val doSelectedBankAccountsSupportTransferringMoney: Boolean
get() = doBankAccountsSupportTransferringMoney(selectedBankAccounts)
open fun doBankAccountsSupportTransferringMoney(bankAccounts: List<BankAccount>): Boolean {
return bankAccounts.firstOrNull { it.supportsTransferringMoney } != null
}
protected open fun getAccountTransactionsForAccounts(accounts: Collection<Account>): List<AccountTransaction> {
return accounts.flatMap { it.transactions }.sortedByDescending { it.bookingDate } // TODO: someday add unbooked transactions
}

View File

@ -59,6 +59,8 @@ open class AccountTransactionAdapter
selectedTransaction = getItem(viewHolder.adapterPosition)
menu.findItem(R.id.mnitmShowTransferMoneyDialog)?.let { mnitmShowTransferMoneyDialog ->
mnitmShowTransferMoneyDialog.isVisible = selectedTransaction?.bankAccount?.supportsTransferringMoney ?: false
val remitteeName = selectedTransaction?.otherPartyName ?: ""
mnitmShowTransferMoneyDialog.title = view.context.getString(R.string.fragment_home_transfer_money_to, remitteeName)

View File

@ -15,9 +15,9 @@ import android.widget.TextView
import kotlinx.android.synthetic.main.dialog_add_account.*
import kotlinx.android.synthetic.main.dialog_add_account.view.*
import net.dankito.banking.fints4java.android.R
import net.dankito.banking.ui.presenter.MainWindowPresenter
import net.dankito.banking.fints4java.android.ui.adapter.BankListAdapter
import net.dankito.banking.ui.model.responses.AddAccountResponse
import net.dankito.banking.ui.presenter.MainWindowPresenter
import net.dankito.fints.model.BankInfo
import net.dankito.utils.android.extensions.asActivity
@ -39,7 +39,7 @@ open class AddAccountDialog : DialogFragment() {
fun show(activity: AppCompatActivity, presenter: MainWindowPresenter, fullscreen: Boolean = false) {
this.presenter = presenter
presenter.preloadBanksAsync()
// presenter.preloadBanksAsync()
this.adapter = BankListAdapter(presenter)
val style = if(fullscreen) R.style.FullscreenDialogWithStatusBar else R.style.Dialog

View File

@ -70,13 +70,13 @@ open class TransferMoneyDialog : DialogFragment() {
protected open fun setupUI(rootView: View) {
setPreselectedValues(rootView)
val allBankAccounts = presenter.bankAccounts
bankAccount = preselectedBankAccount ?: allBankAccounts.first()
val allBankAccountsSupportingTransferringMoney = presenter.bankAccountsSupportingTransferringMoney
bankAccount = preselectedBankAccount ?: allBankAccountsSupportingTransferringMoney.first()
if (allBankAccounts.size > 1) {
if (allBankAccountsSupportingTransferringMoney.size > 1) {
rootView.lytSelectBankAccount.visibility = View.VISIBLE
val adapter = BankAccountsAdapter(allBankAccounts)
val adapter = BankAccountsAdapter(allBankAccountsSupportingTransferringMoney)
rootView.spnBankAccounts.adapter = adapter
rootView.spnBankAccounts.onItemSelectedListener = ListItemSelectedListener(adapter) { selectedBankAccount ->
this.bankAccount = selectedBankAccount

View File

@ -15,9 +15,7 @@ import android.widget.EditText
import net.dankito.banking.fints4java.android.MainActivity
import net.dankito.banking.fints4java.android.R
import net.dankito.banking.fints4java.android.ui.adapter.AccountTransactionAdapter
import net.dankito.banking.ui.model.Account
import net.dankito.banking.ui.model.AccountTransaction
import net.dankito.banking.ui.model.BankAccount
import net.dankito.banking.ui.model.parameters.TransferMoneyData
import net.dankito.banking.ui.model.responses.GetTransactionsResponse
import net.dankito.banking.ui.presenter.MainWindowPresenter
@ -135,9 +133,9 @@ class HomeFragment : Fragment() {
}
private fun initLogicAfterUiInitialized() {
presenter.addAccountsChangedListener { handleAccountsChanged(it) }
presenter.addAccountsChangedListener { handleAccountsChanged() }
presenter.addSelectedBankAccountsChangedListener { handleSelectedBankAccountsChanged(it) }
presenter.addSelectedBankAccountsChangedListener { handleSelectedBankAccountsChanged() }
presenter.addRetrievedAccountTransactionsResponseListener { _, response ->
handleGetTransactionsResponse(response)
@ -147,16 +145,16 @@ class HomeFragment : Fragment() {
}
private fun handleAccountsChanged(accounts: List<Account>) {
private fun handleAccountsChanged() {
context?.asActivity()?.let { activity ->
activity.runOnUiThread {
mnitmSearchTransactions.isVisible = accounts.isNotEmpty()
mnitmUpdateTransactions.isVisible = accounts.isNotEmpty()
mnitmSearchTransactions.isVisible = presenter.doSelectedBankAccountsSupportRetrievingAccountTransactions
mnitmUpdateTransactions.isVisible = presenter.doSelectedBankAccountsSupportRetrievingAccountTransactions
}
}
}
private fun handleSelectedBankAccountsChanged(selectedBankAccounts: List<BankAccount>) {
private fun handleSelectedBankAccountsChanged() {
context?.asActivity()?.let { activity ->
activity.runOnUiThread {
updateTransactionsToDisplayOnUiThread()
@ -220,7 +218,7 @@ class HomeFragment : Fragment() {
// TODO: if transactions are filtered calculate and show balance of displayed transactions?
mnitmBalance.title = presenter.balanceOfSelectedBankAccounts.toString()
mnitmBalance.isVisible = true
mnitmBalance.isVisible = presenter.doSelectedBankAccountsSupportRetrievingBalance
}
}

View File

@ -4,7 +4,6 @@ import android.support.v7.app.AppCompatActivity
import com.github.clans.fab.FloatingActionButton
import com.github.clans.fab.FloatingActionMenu
import kotlinx.android.synthetic.main.view_floating_action_button_main.view.*
import net.dankito.banking.ui.model.Account
import net.dankito.banking.ui.presenter.MainWindowPresenter
@ -17,10 +16,10 @@ open class MainActivityFloatingActionMenuButton(floatingActionMenu: FloatingActi
setupButtons(floatingActionMenu)
presenter.addAccountsChangedListener {
checkIfThereAreAccountsThatCanTransferMoney(it)
checkIfThereAreAccountsThatCanTransferMoney()
}
checkIfThereAreAccountsThatCanTransferMoney(presenter.accounts)
checkIfThereAreAccountsThatCanTransferMoney()
}
private fun setupButtons(floatingActionMenu: FloatingActionMenu) {
@ -38,8 +37,8 @@ open class MainActivityFloatingActionMenuButton(floatingActionMenu: FloatingActi
}
protected open fun checkIfThereAreAccountsThatCanTransferMoney(accounts: List<Account>) {
fabTransferMoney.isEnabled = accounts.isNotEmpty() // TODO: add check if they support transferring money
protected open fun checkIfThereAreAccountsThatCanTransferMoney() {
fabTransferMoney.isEnabled = presenter.hasBankAccountsSupportTransferringMoney
}
}

View File

@ -81,7 +81,8 @@ open class fints4javaModelMapper {
open fun mapBankAccount(account: Account, accountData: AccountData): BankAccount {
return BankAccount(account, accountData.accountIdentifier, accountData.accountHolderName, accountData.iban,
accountData.subAccountAttribute, BigDecimal.ZERO, accountData.currency ?: "EUR",
mapBankAccountType(accountData.accountType))
mapBankAccountType(accountData.accountType), accountData.supportsRetrievingAccountTransactions,
accountData.supportsRetrievingBalance, accountData.supportsTransferringMoney)
}
open fun mapBankAccountType(type: AccountType?): BankAccountType {

View File

@ -153,8 +153,14 @@ open class FinTsClient @JvmOverloads constructor(
getTanMediaList(bank, customer, TanMedienArtVersion.Alle, TanMediumKlasse.AlleMedien)
// also check if retrieving account transactions of last 90 days without tan is supported (and thereby may retrieve first account transactions)
val account = getBestAccountForRetrievingTransactions(customer)
val transactionsOfLast90DaysResponse = if (account != null) tryGetTransactionsOfLast90DaysWithoutTan(bank, customer, account, false) else GetTransactionsResponse(Response(false))
val transactionsOfLast90DaysResponses = mutableListOf<GetTransactionsResponse>()
customer.accounts.forEach { account ->
if (account.supportsRetrievingAccountTransactions) {
transactionsOfLast90DaysResponses.add(
tryGetTransactionsOfLast90DaysWithoutTan(bank, customer, account, false))
}
}
val transactionsOfLast90DaysResponse = transactionsOfLast90DaysResponses.firstOrNull { it.isSuccessful } ?: GetTransactionsResponse(Response(false))
if (didOverwriteUserUnselectedTanProcedure) {
customer.resetSelectedTanProcedure()
@ -179,14 +185,14 @@ open class FinTsClient @JvmOverloads constructor(
val ninetyDaysAgo = Date(Date().time - NinetyDaysAgoMilliseconds)
val response = getTransactions(GetTransactionsParameter(true, ninetyDaysAgo), bank, customer, account)
val response = getTransactions(GetTransactionsParameter(account.supportsRetrievingBalance, ninetyDaysAgo), bank, customer, account)
customer.triedToRetrieveTransactionsOfLast90DaysWithoutTan = true
account.triedToRetrieveTransactionsOfLast90DaysWithoutTan = true
if (response.isSuccessful) {
if (response.isStrongAuthenticationRequired == false || hasRetrievedTransactionsWithTanJustBefore) {
customer.supportsRetrievingTransactionsOfLast90DaysWithoutTan = !!! response.isStrongAuthenticationRequired
account.supportsRetrievingTransactionsOfLast90DaysWithoutTan = !!! response.isStrongAuthenticationRequired
}
}
@ -215,7 +221,7 @@ open class FinTsClient @JvmOverloads constructor(
var balance: BigDecimal? = null
if (parameter.alsoRetrieveBalance) {
if (parameter.alsoRetrieveBalance && account.supportsRetrievingBalance) {
val balanceResponse = getBalanceAfterDialogInit(bank, customer, account, dialogData)
if (balanceResponse.successful == false && balanceResponse.couldCreateMessage == true) { // don't break here if required HKSAL message is not implemented
@ -242,7 +248,7 @@ open class FinTsClient @JvmOverloads constructor(
response.getFirstSegmentById<ReceivedAccountTransactions>(InstituteSegmentId.AccountTransactionsMt940)?.let { transactions ->
// just retrieved all transactions -> check if retrieving that ones of last 90 days is possible without entering TAN
if (customer.supportsRetrievingTransactionsOfLast90DaysWithoutTan == null &&
if (account.supportsRetrievingTransactionsOfLast90DaysWithoutTan == null &&
response.successful && transactions.bookedTransactionsString.isNotEmpty() && parameter.fromDate == null) {
tryGetTransactionsOfLast90DaysWithoutTan(bank, customer, account, true)
}
@ -872,6 +878,10 @@ open class FinTsClient @JvmOverloads constructor(
}
account.allowedJobs = allowedJobsForAccount
account.supportsRetrievingAccountTransactions = messageBuilder.supportsGetTransactions(account)
account.supportsRetrievingBalance = messageBuilder.supportsGetBalance(account)
account.supportsTransferringMoney = messageBuilder.supportsBankTransfer(account)
}
protected open fun mapToTanProcedures(tanInfo: TanInfo): List<TanProcedure> {

View File

@ -110,7 +110,7 @@ open class MessageBuilder(protected val generator: ISegmentNumberGenerator = Seg
open fun createGetTransactionsMessage(parameter: GetTransactionsParameter, bank: BankData, customer: CustomerData,
account: AccountData, product: ProductData, dialogData: DialogData): MessageBuilderResult {
val result = getSupportedVersionsOfJob(CustomerSegmentId.AccountTransactionsMt940, account, listOf(5, 6, 7))
val result = supportsGetTransactionsMt940(account)
if (result.isJobVersionSupported) {
val transactionsJob = if (result.isAllowed(7)) KontoumsaetzeZeitraumMt940Version7(generator.resetSegmentNumber(2), parameter, bank, account)
@ -128,9 +128,18 @@ open class MessageBuilder(protected val generator: ISegmentNumberGenerator = Seg
return result
}
open fun supportsGetTransactions(account: AccountData): Boolean {
return supportsGetTransactionsMt940(account).isJobVersionSupported
}
protected open fun supportsGetTransactionsMt940(account: AccountData): MessageBuilderResult {
return getSupportedVersionsOfJob(CustomerSegmentId.AccountTransactionsMt940, account, listOf(5, 6, 7))
}
open fun createGetBalanceMessage(bank: BankData, customer: CustomerData, account: AccountData, product: ProductData, dialogData: DialogData): MessageBuilderResult {
val result = getSupportedVersionsOfJob(CustomerSegmentId.Balance, account, listOf(5))
val result = supportsGetBalanceMessage(account)
if (result.isJobVersionSupported) {
val segments = listOf(
@ -144,6 +153,14 @@ open class MessageBuilder(protected val generator: ISegmentNumberGenerator = Seg
return result
}
open fun supportsGetBalance(account: AccountData): Boolean {
return supportsGetBalanceMessage(account).isJobVersionSupported
}
protected open fun supportsGetBalanceMessage(account: AccountData): MessageBuilderResult {
return getSupportedVersionsOfJob(CustomerSegmentId.Balance, account, listOf(5))
}
open fun createGetTanMediaListMessage(bank: BankData, customer: CustomerData, dialogData: DialogData,
tanMediaKind: TanMedienArtVersion = TanMedienArtVersion.Alle,
@ -193,11 +210,11 @@ open class MessageBuilder(protected val generator: ISegmentNumberGenerator = Seg
open fun createBankTransferMessage(bankTransferData: BankTransferData, bank: BankData, customer: CustomerData, account: AccountData, dialogData: DialogData): MessageBuilderResult {
val result = getSupportedVersionsOfJob(CustomerSegmentId.SepaBankTransfer, account, listOf(1))
val messageBuilderResultAndNullableUrn = supportsBankTransferAndSepaVersion(account)
val result = messageBuilderResultAndNullableUrn.first
val urn = messageBuilderResultAndNullableUrn.second
if (result.isJobVersionSupported) {
getSepaUrnFor(CustomerSegmentId.SepaAccountInfoParameters, account, "pain.001.001.03")?.let { urn ->
if (result.isJobVersionSupported && urn != null) {
val segments = listOf(
SepaEinzelueberweisung(generator.resetSegmentNumber(2), urn, customer, account, bank.bic, bankTransferData),
ZweiSchrittTanEinreichung(generator.getNextSegmentNumber(), TanProcess.TanProcess4, CustomerSegmentId.SepaBankTransfer)
@ -206,10 +223,26 @@ open class MessageBuilder(protected val generator: ISegmentNumberGenerator = Seg
return createMessageBuilderResult(bank, customer, dialogData, segments)
}
return MessageBuilderResult(true, false, result.allowedVersions, result.supportedVersions, null) // TODO: how to tell that we don't support required SEPA pain version?
return result
}
return result
open fun supportsBankTransfer(account: AccountData): Boolean {
return supportsBankTransferAndSepaVersion(account).first.isJobVersionSupported
}
protected open fun supportsBankTransferAndSepaVersion(account: AccountData): Pair<MessageBuilderResult, String?> {
val result = getSupportedVersionsOfJob(CustomerSegmentId.SepaBankTransfer, account, listOf(1))
if (result.isJobVersionSupported) {
getSepaUrnFor(CustomerSegmentId.SepaAccountInfoParameters, account, "pain.001.001.03")?.let { urn ->
return Pair(result, urn)
}
return Pair(MessageBuilderResult(true, false, result.allowedVersions, result.supportedVersions, null), null) // TODO: how to tell that we don't support required SEPA pain version?
}
return Pair(result, null)
}

View File

@ -17,7 +17,12 @@ open class AccountData(
val productName: String?,
val accountLimit: String?,
val allowedJobNames: List<String>,
var allowedJobs: List<JobParameters> = listOf()
var allowedJobs: List<JobParameters> = listOf(),
var supportsRetrievingAccountTransactions: Boolean = false,
var supportsRetrievingBalance: Boolean = false,
var supportsTransferringMoney: Boolean = false,
var supportsRetrievingTransactionsOfLast90DaysWithoutTan: Boolean? = null,
var triedToRetrieveTransactionsOfLast90DaysWithoutTan: Boolean = false
) {
override fun toString(): String {

View File

@ -17,9 +17,7 @@ open class CustomerData(
var tanMedia: List<TanMedium> = listOf(),
var selectedLanguage: Dialogsprache = Dialogsprache.Default,
var customerSystemId: String = KundensystemID.Anonymous,
var customerSystemStatus: KundensystemStatusWerte = KundensystemStatus.SynchronizingCustomerSystemId,
var supportsRetrievingTransactionsOfLast90DaysWithoutTan: Boolean? = null,
var triedToRetrieveTransactionsOfLast90DaysWithoutTan: Boolean = false
var customerSystemStatus: KundensystemStatusWerte = KundensystemStatus.SynchronizingCustomerSystemId
) {
companion object {