Dublettenprüfung für Adressen

1                     Themenübersicht

Adressen werden in einem eigenständigen Entity gespeichert. Dadurch ist es einfacher möglich, die im Standard vorhandenen String-Attribute zu verlängern und neue Attribute hinzuzufügen. Daneben werden Dublettenprüfungen und Prüfungen gegen Ausschlusslisten besser unterstützt.

In diesem Dokument werden die technisch notwendigen Attribute des Entitys erklärt, wesentliche Hilfsklassen erläutert und beschrieben, wie eine Dublettenprüfung und ähnliches realisiert werden können.

2                     Zielgruppe

  • Entwickler

3                     Programmierschnittstelle

3.1               Datenmodell

Adressen werden in dem Entity com.cisag.app.general.obj.AddressData gespeichert. Die Attribute des Entitys lassen sich in Nutzdaten, wie z. B. Straße und Ort, und technisch bedingte Attribute unterscheiden. Die technisch notwendigen Attribute sind:

Attribut Beschreibung
guid Primärschlüssel
hashCode Aus den Nutzdaten ermittelter Zahlenwert (Hash-Wert) zum Identifizieren der Adresse, Teil des eindeutigen Schlüssels für das HashCode-Verfahren (byPrevious)
previous Referenz auf vorhergehende Instanz mit gleichem Hash-Wert, Teil des eindeutigen Schlüssels für das HashCode-Verfahren (byPrevious)
next Referenz auf nachfolgende Instanz mit gleichem Hash-Wert
code Teil des fachlichen Schlüssels (byCode)
keyExtension Teil des fachlichen Schlüssels (byCode)
backup Flag zur Unterscheidung von originären Adressen und Adresskopien für Belege

Das für Adressen verwendete HashCode-Verfahren findet sich auch an anderen Stellen in Semiramis wieder und vermeidet das Erzeugen von inhaltlich gleichen Datensätzen für Datenstrukturen ohne fachlichen Schlüssel. Beim Speichern einer Adresse wird aus den Nutzdaten mittels Hash-Funktion eine Zahl berechnet. Zugunsten einer performanten Verwendung des Verfahrens soll die Hash-Funktion Kollisionen der Hash-Werte möglichst vermeiden. Da es dennoch andere Adressen geben kann, für die die gleiche Zahl berechnet wurde, wird zunächst versucht, eine solche Adresse über den eindeutigen Schlüssel (byPrevious: hashCode=? + previous=Guid.ZEROGUID) zu laden. Wird eine Adresse mit diesem Schlüssel gefunden und der Inhalt der Adresse stimmt nicht mit dem Inhalt der zu speichernden Adresse überein, dann wird die nächste Adresse gesucht und geprüft, wobei für previous der Primärschlüssel der zuvor gefundenen Adresse verwendet wird. Wird eine vollständig gleiche Adresse gefunden, dann ist das Speichern der Adresse nicht notwendig, es wird der Primärschlüssel der bereits bestehenden Adresse als Referenz verwendet. Wird keine gleiche Adresse gefunden, dann wird der Primärschlüssel der letzten gefundenen Adresse mit dem gleichen Hash-Wert in das Attribut previous der neuen Adresse eingetragen und diese gespeichert.

Im Standard von Semiramis werden Adressen nicht über einen eindeutigen Code identifiziert. Im Rahmen von Adaptierungen soll es aber möglich sein, Adressen auch mit fachlichem Schlüssel zu Verwalten. Das dafür notwendige Datenmodell ist bereits im Standard vorhanden und besteht aus den zwei Attributen code und keyExtension sowie dem Business Key byCode (besteht aus code + keyExtension). Für im Standard erzeugte AddressData-Instanzen enthält das Attribut code einen Leerstring. Um dennoch die Eindeutigkeit des Schlüssels byCode zu gewährleisten, wird in das Attribut keyExtension der Primärschlüssel eingetragen. Somit ist der Schlüssel eindeutig, für den Standard aber ohne Bedeutung, da inhaltlich äquivalent zum Primärschlüssel. Damit der Schlüssel im Rahmen einer Adaptierung verwendet werden kann, muss das Attribut keyExtension fix mit Guid.ZEROGUID gefüllt und ein eindeutiger Code zur Identifizierung angegeben werden. In diesem Fall ist dann der für das HashCode-Verfahren verwendete Schlüssel byPrevious nicht mehr sinnvoll zu verwenden. Das Attribut hashCode ist mit 0 und das Attribut previous ist mit dem Primärschlüssel-Wert zu füllen.

