IDEMPIERE-4187 implement the usage of workflows in processes (#236)

Implement chaining of workflow through the use of Process Action
Implement overwrite of Record_ID for Process through Node Parameter
This commit is contained in:
hengsin 2020-08-27 20:34:26 +08:00 committed by GitHub
parent 9d614b985e
commit 7324dde78e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 358 additions and 65 deletions

View File

@ -25,9 +25,11 @@ import org.adempiere.util.ProcessUtil;
import org.compiere.process.ProcessInfo;
import org.compiere.util.CCache;
import org.compiere.util.DB;
import org.compiere.util.Env;
import org.compiere.util.Trx;
import org.compiere.util.Util;
import org.compiere.wf.MWFNode;
import org.compiere.wf.MWFProcess;
/**
* Process Model
@ -264,38 +266,51 @@ public class MProcess extends X_AD_Process
// Lock
pInstance.setIsProcessing(true);
pInstance.saveEx();
pi.setAD_PInstance_ID(pInstance.getAD_PInstance_ID());
}
boolean ok = false;
// Java Class
String Classname = getClassname();
if (Classname != null && Classname.length() > 0)
if (isWorkflow())
{
pi.setClassName(Classname);
ok = startClass(pi, trx, managedTrx);
}
else
{
// PL/SQL Procedure
String ProcedureName = getProcedureName();
if (ProcedureName != null && ProcedureName.length() > 0)
pi.setTransactionName(trx.getTrxName());
MWFProcess wfprocess = ProcessUtil.startWorkFlow(getCtx(), pi, getAD_Workflow_ID());
if (wfprocess == null)
{
ok = startProcess (ProcedureName, pi, trx, managedTrx);
ok = false;
}
else
{
// BF IDEMPIERE-165
if (this.isReport()) {
ok = true;
}
else {
String msg = "No Classname or ProcedureName for " + getName();
pi.setSummary(msg, ok);
log.warning(msg);
}
MPInstance pinstance = new MPInstance(Env.getCtx(), pi.getAD_PInstance_ID(), null);
String errmsg = pi.getSummary();
pinstance.setResult(!pi.isError());
pinstance.setErrorMsg(errmsg);
pinstance.saveEx();
ok = !pi.isError();
}
}
else if (isJavaProcess())
{
pi.setClassName(getClassname());
ok = startClass(pi, trx, managedTrx);
}
else if (isDatabaseProcedure())
{
// PL/SQL Procedure
ok = startProcess (getProcedureName(), pi, trx, managedTrx);
}
else if (this.isReport())
{
// BF IDEMPIERE-165
ok = true;
}
else
{
String msg = "No Workflow, Classname or ProcedureName for " + getName();
pi.setSummary(msg, ok);
log.warning(msg);
}
return ok;
} // process
@ -305,10 +320,18 @@ public class MProcess extends X_AD_Process
*/
public boolean isJavaProcess()
{
String Classname = getClassname();
return (Classname != null && Classname.length() > 0);
return !Util.isEmpty(getClassname(), true);
} // is JavaProcess
/**
* Is this a db procedure
* @return true if db procedure
*/
public boolean isDatabaseProcedure()
{
return !Util.isEmpty(getProcedureName(), true);
}
/**
* Is Force Background
* @return true if force background

View File

@ -1142,6 +1142,29 @@ public class MWFActivity extends X_AD_WF_Activity implements Runnable
//
ProcessInfo pi = new ProcessInfo (m_node.getName(true), m_node.getAD_Process_ID(),
getAD_Table_ID(), getRecord_ID());
//check record id overwrite
MWFNodePara[] nParams = m_node.getParameters();
for(MWFNodePara p : nParams)
{
if (p.getAD_Process_Para_ID() == 0 && p.getAttributeName().equalsIgnoreCase("Record_ID") && !Util.isEmpty(p.getAttributeValue(), true))
{
try
{
Object value = parseNodeParaAttribute(p);
if (value == p || value == null)
break;
int recordId = Integer.valueOf(value.toString());
pi.setRecord_ID(recordId);
}
catch (NumberFormatException e)
{
log.log(Level.WARNING, e.getMessage(), e);
}
break;
}
}
pi.setAD_User_ID(getAD_User_ID());
pi.setAD_Client_ID(getAD_Client_ID());
pi.setAD_PInstance_ID(pInstance.getAD_PInstance_ID());
@ -1610,50 +1633,13 @@ public class MWFActivity extends X_AD_WF_Activity implements Runnable
MPInstancePara iPara = iParams[pi];
for (int np = 0; np < nParams.length; np++)
{
MWFNodePara nPara = nParams[np];
MWFNodePara nPara = nParams[np];
if (iPara.getParameterName().equals(nPara.getAttributeName()))
{
String variableName = nPara.getAttributeValue();
if (log.isLoggable(Level.FINE)) log.fine(nPara.getAttributeName()
+ " = " + variableName);
// Value - Constant/Variable
Object value = variableName;
if (variableName == null
|| (variableName != null && variableName.length() == 0))
value = null;
else if (variableName.indexOf('@') != -1 && m_po != null) // we have a variable
{
// Strip
int index = variableName.indexOf('@');
String columnName = variableName.substring(index+1);
index = columnName.indexOf('@');
if (index == -1)
{
log.warning(nPara.getAttributeName()
+ " - cannot evaluate=" + variableName);
break;
}
columnName = columnName.substring(0, index);
index = m_po.get_ColumnIndex(columnName);
if (index != -1)
{
value = m_po.get_Value(index);
}
else // not a column
{
// try Env
String env = Env.getContext(getCtx(), columnName);
if (env.length() == 0)
{
log.warning(nPara.getAttributeName()
+ " - not column nor environment =" + columnName
+ "(" + variableName + ")");
break;
}
else
value = env;
}
} // @variable@
Object value = parseNodeParaAttribute(nPara);
if (value == nPara)
break;
// No Value
if (value == null)
@ -1718,6 +1704,52 @@ public class MWFActivity extends X_AD_WF_Activity implements Runnable
} // instance parameter loop
} // fillParameter
private Object parseNodeParaAttribute(MWFNodePara nPara)
{
String variableName = nPara.getAttributeValue();
if (log.isLoggable(Level.FINE)) log.fine(nPara.getAttributeName()
+ " = " + variableName);
// Value - Constant/Variable
Object value = variableName;
if (variableName == null
|| (variableName != null && variableName.length() == 0))
value = null;
else if (variableName.indexOf('@') != -1 && m_po != null) // we have a variable
{
// Strip
int index = variableName.indexOf('@');
String columnName = variableName.substring(index+1);
index = columnName.indexOf('@');
if (index == -1)
{
log.warning(nPara.getAttributeName()
+ " - cannot evaluate=" + variableName);
return nPara;
}
columnName = columnName.substring(0, index);
index = m_po.get_ColumnIndex(columnName);
if (index != -1)
{
value = m_po.get_Value(index);
}
else // not a column
{
// try Env
String env = Env.getContext(getCtx(), columnName);
if (env.length() == 0)
{
log.warning(nPara.getAttributeName()
+ " - not column nor environment =" + columnName
+ "(" + variableName + ")");
return nPara;
}
else
value = env;
}
} // @variable@
return value;
}
/*********************************
* Send EMail
*/

View File

@ -210,7 +210,7 @@ public abstract class AbstractProcessCtl implements Runnable
startWorkflow (AD_Workflow_ID);
MPInstance pinstance = new MPInstance(Env.getCtx(), m_pi.getAD_PInstance_ID(), null);
String errmsg = m_pi.getSummary();
pinstance.setResult(m_pi.isError());
pinstance.setResult(!m_pi.isError());
pinstance.setErrorMsg(errmsg);
pinstance.saveEx();
unlock();

View File

@ -0,0 +1,238 @@
/***********************************************************************
* This file is part of iDempiere ERP Open Source *
* http://www.idempiere.org *
* *
* Copyright (C) Contributors *
* *
* This program is free software; you can redistribute it and/or *
* modify it under the terms of the GNU General Public License *
* as published by the Free Software Foundation; either version 2 *
* of the License, or (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the Free Software *
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, *
* MA 02110-1301, USA. *
* *
* Contributors: *
* - hengsin *
**********************************************************************/
package org.idempiere.test.model;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
import java.math.BigDecimal;
import java.sql.Timestamp;
import org.compiere.model.MBPartner;
import org.compiere.model.MInOut;
import org.compiere.model.MInOutConfirm;
import org.compiere.model.MInOutLine;
import org.compiere.model.MOrder;
import org.compiere.model.MOrderLine;
import org.compiere.model.MProcess;
import org.compiere.model.MProduct;
import org.compiere.process.DocAction;
import org.compiere.process.ProcessInfo;
import org.compiere.process.ServerProcessCtl;
import org.compiere.util.Env;
import org.compiere.util.TimeUtil;
import org.compiere.util.Util;
import org.compiere.wf.MWFNode;
import org.compiere.wf.MWFNodeNext;
import org.compiere.wf.MWFNodePara;
import org.compiere.wf.MWorkflow;
import org.idempiere.test.AbstractTestCase;
import org.junit.jupiter.api.Test;
/**
* @author hengsin
*
*/
public class ProcessTest extends AbstractTestCase {
/**
*
*/
public ProcessTest() {
}
@Test
public void testWorkflowProcess() {
//first test, using MProcess.processIt
MOrder order = new MOrder(Env.getCtx(), 0, getTrxName());
//Joe Block
order.setBPartner(MBPartner.get(Env.getCtx(), 118));
order.setC_DocTypeTarget_ID(MOrder.DocSubTypeSO_Standard);
order.setDeliveryRule(MOrder.DELIVERYRULE_CompleteOrder);
order.setDocStatus(DocAction.STATUS_Drafted);
order.setDocAction(DocAction.ACTION_Complete);
Timestamp today = TimeUtil.getDay(System.currentTimeMillis());
order.setDateOrdered(today);
order.setDatePromised(today);
order.saveEx();
MOrderLine line1 = new MOrderLine(order);
line1.setLine(10);
//Azalea Bush
line1.setProduct(MProduct.get(Env.getCtx(), 128));
line1.setQty(new BigDecimal("1"));
line1.setDatePromised(today);
line1.saveEx();
int Process_Order=104;
MProcess process = MProcess.get(Env.getCtx(), Process_Order);
ProcessInfo pi = new ProcessInfo(process.getName(), process.get_ID());
pi.setAD_Client_ID(getAD_Client_ID());
pi.setAD_User_ID(getAD_User_ID());
pi.setRecord_ID(order.get_ID());
pi.setTransactionName(getTrxName());
boolean ok = process.processIt(pi, getTrx(), false);
if (!ok || pi.isError()) {
fail("Error running Process_Order workflow process" + (Util.isEmpty(pi.getSummary()) ? "" : " : "+pi.getSummary()));
return;
}
order.load(getTrxName());
assertTrue(order.getDocStatus().equals(DocAction.STATUS_Completed), "Order not completed");
//second test, using MWorkflow.runDocumentActionWorkflow
pi = MWorkflow.runDocumentActionWorkflow(order, DocAction.ACTION_ReActivate);
if (pi.isError()) {
fail("Failed to reactivate order" + (Util.isEmpty(pi.getSummary()) ? "" : " : "+pi.getSummary()));
return;
}
order.load(getTrxName());
assertTrue(order.getDocStatus().equals(DocAction.STATUS_InProgress), "Order not reactivated");
}
@Test
public void testJavaProcess() {
int Verify_BOM=136;
int Patio_Chair=133;
//first, test MProcess.processIt
MProcess process = MProcess.get(Env.getCtx(), Verify_BOM);
ProcessInfo pi = new ProcessInfo(process.getName(), process.get_ID());
pi.setAD_Client_ID(getAD_Client_ID());
pi.setAD_User_ID(getAD_User_ID());
pi.setRecord_ID(Patio_Chair);
pi.setTransactionName(getTrxName());
boolean ok = process.processIt(pi, getTrx(), false);
if (!ok || pi.isError()) {
fail("Error running Verify BOM process" + (Util.isEmpty(pi.getSummary()) ? "" : " : "+pi.getSummary()));
return;
}
//second, test ServerProcessCtl.process
pi = new ProcessInfo(process.getName(), process.get_ID());
pi.setAD_Client_ID(getAD_Client_ID());
pi.setAD_User_ID(getAD_User_ID());
pi.setRecord_ID(Patio_Chair);
pi.setTransactionName(getTrxName());
ServerProcessCtl.process(pi, getTrx());
if (pi.isError()) {
fail("Error running Verify BOM process" + (Util.isEmpty(pi.getSummary()) ? "" : " : "+pi.getSummary()));
return;
}
}
@Test
public void testChainWorkflow() {
MOrder order = new MOrder(Env.getCtx(), 0, getTrxName());
//Joe Block
order.setBPartner(MBPartner.get(Env.getCtx(), 118));
order.setC_DocTypeTarget_ID(MOrder.DocSubTypeSO_Standard);
order.setDeliveryRule(MOrder.DELIVERYRULE_CompleteOrder);
order.setDocStatus(DocAction.STATUS_Drafted);
order.setDocAction(DocAction.ACTION_Complete);
Timestamp today = TimeUtil.getDay(System.currentTimeMillis());
order.setDateOrdered(today);
order.setDatePromised(today);
order.saveEx();
MOrderLine line1 = new MOrderLine(order);
line1.setLine(10);
//Azalea Bush
line1.setProduct(MProduct.get(Env.getCtx(), 128));
line1.setQty(new BigDecimal("1"));
line1.setDatePromised(today);
line1.saveEx();
ProcessInfo pi = MWorkflow.runDocumentActionWorkflow(order, DocAction.ACTION_Complete);
if (pi.isError()) {
fail("Failed to complete order" + (Util.isEmpty(pi.getSummary()) ? "" : " : "+pi.getSummary()));
return;
}
order.load(getTrxName());
assertEquals(DocAction.STATUS_Completed, order.getDocStatus(), "Expected Completed Status for Order");
int Process_InOutConfirmation = 124;
MWorkflow wf = new MWorkflow(Env.getCtx(), Process_InOutConfirmation, null);
MWFNode processNode = new MWFNode(wf, "Process_InOut", "Process_InOut");
processNode.set_ValueNoCheck("AD_Client_ID", getAD_Client_ID());
processNode.setAction(MWFNode.ACTION_AppsProcess);
processNode.setAD_Process_ID(109); //M_InOut Process
processNode.setEntityType(MWFNode.ENTITYTYPE_UserMaintained);
processNode.saveEx();
MWFNodePara processNodePara = new MWFNodePara(Env.getCtx(), 0, null);
processNodePara.set_ValueNoCheck("AD_Org_ID", 0);
processNodePara.setAD_WF_Node_ID(processNode.getAD_WF_Node_ID());
processNodePara.setAttributeName("Record_ID");
processNodePara.setAttributeValue("@M_InOut_ID@");
processNodePara.setEntityType(MWFNodePara.ENTITYTYPE_UserMaintained);
processNodePara.saveEx();
MWFNode docCompleteNode = new MWFNode(Env.getCtx(), 219, null);
MWFNodeNext docCompleteNodeNext = new MWFNodeNext(docCompleteNode, processNode.getAD_WF_Node_ID());
docCompleteNodeNext.set_ValueNoCheck("AD_Client_ID", getAD_Client_ID());
docCompleteNodeNext.setEntityType(MWFNodeNext.ENTITYTYPE_UserMaintained);
docCompleteNodeNext.saveEx();
try {
int MM_Shipment_With_Pick=148;
MInOut inout = new MInOut(order, MM_Shipment_With_Pick, order.getDateOrdered());
inout.setDocStatus(DocAction.STATUS_Drafted);
inout.setDocAction(DocAction.STATUS_Completed);
inout.saveEx();
MInOutLine il = new MInOutLine(inout);
il.setOrderLine(line1, 0, new BigDecimal("1"));
il.saveEx();
pi = MWorkflow.runDocumentActionWorkflow(inout, DocAction.ACTION_Complete);
if (pi.isError()) {
fail("Failed to complete shipment" + (Util.isEmpty(pi.getSummary()) ? "" : " : "+pi.getSummary()));
return;
}
inout.load(getTrxName());
assertEquals(DocAction.STATUS_InProgress, inout.getDocStatus(), "Expected In Progress Status for Shipment");
MInOutConfirm[] confirmations = inout.getConfirmations(true);
assertEquals(1, confirmations.length, "Expected 1 Shipment Confirmation Document");
pi = MWorkflow.runDocumentActionWorkflow(confirmations[0], DocAction.ACTION_Complete);
if (pi.isError()) {
fail("Failed to complete shipment confirmation" + (Util.isEmpty(pi.getSummary()) ? "" : " : "+pi.getSummary()));
return;
}
confirmations[0].load(getTrxName());
assertEquals(DocAction.STATUS_Completed, confirmations[0].getDocStatus(), "Expected Completed Status for Shipment Confirmation");
inout.load(getTrxName());
assertEquals(DocAction.STATUS_Completed, inout.getDocStatus(), "Expected Completed Status for Shipment");
} finally {
docCompleteNodeNext.deleteEx(true);
processNodePara.deleteEx(true);
processNode.deleteEx(true);
}
}
}