Ver código fonte

优化考勤管理移动打卡API,以及三次打卡中的相关打卡时间计算逻辑和分析逻辑

o2lee 5 anos atrás
pai
commit
23389b1206
11 arquivos alterados com 556 adições e 378 exclusões
  1. 2 0
      o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/ThisApplication.java
  2. 23 0
      o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/factory/AttendanceDetailFactory.java
  3. 5 2
      o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/factory/AttendanceDetailMobileFactory.java
  4. 0 310
      o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/jaxrs/attendancedetail/ActionListMyMobileRecordToday.java
  5. 82 44
      o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/jaxrs/attendancedetail/ActionReciveAttendanceMobile.java
  6. 345 0
      o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/jaxrs/attendancedetail/BaseAction.java
  7. 50 0
      o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/schedule/DetailLastDayRecordAnalyseTask.java
  8. 5 1
      o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/service/AttendanceDetailAnalyseService.java
  9. 22 17
      o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/service/AttendanceDetailAnalyseSignProxy2.java
  10. 6 1
      o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/service/AttendanceDetailService.java
  11. 16 3
      o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/service/AttendanceDetailServiceAdv.java

+ 2 - 0
o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/ThisApplication.java

@@ -52,6 +52,8 @@ public class ThisApplication {
 
 			context.schedule(AttendanceStatisticTask.class, "0 0 0/4 * * ?");
 			context.schedule(MobileRecordAnalyseTask.class, "0 0 * * * ?");
+			//每天凌晨1点,计算前一天所有的未签退和未分析的打卡数据
+			context.schedule(DetailLastDayRecordAnalyseTask.class, "0 0 1 * * ?");
 
 		} catch (Exception e) {
 			e.printStackTrace();

+ 23 - 0
o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/factory/AttendanceDetailFactory.java

@@ -979,4 +979,27 @@ public class AttendanceDetailFactory extends AbstractFactory {
 		p = cb.and( p, root.get( AttendanceDetail_.recordStatus).in( statusArray ));
 		return em.createQuery(cq.where(p)).setMaxResults(20000).getResultList();
 	}
+
+	public List<String> listRecordWithDateAndNoOffDuty( String recordDate ) throws Exception {
+		List<Integer> statusArray = new ArrayList<Integer>();
+		statusArray.add( 0 ); //未分析的
+		statusArray.add( -1 ); //有错误的
+
+		EntityManager em = this.entityManagerContainer().get( AttendanceDetail.class );
+		CriteriaBuilder cb = em.getCriteriaBuilder();
+		CriteriaQuery<String> cq = cb.createQuery(String.class);
+		Root<AttendanceDetail> root = cq.from( AttendanceDetail.class);
+		cq.select(root.get(AttendanceDetail_.id));
+		Predicate p = cb.equal( root.get( AttendanceDetail_.recordDate ), recordDate );
+
+		Predicate offDutyTime_1 = cb.isNull(root.get( AttendanceDetail_.offDutyTime ));
+		Predicate offDutyTime_2 = cb.equal( root.get( AttendanceDetail_.offDutyTime ), "" );
+		Predicate status0 = root.get( AttendanceDetail_.recordStatus).in( statusArray );
+
+		p = cb.and( p, cb.or( offDutyTime_1, offDutyTime_2 ));
+
+		return em.createQuery(cq.where(p)).setMaxResults(100000).getResultList();
+	}
+
+
 }

+ 5 - 2
o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/factory/AttendanceDetailMobileFactory.java

@@ -153,10 +153,13 @@ public class AttendanceDetailMobileFactory extends AbstractFactory {
 		Root<AttendanceDetailMobile> root = cq.from(AttendanceDetailMobile.class);
 		Predicate p = cb.isNotNull( root.get( AttendanceDetailMobile_.id ) );
 		if( StringUtils.isNotEmpty( distinguishedName ) ){
-			p = cb.and( p, cb.equal( root.get( AttendanceDetailMobile_.empNo ), distinguishedName ) );
+			p = cb.or(
+					cb.equal( root.get( AttendanceDetailMobile_.empNo ), distinguishedName ),
+					cb.equal( root.get( AttendanceDetailMobile_.empName ), distinguishedName )
+			);
 		}
 		if( StringUtils.isNotEmpty( signDate ) ){
-			p = cb.and( p, cb.equal( root.get( AttendanceDetailMobile_.recordDateString ), signDate ) );
+			p = cb.and( cb.equal( root.get( AttendanceDetailMobile_.recordDateString ), signDate ), p );
 		}
 		return em.createQuery(cq.where(p)).setMaxResults( 100 ).getResultList();
 	}

+ 0 - 310
o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/jaxrs/attendancedetail/ActionListMyMobileRecordToday.java

@@ -101,271 +101,6 @@ public class ActionListMyMobileRecordToday extends BaseAction {
 		return result;
 	}
 
