Development Guidelines in German

From ADempiere
Jump to: navigation, search
This Wiki is read-only for reference purposes to avoid broken links.

Java Beans-Generierung

Klasse org.compiere.util.GenerateModel

package org.adempiere.util

public class GenerateModel


  • Die Methode main() generiert Java Beans (X_xxxxxx.java-Dateien wie z.B. X_C_Invoice.java).
Die Methode main() liest die Tabelle AD_Table und generiert unter anderem für jede Spalte die getter- und setter-Methoden von AD-Tabellen.
  • Es ist möglich, einzelne Beans zu generieren.
Siehe weiter im Text.
  • Im Adempiere Sourcecode sind diese Dateien unter org.compiere.model auffindbar.
  • Die compilierte GenerateModel.class-Datei ist unter .../adempiere_trunk/base/build/org/compiere/util
  • Man kann mithilfe von Aufrufparametern das Ergebnis bestimmen:
    • man selektiert in Eclipse die Methode main(), und mit der rechten Maustaste selektiert man run.
    • Will man Paramenter eingeben, so muss man im run die Parameter eintragen:
Parameter 0 : Verzeichnis
Wohin die Dateien kopiert werden.
Default – wenn kein Parameter: "C:\\Adempiere\\adempiere-all\\extend\\src\\adempiere\\model\\"
Im Trunk sind sie in /Adempiere/adempiere_trunk/extend/src/compiere/model//
Parameter 1: Package, zu dem die Java-Bean gehören wird
Default – wenn kein Parameter: "compiere.model"
Parameter2: entity Type
(User-Defined, Adempiere, Dictionary, etc).
In der Datei sind nur User und Application vorgesehen. Will man andere wie Adempiere, so sollte man das einfügen. Man sollte aber bedenken, dass nur User-defined nicht mit jeder neuen Version gelöscht werden.
Default – wenn kein Parameter: "'U','A'"
Parameter 3: Tabelle
Aus welcher Tabelle die Java Bean generiert wird.
Default – wenn kein Parameter: % (alle Tabellen)
  • Man kann auch den Code so ändern, dass die Parameter die gewünschten Einstellungen haben.
  • Jedesmal, wenn die Tabellen des Application Dictionary sich geändert haben, muss man die X_-Dateien generieren, wenn man die Felder im Code ansprechen will.


Seit V. 3.3.0 ist /Adempiere/adempiere_trunk/base/src/org/adempiere/util/GenerateModelJPA.java dafür zuständig. Diese Klasse legt die Dateien ohne „X_“-Präfix an, z.B. „C_InvoiceLine.java“. Man muss sie vor dem Deployen umbenennen.

Java-Beans

Besonderheiten der Beans

  • Sind POJOs
  • Pro Businessobjekt gibt es ein Java Bean
  • Sie sind das Bindeglied zwischen dem Java-Code und den Daten der Datenbank.
Sie ersparen dem Entwickler, Daten direkt anzusprechen oder Informationen fest zu kodieren. Stattdessen werden Objekte manipuliert, indem die Beans instanziiert werden.
Beispiel: bp.getM_PriceList_ID() hier liefert das Objekt BusinessPartner die ID der Preisliste.
  • welchem Package sie angehören: In Adempiere sind im Package org.compiere.model die Java-Bean-Klassen und die Business-Logik-Klassen abgelegt.
  • Deklaration der Java Bean-Klasse.
Zum Beispiel:
public class X_C_Invoice extends PO
Gewisse Methoden werden in der Klasse PO (Persistence Object in org.compiere.model.PO) implementiert. So rufen zum Beispiel alle Konstruktoren der Java Bean-Klassen PO-Konstruktoren auf ( super (ctx, rs, trxName) bzw. super (ctx, C_Invoice_ID, trxName) ).
  • Die static final Property Table_ID liefert die ID der Tabelle.
Beispiel: public static final int Table_ID=MTable.getTable_ID(Table_Name);
Man braucht damit die Tabellen-ID nicht im Programm hart zu kodieren.
  • Die protected static Property Model
Beispiel: protected static KeyNamePair Model = new KeyNamePair(Table_ID, Table_Name);
  • Die static final Property accessLevel
Das sind die Werte, die man bei der Tabellendefinition festglegt hat (Client, Organisation, Client+Organisation, etc).
Beispiel: protected BigDecimal accessLevel = BigDecimal.ValueOf(1);
  • AD_ORGTRX_ID_AD_Reference_ID
Die ID im Dictionary.
  • Methoden
initPO
wird vom in der Afurufkette zuletzt aufgerufenen PO-Konstruktor ( PO (Properties ctx, int ID, String trxName, ResultSet rs) ) aufgerufen.
toString
liefert z.B. bei der Klasse C_Invoice: X_C_Invoice[318]
Wird bei u.a. bei compare() verwendet oder log.info(toString()
getter- und setter-Metoden aller Spalten der Tabelle.
Ausnahmen: die Spalten Processing, Processed, Created, CreatedBy, etc. die in der Methode load() der Klasse PO mit dem Aufruf loadDefaults()/SetStandardDefaults() angelegt werden.
getDocStatus()
setDocStatus()
  • Misc
    • Alle Basisklassen und Beans gehören zum gleichen Package. Dadurch ist das Kreiieren eines Objektes durch den Konstruktor-Aufruf möglich.
    • Datum wird als Timestamp behandelt
    • Boolsche Werte werden als Zeichenketten mit Länge 1 dargestellt (Y=True, N=False).
Bei der Abfrage eines booleans wird keine get- sondern eine is-Methode aufgerufen wie isApproved()
    • Es gibt weitere Beans, die anderen Packages zugehören aber zum Model- und Control-Teil der Architektur gehören, weil sie zum einen BO darstellen (man kann bei Adempiere eine Activity anlegen) aber auch Abläufe kontrollieren.
Sie werden im Laufe der Ausführungen zum Teil behandelt, z.B. MWFActivity, MWFNode, MWFProcess, MDocType, MWorkflow.
Es gibt Java Beans, die nicht Business Objekts sind:
public class X_AD_WF_Activity extends PO
Diese Klasse ist Hilfsklasse zu MWFActivity
public class MWFActivity extends X_AD_WF_Activity implements Runnable
  • Beans und PO zusammen realisieren die Persistenz:
    • Beans halten die Daten
    • PO liefert die Mechanismen zur Persistenz


Persistenz-Engine

org.compiere.model.PO

Realisiert die Klassenpersistenz bei Adempiere. PO hat Methoden zum DB-Transfer, ruft Triggers und Modellvalidatoren auf.

public abstract class PO implements Serializable, Comparator, Evaluatee

  • Serializable
Zum Herausstreamen von Objekten.
  • Comparator
Die Methoden compare() und equals() werden von PO implementiert.
  • Evaluatee
Implementiert die Methode get_ValueAsString(), die u.a. von org.compiere.util.Evaluator. evaluateLogicTuple() aufgerufen wird. Es werden die Tupel evaluiert, die im Dictionary angegben werden, wie @xxxx@.


Klassenhierarchie: eine Adempiere-Klasse wie MInvoice erbt von X_C_Invoice, das ihrerseits von PO erbt:

  • public class MInvoice extends X_C_Invoice implements DocAction
  • public class X_C_Invoice extends PO


Konstruktoren
Aufgerufen wird immer am Ende der Vater-Konstruktor
public PO (Properties ctx, int ID, String trxName, ResultSet rs) um

Eine neue Instanz anlegen: wenn man den Parameter ID mit dem Wert 0 belegt.
Gibt man als Transaktion null an, so wird eine neue Transaktion kreiert.
org.compiere.util.Trx kontrolliert die Transaktionen mit static Methoden:
Trx.createTrxName(): neue Transkaktion anlegen, Trx vergibt einen zufälligen Namen
Trx.createTrxName("Cost"): neue Transkaktion anlegen
Trx.get(trxName, True): Name der aktuellen Transaktion wird geholt
Trx.get(trxName, True): Eine neue Transaktion wird angelegt, wenn es die Transaktion trxName nicht gibt und der zweite Parameter True ist.
In Trx.get wird neu angelegt:
{
 :
retValue = new Trx (trxName)
}
Mit der Angabe des Transkationsnamen wird sichergestellt, dass alle DB-Speicherungen mit gleichem Namen als eine Transaktion behandelt werden mit Commit und Rollback.


Der Vater-Konstruktor ruft auf

{
 :
load (int ID, String trxName)
 :
}

Wenn man die ID und Transaktion kennt

  • ID > 0
Es werden per SQL die Spalten geholt und die Instanz mit Werten versehen.
  • ID <= 0
neues Objekt.
Private Klasse m_createNew bekommt den Wert True, so dass im folgenden das Objekt weiss, dass es sich um eine Neuanlage handelt.
Die Spalten Processing, Processed, Created, CreatedBy, etc. werden mit dem Aufruf loadDefaults() angelegt.

loadComplete (boolean success) kann bei Bedarf von den Unterklassen definiert werden. Es wird aber zurzeit nirgends verwendet.

load(ResultSet rs); Auch ein load(ResultSet rs) ruft am Ende load (int ID, String trxName) auf. Hier wird ein ResultSet zum Anlegen eines Objkts herangezogen. Es wird die aktuelle Position des ResultSets genommen; ausserdem wird nicht im ResultSet navigiert.

Methoden von PO

  • public final Object get_Value (String columnName)
Wird überprüft, ob die Spalte aktiv ist
wenn ja, get_Value (int index) aufrufen, das m_newValues[index]zurückliefert.
Siehe Properties.
  • set_ValueNoCheck (String ColumnName, Object Value)
Die “Spalte” bekommt ohne Check einen Wert.
Unter Umständen wird der Wert abgekürzt. Das ist der Grund dafür, dass man im Programm Werte eingibt und vom Program abgekürzt gespeichert serden: es wird auf die im AD festgelegten Länge abgekürzt.


Die nachfolgenden Methoden werden in der PO-Instanz ausgeführt und nicht in den Unterklassen.

  • delete()
Ein Objekt wird gelöscht.
Es werden u.a. Events validiert mit ModelValidationEngine.
  • delete_Tree()
ID-Bäume werden entfernt
  • save()
Die Speicherung der Unkterklassen wird hier vorgenommen.
Es werden einige Checks durchgeführt: neues Objekt, Organisation)
In save() werden weitere Methoden aufgerufen:
beforeSave(): Wird meist von den Business-Klassen wie MInvoice, MProduct usw. implementiert.
saveNew(): Speicherung eines neuen Objektes
saveUpdate(): Speicherung eines existierenden Objektes.


Änderungsverfolgung:
eine Session-Variable wird angelegtmit MSession session = MSession.get (p_ctx, false)


Bei Änderung wird in der Tabelle MChangeLog den Zustand des Objektes vor und nach dem Speichern festgehalten:
MChangeLog cLog = session.changeLog
Die nötigen Einstellungen muss man im AD vornehmen; dort es ist möglich eine Tabelle zum Log zu melden. Hat man das getan, kann man bei Adempiere am Fenster, wo die Tabelle dargestellt wird, unten rechts mit einem Doppelklick den Änderungslog sehen.
Folgendes ist sichtbar:
Tabellenname
wer/wann den Satz angelegt hat
wer/wann den Satz geändert hat
änderungslog
saveNew() und saveUpdate() rufen am Ende saveFinish() auf, das seinerseits afterSave() aufruft.
Die Methoden beforeSave() und afterSave() werden meist in den Business-Klassen wie MInvoice, MProduct usw. implementiert.

Properties von PO (Auswahl)

  • POInfo p_info (Spalteninfos: Table-ID, Table Name, Access Level, etc).
Wird im Vater-Konstruktor geholt mit p_info = initPO(ctx); die Unterklassen implementieren initPO(). Siehe Klasse POInfo.
Informationen über Spalten liefert p_info.


Informationen der Spalten: ein Aufruf get_Value("Spaltenname") bewirkt am Ende ein Aufruf get_Value (int index); dort wird m_newValues[index] zurückgeliefert (oder m_oldValues[index], wenn m_newValues==null)

protected transient CLogger log = CLogger.getCLogger (getClass()) Zur Anzeige der Unterklassen-Ausgaben der Konsole

  • private static Clogger s_log = CLogger.getCLogger (PO.class);
Zur Anzeige de PO-Ausgaben in der Konsole
  • private Doc m_doc
  • private Object[] m_IDs = new Object[] {I_ZERO} // Ids der sätze
  • private Object[] m_oldValues = null; // alte Werte
  • private Object[] m_newValues = null; // neue Werte
  • private Mattachment m_attachment = null;
  • etc.

Klasse POInfo

package org.compiere.model public class POInfo implements Serializable

Diese Klasse ist der Zugang zu POInfoColumn-Instanzen

Enthält Informationen über die Spalten des Businessobjektes ist serialisierbar (zum übertragen ausserhalb des Programms) Properties u.a. m_AD_Table_ID m_TableName m_AccessLevel POInfoColumn[] m_columns private Properties m_ctx = null Methoden u.a. Beziehen sich meist über den Index auf Properties von m_columns Der Konstruktor POInfo (Properties ctx, int AD_Table_ID, boolean baseLanguageOnly) ruft loadInfo() auf, das in einem SQL, das die Tabellen AD_Table t AD_Column c

AD_Val_Rule vr  und 

