第四天: 考勤管理
任务
每个人登录OA之后, 要有考勤管理一级菜单
点开之后有个人考勤 我的排班表 考勤统计3个二级菜单
个人考勤
显示当天的打卡记录 每条记录 最右边有打卡按钮
业务流程:
用户点击菜单上的个人考勤
从数据库获取当天的打卡情况
service层的方法:
@Override
public UserAttendVo getAttend(HttpSession session) {
User user = (User) session.getAttribute("user");
UserAttendVo userAttendVo = new UserAttendVo();
userAttendVo.setUserid(user);
UserAttendExample userAttendExample = new UserAttendExample();
Criteria uaCriteria = userAttendExample.createCriteria();
uaCriteria.andUseridEqualTo(user.getUserid());
//获取用户排版表
List<UserAttend> userattendList = uam.selectByExample(userAttendExample);
if (userattendList == null || userattendList.size() == 0) {
return null;
}
UserAttend userAttend = userattendList.get(0);
//获取该用户的具体班次信息
Attendconfig attendConfig = acm.selectByPrimaryKey(userAttend.getDutytype());
userAttendVo.setDutytype(attendConfig);
AttenddutyExample attenddutyExample = new AttenddutyExample();
cn.zzpigt.bean.AttenddutyExample.Criteria adCriteria = attenddutyExample.createCriteria();
adCriteria.andUseridEqualTo(user.getUserid());
//获取用户所有的打卡记录
List<Attendduty> attenddutyList = adm.selectByExample(attenddutyExample);
// 获取今天的打卡记录
List<Attendduty> myList = new ArrayList<>();
for (Attendduty attendduty : attenddutyList) {
Date currentDate = new Date();
SimpleDateFormat day = new SimpleDateFormat("yyyy-MM-dd");
String today = day.format(currentDate);
String allDay = day.format(attendduty.getRegistertime());
if (today.equals(allDay)) {
myList.add(attendduty);
}
}
// 按照上下班顺序排序
Collections.sort(myList, new Comparator<Attendduty>() {
@Override
public int compare(Attendduty o1, Attendduty o2) {
return Integer.parseInt(o1.getRegistertype()) - Integer.parseInt(o2.getRegistertype());
}
});
System.out.println(myList);
userAttendVo.setAttenddutyList(myList);
return userAttendVo;
}
前端获取到之后进行显示,用到了<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn"%>这个标签,用于判断list的size大小,以便决定哪一行添加打卡按钮
http://www.cnblogs.com/henuyuxiang/p/6211208.html
前端页面:
<table
class="table table-condensed table-striped table-bordered table-hover">
<tr class="success">
<td>班次类型</td>
<td>登记类型</td>
<td>规定时间</td>
<td>开始登记时间</td>
<td>结束登记时间</td>
<td>实际登记时间</td>
<td>备注</td>
<td>操作</td>
</tr>
<c:if test="${uavo == null}">
<tr>
<td>没有该用户的排班!!!</td>
</tr>
</c:if>
<c:if test="${empty (uavo.dutytype.dutytime3) && uavo != null}">
<tr>
<td>${uavo.dutytype.dutyname}</td>
<td>上班</td>
<td>${uavo.dutytype.dutytime1}</td>
<td>08:00:00</td>
<td>10:00:00</td>
<!-- 格式化时间 -->
<td><fmt:formatDate
value="${uavo.attenddutyList[0].registertime}"
pattern="yyyy-MM-dd HH:mm:ss" /></td>
<td>${uavo.attenddutyList[0].remark}</td>
<!-- 决定这一行是否有打卡按钮,根据list的size判断(数据库有几条记录) -->
<td><c:if
test="${empty uavo.attenddutyList[0].registertime && fn:length(uavo.attenddutyList)==0}">
<!-- 打卡按钮会携带这个参数带到控制层 -->
<input type="hidden" name="type" value="1" />
<input type="button" id="daka" value="打卡" />
</c:if></td>
<td></td>
</tr>
<tr>
<td>${uavo.dutytype.dutyname}</td>
<td>下班</td>
<td>${uavo.dutytype.dutytime2}</td>
<td>16:30:00</td>
<td>18:30:00</td>
<td><fmt:formatDate
value="${uavo.attenddutyList[1].registertime}"
pattern="yyyy-MM-dd HH:mm:ss" /></td>
<td>${uavo.attenddutyList[1].remark}</td>
<td><c:if
test="${empty uavo.attenddutyList[1].registertime && fn:length(uavo.attenddutyList)==1}">
<input type="hidden" name="type" value="2" />
<input type="button" id="daka" value="打卡" />
</c:if></td>
</tr>
</c:if>
<!-- 两头班 -->
<c:if test="${!empty (uavo.dutytype.dutytime3)}">
<tr>
<td>${uavo.dutytype.dutyname}</td>
<td>上班</td>
<td>${uavo.dutytype.dutytime1}</td>
<td>7:00:00</td>
<td>9:00:00</td>
<td><fmt:formatDate
value="${uavo.attenddutyList[0].registertime}"
pattern="yyyy-MM-dd HH:mm:ss" /></td>
<td>${uavo.attenddutyList[0].remark}</td>
<td><c:if
test="${empty uavo.attenddutyList[0].registertime && fn:length(uavo.attenddutyList)==0}">
<input type="hidden" name="type" value="1" />
<input type="button" id="daka" value="打卡" />
</c:if></td>
<tr>
<td>${uavo.dutytype.dutyname}</td>
<td>下班</td>
<td>${uavo.dutytype.dutytime2}</td>
<td>11:00:00</td>
<td>13:00:00</td>
<td><fmt:formatDate
value="${uavo.attenddutyList[1].registertime}"
pattern="yyyy-MM-dd HH:mm:ss" /></td>
<td>${uavo.attenddutyList[1].remark}</td>
<td><c:if
test="${empty uavo.attenddutyList[1].registertime && fn:length(uavo.attenddutyList)==1}">
<input type="hidden" name="type" value="2" />
<input type="button" id="daka" value="打卡" />
</c:if></td>
</tr>
<tr>
<td>${uavo.dutytype.dutyname}</td>
<td>上班</td>
<td>${uavo.dutytype.dutytime3}</td>
<td>16:30:00</td>
<td>18:30:00</td>
<td><fmt:formatDate
value="${uavo.attenddutyList[2].registertime}"
pattern="yyyy-MM-dd HH:mm:ss" /></td>
<td>${uavo.attenddutyList[2].remark}</td>
<td><c:if
test="${empty uavo.attenddutyList[2].registertime && fn:length(uavo.attenddutyList)==2}">
<input type="hidden" name="type" value="3" />
<input type="button" id="daka" value="打卡" />
</c:if></td>
</tr>
<tr>
<td>${uavo.dutytype.dutyname}</td>
<td>下班</td>
<td>${uavo.dutytype.dutytime4}</td>
<td>21:00:00</td>
<td>23:00:00</td>
<td><fmt:formatDate
value="${uavo.attenddutyList[3].registertime}"
pattern="yyyy-MM-dd HH:mm:ss" /></td>
<td>${uavo.attenddutyList[3].remark}</td>
<td><c:if
test="${empty uavo.attenddutyList[3].registertime && fn:length(uavo.attenddutyList)==3}">
<input type="hidden" name="type" value="4" />
<input type="button" id="daka" value="打卡" />
</c:if></td>
</tr>
</c:if>
</table>
点击打卡按钮触发时间,
发送ajax请求到后端
<script type="text/javascript">
$(document).ready(function() {
$("#daka").click(function() {
$.ajax({
type : "post",
data : {"type" : $(this).prev().val()},
dataType : "json",
url : "${pageContext.request.contextPath}/Attend/sign.action",
success : function(data) {
alert(data);
window.location.href = "${pageContext.request.contextPath}/Attend/list.action";
}
});
})
});
</script>
后端控制层:
@RequestMapping(value="/sign.action", produces="application/json; charset=utf-8" )
@ResponseBody
public String sign(HttpSession session,HttpServletRequest request){
try {
//打卡类型
String registerType = request.getParameter("type");
System.out.println("类型"+registerType);
//传了用户id和type进来
as.sign(session,registerType);
} catch (Exception e) {
String json = new Gson().toJson(e.getMessage());
return json;
}
String json = new Gson().toJson("打卡成功");
return json;
}
调用service层:
由于数据库数据的特殊性:
1.general字段存放的是双休日,所以获取到数据时要用字符串分割的方式进行循环比较
2.dutyTime字段名的特殊性,采用反射的方式调用get方法获取要求打卡时间
@Override
public void sign(HttpSession session, String type) throws Exception {
// 周末周日排除
Date currentDate = new Date();
SimpleDateFormat hour = new SimpleDateFormat("HH:mm:ss");
// 用id查班次类型
User user = (User) session.getAttribute("user");
UserAttendExample userAttendExample = new UserAttendExample();
Criteria uaCriteria = userAttendExample.createCriteria();
uaCriteria.andUseridEqualTo(user.getUserid());
// 联合主键,虽然是一对一,但是不能用查主键的方式
List<UserAttend> userattendList = uam.selectByExample(userAttendExample);
// 检查班次是否存在,没有说明,没有该用户的排班,前端已做处理
if (userattendList == null || userattendList.size() == 0) {
System.out.println("没有该用户的排班");
throw new Exception("没有该用户的排班");
}
// 根据id拿到签到表
AttenddutyExample attenddutyExample = new AttenddutyExample();
cn.zzpigt.bean.AttenddutyExample.Criteria adCriteria = attenddutyExample.createCriteria();
adCriteria.andUseridEqualTo(user.getUserid());
Integer userattend = userattendList.get(0).getDutytype();
Attendconfig attendconfig = acm.selectByPrimaryKey(userattend);
String[] split = attendconfig.getGeneral().split(",");
// 根据attendconfig表数据库general字段判断休息日
Calendar today = Calendar.getInstance();
today.setTime(currentDate);
for (int i = 0; i < split.length; i++) {
if (today.get(Calendar.DAY_OF_WEEK) != Integer.parseInt(split[i]) + 1) {
throw new Exception("本日为假期!");
}
}
//反射调用set方法 datetime1,2,3,4
Method method = attendconfig.getClass().getDeclaredMethod("getDutytime" + type);
String dutyTime = (String) method.invoke(attendconfig);
Attendduty attendduty = new Attendduty();
// 要求时间
String todayString = hour.format(currentDate);
int intduty = Integer.parseInt(dutyTime.replace(":", ""));
int intcurrent = Integer.parseInt(todayString.replace(":", ""));
System.out.println(intduty);
System.out.println(intcurrent);
System.out.println(intduty + 10000);
// 上班
if (type.equals("1") || type.equals("3")) {
if (intduty + 10000 > intcurrent && intduty < intcurrent) {
System.out.println("迟到");
attendduty.setRemark("迟到");
}
}
// 下班
else if (type.equals("2") || type.equals("4")) {
if (intduty - 10000 > intcurrent && intduty > intcurrent) {
System.out.println("早退");
attendduty.setRemark("早退");
}
} else {
throw new Exception("打卡类型异常");
}
if (intduty - 10000 > intcurrent || intduty + 10000 < intcurrent) {
throw new Exception("打卡时间异常");
}
attendduty.setDutytype(userattend);
attendduty.setRegistertype(type);
attendduty.setRegistertime(currentDate);
attendduty.setUserid(user.getUserid());
adm.insertSelective(attendduty);
}
我的排班表
显示当月的排班表
业务流程:用户点击菜单上的排班表
从数据库获取当月的排版情况
返回的是一个list
控制层方法:
@Override
public List<AttendconfigVo> getAttendConfig(HttpSession session) {
// 拿到当前用户的id去查到dutytypeid(user_attend表)
User user = (User) session.getAttribute("user");
UserAttendExample userAttendExample = new UserAttendExample();
Criteria userAttendCriteria = userAttendExample.createCriteria();
userAttendCriteria.andUseridEqualTo(user.getUserid());
// 拿到了dutytypeid去拿排版信息
List<UserAttend> ualist = uam.selectByExample(userAttendExample);
Attendconfig config = null;
if (ualist == null || ualist.size() <= 0) {
return null;
}
System.out.println(ualist);
config = acm.selectByPrimaryKey(ualist.get(0).getDutytype());
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
Calendar cal_1 = Calendar.getInstance();// 获取当前日期
cal_1.add(Calendar.MONTH, 0);
cal_1.set(Calendar.DAY_OF_MONTH, 1);// 设置为1号,当前日期既为本月第一天
// 获取前月的最后一天
Calendar cale = Calendar.getInstance();
cale.set(Calendar.DAY_OF_MONTH, cale.getActualMaximum(Calendar.DAY_OF_MONTH));
Calendar calBegin = Calendar.getInstance();
// 使用给定的 Date 设置此 Calendar 的时间
calBegin.setTime(cal_1.getTime());
Calendar calEnd = Calendar.getInstance();
// 使用给定的 Date 设置此 Calendar 的时间
calEnd.setTime(cale.getTime());
// 测试此日期是否在指定日期之后
List<AttendconfigVo> list = new ArrayList<>();
String[] split = config.getGeneral().split(",");
// 本月第一天,当时获取一个月所有的天就是拿不到第一天啊啊啊啊啊啊
//去除休息天
if (calBegin.get(Calendar.DAY_OF_WEEK) != Integer.parseInt(split[0]) + 1
&& calBegin.get(Calendar.DAY_OF_WEEK) != Integer.parseInt(split[1]) + 1) {
AttendconfigVo attendconfigVo = new AttendconfigVo(config);
attendconfigVo.setDate(sdf.format(calBegin.getTime()));
list.add(attendconfigVo);
} else {
//第一天
AttendconfigVo attendconfigVo = new AttendconfigVo();
attendconfigVo.setDate(sdf.format(calBegin.getTime()));
attendconfigVo.setDutytype(config.getDutytype());
attendconfigVo.setDutytime1("休息");
attendconfigVo.setDutytime2("休息");
attendconfigVo.setDutytime3("休息");
attendconfigVo.setDutytime4("休息");
list.add(attendconfigVo);
}
while (cale.getTime().after(calBegin.getTime())) {
// 根据日历的规则,为给定的日历字段添加或减去指定的时间量
//去除休息天
calBegin.add(Calendar.DAY_OF_MONTH, 1);
if (calBegin.get(Calendar.DAY_OF_WEEK) != Integer.parseInt(split[0]) + 1
&& calBegin.get(Calendar.DAY_OF_WEEK) != Integer.parseInt(split[1]) + 1) {
AttendconfigVo attendconfigVo = new AttendconfigVo(config);
attendconfigVo.setDate(sdf.format(calBegin.getTime()));
list.add(attendconfigVo);
} else {
AttendconfigVo attendconfigVo = new AttendconfigVo();
attendconfigVo.setDate(sdf.format(calBegin.getTime()));
attendconfigVo.setDutytype(config.getDutytype());
attendconfigVo.setDutytime1("休息");
attendconfigVo.setDutytime2("休息");
attendconfigVo.setDutytime3("休息");
attendconfigVo.setDutytime4("休息");
list.add(attendconfigVo);
}
}
return list;
}
返回到前端页面遍历即可
考勤统计
功能
用户点击考勤统计,显示自己以及所有下属员工的考勤统计信息
业务流程略
有一个日期的筛选功能,所以每次的上传和返回的域对象中都有日期对象
具体代码解析
获取本月的工作日
SimpleDateFormat dayFormat = new SimpleDateFormat("yyyy-MM-dd");
// 没有筛选项默认为本月
if (date == null) {
date = dayFormat.format(new Date());
}
Date parse = dayFormat.parse(date);
// 获取本月工作日
Calendar c = Calendar.getInstance();
c.setTime(parse);
int max = c.getActualMaximum(Calendar.DAY_OF_MONTH);
// 开始日期为1号
int maxDate = 0;
for (int start = 1; start <= max; start++) {
//把这一天设置进日历
c.set(Calendar.DAY_OF_MONTH, start);
//获取这一天是周几
int week = c.get(Calendar.DAY_OF_WEEK);
// 不是周六和周日的都认为是工作日
if (week != Calendar.SUNDAY && week != Calendar.SATURDAY) {
maxDate++;
}
}
递归遍历用户及其下属
用户表的记录如下,如果用户有上级,那么learid字段指向的就是上级的userid
因此,java中的递归方法如下
// 递归获取下属
public List<User> getFollowers(User user) {
List<User> ulist = new ArrayList<>();
ulist.add(user);
UserExample uExample = new UserExample();
cn.zzpigt.bean.UserExample.Criteria userCriteria = uExample.createCriteria();
userCriteria.andLeaderidEqualTo(user.getUserid());
List<User> flist = um.selectByExample(uExample);
if (flist.size() > 0) {
for (User follower : flist) {
List<User> followers = getFollowers(follower);
ulist.addAll(followers);
}
}
return ulist;
}
接下来用for循环遍历获得的用户list,获取迟到,早退,未打卡的具体信息
// 递归获取下属
List<User> userList = getFollowers(user);
// po转vo准备
List<UserAttendCountVo> uacvList = new ArrayList<>();
// 遍历每一个用户
for (User user2 : userList) {
// 迟到
AttenddutyExample lateExample = new AttenddutyExample();
cn.zzpigt.bean.AttenddutyExample.Criteria lateCriteria = lateExample.createCriteria();
lateCriteria.andRegistertimeBetween(DateUtil.getStartTimeOfMonth(date), DateUtil.getEndTimeOfMonth(date));
lateCriteria.andRemarkEqualTo("迟到");
lateCriteria.andUseridEqualTo(user2.getUserid());
int late = adm.countByExample(lateExample);
// 早退
AttenddutyExample earlyExample = new AttenddutyExample();
cn.zzpigt.bean.AttenddutyExample.Criteria earlyCriteria = earlyExample.createCriteria();
earlyCriteria.andRegistertimeBetween(DateUtil.getStartTimeOfMonth(date), DateUtil.getEndTimeOfMonth(date));
earlyCriteria.andRemarkEqualTo("早退");
earlyCriteria.andUseridEqualTo(user2.getUserid());
int early = adm.countByExample(earlyExample);
// 出勤
int attendday = 0;
Calendar startDate = Calendar.getInstance();
startDate.set(Calendar.YEAR, Integer.parseInt(date.split("-")[0]));
startDate.set(Calendar.MONTH, Integer.parseInt(date.split("-")[1]) - 1);
startDate.set(Calendar.HOUR_OF_DAY, 0);
startDate.set(Calendar.MINUTE, 0);
startDate.set(Calendar.SECOND, 0);
Calendar endDate = Calendar.getInstance();
endDate.set(Calendar.YEAR, Integer.parseInt(date.split("-")[0]));
endDate.set(Calendar.MONTH, Integer.parseInt(date.split("-")[1]) - 1);
endDate.set(Calendar.HOUR_OF_DAY, 0);
endDate.set(Calendar.MINUTE, 0);
endDate.set(Calendar.SECOND, 0);
// 本月的天数,利用sql语句between条件,获取2个时间段之间是否打卡了
for (int i = 1; i <= startDate.getActualMaximum(Calendar.DAY_OF_MONTH); i++) {
startDate.set(Calendar.DAY_OF_MONTH, i);
Date time1 = startDate.getTime();
endDate.set(Calendar.DAY_OF_MONTH, i + 1);
Date time2 = endDate.getTime();
AttenddutyExample adExample = new AttenddutyExample();
cn.zzpigt.bean.AttenddutyExample.Criteria adCriteria = adExample.createCriteria();
adCriteria.andRegistertimeBetween(time1, time2);
adCriteria.andUseridEqualTo(user2.getUserid());
List<Attendduty> selectByExample = adm.selectByExample(adExample);
// 有打卡
if (selectByExample.size() > 0) {
attendday++;
}
}
//上班未登记天数
int noCome = 0;
//下班未登记天数
int noBack = 0;
UserAttendCountVo uacv = new UserAttendCountVo(user2);
UserAttendExample userAttendExample = new UserAttendExample();
Criteria userAttendCriteria = userAttendExample.createCriteria();
userAttendCriteria.andUseridEqualTo(user2.getUserid());
// po转vo,把user对象放进
List<UserAttend> userAttendList = uam.selectByExample(userAttendExample);
// 有打卡,计算未打卡次数
if (attendday > 0) {
// 行政班
if (userAttendList.get(0).getDutytype() == 1) {
int m1 = getSignCount(user2, date, "1");
int m2 = getSignCount(user2, date, "2");
noCome = attendday - m1;
noBack = attendday - m2;
// 两头班
} else {
int m1 = getSignCount(user2, date, "1");
int m2 = getSignCount(user2, date, "2");
int m3 = getSignCount(user2, date, "3");
int m4 = getSignCount(user2, date, "4");
noCome = attendday * 2 - m1 - m3;
noBack = attendday * 2 - m2 - m4;
}
}
uacv.setComesign(noCome);
uacv.setBacksign(noBack);
// 应全勤,实际,旷工,迟到,早退
uacv.setDaycount(maxDate);
uacv.setAttendday(attendday);
uacv.setNevercount(maxDate - attendday);
uacv.setLatecount(late);
uacv.setEarlycount(early);
uacvList.add(uacv);
}
return uacvList;
}
返回到前端遍历