当前位置: 代码迷 >> JavaScript >> 施用Nutz+ExtJS+JBPM4.4实现会签
  详细解决方案

施用Nutz+ExtJS+JBPM4.4实现会签

热度:371   发布时间:2012-07-05 07:59:17.0
使用Nutz+ExtJS+JBPM4.4实现会签

?

会签的例子依然采用Nutz+ExtJS+JBPM来实现。

?

这里只讲讲会签的实现,其他细节可以参考这篇文章http://pangwu86.iteye.com/blog/1114082

?

#######################邪恶的分割线#######################

?

首先介绍下什么是会签

?

会签

?

会签是撰拟公文的过程中,主办单位主动与有关单位协商并核签的一种办文程序,一般当公文的内容涉及本单位的多个部门或与其他单位有关时,需要进行会签。会签根据对象的不同分为内部会签和外部会签。内部会签用于与本单位内部的各有关部门进行协商并核签;外部会签用于与外单位的有关部门进行协商并核签;二者的性质相同,但处理形式不同。

在管理系统中的会签流程,例如公司职员离职、大学生毕业离校都要在不同的部门去签字确认,这里去哪个部门签字没有顺序之分,但所有部门签字完毕后才可以离职或离校。

?

?

?

会签的情况会有很多中,根据复杂程度,一般可以分为单步会签(只有一个活动处理会签任务),以及多步会签(由多个任务组成的)

?

这里只介绍下常见的,也是业务中最常遇到的单步会签。

?

?

单步会签常见有4种情况:

?

?

  1. 一票否决制――参加会签的用户中任何一个人不同意,会签活动就会结束,进去会签否决,全部同意,则进入会签通过
  2. 一票通过制――与一票否决完全相反
  3. 按比例通过――等全部参加会签的用户提交任务后,根据会签意见,按照比例(比如少数服从多数)决定下面的转移
  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;
	}

}

?

最后说一下,在会签时,要做的一些处理,这里只给思路,源码就不放了。

?

  1. 根据当前任务(子任务)获得主任务
  2. 调用CountersignInfo的sign方法,进行会签,然后完成子任务。
  3. 调用CountersignInfo.getConclusion方法,判断是否会签结束
  4. 如果会签结束,完成主任务,流程继续向下流转
1 楼 石头城 2011-08-15  
LZ,非常感谢分享。还是有点不太明白,现在主要是关于如何从子类得到父类task,
还有LZ在CountersignAssignment类中
 private List<String> getUsers() {  
        return CountersignAction.users;  
    } 
的CountersignAction是怎么得到的
希望解惑,非常感谢!
2 楼 zfh_1985 2011-08-27  
同楼上的问,正好在做这一块,希望有机会交流一下,非常感谢。
3 楼 lf84730258 2011-08-30  
代码很经典,楼主你很强,代码思路也很好.可是要想模拟你的代码了解JBPM和会签还是有难度.回个贴谢了.
4 楼 pangwu86 2011-09-06  
石头城 写道
LZ,非常感谢分享。还是有点不太明白,现在主要是关于如何从子类得到父类task,
还有LZ在CountersignAssignment类中
 private List<String> getUsers() {  
        return CountersignAction.users;  
    } 
的CountersignAction是怎么得到的
希望解惑,非常感谢!


这里的getUsers()方法的意思是你可以根据你的系统特点,通过各种方式,拿到这个用户列表就好了
CountersignAction.users 实际上就是ME在CountersignAction这个类中定义的一个String列表
例如 List<String> users = new ArrayList<String>{'user1','user2'};
5 楼 pangwu86 2011-09-06  
zfh_1985 写道
同楼上的问,正好在做这一块,希望有机会交流一下,非常感谢。

请看4楼
6 楼 pangwu86 2011-09-06  
lf84730258 写道
代码很经典,楼主你很强,代码思路也很好.可是要想模拟你的代码了解JBPM和会签还是有难度.回个贴谢了.

恩,没明白你想问什么问题
7 楼 lwei20000 2011-09-14  
您好,最近正好做关于会签的工作,您可以把上面的完整代码发一份给我吗?
由于缺少完整代码,对于刚开始写会签的代码比较困难。谢谢

我的邮箱是lwei20000@163.com

上面的代码看了,但是一些个工厂类,工具类啥的没有,所以看不太清。
8 楼 lf84730258 2011-09-20  
pangwu86 写道
lf84730258 写道
代码很经典,楼主你很强,代码思路也很好.可是要想模拟你的代码了解JBPM和会签还是有难度.回个贴谢了.

恩,没明白你想问什么问题

我参考了你的代码,之后在子流程提交这块我想获得一个监听提交的时候始终是失败的.
  相关解决方案