AD_Element e umfasst (Parameter des SQL:m_AD_Table_ID), pro Spalte eine Instanz von POInfoColumn anlegt und mit den Daten des SQL belegt t.TableName c.ColumnName c.AD_Reference_ID c.IsMandatory c.IsUpdateable c.DefaultValue e.Name,e.Description c.AD_Column_ID c.IsKey c.IsParent c.AD_Reference_Value_ID vr.Code c.FieldLength c.ValueMin c.ValueMax c.IsTranslated t.AccessLevel c.ColumnSQL c.IsEncrypted Die Infos aller Spalten werden nach m_columns gebracht. Bei Übersetzungen wird die Tabelle AD_Element_TRL eingesetzt und die Sprache selektiert public static POInfo getPOInfo (Properties ctx, int AD_Table_ID) ruft den Konstruktor auf. getPOInfo() wird ihrerseits jeweils in initPO() der Java Beans für die entsprechende Tabellen-ID aufgerufen.initPO() wird beim PO-Konstruktor aufgerufen. getColumnName (int index) isColumnMandatory (int index) String getColumnDescription (int index) getColumnCount() Class getColumnClass (int index)


Klasse POInfoColumn

package org.compiere.model
public class POInfoColumn implements Serializable

Enthält Informationen über eine Spalte des Businessobjektes Properties (alle public):

  • AD_Column_ID
  • ColumnName
  • ColumnSQL
  • DisplayType
  • ColumnClass
  • IsMandatory
  • DefaultLogic
  • IsUpdateable
  • ColumnLabel
  • ColumnDescription
  • AD_Reference_Value_ID
  • ValidationCode
  • FieldLength
  • ValueMin
  • ValueMax
  • ValueMin_BD
  • ValueMax_BD
  • Methoden
  • IsKey
  • IsParent
  • IsTranslated
  • isUpdateable
  • toString
  • IsEncrypted

Zusammenhang zwischen POInfoColumn, PO und Beans (X_-Klassen) POInfoColumn-Instanzen halten Information über die Properties der Businessobjekten (=Spalten) Beans halten die Daten PO kümmert sich um Speichern und Lesen

Business Objekt-Klassen

package org.compiere.model

Es gibt zwei Arten von Business-Objekten Workflow-Business-Objekten Sie werden innerhalb eines Workflows aufgerufen. Sie müssen deswegen den Anforderungen des WF genügen zusätzlich zu ihren BO-Pflichten. z.B: Minvoice, MOrder Klassendefinition public class MInvoice extends X_C_Invoice implements DocAction Mit DocAction wird diese Klasse dieMethoden implementieren, die zur Realisierung eines WF erforderlich sind. Diese Methoden werden von Buttons aufgerufen, die im Application Dictionary bei Table&Columns definiert werden können. Abhängig vom Status und nächster DocAction wird dann die entsprechende Methode aufgerufen.

Zum weiteren Verständnis sieheKapitel über WF.

Diese Methoden fragen Stati ab und setzen Actions. prepareIt() Es werden u.a. Events validiert mit ModelValidationEngine. Die Klasse ModelValidationEngine kann eingesetzt werden, um eigene Business-Logik zu realisieren. Hier werden Events aufgefangen und davon abhängig Dokumenten-Stati und -Typen festgelgt und Methoden aufgerufen. Eine eigene -Klasse definiert man in Client-Fenster, Feld Validierungsklasse. Ein Beispiel eine eigene Validierungsklasse findet man bei Libero. completeIt() Es werden u.a. Events validiert mit ModelValidationEngine approveIt() etc. Document Workflow-Klassen definieren die Property private String m_processMsg, die Meldungen unten links an den Fenstern anzeigen wie "PeriodClosed".

Methoden, deren Zusammespiel die eigentliche Business-Logik realisieren. Z.B. bei Minvoice validatePaySchedule() testAllocation() getOpenAmt() etc.

Stammdaten-Business-Objekten z.B: MProduct public class MProduct extends X_M_Product kein implement-Teil Methoden, deren Zusammespiel die eigentliche Business-Logik realisieren. Z.B. bei MProduct isProductStocked() isOneAssetPerUOM() getAttributeSet() etc.

Die Properties hängen vom Zweck der Klasse ab.

Alle BO implementieren Trigger-Methoden.

  • Logik, bevor die Daten gespeichert werden:
beforeSave()
  • Logik, nachdem die Daten gespeichert worden sind:
afterSave()
  • Logik, bevor die Daten gelöscht werden:
beforeDelete()

Logik, nachdem die Daten gelöscht worden sind:

afterDelete()

Business-Klassen implementieren unterschiedliche Konstruktoren entsprechend ihrem Zweck.

Beispiele:
Standard-Konstruktor
MInvoiceLine (Properties ctx, int C_InvoiceLine_ID, String trxName)
MInvoice to = new MInvoice (from.getCtx(), 0, null);

Konstruktor der MProdct-Klasse für den Produkt-Import. Properties eines instanziierten Produktes werden mit den Werten des Objektes impP belegt:

public MProduct (X_I_Product impP)

Man kann beispielsweise das Verhalten des Imports mit der Klasse ImportProduct und X_I_Product beeinflussen oder mit ValidateModel.


Business-Klassen implementieren des öfteren die static-Methode get(), die ein Array von Objekten dieser Klasse liefern. Besipiel: public static MProduct[] get(Properties ctx, String whereClause, String trxName) Zweck?

Business-Klassen implementieren eine Property zum Loggen von Ereignissen in der Konsole private static Clogger s_log = CLogger.getCLogger (Mproduct.class);

Business-Klassen implementieren einen Objektcache

private static CCache<Integer,MInvoice>	s_cache= new CCache<Integer,MInvoice>("C_Invoice", 20, 2);	// 2 minutes

Die Anzahl Objekte im Cache (hier 20) und die Verweildaür (hier 2 Minuten) sind je nach Objekt unterschiedlich. org.compiere.util.CacheMgt verwaltet den Cache über den Application Server.

Businesslogik Ich muss zuerst die Methode beschreiben, die alle anderen aufruft, oder wenigstens darauf verweist. Funktionsweise anhand von Minvoice.prepareIt() Model Validation (wenn es eine gibt) Siehe Kapitel über ModelValidation. Holt sich eine Instanz von DocumentType anhand von "C_DocTypeTarget_ID" Prüfung, ob Periode geöffnet ist anhand des DocBaseTyp Zeilenvalidierung überprüfung des Cashbooks Doctype checken oder setzen BOM expandieren Steürberechnung Landed Costs pro Zeile Zürst werden alle Kosten aufsummiert und dann den Zeilen verteilt Bei einer Zeile werden alle Kosten dem Produkt zugewiesen MinvoiceLine allocateLandedCosts() ruft auf MinvoiceLine getBase() Hier ist die Berechnung nach LANDEDCOSTDISTRIBUTION_Costs nicht implementiert. Validierung Docaction setzen

Interface DocAction

package org.compiere.process public interface DocAction

WF-Businessobjekte implementieren die in DocAction definierten Methoden, wie in

public class MInvoice extends X_C_Invoice implements DocAction. 

Ein BO, das DocAction implementiert, wird Document genannt. Ihre endgültige Fassung hängt vom Objekt ab.

  • Setzt die Property isApproved auf True:
approveIt()
  • Danach ist keine Action möglich:
closeIt()
  • Logik vor Ausführung:
invalidateIt()
prepareIt()
  • Ausführung der Businesslogik:
processIt()
  • Nach einem closeIt() kann das Dokument wieder aktiviert werden.

Es häng vom Objekt ab: ein Invoice kann nicht, ein Order schon wieder aktiviert werden.

reActivateIt()
  • Generiert in Buchhaltung Gegenbuchung. Dafür wird eine Kopie des Originals genommen.
rejectIt()
reverseAccrualIt()
reverseCorrectIt()
  • eine Methode lockIt() gibt es nicht
unlockIt() 
voidIt()

Definiert static final Properties (String-Konstanten) für die Actions und Stati

ACTION_Complete
ACTION_Close
STATUS_Drafted
STATUS_Completed

etc. Weitere Methoden weswegen habe ich sie von den anderen Methoden getrennt? Sind sie Hilfsmethodern?

getApprovalAmt()
getCtx()

ID vom Satz

  • Liefert einen der als static final Property definierten Stati:
getDocAction()
getDocStatus()
  • Name des Dokumententyps und Dokument-Nr.:
getDocumentInfo()
getDocumentNo()
  • Kontrolliert die Sequenz

Z.B. Verkaufs- Kaufs- Zahlungs-Nr.

getDoc_User_ID()
  • Liefert den Wert der Property m_processMsg
getProcessMsg ()

get_TrxName() save()

  • Setzt einen des als static final Property definierten Status
setDocStatus()

u.v.a.m.

Klasse MDocType

package org.compiere.model public class MDocType extends X_C_DocType

Repräsentiert einen Documententyp Wird von BOs mit Dokumententypen instanziiert (in Methoden wie prepare() bzw. getDocumentInfo() ), wie z.B. in

  • MCash
  • MInOut
  • MInventory
  • MInvoice
  • MJournal
  • MMovement
  • MOrder
  • MPayment
  • MPeriod
  • MRequisition

etc. Hat wenige Methoden, keine public Properties Methoden:

  • getOfDocBaseType() liefert das Basisdokument
  • static public MDocType get (Properties ctx, int C_DocType_ID) liefert eine Instanz von MDocType aus dem Cache oder instanziiert ein Objekt.
  • isOffer() Anhand von Properties von X_C_DocType wird überprüft, ob es sich beim Subtype des BO um ein Offer handelt. Auszug:

DOCSUBTYPESO_Proposal.equals(getDocSubTypeSO()) isProposal() und isQuotation() verhalten sich ähnlich wie isOffer().

Klasse X_C_DocType

Wohl auch von GenerateModel generiert.

package org.compiere.model public class X_C_DocType extends PO

Properties public static final int C_DOCTYPEINVOICE_ID_AD_Reference_ID=170;

ID_AD_Reference_ID

public static final String COLUMNNAME_AD_PrintFormat_ID = "AD_PrintFormat_ID" public static final String COLUMNNAME_C_DocType_ID = "C_DocType_ID"; public static final String COLUMNNAME_C_DocTypeInvoice_ID = "C_DocTypeInvoice_ID" public static final String COLUMNNAME_C_DocTypeShipment_ID = "C_DocTypeShipment_ID" public static final String COLUMNNAME_IsDefault = "IsDefault"

public static final String DOCSUBTYPESO_Proposal = "ON" public static final int DOCBASETYPE_AD_Reference_ID=183; /** AP Credit Memo = APC */ public static final String DOCBASETYPE_APCreditMemo = "APC";

    • AP Invoice = API */

public static final String DOCBASETYPE_APInvoice = "API"; /** AP Payment = APP */ usw. für Bank Statement (CMB), GL Document(GLD), Material Movement (MMM), GL Journal (GLJ) etc. Methoden viele Get-Methoden rufen PO-Methoden auf wie z.B. getDocSubTypeSO(), das get_Value("DocSubTypeSO") von PO aufruft getName(), das get_Value("Name") von PO aufruft ebenso die Set-Methoden

Documents Workflow

Klasse DocumentEngine

Package: org.compiere.process public class DocumentEngine implements DocAction

Fazit: Das Business Objekt m_document wird hier Dokument genannt, weil es Methoden von DocAction implementiert.

DocumentEngine ermittelt die nächste Action, fragt Anhand des BO-Status, ob die zu realisierende Action gültig ist und führt im positiven Falle die entsprechende Action des Businessobjektes aus. Das Objekt m_document geht bei der Ausführung der Action von einem Status in den anderen über, der bei DocumentEngine vorgenommen wird. Dadurch wird eine Zustandsmaschine modelliert mit Actions und Zuständen.

Interessant ist die Dualität der DocumentEngine: zum einem implementiert sie Methoden von DocAction d.h., sie führt prepareIt(), processIt(), completeIt() usw. aus

und

zum anderen wird sie instanziiert, damit ein BO durch den Aufruf einer der vom BO implementierten gleichnamigen Methode seinen Zustand ändert: processIt() von BO hat processIt() von DocumentEngine zur Folge, das den Aufruf einer Methode wie complete() vom BO zur Folge hat.

Properties DocAction m_document // ein auf DocaAction gecastetes Business Objekt DocumentEngine implementiert also das Interface DocAction und hat eine Property DocAction String m_status (Default: STATUS_Drafted, definiert in DocAction) String m_message String m_action getter- und setter-Methoden setDocStatus(String ignored) bewirkt nichts isDrafted() return STATUS_Drafted.equals(m_status); // STATUS_Drafted, definiert in DocAction isInvalid() return STATUS_Invalid.equals(m_status); isInProgress() return STATUS_InProgress.equals(m_status); isApproved()return STATUS_Approved.equals(m_status) etc... Methoden Die Methoden verarbeiten das Businessobjekt (Dokument) Konstruktor DocumentEngine (DocAction po, String docStatus) die Property m_document wird belegt. processIt() completeIt() isValidAction (String action) Prüft mit public String[] getActionOptions() ob die Aktion gültig aufgrund des aktuellen Status des BO ist. Beispiel: wenn der aktuelle Status=STATUS_Drafted, dann sind die möglichen Actions: {ACTION_Prepare, ACTION_Invalidate, ACTION_Complete, ACTION_Unlock, ACTION_Void} (Anm.:diese Actions werden in DocAction definiert). Danach bestimmt der aktuelle Status des BO die potentiellen Aktionen.


Der Aufruf processIt() ist derjenige, der den nächsten Aufruf einer Methode innerhalb eines BO veranlasst.

