业务中经常会遇见需要审批的环节,所以审批流应运而生,我自己封装了一个小的审批流,比较简单,跟一些开源框架没有啥可比性,所以有许多不严谨的地方。
审批流主要涉及3张表,前台jsp代码我就不提供了,只写一些后台的核心代码。审批流核心就是用户自己设置审批流程,如图1(这里涉及2张表,一张表为审批流程模板主表,第二张表为模板主表的明细),然后根据审批流程的明细,生成单据(比如项目审批,请假审批,文件审批等等)需要审批的所有流程(第三张表,单据审批流程)。
图1
先介绍3个bean,对应3个表
模板主表,ProcessTemplate.class。get、set方法省略。
public class ProcessTemplate{
private int id;
private String name;//审批流程模板名称
private String number;//编号(唯一)
private String createDate;//创建日期
private String createdById;//创建人id
}
模板主表的明细,ProcessTemplateDtl.class。get、set方法省略。
public class ProcessTemplateDtl extends BaseInfoBean{
public final static String ROLE = "01";//审核标识
public final static String EMPLOYEE = "02";//审核标识
public final static String FUN = "03";//审核标识
private String name;//审批名称
private Integer level;//审批等级,决定审批顺序
private String auditSign;//审核标识 角色审核01 指定人审核02 回调函数 项目职务 03
private Integer roleId;//角色id
private String roleName;
private Integer employeeId;//员工id
private String employeeName;
private String interfaceName;//接口名称
private Integer templateId;//审批流程主表id
}
根据流程模板明细生成的单据审批流程。
public class AuditRecord extends BaseInfoBean{
private Integer templateId;//模版id
private Integer templateDtlId;//模板明细id
private Integer lastId;//上一条记录id
private String updateBillStatusIntf;//更新单据状态 接口名称 整个审批流程结束时调用
private String contentUrl;
private String processUrl;
private Integer level;//审批等级
private Integer roleId;//角色id
private String roleName;//角色名
private Integer employeeId;//员工id
private String employeeName;//员工名
private String billName;//项目名
private Integer billId; //项目明细Id
private String billNumber; //项目编码
private String auditStatus; //审核状态
private String auditResult; //审核结果
private String auditComments; //审核意见
private String badgePictureFile;//电子签名
private String batchNo; //批号
private Integer batchNum; //批次
private String processStatus;//审核不通过是 处理状态 在创建下一条审核记录时更新上一条记录为已审核 默认为空 ,审核不通过为0
private String fileNames;//上传文件名
private String filePaths;//上传文件路径
}
现在3个bean介绍完之后就开始按照图1,用户自定义完流程保存,我就写个save方法吧,增删改查我就不贴了。它还实现了接口ProcessTemplateIntf。
/**
*获得模版明细
*/
public interface ProcessTemplateIntf {
public List<ProcessTemplateDtl> getProcessTemplateDtl(Integer templateId);
}
public class ProcessTemplateImpl extends BaseStaticImpl implements ProcessTemplateIntf {
/**
* 实现接口,获得模板明细
*/
@Override
public List<ProcessTemplateDtl> getProcessTemplateDtl(Integer templateId) {
StringBuilder hql = new StringBuilder("select a from ProcessTemplateDtl as a where a.templateId = ").append(templateId);
//其实就是根据主表id,把所有明细查询出来
List lt = publicService.executeObjectSql(hql.toString());
return lt;
}
/**
* 保存审批模板主表和明细
*/
@Override
public boolean save(ActionForm form, Map parameterMap) {
ProcessTemplateForm userForm = (ProcessTemplateForm)form;
ProcessTemplate user = new ProcessTemplate();
try{
BeanUtils.copyProperties(user, userForm);
}catch(Exception e){
e.printStackTrace();
}
//获取流程主表的唯一number
user.setNumber(getGenerateRuleIntf().generateNumber(user.getClass().getName()));
//保存主表
publicService.saveAutoIdObject(user);
//表单提交上来,根据level来设几级审核
for(int i=0;i<userForm.getLevel().length;i++){
ProcessTemplateDtl dtl = new ProcessTemplateDtl();
dtl.setLevel(userForm.getLevel()[i]);
dtl.setName(userForm.getApprovalName()[i]);
dtl.setAuditSign(userForm.getAuditSign()[i]);
dtl.setEmployeeId(userForm.getEmployeeId()[i]);
dtl.setEmployeeName(userForm.getEmployeeName()[i]);
dtl.setRoleId(userForm.getRoleId()[i]);
dtl.setRoleName(userForm.getRoleName()[i]);
dtl.setInterfaceName(userForm.getInterfaceName()[i]);
dtl.setTemplateId(user.getId());
//保存 主表的明细表
publicService.saveAutoIdObject(dtl);
}
return true;
}
}
生成审批流的接口
public interface AuditProcessIntf {
/**
* 生成审批流
* @param context
* * billName 单据名称
* billNumber 单据编码
* updateBillStatusIntf 更新单据状态接口名称
* contentUrl 审核页面查看内容地址
* processUrl 审核不通过时重新处理的页面 url 例如 文档上传页面的url
* templateId 模版id
* lastId 上一条审核记录id 如果为空表示从第一级开始
* @return
* flowNo 流程编码
* flowId 流程 id
*/
public Map<String,String> generateAuditProcessIntf(Map<String,Object> context);
/**
* 删除所有审批流程
* @param flowNo
*/
public void deleteAuditProcessIntf(String flowNo);
}
保存审批流模板时选择审批类型,如果选的回调函数,那么就需要用到下面这个接口
public interface QueryAuditedByIntf {
/**
* 根据 审核记录 返回 审核人id;
* @param record
* @return
*/
public Integer queryAuditedBy(AuditRecord record);
}
实现这个回调函数有很多,我就举一个例子吧
public class QueryManagerImpl extends BaseStaticImpl implements
QueryAuditedByIntf {
@Override
public Integer queryAuditedBy(AuditRecord record) {
// TODO Auto-generated method stu
TaskDetails taskDtl = (TaskDetails)publicService.lookUp(TaskDetails.class,record.getBillId());
String hql="from SRProjectTeamPlayer where project.id="+ taskDtl.getSrProjectId() +" and dutiesType='02'";
List<SRProjectTeamPlayer> teamLt = publicService.executeObjectSql(hql);
if(teamLt!=null && teamLt.size()>0){
return teamLt.get(0).getPlayerId();
}
return null;
}
}
最精彩的来了,生成审批流的核心代码来了。当然说明一下,最重要的代码只有一个generateAuditProcessIntf,其他可以先无视。
public class AuditProcessImpl extends BaseStaticImpl implements
AuditProcessIntf {
//spring 依赖注入
private ProcessTemplateIntf processTemplateService;
public ProcessTemplateIntf getProcessTemplateService() {
return processTemplateService;
}
public void setProcessTemplateService(ProcessTemplateIntf processTemplateService) {
this.processTemplateService = processTemplateService;
}
/**
* 生成 该项目相关联的所有审批记录
*/
@Override
public Map<String,String> generateAuditProcessIntf(Map<String,Object> context) {
//返回值 返回一个map,key 为flowNo(审核流程唯一码)
Map<String,String> result = new HashMap<String,String>();
//接收的map参数
String billName = (String)context.get("billName");
String billNumber = (String)context.get("billNumber");
Integer templateId= (Integer)context.get("templateId");
String contentUrl = (String) context.get("contentUrl");
String processUrl = (String) context.get("processUrl");
Integer lastId = (Integer) context.get("lastId");
Integer billId = (Integer) context.get("billId");//项目id
String batchNo = (String)context.get("batchNo");
Integer batchNum = (Integer)context.get("batchNum");
String updateBillStatusIntf = (String) context.get("updateBillStatusIntf");
HttpServletRequest req=(HttpServletRequest) context.get("req");
//取得当前登录人
PersonInfo employ=(PersonInfo)req.getSession().getAttribute(GlobalStaticKeys.LOGIN_PERSONINFO);
AuditRecord last = null;
if(lastId!=null && lastId.intValue()>0){
last = (AuditRecord)publicService.lookUp(AuditRecord.class, lastId);
}
//获得审核模板明细
List<ProcessTemplateDtl> templateLt = getProcessTemplateService().getProcessTemplateDtl(templateId);
if(templateLt.size()<=0) return null;
if(last != null){//更新上级审核流程状态为 已处理
last.setProcessStatus(SysGlobalStatic.SIGN_YES);
publicService.mergeUpdate(last);
}
//生成当前所有的审核记录
// flowNo为 唯一码,用来标记唯一的审核流程。
String flowNo = getGenerateRuleIntf().generateNumber(AuditRecord.class.getName());
for(int i =0 ;i<templateLt.size();i++){
ProcessTemplateDtl dtl = templateLt.get(i);
AuditRecord record = new AuditRecord();
record.setName(dtl.getName());
record.setBillName(billName);
record.setBillNumber(billNumber);
record.setBillId(billId);
record.setTemplateId(templateId);
record.setTemplateDtlId(dtl.getId());
record.setContentUrl(contentUrl);
record.setProcessUrl(processUrl);
record.setUpdateBillStatusIntf(updateBillStatusIntf);
record.setLastId(lastId);
record.setLevel(dtl.getLevel());
record.setCreatedById(employ.getId());
record.setCreatedBy(employ.getName());
record.setCreateDate(TypeConvert.formatDate(new Date()));
record.setCreateTime(TypeConvert.formatTime(new Date()));
record.setBatchNo(batchNo);
record.setBatchNum(batchNum);
if(i==0){//默认第一条记录为 待审核
record.setAuditStatus(SysGlobalStatic.STATUS_WAIT);//状态默认为待审核
}
if(ProcessTemplateDtl.ROLE.equals(dtl.getAuditSign())){//按角色审核
record.setRoleId(dtl.getRoleId());
record.setRoleName(dtl.getRoleName());
}else if (ProcessTemplateDtl.FUN.equals(dtl.getAuditSign())){//按回调函数审核 返回人员id
QueryAuditedByIntf impl = (QueryAuditedByIntf)SpringUtil.getSpringBean(dtl.getInterfaceName());
Integer employeeId = impl.queryAuditedBy(record);
PersonInfo employee = (PersonInfo)publicService.lookUp(PersonInfo.class, employeeId);
record.setEmployeeId(employeeId);
record.setEmployeeName(employee.getName());
}else if(ProcessTemplateDtl.EMPLOYEE.equals(dtl.getAuditSign())){//按人员 审核
record.setEmployeeId(dtl.getEmployeeId());
record.setEmployeeName(dtl.getEmployeeName());
}
record.setNumber(flowNo);
//保存审核流程
publicService.saveAutoIdObject(record);
}
result.put("flowNo", flowNo);
return result;
}
/**
* 通过flowNo 删除所有的审批流程
*/
@Override
public void deleteAuditProcessIntf(String flowNo) {
StringBuilder hql = new StringBuilder("delete AuditRecord where number = '").append(flowNo).append("'");
publicService.deleteObject(hql.toString());
}
/**
* 审核记录
*/
@Override
public boolean otherOperate(ActionForm form, Map parameterMap) {
AuditRecordForm userForm = (AuditRecordForm)form;
//根据审核流程的id找到当前审核记录
AuditRecord record = (AuditRecord)publicService.lookUp(AuditRecord.class, userForm.getId());
record.setAuditDate(userForm.getAuditDate());
record.setAuditedById(userForm.getAuditedById());
record.setAuditedBy(userForm.getAuditedBy());
record.setAuditStatus(SysGlobalStatic.STATUS_ALREADY);//设置当前流程审核状态为1,代表已审核
record.setAuditResult(userForm.getAuditResult());//记录当前流程的审核结果
record.setAuditComments(userForm.getAuditComments());
//结束标志
boolean over = false;
//-- 保存附件
String vPath = this.getFilePath();
try {
this.saveAttachment(userForm, vPath,record);
} catch (Exception e) {
e.printStackTrace();
}
//审核通过
if(SysGlobalStatic.SIGN_YES.equals(record.getAuditResult())){
//--查找下一级审核记录
record.setBadgePictureFile(userForm.getBadgePictureFile());//保存签名文件
AuditRecord next = getNextAuditRecord(record.getNumber(), record.getLevel());
if(next == null){ //审核结束
over = true;
}else{
next.setAuditStatus(SysGlobalStatic.STATUS_WAIT);//跟新下级为待审核
publicService.mergeUpdate(next);
}
}else{//审核不通过 ,这个流程自己定义,是全部结束,还是本流程再审核
over = true;
record.setProcessStatus(SysGlobalStatic.STATUS_WAIT);// 跟新处理状态为 待处理
}
//更新审核流程
publicService.mergeUpdate(record);
if(over){//如果结束,这个是整个流程结束后的操作,有不通过的也算结束,这个根据业务修改
//通过srping,getbean,得到对象
UpdateBillStatusIntf impl = (UpdateBillStatusIntf)SpringUtil.getSpringBean(record.getUpdateBillStatusIntf());
List lt = getAllRecord(record.getNumber());//全部的流程记录
impl.updateBillStatus(lt, record.getAuditResult());//更新单据的状态
}
return true;
}
/**
* 查找下一条审核记录
* @param flowNo
* @param level
* @return
*/
private AuditRecord getNextAuditRecord(String flowNo,Integer level){
StringBuilder hql = new StringBuilder("select a from AuditRecord as a where a.number = '").append(flowNo).append("'");
hql.append(" and a.level>").append(level);
hql.append(" order by a.level asc ");
List lt = publicService.executeObjectSql(hql.toString(), 0, 1) ;
if(lt.size()>0 && lt.get(0)!=null){
return (AuditRecord)lt.get(0);
}
return null;
}
/**
* 得到所有审核记录
* @param flowNo
* @return
*/
private List getAllRecord(String flowNo){
StringBuilder hql = new StringBuilder("select a from AuditRecord as a where a.number = '").append(flowNo).append("'");
hql.append(" order by a.level asc ");
List lt = publicService.executeObjectSql(hql.toString()) ;
return lt;
}
}
ok 如此咱的审批流就封装完毕啦,随便搞一个测试一下,生成了审批流程之后,调用审批流程,这个测试比较简单,直接new的,用户可以根据需要自行调用
public static Map<String,String> getMap(){
AuditProcessIntf ap=new AuditProcessImpl();
Map<String,Object> context=new LinkedHashMap<String,Object>();
context.put("billName","升仙贴报单");//项目名称
context.put("billId", 111);//项目id
context.put("billNumber", "");
context.put("templateId", 7); //审批流程 模板id
context.put("contentUrl", ""); //显示列表连接
context.put("processUrl", ""); //上传文件连接
context.put("lastId", 0);
context.put("updateBillStatusIntf", "updateBillStatusService");
context.put("batchNo","ddcc");//批次号
context.put("batchNum",1);
Map<String, String> result = ap.generateAuditProcessIntf(context);
return result;
}
结果展示
AuditRecord
模板主表和明细