Workflow中等待結點的推進

說明:

若Workflow Node 結點中設定等待時間. iDempiere 預設情況下,就算時間到了, 也不會將流程往前推進.
目前設計是由相關權責人員到簽核畫面去按確認才會往下走.

不過,我有一個實際的案例, 需要用到 Wait Timeout 自動往下走.

情境如下:
加班單送出申請後, 系統會自動檢查該員工是否有打下班卡,以核對加班單的有效性.
但是,真實使用情境,通常員工加班完後會先在自己的電腦操作ERP申請完加班申請, 這時候需要先等待一時間等員工離開公司時的打卡紀錄.
另外,若沒有打卡紀錄,系統會通知員工出勤紀錄有誤,再等待半天時間等員工補登.
下面流程兩紅色框起來的兩個 Node 會運用到Timeout and Next 的自動功能.

實作法法:
撰寫一個 IProcess 並安裝到 Scheduler 讓它自動執行.

package tw.ninniku.trade.process;

import java.math.BigDecimal;
import java.net.UnknownHostException;
import java.sql.*;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.Properties;
import java.util.logging.Level;

import javax.xml.bind.JAXBException;
import javax.xml.datatype.DatatypeConfigurationException;

import org.compiere.db.CConnection;
import org.compiere.model.I_M_ProductionPlan;
import org.compiere.model.MClient;
import org.compiere.model.MInOut;
import org.compiere.model.MInOutLine;
import org.compiere.model.MProductCategory;
import org.compiere.model.MProduction;
import org.compiere.model.MProductionPlan;
import org.compiere.model.MRole;
import org.compiere.model.MSysConfig;
import org.compiere.model.MUser;
import org.compiere.model.Query;
import org.compiere.process.ProcessInfoParameter;
import org.compiere.process.StateEngine;
import org.compiere.process.SvrProcess;
import org.compiere.util.AdempiereUserError;
import org.compiere.util.DB;
import org.compiere.util.EMail;
import org.compiere.util.Env;
import org.compiere.util.Msg;
import org.compiere.wf.MWFActivity;

import tw.ninniku.einvoice.A0401.A0401Builder;
import tw.ninniku.trade.model.MTradeInvoice;

/**
 * 
 * 針對 HR 模組 workflow node 去推動
 * @author Ray Lee
 *
 */
public class CheckWaitingWorkflow extends SvrProcess {

	/**	Open Activities				*/
	private MWFActivity[] 		m_activities = null;
	/**	Current Activity			*/
	private MWFActivity 		m_activity = null;
	/**	Current Activity			*/
	private int	 				m_index = 0;
	private String host = null;

	protected void prepare() {
		
		ProcessInfoParameter[] para = getParameter();
		for (int i = 0; i < para.length; i++)
		{
			String name = para[i].getParameterName();
			if ("Host".equals(name))
				host = para[i].getParameterAsString();
			else
				log.log(Level.SEVERE, "Unknown Parameter: " + name);		
		}
	}	//prepare

	@Override
	protected String doIt() throws Exception {

		String hostname;
		try {
			hostname = java.net.InetAddress.getLocalHost().getHostName();
			if(!hostname.equals(host))
				return "Non-specified host.";
			updateActivities();
		} catch (UnknownHostException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}	
		
		return "Done";
	}

	public int updateActivities()
	{
		int counter = 0;
		String sql = "select * from AD_WF_Activity aa"
				 +" where wfstate = 'OS'" 
				 + " and endwaittime < now()" 
				 + " and endwaittime is not null"
				 + " and isactive = 'Y'"
				 + " and exists ( select * from  AD_WF_Node where AD_WF_Node_id = aa.AD_WF_Node_id and action = 'Z' and waittime != 0 and EntityType = 'TG02') ";

		PreparedStatement pstmt = null;
		ResultSet rs = null;
		try
		{
			pstmt = DB.prepareStatement (sql, get_TrxName());

			rs = pstmt.executeQuery ();
			while (rs.next ())
			{
				MWFActivity activity = new MWFActivity(Env.getCtx(), rs, null);
				activity.setWFState(StateEngine.STATE_Running);
				activity.setWFState(StateEngine.STATE_Completed);
				counter++;
			}
		}
		catch (Exception e)
		{
			log.log(Level.SEVERE, sql, e);
		}
		finally
		{
			DB.close(rs, pstmt);
			rs = null; pstmt = null;
		}

		return counter;
	}	//	loadActivities	
}
Leave a comment

Workflow 人機簽核分流

Short Code:[SYSTEM]

Workflow 除了員工進行審核,確認. 許多結點(Node)也會配合自動化設備自動執待.等待系統完成工作後.自動推動Workflow.為了避免人員誤觸系統確認結點. 在結點名字定義一組 Short Code [SYSTEM], 人員簽核畫面會自動過濾, 避免誤觸.

	private String getWhereActivities() {
		final String where =
			"a.Processed='N' AND a.WFState='OS' "
			+ " AND (EXISTS (select * from AD_WF_Node wfn where "
					+ " a.AD_WF_Node_ID = wfn.AD_WF_Node_ID "
					+ " and  not ( substring(wfn.name,1,8) = '[SYSTEM]' and wfn.action = 'Z' ) ) )"
			
			+ " AND ("
			//	Owner of Activity
			+ " a.AD_User_ID=?"	//	#1
			//	Invoker (if no invoker = all)
			+ " OR EXISTS (SELECT * FROM AD_WF_Responsible r WHERE a.AD_WF_Responsible_ID=r.AD_WF_Responsible_ID"
			+ " AND r.ResponsibleType='H' AND COALESCE(r.AD_User_ID,0)=0 AND COALESCE(r.AD_Role_ID,0)=0 AND (a.AD_User_ID=? OR a.AD_User_ID IS NULL))"	//	#2
			//  Responsible User
			+ " OR EXISTS (SELECT * FROM AD_WF_Responsible r WHERE a.AD_WF_Responsible_ID=r.AD_WF_Responsible_ID"
			+ " AND r.ResponsibleType='H' AND r.AD_User_ID=?)"		//	#3
			//	Responsible Role
			+ " OR EXISTS (SELECT * FROM AD_WF_Responsible r INNER JOIN AD_User_Roles ur ON (r.AD_Role_ID=ur.AD_Role_ID)"
			+ " WHERE a.AD_WF_Responsible_ID=r.AD_WF_Responsible_ID AND r.ResponsibleType='R' AND ur.AD_User_ID=? AND ur.isActive = 'Y')"	//	#4
			///* Manual Responsible */ 
			+ " OR EXISTS (SELECT * FROM AD_WF_ActivityApprover r "
			+ " WHERE a.AD_WF_Activity_ID=r.AD_WF_Activity_ID AND r.AD_User_ID=? AND r.isActive = 'Y')" 
			+ ") AND a.AD_Client_ID=?";	//	#5
		return where;
	}
Leave a comment