接上一个
开发多级审批流程
设计约束
每一个请假单对应一个审批流程
请假单创建后,按业务规则生成部门经理、总经理审批任务
审批任务的经办人只能审批自己辖区内的请假申请
所有审批任务"通过",代表请假已经批准
任意审批任务"驳回"操作,其余审批任务取消,请假申请被驳回
请假流程中任意节点产生的操作都要生成对应的系统通知
请假申请表
请假流程表
请假通知表
相关的实体类都进行创建
请假单接口的创建
public interface LeaveFormDao {
public void insert(LeaveForm form);
public List<Map> selectByParams(@Param("pf_state") String pfState,
@Param("pf_operator_id") Long operatorId);
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--namespace与包名一致-->
<mapper namespace="com.imooc.oa.dao.LeaveFormDao">
<!--id与方法名对应
parameterType与方法参数类型对应
resultType与方法返回类型对应-->
<insert id="insert" parameterType="com.imooc.oa.entity.LeaveForm"
useGeneratedKeys="true" keyProperty="formId" keyColumn="form_id">
INSERT INTO adm_leave_form(employee_id, form_type, start_time, end_time,
reason, create_time, state)
VALUES (#{employeeId}, #{formType}, #{startTime}, #{endTime},
#{reason}, #{createTime}, #{state})
</insert>
</mapper>
在配置文件中进行注册
剩下两个表的操作都一样
开发请假功能
请假单流程服务和消息提示 LeaveFormService
public LeaveForm createLeaveForm(LeaveForm form) {
LeaveForm savedForm = (LeaveForm) MybatisUtils.excuteUpdate(sqlSession -> {
//1.持久化form表单数据,8级以下员工表单状态为processing(正在审批) ,8级(总经理)状态为approved(审批通过)
EmployeeDao employeeDao = sqlSession.getMapper(EmployeeDao.class);
Employee employee = employeeDao.selectById(form.getEmployeeId());//传入员工编号 获取员工信息
// 判断请假单审批状态
if (employee.getLevel() == 8) {
form.setState("approved");//审批通过
} else {
form.setState("processing");//正在审批
}
LeaveFormDao leaveFormDao = sqlSession.getMapper(LeaveFormDao.class);
// 将当前状态写入请假表单中
leaveFormDao.insert(form);
//2.增加第一条流程数据,说明申请人的表单已提交,状态为complete(正在处理)
ProcessFlowDao processFlowDao = sqlSession.getMapper(ProcessFlowDao.class);
ProcessFlow flow1 = new ProcessFlow();
flow1.setFormId(form.getFormId());//表单编号
flow1.setOperatorId(employee.getEmployeeId());//经办人编号,这里应该是自己的编号
flow1.setAction("apply");//申请
flow1.setCreateTime(new Date());
flow1.setOrderNo(1);//任务序号
flow1.setState("complete");
flow1.setIsLast(0);//是否为最后一个流程
processFlowDao.insert(flow1);
//3.分情况创建其余流程数据
//3.1 7级以下员工,生成部门经理审批任务,请假时间大于36小时,还需生成总经理审批任务
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd-HH时");
NoticeDao noticeDao = sqlSession.getMapper(NoticeDao.class);
/*
普通员工请假
<72小时 部门经理审批
>72小时 部门经理审批通过,还需要总经理审批
*/
if (employee.getLevel() < 7) {
// 获取上级对象
Employee dmanager = employeeDao.selectLeader(employee);
// 创建部门经理审批流程
ProcessFlow flow2 = new ProcessFlow();
flow2.setFormId(form.getFormId());
flow2.setOperatorId(dmanager.getEmployeeId());
flow2.setAction("audit");//审批
flow2.setCreateTime(new Date());
flow2.setOrderNo(2);
flow2.setState("process");//正在处理
long diff = form.getEndTime().getTime() - form.getStartTime().getTime();
float hours = diff / (1000 * 60 * 60) * 1f;
// 大于72小时需要再创建一个总经理审批流程
if (hours >= BussinessConstants.MANAGER_AUDIT_HOURS) {
flow2.setIsLast(0);
processFlowDao.insert(flow2);
Employee manager = employeeDao.selectLeader(dmanager);
ProcessFlow flow3 = new ProcessFlow();
flow3.setFormId(form.getFormId());
flow3.setOperatorId(manager.getEmployeeId());
flow3.setAction("audit");//审批
flow3.setCreateTime(new Date());
flow3.setState("ready");//准备审批
flow3.setOrderNo(3);//任务序号
flow3.setIsLast(1);//为最后节点
processFlowDao.insert(flow3);
} else {
flow2.setIsLast(1);
processFlowDao.insert(flow2);
}
// 创建请假单用户的提示消息
// 请假单已提交信息
String noticeContent = String.format("您的请假申请[%s-%s]已提交,请等待上级批准.",
sdf.format(form.getStartTime()), sdf.format(form.getEndTime()));
// 接收人(提交表单的员工) 显示的消息
noticeDao.insert(new Notice(employee.getEmployeeId(), noticeContent));
// 接收人(上级) 通知部门经理审批消息
noticeContent = String.format("%s-%s提起请假申请[%s-%s],请尽快审批",
employee.getTitle(), employee.getName(), sdf.format(form.getStartTime()),
sdf.format(form.getEndTime()));
noticeDao.insert(new Notice(dmanager.getEmployeeId(), noticeContent));
} else if (employee.getLevel() == 7) {//部门经理
//3.2 7级员工,生成总经理审批任务
Employee manager = employeeDao.selectLeader(employee);
ProcessFlow flow = new ProcessFlow();
flow.setFormId(form.getFormId());
flow.setOperatorId(manager.getEmployeeId());
flow.setAction("audit");
flow.setCreateTime(new Date());
flow.setState("process");
flow.setOrderNo(2);
flow.setIsLast(1);
processFlowDao.insert(flow);
// 请假单已提交信息
String noticeContent = String.format("您的请假申请[%s-%s]已提交,请等待上级批准.",
sdf.format(form.getStartTime()), sdf.format(form.getEndTime()));
// 接收人(提交表单的员工) 显示的消息
noticeDao.insert(new Notice(employee.getEmployeeId(), noticeContent));
// 接收人(上级) 通知总经理审批消息
noticeContent = String.format("%s-%s提起请假申请[%s-%s],请尽快审批",
employee.getTitle(), employee.getName(), sdf.format(form.getStartTime()),
sdf.format(form.getEndTime()));
noticeDao.insert(new Notice(manager.getEmployeeId(), noticeContent));
} else if (employee.getLevel() == 8) {
//3.3 8级员工,生成总经理审批任务,系统自动通过
ProcessFlow flow = new ProcessFlow();
flow.setFormId(form.getFormId());
flow.setOperatorId(employee.getEmployeeId());
flow.setAction("audit");
flow.setResult("approved");
flow.setReason("自动通过");
flow.setCreateTime(new Date());
flow.setAuditTime(new Date());
flow.setState("complete");
flow.setOrderNo(2);
flow.setIsLast(1);
processFlowDao.insert(flow);
// 请假单已提交信息
String noticeContent = String.format("您的请假申请[%s-%s]系统已自动批准通过,请等待上级批准.",
sdf.format(form.getStartTime()), sdf.format(form.getEndTime()));
// 接收人(提交表单的员工) 显示的消息
noticeDao.insert(new Notice(employee.getEmployeeId(), noticeContent));
}
return form;
});
return savedForm;
}
创建一个动态SQL根据传入的员工获取对应的上级主管对象
/**
* 根据传入员工对象获取上级主管对象
* @param employee 员工对象
* @return 上级主管对象
*/
public Employee selectLeader(@Param("emp") Employee employee);
<select id="selectLeader" parameterType="com.imooc.oa.entity.Employee"
resultType="com.imooc.oa.entity.Employee">
select * from adm_employee
where
<if test="emp.level < 7">
level = 7 and department_id = #{emp.departmentId}
</if>
<if test="emp.level == 7">
level = 8
</if>
<if test="emp.level == 8">
employee_id = #{emp.employeeId}
</if>
</select>
Servlet
@WebServlet(name = "LeaveFormServlet", urlPatterns = "/leave/*")
public class LeaveFormServlet extends HttpServlet {
private LeaveFormService leaveFormService = new LeaveFormService();
private Logger logger = LoggerFactory.getLogger(LeaveFormService.class);
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException,
IOException {
request.setCharacterEncoding("utf-8");
response.setContentType("text/html;charset=utf-8");
//http://localhost/leave/create
String uri = request.getRequestURI();
// 对url进行截取
String methodName = uri.substring(uri.lastIndexOf("/") + 1);
// 创建请假表单
if (methodName.equals("create")) {
this.create(request, response);
} else if (methodName.equals("list")) {
//获取请假表单
this.getLeaveFormList(request, response);
} else if (methodName.equals("audit")) {//审核请假单
this.audit(request, response);
}
}
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException,
IOException {
this.doPost(request, response);
}
// 创建请假单
private void create(HttpServletRequest request, HttpServletResponse response) throws ServletException,
IOException {
// 1.接收各项请假单数据集
HttpSession session = request.getSession();
// 获取登录用户
// 获取用户提交的请假单数据
User user = (User) session.getAttribute("login_user");
String formType = request.getParameter("formType");
String strStartTime = request.getParameter("startTime");
String strEndTime = request.getParameter("endTime");
String reason = request.getParameter("reason");
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd-HH");
Map<String, String> result = new HashMap();
try {
LeaveForm form = new LeaveForm();
form.setEmployeeId(user.getEmployeeId());
form.setStartTime(sdf.parse(strStartTime));
form.setEndTime(sdf.parse(strEndTime));
form.setFormType(Integer.parseInt(formType));
form.setReason(reason);//请假原因
form.setCreateTime(new Date());
// 2.调用业务逻辑方法
// 创建请假单
leaveFormService.createLeaveForm(form);
result.put("code", "0");
result.put("message", "success");
} catch (Exception e) {
e.printStackTrace();
logger.error("请假申请异常", e);
result.put("code", e.getClass().getCanonicalName());
result.put("message", e.getMessage());
}
// 组织相应结果
String json = JSON.toJSONString(result);
response.getWriter().println(json);
}
/**
* 查询需要审核的请假单列表
*
* @param request
* @param response
* @throws ServletException
* @throws IOException
*/
public void getLeaveFormList(HttpServletRequest request, HttpServletResponse response) throws ServletException,
IOException {
// 获取登录用户
User user = (User) request.getSession().getAttribute("login_user");
// 获取
List<Map> formList = leaveFormService.getLeaveFormList("process", user.getEmployeeId());
Map result = new HashMap();
result.put("code", "0");
result.put("msg", "");
result.put("count", formList.size());
result.put("data", formList);
String json = JSON.toJSONString(result);
response.getWriter().println(json);
}
// 审核
public void audit(HttpServletRequest request, HttpServletResponse response) throws ServletException,
IOException {
String formId = request.getParameter("formId");
String result = request.getParameter("result");
String reason = request.getParameter("reason");
User user = (User) request.getSession().getAttribute("login_user");
Map<String, String> mpResult = new HashMap<>();
try {
leaveFormService.audit(Long.parseLong(formId), user.getEmployeeId(), result, reason);
mpResult.put("code", "0");
mpResult.put("message", "success");
} catch (Exception e) {
logger.error("请假单审核失败", e);
mpResult.put("code", e.getClass().getSimpleName());
mpResult.put("message", e.getMessage());
}
String json = JSON.toJSONString(mpResult);
response.getWriter().println(json);
}
}
前端代码就不列出来了。
为了解决页面显示问题
@WebServlet(name = "ForwardServlet", urlPatterns = "/forward/*")
public class ForwardServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException,
IOException {
String uri = request.getRequestURI();
/*
* /forward/form
* /forward/a/b/c/form
*/
String subUri = uri.substring(1);
String page = subUri.substring(subUri.indexOf("/"));
request.getRequestDispatcher(page + ".ftl").forward(request, response);
}
}
请假审批功能
首先查询自己要审批的表单条件是,审批人为自己和审批状态为processing(正在处理)
<select id="selectByParams" parameterType="java.util.Map" resultType="java.util.Map">
select f.*, e.name, d.*
from adm_leave_form f,
adm_process_flow pf,
adm_employee e,
adm_department d
where f.form_id = pf.form_id
and pf.state = #{pf_state}
and pf.operator_id = #{pf_operator_id}
and f.employee_id = e.employee_id
and e.department_id = d.department_id
</select>
public List<Map> getLeaveFormList(String pfState, Long operatorId) {
return (List<Map>) MybatisUtils.executeQuery(sqlSession -> {
LeaveFormDao dao = sqlSession.getMapper(LeaveFormDao.class);
List<Map> formList = dao.selectByParams(pfState, operatorId);//状态、经办人编号
return formList;
});
}
/**
* 查询需要审核的请假单列表
*/
public void getLeaveFormList(HttpServletRequest request, HttpServletResponse response) throws ServletException,
IOException {
User user = (User) request.getSession().getAttribute("login_user");
List<Map> formList = leaveFormService.getLeaveFormList("process", user.getEmployeeId());//当前登录的员工编号
Map result = new HashMap();
result.put("code", "0");
result.put("msg", "");
result.put("count", formList.size());
result.put("data", formList);
String json = JSON.toJSONString(result);
response.getWriter().println(json);
}
审批业务逻辑
情况1:普通员工请假,小于72小时,部门经理审批
情况2:普通员工请假,大于72小时,部门经理审批通过,总经理需要再次审批
情况3:普通员工请假,大于72小时,部门经理审批未通过,总经理不用审批
情况4:部门经理请假,总经理审批
情况5:总经理请假,直接通过
/**
* 修改流程表审批状态、申请表状态、和通知信息
* @param formId 申请表ID
* @param operatorId 经办人编号
* @param result approved-同意 refused-驳回
* @param reason 审批意见
*/
public void audit(Long formId, Long operatorId, String result, String reason) {
MybatisUtils.excuteUpdate(sqlSession -> {
//1.无论同意/驳回,当前任务状态变更为complete(处理完成).
ProcessFlowDao processFlowDao = sqlSession.getMapper(ProcessFlowDao.class);
// 根据对应的表单号查询该表单对应的处理流程顺序信息
List<ProcessFlow> flowList = processFlowDao.selectByFormId(formId);
if (flowList.size() == 0) {
throw new BussinessException("PF001", "无效的审批流程");
}
// process 正在处理 把处理过程为process的提取出来
List<ProcessFlow> processList =
flowList.stream().filter(p -> p.getOperatorId().equals(operatorId) && p.getState().equals(
"process")).collect(Collectors.toList());
ProcessFlow process = null;
if (processList.size() == 0) {
throw new BussinessException("PF002", "未找到待处理任务");
} else {
process = processList.get(0);
process.setState("complete");
process.setResult(result);// 设置同意还是驳回
process.setReason(reason);// 设置审批意见
process.setAuditTime(new Date());
processFlowDao.update(process);
}
//2.如果当前任务是最后一个节点,代表流程结束,更新请假单状态为对应的approved/refused
LeaveFormDao leaveFormDao = sqlSession.getMapper(LeaveFormDao.class);
// 查询请假表内容
LeaveForm form = leaveFormDao.selectById(formId);
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd-HH时");
EmployeeDao employeeDao = sqlSession.getMapper(EmployeeDao.class);
Employee employee = employeeDao.selectById(form.getEmployeeId());
Employee operator = employeeDao.selectById(operatorId);
NoticeDao noticeDao = sqlSession.getMapper(NoticeDao.class);
// 当前为最后一个流程 代表流程结束,更新请假表单状态对应的approved/refused
if (process.getIsLast() == 1) {
form.setState(result);// 通过/拒绝
leaveFormDao.update(form);
String strResult = null;
if (result.equals("approved")) {
strResult = "批准";
} else if (result.equals("refused")) {
strResult = "驳回";
}
// 起始时间-结束时间-经办人职务-经办人名字-批准/驳回-审批意见
String noticeContent = String.format("您的请假申请[%s-%s]%s%s已%s,审批意见:%s,审批流程已结束",
sdf.format(form.getStartTime()), sdf.format(form.getEndTime()), operator.getTitle(),
operator.getName(), strResult, reason);// 发给表单提交人
noticeDao.insert(new Notice(form.getEmployeeId(), noticeContent));
// 职务-员工-起始时间-结束时间-审核/驳回-审批意见
noticeContent = String.format("%s-%s提起的请假申请[%s-%s]您已%s,审批意见:%s,审批流程已结束",
employee.getTitle(), employee.getName(), sdf.format(form.getStartTime()),
sdf.format(form.getEndTime()),
strResult, reason);//发给审批人的通知
noticeDao.insert(new Notice(operator.getEmployeeId(), noticeContent));
} else {// 不是最后一个流程
// readyList包含所有的后序节点
List<ProcessFlow> readyList =
flowList.stream().filter(p -> p.getState().equals("ready")).collect(Collectors.toList());
//3.如果当前任务不是最后一个节点且审批通过,那下一个节点的状态从ready变为process
if (result.equals("approved")) {//同意
ProcessFlow readyProcess = readyList.get(0);
readyProcess.setState("process");
processFlowDao.update(readyProcess);
// 消息1:通知表单提交人,部门经理已经审批通过,交由上级处理
String noticeContent1 = String.format("您的请假申请[%s-%s]%s%s已批准,审批意见:%s,请等待上级领导审批",
sdf.format(form.getStartTime()), sdf.format(form.getEndTime()),
operator.getTitle(), operator.getName(), reason);
noticeDao.insert(new Notice(form.getEmployeeId(), noticeContent1));
// 消息2:通知总经理有新的审批任务
String noticeContent2 = String.format("%s-%s提起请假申请[%s-%s],请尽快审批",
employee.getTitle(), employee.getName(), sdf.format(form.getStartTime()),
sdf.format(form.getEndTime()));
noticeDao.insert(new Notice(readyProcess.getOperatorId(), noticeContent2));
// 消息3:通知部门经理(当前经办人),员工的申请单你已批准,交由上级继续审批
String noticeContent3 = String.format("%s-%s提起的请假申请[%s-%s]您已批准,审批意见:%s,请等待上级领导审批",
employee.getTitle(), employee.getName(), sdf.format(form.getStartTime()),
sdf.format(form.getEndTime()),
reason);
noticeDao.insert(new Notice(operator.getEmployeeId(), noticeContent3));
} else if (result.equals("refused")) {// 驳回
//4.如果当前任务不是最后一个节点且审批驳回,则后续所有任务状态变为cancel,请假单状态变为refused
for (ProcessFlow p :
readyList) {
p.setState("cancel");
processFlowDao.update(p);
}
// 表单设置为被拒绝
form.setState("refused");
leaveFormDao.update(form);
// 消息1:通知申请人表单已经被驳回
String noticeContent1 = String.format("您的请假申请[%s-%s]%s%s已驳回,原因是%s",
sdf.format(form.getStartTime()), sdf.format(form.getEndTime()),
operator.getTitle(), operator.getName(), reason);
noticeDao.insert(new Notice(form.getEmployeeId(), noticeContent1));
//消息2:通知经办人表单"您已驳回"
String noticeContent3 = String.format("%s-%s提起的请假申请[%s-%s]您已驳回,审批意见%s,审批流程已结束",
employee.getTitle(), employee.getName(), sdf.format(form.getStartTime()),
sdf.format(form.getEndTime()),
reason);
noticeDao.insert(new Notice(operator.getEmployeeId(), noticeContent3));
}
}
return null;
});
}