Typische Verwendung von DocumentEngine z.B. in MInvoice processIt(String processAction): DocumentEngine engine = new DocumentEngine (this, getDocStatus()); Eine Instanz von DocumentEngine wird für das aktuelle BO mit seinem aktuellen Status erstellt. return engine.processIt(processAction, getDocAction()); Wobei processAction die vom WF und getDocAction() die vom User gewünschte Action ist. DocumentEngine: processIt(processAction, docAction) // eigene Logik Es werden Validierungen vorgenommen: abhängig davon, ob die WF-Action gültig ist, und wenn nicht, ob die User-Action gültig ist, wird m_action belegt. Siehe isValidAction(). Dann wird processIt( m_action) aufgerufen. DocumentEngine: processIt(String action) // Implementierung v. DocAction Abhängig von m_Action wird die entsprechende (eigene) Methode aufgerufen, in der Zum einem wird der Status geändert. Zum anderen wird die gleichnamige Methode des BusinessObjekts aufgerufen: if (ACTION_Unlock.equals(m_action)) return unlockIt(); ruft m_document.approveIt() auf if (ACTION_Invalidate.equals(m_action)) return invalidateIt(); Setzt m_document.setDocStatus(STATUS_Invalid)

if (ACTION_Complete.equals(m_action) ....) completeIt(), das
m_document.completeIt() vom BO aufruft.

etc. Nach Ausführung dieser Methoden wird der neue Status des BO festgehalten Einige DocumentEngine-DocAction-Methoden wie completeIt() haben vor dem Aufruf der BO-Methode eine Überprüfung der Gültigkeit der Action: isValidAction (String action). eine Besonderheit stellt die Action ACTION_Complete, weil sie abhängig vom Satus nicht nur ein completeIt(), sondern auch u.U. ein m_document.save() und ein postIt() nach sich zieht.

Bei Verwendung der DocumentEngine wird also u.U. 2X überprüft, ob die Action korrekt ist.

Klasse MWFActivity (Workflow Activity)

Wenn hier von Document die Rede ist, mus man sich ein Businessobjekt vorstellen, das das Interface DocAction implementiert.

Package: org.compiere.wf public class MWFActivity extends X_AD_WF_Activity implements Runnable public class X_AD_WF_Activity extends PO

Implemetiert also kein DocAction-Interface

Definition public class MWFActivity extends X_AD_WF_Activity implements Runnable Erbt von X_AD_WF_Activity X_AD_WF_Activity wird von GenerateModel erzeugt und erbt von PO Properties von MWFActivity private m_po // Verweis auf BO, das diese Activity durchführt Gewonnen mit getPO (Trx trx) m_docStatus bekommt einen Wert im Laufe von run() oder performWork() bei DocActions: doc = (DocAction) m_po; // die Businessobjekt-Instanz wird genommen success = doc.processIt (m_node.getDocAction());// Aktion wird durchgeführt setTextMsg(doc.getSummary()); processMsg = doc.getProcessMsg(); m_docStatus = doc.getDocStatus(); private Trx m_trx private MWFNode m_node // Knoten, zu dem die Action gehört private StateEngine m_state = null; private MWFProcess m_process = null; private DocAction m_postImmediate = null;

Methoden Konstruktoren MWFActivity (MWFProcess process, int AD_WF_Node_ID) Hier werden Aufrufe der implementierten Methoden des Interfaces X_AD_WF_Activity getätigt. es gibt weitere wird in WorkflowProcessor für Server WFActivity ich glaube für Client VDocAction für Grids WebInfo und anderen Klassen aufgerufen

getPO (Trx trx) Holt sich einen Verweis auf Businessobjekt. Hier spielen MWFActivity, MTable und PO zusammen getAD_Table_ID() aus X_AD_WF_Activity liefert die ID des Objektes in der Tabelle AD_Table. Dafür ruft X_AD_WF_Activity die Methode get_Value ("AD_Table_ID") von PO auf, die den Wert ermittelt. Mit der ID holt sich die Tabelle des Businessobjektes getRecord_ID() holt sich ID des Businessobjektes. Dafür ruft X_AD_WF_Activity die Methode get_Value("Record_ID") von PO auf, die den Wert ermittelt. Dann wird der Verweis auf das PO geholt. run() Wird in FormFrame, startBatch() aufgerufen, das von VSetup, actionPerformed() aufgerufen wird, nach irgendwelchen Events in Grids, Editoren usw. Ruft performWork() auf

Victor sagte : “Wird aus MWFProcess.startNext() aufgerufen, nachdem ein Button gedruckt worden ist”. Siehe weiter unten.

performWork() Ruft auf success = doc.processIt (m_node.getDocAction()) getActiveInfo (Properties ctx, int AD_Table_ID, int Record_ID) Bereitet Inhalt und Actions von Combo Box vor. public void setWFState (String WFState) Siehe nachfolgendes Beispiel.

Beispiel MinOut.processIt()

Zusammenfassung: der vom Server aktivierten Workflow-Processor ermittelt alle zu bearbeitenden Activities; jede Activity wird nach Überprüfung ihrer Gültigkeit dem MWFProcess übergeben, der alle Activities eines Prozesses berücksichtigt und die nächstgültige zum Ausführen absetzt.Die nächsgültige Activity ruft letzendlich processIt() auf.

In DocumentEngine wurde bereits erläutert, was danach passiert ( processIt() vom BO ruft den Konstruktor von DocumentEngine auf und documentEgine.processIt() auf) etc.


Davor passiert folgende Aufrufkette: A) AdempiereServer: public void run() in einer Schleife wird nach einer gewissen Zeit doWork() aufgerufen. WorkflowProcessor: protected void doWork() ruft WorkflowProcessor.wakeup() auf WorkflowProcessor: private void wakeup() über ein SQL werden alle unterbrochenen , nicht bearbeiteten Activities von Workflow-Knoten mit schlafender Action ermittelt. Tabellen: AD_WORFLOW->AD_WF_NODE-> AD_WF_ACTIVITY Da ein Workflow von einem Prozess angestossen wird, kann man über die Activity zum Knoten, vom Knoten zum Workflow und vom Workflow zum Prozess schliessen. Für jede dieser Activities: activity.setWFState (String WFState) MWFActivity: public void setWFState (String WFState) es wird überprüft, ob der neuer Status WFState gültig ist. wenn ja, der Prozess für den Ctx wird geholt und checkActivities(String trxName) aufgerufen MWFProcess: public void.checkActivities(String trxName) Alle Activities des Prozesses, zu dem die aktuelle Activity gehört, werden abgescannt:

MWFProcess.getActivities(),  das SELECT * FROM AD_WF_Activity WHERE AD_WF_Process_ID=? beinhaltet.

die erste Activity nach einer als “completed” gekennzeichnet, wird an MWFProcess.startNext() übergeben. bei allen anderen Activities wird der Status gemanagt MWFProcess: private boolean.startNext() letzte Activity auf processed setzen und retten (save() aufrufen) nächste Activity wird geholt Abarbeitung der logischen Operatoren (AND / XOR) Überprüfung ob Activity die nächste in der Kette ist. Starten der Activity mit einem Thread new Thread(new WFActivity(....) ).start(); public synchronized void Thread( ).start(); Kommentar: “it calls the run() Method of this thread” MWFActivity: public void run() Ruft performWork() auf Parameter ist m_trx “Feedback to Process via setWFState -> checkActivities” MWFActivity: private boolean performWork() Aus der Property m_node die die Action geholt: String action = m_node.getAction() Mögliche Actions: Document Action, Report, Process, Email, Set variable, User Choice. Task, Sub Workflow, User Workbench, User Form und User Windows werden angeboten, sind aber nicht implementiert. Ist die auszuführende Action Action, dann ruft doc.processIt(DocAction des Knoten) für das DocAtion implementierendes BO auf. Parameter ist m_node.getDocAction().

Also: Action sind Report, Process, Document Action etc.; DocAtion ist prepare, complete usw.

u.U. wird ein “post immediate” vorbereitet.


( Weitere Aufrufe von setWFState (String WFState):

MWFActivity: public void run() ruft auf MWFActivity.setWFState (String WFState) nachher ruft performWork() auf habe ich nicht weiter verfolgt. oder MWFProcess: setWFState (String WFState) Setzt Prozessstatus und aktualisiert alle Actions ruft auf MWFActivity.setWFState (String WFState) scheint nur für Status=closed zu sein habe ich nicht mehr verfolgt


B) InOutGenerate.completeShipment()

)

Klasse MWorkflow

Package: org.compiere.wf public class MWorkflow extends X_AD_Workflow public class X_AD_Workflow extends PO

Siehe Ausführungen in Klasse ProcessModalDialog: MWorkflow wird instanziiert.

Properties m_nodes: Array von MWFNode-Elementen private static Ccache<String,MWorkflow[]> s_cacheDocValue


Methoden loadNodes() abhängig von Workflow-ID werden alle Knoten der Tabelle AD_WF_Node in m_nodes eingelesen. public MWFNode[] getNodes() Die in m_nodes gespeicherten Knoten werden als MWFNode[] zurückgeliefert public MWFNode[] getNextNodes (int AD_WF_Node_ID, int AD_Client_ID) Ermittelt die aus zuführenden Knoten public MWFProcess start (ProcessInfo pi) Startet ein Workflow eine Instanz von MWFProcess wird erstellt: retValue = new MWFProcess (this, pi) wird gespeichert startWork() von dieser Instanz wird ausgeführt. Hier wird validiert ob gestartet werden kann, das Workflow mit seinem ersten Knoten, dessen Activity ermittelt und sie ausgeführt.

Siehe Zusammenhang in Beschreibung des Aufrufs von actionButton() in Klasse ProcessModalDialog. public int getPrevious (int AD_WF_Node_ID, int AD_Client_ID) Hole vorhergehenden Knoten in m_nodes. aftterSave() u.a. alle Knoten gespeichert. einige get-Methoden

Klasse ModelValidationEngine

Package org.compiere.model public class ModelValidationEngine

wird meist in Aufrufen innerhalb von Business-Klassen ( in den Methoden prepareIt(), completIt(), closeIt() usw.) wie folgt verwendet ModelValidationEngine.get().fireDocValidate(this,ModelValidator.TIMING_BEFORE_CLOSE); Die Methode get() liefert (u.U. legt an) eine Instanz von ModelValidationEngine Das Ergebnis des get(). fireDocValidate (ein String) wird der Property m_processMsg der Business-Klasse zugewiesen. hat eine Property s_engine, die eine Instanz ihrer eigenen Klasse enthält (!) Anscheinend gibt es bei Adempiere nur eine Instanz von ModelValidationEngine Im Konstruktor ModelValidationEngine wird pro Client ein ModelValidator instanziiert: ModelValidator validator = (ModelValidator)class.newInstance(); Auf dem ersten Blick hat man ein Problem, denn es wird ein Interface instanziiert, und Interfaces implementiert, nicht instanziiert. Die Lösung: im Client-Fenster kann man kann man eine Client Validator Class angeben, mit der man eine Client-Validierung einbindet. Wenn es sie nicht gibt, spielt der Validator keine Rolle. Beispiel einer Validator-Klasse: compiere.model. ModelValidator, unter extend. public class MyValidator implements ModelValidator. Im weiteren Verlauf wird initialize() vom Validator aufgerufen. fireDocValidate() Die Methode docValidate() fur jeden Validator wird aufgerufen.


interface ModelValidator Package org.compiere.model public interface ModelValidator

ModelValidator ist ein Interface, das vom Entwickler implementiert werden kann.

Wenn die Klasse bei Adempiere regstriert worden ist, kann bei jeder Änderung eines Records oder eines Beleges eine bestimmte Methode aufgerufen werden. Innerhalb dieser Methode kann der Enwickler eigene Actions programmieren, wie bspw. Post auf ein weiteres Konto oder die Post-Logik ändern.

Wenn man Buchungsregeln im Code ändert, muss man RUN_setup nochmals ausführen, damit der Application Server die Änderungen mitbekommt.

ModelValidator wird eingesetzt, um Logik ausserhalb vom Adempiere-Core zu programmieren.

Beispiel eines ModelValidators (von Carlos Ruiz): http://adempiere.svn.sourceforge.net/viewvc/adempiere/trunk/extend/src/compiere/model/MyValidator.java?view=markup

wird innerhalb des Konstruktors ModelValidationEngine verwendet {... Class clazz = Class.forName(className); ModelValidator validator = (ModelValidator)clazz.newInstance(); initialize(validator, clients[i]); ...} definiert Konstanten wie public static final int TYPE_BEFORE_NEW = 1 Sie werden als Parameter von Aufrufen fireDocValidate verwendet deklariert Methoden wie public String docValidate (PO po, int timing) public void initialize (ModelValidationEngine engine, MClient client) public String modelChange (PO po, int type) throws Exception verwendet um eigene Validierung zu implementieren. Beispiel iner Validator-Klasse: compiere.model. ModelValidator, unter extend.


Klasse VDocAction

package org.compiere.grid.ed class VDocAction extends Cdialog implements ActionListener

Zeigt gültige Optionen der Document Actions abhängig vom Kontext. Wenn man beispielsweise am Fenster Order den Button Complete betätigt, erscheint eine Form VDocAction, in der man die DocAction aussuchen kann. Mit OK wird die selektierte DocAction des aktuellen BO aufgerufen, was die Ausführung von Businesslogik und den Statuswechsel des BO zur Folge hat.

