說明:
若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
}