Werden Adressen über einen eindeutigen Code identifiziert, so dient dies vor allem dazu, diese verwalten bzw. ändern zu können. Das Ändern von Adressen darf aber nicht dazu führen, dass beim Nachdruck von Belegen, wie z. B. Ausgangsrechnungen, eine andere Adresse ausgegeben wird als zum Zeitpunkt der Belegerzeugung. Daher ist das Verwenden von änderbaren Adressen in Belegen nicht zulässig. Eine änderbare Adresse muss zunächst in eine Adresse ohne Code umgewandelt und mittels HashCode-Verfahren gespeichert werden (Snapshot). Die so entstandene nicht änderbare Adresse kann im Beleg verwendet werden. Inhaltlich entsteht dabei ein erwünschtes Duplikat der ursprünglichen Adresse. Um diese im Rahmen der Dublettenprüfung leichter ausschließen zu können, wird das Flag backup auf true gesetzt.

3.2               Hilfsklassen

Für die Verwendung von Adressen stehen im Wesentlichen folgende Hilfsklassen zur Verfügung:

Klasse Beschreibung
com.cisag.app.general

.log.HashCodeLogic

Realisiert das HashCode-Verfahren und bietet Unterstützung für die Migration der Daten beim Ändern der Hash-Funktion.
com.cisag.app.general

.log.AddressDataAdapter

Stellt die für das HashCode-Verfahren notwendige Hash-Funktion zur Verfügung.
com.cisag.app.general

.log.AddressDataValidation

Dient zum Prüfen einer Adresse.

Hinweis:
Das Entity AddressData enthält keine Namen, daher ist der Aufruf einer Dublettenprüfung aus dieser Prüfklasse heraus in der Regel nicht zweckmäßig.

com.cisag.app.general

.gui.AddressDataField

Dient zur Erfassung und Anzeige von Adressen.

Das Speichern einer Adresse muss immer mit Hilfe der Klasse HashCodeLogic erfolgen:

AddressDataAdapter adapter = AddressDataAdapter.getInstance();

byte[] guid = HashCodeLogic.getInstance().save(myAddress, adapter);

myBO.setAddress(guid);

Hinweis:
Das direkte Ändern und Speichern der AddressData-Nutzdaten ohne Verwendung der Klasse HashCodeLogic führt regelmäßig zu irreparablen Daten!

4                     Dublettenprüfung

4.1               Allgemeine Hinweise

Im Folgenden werden Möglichkeiten der Erweiterung um Prüfungen, wie das Prüfen auf Dubletten, das Prüfen gegen Ausschlusslisten oder das Prüfen auf Existenz über externe Anbieter (z. B. Post), im Rahmen von Adaptierungen beschrieben.

In der Regel sind die hier betrachteten Prüfungen nicht für alle Adressen in Semiramis erforderlich bzw. wünschenswert. Wird beispielsweise in der Anwendung Banken einer Filiale eine Adresse zugeordnet, dann soll die gleiche Adresse auch einem Partner zugeordnet werden können, wenn die Bankfiliale bspw. als Kunde, Kontakt oder ähnliches geführt werden soll. Zudem ist eine Dublettenprüfung oder Prüfung gegen Ausschlusslisten bei der Erfassung von Bankfilialen vermutlich generell unerwünscht. Grundsätzlich muss also vor einer Adaptierung definiert werden, in welchen Anwendungen die Überprüfung stattfinden soll und welche Dubletten unerwünscht sind.

Exemplarisch werden im Folgenden die Prüfungen beim Erfassen eines neuen Partners und beim Erfassen der Adresse für einen Pseudo-Partner im Vertriebsauftrag vorgestellt. Statt einer unmittelbaren Überprüfung im Rahmen der allgemeinen Prüfungen können insbesondere Existenzprüfungen natürlich auch zu einem späteren Zeitpunkt für mehrere neue Adressen auf einmal erfolgen. Für das Business Object com.cisag.app.general.obj.AddressData wird der Erzeugungszeitpunkt im updateInfo-Part protokolliert, sodass ein Einschränken auf neue Adressen einfach möglich ist.

4.2               Erfassen eines neuen Partners

Bei der Erfassung eines neuen Partners wird die Adresse in der Klasse com.cisag.app.general.partner.log.PartnerBaseValidation geprüft. Diese Klasse wäre auch zu adaptieren, wenn zusätzlich zu den Standard-Prüfungen eine Dublettenprüfung o. ä. durchgeführt werden soll. Die Methode validateCreate(PartnerBaseData) ist derart zu erweitern, dass die Prüfung für alle „echten“ Partner erfolgt, das heißt Pseudo-Partner und Partner-Vorlagen werden nicht geprüft:

PartnerBaseData base = …

PartnerValidation pv = PartnerValidation.getInstance();

boolean pseudo = pv.isPseudoPartner(base.getPartnerObject());