Kontrolliert das Prozessfenster Properties GridTab m_mTab m_AD_Table_ID Wird im Konstruktor initiallisiert aus Env.getContextAsInt() Wird in dynInit() als Parameter erwendet, an der Stelle DocumentEngine.gertValidActions(). private boolean m_OKpressed = false; private boolean m_batch = false; Grafische Elemente wie Panels, Combo Box, Scroll Pane, Text Area, etc. KonstruktorBaut ein Dialogfenster auf mit Panel BorderLayout ComboBox TextArea Jbutton etc. Methoden Konstruktor VDocAction (int WindowNo, GridTab mTab, VButton button, int Record_ID) Ruft jbInit() und dynInit(Record_ID)auf.. jbInit() Wird vom Konstruktor aufgerufen. Initialisiert das Fenster. Die Property ActionLabel erhält den korrekten Wert. dynInit() Wird vom Konstruktor aufgerufen. Bestmmit die gültigen Actions aufgrund der Stati des Businessobjekts. (Dokumente) Der Workflow-Status wird ermittelt: wfStatus=MWFActivity.getActiveInfo()

Ruft DocumentEngine.gertValidActions() auf, was folgendes realisiert Zürst werden abhängig vom DocStatus die Actions ermittelt z.B. aus status_NotApproved --> Action_Prepare und Action_Void Dann, abhängig von der Tabelle und dem Status werden weitere Actions hinzuaddiert. Es handelt sich um folgende Tabellen: Order, MinOut (Shipment), Invoice, Payment, GL-Journal, Allocation, Bank Statement, Inventory Movement, Physical Inventory. Zuletzt wird die Combo Box mit den Aktionskürzeln augebaut: CO, CL, DR, etc. actionPerformed() macht ein paar Abfragen save() Bewirkt durch viele Umwege, dass die folgende Anweisung m_mTab.setValue("DocAction", s_Value[index]) eine Speicherung in die DB veranlasst.

Klasse APanel

package org.compiere.apps

public final class APanel extends CPanel implements DataStatusListener, ChangeListener, ActionListener, ASyncProcess

actionPerformed() Kommandoverteiler je nach getätigter Ikone im Fenster (Speichern, Drucken, Attachment, Vor, Zurück, usw) Die Aktionen werden in privaten Methoden durchgeführt. Manche mit ProcessCtl: process(), oder mit m_curTab.dataSave(manualCmd), wobei m_curTab das Grid ist. actionButton() legt eine Instanz von VdocAction an ruft auf ProcessModalDialog dialog = new ProcessModalDialog() Zeigt zuletzt das Fenster an mit aenv.showCenterWindow(Env.getWindow(m_curWindowNo), dialog)


Klasse ProcessModalDialog

package org.compiere.apps public class ProcessModalDialog extends Cdialog implements ActionListener


Zusammenfassung: ProcessCtl behandelt die unterschiedlichen Ausprägungen von Prozessen und bewirkt ggf. die Instanziierung eines WF; das WF instanziiert ein MWFProcess, der das WF, den Knoten und seine Activity ermittelt; danach wird die Activity und diese mit processIt() ausgeführt . Warum es so kompliziert ist, entzieht sich meiner Kenntnis.

Hier behandelt wegen des Aufrufs von actionButton().

actionPerformed(actionEvent e) actionPerformed(actionEvent e) wird durch Events von grafischen Elementen ausgelöst. Ruft ProcessCtl: process() auf ProcessCtl: process(m_ASyncProcess, m_WindowNo, parameterPanel, m_pi, null) ProcessCtl:public static-Methode process() für synchrone oder asynchrone Prozesse m_pi ist der Klasse ProcessInfo Eine Instanz von MPInstanz wird geholt abhängig von pi- AD_Process_ID und pi- Record_ID. In der statischen Methode wird ein ProcessCtl instanziiert: ProcessCtl worker = new ProcessCtl(parent, WindowNo, pi, trx); Bei asynchronen Prozessen: worker.start() -> startet einen neuen Thread start() ruft new Thread(this).start() auf. letzten Endes wird ProcessCtl..run() aufgerufen. Bei synchronen Prozessen: worker.run() -> startet WF, “etwas” komplizierter.

ProcessCtl:public boolean run() Mit einem komplexen SQL wird die Prozess-Info geholt: 11 Spalten der Tabellen AD_Process-AD_PInstance: Spalte1: Prozessname Spalte2: Prozedurname Spalte3: Classname Spalte4: AD_Process_ID Spalte5: isReport Spalte6: isDirectPrint Spalte7: AD_ReportView_ID Spalte8: AD_Workflow_ID Spalte9: statistischer case-Wert Spalte10: isServerProcess Spalte11: jasperReport Hier werden Prozeduren, Workflows, Jasper Reports, Reports und Prozesse behandelt. Bei Prozessen wird z.B. pi.setPrintPreview(!IsDirectPrint) aufgerufen, was in ReportCtl.start() landet..Hier werden vorgefertigte Prozesse für Order, Invoice, Shipment, Project, Payment und Dunnig abgefragt und evtl. gestartet; wenn der Report keinem dieser Möglichkeiten entspricht, wird der (normale) Report ausgeführt. Handelt es sich bei der Activity des Prozesses um einen Workflow, wird aufgerufen: ProcessCtl: startWorkflow (AD_Workflow_ID); Für andere Alternativen, siehe ProcessCtl. ProcessCtl: private boolean startWorkflow(int AD_Workflow_ID) Bei Remote-Prozessen: Die Server-Verbindung wird geholt Sonst Aufruf wfProcess = ProcessUtil.startWorkFlow(Env.getCtx(), m_pi, AD_Workflow_ID); ProcessUtil: public static MWFProcess startWorkFlow(Properties ctx, ProcessInfo pi, int AD_Workflow_ID) Ein Workflow wird instanziiert wf=MWorkflow.get(ctx, AD_Workflow_ID) und gestartet (im Batch) wf.start(pi) oder sonst verzögert wf.startWait(pi) MWorkflow: public MWFProcess start(ProcessInfo pi) Aus ProcessInfo wird eine MWFProzess-Instanz gebildet Diese Instanz ruft save() und startWork() auf. MWFProcess: public boolean startWork() Das Workflow wird geholt; aus dem Workflow wird AD_WF_Node_ID ermittelt getWorkflow().getAD_WF_Node_ID() eine Instanz von MWFActivity wird gebildet: new MWFActivity (this, AD_WF_Node_ID) In diesem Konstruktor wird aus AD_WF_Node_ID einen Knoten instanziiert. Die Activity wird als Thread gestartet: new Thread(activity).start() siehe MWFActivity, wie hier weiter geht: es endet in processIt() des entsprechenden BO.


MWFProcess: private MWorkflow getWorkflow() Aufruf von MWorkflow.get (getCtx(), getAD_Workflow_ID()) MWorkflow: public static MWorkflow get (Properties ctx, int AD_Workflow_ID) ein Workflow wird instanziiert new MWorkflow (ctx, AD_Workflow_ID, null) MWorkflow: Konstruktor public MWorkflow (Properties ctx, int AD_Workflow_ID, String trxName) siehe Beschreibung der Klasse Properties werden gesetzt Knoten werden geladen loadNodes()


Invoice-Preview Invoice-Report ist festverdrahtet. Man kann nicht einen anderen Print Format oder Prozess dafür bestimmen (es ist KEIN Prozess dafür im Dictionary vorgesehen). Gleiches gilt für Order, Shipment, Project, Response, Payment und Dunning. Man kann höchstens die View ändern (Spalte hinzufügen oder entfernen), aber nicht eine andere View oder einen anderen Print Format bestimmen.

Werte in Tabellen des Invoice-Preview AD_TABLE_ID=318 ( C_INVOICE) AD_PROCESS_ID: 116 (Rpt C_Invoice) AD_PRINTFORMAT_ID: 1000071 (Standard Invoice Header)

Aufrufsequenz zur Anzeige des Invoice-Preview APanel.actionPerformed()

   APanel.cmd_print() 
       ProcessCtl.process() 
           ProcessCtl.start() 
              Thread.start() 
                  :
                  :
                      ProcessCtl.run() 
                          ReportCtl.start() 
                              ReportCtl.startDocumentPrint() 
                                  :
                                  :
                                     ReportEngine.get() 
                                     (hier erst wird seltsamerweise eine ReportEngine-Instanz angelegt)
                                         ReportCtl.startDocumentPrint() 
                                         (hier wird durch den Aufruf CreateOutput()  der Bericht angezeigt)

Klasse MWFProcess

package org.compiere.wf public class MWFProcess extends X_AD_WF_Process public class X_AD_WF_Process extends PO // Diese Klasse hat also Persistenz-Funktionalität.

Properties private StateEngine m_state = null; private MWFActivity[]m_activities = null; private Mworkflow m_wf = null; private ProcessInfo m_pi = null; private PO m_po = null; private String m_processMsg

Methoden checkActivities() Die nächste Activity nach der ersten completed wird gestartet: startNext (activity, activities) public boolean startWork() Hier wird validiert ob gestartet werden kann, das Workflow mit seinem ersten Knoten, dessen Activity ermittelt und sie ausgeführt. public void setAD_WF_Responsible_ID () ID des WF-Verantwortlichen wird gesetzt



Klasse WorkflowProcessor

package org.compiere.server public class WorkflowProcessor extends AdempiereServer

Properties private MworkflowProcessor m_model = null; // Das Model private StringBuffer m_summary = new StringBuffer(); // Last Summary private Mclient m_client = null; // Client-Info

Methoden Konstruktor public WorkflowProcessor (MWorkflowProcessor model) m_model gesetzt; m_Client anhand des Models gesetzt doWork() von AdempiereServer aufgerufen Ruft wakeUp() auf.

wakeUp()

über ein SQL werden alle unterbrochenen , nicht bearbeiteten Activities von Workflow-Knoten mit schlafender Action instanziiert. Für jede dieser Activities: activity.setWFState (String WFState) private int sendAlertToResponsible (MWFResponsible responsible, ArrayList<Integer> list, MWFProcess process, String subject, String message, File pdf) private int sendEmail (MWFActivity activity, String AD_Message, boolean toProcess, boolean toSupervisor)



Klasse MWFNode

org.compiere.wf public class MWFNode extends X_AD_WF_Node public class X_AD_WF_Node extends PO // Diese Klasse hat also Persistenz-Funktionalität.

Properties private ArrayList<MWFNodeNext> m_next // nächste Knoten private MColumn m_column = null; // Spaltenbeschreibung private MWFNodePara[] m_paras = null; // Prozessparameter

Methoden public String getActionInfo() von toString() aufgerufen Action wird geholt (geht bis PO) Wenn die Action ein Application Process ist, das zu Infozwecken aufgerufen wird. return "Process:AD_Process_ID=" + getAD_Process_ID() ( Mögliche Actions (public static final Strings, definiert in X_AD_WF_Node) ACTION_UserWorkbench = "B" ACTION_UserChoice = "C"; ACTION_DocumentAction = "D"; ACTION_SubWorkflow = "F"; ACTION_EMail = "M"; ACTION_AppsProcess = "P"; ACTION_AppsReport = "R"; ACTION_AppsTask = "T"; ACTION_SetVariable = "V"; ACTION_UserWindow = "W"; ACTION_UserForm = "X"; ACTION_WaitSleep = "Z"; ) Diese Werte müssen mit den Werten der Combobox für Actions im Knoten eines WF übereinstimmen. public MWFNodePara[] getParameters() Knotenparameter geholt: m_paras = MWFNodePara.getParameters(getCtx(), getAD_WF_Node_ID()) getAD_Workflow_ID() holt die WF-ID über X_AD_WF_Node bis PO public MWorkflow getWorkflow() Mit MWorkflow.get(getCtx(), getAD_Workflow_ID()) public boolean isUserApproval() Ist die Action=ACTION_UserChoice? ACTION_UserChoice.equals(getAction()) Es wird abgefragt, ob angenommen worden ist: "IsApproved".equals(getColumn().getColumnName())


Klasse ProcessCtl

package org.compiere.apps public class ProcessCtl implements Runnable

Managt Report&Process.Siehe dessen Beschreibung.

Properties ASyncProcess m_parent; ProcessInfo m_pi; // Properties und Methoden zum Prozess (Transaction-Name, Table-ID, AD_Process_ID, AD_Workflow_ID, Parameter, Record-Id, etc) private Trx m_trx; private Waiting m_waiting; private boolean m_IsServerProcess = false;

Methoden run() Mit einem SQL werden 11 Spalten von AD_Process gelesen und m_pi befüllt: Spalte1: Prozessname Spalte2: Prozedurname Spalte3: Classname Spalte4: AD_Process_ID Spalte5: isReport Spalte6: isDirectPrint Spalte7: AD_ReportView_ID Spalte8: AD_Workflow_ID Spalte9: statistischer case-Wert Spalte10: isServerProcess Spalte11: jasperReport

Abhängig von den erhaltenen m_pi-Werten wird unterschiedliches ausgeführt: ist z.B. AD_Workflow_ID>0, so wird ein Workflow gestartet: startWorkflow (AD_Workflow_ID) , eine private Methode, die das WF startet: ProcessUtil.startWorkFlow(...).

Alternativ werden auch ausgeführt:

Java-Klassen

