会签的例子依然采用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; } }
最后说一下,在会签时,要做的一些处理,这里只给思路,源码就不放了。