-	/**
-	 * 计算下一次打卡是什么打卡
-	 * 3-四次打卡(上午下午都打上班下班卡)
-	 *
-	 * @param wraps
-	 * @param scheduleSetting
-	 * @return
-	 */
-	private WoSignFeature getWoSignFeatureWithProxy3(List<WoMobileRecord> wraps, AttendanceScheduleSetting scheduleSetting) throws Exception {
-		WoSignFeature woSignFeature = new WoSignFeature();
-		Date now = new Date();
-		Date onDutyTime = null, offDutyTime = null;
-		Date morningOffdutyTime = null, afternoonOndutyTime = null;
-		String todayDateStr = dateOperation.getDateStringFromDate( now, "YYYY-MM-DD");
-		SignRecordStatus signRecordStatus = null;
-
-		if( StringUtils.isEmpty( scheduleSetting.getMiddayRestStartTime())){
-			return null;
-		}
-
-		if( StringUtils.isEmpty( scheduleSetting.getMiddayRestEndTime())){
-			return null;
-		}
-
-		//计算,上班下班时间
-		onDutyTime = dateOperation.getDateFromString( todayDateStr + " " + scheduleSetting.getOnDutyTime() );
-		morningOffdutyTime = dateOperation.getDateFromString( todayDateStr + " " + scheduleSetting.getMiddayRestStartTime() );
-		afternoonOndutyTime = dateOperation.getDateFromString( todayDateStr + " " + scheduleSetting.getMiddayRestEndTime() );
-		offDutyTime = dateOperation.getDateFromString( todayDateStr + " " + scheduleSetting.getOffDutyTime() );
-
-		signRecordStatus = getSignRecordStatus( wraps, onDutyTime, morningOffdutyTime, afternoonOndutyTime, offDutyTime );
-
-		if( ListTools.isEmpty( wraps )){
-			//一次都没有打过,看看当前时间是上班还是下午,如果是上午,就是上午签到,如果是下午,就是下午签到
-			if( now.after(afternoonOndutyTime)){
-				//在下午下班时间之后了,下午上班签到打卡
-				woSignFeature.setSignSeq(1);
-				woSignFeature.setCheckinType( AttendanceDetailMobile.CHECKIN_TYPE_AFTERNOON_ONDUTY );
-				woSignFeature.setSignTime(scheduleSetting.getOnDutyTime());
-			}else{
-				woSignFeature.setSignSeq(1);
-				woSignFeature.setCheckinType( AttendanceDetailMobile.CHECKIN_TYPE_ONDUTY );
-				woSignFeature.setSignTime(scheduleSetting.getOnDutyTime());
-			}
-		}else{
-			woSignFeature.setSignSeq(2);
-			//当前是什么区间
-			if( onDutyTime.after(now)){
-				//上午上班之前,无论几次,都只可能是上午的下班卡
-				woSignFeature.setCheckinType( AttendanceDetailMobile.CHECKIN_TYPE_MORNING_OFFDUTY );
-				woSignFeature.setSignTime(scheduleSetting.getMiddayRestStartTime());
-				woSignFeature.setSignSeq(2);
-			}else if( now.after(onDutyTime) && morningOffdutyTime.after(now)){
-				//上午上班时段: 上午签退
-				woSignFeature.setSignSeq(2);
-				woSignFeature.setCheckinType( AttendanceDetailMobile.CHECKIN_TYPE_MORNING_OFFDUTY );
-				woSignFeature.setSignTime(scheduleSetting.getMiddayRestStartTime());
-			}else if( now.after(morningOffdutyTime) && afternoonOndutyTime.after( now )){
-				//午休时段:前一次打卡有可能上午签到卡,可能下午签到卡
-				if( signRecordStatus.alreadyOnduty ){ //已经上午签到过了,只有一次卡,应该就是签到
-					woSignFeature.setCheckinType( AttendanceDetailMobile.CHECKIN_TYPE_MORNING_OFFDUTY );
-					woSignFeature.setSignTime(scheduleSetting.getMiddayRestStartTime());
-				}else if( signRecordStatus.alreadyAfternoonOnDuty){
-					//如果上午没有签到,是下午的签到的话,第二次就应该是下午签退打卡了
-					woSignFeature.setCheckinType( AttendanceDetailMobile.CHECKIN_TYPE_OFFDUTY );
-					woSignFeature.setSignTime(scheduleSetting.getOffDutyTime());
-				}
-			}else if( now.after(afternoonOndutyTime) && offDutyTime.after(now)){
-				//下午上班时段,如果前一次是下午签到,那么下一次就应该是下午签退了,否则,就是下午签到
-				if( signRecordStatus.alreadyAfternoonOnDuty){
-					woSignFeature.setCheckinType( AttendanceDetailMobile.CHECKIN_TYPE_OFFDUTY  );
-					woSignFeature.setSignTime(scheduleSetting.getOffDutyTime());
-				}else{
-					woSignFeature.setCheckinType( AttendanceDetailMobile.CHECKIN_TYPE_AFTERNOON_ONDUTY  );
-					woSignFeature.setSignTime(scheduleSetting.getMiddayRestEndTime());
-				}
-			}else{
-				//下午下班之后,只可能是下午的签到签退卡了
-				if( signRecordStatus.alreadyAfternoonOnDuty){
-					woSignFeature.setSignSeq(-1);
-					woSignFeature.setCheckinType( AttendanceDetailMobile.CHECKIN_TYPE_OFFDUTY  );
-					woSignFeature.setSignTime(scheduleSetting.getOffDutyTime());
-				}else{
-					woSignFeature.setCheckinType( AttendanceDetailMobile.CHECKIN_TYPE_AFTERNOON_ONDUTY  );
-					woSignFeature.setSignTime(scheduleSetting.getMiddayRestEndTime());
-				}
-			}
-		}
-		return woSignFeature;
-	}
-
-	/**
-	 * 计算下一次打卡是什么打卡
-	 * 2-三次打卡(上午上班,下午下班加中午一次共三次)
-	 *
-	 * @param wraps
-	 * @param scheduleSetting
-	 * @return
-	 */
-	private WoSignFeature getWoSignFeatureWithProxy2(List<WoMobileRecord> wraps, AttendanceScheduleSetting scheduleSetting) throws Exception {
-		WoSignFeature woSignFeature = new WoSignFeature();
-		SignRecordStatus signRecordStatus = null;
-
-		Date now = new Date();
-		Date middayRestEndTime = null;
-		Date onDutyTime = null, offDutyTime = null;
-		Date morningOffdutyTime = null, afternoonOndutyTime = null;
-		String todayDateStr = dateOperation.getDateStringFromDate( now, "YYYY-MM-DD");
-
-		//计算,上班下班时间
-		onDutyTime = dateOperation.getDateFromString( todayDateStr + " " + scheduleSetting.getOnDutyTime() );
-		morningOffdutyTime = dateOperation.getDateFromString( todayDateStr + " " + scheduleSetting.getMiddayRestStartTime() );
-		afternoonOndutyTime = dateOperation.getDateFromString( todayDateStr + " " + scheduleSetting.getMiddayRestEndTime() );
-		offDutyTime = dateOperation.getDateFromString( todayDateStr + " " + scheduleSetting.getOffDutyTime() );
-
-		signRecordStatus = getSignRecordStatus( wraps, onDutyTime, morningOffdutyTime, afternoonOndutyTime, offDutyTime );
-
-		if( ListTools.isEmpty( wraps )){
-			//一次都没有打过,不管几点,都是上班打卡
-			woSignFeature.setSignSeq(1);
-			woSignFeature.setCheckinType( "上班签到" );
-			woSignFeature.setSignTime(scheduleSetting.getOnDutyTime());
-		}else{
-			//打了一次,之后,有可能是中午签到打卡,有可能是下午下班签退打卡
-			if( wraps.size() == 1 ){
-				woSignFeature.setSignSeq(2);
-				//第一次打卡,肯定是打了上班卡,第二次就可能是午休或者下班卡了
-				if( StringUtils.isNotEmpty( scheduleSetting.getMiddayRestEndTime())){
-					middayRestEndTime = dateOperation.getDateFromString( todayDateStr + " " + scheduleSetting.getMiddayRestEndTime() );
-					//看当前时间,有没有过中午签到时间
-					if( middayRestEndTime !=null && middayRestEndTime.before( now )){
-						//在午休结束之后了,就是下班打卡了,上班和午休都缺卡了
-						woSignFeature.setCheckinType( AttendanceDetailMobile.CHECKIN_TYPE_OFFDUTY );
-						woSignFeature.setSignTime(scheduleSetting.getOffDutyTime());
-					}else{
-						//午休卡
-						woSignFeature.setCheckinType( AttendanceDetailMobile.CHECKIN_TYPE_AFTERNOON );
-						woSignFeature.setSignTime(scheduleSetting.getMiddayRestEndTime());
-					}
-				}
-			}else if( wraps.size() == 2 ){
-				if( StringUtils.isNotEmpty( scheduleSetting.getMiddayRestEndTime())) {
-					middayRestEndTime = dateOperation.getDateFromString(todayDateStr + " " + scheduleSetting.getMiddayRestEndTime());
-					//只可能是签退卡了,如果签退卡都打了,那么就是不需要再打卡了。
-					//判断是否已经打了下班签退卡,看看是否有午休结束时间之后的打卡
-					Boolean exists_offDuty = false;
-					Date signTime = null;
-					for( WoMobileRecord record : wraps ){
-						signTime = dateOperation.getDateFromString(todayDateStr + " " + record.getSignTime() );
-						if( signTime.after( middayRestEndTime )){
-							exists_offDuty = true;
-							break;
-						}
-					}
-					if( exists_offDuty ){
-						woSignFeature.setSignSeq(-1);
-						woSignFeature.setCheckinType( AttendanceDetailMobile.CHECKIN_TYPE_OFFDUTY );
-						woSignFeature.setSignTime(scheduleSetting.getOffDutyTime());
-					}else{
-						woSignFeature.setSignSeq(3);
-						woSignFeature.setCheckinType( AttendanceDetailMobile.CHECKIN_TYPE_OFFDUTY );
-						woSignFeature.setSignTime(scheduleSetting.getOffDutyTime());
-					}
-				}
-			}else{
-				woSignFeature.setSignSeq(-1);
-				woSignFeature.setCheckinType( AttendanceDetailMobile.CHECKIN_TYPE_OFFDUTY );
-				woSignFeature.setSignTime(scheduleSetting.getOffDutyTime());
-			}
-		}
-
-		return woSignFeature;
-	}
-
-	/**
-	 * 计算下一次打卡是什么打卡
-	 * 1-两次打卡(上午上班,下午下班)
-	 * @param wraps
-	 * @param scheduleSetting
-	 * @return
-	 */
-	private WoSignFeature getWoSignFeatureWithProxy1(List<WoMobileRecord> wraps, AttendanceScheduleSetting scheduleSetting) {
-		WoSignFeature woSignFeature = new WoSignFeature();
-		if( ListTools.isEmpty( wraps )){
-			//一次都没有打过,不管几点,都是上班打卡
-			woSignFeature.setSignSeq(1);
-			woSignFeature.setCheckinType( AttendanceDetailMobile.CHECKIN_TYPE_ONDUTY );
-			woSignFeature.setSignTime(scheduleSetting.getOnDutyTime());
-		}else{
-			//打了一次,就是下班打卡,打了两次,就没有了
-			if( wraps.size() == 1 ){
-				woSignFeature.setSignSeq(2);
-				woSignFeature.setCheckinType( AttendanceDetailMobile.CHECKIN_TYPE_OFFDUTY );
-				woSignFeature.setSignTime(scheduleSetting.getOffDutyTime());
-			}else{
-				woSignFeature.setSignSeq(-1); //没有需要的打卡了
-				woSignFeature.setCheckinType( AttendanceDetailMobile.CHECKIN_TYPE_OFFDUTY );
-				woSignFeature.setSignTime(scheduleSetting.getOffDutyTime());
-			}
-		}
-		return woSignFeature;
-	}
-
-	private SignRecordStatus getSignRecordStatus(List<WoMobileRecord> wraps, Date onDutyTime, Date morningOffdutyTime, Date afternoonOndutyTime, Date offDutyTime) {
-		SignRecordStatus signRecordStatus = new SignRecordStatus();
-		if( ListTools.isNotEmpty( wraps )){
-			for( WoMobileRecord record : wraps ){
-				if( StringUtils.equalsAnyIgnoreCase( AttendanceDetailMobile.CHECKIN_TYPE_ONDUTY, record.getCheckin_type() )){
-					signRecordStatus.setAlreadyOnduty( true );
-				}
-				if( StringUtils.equalsAnyIgnoreCase( AttendanceDetailMobile.CHECKIN_TYPE_AFTERNOON, record.getCheckin_type() )){
-					signRecordStatus.setAlreadyAfternoon( true );
-				}
-				if( StringUtils.equalsAnyIgnoreCase( AttendanceDetailMobile.CHECKIN_TYPE_MORNING_OFFDUTY, record.getCheckin_type() )){
-					signRecordStatus.setAlreadyMorningOffDuty( true );
-				}
-				if( StringUtils.equalsAnyIgnoreCase( AttendanceDetailMobile.CHECKIN_TYPE_AFTERNOON_ONDUTY, record.getCheckin_type() )){
-					signRecordStatus.setAlreadyAfternoonOnDuty( true );
-				}
-				if( StringUtils.equalsAnyIgnoreCase( AttendanceDetailMobile.CHECKIN_TYPE_OFFDUTY, record.getCheckin_type() )){
-					signRecordStatus.setAlreadyOffDuty( true );
-				}
-			}
-		}
-		return signRecordStatus;
-	}
-
-	public static class SignRecordStatus {
-
-		@FieldDescribe("是否上午上班打卡过了ONDUTY")
-		private Boolean alreadyOnduty = false;
-
-		@FieldDescribe("是否上午下班打卡过了MORNING_OFFDUTY.")
-		private Boolean alreadyMorningOffDuty = false;
-
-		@FieldDescribe("是否下午上班打卡过了AFTERNOON_ONDUTY")
-		private Boolean alreadyAfternoonOnDuty = false;
-
-		@FieldDescribe("是否下午下班打卡过了OFFDUTY.")
-		private Boolean alreadyOffDuty = false;
-
-		@FieldDescribe("是否午间打卡过了AfternoonSign.")
-		private Boolean alreadyAfternoon = false;
-
-		public Boolean getAlreadyOnduty() { return alreadyOnduty; }
-
-		public void setAlreadyOnduty(Boolean alreadyOnduty) { this.alreadyOnduty = alreadyOnduty; }
-
-		public Boolean getAlreadyMorningOffDuty() { return alreadyMorningOffDuty; }
-
-		public void setAlreadyMorningOffDuty(Boolean alreadyMorningOffDuty) { this.alreadyMorningOffDuty = alreadyMorningOffDuty; }
-
-		public Boolean getAlreadyAfternoonOnDuty() { return alreadyAfternoonOnDuty; }
-
-		public void setAlreadyAfternoonOnDuty(Boolean alreadyAfternoonOnDuty) { this.alreadyAfternoonOnDuty = alreadyAfternoonOnDuty; }
-
-		public Boolean getAlreadyOffDuty() { return alreadyOffDuty; }
-
-		public void setAlreadyOffDuty(Boolean alreadyOffDuty) { this.alreadyOffDuty = alreadyOffDuty; }
-
-		public Boolean getAlreadyAfternoon() { return alreadyAfternoon; }
-
-		public void setAlreadyAfternoon(Boolean alreadyAfternoon) { this.alreadyAfternoon = alreadyAfternoon; }
-	}
-
 	public static class Wo{
 
 		@FieldDescribe("所有的打卡记录.")
@@ -390,14 +125,6 @@ public class ActionListMyMobileRecordToday extends BaseAction {
 		public void setFeature(WoSignFeature feature) { this.feature = feature; }
 	}
 
-	public static class WoMobileRecord extends AttendanceDetailMobile {
-
-		private static final long serialVersionUID = -5076990764713538973L;
-
-		public static WrapCopier<AttendanceDetailMobile, WoMobileRecord> copier = WrapCopierFactory.wo(AttendanceDetailMobile.class,
-				WoMobileRecord.class, null, JpaObject.FieldsInvisible);
-	}
-
 	public static class WoScheduleSetting extends AttendanceScheduleSetting {
 
 		private static final long serialVersionUID = -5076990764713538973L;
@@ -406,42 +133,5 @@ public class ActionListMyMobileRecordToday extends BaseAction {
 				WoScheduleSetting.class, null, JpaObject.FieldsInvisible);
 	}
 
-	public static class WoSignFeature{
-
-		@FieldDescribe("下一次打卡次数,第一次为1")
-		private Integer signSeq = 1;
-
-		@FieldDescribe("打卡日期:yyyy-mm-dd.")
-		private String signDate = null;
-
-		@FieldDescribe("打卡时间:HH:mi:ss.")
-		private String signTime = null;
-
-		@FieldDescribe("打卡操作名称:上班打卡|下班打卡|午休打卡|上午下班打卡|下午上班打卡(根据不同的打卡策略稍有不同)....")
-		private String checkinType = "上班打卡";
-
-		@FieldDescribe("最晚打卡时间")
-		private Date latestSignTime;
-
-		public String getSignTime() { return signTime; }
-
-		public void setSignTime(String signTime) { this.signTime = signTime; }
 
-		public Integer getSignSeq() { return signSeq; }
-
-		public void setSignSeq(Integer signSeq) { this.signSeq = signSeq; }
-
-		public String getSignDate() { return signDate; }
-
-		public void setSignDate(String signDate) { this.signDate = signDate; }
-
-		public String getCheckinType() { return checkinType; }
-
-		public void setCheckinType(String checkinType) { this.checkinType = checkinType; }
-
-		public Date getLatestSignTime() { return latestSignTime; }
-
-		public void setLatestSignTime(Date latestSignTime) { this.latestSignTime = latestSignTime; }
-
-	}
 }