Ausgehend von der Methode run() von ProcessCtl, die startProcess() aufruft, werden am Ende die Methoden prepare() und doit() der Klasse ImportInventory aufgerufen, die das Verhalten realisieren. In der Beschreibung von Reports im AD wird der Zusammenhang genau erläutert. Jasper Reports startProcess() normale Reports ReportCtl.start() Oracle-DB-Procedures startDBProcess() Konstruktor public static ProcessCtl process(ASyncProcess parent, int WindowNo, IProcessParameter parameter, ProcessInfo pi, Trx trx) Eine Instanz von MPInstance wird geholt: MPInstance instance (mithilfe von pi) Parameter werden geholt (save() ) Bei synchronen Prozessen wird Prozess sofort ausgeführt mit run()


Invoice Preview

Invoice-Preview Wird hier erläutert, weil es etwas mit ProcessCtl zu tun hat..

Man kann Printformats in Business Partner, Document Types und ad_printform definieren..

Werte in Tabellen eines Invoice-Previews AD_TABLE_ID=318 ( C_INVOICE) AD_PROCESS_ID: 116 (Rpt C_Invoice) AD_PRINTFORMAT_ID: 1000071 (Standard Invoice Header). Deren Spalte ad_table_id verweist auf eine Tabelle mit Namen C_Invoice_Header_v, die eine Invoice Print View darstellt.

Aufrufsequenz zur Anzeige des Invoice-Preview APanel.actionPerformed()

   APanel.cmd_print() 
       ProcessCtl.process() : AD_TABLE_ID=318 ( C_INVOICE), AD_PROCESS_ID: 116 
           ProcessCtl.start() 
              Thread.start() 
                  :
                  :

Der Prozess wird ausgeführt: ProcessCtl.run:

 ReportCtl.start() 
   ReportCtl.startDocumentPrint()
     ReportEngine.get() (Klassenmethode)
       ReportEngine.<init>(Konstruktor)
         ReportEngine.getQuery()
           ReportEngine.setPrintData()
             ReportEngine.getPrintData() 
               DataEngine.getPrintDataInfo()
             
             zurück in getPrintData(), wird loadPrintData() aufgerufen, wo der SQL ausgeführt wird.
     zurück in ReportEngine.get() wird ReportEngine der aufrufenden Methode startDocumentPrint()     	zurückgeliefert
   zurück in startDocumentPrint()
     CreateOutput()
       Der Bericht wird angezeigt, unter Berücksichtigung, ob es ein Direkter Druck oder Preview ist.
       Ist das der Fall, wird mit folgendem Code der Viewer angezeigt:
         ReportViewerProvider provider = getReportViewerProvider();
         provider.openViewer(re)


Nun dasselbe detaillierter (zum Teil aus der Console und eigenen Anmerkungen):

HIER WURDE DER BUTTON PRINT PREVIEW GEDRÜCKT

Apanel.actionPerformed(): PrintPreview - 16

Apanel.cmd_print() ID=116 [12] AD_TABLE_ID=318 ( C_INVOICE) AD_PROCESS_ID= 116 (Rpt C_Invoice) ProcessInfo pi: pi.m_Title=Invoice (Customer) SuperUser@Ovidio J. Vides SA de C.V..Droguería pi.m_AD_Process_ID=116 pi.m_Record_ID=1004868

ProcessCtl.process() WindowNo=2 - ProcessInfo[Invoice (Customer) SuperUser@Ovidio J. Vides SA de C.V..Droguería [linux-jupiter{linux-jupiter-orcl-adempiere}], Process_ID=116, Record_ID=1004772, Error=false,Summary=,Log=0] [12] (Record_id ist die id der invoice) (Process_ID=116 ist Rpt C_Invoice)


ReportCtl.start: start() ProcessInfo[Invoice Print ,Process_ID=116,AD_PInstance_ID=1004057,Record_ID=1004772, Error=false, Summary=, Log=0] [42] asynchroner Prozess AD_PInstance_ID wechselt immer in AD_PInstance werden die Prozess- (hier 116) und die Record-ID (hier 1004868) festgehalten, damit bei der Wiederaufnahme diese Werte nicht verloren gehen.


Thread wird ausgeführt

ProcessCtl.run() mit einem SQL auf AD_PInstance werden die Einstellungen von AD_Process geholt: Name, ProcedureName,ClassName, AD_Process_ID, isReport (=Y), isDirectPint (=Y), ReportView-ID (=keine), Workflow-ID (=keine), isServerProcess(=N) und JasperReport-Name (keiner). Diese Werte werden der Klassenvariable m_pi zugewiesen. Ich glaube, Client- und User-ID haben in m_pi schon die richtigen Werte.

Anschliessend wird abgefragt, worum es sich handelt (Workflow, Report, Prozess). Da Invoicedruck ein Report ist, wird die nächste Methode aufgerufen:

ReportCtl.start() Festkodierte Abhängigkeit: abhängig von der process_id, wird eine Methode aufgerufen. Bei Invoice (Process_ID()= 116) wird aufgerufen:


ReportCtl.startDocumentPrint() ruft sofort ReportEngine.get() auf, wo abhängig vom Type (Check, Dunning, Remmitance, Project, RfQ, Order/Invoice/Shipment) ein SQL ausgeführt wird. Da seit ReportCtl.start() der Typ Invoice (=2) durchgereicht wurde, wird hier der SQL für einen Invoice ausgeführt.

Es gibt drei Stellen, wo man ein Print Format bei Adempiere festlegen kann: Im Business Partner, bei dem Register "Client" kann man einen Print Format angeben.

Die Tabelle c_doctype hat ausser dem Doc Base Type einen Print Format.

AD_PRINTFORM ist eine Tabelle, die Pro Client_id und Organisation vordefinierte Print Formats hat (für Invoice, Order, Shipment), ebenso wie Mailtexte (für Invoice, Order, Shipment, Projekte...). So ist in dieser Tabelle für den Kunden "Vides", die Invoice-Print Format-ID=1000071 deklariert. Diese ID ist in der Tabelle AD_PRINT_FORMAT "Invoice_Header"; deren Spalte ad_table_id verweist auf eine Tabelle mit Namen C_Invoice_Header_v, die eine Invoice Print View darstellt.

Wenn also weder Business Partner noch Document Type das Print Format deklariert hat, so greift ad_printform.

Der SQL ist ein Join vom c_invoice, ad_printform, ad_client, c_doctype, c_bpartner, wo das Print Format von Order, Shipment, Projekt, Remmitance und Invoice ermittelt wird. Bei Invoice: Prio: 1. BPartner 2. DocType, 3. PrintFormat (ad_printform). Ausserdem Anzahl Kopien (entweder aus Business Partner oder Document Type), Beleg-Nr. vom Invoice und BP-ID:

SELECT pf.Order_PrintFormat_ID,pf.Shipment_PrintFormat_ID, COALESCE (bp.Invoice_PrintFormat_ID,dt.AD_PrintFormat_ID,pf.Invoice_PrintFormat_ID), pf.Project_PrintFormat_ID, pf.Remittance_PrintFormat_ID, c.IsMultiLingualDocument, bp.AD_Language, COALESCE(dt.DocumentCopies,0)+COALESCE(bp.DocumentCopies,1), dt.AD_PrintFormat_ID,bp.C_BPartner_ID,d.DocumentNo FROM C_Invoice d INNER JOIN AD_Client c ON (d.AD_Client_ID=c.AD_Client_ID) INNER JOIN AD_PrintForm pf ON (c.AD_Client_ID=pf.AD_Client_ID) INNER JOIN C_BPartner bp ON (d.C_BPartner_ID=bp.C_BPartner_ID) LEFT OUTER JOIN C_DocType dt ON (d.C_DocType_ID=dt.C_DocType_ID) WHERE d.C_Invoice_ID=? AND pf.AD_Org_ID IN (0,d.AD_Org_ID) ORDER BY pf.AD_Org_ID DESC

Aus dem SQL wird die ad_printformat_id, c_bpartner_id, beleg-Nr und Anzahl Kopien gewonnen und in Variablen gespeichert.

Eine Instanz von MPrintFormat und PrintInfo werden erstellt. Die lokale Variable query wird dem Konstruktor übergeben. Sie kann z.B. sein "C_Invoice_Header_v.C_Invoice_ID=1004868" und entsteht direkt aus dem Invoicetyp (Ergebnis: C_Invoice_Header_v) und dem Dokumententyp + -ID (Ergebnis: C_Invoice_ID=1004868).

Zuletzt wird in ReportEngine.get() eine ReportEngine instanziiert, wo mit den gewonnenen Instanzen der Klassen MPrintFormat MQuery und PrintInfo Klassenvariablen belegt werden. Die instanziierten ReportEngine wird der aufrufenden Methode zurückgeliefert wird. Siehe, was passiert, wenn der Konstruktor von ReportEngine aufgerufen wird:

ReportEngine.<init>(Konstruktor): MPrintFormat[ID=1000071,Name=Invoice_Header,Language=Language=[Español (El Salvador), Locale=es_SV, AD_Language=es_SV, DatePattern=DD/MM/YYYY, DecimalPoint=true],Ite ms=56] -- C_Invoice_Header_v.C_Invoice_ID=1004772 [42]

ReportEngine.getQuery()

ReportEngine.setPrintData(). Instanziiert eine DataEngine und ruft getPrintData() auf.

ReportEngine.getPrintData() Da es sich nicht um einen Reportview handelt, wird erstmal der Name der View ermittelt (SELECT TableName FROM AD_Table WHERE AD_Table_ID=516;): C_Invoice_Header_v. Hier wird der Tabellenname auf C_Invoice_Header_vt geändert. Query wird auch geändert: C_Invoice_Header_vt.C_Invoice_ID=1004868 Mit diesen Parametern wird getPrintDataInfo() aufgerufen (eine riesige Methode).

DataEngine.getPrintDataInfo() Die Wichtigen Daten werden ausgegeben: Name des Reports: Invoice_Header Sprache: es_SV TableName=C_Invoice_Header_vt Query=C_Invoice_Header_vt.C_Invoice_ID=1004772 AND C_Invoice_Header_vt.AD_Language='es_SV' Format=MPrintFormat[ID=1000071,Name=Invoice_Header,Language=Language=[Español (El Salvador), Locale=es_SV, AD_Language=es_SV, DatePattern=DD/MM/YYYY, DecimalPoint=true], Items=56]

Spaltenreihenfolge AD_Column_ID=7483

Ein SQL wird gebildet, mit ad_printformat_id als Parameter. Das Ergebnis ist teilweise wie folgt: AD_COLUMN_ID COLUMNNAME AD_REFERENCE_ID und weitere Spalten 7466 C_Order_ID 30 wie Länge und Mandatory 7463 DateInvoiced 15 7475 Name 10 7448 C_Location_ID 21 7586 PaymentTerm 10 7460 M_PriceList_ID 19 7483 DocumentNo 10

Die Daten des SQL dienen zur Printeinstellungen jeder Spalte (ob nach dieser Spalte geordnet wird, Page break, Summe, etc. ist). Das werden die Spalten des Print Formats sein.

Der SQL lautet: SELECT (SELECT C_Order.DocumentNo||' - '||TRIM( TO_CHAR( C_Order.DateOrdered, 'DD/MM/YYYY')) FROM C_Order WHERE C_Invoice_Header_vt.C_Order_ID=C_Order.C_Order_ID) AS AC_Order_ID, // Das war die erste Spalte, genannt AC_Order_ID C_Invoice_Header_vt.C_Order_ID, C_Invoice_Header_vt.DateInvoiced, C_Invoice_Header_vt.Name,B.City||'.' AS BAddress, C_Invoice_Header_vt.C_Location_ID, C_Invoice_Header_vt.PaymentTerm, (SELECT M_PriceList.Name FROM M_PriceList WHERE C_Invoice_Header_vt.M_PriceList_ID=M_PriceList.M_PriceList_ID) AS CM_PriceList_ID, // Das war eine weitere Spalte, genannt CM_PriceList_ID C_Invoice_Header_vt.M_PriceList_ID FROM C_Invoice_Header_vt LEFT OUTER JOIN C_Location B ON (C_Invoice_Header_vt.C_Location_ID=B.C_Location_ID) WHERE C_Invoice_Header_vt.C_Invoice_ID=1006390 AND C_Invoice_Header_vt.AD_Language='es_SV' AND C_Invoice_Header_vt.AD_Client_ID IN (0,1000001) AND B.C_Location_ID NOT IN ( SELECT Record_ID FROM AD_Private_Access WHERE AD_Table_ID = 162 AND AD_User_ID <> 100 AND IsActive = 'Y' ) ORDER BY C_Invoice_Header_vt.DocumentNo

Eine PrintData-Instanz mit Spalten, SQL und Tabelle (C_Invoice_Header_vt) wird erstellt und zurückgeliefert. Ende von getPrintDataInfo().

zurück in getPrintData(), wird loadPrintData(), wo der SQL ausgeführt wird.

zurück in ReportEngine.get() wird ReportEngine der aufrufenden Methode startDocumentPrint() zurückgeliefert

startDocumentPrint(): Aufruf CreateOutput() : der Bericht wird angezeigt, unter Berücksichtigung, ob es ein Direkter Druck oder Preview ist. Ist das der Fall, wird mit folgendem Code der Viewer angezeigt: ReportViewerProvider provider = getReportViewerProvider(); provider.openViewer(re)

Klasse ProcessUtil

org.adempiere.util public final class ProcessUtil

Keine nenneswerten Properties (nur der Logger)

