Difference between revisions of "Development Guidelines in German"

From ADempiere
Jump to: navigation, search
This Wiki is read-only for reference purposes to avoid broken links.
Line 470: Line 470:
 
Setzt  einen des als static final Property definierten Status
 
Setzt  einen des als static final Property definierten Status
 
u.v.a.m.
 
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)clazz.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()
 +
 +
 +
==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.

Revision as of 14:01, 21 September 2007

Generiert Java Beans

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 GenerateMdel.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.
  • die 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, die xxxxxx.
  • Methoden
    • initPO
    • wird vom in der Afurufkette zuletzt aufgerufenen PO-Konstruktor ( PO (Properties ctx, int ID, String trxName, ResultSet rs) ) aufgerufen.
    • toString (z.B. 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-eil 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, das nicht eines Business Objekts entstammt:
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 Methodenzum DB-Transfer, ruft Triggers und Modellvalidatoren.

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


Wichtige 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 kreiert. org.compiere.util.Trx kontrolliert die Transaktionen mit static Methoden neue Transkaktion anlegen, Trx vergibt einen zufälligen Namen Trx.createTrxName() neue Transkaktion anlegen Trx.createTrxName("Cost") 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. Trx.get(trxName, True) 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. Verweis auf die Stelle, wo erwähnt wird, dass diese Felder nicht erfasst werden.

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.

Konstruktor PO (Properties ctx, int ID, String trxName) MBPartner bp = new MBPartner (line.getCtx(), line.getC_BPartner_ID(), line.get_TrxName()); Konstruktor PO (Properties ctx, ResultSet rs, String trxName)

Weitere 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 hier 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 anglegt. 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 (u.a.) 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 beforeSave() Logik, bevor die Daten gespeichert werden. afterSave() Logik, nachdem die Daten gespeichert worden sind. beforeDelete() Logik, bevor die Daten gelöscht werden afterDelete()

Logik, nachdem die Daten gelöscht worden sind

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); public MProduct (X_I_Product impP) Konstruktor der MProdct-Klasse für den Produkt-Import. Properties eines instanziierten Produktes werden mit den Werten des Objektes impP belegt. 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 managt 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. approveIt() Setzt die Property isApproved auf True closeIt() Danach ist keine Action möglich invalidateIt() prepareIt() Logik vor Ausführung processIt() Ausführung der Businesslogik reActivateIt() 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. rejectIt() reverseAccrualIt() reverseCorrectIt() Generiert in Buchhaltung Gegenbuchung. Dafür wird eine Kopie des Originals genommen. unlockIt() eine Methode lockIt() gibt es nicht 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 getDocAction() getDocStatus() Liefert einen der als static final Property definierten Stati getDocumentInfo() Name des Dokumententyps und Dokument-Nr. getDocumentNo() getDoc_User_ID() Kontrolliert die Sequenz Z.B. Verkaufs- Kaufs- Zahlungs-Nr. getProcessMsg () Liefert den Wert der Property m_processMsg get_TrxName() save() setDocStatus () Setzt einen des als static final Property definierten Status 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)clazz.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()


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.