From 7324dde78eb71da9d0ce69944e86cc96aa898df0 Mon Sep 17 00:00:00 2001 From: hengsin Date: Thu, 27 Aug 2020 20:34:26 +0800 Subject: [PATCH] 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 --- .../src/org/compiere/model/MProcess.java | 69 +++-- .../src/org/compiere/wf/MWFActivity.java | 114 ++++++--- .../org/compiere/apps/AbstractProcessCtl.java | 2 +- .../org/idempiere/test/model/ProcessTest.java | 238 ++++++++++++++++++ 4 files changed, 358 insertions(+), 65 deletions(-) create mode 100644 org.idempiere.test/src/org/idempiere/test/model/ProcessTest.java diff --git a/org.adempiere.base/src/org/compiere/model/MProcess.java b/org.adempiere.base/src/org/compiere/model/MProcess.java index 1795ae3e57..2f14c8ce88 100644 --- a/org.adempiere.base/src/org/compiere/model/MProcess.java +++ b/org.adempiere.base/src/org/compiere/model/MProcess.java @@ -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 diff --git a/org.adempiere.base/src/org/compiere/wf/MWFActivity.java b/org.adempiere.base/src/org/compiere/wf/MWFActivity.java index 2d95c9370b..2422fc1a45 100644 --- a/org.adempiere.base/src/org/compiere/wf/MWFActivity.java +++ b/org.adempiere.base/src/org/compiere/wf/MWFActivity.java @@ -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 */ diff --git a/org.adempiere.ui/src/org/compiere/apps/AbstractProcessCtl.java b/org.adempiere.ui/src/org/compiere/apps/AbstractProcessCtl.java index b094c5d6fb..9ba960834d 100644 --- a/org.adempiere.ui/src/org/compiere/apps/AbstractProcessCtl.java +++ b/org.adempiere.ui/src/org/compiere/apps/AbstractProcessCtl.java @@ -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(); diff --git a/org.idempiere.test/src/org/idempiere/test/model/ProcessTest.java b/org.idempiere.test/src/org/idempiere/test/model/ProcessTest.java new file mode 100644 index 0000000000..3d97a3c32c --- /dev/null +++ b/org.idempiere.test/src/org/idempiere/test/model/ProcessTest.java @@ -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); + } + } +}