Nur 3 Methoden public static boolean startDatabaseProcedure(ProcessInfo processInfo, String ProcedureName, Trx trx) Die Procedure wird ausgeführt public static boolean startJavaProcess(ProcessInfo pi, Trx trx) aus pi wird der Klassenname geholt, davon die Klasse uns diese zuletzt als Prozess instanziiert. public static MWFProcess startWorkFlow(Properties ctx, ProcessInfo pi, int AD_Workflow_ID) ein WF wird instanziiert: wf = MWorkflow.get (ctx, AD_Workflow_ID) das WF wird gestartet: wf.start(pi)

Workflow (WF) im AD

WF-Typen werden gefunden (überhaupt, wie gestaltet man Combo Boxen) Als System Admin anmelden Menü, Fenster Workflow Zoomen am Feld Window Im Window, Tab & Field mit Namen Workflow, Tab Workflow, Feld Workflow Type zoomen an Spalte WorkflowType. Table&Column mit Namen AD_Workflow zur Tab Column, am Feld Reference Key (ist AD_Workflow Type) zoomen Im Fenster Reference mit Namen AD_Workflow Type, Tab List Validation selektieren. Hier findet man die Werte General, Document Process und Document Value.


Es gibt bei Adempiere drei WF-Typen General (allgemeiner Prozess) G WFs, die man als normaler User sieht und verwendet. Accounting Setup BP Setup Price List Setup Product Setup Request Setup Requisition Setup Sales Setup Tax Setup etc.

Document Process P Process_Cash Process_Inventory Process_Invoice Process_Journal Process_Journal Batch Process_Movement Process_Order Process_Payment Process_Requisition etc.

Document Value V Keine Einträge (vielleicht in Garden World?)


Workflow-Fenster

Tab Workflow Workflow Type (siehe oben) Data Access Level (All, Organisation, Client, etc) Start Node Workflow Processor (es ist bei allen Einträgen leer; es existiert nur einen: System Workflow Processor) Tab Node Combo Box WF Responsible ( Invoker, Organisation) Combo Box Start Mode (Automatic, Manual) Combo Box Join Element (XOR, AND) Combo Box Split Element (XOR, AND) Combo Box Action (Apps Process, Apps Report, Apps Task, Document Action, Email, Set Variable, Sub Workflow, User Choice, User Form, User Window, Wait (Sleep). Sie entsprechen wohl den Möglichkeiten in MWFActivity: performWork(). Task, Sub Workflow, User Workbench, User Form und User Window werden angeboten, sind aber nicht implementiert.

Abhängig von der ausgewählten Action werden Felder angeboten: bei User Window erscheint eine Combo Box Window mit allen möglichen Windows; bei Document Action, eine Combo Box mit den Einträgen aus der Reference _Document Action (Approve, Close, Complete, Invalidate, Post, Prepare, Void, Unlock etc. Sie entsprechen den DocAction-Methoden im Code von BOs). Tab Transition Next Node: nächster auszuführender Knoten Es können mehrere Knoten als nächstes möglich sein. Einer davon ist Standard. Die Operation vom Knoten bestimmt, wann welcher Knoten ausführbar wird. Tab Condition kaum verwendet And/Or Spalte Operation (+, -, etc.) Wert

Zusammenfassung: Im Tab Workflow wird angegeben, welcher Knoten gestartet wird. Der Knoten gibt bei Actions an, welche Action dieser Knoten durchführt. Bei einer Action des Typs Document Action sind die möglichen Document Actions close, prepare, etc. Diese bewirken letzendlich den Aufruf der gleichnamigen BO-Methode und einen Statuswechsel. Transition gibt an, welcher Knoten als nächstes dran ist.

ERM Statisch Was man im AD festlegt Ein AD_WORKFLOW kann mehere AD_WF_NODEs haben Dynamisch Im Laufe der Ausführungen werden Prozesse erzeugt AD_WF_ACTIVITYs können sich auf ein AD_WF_NODE beziehen

Ein AD_WORKFLOW kann mehere AD_WF_PROCESSs haben Ein AD_WF_PROCESS kann mehere AD_WF_ACTIVITYs haben



Actions in Tab Node: Reference: WF_Action List Validation Search Key Apps Process P Apps Report R Apps Task T Document Action D Email M Set Variable V Sub Workflow F User Choice C User Form X User Window W User Workbench B (Inaktiv) Wait (Sleep) Z


Workflow-Dokumentation bei BOs: In allen Zeilen von BOs, die DocAction implementieren, kann man mit dem Icon Active Workflows (zwei Vierecke, mit einem Pfeil verbunden), die Historie des Workflows in dem dieses BO involviert ist,sehen. Man kann sehen, wo das WF steckt.

Lesen: APanel: actionPerformed (ActionEvent e) ist der Verteiler der Icons. Beim Workflow-Icon wird AEnv: public static void startWorkflowProcess (int AD_Table_ID, int Record_ID) aufgerufen. Hier wird einen Satz von AD_WF_Process eingelesen (für eben die aktuelle Tabelle und den aktuellen Satz).

Speichern: Wann und wie, weiss ich nicht. Es ist auf jeden Fall das Objekt X_AD_WF_Process, die Oberklasse von MWFProcess. Aber X_AD_WF_Process ist Unterklasse von PO, so dass Instanzen davon gespeichert werden. Irgendwo im Konstruktor wird gespeichert und aktualisiert. Beispiel eines Workflows: Order Hier wird der umgekehrte Weg gegangen: vom Fenster zum Code.

Sales Order vom Menü Window Sales Order, Tab Order, Feld Table (Inhalt: C_Order) zoomen. Tabelle C_Order, Tab Column, Spaltenname Processing Feld Reference ist ein Button; Feld Process verweist auf C_Order Process. Zoomen daran: Das Feld Workflow hat den Inhalt Process_Order (eines der definierten General-Workflows)


Fenster Workflow Editor Liest die Knoten eines Workflows, die Transition zum nächsten Knoten, Join- und Split-Bedingungen und stellt das grafisch dar. XOR bei Join-Bedingung bedeutet, die erste der möglichen Actions kommt durch.

Fenster Report & Process

Der Verteiler ist ProcessCtl.run().

Ausprägungen von Reports/Processes: Document Action Bereits in ProcessCtl erläutert Java-Klasse Im Feld Class Name einer Reportdefinition im AD steht die Java-Klasse, die ausgeführt wird (in unserem Beispiel org.compiere.process.ImportInventory). Deren Methoden prepare() und doit() erledigen das Verhalten des Buttons.

Wie werden prepare() und doit() der Klasse ImportInventory aufgerufen? ProcessCtl managt wie gesagt Prozessaufrufe. Nachdem festgestellt worden ist, dass es sich um einen Java Call-Aufruf handelt, wird in run() von ProcessCtl die Methode startProcess() aufgerufen ProcessCtl: private boolean startProcess () Ruft (bei lokalen Prozessen) startJavaProcess(m_pi, m_trx) auf ProcessUtil: public static boolean startJavaProcess(ProcessInfo pi, Trx trx) ruft eine ProcessCall-Methode auf: startProcess(Env.getCtx(), pi, trx) Das Interface ProcessCall deklariert diese Methode, die irgend eine Klasse implementieren muss: public boolean startProcess (Properties ctx, ProcessInfo pi, Trx trx) Das wird von SvrProcess getan.

Da die Java-Klasse ImportInventory wie folgt definiert wird: public class ImportInventory extends SvrProcess und SvrProcess derart: public abstract class SvrProcess implements ProcessCall Insbesondere implementiert SvrProcess die Methode startProcess() von ProcessCall. Also hat SvrProcess startProcess() implementiert. dort passiert folgendes: die private Methode process() wird aufgerufen, wo unmittelbar und hintereinander prepare() und doIt() aufgerufen werden. Beide sind in SvrProcess abstrakt definiert, so dass sie in den Unterklassen definiert werden müssen. Das ist der Fall bei ImportInventory, und so kommt dass diese Methoden von ImportInventory ausgeführt werden.

protected void prepare() Holt sich aus der Prozessinfo m_pi die Aufrufparameter und belegt Properties damit. protected String doIt() Mit den Übergabeparametern wird der Prozess realisiert. Liefert einen String zurück, der u.a. zu Fehlerbehandlung und Benachrichtigunen dient.


So spielen AD und Code bei Java Class-Aufrufen zusammen. Zur weiteren Erläuterung, siehe Beschreibung der Klasse ProcessCtl.

Oracle-Procedure Workflow Report Jasper Report

Callouts

Aus Adempiere-Wikipedia + eigene Recherche: What is or what mean "callout"? A Callout is a java method that is executed after the focus has changeg away from a field and the field's Value is changed?. Callout is a java method which is executed when a field in Adempiere window is modified. A callout class (extends CalloutEngine) groups different methods that are called when the column is changed using the UI. For a column (see AD_Column.Callout database column and Table and Column tab), you may specify a list of fully qualified methods (separated by ";"). Callouts are not for data validation - use dynamic validation (AD) instead Callouts can read the field or other fields. It is used for data entry consequences like calculate totals that need direct feedback at the GUI. Callouts are deployed in the package org.compiere.model, as well as CalloutInvoice and CalloutEngine If you do calculations in callouts you have to repeat them in the related PO classes (beforeSave()) to allow the acces via a different UI like HTML interface. Callouts in AD System Admin Window Table&Column, Tab Column Field Callout contains method, e.g. the contents of Table C_Order, Column Target Document Type, Field Callout is org.compiere.model.CalloutOrder.docType Callouts in Code Callout-Hierarchy in an example public class CalloutOrder extends CalloutEngine Package: org.compiere.model public class CalloutEngine implements Callout The implemented method start() parses the text and invokes the Method with parameters : (String) method.invoke(this, args). start() is called by ???? Method (e.g. docType)is called and executed. How it proceeds to call the specific callout I can not explain. public interface Callout

Callout signature Every callout has the same signature. public String my_callout_method (Properties ctx, int WindowNo, GridTab mTab, GridField mField, Object Value) Example in class CalloutOrder: public String docType (Properties ctx, int WindowNo, GridTab mTab, GridField mField, Object Value) Properties ctx Used in cases like MPriceList.getStandardPrecision(ctx, M_PriceList_ID) Env.setContext(ctx, WindowNo, "OrderType", DocSubTypeSO) MWorkflow.get (ctx, AD_Workflow_ID) int WindowNo Used mostly with org.compiere.util.Env class: Env.setContext(ctx, WindowNo, "OrderType", DocSubTypeSO) Env.getContextAsInt(ctx, WindowNo, "C_BankAccount_ID") Env.getContext(ctx, WindowNo, "DiscountSchema") Env.getContextAsDate(ctx, WindowNo, "PayDate") GridTab mTab It represents all columns within a record. Used in cases like (BigDecimal)mTab.getValue("QtyEntered") mTab.setValue ("M_Product_ID", new Integer (M_Product_ID)) mTab.setValue("DateAcct", Value) GridField mField The methods of the Grid Field Model class GridField can be used (getAD_Tab_ID(), getValue(), isDisplayed(), getAD_Column_ID() etc.). Example: String colName = mField.getColumnName(); Object Value Value represents the Value of the field and has to be cast to the class required. It depends on the data type of the underlying field. Examples: ((Integer)Value).intValue() (Timestamp)Value (BigDecimal)Value

Functionality using getValue() and setValue() to interpret and change logic. To avoid loops (not always used): at the beginning of a callout: setCalloutActive(true); at the end of a callout: setCalloutActive(false)


Kontextwerte validieren

Einstellungen im AD Anzeige Window, Tab&Field eines Fensters Irgendein Tab, Feld Display Logic enthält Anweisungen wie @IsEmployee@=N 1=2 // wird also nie angezeigt @$Element_U2@=Y @Processed@=Y & @#ShowAcct@=Y @IsCustomer@='Y' etc Read Only Table&Column einer Tabelle Irgendein e Spalte, Feld Dread Only Logic enthält Anweisungen wie @OrderType@='WP' @IsDropShip@=Y @AD_OrgBP_ID@!0 @ProductType@=R | @ProductType@=E | @ProductType@=O @CostingMethod@!x & @CostingMethod@!S // was ist das? (in MCost, Spalte Current Cost Price) etc. Mandatory Table&Column einer Tabelle Checkbox Mandatory Code I APanel: initPanel() ruft auf m_curTab.getTableModel().setChanged(false) m_cur_tab ist das aktuelle Tab bei der Initialisierung (initPanel() wird in AWindow von initWorkbench() aufgerufen) Initialisiert das Panel. public class GridTab implements DataStatusListener, Evaluatee, Serializable query() und getTableModel() rufen initTab() auf. initTab() von der Klasse GridTab ruft loadTab() auf GridTab: protected boolean loadTab() ruft loadFields() auf macht order by GridTab: private boolean loadFields() Holt sich alle Felder getrennt nach Standard und Inhalt ( Standardfelder wie Created, CreatedBy etc.) Ruft die Liste der Spaltennamen von denen dieses Feld abhängig ist: field.getDependentOn() public class GridField implements Serializable, Evaluatee GridField: public ArrayList<String> getDependentOn(): {

Evaluator.parseDepends(list, m_vo.DisplayLogic); Evaluator.parseDepends(list, m_vo.ReadOnlyLogic); Evaluator.parseDepends(list, m_vo.MandatoryLogic);

Evaluator.parseDepends(list, m_lookup.getValidation())

} Evaluator public static void parseDepends (ArrayList<String> list, String parseString) Parst den enstprechenden String: alles zwischen @. Code II Evaluate-Klasse evaluateLogic() wird auch in Methoden von GridTab ( isReadOnly() ) und Grid Field ( isMandatory() ) verwendet. Diese Aufrufe werden oft im Programm getätigt. Werten aus. Code III Evaluate-Klasse Es gibt auch isAllVaraiblesDefined() in Evaluate

