Implemented making data element's value optional

This commit is contained in:
dankl 2019-10-13 14:07:28 +02:00 committed by dankito
parent 9a094ed3fc
commit 4dfc53994c
19 changed files with 101 additions and 86 deletions

View File

@ -6,6 +6,13 @@ import net.dankito.fints.messages.Existenzstatus
abstract class Datenelement(existenzstatus: Existenzstatus): DatenelementBase(existenzstatus) { abstract class Datenelement(existenzstatus: Existenzstatus): DatenelementBase(existenzstatus) {
abstract val isValueSet: Boolean
open val writeToOutput: Boolean
get() = existenzstatus == Existenzstatus.Mandatory
|| existenzstatus == Existenzstatus.Optional && isValueSet
@Throws(IllegalArgumentException::class) @Throws(IllegalArgumentException::class)
abstract fun validate() abstract fun validate()

View File

@ -7,14 +7,14 @@ import net.dankito.fints.messages.datenelemente.basisformate.AlphanumerischesDat
/** /**
* Es sind nur die jeweils aufgeführten Werte zulässig. * Es sind nur die jeweils aufgeführten Werte zulässig.
*/ */
abstract class Code(code: String, val allowedValues: List<String>, existenzstatus: Existenzstatus) abstract class Code(code: String?, val allowedValues: List<String>, existenzstatus: Existenzstatus)
: AlphanumerischesDatenelement(code, existenzstatus) { : AlphanumerischesDatenelement(code, existenzstatus) {
override fun validate() { override fun validate() {
super.validate() super.validate()
if (allowedValues.contains(text) == false) { if (allowedValues.contains(value) == false) {
throwValidationException("'$text' ist kein Wert aus der Liste der zulässigen Werte: " + throwValidationException("'$value' ist kein Wert aus der Liste der zulässigen Werte: " +
allowedValues.joinToString(", ") allowedValues.joinToString(", ")
) )
} }

View File

@ -11,27 +11,16 @@ import java.util.*
* *
* Erlaubt sind alle existenten Datumsangaben. * Erlaubt sind alle existenten Datumsangaben.
*/ */
open class Datum(date: Int, existenzstatus: Existenzstatus) : NumerischesDatenelement(date, 8, existenzstatus) { open class Datum(date: Int?, existenzstatus: Existenzstatus) : NumerischesDatenelement(date, 8, existenzstatus) {
companion object { companion object {
const val HbciDateFormatString = "yyyyMMdd" const val HbciDateFormatString = "yyyyMMdd"
val HbciDateFormat = SimpleDateFormat(HbciDateFormatString) val HbciDateFormat = SimpleDateFormat(HbciDateFormatString)
const val DateNotSet = Int.MIN_VALUE
} }
constructor(date: Date?, existenzstatus: Existenzstatus) constructor(date: Date?, existenzstatus: Existenzstatus)
: this(date?.let { HbciDateFormat.format(it).toInt() } ?: DateNotSet, existenzstatus) : this(date?.let { HbciDateFormat.format(it).toInt() }, existenzstatus)
override fun format(): String {
if (value == DateNotSet) {
return "" // optional element and value not set -> write nothing to output
}
return super.format()
}
} }

View File

@ -9,5 +9,5 @@ import net.dankito.fints.messages.datenelemente.basisformate.AlphanumerischesDat
* *
* Maximal 30 Zeichen * Maximal 30 Zeichen
*/ */
abstract class Identifikation(identification: String, existenzstatus: Existenzstatus) abstract class Identifikation(identification: String?, existenzstatus: Existenzstatus)
: AlphanumerischesDatenelement(identification, existenzstatus, 30) : AlphanumerischesDatenelement(identification, existenzstatus, 30)

View File

@ -10,4 +10,4 @@ import net.dankito.fints.messages.datenelemente.basisformate.AlphanumerischesDat
* Hat das DE den Status Kann, so gilt bei Auslassung der Standardwert N. * Hat das DE den Status Kann, so gilt bei Auslassung der Standardwert N.
*/ */
open class JaNein(yes: Boolean?, existenzstatus: Existenzstatus) open class JaNein(yes: Boolean?, existenzstatus: Existenzstatus)
: AlphanumerischesDatenelement(if (yes == true) "J" else "N", existenzstatus, 1) : AlphanumerischesDatenelement(yes?.let { if (yes == true) "J" else "N" }, existenzstatus, 1)

View File

@ -9,7 +9,7 @@ import net.dankito.fints.messages.datenelemente.basisformate.ZiffernDatenelement
* *
* (Siehe z.B. PDF Messages_Geschaeftsvorfaelle Kapitel E.4 (S. 813) oder https://en.wikipedia.org/wiki/ISO_3166-1#Current_codes .) * (Siehe z.B. PDF Messages_Geschaeftsvorfaelle Kapitel E.4 (S. 813) oder https://en.wikipedia.org/wiki/ISO_3166-1#Current_codes .)
*/ */
open class Laenderkennzeichen(countryCode: Int, existenzstatus: Existenzstatus) open class Laenderkennzeichen(countryCode: Int?, existenzstatus: Existenzstatus)
: ZiffernDatenelement(countryCode, 3, existenzstatus) { : ZiffernDatenelement(countryCode, 3, existenzstatus) {
companion object { companion object {

View File

@ -12,27 +12,16 @@ import java.util.*
* Gültige Uhrzeit. Es ist immer Ortszeit des sendenden Systems einzustellen. * Gültige Uhrzeit. Es ist immer Ortszeit des sendenden Systems einzustellen.
* Unterschiedliche Zeitzonen werden nicht unterstützt * Unterschiedliche Zeitzonen werden nicht unterstützt
*/ */
open class Uhrzeit(time: Int, existenzstatus: Existenzstatus) : ZiffernDatenelement(time, 6, existenzstatus) { open class Uhrzeit(time: Int?, existenzstatus: Existenzstatus) : ZiffernDatenelement(time, 6, existenzstatus) {
companion object { companion object {
const val HbciTimeFormatString = "HHmmss" const val HbciTimeFormatString = "HHmmss"
val HbciTimeFormat = SimpleDateFormat(HbciTimeFormatString) val HbciTimeFormat = SimpleDateFormat(HbciTimeFormatString)
const val TimeNotSet = Int.MIN_VALUE
} }
constructor(time: Date?, existenzstatus: Existenzstatus) constructor(time: Date?, existenzstatus: Existenzstatus)
: this(time?.let { HbciTimeFormat.format(it).toInt() } ?: 0, existenzstatus) : this(time?.let { HbciTimeFormat.format(it).toInt() }, existenzstatus)
override fun format(): String {
if (value == TimeNotSet) {
return "" // optional element and value not set -> write nothing to output
}
return super.format()
}
} }

View File

@ -7,22 +7,24 @@ import net.dankito.fints.messages.Existenzstatus
* Es gilt der FinTS-Basiszeichensatz ohne die Zeichen CR und LF. * Es gilt der FinTS-Basiszeichensatz ohne die Zeichen CR und LF.
*/ */
abstract class AlphanumerischesDatenelement @JvmOverloads constructor( abstract class AlphanumerischesDatenelement @JvmOverloads constructor(
alphanumericValue: String, existenzstatus: Existenzstatus, val maxLength: Int? = null alphanumericValue: String?, existenzstatus: Existenzstatus, val maxLength: Int? = null
) : TextDatenelement(alphanumericValue, existenzstatus) { ) : TextDatenelement(alphanumericValue, existenzstatus) {
override fun validate() { override fun validate() {
super.validate() super.validate()
if (text.contains("\r") || text.contains("\n")) { if (writeToOutput && value != null) { // if value is null and value has to be written to output then validation already fails above
throwValidationException("Alphanumerischer Wert '$text' darf kein Carriage Return (\r) oder " + if (value.contains("\r") || value.contains("\n")) {
throwValidationException("Alphanumerischer Wert '$value' darf kein Carriage Return (\r) oder " +
"Line Feed (\n) enthalten.") "Line Feed (\n) enthalten.")
} }
maxLength?.let { maxLength?.let {
if (text.length > maxLength) { if (value.length > maxLength) {
throwValidationException("Wert '$text' darf maximal $maxLength Zeichen lang sein, " + throwValidationException("Wert '$value' darf maximal $maxLength Zeichen lang sein, " +
"hat aber ${text.length} Zeichen.") "hat aber ${value.length} Zeichen.")
}
} }
} }
} }

View File

@ -1,7 +1,6 @@
package net.dankito.fints.messages.datenelemente.basisformate package net.dankito.fints.messages.datenelemente.basisformate
import net.dankito.fints.messages.Existenzstatus import net.dankito.fints.messages.Existenzstatus
import net.dankito.fints.messages.datenelemente.Datenelement
/** /**
@ -10,12 +9,13 @@ import net.dankito.fints.messages.datenelemente.Datenelement
* für binäre Daten keine Gültigkeit besitzt. Ferner gelten die speziellen Syntaxregeln für * für binäre Daten keine Gültigkeit besitzt. Ferner gelten die speziellen Syntaxregeln für
* binäre Daten (s. Kap. H.1.3). * binäre Daten (s. Kap. H.1.3).
*/ */
open class BinaerDatenelement @JvmOverloads constructor(val data: String, existenzstatus: Existenzstatus, val maxLength: Int? = null) open class BinaerDatenelement @JvmOverloads constructor(data: String?, existenzstatus: Existenzstatus, val maxLength: Int? = null)
: Datenelement(existenzstatus) { : TextDatenelement(data, existenzstatus) {
@JvmOverloads constructor(data: ByteArray, existenzstatus: Existenzstatus, maxLength: Int? = null) : @JvmOverloads constructor(data: ByteArray, existenzstatus: Existenzstatus, maxLength: Int? = null) :
this(String(data), existenzstatus, maxLength) this(String(data), existenzstatus, maxLength)
/** /**
* Für binäre Daten gilt eine besondere Syntaxregelung: Das Auftreten dieser Daten wird eingeleitet mit dem * Für binäre Daten gilt eine besondere Syntaxregelung: Das Auftreten dieser Daten wird eingeleitet mit dem
* Binärdatenkennzeichen (@). Anschließend folgt die Längenangabe zu den binären Daten und der binäre Wert selbst, * Binärdatenkennzeichen (@). Anschließend folgt die Längenangabe zu den binären Daten und der binäre Wert selbst,
@ -27,21 +27,23 @@ open class BinaerDatenelement @JvmOverloads constructor(val data: String, existe
* Bei Elementen, die entsprechende Zeichen enthalten können (z. B. DE SEPAName) ist eine base64-Kodierung in der * Bei Elementen, die entsprechende Zeichen enthalten können (z. B. DE SEPAName) ist eine base64-Kodierung in der
* Spezifikation vorzusehen. * Spezifikation vorzusehen.
*/ */
override fun format(): String { override fun formatValue(value: String): String {
if (data.length > 0) { return "@${value.length}@" + value
return "@${data.length}@" + data
}
return ""
} }
override fun validate() { override fun validate() {
// binary data aren't checked, so they are always valid // binary data aren't checked, so they are always valid
if (writeToOutput) {
checkIfMandatoryValueIsSet()
value?.let { // if value is null and value has to be written to output then validation already fails above
maxLength?.let { maxLength?.let {
if (data.length > maxLength) { if (value.length > maxLength) {
throwValidationException("Binäre Daten dürfen nur eine maximale Größe von $maxLength Bytes haben, " + throwValidationException("Binäre Daten dürfen nur eine maximale Größe von $maxLength Bytes " +
"haben aber ${data.length} Bytes.") "haben, haben aber ${value.length} Bytes.")
}
}
} }
} }
} }

View File

@ -1,26 +1,24 @@
package net.dankito.fints.messages.datenelemente.basisformate package net.dankito.fints.messages.datenelemente.basisformate
import net.dankito.fints.messages.Existenzstatus import net.dankito.fints.messages.Existenzstatus
import net.dankito.fints.messages.datenelemente.Datenelement
/** /**
* Zulässig sind lediglich die Ziffern 0 bis 9. Führende Nullen sind nicht zugelassen. * Zulässig sind lediglich die Ziffern 0 bis 9. Führende Nullen sind nicht zugelassen.
*/ */
abstract class NumerischesDatenelement(val value: Int, val numberOfDigits: Int, existenzstatus: Existenzstatus) abstract class NumerischesDatenelement(val number: Int?, val numberOfDigits: Int, existenzstatus: Existenzstatus)
: Datenelement(existenzstatus) { : TextDatenelement(number?.toString(), existenzstatus) {
override fun format(): String {
return value.toString()
}
override fun validate() { override fun validate() {
super.validate()
if (writeToOutput && number != null) { // if number is null and number has to be written to output then validation already fails above
val maxValue = Math.pow(10.0, numberOfDigits.toDouble()) - 1 val maxValue = Math.pow(10.0, numberOfDigits.toDouble()) - 1
if (value < 0 || value > maxValue) { if (number < 0 || number > maxValue) {
throwValidationException("Wert '$value' muss im Wertebereich von 0 - $maxValue liegen.") throwValidationException("Wert '$number' muss im Wertebereich von 0 - $maxValue liegen.")
}
} }
} }

View File

@ -8,27 +8,49 @@ import net.dankito.fints.messages.datenelemente.Datenelement
/** /**
* Es gilt der vollständige FinTS-Basiszeichensatz. * Es gilt der vollständige FinTS-Basiszeichensatz.
*/ */
abstract class TextDatenelement(val text: String, existenzstatus: Existenzstatus) : Datenelement(existenzstatus) { abstract class TextDatenelement(val value: String?, existenzstatus: Existenzstatus) : Datenelement(existenzstatus) {
override val isValueSet = value != null
override fun format(): String { override fun format(): String {
return text if (writeToOutput) {
value?.let {
return formatValue(it)
}
}
return ""
}
protected open fun formatValue(value: String): String {
return value // may overwritten in sub classes
} }
override fun validate() { override fun validate() {
if (writeToOutput) {
checkIfMandatoryValueIsSet()
try { try {
if (HbciCharset.DefaultCharset.newEncoder().canEncode(text) == false) { if (HbciCharset.DefaultCharset.newEncoder().canEncode(value) == false) {
throwInvalidCharacterException() throwInvalidCharacterException()
} }
} catch (e: Exception) { } catch (e: Exception) {
throwInvalidCharacterException() throwInvalidCharacterException()
} }
} }
}
protected open fun checkIfMandatoryValueIsSet() {
if (existenzstatus == Existenzstatus.Mandatory && value == null) {
throwValidationException("Wert ist auf dem Pflichtfeld ${javaClass.simpleName} not set")
}
}
protected open fun throwInvalidCharacterException() { protected open fun throwInvalidCharacterException() {
throwValidationException( throwValidationException(
"Wert '$text' enthält Zeichen die gemäß des Zeichensatzes " + "Wert '$value' enthält Zeichen die gemäß des Zeichensatzes " +
"${HbciCharset.DefaultCharset.displayName()} nicht erlaubt sind." "${HbciCharset.DefaultCharset.displayName()} nicht erlaubt sind."
) )
} }

View File

@ -6,12 +6,12 @@ import net.dankito.fints.messages.Existenzstatus
/** /**
* Zulässig sind die Ziffern 0 bis 9. Führende Nullen sind zugelassen. * Zulässig sind die Ziffern 0 bis 9. Führende Nullen sind zugelassen.
*/ */
abstract class ZiffernDatenelement(value: Int, numberOfDigits: Int, existenzstatus: Existenzstatus) abstract class ZiffernDatenelement(value: Int?, numberOfDigits: Int, existenzstatus: Existenzstatus)
: NumerischesDatenelement(value, numberOfDigits, existenzstatus) { : NumerischesDatenelement(value, numberOfDigits, existenzstatus) {
override fun format(): String { override fun formatValue(value: String): String {
return String.format("%0${numberOfDigits}d", value) return String.format("%0${numberOfDigits}d", number)
} }
} }

View File

@ -18,11 +18,14 @@ import net.dankito.fints.messages.datenelemente.basisformate.NumerischesDatenele
abstract class Bezugssegment(segmentNumber: Int, existenzstatus: Existenzstatus) abstract class Bezugssegment(segmentNumber: Int, existenzstatus: Existenzstatus)
: NumerischesDatenelement(segmentNumber, 3, existenzstatus) { : NumerischesDatenelement(segmentNumber, 3, existenzstatus) {
override fun validate() { override fun validate() {
super.validate() super.validate()
if (value < 1) { number?.let { // if number is null and number has to be written to output then validation already fails above
throwValidationException("Bezug Segmentnummer '$value' muss größer oder gleich 1 sein.") if (number < 1) {
throwValidationException("Bezug Segmentnummer '$number' muss größer oder gleich 1 sein.")
}
} }
} }

View File

@ -11,11 +11,14 @@ import net.dankito.fints.messages.datenelemente.basisformate.NumerischesDatenele
*/ */
open class Segmentnummer(number: Int) : NumerischesDatenelement(number, 3, Existenzstatus.Mandatory) { open class Segmentnummer(number: Int) : NumerischesDatenelement(number, 3, Existenzstatus.Mandatory) {
override fun validate() { override fun validate() {
super.validate() super.validate()
if (value < 1) { number?.let { // if number is null and number has to be written to output then validation already fails above
throwValidationException("Segmentnummer '$value' muss größer oder gleich 1 sein.") if (number < 1) {
throwValidationException("Segmentnummer '$number' muss größer oder gleich 1 sein.")
}
} }
} }

View File

@ -12,4 +12,4 @@ import net.dankito.fints.messages.datenelemente.abgeleiteteformate.Identifikatio
* Es ist zu beachten, dass Kontonummern auch führende Nullen beinhalten können, die * Es ist zu beachten, dass Kontonummern auch führende Nullen beinhalten können, die
* bankfachlich relevant sind und nicht abgeschnitten werden dürfen. * bankfachlich relevant sind und nicht abgeschnitten werden dürfen.
*/ */
open class KontoDepotnummer(number: String, existenzstatus: Existenzstatus) : Identifikation(number, existenzstatus) open class KontoDepotnummer(number: String?, existenzstatus: Existenzstatus) : Identifikation(number, existenzstatus)

View File

@ -9,4 +9,4 @@ import net.dankito.fints.messages.datenelemente.abgeleiteteformate.Identifikatio
* geführt werden. Anstatt einer Kontonummer kann auch ein anderes identifizierendes Merkmal * geführt werden. Anstatt einer Kontonummer kann auch ein anderes identifizierendes Merkmal
* angegeben werden (z.B. der entsprechende ISO-Währungscode bei Währungskonten). * angegeben werden (z.B. der entsprechende ISO-Währungscode bei Währungskonten).
*/ */
open class Unterkontomerkmal(attribute: String, existenzstatus: Existenzstatus) : Identifikation(attribute, existenzstatus) open class Unterkontomerkmal(attribute: String?, existenzstatus: Existenzstatus) : Identifikation(attribute, existenzstatus)

View File

@ -9,5 +9,5 @@ import net.dankito.fints.messages.datenelemente.basisformate.AlphanumerischesDat
* kann in Verwaltungs-Geschäftsvorfällen benutzt werden, wenn z. B. die Angabe der echten Handynummer * kann in Verwaltungs-Geschäftsvorfällen benutzt werden, wenn z. B. die Angabe der echten Handynummer
* aus Datenschutzgründen nicht möglich ist oder auch um die Benutzerfreundlichkeit zu erhöhen. * aus Datenschutzgründen nicht möglich ist oder auch um die Benutzerfreundlichkeit zu erhöhen.
*/ */
open class BezeichnungDesTANMediums(identifier: String, existenzstatus: Existenzstatus) open class BezeichnungDesTANMediums(identifier: String?, existenzstatus: Existenzstatus)
: AlphanumerischesDatenelement(identifier, existenzstatus, 32) : AlphanumerischesDatenelement(identifier, existenzstatus, 32)

View File

@ -27,9 +27,9 @@ open class KontoverbindungInternational(
) : Datenelementgruppe(listOf( ) : Datenelementgruppe(listOf(
IBAN(iban ?: "", Existenzstatus.Optional), IBAN(iban ?: "", Existenzstatus.Optional),
BIC(bic ?: "", Existenzstatus.Optional), BIC(bic ?: "", Existenzstatus.Optional),
KontoDepotnummer(accountNumber ?: "", Existenzstatus.Optional), KontoDepotnummer(accountNumber, Existenzstatus.Optional),
Unterkontomerkmal(subAccountAttribute ?: "", Existenzstatus.Optional), Unterkontomerkmal(subAccountAttribute, Existenzstatus.Optional),
Kreditinstitutskennung(bankCountryCode ?: 0, bankCode ?: "", Existenzstatus.Optional) Kreditinstitutskennung(bankCountryCode ?: 0, bankCode ?: "", if (bankCountryCode != null && bankCode != null) Existenzstatus.Optional else Existenzstatus.NotAllowed)
), Existenzstatus.Mandatory) { ), Existenzstatus.Mandatory) {
constructor(bank: BankData, customer: CustomerData, subAccountAttribute: String?) constructor(bank: BankData, customer: CustomerData, subAccountAttribute: String?)

View File

@ -33,6 +33,6 @@ open class ZweiSchrittTanEinreichung(
NotAllowedDatenelement(), // TODO: SMS-Abbuchungskonto // M: Bei TAN-Process=1, 3, 4 und „SMS-Abbuchungskonto erforderlich“=2. O: sonst NotAllowedDatenelement(), // TODO: SMS-Abbuchungskonto // M: Bei TAN-Process=1, 3, 4 und „SMS-Abbuchungskonto erforderlich“=2. O: sonst
NotAllowedDatenelement(), // TODO: Challenge-Klasse // M: bei TAN-Prozess=1 und „Challenge-Klasse erforderlich“=J. N: sonst NotAllowedDatenelement(), // TODO: Challenge-Klasse // M: bei TAN-Prozess=1 und „Challenge-Klasse erforderlich“=J. N: sonst
NotAllowedDatenelement(), // TODO: Parameter Challenge-Klasse // O: Bei TAN-Process=1 „Challenge-Klasse erforderlich“=J. N: sonst NotAllowedDatenelement(), // TODO: Parameter Challenge-Klasse // O: Bei TAN-Process=1 „Challenge-Klasse erforderlich“=J. N: sonst
BezeichnungDesTANMediums(tanMediaIdentifier ?: "", Existenzstatus.Optional), // M: bei TAN-Prozess=1, 3, 4 und „Anzahl unterstützter aktiver TAN-Medien“ nicht vorhanden. O: sonst BezeichnungDesTANMediums(tanMediaIdentifier, Existenzstatus.Optional), // M: bei TAN-Prozess=1, 3, 4 und „Anzahl unterstützter aktiver TAN-Medien“ nicht vorhanden. O: sonst
NotAllowedDatenelement() // TODO: Antwort HHD_UC // M: bei TAN-Prozess=2 und „Antwort HHD_UC erforderlich“=“J“. O: sonst NotAllowedDatenelement() // TODO: Antwort HHD_UC // M: bei TAN-Prozess=2 und „Antwort HHD_UC erforderlich“=“J“. O: sonst
), Existenzstatus.Mandatory) ), Existenzstatus.Mandatory)