Posted by: Bayu Cahya P on: August 15, 2008
Recently, within our internal testing, they are some interesting question about Adempiere workflow.
“I have followed this sample process requisition work flow, but I didn’t got an idea about how this work flow works in real case. It just set this, set that and you are done. Can somebody teach me with simple real implementation example?”
Well, I think that this question is very important, even for me. To make us focus in this issue, we are going to watch closely those sample Process Requisition work flow.
Within node DocApprove, we have action User Choice and column IsApproved_Approved. While we go further for IsApproved column, from window Table and Column, reference for this column was Yes-No (means that this column applicable for Yes or No value only).
Within our internal testing, we have userid adminidr with role admin and useridr with role user. We just create simple requisition which buy some products with total value, let say Rp 2.100.000,-. Trying to complete this document, we have message “Active Workflow for this Record exists (complete first): Suspended: (DocApprove)”.
We guess that we will use user adminidr which have role admin to complete this kind of document. Re-login Adempiere with adminidr user and open window Workflow Activities (or just switch to Workflow Activities tab). Set answer to Yes and click Ok button. Now, this records was gone from our Workflow activities window.
Seems working as expected. Let’s see our requisition within Adempiere Requisition Window. In this window, status for this requisition was complete.
“Seems good. But there are no reason, about in which condition this document will complete or not. Please help us to understand about what is the criteria to make this document complete or not”
Hmmm, from technical side, we have to debug this process to know about what exactly happen within Adempiere. Since this work flow have user choice action, we will check Adempiere org.compiere.apps.wf.WFActivity.java.
With this preliminary information, we watched actionPerformed(ActionEvent e) method. For Ok button, they called cmd_OK() method. Tracing within this methods, we got this code
// User Choice - Answer
else if (MWFNode.ACTION_UserChoice.equals(node.getAction()))
{
if (m_column == null)
m_column = node.getColumn();
// Do we have an answer?
int dt = m_column.getAD_Reference_ID();
String value = fAnswerText.getText();
if (dt == DisplayType.YesNo || dt == DisplayType.List)
{
ValueNamePair pp = (ValueNamePair)fAnswerList.getSelectedItem();
value = pp.getValue();
}
if (value == null || value.length() == 0)
{
ADialog.error(m_WindowNo, this, "FillMandatory", Msg.getMsg(Env.getCtx(), "Answer"));
trx.rollback();
trx.close();
return;
}
//
log.config("Answer=" + value + " - " + textMsg);
try
{
m_activity.setUserChoice(AD_User_ID, value, dt, textMsg);
}
catch (Exception e)
{
log.log(Level.SEVERE, node.getName(), e);
ADialog.error(m_WindowNo, this, "Error", e.toString());
trx.rollback();
trx.close();
return;
}
}
Okay, for now, debug to setUserChoice(AD_User_ID, value, dt, textMsg). Let’s see this method internally
// Approval
if (getNode().isUserApproval() && getPO(trx) instanceof DocAction)
{
DocAction doc = (DocAction)m_po;
try
{
// Not pproved
if (!"Y".equals(value))
{
newState = StateEngine.STATE_Aborted;
if (!(doc.processIt (DocAction.ACTION_Reject)))
setTextMsg ("Cannot Reject - Document Status: " + doc.getDocStatus());
}
else
{
if (isInvoker())
{
int startAD_User_ID = getAD_User_ID();
if (startAD_User_ID == 0)
startAD_User_ID = doc.getDoc_User_ID();
int nextAD_User_ID = getApprovalUser(startAD_User_ID,
doc.getC_Currency_ID(), doc.getApprovalAmt(),
doc.getAD_Org_ID(),
startAD_User_ID == doc.getDoc_User_ID()); // own doc
// No Approver
if (nextAD_User_ID <= 0)
{
newState = StateEngine.STATE_Aborted;
setTextMsg ("Cannot Approve - No Approver");
doc.processIt (DocAction.ACTION_Reject);
}
else if (startAD_User_ID != nextAD_User_ID)
{
forwardTo(nextAD_User_ID, "Next Approver");
newState = StateEngine.STATE_Suspended;
}
else // Approve
{
if (!(doc.processIt (DocAction.ACTION_Approve)))
{
newState = StateEngine.STATE_Aborted;
setTextMsg ("Cannot Approve - Document Status: " + doc.getDocStatus());
}
}
}
// No Invoker - Approve
else if (!(doc.processIt (DocAction.ACTION_Approve)))
{
newState = StateEngine.STATE_Aborted;
setTextMsg ("Cannot Approve - Document Status: " + doc.getDocStatus());
}
}
doc.save();
}
The interesting point with this code was task approval will be executed while nextAD_User_ID greater than 0.
“Go through getApprovalUser method”, said our teammates.
Okay, from our perspective, this is the essential code while trying to understand getApprovalUser method
// Get Roles of User
MRole[] roles = user.getRoles(AD_Org_ID);
for (int i = 0; i < roles.length; i++)
{
MRole role = roles[i];
if (ownDocument && !role.isCanApproveOwnDoc())
continue; // find a role with allows them to approve own
BigDecimal roleAmt = role.getAmtApproval();
if (roleAmt == null || roleAmt.signum() == 0)
continue;
if (C_Currency_ID != role.getC_Currency_ID()
&& role.getC_Currency_ID() != 0) // No currency = amt only
{
roleAmt = MConversionRate.convert(getCtx(),// today & default rate
roleAmt, role.getC_Currency_ID(),
C_Currency_ID, getAD_Client_ID(), AD_Org_ID);
if (roleAmt == null || roleAmt.signum() == 0)
continue;
}
boolean approved = amount.compareTo(roleAmt) <= 0;
log.fine("Approved=" + approved
+ " - User=" + user.getName() + ", Role=" + role.getName()
+ ", ApprovalAmt=" + roleAmt);
if (approved)
return user.getAD_User_ID();
}
Finally, we knew about how user choice workflow works. This code tells us to get role which assigned for this user. Then get amount approval from this role. Within our study case, this role was admin. And then check this role amount with our requisition amount. While amount less than or equal with our role amount than approved this document.
Recent Comments