Buchhaltungsmaschine

Klasse Doc

package org.compiere.acct
public abstract class Doc

Klassen mit Buchhaltungslogik wie Doc_GLJournal, Doc_Cash, Doc_Bank, Doc_Invoice, Doc_Inventory, Doc_InOut, Doc_Order, Doc_Payment, Doc_Requisition erben davon. Die Buchhaltungslogik wird in diesen Klassen implementiert.

Jedes BO, das von PO erbt (eben die o.g. BOs), hat eine Referenz auf eine Doc-Instanz, den Beleg des Business Objektes: private Doc m_doc, mit den Zugriffsmethoden

  • public Doc getDoc()
  • public void setDoc(Doc doc)

Beispiel: public class Doc_GLJournal extends Doc

gewisse Methoden wie loadDocumentDetails(), createFacts(), getBalance() müssen von der Unterklasse mit eigener Logik überschrieben werden.

Properties der Klasse Doc

Alle IDs der Tabellen, die Belege (asientos contables, documents) behandeln:

  • public static int[] documentsTableID

Zurzeit:

  • C_Invoice
  • C_Allocation
  • C_Cash
  • C_BankStatement
  • C_Order
  • C_Payment
  • M_InOut
  • M_Inventory
  • M_Movement
  • M_Production
  • GL_Journal
  • M_MatchInv
  • M_MatchPO
  • C_ProjectIssue
  • M_Requisition

Alle Namen der Tabellen, die Documents behandeln:

  • public static String[] documentsTableName

Konstanten für Dokumententypen

Beispiele:

  • Dokumenttypen für C_Invoice sind ARI, ARC, ARF, API, APC
  • für Accounts Payable Invoices public static final String DOCTYPE_APInvoice = "API";
  • C_Payment: ARP, APP
  • C_Order: SOO, POO
  • Transaction: MMI, MMM, MMS, MMR
  • C_BankStatement: CMB
  • C_Cash: CMC
  • C_Allocation: CMA
  • GL_Journal: GLJ

usw.

Konstanten für Postingstatus

z.B:

  • public static final String STATUS_NotPosted = "N";
  • NotBalanced b
  • NotConvertible c
  • PeriodClosed p
  • InvalidAccount i
  • PostPrepared y
  • Posted Y
  • Error E

Account Type-Konstanten

Invoice – Charge z.B. public static final int ACCTTYPE_Charge = 0; Invoice - AR Invoice - AP AP Service AR Service etc.

Sonstige Properties

Accounting Schema

private MAcctSchema[]m_ass = null;

Properties

private Properties m_ctx = null;

Das "Dokument" BO, von PO abgeleitet

protected PO p_po = null;

Doc Lines

protected DocLine[] p_lines;

Facts: Hier werden Soll+Haben gebucht

private ArrayList<Fact> m_fact = null;

Weitere Felder sind

  • Dokumententyp
  • Dokumentenstatus
  • Dokumenten-Nr
  • Beschreibung
  • GL-Kategorie
  • Periode
  • Buchungsdatum
  • Dokumentendatum
  • Business Partner
  • Bankkonto
  • Währung

Methoden

public final String post(boolean force, boolean repost)

Ausser postImmediate() habe ich keine weiteren Aufrufer gefunden. Überprüft Dokumentstatus auf Gültigkeit Überprüft, ob Accounting Schema und BO auf gleichen Client zeigen. Sperrt den Satz.

loadDocumentDetails() 

wird aufgerufen (von jeder Doc-Klasse mit eigener Logik versehen). Beispiel Doc_Invoice:

  • MInvoice wird instanziiert
  • Dokumentdatum wird ermittelt
  • Mengen werden ermittelt
  • getGrandTotal()
  • getTotalLines()
  • getChargetAmt()
  • Steuern ermittelt
  • Rechnungszeilen geladen: mit loadLines() wird p_lines belegt und die Menge gesetzt (docLine.setAmount() )
  • bei repost==true, wird deleteAcct() aufgerufen, wo per SQL der Eintrag für Tabelle und Record_ID aus der Fact_Acct-Tabelle entfernt wird.
  • Aus Accounting Schema wird m_fact gefüllt:
  • Für jeden Accounting Schema-Eintrag wird postLogic() aufgerufen, wo createFacts() von den Unterklassen aufgerufen werden und m_fact befüllt wird. Auch wird der Status des Postings gesetzt.
  • dem BO wird mit setDoc(this) der Beleg verpasst
  • das BO wird validiert
  • postCommit (String status) , wo Facts&Beleg gespeichert werden
  • eine MNote wird instanziiert + gefüllt mit Daten wie DocumentNo, Buchungsdatum, Menge, Status, Periode geöffnet?, Balanced?.
  • public static String postImmediate (MAcctSchema[] ass, int AD_Table_ID, int Record_ID, boolean force, String trxName) ruft post(force, true) auf
    • wird z.B. bei DocumentEngine: postIt() aufgerufen.
  • public ArrayList<Fact> createFacts(MAcctSchema as) ist Verantwortlich für die Buchhaltungslogik:
    • Wird von den Buchhaltungsklassen überschrieben. Beispiel Doc_Invoice:
    • Legt in einer riesigen Methode die Facts (Buchungslogik) für ARI, ARC, ARF, API, APC
    • fact.createLine() legt eine Zeile mit soll/haben an.
    • m_fact wird in der Methode post() befüllt.

public BigDecimal getBalance()

Klasse Fact

package org.compiere.acct public final class Fact Am Ende gibt es eine Klasse Balance

Properties private Doc m_doc = null; private MacctSchema m_acctSchema = null; private ArrayList<FactLine> m_lines = new ArrayList<FactLine>() Die Zeilen in Fact_Act Methoden Konstruktor public Fact (Doc document, MAcctSchema acctSchema, String defaultPostingType) Die Properties werden belegt public FactLine creatLine(DocLine docLine, MAccount account, int C_Currency_ID, BigDecimal debitAmt, BigDecimal creditAmt) Legt eine Instanz von FactLine an. weitere crateLine-Methoden public FactLine[] getLines() Die FactLines werden als Array zurückgeliefert public boolean save (String trxName) Alle Zeilen werden gespeichert. etc.


Klasse FactLine

package org.compiere.acct public final class FactLine extends X_Fact_Acct public class X_Fact_Acct extends PO FactLine hat also Persistenz Realisiert Funktionalität auf Tabelle Fact_Acct wird oft in createFacts der Buchhaltungsklassen verwendet


Properties private Maccount m_acct = null private MacctSchema m_acctSchema = null private Doc m_doc = null; Document Header private DocLine m_docLine = null;

Methoden public void setDocumentInfo(Doc doc, DocLine docLine) Client. Date Acct, Period, Tax, Product, Quantity, BP, Project, Campain, Activity, usw. public void setLocationFromBPartner (int C_BPartner_Location_ID, boolean isFrom) public BigDecimal getSourceBalance() Der Zeile public BigDecimal getAcctBalance() Accounted Balance beforeSave() wg. PO createRevenueRecognition() Aufgerufen von FactLine.save() public boolean updateReverseLine (int AD_Table_ID, int Record_ID, int Line_ID, BigDecimal multiplier) Aufrufer: Doc_MatchInv etc.

DocTypes und DocBaseTypes

DocBaseTypes

Werden Im AD definiert : es gibt eine AD-Reference namens C_DocType DocBaseType als List Validation (=Combo Box) mit folgenden Einträgen

DocBase Type Search Key
AP Credit Memo APC
AP Invoice API
AP Payment APP
AR Credit Memo ARC
AR Invoice ARI
GL Journal GLJ
Material Movement MMM
Material Receipt MMR

etc. (insgesamt 23)

DocTypes

Alle Belegtypen, die in der Buchhaltung möglich sind; werden in der Anwendung definiert. Sie benötigen einen DocBase Type. Beispiele:

DocType DocBase Type
CXP Nota de Crédito AP Credit Memo
CxP Retención de IVA AP Credit Memo
CxP Crédito Fiscal AP Invoice
CxP Factura AP Invoice
CxC Crédito Fiscal AR Invoice
Return Material Sales Order
CxP Pedido Purchase Order

etc. (insgesamt 44 in einer Anwendung)

Application Dictionary: Financial Report

In Adempiere-Menue/Performance Analysys/Financial Reporting/Financial Report eirscheint ein Fenster mit dem Button Financial Report. Was macht dieser Buton, und wo in Adempiere befindet sich die ausfuehrende Stelle davon?

Antwort Login als System Administrator

Application Dictionary/Menue Pfad zum Financial Reporting-Fenster selektieren

In Window (hat den Inhalt Financial Report) des Reports zoomen (mit Rechter Maustaste)

Dort das Feld Create Report selektieren (es ist als Button definiert)

An der Spalte Processing-Process Now zoomen

Es erscheint die Spalte Processing der Tabelle PA_Report_Financial Report.

In der Spalte Process steht FinReport.

Im Application Dictionary/Report & Process steht tatsaechlich FinReport als Inhalt der Spalte Search Key des Reports Create Report (Spalte Description: Create Financial Report).

Die Spalte Classname des Reports Create Report hat den Eintrag org.compiere.report.FinReport. Startet man Netbeans, so findet man die Datei FinReport.java, die die Klasse FinReport enthaelt, unter /data2/aplicaciones/trunk/base/src/org/compiere/report. => Diese Klasse wird ausgefuehrt. Es befinden sich dort DB-Aktivitaeten.

Die Spalte ReportView des Reports Create Report hat den Eintrag T_Report Ein zoomen des T_Report ergibt eine Temporary Reporting Table, die eine vordefinierte Anzahl Spalten (29) mit ihren Formaten hat. Der Report bedient sich derer und fuellt sie mit den enstprechenden Werten. Ich vermute, andere Reports koennen T_Report verwenden.


=Zusammenspiel Application Dictionary-Anwendung-Code am Beispiel Landed Costs=


Funktionsweise

Application Dictionary: Landed Costs und die Java-Methode, die sie implementiert Als System Administrator einloggen Fenster Menü/Requisition-to-invoice/Invoice (Vendor), Tab Landed Costs: hier kann man die Methode auswählen und die eteilung mit dem Button Distribute Costs veranlassen. Welche Klasse wird angestossen?

Als System Administrator einloggen, Menue-Fenster öffnen. Zum Fenster Landed Costs (Vendor) gehen. Tab Landed Costs Feldname Distribute Costs

Am Feld Column (der Spaltenname ist Processing) zoomen. Man gelangt in die Tabellendefinition von C_LANDED_COST. Dort ist die Spalte Processing als ein Button definiert, der den Process C_Landed_Cost_Dsitribution aufruft.

Zoomt man am Process C_Landed_Cost_Dsitribution so gelangt man an seine Definition: das Feld Classname lautet org.compiere.process.LandedCostDistribute.

Anwendung: Vorbereitung zur Berechnung von Landed Costs In der Rechnung muss einen BP angegeben werden, einen Company Agent, etc. Im Tab Rechnungszeilen, ein paar Rechnungszeilen anlegen mit Produkte/Charge, Menge und Preis Im Tab Landed Costs werden Cost Distribution (Wert, Menge, etc.), Cost Element (Transport, etc.) und den Materialempfang ( Englisch: Receipt) ggf. mit Zeile angegeben. Speichern: Die Tabelle C_LandedCost erhält die Daten. Es können mehrere Landed Costs für eine Rechnungszeile definiert werden. Tab Landed Cost Allocation muss leer sein; sie wird erst nach der Berechnung programmatisch gefüllt. Button Distribute Costs aktivieren: In der Anwendung wird Landed Costs einer Rechnungszeile angestossen. Das bedeutet, wenn man eine ganze Rechnung mit mehreren Rechnungszeilen berechnen will, muss man es manuell pro Rechnungszeile tun.

Code ProcessCtl: Aufruf von ProcessUtil.startJavaProcess() ProcessUtil.startJavaProcess(): Aufruf von process.startProcess() LandedCostDistribute (SvrProcess): startProcess() ruft process() auf. LandedCostDistribute (SvrProcess): process() ruft prepare() und doIt() auf. LandedCostDistribute (SvrProcess): doIt() legt eine Instanz von Landed Cost an und ruft auf m_lc.allocateCosts(). Da es sich um eine doIt()-Methode handelt, haben wir es mit einem AD-Prozess zu tun, das irgendwo aufgerufen wird. Log-Eintrag wie etwa: LandedCostDistribute.doIt: MLandedCost[1000000,CostDistribution=Q,M_CostElement_ID=1000006,M_InOut_ID=1000039] [43] MLandedCost: allocateCosts(): ruft invoiceLine.allocateLandedCosts() auf. MInvoiceLine: allocateLandedCosts() macht die Arbeit: Aus Tabelle C_LandedCost werden alle definierten Landed Costs für die Rechnungszeile geholt und als MLandedCost-Array instanziiert. Alle Einträge der Tabelle C_LandedCostAllocation für die betroffene Rechnungszeile werden -falls es welche gibt- gelöscht. wird unterschieden zw. einer und mehreren Zeilen Eine Zeile: aus dem ermittelten Landed Cost wird die Lieferung und eine Liste der Lieferungszeilen geholt. Die Gesamtsumme wird geholt Etc..

