Renamed usage to reference, remittee to recipient and instant payment to real-time transfer

This commit is contained in:
dankito 2020-09-24 02:53:09 +02:00
parent 66feba0614
commit bf9d406053
95 changed files with 921 additions and 917 deletions

View File

@ -8,7 +8,7 @@ However it's not a full implementation of FinTS standard but implements all comm
## Features
- Retrieving account information, balances and turnovers (Kontoumsätze und -saldo).
- Transfer money and instant payments (SEPA Überweisungen und Echtzeitüberweisung).
- Transfer money and real-time transfers (SEPA Überweisungen und Echtzeitüberweisung).
- Supports TAN methods chipTAN manual, Flickercode, QrCode and Photo (Matrix code), pushTAN, smsTAN and appTAN.
## Setup

View File

@ -400,7 +400,7 @@ open class FinTsClient(
response.getFirstSegmentById<ReceivedCreditCardTransactionsAndBalance>(InstituteSegmentId.CreditCardTransactions)?.let { transactionsSegment ->
balance = Money(transactionsSegment.balance.amount, transactionsSegment.balance.currency ?: "EUR")
bookedTransactions.addAll(transactionsSegment.transactions.map { AccountTransaction(parameter.account, it.amount, "", it.bookingDate, it.otherPartyName, null, null, "", it.valueDate) })
bookedTransactions.addAll(transactionsSegment.transactions.map { AccountTransaction(parameter.account, it.amount, it.otherPartyName, it.bookingDate, it.otherPartyName, null, null, "", it.valueDate) })
}
}
@ -1138,7 +1138,7 @@ open class FinTsClient(
account.setSupportsFeature(AccountFeature.RetrieveAccountTransactions, messageBuilder.supportsGetTransactions(account))
account.setSupportsFeature(AccountFeature.RetrieveBalance, messageBuilder.supportsGetBalance(account))
account.setSupportsFeature(AccountFeature.TransferMoney, messageBuilder.supportsBankTransfer(bank, account))
account.setSupportsFeature(AccountFeature.InstantPayment, messageBuilder.supportsSepaInstantPaymentBankTransfer(bank, account))
account.setSupportsFeature(AccountFeature.RealTimeTransfer, messageBuilder.supportsSepaRealTimeTransfer(bank, account))
}
protected open fun mapToTanMethods(tanInfo: TanInfo): List<TanMethod> {

View File

@ -289,7 +289,7 @@ open class MessageBuilder(protected val generator: ISegmentNumberGenerator = Seg
open fun createBankTransferMessage(data: BankTransferData, account: AccountData, dialogContext: DialogContext): MessageBuilderResult {
val segmentId = if (data.instantPayment) CustomerSegmentId.SepaInstantPaymentBankTransfer else CustomerSegmentId.SepaBankTransfer
val segmentId = if (data.realTimeTransfer) CustomerSegmentId.SepaRealTimeTransfer else CustomerSegmentId.SepaBankTransfer
val (result, urn) = supportsBankTransferAndSepaVersion(dialogContext.bank, account, segmentId)
@ -309,8 +309,8 @@ open class MessageBuilder(protected val generator: ISegmentNumberGenerator = Seg
return supportsBankTransferAndSepaVersion(bank, account, CustomerSegmentId.SepaBankTransfer).first.isJobVersionSupported
}
open fun supportsSepaInstantPaymentBankTransfer(bank: BankData, account: AccountData): Boolean {
return supportsBankTransferAndSepaVersion(bank, account, CustomerSegmentId.SepaInstantPaymentBankTransfer).first.isJobVersionSupported
open fun supportsSepaRealTimeTransfer(bank: BankData, account: AccountData): Boolean {
return supportsBankTransferAndSepaVersion(bank, account, CustomerSegmentId.SepaRealTimeTransfer).first.isJobVersionSupported
}
protected open fun supportsBankTransferAndSepaVersion(bank: BankData, account: AccountData, segmentId: CustomerSegmentId): Pair<MessageBuilderResult, String?> {

View File

@ -25,7 +25,7 @@ enum class CustomerSegmentId(override val id: String) : ISegmentId {
SepaBankTransfer("HKCCS"),
SepaInstantPaymentBankTransfer("HKIPZ"),
SepaRealTimeTransfer("HKIPZ"),
SepaAccountInfoParameters("HKSPA") // not implemented, retrieved automatically with UPD

View File

@ -3,8 +3,8 @@ package net.dankito.banking.fints.messages.segmente.implementierte.sepa
enum class PaymentInformationMessages(val xmlTemplate: String) {
Pain_001_001_03("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?><Document xmlns=\"urn:iso:std:iso:20022:tech:xsd:pain.001.001.03\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"urn:iso:std:iso:20022:tech:xsd:pain.001.001.03 pain.001.001.03.xsd\"><CstmrCdtTrfInitn><GrpHdr><MsgId>\$MessageId\$</MsgId><CreDtTm>\$CreationDateTime\$</CreDtTm><NbOfTxs>\$NumberOfTransactions\$</NbOfTxs><CtrlSum>\$Amount\$</CtrlSum><InitgPty><Nm>\$DebitorName\$</Nm></InitgPty></GrpHdr><PmtInf><PmtInfId>\$PaymentInformationId\$</PmtInfId><PmtMtd>TRF</PmtMtd><NbOfTxs>\$NumberOfTransactions\$</NbOfTxs><CtrlSum>\$Amount\$</CtrlSum><PmtTpInf><SvcLvl><Cd>SEPA</Cd></SvcLvl></PmtTpInf><ReqdExctnDt>\$RequestedExecutionDate\$</ReqdExctnDt><Dbtr><Nm>\$DebitorName\$</Nm></Dbtr><DbtrAcct><Id><IBAN>\$DebitorIban\$</IBAN></Id></DbtrAcct><DbtrAgt><FinInstnId><BIC>\$DebitorBic\$</BIC></FinInstnId></DbtrAgt><ChrgBr>SLEV</ChrgBr><CdtTrfTxInf><PmtId><EndToEndId>NOTPROVIDED</EndToEndId></PmtId><Amt><InstdAmt Ccy=\"EUR\">\$Amount\$</InstdAmt></Amt><CdtrAgt><FinInstnId><BIC>\$CreditorBic\$</BIC></FinInstnId></CdtrAgt><Cdtr><Nm>\$CreditorName\$</Nm></Cdtr><CdtrAcct><Id><IBAN>\$CreditorIban\$</IBAN></Id></CdtrAcct><RmtInf><Ustrd>\$Usage\$</Ustrd></RmtInf></CdtTrfTxInf></PmtInf></CstmrCdtTrfInitn></Document>"),
Pain_001_001_03("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?><Document xmlns=\"urn:iso:std:iso:20022:tech:xsd:pain.001.001.03\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"urn:iso:std:iso:20022:tech:xsd:pain.001.001.03 pain.001.001.03.xsd\"><CstmrCdtTrfInitn><GrpHdr><MsgId>\$MessageId\$</MsgId><CreDtTm>\$CreationDateTime\$</CreDtTm><NbOfTxs>\$NumberOfTransactions\$</NbOfTxs><CtrlSum>\$Amount\$</CtrlSum><InitgPty><Nm>\$DebitorName\$</Nm></InitgPty></GrpHdr><PmtInf><PmtInfId>\$PaymentInformationId\$</PmtInfId><PmtMtd>TRF</PmtMtd><NbOfTxs>\$NumberOfTransactions\$</NbOfTxs><CtrlSum>\$Amount\$</CtrlSum><PmtTpInf><SvcLvl><Cd>SEPA</Cd></SvcLvl></PmtTpInf><ReqdExctnDt>\$RequestedExecutionDate\$</ReqdExctnDt><Dbtr><Nm>\$DebitorName\$</Nm></Dbtr><DbtrAcct><Id><IBAN>\$DebitorIban\$</IBAN></Id></DbtrAcct><DbtrAgt><FinInstnId><BIC>\$DebitorBic\$</BIC></FinInstnId></DbtrAgt><ChrgBr>SLEV</ChrgBr><CdtTrfTxInf><PmtId><EndToEndId>NOTPROVIDED</EndToEndId></PmtId><Amt><InstdAmt Ccy=\"EUR\">\$Amount\$</InstdAmt></Amt><CdtrAgt><FinInstnId><BIC>\$RecipientBic\$</BIC></FinInstnId></CdtrAgt><Cdtr><Nm>\$RecipientName\$</Nm></Cdtr><CdtrAcct><Id><IBAN>\$RecipientIban\$</IBAN></Id></CdtrAcct><RmtInf><Ustrd>\$Reference\$</Ustrd></RmtInf></CdtTrfTxInf></PmtInf></CstmrCdtTrfInitn></Document>"),
Pain_001_003_03("<?xml version=\"1.0\" encoding=\"UTF-8\"?><Document xmlns=\"urn:iso:std:iso:20022:tech:xsd:pain.001.003.03\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"urn:iso:std:iso:20022:tech:xsd:pain.001.003.03 pain.001.003.03.xsd\"><CstmrCdtTrfInitn><GrpHdr><MsgId>\$MessageId\$</MsgId><CreDtTm>\$CreationDateTime\$</CreDtTm><NbOfTxs>\$NumberOfTransactions\$</NbOfTxs><InitgPty><Nm>\$DebitorName\$</Nm></InitgPty></GrpHdr><PmtInf><PmtInfId>\$PaymentInformationId\$</PmtInfId><PmtMtd>TRF</PmtMtd><BtchBookg>true</BtchBookg><NbOfTxs>\$NumberOfTransactions\$</NbOfTxs><CtrlSum>\$Amount\$</CtrlSum><PmtTpInf><SvcLvl><Cd>SEPA</Cd></SvcLvl></PmtTpInf><ReqdExctnDt>\$RequestedExecutionDate\$</ReqdExctnDt><Dbtr><Nm>\$DebitorName\$</Nm></Dbtr><DbtrAcct><Id><IBAN>\$DebitorIban\$</IBAN></Id></DbtrAcct><DbtrAgt><FinInstnId><BIC>\$DebitorBic\$</BIC></FinInstnId></DbtrAgt><ChrgBr>SLEV</ChrgBr><CdtTrfTxInf><PmtId><EndToEndId>NOTPROVIDED</EndToEndId></PmtId><Amt><InstdAmt Ccy=\"EUR\">\$Amount\$</InstdAmt></Amt><CdtrAgt><FinInstnId><BIC>\$CreditorBic\$</BIC></FinInstnId></CdtrAgt><Cdtr><Nm>\$CreditorName\$</Nm></Cdtr><CdtrAcct><Id><IBAN>\$CreditorIban\$</IBAN></Id></CdtrAcct><RmtInf><Ustrd>\$Usage\$</Ustrd></RmtInf></CdtTrfTxInf></PmtInf></CstmrCdtTrfInitn></Document>")
Pain_001_003_03("<?xml version=\"1.0\" encoding=\"UTF-8\"?><Document xmlns=\"urn:iso:std:iso:20022:tech:xsd:pain.001.003.03\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"urn:iso:std:iso:20022:tech:xsd:pain.001.003.03 pain.001.003.03.xsd\"><CstmrCdtTrfInitn><GrpHdr><MsgId>\$MessageId\$</MsgId><CreDtTm>\$CreationDateTime\$</CreDtTm><NbOfTxs>\$NumberOfTransactions\$</NbOfTxs><InitgPty><Nm>\$DebitorName\$</Nm></InitgPty></GrpHdr><PmtInf><PmtInfId>\$PaymentInformationId\$</PmtInfId><PmtMtd>TRF</PmtMtd><BtchBookg>true</BtchBookg><NbOfTxs>\$NumberOfTransactions\$</NbOfTxs><CtrlSum>\$Amount\$</CtrlSum><PmtTpInf><SvcLvl><Cd>SEPA</Cd></SvcLvl></PmtTpInf><ReqdExctnDt>\$RequestedExecutionDate\$</ReqdExctnDt><Dbtr><Nm>\$DebitorName\$</Nm></Dbtr><DbtrAcct><Id><IBAN>\$DebitorIban\$</IBAN></Id></DbtrAcct><DbtrAgt><FinInstnId><BIC>\$DebitorBic\$</BIC></FinInstnId></DbtrAgt><ChrgBr>SLEV</ChrgBr><CdtTrfTxInf><PmtId><EndToEndId>NOTPROVIDED</EndToEndId></PmtId><Amt><InstdAmt Ccy=\"EUR\">\$Amount\$</InstdAmt></Amt><CdtrAgt><FinInstnId><BIC>\$RecipientBic\$</BIC></FinInstnId></CdtrAgt><Cdtr><Nm>\$RecipientName\$</Nm></Cdtr><CdtrAcct><Id><IBAN>\$RecipientIban\$</IBAN></Id></CdtrAcct><RmtInf><Ustrd>\$Reference\$</Ustrd></RmtInf></CdtTrfTxInf></PmtInf></CstmrCdtTrfInitn></Document>")
}

View File

@ -28,11 +28,11 @@ open class SepaBankTransferBase(
"DebitorName" to messageCreator.convertDiacriticsAndReservedXmlCharacters(debitorName),
"DebitorIban" to account.iban!!,
"DebitorBic" to debitorBic,
"CreditorName" to messageCreator.convertDiacriticsAndReservedXmlCharacters(data.creditorName),
"CreditorIban" to data.creditorIban.replace(" ", ""),
"CreditorBic" to data.creditorBic.replace(" ", ""),
"RecipientName" to messageCreator.convertDiacriticsAndReservedXmlCharacters(data.recipientName),
"RecipientIban" to data.recipientAccountId.replace(" ", ""),
"RecipientBic" to data.recipientBankCode.replace(" ", ""),
"Amount" to data.amount.amount.string.replace(',', '.'), // TODO: check if ',' or '.' should be used as decimal separator
"Usage" to if (data.usage.isEmpty()) " " else messageCreator.convertDiacriticsAndReservedXmlCharacters(data.usage),
"Reference" to if (data.reference.isEmpty()) " " else messageCreator.convertDiacriticsAndReservedXmlCharacters(data.reference),
"RequestedExecutionDate" to RequestedExecutionDateValueForNotScheduledTransfers
),
messageCreator

View File

@ -44,8 +44,8 @@ open class AccountData(
open val supportsTransferringMoney: Boolean
get() = supportsFeature(AccountFeature.TransferMoney)
open val supportsInstantPaymentMoneyTransfer: Boolean
get() = supportsFeature(AccountFeature.InstantPayment)
open val supportsRealTimeTransfer: Boolean
get() = supportsFeature(AccountFeature.RealTimeTransfer)
open fun supportsFeature(feature: AccountFeature): Boolean {

View File

@ -9,6 +9,6 @@ enum class AccountFeature {
TransferMoney,
InstantPayment
RealTimeTransfer
}

View File

@ -7,7 +7,7 @@ open class AccountTransaction(
val account: AccountData,
val amount: Money,
val isReversal: Boolean,
val unparsedUsage: String,
val unparsedReference: String,
val bookingDate: Date,
val otherPartyName: String?,
val otherPartyBankCode: String?,
@ -26,10 +26,10 @@ open class AccountTransaction(
val originatorsIdentificationCode: String?,
val compensationAmount: String?,
val originalAmount: String?,
val sepaUsage: String?,
val sepaReference: String?,
val deviantOriginator: String?,
val deviantRecipient: String?,
val usageWithNoSpecialType: String?,
val referenceWithNoSpecialType: String?,
val primaNotaNumber: String?,
val textKeySupplement: String?,
@ -46,20 +46,24 @@ open class AccountTransaction(
// for object deserializers
internal constructor() : this(AccountData(), Money(Amount.Zero, ""), "", Date(0), null, null, null, null, Date(0))
constructor(account: AccountData, amount: Money, unparsedUsage: String, bookingDate: Date, otherPartyName: String?, otherPartyBankCode: String?, otherPartyAccountId: String?, bookingText: String?, valueDate: Date)
: this(account, amount, false, unparsedUsage, bookingDate, otherPartyName, otherPartyBankCode, otherPartyAccountId, bookingText, valueDate,
constructor(account: AccountData, amount: Money, unparsedReference: String, bookingDate: Date, otherPartyName: String?, otherPartyBankCode: String?, otherPartyAccountId: String?, bookingText: String?, valueDate: Date)
: this(account, amount, false, unparsedReference, bookingDate, otherPartyName, otherPartyBankCode, otherPartyAccountId, bookingText, valueDate,
0, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null,
null, "", "", null, null, "", null)
val reference: String
get() = sepaReference ?: unparsedReference
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is AccountTransaction) return false
if (account != other.account) return false
if (amount != other.amount) return false
if (unparsedUsage != other.unparsedUsage) return false
if (unparsedReference != other.unparsedReference) return false
if (bookingDate != other.bookingDate) return false
if (otherPartyName != other.otherPartyName) return false
if (otherPartyBankCode != other.otherPartyBankCode) return false
@ -73,7 +77,7 @@ open class AccountTransaction(
override fun hashCode(): Int {
var result = account.hashCode()
result = 31 * result + amount.hashCode()
result = 31 * result + unparsedUsage.hashCode()
result = 31 * result + unparsedReference.hashCode()
result = 31 * result + bookingDate.hashCode()
result = 31 * result + (otherPartyName?.hashCode() ?: 0)
result = 31 * result + (otherPartyBankCode?.hashCode() ?: 0)
@ -85,7 +89,7 @@ open class AccountTransaction(
override fun toString(): String {
return "$valueDate $amount $otherPartyName: $unparsedUsage"
return "$valueDate $amount $otherPartyName: $unparsedReference"
}
}

View File

@ -2,10 +2,10 @@ package net.dankito.banking.fints.model
open class BankTransferData(
val creditorName: String,
val creditorIban: String,
val creditorBic: String,
val recipientName: String,
val recipientAccountId: String,
val recipientBankCode: String,
val amount: Money,
val usage: String,
val instantPayment: Boolean = false
val reference: String,
val realTimeTransfer: Boolean = false
)

View File

@ -306,7 +306,7 @@ open class ResponseParser(
parseBoolean(parametersDataElements[1]),
parseBoolean(parametersDataElements[2]),
if (segmentVersion >= 2) parseBoolean(parametersDataElements[3]) else false,
if (segmentVersion >= 3) parseInt(parametersDataElements[4]) else SepaAccountInfoParameters.CountReservedUsageLengthNotSet,
if (segmentVersion >= 3) parseInt(parametersDataElements[4]) else SepaAccountInfoParameters.CountReservedReferenceLengthNotSet,
parametersDataElements.subList(supportedSepaFormatsBeginIndex, parametersDataElements.size)
)
}

View File

@ -29,7 +29,7 @@ open class SepaAccountInfoParameters(
* Über diese Information legt das Kreditinstitut fest, ob bei SEPA-Zahlungsverkehrsinstrumenten die Verwendung
* von strukturierten Verwendungszweckinformationen (StructuredRemittanceInformation) erlaubt ist oder nicht.
*/
val structuredUsageAllowed: Boolean,
val structuredReferenceAllowed: Boolean,
/**
* Kennzeichen dafür, ob die Belegung des Feldes Maximale Anzahl Einträge im Kundenauftrag zugelassen ist.
@ -47,7 +47,7 @@ open class SepaAccountInfoParameters(
* reserviert sind. Diese Stellen dürfen vom Kundenprodukt nicht für andere Zwecke verwendet werden. Die Anzahl
* wird vom Ende des letzten SEPA-Elementes aus gezählt und darf den Wert 35 nicht überschreiten.
*/
val countReservedUsageLength: Int,
val countReservedReferenceLength: Int,
/**
* Dieses DE beschreibt Ort, Name und Version einer SEPA pain message als URN. Die korrekte Bezeichnung des URN
@ -69,7 +69,7 @@ open class SepaAccountInfoParameters(
companion object {
const val CountReservedUsageLengthNotSet = 0
const val CountReservedReferenceLengthNotSet = 0
}
}

View File

@ -51,7 +51,7 @@ open class Mt940AccountTransactionsParser(
account,
Money(mapAmount(transaction.statementLine), currency),
transaction.statementLine.isReversal,
transaction.information?.unparsedUsage ?: "",
transaction.information?.unparsedReference ?: "",
transaction.statementLine.bookingDate ?: statement.closingBalance.bookingDate,
transaction.information?.otherPartyName,
transaction.information?.otherPartyBankCode,
@ -70,10 +70,10 @@ open class Mt940AccountTransactionsParser(
transaction.information?.originatorsIdentificationCode,
transaction.information?.compensationAmount,
transaction.information?.originalAmount,
transaction.information?.sepaUsage,
transaction.information?.sepaReference,
transaction.information?.deviantOriginator,
transaction.information?.deviantRecipient,
transaction.information?.usageWithNoSpecialType,
transaction.information?.referenceWithNoSpecialType,
transaction.information?.primaNotaNumber,
transaction.information?.textKeySupplement,

View File

@ -56,19 +56,19 @@ open class Mt940Parser : IMt940Parser {
val AmountRegex = Regex("\\d+,\\d*")
val UsageTypeRegex = Regex("[A-Z]{4}\\+")
val ReferenceTypeRegex = Regex("[A-Z]{4}\\+")
const val EndToEndReferenceUsageKey = "EREF+"
const val CustomerReferenceUsageKey = "KREF+"
const val MandateReferenceUsageKey = "MREF+"
const val CreditorIdentifierUsageKey = "CRED+"
const val OriginatorsIdentificationCodeUsageKey = "DEBT+"
const val CompensationAmountUsageKey = "COAM+"
const val OriginalAmountUsageKey = "OAMT+"
const val SepaUsageUsageKey = "SVWZ+"
const val DeviantOriginatorUsageKey = "ABWA+"
const val DeviantRecipientUsageKey = "ABWE+"
const val EndToEndReferenceKey = "EREF+"
const val CustomerReferenceKey = "KREF+"
const val MandateReferenceKey = "MREF+"
const val CreditorIdentifierKey = "CRED+"
const val OriginatorsIdentificationCodeKey = "DEBT+"
const val CompensationAmountKey = "COAM+"
const val OriginalAmountKey = "OAMT+"
const val SepaReferenceKey = "SVWZ+"
const val DeviantOriginatorKey = "ABWA+"
const val DeviantRecipientKey = "ABWE+"
private val log = LoggerFactory.getLogger(Mt940Parser::class)
@ -295,7 +295,7 @@ open class Mt940Parser : IMt940Parser {
try {
val information = parseInformationToAccountOwner(informationToAccountOwnerString)
mapUsage(information)
mapReference(information)
return information
} catch (e: Exception) {
@ -310,7 +310,7 @@ open class Mt940Parser : IMt940Parser {
// see Finanzdatenformate p. 209 - 215
val geschaeftsvorfallCode = informationToAccountOwnerString.substring(0, 2) // TODO: may map
val usageParts = mutableListOf<String>()
val referenceParts = mutableListOf<String>()
val otherPartyName = StringBuilder()
var otherPartyBankCode: String? = null
var otherPartyAccountId: String? = null
@ -326,48 +326,48 @@ open class Mt940Parser : IMt940Parser {
when (fieldCode) {
0 -> bookingText = fieldValue
10 -> primaNotaNumber = fieldValue
in 20..29 -> usageParts.add(fieldValue)
in 20..29 -> referenceParts.add(fieldValue)
30 -> otherPartyBankCode = fieldValue
31 -> otherPartyAccountId = fieldValue
32, 33 -> otherPartyName.append(fieldValue)
34 -> textKeySupplement = fieldValue
in 60..63 -> usageParts.add(fieldValue)
in 60..63 -> referenceParts.add(fieldValue)
}
}
}
val usage = if (isFormattedUsage(usageParts)) joinUsageParts(usageParts)
else usageParts.joinToString(" ")
val reference = if (isFormattedReference(referenceParts)) joinReferenceParts(referenceParts)
else referenceParts.joinToString(" ")
val otherPartyNameString = if (otherPartyName.isEmpty()) null else otherPartyName.toString()
return InformationToAccountOwner(
usage, otherPartyNameString, otherPartyBankCode, otherPartyAccountId,
reference, otherPartyNameString, otherPartyBankCode, otherPartyAccountId,
bookingText, primaNotaNumber, textKeySupplement
)
}
protected open fun joinUsageParts(usageParts: List<String>): String {
val usage = StringBuilder()
protected open fun joinReferenceParts(referenceParts: List<String>): String {
val reference = StringBuilder()
usageParts.firstOrNull()?.let {
usage.append(it)
referenceParts.firstOrNull()?.let {
reference.append(it)
}
for (i in 1..usageParts.size - 1) {
val part = usageParts[i]
if (part.isNotEmpty() && part.first().isUpperCase && usageParts[i - 1].last().isUpperCase == false) {
usage.append(" ")
for (i in 1..referenceParts.size - 1) {
val part = referenceParts[i]
if (part.isNotEmpty() && part.first().isUpperCase && referenceParts[i - 1].last().isUpperCase == false) {
reference.append(" ")
}
usage.append(part)
reference.append(part)
}
return usage.toString()
return reference.toString()
}
protected open fun isFormattedUsage(usageParts: List<String>): Boolean {
return usageParts.any { UsageTypeRegex.find(it) != null }
protected open fun isFormattedReference(referenceParts: List<String>): Boolean {
return referenceParts.any { ReferenceTypeRegex.find(it) != null }
}
/**
@ -392,53 +392,54 @@ open class Mt940Parser : IMt940Parser {
*
* Weitere 4 Verwendungszwecke können zu den Feldschlüsseln 60 bis 63 eingestellt werden.
*/
protected open fun mapUsage(information: InformationToAccountOwner) {
val usageParts = getUsageParts(information.unparsedUsage)
protected open fun mapReference(information: InformationToAccountOwner) {
val referenceParts = getReferenceParts(information.unparsedReference)
usageParts.forEach { entry ->
setUsageLineValue(information, entry.key, entry.value)
referenceParts.forEach { entry ->
setReferenceLineValue(information, entry.key, entry.value)
}
}
open fun getUsageParts(unparsedUsage: String): Map<String, String> {
open fun getReferenceParts(unparsedReference: String): Map<String, String> {
var previousMatchType = ""
var previousMatchEnd = 0
val usageParts = mutableMapOf<String, String>()
val referenceParts = mutableMapOf<String, String>()
UsageTypeRegex.findAll(unparsedUsage).forEach { matchResult ->
ReferenceTypeRegex.findAll(unparsedReference).forEach { matchResult ->
if (previousMatchEnd > 0) {
val typeValue = unparsedUsage.substring(previousMatchEnd, matchResult.range.first)
val typeValue = unparsedReference.substring(previousMatchEnd, matchResult.range.first)
usageParts[previousMatchType] = typeValue
referenceParts[previousMatchType] = typeValue
}
previousMatchType = unparsedUsage.substring(matchResult.range)
previousMatchType = unparsedReference.substring(matchResult.range)
previousMatchEnd = matchResult.range.last + 1
}
if (previousMatchEnd > 0) {
val typeValue = unparsedUsage.substring(previousMatchEnd, unparsedUsage.length)
val typeValue = unparsedReference.substring(previousMatchEnd, unparsedReference.length)
usageParts[previousMatchType] = typeValue
referenceParts[previousMatchType] = typeValue
}
return usageParts
return referenceParts
}
protected open fun setUsageLineValue(information: InformationToAccountOwner, usageType: String, typeValue: String) {
when (usageType) {
EndToEndReferenceUsageKey -> information.endToEndReference = typeValue
CustomerReferenceUsageKey -> information.customerReference = typeValue
MandateReferenceUsageKey -> information.mandateReference = typeValue
CreditorIdentifierUsageKey -> information.creditorIdentifier = typeValue
OriginatorsIdentificationCodeUsageKey -> information.originatorsIdentificationCode = typeValue
CompensationAmountUsageKey -> information.compensationAmount = typeValue
OriginalAmountUsageKey -> information.originalAmount = typeValue
SepaUsageUsageKey -> information.sepaUsage = typeValue
DeviantOriginatorUsageKey -> information.deviantOriginator = typeValue
DeviantRecipientUsageKey -> information.deviantRecipient = typeValue
else -> information.usageWithNoSpecialType = typeValue
// TODO: there are more. See .pdf from Deutsche Bank
protected open fun setReferenceLineValue(information: InformationToAccountOwner, referenceType: String, typeValue: String) {
when (referenceType) {
EndToEndReferenceKey -> information.endToEndReference = typeValue
CustomerReferenceKey -> information.customerReference = typeValue
MandateReferenceKey -> information.mandateReference = typeValue
CreditorIdentifierKey -> information.creditorIdentifier = typeValue
OriginatorsIdentificationCodeKey -> information.originatorsIdentificationCode = typeValue
CompensationAmountKey -> information.compensationAmount = typeValue
OriginalAmountKey -> information.originalAmount = typeValue
SepaReferenceKey -> information.sepaReference = typeValue
DeviantOriginatorKey -> information.deviantOriginator = typeValue
DeviantRecipientKey -> information.deviantRecipient = typeValue
else -> information.referenceWithNoSpecialType = typeValue
}
}

View File

@ -2,7 +2,7 @@ package net.dankito.banking.fints.transactions.mt940.model
open class InformationToAccountOwner(
val unparsedUsage: String,
val unparsedReference: String,
val otherPartyName: String?,
val otherPartyBankCode: String?,
val otherPartyAccountId: String?,
@ -25,17 +25,17 @@ open class InformationToAccountOwner(
var originalAmount: String? = null
var sepaUsage: String? = null
var sepaReference: String? = null
var deviantOriginator: String? = null
var deviantRecipient: String? = null
var usageWithNoSpecialType: String? = null
var referenceWithNoSpecialType: String? = null
override fun toString(): String {
return "$otherPartyName $unparsedUsage"
return "$otherPartyName $unparsedReference"
}
}

View File

@ -15,11 +15,11 @@ class SepaBankTransferBaseTest {
val debitorName = "Nelson Mandela"
val debitorIban = "ZA123456780987654321"
val debitorBic = "ABCDZAEFXXX"
val creditorName = "Mahatma Gandhi"
val creditorIban = "IN123456780987654321"
val creditorBic = "ABCDINEFXXX"
val recipientName = "Mahatma Gandhi"
val recipientIban = "IN123456780987654321"
val recipientBic = "ABCDINEFXXX"
val amount = Amount("1234,56")
val usage = "What should Mahatma Gandhi want with money?"
val reference = "What should Mahatma Gandhi want with money?"
}
@ -33,7 +33,7 @@ class SepaBankTransferBaseTest {
debitorName,
AccountData("", null, 0, "", debitorIban, "", null, null, "", null, null, listOf()),
debitorBic,
BankTransferData(creditorName, creditorIban, creditorBic, Money(amount, "EUR"), usage)
BankTransferData(recipientName, recipientIban, recipientBic, Money(amount, "EUR"), reference)
)
@ -42,8 +42,8 @@ class SepaBankTransferBaseTest {
// then
expect(result).contains(debitorName, debitorIban, debitorBic, creditorName, creditorIban, creditorBic,
amount.toString().replace(',', '.'), usage, "urn?:iso?:std?:iso?:20022?:tech?:xsd?:pain.001.001.03")
expect(result).contains(debitorName, debitorIban, debitorBic, recipientName, recipientIban, recipientBic,
amount.toString().replace(',', '.'), reference, "urn?:iso?:std?:iso?:20022?:tech?:xsd?:pain.001.001.03")
}
@Test
@ -55,7 +55,7 @@ class SepaBankTransferBaseTest {
debitorName,
AccountData("", null, 0, "", debitorIban, "", null, null, "", null, null, listOf()),
debitorBic,
BankTransferData(creditorName, creditorIban, creditorBic, Money(amount, "EUR"), usage)
BankTransferData(recipientName, recipientIban, recipientBic, Money(amount, "EUR"), reference)
)
@ -64,8 +64,8 @@ class SepaBankTransferBaseTest {
// then
expect(result).contains(debitorName, debitorIban, debitorBic, creditorName, creditorIban, creditorBic,
amount.toString().replace(',', '.'), usage, "urn?:iso?:std?:iso?:20022?:tech?:xsd?:pain.001.003.03")
expect(result).contains(debitorName, debitorIban, debitorBic, recipientName, recipientIban, recipientBic,
amount.toString().replace(',', '.'), reference, "urn?:iso?:std?:iso?:20022?:tech?:xsd?:pain.001.003.03")
}
}

View File

@ -558,9 +558,9 @@ class ResponseParserTest : FinTsTestBase() {
for (segment in sepaAccountInfoParameters) {
expect(segment.retrieveSingleAccountAllowed).isTrue()
expect(segment.nationalAccountRelationshipAllowed).isFalse()
expect(segment.structuredUsageAllowed).isFalse()
expect(segment.structuredReferenceAllowed).isFalse()
expect(segment.settingMaxAllowedEntriesAllowed).isFalse()
expect(segment.countReservedUsageLength).toBe(SepaAccountInfoParameters.CountReservedUsageLengthNotSet)
expect(segment.countReservedReferenceLength).toBe(SepaAccountInfoParameters.CountReservedReferenceLengthNotSet)
expect(segment.supportedSepaFormats).containsExactly(
"sepade.pain.001.001.02.xsd",
"sepade.pain.001.002.02.xsd",

View File

@ -269,9 +269,9 @@ class Mt940ParserTest : FinTsTestBase() {
result.flatMap { it.transactions }.forEach { transaction ->
expect(transaction.information).notToBeNull()
expect(transaction.information?.sepaUsage).notToBeNull()
expect(transaction.information?.sepaReference).notToBeNull()
if (transaction.information?.unparsedUsage?.contains("KREF+") == true) {
if (transaction.information?.unparsedReference?.contains("KREF+") == true) {
expect(transaction.information?.customerReference).notToBeNull()
}
}

View File

@ -44,7 +44,7 @@ open class FinTsClientTestBase {
val Password = "<your PIN (Online-Banking Passwort) here>"
val DateTimeFormatForUniqueBankTransferUsage = DateFormatter("yyyy-MM-dd'T'HH:mm:ss.SSS")
val DateTimeFormatForUniqueBankTransferReference = DateFormatter("yyyy-MM-dd'T'HH:mm:ss.SSS")
}
@ -234,7 +234,7 @@ open class FinTsClientTestBase {
// transfer 1 cent to yourself. Transferring money to oneself also doesn't require to enter a TAN according to PSD2
val BankTransferData = BankTransferData(Bank.customerName, account?.iban!!, Bank.bic, Money(Amount("0,01"), "EUR"),
"${DateTimeFormatForUniqueBankTransferUsage.format(Date())} Test transaction ${UUID.random()}")
"${DateTimeFormatForUniqueBankTransferReference.format(Date())} Test transaction ${UUID.random()}")
// when

View File

@ -19,7 +19,7 @@ class LuceneConfig {
const val BookingDateFieldName = "booking_date"
const val DateSortFieldName = "value_date_sort"
const val UsageFieldName = "usage"
const val ReferenceFieldName = "reference"
const val BookingTextFieldName = "booking_text"

View File

@ -13,7 +13,7 @@ import net.dankito.banking.LuceneConfig.Companion.IdFieldName
import net.dankito.banking.LuceneConfig.Companion.OtherPartyAccountIdFieldName
import net.dankito.banking.LuceneConfig.Companion.OtherPartyBankCodeFieldName
import net.dankito.banking.LuceneConfig.Companion.OtherPartyNameFieldName
import net.dankito.banking.LuceneConfig.Companion.UsageFieldName
import net.dankito.banking.LuceneConfig.Companion.ReferenceFieldName
import net.dankito.banking.ui.model.*
import net.dankito.banking.util.ISerializer
import net.dankito.banking.util.JacksonJsonSerializer
@ -62,7 +62,7 @@ open class LuceneBankingPersistence(
return listOf(
fields.keywordField(BankAccountIdFieldName, account.technicalId),
fields.nullableFullTextSearchField(OtherPartyNameFieldName, transaction.otherPartyName, true),
fields.fullTextSearchField(UsageFieldName, transaction.usage, true),
fields.fullTextSearchField(ReferenceFieldName, transaction.reference, true),
fields.nullableFullTextSearchField(BookingTextFieldName, transaction.bookingText, true),
fields.nullableStoredField(OtherPartyBankCodeFieldName, transaction.otherPartyBankCode),

View File

@ -12,14 +12,14 @@ import net.dankito.utils.lucene.search.Searcher
import java.io.File
open class LuceneRemitteeSearcher(indexFolder: File) : IRemitteeSearcher {
open class LuceneTransactionPartySearcher(indexFolder: File) : ITransactionPartySearcher {
companion object {
private val properties = listOf(
PropertyDescription(PropertyType.NullableString, OtherPartyNameFieldName, Remittee::name),
PropertyDescription(PropertyType.NullableString, OtherPartyBankCodeFieldName, Remittee::bic),
PropertyDescription(PropertyType.NullableString, OtherPartyAccountIdFieldName, Remittee::iban)
PropertyDescription(PropertyType.NullableString, OtherPartyNameFieldName, TransactionParty::name),
PropertyDescription(PropertyType.NullableString, OtherPartyBankCodeFieldName, TransactionParty::bic),
PropertyDescription(PropertyType.NullableString, OtherPartyAccountIdFieldName, TransactionParty::iban)
)
}
@ -30,16 +30,16 @@ open class LuceneRemitteeSearcher(indexFolder: File) : IRemitteeSearcher {
protected val searcher = Searcher(LuceneConfig.getAccountTransactionsIndexFolder(indexFolder))
override fun findRemittees(query: String): List<Remittee> {
override fun findTransactionParty(query: String): List<TransactionParty> {
val luceneQuery = queries.createQueriesForSingleTerms(query.toLowerCase()) { singleTerm ->
listOf(
queries.fulltextQuery(OtherPartyNameFieldName, singleTerm)
)
}
return searcher.searchAndMap(MappedSearchConfig(luceneQuery, Remittee::class.java, properties))
.toSet() // don't display same Remittee multiple times
.filterNot { it.iban.isNullOrBlank() || it.bic.isNullOrBlank() } // e.g. comdirect doesn't supply other party's IBAN and BIC -> filter these as they have no value for auto-entering a remittee's IBAN and BIC
return searcher.searchAndMap(MappedSearchConfig(luceneQuery, TransactionParty::class.java, properties))
.toSet() // don't display same transaction party multiple times
.filterNot { it.iban.isNullOrBlank() || it.bic.isNullOrBlank() } // e.g. comdirect doesn't supply other party's IBAN and BIC -> filter these as they have no value for auto-entering a transaction party's IBAN and BIC
}
}

View File

@ -19,7 +19,7 @@ import java.util.*
import java.util.concurrent.ThreadLocalRandom
class LuceneRemitteeSearcherTest {
class LuceneTransactionPartySearcherTest {
companion object {
@ -49,7 +49,7 @@ class LuceneRemitteeSearcherTest {
private val bankingPersistence = LuceneBankingPersistence(indexFolder, databaseFolder)
private val underTest = LuceneRemitteeSearcher(indexFolder)
private val underTest = LuceneTransactionPartySearcher(indexFolder)
@Before
@ -68,12 +68,12 @@ class LuceneRemitteeSearcherTest {
@Test
fun findRemittees_ByFullName() {
fun findTransactionParty_ByFullName() {
// given
val query = OtherPartyName
val before = underTest.findRemittees(query)
val before = underTest.findTransactionParty(query)
assertThat(before).isEmpty()
bankingPersistence.saveOrUpdateAccountTransactions(bankAccountMock, listOf(
@ -84,7 +84,7 @@ class LuceneRemitteeSearcherTest {
// when
val result = underTest.findRemittees(query)
val result = underTest.findTransactionParty(query)
// then
@ -95,12 +95,12 @@ class LuceneRemitteeSearcherTest {
}
@Test
fun findRemittees_ByPartialName() {
fun findTransactionParty_ByPartialName() {
// given
val query = "gand"
val before = underTest.findRemittees(query)
val before = underTest.findTransactionParty(query)
assertThat(before).isEmpty()
bankingPersistence.saveOrUpdateAccountTransactions(bankAccountMock, listOf(
@ -111,7 +111,7 @@ class LuceneRemitteeSearcherTest {
// when
val result = underTest.findRemittees(query)
val result = underTest.findTransactionParty(query)
// then
@ -122,13 +122,13 @@ class LuceneRemitteeSearcherTest {
}
@Test
fun findRemittees_SimilarNames() {
fun findTransactionParty_SimilarNames() {
// given
val query = "gand"
val secondOtherPartyName = "Gandalf"
val before = underTest.findRemittees(query)
val before = underTest.findTransactionParty(query)
assertThat(before).isEmpty()
bankingPersistence.saveOrUpdateAccountTransactions(bankAccountMock, listOf(
@ -139,7 +139,7 @@ class LuceneRemitteeSearcherTest {
// when
val result = underTest.findRemittees(query)
val result = underTest.findTransactionParty(query)
// then
@ -148,12 +148,12 @@ class LuceneRemitteeSearcherTest {
}
@Test
fun findRemittees_DuplicateEntries() {
fun findTransactionParty_DuplicateEntries() {
// given
val query = OtherPartyName
val before = underTest.findRemittees(query)
val before = underTest.findTransactionParty(query)
assertThat(before).isEmpty()
bankingPersistence.saveOrUpdateAccountTransactions(bankAccountMock, listOf(
@ -166,7 +166,7 @@ class LuceneRemitteeSearcherTest {
// when
val result = underTest.findRemittees(query)
val result = underTest.findTransactionParty(query)
// then
@ -177,12 +177,12 @@ class LuceneRemitteeSearcherTest {
}
@Test
fun findRemittees_OtherName() {
fun findTransactionParty_OtherName() {
// given
val query = "Mandela"
val before = underTest.findRemittees(query)
val before = underTest.findTransactionParty(query)
assertThat(before).isEmpty()
bankingPersistence.saveOrUpdateAccountTransactions(bankAccountMock, listOf(
@ -193,7 +193,7 @@ class LuceneRemitteeSearcherTest {
// when
val result = underTest.findRemittees(query)
val result = underTest.findTransactionParty(query)
// then
@ -203,17 +203,17 @@ class LuceneRemitteeSearcherTest {
private fun createTransaction(bankAccount: BankAccount = bankAccountMock, bookingDate: String, amount: BigDecimal = randomBigDecimal(),
otherPartyName: String = randomString(), otherPartyBankCode: String = randomString(),
otherPartyAccountId: String = randomString(), usage: String = randomString()): AccountTransaction {
otherPartyAccountId: String = randomString(), reference: String = randomString()): AccountTransaction {
return createTransaction(bankAccount, dateFormat.parse(bookingDate), amount, otherPartyName,
otherPartyBankCode, otherPartyAccountId, usage)
otherPartyBankCode, otherPartyAccountId, reference)
}
private fun createTransaction(bankAccount: BankAccount = bankAccountMock, bookingDate: Date = randomDate(), amount: BigDecimal = randomBigDecimal(),
otherPartyName: String = randomString(), otherPartyBankCode: String = randomString(),
otherPartyAccountId: String = randomString(), usage: String = randomString()): AccountTransaction {
otherPartyAccountId: String = randomString(), reference: String = randomString()): AccountTransaction {
return AccountTransaction(bankAccount, amount.toBigDecimal(), "EUR", usage, bookingDate.toDate(), otherPartyName, otherPartyBankCode, otherPartyAccountId, null, bookingDate.toDate())
return AccountTransaction(bankAccount, amount.toBigDecimal(), "EUR", reference, bookingDate.toDate(), otherPartyName, otherPartyBankCode, otherPartyAccountId, null, bookingDate.toDate())
}
private fun randomString(): String {

View File

@ -5,8 +5,8 @@ import androidx.room.Room
import net.dankito.banking.persistence.dao.BaseDao
import net.dankito.banking.persistence.dao.saveOrUpdate
import net.dankito.banking.persistence.model.*
import net.dankito.banking.search.IRemitteeSearcher
import net.dankito.banking.search.Remittee
import net.dankito.banking.search.ITransactionPartySearcher
import net.dankito.banking.search.TransactionParty
import net.dankito.banking.ui.model.IAccountTransaction
import net.dankito.banking.ui.model.TypedBankAccount
import net.dankito.banking.ui.model.TypedBankData
@ -18,7 +18,7 @@ import net.sqlcipher.database.SQLiteDatabase
import net.sqlcipher.database.SupportFactory
open class RoomBankingPersistence(applicationContext: Context, password: String? = null) : IBankingPersistence, IRemitteeSearcher {
open class RoomBankingPersistence(applicationContext: Context, password: String? = null) : IBankingPersistence, ITransactionPartySearcher {
protected val db: BankingDatabase
@ -154,11 +154,11 @@ open class RoomBankingPersistence(applicationContext: Context, password: String?
}
override fun findRemittees(query: String): List<Remittee> {
return db.accountTransactionDao().findRemittees(query)
.toSet() // don't display same Remittee multiple times
override fun findTransactionParty(query: String): List<TransactionParty> {
return db.accountTransactionDao().findTransactionParty(query)
.toSet() // don't display same transaction party multiple times
.filterNot { it.bankCode.isNullOrBlank() || it.accountId.isNullOrBlank() }
.map { Remittee(it.name, it.accountId, it.bankCode) }
.map { TransactionParty(it.name, it.accountId, it.bankCode) }
}
}

View File

@ -3,7 +3,7 @@ package net.dankito.banking.persistence.dao
import androidx.room.Dao
import androidx.room.Query
import net.dankito.banking.persistence.model.AccountTransaction
import net.dankito.banking.persistence.model.Remittee
import net.dankito.banking.persistence.model.TransactionParty
@Dao
@ -13,6 +13,6 @@ interface AccountTransactionDao : BaseDao<AccountTransaction> {
fun getAll(): List<AccountTransaction>
@Query("SELECT otherPartyName, otherPartyBankCode, otherPartyAccountId FROM AccountTransaction WHERE otherPartyName LIKE '%' || :query || '%'")
fun findRemittees(query: String): List<Remittee>
fun findTransactionParty(query: String): List<TransactionParty>
}

View File

@ -15,7 +15,7 @@ open class AccountTransaction(
override var amount: BigDecimal,
override var currency: String,
override var unparsedUsage: String,
override var unparsedReference: String,
override var bookingDate: Date,
override var otherPartyName: String?,
override var otherPartyBankCode: String?,
@ -34,10 +34,10 @@ open class AccountTransaction(
override var originatorsIdentificationCode: String?,
override var compensationAmount: String?,
override var originalAmount: String?,
override var sepaUsage: String?,
override var sepaReference: String?,
override var deviantOriginator: String?,
override var deviantRecipient: String?,
override var usageWithNoSpecialType: String?,
override var referenceWithNoSpecialType: String?,
override var primaNotaNumber: String?,
override var textKeySupplement: String?,
@ -56,15 +56,15 @@ open class AccountTransaction(
/* convenience constructors for languages not supporting default values */
constructor(account: BankAccount, otherPartyName: String?, unparsedUsage: String, amount: BigDecimal, valueDate: Date, bookingText: String?)
: this(account, amount, "EUR", unparsedUsage, valueDate,
constructor(account: BankAccount, otherPartyName: String?, unparsedReference: String, amount: BigDecimal, valueDate: Date, bookingText: String?)
: this(account, amount, "EUR", unparsedReference, valueDate,
otherPartyName, null, null, bookingText, valueDate)
constructor(account: BankAccount, amount: BigDecimal, currency: String, unparsedUsage: String, bookingDate: Date,
constructor(account: BankAccount, amount: BigDecimal, currency: String, unparsedReference: String, bookingDate: Date,
otherPartyName: String?, otherPartyBankCode: String?, otherPartyAccountId: String?,
bookingText: String?, valueDate: Date)
: this(account, amount, currency, unparsedUsage, bookingDate,
: this(account, amount, currency, unparsedReference, bookingDate,
otherPartyName, otherPartyBankCode, otherPartyAccountId, bookingText, valueDate,
0, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, "", "", null, null, "", null)

View File

@ -30,7 +30,7 @@ open class BankAccount(
override var supportsRetrievingAccountTransactions: Boolean = false,
override var supportsRetrievingBalance: Boolean = false,
override var supportsTransferringMoney: Boolean = false,
override var supportsInstantPaymentMoneyTransfer: Boolean = false,
override var supportsRealTimeTransfer: Boolean = false,
@Ignore
override var bookedTransactions: List<IAccountTransaction> = listOf(),

View File

@ -26,7 +26,7 @@ open class RoomModelCreator : IModelCreator {
account: TypedBankAccount,
amount: BigDecimal,
currency: String,
unparsedUsage: String,
unparsedReference: String,
bookingDate: Date,
otherPartyName: String?,
otherPartyBankCode: String?,
@ -44,10 +44,10 @@ open class RoomModelCreator : IModelCreator {
originatorsIdentificationCode: String?,
compensationAmount: String?,
originalAmount: String?,
sepaUsage: String?,
sepaReference: String?,
deviantOriginator: String?,
deviantRecipient: String?,
usageWithNoSpecialType: String?,
referenceWithNoSpecialType: String?,
primaNotaNumber: String?,
textKeySupplement: String?,
currencyType: String?,
@ -58,10 +58,10 @@ open class RoomModelCreator : IModelCreator {
transactionReferenceNumber: String,
relatedReferenceNumber: String?
): IAccountTransaction {
return AccountTransaction(account as BankAccount, amount, currency, unparsedUsage, bookingDate, otherPartyName, otherPartyBankCode, otherPartyAccountId,
return AccountTransaction(account as BankAccount, amount, currency, unparsedReference, bookingDate, otherPartyName, otherPartyBankCode, otherPartyAccountId,
bookingText, valueDate, statementNumber, sequenceNumber, openingBalance, closingBalance, endToEndReference, customerReference, mandateReference,
creditorIdentifier, originatorsIdentificationCode, compensationAmount, originalAmount, sepaUsage, deviantOriginator, deviantRecipient,
usageWithNoSpecialType, primaNotaNumber, textKeySupplement, currencyType, bookingKey, referenceForTheAccountOwner,
creditorIdentifier, originatorsIdentificationCode, compensationAmount, originalAmount, sepaReference, deviantOriginator, deviantRecipient,
referenceWithNoSpecialType, primaNotaNumber, textKeySupplement, currencyType, bookingKey, referenceForTheAccountOwner,
referenceOfTheAccountServicingInstitution, supplementaryDetails, transactionReferenceNumber, relatedReferenceNumber)
}

View File

@ -3,7 +3,7 @@ package net.dankito.banking.persistence.model
import androidx.room.ColumnInfo
data class Remittee(
data class TransactionParty(
@ColumnInfo(name = "otherPartyName") val name: String,
@ColumnInfo(name = "otherPartyBankCode") val bankCode: String?,

View File

@ -28,7 +28,7 @@ open class EntitiesModelCreator : IModelCreator {
account: TypedBankAccount,
amount: BigDecimal,
currency: String,
unparsedUsage: String,
unparsedReference: String,
bookingDate: Date,
otherPartyName: String?,
otherPartyBankCode: String?,
@ -46,10 +46,10 @@ open class EntitiesModelCreator : IModelCreator {
originatorsIdentificationCode: String?,
compensationAmount: String?,
originalAmount: String?,
sepaUsage: String?,
sepaReference: String?,
deviantOriginator: String?,
deviantRecipient: String?,
usageWithNoSpecialType: String?,
referenceWithNoSpecialType: String?,
primaNotaNumber: String?,
textKeySupplement: String?,
currencyType: String?,
@ -61,11 +61,11 @@ open class EntitiesModelCreator : IModelCreator {
relatedReferenceNumber: String?
) : IAccountTransaction {
return AccountTransactionEntity(account as BankAccountEntity, amount, currency, unparsedUsage, bookingDate,
return AccountTransactionEntity(account as BankAccountEntity, amount, currency, unparsedReference, bookingDate,
otherPartyName, otherPartyBankCode, otherPartyAccountId, bookingText, valueDate, statementNumber, sequenceNumber,
openingBalance, closingBalance, endToEndReference, customerReference, mandateReference, creditorIdentifier,
originatorsIdentificationCode, compensationAmount, originalAmount, sepaUsage, deviantOriginator, deviantRecipient,
usageWithNoSpecialType, primaNotaNumber, textKeySupplement, currencyType, bookingKey, referenceForTheAccountOwner,
originatorsIdentificationCode, compensationAmount, originalAmount, sepaReference, deviantOriginator, deviantRecipient,
referenceWithNoSpecialType, primaNotaNumber, textKeySupplement, currencyType, bookingKey, referenceForTheAccountOwner,
referenceOfTheAccountServicingInstitution, supplementaryDetails, transactionReferenceNumber, relatedReferenceNumber)
}

View File

@ -14,7 +14,7 @@ open class AccountTransactionEntity(
override var account: BankAccountEntity,
override var amount: BigDecimal,
override var currency: String,
override var unparsedUsage: String,
override var unparsedReference: String,
override var bookingDate: Date,
override var otherPartyName: String?,
override var otherPartyBankCode: String?,
@ -33,10 +33,10 @@ open class AccountTransactionEntity(
override var originatorsIdentificationCode: String?,
override var compensationAmount: String?,
override var originalAmount: String?,
override var sepaUsage: String?,
override var sepaReference: String?,
override var deviantOriginator: String?,
override var deviantRecipient: String?,
override var usageWithNoSpecialType: String?,
override var referenceWithNoSpecialType: String?,
override var primaNotaNumber: String?,
override var textKeySupplement: String?,
@ -56,8 +56,8 @@ open class AccountTransactionEntity(
-1, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, "", "", null,
null, "", null)
constructor(account: BankAccountEntity, otherPartyName: String?, unparsedUsage: String, amount: BigDecimal, valueDate: Date, bookingText: String?)
: this(account, amount, "EUR", unparsedUsage, valueDate, otherPartyName, null, null, bookingText, valueDate, 0, null, null, null,
constructor(account: BankAccountEntity, otherPartyName: String?, unparsedReference: String, amount: BigDecimal, valueDate: Date, bookingText: String?)
: this(account, amount, "EUR", unparsedReference, valueDate, otherPartyName, null, null, bookingText, valueDate, 0, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null,
null, "", "", null, null, "", null)

View File

@ -27,7 +27,7 @@ open class BankAccountEntity(
override var supportsRetrievingAccountTransactions: Boolean = false,
override var supportsRetrievingBalance: Boolean = false,
override var supportsTransferringMoney: Boolean = false,
override var supportsInstantPaymentMoneyTransfer: Boolean = false,
override var supportsRealTimeTransfer: Boolean = false,
override var bookedTransactions: List<AccountTransactionEntity> = listOf(),
override var unbookedTransactions: List<Any> = listOf(),
override var technicalId: String = UUID.random(),

View File

@ -147,7 +147,7 @@ class BankingPersistenceJsonTest {
}
private fun createTransaction(transactionIndex: Int, account: BankAccountEntity): AccountTransactionEntity {
return AccountTransactionEntity(account, "OtherParty_$transactionIndex", "Usage_$transactionIndex", BigDecimal(transactionIndex.toDouble()), createDate(), null)
return AccountTransactionEntity(account, "OtherParty_$transactionIndex", "Reference_$transactionIndex", BigDecimal(transactionIndex.toDouble()), createDate(), null)
}
private fun createDate(): Date {
@ -233,7 +233,7 @@ class BankingPersistenceJsonTest {
assertThat(deserializedTransaction.account.technicalId).isEqualTo(transaction.account.technicalId)
assertThat(deserializedTransaction.otherPartyName).isEqualTo(transaction.otherPartyName)
assertThat(deserializedTransaction.unparsedUsage).isEqualTo(transaction.unparsedUsage)
assertThat(deserializedTransaction.unparsedReference).isEqualTo(transaction.unparsedReference)
assertThat(deserializedTransaction.amount).isEqualTo(transaction.amount)
assertThat(deserializedTransaction.valueDate).isEqualTo(transaction.valueDate)
}

View File

@ -40,7 +40,7 @@ open class AccountTransactionAdapter(protected val presenter: BankingPresenter)
val label = if (item.showOtherPartyName) item.otherPartyName else item.bookingText
viewHolder.txtvwTransactionLabel.text = label ?: item.bookingText ?: ""
viewHolder.txtvwUsage.text = item.usage
viewHolder.txtvwReference.text = item.reference
viewHolder.txtvwAmount.showAmount(presenter, item.amount)
@ -66,12 +66,12 @@ open class AccountTransactionAdapter(protected val presenter: BankingPresenter)
menu.findItem(R.id.mnitmNewTransferWithSameData)?.isVisible = canCreateMoneyTransferFrom
menu.findItem(R.id.mnitmNewTransferToSameRemittee)?.let { mnitmShowTransferMoneyDialog ->
menu.findItem(R.id.mnitmNewTransferToSameTransactionParty)?.let { mnitmShowTransferMoneyDialog ->
mnitmShowTransferMoneyDialog.isVisible = canCreateMoneyTransferFrom
val remitteeName = selectedTransaction?.otherPartyName ?: ""
val recipientName = selectedTransaction?.otherPartyName ?: ""
mnitmShowTransferMoneyDialog.title = view.context.getString(R.string.fragment_home_transfer_money_to, remitteeName)
mnitmShowTransferMoneyDialog.title = view.context.getString(R.string.fragment_home_transfer_money_to, recipientName)
}
}

View File

@ -0,0 +1,33 @@
package net.dankito.banking.ui.android.adapter
import android.view.View
import net.dankito.banking.ui.android.R
import net.dankito.banking.ui.android.adapter.viewholder.RecipientViewHolder
import net.dankito.banking.search.TransactionParty
import net.dankito.utils.android.ui.adapter.ListRecyclerAdapter
open class RecipientListAdapter(protected val itemClicked: ((TransactionParty) -> Unit)? = null) : ListRecyclerAdapter<TransactionParty, RecipientViewHolder>() {
override fun getListItemLayoutId() = R.layout.list_item_recipient
override fun createViewHolder(itemView: View): RecipientViewHolder {
return RecipientViewHolder(itemView)
}
override fun bindItemToView(viewHolder: RecipientViewHolder, item: TransactionParty) {
viewHolder.txtvwRecipientName.text = item.name
viewHolder.txtvwRecipientBankName.text = item.bankName
viewHolder.txtvwRecipientBankName.visibility = if (item.bankName.isNullOrBlank()) View.GONE else View.VISIBLE
viewHolder.txtvwRecipientAccountId.text = item.iban
viewHolder.txtvwRecipientBankCode.text = item.bic
viewHolder.itemView.setOnClickListener {
itemClicked?.invoke(item)
}
}
}

View File

@ -1,33 +0,0 @@
package net.dankito.banking.ui.android.adapter
import android.view.View
import net.dankito.banking.ui.android.R
import net.dankito.banking.ui.android.adapter.viewholder.RemitteeViewHolder
import net.dankito.banking.search.Remittee
import net.dankito.utils.android.ui.adapter.ListRecyclerAdapter
open class RemitteeListAdapter(protected val itemClicked: ((Remittee) -> Unit)? = null) : ListRecyclerAdapter<Remittee, RemitteeViewHolder>() {
override fun getListItemLayoutId() = R.layout.list_item_remittee
override fun createViewHolder(itemView: View): RemitteeViewHolder {
return RemitteeViewHolder(itemView)
}
override fun bindItemToView(viewHolder: RemitteeViewHolder, item: Remittee) {
viewHolder.txtvwRemitteeName.text = item.name
viewHolder.txtvwRemitteeBankName.text = item.bankName
viewHolder.txtvwRemitteeBankName.visibility = if (item.bankName.isNullOrBlank()) View.GONE else View.VISIBLE
viewHolder.txtvwRemitteeAccountId.text = item.iban
viewHolder.txtvwRemitteeBankCode.text = item.bic
viewHolder.itemView.setOnClickListener {
itemClicked?.invoke(item)
}
}
}

View File

@ -0,0 +1,38 @@
package net.dankito.banking.ui.android.adapter.presenter
import android.content.Context
import androidx.recyclerview.widget.RecyclerView
import com.otaliastudios.autocomplete.RecyclerViewPresenter
import kotlinx.coroutines.*
import net.dankito.banking.ui.android.adapter.RecipientListAdapter
import net.dankito.banking.search.TransactionParty
import net.dankito.banking.ui.android.extensions.addHorizontalItemDivider
import net.dankito.banking.ui.presenter.BankingPresenter
open class RecipientPresenter(protected val bankingPresenter: BankingPresenter, context: Context) : RecyclerViewPresenter<TransactionParty>(context) {
protected val adapter = RecipientListAdapter { dispatchClick(it) }
protected var lastSearchRecipientJob: Job? = null
override fun instantiateAdapter(): RecyclerView.Adapter<*> {
recyclerView?.addHorizontalItemDivider()
return adapter
}
override fun onQuery(query: CharSequence?) {
lastSearchRecipientJob?.cancel()
lastSearchRecipientJob = GlobalScope.launch(Dispatchers.IO) {
val potentialRecipients = bankingPresenter.findRecipientsForName(query?.toString() ?: "")
withContext(Dispatchers.Main) {
adapter.items = potentialRecipients
}
}
}
}

View File

@ -1,38 +0,0 @@
package net.dankito.banking.ui.android.adapter.presenter
import android.content.Context
import androidx.recyclerview.widget.RecyclerView
import com.otaliastudios.autocomplete.RecyclerViewPresenter
import kotlinx.coroutines.*
import net.dankito.banking.ui.android.adapter.RemitteeListAdapter
import net.dankito.banking.search.Remittee
import net.dankito.banking.ui.android.extensions.addHorizontalItemDivider
import net.dankito.banking.ui.presenter.BankingPresenter
open class RemitteePresenter(protected val bankingPresenter: BankingPresenter, context: Context) : RecyclerViewPresenter<Remittee>(context) {
protected val adapter = RemitteeListAdapter { dispatchClick(it) }
protected var lastSearchRemitteeJob: Job? = null
override fun instantiateAdapter(): RecyclerView.Adapter<*> {
recyclerView?.addHorizontalItemDivider()
return adapter
}
override fun onQuery(query: CharSequence?) {
lastSearchRemitteeJob?.cancel()
lastSearchRemitteeJob = GlobalScope.launch(Dispatchers.IO) {
val potentialRemittees = bankingPresenter.findRemitteesForName(query?.toString() ?: "")
withContext(Dispatchers.Main) {
adapter.items = potentialRemittees
}
}
}
}

View File

@ -13,7 +13,7 @@ open class AccountTransactionViewHolder(itemView: View) : RecyclerView.ViewHolde
val txtvwTransactionLabel: TextView = itemView.txtvwTransactionLabel
val txtvwUsage: TextView = itemView.txtvwUsage
val txtvwReference: TextView = itemView.txtvwReference
val txtvwAmount: TextView = itemView.txtvwAmount

View File

@ -0,0 +1,19 @@
package net.dankito.banking.ui.android.adapter.viewholder
import android.view.View
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import kotlinx.android.synthetic.main.list_item_recipient.view.*
open class RecipientViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val txtvwRecipientName: TextView = itemView.txtvwRecipientName
val txtvwRecipientBankName: TextView = itemView.txtvwRecipientBankName
val txtvwRecipientAccountId: TextView = itemView.txtvwRecipientAccountId
val txtvwRecipientBankCode: TextView = itemView.txtvwRecipientBankCode
}

View File

@ -1,19 +0,0 @@
package net.dankito.banking.ui.android.adapter.viewholder
import android.view.View
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import kotlinx.android.synthetic.main.list_item_remittee.view.*
open class RemitteeViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val txtvwRemitteeName: TextView = itemView.txtvwRemitteeName
val txtvwRemitteeBankName: TextView = itemView.txtvwRemitteeBankName
val txtvwRemitteeAccountId: TextView = itemView.txtvwRemitteeAccountId
val txtvwRemitteeBankCode: TextView = itemView.txtvwRemitteeBankCode
}

View File

@ -9,8 +9,7 @@ import net.dankito.banking.ui.android.RouterAndroid
import net.dankito.banking.ui.android.util.CurrentActivityTracker
import net.dankito.banking.fints4kBankingClientCreator
import net.dankito.banking.persistence.IBankingPersistence
import net.dankito.banking.search.IRemitteeSearcher
import net.dankito.banking.search.LuceneRemitteeSearcher
import net.dankito.banking.search.ITransactionPartySearcher
import net.dankito.banking.ui.IBankingClientCreator
import net.dankito.banking.ui.IRouter
import net.dankito.banking.ui.presenter.BankingPresenter
@ -88,11 +87,11 @@ class BankingModule(private val applicationContext: Context) {
@Singleton
fun provideBankingPresenter(bankingClientCreator: IBankingClientCreator, bankFinder: IBankFinder,
@Named(DataFolderKey) dataFolder: File,
persister: IBankingPersistence, remitteeSearcher: IRemitteeSearcher, bankIconFinder: IBankIconFinder,
persister: IBankingPersistence, transactionPartySearcher: ITransactionPartySearcher, bankIconFinder: IBankIconFinder,
textExtractorRegistry: ITextExtractorRegistry, router: IRouter, invoiceDataExtractor: IInvoiceDataExtractor,
modelCreator: IModelCreator, serializer: ISerializer, asyncRunner: IAsyncRunner) : BankingPresenter {
return BankingPresenter(bankingClientCreator, bankFinder, dataFolder, persister, router, modelCreator,
remitteeSearcher, bankIconFinder, textExtractorRegistry, invoiceDataExtractor, serializer, asyncRunner)
transactionPartySearcher, bankIconFinder, textExtractorRegistry, invoiceDataExtractor, serializer, asyncRunner)
}
@Provides
@ -121,7 +120,7 @@ class BankingModule(private val applicationContext: Context) {
@Provides
@Singleton
fun provideRemitteeSearcher(bankingPersistence: IBankingPersistence) : IRemitteeSearcher {
fun provideTransactionPartySearcher(bankingPersistence: IBankingPersistence) : ITransactionPartySearcher {
return bankingPersistence as RoomBankingPersistence
}

View File

@ -20,13 +20,13 @@ import kotlinx.android.synthetic.main.dialog_transfer_money.view.*
import net.dankito.banking.ui.android.R
import net.dankito.banking.ui.android.di.BankingComponent
import net.dankito.banking.ui.android.adapter.BankAccountsAdapter
import net.dankito.banking.ui.android.adapter.presenter.RemitteePresenter
import net.dankito.banking.ui.android.adapter.presenter.RecipientPresenter
import net.dankito.banking.ui.android.extensions.addEnterPressedListener
import net.dankito.banking.ui.android.extensions.closePopupOnBackButtonPress
import net.dankito.banking.ui.android.listener.ListItemSelectedListener
import net.dankito.banking.ui.android.util.StandardAutocompleteCallback
import net.dankito.banking.ui.android.util.StandardTextWatcher
import net.dankito.banking.search.Remittee
import net.dankito.banking.search.TransactionParty
import net.dankito.banking.ui.model.TypedBankAccount
import net.dankito.banking.ui.model.parameters.TransferMoneyData
import net.dankito.banking.ui.model.responses.BankingClientResponse
@ -60,16 +60,16 @@ open class TransferMoneyDialog : DialogFragment() {
protected val inputValidator = InputValidator() // TODO: move to presenter
protected var remitteeBic: String? = null
protected var recipientBic: String? = null
protected var validRemitteeNameEntered = false
protected var validRecipientNameEntered = false
protected var validRemitteeIbanEntered = false
protected var validRecipientIbanEntered = false
protected var validRemitteeBicEntered = false
protected var validRecipientBicEntered = false
protected var validUsageEntered = true
protected var validReferenceEntered = true
protected var validAmountEntered = false
@ -118,73 +118,73 @@ open class TransferMoneyDialog : DialogFragment() {
rootView.spnBankAccounts.adapter = adapter
rootView.spnBankAccounts.onItemSelectedListener = ListItemSelectedListener(adapter) { selectedBankAccount ->
this.account = selectedBankAccount
setInstantPaymentControlsVisibility(rootView)
setRealTimeTransferControlsVisibility(rootView)
}
preselectedValues?.account?.let { rootView.spnBankAccounts.setSelection(adapter.getItems().indexOf(it)) }
}
initRemitteeAutocompletion(rootView.edtxtRemitteeName)
initRecipientAutocompletion(rootView.edtxtRecipientName)
rootView.edtxtRemitteeName.addTextChangedListener(checkRequiredDataWatcher {
checkIfEnteredRemitteeNameIsValidWhileUserIsTyping()
rootView.edtxtRecipientName.addTextChangedListener(checkRequiredDataWatcher {
checkIfEnteredRecipientNameIsValidWhileUserIsTyping()
})
rootView.edtxtRemitteeIban.addTextChangedListener(StandardTextWatcher {
checkIfEnteredRemitteeIbanIsValidWhileUserIsTyping()
rootView.edtxtRecipientIban.addTextChangedListener(StandardTextWatcher {
checkIfEnteredRecipientIbanIsValidWhileUserIsTyping()
tryToGetBicFromIban(it)
})
rootView.edtxtAmount.addTextChangedListener(checkRequiredDataWatcher {
checkIfEnteredAmountIsValid()
})
rootView.edtxtUsage.addTextChangedListener(checkRequiredDataWatcher {
checkIfEnteredUsageTextIsValid()
rootView.edtxtReference.addTextChangedListener(checkRequiredDataWatcher {
checkIfEnteredReferenceTextIsValid()
})
rootView.edtxtRemitteeName.setOnFocusChangeListener { _, hasFocus -> if (hasFocus == false) checkIfEnteredRemitteeNameIsValidAfterFocusLost() }
rootView.edtxtRemitteeIban.setOnFocusChangeListener { _, hasFocus -> if (hasFocus == false) checkIfEnteredRemitteeIbanIsValidAfterFocusLost() }
rootView.edtxtRecipientName.setOnFocusChangeListener { _, hasFocus -> if (hasFocus == false) checkIfEnteredRecipientNameIsValidAfterFocusLost() }
rootView.edtxtRecipientIban.setOnFocusChangeListener { _, hasFocus -> if (hasFocus == false) checkIfEnteredRecipientIbanIsValidAfterFocusLost() }
rootView.edtxtAmount.setOnFocusChangeListener { _, hasFocus -> if (hasFocus == false) checkIfEnteredAmountIsValid() }
rootView.edtxtUsage.setOnFocusChangeListener { _, hasFocus -> if (hasFocus == false) checkIfEnteredUsageTextIsValid() }
rootView.edtxtReference.setOnFocusChangeListener { _, hasFocus -> if (hasFocus == false) checkIfEnteredReferenceTextIsValid() }
transferMoneyIfEnterPressed(rootView.edtxtRemitteeName)
transferMoneyIfEnterPressed(rootView.edtxtRemitteeIban)
transferMoneyIfEnterPressed(rootView.edtxtRecipientName)
transferMoneyIfEnterPressed(rootView.edtxtRecipientIban)
transferMoneyIfEnterPressed(rootView.edtxtAmount)
transferMoneyIfEnterPressed(rootView.edtxtUsage)
transferMoneyIfEnterPressed(rootView.edtxtReference)
// fix that even in Locales using ',' as decimal separator entering ',' is not allowed (thanks dstibbe! https://stackoverflow.com/a/34256139)
val decimalSeparator = DecimalFormatSymbols.getInstance().getDecimalSeparator()
rootView.edtxtAmount.keyListener = DigitsKeyListener.getInstance("0123456789$decimalSeparator")
rootView.btnShowInstantPaymentInfo.setOnClickListener { showInstantPaymentInfo(rootView.btnShowInstantPaymentInfo, rootView) }
rootView.btnShowRealTimeTransferInfo.setOnClickListener { showRealTimeTransferInfo(rootView.btnShowRealTimeTransferInfo, rootView) }
setInstantPaymentControlsVisibility(rootView)
setRealTimeTransferControlsVisibility(rootView)
rootView.btnCancel.setOnClickListener { dismiss() }
rootView.btnTransferMoney.setOnClickListener { transferMoney() }
adjustCheckBoxInstantPaymentWidth()
adjustCheckBoxRealTimeTransferWidth()
}
protected open fun adjustCheckBoxInstantPaymentWidth() {
protected open fun adjustCheckBoxRealTimeTransferWidth() {
// wait some time till CheckBox is layout and lineCount is set
val timer = Timer()
timer.schedule(10) { requireActivity().runOnUiThread { adjustCheckBoxInstantPaymentWidthOnUiThread() }}
timer.schedule(2500) { requireActivity().runOnUiThread { adjustCheckBoxInstantPaymentWidthOnUiThread() }}
timer.schedule(10) { requireActivity().runOnUiThread { adjustCheckBoxRealTimeTransferWidthOnUiThread() }}
timer.schedule(2500) { requireActivity().runOnUiThread { adjustCheckBoxRealTimeTransferWidthOnUiThread() }}
}
protected open fun adjustCheckBoxInstantPaymentWidthOnUiThread() {
if (chkbxInstantPayment.isEllipsized == false) {
// by default chkbxInstantPayment uses full width, even though if its text doesn't need this space -> there
chkbxInstantPayment.layoutParams = LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT, 0f)
chkbxInstantPayment.requestLayout()
protected open fun adjustCheckBoxRealTimeTransferWidthOnUiThread() {
if (chkbxRealTimeTransfer.isEllipsized == false) {
// by default chkbxRealTimeTransfer uses full width, even though if its text doesn't need this space -> there
chkbxRealTimeTransfer.layoutParams = LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT, 0f)
chkbxRealTimeTransfer.requestLayout()
}
}
protected open fun setInstantPaymentControlsVisibility(rootView: View) {
rootView.lytInstantPayment.visibility =
if (account.supportsInstantPaymentMoneyTransfer) {
protected open fun setRealTimeTransferControlsVisibility(rootView: View) {
rootView.lytRealTimeTransfer.visibility =
if (account.supportsRealTimeTransfer) {
View.VISIBLE
}
else {
@ -192,9 +192,9 @@ open class TransferMoneyDialog : DialogFragment() {
}
}
protected open fun showInstantPaymentInfo(btnShowInstantPaymentInfo: ImageButton, rootView: View) {
requireActivity().layoutInflater.inflate(R.layout.view_instant_payment_info, null)?.let { contentView ->
requireContext().hideKeyboard(lytInstantPayment)
protected open fun showRealTimeTransferInfo(btnShowRealTimeTransferInfo: ImageButton, rootView: View) {
requireActivity().layoutInflater.inflate(R.layout.view_real_time_transfer_info, null)?.let { contentView ->
requireContext().hideKeyboard(lytRealTimeTransfer)
val popupWindow = PopupWindow(contentView, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)
@ -203,9 +203,9 @@ open class TransferMoneyDialog : DialogFragment() {
contentView.findViewById<Button>(R.id.btnDismissPopup)?.setOnClickListener { popupWindow.dismiss() }
popupWindow.showAtLocation(btnShowInstantPaymentInfo, Gravity.TOP, 0, 0)
popupWindow.showAtLocation(btnShowRealTimeTransferInfo, Gravity.TOP, 0, 0)
popupWindow.showAsDropDown(btnShowInstantPaymentInfo)
popupWindow.showAsDropDown(btnShowRealTimeTransferInfo)
}
}
@ -223,17 +223,17 @@ open class TransferMoneyDialog : DialogFragment() {
private fun isRequiredDataEntered() = btnTransferMoney.isEnabled
private fun initRemitteeAutocompletion(edtxtRemitteeName: EditText) {
val autocompleteCallback = StandardAutocompleteCallback<Remittee> { _, item ->
remitteeSelected(item)
private fun initRecipientAutocompletion(edtxtRecipientName: EditText) {
val autocompleteCallback = StandardAutocompleteCallback<TransactionParty> { _, item ->
recipientSelected(item)
true
}
Autocomplete.on<Remittee>(edtxtRemitteeName)
Autocomplete.on<TransactionParty>(edtxtRecipientName)
.with(6f)
.with(ColorDrawable(Color.WHITE))
.with(autocompleteCallback)
.with(RemitteePresenter(presenter, edtxtRemitteeName.context))
.with(RecipientPresenter(presenter, edtxtRecipientName.context))
.build()
.closePopupOnBackButtonPress(dialog)
}
@ -244,8 +244,8 @@ open class TransferMoneyDialog : DialogFragment() {
setPreselectedValues()
if (remitteeBic != null) {
tryToGetBicFromIban(edtxtRemitteeIban.text.toString())
if (recipientBic != null) {
tryToGetBicFromIban(edtxtRecipientIban.text.toString())
}
}
@ -254,20 +254,20 @@ open class TransferMoneyDialog : DialogFragment() {
preselectedValues?.let { data ->
preselectedValues = null
edtxtRemitteeName.setText(data.creditorName)
edtxtRecipientName.setText(data.recipientName)
if (data.creditorIban.isNotBlank()) { // set only if creditorIban has a value as otherwise creditorBic would be overridden by empty search result
edtxtRemitteeIban.setText(data.creditorIban)
if (data.recipientAccountId.isNotBlank()) { // set only if recipientAccountId has a value as otherwise recipientBankCode would be overridden by empty search result
edtxtRecipientIban.setText(data.recipientAccountId)
}
// a little bit inconsistent as if IBAN is not set bank's name won't be displayed even though it can be retrieved by BIC
remitteeBic = data.creditorBic
recipientBic = data.recipientBankCode
if (data.amount > BigDecimal.ZERO) {
edtxtAmount.setText(data.amount.toString())
}
edtxtUsage.setText(data.usage)
edtxtReference.setText(data.reference)
focusEditTextAccordingToPreselectedValues()
}
@ -275,19 +275,19 @@ open class TransferMoneyDialog : DialogFragment() {
protected open fun focusEditTextAccordingToPreselectedValues() {
when {
edtxtRemitteeName.text.toString().isBlank() -> edtxtRemitteeName.requestFocus()
edtxtRemitteeIban.text.toString().isBlank() -> edtxtRemitteeIban.requestFocus()
edtxtRecipientName.text.toString().isBlank() -> edtxtRecipientName.requestFocus()
edtxtRecipientIban.text.toString().isBlank() -> edtxtRecipientIban.requestFocus()
edtxtAmount.text.toString().isBlank() -> edtxtAmount.requestFocus()
edtxtUsage.text.toString().isBlank() -> edtxtUsage.requestFocus()
else -> edtxtUsage.requestFocus()
edtxtReference.text.toString().isBlank() -> edtxtReference.requestFocus()
else -> edtxtReference.requestFocus()
}
}
protected open fun remitteeSelected(item: Remittee) {
edtxtRemitteeName.setText(item.name)
edtxtRemitteeIban.setText(item.iban)
remitteeBic = item.bic
protected open fun recipientSelected(item: TransactionParty) {
edtxtRecipientName.setText(item.name)
edtxtRecipientIban.setText(item.iban)
recipientBic = item.bic
focusEditTextAccordingToPreselectedValues()
}
@ -296,12 +296,12 @@ open class TransferMoneyDialog : DialogFragment() {
getEnteredAmount()?.let { amount -> // should only come at this stage when a valid amount has been entered
val data = TransferMoneyData(
account,
inputValidator.convertToAllowedSepaCharacters(edtxtRemitteeName.text.toString()),
edtxtRemitteeIban.text.toString().replace(" ", ""),
remitteeBic?.replace(" ", "") ?: "", // should always be != null at this point
inputValidator.convertToAllowedSepaCharacters(edtxtRecipientName.text.toString()),
edtxtRecipientIban.text.toString().replace(" ", ""),
recipientBic?.replace(" ", "") ?: "", // should always be != null at this point
amount.toBigDecimal(),
inputValidator.convertToAllowedSepaCharacters(edtxtUsage.text.toString()),
chkbxInstantPayment.isChecked
inputValidator.convertToAllowedSepaCharacters(edtxtReference.text.toString()),
chkbxRealTimeTransfer.isChecked
)
presenter.transferMoneyAsync(data) {
@ -317,11 +317,11 @@ open class TransferMoneyDialog : DialogFragment() {
if (response.userCancelledAction == false) {
val message = if (response.successful) {
context.getString(R.string.dialog_transfer_money_message_transfer_successful,
String.format("%.02f", transferData.amount), "", transferData.creditorName) // TODO: where to get currency from?
String.format("%.02f", transferData.amount), "", transferData.recipientName) // TODO: where to get currency from?
}
else {
context.getString(R.string.dialog_transfer_money_message_transfer_failed,
String.format("%.02f", transferData.amount), "", transferData.creditorName, // TODO: where to get currency from?
String.format("%.02f", transferData.amount), "", transferData.recipientName, // TODO: where to get currency from?
response.errorToShowToUser
)
}
@ -356,66 +356,66 @@ open class TransferMoneyDialog : DialogFragment() {
}
private fun showValuesForFoundBankOnUiThread(enteredIban: CharSequence, foundBank: BankInfo?) {
validRemitteeBicEntered = foundBank != null
remitteeBic = foundBank?.bic
validRecipientBicEntered = foundBank != null
recipientBic = foundBank?.bic
if (foundBank != null) {
txtRemitteeBankInfo.text = getString(R.string.dialog_transfer_money_bic_detected_from_iban, foundBank.bic, foundBank.name)
txtRemitteeBankInfo.visibility = View.VISIBLE
txtRecipientBankInfo.text = getString(R.string.dialog_transfer_money_bic_detected_from_iban, foundBank.bic, foundBank.name)
txtRecipientBankInfo.visibility = View.VISIBLE
}
else if (enteredIban.length >= InputValidator.MinimumLengthToDetermineBicFromIban) {
txtRemitteeBankInfo.text = getString(R.string.dialog_transfer_money_could_not_determine_bic_from_iban, enteredIban.substring(4, InputValidator.MinimumLengthToDetermineBicFromIban))
txtRemitteeBankInfo.visibility = View.VISIBLE
txtRecipientBankInfo.text = getString(R.string.dialog_transfer_money_could_not_determine_bic_from_iban, enteredIban.substring(4, InputValidator.MinimumLengthToDetermineBicFromIban))
txtRecipientBankInfo.visibility = View.VISIBLE
}
else {
txtRemitteeBankInfo.visibility = View.GONE
txtRecipientBankInfo.visibility = View.GONE
}
checkIfRequiredDataEnteredOnUiThread()
}
protected open fun checkIfRequiredDataEnteredOnUiThread() {
btnTransferMoney.isEnabled = validRemitteeNameEntered && validRemitteeIbanEntered
&& validRemitteeBicEntered
&& validAmountEntered && validUsageEntered
btnTransferMoney.isEnabled = validRecipientNameEntered && validRecipientIbanEntered
&& validRecipientBicEntered
&& validAmountEntered && validReferenceEntered
}
protected open fun checkIfEnteredRemitteeNameIsValidWhileUserIsTyping() {
val enteredRemitteeName = edtxtRemitteeName.text.toString()
val validationResult = inputValidator.validateRemitteeNameWhileTyping(enteredRemitteeName)
protected open fun checkIfEnteredRecipientNameIsValidWhileUserIsTyping() {
val enteredRecipientName = edtxtRecipientName.text.toString()
val validationResult = inputValidator.validateRecipientNameWhileTyping(enteredRecipientName)
this.validRemitteeNameEntered = validationResult.validationSuccessfulOrCouldCorrectString
this.validRecipientNameEntered = validationResult.validationSuccessfulOrCouldCorrectString
showValidationResult(lytRemitteeName, validationResult)
showValidationResult(lytRecipientName, validationResult)
}
protected open fun checkIfEnteredRemitteeNameIsValidAfterFocusLost() {
val enteredRemitteeName = edtxtRemitteeName.text.toString()
val validationResult = inputValidator.validateRemitteeName(enteredRemitteeName)
protected open fun checkIfEnteredRecipientNameIsValidAfterFocusLost() {
val enteredRecipientName = edtxtRecipientName.text.toString()
val validationResult = inputValidator.validateRecipientName(enteredRecipientName)
this.validRemitteeNameEntered = validationResult.validationSuccessfulOrCouldCorrectString
this.validRecipientNameEntered = validationResult.validationSuccessfulOrCouldCorrectString
if (validationResult.validationSuccessful == false) { // only update hint / error if validation fails, don't hide previous hint / error otherwise
showValidationResult(lytRemitteeName, validationResult)
showValidationResult(lytRecipientName, validationResult)
}
}
protected open fun checkIfEnteredRemitteeIbanIsValidWhileUserIsTyping() {
val enteredIban = edtxtRemitteeIban.text.toString()
protected open fun checkIfEnteredRecipientIbanIsValidWhileUserIsTyping() {
val enteredIban = edtxtRecipientIban.text.toString()
val validationResult = inputValidator.validateIbanWhileTyping(enteredIban)
this.validRemitteeIbanEntered = validationResult.validationSuccessfulOrCouldCorrectString
this.validRecipientIbanEntered = validationResult.validationSuccessfulOrCouldCorrectString
showValidationResult(lytRemitteeIban, validationResult)
showValidationResult(lytRecipientIban, validationResult)
}
protected open fun checkIfEnteredRemitteeIbanIsValidAfterFocusLost() {
val validationResult = inputValidator.validateIban(edtxtRemitteeIban.text.toString())
protected open fun checkIfEnteredRecipientIbanIsValidAfterFocusLost() {
val validationResult = inputValidator.validateIban(edtxtRecipientIban.text.toString())
this.validRemitteeIbanEntered = validationResult.validationSuccessfulOrCouldCorrectString
this.validRecipientIbanEntered = validationResult.validationSuccessfulOrCouldCorrectString
if (validationResult.validationSuccessful == false) { // only update hint / error if validation fails, don't hide previous hint / error otherwise
showValidationResult(lytRemitteeIban, validationResult)
showValidationResult(lytRecipientIban, validationResult)
}
}
@ -437,12 +437,12 @@ open class TransferMoneyDialog : DialogFragment() {
return null
}
protected open fun checkIfEnteredUsageTextIsValid() {
val validationResult = inputValidator.validateUsage(edtxtUsage.text.toString())
protected open fun checkIfEnteredReferenceTextIsValid() {
val validationResult = inputValidator.validateReference(edtxtReference.text.toString())
this.validUsageEntered = validationResult.validationSuccessfulOrCouldCorrectString
this.validReferenceEntered = validationResult.validationSuccessfulOrCouldCorrectString
showValidationResult(lytUsage, validationResult)
showValidationResult(lytReference, validationResult)
}
protected open fun showValidationResult(textInputLayout: TextInputLayout, validationResult: ValidationResult) {
@ -475,7 +475,7 @@ open class TransferMoneyDialog : DialogFragment() {
(textInputLayout.layoutParams as? ViewGroup.MarginLayoutParams)?.let { params ->
val isShowingHintOrError = validationResult.validationError != null || validationResult.validationHint != null
params.bottomMargin = if (isShowingHintOrError == false || textInputLayout == lytUsage) 0
params.bottomMargin = if (isShowingHintOrError == false || textInputLayout == lytReference) 0
else context!!.getDimension(R.dimen.dialog_transfer_money_input_fields_bottom_margin_when_displaying_validation_label)
}
}

View File

@ -25,7 +25,6 @@ import net.dankito.banking.ui.model.parameters.TransferMoneyData
import net.dankito.banking.ui.model.responses.GetTransactionsResponse
import net.dankito.banking.ui.presenter.BankingPresenter
import net.dankito.utils.android.extensions.asActivity
import net.dankito.utils.android.extensions.getDimension
import net.dankito.utils.multiplatform.sum
import java.text.DateFormat
import javax.inject.Inject
@ -148,8 +147,8 @@ class HomeFragment : Fragment() {
override fun onContextItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.mnitmNewTransferToSameRemittee -> {
newTransferToSameRemittee()
R.id.mnitmNewTransferToSameTransactionParty -> {
newTransferToSameTransactionParty()
return true
}
R.id.mnitmNewTransferWithSameData -> {
@ -211,9 +210,9 @@ class HomeFragment : Fragment() {
}
private fun newTransferToSameRemittee() {
private fun newTransferToSameTransactionParty() {
transactionAdapter.selectedTransaction?.let { selectedTransaction ->
presenter.showTransferMoneyDialog(TransferMoneyData.fromAccountTransactionWithoutAmountAndUsage(selectedTransaction))
presenter.showTransferMoneyDialog(TransferMoneyData.fromAccountTransactionWithoutAmountAndReference(selectedTransaction))
}
}

View File

@ -47,14 +47,14 @@
</LinearLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/lytRemitteeName"
android:id="@+id/lytRecipientName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/dialog_transfer_money_remittee_name"
android:hint="@string/dialog_transfer_money_recipient_name"
>
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/edtxtRemitteeName"
android:id="@+id/edtxtRecipientName"
android:layout_width="match_parent"
android:layout_height="@dimen/dialog_transfer_money_input_fields_height"
android:inputType="textPersonName"
@ -67,14 +67,14 @@
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/lytRemitteeIban"
android:id="@+id/lytRecipientIban"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/dialog_transfer_money_remittee_iban"
android:hint="@string/dialog_transfer_money_recipient_iban"
>
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/edtxtRemitteeIban"
android:id="@+id/edtxtRecipientIban"
android:layout_width="match_parent"
android:layout_height="@dimen/dialog_transfer_money_autocomplete_fields_height"
android:inputType="textCapCharacters"
@ -83,7 +83,7 @@
</com.google.android.material.textfield.TextInputLayout>
<TextView
android:id="@+id/txtRemitteeBankInfo"
android:id="@+id/txtRecipientBankInfo"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/TextAppearance.AppCompat.Small"
@ -109,14 +109,14 @@
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/lytUsage"
android:id="@+id/lytReference"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/dialog_transfer_money_usage"
android:hint="@string/dialog_transfer_money_reference"
>
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/edtxtUsage"
android:id="@+id/edtxtReference"
android:layout_width="match_parent"
android:layout_height="@dimen/dialog_transfer_money_input_fields_height"
android:inputType="text"
@ -127,20 +127,20 @@
<LinearLayout
android:orientation="horizontal"
android:id="@+id/lytInstantPayment"
android:id="@+id/lytRealTimeTransfer"
android:layout_width="match_parent"
android:layout_height="30dp"
android:layout_marginTop="@dimen/dialog_transfer_money_instant_payment_margin_top"
android:layout_marginBottom="@dimen/dialog_transfer_money_instant_payment_margin_bottom"
android:layout_marginTop="@dimen/dialog_transfer_money_real_time_transfer_margin_top"
android:layout_marginBottom="@dimen/dialog_transfer_money_real_time_transfer_margin_bottom"
android:gravity="center_vertical"
android:layout_gravity="center_vertical"
android:visibility="gone"
android:layout_marginRight="@dimen/dialog_transfer_money_instant_payment_show_info_button_margin_end"
android:layout_marginEnd="@dimen/dialog_transfer_money_instant_payment_show_info_button_margin_end"
android:layout_marginRight="@dimen/dialog_transfer_money_real_time_transfer_show_info_button_margin_end"
android:layout_marginEnd="@dimen/dialog_transfer_money_real_time_transfer_show_info_button_margin_end"
>
<androidx.appcompat.widget.AppCompatCheckBox
android:id="@+id/chkbxInstantPayment"
android:id="@+id/chkbxRealTimeTransfer"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
@ -148,18 +148,18 @@
android:ellipsize="end"
android:gravity="center_vertical"
android:textAlignment="gravity"
android:textSize="@dimen/view_instant_payment_info_text_size"
android:text="@string/dialog_transfer_money_instant_payment"
android:textSize="@dimen/view_real_time_transfer_info_text_size"
android:text="@string/dialog_transfer_money_real_time_transfer"
/>
<ImageButton
android:id="@+id/btnShowInstantPaymentInfo"
android:layout_width="@dimen/dialog_transfer_money_instant_payment_show_info_button_size"
android:layout_height="@dimen/dialog_transfer_money_instant_payment_show_info_button_size"
android:id="@+id/btnShowRealTimeTransferInfo"
android:layout_width="@dimen/dialog_transfer_money_real_time_transfer_show_info_button_size"
android:layout_height="@dimen/dialog_transfer_money_real_time_transfer_show_info_button_size"
android:layout_weight="0"
android:layout_gravity="start|center_vertical"
android:layout_marginLeft="@dimen/dialog_transfer_money_instant_payment_show_info_button_margin_start"
android:layout_marginStart="@dimen/dialog_transfer_money_instant_payment_show_info_button_margin_start"
android:layout_marginLeft="@dimen/dialog_transfer_money_real_time_transfer_show_info_button_margin_start"
android:layout_marginStart="@dimen/dialog_transfer_money_real_time_transfer_show_info_button_margin_start"
android:background="@null"
app:srcCompat="@drawable/ic_baseline_info_24"
android:tint="@color/infoIconColor"

View File

@ -25,7 +25,7 @@
android:id="@+id/txtvwTransactionLabel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/list_item_account_transaction_space_between_label_and_usage"
android:layout_marginBottom="@dimen/list_item_account_transaction_space_between_label_and_reference"
android:lines="1"
android:ellipsize="end"
android:textSize="@dimen/list_item_account_transaction_label_text_size"
@ -34,14 +34,14 @@
/>
<TextView
android:id="@+id/txtvwUsage"
android:id="@+id/txtvwReference"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:textAlignment="gravity"
android:lines="2"
android:ellipsize="end"
android:textSize="@dimen/list_item_account_transaction_usage_text_size"
android:textSize="@dimen/list_item_account_transaction_reference_text_size"
android:textColor="@color/textColorSecondary"
/>

View File

@ -5,11 +5,11 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:padding="@dimen/list_item_remittee_padding"
android:padding="@dimen/list_item_recipient_padding"
>
<TextView
android:id="@+id/txtvwRemitteeName"
android:id="@+id/txtvwRecipientName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/TextAppearance.AppCompat.Medium"
@ -21,10 +21,10 @@
/>
<TextView
android:id="@+id/txtvwRemitteeBankName"
android:id="@+id/txtvwRecipientBankName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/list_item_remittee_space_between_fields"
android:layout_marginTop="@dimen/list_item_recipient_space_between_fields"
android:gravity="center_vertical"
android:lines="1"
android:ellipsize="end"
@ -35,11 +35,11 @@
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/list_item_remittee_space_between_fields"
android:layout_marginTop="@dimen/list_item_recipient_space_between_fields"
>
<TextView
android:id="@+id/txtvwRemitteeAccountId"
android:id="@+id/txtvwRecipientAccountId"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
@ -50,7 +50,7 @@
/>
<TextView
android:id="@+id/txtvwRemitteeBankCode"
android:id="@+id/txtvwRecipientBankCode"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"

View File

@ -18,8 +18,8 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/TextAppearance.AppCompat.Medium"
android:textSize="@dimen/view_instant_payment_info_text_size"
android:text="@string/dialog_transfer_money_instant_payment_info"
android:textSize="@dimen/view_real_time_transfer_info_text_size"
android:text="@string/dialog_transfer_money_real_time_transfer_info"
/>
</ScrollView>
@ -27,14 +27,14 @@
<LinearLayout
android:id="@+id/lytButtonBar"
android:layout_width="match_parent"
android:layout_height="@dimen/view_instant_payment_info_dismiss_button_height"
android:layout_height="@dimen/view_real_time_info_dismiss_button_height"
android:layout_alignParentBottom="true"
android:gravity="center"
>
<Button
android:id="@+id/btnDismissPopup"
android:layout_width="@dimen/view_instant_payment_info_dismiss_button_width"
android:layout_width="@dimen/view_real_time_transfer_info_dismiss_button_width"
android:layout_height="match_parent"
style="?android:attr/buttonBarButtonStyle"
android:text="@android:string/ok"

View File

@ -2,7 +2,7 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/mnitmNewTransferToSameRemittee"
android:id="@+id/mnitmNewTransferToSameTransactionParty"
android:title="@string/fragment_home_transfer_money_to"
/>

View File

@ -53,14 +53,14 @@
<string name="dialog_add_account_message_could_not_add_account">Konto konnte nicht hinzugefügt werden.\n\nFehlermeldung Ihrer Bank:\n\n%s</string>
<string name="dialog_transfer_money_account">Konto:</string>
<string name="dialog_transfer_money_remittee_name">Name:</string>
<string name="dialog_transfer_money_remittee_iban">IBAN:</string>
<string name="dialog_transfer_money_recipient_name">Name:</string>
<string name="dialog_transfer_money_recipient_iban">IBAN:</string>
<string name="dialog_transfer_money_bic_detected_from_iban">BIC: %1$s, %2$s</string>
<string name="dialog_transfer_money_could_not_determine_bic_from_iban">Keine BIC gefunden für BLZ %1$s</string>
<string name="dialog_transfer_money_amount">Betrag:</string>
<string name="dialog_transfer_money_usage">Verwendungszweck:</string>
<string name="dialog_transfer_money_instant_payment">Echtzeitüberweisung (evtl. kostenpflichtig)</string>
<string name="dialog_transfer_money_instant_payment_info">Normale Überweisungen werden in der Regel innerhalb eines Werktages gutgeschrieben. Dies gilt jedoch nur zu Geschäftszeiten der Banken, also schon mal nicht am Wochenende und an Feiertagen. Zudem unterscheiden sich die Geschäftszeiten von Bank zu Bank. Meistens gehen diese von 10 - 18 Uhr, manchmal auch bis 22 Uhr, manchmal (Freitags) aber auch nur bis 14 Uhr.
<string name="dialog_transfer_money_reference">Verwendungszweck:</string>
<string name="dialog_transfer_money_real_time_transfer">Echtzeitüberweisung (evtl. kostenpflichtig)</string>
<string name="dialog_transfer_money_real_time_transfer_info">Normale Überweisungen werden in der Regel innerhalb eines Werktages gutgeschrieben. Dies gilt jedoch nur zu Geschäftszeiten der Banken, also schon mal nicht am Wochenende und an Feiertagen. Zudem unterscheiden sich die Geschäftszeiten von Bank zu Bank. Meistens gehen diese von 10 - 18 Uhr, manchmal auch bis 22 Uhr, manchmal (Freitags) aber auch nur bis 14 Uhr.
\n\nEchtzeitüberweisungen werden hingegen innerhalb von maximal 10 Sekunden überwiesen, egal an welchem Tag und zu welcher Uhrzeit.
\n\nHäufig sind Echtzeitüberweisungen jedoch kostenpflichtig.
\n\nOb eine Bank Gebühren für Echtzeitüberweisungen erhebt, kann Bankmeister leider nicht wissen. Dies entnehmen Sie bitte dem Preis- / Leistungsverzeichnis Ihrer Bank.</string>
@ -120,7 +120,7 @@
<string name="error_invalid_bic_pattern_entered">Eine BIC besteht aus 8 oder 11 Zeichen und folgt dem Muster: ABCDED12(XYZ)</string>
<string name="error_no_amount_entered">Bitte geben Sie den zu überweisenden Betrag ein</string>
<string name="error_invalid_amount_entered">Bitte geben Sie einen Betrag größer 0 ein.</string>
<string name="error_entered_usage_too_long">Verwendungszweck darf nur 140 Zeichen lang sein</string>
<string name="error_entered_recipient_too_long">Verwendungszweck darf nur 140 Zeichen lang sein</string>
<string name="dialog_send_message_log_message_log_label">Message Log</string>

View File

@ -34,9 +34,9 @@
<dimen name="list_item_account_transaction_height">74dp</dimen>
<dimen name="list_item_account_transaction_padding">0dp</dimen>
<dimen name="list_item_account_transaction_space_between_label_and_usage">2dp</dimen>
<dimen name="list_item_account_transaction_space_between_label_and_reference">2dp</dimen>
<dimen name="list_item_account_transaction_label_text_size">16sp</dimen>
<dimen name="list_item_account_transaction_usage_text_size">14sp</dimen>
<dimen name="list_item_account_transaction_reference_text_size">14sp</dimen>
<dimen name="list_item_account_transaction_bank_icon_width_and_height">16dp</dimen>
<dimen name="list_item_account_transaction_bank_icon_margin_top">0dp</dimen>
<dimen name="list_item_account_transaction_bank_icon_margin_bottom">2dp</dimen>
@ -75,25 +75,25 @@
<dimen name="dialog_transfer_money_autocomplete_fields_height">50dp</dimen>
<dimen name="dialog_transfer_money_input_fields_height">40dp</dimen>
<dimen name="dialog_transfer_money_input_fields_bottom_margin_when_displaying_validation_label">8dp</dimen>
<dimen name="dialog_transfer_money_instant_payment_text_size">14sp</dimen>
<dimen name="dialog_transfer_money_instant_payment_margin_top">6dp</dimen>
<dimen name="dialog_transfer_money_instant_payment_margin_bottom">6dp</dimen>
<dimen name="dialog_transfer_money_instant_payment_show_info_button_size">20dp</dimen>
<dimen name="dialog_transfer_money_instant_payment_show_info_button_margin_start">6dp</dimen>
<dimen name="dialog_transfer_money_instant_payment_show_info_button_margin_end">12dp</dimen>
<dimen name="dialog_transfer_money_real_time_transfer_text_size">14sp</dimen>
<dimen name="dialog_transfer_money_real_time_transfer_margin_top">6dp</dimen>
<dimen name="dialog_transfer_money_real_time_transfer_margin_bottom">6dp</dimen>
<dimen name="dialog_transfer_money_real_time_transfer_show_info_button_size">20dp</dimen>
<dimen name="dialog_transfer_money_real_time_transfer_show_info_button_margin_start">6dp</dimen>
<dimen name="dialog_transfer_money_real_time_transfer_show_info_button_margin_end">12dp</dimen>
<dimen name="dialog_transfer_money_buttons_width">120dp</dimen>
<dimen name="list_item_bank_account_padding">2dp</dimen>
<dimen name="list_item_bank_account_icon_size">20dp</dimen>
<dimen name="list_item_bank_account_icon_margin_right">8dp</dimen>
<dimen name="list_item_remittee_padding">4dp</dimen>
<dimen name="list_item_remittee_space_between_fields">4dp</dimen>
<dimen name="list_item_recipient_padding">4dp</dimen>
<dimen name="list_item_recipient_space_between_fields">4dp</dimen>
<dimen name="view_instant_payment_info_padding">8dp</dimen>
<dimen name="view_instant_payment_info_text_size">15sp</dimen>
<dimen name="view_instant_payment_info_dismiss_button_width">150dp</dimen>
<dimen name="view_instant_payment_info_dismiss_button_height">40dp</dimen>
<dimen name="view_real_time_transfer_info_padding">8dp</dimen>
<dimen name="view_real_time_transfer_info_text_size">15sp</dimen>
<dimen name="view_real_time_transfer_info_dismiss_button_width">150dp</dimen>
<dimen name="view_real_time_info_dismiss_button_height">40dp</dimen>
<dimen name="view_tan_image_controls_buttons_height">40dp</dimen>
<dimen name="view_tan_image_controls_buttons_width">40dp</dimen>

View File

@ -53,17 +53,17 @@
<string name="dialog_add_account_message_could_not_add_account">Could not add account.\n\nError message from your bank:\n\n%s</string>
<string name="dialog_transfer_money_account">Account:</string>
<string name="dialog_transfer_money_remittee_name">Name:</string>
<string name="dialog_transfer_money_remittee_iban">IBAN:</string>
<string name="dialog_transfer_money_recipient_name">Name:</string>
<string name="dialog_transfer_money_recipient_iban">IBAN:</string>
<string name="dialog_transfer_money_bic_detected_from_iban">BIC: %1$s, %2$s</string>
<string name="dialog_transfer_money_could_not_determine_bic_from_iban">No BIC found for bank code %1$s</string>
<string name="dialog_transfer_money_amount">Amount:</string>
<string name="dialog_transfer_money_usage">Usage:</string>
<string name="dialog_transfer_money_instant_payment">Instant payment (may with costs)</string>
<string name="dialog_transfer_money_instant_payment_info">Bank transfers are usually credited within one business day. However, this only applies during bank business hours, and therefore not at weekends and on public holidays. In addition, business hours vary from bank to bank. Mostly they are from 10 - 18 o\'clock, sometimes also until 22 o\'clock, but sometimes (e. g. Fridays) only until 14 o\'clock.
\n\nInstant payment transfers on the other hand are transferred within a maximum of 10 seconds, regardless of the day and time of day.
<string name="dialog_transfer_money_reference">Reference:</string>
<string name="dialog_transfer_money_real_time_transfer">Real-time transfer (may with costs)</string>
<string name="dialog_transfer_money_real_time_transfer_info">Bank transfers are usually credited within one business day. However, this only applies during bank business hours, and therefore not at weekends and on public holidays. In addition, business hours vary from bank to bank. Mostly they are from 10 - 18 o\'clock, sometimes also until 22 o\'clock, but sometimes (e. g. Fridays) only until 14 o\'clock.
\n\nReal-time transfers on the other hand are transferred within a maximum of 10 seconds, regardless of the day and time of day.
\n\nHowever, real-time transfers are often subject to a fee.
\n\nUnfortunately, Bankmeister cannot know whether a bank charges for instant payment transfers. Please refer to the list of prices and services of your bank.</string>
\n\nUnfortunately, Bankmeister cannot know whether a bank charges for real-time transfers. Please refer to the list of prices and services of your bank.</string>
<string name="dialog_transfer_money_transfer">Transfer</string>
<string name="dialog_transfer_money_message_transfer_successful">Successfully transferred %1$s %2$s to %3$s.</string>
<string name="dialog_transfer_money_message_transfer_failed">Could not transfer %1$s %2$s to %3$s.\n\nError message from your bank:\n\n%4$s</string>
@ -110,17 +110,17 @@
\n\nThey can only be restored, if then still possible, by retrieving it again from your bank server.</string>
<string name="error_no_name_entered">Please enter remittee\'s name</string>
<string name="error_no_name_entered">Please enter recipient\'s name</string>
<string name="error_entered_name_too_long">Name may only have 70 characters</string>
<string name="error_invalid_sepa_characters_entered">Invalid character(s) entered: %s</string>
<string name="error_no_iban_entered">Please enter remittee\'s IBAN</string>
<string name="error_no_iban_entered">Please enter recipient\'s IBAN</string>
<string name="error_invalid_iban_characters_entered">Invalid character(s) entered: %s</string>
<string name="error_invalid_iban_pattern_entered">IBAN has to have pattern: EN12 1234 5678 9012 3456 78</string>
<string name="error_invalid_bic_characters_entered">Invalid character(s) entered: %s</string>
<string name="error_invalid_bic_pattern_entered">A BIC consists of 8 or 11 characters and has the pattern: ABCDED12(XYZ)</string>
<string name="error_no_amount_entered">Please enter the amount to be transferred</string>
<string name="error_invalid_amount_entered">Please enter an amount greater zero.</string>
<string name="error_entered_usage_too_long">Usage may only have 140 characters</string>
<string name="error_entered_recipient_too_long">Reference may only have 140 characters</string>
<string name="dialog_send_message_log_message_log_label">Message log</string>

View File

@ -12,7 +12,7 @@ import net.dankito.banking.util.BankIconFinder
import net.dankito.banking.bankfinder.LuceneBankFinder
import net.dankito.banking.persistence.LuceneBankingPersistence
import net.dankito.banking.persistence.mapper.EntitiesModelCreator
import net.dankito.banking.search.LuceneRemitteeSearcher
import net.dankito.banking.search.LuceneTransactionPartySearcher
import net.dankito.banking.util.JacksonJsonSerializer
import net.dankito.banking.util.extraction.JavaTextExtractorRegistry
import net.dankito.text.extraction.TextExtractorRegistry
@ -47,9 +47,9 @@ class MainWindow : View(messages["application.title"]) {
private val presenter = BankingPresenter(fints4kBankingClientCreator(modelCreator, serializer),
LuceneBankFinder(indexFolder), dataFolder, LuceneBankingPersistence(indexFolder, databaseFolder),
RouterJavaFx(), modelCreator, LuceneRemitteeSearcher(indexFolder), BankIconFinder(), textExtractorRegistry)
RouterJavaFx(), modelCreator, LuceneTransactionPartySearcher(indexFolder), BankIconFinder(), textExtractorRegistry)
// private val presenter = BankingPresenter(hbci4jBankingClientCreator(), LuceneBankFinder(indexFolder),
// dataFolder, LuceneBankingPersistence(indexFolder, databaseFolder), RouterJavaFx(), modelCreator, LuceneRemitteeSearcher(indexFolder),
// dataFolder, LuceneBankingPersistence(indexFolder, databaseFolder), RouterJavaFx(), modelCreator, LuceneTransactionPartySearcher(indexFolder),
// BankIconFinder(), textExtractorRegistry)

View File

@ -28,9 +28,9 @@ account.transactions.control.view.balance.label=Balance:
account.transactions.control.view.could.not.retrieve.account.transactions=Could not retrieve account transactions for \'%1$s\'.\n\nError message from your bank:\n\n%2$s
account.transactions.table.column.header.value.date=Value date
account.transactions.table.column.header.usage=Usage
account.transactions.table.column.header.reference=Reference
account.transactions.table.column.header.amount=Amount
account.transactions.table.context.menu.new.transfer.to.same.remittee=Transfer money to %s
account.transactions.table.context.menu.new.transfer.to.same.recipient=Transfer money to %s
account.transactions.table.context.menu.new.transfer.with.same.data=New transfer with same data
account.transactions.table.context.menu.show.transaction.details=Show details
@ -62,13 +62,13 @@ enter.tan.dialog.tan.error.changing.tan.medium=Could not change TAN medium to \'
transfer.money.dialog.title=Transfer money
transfer.money.dialog.account.label=Account:
transfer.money.dialog.remittee.name.label=Remittee:
transfer.money.dialog.remittee.iban.label=IBAN:
transfer.money.dialog.remittee.bank.label=Bank:
transfer.money.dialog.remittee.bic.label=BIC:
transfer.money.dialog.recipient.name.label=Recipient:
transfer.money.dialog.recipient.iban.label=IBAN:
transfer.money.dialog.recipient.bank.label=Bank:
transfer.money.dialog.recipient.bic.label=BIC:
transfer.money.dialog.amount.label=Amount:
transfer.money.dialog.usage.label=Usage:
transfer.money.dialog.instant.payment.label=Instant payment
transfer.money.dialog.reference.label=Reference:
transfer.money.dialog.real.time.transfer.label=Real-time transfer
transfer.money.dialog.transfer.money.label=Transfer
transfer.money.dialog.bank.name.will.be.entered.automatically=Will be entered automatically
transfer.money.dialog.bank.not.found.for.iban=No bank found for this IBAN

View File

@ -28,9 +28,9 @@ account.transactions.control.view.balance.label=Saldo:
account.transactions.control.view.could.not.retrieve.account.transactions=Kontoumsätze für \'%1$s\' konnten nicht empfangen werden.\n\nFehlermeldung Ihrer Bank:\n\n%2$s
account.transactions.table.column.header.value.date=Buchungstag
account.transactions.table.column.header.usage=Verwendungszweck
account.transactions.table.column.header.reference=Verwendungszweck
account.transactions.table.column.header.amount=Betrag
account.transactions.table.context.menu.new.transfer.to.same.remittee=Neue Überweisung an %s
account.transactions.table.context.menu.new.transfer.to.same.recipient=Neue Überweisung an %s
account.transactions.table.context.menu.new.transfer.with.same.data=Neue Überweisung mit gleichen Daten
account.transactions.table.context.menu.show.transaction.details=Details anzeigen
@ -62,13 +62,13 @@ enter.tan.dialog.tan.error.changing.tan.medium=TAN Medium konnte nicht geändert
transfer.money.dialog.title=Neue Überweisung
transfer.money.dialog.account.label=Konto:
transfer.money.dialog.remittee.name.label=Begünstigter (Name oder Firma):
transfer.money.dialog.remittee.iban.label=IBAN:
transfer.money.dialog.remittee.bank.label=Bank:
transfer.money.dialog.remittee.bic.label=BIC:
transfer.money.dialog.recipient.name.label=Begünstigter (Name oder Firma):
transfer.money.dialog.recipient.iban.label=IBAN:
transfer.money.dialog.recipient.bank.label=Bank:
transfer.money.dialog.recipient.bic.label=BIC:
transfer.money.dialog.amount.label=Betrag:
transfer.money.dialog.usage.label=Verwendungszweck:
transfer.money.dialog.instant.payment.label=Echtzeitüberweisung
transfer.money.dialog.reference.label=Verwendungszweck:
transfer.money.dialog.real.time.transfer.label=Echtzeitüberweisung
transfer.money.dialog.transfer.money.label=Überweisen
transfer.money.dialog.bank.name.will.be.entered.automatically=Wird automatisch eingetragen
transfer.money.dialog.bank.not.found.for.iban=Für diese IBAN wurde keine Bank gefunden

View File

@ -48,7 +48,7 @@ open class AccountTransactionsTable @JvmOverloads constructor(
}
}
columns.add(TableColumn<IAccountTransaction, IAccountTransaction>(messages["account.transactions.table.column.header.usage"]).apply {
columns.add(TableColumn<IAccountTransaction, IAccountTransaction>(messages["account.transactions.table.column.header.reference"]).apply {
this.cellFormat {
contentDisplay = ContentDisplay.GRAPHIC_ONLY
@ -72,7 +72,7 @@ open class AccountTransactionsTable @JvmOverloads constructor(
}
}
label(it.usage) {
label(it.reference) {
vboxConstraints {
margin = LabelMargin
}

View File

@ -84,8 +84,8 @@ open class AccountTransactionsView(private val presenter: BankingPresenter) : Vi
contextMenu.apply {
if (selectedItem.canCreateMoneyTransferFrom) {
item(String.format(FX.messages["account.transactions.table.context.menu.new.transfer.to.same.remittee"], selectedItem.otherPartyName)) {
action { newTransferToSameRemittee(selectedItem) }
item(String.format(FX.messages["account.transactions.table.context.menu.new.transfer.to.same.recipient"], selectedItem.otherPartyName)) {
action { newTransferToSameTransactionParty(selectedItem) }
}
item(String.format(FX.messages["account.transactions.table.context.menu.new.transfer.with.same.data"], selectedItem.otherPartyName)) {
@ -110,8 +110,8 @@ open class AccountTransactionsView(private val presenter: BankingPresenter) : Vi
// presenter.showTransactionDetailsWindow(transaction.item)
}
protected open fun newTransferToSameRemittee(transaction: IAccountTransaction) {
presenter.showTransferMoneyDialog(TransferMoneyData.fromAccountTransactionWithoutAmountAndUsage(transaction))
protected open fun newTransferToSameTransactionParty(transaction: IAccountTransaction) {
presenter.showTransferMoneyDialog(TransferMoneyData.fromAccountTransactionWithoutAmountAndReference(transaction))
}
protected open fun newTransferWithSameData(transaction: IAccountTransaction) {

View File

@ -0,0 +1,29 @@
package net.dankito.banking.ui.javafx.dialogs.cashtransfer
import net.dankito.banking.search.TransactionParty
import tornadofx.*
open class RecipientListCellFragment : ListCellFragment<TransactionParty>() {
companion object {
const val ItemHeight = 60.0
}
open val recipient = RecipientViewModel().bindTo(this)
override val root = vbox {
label(recipient.name)
label(recipient.bankName) {
vboxConstraints {
marginTopBottom(6.0)
}
}
label(recipient.iban)
}
}

View File

@ -1,11 +1,11 @@
package net.dankito.banking.ui.javafx.dialogs.cashtransfer
import javafx.beans.property.SimpleStringProperty
import net.dankito.banking.search.Remittee
import net.dankito.banking.search.TransactionParty
import tornadofx.ItemViewModel
open class RemitteeViewModel : ItemViewModel<Remittee>() {
open class RecipientViewModel : ItemViewModel<TransactionParty>() {
val name = bind { SimpleStringProperty(item?.name) }

View File

@ -1,29 +0,0 @@
package net.dankito.banking.ui.javafx.dialogs.cashtransfer
import net.dankito.banking.search.Remittee
import tornadofx.*
open class RemitteeListCellFragment : ListCellFragment<Remittee>() {
companion object {
const val ItemHeight = 60.0
}
open val remittee = RemitteeViewModel().bindTo(this)
override val root = vbox {
label(remittee.name)
label(remittee.bankName) {
vboxConstraints {
marginTopBottom(6.0)
}
}
label(remittee.iban)
}
}

View File

@ -18,7 +18,7 @@ import net.dankito.banking.ui.model.responses.BankingClientResponse
import net.dankito.banking.ui.presenter.BankingPresenter
import net.dankito.banking.util.InputValidator
import net.dankito.banking.bankfinder.BankInfo
import net.dankito.banking.search.Remittee
import net.dankito.banking.search.TransactionParty
import net.dankito.utils.multiplatform.toBigDecimal
import net.dankito.banking.ui.javafx.extensions.focusNextControl
import net.dankito.utils.javafx.ui.controls.AutoCompletionSearchTextField
@ -53,30 +53,31 @@ open class TransferMoneyDialog @JvmOverloads constructor(
protected val showBankAccounts = SimpleBooleanProperty(bankAccountsSupportingTransferringMoney.size > 1)
protected val remitteeName = SimpleStringProperty(preselectedValues?.creditorName ?: "")
protected val recipientName = SimpleStringProperty(preselectedValues?.recipientName ?: "")
protected val remitteeIban = SimpleStringProperty(preselectedValues?.creditorIban ?: "")
protected val recipientIban = SimpleStringProperty(preselectedValues?.recipientAccountId ?: "")
protected val remitteeBank = SimpleObjectProperty<BankInfo>()
protected val recipientBank = SimpleObjectProperty<BankInfo>()
protected val remitteeBankName = SimpleStringProperty()
protected val recipientBankName = SimpleStringProperty()
protected val remitteeBic = SimpleStringProperty(preselectedValues?.creditorBic ?: "")
protected val recipientBic = SimpleStringProperty(preselectedValues?.recipientBankCode ?: "")
protected val amount = SimpleDoubleProperty(preselectedValues?.amount?.toDouble() ?: 0.0)
protected val usage = SimpleStringProperty(preselectedValues?.usage ?: "")
protected val reference = SimpleStringProperty(preselectedValues?.reference ?: "")
protected val instantPayment = SimpleBooleanProperty(false)
protected val realTimeTransfer = SimpleBooleanProperty(false)
protected val supportsInstantPayment = SimpleBooleanProperty(selectedBankAccount.value?.supportsInstantPaymentMoneyTransfer ?: false)
protected val supportsRealTimeTransfer
= SimpleBooleanProperty(selectedBankAccount.value?.supportsRealTimeTransfer ?: false)
protected val requiredDataEntered = SimpleBooleanProperty(false)
protected var txtfldRemitteeName: AutoCompletionSearchTextField<Remittee> by singleAssign()
protected var txtfldRecipientName: AutoCompletionSearchTextField<TransactionParty> by singleAssign()
protected var lastSearchRemitteeJob: Job? = null
protected var lastSearchRecipientJob: Job? = null
protected val inputValidator = InputValidator()
@ -87,11 +88,11 @@ open class TransferMoneyDialog @JvmOverloads constructor(
init {
selectedBankAccount.addListener { _, _, newValue -> selectedBankAccountChanged(newValue) }
remitteeName.addListener { _, _, _ -> checkIfRequiredDataEnteredOnUiThread() }
remitteeIban.addListener { _, _, newValue -> tryToGetBicFromIban(newValue) }
remitteeBic.addListener { _, _, _ -> checkIfRequiredDataEnteredOnUiThread() }
recipientName.addListener { _, _, _ -> checkIfRequiredDataEnteredOnUiThread() }
recipientIban.addListener { _, _, newValue -> tryToGetBicFromIban(newValue) }
recipientBic.addListener { _, _, _ -> checkIfRequiredDataEnteredOnUiThread() }
amount.addListener { _, _, _ -> checkIfRequiredDataEnteredOnUiThread() }
usage.addListener { _, _, _ -> checkIfRequiredDataEnteredOnUiThread() }
reference.addListener { _, _, _ -> checkIfRequiredDataEnteredOnUiThread() }
}
@ -131,30 +132,30 @@ open class TransferMoneyDialog @JvmOverloads constructor(
}
}
field(messages["transfer.money.dialog.remittee.name.label"]) {
field(messages["transfer.money.dialog.recipient.name.label"]) {
fixedHeight = FieldHeight
txtfldRemitteeName = autocompletionsearchtextfield(this@TransferMoneyDialog.remitteeName) {
txtfldRecipientName = autocompletionsearchtextfield(this@TransferMoneyDialog.recipientName) {
fixedHeight = TextFieldHeight
textProperty().addListener { _, _, newValue -> searchRemittees(newValue) }
textProperty().addListener { _, _, newValue -> searchRecipients(newValue) }
onAutoCompletion = { remitteeSelected(it) }
listCellFragment = RemitteeListCellFragment::class
onAutoCompletion = { recipientSelected(it) }
listCellFragment = RecipientListCellFragment::class
setPrefItemHeight(RemitteeListCellFragment.ItemHeight)
setPrefItemHeight(RecipientListCellFragment.ItemHeight)
}
}
field(messages["transfer.money.dialog.remittee.iban.label"]) {
field(messages["transfer.money.dialog.recipient.iban.label"]) {
fixedHeight = FieldHeight
textfield(remitteeIban) {
textfield(recipientIban) {
fixedHeight = TextFieldHeight
paddingLeft = 8.0
if (this@TransferMoneyDialog.remitteeName.value.isNotBlank()) {
if (this@TransferMoneyDialog.recipientName.value.isNotBlank()) {
runLater {
requestFocus()
}
@ -162,10 +163,10 @@ open class TransferMoneyDialog @JvmOverloads constructor(
}
}
field(messages["transfer.money.dialog.remittee.bank.label"]) {
field(messages["transfer.money.dialog.recipient.bank.label"]) {
fixedHeight = FieldHeight
textfield(remitteeBankName) {
textfield(recipientBankName) {
fixedHeight = TextFieldHeight
isDisable = true
@ -174,10 +175,10 @@ open class TransferMoneyDialog @JvmOverloads constructor(
}
}
field(messages["transfer.money.dialog.remittee.bic.label"]) {
field(messages["transfer.money.dialog.recipient.bic.label"]) {
fixedHeight = FieldHeight
textfield(remitteeBic) {
textfield(recipientBic) {
fixedHeight = TextFieldHeight
isDisable = true
@ -197,7 +198,7 @@ open class TransferMoneyDialog @JvmOverloads constructor(
fixedWidth = 100.0
alignment = Pos.CENTER_RIGHT
if (this@TransferMoneyDialog.remitteeName.value.isNotBlank() && remitteeIban.value.isNotBlank()) {
if (this@TransferMoneyDialog.recipientName.value.isNotBlank() && recipientIban.value.isNotBlank()) {
runLater {
requestFocus()
}
@ -212,10 +213,10 @@ open class TransferMoneyDialog @JvmOverloads constructor(
}
}
field(messages["transfer.money.dialog.usage.label"]) {
field(messages["transfer.money.dialog.reference.label"]) {
fixedHeight = FieldHeight
textfield(usage) {
textfield(reference) {
fixedHeight = TextFieldHeight
}
}
@ -223,10 +224,10 @@ open class TransferMoneyDialog @JvmOverloads constructor(
field {
fixedHeight = FieldHeight
checkbox(messages["transfer.money.dialog.instant.payment.label"], instantPayment) {
checkbox(messages["transfer.money.dialog.real.time.transfer.label"], realTimeTransfer) {
fixedHeight = TextFieldHeight
enableWhen(supportsInstantPayment)
enableWhen(supportsRealTimeTransfer)
}
}
}
@ -264,37 +265,37 @@ open class TransferMoneyDialog @JvmOverloads constructor(
}
}
tryToGetBicFromIban(remitteeIban.value)
tryToGetBicFromIban(recipientIban.value)
}
private fun selectedBankAccountChanged(newValue: TypedBankAccount?) {
supportsInstantPayment.value = newValue?.supportsInstantPaymentMoneyTransfer ?: false
supportsRealTimeTransfer.value = newValue?.supportsRealTimeTransfer ?: false
if (supportsInstantPayment.value == false) {
instantPayment.value = false
if (supportsRealTimeTransfer.value == false) {
realTimeTransfer.value = false
}
}
protected open fun searchRemittees(query: String?) {
lastSearchRemitteeJob?.cancel()
protected open fun searchRecipients(query: String?) {
lastSearchRecipientJob?.cancel()
lastSearchRemitteeJob = GlobalScope.launch(Dispatchers.IO) {
val potentialRemittees = presenter.findRemitteesForName(query?.toString() ?: "")
lastSearchRecipientJob = GlobalScope.launch(Dispatchers.IO) {
val potentialRecipients = presenter.findRecipientsForName(query?.toString() ?: "")
withContext(Dispatchers.Main) {
txtfldRemitteeName.setAutoCompleteList(potentialRemittees)
txtfldRecipientName.setAutoCompleteList(potentialRecipients)
}
}
}
protected open fun remitteeSelected(remittee: Remittee) {
txtfldRemitteeName.focusNextControl()
protected open fun recipientSelected(transactionParty: TransactionParty) {
txtfldRecipientName.focusNextControl()
remitteeName.value = remittee.name
remitteeBic.value = remittee.bic
remitteeIban.value = remittee.iban
recipientName.value = transactionParty.name
recipientBic.value = transactionParty.bic
recipientIban.value = transactionParty.iban
}
@ -307,11 +308,11 @@ open class TransferMoneyDialog @JvmOverloads constructor(
}
protected open fun showValuesForFoundBankOnUiThread(firstFoundBank: BankInfo?, enteredIban: String) {
remitteeBank.value = firstFoundBank
recipientBank.value = firstFoundBank
remitteeBankName.value = determineFoundBankLabel(enteredIban, firstFoundBank)
recipientBankName.value = determineFoundBankLabel(enteredIban, firstFoundBank)
remitteeBic.value = firstFoundBank?.bic ?: messages["transfer.money.dialog.bank.name.will.be.entered.automatically"]
recipientBic.value = firstFoundBank?.bic ?: messages["transfer.money.dialog.bank.name.will.be.entered.automatically"]
checkIfRequiredDataEnteredOnUiThread()
}
@ -331,12 +332,12 @@ open class TransferMoneyDialog @JvmOverloads constructor(
protected open fun checkIfRequiredDataEnteredOnUiThread() {
requiredDataEntered.value =
remitteeName.value.isNotBlank()
&& inputValidator.isRemitteeNameValid(remitteeName.value) // TODO: show error message for illegal characters
&& inputValidator.isValidIban(remitteeIban.value)
&& inputValidator.isValidBic(remitteeBic.value)
recipientName.value.isNotBlank()
&& inputValidator.isRecipientNameValid(recipientName.value) // TODO: show error message for illegal characters
&& inputValidator.isValidIban(recipientIban.value)
&& inputValidator.isValidBic(recipientBic.value)
&& amount.value > 0
&& inputValidator.isUsageValid(usage.value) // TODO: show error message for illegal characters
&& inputValidator.isReferenceValid(reference.value) // TODO: show error message for illegal characters
}
@ -345,17 +346,17 @@ open class TransferMoneyDialog @JvmOverloads constructor(
}
protected open fun transferMoney() {
remitteeBank.value?.let {
recipientBank.value?.let {
val account = selectedBankAccount.value
val data = TransferMoneyData(
account,
inputValidator.convertToAllowedSepaCharacters(remitteeName.value),
remitteeIban.value.replace(" ", ""),
remitteeBic.value.replace(" ", ""),
inputValidator.convertToAllowedSepaCharacters(recipientName.value),
recipientIban.value.replace(" ", ""),
recipientBic.value.replace(" ", ""),
amount.value.toBigDecimal().toBigDecimal(),
inputValidator.convertToAllowedSepaCharacters(usage.value),
instantPayment.value
inputValidator.convertToAllowedSepaCharacters(reference.value),
realTimeTransfer.value
)
presenter.transferMoneyAsync(data) {
@ -371,11 +372,11 @@ open class TransferMoneyDialog @JvmOverloads constructor(
if (response.successful) {
dialogService.showInfoMessage(String.format(messages["transfer.money.dialog.message.transfer.cash.success"],
transferData.amount, currency, transferData.creditorName), null, currentStage)
transferData.amount, currency, transferData.recipientName), null, currentStage)
}
else if (response.userCancelledAction == false) {
dialogService.showErrorMessage(String.format(messages["transfer.money.dialog.message.transfer.cash.error"],
transferData.amount, currency, transferData.creditorName, response.errorToShowToUser), null, null, currentStage)
transferData.amount, currency, transferData.recipientName, response.errorToShowToUser), null, null, currentStage)
}
if (response.successful || response.userCancelledAction) { // do not close dialog if an error occurred

View File

@ -1,8 +0,0 @@
package net.dankito.banking.search
interface IRemitteeSearcher {
fun findRemittees(query: String): List<Remittee>
}

View File

@ -0,0 +1,8 @@
package net.dankito.banking.search
interface ITransactionPartySearcher {
fun findTransactionParty(query: String): List<TransactionParty>
}

View File

@ -1,10 +0,0 @@
package net.dankito.banking.search
open class NoOpRemitteeSearcher : IRemitteeSearcher {
override fun findRemittees(query: String): List<Remittee> {
return listOf()
}
}

View File

@ -0,0 +1,10 @@
package net.dankito.banking.search
open class NoOpTransactionPartySearcher : ITransactionPartySearcher {
override fun findTransactionParty(query: String): List<TransactionParty> {
return listOf()
}
}

View File

@ -1,7 +1,7 @@
package net.dankito.banking.search
data class Remittee(
data class TransactionParty(
val name: String,
val iban: String?,
val bic: String?,
@ -14,7 +14,7 @@ data class Remittee(
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is Remittee) return false
if (other !is TransactionParty) return false
if (name.equals(other.name, true) == false) return false
if (iban != other.iban) return false

View File

@ -8,7 +8,7 @@ open class AccountTransaction(
override val account: TypedBankAccount,
override val amount: BigDecimal,
override val currency: String,
override val unparsedUsage: String,
override val unparsedReference: String,
override val bookingDate: Date,
override val otherPartyName: String?,
override val otherPartyBankCode: String?,
@ -27,10 +27,10 @@ open class AccountTransaction(
override val originatorsIdentificationCode: String?,
override val compensationAmount: String?,
override val originalAmount: String?,
override val sepaUsage: String?,
override val sepaReference: String?,
override val deviantOriginator: String?,
override val deviantRecipient: String?,
override val usageWithNoSpecialType: String?,
override val referenceWithNoSpecialType: String?,
override val primaNotaNumber: String?,
override val textKeySupplement: String?,
@ -49,15 +49,15 @@ open class AccountTransaction(
/* convenience constructors for languages not supporting default values */
constructor(account: BankAccount, otherPartyName: String?, unparsedUsage: String, amount: BigDecimal, valueDate: Date, bookingText: String?)
: this(account, amount, "EUR", unparsedUsage, valueDate,
constructor(account: BankAccount, otherPartyName: String?, unparsedReference: String, amount: BigDecimal, valueDate: Date, bookingText: String?)
: this(account, amount, "EUR", unparsedReference, valueDate,
otherPartyName, null, null, bookingText, valueDate)
constructor(account: BankAccount, amount: BigDecimal, currency: String, unparsedUsage: String, bookingDate: Date,
constructor(account: BankAccount, amount: BigDecimal, currency: String, unparsedReference: String, bookingDate: Date,
otherPartyName: String?, otherPartyBankCode: String?, otherPartyAccountId: String?,
bookingText: String?, valueDate: Date)
: this(account, amount, currency, unparsedUsage, bookingDate,
: this(account, amount, currency, unparsedReference, bookingDate,
otherPartyName, otherPartyBankCode, otherPartyAccountId, bookingText, valueDate,
0, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, "", "", null, null, "", null)

View File

@ -23,7 +23,7 @@ open class BankAccount @JvmOverloads constructor(
override var supportsRetrievingAccountTransactions: Boolean = false,
override var supportsRetrievingBalance: Boolean = false,
override var supportsTransferringMoney: Boolean = false,
override var supportsInstantPaymentMoneyTransfer: Boolean = false,
override var supportsRealTimeTransfer: Boolean = false,
override var bookedTransactions: List<IAccountTransaction> = listOf(),
override var unbookedTransactions: List<Any> = listOf()
) : TypedBankAccount {

View File

@ -16,7 +16,7 @@ interface IAccountTransaction {
val account: IBankAccount<*>
val amount: BigDecimal
val currency: String
val unparsedUsage: String
val unparsedReference: String
val bookingDate: Date
val otherPartyName: String?
val otherPartyBankCode: String?
@ -35,10 +35,10 @@ interface IAccountTransaction {
val originatorsIdentificationCode: String?
val compensationAmount: String?
val originalAmount: String?
val sepaUsage: String?
val sepaReference: String?
val deviantOriginator: String?
val deviantRecipient: String?
val usageWithNoSpecialType: String?
val referenceWithNoSpecialType: String?
val primaNotaNumber: String?
val textKeySupplement: String?
@ -54,7 +54,7 @@ interface IAccountTransaction {
var technicalId: String
open val transactionIdentifier: String
val transactionIdentifier: String
get() = buildTransactionIdentifier()
@ -64,16 +64,16 @@ interface IAccountTransaction {
val canCreateMoneyTransferFrom: Boolean
get() = otherPartyAccountId != null && account.supportsTransferringMoney
val usage: String
get() = sepaUsage ?: unparsedUsage
val reference: String
get() = sepaReference ?: unparsedReference
fun buildTransactionIdentifier() : String {
if (account != null) {
return "${account.technicalId} ${IdDateFormat.format(bookingDate)} ${IdDateFormat.format(valueDate)} $amount $currency $unparsedUsage $otherPartyName $otherPartyBankCode $otherPartyAccountId"
return "${account.technicalId} ${IdDateFormat.format(bookingDate)} ${IdDateFormat.format(valueDate)} $amount $currency $unparsedReference $otherPartyName $otherPartyBankCode $otherPartyAccountId"
}
else { // happens for derived classes during initialization. These have to set technicalId after initialization by themselves
return "<uninitialized_bank_acccount> ${IdDateFormat.format(bookingDate)} ${IdDateFormat.format(valueDate)} $amount $currency $unparsedUsage $otherPartyName $otherPartyBankCode $otherPartyAccountId"
return "<uninitialized_bank_acccount> ${IdDateFormat.format(bookingDate)} ${IdDateFormat.format(valueDate)} $amount $currency $unparsedReference $otherPartyName $otherPartyBankCode $otherPartyAccountId"
}
}
@ -86,7 +86,7 @@ interface IAccountTransaction {
if (account != other.account) return false
if (amount != other.amount) return false
if (currency != other.currency) return false
if (unparsedUsage != other.unparsedUsage) return false
if (unparsedReference != other.unparsedReference) return false
if (bookingDate != other.bookingDate) return false
if (otherPartyName != other.otherPartyName) return false
if (otherPartyBankCode != other.otherPartyBankCode) return false
@ -101,7 +101,7 @@ interface IAccountTransaction {
var result = account.hashCode()
result = 31 * result + amount.hashCode()
result = 31 * result + currency.hashCode()
result = 31 * result + unparsedUsage.hashCode()
result = 31 * result + unparsedReference.hashCode()
result = 31 * result + bookingDate.hashCode()
result = 31 * result + (otherPartyName?.hashCode() ?: 0)
result = 31 * result + (otherPartyBankCode?.hashCode() ?: 0)
@ -112,6 +112,6 @@ interface IAccountTransaction {
}
val stringRepresentation: String
get() = "${DateFormatter(DateFormatStyle.Medium).format(valueDate)} $amount $otherPartyName: $usage"
get() = "${DateFormatter(DateFormatStyle.Medium).format(valueDate)} $amount $otherPartyName: $reference"
}

View File

@ -24,7 +24,7 @@ interface IBankAccount<TTransaction: IAccountTransaction> : OrderedDisplayable {
var supportsRetrievingAccountTransactions: Boolean
var supportsRetrievingBalance: Boolean
var supportsTransferringMoney: Boolean
var supportsInstantPaymentMoneyTransfer: Boolean
var supportsRealTimeTransfer: Boolean
var bookedTransactions: List<TTransaction>
var unbookedTransactions: List<Any>
var technicalId: String

View File

@ -22,7 +22,7 @@ open class DefaultModelCreator : IModelCreator {
account: TypedBankAccount,
amount: BigDecimal,
currency: String,
unparsedUsage: String,
unparsedReference: String,
bookingDate: Date,
otherPartyName: String?,
otherPartyBankCode: String?,
@ -40,10 +40,10 @@ open class DefaultModelCreator : IModelCreator {
originatorsIdentificationCode: String?,
compensationAmount: String?,
originalAmount: String?,
sepaUsage: String?,
sepaReference: String?,
deviantOriginator: String?,
deviantRecipient: String?,
usageWithNoSpecialType: String?,
referenceWithNoSpecialType: String?,
primaNotaNumber: String?,
textKeySupplement: String?,
currencyType: String?,
@ -55,11 +55,11 @@ open class DefaultModelCreator : IModelCreator {
relatedReferenceNumber: String?
) : IAccountTransaction {
return AccountTransaction(account, amount, currency, unparsedUsage, bookingDate,
return AccountTransaction(account, amount, currency, unparsedReference, bookingDate,
otherPartyName, otherPartyBankCode, otherPartyAccountId, bookingText, valueDate, statementNumber, sequenceNumber,
openingBalance, closingBalance, endToEndReference, customerReference, mandateReference, creditorIdentifier,
originatorsIdentificationCode, compensationAmount, originalAmount, sepaUsage, deviantOriginator, deviantRecipient,
usageWithNoSpecialType, primaNotaNumber, textKeySupplement, currencyType, bookingKey, referenceForTheAccountOwner,
originatorsIdentificationCode, compensationAmount, originalAmount, sepaReference, deviantOriginator, deviantRecipient,
referenceWithNoSpecialType, primaNotaNumber, textKeySupplement, currencyType, bookingKey, referenceForTheAccountOwner,
referenceOfTheAccountServicingInstitution, supplementaryDetails, transactionReferenceNumber, relatedReferenceNumber)
}

View File

@ -19,7 +19,7 @@ interface IModelCreator {
account: TypedBankAccount,
amount: BigDecimal,
currency: String,
unparsedUsage: String,
unparsedReference: String,
bookingDate: Date,
otherPartyName: String?,
otherPartyBankCode: String?,
@ -38,10 +38,10 @@ interface IModelCreator {
originatorsIdentificationCode: String?,
compensationAmount: String?,
originalAmount: String?,
sepaUsage: String?,
sepaReference: String?,
deviantOriginator: String?,
deviantRecipient: String?,
usageWithNoSpecialType: String?,
referenceWithNoSpecialType: String?,
primaNotaNumber: String?,
textKeySupplement: String?,

View File

@ -6,17 +6,17 @@ import net.dankito.utils.multiplatform.BigDecimal
open class TransferMoneyData(
val account: TypedBankAccount,
val creditorName: String,
val creditorIban: String,
val creditorBic: String,
val recipientName: String,
val recipientAccountId: String,
val recipientBankCode: String,
val amount: BigDecimal,
val usage: String,
val instantPayment: Boolean = false
val reference: String,
val realTimeTransfer: Boolean = false
) {
companion object {
fun fromAccountTransactionWithoutAmountAndUsage(transaction: IAccountTransaction): TransferMoneyData {
fun fromAccountTransactionWithoutAmountAndReference(transaction: IAccountTransaction): TransferMoneyData {
return TransferMoneyData(
transaction.account as TypedBankAccount,
transaction.otherPartyName ?: "",
@ -34,7 +34,7 @@ open class TransferMoneyData(
transaction.otherPartyAccountId ?: "",
transaction.otherPartyBankCode ?: "",
if (transaction.amount.isPositive) transaction.amount else transaction.amount.negated(),
transaction.usage
transaction.reference
)
}

View File

@ -12,9 +12,9 @@ import net.dankito.banking.ui.model.responses.BankingClientResponse
import net.dankito.banking.ui.model.responses.GetTransactionsResponse
import net.dankito.banking.bankfinder.IBankFinder
import net.dankito.banking.bankfinder.BankInfo
import net.dankito.banking.search.IRemitteeSearcher
import net.dankito.banking.search.NoOpRemitteeSearcher
import net.dankito.banking.search.Remittee
import net.dankito.banking.search.ITransactionPartySearcher
import net.dankito.banking.search.NoOpTransactionPartySearcher
import net.dankito.banking.search.TransactionParty
import net.dankito.banking.ui.model.mapper.DefaultModelCreator
import net.dankito.banking.ui.model.mapper.IModelCreator
import net.dankito.banking.ui.model.moneytransfer.ExtractTransferMoneyDataFromPdfResult
@ -39,7 +39,7 @@ open class BankingPresenter(
protected val persister: IBankingPersistence,
protected val router: IRouter,
protected val modelCreator: IModelCreator = DefaultModelCreator(),
protected val remitteeSearcher: IRemitteeSearcher = NoOpRemitteeSearcher(),
protected val transactionPartySearcher: ITransactionPartySearcher = NoOpTransactionPartySearcher(),
protected val bankIconFinder: IBankIconFinder = NoOpBankIconFinder(),
protected val textExtractorRegistry: ITextExtractorRegistry = NoOpTextExtractorRegistry(),
protected val invoiceDataExtractor: IInvoiceDataExtractor = NoOpInvoiceDataExtractor(),
@ -553,16 +553,16 @@ open class BankingPresenter(
return bankFinder.findBankByNameBankCodeOrCity(query)
}
open fun findRemitteesForName(name: String): List<Remittee> {
return remitteeSearcher.findRemittees(name).map { remittee ->
remittee.bankName = tryToFindBankName(remittee)
open fun findRecipientsForName(name: String): List<TransactionParty> {
return transactionPartySearcher.findTransactionParty(name).map { recipient ->
recipient.bankName = tryToFindBankName(recipient)
remittee
recipient
}.toSet().toList()
}
protected open fun tryToFindBankName(remittee: Remittee): String? {
remittee.bic?.let { bic ->
protected open fun tryToFindBankName(transactionParty: TransactionParty): String? {
transactionParty.bic?.let { bic ->
bankFinder.findBankByBic(bic)?.name?.let {
return it
}
@ -574,7 +574,7 @@ open class BankingPresenter(
}
}
remittee.iban?.let { iban ->
transactionParty.iban?.let { iban ->
if (iban.length > 12) {
val bankCode = iban.substring(4, 12)
return bankFinder.findBankByBankCode(bankCode).firstOrNull()?.name
@ -598,7 +598,7 @@ open class BankingPresenter(
return transactions.filter {
it.otherPartyName?.toLowerCase()?.contains(queryLowercase) == true
|| it.usage.toLowerCase().contains(queryLowercase)
|| it.reference.toLowerCase().contains(queryLowercase)
|| it.bookingText?.toLowerCase()?.contains(queryLowercase) == true
}
}

View File

@ -9,13 +9,13 @@ open class InputValidator {
companion object {
const val RemitteNameMaxLength = 70
const val RecipientNameMaxLength = 70
const val IbanMaxLength = 34
const val BicMaxLength = 11
const val UsageMaxLength = 140
const val ReferenceMaxLength = 140
const val MinimumLengthToDetermineBicFromIban = 12 // TODO: this is only true for German (and may some other) IBANs
@ -65,45 +65,45 @@ open class InputValidator {
protected val sepaMessageCreator: ISepaMessageCreator = SepaMessageCreator()
open fun validateRemitteeNameWhileTyping(remitteeNameToTest: String): ValidationResult {
return validateRemitteeName(remitteeNameToTest, true)
open fun validateRecipientNameWhileTyping(recipientNameToTest: String): ValidationResult {
return validateRecipientName(recipientNameToTest, true)
}
open fun validateRemitteeName(remitteeNameToTest: String): ValidationResult {
return validateRemitteeName(remitteeNameToTest, false)
open fun validateRecipientName(recipientNameToTest: String): ValidationResult {
return validateRecipientName(recipientNameToTest, false)
}
open fun validateRemitteeName(remitteeNameToTest: String, userIsStillTyping: Boolean = false): ValidationResult {
if (isRemitteeNameValid(remitteeNameToTest)) {
return ValidationResult(remitteeNameToTest, true)
open fun validateRecipientName(recipientNameToTest: String, userIsStillTyping: Boolean = false): ValidationResult {
if (isRecipientNameValid(recipientNameToTest)) {
return ValidationResult(recipientNameToTest, true)
}
if (remitteeNameToTest.isEmpty()) {
if (recipientNameToTest.isEmpty()) {
if (userIsStillTyping) { // if user is still typing, don't check if something has been entered yet
return ValidationResult(remitteeNameToTest, true)
return ValidationResult(recipientNameToTest, true)
}
return ValidationResult(remitteeNameToTest, false, validationError = "Bitte geben Sie den Namen des Empfängers ein") // TODO: translate
return ValidationResult(recipientNameToTest, false, validationError = "Bitte geben Sie den Namen des Empfängers ein") // TODO: translate
}
if (hasRemitteeNameValidLength(remitteeNameToTest) == false) {
val correctedString = remitteeNameToTest.substring(0, RemitteNameMaxLength)
return ValidationResult(remitteeNameToTest, isRemitteeNameValid(correctedString), true, correctedString, "Name darf maximal 70 Zeichen lang sein") // TODO: translate
if (hasRecipientNameValidLength(recipientNameToTest) == false) {
val correctedString = recipientNameToTest.substring(0, RecipientNameMaxLength)
return ValidationResult(recipientNameToTest, isRecipientNameValid(correctedString), true, correctedString, "Name darf maximal 70 Zeichen lang sein") // TODO: translate
}
val invalidRemitteeNameCharacters = getInvalidSepaCharacters(remitteeNameToTest)
val invalidRecipientNameCharacters = getInvalidSepaCharacters(recipientNameToTest)
val correctedString = getCorrectedString(remitteeNameToTest, invalidRemitteeNameCharacters, true)
return ValidationResult(remitteeNameToTest, isRemitteeNameValid(correctedString), true, correctedString, null, "Unzulässige(s) Zeichen eingegeben: $invalidRemitteeNameCharacters") // TODO: translate
val correctedString = getCorrectedString(recipientNameToTest, invalidRecipientNameCharacters, true)
return ValidationResult(recipientNameToTest, isRecipientNameValid(correctedString), true, correctedString, null, "Unzulässige(s) Zeichen eingegeben: $invalidRecipientNameCharacters") // TODO: translate
}
open fun isRemitteeNameValid(stringToTest: String): Boolean {
return hasRemitteeNameValidLength(stringToTest)
open fun isRecipientNameValid(stringToTest: String): Boolean {
return hasRecipientNameValidLength(stringToTest)
&& containsOnlyValidSepaCharacters(stringToTest)
}
open fun hasRemitteeNameValidLength(stringToTest: String): Boolean {
return stringToTest.length in 1..RemitteNameMaxLength
open fun hasRecipientNameValidLength(stringToTest: String): Boolean {
return stringToTest.length in 1..RecipientNameMaxLength
}
@ -221,29 +221,29 @@ open class InputValidator {
}
open fun validateUsage(usageToTest: String): ValidationResult {
if (isUsageValid(usageToTest)) {
return ValidationResult(usageToTest, true)
open fun validateReference(referenceToTest: String): ValidationResult {
if (isReferenceValid(referenceToTest)) {
return ValidationResult(referenceToTest, true)
}
if (hasUsageValidLength(usageToTest) == false) {
val correctedString = usageToTest.substring(0, UsageMaxLength)
return ValidationResult(usageToTest, isUsageValid(correctedString), true, correctedString, "Verwendungszweck darf nur 140 Zeichen lang sein") // TODO: translate
if (hasReferenceValidLength(referenceToTest) == false) {
val correctedString = referenceToTest.substring(0, ReferenceMaxLength)
return ValidationResult(referenceToTest, isReferenceValid(correctedString), true, correctedString, "Verwendungszweck darf nur 140 Zeichen lang sein") // TODO: translate
}
val invalidUsageCharacters = getInvalidSepaCharacters(usageToTest)
val correctedString = getCorrectedString(usageToTest, invalidUsageCharacters, true)
return ValidationResult(usageToTest, isUsageValid(correctedString), true, correctedString, null, "Unzulässige(s) Zeichen eingegeben: $invalidUsageCharacters") // TODO: translate return ValidationResult(remitteeNameToTest, false, validationError = "Unzulässige(s) Zeichen eingegeben: ") // TODO: translate
val invalidReferenceCharacters = getInvalidSepaCharacters(referenceToTest)
val correctedString = getCorrectedString(referenceToTest, invalidReferenceCharacters, true)
return ValidationResult(referenceToTest, isReferenceValid(correctedString), true, correctedString, null, "Unzulässige(s) Zeichen eingegeben: $invalidReferenceCharacters") // TODO: translate return ValidationResult(recipentNameToTest, false, validationError = "Unzulässige(s) Zeichen eingegeben: ") // TODO: translate
}
open fun isUsageValid(stringToTest: String): Boolean {
return hasUsageValidLength(stringToTest)
open fun isReferenceValid(stringToTest: String): Boolean {
return hasReferenceValidLength(stringToTest)
&& containsOnlyValidSepaCharacters(stringToTest)
}
open fun hasUsageValidLength(stringToTest: String): Boolean {
return stringToTest.length in 0..UsageMaxLength // usage is not a required field -> may be empty
open fun hasReferenceValidLength(stringToTest: String): Boolean {
return stringToTest.length in 0..ReferenceMaxLength // reference is not a required field -> may be empty
}
@ -265,7 +265,7 @@ open class InputValidator {
}
// TODO: do not convert XML entities in user's. User will a) not understand what happened and b) afterwards auto correction will not work anymore (i think the issue lies in used Regex: '(&\w{2,4};)').
// But take converted XML entities length into account when checking if remittee's name and usage length isn't too long
// But take converted XML entities length into account when checking if recipient's name and reference length isn't too long
protected open fun getCorrectedString(inputString: String, invalidCharacters: String, convertToAllowedSepaCharacters: Boolean = false): String {
var correctedString = if (convertToAllowedSepaCharacters) convertToAllowedSepaCharacters(inputString) else inputString

View File

@ -10,13 +10,13 @@ class InputValidatorTest {
companion object {
const val ValidRemitteeName = "Marieke Musterfrau"
const val ValidRecipientName = "Marieke Musterfrau"
const val ValidIban = "DE11123456780987654321"
const val ValidBic = "ABCDDEBBXXX"
const val ValidUsage = "Usage"
const val ValidReference = "Reference"
const val InvalidSepaCharacter = "!"
@ -56,13 +56,13 @@ class InputValidatorTest {
@Test
fun validateRemitteeName_EmptyStringEntered() {
fun validateRecipientName_EmptyStringEntered() {
// given
val enteredName = ""
// when
val result = underTest.validateRemitteeName(enteredName)
val result = underTest.validateRecipientName(enteredName)
// then
expect(result.validationSuccessful).toBe(false)
@ -74,13 +74,13 @@ class InputValidatorTest {
}
@Test
fun validateRemitteeName_ValidNameEntered() {
fun validateRecipientName_ValidNameEntered() {
// given
val enteredName = ValidRemitteeName
val enteredName = ValidRecipientName
// when
val result = underTest.validateRemitteeName(enteredName)
val result = underTest.validateRecipientName(enteredName)
// then
expect(result.validationSuccessful).toBe(true)
@ -92,50 +92,50 @@ class InputValidatorTest {
}
@Test
fun validateRemitteeName_UmlautGetsConverted() {
fun validateRecipientName_UmlautGetsConverted() {
// given
val enteredName = ValidRemitteeName + InvalidUmlaut
val enteredName = ValidRecipientName + InvalidUmlaut
// when
val result = underTest.validateRemitteeName(enteredName)
val result = underTest.validateRecipientName(enteredName)
// then
expect(result.validationSuccessful).toBe(true)
expect(result.didCorrectString).toBe(true)
expect(result.inputString).toBe(enteredName)
expect(result.correctedInputString).toBe(ValidRemitteeName + ConvertedInvalidUmlaut)
expect(result.correctedInputString).toBe(ValidRecipientName + ConvertedInvalidUmlaut)
expect(result.validationHint?.contains(InvalidUmlaut)).toBe(true)
expect(result.validationError).toBe(null)
}
@Test
fun validateRemitteeName_InvalidCharacterGetsRemoved() {
fun validateRecipientName_InvalidCharacterGetsRemoved() {
// given
val enteredName = ValidRemitteeName + InvalidSepaCharacter
val enteredName = ValidRecipientName + InvalidSepaCharacter
// when
val result = underTest.validateRemitteeName(enteredName)
val result = underTest.validateRecipientName(enteredName)
// then
expect(result.validationSuccessful).toBe(true)
expect(result.didCorrectString).toBe(true)
expect(result.inputString).toBe(enteredName)
expect(result.correctedInputString).toBe(ValidRemitteeName)
expect(result.correctedInputString).toBe(ValidRecipientName)
expect(result.validationHint?.contains(InvalidSepaCharacter)).toBe(true)
expect(result.validationError).toBe(null)
}
@Test
fun validateRemitteeName_TooLong() {
fun validateRecipientName_TooLong() {
// given
val nameWithMaxLength = IntRange(0, InputValidator.RemitteNameMaxLength - 1).map { "a" }.joinToString("")
val enteredName = nameWithMaxLength + "a"
// when
val result = underTest.validateRemitteeName(enteredName)
val result = underTest.validateRecipientName(enteredName)
// then
expect(result.validationSuccessful).toBe(true)
@ -480,133 +480,133 @@ class InputValidatorTest {
@Test
fun validateUsage_EmptyStringEntered() {
fun validateReference_EmptyStringEntered() {
// given
val enteredUsage = ""
val enteredReference = ""
// when
val result = underTest.validateUsage(enteredUsage)
val result = underTest.validateReference(enteredReference)
// then
expect(result.validationSuccessful).toBe(true)
expect(result.didCorrectString).toBe(false)
expect(result.inputString).toBe(enteredUsage)
expect(result.correctedInputString).toBe(enteredUsage)
expect(result.inputString).toBe(enteredReference)
expect(result.correctedInputString).toBe(enteredReference)
expect(result.validationHint).toBe(null)
expect(result.validationError).toBe(null)
}
@Test
fun validateUsage_ValidUsageEntered() {
fun validateReference_ValidReferenceEntered() {
// given
val enteredUsage = ValidUsage
val enteredReference = ValidReference
// when
val result = underTest.validateUsage(enteredUsage)
val result = underTest.validateReference(enteredReference)
// then
expect(result.validationSuccessful).toBe(true)
expect(result.didCorrectString).toBe(false)
expect(result.inputString).toBe(enteredUsage)
expect(result.correctedInputString).toBe(enteredUsage)
expect(result.inputString).toBe(enteredReference)
expect(result.correctedInputString).toBe(enteredReference)
expect(result.validationHint).toBe(null)
expect(result.validationError).toBe(null)
}
@Test
fun validateUsage_UmlautGetsConverted() {
fun validateReference_UmlautGetsConverted() {
// given
val enteredUsage = ValidUsage + InvalidUmlaut
val enteredReference = ValidReference + InvalidUmlaut
// when
val result = underTest.validateUsage(enteredUsage)
val result = underTest.validateReference(enteredReference)
// then
expect(result.validationSuccessful).toBe(true)
expect(result.didCorrectString).toBe(true)
expect(result.inputString).toBe(enteredUsage)
expect(result.correctedInputString).toBe(ValidUsage + ConvertedInvalidUmlaut)
expect(result.inputString).toBe(enteredReference)
expect(result.correctedInputString).toBe(ValidReference + ConvertedInvalidUmlaut)
expect(result.validationHint?.contains(InvalidUmlaut)).toBe(true)
expect(result.validationError).toBe(null)
}
@Test
fun validateUsage_InvalidCharacterGetsRemoved() {
fun validateReference_InvalidCharacterGetsRemoved() {
// given
val enteredUsage = ValidUsage + InvalidSepaCharacter
val enteredReference = ValidReference + InvalidSepaCharacter
// when
val result = underTest.validateUsage(enteredUsage)
val result = underTest.validateReference(enteredReference)
// then
expect(result.validationSuccessful).toBe(true)
expect(result.didCorrectString).toBe(true)
expect(result.inputString).toBe(enteredUsage)
expect(result.correctedInputString).toBe(ValidUsage)
expect(result.inputString).toBe(enteredReference)
expect(result.correctedInputString).toBe(ValidReference)
expect(result.validationHint?.contains(InvalidSepaCharacter)).toBe(true)
expect(result.validationError).toBe(null)
}
// TODO: does not work yet
@Test
fun validateUsage_AmpersandGetsRemoved() {
fun validateReference_AmpersandGetsRemoved() {
// given
val invalidSepaCharacter = "&"
val enteredUsage = ValidUsage + invalidSepaCharacter
val enteredReference = ValidReference + invalidSepaCharacter
// when
val result = underTest.validateUsage(enteredUsage)
val result = underTest.validateReference(enteredReference)
// then
expect(result.validationSuccessful).toBe(true)
expect(result.didCorrectString).toBe(true)
expect(result.inputString).toBe(enteredUsage)
expect(result.correctedInputString).toBe(ValidUsage)
expect(result.inputString).toBe(enteredReference)
expect(result.correctedInputString).toBe(ValidReference)
expect(result.validationHint?.contains(invalidSepaCharacter)).toBe(true)
expect(result.validationError).toBe(null)
}
// TODO: does not work yet
@Test
fun validateUsage_EnteringACharacterAfterConvertingAXmlEntityDoesNotFail() {
fun validateReference_EnteringACharacterAfterConvertingAXmlEntityDoesNotFail() {
// given
val convertedXmlEntity = "&amp;"
val validSepaCharacter = "h"
val enteredUsage = ValidUsage + convertedXmlEntity + validSepaCharacter
val enteredReference = ValidReference + convertedXmlEntity + validSepaCharacter
// when
val result = underTest.validateUsage(enteredUsage)
val result = underTest.validateReference(enteredReference)
// then
expect(result.validationSuccessful).toBe(true)
expect(result.didCorrectString).toBe(true)
expect(result.inputString).toBe(enteredUsage)
expect(result.correctedInputString).toBe(ValidUsage + convertedXmlEntity + validSepaCharacter)
expect(result.inputString).toBe(enteredReference)
expect(result.correctedInputString).toBe(ValidReference + convertedXmlEntity + validSepaCharacter)
expect(result.validationHint).toBe(null)
expect(result.validationError).toBe(null)
}
@Test
fun validateUsage_TooLong() {
fun validateReference_TooLong() {
// given
val usageWithMaxLength = IntRange(0, InputValidator.UsageMaxLength - 1).map { "a" }.joinToString("")
val enteredUsage = usageWithMaxLength + "a"
val referenceWithMaxLength = IntRange(0, InputValidator.ReferenceMaxLength - 1).map { "a" }.joinToString("")
val enteredReference = referenceWithMaxLength + "a"
// when
val result = underTest.validateUsage(enteredUsage)
val result = underTest.validateReference(enteredReference)
// then
expect(result.validationSuccessful).toBe(true)
expect(result.didCorrectString).toBe(true)
expect(result.inputString).toBe(enteredUsage)
expect(result.correctedInputString).toBe(usageWithMaxLength)
expect(result.inputString).toBe(enteredReference)
expect(result.correctedInputString).toBe(referenceWithMaxLength)
expect(result.validationHint).toBe(null)
expect(result.validationError).notToBeNull()
}

View File

@ -3,7 +3,7 @@ package net.dankito.banking
import net.dankito.banking.bankfinder.InMemoryBankFinder
import net.dankito.banking.fints.webclient.IWebClient
import net.dankito.banking.persistence.IBankingPersistence
import net.dankito.banking.search.IRemitteeSearcher
import net.dankito.banking.search.ITransactionPartySearcher
import net.dankito.banking.ui.IRouter
import net.dankito.banking.ui.model.mapper.DefaultModelCreator
import net.dankito.banking.ui.presenter.BankingPresenter
@ -14,8 +14,8 @@ import net.dankito.utils.multiplatform.File
class BankingPresenterSwift(dataFolder: File, router: IRouter, webClient: IWebClient, persistence: IBankingPersistence,
remitteeSearcher: IRemitteeSearcher, bankIconFinder: IBankIconFinder, serializer: ISerializer, asyncRunner: IAsyncRunner)
transactionPartySearcher: ITransactionPartySearcher, bankIconFinder: IBankIconFinder, serializer: ISerializer, asyncRunner: IAsyncRunner)
: BankingPresenter(fints4kBankingClientCreator(DefaultModelCreator(), serializer, webClient), InMemoryBankFinder(), dataFolder, persistence, router, DefaultModelCreator(),
remitteeSearcher, bankIconFinder, NoOpTextExtractorRegistry(), NoOpInvoiceDataExtractor(), serializer, asyncRunner) {
transactionPartySearcher, bankIconFinder, NoOpTextExtractorRegistry(), NoOpInvoiceDataExtractor(), serializer, asyncRunner) {
}

View File

@ -28,11 +28,11 @@
3642F01425018BA9005186FE /* TabBarController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3642F01325018BA9005186FE /* TabBarController.swift */; };
3642F01625018DA1005186FE /* InterceptTabClickViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3642F01525018DA1005186FE /* InterceptTabClickViewController.swift */; };
3642F0182502723A005186FE /* UIKitButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3642F0172502723A005186FE /* UIKitButton.swift */; };
3642F01A2502931F005186FE /* InstantPaymentInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3642F0192502931F005186FE /* InstantPaymentInfoView.swift */; };
3642F01A2502931F005186FE /* RealTimeTransferInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3642F0192502931F005186FE /* RealTimeTransferInfoView.swift */; };
3642F04B25031157005186FE /* SectionHeaderWithRightAlignedEditButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3642F04A25031157005186FE /* SectionHeaderWithRightAlignedEditButton.swift */; };
366FA4DA24C472A90094F009 /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 366FA4D924C472A90094F009 /* Extensions.swift */; };
366FA4DC24C479120094F009 /* BankInfoListItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 366FA4DB24C479120094F009 /* BankInfoListItem.swift */; };
366FA4E024C4924A0094F009 /* RemitteeListItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 366FA4DF24C4924A0094F009 /* RemitteeListItem.swift */; };
366FA4E024C4924A0094F009 /* RecipientListItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 366FA4DF24C4924A0094F009 /* RecipientListItem.swift */; };
366FA4E224C4ED6C0094F009 /* EnterTanDialog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 366FA4E124C4ED6C0094F009 /* EnterTanDialog.swift */; };
366FA4E624C6EBF40094F009 /* EnterTanState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 366FA4E524C6EBF40094F009 /* EnterTanState.swift */; };
3684EB8B2508F6F00001139E /* SearchBarWithLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3684EB8A2508F6F00001139E /* SearchBarWithLabel.swift */; };
@ -175,11 +175,11 @@
3642F01325018BA9005186FE /* TabBarController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabBarController.swift; sourceTree = "<group>"; };
3642F01525018DA1005186FE /* InterceptTabClickViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InterceptTabClickViewController.swift; sourceTree = "<group>"; };
3642F0172502723A005186FE /* UIKitButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIKitButton.swift; sourceTree = "<group>"; };
3642F0192502931F005186FE /* InstantPaymentInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstantPaymentInfoView.swift; sourceTree = "<group>"; };
3642F0192502931F005186FE /* RealTimeTransferInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RealTimeTransferInfoView.swift; sourceTree = "<group>"; };
3642F04A25031157005186FE /* SectionHeaderWithRightAlignedEditButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SectionHeaderWithRightAlignedEditButton.swift; sourceTree = "<group>"; };
366FA4D924C472A90094F009 /* Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Extensions.swift; sourceTree = "<group>"; };
366FA4DB24C479120094F009 /* BankInfoListItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BankInfoListItem.swift; sourceTree = "<group>"; };
366FA4DF24C4924A0094F009 /* RemitteeListItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemitteeListItem.swift; sourceTree = "<group>"; };
366FA4DF24C4924A0094F009 /* RecipientListItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecipientListItem.swift; sourceTree = "<group>"; };
366FA4E124C4ED6C0094F009 /* EnterTanDialog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EnterTanDialog.swift; sourceTree = "<group>"; };
366FA4E524C6EBF40094F009 /* EnterTanState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EnterTanState.swift; sourceTree = "<group>"; };
3684EB8A2508F6F00001139E /* SearchBarWithLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchBarWithLabel.swift; sourceTree = "<group>"; };
@ -340,7 +340,7 @@
36BCF88424C098C8005BEC29 /* BankAccountListItem.swift */,
36BE066424CDE62800CBBB68 /* AccountTransactionListItem.swift */,
366FA4DB24C479120094F009 /* BankInfoListItem.swift */,
366FA4DF24C4924A0094F009 /* RemitteeListItem.swift */,
366FA4DF24C4924A0094F009 /* RecipientListItem.swift */,
36E21EDE24DCCC2700649DC8 /* CheckmarkListItem.swift */,
);
path = listitems;
@ -552,7 +552,7 @@
36BE06B424CF85A300CBBB68 /* AmountLabel.swift */,
360782CE24F3D6610098FEFE /* InfoLabel.swift */,
360782C224E49FF70098FEFE /* ValidationLabel.swift */,
3642F0192502931F005186FE /* InstantPaymentInfoView.swift */,
3642F0192502931F005186FE /* RealTimeTransferInfoView.swift */,
36BE065C24CB08FB00CBBB68 /* LazyView.swift */,
36C4009A24D2F9E4005227AD /* IconedTitleView.swift */,
36E21ECE24DA0EEE00649DC8 /* IconView.swift */,
@ -756,7 +756,7 @@
360782D124F3F4120098FEFE /* SelectorWrapper.swift in Sources */,
36BE06BA24D0783900CBBB68 /* FaviconFinder.swift in Sources */,
36BCF89524C31F02005BEC29 /* AppData.swift in Sources */,
3642F01A2502931F005186FE /* InstantPaymentInfoView.swift in Sources */,
3642F01A2502931F005186FE /* RealTimeTransferInfoView.swift in Sources */,
36B8A44F2503D97D00C15359 /* AuthenticationType.swift in Sources */,
36E21EDD24DCA89100649DC8 /* TanMethodPicker.swift in Sources */,
3608D6C624FBAB41006C93A8 /* TanGeneratorPositionMarker.swift in Sources */,
@ -771,7 +771,7 @@
36E21EDF24DCCC2700649DC8 /* CheckmarkListItem.swift in Sources */,
36E7BA1424B3D05C00757859 /* ViewExtensions.swift in Sources */,
36BCF88924C0A7D7005BEC29 /* Message.swift in Sources */,
366FA4E024C4924A0094F009 /* RemitteeListItem.swift in Sources */,
366FA4E024C4924A0094F009 /* RecipientListItem.swift in Sources */,
3608D6C224FBA9C6006C93A8 /* TrianglePointingDown.swift in Sources */,
36B8A4582503EEB600C15359 /* ActionSheet.swift in Sources */,
36BE068B24CE3B0400CBBB68 /* SwiftExtensions.swift in Sources */,

View File

@ -24,15 +24,15 @@
<attribute name="primaNotaNumber" optional="YES" attributeType="String"/>
<attribute name="referenceForTheAccountOwner" attributeType="String"/>
<attribute name="referenceOfTheAccountServicingInstitution" optional="YES" attributeType="String"/>
<attribute name="referenceWithNoSpecialType" optional="YES" attributeType="String"/>
<attribute name="relatedReferenceNumber" optional="YES" attributeType="String"/>
<attribute name="sepaUsage" optional="YES" attributeType="String"/>
<attribute name="sepaReference" optional="YES" attributeType="String"/>
<attribute name="sequenceNumber" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="statementNumber" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="supplementaryDetails" optional="YES" attributeType="String"/>
<attribute name="textKeySupplement" optional="YES" attributeType="String"/>
<attribute name="transactionReferenceNumber" attributeType="String"/>
<attribute name="unparsedUsage" attributeType="String"/>
<attribute name="usageWithNoSpecialType" optional="YES" attributeType="String"/>
<attribute name="unparsedReference" attributeType="String"/>
<attribute name="valueDate" attributeType="Date" usesScalarValueType="NO"/>
<relationship name="account" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="PersistedBankAccount" inverseName="transactions" inverseEntity="PersistedBankAccount"/>
</entity>
@ -51,7 +51,7 @@
<attribute name="retrievedTransactionsFromOn" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="retrievedTransactionsUpTo" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="subAccountNumber" optional="YES" attributeType="String"/>
<attribute name="supportsInstantPaymentMoneyTransfer" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="supportsRealTimeTransfer" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="supportsRetrievingAccountTransactions" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="supportsRetrievingBalance" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="supportsTransferringMoney" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>

View File

@ -101,20 +101,20 @@
/* TransferMoneyDialog */
"Transfer Money Dialog Title" = "Bank transfer";
"Remittee Name" = "Name";
"Remittee IBAN" = "IBAN";
"Recipient Name" = "Name";
"Recipient IBAN" = "IBAN";
"BIC: %@, %@" = "BIC: %@, %@";
"No BIC found for bank code %@" = "No BIC found for bank code %@";
"Amount" = "Amount";
"Usage" = "Usage";
"Enter usage" = "Optionally enter usage";
"Reference" = "Reference";
"Enter reference" = "Optionally enter reference";
"Instant Payment" = "Instant Payment";
"Real-time transfer" = "Real-time transfer";
"may with costs" = "may with costs";
"Instant payment information" = "Bank transfers are usually credited within one business day. However, this only applies during bank business hours, and therefore not at weekends and on public holidays. In addition, business hours vary from bank to bank. Mostly they are from 10 - 18 o'clock, sometimes also until 22 o'clock, but sometimes (e. g. Fridays) only until 14 o'clock.\n
Instant payment transfers on the other hand are transferred within a maximum of 10 seconds, regardless of the day and time of day.\n
"Real-time transfer information" = "Bank transfers are usually credited within one business day. However, this only applies during bank business hours, and therefore not at weekends and on public holidays. In addition, business hours vary from bank to bank. Mostly they are from 10 - 18 o'clock, sometimes also until 22 o'clock, but sometimes (e. g. Fridays) only until 14 o'clock.\n
Real-time transfers on the other hand are transferred within a maximum of 10 seconds, regardless of the day and time of day.\n
However, real-time transfers are often subject to a fee.\n
Unfortunately, Bankmeister cannot know whether a bank charges for instant payment transfers. Please refer to the list of prices and services of your bank.";
Unfortunately, Bankmeister cannot know whether a bank charges for real-time transfers. Please refer to the list of prices and services of your bank.";
"Transfer Money" = "Transfer";
@ -170,7 +170,7 @@ Unfortunately, Bankmeister cannot know whether a bank charges for instant paymen
"Supports Retrieving Balance" = "Retrieve balance";
"Supports Retrieving Account Transactions" = "Retrieve transactions";
"Supports Transferring Money" = "Transfer money";
"Supports Instant payment transfer" = "Instant payment transfer";
"Supports Real-time transfer" = "Real-time transfer";
/* ProtectAppSettingsDialog */

View File

@ -47,7 +47,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
let dataFolder = URL(fileURLWithPath: "data", isDirectory: true, relativeTo: URL(fileURLWithPath: appDataFolder))
let presenter = BankingPresenterSwift(dataFolder: dataFolder, router: SwiftUiRouter(), webClient: UrlSessionWebClient(), persistence: persistence, remitteeSearcher: persistence, bankIconFinder: SwiftBankIconFinder(), serializer: NoOpSerializer(), asyncRunner: DispatchQueueAsyncRunner())
let presenter = BankingPresenterSwift(dataFolder: dataFolder, router: SwiftUiRouter(), webClient: UrlSessionWebClient(), persistence: persistence, transactionPartySearcher: persistence, bankIconFinder: SwiftBankIconFinder(), serializer: NoOpSerializer(), asyncRunner: DispatchQueueAsyncRunner())
DependencyInjector.register(dependency: persistence)
DependencyInjector.register(dependency: presenter)

View File

@ -101,17 +101,17 @@
/* TransferMoneyDialog */
"Transfer Money Dialog Title" = "Überweisung";
"Remittee Name" = "Name";
"Remittee IBAN" = "IBAN";
"Recipient Name" = "Name";
"Recipient IBAN" = "IBAN";
"BIC: %@, %@" = "BIC: %@, %@";
"No BIC found for bank code %@" = "Keine BIC gefunden für BLZ %@";
"Amount" = "Betrag";
"Usage" = "Verwendungszweck";
"Enter usage" = "Optional einen Verwendungszweck eingeben";
"Reference" = "Verwendungszweck";
"Enter reference" = "Optional einen Verwendungszweck eingeben";
"Instant Payment" = "Echtzeitüberweisung";
"Real-time transfer" = "Echtzeitüberweisung";
"may with costs" = "evtl. kostenpflichtig";
"Instant payment information" = "Normale Überweisungen werden in der Regel innerhalb eines Werktages gutgeschrieben. Dies gilt jedoch nur zu Geschäftszeiten der Banken, also schon mal nicht am Wochenende und an Feiertagen. Zudem unterscheiden sich die Geschäftszeiten von Bank zu Bank. Meistens gehen diese von 10 - 18 Uhr, manchmal auch bis 22 Uhr, manchmal (Freitags) aber auch nur bis 14 Uhr.\n
"Real-time transfer information" = "Normale Überweisungen werden in der Regel innerhalb eines Werktages gutgeschrieben. Dies gilt jedoch nur zu Geschäftszeiten der Banken, also schon mal nicht am Wochenende und an Feiertagen. Zudem unterscheiden sich die Geschäftszeiten von Bank zu Bank. Meistens gehen diese von 10 - 18 Uhr, manchmal auch bis 22 Uhr, manchmal (Freitags) aber auch nur bis 14 Uhr.\n
Echtzeitüberweisungen werden hingegen innerhalb von maximal 10 Sekunden überwiesen, egal an welchem Tag und zu welcher Uhrzeit.\n
Häufig sind Echtzeitüberweisungen jedoch kostenpflichtig.\n
Ob eine Bank Gebühren für Echtzeitüberweisungen erhebt, kann Bankmeister leider nicht wissen. Dies entnehmen Sie bitte dem Preis- / Leistungsverzeichnis Ihrer Bank.";
@ -170,7 +170,7 @@ Ob eine Bank Gebühren für Echtzeitüberweisungen erhebt, kann Bankmeister leid
"Supports Retrieving Balance" = "Kontostand abrufen";
"Supports Retrieving Account Transactions" = "Kontoumsätze abrufen";
"Supports Transferring Money" = "Überweisen";
"Supports Instant payment transfer" = "Echtzeitüberweisung";
"Supports Real-time transfer" = "Echtzeitüberweisung";
/* ProtectAppSettingsDialog */

View File

@ -159,7 +159,7 @@ extension BankInfo : Identifiable {
}
extension Remittee : Identifiable {
extension TransactionParty : Identifiable {
public var id: String { name.localizedLowercase + "_" + (iban ?? "") }

View File

@ -4,7 +4,7 @@ import UIKit
import BankingUiSwift
class CoreDataBankingPersistence: IBankingPersistence, IRemitteeSearcher {
class CoreDataBankingPersistence: IBankingPersistence, ITransactionPartySearcher {
private let mapper = Mapper()
@ -123,7 +123,7 @@ class CoreDataBankingPersistence: IBankingPersistence, IRemitteeSearcher {
return UserDefaults.standard.data(forKey: filePath)
}
func findRemittees(query: String) -> [Remittee] {
func findTransactionParty(query: String) -> [TransactionParty] {
var transactions: [PersistedAccountTransaction] = []
do {
@ -140,13 +140,13 @@ class CoreDataBankingPersistence: IBankingPersistence, IRemitteeSearcher {
NSLog("Could not request banks: \(error)")
}
let remittees = transactions
let transactionParties = transactions
.filter { $0.otherPartyAccountId != nil } // if IBAN is not set we cannot make use of it
.map( { Remittee(name: $0.otherPartyName ?? "", iban: $0.otherPartyAccountId, bic: $0.otherPartyBankCode, bankName: nil) } )
.map( { TransactionParty(name: $0.otherPartyName ?? "", iban: $0.otherPartyAccountId, bic: $0.otherPartyBankCode, bankName: nil) } )
let uniqueRemittees = Set<Remittee>(remittees)
let uniqueTransactionParties = Set<TransactionParty>(transactionParties)
return Array(uniqueRemittees)
return Array(uniqueTransactionParties)
}

View File

@ -58,7 +58,7 @@ class Mapper {
}
func map(_ bank: IBankData, _ account: PersistedBankAccount) -> IBankAccount {
let mapped = BankAccount(bank: bank, identifier: map(account.identifier), accountHolderName: map(account.accountHolderName), iban: account.iban, subAccountNumber: account.subAccountNumber, customerId: map(account.customerId), balance: map(account.balance), currency: map(account.currency), type: map(account.type), productName: account.productName, accountLimit: account.accountLimit, retrievedTransactionsFromOn: map(account.retrievedTransactionsFromOn), retrievedTransactionsUpTo: map(account.retrievedTransactionsUpTo), supportsRetrievingAccountTransactions: account.supportsRetrievingAccountTransactions, supportsRetrievingBalance: account.supportsRetrievingBalance, supportsTransferringMoney: account.supportsTransferringMoney, supportsInstantPaymentMoneyTransfer: account.supportsInstantPaymentMoneyTransfer, bookedTransactions: [], unbookedTransactions: [])
let mapped = BankAccount(bank: bank, identifier: map(account.identifier), accountHolderName: map(account.accountHolderName), iban: account.iban, subAccountNumber: account.subAccountNumber, customerId: map(account.customerId), balance: map(account.balance), currency: map(account.currency), type: map(account.type), productName: account.productName, accountLimit: account.accountLimit, retrievedTransactionsFromOn: map(account.retrievedTransactionsFromOn), retrievedTransactionsUpTo: map(account.retrievedTransactionsUpTo), supportsRetrievingAccountTransactions: account.supportsRetrievingAccountTransactions, supportsRetrievingBalance: account.supportsRetrievingBalance, supportsTransferringMoney: account.supportsTransferringMoney, supportsRealTimeTransfer: account.supportsRealTimeTransfer, bookedTransactions: [], unbookedTransactions: [])
mapped.haveAllTransactionsBeenFetched = account.haveAllTransactionsBeenFetched
mapped.isAccountTypeSupported = account.isAccountTypeSupported
@ -97,7 +97,7 @@ class Mapper {
mapped.supportsRetrievingAccountTransactions = account.supportsRetrievingAccountTransactions
mapped.supportsRetrievingBalance = account.supportsRetrievingBalance
mapped.supportsTransferringMoney = account.supportsTransferringMoney
mapped.supportsInstantPaymentMoneyTransfer = account.supportsInstantPaymentMoneyTransfer
mapped.supportsRealTimeTransfer = account.supportsRealTimeTransfer
mapped.haveAllTransactionsBeenFetched = account.haveAllTransactionsBeenFetched
@ -147,7 +147,7 @@ class Mapper {
}
func map(_ account: IBankAccount, _ transaction: PersistedAccountTransaction) -> IAccountTransaction {
let mapped = AccountTransaction(account: account, amount: map(transaction.amount), currency: map(transaction.currency), unparsedUsage: map(transaction.unparsedUsage), bookingDate: map(transaction.bookingDate), otherPartyName: transaction.otherPartyName, otherPartyBankCode: transaction.otherPartyBankCode, otherPartyAccountId: transaction.otherPartyAccountId, bookingText: transaction.bookingText, valueDate: map(transaction.valueDate), statementNumber: Int32(transaction.statementNumber), sequenceNumber: map(transaction.sequenceNumber), openingBalance: map(transaction.openingBalance), closingBalance: map(transaction.closingBalance), endToEndReference: transaction.endToEndReference, customerReference: transaction.customerReference, mandateReference: transaction.mandateReference, creditorIdentifier: transaction.creditorIdentifier, originatorsIdentificationCode: transaction.originatorsIdentificationCode, compensationAmount: transaction.compensationAmount, originalAmount: transaction.originalAmount, sepaUsage: transaction.sepaUsage, deviantOriginator: transaction.deviantOriginator, deviantRecipient: transaction.deviantRecipient, usageWithNoSpecialType: transaction.usageWithNoSpecialType, primaNotaNumber: transaction.primaNotaNumber, textKeySupplement: transaction.textKeySupplement, currencyType: transaction.currencyType, bookingKey: map(transaction.bookingKey), referenceForTheAccountOwner: map(transaction.referenceForTheAccountOwner), referenceOfTheAccountServicingInstitution: transaction.referenceOfTheAccountServicingInstitution, supplementaryDetails: transaction.supplementaryDetails, transactionReferenceNumber: map(transaction.transactionReferenceNumber), relatedReferenceNumber: transaction.relatedReferenceNumber)
let mapped = AccountTransaction(account: account, amount: map(transaction.amount), currency: map(transaction.currency), unparsedReference: map(transaction.unparsedReference), bookingDate: map(transaction.bookingDate), otherPartyName: transaction.otherPartyName, otherPartyBankCode: transaction.otherPartyBankCode, otherPartyAccountId: transaction.otherPartyAccountId, bookingText: transaction.bookingText, valueDate: map(transaction.valueDate), statementNumber: Int32(transaction.statementNumber), sequenceNumber: map(transaction.sequenceNumber), openingBalance: map(transaction.openingBalance), closingBalance: map(transaction.closingBalance), endToEndReference: transaction.endToEndReference, customerReference: transaction.customerReference, mandateReference: transaction.mandateReference, creditorIdentifier: transaction.creditorIdentifier, originatorsIdentificationCode: transaction.originatorsIdentificationCode, compensationAmount: transaction.compensationAmount, originalAmount: transaction.originalAmount, sepaReference: transaction.sepaReference, deviantOriginator: transaction.deviantOriginator, deviantRecipient: transaction.deviantRecipient, referenceWithNoSpecialType: transaction.referenceWithNoSpecialType, primaNotaNumber: transaction.primaNotaNumber, textKeySupplement: transaction.textKeySupplement, currencyType: transaction.currencyType, bookingKey: map(transaction.bookingKey), referenceForTheAccountOwner: map(transaction.referenceForTheAccountOwner), referenceOfTheAccountServicingInstitution: transaction.referenceOfTheAccountServicingInstitution, supplementaryDetails: transaction.supplementaryDetails, transactionReferenceNumber: map(transaction.transactionReferenceNumber), relatedReferenceNumber: transaction.relatedReferenceNumber)
mapped.technicalId = transaction.objectIDAsString
@ -166,7 +166,7 @@ class Mapper {
mapped.amount = map(transaction.amount)
mapped.currency = transaction.currency
mapped.unparsedUsage = transaction.unparsedUsage
mapped.unparsedReference = transaction.unparsedReference
mapped.bookingDate = map(transaction.bookingDate)
mapped.otherPartyName = transaction.otherPartyName
mapped.otherPartyBankCode = transaction.otherPartyBankCode
@ -186,10 +186,10 @@ class Mapper {
mapped.originatorsIdentificationCode = transaction.originatorsIdentificationCode
mapped.compensationAmount = transaction.compensationAmount
mapped.originalAmount = transaction.originalAmount
mapped.sepaUsage = transaction.sepaUsage
mapped.sepaReference = transaction.sepaReference
mapped.deviantOriginator = transaction.deviantOriginator
mapped.deviantRecipient = transaction.deviantRecipient
mapped.usageWithNoSpecialType = transaction.usageWithNoSpecialType
mapped.referenceWithNoSpecialType = transaction.referenceWithNoSpecialType
mapped.primaNotaNumber = transaction.primaNotaNumber
mapped.textKeySupplement = transaction.textKeySupplement

View File

@ -57,7 +57,7 @@ struct BankAccountSettingsDialog: View {
CheckmarkListItem("Supports Transferring Money", account.supportsTransferringMoney)
CheckmarkListItem("Supports Instant payment transfer", account.supportsInstantPaymentMoneyTransfer)
CheckmarkListItem("Supports Real-time transfer", account.supportsRealTimeTransfer)
}
}
.alert(message: $unsavedChangesMessage)

View File

@ -14,34 +14,34 @@ struct TransferMoneyDialog: View {
@State private var selectedAccountIndex = 0
@State private var remitteeName: String = ""
@State private var isValidRemitteeNameEntered = false
@State private var remitteeNameValidationResult: ValidationResult? = nil
@State private var recipientName: String = ""
@State private var isValidRecipientNameEntered = false
@State private var recipientNameValidationResult: ValidationResult? = nil
@State private var showRemitteeAutocompleteList = false
@State private var remitteeSearchResults = [Remittee]()
@State private var showRecipientAutocompleteList = false
@State private var recipientSearchResults = [TransactionParty]()
@State private var remitteeIban: String = ""
@State private var isValidRemitteeIbanEntered = false
@State private var remitteeIbanValidationResult: ValidationResult? = nil
@State private var recipientIban: String = ""
@State private var isValidRecipientIbanEntered = false
@State private var recipientIbanValidationResult: ValidationResult? = nil
@State private var remitteeBic: String = ""
@State private var isValidRemitteeBicEntered = false
@State private var remitteeBicValidationResult: ValidationResult? = nil
@State private var recipientBic: String = ""
@State private var isValidRecipientBicEntered = false
@State private var recipientBicValidationResult: ValidationResult? = nil
@State private var remitteeBankInfo: String? = nil
@State private var recipientBankInfo: String? = nil
@State private var amount = ""
@State private var isValidAmountEntered = false
@State private var amountValidationResult: ValidationResult? = nil
@State private var usage: String = ""
@State private var isValidUsageEntered = true
@State private var usageValidationResult: ValidationResult? = nil
@State private var reference: String = ""
@State private var isValidReferenceEntered = true
@State private var referenceValidationResult: ValidationResult? = nil
@State private var validateDataWhenShowingDialog = false
@State private var instantPayment = false
@State private var realTimeTransfer = false
@State private var isTransferringMoney = false
@ -60,8 +60,8 @@ struct TransferMoneyDialog: View {
return self.accountsSupportingTransferringMoney.first
}
private var supportsInstantPayment: Bool {
return self.account?.supportsInstantPaymentMoneyTransfer ?? false
private var supportsRealTimeTransfer: Bool {
return self.account?.supportsRealTimeTransfer ?? false
}
@ -82,22 +82,22 @@ struct TransferMoneyDialog: View {
let preselectedBankAccount = preselectedValues.account
self._selectedAccountIndex = State(initialValue: accountsSupportingTransferringMoney.firstIndex(where: { account in account == preselectedBankAccount }) ?? 0)
self._remitteeName = State(initialValue: preselectedValues.creditorName)
self._remitteeBic = State(initialValue: preselectedValues.creditorBic)
self._remitteeIban = State(initialValue: preselectedValues.creditorIban)
self._recipientName = State(initialValue: preselectedValues.recipientName)
self._recipientBic = State(initialValue: preselectedValues.recipientBankCode)
self._recipientIban = State(initialValue: preselectedValues.recipientAccountId)
if remitteeBic.isBlank && remitteeIban.isNotBlank {
tryToGetBicFromIban(remitteeIban)
if recipientBic.isBlank && recipientIban.isNotBlank {
tryToGetBicFromIban(recipientIban)
}
self._usage = State(initialValue: preselectedValues.usage)
self._reference = State(initialValue: preselectedValues.reference)
if preselectedValues.amount.decimal != NSDecimalNumber.zero {
self._amount = State(initialValue: preselectedValues.amount.format(countDecimalPlaces: 2))
}
if preselectedBankAccount.supportsInstantPaymentMoneyTransfer {
self._instantPayment = State(initialValue: preselectedValues.instantPayment)
if preselectedBankAccount.supportsRealTimeTransfer {
self._realTimeTransfer = State(initialValue: preselectedValues.realTimeTransfer)
}
_validateDataWhenShowingDialog = State(initialValue: true)
@ -117,29 +117,29 @@ struct TransferMoneyDialog: View {
}
Section {
LabelledUIKitTextField(label: "Remittee Name", text: $remitteeName, focusOnStart: true, focusNextTextFieldOnReturnKeyPress: true,
isFocusedChanged: remitteeNameisFocusedChanged, actionOnReturnKeyPress: handleReturnKeyPress, textChanged: enteredRemitteeNameChanged)
LabelledUIKitTextField(label: "Recipient Name", text: $recipientName, focusOnStart: true, focusNextTextFieldOnReturnKeyPress: true,
isFocusedChanged: recipientNameisFocusedChanged, actionOnReturnKeyPress: handleReturnKeyPress, textChanged: enteredRecipientNameChanged)
.padding(.bottom, 0)
remitteeNameValidationResult.map { validationError in
recipientNameValidationResult.map { validationError in
ValidationLabel(validationError)
}
if self.showRemitteeAutocompleteList {
List(self.remitteeSearchResults) { remittee in
RemitteeListItem(remittee: remittee)
.onTapGesture { self.remitteeSelected(remittee) }
if self.showRecipientAutocompleteList {
List(self.recipientSearchResults) { recipient in
RecipientListItem(recipient: recipient)
.onTapGesture { self.recipientSelected(recipient) }
}
}
LabelledUIKitTextField(label: "Remittee IBAN", text: $remitteeIban, autocapitalizationType: .allCharacters, focusNextTextFieldOnReturnKeyPress: true, isFocusedChanged: validateRemitteeIbanOnFocusLost,
actionOnReturnKeyPress: handleReturnKeyPress, textChanged: remitteeIbanisFocusedChanged)
LabelledUIKitTextField(label: "Recipient IBAN", text: $recipientIban, autocapitalizationType: .allCharacters, focusNextTextFieldOnReturnKeyPress: true, isFocusedChanged: validateRecipientIbanOnFocusLost,
actionOnReturnKeyPress: handleReturnKeyPress, textChanged: recipientIbanisFocusedChanged)
remitteeIbanValidationResult.map { validationError in
recipientIbanValidationResult.map { validationError in
ValidationLabel(validationError)
}
remitteeBankInfo.map {
recipientBankInfo.map {
InfoLabel($0)
.font(.caption)
}
@ -157,29 +157,29 @@ struct TransferMoneyDialog: View {
Section {
VStack(alignment: .leading) {
HStack {
Text("Usage")
Text("Reference")
Spacer()
}
UIKitTextField("Enter usage", text: $usage, actionOnReturnKeyPress: handleReturnKeyPress, textChanged: validateUsage)
UIKitTextField("Enter reference", text: $reference, actionOnReturnKeyPress: handleReturnKeyPress, textChanged: validateReference)
}
usageValidationResult.map { validationError in
referenceValidationResult.map { validationError in
ValidationLabel(validationError)
}
}
if supportsInstantPayment {
if supportsRealTimeTransfer {
Section {
VStack {
Toggle(isOn: $instantPayment) {
Toggle(isOn: $realTimeTransfer) {
HStack {
Text("Instant Payment")
Text("Real-time transfer")
.lineLimit(1)
if instantPayment {
InstantPaymentInfoView()
if realTimeTransfer {
RealTimeTransferInfoView()
}
}
}
@ -205,8 +205,8 @@ struct TransferMoneyDialog: View {
self.validateDataWhenShowingDialog = false
self.validateAllFields()
if self.remitteeBankInfo == nil {
self.showRemitteeBankInfo(self.remitteeBic, "")
if self.recipientBankInfo == nil {
self.showRecipientBankInfo(self.recipientBic, "")
}
}
}
@ -228,104 +228,104 @@ struct TransferMoneyDialog: View {
private func validateAllFields() {
self.validateRemitteeNameOnFocusLost()
self.validateRemitteeIbanOnFocusLost()
self.validateRemitteeBic()
self.validateRecipientNameOnFocusLost()
self.validateRecipientIbanOnFocusLost()
self.validateRecipientBic()
self.validateAmount()
self.validateUsage()
self.validateReference()
}
private func remitteeNameisFocusedChanged(_ isFocused: Bool) {
private func recipientNameisFocusedChanged(_ isFocused: Bool) {
if isFocused == false {
validateRemitteeNameOnFocusLost()
validateRecipientNameOnFocusLost()
self.showRemitteeAutocompleteList = false
self.showRecipientAutocompleteList = false
}
else {
self.showRemitteeAutocompleteList = self.remitteeSearchResults.isNotEmpty
self.showRecipientAutocompleteList = self.recipientSearchResults.isNotEmpty
}
}
private func validateRemitteeNameOnFocusLost() {
validateField($remitteeName, $remitteeNameValidationResult, $isValidRemitteeNameEntered) {
inputValidator.validateRemitteeName(remitteeNameToTest: remitteeName)
private func validateRecipientNameOnFocusLost() {
validateField($recipientName, $recipientNameValidationResult, $isValidRecipientNameEntered) {
inputValidator.validateRecipientName(recipientNameToTest: recipientName)
}
}
private func enteredRemitteeNameChanged(enteredRemitteeName: String) {
validateField($remitteeName, $remitteeNameValidationResult, $isValidRemitteeNameEntered) {
inputValidator.validateRemitteeNameWhileTyping(remitteeNameToTest: remitteeName)
private func enteredRecipientNameChanged(enteredRecipientName: String) {
validateField($recipientName, $recipientNameValidationResult, $isValidRecipientNameEntered) {
inputValidator.validateRecipientNameWhileTyping(recipientNameToTest: recipientName)
}
searchRemittees(remitteeName)
searchRecipients(recipientName)
}
private func searchRemittees(_ searchText: String) {
private func searchRecipients(_ searchText: String) {
// TODO: why doesn't it work to search on background thread?
self.remitteeSearchResults = self.presenter.findRemitteesForName(name: searchText)
self.recipientSearchResults = self.presenter.findRecipientsForName(name: searchText)
self.showRemitteeAutocompleteList = self.remitteeSearchResults.isNotEmpty
self.showRecipientAutocompleteList = self.recipientSearchResults.isNotEmpty
}
private func remitteeSelected(_ remittee: Remittee) {
self.remitteeName = remittee.name
self.remitteeIban = remittee.iban ?? self.remitteeIban
self.remitteeBic = remittee.bic ?? self.remitteeBic
private func recipientSelected(_ recipient: TransactionParty) {
self.recipientName = recipient.name
self.recipientIban = recipient.iban ?? self.recipientIban
self.recipientBic = recipient.bic ?? self.recipientBic
tryToGetBicFromIban(self.remitteeIban)
tryToGetBicFromIban(self.recipientIban)
validateAllFields()
DispatchQueue.main.asyncAfter(deadline: .now() + 0.25) {
self.showRemitteeAutocompleteList = false
self.showRecipientAutocompleteList = false
}
}
private func remitteeIbanisFocusedChanged(_ enteredIban: String) {
validateField($remitteeIban, $remitteeIbanValidationResult, $isValidRemitteeIbanEntered) { inputValidator.validateIbanWhileTyping(ibanToTest: enteredIban) }
private func recipientIbanisFocusedChanged(_ enteredIban: String) {
validateField($recipientIban, $recipientIbanValidationResult, $isValidRecipientIbanEntered) { inputValidator.validateIbanWhileTyping(ibanToTest: enteredIban) }
tryToGetBicFromIban(enteredIban)
}
private func validateRemitteeIbanOnFocusLost(_ isFocused: Bool) {
private func validateRecipientIbanOnFocusLost(_ isFocused: Bool) {
if isFocused == false {
validateRemitteeIbanOnFocusLost()
validateRecipientIbanOnFocusLost()
}
}
private func validateRemitteeIbanOnFocusLost() {
validateField($remitteeIban, $remitteeIbanValidationResult, $isValidRemitteeIbanEntered) { inputValidator.validateIban(ibanToTest: remitteeIban) }
private func validateRecipientIbanOnFocusLost() {
validateField($recipientIban, $recipientIbanValidationResult, $isValidRecipientIbanEntered) { inputValidator.validateIban(ibanToTest: recipientIban) }
}
private func tryToGetBicFromIban(_ enteredIban: String) {
let foundBank = presenter.findUniqueBankForIban(iban: enteredIban)
if let foundBank = foundBank {
self.remitteeBic = foundBank.bic
showRemitteeBankInfo(foundBank.bic, foundBank.name)
self.recipientBic = foundBank.bic
showRecipientBankInfo(foundBank.bic, foundBank.name)
}
else {
self.remitteeBic = ""
self.recipientBic = ""
if enteredIban.count >= InputValidator.Companion().MinimumLengthToDetermineBicFromIban {
self.remitteeBankInfo = "No BIC found for bank code \(enteredIban[4..<Int(InputValidator.Companion().MinimumLengthToDetermineBicFromIban)])"
self.recipientBankInfo = "No BIC found for bank code \(enteredIban[4..<Int(InputValidator.Companion().MinimumLengthToDetermineBicFromIban)])"
}
else {
self.remitteeBankInfo = nil
self.recipientBankInfo = nil
}
}
validateRemitteeBic()
validateRecipientBic()
}
private func validateRemitteeBic() {
self.isValidRemitteeBicEntered = inputValidator.validateBic(bicToTest: remitteeBic).validationSuccessful
private func validateRecipientBic() {
self.isValidRecipientBicEntered = inputValidator.validateBic(bicToTest: recipientBic).validationSuccessful
}
private func showRemitteeBankInfo(_ bic: String, _ bankName: String) {
self.remitteeBankInfo = "BIC: \(bic), \(bankName)"
private func showRecipientBankInfo(_ bic: String, _ bankName: String) {
self.recipientBankInfo = "BIC: \(bic), \(bankName)"
}
@ -352,12 +352,12 @@ struct TransferMoneyDialog: View {
}
private func validateUsage(enteredUsage: String) {
validateUsage()
private func validateReference(enteredReference: String) {
validateReference()
}
private func validateUsage() {
validateField($usage, $usageValidationResult, $isValidUsageEntered) { inputValidator.validateUsage(usageToTest: self.usage) }
private func validateReference() {
validateField($reference, $referenceValidationResult, $isValidReferenceEntered) { inputValidator.validateReference(referenceToTest: self.reference) }
}
private func validateField(_ newValue: Binding<String>, _ validationResult: Binding<ValidationResult?>, _ isValidValueEntered: Binding<Bool>, _ validateValue: () -> ValidationResult) {
@ -382,11 +382,11 @@ struct TransferMoneyDialog: View {
private func isRequiredDataEntered() -> Bool {
return account != nil
&& isValidRemitteeNameEntered
&& isValidRemitteeIbanEntered
&& isValidRemitteeBicEntered
&& isValidRecipientNameEntered
&& isValidRecipientIbanEntered
&& isValidRecipientBicEntered
&& isValidAmountEntered
&& isValidUsageEntered
&& isValidReferenceEntered
}
private func transferMoney() {
@ -394,7 +394,7 @@ struct TransferMoneyDialog: View {
isTransferringMoney = true
UIApplication.hideKeyboard()
let data = TransferMoneyData(account: account!, creditorName: remitteeName, creditorIban: remitteeIban, creditorBic: remitteeBic, amount: amount, usage: usage, instantPayment: instantPayment)
let data = TransferMoneyData(account: account!, recipientName: recipientName, recipientAccountId: recipientIban, recipientBankCode: recipientBic, amount: amount, reference: reference, realTimeTransfer: realTimeTransfer)
presenter.transferMoneyAsync(data: data) { response in
self.handleTransferMoneyResponse(data, response)
@ -406,12 +406,12 @@ struct TransferMoneyDialog: View {
isTransferringMoney = false
if (response.successful) {
self.transferMoneyResponseMessage = Message(message: Text("Successfully transferred \(data.amount) \("") to \(data.creditorName)."), primaryButton: .ok {
self.transferMoneyResponseMessage = Message(message: Text("Successfully transferred \(data.amount) \("") to \(data.recipientName)."), primaryButton: .ok {
self.presentation.wrappedValue.dismiss()
})
}
else if response.userCancelledAction == false {
self.transferMoneyResponseMessage = Message(message: Text("Could not transfer \(data.amount) \("") to \(data.creditorName). Error: \(response.errorToShowToUser ?? "")."))
self.transferMoneyResponseMessage = Message(message: Text("Could not transfer \(data.amount) \("") to \(data.recipientName). Error: \(response.errorToShowToUser ?? "")."))
}
}

View File

@ -1,9 +1,9 @@
import SwiftUI
struct InstantPaymentInfoView: View {
struct RealTimeTransferInfoView: View {
@State private var showInstantPaymentInfoPopover = false
@State private var showRealTimeTransferInfoPopover = false
var body: some View {
@ -15,26 +15,26 @@ struct InstantPaymentInfoView: View {
.minimumScaleFactor(0.5)
.padding(.horizontal, 0)
UIKitButton(.infoLight) { self.showInstantPaymentInfoPopover = true }
UIKitButton(.infoLight) { self.showRealTimeTransferInfoPopover = true }
.frame(width: 15, height: 15)
.popover(isPresented: $showInstantPaymentInfoPopover, arrowEdge: .leading ) {
.popover(isPresented: $showRealTimeTransferInfoPopover, arrowEdge: .leading ) {
if UIDevice.isRunningOniPad {
ScrollView {
Text("Instant payment information")
Text("Real-time transfer information")
.padding()
.detailForegroundColor()
}
}
else {
VStack {
Text("Instant payment information")
Text("Real-time transfer information")
.padding()
.detailForegroundColor()
HStack {
Spacer()
Button("OK") { self.showInstantPaymentInfoPopover = false }
Button("OK") { self.showRealTimeTransferInfoPopover = false }
Spacer()
}
@ -47,13 +47,13 @@ struct InstantPaymentInfoView: View {
}
struct InstantPaymentInfoView_Previews: PreviewProvider {
struct RealTimeTransferInfoView_Previews: PreviewProvider {
static var previews: some View {
Form {
Section {
Toggle(isOn: .constant(true)) {
InstantPaymentInfoView()
RealTimeTransferInfoView()
}
}
}

View File

@ -36,7 +36,7 @@ struct AccountTransactionListItem: View {
.lineLimit(1)
.frame(height: 20)
Text(transaction.usage)
Text(transaction.reference)
.styleAsDetail()
.padding(.top, 4)
.lineLimit(2)
@ -62,7 +62,7 @@ struct AccountTransactionListItem: View {
}
.contextMenu {
if transaction.canCreateMoneyTransferFrom {
NavigationLink(destination: LazyView(TransferMoneyDialog(preselectedValues: TransferMoneyData.Companion().fromAccountTransactionWithoutAmountAndUsage(transaction: self.transaction)))) {
NavigationLink(destination: LazyView(TransferMoneyDialog(preselectedValues: TransferMoneyData.Companion().fromAccountTransactionWithoutAmountAndReference(transaction: self.transaction)))) {
HStack {
Text("Transfer money to \(transaction.otherPartyName ?? "")")
@ -98,6 +98,6 @@ struct AccountTransactionListItem: View {
struct AccountTransactionListItem_Previews: PreviewProvider {
static var previews: some View {
AccountTransactionListItem(AccountTransaction(account: previewBanks[0].accounts[0] as! BankAccount, otherPartyName: "Marieke Musterfrau", unparsedUsage: "Vielen Dank für Ihre Mühen", amount: CommonBigDecimal(double: 1234.56), valueDate: CommonDate(year: 2020, month: .march, day_: 27), bookingText: "SEPA Überweisung"), false)
AccountTransactionListItem(AccountTransaction(account: previewBanks[0].accounts[0] as! BankAccount, otherPartyName: "Marieke Musterfrau", unparsedReference: "Vielen Dank für Ihre Mühen", amount: CommonBigDecimal(double: 1234.56), valueDate: CommonDate(year: 2020, month: .march, day_: 27), bookingText: "SEPA Überweisung"), false)
}
}

View File

@ -2,16 +2,16 @@ import SwiftUI
import BankingUiSwift
struct RemitteeListItem: View {
struct RecipientListItem: View {
let remittee: Remittee
let recipient: TransactionParty
var body: some View {
VStack {
HStack {
Text(remittee.name)
Text(recipient.name)
.bold()
.lineLimit(1)
@ -19,7 +19,7 @@ struct RemitteeListItem: View {
}
.padding(.vertical, 6)
remittee.bankName.map { bankName in
recipient.bankName.map { bankName in
HStack {
Text(bankName)
.font(.footnote)
@ -31,11 +31,11 @@ struct RemitteeListItem: View {
}
HStack {
Text(remittee.iban ?? "")
Text(recipient.iban ?? "")
Spacer()
Text(remittee.bic ?? "")
Text(recipient.bic ?? "")
}
.font(.footnote)
.lineLimit(1)
@ -45,10 +45,10 @@ struct RemitteeListItem: View {
}
}
struct RemitteeListItem_Previews: PreviewProvider {
struct RecipientListItem_Previews: PreviewProvider {
static var previews: some View {
RemitteeListItem(remittee: Remittee(name: "Marieke Musterfrau", iban: "DE12876543211234567890", bic: "ABZODEBBXX", bankName: "Abzockbank Berlin"))
RecipientListItem(recipient: TransactionParty(name: "Marieke Musterfrau", iban: "DE12876543211234567890", bic: "ABZODEBBXX", bankName: "Abzockbank Berlin"))
}
}

View File

@ -117,7 +117,7 @@ open class fints4kBankingClient(
callback(BankingClientResponse(false, errorMessage))
}
else {
val mappedData = BankTransferData(data.creditorName, data.creditorIban, data.creditorBic, data.amount.toMoney(), data.usage, data.instantPayment)
val mappedData = BankTransferData(data.recipientName, data.recipientAccountId, data.recipientBankCode, data.amount.toMoney(), data.reference, data.realTimeTransfer)
doBankTransferAsync(mappedData, account, callback)
}

View File

@ -127,7 +127,7 @@ open class fints4kModelMapper(protected val modelCreator: IModelCreator) {
account.supportsRetrievingBalance = accountData.supportsRetrievingBalance
account.supportsRetrievingAccountTransactions = accountData.supportsRetrievingAccountTransactions
account.supportsTransferringMoney = accountData.supportsTransferringMoney
account.supportsInstantPaymentMoneyTransfer = accountData.supportsInstantPaymentMoneyTransfer
account.supportsRealTimeTransfer = accountData.supportsRealTimeTransfer
}
open fun mapBankAccountType(type: AccountType?): BankAccountType {
@ -199,7 +199,7 @@ open class fints4kModelMapper(protected val modelCreator: IModelCreator) {
account,
transaction.amount.bigDecimal,
transaction.amount.currency.code,
transaction.unparsedUsage,
transaction.unparsedReference,
transaction.bookingDate,
transaction.otherPartyName,
transaction.otherPartyBankCode,
@ -218,10 +218,10 @@ open class fints4kModelMapper(protected val modelCreator: IModelCreator) {
transaction.originatorsIdentificationCode,
transaction.compensationAmount,
transaction.originalAmount,
transaction.sepaUsage,
transaction.sepaReference,
transaction.deviantOriginator,
transaction.deviantRecipient,
transaction.usageWithNoSpecialType,
transaction.referenceWithNoSpecialType,
transaction.primaNotaNumber,
transaction.textKeySupplement,

View File

@ -256,7 +256,7 @@ open class hbci4jBankingClient(
}
protected open fun createTransferCashJob(handle: HBCIHandler, data: TransferMoneyData) {
// TODO: implement instant payment
// TODO: implement real-time transfer
val transferCashJob = handle.newJob("UebSEPA")
val source = mapper.mapToKonto(data.account)
@ -266,7 +266,7 @@ open class hbci4jBankingClient(
transferCashJob.setParam("src", source)
transferCashJob.setParam("dst", destination)
transferCashJob.setParam("btg", amount)
transferCashJob.setParam("usage", data.usage)
transferCashJob.setParam("usage", data.reference)
transferCashJob.addToQueue()
}

View File

@ -43,12 +43,12 @@ open class AccountTransactionMapper(
}
protected open fun mapTransaction(account: TypedBankAccount, btag: GVRKUms.BTag, transaction: GVRKUms.UmsLine): IAccountTransaction {
val unparsedUsage = transaction.usage.joinToString("")
val parsedUsage = Mt940Parser().getUsageParts(unparsedUsage)
val unparsedReference = transaction.usage.joinToString("")
val parsedReference = Mt940Parser().getReferenceParts(unparsedReference)
val statementAndMaySequenceNumber = btag.counter.split('/')
return modelCreator.createTransaction(account,
mapValue(transaction.value), transaction.value.curr, unparsedUsage, transaction.bdate.toDate(),
mapValue(transaction.value), transaction.value.curr, unparsedReference, transaction.bdate.toDate(),
transaction.other.name + (transaction.other.name2 ?: ""),
transaction.other.bic ?: transaction.other.blz,
transaction.other.iban ?: transaction.other.number,
@ -57,17 +57,17 @@ open class AccountTransactionMapper(
if (statementAndMaySequenceNumber.size > 1) statementAndMaySequenceNumber[1].toInt() else null,
mapValue(btag.start.value), mapValue(btag.end.value),
parsedUsage[Mt940Parser.EndToEndReferenceUsageKey],
parsedUsage[Mt940Parser.CustomerReferenceUsageKey],
parsedUsage[Mt940Parser.MandateReferenceUsageKey],
parsedUsage[Mt940Parser.CreditorIdentifierUsageKey],
parsedUsage[Mt940Parser.OriginatorsIdentificationCodeUsageKey],
parsedUsage[Mt940Parser.CompensationAmountUsageKey],
parsedUsage[Mt940Parser.OriginalAmountUsageKey],
parsedUsage[Mt940Parser.SepaUsageUsageKey],
parsedUsage[Mt940Parser.DeviantOriginatorUsageKey],
parsedUsage[Mt940Parser.DeviantRecipientUsageKey],
parsedUsage[""],
parsedReference[Mt940Parser.EndToEndReferenceKey],
parsedReference[Mt940Parser.CustomerReferenceKey],
parsedReference[Mt940Parser.MandateReferenceKey],
parsedReference[Mt940Parser.CreditorIdentifierKey],
parsedReference[Mt940Parser.OriginatorsIdentificationCodeKey],
parsedReference[Mt940Parser.CompensationAmountKey],
parsedReference[Mt940Parser.OriginalAmountKey],
parsedReference[Mt940Parser.SepaReferenceKey],
parsedReference[Mt940Parser.DeviantOriginatorKey],
parsedReference[Mt940Parser.DeviantRecipientKey],
parsedReference[""],
transaction.primanota,
transaction.addkey,

View File

@ -28,7 +28,7 @@ open class hbci4jModelMapper(
}
open fun mapToKonto(data: TransferMoneyData): Konto {
return mapToKonto(data.creditorName, data.creditorIban, data.creditorBic)
return mapToKonto(data.recipientName, data.recipientAccountId, data.recipientBankCode)
}
open fun mapToKonto(accountHolderName: String, iban: String, bic: String): Konto {