会签的例子依然采用Nutz+ExtJS+JBPM来实现。
这里只讲讲会签的实现,其他细节可以参考这篇文章http://pangwu86.iteye.com/blog/1114082
#######################邪恶的分割线#######################
首先介绍下什么是会签
会签
会签是撰拟公文的过程中,主办单位主动与有关单位协商并核签的一种办文程序,一般当公文的内容涉及本单位的多个部门或与其他单位有关时,需要进行会签。会签根据对象的不同分为内部会签和外部会签。内部会签用于与本单位内部的各有关部门进行协商并核签;外部会签用于与外单位的有关部门进行协商并核签;二者的性质相同,但处理形式不同。
在管理系统中的会签流程,例如公司职员离职、大学生毕业离校都要在不同的部门去签字确认,这里去哪个部门签字没有顺序之分,但所有部门签字完毕后才可以离职或离校。
会签的情况会有很多中,根据复杂程度,一般可以分为单步会签(只有一个活动处理会签任务),以及多步会签(由多个任务组成的)
这里只介绍下常见的,也是业务中最常遇到的单步会签。
单步会签常见有4种情况:
- 一票否决制——参加会签的用户中任何一个人不同意,会签活动就会结束,进去会签否决,全部同意,则进入会签通过
- 一票通过制——与一票否决完全相反
- 按比例通过——等全部参加会签的用户提交任务后,根据会签意见,按照比例(比如少数服从多数)决定下面的转移
- 意见收集制——等全部参加会签的用户提交完意见后(这里就是一个收集意见的作用而已),会签结束,进入下一个节点
这里要说一下,在查找会签的资料时,yy269兄的http://yy629.iteye.com/blog/660701与phoenix.clt兄的http://phoenix-clt.iteye.com/blog/428242这两篇文章给了很大的启发,后面的实现也借鉴了他们的一些好的思想,需要的朋友可以去看一看。
下面的部分将会讲述如何实现一个动态会签(会签人数,人员,会签规则都可以自由设定)
好,接下来看一下今天举得这个申请经费的例子:
贴一下jpdl.xml文件
<?xml version="1.0" encoding="UTF-8"?> <process name="jingfeishenqing" xmlns="http://jbpm.org/4.4/jpdl"> <description><![CDATA[ 经费申请,大于30万需要老总们会签(采用一票否决制) ]]></description> <!-- 会签决策实现类 --> <variable name="calc.countersignCalculatorImpl" type="string"> <string value="org.nutz.jbpm.countersign.impl.AllAgreeSign" /> </variable> <!-- 以下三个参数只有按百分比策略才会用到,可以根据需要来设定 --> <!-- 最小同意数 --> <variable name="calc.minAgreeSize" type="int"> <int value="2"/> </variable> <!-- 最小同意比例(请设定一个在0-1之间的数字) --> <variable name="calc.minAgreePercent" type="float"> <float value="0.6"/> </variable> <!-- 是否使用按比例(true为按照比例,false为按照人数) --> <variable name="calc.userPercent" type="boolean"> <false/> </variable> <start g="81,8,48,48" name="start1"> <transition g="-60,13" name="申请经费" to="申请经费" /> </start> <task assignee="${employee}" form="countersign/applyfor.jsp" g="304,65,92,52" name="申请经费"> <transition name="金额判断" to="判断1" g="-69,-22" /> </task> <decision expr="#{money > 300000 ? '大于30万' : '小于等于30万'}" g="190,144,48,48" name="判断1"> <transition g="-39,14" name="大于30万" to="领导会签" /> <transition g="-80,-21" name="小于等于30万" to="财务部拨款" /> </decision> <task form="countersign/countersign.jsp" g="302,183,92,52" name="领导会签"> <assignment-handler class="org.nutz.jbpm.countersign.CountersignAssignment"> </assignment-handler> <transition g="-79,-11" name="会签结果判断" to="会签判断" /> </task> <decision expr="#{agree == true ? '同意' : '不同意'}" g="325,293,48,48" name="会签判断"> <transition g="-13,-26" name="同意" to="财务部拨款" /> <transition g="-50,-22" name="不同意" to="申请驳回" /> </decision> <task g="68,291,92,52" name="财务部拨款"> <transition g="-33,-10" name="拨款" to="拨款完毕" /> </task> <end g="91,400,48,48" name="拨款完毕" /> <end g="327,398,48,48" name="申请驳回" /> </process>
可以说会签的难点就是如何在一个任务中,实现由多个人去完成。
JBPM4中提供了建立子任务的API,这一点就提供了一个思路.
那就是在进入这个任务后,给相应的会签人员建立对应的子任务,然后等待他们完成子任务后,该会签任务设定为完成,流程向下流转。
下面这段配置就是会签任务,可以看到,与普通task不同的是,添加一个assignment-handler属性,同时指定了一个java类
<task form="countersign/countersign.jsp" g="302,183,92,52" name="领导会签"> <assignment-handler class="org.nutz.jbpm.countersign.CountersignAssignment"> </assignment-handler> <transition g="-79,-11" name="会签结果判断" to="会签判断" /> </task>
这里的作用就是,在流程进入到这个节点时,会执行你指定的这个类
这个类的要求是,实现AssignmentHandler接口
public class CountersignAssignment implements AssignmentHandler
查看这个接口,会发现需呀实现一个assign方法
/** interface to delegate {@link Task} or {@link Swimlane} assignment.
*
* @author Tom Baeyens
*/
public interface AssignmentHandler extends Serializable {
/** sets the actorId and candidates for the given task. */
void assign(Assignable assignable, OpenExecution execution) throws Exception;
}
下面给出ME的一个实现:
public class CountersignAssignment implements AssignmentHandler {
public void assign(Assignable assignable, OpenExecution execution) throws Exception {
// 获得实例ID
String pid = execution.getProcessInstance().getId();
// 获得当前的主任务
Task task = JbpmUtil.taskService.createTaskQuery().processInstanceId(pid)
.activityName(execution.getName()).uniqueResult();
// 创建子任务
createSubTasks(task);
// 获得会签决策(通过jpdl配置文件中配置的属性)
String className = (String) JbpmUtil.taskService.getVariable(task.getId(),
"calc.countersignCalculatorImpl");
// 通过会签决策工厂生产获得决策对象
CountersignCalculator calculator = CountersignCalculatorFactory
.makeCountersignCalculator(className);
// 如果是按比例通过的话,需要设定通过人数,或者比例
SetCalculatorVars(calculator, task.getId());
// TODO 这里用户采用一个模拟的方式,以后可以通过要整合的系统提供的API 获得用户名字列表
CountersignInfo countersignInfo = CountersignInfoFactory.makeCountersignInfo(getUsers(),
calculator);
// 记录当前的主任务与会签信息
Map<String, Object> vars = new HashMap<String, Object>();
vars.put("parentTaskId", task.getId());
vars.put("countersignInfo", countersignInfo);
JbpmUtil.taskService.setVariables(task.getId(), vars);
return;
}
private void SetCalculatorVars(CountersignCalculator calculator, String taskId) {
// 只有按比例会签决策需要设定参数
if (calculator instanceof PercentAgreeSign) {
// 获取参数
Object minAgreeSize = JbpmUtil.taskService.getVariable(taskId, "calc.minAgreeSize");
Object minAgreePercent = JbpmUtil.taskService.getVariable(taskId,
"calc.minAgreePercent");
Object userPercent = JbpmUtil.taskService.getVariable(taskId, "calc.userPercent");
// 设定参数
PercentAgreeSign calc = (PercentAgreeSign) calculator;
if (null != minAgreeSize) {
calc.setMinAgreeSize((Integer) minAgreeSize);
}
if (null != minAgreePercent) {
calc.setMinAgreePrecent((Float) minAgreePercent);
}
if (null != userPercent) {
calc.setUserPercent((Boolean) userPercent);
}
}
}
private void createSubTasks(Task task) {
// OpenTask才有createSubTask方法,Task接口是没有的
OpenTask oTask = (OpenTask) task;
// 这个对象非常重要,没有它,通过子任务无法跟主任务获得联系
Execution execution = JbpmUtil.executionService.findExecutionById(task.getExecutionId());
// 获得所有的参与者
for (String assignee : getUsers()) {
TaskImpl subTask = (TaskImpl) oTask.createSubTask();
subTask.setAssignee(assignee);
subTask.setName(task.getName());
subTask.setFormResourceName(task.getFormResourceName());
// 这句话是关键 只有设定同样的实例 子任务才能获得主任务设定的变量
subTask.setExecution((ExecutionImpl) execution);
JbpmUtil.taskService.addTaskParticipatingUser(task.getId(), assignee,
Participation.CLIENT);
}
}
private List<String> getUsers() {
return CountersignAction.users;
}
}
代码中注释解释的很详细了,这里就不再复述了,这段代码相关参数,可以参照上面的jpdl.xml中的变量设置。
下面介绍下上面代码中的CountersignInfo类与CountersignCalculator类
CountersignInfo
/**
* 会签信息
*
*/
public interface CountersignInfo extends Serializable {
/**
* 用户下达会签结论
*
* @param user
* @param conclusion
* @return
*/
public boolean sign(String user, Conclusion conclusion);
/**
* 获取会签人员列表
*
* @return
*/
public List<String> getUsers();
/**
* 获取会签会议结论
*
* @return
*/
public Conclusion getConclusion();
/**
* 是否全部人员都已签完
*
* @return
*/
public boolean isAllSigned();
/**
* 获取特定用户的会签结论
*
* @param userId
* @return
*/
public Conclusion getUserConclusion(String userId);
/**
* 获取所有用户的会签结论
*
* @return
*/
public Map<String, Conclusion> getConclusions();
/**
* 获取会签结论计算方式
*
* @return
*/
public CountersignCalculator getConclusionCalculator();
}
可以通过这个API获得当前会签的所有相关信息。
CountersignCalculator
/**
* 会签决策
*
*/
public interface CountersignCalculator extends Serializable {
/**
* 计算会签结果
*
* @param info
* @return
*/
public Conclusion calculate(CountersignInfo info);
}
这里就只有一个方法,就是通过会签信息,计算出当前的会签结果,这个接口的实现类,你就可以在里面根据业务来进行实现,比如说实现上面提到的单步会签的常见的四种情况(后面会给出实现类)。
还有一个会签结果的枚举类,这个可以根据你的需要自己定制,这里给出ME的方案
Conclusion
/**
* 会议结论定义
*
*/
public enum Conclusion implements Serializable {
// 通过
AGREE,
// 否决
DENY,
// 弃权
ABSTAIN,
// 继续(会签没有结束)
CONTINUE;
public static Conclusion getConclusion(String conclusion) {
if (AGREE.toString().equals(conclusion)) {
return Conclusion.AGREE;
} else if (DENY.toString().equals(conclusion)) {
return Conclusion.DENY;
} else if (ABSTAIN.toString().equals(conclusion)) {
return Conclusion.ABSTAIN;
} else if (CONTINUE.toString().equals(conclusion)) {
return Conclusion.CONTINUE;
}
return Conclusion.AGREE;
}
}
下面给出几个实现类,给大家做个参考:
首先是会签决策实现类:
一票否决制
/**
* 一票否决制(全票通过制)
*
*/
public class AllAgreeSign implements CountersignCalculator {
public Conclusion calculate(CountersignInfo info) {
// 是否有否决票
for (Entry<String, Conclusion> entry : info.getConclusions().entrySet()) {
if (entry.getValue() == Conclusion.DENY) {
// 一旦出现否决票,立刻作为否决处理
return Conclusion.DENY;
}
}
if (info.isAllSigned()) {
// 所有票投完了
return Conclusion.AGREE;
}
return Conclusion.CONTINUE;
}
public String toString() {
return "采用一票否决制,只要有一票否决,则会签不通过。";
}
}
同理大家可以如法炮制一票通过制。
至于按比例,按人数是如何实现的,这里就不给出例子了,留给大家留一个思考的空间。
下面是会签信息的实现类:
默认会签信息实现
public class CountersignInfoDefaultImpl implements CountersignInfo {
private List<String> users;
private boolean isAllSigned = false;
private Map<String, Conclusion> results = new HashMap<String, Conclusion>();
private CountersignCalculator countersignCalculator;
public CountersignInfoDefaultImpl(List<String> users,
CountersignCalculator countersignCalculator) {
this.users = users;
this.countersignCalculator = countersignCalculator;
}
/**
* 用户提交决策
*
* @param user
* @param conclusion
* @return
*/
public synchronized boolean sign(String user, Conclusion conclusion) {
if (!users.contains(user)) {
return false;
}
results.put(user, conclusion);
if (results.size() == users.size()) {
isAllSigned = true;
}
return true;
}
// *********接口实现***********
public List<String> getUsers() {
// 不允许修改
return Collections.unmodifiableList(users);
}
public Conclusion getConclusion() {
return countersignCalculator.calculate(this);
}
public boolean isAllSigned() {
return isAllSigned;
}
public Conclusion getUserConclusion(String userId) {
return results.get(userId);
}
public Map<String, Conclusion> getConclusions() {
return Collections.unmodifiableMap(results);
}
public CountersignCalculator getConclusionCalculator() {
return countersignCalculator;
}
}
最后说一下,在会签时,要做的一些处理,这里只给思路,源码就不放了。
- 根据当前任务(子任务)获得主任务
- 调用CountersignInfo的sign方法,进行会签,然后完成子任务。
- 调用CountersignInfo.getConclusion方法,判断是否会签结束
- 如果会签结束,完成主任务,流程继续向下流转