Ab Semiramis 4.2 können zum Löschen markierte Artikel und Verwendungen mit der Reorganisation gelöscht werden. Um zum Löschen markierte Daten tatsächlich physisch löschen zu können, muss deren Verwendung in anderen Daten geprüft werden.
In diesem Dokument werden die technisch notwendigen Registrierungen erklärt und beschrieben, wie eine Erweiterung für eigene zu prüfende Objekte aussehen könnte.
1 Zielgruppe
- Entwickler
2 Programmierschnittstelle
Um für die Artikel-Reorganisation ein BO zur Überprüfung zu registrieren, sind zwei Dinge notwendig. Erstens eine Klasse, die das Interface com.cisag.app.general.item.reorg.Usage implementiert, zweitens die Registrierung dieser Klasse.
Wird ein Artikel bzw. eine Artikelrolle nicht mehr verwendet, müssen noch alle Objekte registriert werden, die dann jeweils gelöscht werden müssen. Hierzu muss ebenfalls eine Klasse registriert werden. Diese muss das Interface com.cisag.app.general.item.reorg.Deletion implementieren.
2.1 Erzeugung der Usage-Klassen
Außer der Verwendung „Basis“ werden alle Artikel Verwendungen organisationsabhängig gespeichert. Für den Zugriff auf diese Daten ist also immer eine Organisation erforderlich. Somit ist bei den verwendenden Business Objekten auch ein Organisationsbezug zu ermitteln. Einige Standardfälle werden unterstützt:
- Es wird nur die Artikel-GUID ohne Organisationsbezug verwendet, z.B. Verwendungen, die nur auf Ebene des Mandanten erhalten bleiben müssen oder bezogen auf Basisdaten.
- Artikel- und Organisations-GUID sind in dem BO (Business Objekt) enthalten.
- Die Artikel-GUID ist in einem BO und die Organisations-GUID in einem anderen BO, das über einen einfachen join verbunden werden kann.
- Die Artikel-Guid ist in dem BO enthalten, die Organisation-Guid ist jedoch in einem anderen BO enthalten, das über einen zweifachen Join verbunden werden kann (typisch für detail-header-orderinfo Kombinationen).
Im Normalfall gibt es in den verwendenden BOs nur ein Attribut, dass sich auf den Artikel bezieht. Ebenso werden praktisch immer die Joins nur über je ein Attribut gesteuert. Für diese Fälle existieren createUsage(..) Methoden, die diese Möglichkeiten abbilden. Es gibt eine Standardlogik, die mit den Registrierungsinformationen entsprechende OQL-Statements erstellt, ausführt und die möglichen Verwender ermittelt. Der Rückgabewert dieser createUsage(..) Methoden ist eine Instance der StandardUsage Klasse, die diese Standardlogik implementiert.
Weiterhin existiert eine Signatur der createUsage Methode, die den allgemeinst möglichen Fall der Standardlogik abdeckt. Die joins können mehrere Attribute benötigen. Ebenso ist es möglich, dass ein BO mehrere Artikel-GUID Attribute enthält, die gleichzeitig überprüft werden sollen.
Für komplexere Datenmodelle oder wenn besondere Bedingungen erfüllt sein müssen, ist es notwendig eine Prüfklasse zu programmieren, die das Interface com.cisag.app.general.item.reorg.Usage implementiert.
2.1.1 Artikel-GUID ohne Organisationsbezug
Der Aufruf der Methode
createUsage(clazz, „itemAttribute“)
Erzeugt eine Standardlogik Instanz die im Prinzip folgendes OQL Statement enthält.
Select .. from clazz uc where uc:itemAttribute = ?
2.1.2 Artikel- und Organisations-GUID in einem BO
Der Aufruf der Methode
createUsage(clazz, „itemAttribute“, “organizationalUnitAttribute”)
Erzeugt eine Standardlogik Instanz die im Prinzip folgendes OQL Statement enthält.
Select .. from clazz uc where uc:itemAttribute = ? and uc:organizationalUnitAttribute = ?
2.1.3 Artikel- und Organisations-GUID in unterschiedlichen BOs (einfacher Join)
Der Aufruf der Methode
createUsage(clazz, „itemAttribute“, “source”, targetClass, “target” “organizationalUnitAttribute”)
Erzeugt eine Standardlogik Instanz die im Prinzip folgendes OQL Statement enthält.
Select .. from clazz uc
join targetClass tc on uc:source = tc:target
where uc:itemAttribute = ? and tc:organizationalUnitAttribute = ?
2.1.4 Artikel- und Organisations-GUID in unterschiedlichen BOs (doppelter Join)
Der Aufruf der Methode
createUsage(clazz, „itemAttribute“, “source”, linkingClass, “linkToSource”, “linkToTarget”, targetClass, “target” “organizationalUnitAttribute”)
Erzeugt eine Standardlogik Instanz die im Prinzip folgendes OQL Statement enthält.
Select .. from clazz uc
join linkingClass lc on uc:source = lc:linkToSource
join targetClass tc on lc:linkToTarget = tc:target
where uc:itemAttribute = ? and tc:organizationalUnitAttribute = ?
2.2 Registrierung der Usage-Klassen
In der Klasse com.cisag.app.general.item.reorg.ItemUsageRegistry werden die zu überprüfenden Business Objekte registriert. Diese Business Objekt müssen einen Artikel als Fremdschlüssel enthalten. Außerdem benötigen sie die Artikeldaten auch weiterhin. Enthält ein Business Objekt zwar einen Artikel als Fremdschlüssel, benötigt die Daten jedoch nicht zwingend, dann braucht dieses Business Objekt nicht registriert werden. Allgemein ausgedrückt, die Registrierung enthält die Informationen über Business Objekte, die unter bestimmten Bedingungen gegen das endgültige Löschen von Artikeldaten Einspruch einlegen.
Bei der Registrierung muss die Artikelverwendung berücksichtigt werden und ob es sich bei dem Business Objekt um Stammdaten handelt. Für jede Verwendung existiert eine Methode, deren Name sich an den Einträgen im ValueSet ItemView orientiert.
Beispiele:
- registerCommon(true , createUsage(com.cisag.app.general.obj.Kit.class,”guid”))
- registerSales(false, createUsage(com.cisag.app.sales.obj.SalesOrderDetail.class, “item”, “header”,com.cisag.app.sales.obj.SalesOrder.class,”guid”, “invoicingPartyData”,com.cisag.app.general.obj.OrderPartnerDataInfo.class,”guid”, “partner”))
- registerInventory(false, new InventoryTransactionUsage())
Im ersten Beispiel wird das Business Objekt Kit unter Verwendung einer der beschriebenen createUsage Methoden registriert. Die Basisdaten eines Artikels dürfen somit nur gelöscht werden, falls es keine Einträge im Business Objekt Kit gibt, die sich über das Attribut „guid“ auf den in Frage stehenden Artikel beziehen.
Im zweiten Beispiel wird das Business Objekt SalesOrderDetail unter Verwendung einer der beschriebenen createUsage Methoden registriert. Die Vertriebsdaten eines Artikels dürfen somit nur gelöscht werden, falls es keine Einträge im Business Objekt SalesOrderDetail gibt, die sich über das Attribut „item“ auf den in Frage stehenden Artikel beziehen. In diesem Fall lässt sich der Organisationsbezug über den Zusammenhang zwischen SalesOrderDetail, SalesOrder und OrderPartnerDataInfo bestimmen.
Im dritten Beispiel wird direkt eine Klasse zur Überprüfung der Abhängigkeiten im Business Objekt InventoryTransaction bezogen auf die Lagerlogistikdaten des Artikels registriert. Die Klasse InventoryTransactionUsage muss das Interface Usage implementieren. Dies ist der Weg eine Prüfung zu registrieren, die sich den mit den createUsage Methoden erzeugten Standardfällen entzieht.
2.3 Implementierung des Usage-Interfaces
Für alle BOs, die sich nicht durch die o.a. Standards behandeln lassen, ist die Programmierung einer Prüfklasse notwendig, die das Interface com.cisag.app.general.item.reorg.Usage implementiert. Die Namenskonvention ist Name des zu prüfenden BOs plus Suffix „Usage“. Das Interface „Usage“ enthält die Methoden getUsageClass, isInUse und usedBy. Die Methode getUsageClass gibt die zur Überprüfung registrierte Klasse usageClass zurück und dient hauptsächlich der Wartung (pro Verwendung registrierte Klassen können so ausgegeben werden etc.). Die Methode isInUse prüft die Existenz von Artikelreferenzen und usedBy ermittelt die referenzierenden Instanzen. Die Funktionsweise wird an einem Beispiel erläutert.
public class ReceiptOfGoodsDetailUsage implements Usage{
private final CisEnvironment env =CisEnvironment.getInstance();
private final CisObjectManager om = env.getObjectManager();
private final Class usageClass = ReceiptOfGoodsDetail.class;
public Class getUsageClass(){
return usageClass;
}
public boolean isInUse(Chunk chunk){
switch(chunk.getRole()){
case ItemView.INVENTORY:
break;
default:
throw new IllegalArgumentException(
this.getClass().getName()+” not defined for item role
“+chunk.getRole());
}
boolean isInUse = false;
int size = chunk.countToCheck();
if (size<=0)return isInUse;
StringBuffer oql = new StringBuffer();
oql.append(“Select uc:item, lc:inventoryOrganization from
“+usageClass.getName()+” uc join com.cisag.app.purchasing.obj.
ReceiptOfGoods lc on uc:header = lc:guid “+
“join com.cisag.app.purchasing.obj.ReceiptOfGoodsType tc on
lc:type = tc:guid ”
+”where tc:type = ? and (“);
for (int i=0;i<size;i++){
oql.append(“(uc:item =? and lc:inventoryOrganization =?) or “);
}
oql.setLength(oql.length()-4);
oql.append(“)”);
CisResultSet rs =
om.getResultSet(oql.toString(),CisObjectManager.READ);
try {
int offset = 0;
rs.setShort(++offset,
com.cisag.app.purchasing.ReceiptOfGoodsType.PRODUCTION);
Iterator comboIt = chunk.retrieveCombinationsToCheck();
while(comboIt.hasNext()){
Chunk.Entry currentCombo = (Chunk.Entry)comboIt.next();
rs.setGuid(++offset,currentCombo.getGuid());
rs.setGuid(++offset,currentCombo.getOrganizationalUnit());
}
while (rs.next()){
chunk.setInUse(rs.getGuid(1), rs.getGuid(2));
isInUse = true;
}
} catch (java.sql.SQLException ex) {
throw new RuntimeException(ex);
} finally {
rs.close();
}
return isInUse;
}
public CisObject[] usedBy(Chunk.Entry entry) {
switch(entry.getRole()){
case ItemView.INVENTORY:
break;
default:
throw new IllegalArgumentException(this.getClass().getName()
+” not defined for item role “+entry.getRole());
}
String oqlString = “Select uc:header, uc:guid from
“+usageClass.getName()+” uc join com.cisag.app.purchasing.obj.
ReceiptOfGoods lc on uc:header = lc:guid join
com.cisag.app.purchasing.obj.ReceiptOfGoodsType tc
on lc:type = tc:guid ”
+”where tc:type=? and uc:item=? and lc:inventoryOrganization= ?”;
CisResultSet rs =
om.getResultSet(oqlString, CisObjectManager.READ);
List keys = new ArrayList();
try {
int offset = 0;
rs.setShort(++offset,
com.cisag.app.purchasing.ReceiptOfGoodsType.PRODUCTION);
rs.setGuid(++offset,entry.getGuid());
rs.setGuid(++offset,entry.getOrganizationalUnit());
rs.setMaxRows(ItemReorganizationLogic.MAX_USAGE);
while (rs.next()){
keys.add(
ReceiptOfGoodsDetail.buildPrimaryKey(rs.getGuid(1),rs.getGuid(2)));
}
} catch (java.sql.SQLException ex) {
throw new RuntimeException(ex);
} finally {
rs.close();
}
CisObject[] resultObj =
om.getObjectArray(keys, CisObjectManager.READ);
return resultObj;
}
Hinweis: Das Beispiel dient nur der Verdeutlichung. Es entspricht nicht dem aktuellen Stand der Javaklasse im System und wird bei möglichen Änderungen der Javaklasse im System auch nicht aktualisiert.
Das Überprüfen der richtigen Rolle erfolgt in den Methoden isInUse und usedBy. Die Rolle kann sowohl am Chunk, als auch am Entry abgefragt werden.
Die usedBy-Methode ermittelt die Artikel-Organisations-Kombinationen, die von Instanzen des BOs noch referenziert werden. Die Methode bekommt einen Chunk übergeben. Dieser enthält die zu prüfenden Artikel-Organisations-Kombinationen. Ein Chunk enthält immer nur die Daten für eine Verwendung. Er kann mehrere Artikel-GUIDs enthalten. Zu jeder Artikel-GUID enthält er jedoch alle Organisations-GUIDs, die geprüft werden müssen. Die Kombinationen, die geprüft werden müssen, können am Chunk mit retrieveCombinationsToCheck abgeholt werden. Im Chunk wird dann mittels setInUse-Aufruf angegeben, welche Artikel-Organisations-Kombinationen noch verwendet wird. Ist die betrachtete Rolle organisationsunabhängig (z.B. Artikel-Basis) so enthält die Organisation trotzdem immer den Mandanten, auch wenn diese Information nicht benötigt wird. Dies ist bei dem setInUse-Aufruf zu berücksichtigen.
Bei der isInUse-Methode sollte bei der Definition der OQL-Abfrage darauf geachtet werden, dass mehrere Artikel-Organisations-Kombinationen in einer Abfrage erfolgen. Die Selektionsfelder sollten die Artikel-Organisations-Kombinationen sein. So können die referenzierten Kombinationen mittels setInUse gekennzeichnet werden.
Die usedBy-Methode wird nur aufgerufen, wenn bei der Reorganisation ausführliche Meldungen gewünscht werden. Ihr wird ein Chunk.Entry übergeben. Dieser enthält eine Artikel-Organisations-Kombination, für die referenzierende Instanzen ermittelt wurden. Diese referenzierenden Instanzen gibt die usedBy-Methode zurück. Bei der Definition der OQL-Abfrage sollte hier als Selektionsfeld der primäre Schlüssel des BOs gewählt werden.
Hinweis: Lieferanten-Artikeldaten sind ein Sonderfall. In den Beschaffungsdaten des Artikels kann angegeben werden, ob nur bei zugeordneten Lieferanten beschafft werden darf. In diesem Fall müssen für einige BOs nicht nur die Beschaffungsdaten sondern auch die speziellen Lieferanten-Artikeldaten erhalten bleiben. Hier besteht der Schlüssel aus Artikel, Organisation und Lieferant. Um dies abzubilden, wird im Chunk.Entry ein Attribut partnerKey verwaltet. Im Normalfall enthält dies nur die ZEROGUID, bei Lieferanten-Artikeldaten jedoch die des Lieferanten.
2.4 Registrierung von zu löschenden BOs
Falls eine Schlüssel Kombination durch alle Tests gekommen ist, müssen die entsprechenden Daten nun noch physisch gelöscht werden. Dies erfordert die Registrierung der zugehörigen Business Objekte. Im Normalfall umfasst die Registrierung für die Artikel Reorganisation das Business Objekt der Verwendung (also z.B. InventoryItem für die Lagerlogistik) plus zusätzliche Dependents.
Für die Registrierung existiert pro Verwendung eine Methode an der Klasse ItemUsageRegistry:
Beispiele:
- registerDeletionSales(new ItemClassDeletion(com.cisag.app.sales.obj.SalesItem.class));
- registerDeletionInventory(new ItemClassDeletion(com.cisag.app.inventory.obj.ItemStorageData.class));
Im ersten Beispiel wird einfach das Hauptobjekt für die Verwendung „Vertrieb“ zum Löschen registriert. Im zweiten Beispiel wird ein Dependent (ItemStorageData) registriert, das mitgelöscht werden muss, wenn die Verwendung Lagerlogistik gelöscht wird.
2.5 Implementieren des Deletion-Interfaces
Das Deletion-Interfaces umfasst die Methoden getDeletionClass, delete und deleteExpiredVersions implementieren.
Die Methode getDeletionClass gibt die zum Löschen registrierte Klasse deletionClass zurück und dient hauptsächlich der Wartung (pro Verwendung registrierte Klassen können so ausgegeben werden etc.).
Die Methode deleteExpiredVersions ist für zukünftige Erweiterungen vorgesehen. Sie löscht bei einem zeitabhängigen BO die Versionen, deren Gültigkeitszeitraum vor dem übergebenen Datum liegt. Bei nicht zeitabhängigen BOs braucht die Methode nur true zurückgeben.
Die Methode delete löscht bei zeitabhängigen BOs alle Versionen. Für welche Artikel-Organisations-Kombinationen die Daten gelöscht werden müssen, wird über retrieveFreeCombinations am Chunk abgefragt.
Als Beispiel kann com.cisag.app.general.item.reorg.ItemClassDeletion herangezogen werden, die dazu dient direkt artikelbezogene Daten zu löschen.