Preislisten

Jedes Produkt gehoert zu einer Product Category.

Jedes Produkt kann mehrere Price List Versions haben. Sie (die Price List Versions) unterscheiden sich voneinander in List Price, Standard Price und Limit Price.

Es gibt ein Price List Schema (Tabelle: M_DiscountSchema) unter Material Management/Material Management Rules. Hier kann man pro Price List Schema die Felder Valid From, Discount Type und den Button Renumber selektieren; zusaetzlich kann man pro Price List Schema mehrere Bedingungen als Schema Line (Tabelle: M_DiscountSchemaLine) definieren. In jeder Bedingung kann man festlegen, für wen die Bedingung gilt: Business Partner, Product Category oder Product. Die Schema Lines haben eine Sequenz, in der sie abgearbeitet werden: von den kleinen zu den grossen Sequence-Zahlen. Defaultmässig ist eine 10er-Schritt-Folge festgelegt. Die Bedingungen sind vor allem Preise und Rabatte (List Price Discount in %, List Price Min Margin, List Price Max Margin etc.).

Es gibt ein Price List (Tabelle: M_PriceList) unter Material Management/Material Management Rules. Hier kann man Preislisten und ihre Versionen (Tabelle: M_PriceListVersion) definieren. Wichtiger ist es, dass man zu jeder Preisliste Product Prices im Register Version anlegen kann. Dazu werden das Price List Schema (siehe oben) und eine oder keine aktive Price List Version (im Programm steht fälschlicherweise Base Price List!!) benötigt. In der Combobox werden alle aktiven Price List Versions zur Auswahl aufgezeigt. Letzendlich kann man -basierend auf Price List Schema und Price List Version- eine Price List angelegt, die auf Angaben des Schemas und der Version basiert. Dazu muss man den Button Create Price List anklicken

Diesen Sachverhalt kann man in der DB nachvollziehen: es gibt in der Tabelle M_PRICELIST_VERSION das Feld M_PRICELIST_VERSION_BASE_ID, das auf einen Satz der Tabelle M_PRICELIST_VERSION verweist. Seltsamerweise ist dieses Feld nicht als Fremdschlüssel deklariert.

Zusammengefasst:


Preiskalkulation



Payment Term




Views RV_C_INVOICE, RV_OPENITEM

RV_OPENITEM ist eine View, die aufgerufen wird, wenn man Menue/Open Items/Open Items waehlt. Die View RV_C_INVOICE wird in RV_OPENITEM verwendet; deswegen wird sie zuerst untersucht.

1.- In RV_C_INVOICE wird die Tabelle C_INVOICE mit den Tabellen C_DocType, C_BPartner, C_BPartner_Location und C_Location in einem Inner-Join verbunden. Ergebnis sind alle Zeilen aus C_INVOICE, die Verweise auf diese Tabellen haben. Es koennen also nicht mehr Zeilen herauskommen als es in C_INVOICE gibt.

Semantik: wahrscheinlich werden nur komplette Invoices behandelt.

Fast alle Spalten der View RV_C_INVOICE (52 Spalten) stammen von der Tabelle C_INVOICE (60 Spalten) ausser C_CountryID, C_Region_ID, Postal und City, die aus der Tabelle C_Location stammen.

Es gibt eine Spalte DocStatus in der Tabelle C_INVOICE; ihre Werte sind 2 Zeichen. CO -> completed, DR -> Drafted. Weitere Werte kenne ich nicht.

Die Spalten ChargeAmt, Totallines und GrandTotal der Tabelle C_INVOICE koennen bei der Bildung der View negativ werden waehrend Multiplier -1, wenn das 3. Zeichen der Spalte DocBaseType = „C“ ist.

DocBaseType ist eine Spalte der Tabelle , und hat Werte wie CMC (Cash Journal), APC (Vendor Credit Memo), ARC (Credit Memo), MMR (Vendor Delivery), POO (purchase Order), SOO (Order Confirmation, Proposal, Quotation, etc).

CMC , APC und ARC sind die einzigen Werte, die ein C an der 3. Stelle haben. Es werden wohl nur diese Dokumententypen zur Bildung der View RV_C_INVOICE herangezogen. Was das genau bedeutet, weiss ich nicht.

Die Funtion charAt() findet das n-te Zeichen innerhalb einer Zeichenkette (der Code ist einfach: RETURN SUBSTR(p_string, p_pos, 1) ).


2.- Die View RV_C_INVOICE wird in der View RV_OPENITEM verwendet. Hier wird es etwas komplizierter. Die View RV_C_INVOICE besteht aus einer Union Zur Erinnerung: die Datentypen der selektierten Tabellenspalten muessen bei einer Union in beiden Tabellen gleich sein. eine einfache Union.liefert nicht wiederholte Zeilen. D.h., gleiche Zeilen werden uebergangen. erst eine UNION ALL liefert auch wiederholte Zeilen.

Aus diesem Grunde sind die Spaltennamen des 2. Teils der Union und ihre Reihenfolge die gleichen wie die des 1. Aus diesem Teils

Erste Menge der Union Als erstes wird ein inner Join zwischen der View RV_C_INVOICE und der Tabelle C_PaymentTerm gebildet. Zur Erlaeuterung: ein Payment Term kann mehrere Schedules haben. Den Payment Term bestimmt man im Register Invoice beim Anlegen eines Invoices an. Mit diesem ersten Join errechnet man die Werte fuer DueDate, DaysDue, DiscountDate, DiscountAmt, PaidAmt, OpenAmt aus dem selektierten Payment Term. Es werden Berechnungen mit Funtionen wie paymentTermDueDate(), was den Aufruf der Methode org.compiere.sqlj.PaymentTerm.dueDate(i.C_PaymentTerm_ID, i.DateInvoiced) bewirkt. Diese Methode ermittelt aus dem invoiced-Datum und der Zahlungsbedingung die Anzahl Tage der Faelligkeit. Andere Oracle-Funktionen wie addDays(i.DateInvoiced,p.DiscountDays) werden verwendet. Hierbei wird DiscountDays aus der Tabelle C_PaymentTerm geholt. Diese Funktion wird wie folgt aufgeloest: RETURN TRUNC(p_date) + p_days. Manche Spalten der View RV_OPENITEM werden neu in der View definiert: DueDate, DaysDue, DiscountDate, DiscountAmt, PaidAmt, OpenAmt. Ihre Werte werden mithilfe von Funktionen errechnet. Die Berechnung dieser Spalten sind der Unterschied zum 2. Join. Fast alle anderen Spalten kommen aus der View RV_C_INVOICE. Da fast alle Spalten der View RV_C_INVOICE aus der Tabelle C_INVOICE entstammen, kann man behaupten, dass die View RV_OPENITEM aus der Tabelle C_INVOICE erzeugt wird, mit zusaetzlichen, berechneten Feldern. Die where-Bedingung besagt u.a., dass Zeilen in Frage kommen, wo noch nicht bezahlt wurde (i.IsPayScheduleValid<>'Y') und das Dokument den Status „Draft“ nicht haben sollte (i.DocStatus<>'DR'). Zweite Menge der Union Es wird ein inner Join zwischen der View RV_C_INVOICE und der Tabelle C_InvoicePaySchedule gebildet. Info: die Tabelle C_InvoicePaySchedule wird im Fenster Invoice (Customer), als 4. Register angezeigt (unter dem Titel Payment Schedule). Hier selektiert man ein Payment Schedule und bestimmt Parameter wie Faelligkeitsdatum und Rabattdatum. Das Payment Schedule haengt vom Payment Term ab, den man im Register Invoice auswaehlt.

Das Fenster Invoice (Customer) wird unter Menue/Quote-to-Invoice/Sales Invoices/Invoice (Customer) ausgefuehrt. Es werden die gleichen Spalten wie beim 1. Join genommen; nur die Ermittlung von Spalten wie DueDate, DaysDue, DiscountDate, DiscountAmt, PaidAmt, OpenAmt ist anders. Die where-Bedingung hat eine zusaetzliche Komponente: on die payschedule gueltig ist.

Was macht also diese View? Es ermittelt aus den Invoices die faelligen Invoices und berechnet die aktullen Tage und Rabatte fuer jeden Invoice. Warum dazu 2 mal diese Werte errechnet (einmal ueber die Payment Term und ein anderes Mal ueber die Schedules), weiss ich nicht.

Interessanterweise wird diese View in die temporäre Tabelle T_AGING geladen (ich vermute aus Performance-Gründen). Das Dictionary ergibt folgendes: Der Report&Process RV_T_Aging verwendet die Report View T_Aging, RV_T_Aging verwendet als Classname org.compiere.process.Aging, wo u.a. die View RV_OpenItem aufgerufen wird. T_Aging ist als Tabelle definiert (und nicht als View wie bei anderen Reports).

Eine Darstellung des Zusammenhangs zwischen den Tabellen liefert die Grafik:



Anmerkung: am Beispiel der Tabelle C_InvoicePaySchedule und der Registerdefinition von Payment Schedule im Dictionary sieht man, dass nicht alle Spalten, die in der Tabelle definiert werden, im Dictionary zur Verfuegung stehen. In der Tabellendefinition von C_InvoicePaySchedule gibt es 17 Spalten. Im Dictonary dagegen kann man fuer die Registerdefinition von Payment Schedule nur aus 13 Spalte auswahlen. Dis Spalten created, createdBy, updated und updatedBy werden im Dictionary nicht zur Verfügung gestellt.

Erläuterung der Lagerstruktur (warehouse) in der DB

  • Das Lager kann man in ADempiere unter Material Management->Material Management Rules->Warehouse&Locators finden. Hier kann man das Lager, sein(e) Locator(s) und die Storage pro Locator sehen. Die Lager werden in der Tabelle m_warehouse gespeichert.

Locator und Storage

Jedes Lager kann mehrere Locators' besitzen. Sie werden in der Tabelle m_locator vermerkt. Locators haben Search value (zum schnellen Finden). Ein Format-Vorschlag zum Value-Feld bei 3-dimensionalen Lagern ist xx-yy-zz, wo 08-13-06 den achten Gang, das 13. Regal und dort das sechste Fach bestimmt.

  • Aisle (Gang) oder "X"
  • Bin (Regal) oder "Y"
  • Level (Fach) oder "Z"

Damit kann eine Menge Locators jedes 3-dimensionale Lager modellieren. Jedem Locator und Produkt kann einen Lagerplatz (Storage) zugewiesen werden.

Es empfiehlt sich, für den Search Key folgendes Format zu wählen: Lager xx-yy-zz. In den Berichten würde stehen: “Bewegung von Zentrallager 23-44-3 nach Kommissionierungslager 21-44-4“.

Storages (die Lagerung) werden in der Tabelle m_storage aufbewahrt und haben als Eigenschaften on hand quantity, reserverd quantity, ordered quantity, last inventory count, etc.

Es ist gestattet, dass pro Produkt und Locator unterschiedliche Lagerungen aufbewahrt werden, so dass eine 3-dimensionale Abbildung nicht notwendig sein muss. Das ist sinnvoll, denn z.B. bei Chargen man das gleiche Produkt mit unterschiedlichen Chargennummern und Garantiedaten haben kann.

Die Einträge von m_storage können nicht gelöscht werden (auch nicht mit delete from m_strorage where ad_client_id=1000001). Wenn aus unterschiedlichen Gründen ein m_storage-Eintrag falsch ist, muss ad_client_id, m_product oder m_locator geändert werden.

SQL zum Finden der Beziehung Locator-Warehouse

select w.m_warehouse_id, w.name, l.m_locator_id
FROM m_warehouse w
INNER JOIN m_locator l ON (w.m_warehouse_ID=l.m_warehouse_ID)
where w.ad_client_id=xxxxx

Hierbei muss man darauf Acht geben, dass die ad_client_id stimmt.

Attribute Set Instances

Wo man die Description der Attributsetinstance bestimmt.

MAttributeSetInstance.setDescription()

Hier wird die Description aus den Attributen zusammengesetzt. U.a.werden MAttributeSet.getLotCharStart() und MAttributeSet.getLotCharEnd() aufgerufen.

Die Werte können im Fenster Attribute Set, Felder Lot Char Start Overwrite und Lot Char Start Overwrite definiert werden. Blanks werden nicht ausgewertet.

Tabellenbeziehungen zusäztlich zu den in Storage:

1 -> n
MAttributesetInstance (Lot, Serno, Guaranteedate) -> MStorage (m_attributesetinstance_id, qtyonhand, qtyreserved, qtyordered)

Das bedeutet, dass in einem Locator unterschiedliche Chargen (MattributesetInstances) für ein und dasselbe Produkt existieren können.

SQL, um Locator, Produkt, Menge und Beschreibung darzustellen:

select loc.value, prd.value, st.QTYONHAND, asi.m_attributesetinstance_id, asi.description 
from  m_storage st
join m_attributesetinstance asi on (st.M_ATTRIBUTESETINSTANCE_ID=asi.M_ATTRIBUTESETINSTANCE_ID)
join m_locator loc on (st.M_LOCATOR_ID=loc.M_LOCATOR_ID)
join m_product prd on (st.M_PRODUCT_ID=prd.M_PRODUCT_ID)
order by asi.lot, asi.guaranteedate, prd.value;

Weitere Beziehungen II:

1 -> n
MAttributeSetInstance (Lot, Serno, Guaranteedate) -> MOrderline (m_attributesetinstance_id, qtyordered, qtyreserved, qtydelivered, m_product_id)