每一个请假单对应一个审批流程。
请假单创建后,按业务规则生成部门经理、总经理审批任务。
审批任务的经办人只能审批自己辖区内的请假申请。
所有审批任务“通过”,代表请假已经批准。
任意审批任务“驳回”操作,其余审批任务取消,请假申请驳回。
请假流程中的任意节点产生的操作都要生成对应的系统通知。
打开navicat,创建adm_leave_form请假单表
创建adm_process_flow审批任务流程表
创建sys_notice消息通知表
先将实体类创建好
package com.ql.oa.entity;
import java.util.Date;
public class LeaveForm {
private Long formId;
private Long employeeId;
private Integer formType;
private Date startTime;
private Date endTime;
private String reason;
private Date createTime;
private String state;
public Long getFormId() {
return formId;
}
public void setFormId(Long formId) {
this.formId = formId;
}
public Long getEmployeeId() {
return employeeId;
}
public void setEmployeeId(Long employeeId) {
this.employeeId = employeeId;
}
public Integer getFormType() {
return formType;
}
public void setFormType(Integer formType) {
this.formType = formType;
}
public Date getStartTime() {
return startTime;
}
public void setStartTime(Date startTime) {
this.startTime = startTime;
}
public Date getEndTime() {
return endTime;
}
public void setEndTime(Date endTime) {
this.endTime = endTime;
}
public String getReason() {
return reason;
}
public void setReason(String reason) {
this.reason = reason;
}
public Date getCreateTime() {
return createTime;
}
public void setCreateTime(Date createTime) {
this.createTime = createTime;
}
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
}
package com.ql.oa.entity;
import java.util.Date;
public class ProcessFlow {
private Long processId;
private Long formId;
private Long operatorId;
private String action;
private String result;
private String reason;
private Date createTime;
private Date auditTime;
private Integer orderNo;
private String state;
private Integer isLast;
public Long getProcessId() {
return processId;
}
public void setProcessId(Long processId) {
this.processId = processId;
}
public Long getFormId() {
return formId;
}
public void setFormId(Long formId) {
this.formId = formId;
}
public Long getOperatorId() {
return operatorId;
}
public void setOperatorId(Long operatorId) {
this.operatorId = operatorId;
}
public String getAction() {
return action;
}
public void setAction(String action) {
this.action = action;
}
public String getResult() {
return result;
}
public void setResult(String result) {
this.result = result;
}
public String getReason() {
return reason;
}
public void setReason(String reason) {
this.reason = reason;
}
public Date getCreateTime() {
return createTime;
}
public void setCreateTime(Date createTime) {
this.createTime = createTime;
}
public Date getAuditTime() {
return auditTime;
}
public void setAuditTime(Date auditTime) {
this.auditTime = auditTime;
}
public Integer getOrderNo() {
return orderNo;
}
public void setOrderNo(Integer orderNo) {
this.orderNo = orderNo;
}
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
public Integer getIsLast() {
return isLast;
}
public void setIsLast(Integer isLast) {
this.isLast = isLast;
}
}
package com.ql.oa.entity;
import java.util.Date;
public class Notice {
private Long noticeId;
private Long receiverId;
private String content;
private Date createTime;
public Notice(){
}
public Notice(Long receiverId , String content){
this.receiverId = receiverId;
this.content = content;
this.createTime = new Date();
}
public Long getNoticeId() {
return noticeId;
}
public void setNoticeId(Long noticeId) {
this.noticeId = noticeId;
}
public Long getReceiverId() {
return receiverId;
}
public void setReceiverId(Long receiverId) {
this.receiverId = receiverId;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public Date getCreateTime() {
return createTime;
}
public void setCreateTime(Date createTime) {
this.createTime = createTime;
}
}
然后把每个实体类的数据交互的接口Dao创建好并填写新增数据的方法:
package com.ql.oa.dao;
import com.ql.oa.entity.LeaveForm;
public interface LeaveFormDao {
public void insert(LeaveForm form);
}
package com.ql.oa.dao;
import com.ql.oa.entity.ProcessFlow;
public interface ProcessFlowDao {
public void insert(ProcessFlow processFlow);
}
package com.ql.oa.dao;
import com.ql.oa.entity.Notice;
public interface NoticeDao {
public void insert(Notice notice);
}
然后在resources\mappers下创建mapper文件并在mybatis-config.xml里配置
DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ql.oa.dao.LeaveFormDao">
<insert id="insert" parameterType="com.ql.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>
DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ql.oa.dao.ProcessFlowDao">
<insert id="insert" parameterType="com.ql.oa.entity.ProcessFlow"
useGeneratedKeys="true" keyColumn="process_id" keyProperty="processId">
INSERT INTO adm_process_flow(form_id, operator_id, action, result, reason, create_time , audit_time , order_no , state,is_last)
VALUES (#{formId}, #{operatorId}, #{action}, #{result}, #{reason}, #{createTime} , #{auditTime} , #{orderNo} , #{state} , #{isLast});
insert>
mapper>
DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ql.oa.dao.NoticeDao">
<insert id="insert" parameterType="com.ql.oa.entity.Notice"
useGeneratedKeys="true" keyProperty="noticeId" keyColumn="notice_id">
INSERT INTO sys_notice( receiver_id, content, create_time) VALUES (#{receiverId}, #{content}, #{createTime})
insert>
mapper>
<mappers>
<mapper resource="mappers/user.xml"/>
<mapper resource="mappers/rbac.xml"/>
<mapper resource="mappers/employee.xml"/>
<mapper resource="mappers/department.xml"/>
<mapper resource="mappers/leave_form.xml"/>
<mapper resource="mappers/process_flow.xml"/>
<mapper resource="mappers/notice.xml"/>
mappers>
在EmployeeDao接口中新增一个方法,并且在相应的employee.xml中添加实现:
/**
* 根据传入员工对象获取上级主管对象
* @param employee 员工对象
* @return 上级主管对象
*/
public Employee selectLeader(@Param("emp") Employee employee);
<select id="selectLeader" parameterType="com.ql.oa.entity.Employee" resultType="com.ql.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>
在com.ql.oa.service包下创建LeaveFormService.java请假单流程业务类,并编写创建请假单的方法
/**
* 请假单流程服务
*/
public class LeaveFormService {
/**
* 创建请假单
* @param form 前端输入的请假单数据
* @return 持久化后的请假单对象
*/
public LeaveForm createLeaveForm(LeaveForm form){
LeaveForm savedForm = (LeaveForm)MyBatisUtils.executeUpdate(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级以下员工,生成部门经理审批任务,请假时间大于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;
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);
}
}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);
}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);
}
return form;
});
return savedForm;
}
}
然后在com.ql.oa.controller包下创建LeaveFormServlet并实现请假申请控制器:
@WebServlet(name = "LeaveFormServlet",urlPatterns = "/leave/*")
public class LeaveFormServlet extends HttpServlet {
private LeaveFormService leaveFormService = new LeaveFormService();
private Logger logger = LoggerFactory.getLogger(LeaveFormServlet.class);
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();
String methodName = uri.substring(uri.lastIndexOf("/")+1);
if(methodName.equals("create")){
this.create(request,response);
}
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request,response);
}
/**
* 创建请假单
* @param request
* @param response
* @throws ServletException
* @throws IOException
*/
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 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) {
logger.error("请假申请异常" ,e);
result.put("code", e.getClass().getSimpleName());
result.put("message", e.getMessage());
}
//3. 组织响应数据
String json = JSON.toJSONString(result);
response.getWriter().println(json);
}
}
在com.ql.oa.controller下新增ForwardServlet.java,实现页面跳转到对应的ftl文件
package com.ql.oa.controller;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* 页面跳转Servlet
*/
@WebServlet(name = "ForwardServlet" , urlPatterns = "/forward/*")
public class ForwardServlet extends HttpServlet {
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);
}
}
然后在webapp\resources路径下引入sweetalert2.all.min.js弹窗JS插件。
然后在src/main/webapp/WEB-INF/ftl下创建form.ftl请假表单申请页面
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>请假申请title>
<link rel="stylesheet" href="/resources/layui/css/layui.css">
<style>
/*表单容器*/
.ns-container {
position: absolute;
width: 500px;
height: 450px;
top: 150px;
left: 50%;
margin-left: -250px;
padding: 20px;
box-sizing: border-box;
border: 1px solid #cccccc;
}
style>
head>
<body>
<div class="layui-row">
<blockquote class="layui-elem-quote">
<h2>请假申请h2>
blockquote>
<table id="grdNoticeList" lay-filter="grdNoticeList">table>
div>
<div class="ns-container">
<h1 style="text-align: center;margin-bottom: 20px">请假申请单h1>
<form class="layui-form">
<div class="layui-form-item">
<label class="layui-form-label">部门label>
<div class="layui-input-block">
<div class="layui-col-md12" style="padding-top: 10px;">
${current_department.departmentName}
div>
div>
div>
<div class="layui-form-item">
<label class="layui-form-label">申请人label>
<div class="layui-input-block">
<div class="layui-col-md12" style="padding-top: 10px;">
${current_employee.name}[${current_employee.title}]
div>
div>
div>
<div class="layui-form-item">
<label class="layui-form-label">请假类别label>
<div class="layui-input-block layui-col-space5">
<select name="formType" lay-verify="required" lay-filter="cityCode">
<option value="1">事假option>
<option value="2">病假option>
<option value="3">工伤假option>
<option value="4">婚嫁option>
<option value="5">产假option>
<option value="6">丧假option>
select>
div>
div>
<div class="layui-form-item">
<label class="layui-form-label">请假时长label>
<div class="layui-input-block layui-col-space5">
<input name="leaveRange" type="text" class="layui-input" id="daterange" placeholder=" - " autocomplete="off">
<input id="startTime" name="startTime" type="hidden">
<input id="endTime" name="endTime" type="hidden">
div>
div>
<div class="layui-form-item">
<label class="layui-form-label">请假事由label>
<div class="layui-input-block layui-col-space5">
<input name="reason" type="text" lay-verify="required|mobile" placeholder="" autocomplete="off" class="layui-input">
div>
div>
<div class="layui-form-item " style="text-align: center">
<button class="layui-btn" type="button" lay-submit lay-filter="sub">立即申请button>
div>
form>
div>
<script src="/resources/layui/layui.js">script>
<script src="/resources/sweetalert2.all.min.js">script>
<script>
var layDate = layui.laydate; //Layui日期选择框JS对象
var layForm = layui.form; //layui表单对象
var $ = layui.$; //jQuery对象
//日期时间范围
layDate.render({
elem: '#daterange'
,type: 'datetime'
,range: true
,format: 'yyyy年M月d日H时'
,done: function(value, start, end){
//选择日期后触发事件,设置startTime与endTime隐藏域
var startTime = start.year + "-" + start.month + "-" + start.date + "-" + start.hours;
var endTime = end.year + "-" + end.month + "-" + end.date + "-" + end.hours;
console.info("请假开始时间",startTime);
$("#startTime").val(startTime);
console.info("请假结束时间",endTime);
$("#endTime").val(endTime);
}
});
//表单提交事件
layForm.on('submit(sub)', function(data){
console.info("向服务器提交的表单数据",data.field);
$.post("/leave/create",data.field,function (json) {
console.info("服务器返回数据",json);
if(json.code == "0"){
/*SweetAlert2确定对话框*/
swal({
type: 'success',
html: "请假单已提交,等待上级审批
",
confirmButtonText: "确定"
}).then(function (result) {
window.location.href="/forward/notice";
});
}else{
swal({
type: 'warning',
html: ""
+ json.message + "",
confirmButtonText: "确定"
});
}
},"json");
return false;
});
script>
body>
html>
然后打开navicat,打开sys_node表把菜单url填充,并打开index.ftl文件把菜单跳转路径改成动态的。
<dd class="function" data-parent-id="${node.parentId}">
<a href="${node.url}" target="ifmMain">${node.nodeName}a>
dd>
运行项目,登录后点击请假申请测试。