boolean template = base.getPartnerEntity().isTemplateMode();

if (!pseudo && !template) {

}

Der für Dublettenprüfungen regelmäßig benötigte Name wird abhängig vom Typ aus der Person oder der Organisation ermittelt:

Partner partner = base.getPartnerObject();

if (partner.isHuman()) {

PersonMutable person = partner.getMutablePerson();

String name = person.getSurname();

} else {

OrganizationalUnitMutable ou =

partner.getMutableOrganizationalUnit();

String[] names = ou.getName();

}

Wenn ausschließlich exakt gleiche Adressen als Dubletten erkannt werden sollen, kann der im Standard berechnete Hash-Wert zur Einschränkung der zu überprüfenden Adressen verwendet werden. Ansonsten ist zu empfehlen, ein neues Attribut für die Vorselektion einzuführen und mit einem geeigneten Algorithmus ähnliche Adressen entsprechend zu indizieren.

Die folgende Methode ermittelt exemplarisch den Primärschlüssel einer exakt gleichen Adresse. Anhand des Rückgabewertes können dann bspw. die Partner ermittelt werden, denen die potentiell bereits existierende Adresse zugeordnet ist. Stimmt der Name des zu prüfenden Partners mit dem Namen eines der so ermittelten Partner überein, handelt es sich um eine Dublette.

private byte[] findDuplicateCopy(AddressData address) {

AddressDataAdapter adapter = AddressDataAdapter.getInstance();

int hash = adapter.hashCode(data.getAddressData());

byte[] previous = Guid.ZEROGUID;

while(true) {

AddressData temp = (AddressData)

om.getObject(AddressData.buildByPreviousKey(hash, previous));

if (temp == null) return null;

if (!temp.isBackup() && equals(address, temp))
return temp.getGuid();

previous = temp.getGuid();

}

}

Es ist davon auszugehen, dass die Hash-Funktion nur selten den gleichen Hash-Wert für unterschiedliche Adressen berechnet, das heißt Kollisionen sollten selten auftreten. Daher wird empfohlen, die Adressen einzeln mit dem eindeutigen Schlüssel byPrevious zu laden und nicht mittels CisObjectIterator alle Adressen mit dem gleichen Hash-Wert auf einmal zu ermitteln.

Für das Vergleichen der Adressen kann hier nicht die Methode contentEquals von AddressData verwendet werden, da diese Methode auch die technisch bedingten Attribute hashCode, previous usw. vergleicht. Stattdessen muss der Vergleich der Nutzdaten explizit programmiert werden.

Insgesamt ist der Nutzen einer auf exakter Gleichheit beruhenden Dublettenprüfung natürlich fraglich. Hier soll damit primär die Funktionsweise des HashCode-Verfahrens verdeutlich werden.

4.3               Pseudo-Partner im Vertriebsauftrag

Eine Dublettenprüfung für Pseudo-Partner im Vertriebsauftrag sollte sicherstellen, dass der Kunde nicht bereits als „normaler“ Partner erfasst ist. Das heißt, es darf kein Partner existieren, dessen Name und Adresse mit den im Auftrag erfassten Daten übereinstimmt.

Das Überprüfen der Kundenadresse erfolgt standardmäßig in der Klasse com.cisag.app.sales.order.log.OrderHeaderValidation. Die Methode validateCustomerData(Data) kann entsprechend erweitert werden, wobei die Prüfung nur dann erfolgen muss, wenn es sich um einen Pseudo-Partner handelt:

Data header = …

Partner partner = header.getCustomer().retrievePartner();

boolean pseudo =

PartnerValidation.getInstance().isPseudoPartner(partner);

 

Der für die Prüfung notwendige Name ist im OrderCustomerDataInfo-Objekt (ebenfalls ein Business Object, welches mit dem HashCode-Verfahren verwaltet wird) enthalten, und auf die Adresse selbst wird über die Methode getCustomerAddressData() zugegriffen:

String name = header.getCustomerData().getName();

AddressData address = header.getCustomerAddressData();

Da die Prüfung nur beim Ändern der Daten erfolgen muss, ist ein Vergleich mit den ggf. bereits persistenten Daten zu empfehlen. Der Zugriff erfolgt mit getPersistentCustomerData() bzw. getPersistentCustomerAddressData().

Wie beim Erfassen eines neuen Partners können die Dubletten ermittelt werden, indem zunächst gleiche oder ähnliche Adressen identifiziert und dann die Namen der zugehörenden Partner verglichen werden.

Die Adressen für Lieferempfänger und Rechnungsempfänger lassen sich völlig analog durch Erweitern der Methoden validateDeliveryCustomerData bzw. validateInvoiceCustomerData prüfen.

Czy ten artykuł był pomocny?