+ 82 - 44
o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/jaxrs/attendancedetail/ActionReciveAttendanceMobile.java

@@ -1,10 +1,14 @@
 package com.x.attendance.assemble.control.jaxrs.attendancedetail;
 
+import java.util.ArrayList;
 import java.util.Date;
+import java.util.List;
 
 import javax.servlet.http.HttpServletRequest;
 
+import com.x.attendance.entity.AttendanceScheduleSetting;
 import com.x.base.core.project.organization.Person;
+import com.x.base.core.project.tools.ListTools;
 import org.apache.commons.lang3.StringUtils;
 
 import com.google.gson.JsonElement;
@@ -28,11 +32,24 @@ public class ActionReciveAttendanceMobile extends BaseAction {
 		DateOperation dateOperation = new DateOperation();
 		EffectivePerson currentPerson = this.effectivePerson( request );
 		AttendanceDetailMobile attendanceDetailMobile = new AttendanceDetailMobile();
+		List<AttendanceDetailMobile> attendanceDetailMobileList = null;
+		List<WoMobileRecord> wraps = new ArrayList<>();
 		Wi wrapIn = null;
 		Boolean check = true;
+		Date now = new Date();
+		String signDate = null;
+		String signTime = null;
+
 
 		try {
 			wrapIn = this.convertToWrapIn( jsonElement, Wi.class );
+			signDate = dateOperation.getDateStringFromDate( now, "YYYY-MM-DD");
+			signTime = dateOperation.getDateStringFromDate( now, "HH:mm:ss");
+
+			attendanceDetailMobile.setRecordDateString( signDate ); //打卡日期
+			attendanceDetailMobile.setSignTime( signTime ); //打卡时间
+			attendanceDetailMobile.setCheckin_time( now.getTime() );
+			attendanceDetailMobile.setRecordDate( now );
 		} catch (Exception e) {
 			check = false;
 			Exception exception = new ExceptionWrapInConvert(e, jsonElement);
@@ -57,65 +74,86 @@ public class ActionReciveAttendanceMobile extends BaseAction {
 		}
 
 		if( check ){
+			String distinguishedName = wrapIn.getEmpName();
+			if( StringUtils.isEmpty( distinguishedName )){
+				distinguishedName = currentPerson.getDistinguishedName();
+			}
+			attendanceDetailMobile.setEmpName( distinguishedName );
 			if( StringUtils.isEmpty( wrapIn.getEmpNo() )){
-				Person person = userManagerService.getPersonObjByName( currentPerson.getDistinguishedName() );
+				Person person = userManagerService.getPersonObjByName( distinguishedName );
 				if( person != null ){
 					if( StringUtils.isNotEmpty( person.getEmployee() )){
 						attendanceDetailMobile.setEmpNo(person.getEmployee());
 					}else{
-						attendanceDetailMobile.setEmpNo(currentPerson.getDistinguishedName());
+						attendanceDetailMobile.setEmpNo( distinguishedName );
 					}
 				}
 			}else{
 				attendanceDetailMobile.setEmpNo( wrapIn.getEmpNo() );
 			}
-			attendanceDetailMobile.setEmpName( currentPerson.getDistinguishedName() );
-			attendanceDetailMobile.setCheckin_time(wrapIn.getCheckin_time());
-			attendanceDetailMobile.setCheckin_type(wrapIn.getCheckin_type());
 		}
 
-		if( check ){
-			if( StringUtils.isNotEmpty(wrapIn.getSignTime()) ){
-				try{
-					datetime = dateOperation.getDateFromString( wrapIn.getSignTime() );
-					attendanceDetailMobile.setSignTime( dateOperation.getDateStringFromDate( datetime, "HH:mm:ss") ); //打卡时间
-				}catch( Exception e ){
-					check = false;
-					Exception exception = new ExceptionAttendanceDetailProcess( e, "员工手机打卡信息中打卡时间格式异常,格式:  HH:mm:ss. 时间:" + wrapIn.getSignTime() );
-					result.error( exception );
-					logger.error( e, currentPerson, request, null);
-				}
-			}else{//打卡时间没有填写就填写为当前时间
-				attendanceDetailMobile.setSignTime( dateOperation.getNowTime() ); //打卡时间
+		//计算当前打卡的checking_type
+		//先查询该员工所有的考勤数据
+		if (check) {
+			try {
+				attendanceDetailMobileList = attendanceDetailServiceAdv.listAttendanceDetailMobile( effectivePerson.getDistinguishedName(), signDate );
+			} catch (Exception e) {
+				check = false;
+				Exception exception = new ExceptionAttendanceDetailProcess(e,
+						"根据条件查询员工手机打卡信息列表时发生异常.DistinguishedName:" + effectivePerson.getDistinguishedName() + ",Date:" + signDate);
+				result.error(exception);
+				logger.error(e, currentPerson, request, null);
 			}
+		}
 
-			if( wrapIn.checkin_time < 1500000000000L ){ //无效
-				attendanceDetailMobile.setCheckin_time( new Date().getTime() ); //打卡时间
-			}else{//打卡时间没有填写就填写为当前时间
-				attendanceDetailMobile.setCheckin_time( wrapIn.checkin_time ); //打卡时间
+		if (check) {
+			if ( ListTools.isNotEmpty(attendanceDetailMobileList)) {
+				try {
+					wraps = WoMobileRecord.copier.copy(attendanceDetailMobileList);
+				} catch (Exception e) {
+					check = false;
+					Exception exception = new ExceptionAttendanceDetailProcess(e, "系统在转换员工手机打卡信息为输出对象时发生异常.");
+					result.error(exception);
+					logger.error(e, currentPerson, request, null);
+				}
 			}
 		}
-		if( check ){
-			if( StringUtils.isNotEmpty( wrapIn.getRecordDateString() ) ){
-				try{
-					datetime = dateOperation.getDateFromString( wrapIn.getRecordDateString() );
-					attendanceDetailMobile.setRecordDate( datetime );
-					attendanceDetailMobile.setRecordDateString( dateOperation.getDateStringFromDate( datetime, "yyyy-MM-dd") ); //打卡时间
-				}catch( Exception e ){
-					check = false;
-					Exception exception = new ExceptionAttendanceDetailProcess( e,  "员工手机打卡信息中打卡日期格式异常,格式: yyyy-mm-dd. 日期:" + wrapIn.getRecordDateString() );
-					result.error( exception );
-					logger.error( e, currentPerson, request, null);
-				}				
+		//根据最后一次打卡信息,计算下一次打卡的信息
+		WoSignFeature woSignFeature = null;
+		if (check
+				&& !StringUtils.equalsAnyIgnoreCase("xadmin", effectivePerson.getName())
+				&& !StringUtils.equalsAnyIgnoreCase("cipher", effectivePerson.getName())) {
+			AttendanceScheduleSetting scheduleSetting = null;
+			//打卡策略:1-两次打卡(上午上班,下午下班) 2-三次打卡(上午上班,下午下班加中午一次共三次) 3-四次打卡(上午下午都打上班下班卡)
+			scheduleSetting = attendanceScheduleSettingServiceAdv.getAttendanceScheduleSettingWithPerson( effectivePerson.getDistinguishedName(), effectivePerson.getDebugger() );
+			if( scheduleSetting != null ){
+				if( scheduleSetting.getSignProxy() == 3 ){
+					//3-四次打卡(上午下午都打上班下班卡)
+					woSignFeature = getWoSignFeatureWithProxy3(wraps, scheduleSetting);
+				}else if( scheduleSetting.getSignProxy() == 2 ){
+					//2-三次打卡(上午上班,下午下班加中午一次共三次)
+					woSignFeature = getWoSignFeatureWithProxy2(wraps, scheduleSetting);
+				}else{
+					//1-两次打卡(上午上班,下午下班)
+					woSignFeature = getWoSignFeatureWithProxy1(wraps, scheduleSetting);
+				}
+			}
+			if( woSignFeature != null ){
+				woSignFeature.setSignDate( signDate );
+			}
+			attendanceDetailMobile.setCheckin_type( woSignFeature.getCheckinType() );
+			if( StringUtils.isEmpty( wrapIn.getSignDescription() )){
+				attendanceDetailMobile.setSignDescription( woSignFeature.getCheckinType() );
 			}else{
-				attendanceDetailMobile.setRecordDateString( dateOperation.getNowDate() ); //打卡日期
+				attendanceDetailMobile.setSignDescription( wrapIn.getSignDescription() );
 			}
 		}
+
 		if( check ){
 			if( StringUtils.isNotEmpty( wrapIn.getId() )){
 				attendanceDetailMobile.setId( wrapIn.getId() );
 			}
-			attendanceDetailMobile.setSignDescription( wrapIn.getSignDescription() );
 			try {
 				attendanceDetailMobile = attendanceDetailServiceAdv.save( attendanceDetailMobile );
 				result.setData( new Wo( attendanceDetailMobile.getId() ) );
@@ -136,28 +174,28 @@ public class ActionReciveAttendanceMobile extends BaseAction {
 	
 	public static class Wi {
 		
-		@FieldDescribe( "Id, 可以为空." )
+		@FieldDescribe( "Id, 可以为空,如果ID重复,则为更新原有数据." )
 		private String id;
 		
-		@FieldDescribe( "员工号, 可以为空." )
+		@FieldDescribe( "员工号, 可以为空,如果为空则与empName相同." )
 		private String empNo;
 
-		@FieldDescribe( "员工姓名, 必须填写." )
+		@FieldDescribe( "员工姓名, 可以为空,如果为空则取当前登录人员." )
 		private String empName;
 
-		@FieldDescribe( "打卡记录日期字符串:yyyy-mm-dd, 必须填写." )
+//		@FieldDescribe( "打卡记录日期字符串:yyyy-mm-dd, 必须填写." )
 		private String recordDateString;
 
-		@FieldDescribe("打卡类型。字符串,目前有:上午上班打卡,上午下班打卡,下午上班打卡,下午下班打卡,外出打卡,午间打卡")
+//		@FieldDescribe("打卡类型。字符串,目前有:上午上班打卡,上午下班打卡,下午上班打卡,下午下班打卡,外出打卡,午间打卡")
 		private String checkin_type;
 
-		@FieldDescribe("打卡时间。Unix时间戳")
+		@FieldDescribe("打卡时间,可以为空,为空则取服务器当前时间。Unix时间戳")
 		private long checkin_time;
 
-		@FieldDescribe( "打卡时间: hh24:mi:ss, 必须填写." )
+//		@FieldDescribe( "打卡时间: hh24:mi:ss, 必须填写." )
 		private String signTime;
 
-		@FieldDescribe( "打卡说明:上班打卡,下班打卡, 可以为空." )
+//		@FieldDescribe( "打卡说明:上班打卡,下班打卡, 可以为空." )
 		private String signDescription;
 
 		@FieldDescribe( "其他说明备注, 可以为空." )

+ 345 - 0
o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/jaxrs/attendancedetail/BaseAction.java

@@ -1,5 +1,6 @@
 package com.x.attendance.assemble.control.jaxrs.attendancedetail;
 
+import com.x.attendance.assemble.common.date.DateOperation;
 import com.x.attendance.assemble.control.service.AttendanceDetailAnalyseServiceAdv;
 import com.x.attendance.assemble.control.service.AttendanceDetailMobileAnalyseServiceAdv;
 import com.x.attendance.assemble.control.service.AttendanceDetailServiceAdv;
@@ -9,7 +10,18 @@ import com.x.attendance.assemble.control.service.AttendanceSelfHolidayServiceAdv
 import com.x.attendance.assemble.control.service.AttendanceStatisticalCycleServiceAdv;
 import com.x.attendance.assemble.control.service.AttendanceWorkDayConfigServiceAdv;
 import com.x.attendance.assemble.control.service.UserManagerService;
+import com.x.attendance.entity.AttendanceDetailMobile;
+import com.x.attendance.entity.AttendanceScheduleSetting;
+import com.x.base.core.entity.JpaObject;
+import com.x.base.core.project.annotation.FieldDescribe;
+import com.x.base.core.project.bean.WrapCopier;
+import com.x.base.core.project.bean.WrapCopierFactory;
 import com.x.base.core.project.jaxrs.StandardJaxrsAction;
+import com.x.base.core.project.tools.ListTools;
+import org.apache.commons.lang3.StringUtils;
+
+import java.util.Date;
+import java.util.List;
 
 public class BaseAction extends StandardJaxrsAction{
 	
@@ -22,4 +34,337 @@ public class BaseAction extends StandardJaxrsAction{
 	protected AttendanceSelfHolidayServiceAdv attendanceSelfHolidayServiceAdv = new AttendanceSelfHolidayServiceAdv();
 	protected AttendanceScheduleSettingServiceAdv attendanceScheduleSettingServiceAdv = new AttendanceScheduleSettingServiceAdv();
 	protected AttendanceDetailMobileAnalyseServiceAdv attendanceDetailMobileAnalyseServiceAdv = new AttendanceDetailMobileAnalyseServiceAdv();
+
+	public static class WoSignFeature{
+
+		@FieldDescribe("下一次打卡次数,第一次为1")
+		private Integer signSeq = 1;
+
+		@FieldDescribe("打卡日期:yyyy-mm-dd.")
+		private String signDate = null;
+
+		@FieldDescribe("打卡时间:HH:mi:ss.")
+		private String signTime = null;
+
+		@FieldDescribe("打卡操作名称:上班打卡|下班打卡|午休打卡|上午下班打卡|下午上班打卡(根据不同的打卡策略稍有不同)....")
+		private String checkinType = "上班打卡";
+
+		@FieldDescribe("最晚打卡时间")
+		private Date latestSignTime;
+
+		public String getSignTime() { return signTime; }
+
+		public void setSignTime(String signTime) { this.signTime = signTime; }
+
+		public Integer getSignSeq() { return signSeq; }
+
+		public void setSignSeq(Integer signSeq) { this.signSeq = signSeq; }
+
+		public String getSignDate() { return signDate; }
+
+		public void setSignDate(String signDate) { this.signDate = signDate; }
+
+		public String getCheckinType() { return checkinType; }
+
+		public void setCheckinType(String checkinType) { this.checkinType = checkinType; }
+
+		public Date getLatestSignTime() { return latestSignTime; }
+
+		public void setLatestSignTime(Date latestSignTime) { this.latestSignTime = latestSignTime; }
+
+	}
+
+
+	/**
+	 * 计算下一次打卡是什么打卡
+	 * 3-四次打卡(上午下午都打上班下班卡)
+	 *
+	 * @param wraps
+	 * @param scheduleSetting
+	 * @return
+	 */
+	protected WoSignFeature getWoSignFeatureWithProxy3(List<ActionListMyMobileRecordToday.WoMobileRecord> wraps, AttendanceScheduleSetting scheduleSetting) throws Exception {
+		DateOperation dateOperation = new DateOperation();
+		WoSignFeature woSignFeature = new WoSignFeature();
+		Date now = new Date();
+		Date onDutyTime = null, offDutyTime = null;
+		Date morningOffdutyTime = null, afternoonOndutyTime = null;
+		String todayDateStr = dateOperation.getDateStringFromDate( now, "YYYY-MM-DD");
+		ActionListMyMobileRecordToday.SignRecordStatus signRecordStatus = null;
+
+		if( StringUtils.isEmpty( scheduleSetting.getMiddayRestStartTime())){
+			return null;
+		}
+
+		if( StringUtils.isEmpty( scheduleSetting.getMiddayRestEndTime())){
+			return null;
+		}
+
+		//计算,上班下班时间
+		onDutyTime = dateOperation.getDateFromString( todayDateStr + " " + scheduleSetting.getOnDutyTime() );
+		morningOffdutyTime = dateOperation.getDateFromString( todayDateStr + " " + scheduleSetting.getMiddayRestStartTime() );
+		afternoonOndutyTime = dateOperation.getDateFromString( todayDateStr + " " + scheduleSetting.getMiddayRestEndTime() );
+		offDutyTime = dateOperation.getDateFromString( todayDateStr + " " + scheduleSetting.getOffDutyTime() );
+
+		signRecordStatus = getSignRecordStatus( wraps, onDutyTime, morningOffdutyTime, afternoonOndutyTime, offDutyTime );
+
+		if( ListTools.isEmpty( wraps )){
+			//一次都没有打过,看看当前时间是上班还是下午,如果是上午,就是上午签到,如果是下午,就是下午签到
+			if( now.after(afternoonOndutyTime)){
+				//在下午下班时间之后了,下午上班签到打卡
+				woSignFeature.setSignSeq(1);
+				woSignFeature.setCheckinType( AttendanceDetailMobile.CHECKIN_TYPE_AFTERNOON_ONDUTY );
+				woSignFeature.setSignTime(scheduleSetting.getOnDutyTime());
+			}else{
+				woSignFeature.setSignSeq(1);
+				woSignFeature.setCheckinType( AttendanceDetailMobile.CHECKIN_TYPE_ONDUTY );
+				woSignFeature.setSignTime(scheduleSetting.getOnDutyTime());
+			}
+		}else{
+			woSignFeature.setSignSeq(2);
+			//当前是什么区间
+			if( onDutyTime.after(now)){
+				//上午上班之前,无论几次,都只可能是上午的下班卡
+				woSignFeature.setCheckinType( AttendanceDetailMobile.CHECKIN_TYPE_MORNING_OFFDUTY );
+				woSignFeature.setSignTime(scheduleSetting.getMiddayRestStartTime());
+				woSignFeature.setSignSeq(2);
+			}else if( now.after(onDutyTime) && morningOffdutyTime.after(now)){
+				//上午上班时段: 上午签退
+				woSignFeature.setSignSeq(2);
+				woSignFeature.setCheckinType( AttendanceDetailMobile.CHECKIN_TYPE_MORNING_OFFDUTY );
+				woSignFeature.setSignTime(scheduleSetting.getMiddayRestStartTime());
+			}else if( now.after(morningOffdutyTime) && afternoonOndutyTime.after( now )){
+				//午休时段:前一次打卡有可能上午签到卡,可能下午签到卡
+				if( signRecordStatus.alreadyOnduty ){ //已经上午签到过了,只有一次卡,应该就是签到
+					woSignFeature.setCheckinType( AttendanceDetailMobile.CHECKIN_TYPE_MORNING_OFFDUTY );
+					woSignFeature.setSignTime(scheduleSetting.getMiddayRestStartTime());
+				}else if( signRecordStatus.alreadyAfternoonOnDuty){
+					//如果上午没有签到,是下午的签到的话,第二次就应该是下午签退打卡了
+					woSignFeature.setCheckinType( AttendanceDetailMobile.CHECKIN_TYPE_OFFDUTY );
+					woSignFeature.setSignTime(scheduleSetting.getOffDutyTime());
+				}
+			}else if( now.after(afternoonOndutyTime) && offDutyTime.after(now)){
+				//下午上班时段,如果前一次是下午签到,那么下一次就应该是下午签退了,否则,就是下午签到
+				if( signRecordStatus.alreadyAfternoonOnDuty){
+					woSignFeature.setCheckinType( AttendanceDetailMobile.CHECKIN_TYPE_OFFDUTY  );
+					woSignFeature.setSignTime(scheduleSetting.getOffDutyTime());
+				}else{
+					woSignFeature.setCheckinType( AttendanceDetailMobile.CHECKIN_TYPE_AFTERNOON_ONDUTY  );
+					woSignFeature.setSignTime(scheduleSetting.getMiddayRestEndTime());
+				}
+			}else{
+				//下午下班之后,只可能是下午的签到签退卡了
+				if( signRecordStatus.alreadyAfternoonOnDuty){
+					woSignFeature.setSignSeq(-1);
+					woSignFeature.setCheckinType( AttendanceDetailMobile.CHECKIN_TYPE_OFFDUTY  );
+					woSignFeature.setSignTime(scheduleSetting.getOffDutyTime());
+				}else{
+					woSignFeature.setCheckinType( AttendanceDetailMobile.CHECKIN_TYPE_AFTERNOON_ONDUTY  );
+					woSignFeature.setSignTime(scheduleSetting.getMiddayRestEndTime());
+				}
+			}
+		}
+		return woSignFeature;
+	}
+
+	/**
+	 * 计算下一次打卡是什么打卡
+	 * 2-三次打卡(上午上班,下午下班加中午一次共三次)
+	 *
+	 * @param wraps
+	 * @param scheduleSetting
+	 * @return
+	 */
+	protected WoSignFeature getWoSignFeatureWithProxy2(List<ActionListMyMobileRecordToday.WoMobileRecord> wraps, AttendanceScheduleSetting scheduleSetting) throws Exception {
+		DateOperation dateOperation = new DateOperation();
+		WoSignFeature woSignFeature = new WoSignFeature();
+		ActionListMyMobileRecordToday.SignRecordStatus signRecordStatus = null;
+
+		Date now = new Date();
+		Date middayRestEndTime = null;
+		Date onDutyTime = null, offDutyTime = null;
+		Date morningOffdutyTime = null, afternoonOndutyTime = null;
+		String todayDateStr = dateOperation.getDateStringFromDate( now, "YYYY-MM-DD");
+
+		//计算,上班下班时间
+		if( StringUtils.isNotEmpty( scheduleSetting.getOnDutyTime())) {
+			onDutyTime = dateOperation.getDateFromString( todayDateStr + " " + scheduleSetting.getOnDutyTime() );
+		}
+		if( StringUtils.isNotEmpty( scheduleSetting.getOffDutyTime())) {
+			offDutyTime = dateOperation.getDateFromString( todayDateStr + " " + scheduleSetting.getOffDutyTime() );
+		}
+		if( StringUtils.isNotEmpty( scheduleSetting.getMiddayRestStartTime())) {
+			morningOffdutyTime = dateOperation.getDateFromString( todayDateStr + " " + scheduleSetting.getMiddayRestStartTime() );
+		}
+		if( StringUtils.isNotEmpty( scheduleSetting.getMiddayRestEndTime())) {
+			afternoonOndutyTime = dateOperation.getDateFromString( todayDateStr + " " + scheduleSetting.getMiddayRestEndTime() );
+		}
+		signRecordStatus = getSignRecordStatus( wraps, onDutyTime, morningOffdutyTime, afternoonOndutyTime, offDutyTime );
+
+		if( ListTools.isEmpty( wraps )){
+			//一次都没有打过,只能打上班卡
+			if( woSignFeature.getSignSeq() == 0 ){
+				woSignFeature.setSignSeq(1);
+				woSignFeature.setCheckinType( AttendanceDetailMobile.CHECKIN_TYPE_ONDUTY );
+				woSignFeature.setSignTime(scheduleSetting.getOnDutyTime());
+			}
+		}else{
+			//打了一次,之后,有可能是中午签到打卡,有可能是下午下班签退打卡
+			woSignFeature.setSignSeq( wraps.size() + 1 );
+			//可能是午休或者下班卡了,上班打不可能打了
+			//看当前时间,有没有过中午签到时间
+			if( morningOffdutyTime !=null && morningOffdutyTime.before( now ) ){//现在已经是午休之后了
+				//看看下班没,如果下班了,就直接打下班卡了,否则,还可以打个午休卡
+				if( offDutyTime !=null && offDutyTime.before( now ) ){
+					//下班了,要么打下班卡
+					if( !signRecordStatus.alreadyOffDuty ){
+						woSignFeature.setCheckinType( AttendanceDetailMobile.CHECKIN_TYPE_OFFDUTY );
+						woSignFeature.setSignTime(scheduleSetting.getOffDutyTime());
+					}else{
+						//下班卡打过了,不用再打卡了
+						woSignFeature.setSignSeq(-1);
+						woSignFeature.setCheckinType( AttendanceDetailMobile.CHECKIN_TYPE_OFFDUTY );
+						woSignFeature.setSignTime(scheduleSetting.getOffDutyTime());
+					}
+				}else{
+					//还没有下午,可以打午休卡,看看是否已经打过午休卡了
+					if( !signRecordStatus.alreadyAfternoon ){
+						//没有打过午休卡,那么可以打一下午休卡
+						woSignFeature.setCheckinType( AttendanceDetailMobile.CHECKIN_TYPE_AFTERNOON );
+						woSignFeature.setSignTime(scheduleSetting.getMiddayRestEndTime());
+					}else{
+						//午休卡打过了,看看下班卡打过没有,如果没有,可以打下班卡
+						if( !signRecordStatus.alreadyOffDuty ){
+							woSignFeature.setCheckinType( AttendanceDetailMobile.CHECKIN_TYPE_OFFDUTY );
+							woSignFeature.setSignTime(scheduleSetting.getOffDutyTime());
+						}else{
+							//下班卡打过了,不用再打卡了
+							woSignFeature.setSignSeq(-1);
+							woSignFeature.setCheckinType( AttendanceDetailMobile.CHECKIN_TYPE_OFFDUTY );
+							woSignFeature.setSignTime(scheduleSetting.getOffDutyTime());
+						}
+					}
+				}
+			}else{
+				//现在在午休之前,要么上班卡,要么午休卡,已经打过一次了,肯定不能再打上班卡了,看看是否已经打过了午休卡
+				if( !signRecordStatus.alreadyAfternoon ){
+					//没有打过午休卡,那么可以打午休卡
+					woSignFeature.setCheckinType( AttendanceDetailMobile.CHECKIN_TYPE_AFTERNOON );
+					woSignFeature.setSignTime(scheduleSetting.getMiddayRestEndTime());
+				}else{
+					//午休卡打过了,看看下班卡打过没有,如果没有,可以打下班卡
+					if( !signRecordStatus.alreadyOffDuty ){
+						woSignFeature.setCheckinType( AttendanceDetailMobile.CHECKIN_TYPE_OFFDUTY );
+						woSignFeature.setSignTime(scheduleSetting.getOffDutyTime());
+					}else{
+						//下班卡打过了,不用再打卡了
+						woSignFeature.setSignSeq(-1);
+						woSignFeature.setCheckinType( AttendanceDetailMobile.CHECKIN_TYPE_OFFDUTY );
+						woSignFeature.setSignTime(scheduleSetting.getOffDutyTime());
+					}
+				}
+			}
+		}
+
+		return woSignFeature;
+	}
+
+	/**
+	 * 计算下一次打卡是什么打卡
+	 * 1-两次打卡(上午上班,下午下班)
+	 * @param wraps
+	 * @param scheduleSetting
+	 * @return
+	 */
+	protected WoSignFeature getWoSignFeatureWithProxy1(List<ActionListMyMobileRecordToday.WoMobileRecord> wraps, AttendanceScheduleSetting scheduleSetting) {
+		WoSignFeature woSignFeature = new WoSignFeature();
+		if( ListTools.isEmpty( wraps )){
+			//一次都没有打过,不管几点,都是上班打卡
+			woSignFeature.setSignSeq(1);
+			woSignFeature.setCheckinType( AttendanceDetailMobile.CHECKIN_TYPE_ONDUTY );
+			woSignFeature.setSignTime(scheduleSetting.getOnDutyTime());
+		}else{
+			//打了一次,就是下班打卡,打了两次,就没有了
+			if( wraps.size() == 1 ){
+				woSignFeature.setSignSeq(2);
+				woSignFeature.setCheckinType( AttendanceDetailMobile.CHECKIN_TYPE_OFFDUTY );
+				woSignFeature.setSignTime(scheduleSetting.getOffDutyTime());
+			}else{
+				woSignFeature.setSignSeq(-1); //没有需要的打卡了
+				woSignFeature.setCheckinType( AttendanceDetailMobile.CHECKIN_TYPE_OFFDUTY );
+				woSignFeature.setSignTime(scheduleSetting.getOffDutyTime());
+			}
+		}
+		return woSignFeature;
+	}
+
+	private ActionListMyMobileRecordToday.SignRecordStatus getSignRecordStatus(List<ActionListMyMobileRecordToday.WoMobileRecord> wraps, Date onDutyTime, Date morningOffdutyTime, Date afternoonOndutyTime, Date offDutyTime) {
+		ActionListMyMobileRecordToday.SignRecordStatus signRecordStatus = new ActionListMyMobileRecordToday.SignRecordStatus();
+		if( ListTools.isNotEmpty( wraps )){
+			for( ActionListMyMobileRecordToday.WoMobileRecord record : wraps ){
+				if( StringUtils.equalsAnyIgnoreCase( AttendanceDetailMobile.CHECKIN_TYPE_ONDUTY, record.getCheckin_type() )){
+					signRecordStatus.setAlreadyOnduty( true );
+				}
+				if( StringUtils.equalsAnyIgnoreCase( AttendanceDetailMobile.CHECKIN_TYPE_AFTERNOON, record.getCheckin_type() )){
+					signRecordStatus.setAlreadyAfternoon( true );
+				}
+				if( StringUtils.equalsAnyIgnoreCase( AttendanceDetailMobile.CHECKIN_TYPE_MORNING_OFFDUTY, record.getCheckin_type() )){
+					signRecordStatus.setAlreadyMorningOffDuty( true );
+				}
+				if( StringUtils.equalsAnyIgnoreCase( AttendanceDetailMobile.CHECKIN_TYPE_AFTERNOON_ONDUTY, record.getCheckin_type() )){
+					signRecordStatus.setAlreadyAfternoonOnDuty( true );
+				}
+				if( StringUtils.equalsAnyIgnoreCase( AttendanceDetailMobile.CHECKIN_TYPE_OFFDUTY, record.getCheckin_type() )){
+					signRecordStatus.setAlreadyOffDuty( true );
+				}
+			}
+		}
+		return signRecordStatus;
+	}
+
+	public static class SignRecordStatus {
+
+		@FieldDescribe("是否上午上班打卡过了ONDUTY")
+		private Boolean alreadyOnduty = false;
+
+		@FieldDescribe("是否上午下班打卡过了MORNING_OFFDUTY.")
+		private Boolean alreadyMorningOffDuty = false;
+
+		@FieldDescribe("是否下午上班打卡过了AFTERNOON_ONDUTY")
+		private Boolean alreadyAfternoonOnDuty = false;
+
+		@FieldDescribe("是否下午下班打卡过了OFFDUTY.")
+		private Boolean alreadyOffDuty = false;
+
+		@FieldDescribe("是否午间打卡过了AfternoonSign.")
+		private Boolean alreadyAfternoon = false;
+
+		public Boolean getAlreadyOnduty() { return alreadyOnduty; }
+
+		public void setAlreadyOnduty(Boolean alreadyOnduty) { this.alreadyOnduty = alreadyOnduty; }
+
+		public Boolean getAlreadyMorningOffDuty() { return alreadyMorningOffDuty; }
+
+		public void setAlreadyMorningOffDuty(Boolean alreadyMorningOffDuty) { this.alreadyMorningOffDuty = alreadyMorningOffDuty; }
+
+		public Boolean getAlreadyAfternoonOnDuty() { return alreadyAfternoonOnDuty; }
+
+		public void setAlreadyAfternoonOnDuty(Boolean alreadyAfternoonOnDuty) { this.alreadyAfternoonOnDuty = alreadyAfternoonOnDuty; }
+
+		public Boolean getAlreadyOffDuty() { return alreadyOffDuty; }
+
+		public void setAlreadyOffDuty(Boolean alreadyOffDuty) { this.alreadyOffDuty = alreadyOffDuty; }
+
+		public Boolean getAlreadyAfternoon() { return alreadyAfternoon; }
+
+		public void setAlreadyAfternoon(Boolean alreadyAfternoon) { this.alreadyAfternoon = alreadyAfternoon; }
+	}
+
+	public static class WoMobileRecord extends AttendanceDetailMobile {
+
+		private static final long serialVersionUID = -5076990764713538973L;
+
+		public static WrapCopier<AttendanceDetailMobile, WoMobileRecord> copier = WrapCopierFactory.wo(AttendanceDetailMobile.class,
+				WoMobileRecord.class, null, JpaObject.FieldsInvisible);
+	}
 }

+ 50 - 0
o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/schedule/DetailLastDayRecordAnalyseTask.java

@@ -0,0 +1,50 @@
+package com.x.attendance.assemble.control.schedule;
+
+import com.x.attendance.assemble.common.date.DateOperation;
+import com.x.attendance.assemble.control.ThisApplication;
+import com.x.attendance.assemble.control.service.AttendanceDetailMobileAnalyseServiceAdv;
+import com.x.attendance.assemble.control.service.AttendanceDetailMobileService;
+import com.x.attendance.assemble.control.service.AttendanceDetailServiceAdv;
+import com.x.base.core.container.EntityManagerContainer;
+import com.x.base.core.container.factory.EntityManagerContainerFactory;
+import com.x.base.core.project.logger.Logger;
+import com.x.base.core.project.logger.LoggerFactory;
+import com.x.base.core.project.schedule.AbstractJob;
+import com.x.base.core.project.tools.ListTools;
+import org.quartz.JobExecutionContext;
+
+import java.util.List;
+
+/**
+ * 每天凌晨分析前一天未签退的打卡数据
+ * 有一些用户是经常会忘记签退,没有最终的下班打卡数据
+ */
+public class DetailLastDayRecordAnalyseTask extends AbstractJob {
+
+	private static Logger logger = LoggerFactory.getLogger(DetailLastDayRecordAnalyseTask.class);
+
+	private AttendanceDetailServiceAdv attendanceDetailServiceAdv = new AttendanceDetailServiceAdv();
+
+	@Override
+	public void schedule(JobExecutionContext jobExecutionContext) throws Exception {
+		List<String> ids = null;
+		DateOperation dateOperation = new DateOperation();
+		try (EntityManagerContainer emc = EntityManagerContainerFactory.instance().create()) {
+			ids = attendanceDetailServiceAdv.listRecordWithDateAndNoOffDuty( dateOperation.getNowDate() );
+		} catch (Exception e) {
+			logger.error(new QueryMobileDetailWithStatusException(0));
+		}
+		if(ListTools.isNotEmpty( ids )){
+			for( String id : ids ){
+				try {
+					ThisApplication.detailAnalyseQueue.send( id );
+				} catch ( Exception e1 ) {
+					e1.printStackTrace();
+				}
+			}
+		}
+
+		logger.info("Timertask MobileRecordAnalyseTask completed and excute success.");
+	}
+
+}

+ 5 - 1
o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/service/AttendanceDetailAnalyseService.java

@@ -296,7 +296,11 @@ public class AttendanceDetailAnalyseService {
 						saveAnalyseResultAndStatus( emc, detail.getId(), -1, "系统格式化记录的打卡日期发生异常,未能设置日期格式的打卡时间属性recordDate,日期recordDateString:" + detail.getRecordDateString() );
 						check = false;
 					}
-					if( recordDate != null ){ detail.setRecordDate( recordDate ); }
+					if( recordDate != null ){
+						detail.setRecordDate( recordDate );
+						detail.setYearString( dateOperation.getYear( detail.getRecordDate() ) );
+						detail.setMonthString( dateOperation.getMonth( detail.getRecordDate() ) );
+					}
 				}else{
 					saveAnalyseResultAndStatus( emc, detail.getId(), -1, "系统格式化记录的打卡日期发生异常,未能设置日期格式的打卡时间属性为空" );
 					check = false;

+ 22 - 17
o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/service/AttendanceDetailAnalyseSignProxy2.java

@@ -51,6 +51,8 @@ class AttendanceDetailAnalyseSignProxy2 {
 		morningEndTime = AttendanceDetailAnalyseCoreService.getMiddleRestStartTimeFromDetail( detail, scheduleSetting, debugger );
 		afternoonStartTime = AttendanceDetailAnalyseCoreService.getMiddleRestEndTimeFromDetail( detail, scheduleSetting, debugger );
 
+		Date now = new Date();
+
 		if ( onWorkTime != null && offWorkTime != null && morningEndTime != null && afternoonStartTime != null ) {
 			logger.debug( debugger, "上下班排班信息获取正常:onWorkTime=" +  onWorkTime + ", morningEndTime="+morningEndTime + ", afternoonStartTime="+afternoonStartTime + ", offWorkTime="+offWorkTime );
 			logger.debug( debugger, "上下班签到信息获取正常:onDutyTime=" +  onDutyTime + ", middleDutyTime="+middleDutyTime + ", offDutyTime="+offDutyTime );
@@ -122,29 +124,32 @@ class AttendanceDetailAnalyseSignProxy2 {
 
 					}
 				}else{
-					logger.debug( debugger, "员工上午缺卡" );
-					if(  isSelfHoliday_Morning || isSelfHoliday_Afternoon || isNotWorkDay ){
-						logger.debug( debugger, "请假不计考勤,不需要打卡,不算异常" );
-						detail.setIsAbsent( false );
-						detail.setAbnormalDutyDayTime("无");
-						detail.setIsAbnormalDuty( false );
-					}else{
-						logger.debug(debugger, "没打卡,没请假,工作日,算缺勤。");
-						detail.setAbnormalDutyDayTime("上午");
-						detail.setIsAbnormalDuty(true);
-						detail.setAbsentDayTime("上午");
-						detail.setIsAbsent(true);
-						AttendanceDetailAnalyseCoreService.increaseAbsenceStatusForAttendanceDetail(detail);
-						AttendanceDetailAnalyseCoreService.increaseAttendanceStatusForAttendanceDetail(detail);
+					if( offWorkTime.before( now ) ){
+						//上班卡没打的情况
+						logger.debug( debugger, "员工上午缺卡" );
+						if(  isSelfHoliday_Morning || isSelfHoliday_Afternoon || isNotWorkDay ){
+							logger.debug( debugger, "请假不计考勤,不需要打卡,不算异常" );
+							detail.setIsAbsent( false );
+							detail.setAbnormalDutyDayTime("无");
+							detail.setIsAbnormalDuty( false );
+						}else{
+							logger.debug(debugger, "没打卡,没请假,工作日,算缺勤。");
+							detail.setAbnormalDutyDayTime("上午");
+							detail.setIsAbnormalDuty(true);
+							detail.setAbsentDayTime("上午");
+							detail.setIsAbsent(true);
+							AttendanceDetailAnalyseCoreService.increaseAbsenceStatusForAttendanceDetail(detail);
+							AttendanceDetailAnalyseCoreService.increaseAttendanceStatusForAttendanceDetail(detail);
+						}
+						logger.debug( debugger, "上午工作时长, 未打卡:minutes= 0 分钟。" );
+						detail.setWorkTimeDuration( 0L );
 					}
-					logger.debug( debugger, "上午工作时长, 未打卡:minutes= 0 分钟。" );
-					detail.setWorkTimeDuration( 0L );
 				}
 
 				//=========================================================================================================
 				//=====中午  如果员工已经签到, 中午签到只是一个记录 ,如果未签到,则记录中缺卡 ================================================================================
 				//=========================================================================================================
-				if( middleDutyTime == null ){
+				if( middleDutyTime == null && morningEndTime.before( now ) ){
 					if ( isSelfHoliday_Afternoon || isSelfHoliday_Afternoon || isNotWorkDay ) {
 						logger.debug(debugger, "请假不计考勤,不算出勤");
 						detail.setIsAbsent(false);

+ 6 - 1
o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/service/AttendanceDetailService.java

@@ -313,5 +313,10 @@ public class AttendanceDetailService {
 		}else {
 			return null;
 		}
-	}	
+	}
+
+    public List<String> listRecordWithDateAndNoOffDuty(EntityManagerContainer emc, String dateString) throws Exception {
+		Business business =  new Business( emc );
+		return business.getAttendanceDetailFactory().listRecordWithDateAndNoOffDuty( dateString );
+    }
 }

+ 16 - 3
o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/service/AttendanceDetailServiceAdv.java

@@ -229,7 +229,7 @@ public class AttendanceDetailServiceAdv {
 	}
 
 	public AttendanceDetailMobile getMobile(String id) throws Exception {
-		if( id == null || id.isEmpty() ){
+		if( StringUtils.isEmpty( id )){
 			return null;
 		}
 		try (EntityManagerContainer emc = EntityManagerContainerFactory.instance().create()) {
@@ -269,7 +269,6 @@ public class AttendanceDetailServiceAdv {
 					//1、一天只打上下班两次卡
 					detail = new ComposeDetailWithMobileInSignProxy1().compose( mobileDetails, scheduleSetting, debugger);
 				}
-
 				if( detail_old == null ) {
 					detail.setBatchName( "FromMobile_" + dateOperation.getNowTimeChar() );
 					emc.beginTransaction( AttendanceDetail.class );
@@ -282,7 +281,6 @@ public class AttendanceDetailServiceAdv {
 					emc.check( detail_old , CheckPersistType.all );
 					emc.commit();
 				}
-
 				emc.beginTransaction( AttendanceDetailMobile.class );
 				for( AttendanceDetailMobile detailMobile : mobileDetails ) {
 					detailMobile.setRecordStatus(1);
@@ -303,4 +301,19 @@ public class AttendanceDetailServiceAdv {
 	}
 
 
+	/**
+	 * 根据日期查询未签退的所有打卡记录ID
+	 * @param dateString yyyy-mm-dd
+	 * @return
+	 */
+	public List<String> listRecordWithDateAndNoOffDuty( String dateString ) throws Exception {
+		if( StringUtils.isEmpty( dateString )){
+			return null;
+		}
+		try (EntityManagerContainer emc = EntityManagerContainerFactory.instance().create()) {
+			return attendanceDetailService.listRecordWithDateAndNoOffDuty( emc, dateString );
+		} catch ( Exception e ) {
+			throw e;
+		}
+	}
 }