Sfoglia il codice sorgente

Merge branch 'feature/new-dd-qywx-attendance' into 'develop'

Feature/钉钉考勤打卡功能

See merge request o2oa/o2oa!256
楼国栋 5 anni fa
parent
commit
4a4415d4a6
72 ha cambiato i file con 5971 aggiunte e 341 eliminazioni
  1. 10 19
      o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/Business.java
  2. 376 25
      o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/DingdingAttendanceQueue.java
  3. 130 0
      o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/DingdingPersonStatisticQueue.java
  4. 145 0
      o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/DingdingUnitStatisticQueue.java
  5. 339 0
      o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/QywxAttendanceSyncQueue.java
  6. 29 2
      o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/ThisApplication.java
  7. 12 0
      o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/exception/DingDingRequestException.java
  8. 9 0
      o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/exception/DingdingFindNoArgumentError.java
  9. 9 0
      o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/exception/QywxFindNoArgumentError.java
  10. 509 0
      o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/factory/DingdingAttendanceFactory.java
  11. 6 0
      o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/jaxrs/ActionApplication.java
  12. 2 1
      o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/jaxrs/DingdingJaxrsFilter.java
  13. 10 0
      o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/jaxrs/DingdingStatisticJaxrsFilter.java
  14. 10 0
      o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/jaxrs/QywxJaxrsFilter.java
  15. 32 0
      o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/jaxrs/attendancesetting/ActionEnableType.java
  16. 18 0
      o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/jaxrs/attendancesetting/AttendanceSettingAction.java
  17. 2 2
      o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/jaxrs/dingding/ActionDeleteAllData.java
  18. 337 0
      o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/jaxrs/dingding/ActionListDDAttendanceDetail.java
  19. 81 0
      o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/jaxrs/dingding/ActionListDingdingSyncRecord.java
  20. 34 0
      o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/jaxrs/dingding/ActionStatisticPersonMonthData.java
  21. 34 0
      o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/jaxrs/dingding/ActionStatisticUnitDayData.java
  22. 22 14
      o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/jaxrs/dingding/ActionSyncData.java
  23. 13 0
      o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/jaxrs/dingding/ConflictSyncRecordException.java
  24. 85 7
      o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/jaxrs/dingding/DingdingAttendanceAction.java
  25. 13 0
      o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/jaxrs/dingding/MoreThanSevenDayException.java
  26. 1 1
      o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/jaxrs/dingding/SyncWayException.java
  27. 14 0
      o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/jaxrs/dingding/exception/SearchArgEmptyException.java
  28. 14 0
      o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/jaxrs/dingding/exception/TimeEmptyException.java
  29. 42 0
      o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/jaxrs/dingdingstatistic/ActionPersonStatistic.java
  30. 42 0
      o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/jaxrs/dingdingstatistic/ActionPersonStatisticWithUnit.java
  31. 15 0
      o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/jaxrs/dingdingstatistic/ActionTest.java
  32. 46 0
      o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/jaxrs/dingdingstatistic/ActionUnitStatistic.java
  33. 6 0
      o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/jaxrs/dingdingstatistic/BaseAction.java
  34. 110 0
      o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/jaxrs/dingdingstatistic/DingdingAttendanceStatisticAction.java
  35. 13 0
      o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/jaxrs/dingdingstatistic/EmptyArgsException.java
  36. 45 0
      o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/jaxrs/qywx/ActionDeleteAllQywxAttendanceData.java
  37. 117 0
      o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/jaxrs/qywx/ActionListQywxAttendanceDetail.java
  38. 87 0
      o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/jaxrs/qywx/ActionListQywxSyncRecord.java
  39. 54 0
      o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/jaxrs/qywx/ActionSyncQywxData.java
  40. 6 0
      o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/jaxrs/qywx/BaseAction.java
  41. 103 0
      o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/jaxrs/qywx/QywxAttendanceAction.java
  42. 13 0
      o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/jaxrs/qywx/SyncDateException.java
  43. 37 0
      o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/schedule/DingdingAttendanceStatisticPersonScheduleTask.java
  44. 37 0
      o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/schedule/DingdingAttendanceStatisticScheduleTask.java
  45. 48 0
      o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/schedule/DingdingAttendanceSyncScheduleTask.java
  46. 47 0
      o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/schedule/QywxAttendanceSyncScheduleTask.java
  47. 3 3
      o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/service/UserManagerService.java
  48. 122 18
      o2server/x_attendance_assemble_control/src/main/webapp/describe/sources/com/x/attendance/assemble/control/DingdingAttendanceQueue.java
  49. 2 0
      o2server/x_attendance_assemble_control/src/main/webapp/describe/sources/com/x/attendance/assemble/control/jaxrs/ActionApplication.java
  50. 2 1
      o2server/x_attendance_assemble_control/src/main/webapp/describe/sources/com/x/attendance/assemble/control/jaxrs/DingdingJaxrsFilter.java
  51. 18 0
      o2server/x_attendance_assemble_control/src/main/webapp/describe/sources/com/x/attendance/assemble/control/jaxrs/attendancesetting/AttendanceSettingAction.java
  52. 5 14
      o2server/x_attendance_assemble_control/src/main/webapp/describe/sources/com/x/attendance/assemble/control/jaxrs/dingding/ActionSyncData.java
  53. 5 4
      o2server/x_attendance_assemble_control/src/main/webapp/describe/sources/com/x/attendance/assemble/control/jaxrs/dingding/DingdingAttendanceAction.java
  54. 38 1
      o2server/x_attendance_core_entity/src/main/java/com/x/attendance/entity/AttendanceDingtalkDetail.java
  55. 19 9
      o2server/x_attendance_core_entity/src/main/java/com/x/attendance/entity/DingdingQywxSyncRecord.java
  56. 9 0
      o2server/x_attendance_core_entity/src/main/java/com/x/attendance/entity/PersistenceProperties.java
  57. 214 0
      o2server/x_attendance_core_entity/src/main/java/com/x/attendance/entity/StatisticDingdingPersonForMonth.java
  58. 215 0
      o2server/x_attendance_core_entity/src/main/java/com/x/attendance/entity/StatisticDingdingUnitForDay.java
  59. 201 0
      o2server/x_attendance_core_entity/src/main/java/com/x/attendance/entity/StatisticDingdingUnitForMonth.java
  60. 59 1
      o2server/x_base_core_project/src/main/java/com/x/base/core/project/config/Qiyeweixin.java
  61. 28 0
      o2server/x_base_core_project/src/main/java/com/x/base/core/project/tools/DateTools.java
  62. 2 1
      o2server/x_base_core_project/src/main/java/com/x/base/core/project/x_attendance_assemble_control.java
  63. 74 57
      o2web/source/x_component_Attendance/$Main/navi.json
  64. 42 0
      o2web/source/x_component_Attendance/$PeopleDetail/listItem_dingding.json
  65. 83 0
      o2web/source/x_component_Attendance/$PeopleDetail/listItem_dingding_detailStatic.json
  66. 42 0
      o2web/source/x_component_Attendance/$UnitDetail/listItem_dingding.json
  67. 83 0
      o2web/source/x_component_Attendance/$UnitDetail/listItem_dingding_detailStatic.json
  68. 208 161
      o2web/source/x_component_Attendance/Main.js
  69. 455 0
      o2web/source/x_component_Attendance/PeopleDingdingDetail.js
  70. 372 0
      o2web/source/x_component_Attendance/UnitDingdingDetail.js
  71. 501 0
      o2web/source/x_component_Attendance/UnitDingdingIndex.js
  72. 5 0
      o2web/source/x_component_Attendance/lp/zh-cn.js

+ 10 - 19
o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/Business.java

@@ -1,24 +1,6 @@
 package com.x.attendance.assemble.control;
 
-import com.x.attendance.assemble.control.factory.AttendanceAdminFactory;
-import com.x.attendance.assemble.control.factory.AttendanceAppealInfoFactory;
-import com.x.attendance.assemble.control.factory.AttendanceDetailFactory;
-import com.x.attendance.assemble.control.factory.AttendanceDetailMobileFactory;
-import com.x.attendance.assemble.control.factory.AttendanceDetailStatisticFactory;
-import com.x.attendance.assemble.control.factory.AttendanceEmployeeConfigFactory;
-import com.x.attendance.assemble.control.factory.AttendanceImportFileInfoFactory;
-import com.x.attendance.assemble.control.factory.AttendanceScheduleSettingFactory;
-import com.x.attendance.assemble.control.factory.AttendanceSelfHolidayFactory;
-import com.x.attendance.assemble.control.factory.AttendanceSettingFactory;
-import com.x.attendance.assemble.control.factory.AttendanceStatisticRequireLogFactory;
-import com.x.attendance.assemble.control.factory.AttendanceStatisticalCycleFactory;
-import com.x.attendance.assemble.control.factory.AttendanceWorkDayConfigFactory;
-import com.x.attendance.assemble.control.factory.AttendanceWorkPlaceFactory;
-import com.x.attendance.assemble.control.factory.StatisticPersonForMonthFactory;
-import com.x.attendance.assemble.control.factory.StatisticTopUnitForDayFactory;
-import com.x.attendance.assemble.control.factory.StatisticTopUnitForMonthFactory;
-import com.x.attendance.assemble.control.factory.StatisticUnitForDayFactory;
-import com.x.attendance.assemble.control.factory.StatisticUnitForMonthFactory;
+import com.x.attendance.assemble.control.factory.*;
 import com.x.base.core.container.EntityManagerContainer;
 import com.x.organization.core.express.Organization;
 
@@ -34,6 +16,8 @@ public class Business {
 		return this.emc;
 	}
 
+	//钉钉同步数据处理
+	private DingdingAttendanceFactory dingdingAttendanceFactory;
 	// 人员组织业务处理类
 	private Organization organization;
 	// 系统配置业务处理类
@@ -74,6 +58,13 @@ public class Business {
 
 	private AttendanceDetailStatisticFactory attendanceDetailStatisticFactory;
 
+	public DingdingAttendanceFactory dingdingAttendanceFactory() throws Exception {
+		if (null == this.dingdingAttendanceFactory) {
+			this.dingdingAttendanceFactory = new DingdingAttendanceFactory(this);
+		}
+		return this.dingdingAttendanceFactory;
+	}
+
 	public AttendanceWorkPlaceFactory attendanceWorkPlaceFactory() throws Exception {
 		if (null == this.attendanceWorkPlaceFactory) {
 			this.attendanceWorkPlaceFactory = new AttendanceWorkPlaceFactory(this);

+ 376 - 25
o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/DingdingAttendanceQueue.java

@@ -1,19 +1,38 @@
 package com.x.attendance.assemble.control;
 
+import com.x.attendance.assemble.control.exception.DingDingRequestException;
+import com.x.attendance.entity.AttendanceDingtalkDetail;
+import com.x.attendance.entity.AttendanceDingtalkDetail_;
 import com.x.attendance.entity.DingdingQywxSyncRecord;
 import com.x.base.core.container.EntityManagerContainer;
 import com.x.base.core.container.factory.EntityManagerContainerFactory;
+import com.x.base.core.entity.JpaObject;
 import com.x.base.core.project.Application;
+import com.x.base.core.project.bean.WrapCopier;
+import com.x.base.core.project.bean.WrapCopierFactory;
 import com.x.base.core.project.config.Config;
 import com.x.base.core.project.connection.HttpConnection;
 import com.x.base.core.project.gson.GsonPropertyObject;
 import com.x.base.core.project.logger.Logger;
 import com.x.base.core.project.logger.LoggerFactory;
 import com.x.base.core.project.organization.Person;
+import com.x.base.core.project.organization.Unit;
 import com.x.base.core.project.queue.AbstractQueue;
+import com.x.base.core.project.tools.DateTools;
+import com.x.base.core.project.tools.ListTools;
 import com.x.base.core.project.x_organization_assemble_control;
+import org.apache.commons.lang3.StringUtils;
 
+import javax.persistence.EntityManager;
+import javax.persistence.criteria.CriteriaBuilder;
+import javax.persistence.criteria.CriteriaQuery;
+import javax.persistence.criteria.Predicate;
+import javax.persistence.criteria.Root;
+import java.util.Calendar;
+import java.util.Date;
 import java.util.List;
+import java.util.Optional;
+import java.util.stream.Collectors;
 
 public class DingdingAttendanceQueue extends AbstractQueue<DingdingQywxSyncRecord> {
 
@@ -21,37 +40,204 @@ public class DingdingAttendanceQueue extends AbstractQueue<DingdingQywxSyncRecor
 
     @Override
     protected void execute(DingdingQywxSyncRecord record) throws Exception {
-        logger.info("开始执行钉钉打卡数据同步," + record.getWay());
+        logger.info("开始执行钉钉打卡数据同步,from:" + record.getDateFrom() + ", to:"+record.getDateTo());
+        if (DingdingQywxSyncRecord.syncType_dingding.equals(record.getType())) {
+            try {
+                dingdingSync(record);
+            }catch (Exception e) {
+                logger.error(e);
+                updateSyncRecord(record, e.getMessage());
+            }
+        } else {
+            logger.info("不是钉钉同步任务。。。。。。。。。。。。。");
+        }
+    }
 
+    private boolean isSameDay(Date startDate, Date endDate) {
+        Calendar start = Calendar.getInstance();
+        start.setTime( startDate );
+        Calendar end = Calendar.getInstance();
+        end.setTime(endDate);
+        return (start.get(Calendar.YEAR) == end.get(Calendar.YEAR) && start.get(Calendar.DAY_OF_YEAR) == end.get(Calendar.DAY_OF_YEAR));
     }
+    private void dingdingSync(DingdingQywxSyncRecord record) throws Exception {
+        Application app = ThisApplication.context().applications().randomWithWeight(x_organization_assemble_control.class.getName());
+        //开始分页查询人员
+        boolean hasNextPerson = true;
+        int personPageSize = 50;
+        //开始时间和结束时间
+        Date fromDate = new Date();
+        fromDate.setTime(record.getDateFrom());
+        Date toDate =new Date();
+        toDate.setTime(record.getDateTo());
+        //先删除
+        deleteDingdingAttendance(fromDate, toDate);
+        //人员查询地址
+        String uri = "person/list/(0)/next/50";
+        //钉钉考勤同步接口地址
+        String dingdingUrl = "https://oapi.dingtalk.com/attendance/list?access_token=" + Config.dingding().corpAccessToken();
+        int saveNumber = 0;
+        while (hasNextPerson) {
+            List<Person> list = ThisApplication.context().applications().getQuery(false, app, uri).getDataAsList(Person.class);
+            if (list != null && list.size() > 0) {
+                //钉钉用户id
+                List<String> ddUsers = list.stream().filter(person -> StringUtils.isNotEmpty(person.getDingdingId()))
+                        .map(Person::getDingdingId).collect(Collectors.toList());
+                if (ListTools.isNotEmpty(ddUsers)) {
+                    //分页查询
+                    int page = 0;
+                    boolean hasMoreResult = true;
+                    while (hasMoreResult) {
+                        DingdingAttendancePost post = new DingdingAttendancePost();
+                        //post传入 时间(时间间隔不能超过7天) 人员(人员数量不能超过50个)
+                        post.setLimit(50L);
+                        post.setOffset(page * 50L);//从0开始翻页
+                        //这里的开始时间和结束时间 查询的是钉钉返回结果中的workDate
+                        //查询考勤打卡记录的起始工作日
+                        post.setWorkDateFrom(DateTools.format(fromDate));
+                        //查询考勤打卡记录的结束工作日
+                        post.setWorkDateTo(DateTools.format(toDate));
+                        post.setUserIdList(ddUsers);
+                        DingdingAttendanceResult result = HttpConnection.postAsObject(dingdingUrl, null, post.toString(), DingdingAttendanceResult.class);
+                        if (result.errcode != null && result.errcode == 0) {
+                            List<DingdingAttendanceResultItem> resultList = result.getRecordresult();
+                            saveDingdingAttendance(resultList, list);
+                            saveNumber += resultList.size();
+                            if (result.hasMore) {
+                                page++;
+                            } else {
+                                logger.info("同步钉钉考勤结束。。。。。。。。。。。。。。。。");
+                                hasMoreResult = false;
+                            }
+                        } else {
+                            //请求结果异常 结束
+                            throw new DingDingRequestException(result.errmsg);
+                        }
+                    }
+                }
+                //是否还有更多用户
+                if (list.size() < personPageSize) {
+                    logger.info("同步钉钉考勤 没有更多用户了,结束。。。。。。。。。。。。。。。");
+                    hasNextPerson = false;
+                    updateSyncRecord(record, null);
+                } else {
+                    //还有更多用户继续查询
+                    uri = "person/list/" + list.get(list.size() - 1).getDistinguishedName() + "/next/50";
+                }
+            } else {
+                //没有用户查询到结束
+                logger.info("同步钉钉考勤 查询不到用户了,结束。。。。。。。。。。。。。。。");
+                hasNextPerson = false;
+                updateSyncRecord(record, null);
+            }
+        }
+        logger.info("结束 插入:"+saveNumber+" 条");
+        //插入数据成功 开始统计程序
 
-    private void dingdingSync() throws Exception {
+        boolean hasNextDate = true;
+        Date statisticDate = fromDate;
+        while (hasNextDate) {
+            logger.info("发起钉钉考勤数据统计, date:"+ DateTools.format(statisticDate));
+            ThisApplication.personStatisticQueue.send(statisticDate);
+            ThisApplication.unitStatisticQueue.send(statisticDate);
+            if (!isSameDay(statisticDate, toDate)) {
+                statisticDate = DateTools.addDay(statisticDate, 1);
+            }else {
+                hasNextDate = false;
+            }
+        }
+        logger.info("发起数据统计程序 完成。。。。。。。。。。");
+    }
+    private void deleteDingdingAttendance(Date fromDate, Date toDate) throws Exception{
         try (EntityManagerContainer emc = EntityManagerContainerFactory.instance().create()) {
-            Business business = new Business(emc);
-            Application app = ThisApplication.context().applications().randomWithWeight(x_organization_assemble_control.class.getName());
-            //开始分页查询人员
-            boolean hasNextPerson = true;
-            String uri = "person/list/(0)/next/50";
-            String dingdingUrl = "https://oapi.dingtalk.com/attendance/list?access_token="+ Config.dingding().corpAccessToken();
-
-            while (hasNextPerson) {
-                List<Person> list = ThisApplication.context().applications().getQuery(false, app, uri).getDataAsList(Person.class);
-                if (list != null && list.size() > 0) {
-                    DingdingAttendancePost post = new DingdingAttendancePost();
-
-                }else {
-                    //没有用户查询到结束
-                    logger.info("查询不到用户了,结束。。。。。。。。。。。。。。。");
-                    hasNextPerson = false;
+            //先删除 再同步
+            EntityManager em = emc.get(AttendanceDingtalkDetail.class);
+            CriteriaBuilder cb = em.getCriteriaBuilder();
+            CriteriaQuery<AttendanceDingtalkDetail> query = cb.createQuery(AttendanceDingtalkDetail.class);
+            Root<AttendanceDingtalkDetail> root = query.from(AttendanceDingtalkDetail.class);
+            long start = fromDate.getTime();
+            long end = toDate.getTime();
+            Predicate p = cb.between(root.get(AttendanceDingtalkDetail_.workDate), start, end);
+            query.select(root).where(p);
+            List<AttendanceDingtalkDetail> detailList = em.createQuery(query).getResultList();
+            //先删除
+            logger.info("删除");
+            if (detailList != null) {
+                logger.info("删除 list:"+detailList.size());
+                emc.beginTransaction(AttendanceDingtalkDetail.class);
+                for (int i = 0; i < detailList.size(); i++) {
+                    emc.remove(detailList.get(i));
                 }
+                emc.commit();
             }
+            logger.info("删除结束");
+        }
+    }
+
+    private void saveDingdingAttendance(List<DingdingAttendanceResultItem> list, List<Person> personList) throws Exception {
+        if (list != null && !list.isEmpty()) {
+            try (EntityManagerContainer emc = EntityManagerContainerFactory.instance().create()) {
+                Business business = new Business(emc);
+                emc.beginTransaction(AttendanceDingtalkDetail.class);
+                for (int i = 0; i < list.size(); i++) {
+                    DingdingAttendanceResultItem item = list.get(i);
+                    AttendanceDingtalkDetail detail = DingdingAttendanceResultItem.copier.copy(item);
+                    detail.setDdId(item.getId());
+                    if (detail.getUserCheckTime() > 0) {
+                        Date date = new Date(detail.getUserCheckTime());
+                        detail.setUserCheckTimeDate(date);
+                    }
+                    //添加o2组织和用户
+                    Optional<Person> first = personList.stream().filter(p -> item.userId.equals(p.getDingdingId())).findFirst();
+                    if (first.isPresent()) {
+                        Person person = first.get();
+                        String unit = getUnitWithPerson(person.getDistinguishedName(), business);
+                        detail.setO2Unit(unit);
+                        detail.setO2User(person.getDistinguishedName());
+                    }
+                    emc.persist(detail);
+                }
+                emc.commit();
+            }
+        }
+    }
+    private String getUnitWithPerson(String person, Business business) throws Exception {
+        String result = null;
+        Integer level = 0;
+        Unit unit = null;
+        List<String> unitNames = business.organization().unit().listWithPerson( person );
+        if( ListTools.isNotEmpty( unitNames ) ) {
+            for( String unitName : unitNames ) {
+                if( StringUtils.isNotEmpty( unitName ) && !"null".equals( unitName ) ) {
+                    unit = business.organization().unit().getObject( unitName );
+                    if( level < unit.getLevel() ) {
+                        level = unit.getLevel();
+                        result = unitName;
+                    }
+                }
+            }
+        }
+        return result;
+    }
 
+    private void updateSyncRecord(DingdingQywxSyncRecord record, String errMsg) throws Exception {
+        try (EntityManagerContainer emc = EntityManagerContainerFactory.instance().create()) {
+            emc.beginTransaction(DingdingQywxSyncRecord.class);
+            DingdingQywxSyncRecord entity = emc.find(record.getId(), DingdingQywxSyncRecord.class);
+            entity.setEndTime(new Date());
+            if (errMsg == null || errMsg.isEmpty()) {
+                entity.setStatus(DingdingQywxSyncRecord.status_end);
+            } else {
+                entity.setStatus(DingdingQywxSyncRecord.status_error);
+                entity.setExceptionMessage(errMsg);
+            }
+            emc.commit();
         }
     }
 
 
     public static class DingdingAttendancePost extends GsonPropertyObject {
-//        {
+        //        {
 //            "workDateFrom": "yyyy-MM-dd HH:mm:ss",
 //                "workDateTo": "yyyy-MM-dd HH:mm:ss",
 //                "userIdList":["员工UserId列表"],    // 必填,与offset和limit配合使用
@@ -61,8 +247,8 @@ public class DingdingAttendanceQueue extends AbstractQueue<DingdingQywxSyncRecor
         private String workDateFrom;
         private String workDateTo;
         private List<String> userIdList;
-        private Integer offset;
-        private Integer limit;
+        private Long offset;
+        private Long limit;
 
         public String getWorkDateFrom() {
             return workDateFrom;
@@ -88,20 +274,185 @@ public class DingdingAttendanceQueue extends AbstractQueue<DingdingQywxSyncRecor
             this.userIdList = userIdList;
         }
 
-        public Integer getOffset() {
+        public Long getOffset() {
             return offset;
         }
 
-        public void setOffset(Integer offset) {
+        public void setOffset(Long offset) {
             this.offset = offset;
         }
 
-        public Integer getLimit() {
+        public Long getLimit() {
             return limit;
         }
 
-        public void setLimit(Integer limit) {
+        public void setLimit(Long limit) {
             this.limit = limit;
         }
     }
+
+    public static class DingdingAttendanceResult extends GsonPropertyObject {
+        private Integer errcode;
+        private String errmsg;
+        private Boolean hasMore;
+        private List<DingdingAttendanceResultItem> recordresult;
+
+        public Integer getErrcode() {
+            return errcode;
+        }
+
+        public void setErrcode(Integer errcode) {
+            this.errcode = errcode;
+        }
+
+        public String getErrmsg() {
+            return errmsg;
+        }
+
+        public void setErrmsg(String errmsg) {
+            this.errmsg = errmsg;
+        }
+
+        public Boolean getHasMore() {
+            return hasMore;
+        }
+
+        public void setHasMore(Boolean hasMore) {
+            this.hasMore = hasMore;
+        }
+
+        public List<DingdingAttendanceResultItem> getRecordresult() {
+            return recordresult;
+        }
+
+        public void setRecordresult(List<DingdingAttendanceResultItem> recordresult) {
+            this.recordresult = recordresult;
+        }
+    }
+
+    public static class DingdingAttendanceResultItem extends GsonPropertyObject {
+
+        private static final long serialVersionUID = 1618421987561110713L;
+
+        static WrapCopier<DingdingAttendanceResultItem, AttendanceDingtalkDetail> copier = WrapCopierFactory.wi(DingdingAttendanceResultItem.class,
+                AttendanceDingtalkDetail.class, null, JpaObject.FieldsUnmodify);
+
+        private long id;
+        private long ddId;
+        private String userId;
+        private long baseCheckTime;
+        private long userCheckTime;
+        private long workDate;
+        private String timeResult;
+        private String checkType;
+        private String locationResult;
+        private String sourceType;
+        private long groupId;
+        private long planId;
+        private long recordId;
+
+        public long getId() {
+            return id;
+        }
+
+        public void setId(long id) {
+            this.id = id;
+        }
+
+        public long getDdId() {
+            return ddId;
+        }
+
+        public void setDdId(long ddId) {
+            this.ddId = ddId;
+        }
+
+        public String getUserId() {
+            return userId;
+        }
+
+        public void setUserId(String userId) {
+            this.userId = userId;
+        }
+
+        public long getBaseCheckTime() {
+            return baseCheckTime;
+        }
+
+        public void setBaseCheckTime(long baseCheckTime) {
+            this.baseCheckTime = baseCheckTime;
+        }
+
+        public long getUserCheckTime() {
+            return userCheckTime;
+        }
+
+        public void setUserCheckTime(long userCheckTime) {
+            this.userCheckTime = userCheckTime;
+        }
+
+        public long getWorkDate() {
+            return workDate;
+        }
+
+        public void setWorkDate(long workDate) {
+            this.workDate = workDate;
+        }
+
+        public String getTimeResult() {
+            return timeResult;
+        }
+
+        public void setTimeResult(String timeResult) {
+            this.timeResult = timeResult;
+        }
+
+        public String getCheckType() {
+            return checkType;
+        }
+
+        public void setCheckType(String checkType) {
+            this.checkType = checkType;
+        }
+
+        public String getLocationResult() {
+            return locationResult;
+        }
+
+        public void setLocationResult(String locationResult) {
+            this.locationResult = locationResult;
+        }
+
+        public String getSourceType() {
+            return sourceType;
+        }
+
+        public void setSourceType(String sourceType) {
+            this.sourceType = sourceType;
+        }
+
+        public long getGroupId() {
+            return groupId;
+        }
+
+        public void setGroupId(long groupId) {
+            this.groupId = groupId;
+        }
+
+        public long getPlanId() {
+            return planId;
+        }
+
+        public void setPlanId(long planId) {
+            this.planId = planId;
+        }
+
+        public long getRecordId() {
+            return recordId;
+        }
+
+        public void setRecordId(long recordId) {
+            this.recordId = recordId;
+        }
+    }
 }

+ 130 - 0
o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/DingdingPersonStatisticQueue.java

@@ -0,0 +1,130 @@
+package com.x.attendance.assemble.control;
+
+import com.x.attendance.entity.AttendanceDingtalkDetail;
+import com.x.attendance.entity.StatisticDingdingPersonForMonth;
+import com.x.attendance.entity.StatisticDingdingUnitForDay;
+import com.x.attendance.entity.StatisticDingdingUnitForMonth;
+import com.x.base.core.container.EntityManagerContainer;
+import com.x.base.core.container.factory.EntityManagerContainerFactory;
+import com.x.base.core.project.Application;
+import com.x.base.core.project.logger.Logger;
+import com.x.base.core.project.logger.LoggerFactory;
+import com.x.base.core.project.organization.Person;
+import com.x.base.core.project.organization.Unit;
+import com.x.base.core.project.queue.AbstractQueue;
+import com.x.base.core.project.tools.DateTools;
+import com.x.base.core.project.tools.ListTools;
+import com.x.base.core.project.x_organization_assemble_control;
+import org.apache.commons.lang3.StringUtils;
+
+import java.util.Date;
+import java.util.List;
+
+/**
+ * Created by fancyLou on 2020-04-05.
+ * Copyright © 2020 O2. All rights reserved.
+ */
+public class DingdingPersonStatisticQueue extends AbstractQueue<Date> {
+    private static final Logger logger = LoggerFactory.getLogger(DingdingPersonStatisticQueue.class);
+
+    @Override
+    protected void execute(Date date) throws Exception {
+        logger.info("开始执行人员钉钉考勤统计。。。time:"+DateTools.format(date));
+        try ( EntityManagerContainer emc = EntityManagerContainerFactory.instance().create()) {
+            Business business = new Business(emc);
+            saveStatisticPersonForMonth(business, emc, date);
+        }
+    }
+
+
+    private void saveStatisticPersonForMonth(Business business, EntityManagerContainer emc, Date date) throws Exception {
+        String dateString = DateTools.format(date, DateTools.format_yyyyMMdd);
+        String year = dateString.substring(0, 4);
+        String month = dateString.substring(5, 7);
+
+        Application app = ThisApplication.context().applications().randomWithWeight(x_organization_assemble_control.class.getName());
+        //开始分页查询人员
+        boolean hasNextPerson = true;
+        int personPageSize = 50;
+        //人员查询地址
+        String uri = "person/list/(0)/next/50";
+        while (hasNextPerson) {
+            List<Person> list = ThisApplication.context().applications()
+                    .getQuery(false, app, uri).getDataAsList(Person.class);
+            if (list != null && list.size() > 0) {
+                for (Person person : list) {
+                    List<String> ids = business.dingdingAttendanceFactory()
+                            .getStatPersonForMonthIds(year, month, person.getDistinguishedName());
+                    emc.beginTransaction(StatisticDingdingPersonForMonth.class);
+                    if (ids != null && ids.size() > 0) {
+                        for (String item : ids) {
+                            StatisticDingdingPersonForMonth personForMonth_temp = emc.find(item, StatisticDingdingPersonForMonth.class);
+                            emc.remove(personForMonth_temp);
+                        }
+                    }
+                    StatisticDingdingPersonForMonth personForMonth = new StatisticDingdingPersonForMonth();
+                    personForMonth.setStatisticYear(year);
+                    personForMonth.setStatisticMonth(month);
+                    personForMonth.setO2Unit(getUnitWithPerson(person.getDistinguishedName(), business));
+                    personForMonth.setO2User(person.getDistinguishedName());
+                    Long onduty = business.dingdingAttendanceFactory().dingdingPersonForMonthDutyTimesCount(year, month,
+                            person.getDistinguishedName(), AttendanceDingtalkDetail.OnDuty);
+                    personForMonth.setWorkDayCount(onduty);
+                    personForMonth.setOnDutyTimes(onduty);
+                    personForMonth.setOffDutyTimes(business.dingdingAttendanceFactory().dingdingPersonForMonthDutyTimesCount(year, month,
+                            person.getDistinguishedName(), AttendanceDingtalkDetail.OffDuty));
+                    personForMonth.setResultNormal(business.dingdingAttendanceFactory().dingdingPersonForMonthTimeResultCount(year, month,
+                            person.getDistinguishedName(), AttendanceDingtalkDetail.TIMERESULT_NORMAL));
+                    personForMonth.setLateTimes(business.dingdingAttendanceFactory().dingdingPersonForMonthTimeResultCount(year, month,
+                            person.getDistinguishedName(), AttendanceDingtalkDetail.TIMERESULT_Late));
+                    personForMonth.setLeaveEarlyTimes(business.dingdingAttendanceFactory().dingdingPersonForMonthTimeResultCount(year, month,
+                            person.getDistinguishedName(), AttendanceDingtalkDetail.TIMERESULT_Early));
+                    personForMonth.setAbsenteeismTimes(business.dingdingAttendanceFactory().dingdingPersonForMonthTimeResultCount(year, month,
+                            person.getDistinguishedName(), AttendanceDingtalkDetail.TIMERESULT_Absenteeism));
+                    personForMonth.setNotSignedCount(business.dingdingAttendanceFactory().dingdingPersonForMonthTimeResultCount(year, month,
+                            person.getDistinguishedName(), AttendanceDingtalkDetail.TIMERESULT_NotSigned));
+                    personForMonth.setSeriousLateTimes(business.dingdingAttendanceFactory().dingdingPersonForMonthTimeResultCount(year, month,
+                            person.getDistinguishedName(), AttendanceDingtalkDetail.TIMERESULT_SeriousLate));
+                    emc.persist(personForMonth);
+                    emc.commit();
+                }
+
+                //是否还有更多用户
+                if (list.size() < personPageSize) {
+                    logger.info("统计钉钉考勤个人数据 结束。。。。。。。。。。。。。。。");
+                    hasNextPerson = false;
+                } else {
+                    //还有更多用户继续查询
+                    uri = "person/list/" + list.get(list.size() - 1).getDistinguishedName() + "/next/50";
+                }
+            }else {
+                //没有用户查询到结束
+                logger.info("统计钉钉考勤个人数据 结束。。。。。。。。。。。。。。。");
+                hasNextPerson = false;
+            }
+        }
+    }
+
+    private String getUnitWithPerson(String person, Business business) throws Exception {
+        String result = null;
+        Integer level = 0;
+        Unit unit = null;
+        List<String> unitNames = business.organization().unit().listWithPerson( person );
+        if( ListTools.isNotEmpty( unitNames ) ) {
+            for( String unitName : unitNames ) {
+                if( StringUtils.isNotEmpty( unitName ) && !"null".equals( unitName ) ) {
+                    unit = business.organization().unit().getObject( unitName );
+                    if( level < unit.getLevel() ) {
+                        level = unit.getLevel();
+                        result = unitName;
+                    }
+                }
+            }
+        }
+        return result;
+    }
+
+
+
+
+}

+ 145 - 0
o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/DingdingUnitStatisticQueue.java

@@ -0,0 +1,145 @@
+package com.x.attendance.assemble.control;
+
+import com.x.attendance.entity.AttendanceDingtalkDetail;
+import com.x.attendance.entity.StatisticDingdingUnitForDay;
+import com.x.attendance.entity.StatisticDingdingUnitForMonth;
+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.queue.AbstractQueue;
+import com.x.base.core.project.tools.DateTools;
+import org.apache.commons.lang3.StringUtils;
+
+import java.util.Date;
+import java.util.List;
+
+/**
+ * Created by fancyLou on 2020-04-05.
+ * Copyright © 2020 O2. All rights reserved.
+ */
+public class DingdingUnitStatisticQueue extends AbstractQueue<Date> {
+    private static final Logger logger = LoggerFactory.getLogger(DingdingUnitStatisticQueue.class);
+
+    @Override
+    protected void execute(Date date) throws Exception {
+        logger.info("开始执行组织钉钉考勤统计。。。time:"+DateTools.format(date));
+        try ( EntityManagerContainer emc = EntityManagerContainerFactory.instance().create()) {
+            Business business = new Business(emc);
+            saveStatisticUnitForDay(business, emc, date);
+        }
+    }
+
+
+
+    /**
+     * 单位 日统计
+     * @param business
+     * @param emc
+     * @param date
+     * @throws Exception
+     */
+    private void saveStatisticUnitForDay(Business business, EntityManagerContainer emc, Date date) throws Exception{
+        String dateString = DateTools.format(date, DateTools.format_yyyyMMdd);
+        String year = dateString.substring(0, 4);
+        String month = dateString.substring(5, 7);
+        String day = dateString.substring(8, 10);
+        List<String> units = business.dingdingAttendanceFactory().dingdingUnitDistinct(dateString);
+        if (units != null && !units.isEmpty()) {
+            for (String unit : units) {
+                if (StringUtils.isEmpty(unit)){
+                    continue;
+                }
+                List<String> ids = business.dingdingAttendanceFactory().getStatUnitForDayIds(year, month, day, unit);
+                emc.beginTransaction(StatisticDingdingUnitForDay.class);
+                if (ids != null && ids.size() > 0) {
+                    for (String item : ids) {
+                        StatisticDingdingUnitForDay statisticTopUnitForDay_tmp = emc.find(item, StatisticDingdingUnitForDay.class);
+                        emc.remove(statisticTopUnitForDay_tmp);
+                    }
+                }
+                //for day
+                StatisticDingdingUnitForDay unitForDay = new StatisticDingdingUnitForDay();
+                unitForDay.setO2Unit(unit);
+                unitForDay.setStatisticYear(year);
+                unitForDay.setStatisticMonth(month);
+                unitForDay.setStatisticDate(day);
+                Long on = business.dingdingAttendanceFactory().dingdingUnitForDayDutyTimesCount(dateString, unit, AttendanceDingtalkDetail.OnDuty);
+                unitForDay.setWorkDayCount(on);
+                unitForDay.setOnDutyTimes(on);
+                unitForDay.setOffDutyTimes(business.dingdingAttendanceFactory().
+                        dingdingUnitForDayDutyTimesCount(dateString, unit, AttendanceDingtalkDetail.OffDuty));
+                unitForDay.setResultNormal(business.dingdingAttendanceFactory().dingdingUnitForDayTimeResultCount(dateString, unit,
+                        AttendanceDingtalkDetail.TIMERESULT_NORMAL));
+                unitForDay.setLateTimes(business.dingdingAttendanceFactory().dingdingUnitForDayTimeResultCount(dateString, unit,
+                        AttendanceDingtalkDetail.TIMERESULT_Late));
+                unitForDay.setLeaveEarlyTimes(business.dingdingAttendanceFactory().dingdingUnitForDayTimeResultCount(dateString, unit,
+                        AttendanceDingtalkDetail.TIMERESULT_Early));
+                unitForDay.setNotSignedCount(business.dingdingAttendanceFactory().dingdingUnitForDayTimeResultCount(dateString, unit,
+                        AttendanceDingtalkDetail.TIMERESULT_NotSigned));
+                unitForDay.setAbsenteeismTimes(business.dingdingAttendanceFactory().dingdingUnitForDayTimeResultCount(dateString, unit,
+                        AttendanceDingtalkDetail.TIMERESULT_Absenteeism));
+                unitForDay.setSeriousLateTimes(business.dingdingAttendanceFactory().dingdingUnitForDayTimeResultCount(dateString, unit,
+                        AttendanceDingtalkDetail.TIMERESULT_SeriousLate));
+                emc.persist(unitForDay);
+                emc.commit();
+            }
+
+            saveStatisticUnitForMonth(business, emc, units, year, month);
+        }
+
+
+    }
+
+    /**
+     * 单位月统计
+     * @param business
+     * @param emc
+     * @param units
+     * @param year
+     * @param month
+     * @throws Exception
+     */
+    private void saveStatisticUnitForMonth(Business business, EntityManagerContainer emc, List<String> units,
+                                           String year, String month) throws Exception {
+
+        for (String unit : units) {
+            if (StringUtils.isEmpty(unit)){
+                continue;
+            }
+            Long workDay = business.dingdingAttendanceFactory().sumWorkDayUnitForDayWithMonth(year, month, unit);
+            Long onduty = business.dingdingAttendanceFactory().sumOnDutyUnitForDayWithMonth(year, month, unit);
+            Long offDuty = business.dingdingAttendanceFactory().sumOffDutyUnitForDayWithMonth(year, month, unit);
+            Long normal = business.dingdingAttendanceFactory().sumNormalUnitForDayWithMonth(year, month, unit);
+            Long late = business.dingdingAttendanceFactory().sumLateTimesUnitForDayWithMonth(year, month, unit);
+            Long leaveearly = business.dingdingAttendanceFactory().sumLeaveEarlyUnitForDayWithMonth(year, month, unit);
+            Long notSign = business.dingdingAttendanceFactory().sumNotSignedUnitForDayWithMonth(year, month, unit);
+            Long absenteeism = business.dingdingAttendanceFactory().sumAbsenteeismUnitForDayWithMonth(year, month, unit);
+            Long serious = business.dingdingAttendanceFactory().sumSeriousLateUnitForDayWithMonth(year, month, unit);
+            List<String> list = business.dingdingAttendanceFactory().getStatUnitForMonthIds(year, month, unit);
+            emc.beginTransaction(StatisticDingdingUnitForMonth.class);
+            if (list != null && list.size() > 0) {
+                for (String item : list) {
+                    StatisticDingdingUnitForMonth statisticTopUnitForMonth_tmp = emc.find(item, StatisticDingdingUnitForMonth.class);
+                    emc.remove(statisticTopUnitForMonth_tmp);
+                }
+            }
+            StatisticDingdingUnitForMonth unitForMonth = new StatisticDingdingUnitForMonth();
+            unitForMonth.setO2Unit(unit);
+            unitForMonth.setStatisticYear(year);
+            unitForMonth.setStatisticMonth(month);
+            unitForMonth.setWorkDayCount(workDay);
+            unitForMonth.setOnDutyTimes(onduty);
+            unitForMonth.setOffDutyTimes(offDuty);
+            unitForMonth.setResultNormal(normal);
+            unitForMonth.setLateTimes(late);
+            unitForMonth.setLeaveEarlyTimes(leaveearly);
+            unitForMonth.setNotSignedCount(notSign);
+            unitForMonth.setAbsenteeismTimes(absenteeism);
+            unitForMonth.setSeriousLateTimes(serious);
+            emc.persist(unitForMonth);
+            emc.commit();
+        }
+
+    }
+}

+ 339 - 0
o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/QywxAttendanceSyncQueue.java

@@ -0,0 +1,339 @@
+package com.x.attendance.assemble.control;
+
+import com.x.attendance.assemble.control.exception.DingDingRequestException;
+import com.x.attendance.entity.*;
+import com.x.base.core.container.EntityManagerContainer;
+import com.x.base.core.container.factory.EntityManagerContainerFactory;
+import com.x.base.core.entity.JpaObject;
+import com.x.base.core.project.Application;
+import com.x.base.core.project.bean.WrapCopier;
+import com.x.base.core.project.bean.WrapCopierFactory;
+import com.x.base.core.project.config.Config;
+import com.x.base.core.project.connection.HttpConnection;
+import com.x.base.core.project.gson.GsonPropertyObject;
+import com.x.base.core.project.logger.Logger;
+import com.x.base.core.project.logger.LoggerFactory;
+import com.x.base.core.project.organization.Person;
+import com.x.base.core.project.queue.AbstractQueue;
+import com.x.base.core.project.tools.ListTools;
+import com.x.base.core.project.tools.StringTools;
+import com.x.base.core.project.x_organization_assemble_control;
+import org.apache.commons.lang3.BooleanUtils;
+import org.apache.commons.lang3.StringUtils;
+
+import javax.persistence.EntityManager;
+import javax.persistence.criteria.CriteriaBuilder;
+import javax.persistence.criteria.CriteriaQuery;
+import javax.persistence.criteria.Predicate;
+import javax.persistence.criteria.Root;
+import java.util.Date;
+import java.util.List;
+import java.util.stream.Collectors;
+
+public class QywxAttendanceSyncQueue  extends AbstractQueue<DingdingQywxSyncRecord> {
+
+    private static final Logger logger = LoggerFactory.getLogger(QywxAttendanceSyncQueue.class);
+
+
+    @Override
+    protected void execute(DingdingQywxSyncRecord record) throws Exception {
+        logger.info("开始执行企业微信打卡数据同步,from:" + record.getDateFrom() + ", to:"+record.getDateTo());
+        if (DingdingQywxSyncRecord.syncType_qywx.equals(record.getType())) {
+            try {
+                qywxSync(record);
+            }catch (Exception e) {
+                logger.error(e);
+                updateSyncRecord(record, e.getMessage());
+            }
+        } else {
+            logger.info("不是企业微信同步任务。。。。。。。。。。。。。");
+        }
+    }
+
+
+    private void qywxSync(DingdingQywxSyncRecord record) throws Exception{
+        Application app = ThisApplication.context().applications().randomWithWeight(x_organization_assemble_control.class.getName());
+        //开始分页查询人员
+        boolean hasNextPerson = true;
+        int personPageSize = 50;
+        //开始时间和结束时间
+        Date fromDate = new Date();
+        fromDate.setTime(record.getDateFrom());
+        Date toDate =new Date();
+        toDate.setTime(record.getDateTo());
+        //先删除
+        deleteQywxAttendance(fromDate, toDate);
+        //人员查询地址
+        String uri = "person/list/(0)/next/50";
+        String qywxuri = "https://qyapi.weixin.qq.com/cgi-bin/checkin/getcheckindata?access_token="+ Config.qiyeweixin().attendanceAccessToken();
+        int saveNumber = 0;
+        while (hasNextPerson) {
+            List<Person> list = ThisApplication.context().applications().getQuery(false, app, uri).getDataAsList(Person.class);
+            if (list != null && list.size() > 0) {
+                //钉钉用户id
+                List<String> qywxUsers = list.stream().filter(person -> StringUtils.isNotEmpty(person.getQiyeweixinId()))
+                        .map(Person::getQiyeweixinId).collect(Collectors.toList());
+
+                if(ListTools.isNotEmpty(qywxUsers)){
+                    QywxPost post = new QywxPost();
+                    post.setStarttime(fromDate.getTime());
+                    post.setEndtime(toDate.getTime());
+                    post.setUseridlist(qywxUsers);
+                    post.setOpencheckindatatype(3);//全部打卡信息
+                    logger.info("企业微信 post :" + post.toString());
+                    QywxResult result = HttpConnection.postAsObject(qywxuri, null, post.toString(), QywxResult.class);
+                    logger.info("返回结果:"+result.toString());
+                    if (result.errcode == 0) {
+                        List<QywxResultItem> resultList = result.getCheckindata();
+                        saveQywxAttendance(resultList);
+                        saveNumber += resultList.size();
+                    } else {
+                        //请求结果异常 结束
+                        throw new DingDingRequestException(result.errmsg);
+                    }
+
+                }
+
+                //是否还有更多用户
+                if (list.size() < personPageSize) {
+                    logger.info("同步钉钉考勤 没有更多用户了,结束。。。。。。。。。。。。。。。");
+                    hasNextPerson = false;
+                    updateSyncRecord(record, null);
+                } else {
+                    //还有更多用户继续查询
+                    uri = "person/list/" + list.get(list.size() - 1).getDistinguishedName() + "/next/50";
+                }
+            } else {
+                //没有用户查询到结束
+                logger.info("同步钉钉考勤 查询不到用户了,结束。。。。。。。。。。。。。。。");
+                hasNextPerson = false;
+                updateSyncRecord(record, null);
+            }
+        }
+        logger.info("结束 插入:"+saveNumber+" 条");
+
+
+    }
+
+    private void saveQywxAttendance(List<QywxResultItem> resultList) throws Exception {
+        if (resultList != null && !resultList.isEmpty()) {
+            try (EntityManagerContainer emc = EntityManagerContainerFactory.instance().create()) {
+                emc.beginTransaction(AttendanceQywxDetail.class);
+                for (int i = 0; i < resultList.size(); i++) {
+                    QywxResultItem item = resultList.get(i);
+                    AttendanceQywxDetail detail = QywxResultItem.copier.copy(item);
+                    emc.persist(detail);
+                }
+                emc.commit();
+            }
+        }
+    }
+
+    private void deleteQywxAttendance(Date fromDate, Date toDate) throws Exception {
+        try (EntityManagerContainer emc = EntityManagerContainerFactory.instance().create()) {
+            //先删除 再同步
+            EntityManager em = emc.get(AttendanceQywxDetail.class);
+            CriteriaBuilder cb = em.getCriteriaBuilder();
+            CriteriaQuery<AttendanceQywxDetail> query = cb.createQuery(AttendanceQywxDetail.class);
+            Root<AttendanceQywxDetail> root = query.from(AttendanceQywxDetail.class);
+            long start = fromDate.getTime();
+            long end = toDate.getTime();
+            Predicate p = cb.between(root.get(AttendanceQywxDetail_.checkin_time), start, end);
+            query.select(root).where(p);
+            List<AttendanceQywxDetail> detailList = em.createQuery(query).getResultList();
+            //先删除
+            logger.info("删除");
+            if (detailList != null) {
+                logger.info("删除 list:"+detailList.size());
+                emc.beginTransaction(AttendanceQywxDetail.class);
+                for (int i = 0; i < detailList.size(); i++) {
+                    emc.remove(detailList.get(i));
+                }
+                emc.commit();
+            }
+            logger.info("删除结束");
+        }
+    }
+
+
+    private void updateSyncRecord(DingdingQywxSyncRecord record, String message) throws Exception {
+        try (EntityManagerContainer emc = EntityManagerContainerFactory.instance().create()) {
+            emc.beginTransaction(DingdingQywxSyncRecord.class);
+            DingdingQywxSyncRecord entity = emc.find(record.getId(), DingdingQywxSyncRecord.class);
+            entity.setEndTime(new Date());
+            if (message == null || message.isEmpty()) {
+                entity.setStatus(DingdingQywxSyncRecord.status_end);
+            } else {
+                entity.setStatus(DingdingQywxSyncRecord.status_error);
+                entity.setExceptionMessage(message);
+            }
+            emc.commit();
+        }
+    }
+
+
+    public static class QywxPost extends GsonPropertyObject {
+        //{
+        //   "opencheckindatatype": 3,
+        //   "starttime": 1492617600,
+        //   "endtime": 1492790400,
+        //   "useridlist": ["james","paul"]
+        //}
+
+        //打卡类型。1:上下班打卡;2:外出打卡;3:全部打卡
+        private int opencheckindatatype;
+        private long starttime;
+        private long endtime;
+        private List<String> useridlist;
+
+        public int getOpencheckindatatype() {
+            return opencheckindatatype;
+        }
+
+        public void setOpencheckindatatype(int opencheckindatatype) {
+            this.opencheckindatatype = opencheckindatatype;
+        }
+
+        public long getStarttime() {
+            return starttime;
+        }
+
+        public void setStarttime(long starttime) {
+            this.starttime = starttime;
+        }
+
+        public long getEndtime() {
+            return endtime;
+        }
+
+        public void setEndtime(long endtime) {
+            this.endtime = endtime;
+        }
+
+        public List<String> getUseridlist() {
+            return useridlist;
+        }
+
+        public void setUseridlist(List<String> useridlist) {
+            this.useridlist = useridlist;
+        }
+    }
+
+    public static class QywxResult extends GsonPropertyObject {
+        private int errcode;
+        private String errmsg;
+        private List<QywxResultItem> checkindata;
+
+        public int getErrcode() {
+            return errcode;
+        }
+
+        public void setErrcode(int errcode) {
+            this.errcode = errcode;
+        }
+
+        public String getErrmsg() {
+            return errmsg;
+        }
+
+        public void setErrmsg(String errmsg) {
+            this.errmsg = errmsg;
+        }
+
+        public List<QywxResultItem> getCheckindata() {
+            return checkindata;
+        }
+
+        public void setCheckindata(List<QywxResultItem> checkindata) {
+            this.checkindata = checkindata;
+        }
+    }
+    public static class QywxResultItem extends GsonPropertyObject {
+
+
+        static WrapCopier<QywxResultItem, AttendanceQywxDetail> copier = WrapCopierFactory.wi(QywxResultItem.class,
+                AttendanceQywxDetail.class, null, JpaObject.FieldsUnmodify);
+
+        private String userid;
+        private String groupname;
+        private String checkin_type;
+        private long checkin_time;
+        private String exception_type;
+        private String location_title;
+        private String location_detail;
+        private String wifiname;
+        private String notes;
+
+        public String getUserid() {
+            return userid;
+        }
+
+        public void setUserid(String userid) {
+            this.userid = userid;
+        }
+
+        public String getGroupname() {
+            return groupname;
+        }
+
+        public void setGroupname(String groupname) {
+            this.groupname = groupname;
+        }
+
+        public String getCheckin_type() {
+            return checkin_type;
+        }
+
+        public void setCheckin_type(String checkin_type) {
+            this.checkin_type = checkin_type;
+        }
+
+        public long getCheckin_time() {
+            return checkin_time;
+        }
+
+        public void setCheckin_time(long checkin_time) {
+            this.checkin_time = checkin_time;
+        }
+
+        public String getException_type() {
+            return exception_type;
+        }
+
+        public void setException_type(String exception_type) {
+            this.exception_type = exception_type;
+        }
+
+        public String getLocation_title() {
+            return location_title;
+        }
+
+        public void setLocation_title(String location_title) {
+            this.location_title = location_title;
+        }
+
+        public String getLocation_detail() {
+            return location_detail;
+        }
+
+        public void setLocation_detail(String location_detail) {
+            this.location_detail = location_detail;
+        }
+
+        public String getWifiname() {
+            return wifiname;
+        }
+
+        public void setWifiname(String wifiname) {
+            this.wifiname = wifiname;
+        }
+
+        public String getNotes() {
+            return notes;
+        }
+
+        public void setNotes(String notes) {
+            this.notes = notes;
+        }
+    }
+}

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

@@ -2,8 +2,7 @@ package com.x.attendance.assemble.control;
 
 import com.x.attendance.assemble.control.processor.monitor.MonitorFileDataOpt;
 import com.x.attendance.assemble.control.processor.thread.DataProcessThreadFactory;
-import com.x.attendance.assemble.control.schedule.AttendanceStatisticTask;
-import com.x.attendance.assemble.control.schedule.MobileRecordAnalyseTask;
+import com.x.attendance.assemble.control.schedule.*;
 import com.x.attendance.assemble.control.service.AttendanceSettingService;
 import com.x.base.core.project.Context;
 import com.x.base.core.project.config.Config;
@@ -18,6 +17,9 @@ public class ThisApplication {
 	}
 
 	public static DingdingAttendanceQueue dingdingQueue = new DingdingAttendanceQueue();
+	public static QywxAttendanceSyncQueue qywxQueue = new QywxAttendanceSyncQueue();
+	public static DingdingPersonStatisticQueue personStatisticQueue = new DingdingPersonStatisticQueue();
+	public static DingdingUnitStatisticQueue unitStatisticQueue = new DingdingUnitStatisticQueue();
 
 	public static void init() throws Exception {
 		try {
@@ -26,6 +28,16 @@ public class ThisApplication {
 			context.schedule(MobileRecordAnalyseTask.class, "0 0/10 * * * ?");
 			if (BooleanUtils.isTrue(Config.dingding().getAttendanceSyncEnable())) {
 				dingdingQueue.start();
+				personStatisticQueue.start();
+				unitStatisticQueue.start();
+				context.schedule(DingdingAttendanceSyncScheduleTask.class, "0 0 1 * * ?");
+				//已经将任务 放到了同步结束后执行 暂时不需要开定时任务了
+//				context.schedule(DingdingAttendanceStatisticScheduleTask.class, "0 0 3 * * ?");
+//				context.schedule(DingdingAttendanceStatisticPersonScheduleTask.class, "0 0 3 * * ?");
+			}
+			if (BooleanUtils.isTrue(Config.qiyeweixin().getAttendanceSyncEnable())) {
+				qywxQueue.start();
+				context.schedule(QywxAttendanceSyncScheduleTask.class, "0 0 1 * * ?");
 			}
 		} catch (Exception e) {
 			e.printStackTrace();
@@ -48,5 +60,20 @@ public class ThisApplication {
 		} catch (Exception e) {
 			e.printStackTrace();
 		}
+		try {
+			personStatisticQueue.stop();
+		} catch (Exception e) {
+			e.printStackTrace();
+		}
+		try {
+			unitStatisticQueue.stop();
+		} catch (Exception e) {
+			e.printStackTrace();
+		}
+		try {
+			qywxQueue.stop();
+		} catch (Exception e) {
+			e.printStackTrace();
+		}
 	}
 }

+ 12 - 0
o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/exception/DingDingRequestException.java

@@ -0,0 +1,12 @@
+package com.x.attendance.assemble.control.exception;
+
+import com.x.base.core.project.exception.PromptException;
+
+public class DingDingRequestException extends PromptException {
+
+    private static final long serialVersionUID = -2160589718239895222L;
+
+    public DingDingRequestException(String errorMsg) {
+        super(errorMsg);
+    }
+}

+ 9 - 0
o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/exception/DingdingFindNoArgumentError.java

@@ -0,0 +1,9 @@
+package com.x.attendance.assemble.control.exception;
+
+import com.x.base.core.project.exception.PromptException;
+
+public class DingdingFindNoArgumentError extends PromptException {
+    public DingdingFindNoArgumentError() {
+        super("没有传入正确的参数");
+    }
+}

+ 9 - 0
o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/exception/QywxFindNoArgumentError.java

@@ -0,0 +1,9 @@
+package com.x.attendance.assemble.control.exception;
+
+import com.x.base.core.project.exception.PromptException;
+
+public class QywxFindNoArgumentError extends PromptException {
+    public QywxFindNoArgumentError() {
+        super("没有传入正确的参数");
+    }
+}

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

@@ -0,0 +1,509 @@
+package com.x.attendance.assemble.control.factory;
+
+import com.x.attendance.assemble.control.AbstractFactory;
+import com.x.attendance.assemble.control.Business;
+import com.x.attendance.assemble.control.exception.DingdingFindNoArgumentError;
+import com.x.attendance.assemble.control.exception.QywxFindNoArgumentError;
+import com.x.attendance.entity.*;
+import com.x.base.core.project.tools.DateTools;
+import org.apache.commons.lang3.time.DateUtils;
+
+import javax.persistence.EntityManager;
+import javax.persistence.criteria.CriteriaBuilder;
+import javax.persistence.criteria.CriteriaQuery;
+import javax.persistence.criteria.Predicate;
+import javax.persistence.criteria.Root;
+import javax.persistence.metamodel.SingularAttribute;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.List;
+
+public class DingdingAttendanceFactory extends AbstractFactory {
+
+    public DingdingAttendanceFactory(Business business) throws Exception {
+        super(business);
+    }
+
+
+    /**
+     * 查询所有同步记录
+     *
+     * @param type DingdingQywxSyncRecord.syncType_dingding DingdingQywxSyncRecord.syncType_qywx
+     * @return
+     * @throws Exception
+     */
+    public List<DingdingQywxSyncRecord> findAllSyncRecordWithType(String type) throws Exception {
+        EntityManager em = this.entityManagerContainer().get(DingdingQywxSyncRecord.class);
+        CriteriaBuilder cb = em.getCriteriaBuilder();
+        CriteriaQuery<DingdingQywxSyncRecord> query = cb.createQuery(DingdingQywxSyncRecord.class);
+        Root<DingdingQywxSyncRecord> root = query.from(DingdingQywxSyncRecord.class);
+        Predicate p = cb.equal(root.get(DingdingQywxSyncRecord_.type), type);
+        query.select(root).where(p).orderBy(cb.desc(root.get(DingdingQywxSyncRecord_.startTime)));
+        return em.createQuery(query).getResultList();
+    }
+
+    /**
+     * 查询冲突的钉钉同步记录
+     *
+     * @param fromTime
+     * @param toTime
+     * @return
+     * @throws Exception
+     */
+    public List<DingdingQywxSyncRecord> findConflictSyncRecord(long fromTime, long toTime) throws Exception {
+        EntityManager em = this.entityManagerContainer().get(DingdingQywxSyncRecord.class);
+        CriteriaBuilder cb = em.getCriteriaBuilder();
+        CriteriaQuery<DingdingQywxSyncRecord> query = cb.createQuery(DingdingQywxSyncRecord.class);
+        Root<DingdingQywxSyncRecord> root = query.from(DingdingQywxSyncRecord.class);
+        Predicate p = cb.equal(root.get(DingdingQywxSyncRecord_.type), DingdingQywxSyncRecord.syncType_dingding);
+        Predicate p1 = cb.or(cb.between(root.get(DingdingQywxSyncRecord_.dateFrom), fromTime, toTime), cb.between(root.get(DingdingQywxSyncRecord_.dateTo), fromTime, toTime));
+        p = cb.and(p, p1);
+        query.select(root).where(p);
+        return em.createQuery(query).getResultList();
+    }
+
+    /**
+     * 根据用户查询一段时间内的打开数据
+     *
+     * @param startTime
+     * @param endTime
+     * @param userId
+     * @return
+     * @throws Exception
+     */
+    public List<AttendanceDingtalkDetail> findAllDingdingAttendanceDetail(Date startTime, Date endTime, String userId) throws Exception {
+        if (startTime == null && endTime == null && userId == null) {
+            throw new DingdingFindNoArgumentError();
+        }
+        EntityManager em = this.entityManagerContainer().get(AttendanceDingtalkDetail.class);
+        CriteriaBuilder cb = em.getCriteriaBuilder();
+        CriteriaQuery<AttendanceDingtalkDetail> query = cb.createQuery(AttendanceDingtalkDetail.class);
+        Root<AttendanceDingtalkDetail> root = query.from(AttendanceDingtalkDetail.class);
+        Predicate p = null;
+        if (startTime != null && endTime != null) {
+            long start = startTime.getTime();
+            long end = endTime.getTime();
+            p = cb.between(root.get(AttendanceDingtalkDetail_.userCheckTime), start, end);
+        }
+        if (userId != null && !userId.isEmpty()) {
+            if (p != null) {
+                p = cb.and(p, cb.equal(root.get(AttendanceDingtalkDetail_.userId), userId));
+            } else {
+                p = cb.equal(root.get(AttendanceDingtalkDetail_.userId), userId);
+            }
+        }
+        query.select(root).where(p).orderBy(cb.desc(root.get(AttendanceDingtalkDetail_.userCheckTime)));
+        return em.createQuery(query).getResultList();
+    }
+
+    /**
+     * 企业微信 打卡数据查询
+     *
+     * @param startTime
+     * @param endTime
+     * @param userId
+     * @return
+     * @throws Exception
+     */
+    public List<AttendanceQywxDetail> findQywxAttendanceDetail(Date startTime, Date endTime, String userId) throws Exception {
+        if (startTime == null && endTime == null && userId == null) {
+            throw new QywxFindNoArgumentError();
+        }
+        EntityManager em = this.entityManagerContainer().get(AttendanceQywxDetail.class);
+        CriteriaBuilder cb = em.getCriteriaBuilder();
+        CriteriaQuery<AttendanceQywxDetail> query = cb.createQuery(AttendanceQywxDetail.class);
+        Root<AttendanceQywxDetail> root = query.from(AttendanceQywxDetail.class);
+
+        Predicate p = null;
+        if (startTime != null && endTime != null) {
+            long start = startTime.getTime();
+            long end = endTime.getTime();
+            p = cb.between(root.get(AttendanceQywxDetail_.checkin_time), start, end);
+        }
+        if (userId != null && !userId.isEmpty()) {
+            if (p != null) {
+                p = cb.and(p, cb.equal(root.get(AttendanceQywxDetail_.userid), userId));
+            } else {
+                p = cb.equal(root.get(AttendanceQywxDetail_.userid), userId);
+            }
+        }
+        query.select(root).where(p).orderBy(cb.desc(root.get(AttendanceQywxDetail_.checkin_time)));
+        return em.createQuery(query).getResultList();
+
+    }
+
+
+    /**
+     * 人员统计数据
+     * @param person
+     * @param year
+     * @param month
+     * @return
+     * @throws Exception
+     */
+    public List<StatisticDingdingPersonForMonth> findPersonStatistic(String person, String year, String month) throws Exception {
+        EntityManager em = this.entityManagerContainer().get(StatisticDingdingPersonForMonth.class);
+        CriteriaBuilder cb = em.getCriteriaBuilder();
+        CriteriaQuery<StatisticDingdingPersonForMonth> query = cb.createQuery(StatisticDingdingPersonForMonth.class);
+        Root<StatisticDingdingPersonForMonth> root = query.from(StatisticDingdingPersonForMonth.class);
+        Predicate p = cb.equal(root.get(StatisticDingdingPersonForMonth_.o2User), person);
+        p = cb.and(p, cb.equal(root.get(StatisticDingdingPersonForMonth_.statisticYear), year));
+        p = cb.and(p, cb.equal(root.get(StatisticDingdingPersonForMonth_.statisticMonth), month));
+
+        query.select(root).where(p);
+        return em.createQuery(query).getResultList();
+    }
+
+    /**
+     * 人员统计数据
+     * @param unit
+     * @param year
+     * @param month
+     * @return
+     * @throws Exception
+     */
+    public List<StatisticDingdingPersonForMonth> findPersonStatisticWithUnit(String unit, String year, String month) throws Exception {
+        EntityManager em = this.entityManagerContainer().get(StatisticDingdingPersonForMonth.class);
+        CriteriaBuilder cb = em.getCriteriaBuilder();
+        CriteriaQuery<StatisticDingdingPersonForMonth> query = cb.createQuery(StatisticDingdingPersonForMonth.class);
+        Root<StatisticDingdingPersonForMonth> root = query.from(StatisticDingdingPersonForMonth.class);
+        Predicate p = cb.equal(root.get(StatisticDingdingPersonForMonth_.o2Unit), unit);
+        p = cb.and(p, cb.equal(root.get(StatisticDingdingPersonForMonth_.statisticYear), year));
+        p = cb.and(p, cb.equal(root.get(StatisticDingdingPersonForMonth_.statisticMonth), month));
+
+        query.select(root).where(p);
+        return em.createQuery(query).getResultList();
+    }
+
+
+    /**
+     * 部门统计数据
+     * @param unit
+     * @param year
+     * @param month
+     * @return
+     * @throws Exception
+     */
+    public List<StatisticDingdingUnitForMonth> findUnitStatistic(String unit, String year, String month) throws Exception {
+        EntityManager em = this.entityManagerContainer().get(StatisticDingdingUnitForMonth.class);
+        CriteriaBuilder cb = em.getCriteriaBuilder();
+        CriteriaQuery<StatisticDingdingUnitForMonth> query = cb.createQuery(StatisticDingdingUnitForMonth.class);
+        Root<StatisticDingdingUnitForMonth> root = query.from(StatisticDingdingUnitForMonth.class);
+        Predicate p = cb.equal(root.get(StatisticDingdingUnitForMonth_.o2Unit), unit);
+        p = cb.and(p, cb.equal(root.get(StatisticDingdingUnitForMonth_.statisticYear), year));
+        p = cb.and(p, cb.equal(root.get(StatisticDingdingUnitForMonth_.statisticMonth), month));
+
+        query.select(root).where(p);
+        return em.createQuery(query).getResultList();
+    }
+
+    ////////////////////////////////统计/////////////////////////////
+
+    /**
+     * 钉钉考勤 个人统计
+     * @param year
+     * @param month
+     * @param person
+     * @param duty
+     * @return
+     * @throws Exception
+     */
+    public Long dingdingPersonForMonthDutyTimesCount(String year, String month, String person, String duty) throws Exception {
+        Date start = monthFirstDay(year, month);
+        Date end = monthLastDay(year, month);
+        EntityManager em = this.entityManagerContainer().get(AttendanceDingtalkDetail.class);
+        CriteriaBuilder cb = em.getCriteriaBuilder();
+        CriteriaQuery<Long> query = cb.createQuery(Long.class);
+        Root<AttendanceDingtalkDetail> root = query.from(AttendanceDingtalkDetail.class);
+        Predicate p = cb.between(root.get(AttendanceDingtalkDetail_.userCheckTime), start.getTime(), end.getTime());
+        p = cb.and(p, cb.equal(root.get(AttendanceDingtalkDetail_.o2User), person));
+        p = cb.and(p, cb.equal(root.get(AttendanceDingtalkDetail_.checkType), duty));
+        query.select(cb.count(root)).where(p);
+        return em.createQuery(query).getSingleResult();
+    }
+
+    public Long dingdingPersonForMonthTimeResultCount(String year, String month, String person, String timeresult) throws Exception {
+        Date start = monthFirstDay(year, month);
+        Date end = monthLastDay(year, month);
+        EntityManager em = this.entityManagerContainer().get(AttendanceDingtalkDetail.class);
+        CriteriaBuilder cb = em.getCriteriaBuilder();
+        CriteriaQuery<Long> query = cb.createQuery(Long.class);
+        Root<AttendanceDingtalkDetail> root = query.from(AttendanceDingtalkDetail.class);
+        Predicate p = cb.between(root.get(AttendanceDingtalkDetail_.userCheckTime), start.getTime(), end.getTime());
+        p = cb.and(p, cb.equal(root.get(AttendanceDingtalkDetail_.o2User), person));
+        p = cb.and(p, cb.equal(root.get(AttendanceDingtalkDetail_.timeResult), timeresult));
+        query.select(cb.count(root)).where(p);
+        return em.createQuery(query).getSingleResult();
+    }
+
+
+
+    /**
+     * StatisticDingdingPersonForMonth ids
+     * @param year
+     * @param month
+     * @param person
+     * @return
+     * @throws Exception
+     */
+    public List<String> getStatPersonForMonthIds(String year, String month, String person) throws Exception {
+        EntityManager em = this.entityManagerContainer().get(StatisticDingdingPersonForMonth.class);
+        CriteriaBuilder cb = em.getCriteriaBuilder();
+        CriteriaQuery<String> query = cb.createQuery(String.class);
+        Root<StatisticDingdingPersonForMonth> root = query.from(StatisticDingdingPersonForMonth.class);
+        Predicate p = cb.equal(root.get(StatisticDingdingPersonForMonth_.statisticYear), year);
+        p = cb.and(p, cb.equal(root.get(StatisticDingdingPersonForMonth_.statisticMonth), month));
+        p = cb.and(p, cb.equal(root.get(StatisticDingdingPersonForMonth_.o2User), person));
+        query.select(root.get(StatisticDingdingPersonForMonth_.id)).where(p);
+        return em.createQuery(query).getResultList();
+    }
+
+    /**
+     * 钉钉考勤 部门出勤人数 、上班签到人数、下班班签到人数
+     *
+     * @param date yyyy-MM-dd
+     * @param unit 单位
+     * @param duty OnDuty OffDuty
+     * @return
+     * @throws Exception
+     */
+    public Long dingdingUnitForDayDutyTimesCount(String date, String unit, String duty) throws Exception {
+        Date startTime = DateTools.parse(date);
+        startTime = startOneDate(startTime);
+        Date endTime = endOneDate(startTime);
+        EntityManager em = this.entityManagerContainer().get(AttendanceDingtalkDetail.class);
+        CriteriaBuilder cb = em.getCriteriaBuilder();
+        CriteriaQuery<Long> query = cb.createQuery(Long.class);
+        Root<AttendanceDingtalkDetail> root = query.from(AttendanceDingtalkDetail.class);
+        Predicate p = null;
+        long start = startTime.getTime();
+        long end = endTime.getTime();
+        p = cb.between(root.get(AttendanceDingtalkDetail_.userCheckTime), start, end);
+        p = cb.and(p, cb.equal(root.get(AttendanceDingtalkDetail_.o2Unit), unit));
+        p = cb.and(p, cb.equal(root.get(AttendanceDingtalkDetail_.checkType), duty));
+        query.select(cb.count(root)).where(p);
+        return em.createQuery(query).getSingleResult();
+    }
+
+    public Long dingdingUnitForDayTimeResultCount(String date, String unit, String timeresult) throws Exception {
+
+        Date startTime = DateTools.parse(date);
+        startTime = startOneDate(startTime);
+        Date endTime = endOneDate(startTime);
+        EntityManager em = this.entityManagerContainer().get(AttendanceDingtalkDetail.class);
+        CriteriaBuilder cb = em.getCriteriaBuilder();
+        CriteriaQuery<Long> query = cb.createQuery(Long.class);
+        Root<AttendanceDingtalkDetail> root = query.from(AttendanceDingtalkDetail.class);
+        Predicate p = null;
+        long start = startTime.getTime();
+        long end = endTime.getTime();
+        p = cb.between(root.get(AttendanceDingtalkDetail_.userCheckTime), start, end);
+        p = cb.and(p, cb.equal(root.get(AttendanceDingtalkDetail_.o2Unit), unit));
+        p = cb.and(p, cb.equal(root.get(AttendanceDingtalkDetail_.timeResult), timeresult));
+        query.select(cb.count(root)).where(p);
+        return em.createQuery(query).getSingleResult();
+    }
+
+    /**
+     * 查询所有有数据的组织 去重的
+     * @param date
+     * @return
+     * @throws Exception
+     */
+    public List<String> dingdingUnitDistinct(String date) throws Exception {
+        Date startTime = DateTools.parse(date);
+        startTime = startOneDate(startTime);
+        Date endTime = endOneDate(startTime);
+        EntityManager em = this.entityManagerContainer().get(AttendanceDingtalkDetail.class);
+        CriteriaBuilder cb = em.getCriteriaBuilder();
+        CriteriaQuery<String> query = cb.createQuery(String.class);
+        Root<AttendanceDingtalkDetail> root = query.from(AttendanceDingtalkDetail.class);
+        Predicate p = cb.between(root.get(AttendanceDingtalkDetail_.userCheckTime), startTime.getTime(), endTime.getTime());
+        query.select(root.get(AttendanceDingtalkDetail_.o2Unit)).where(p).distinct(true);
+        return em.createQuery(query).getResultList();
+    }
+
+
+    /**
+     * 获取StatitsticDingdingForMonth ids
+     * @param year
+     * @param month
+     * @param unit
+     * @return
+     * @throws Exception
+     */
+    public List<String> getStatUnitForMonthIds(String year, String month, String unit) throws Exception {
+        EntityManager em = this.entityManagerContainer().get(StatisticDingdingUnitForMonth.class);
+        CriteriaBuilder cb = em.getCriteriaBuilder();
+        CriteriaQuery<String> query = cb.createQuery(String.class);
+        Root<StatisticDingdingUnitForMonth> root = query.from(StatisticDingdingUnitForMonth.class);
+        Predicate p = cb.equal(root.get(StatisticDingdingUnitForMonth_.statisticYear), year);
+        p = cb.and(p, cb.equal(root.get(StatisticDingdingUnitForMonth_.statisticMonth), month));
+        p = cb.and(p, cb.equal(root.get(StatisticDingdingUnitForMonth_.o2Unit), unit));
+        query.select(root.get(StatisticDingdingUnitForMonth_.id)).where(p);
+        return em.createQuery(query).getResultList();
+    }
+
+    public List<String> getStatUnitForDayIds(String year, String month, String day, String unit) throws Exception {
+        EntityManager em = this.entityManagerContainer().get(StatisticDingdingUnitForDay.class);
+        CriteriaBuilder cb = em.getCriteriaBuilder();
+        CriteriaQuery<String> query = cb.createQuery(String.class);
+        Root<StatisticDingdingUnitForDay> root = query.from(StatisticDingdingUnitForDay.class);
+        Predicate p = cb.equal(root.get(StatisticDingdingUnitForDay_.statisticYear), year);
+        p = cb.and(p, cb.equal(root.get(StatisticDingdingUnitForDay_.statisticMonth), month));
+        p = cb.and(p, cb.equal(root.get(StatisticDingdingUnitForDay_.statisticDate), day));
+        p = cb.and(p, cb.equal(root.get(StatisticDingdingUnitForDay_.o2Unit), unit));
+        query.select(root.get(StatisticDingdingUnitForDay_.id)).where(p);
+        return em.createQuery(query).getResultList();
+    }
+
+
+
+    public Long sumWorkDayUnitForDayWithMonth(String year, String month, String unit) throws Exception {
+        EntityManager em = this.entityManagerContainer().get(StatisticDingdingUnitForDay.class);
+        CriteriaBuilder cb = em.getCriteriaBuilder();
+        CriteriaQuery<Long> query = cb.createQuery(Long.class);
+        Root<StatisticDingdingUnitForDay> root = query.from(StatisticDingdingUnitForDay.class);
+        Predicate p = cb.equal(root.get(StatisticDingdingUnitForDay_.statisticYear), year);
+        p = cb.and(p, cb.equal(root.get(StatisticDingdingUnitForDay_.statisticMonth), month));
+        p = cb.and(p, cb.equal(root.get(StatisticDingdingUnitForDay_.o2Unit), unit));
+        query.select(cb.sum(root.get(StatisticDingdingUnitForDay_.workDayCount))).where(p);
+        return em.createQuery(query).getSingleResult();
+    }
+
+    public Long sumOnDutyUnitForDayWithMonth(String year, String month, String unit) throws Exception {
+        EntityManager em = this.entityManagerContainer().get(StatisticDingdingUnitForDay.class);
+        CriteriaBuilder cb = em.getCriteriaBuilder();
+        CriteriaQuery<Long> query = cb.createQuery(Long.class);
+        Root<StatisticDingdingUnitForDay> root = query.from(StatisticDingdingUnitForDay.class);
+        Predicate p = cb.equal(root.get(StatisticDingdingUnitForDay_.statisticYear), year);
+        p = cb.and(p, cb.equal(root.get(StatisticDingdingUnitForDay_.statisticMonth), month));
+        p = cb.and(p, cb.equal(root.get(StatisticDingdingUnitForDay_.o2Unit), unit));
+        query.select(cb.sum(root.get(StatisticDingdingUnitForDay_.onDutyTimes))).where(p);
+        return em.createQuery(query).getSingleResult();
+    }
+    public Long sumOffDutyUnitForDayWithMonth(String year, String month, String unit) throws Exception {
+        EntityManager em = this.entityManagerContainer().get(StatisticDingdingUnitForDay.class);
+        CriteriaBuilder cb = em.getCriteriaBuilder();
+        CriteriaQuery<Long> query = cb.createQuery(Long.class);
+        Root<StatisticDingdingUnitForDay> root = query.from(StatisticDingdingUnitForDay.class);
+        Predicate p = cb.equal(root.get(StatisticDingdingUnitForDay_.statisticYear), year);
+        p = cb.and(p, cb.equal(root.get(StatisticDingdingUnitForDay_.statisticMonth), month));
+        p = cb.and(p, cb.equal(root.get(StatisticDingdingUnitForDay_.o2Unit), unit));
+        query.select(cb.sum(root.get(StatisticDingdingUnitForDay_.offDutyTimes))).where(p);
+        return em.createQuery(query).getSingleResult();
+    }
+
+    public Long sumNormalUnitForDayWithMonth(String year, String month, String unit) throws Exception {
+        EntityManager em = this.entityManagerContainer().get(StatisticDingdingUnitForDay.class);
+        CriteriaBuilder cb = em.getCriteriaBuilder();
+        CriteriaQuery<Long> query = cb.createQuery(Long.class);
+        Root<StatisticDingdingUnitForDay> root = query.from(StatisticDingdingUnitForDay.class);
+        Predicate p = cb.equal(root.get(StatisticDingdingUnitForDay_.statisticYear), year);
+        p = cb.and(p, cb.equal(root.get(StatisticDingdingUnitForDay_.statisticMonth), month));
+        p = cb.and(p, cb.equal(root.get(StatisticDingdingUnitForDay_.o2Unit), unit));
+        query.select(cb.sum(root.get(StatisticDingdingUnitForDay_.resultNormal))).where(p);
+        return em.createQuery(query).getSingleResult();
+    }
+    public Long sumLateTimesUnitForDayWithMonth(String year, String month, String unit) throws Exception {
+        EntityManager em = this.entityManagerContainer().get(StatisticDingdingUnitForDay.class);
+        CriteriaBuilder cb = em.getCriteriaBuilder();
+        CriteriaQuery<Long> query = cb.createQuery(Long.class);
+        Root<StatisticDingdingUnitForDay> root = query.from(StatisticDingdingUnitForDay.class);
+        Predicate p = cb.equal(root.get(StatisticDingdingUnitForDay_.statisticYear), year);
+        p = cb.and(p, cb.equal(root.get(StatisticDingdingUnitForDay_.statisticMonth), month));
+        p = cb.and(p, cb.equal(root.get(StatisticDingdingUnitForDay_.o2Unit), unit));
+        query.select(cb.sum(root.get(StatisticDingdingUnitForDay_.lateTimes))).where(p);
+        return em.createQuery(query).getSingleResult();
+    }
+    public Long sumLeaveEarlyUnitForDayWithMonth(String year, String month, String unit) throws Exception {
+        EntityManager em = this.entityManagerContainer().get(StatisticDingdingUnitForDay.class);
+        CriteriaBuilder cb = em.getCriteriaBuilder();
+        CriteriaQuery<Long> query = cb.createQuery(Long.class);
+        Root<StatisticDingdingUnitForDay> root = query.from(StatisticDingdingUnitForDay.class);
+        Predicate p = cb.equal(root.get(StatisticDingdingUnitForDay_.statisticYear), year);
+        p = cb.and(p, cb.equal(root.get(StatisticDingdingUnitForDay_.statisticMonth), month));
+        p = cb.and(p, cb.equal(root.get(StatisticDingdingUnitForDay_.o2Unit), unit));
+        query.select(cb.sum(root.get(StatisticDingdingUnitForDay_.leaveEarlyTimes))).where(p);
+        return em.createQuery(query).getSingleResult();
+    }
+
+    public Long sumNotSignedUnitForDayWithMonth(String year, String month, String unit) throws Exception {
+        EntityManager em = this.entityManagerContainer().get(StatisticDingdingUnitForDay.class);
+        CriteriaBuilder cb = em.getCriteriaBuilder();
+        CriteriaQuery<Long> query = cb.createQuery(Long.class);
+        Root<StatisticDingdingUnitForDay> root = query.from(StatisticDingdingUnitForDay.class);
+        Predicate p = cb.equal(root.get(StatisticDingdingUnitForDay_.statisticYear), year);
+        p = cb.and(p, cb.equal(root.get(StatisticDingdingUnitForDay_.statisticMonth), month));
+        p = cb.and(p, cb.equal(root.get(StatisticDingdingUnitForDay_.o2Unit), unit));
+        query.select(cb.sum(root.get(StatisticDingdingUnitForDay_.notSignedCount))).where(p);
+        return em.createQuery(query).getSingleResult();
+    }
+
+    public Long sumAbsenteeismUnitForDayWithMonth(String year, String month, String unit) throws Exception {
+        EntityManager em = this.entityManagerContainer().get(StatisticDingdingUnitForDay.class);
+        CriteriaBuilder cb = em.getCriteriaBuilder();
+        CriteriaQuery<Long> query = cb.createQuery(Long.class);
+        Root<StatisticDingdingUnitForDay> root = query.from(StatisticDingdingUnitForDay.class);
+        Predicate p = cb.equal(root.get(StatisticDingdingUnitForDay_.statisticYear), year);
+        p = cb.and(p, cb.equal(root.get(StatisticDingdingUnitForDay_.statisticMonth), month));
+        p = cb.and(p, cb.equal(root.get(StatisticDingdingUnitForDay_.o2Unit), unit));
+        query.select(cb.sum(root.get(StatisticDingdingUnitForDay_.absenteeismTimes))).where(p);
+        return em.createQuery(query).getSingleResult();
+    }
+
+    public Long sumSeriousLateUnitForDayWithMonth(String year, String month, String unit) throws Exception {
+        EntityManager em = this.entityManagerContainer().get(StatisticDingdingUnitForDay.class);
+        CriteriaBuilder cb = em.getCriteriaBuilder();
+        CriteriaQuery<Long> query = cb.createQuery(Long.class);
+        Root<StatisticDingdingUnitForDay> root = query.from(StatisticDingdingUnitForDay.class);
+        Predicate p = cb.equal(root.get(StatisticDingdingUnitForDay_.statisticYear), year);
+        p = cb.and(p, cb.equal(root.get(StatisticDingdingUnitForDay_.statisticMonth), month));
+        p = cb.and(p, cb.equal(root.get(StatisticDingdingUnitForDay_.o2Unit), unit));
+        query.select(cb.sum(root.get(StatisticDingdingUnitForDay_.seriousLateTimes))).where(p);
+        return em.createQuery(query).getSingleResult();
+    }
+
+
+
+
+    private Date monthLastDay(String year, String month) throws Exception {
+        Calendar cal = Calendar.getInstance();
+        int yearInt = Integer.parseInt(year);
+        cal.set(Calendar.YEAR, yearInt);
+        int monthInt = Integer.parseInt(month);
+        cal.set(Calendar.MONTH, monthInt);
+        cal.set(Calendar.DAY_OF_MONTH, 1);
+        cal.set(Calendar.HOUR_OF_DAY, 23);
+        cal.set(Calendar.MINUTE, 59);
+        cal.set(Calendar.SECOND, 59);
+        cal.set(Calendar.MILLISECOND, 0);
+        cal.add(Calendar.DAY_OF_MONTH, -1);
+        return cal.getTime();
+    }
+    private Date monthFirstDay(String year, String month) throws Exception {
+        Calendar cal = Calendar.getInstance();
+        int yearInt = Integer.parseInt(year);
+        cal.set(Calendar.YEAR, yearInt);
+        int monthInt = Integer.parseInt(month);
+        cal.set(Calendar.MONTH, monthInt-1);
+        cal.set(Calendar.DATE, 1);
+        cal.set(Calendar.HOUR_OF_DAY, 0);
+        cal.set(Calendar.MINUTE, 0);
+        cal.set(Calendar.SECOND, 0);
+        cal.set(Calendar.MILLISECOND, 0);
+        return cal.getTime();
+    }
+    private Date startOneDate(Date date) throws Exception {
+        return DateTools.floorDate(date, null);
+    }
+
+    private Date endOneDate(Date date) throws Exception {
+        Calendar cal = DateUtils.toCalendar(date);
+        cal.set(Calendar.HOUR_OF_DAY, 23);
+        cal.set(Calendar.MINUTE, 59);
+        cal.set(Calendar.SECOND, 59);
+        cal.set(Calendar.MILLISECOND, 0);
+        return cal.getTime();
+    }
+}

+ 6 - 0
o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/jaxrs/ActionApplication.java

@@ -18,7 +18,10 @@ import com.x.attendance.assemble.control.jaxrs.attendancestatistic.AttendanceSta
 import com.x.attendance.assemble.control.jaxrs.attendancestatisticalcycle.AttendanceStatisticalCycleAction;
 import com.x.attendance.assemble.control.jaxrs.attendancestatisticrequirelog.AttendanceStatisticRequireLogAction;
 import com.x.attendance.assemble.control.jaxrs.attendanceworkdayconfig.AttendanceWorkDayConfigAction;
+import com.x.attendance.assemble.control.jaxrs.dingding.DingdingAttendanceAction;
+import com.x.attendance.assemble.control.jaxrs.dingdingstatistic.DingdingAttendanceStatisticAction;
 import com.x.attendance.assemble.control.jaxrs.fileimport.AttendanceDetailFileImportAction;
+import com.x.attendance.assemble.control.jaxrs.qywx.QywxAttendanceAction;
 import com.x.attendance.assemble.control.jaxrs.selfholiday.AttendanceSelfHolidayAction;
 import com.x.attendance.assemble.control.jaxrs.selfholiday.AttendanceSelfHolidaySimpleAction;
 import com.x.attendance.assemble.control.jaxrs.uuid.UUIDAction;
@@ -48,6 +51,9 @@ public class ActionApplication extends AbstractActionApplication {
 		this.classes.add(AttendanceStatisticalCycleAction.class);
 		this.classes.add(AttendanceEmployeeConfigAction.class);
 		this.classes.add(AttendanceStatisticRequireLogAction.class);
+		this.classes.add(DingdingAttendanceAction.class);
+		this.classes.add(QywxAttendanceAction.class);
+		this.classes.add(DingdingAttendanceStatisticAction.class);
 		return this.classes;
 	}
 

+ 2 - 1
o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/jaxrs/DingdingJaxrsFilter.java

@@ -1,10 +1,11 @@
 package com.x.attendance.assemble.control.jaxrs;
 
 import com.x.base.core.project.jaxrs.CipherManagerJaxrsFilter;
+import com.x.base.core.project.jaxrs.ManagerUserJaxrsFilter;
 
 import javax.servlet.annotation.WebFilter;
 
 
 @WebFilter(urlPatterns = "/jaxrs/dingding/*", asyncSupported = true)
-public class DingdingJaxrsFilter extends CipherManagerJaxrsFilter {
+public class DingdingJaxrsFilter extends ManagerUserJaxrsFilter {
 }

+ 10 - 0
o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/jaxrs/DingdingStatisticJaxrsFilter.java

@@ -0,0 +1,10 @@
+package com.x.attendance.assemble.control.jaxrs;
+
+import com.x.base.core.project.jaxrs.ManagerUserJaxrsFilter;
+
+import javax.servlet.annotation.WebFilter;
+
+
+@WebFilter(urlPatterns = "/jaxrs/dingdingstatistic/*", asyncSupported = true)
+public class DingdingStatisticJaxrsFilter extends ManagerUserJaxrsFilter {
+}

+ 10 - 0
o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/jaxrs/QywxJaxrsFilter.java

@@ -0,0 +1,10 @@
+package com.x.attendance.assemble.control.jaxrs;
+
+import com.x.base.core.project.jaxrs.CipherManagerJaxrsFilter;
+
+import javax.servlet.annotation.WebFilter;
+
+
+@WebFilter(urlPatterns = "/jaxrs/qywx/*", asyncSupported = true)
+public class QywxJaxrsFilter extends CipherManagerJaxrsFilter {
+}

+ 32 - 0
o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/jaxrs/attendancesetting/ActionEnableType.java

@@ -0,0 +1,32 @@
+package com.x.attendance.assemble.control.jaxrs.attendancesetting;
+
+import com.x.base.core.project.config.Config;
+import com.x.base.core.project.http.ActionResult;
+import com.x.base.core.project.http.EffectivePerson;
+import com.x.base.core.project.jaxrs.WrapString;
+
+
+class ActionEnableType extends BaseAction {
+
+	public static final String TYPE_QIYEWEIXIN = "qywx";
+	public static final String TYPE_DINGDING = "dingding";
+
+	ActionResult<Wo> execute(EffectivePerson effectivePerson) throws Exception {
+		ActionResult<Wo> result = new ActionResult<>();
+		Wo wo = new Wo();
+		if (Config.qiyeweixin().getEnable() && Config.qiyeweixin().getAttendanceSyncEnable()) {
+			wo.setValue(TYPE_QIYEWEIXIN);
+		} else if (Config.dingding().getEnable() && Config.dingding().getAttendanceSyncEnable()) {
+			wo.setValue(TYPE_DINGDING);
+		} else {
+			wo.setValue("");
+		}
+		result.setData(wo);
+		return result;
+	}
+
+	public static class Wo extends WrapString {
+
+	}
+
+}

+ 18 - 0
o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/jaxrs/attendancesetting/AttendanceSettingAction.java

@@ -152,4 +152,22 @@ public class AttendanceSettingAction extends StandardJaxrsAction {
 		}
 		asyncResponse.resume(ResponseFactory.getEntityTagActionResultResponse(request, result));
 	}
+
+	@JaxrsMethodDescribe(value = "是否启用钉钉考勤或企业微信考勤", action = ActionEnableType.class)
+	@GET
+	@Path("enable/type")
+	@Produces(HttpMediaType.APPLICATION_JSON_UTF_8)
+	@Consumes(MediaType.APPLICATION_JSON)
+	public void enableType(@Suspended final AsyncResponse asyncResponse, @Context HttpServletRequest request) {
+		ActionResult<ActionEnableType.Wo> result = new ActionResult<>();
+		EffectivePerson effectivePerson = this.effectivePerson(request);
+		try {
+			result = new ActionEnableType().execute(effectivePerson);
+		} catch (Exception e) {
+			result = new ActionResult<>();
+			result.error(e);
+			logger.error(e, effectivePerson, request, null);
+		}
+		asyncResponse.resume(ResponseFactory.getEntityTagActionResultResponse(request, result));
+	}
 }

+ 2 - 2
o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/jaxrs/dingding/ActionDeleteAllData.java

@@ -22,14 +22,14 @@ public class ActionDeleteAllData extends BaseAction {
         ActionResult<WrapBoolean> result = new ActionResult<>();
 
         try ( EntityManagerContainer emc = EntityManagerContainerFactory.instance().create()) {
-            List<AttendanceDingtalkDetail> details  = emc.fetchAll(AttendanceDingtalkDetail.class);
+            List<AttendanceDingtalkDetail> details  = emc.listAll(AttendanceDingtalkDetail.class);
             if ( null == details ) {
                 result.error( new FindEmptyException() );
             }else{
                 //进行数据库持久化操作
                 emc.beginTransaction( AttendanceDingtalkDetail.class );
                 for (AttendanceDingtalkDetail d : details) {
-                    emc.remove(d, CheckRemoveType.all);
+                    emc.remove(d);
                 }
                 emc.commit();
                 result.setData(new WrapBoolean(true));

+ 337 - 0
o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/jaxrs/dingding/ActionListDDAttendanceDetail.java

@@ -0,0 +1,337 @@
+package com.x.attendance.assemble.control.jaxrs.dingding;
+
+import com.google.gson.JsonElement;
+import com.x.attendance.assemble.control.Business;
+import com.x.attendance.assemble.control.jaxrs.dingding.exception.SearchArgEmptyException;
+import com.x.attendance.assemble.control.jaxrs.dingding.exception.TimeEmptyException;
+import com.x.attendance.entity.AttendanceDingtalkDetail;
+import com.x.attendance.entity.AttendanceDingtalkDetail_;
+import com.x.base.core.container.EntityManagerContainer;
+import com.x.base.core.container.factory.EntityManagerContainerFactory;
+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.gson.GsonPropertyObject;
+import com.x.base.core.project.http.ActionResult;
+import com.x.base.core.project.jaxrs.BetweenTerms;
+import com.x.base.core.project.jaxrs.EqualsTerms;
+import com.x.base.core.project.jaxrs.InTerms;
+import com.x.base.core.project.logger.Logger;
+import com.x.base.core.project.logger.LoggerFactory;
+import com.x.base.core.project.organization.Person;
+import com.x.base.core.project.tools.DateTools;
+import com.x.base.core.project.tools.ListTools;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.time.DateUtils;
+
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.List;
+import java.util.stream.Collectors;
+
+public class ActionListDDAttendanceDetail extends BaseAction {
+
+    private static final Logger logger = LoggerFactory.getLogger(ActionListDDAttendanceDetail.class);
+
+    public ActionResult<List<Wo>> execute(String flag, Integer count, JsonElement jsonElement) throws Exception {
+        ActionResult<List<Wo>> result = new ActionResult<>();
+        try ( EntityManagerContainer emc = EntityManagerContainerFactory.instance().create()) {
+            Business business = new Business(emc);
+            Wi wi = this.convertToWrapIn(jsonElement , Wi.class);
+            logger.info("传入参数:"+wi.toString());
+            if (StringUtils.isEmpty(wi.getYear())) {
+                throw new TimeEmptyException();
+            }
+            if (StringUtils.isEmpty(wi.getPerson()) && StringUtils.isEmpty(wi.getUnit()) && StringUtils.isEmpty(wi.getTopUnit())) {
+                throw new SearchArgEmptyException();
+            }
+            Date startDay  ;
+            Date endDay;
+            if (StringUtils.isEmpty(wi.getMonth())) {
+                startDay = getDay(wi.getYear(), "1", "1");
+                endDay = getDay(wi.getYear(), "12", "31");
+            }else {
+                if (StringUtils.isEmpty(wi.getDay())) {
+                    startDay = getDay(wi.getYear(), wi.getMonth(), "1");
+                    endDay = getMonthLastDay(wi.getYear(), wi.getMonth());
+                }else {
+                    startDay = getDay(wi.getYear(), wi.getMonth(), wi.getDay());
+                    endDay = getEndDay(wi.getYear(), wi.getMonth(), wi.getDay());
+                }
+            }
+            logger.info("startDay:"+DateTools.format(startDay));
+            logger.info("endDay:"+DateTools.format(endDay));
+            BetweenTerms betweenTerms = new BetweenTerms();
+            betweenTerms.put("userCheckTime", ListTools.toList(startDay.getTime(), endDay.getTime()));
+            logger.info("between :"+betweenTerms.toString());
+            String id = EMPTY_SYMBOL;
+            /** 如果不是空位标志位 */
+            if (!StringUtils.equals(EMPTY_SYMBOL, flag)) {
+                id = flag;
+            }
+            if (StringUtils.isNotEmpty(wi.getPerson())) {
+                EqualsTerms equals = new EqualsTerms();
+                equals.put("o2User", wi.getPerson());
+                if (isCheckTypeEnable(wi.getCheckType())){
+                    equals.put("checkType", wi.getCheckType());
+                }
+                if (isTimeResultEnable(wi.getTimeResult())) {
+                    equals.put("timeResult", wi.getTimeResult());
+                }
+                logger.info("equals :"+equals.toString());
+                result = this.standardListNext(Wo.copier, id, count, JpaObject.sequence_FIELDNAME, equals, null,
+                        null, null, null, null, null, betweenTerms, true, DESC);
+            }
+            if (StringUtils.isNotEmpty(wi.getUnit())) {
+                EqualsTerms equals = new EqualsTerms();
+                equals.put("o2Unit", wi.getUnit());
+                if (isCheckTypeEnable(wi.getCheckType())){
+                    equals.put("checkType", wi.getCheckType());
+                }
+                if (isTimeResultEnable(wi.getTimeResult())) {
+                    equals.put("timeResult", wi.getTimeResult());
+                }
+                logger.info("equals :"+equals.toString());
+                result = this.standardListNext(Wo.copier, id, count, JpaObject.sequence_FIELDNAME, equals, null,
+                        null, null, null, null, null, betweenTerms, true, DESC);
+            }
+            if (StringUtils.isNotEmpty(wi.getTopUnit())) {
+                EqualsTerms equals = new EqualsTerms();
+                if (isCheckTypeEnable(wi.getCheckType())){
+                    equals.put("checkType", wi.getCheckType());
+                }
+                if (isTimeResultEnable(wi.getTimeResult())) {
+                    equals.put("timeResult", wi.getTimeResult());
+                }
+                InTerms ins = new InTerms();
+                List<String> subUnits = business.organization().unit().listWithUnitSubNested( wi.getTopUnit() );
+                if (subUnits == null || subUnits.isEmpty()) {
+                    subUnits = new ArrayList<>();
+                }
+                subUnits.add(wi.getTopUnit());
+                ins.put("o2Unit", subUnits);
+                logger.info("ins :"+ins.toString());
+                result = this.standardListNext(Wo.copier, id, count, JpaObject.sequence_FIELDNAME, equals, null,
+                        null, ins, null, null, null, betweenTerms, true, DESC);
+            }
+
+        }
+        return result;
+    }
+
+    private boolean isCheckTypeEnable(String type) {
+        if (StringUtils.isEmpty(type) || (!AttendanceDingtalkDetail.OffDuty.equals(type) && !AttendanceDingtalkDetail.OnDuty.equals(type))) {
+            return false;
+        }
+        return true;
+    }
+
+    private boolean isTimeResultEnable(String result) {
+        if (StringUtils.isEmpty(result) ||
+                (!AttendanceDingtalkDetail.TIMERESULT_Absenteeism.equals(result)
+                        && !AttendanceDingtalkDetail.TIMERESULT_Early.equals(result)
+                        && !AttendanceDingtalkDetail.TIMERESULT_Late.equals(result)
+                        && !AttendanceDingtalkDetail.TIMERESULT_NORMAL.equals(result)
+                        && !AttendanceDingtalkDetail.TIMERESULT_NotSigned.equals(result)
+                        && !AttendanceDingtalkDetail.TIMERESULT_SeriousLate.equals(result))) {
+            return false;
+        }
+        return true;
+    }
+
+    public static Date getMonthLastDay(String year, String month) throws Exception {
+        Calendar cal = Calendar.getInstance();
+        int yearInt = Integer.parseInt(year);
+        cal.set(Calendar.YEAR, yearInt);
+        int monthInt = Integer.parseInt(month);
+        cal.set(Calendar.MONTH, monthInt);
+        cal.set(Calendar.DAY_OF_MONTH, 1);
+        cal.set(Calendar.HOUR_OF_DAY, 23);
+        cal.set(Calendar.MINUTE, 59);
+        cal.set(Calendar.SECOND, 59);
+        cal.set(Calendar.MILLISECOND, 0);
+        cal.add(Calendar.DAY_OF_MONTH, -1);
+        return cal.getTime();
+    }
+
+    public static Date getDay(String year, String month, String day) throws Exception {
+        Calendar cal = Calendar.getInstance();
+        int yearInt = Integer.parseInt(year);
+        cal.set(Calendar.YEAR, yearInt);
+        int monthInt = Integer.parseInt(month);
+        cal.set(Calendar.MONTH, monthInt-1);
+        int dayInt = Integer.parseInt(day);
+        cal.set(Calendar.DATE, dayInt);
+        cal.set(Calendar.HOUR_OF_DAY, 0);
+        cal.set(Calendar.MINUTE, 0);
+        cal.set(Calendar.SECOND, 0);
+        cal.set(Calendar.MILLISECOND, 0);
+        return cal.getTime();
+    }
+    public static Date getEndDay(String year, String month, String day) throws Exception {
+        Calendar cal = Calendar.getInstance();
+        int yearInt = Integer.parseInt(year);
+        cal.set(Calendar.YEAR, yearInt);
+        int monthInt = Integer.parseInt(month);
+        cal.set(Calendar.MONTH, monthInt-1);
+        int dayInt = Integer.parseInt(day);
+        cal.set(Calendar.DATE, dayInt);
+        cal.set(Calendar.HOUR_OF_DAY, 23);
+        cal.set(Calendar.MINUTE, 59);
+        cal.set(Calendar.SECOND, 59);
+        cal.set(Calendar.MILLISECOND, 0);
+        return cal.getTime();
+    }
+
+    public static class Wi extends GsonPropertyObject {
+
+        @FieldDescribe("年份")
+        private String year;
+        @FieldDescribe("月份")
+        private String month;
+        @FieldDescribe("日期")
+        private String day;
+        @FieldDescribe("人员")
+        private String person;
+        @FieldDescribe("部门")
+        private String unit;
+        @FieldDescribe("顶级部门,会及联查询下级部门")
+        private String topUnit;
+        @FieldDescribe("打卡类型:OffDuty|OnDuty")
+        private String checkType;
+        @FieldDescribe("打卡结果:Normal|Early|Late|SeriousLate|Absenteeism|NotSigned")
+        private String timeResult;
+
+
+        public String getCheckType() {
+            return checkType;
+        }
+
+        public void setCheckType(String checkType) {
+            this.checkType = checkType;
+        }
+
+        public String getTimeResult() {
+            return timeResult;
+        }
+
+        public void setTimeResult(String timeResult) {
+            this.timeResult = timeResult;
+        }
+
+        public String getYear() {
+            return year;
+        }
+
+        public void setYear(String year) {
+            this.year = year;
+        }
+
+        public String getMonth() {
+            return month;
+        }
+
+        public void setMonth(String month) {
+            this.month = month;
+        }
+
+        public String getDay() {
+            return day;
+        }
+
+        public void setDay(String day) {
+            this.day = day;
+        }
+
+        public String getUnit() {
+            return unit;
+        }
+
+        public void setUnit(String unit) {
+            this.unit = unit;
+        }
+
+        public String getPerson() {
+            return person;
+        }
+
+        public void setPerson(String person) {
+            this.person = person;
+        }
+
+        public String getTopUnit() {
+            return topUnit;
+        }
+
+        public void setTopUnit(String topUnit) {
+            this.topUnit = topUnit;
+        }
+    }
+
+    public static class Wo extends AttendanceDingtalkDetail {
+        static final WrapCopier<AttendanceDingtalkDetail, Wo> copier =
+                WrapCopierFactory.wo(AttendanceDingtalkDetail.class, Wo.class, null, JpaObject.FieldsInvisible);
+
+        @FieldDescribe("实际打卡时间")
+        private Date userCheckTimeFormat;
+        @FieldDescribe("工作日")
+        private Date workDateFormat;
+        @FieldDescribe("基准时间,用于计算迟到和早退")
+        private Date baseCheckTimeFormat;
+        @FieldDescribe("排序号")
+        private Long rank;
+
+
+        public void formatDateTime() {
+            if (userCheckTimeFormat == null) {
+                Date date = new Date();
+                date.setTime(getUserCheckTime());
+                setUserCheckTimeFormat(date);
+            }
+            if (workDateFormat == null) {
+                Date date = new Date();
+                date.setTime(getWorkDate());
+                setWorkDateFormat(date);
+            }
+            if (baseCheckTimeFormat == null) {
+                Date date = new Date();
+                date.setTime(getBaseCheckTime());
+                setBaseCheckTimeFormat(date);
+            }
+        }
+
+        public Long getRank() {
+            return rank;
+        }
+
+        public void setRank(Long rank) {
+            this.rank = rank;
+        }
+
+        public Date getUserCheckTimeFormat() {
+            return userCheckTimeFormat;
+        }
+
+        public void setUserCheckTimeFormat(Date userCheckTimeFormat) {
+            this.userCheckTimeFormat = userCheckTimeFormat;
+        }
+
+        public Date getWorkDateFormat() {
+            return workDateFormat;
+        }
+
+        public void setWorkDateFormat(Date workDateFormat) {
+            this.workDateFormat = workDateFormat;
+        }
+
+        public Date getBaseCheckTimeFormat() {
+            return baseCheckTimeFormat;
+        }
+
+        public void setBaseCheckTimeFormat(Date baseCheckTimeFormat) {
+            this.baseCheckTimeFormat = baseCheckTimeFormat;
+        }
+    }
+}

+ 81 - 0
o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/jaxrs/dingding/ActionListDingdingSyncRecord.java

@@ -0,0 +1,81 @@
+package com.x.attendance.assemble.control.jaxrs.dingding;
+
+import com.x.attendance.assemble.control.Business;
+import com.x.attendance.entity.DingdingQywxSyncRecord;
+import com.x.base.core.container.EntityManagerContainer;
+import com.x.base.core.container.factory.EntityManagerContainerFactory;
+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.http.ActionResult;
+import com.x.base.core.project.logger.Logger;
+import com.x.base.core.project.logger.LoggerFactory;
+
+import java.util.Date;
+import java.util.List;
+import java.util.stream.Collectors;
+
+public class ActionListDingdingSyncRecord extends BaseAction {
+
+    private static final Logger logger = LoggerFactory.getLogger(ActionListDingdingSyncRecord.class);
+
+    public ActionResult<List<Wo>> execute() throws Exception {
+        ActionResult<List<Wo>> result = new ActionResult<>();
+        try ( EntityManagerContainer emc = EntityManagerContainerFactory.instance().create()) {
+            Business business = new Business(emc);
+            List<DingdingQywxSyncRecord> list = business.dingdingAttendanceFactory().findAllSyncRecordWithType(DingdingQywxSyncRecord.syncType_dingding);
+            if (list != null && !list.isEmpty()) {
+                List<Wo> wos = list.stream().map(record -> {
+                    Wo wo = new Wo();
+                    try {
+                        wo = Wo.copier.copy(record, wo);
+                        wo.formatDate();
+                    }catch (Exception e) {
+                        logger.error(e);
+                    }
+                    return wo;
+                }).collect(Collectors.toList());
+                result.setData(wos);
+            }
+        }
+        return result;
+    }
+
+
+    public static class Wo extends DingdingQywxSyncRecord {
+        static final WrapCopier<DingdingQywxSyncRecord, Wo> copier =
+                WrapCopierFactory.wo(DingdingQywxSyncRecord.class, Wo.class, null, JpaObject.FieldsInvisible);
+
+        @FieldDescribe("同步打卡记录的开始时间")
+        private Date dateFromFormat;
+        @FieldDescribe("同步打卡记录的结束时间")
+        private Date dateToFormat;
+
+        public void formatDate() {
+            Date date = new Date();
+            date.setTime(getDateFrom());
+            setDateFromFormat(date);
+            Date dateto = new Date();
+            dateto.setTime(getDateTo());
+            setDateToFormat(dateto);
+        }
+
+
+        public Date getDateFromFormat() {
+            return dateFromFormat;
+        }
+
+        public void setDateFromFormat(Date dateFromFormat) {
+            this.dateFromFormat = dateFromFormat;
+        }
+
+        public Date getDateToFormat() {
+            return dateToFormat;
+        }
+
+        public void setDateToFormat(Date dateToFormat) {
+            this.dateToFormat = dateToFormat;
+        }
+    }
+}

+ 34 - 0
o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/jaxrs/dingding/ActionStatisticPersonMonthData.java

@@ -0,0 +1,34 @@
+package com.x.attendance.assemble.control.jaxrs.dingding;
+
+import com.x.attendance.assemble.control.ThisApplication;
+import com.x.attendance.assemble.control.jaxrs.dingding.exception.SearchArgEmptyException;
+import com.x.attendance.assemble.control.jaxrs.dingdingstatistic.BaseAction;
+import com.x.base.core.project.http.ActionResult;
+import com.x.base.core.project.jaxrs.WrapBoolean;
+import com.x.base.core.project.logger.Logger;
+import com.x.base.core.project.logger.LoggerFactory;
+import com.x.base.core.project.tools.DateTools;
+import org.apache.commons.lang3.StringUtils;
+
+import java.util.Date;
+
+/**
+ * Created by fancyLou on 2020-04-05.
+ * Copyright © 2020 O2. All rights reserved.
+ */
+public class ActionStatisticPersonMonthData extends BaseAction {
+
+    private static final Logger logger = LoggerFactory.getLogger(ActionStatisticPersonMonthData.class);
+
+    ActionResult<WrapBoolean> execute(String year, String month) throws Exception{
+        ActionResult<WrapBoolean> result = new ActionResult<WrapBoolean>();
+        if (StringUtils.isEmpty(year) || StringUtils.isEmpty(month)) {
+            throw new SearchArgEmptyException();
+        }
+        logger.info("开始执行全部人员考勤信息统计 year:"+year+", month:"+month);
+        Date date = DateTools.parse(year+"-"+month+"-01");
+        ThisApplication.personStatisticQueue.send(date);
+        result.setData(new WrapBoolean(true));
+        return result;
+    }
+}

+ 34 - 0
o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/jaxrs/dingding/ActionStatisticUnitDayData.java

@@ -0,0 +1,34 @@
+package com.x.attendance.assemble.control.jaxrs.dingding;
+
+import com.x.attendance.assemble.control.ThisApplication;
+import com.x.attendance.assemble.control.jaxrs.dingding.exception.SearchArgEmptyException;
+import com.x.attendance.assemble.control.jaxrs.dingdingstatistic.BaseAction;
+import com.x.base.core.project.http.ActionResult;
+import com.x.base.core.project.jaxrs.WrapBoolean;
+import com.x.base.core.project.logger.Logger;
+import com.x.base.core.project.logger.LoggerFactory;
+import com.x.base.core.project.tools.DateTools;
+import org.apache.commons.lang3.StringUtils;
+
+import java.util.Date;
+
+/**
+ * Created by fancyLou on 2020-04-05.
+ * Copyright © 2020 O2. All rights reserved.
+ */
+public class ActionStatisticUnitDayData extends BaseAction {
+
+    private static final Logger logger = LoggerFactory.getLogger(ActionStatisticUnitDayData.class);
+
+    ActionResult<WrapBoolean> execute(String year, String month, String day) throws Exception{
+        ActionResult<WrapBoolean> result = new ActionResult<WrapBoolean>();
+        if (StringUtils.isEmpty(year) || StringUtils.isEmpty(month) || StringUtils.isEmpty(day)) {
+            throw new SearchArgEmptyException();
+        }
+        logger.info("开始执行全部部门考勤信息统计 year:"+year+", month:"+month);
+        Date date = DateTools.parse(year+"-"+month+"-"+day);
+        ThisApplication.unitStatisticQueue.send(date);
+        result.setData(new WrapBoolean(true));
+        return result;
+    }
+}

+ 22 - 14
o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/jaxrs/dingding/ActionSyncData.java

@@ -1,50 +1,58 @@
 package com.x.attendance.assemble.control.jaxrs.dingding;
 
-import com.google.gson.JsonElement;
+import com.x.attendance.assemble.control.Business;
 import com.x.attendance.assemble.control.ThisApplication;
 import com.x.attendance.entity.DingdingQywxSyncRecord;
 import com.x.base.core.container.EntityManagerContainer;
 import com.x.base.core.container.factory.EntityManagerContainerFactory;
-import com.x.base.core.entity.JpaObject;
-import com.x.base.core.project.bean.WrapCopier;
-import com.x.base.core.project.bean.WrapCopierFactory;
 import com.x.base.core.project.http.ActionResult;
 import com.x.base.core.project.http.EffectivePerson;
 import com.x.base.core.project.jaxrs.WrapBoolean;
 import com.x.base.core.project.logger.Logger;
 import com.x.base.core.project.logger.LoggerFactory;
+import com.x.base.core.project.tools.DateTools;
 
 import java.util.Date;
+import java.util.List;
 
 
 public class ActionSyncData extends BaseAction {
 
     private static final Logger logger = LoggerFactory.getLogger(ActionSyncData.class);
 
-    public ActionResult<WrapBoolean> execute(EffectivePerson effectivePerson, JsonElement jsonElement) throws Exception {
+    public ActionResult<WrapBoolean> execute(EffectivePerson effectivePerson, String dateFrom, String dateTo) throws Exception {
         ActionResult<WrapBoolean> result = new ActionResult<>();
         try ( EntityManagerContainer emc = EntityManagerContainerFactory.instance().create()) {
-            Wi wi = this.convertToWrapIn(jsonElement, Wi.class);
-            if (null == wi.getWay() ||
-                    (!DingdingQywxSyncRecord.syncWay_week.equals(wi.getWay()) && !DingdingQywxSyncRecord.syncWay_year.equals(wi.getWay()))) {
+            if (null == dateFrom || null == dateTo) {
                 throw new SyncWayException();
             }
+            Date from = DateTools.parse(dateFrom);
+            Date to = DateTools.parse(dateTo);
+            long gap = to.getTime() - from.getTime();
+            if (gap < 0) {
+                throw new SyncWayException();
+            }
+            if ((gap / (1000 * 60 * 60 * 24)) > 6 ) {
+                throw new MoreThanSevenDayException();
+            }
+//            Business business = new Business(emc);
+//            List<DingdingQywxSyncRecord> conflictList = business.dingdingAttendanceFactory().findConflictSyncRecord(from.getTime(), to.getTime());
+//            if (conflictList != null && !conflictList.isEmpty()) {
+//                throw new ConflictSyncRecordException();
+//            }
             DingdingQywxSyncRecord record = new DingdingQywxSyncRecord();
-            record.setWay(wi.getWay());
+            record.setDateFrom(from.getTime());
+            record.setDateTo(to.getTime());
             record.setStartTime(new Date());
             record.setType(DingdingQywxSyncRecord.syncType_dingding);
             record.setStatus(DingdingQywxSyncRecord.status_loading);
             emc.beginTransaction(DingdingQywxSyncRecord.class);
             emc.persist(record);
             emc.commit();
-            ThisApplication.dingdingQueue.executing(record);
+            ThisApplication.dingdingQueue.send(record);
             result.setData(new WrapBoolean(true));
         }
         return result;
     }
 
-    public static class Wi extends DingdingQywxSyncRecord {
-        public static WrapCopier<Wi, DingdingQywxSyncRecord> copier = WrapCopierFactory.wi(Wi.class,
-                DingdingQywxSyncRecord.class, null, JpaObject.FieldsUnmodify);
-    }
 }

+ 13 - 0
o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/jaxrs/dingding/ConflictSyncRecordException.java

@@ -0,0 +1,13 @@
+package com.x.attendance.assemble.control.jaxrs.dingding;
+
+import com.x.base.core.project.exception.PromptException;
+
+public class ConflictSyncRecordException extends PromptException {
+
+
+    private static final long serialVersionUID = 7077761236199564642L;
+
+    public ConflictSyncRecordException() {
+        super("同步时间和历史有冲突!");
+    }
+}

+ 85 - 7
o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/jaxrs/dingding/DingdingAttendanceAction.java

@@ -3,6 +3,7 @@ package com.x.attendance.assemble.control.jaxrs.dingding;
 import com.google.gson.JsonElement;
 import com.x.base.core.project.annotation.JaxrsDescribe;
 import com.x.base.core.project.annotation.JaxrsMethodDescribe;
+import com.x.base.core.project.annotation.JaxrsParameterDescribe;
 import com.x.base.core.project.http.ActionResult;
 import com.x.base.core.project.http.EffectivePerson;
 import com.x.base.core.project.http.HttpMediaType;
@@ -18,6 +19,7 @@ import javax.ws.rs.container.AsyncResponse;
 import javax.ws.rs.container.Suspended;
 import javax.ws.rs.core.Context;
 import javax.ws.rs.core.MediaType;
+import java.util.List;
 
 
 @Path("dingding")
@@ -47,20 +49,77 @@ public class DingdingAttendanceAction extends StandardJaxrsAction {
     }
 
 
-    //获取一年的数据 ???不知道是否能成 接口限制
-
-    //获取7天数据   可以做定时每天晚上更新
+    //
     @JaxrsMethodDescribe(value = "同步钉钉考勤结果", action = ActionSyncData.class)
-    @POST
-    @Path("sync")
+    @GET
+    @Path("sync/from/{dateFrom}/to/{dateTo}/start")
     @Produces(HttpMediaType.APPLICATION_JSON_UTF_8)
     @Consumes(MediaType.APPLICATION_JSON)
     public void syncData(@Suspended final AsyncResponse asyncResponse, @Context HttpServletRequest request,
-                         JsonElement jsonElement) {
+                         @JaxrsParameterDescribe("开始时间: yyyy-MM-dd") @PathParam("dateFrom") String dateFrom,
+                         @JaxrsParameterDescribe("结束时间: yyyy-MM-dd") @PathParam("dateTo") String dateTo) {
+        ActionResult<WrapBoolean> result = new ActionResult<>();
+        EffectivePerson effectivePerson = this.effectivePerson(request);
+        try {
+            result = new ActionSyncData().execute(effectivePerson, dateFrom, dateTo);
+        }catch (Exception e) {
+            logger.error(e, effectivePerson, request, null);
+            result.error(e);
+        }
+        asyncResponse.resume(ResponseFactory.getEntityTagActionResultResponse(request, result));
+    }
+
+    @JaxrsMethodDescribe(value = "查询钉钉同步记录信息", action = ActionListDingdingSyncRecord.class)
+    @GET
+    @Path("sync/list")
+    @Produces(HttpMediaType.APPLICATION_JSON_UTF_8)
+    @Consumes(MediaType.APPLICATION_JSON)
+    public void listDingdingSyncRecord(@Suspended final AsyncResponse asyncResponse, @Context HttpServletRequest request) {
+        ActionResult<List<ActionListDingdingSyncRecord.Wo>> result = new ActionResult<>();
+        EffectivePerson effectivePerson = this.effectivePerson(request);
+        try {
+            result = new ActionListDingdingSyncRecord().execute();
+        }catch (Exception e) {
+            logger.error(e, effectivePerson, request, null);
+            result.error(e);
+        }
+        asyncResponse.resume(ResponseFactory.getEntityTagActionResultResponse(request, result));
+    }
+
+    @JaxrsMethodDescribe(value = "查询钉钉打卡结果", action = ActionListDDAttendanceDetail.class)
+    @PUT
+    @Path("attendance/list/{id}/next/{count}")
+    @Produces(HttpMediaType.APPLICATION_JSON_UTF_8)
+    @Consumes(MediaType.APPLICATION_JSON)
+    public void listNextDingdingAttendance(@Suspended final AsyncResponse asyncResponse, @Context HttpServletRequest request,
+                                       @JaxrsParameterDescribe("最后一条数据ID") @PathParam("id") String id,
+                                       @JaxrsParameterDescribe("每页显示的条目数量") @PathParam("count") Integer count,
+                                       JsonElement jsonElement) {
+        ActionResult<List<ActionListDDAttendanceDetail.Wo>> result = new ActionResult<>();
+        EffectivePerson effectivePerson = this.effectivePerson(request);
+        try {
+            result = new ActionListDDAttendanceDetail().execute(id, count, jsonElement);
+        }catch (Exception e) {
+            logger.error(e, effectivePerson, request, null);
+            result.error(e);
+        }
+        asyncResponse.resume(ResponseFactory.getEntityTagActionResultResponse(request, result));
+    }
+
+
+
+    @JaxrsMethodDescribe(value = "钉钉考勤全部个人数据统计", action = ActionStatisticPersonMonthData.class)
+    @GET
+    @Path("statistic/person/year/{year}/month/{month}")
+    @Produces(HttpMediaType.APPLICATION_JSON_UTF_8)
+    @Consumes(MediaType.APPLICATION_JSON)
+    public void statisticPerson(@Suspended final AsyncResponse asyncResponse, @Context HttpServletRequest request,
+                         @JaxrsParameterDescribe("年份: yyyy") @PathParam("year") String year,
+                         @JaxrsParameterDescribe("月份: MM") @PathParam("month") String month) {
         ActionResult<WrapBoolean> result = new ActionResult<>();
         EffectivePerson effectivePerson = this.effectivePerson(request);
         try {
-            result = new ActionSyncData().execute(effectivePerson, jsonElement);
+            result = new ActionStatisticPersonMonthData().execute(year, month);
         }catch (Exception e) {
             logger.error(e, effectivePerson, request, null);
             result.error(e);
@@ -69,5 +128,24 @@ public class DingdingAttendanceAction extends StandardJaxrsAction {
     }
 
 
+    @JaxrsMethodDescribe(value = "钉钉考勤全部组织数据统计", action = ActionStatisticUnitDayData.class)
+    @GET
+    @Path("statistic/unit/year/{year}/month/{month}/day/{day}")
+    @Produces(HttpMediaType.APPLICATION_JSON_UTF_8)
+    @Consumes(MediaType.APPLICATION_JSON)
+    public void statisticUnit(@Suspended final AsyncResponse asyncResponse, @Context HttpServletRequest request,
+                                @JaxrsParameterDescribe("年份: yyyy") @PathParam("year") String year,
+                                @JaxrsParameterDescribe("月份: MM") @PathParam("month") String month,
+                                @JaxrsParameterDescribe("日期: dd") @PathParam("day") String day) {
+        ActionResult<WrapBoolean> result = new ActionResult<>();
+        EffectivePerson effectivePerson = this.effectivePerson(request);
+        try {
+            result = new ActionStatisticUnitDayData().execute(year, month, day);
+        }catch (Exception e) {
+            logger.error(e, effectivePerson, request, null);
+            result.error(e);
+        }
+        asyncResponse.resume(ResponseFactory.getEntityTagActionResultResponse(request, result));
+    }
 
 }

+ 13 - 0
o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/jaxrs/dingding/MoreThanSevenDayException.java

@@ -0,0 +1,13 @@
+package com.x.attendance.assemble.control.jaxrs.dingding;
+
+import com.x.base.core.project.exception.PromptException;
+
+public class MoreThanSevenDayException extends PromptException {
+
+
+    private static final long serialVersionUID = 7077761236199564642L;
+
+    public MoreThanSevenDayException() {
+        super("同步时间间隔大于7天!");
+    }
+}

+ 1 - 1
o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/jaxrs/dingding/SyncWayException.java

@@ -6,6 +6,6 @@ public class SyncWayException extends PromptException {
 
     private static final long serialVersionUID = -6072567404702590349L;
     public SyncWayException() {
-        super("同步方式不正确!");
+        super("传入的同步时间不正确!");
     }
 }

+ 14 - 0
o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/jaxrs/dingding/exception/SearchArgEmptyException.java

@@ -0,0 +1,14 @@
+package com.x.attendance.assemble.control.jaxrs.dingding.exception;
+
+import com.x.base.core.project.exception.PromptException;
+
+/**
+ * Created by fancyLou on 2020-04-01.
+ * Copyright © 2020 O2. All rights reserved.
+ */
+public class SearchArgEmptyException extends PromptException {
+
+    public SearchArgEmptyException() {
+        super("传入参数不正确!");
+    }
+}

+ 14 - 0
o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/jaxrs/dingding/exception/TimeEmptyException.java

@@ -0,0 +1,14 @@
+package com.x.attendance.assemble.control.jaxrs.dingding.exception;
+
+import com.x.base.core.project.exception.PromptException;
+
+/**
+ * Created by fancyLou on 2020-04-01.
+ * Copyright © 2020 O2. All rights reserved.
+ */
+public class TimeEmptyException extends PromptException {
+
+    public TimeEmptyException() {
+        super("传入的时间参数不正确!");
+    }
+}

+ 42 - 0
o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/jaxrs/dingdingstatistic/ActionPersonStatistic.java

@@ -0,0 +1,42 @@
+package com.x.attendance.assemble.control.jaxrs.dingdingstatistic;
+
+import com.x.attendance.assemble.control.Business;
+import com.x.attendance.entity.StatisticDingdingPersonForMonth;
+import com.x.base.core.container.EntityManagerContainer;
+import com.x.base.core.container.factory.EntityManagerContainerFactory;
+import com.x.base.core.entity.JpaObject;
+import com.x.base.core.project.bean.WrapCopier;
+import com.x.base.core.project.bean.WrapCopierFactory;
+import com.x.base.core.project.http.ActionResult;
+import org.apache.commons.lang3.StringUtils;
+
+import java.util.List;
+
+/**
+ * Created by fancyLou on 2020-04-07.
+ * Copyright © 2020 O2. All rights reserved.
+ */
+public class ActionPersonStatistic extends BaseAction {
+
+
+    ActionResult<List<Wo>> execute(String person, String year, String month) throws Exception {
+        ActionResult<List<Wo>> result = new ActionResult<>();
+        if (StringUtils.isEmpty(person) || StringUtils.isEmpty(year) || StringUtils.isEmpty(month)) {
+            throw new EmptyArgsException();
+        }
+        try ( EntityManagerContainer emc = EntityManagerContainerFactory.instance().create()) {
+            Business business = new Business(emc);
+            List<StatisticDingdingPersonForMonth> list = business.dingdingAttendanceFactory().findPersonStatistic(person, year, month);
+            result.setData(Wo.copier.copy(list));
+        }
+        return result;
+    }
+
+
+
+    public static class Wo extends StatisticDingdingPersonForMonth {
+        static final WrapCopier<StatisticDingdingPersonForMonth, Wo> copier = WrapCopierFactory.wo(StatisticDingdingPersonForMonth.class, Wo.class,
+                null, JpaObject.FieldsInvisible);
+
+    }
+}

+ 42 - 0
o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/jaxrs/dingdingstatistic/ActionPersonStatisticWithUnit.java

@@ -0,0 +1,42 @@
+package com.x.attendance.assemble.control.jaxrs.dingdingstatistic;
+
+import com.x.attendance.assemble.control.Business;
+import com.x.attendance.entity.StatisticDingdingPersonForMonth;
+import com.x.base.core.container.EntityManagerContainer;
+import com.x.base.core.container.factory.EntityManagerContainerFactory;
+import com.x.base.core.entity.JpaObject;
+import com.x.base.core.project.bean.WrapCopier;
+import com.x.base.core.project.bean.WrapCopierFactory;
+import com.x.base.core.project.http.ActionResult;
+import org.apache.commons.lang3.StringUtils;
+
+import java.util.List;
+
+/**
+ * Created by fancyLou on 2020-04-07.
+ * Copyright © 2020 O2. All rights reserved.
+ */
+public class ActionPersonStatisticWithUnit extends BaseAction {
+
+
+    ActionResult<List<Wo>> execute(String unit, String year, String month) throws Exception {
+        ActionResult<List<Wo>> result = new ActionResult<>();
+        if (StringUtils.isEmpty(unit) || StringUtils.isEmpty(year) || StringUtils.isEmpty(month)) {
+            throw new EmptyArgsException();
+        }
+        try ( EntityManagerContainer emc = EntityManagerContainerFactory.instance().create()) {
+            Business business = new Business(emc);
+            List<StatisticDingdingPersonForMonth> list = business.dingdingAttendanceFactory().findPersonStatisticWithUnit(unit, year, month);
+            result.setData(Wo.copier.copy(list));
+        }
+        return result;
+    }
+
+
+
+    public static class Wo extends StatisticDingdingPersonForMonth {
+        static final WrapCopier<StatisticDingdingPersonForMonth, Wo> copier = WrapCopierFactory.wo(StatisticDingdingPersonForMonth.class, Wo.class,
+                null, JpaObject.FieldsInvisible);
+
+    }
+}

+ 15 - 0
o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/jaxrs/dingdingstatistic/ActionTest.java

@@ -0,0 +1,15 @@
+package com.x.attendance.assemble.control.jaxrs.dingdingstatistic;
+
+import com.x.base.core.project.http.ActionResult;
+import com.x.base.core.project.jaxrs.WrapBoolean;
+
+/**
+ * Created by fancyLou on 2020-04-05.
+ * Copyright © 2020 O2. All rights reserved.
+ */
+public class ActionTest extends BaseAction {
+
+    ActionResult<WrapBoolean> execute() throws Exception {
+        return new ActionResult<>();
+    }
+}

+ 46 - 0
o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/jaxrs/dingdingstatistic/ActionUnitStatistic.java

@@ -0,0 +1,46 @@
+package com.x.attendance.assemble.control.jaxrs.dingdingstatistic;
+
+import com.x.attendance.assemble.control.Business;
+import com.x.attendance.entity.StatisticDingdingUnitForMonth;
+import com.x.base.core.container.EntityManagerContainer;
+import com.x.base.core.container.factory.EntityManagerContainerFactory;
+import com.x.base.core.entity.JpaObject;
+import com.x.base.core.project.bean.WrapCopier;
+import com.x.base.core.project.bean.WrapCopierFactory;
+import com.x.base.core.project.http.ActionResult;
+import org.apache.commons.lang3.StringUtils;
+
+import java.util.List;
+
+/**
+ * Created by fancyLou on 2020-04-07.
+ * Copyright © 2020 O2. All rights reserved.
+ */
+public class ActionUnitStatistic extends BaseAction {
+
+
+    ActionResult<Wo> execute(String unit, String year, String month) throws Exception {
+        ActionResult<Wo> result = new ActionResult<>();
+        if (StringUtils.isEmpty(unit) || StringUtils.isEmpty(year) || StringUtils.isEmpty(month)) {
+            throw new EmptyArgsException();
+        }
+        try ( EntityManagerContainer emc = EntityManagerContainerFactory.instance().create()) {
+            Business business = new Business(emc);
+            List<StatisticDingdingUnitForMonth> list = business.dingdingAttendanceFactory().findUnitStatistic(unit, year, month);
+            if (list!=null && !list.isEmpty()) {
+                result.setData(Wo.copier.copy(list.get(0)));
+            }else {
+                result.setData(new Wo());
+            }
+        }
+        return result;
+    }
+
+
+
+    public static class Wo extends StatisticDingdingUnitForMonth {
+        static final WrapCopier<StatisticDingdingUnitForMonth, Wo> copier = WrapCopierFactory.wo(StatisticDingdingUnitForMonth.class, Wo.class,
+                null, JpaObject.FieldsInvisible);
+
+    }
+}

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

@@ -0,0 +1,6 @@
+package com.x.attendance.assemble.control.jaxrs.dingdingstatistic;
+
+import com.x.base.core.project.jaxrs.StandardJaxrsAction;
+
+public class BaseAction extends StandardJaxrsAction {
+}

+ 110 - 0
o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/jaxrs/dingdingstatistic/DingdingAttendanceStatisticAction.java

@@ -0,0 +1,110 @@
+package com.x.attendance.assemble.control.jaxrs.dingdingstatistic;
+
+import com.x.base.core.project.annotation.JaxrsDescribe;
+import com.x.base.core.project.annotation.JaxrsMethodDescribe;
+import com.x.base.core.project.annotation.JaxrsParameterDescribe;
+import com.x.base.core.project.http.ActionResult;
+import com.x.base.core.project.http.EffectivePerson;
+import com.x.base.core.project.http.HttpMediaType;
+import com.x.base.core.project.jaxrs.ResponseFactory;
+import com.x.base.core.project.jaxrs.StandardJaxrsAction;
+import com.x.base.core.project.jaxrs.WrapBoolean;
+import com.x.base.core.project.logger.Logger;
+import com.x.base.core.project.logger.LoggerFactory;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.ws.rs.*;
+import javax.ws.rs.container.AsyncResponse;
+import javax.ws.rs.container.Suspended;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import java.util.List;
+
+/**
+ * Created by fancyLou on 2020-04-05.
+ * Copyright © 2020 O2. All rights reserved.
+ */
+@Path("dingdingstatistic")
+@JaxrsDescribe("钉钉打卡数据统计管理")
+public class DingdingAttendanceStatisticAction extends StandardJaxrsAction {
+    private static final Logger logger = LoggerFactory.getLogger(DingdingAttendanceStatisticAction.class);
+
+
+    @JaxrsMethodDescribe(value = "测试", action = ActionTest.class)
+    @GET
+    @Path("demo")
+    @Produces(HttpMediaType.APPLICATION_JSON_UTF_8)
+    @Consumes(MediaType.APPLICATION_JSON)
+    public void syncData(@Suspended final AsyncResponse asyncResponse, @Context HttpServletRequest request) {
+        ActionResult<WrapBoolean> result = new ActionResult<>();
+        EffectivePerson effectivePerson = this.effectivePerson(request);
+        try {
+            result = new ActionTest().execute();
+        }catch (Exception e) {
+            logger.error(e, effectivePerson, request, null);
+            result.error(e);
+        }
+        asyncResponse.resume(ResponseFactory.getEntityTagActionResultResponse(request, result));
+    }
+
+    @JaxrsMethodDescribe(value = "人员月份统计查询", action = ActionPersonStatistic.class)
+    @GET
+    @Path("person/{person}/{year}/{month}")
+    @Produces(HttpMediaType.APPLICATION_JSON_UTF_8)
+    @Consumes(MediaType.APPLICATION_JSON)
+    public void personMonth(@Suspended final AsyncResponse asyncResponse, @Context HttpServletRequest request,
+                            @JaxrsParameterDescribe("人员") @PathParam("person") String person,
+                            @JaxrsParameterDescribe("年份: yyyy") @PathParam("year") String year,
+                            @JaxrsParameterDescribe("月份: MM") @PathParam("month") String month) {
+        ActionResult<List<ActionPersonStatistic.Wo>> result = new ActionResult<>();
+        EffectivePerson effectivePerson = this.effectivePerson(request);
+        try {
+            result = new ActionPersonStatistic().execute(person, year, month);
+        }catch (Exception e) {
+            logger.error(e, effectivePerson, request, null);
+            result.error(e);
+        }
+        asyncResponse.resume(ResponseFactory.getEntityTagActionResultResponse(request, result));
+    }
+
+    @JaxrsMethodDescribe(value = "根据部门查询人员月份统计", action = ActionPersonStatisticWithUnit.class)
+    @GET
+    @Path("person/unit/{unit}/{year}/{month}")
+    @Produces(HttpMediaType.APPLICATION_JSON_UTF_8)
+    @Consumes(MediaType.APPLICATION_JSON)
+    public void personMonthWithUnit(@Suspended final AsyncResponse asyncResponse, @Context HttpServletRequest request,
+                            @JaxrsParameterDescribe("部门") @PathParam("unit") String unit,
+                            @JaxrsParameterDescribe("年份: yyyy") @PathParam("year") String year,
+                            @JaxrsParameterDescribe("月份: MM") @PathParam("month") String month) {
+        ActionResult<List<ActionPersonStatisticWithUnit.Wo>> result = new ActionResult<>();
+        EffectivePerson effectivePerson = this.effectivePerson(request);
+        try {
+            result = new ActionPersonStatisticWithUnit().execute(unit, year, month);
+        }catch (Exception e) {
+            logger.error(e, effectivePerson, request, null);
+            result.error(e);
+        }
+        asyncResponse.resume(ResponseFactory.getEntityTagActionResultResponse(request, result));
+    }
+
+    @JaxrsMethodDescribe(value = "部门月份统计查询", action = ActionUnitStatistic.class)
+    @GET
+    @Path("unit/{unit}/{year}/{month}")
+    @Produces(HttpMediaType.APPLICATION_JSON_UTF_8)
+    @Consumes(MediaType.APPLICATION_JSON)
+    public void unitMonth(@Suspended final AsyncResponse asyncResponse, @Context HttpServletRequest request,
+                            @JaxrsParameterDescribe("部门") @PathParam("unit") String unit,
+                            @JaxrsParameterDescribe("年份: yyyy") @PathParam("year") String year,
+                            @JaxrsParameterDescribe("月份: MM") @PathParam("month") String month) {
+        ActionResult<ActionUnitStatistic.Wo> result = new ActionResult<>();
+        EffectivePerson effectivePerson = this.effectivePerson(request);
+        try {
+            result = new ActionUnitStatistic().execute(unit, year, month);
+        }catch (Exception e) {
+            logger.error(e, effectivePerson, request, null);
+            result.error(e);
+        }
+        asyncResponse.resume(ResponseFactory.getEntityTagActionResultResponse(request, result));
+    }
+
+}

+ 13 - 0
o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/jaxrs/dingdingstatistic/EmptyArgsException.java

@@ -0,0 +1,13 @@
+package com.x.attendance.assemble.control.jaxrs.dingdingstatistic;
+
+import com.x.base.core.project.exception.PromptException;
+
+/**
+ * Created by fancyLou on 2020-04-07.
+ * Copyright © 2020 O2. All rights reserved.
+ */
+public class EmptyArgsException extends PromptException {
+    public EmptyArgsException() {
+        super("传入参数不能为空!");
+    }
+}

+ 45 - 0
o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/jaxrs/qywx/ActionDeleteAllQywxAttendanceData.java

@@ -0,0 +1,45 @@
+package com.x.attendance.assemble.control.jaxrs.qywx;
+
+
+
+import com.x.attendance.entity.AttendanceQywxDetail;
+import com.x.base.core.container.EntityManagerContainer;
+import com.x.base.core.container.factory.EntityManagerContainerFactory;
+import com.x.base.core.project.http.ActionResult;
+import com.x.base.core.project.http.EffectivePerson;
+import com.x.base.core.project.jaxrs.WrapBoolean;
+import com.x.base.core.project.logger.Logger;
+import com.x.base.core.project.logger.LoggerFactory;
+
+import java.util.List;
+
+public class ActionDeleteAllQywxAttendanceData extends BaseAction {
+
+    private static final Logger logger = LoggerFactory.getLogger(ActionDeleteAllQywxAttendanceData.class);
+
+
+    public ActionResult<WrapBoolean> execute(EffectivePerson effectivePerson) {
+        ActionResult<WrapBoolean> result = new ActionResult<>();
+
+        try ( EntityManagerContainer emc = EntityManagerContainerFactory.instance().create()) {
+            List<AttendanceQywxDetail> details  = emc.listAll(AttendanceQywxDetail.class);
+            if ( null != details && !details.isEmpty() ) {
+                //进行数据库持久化操作
+                emc.beginTransaction( AttendanceQywxDetail.class );
+                for (AttendanceQywxDetail d : details) {
+                    emc.remove(d);
+                }
+                emc.commit();
+                logger.info( "成功删除所有的企业微信打卡数据信息"  );
+            }
+            result.setData(new WrapBoolean(true));
+        } catch ( Exception e ) {
+            result.error(e);
+            logger.error(e);
+        }
+
+        return result;
+    }
+
+
+}

+ 117 - 0
o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/jaxrs/qywx/ActionListQywxAttendanceDetail.java

@@ -0,0 +1,117 @@
+package com.x.attendance.assemble.control.jaxrs.qywx;
+
+import com.google.gson.JsonElement;
+import com.x.attendance.assemble.control.Business;
+import com.x.attendance.assemble.control.jaxrs.dingding.BaseAction;
+import com.x.attendance.entity.AttendanceQywxDetail;
+import com.x.base.core.container.EntityManagerContainer;
+import com.x.base.core.container.factory.EntityManagerContainerFactory;
+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.gson.GsonPropertyObject;
+import com.x.base.core.project.http.ActionResult;
+import com.x.base.core.project.logger.Logger;
+import com.x.base.core.project.logger.LoggerFactory;
+import com.x.base.core.project.organization.Person;
+import com.x.base.core.project.tools.DateTools;
+
+import java.util.Date;
+import java.util.List;
+import java.util.stream.Collectors;
+
+public class ActionListQywxAttendanceDetail extends BaseAction {
+
+    private static final Logger logger = LoggerFactory.getLogger(ActionListQywxAttendanceDetail.class);
+
+    public ActionResult<List<Wo>> execute(JsonElement jsonElement) throws Exception {
+        ActionResult<List<Wo>> result = new ActionResult<>();
+        try ( EntityManagerContainer emc = EntityManagerContainerFactory.instance().create()) {
+            Business business = new Business(emc);
+            Wi wi = this.convertToWrapIn(jsonElement , Wi.class);
+            Date start = DateTools.parseDateTime(wi.getStartTime());
+            Date end = DateTools.parseDateTime(wi.getEndTime());
+            String qywxUser = null;
+            //转化成企业微信的id
+            if (wi.getPerson() != null && !wi.getPerson().isEmpty()) {
+                Person person = business.organization().person().getObject(wi.getPerson());
+                qywxUser = person.getQiyeweixinId();
+            }
+            List<AttendanceQywxDetail> list = business.dingdingAttendanceFactory().findQywxAttendanceDetail(start, end, qywxUser);
+            if (list != null && !list.isEmpty()) {
+                List<Wo> wos = list.stream().map(detail -> {
+                    Wo wo = new Wo();
+                    try {
+                        wo = Wo.copier.copy(detail, wo);
+                        wo.formatDateTime();
+                    }catch (Exception e) {
+                        logger.error(e);
+                    }
+                    return wo;
+                }).collect(Collectors.toList());
+                result.setData(wos);
+            }
+        }
+        return result;
+    }
+
+    public static class Wi extends GsonPropertyObject {
+        @FieldDescribe("开始时间:yyyy-MM-dd HH:mm:ss")
+        private String startTime;
+        @FieldDescribe("结束时间:yyyy-MM-dd HH:mm:ss")
+        private String endTime;
+        @FieldDescribe("人员")
+        private String person;
+
+
+        public String getStartTime() {
+            return startTime;
+        }
+
+        public void setStartTime(String startTime) {
+            this.startTime = startTime;
+        }
+
+        public String getEndTime() {
+            return endTime;
+        }
+
+        public void setEndTime(String endTime) {
+            this.endTime = endTime;
+        }
+
+        public String getPerson() {
+            return person;
+        }
+
+        public void setPerson(String person) {
+            this.person = person;
+        }
+    }
+
+    public static class Wo extends AttendanceQywxDetail {
+        static final WrapCopier<AttendanceQywxDetail, Wo> copier =
+                WrapCopierFactory.wo(AttendanceQywxDetail.class, Wo.class, null, JpaObject.FieldsInvisible);
+
+        @FieldDescribe("实际打卡时间")
+        private Date checkTimeFormat;
+
+
+        public void formatDateTime() {
+            if (checkTimeFormat == null) {
+                Date date = new Date();
+                date.setTime(getCheckin_time());
+                setCheckTimeFormat(date);
+            }
+        }
+
+        public Date getCheckTimeFormat() {
+            return checkTimeFormat;
+        }
+
+        public void setCheckTimeFormat(Date checkTimeFormat) {
+            this.checkTimeFormat = checkTimeFormat;
+        }
+    }
+}

+ 87 - 0
o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/jaxrs/qywx/ActionListQywxSyncRecord.java

@@ -0,0 +1,87 @@
+package com.x.attendance.assemble.control.jaxrs.qywx;
+
+import com.x.attendance.assemble.control.Business;
+import com.x.attendance.assemble.control.jaxrs.dingding.BaseAction;
+import com.x.attendance.entity.DingdingQywxSyncRecord;
+import com.x.base.core.container.EntityManagerContainer;
+import com.x.base.core.container.factory.EntityManagerContainerFactory;
+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.http.ActionResult;
+import com.x.base.core.project.logger.Logger;
+import com.x.base.core.project.logger.LoggerFactory;
+
+import java.util.Date;
+import java.util.List;
+import java.util.stream.Collectors;
+
+public class ActionListQywxSyncRecord extends BaseAction {
+
+    private static final Logger logger = LoggerFactory.getLogger(ActionListQywxSyncRecord.class);
+
+    public ActionResult<List<Wo>> execute() throws Exception {
+        ActionResult<List<Wo>> result = new ActionResult<>();
+        try ( EntityManagerContainer emc = EntityManagerContainerFactory.instance().create()) {
+            Business business = new Business(emc);
+            List<DingdingQywxSyncRecord> list = business.dingdingAttendanceFactory().findAllSyncRecordWithType(DingdingQywxSyncRecord.syncType_qywx);
+            if (list != null && !list.isEmpty()) {
+                List<Wo> wos = list.stream().map(record -> {
+                    Wo wo = new Wo();
+                    try {
+                        wo = Wo.copier.copy(record, wo);
+                        wo.formatDate();
+                    }catch (Exception e) {
+                        logger.error(e);
+                    }
+                    return wo;
+                }).collect(Collectors.toList());
+                result.setData(wos);
+            }
+        }
+        return result;
+    }
+
+
+    public static class Wo extends DingdingQywxSyncRecord {
+        static final WrapCopier<DingdingQywxSyncRecord, Wo> copier =
+                WrapCopierFactory.wo(DingdingQywxSyncRecord.class, Wo.class, null, JpaObject.FieldsInvisible);
+
+
+        @FieldDescribe("同步打卡记录的开始时间")
+        private Date dateFromFormat;
+        @FieldDescribe("同步打卡记录的结束时间")
+        private Date dateToFormat;
+
+        public void formatDate() {
+            if (dateFromFormat == null) {
+                Date date = new Date();
+                date.setTime(getDateFrom());
+                setDateFromFormat(date);
+            }
+            if (dateToFormat == null) {
+                Date dateto = new Date();
+                dateto.setTime(getDateTo());
+                setDateToFormat(dateto);
+            }
+        }
+
+
+        public Date getDateFromFormat() {
+            return dateFromFormat;
+        }
+
+        public void setDateFromFormat(Date dateFromFormat) {
+            this.dateFromFormat = dateFromFormat;
+        }
+
+        public Date getDateToFormat() {
+            return dateToFormat;
+        }
+
+        public void setDateToFormat(Date dateToFormat) {
+            this.dateToFormat = dateToFormat;
+        }
+    }
+}

+ 54 - 0
o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/jaxrs/qywx/ActionSyncQywxData.java

@@ -0,0 +1,54 @@
+package com.x.attendance.assemble.control.jaxrs.qywx;
+
+import com.x.attendance.assemble.control.ThisApplication;
+import com.x.attendance.assemble.control.jaxrs.dingding.MoreThanSevenDayException;
+import com.x.attendance.entity.DingdingQywxSyncRecord;
+import com.x.base.core.container.EntityManagerContainer;
+import com.x.base.core.container.factory.EntityManagerContainerFactory;
+import com.x.base.core.project.http.ActionResult;
+import com.x.base.core.project.http.EffectivePerson;
+import com.x.base.core.project.jaxrs.WrapBoolean;
+import com.x.base.core.project.logger.Logger;
+import com.x.base.core.project.logger.LoggerFactory;
+import com.x.base.core.project.tools.DateTools;
+
+import java.util.Date;
+
+
+public class ActionSyncQywxData extends BaseAction {
+
+    private static final Logger logger = LoggerFactory.getLogger(ActionSyncQywxData.class);
+
+    public ActionResult<WrapBoolean> execute(EffectivePerson effectivePerson, String dateFrom, String dateTo) throws Exception {
+        ActionResult<WrapBoolean> result = new ActionResult<>();
+        try ( EntityManagerContainer emc = EntityManagerContainerFactory.instance().create()) {
+            if (null == dateFrom || null == dateTo) {
+                throw new SyncDateException();
+            }
+            Date from = DateTools.parse(dateFrom);
+            Date to = DateTools.parse(dateTo);
+            long gap = to.getTime() - from.getTime();
+            if (gap < 0) {
+                throw new SyncDateException();
+            }
+            if ((gap / (1000 * 60 * 60 * 24)) > 6 ) {
+                throw new MoreThanSevenDayException();
+            }
+            Date wxTo = DateTools.parse(dateTo+" 23:59:59");//企业微信和钉钉查询时间不一样
+            DingdingQywxSyncRecord record = new DingdingQywxSyncRecord();
+            record.setDateFrom(from.getTime());
+            record.setDateTo(wxTo.getTime());
+            record.setStartTime(new Date());
+            record.setType(DingdingQywxSyncRecord.syncType_qywx);
+            record.setStatus(DingdingQywxSyncRecord.status_loading);
+            emc.beginTransaction(DingdingQywxSyncRecord.class);
+            emc.persist(record);
+            emc.commit();
+            //企业微信的处理队列
+            ThisApplication.qywxQueue.send(record);
+            result.setData(new WrapBoolean(true));
+        }
+        return result;
+    }
+
+}

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

@@ -0,0 +1,6 @@
+package com.x.attendance.assemble.control.jaxrs.qywx;
+
+import com.x.base.core.project.jaxrs.StandardJaxrsAction;
+
+public class BaseAction extends StandardJaxrsAction {
+}

+ 103 - 0
o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/jaxrs/qywx/QywxAttendanceAction.java

@@ -0,0 +1,103 @@
+package com.x.attendance.assemble.control.jaxrs.qywx;
+
+import com.google.gson.JsonElement;
+import com.x.base.core.project.annotation.JaxrsDescribe;
+import com.x.base.core.project.annotation.JaxrsMethodDescribe;
+import com.x.base.core.project.annotation.JaxrsParameterDescribe;
+import com.x.base.core.project.http.ActionResult;
+import com.x.base.core.project.http.EffectivePerson;
+import com.x.base.core.project.http.HttpMediaType;
+import com.x.base.core.project.jaxrs.ResponseFactory;
+import com.x.base.core.project.jaxrs.StandardJaxrsAction;
+import com.x.base.core.project.jaxrs.WrapBoolean;
+import com.x.base.core.project.logger.Logger;
+import com.x.base.core.project.logger.LoggerFactory;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.ws.rs.*;
+import javax.ws.rs.container.AsyncResponse;
+import javax.ws.rs.container.Suspended;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import java.util.List;
+
+@Path("qywx")
+@JaxrsDescribe("企业微信打卡数据管理")
+public class QywxAttendanceAction  extends StandardJaxrsAction {
+
+    private static final Logger logger = LoggerFactory.getLogger(QywxAttendanceAction.class);
+
+
+    //删除所有打卡数据
+    @JaxrsMethodDescribe(value = "删除所有打卡数据", action = ActionDeleteAllQywxAttendanceData.class)
+    @DELETE
+    @Path("all")
+    @Produces(HttpMediaType.APPLICATION_JSON_UTF_8)
+    @Consumes(MediaType.APPLICATION_JSON)
+    public void deleteAllData(@Suspended final AsyncResponse asyncResponse, @Context HttpServletRequest request) {
+        ActionResult<WrapBoolean> result = new ActionResult<>();
+        EffectivePerson effectivePerson = this.effectivePerson(request);
+        try {
+            result = new ActionDeleteAllQywxAttendanceData().execute(effectivePerson);
+        }catch (Exception e) {
+            logger.error(e, effectivePerson, request, null);
+            result.error(e);
+        }
+        asyncResponse.resume(ResponseFactory.getEntityTagActionResultResponse(request, result));
+    }
+
+
+    //
+    @JaxrsMethodDescribe(value = "同步企业微信考勤结果", action = ActionSyncQywxData.class)
+    @GET
+    @Path("sync/from/{dateFrom}/to/{dateTo}/start")
+    @Produces(HttpMediaType.APPLICATION_JSON_UTF_8)
+    @Consumes(MediaType.APPLICATION_JSON)
+    public void syncData(@Suspended final AsyncResponse asyncResponse, @Context HttpServletRequest request,
+                         @JaxrsParameterDescribe("开始时间: yyyy-MM-dd") @PathParam("dateFrom") String dateFrom,
+                         @JaxrsParameterDescribe("结束时间: yyyy-MM-dd") @PathParam("dateTo") String dateTo) {
+        ActionResult<WrapBoolean> result = new ActionResult<>();
+        EffectivePerson effectivePerson = this.effectivePerson(request);
+        try {
+            result = new ActionSyncQywxData().execute(effectivePerson, dateFrom, dateTo);
+        }catch (Exception e) {
+            logger.error(e, effectivePerson, request, null);
+            result.error(e);
+        }
+        asyncResponse.resume(ResponseFactory.getEntityTagActionResultResponse(request, result));
+    }
+
+    @JaxrsMethodDescribe(value = "查询企业微信同步记录信息", action = ActionListQywxSyncRecord.class)
+    @GET
+    @Path("sync/list")
+    @Produces(HttpMediaType.APPLICATION_JSON_UTF_8)
+    @Consumes(MediaType.APPLICATION_JSON)
+    public void listDingdingSyncRecord(@Suspended final AsyncResponse asyncResponse, @Context HttpServletRequest request) {
+        ActionResult<List<ActionListQywxSyncRecord.Wo>> result = new ActionResult<>();
+        EffectivePerson effectivePerson = this.effectivePerson(request);
+        try {
+            result = new ActionListQywxSyncRecord().execute();
+        }catch (Exception e) {
+            logger.error(e, effectivePerson, request, null);
+            result.error(e);
+        }
+        asyncResponse.resume(ResponseFactory.getEntityTagActionResultResponse(request, result));
+    }
+
+    @JaxrsMethodDescribe(value = "查询企业微信打卡结果", action = ActionListQywxAttendanceDetail.class)
+    @PUT
+    @Path("attendance/list")
+    @Produces(HttpMediaType.APPLICATION_JSON_UTF_8)
+    @Consumes(MediaType.APPLICATION_JSON)
+    public void listDingdingAttendance(@Suspended final AsyncResponse asyncResponse, @Context HttpServletRequest request, JsonElement jsonElement) {
+        ActionResult<List<ActionListQywxAttendanceDetail.Wo>> result = new ActionResult<>();
+        EffectivePerson effectivePerson = this.effectivePerson(request);
+        try {
+            result = new ActionListQywxAttendanceDetail().execute(jsonElement);
+        }catch (Exception e) {
+            logger.error(e, effectivePerson, request, null);
+            result.error(e);
+        }
+        asyncResponse.resume(ResponseFactory.getEntityTagActionResultResponse(request, result));
+    }
+}

+ 13 - 0
o2server/x_attendance_assemble_control/src/main/java/com/x/attendance/assemble/control/jaxrs/qywx/SyncDateException.java

@@ -0,0 +1,13 @@
+package com.x.attendance.assemble.control.jaxrs.qywx;
+
+import com.x.base.core.project.exception.PromptException;
+
+public class SyncDateException extends PromptException {
+
+
+    private static final long serialVersionUID = -6409463169780597687L;
+
+    public SyncDateException() {
+        super("传入的同步时间不正确!");
+    }
+}

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

@@ -0,0 +1,37 @@
+package com.x.attendance.assemble.control.schedule;
+
+import com.x.attendance.assemble.control.ThisApplication;
+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.DateTools;
+import org.quartz.JobExecutionContext;
+
+import java.util.Date;
+
+/**
+ * 钉钉考勤结果统计定时器
+ * 每天晚上3点定时处理钉钉考勤的个人统计数据
+ * Created by fancyLou on 2020-04-05.
+ * Copyright © 2020 O2. All rights reserved.
+ */
+public class DingdingAttendanceStatisticPersonScheduleTask extends AbstractJob {
+
+    private static final Logger logger = LoggerFactory.getLogger(DingdingAttendanceStatisticPersonScheduleTask.class);
+
+    @Override
+    public void schedule(JobExecutionContext jobExecutionContext) throws Exception {
+        try ( EntityManagerContainer emc = EntityManagerContainerFactory.instance().create()) {
+            logger.info("开始执行钉钉考勤统计的程序。。。。。。。。。。。。。。。。。。。。。");
+            //前一天 0点到23点
+            Date from = new Date();
+            from = DateTools.addDay(from, -1);//前面一天
+            ThisApplication.personStatisticQueue.send(from);
+        }
+    }
+
+
+
+}

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

@@ -0,0 +1,37 @@
+package com.x.attendance.assemble.control.schedule;
+
+import com.x.attendance.assemble.control.ThisApplication;
+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.DateTools;
+import org.quartz.JobExecutionContext;
+
+import java.util.Date;
+
+/**
+ * 钉钉考勤结果统计定时器
+ * 每天晚上3点定时处理钉钉考勤的统计数据
+ * Created by fancyLou on 2020-04-03.
+ * Copyright © 2020 O2. All rights reserved.
+ */
+public class DingdingAttendanceStatisticScheduleTask extends AbstractJob {
+
+    private static final Logger logger = LoggerFactory.getLogger(DingdingAttendanceStatisticScheduleTask.class);
+
+    @Override
+    public void schedule(JobExecutionContext jobExecutionContext) throws Exception {
+        try ( EntityManagerContainer emc = EntityManagerContainerFactory.instance().create()) {
+            logger.info("开始执行钉钉考勤统计的程序。。。。。。。。。。。。。。。。。。。。。");
+            //前一天 0点到23点
+            Date from = new Date();
+            from = DateTools.addDay(from, -1);//前面一天
+            ThisApplication.unitStatisticQueue.send(from);
+        }
+    }
+
+
+
+}

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

@@ -0,0 +1,48 @@
+package com.x.attendance.assemble.control.schedule;
+
+import com.x.attendance.assemble.control.ThisApplication;
+import com.x.attendance.entity.DingdingQywxSyncRecord;
+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.DateTools;
+import org.quartz.JobExecutionContext;
+
+import java.util.Date;
+
+/**
+ * 钉钉考勤结果同步的定时器
+ * 每天晚上1点同步前一天的结果
+ * Created by fancyLou on 2020-03-26.
+ * Copyright © 2020 O2. All rights reserved.
+ */
+public class DingdingAttendanceSyncScheduleTask extends AbstractJob {
+
+    private static final Logger logger = LoggerFactory.getLogger(DingdingAttendanceSyncScheduleTask.class);
+
+    @Override
+    public void schedule(JobExecutionContext jobExecutionContext) throws Exception {
+        try ( EntityManagerContainer emc = EntityManagerContainerFactory.instance().create()) {
+            logger.info("开始同步钉钉考勤打卡结果。。。。。。。。。。。。。。。。。。。。。");
+            //前一天 0点到23点
+            Date from = new Date();
+            from = DateTools.addDay(from, -1);
+            from = DateTools.floorDate(from, null);
+            String toDay = DateTools.format(from, DateTools.format_yyyyMMdd);
+            Date to = DateTools.parse(toDay+" 23:59:59");
+
+            DingdingQywxSyncRecord record = new DingdingQywxSyncRecord();
+            record.setDateFrom(from.getTime());
+            record.setDateTo(to.getTime());
+            record.setStartTime(new Date());
+            record.setType(DingdingQywxSyncRecord.syncType_dingding);
+            record.setStatus(DingdingQywxSyncRecord.status_loading);
+            emc.beginTransaction(DingdingQywxSyncRecord.class);
+            emc.persist(record);
+            emc.commit();
+            ThisApplication.dingdingQueue.send(record);
+        }
+    }
+}

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

@@ -0,0 +1,47 @@
+package com.x.attendance.assemble.control.schedule;
+
+import com.x.attendance.assemble.control.ThisApplication;
+import com.x.attendance.entity.DingdingQywxSyncRecord;
+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.DateTools;
+import org.quartz.JobExecutionContext;
+
+import java.util.Date;
+
+/**
+ * 企业微信考勤同步定时器
+ * 每天晚上1点同步前一天的考勤数据
+ * Created by fancyLou on 2020-03-26.
+ * Copyright © 2020 O2. All rights reserved.
+ */
+public class QywxAttendanceSyncScheduleTask extends AbstractJob {
+
+    private static final Logger logger = LoggerFactory.getLogger(QywxAttendanceSyncScheduleTask.class);
+
+    @Override
+    public void schedule(JobExecutionContext jobExecutionContext) throws Exception {
+        try ( EntityManagerContainer emc = EntityManagerContainerFactory.instance().create()) {
+            logger.info("开始同步企业微信考勤打卡结果。。。。。。。。。。。。。。。。。。。。。");
+            //前一天 0点到23点
+            Date from = new Date();
+            from = DateTools.addDay(from, -1);
+            from = DateTools.floorDate(from, null);
+            String toDay = DateTools.format(from, DateTools.format_yyyyMMdd);
+            Date to = DateTools.parse(toDay+" 23:59:59");
+            DingdingQywxSyncRecord record = new DingdingQywxSyncRecord();
+            record.setDateFrom(from.getTime());
+            record.setDateTo(to.getTime());
+            record.setStartTime(new Date());
+            record.setType(DingdingQywxSyncRecord.syncType_qywx);
+            record.setStatus(DingdingQywxSyncRecord.status_loading);
+            emc.beginTransaction(DingdingQywxSyncRecord.class);
+            emc.persist(record);
+            emc.commit();
+            ThisApplication.qywxQueue.send(record);
+        }
+    }
+}

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

@@ -27,10 +27,10 @@ public class UserManagerService {
 		}
 	}
 	/**
-	 * 根据员工姓名获取组织名称
+	 * 根据员工获取所在组织
 	 * 如果用户有多个身份,则取组织级别最大的组织名称
-	 * @param employeeName
-	 * @return
+	 * @param personName dn
+	 * @return unit 也是 dn
 	 * @throws Exception 
 	 */
 	public String getUnitNameWithPersonName( String personName ) throws Exception{

+ 122 - 18
o2server/x_attendance_assemble_control/src/main/webapp/describe/sources/com/x/attendance/assemble/control/DingdingAttendanceQueue.java

@@ -13,6 +13,13 @@ import com.x.base.core.project.organization.Person;
 import com.x.base.core.project.queue.AbstractQueue;
 import com.x.base.core.project.x_organization_assemble_control;
 
+import javax.persistence.EntityManager;
+import javax.persistence.criteria.CriteriaBuilder;
+import javax.persistence.criteria.CriteriaQuery;
+import javax.persistence.criteria.Predicate;
+import javax.persistence.criteria.Root;
+import java.util.Calendar;
+import java.util.Date;
 import java.util.List;
 
 public class DingdingAttendanceQueue extends AbstractQueue<DingdingQywxSyncRecord> {
@@ -22,36 +29,133 @@ public class DingdingAttendanceQueue extends AbstractQueue<DingdingQywxSyncRecor
     @Override
     protected void execute(DingdingQywxSyncRecord record) throws Exception {
         logger.info("开始执行钉钉打卡数据同步," + record.getWay());
-
+        if (DingdingQywxSyncRecord.syncType_dingding.equals(record.getType())) {
+            dingdingSync(record);
+        } else {
+            logger.info("其它类型:");
+        }
     }
 
-    private void dingdingSync() throws Exception {
-        try (EntityManagerContainer emc = EntityManagerContainerFactory.instance().create()) {
-            Business business = new Business(emc);
-            Application app = ThisApplication.context().applications().randomWithWeight(x_organization_assemble_control.class.getName());
-            //开始分页查询人员
-            boolean hasNextPerson = true;
-            String uri = "person/list/(0)/next/50";
-            String dingdingUrl = "https://oapi.dingtalk.com/attendance/list?access_token="+ Config.dingding().corpAccessToken();
-
-            while (hasNextPerson) {
-                List<Person> list = ThisApplication.context().applications().getQuery(false, app, uri).getDataAsList(Person.class);
-                if (list != null && list.size() > 0) {
+    private boolean isSameDay(Date startDate, Date endDate) {
+        Calendar start = Calendar.getInstance();
+        start.setTime( startDate );
+        Calendar end = Calendar.getInstance();
+        end.setTime(endDate);
+        return (start.get(Calendar.YEAR) == end.get(Calendar.YEAR) && start.get(Calendar.DAY_OF_YEAR) == end.get(Calendar.DAY_OF_YEAR));
+    }
+    private void dingdingSync(DingdingQywxSyncRecord record) throws Exception {
+        logger.debug(record.toString());
+        Application app = ThisApplication.context().applications().randomWithWeight(x_organization_assemble_control.class.getName());
+        //开始分页查询人员
+        boolean hasNextPerson = true;
+        int personPageSize = 50;
+        String uri = "person/list/(0)/next/50";
+        String dingdingUrl = "https://oapi.dingtalk.com/attendance/list?access_token=" + Config.dingding().corpAccessToken();
+        logger.debug("dingding url :"+dingdingUrl);
+        while (hasNextPerson) {
+            logger.info("查询人员 uri:" + uri);
+            List<Person> list = ThisApplication.context().applications().getQuery(false, app, uri).getDataAsList(Person.class);
+            if (list != null && list.size() > 0) {
+                //钉钉用户id
+                List<String> ddUsers = list.stream().map(Person::getDingdingId).collect(Collectors.toList());
+                //week
+                Date today = new Date();
+                today = DateTools.floorDate(today, null);
+                Date sevenDayBefore = DateTools.addDay(today, -7);
+                //分页查询
+                int page = 0;
+                boolean hasMoreResult = true;
+                while (hasMoreResult) {
                     DingdingAttendancePost post = new DingdingAttendancePost();
-
-                }else {
-                    //没有用户查询到结束
-                    logger.info("查询不到用户了,结束。。。。。。。。。。。。。。。");
+                    //post传入 时间(时间间隔不能超过7天) 人员(人员数量不能超过50个)
+                    post.setLimit(50L);
+                    post.setOffset(page * 50L);//从0开始翻页
+                    post.setWorkDateFrom(DateTools.format(sevenDayBefore, DateTools.format_yyyyMMddHHmmss));
+                    post.setWorkDateTo(DateTools.format(today, DateTools.format_yyyyMMddHHmmss));
+                    post.setUserIdList(ddUsers);
+                    String body = post.toString();
+                    logger.info("查询钉钉打卡结果:" + body);
+                    DingdingAttendanceResult result = HttpConnection.postAsObject(dingdingUrl, null, post.toString(), DingdingAttendanceResult.class);
+                    if (result.errcode != null && result.errcode == 0) {
+                        saveDingdingRecord(result.getRecordresult());
+                        if (result.hasMore) {
+                            page++;
+                        } else {
+                            logger.info("同步钉钉考勤结束。。。。。。。。。。。。。。。。");
+                            hasMoreResult = false;
+                        }
+                    } else {
+                        //请求结果异常 结束
+                        logger.error(new DingDingRequestException(result.errmsg));
+                        hasMoreResult = false;
+                    }
+                }
+                //是否还有更多用户
+                if (list.size() < personPageSize) {
+                    logger.info("同步钉钉考勤 没有更多用户了,结束。。。。。。。。。。。。。。。");
                     hasNextPerson = false;
+                    updateSyncRecord(record, null);
+                } else {
+                    //还有更多用户继续查询
+                    uri = "person/list/" + list.get(list.size() - 1).getDistinguishedName() + "/next/50";
                 }
+            } else {
+                //没有用户查询到结束
+                logger.info("同步钉钉考勤 查询不到用户了,结束。。。。。。。。。。。。。。。");
+                hasNextPerson = false;
+                updateSyncRecord(record, null);
+            }
+        }
+        logger.info("结束 插入:"+saveNumber+" 条");
+        //插入数据成功 开始统计程序
+
+        boolean hasNextDate = true;
+        Date statisticDate = fromDate;
+        while (hasNextDate) {
+            logger.info("发起钉钉考勤数据统计, date:"+ DateTools.format(statisticDate));
+            ThisApplication.personStatisticQueue.send(statisticDate);
+            ThisApplication.unitStatisticQueue.send(statisticDate);
+            if (!isSameDay(statisticDate, toDate)) {
+                statisticDate = DateTools.addDay(statisticDate, 1);
+            }else {
+                hasNextDate = false;
             }
+        }
+        logger.info("发起数据统计程序 完成。。。。。。。。。。");
+    }
+
+    private void saveDingdingRecord(List<DingdingAttendanceResultItem> list) throws Exception {
+        if (list != null && !list.isEmpty()) {
+            try (EntityManagerContainer emc = EntityManagerContainerFactory.instance().create()) {
+                emc.beginTransaction(AttendanceDingtalkDetail.class);
+                for (int i = 0; i < list.size(); i++) {
+                    DingdingAttendanceResultItem item = list.get(i);
+                    AttendanceDingtalkDetail detail = DingdingAttendanceResultItem.copier.copy(item);
+                    detail.setDdId(item.getId());
+                    emc.persist(detail);
+                }
+                emc.commit();
+            }
+        }
+    }
 
+    private void updateSyncRecord(DingdingQywxSyncRecord record, String errMsg) throws Exception {
+        try (EntityManagerContainer emc = EntityManagerContainerFactory.instance().create()) {
+            emc.beginTransaction(DingdingQywxSyncRecord.class);
+            record.setEndTime(new Date());
+            if (errMsg == null || errMsg.isEmpty()) {
+                record.setExceptionMessage(errMsg);
+                record.setStatus(DingdingQywxSyncRecord.status_error);
+            } else {
+                record.setStatus(DingdingQywxSyncRecord.status_end);
+            }
+            emc.commit();
         }
     }
 
 
     public static class DingdingAttendancePost extends GsonPropertyObject {
-//        {
+        //        {
 //            "workDateFrom": "yyyy-MM-dd HH:mm:ss",
 //                "workDateTo": "yyyy-MM-dd HH:mm:ss",
 //                "userIdList":["员工UserId列表"],    // 必填,与offset和limit配合使用

+ 2 - 0
o2server/x_attendance_assemble_control/src/main/webapp/describe/sources/com/x/attendance/assemble/control/jaxrs/ActionApplication.java

@@ -18,6 +18,7 @@ import com.x.attendance.assemble.control.jaxrs.attendancestatistic.AttendanceSta
 import com.x.attendance.assemble.control.jaxrs.attendancestatisticalcycle.AttendanceStatisticalCycleAction;
 import com.x.attendance.assemble.control.jaxrs.attendancestatisticrequirelog.AttendanceStatisticRequireLogAction;
 import com.x.attendance.assemble.control.jaxrs.attendanceworkdayconfig.AttendanceWorkDayConfigAction;
+import com.x.attendance.assemble.control.jaxrs.dingding.DingdingAttendanceAction;
 import com.x.attendance.assemble.control.jaxrs.fileimport.AttendanceDetailFileImportAction;
 import com.x.attendance.assemble.control.jaxrs.selfholiday.AttendanceSelfHolidayAction;
 import com.x.attendance.assemble.control.jaxrs.selfholiday.AttendanceSelfHolidaySimpleAction;
@@ -48,6 +49,7 @@ public class ActionApplication extends AbstractActionApplication {
 		this.classes.add(AttendanceStatisticalCycleAction.class);
 		this.classes.add(AttendanceEmployeeConfigAction.class);
 		this.classes.add(AttendanceStatisticRequireLogAction.class);
+		this.classes.add(DingdingAttendanceAction.class);
 		return this.classes;
 	}
 

+ 2 - 1
o2server/x_attendance_assemble_control/src/main/webapp/describe/sources/com/x/attendance/assemble/control/jaxrs/DingdingJaxrsFilter.java

@@ -1,10 +1,11 @@
 package com.x.attendance.assemble.control.jaxrs;
 
 import com.x.base.core.project.jaxrs.CipherManagerJaxrsFilter;
+import com.x.base.core.project.jaxrs.ManagerUserJaxrsFilter;
 
 import javax.servlet.annotation.WebFilter;
 
 
 @WebFilter(urlPatterns = "/jaxrs/dingding/*", asyncSupported = true)
-public class DingdingJaxrsFilter extends CipherManagerJaxrsFilter {
+public class DingdingJaxrsFilter extends ManagerUserJaxrsFilter {
 }

+ 18 - 0
o2server/x_attendance_assemble_control/src/main/webapp/describe/sources/com/x/attendance/assemble/control/jaxrs/attendancesetting/AttendanceSettingAction.java

@@ -152,4 +152,22 @@ public class AttendanceSettingAction extends StandardJaxrsAction {
 		}
 		asyncResponse.resume(ResponseFactory.getEntityTagActionResultResponse(request, result));
 	}
+
+	@JaxrsMethodDescribe(value = "是否启用钉钉考勤或企业微信考勤", action = ActionEnableType.class)
+	@GET
+	@Path("enable/type")
+	@Produces(HttpMediaType.APPLICATION_JSON_UTF_8)
+	@Consumes(MediaType.APPLICATION_JSON)
+	public void enableType(@Suspended final AsyncResponse asyncResponse, @Context HttpServletRequest request) {
+		ActionResult<ActionEnableType.Wo> result = new ActionResult<>();
+		EffectivePerson effectivePerson = this.effectivePerson(request);
+		try {
+			result = new ActionEnableType().execute(effectivePerson);
+		} catch (Exception e) {
+			result = new ActionResult<>();
+			result.error(e);
+			logger.error(e, effectivePerson, request, null);
+		}
+		asyncResponse.resume(ResponseFactory.getEntityTagActionResultResponse(request, result));
+	}
 }

+ 5 - 14
o2server/x_attendance_assemble_control/src/main/webapp/describe/sources/com/x/attendance/assemble/control/jaxrs/dingding/ActionSyncData.java

@@ -1,13 +1,9 @@
 package com.x.attendance.assemble.control.jaxrs.dingding;
 
-import com.google.gson.JsonElement;
 import com.x.attendance.assemble.control.ThisApplication;
 import com.x.attendance.entity.DingdingQywxSyncRecord;
 import com.x.base.core.container.EntityManagerContainer;
 import com.x.base.core.container.factory.EntityManagerContainerFactory;
-import com.x.base.core.entity.JpaObject;
-import com.x.base.core.project.bean.WrapCopier;
-import com.x.base.core.project.bean.WrapCopierFactory;
 import com.x.base.core.project.http.ActionResult;
 import com.x.base.core.project.http.EffectivePerson;
 import com.x.base.core.project.jaxrs.WrapBoolean;
@@ -21,30 +17,25 @@ public class ActionSyncData extends BaseAction {
 
     private static final Logger logger = LoggerFactory.getLogger(ActionSyncData.class);
 
-    public ActionResult<WrapBoolean> execute(EffectivePerson effectivePerson, JsonElement jsonElement) throws Exception {
+    public ActionResult<WrapBoolean> execute(EffectivePerson effectivePerson, String way) throws Exception {
         ActionResult<WrapBoolean> result = new ActionResult<>();
         try ( EntityManagerContainer emc = EntityManagerContainerFactory.instance().create()) {
-            Wi wi = this.convertToWrapIn(jsonElement, Wi.class);
-            if (null == wi.getWay() ||
-                    (!DingdingQywxSyncRecord.syncWay_week.equals(wi.getWay()) && !DingdingQywxSyncRecord.syncWay_year.equals(wi.getWay()))) {
+            if (null == way ||
+                    (!DingdingQywxSyncRecord.syncWay_week.equals(way) && !DingdingQywxSyncRecord.syncWay_year.equals(way))) {
                 throw new SyncWayException();
             }
             DingdingQywxSyncRecord record = new DingdingQywxSyncRecord();
-            record.setWay(wi.getWay());
+            record.setWay(way);
             record.setStartTime(new Date());
             record.setType(DingdingQywxSyncRecord.syncType_dingding);
             record.setStatus(DingdingQywxSyncRecord.status_loading);
             emc.beginTransaction(DingdingQywxSyncRecord.class);
             emc.persist(record);
             emc.commit();
-            ThisApplication.dingdingQueue.executing(record);
+            ThisApplication.dingdingQueue.send(record);
             result.setData(new WrapBoolean(true));
         }
         return result;
     }
 
-    public static class Wi extends DingdingQywxSyncRecord {
-        public static WrapCopier<Wi, DingdingQywxSyncRecord> copier = WrapCopierFactory.wi(Wi.class,
-                DingdingQywxSyncRecord.class, null, JpaObject.FieldsUnmodify);
-    }
 }

+ 5 - 4
o2server/x_attendance_assemble_control/src/main/webapp/describe/sources/com/x/attendance/assemble/control/jaxrs/dingding/DingdingAttendanceAction.java

@@ -3,6 +3,7 @@ package com.x.attendance.assemble.control.jaxrs.dingding;
 import com.google.gson.JsonElement;
 import com.x.base.core.project.annotation.JaxrsDescribe;
 import com.x.base.core.project.annotation.JaxrsMethodDescribe;
+import com.x.base.core.project.annotation.JaxrsParameterDescribe;
 import com.x.base.core.project.http.ActionResult;
 import com.x.base.core.project.http.EffectivePerson;
 import com.x.base.core.project.http.HttpMediaType;
@@ -51,16 +52,16 @@ public class DingdingAttendanceAction extends StandardJaxrsAction {
 
     //获取7天数据   可以做定时每天晚上更新
     @JaxrsMethodDescribe(value = "同步钉钉考勤结果", action = ActionSyncData.class)
-    @POST
-    @Path("sync")
+    @GET
+    @Path("sync/{way}/start")
     @Produces(HttpMediaType.APPLICATION_JSON_UTF_8)
     @Consumes(MediaType.APPLICATION_JSON)
     public void syncData(@Suspended final AsyncResponse asyncResponse, @Context HttpServletRequest request,
-                         JsonElement jsonElement) {
+                         @JaxrsParameterDescribe("同步方式") @PathParam("way") String way) {
         ActionResult<WrapBoolean> result = new ActionResult<>();
         EffectivePerson effectivePerson = this.effectivePerson(request);
         try {
-            result = new ActionSyncData().execute(effectivePerson, jsonElement);
+            result = new ActionSyncData().execute(effectivePerson, way);
         }catch (Exception e) {
             logger.error(e, effectivePerson, request, null);
             result.error(e);

+ 38 - 1
o2server/x_attendance_core_entity/src/main/java/com/x/attendance/entity/AttendanceDingtalkDetail.java

@@ -7,6 +7,7 @@ import com.x.base.core.entity.annotation.ContainerEntity;
 import com.x.base.core.project.annotation.FieldDescribe;
 
 import javax.persistence.*;
+import java.util.Date;
 
 @ContainerEntity
 @Entity
@@ -49,10 +50,18 @@ public class AttendanceDingtalkDetail extends SliceJpaObject  {
     @Column(name = ColumnNamePrefix + "ddID")
     private long ddId;
 
-    @FieldDescribe("用户id")
+    @FieldDescribe("钉钉的用户id")
     @Column(name = ColumnNamePrefix + "userId", length = length_96B)
     private String userId;
 
+    @FieldDescribe("O2用户")
+    @Column(name = ColumnNamePrefix + "o2User", length = length_128B)
+    private String o2User;
+
+    @FieldDescribe("O2用户所在的组织")
+    @Column(name = ColumnNamePrefix + "o2Unit", length = length_128B)
+    private String o2Unit;
+
     @FieldDescribe("基准时间,用于计算迟到和早退")
     @Column(name = ColumnNamePrefix + "baseCheckTime")
     private long baseCheckTime;
@@ -61,6 +70,10 @@ public class AttendanceDingtalkDetail extends SliceJpaObject  {
     @Column(name = ColumnNamePrefix + "userCheckTime")
     private long userCheckTime;
 
+    @FieldDescribe("实际打卡时间,  用Date格式存储")
+    @Column(name = ColumnNamePrefix + "userCheckTimeDate")
+    private Date userCheckTimeDate;
+
     @FieldDescribe("工作日")
     @Column(name = ColumnNamePrefix + "workDate")
     private long workDate;
@@ -142,6 +155,22 @@ public class AttendanceDingtalkDetail extends SliceJpaObject  {
 //    private String procInstId;
 
 
+    public String getO2User() {
+        return o2User;
+    }
+
+    public void setO2User(String o2User) {
+        this.o2User = o2User;
+    }
+
+    public String getO2Unit() {
+        return o2Unit;
+    }
+
+    public void setO2Unit(String o2Unit) {
+        this.o2Unit = o2Unit;
+    }
+
     public long getDdId() {
         return ddId;
     }
@@ -237,4 +266,12 @@ public class AttendanceDingtalkDetail extends SliceJpaObject  {
     public void setRecordId(long recordId) {
         this.recordId = recordId;
     }
+
+    public Date getUserCheckTimeDate() {
+        return userCheckTimeDate;
+    }
+
+    public void setUserCheckTimeDate(Date userCheckTimeDate) {
+        this.userCheckTimeDate = userCheckTimeDate;
+    }
 }

+ 19 - 9
o2server/x_attendance_core_entity/src/main/java/com/x/attendance/entity/DingdingQywxSyncRecord.java

@@ -61,11 +61,13 @@ public class DingdingQywxSyncRecord extends SliceJpaObject  {
     @Column(name = ColumnNamePrefix + "type", length = length_32B)
     private String type;
 
-    public static final String syncWay_year = "year";
-    public static final String syncWay_week = "week";
-    @FieldDescribe("同步方式,year(同步一年数据) , week(同步一周数据)")
-    @Column(name = ColumnNamePrefix + "way", length = length_32B)
-    private String way;
+    @FieldDescribe("同步打卡记录的开始时间")
+    @Column(name = ColumnNamePrefix + "dateFrom")
+    private long dateFrom;
+
+    @FieldDescribe("同步打卡记录的结束时间, 起始与结束工作日最多相隔7天")
+    @Column(name = ColumnNamePrefix + "dateTo")
+    private long dateTo;
 
     public static final String status_loading = "loading";
     public static final String status_end = "end";
@@ -104,12 +106,20 @@ public class DingdingQywxSyncRecord extends SliceJpaObject  {
         this.type = type;
     }
 
-    public String getWay() {
-        return way;
+    public long getDateFrom() {
+        return dateFrom;
+    }
+
+    public void setDateFrom(long dateFrom) {
+        this.dateFrom = dateFrom;
+    }
+
+    public long getDateTo() {
+        return dateTo;
     }
 
-    public void setWay(String way) {
-        this.way = way;
+    public void setDateTo(long dateTo) {
+        this.dateTo = dateTo;
     }
 
     public String getStatus() {

+ 9 - 0
o2server/x_attendance_core_entity/src/main/java/com/x/attendance/entity/PersistenceProperties.java

@@ -78,6 +78,15 @@ public final class PersistenceProperties extends AbstractPersistenceProperties {
 	public static class StatisticPersonForMonth {
 		public static final String table = "ATDC_STATISTIC_PERSON_FORMONTH";
 	}
+	public static class StatisticDingdingPersonForMonth {
+		public static final String table = "ATDC_STATISTIC_DD_PERSON_FORMONTH";
+	}
+	public static class StatisticDingdingUnitForMonth {
+		public static final String table = "ATDC_STATISTIC_DD_UNIT_FORMONTH";
+	}
+	public static class StatisticDingdingUnitForDay {
+		public static final String table = "ATDC_STATISTIC_DD_UNIT_FORDAY";
+	}
 	
 	public static class AttendanceWorkPlace {
 		public static final String table = "ATDC_WORK_PLACE";

+ 214 - 0
o2server/x_attendance_core_entity/src/main/java/com/x/attendance/entity/StatisticDingdingPersonForMonth.java

@@ -0,0 +1,214 @@
+package com.x.attendance.entity;
+
+import com.x.base.core.entity.JpaObject;
+import com.x.base.core.entity.SliceJpaObject;
+import com.x.base.core.entity.annotation.CheckPersist;
+import com.x.base.core.entity.annotation.ContainerEntity;
+import com.x.base.core.project.annotation.FieldDescribe;
+
+import javax.persistence.*;
+
+@ContainerEntity
+@Entity
+@Table(name = PersistenceProperties.StatisticDingdingPersonForMonth.table, uniqueConstraints = {
+		@UniqueConstraint(name = PersistenceProperties.StatisticDingdingPersonForMonth.table + JpaObject.IndexNameMiddle
+				+ JpaObject.DefaultUniqueConstraintSuffix, columnNames = { JpaObject.IDCOLUMN,
+						JpaObject.CREATETIMECOLUMN, JpaObject.UPDATETIMECOLUMN, JpaObject.SEQUENCECOLUMN }) })
+@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
+public class StatisticDingdingPersonForMonth extends SliceJpaObject {
+
+
+
+	private static final String TABLE = PersistenceProperties.StatisticDingdingPersonForMonth.table;
+	private static final long serialVersionUID = 2006440116216111693L;
+
+	public String getId() {
+		return id;
+	}
+
+	public void setId(String id) {
+		this.id = id;
+	}
+
+	@FieldDescribe("数据库主键,自动生成.")
+	@Id
+	@Column(length = length_id, name = ColumnNamePrefix + id_FIELDNAME)
+	private String id = createId();
+
+	public void onPersist() throws Exception {
+	}
+	/*
+	 * =============================================================================
+	 * ===== 以上为 JpaObject 默认字段
+	 * =============================================================================
+	 * =====
+	 */
+
+	/*
+	 * =============================================================================
+	 * ===== 以下为具体不同的业务及数据表字段要求
+	 * =============================================================================
+	 * =====
+	 */
+	@FieldDescribe("O2用户")
+	@Column(name = ColumnNamePrefix + "o2User", length = length_128B)
+	@CheckPersist(allowEmpty = false)
+	private String o2User;
+
+	@FieldDescribe("O2用户所在的组织")
+	@Column(name = ColumnNamePrefix + "o2Unit", length = length_128B)
+	@CheckPersist(allowEmpty = false)
+	private String o2Unit;
+
+
+	@FieldDescribe("统计年份")
+	@Column(name = "xstatisticYear", length = JpaObject.length_16B)
+	@CheckPersist(allowEmpty = false)
+	private String statisticYear;
+
+	@FieldDescribe("统计月份")
+	@Column(name = "xstatisticMonth", length = JpaObject.length_16B)
+	@CheckPersist(allowEmpty = false)
+	private String statisticMonth;
+
+	@FieldDescribe("出勤天数")
+	@Column(name = "xworkDayCount")
+	private Long workDayCount;
+
+	@FieldDescribe("上班签到次数")
+	@Column(name = "xonDutyTimes")
+	private Long onDutyTimes;
+
+	@FieldDescribe("下班签到次数")
+	@Column(name = "xoffDutyTimes")
+	private Long offDutyTimes;
+
+	@FieldDescribe("正常签到次数")
+	@Column(name = "xresultNormal")
+	private Long resultNormal;
+
+	@FieldDescribe("迟到次数")
+	@Column(name = "xlateTimes")
+	private Long lateTimes;
+
+	@FieldDescribe("严重迟到次数")
+	@Column(name = "xSeriousLateTimes")
+	private Long seriousLateTimes;
+
+	@FieldDescribe("早退次数")
+	@Column(name = "xleaveEarlyTimes")
+	private Long leaveEarlyTimes;
+
+	@FieldDescribe("旷工次数")
+	@Column(name = "xAbsenteeismTimes")
+	private Long absenteeismTimes;
+
+	@FieldDescribe("未打卡次数")
+	@Column(name = "xNotSignedCount")
+	private Long notSignedCount;
+
+
+	public Long getResultNormal() {
+		return resultNormal;
+	}
+
+	public void setResultNormal(Long resultNormal) {
+		this.resultNormal = resultNormal;
+	}
+
+	public String getO2User() {
+		return o2User;
+	}
+
+	public void setO2User(String o2User) {
+		this.o2User = o2User;
+	}
+
+	public String getO2Unit() {
+		return o2Unit;
+	}
+
+	public void setO2Unit(String o2Unit) {
+		this.o2Unit = o2Unit;
+	}
+
+	public String getStatisticYear() {
+		return statisticYear;
+	}
+
+	public void setStatisticYear(String statisticYear) {
+		this.statisticYear = statisticYear;
+	}
+
+	public String getStatisticMonth() {
+		return statisticMonth;
+	}
+
+	public void setStatisticMonth(String statisticMonth) {
+		this.statisticMonth = statisticMonth;
+	}
+
+	public Long getWorkDayCount() {
+		return workDayCount;
+	}
+
+	public void setWorkDayCount(Long workDayCount) {
+		this.workDayCount = workDayCount;
+	}
+
+	public Long getOnDutyTimes() {
+		return onDutyTimes;
+	}
+
+	public void setOnDutyTimes(Long onDutyTimes) {
+		this.onDutyTimes = onDutyTimes;
+	}
+
+	public Long getOffDutyTimes() {
+		return offDutyTimes;
+	}
+
+	public void setOffDutyTimes(Long offDutyTimes) {
+		this.offDutyTimes = offDutyTimes;
+	}
+
+	public Long getLateTimes() {
+		return lateTimes;
+	}
+
+	public void setLateTimes(Long lateTimes) {
+		this.lateTimes = lateTimes;
+	}
+
+	public Long getSeriousLateTimes() {
+		return seriousLateTimes;
+	}
+
+	public void setSeriousLateTimes(Long seriousLateTimes) {
+		this.seriousLateTimes = seriousLateTimes;
+	}
+
+	public Long getLeaveEarlyTimes() {
+		return leaveEarlyTimes;
+	}
+
+	public void setLeaveEarlyTimes(Long leaveEarlyTimes) {
+		this.leaveEarlyTimes = leaveEarlyTimes;
+	}
+
+	public Long getAbsenteeismTimes() {
+		return absenteeismTimes;
+	}
+
+	public void setAbsenteeismTimes(Long absenteeismTimes) {
+		this.absenteeismTimes = absenteeismTimes;
+	}
+
+	public Long getNotSignedCount() {
+		return notSignedCount;
+	}
+
+	public void setNotSignedCount(Long notSignedCount) {
+		this.notSignedCount = notSignedCount;
+	}
+}

+ 215 - 0
o2server/x_attendance_core_entity/src/main/java/com/x/attendance/entity/StatisticDingdingUnitForDay.java

@@ -0,0 +1,215 @@
+package com.x.attendance.entity;
+
+import com.x.base.core.entity.JpaObject;
+import com.x.base.core.entity.SliceJpaObject;
+import com.x.base.core.entity.annotation.CheckPersist;
+import com.x.base.core.entity.annotation.ContainerEntity;
+import com.x.base.core.project.annotation.FieldDescribe;
+
+import javax.persistence.*;
+
+@ContainerEntity
+@Entity
+@Table(name = PersistenceProperties.StatisticDingdingUnitForDay.table, uniqueConstraints = {
+		@UniqueConstraint(name = PersistenceProperties.StatisticDingdingUnitForDay.table + JpaObject.IndexNameMiddle
+				+ JpaObject.DefaultUniqueConstraintSuffix, columnNames = { JpaObject.IDCOLUMN,
+						JpaObject.CREATETIMECOLUMN, JpaObject.UPDATETIMECOLUMN, JpaObject.SEQUENCECOLUMN }) })
+@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
+public class StatisticDingdingUnitForDay extends SliceJpaObject {
+
+
+
+	private static final String TABLE = PersistenceProperties.StatisticDingdingUnitForDay.table;
+	private static final long serialVersionUID = 2090817422412907325L;
+
+	public String getId() {
+		return id;
+	}
+
+	public void setId(String id) {
+		this.id = id;
+	}
+
+	@FieldDescribe("数据库主键,自动生成.")
+	@Id
+	@Column(length = length_id, name = ColumnNamePrefix + id_FIELDNAME)
+	private String id = createId();
+
+	public void onPersist() throws Exception {
+	}
+	/*
+	 * =============================================================================
+	 * ===== 以上为 JpaObject 默认字段
+	 * =============================================================================
+	 * =====
+	 */
+
+	/*
+	 * =============================================================================
+	 * ===== 以下为具体不同的业务及数据表字段要求
+	 * =============================================================================
+	 * =====
+	 */
+
+	@FieldDescribe("O2用户所在的组织")
+	@Column(name = ColumnNamePrefix + "o2Unit", length = length_128B)
+	@CheckPersist(allowEmpty = false)
+	private String o2Unit;
+
+	@FieldDescribe("统计年份")
+	@Column(name = "xstatisticYear", length = JpaObject.length_16B)
+	@CheckPersist(allowEmpty = false)
+	private String statisticYear;
+
+	@FieldDescribe("统计月份")
+	@Column(name = "xstatisticMonth", length = JpaObject.length_16B)
+	@CheckPersist(allowEmpty = false)
+	private String statisticMonth;
+
+	@FieldDescribe("统计日期")
+	@Column(name = "xstatisticDate", length = JpaObject.length_16B)
+	@CheckPersist(allowEmpty = false)
+	private String statisticDate;
+
+	@FieldDescribe("出勤人数")
+	@Column(name = "xworkDayCount")
+	private Long workDayCount;
+
+	@FieldDescribe("上班签到人数")
+	@Column(name = "xonDutyTimes")
+	private Long onDutyTimes;
+
+	@FieldDescribe("下班签到人数")
+	@Column(name = "xoffDutyTimes")
+	private Long offDutyTimes;
+
+
+	@FieldDescribe("正常签到次数")
+	@Column(name = "xresultNormal")
+	private Long resultNormal;
+
+	@FieldDescribe("迟到人数")
+	@Column(name = "xlateTimes")
+	private Long lateTimes;
+
+	@FieldDescribe("严重迟到人数")
+	@Column(name = "xSeriousLateTimes")
+	private Long seriousLateTimes;
+
+	@FieldDescribe("早退人数")
+	@Column(name = "xleaveEarlyTimes")
+	private Long leaveEarlyTimes;
+
+	@FieldDescribe("旷工人数")
+	@Column(name = "xAbsenteeismTimes")
+	private Long absenteeismTimes;
+
+	@FieldDescribe("未打卡人数")
+	@Column(name = "xNotSignedCount")
+	private Long notSignedCount;
+
+
+	public Long getResultNormal() {
+		return resultNormal;
+	}
+
+	public void setResultNormal(Long resultNormal) {
+		this.resultNormal = resultNormal;
+	}
+
+	public String getStatisticDate() {
+		return statisticDate;
+	}
+
+	public void setStatisticDate(String statisticDate) {
+		this.statisticDate = statisticDate;
+	}
+
+	public String getO2Unit() {
+		return o2Unit;
+	}
+
+	public void setO2Unit(String o2Unit) {
+		this.o2Unit = o2Unit;
+	}
+
+	public String getStatisticYear() {
+		return statisticYear;
+	}
+
+	public void setStatisticYear(String statisticYear) {
+		this.statisticYear = statisticYear;
+	}
+
+	public String getStatisticMonth() {
+		return statisticMonth;
+	}
+
+	public void setStatisticMonth(String statisticMonth) {
+		this.statisticMonth = statisticMonth;
+	}
+
+	public Long getWorkDayCount() {
+		return workDayCount;
+	}
+
+	public void setWorkDayCount(Long workDayCount) {
+		this.workDayCount = workDayCount;
+	}
+
+	public Long getOnDutyTimes() {
+		return onDutyTimes;
+	}
+
+	public void setOnDutyTimes(Long onDutyTimes) {
+		this.onDutyTimes = onDutyTimes;
+	}
+
+	public Long getOffDutyTimes() {
+		return offDutyTimes;
+	}
+
+	public void setOffDutyTimes(Long offDutyTimes) {
+		this.offDutyTimes = offDutyTimes;
+	}
+
+	public Long getLateTimes() {
+		return lateTimes;
+	}
+
+	public void setLateTimes(Long lateTimes) {
+		this.lateTimes = lateTimes;
+	}
+
+	public Long getSeriousLateTimes() {
+		return seriousLateTimes;
+	}
+
+	public void setSeriousLateTimes(Long seriousLateTimes) {
+		this.seriousLateTimes = seriousLateTimes;
+	}
+
+	public Long getLeaveEarlyTimes() {
+		return leaveEarlyTimes;
+	}
+
+	public void setLeaveEarlyTimes(Long leaveEarlyTimes) {
+		this.leaveEarlyTimes = leaveEarlyTimes;
+	}
+
+	public Long getAbsenteeismTimes() {
+		return absenteeismTimes;
+	}
+
+	public void setAbsenteeismTimes(Long absenteeismTimes) {
+		this.absenteeismTimes = absenteeismTimes;
+	}
+
+	public Long getNotSignedCount() {
+		return notSignedCount;
+	}
+
+	public void setNotSignedCount(Long notSignedCount) {
+		this.notSignedCount = notSignedCount;
+	}
+}

+ 201 - 0
o2server/x_attendance_core_entity/src/main/java/com/x/attendance/entity/StatisticDingdingUnitForMonth.java

@@ -0,0 +1,201 @@
+package com.x.attendance.entity;
+
+import com.x.base.core.entity.JpaObject;
+import com.x.base.core.entity.SliceJpaObject;
+import com.x.base.core.entity.annotation.CheckPersist;
+import com.x.base.core.entity.annotation.ContainerEntity;
+import com.x.base.core.project.annotation.FieldDescribe;
+
+import javax.persistence.*;
+
+@ContainerEntity
+@Entity
+@Table(name = PersistenceProperties.StatisticDingdingUnitForMonth.table, uniqueConstraints = {
+		@UniqueConstraint(name = PersistenceProperties.StatisticDingdingUnitForMonth.table + JpaObject.IndexNameMiddle
+				+ JpaObject.DefaultUniqueConstraintSuffix, columnNames = { JpaObject.IDCOLUMN,
+						JpaObject.CREATETIMECOLUMN, JpaObject.UPDATETIMECOLUMN, JpaObject.SEQUENCECOLUMN }) })
+@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
+public class StatisticDingdingUnitForMonth extends SliceJpaObject {
+
+
+
+	private static final String TABLE = PersistenceProperties.StatisticDingdingUnitForMonth.table;
+	private static final long serialVersionUID = 2831416127767736230L;
+
+	public String getId() {
+		return id;
+	}
+
+	public void setId(String id) {
+		this.id = id;
+	}
+
+	@FieldDescribe("数据库主键,自动生成.")
+	@Id
+	@Column(length = length_id, name = ColumnNamePrefix + id_FIELDNAME)
+	private String id = createId();
+
+	public void onPersist() throws Exception {
+	}
+	/*
+	 * =============================================================================
+	 * ===== 以上为 JpaObject 默认字段
+	 * =============================================================================
+	 * =====
+	 */
+
+	/*
+	 * =============================================================================
+	 * ===== 以下为具体不同的业务及数据表字段要求
+	 * =============================================================================
+	 * =====
+	 */
+
+	@FieldDescribe("O2用户所在的组织")
+	@Column(name = ColumnNamePrefix + "o2Unit", length = length_128B)
+	@CheckPersist(allowEmpty = false)
+	private String o2Unit;
+
+
+	@FieldDescribe("统计年份")
+	@Column(name = "xstatisticYear", length = JpaObject.length_16B)
+	@CheckPersist(allowEmpty = false)
+	private String statisticYear;
+
+	@FieldDescribe("统计月份")
+	@Column(name = "xstatisticMonth", length = JpaObject.length_16B)
+	@CheckPersist(allowEmpty = false)
+	private String statisticMonth;
+
+	@FieldDescribe("出勤天数")
+	@Column(name = "xworkDayCount")
+	private Long workDayCount;
+
+	@FieldDescribe("上班签到人数")
+	@Column(name = "xonDutyTimes")
+	private Long onDutyTimes;
+
+	@FieldDescribe("下班签到人数")
+	@Column(name = "xoffDutyTimes")
+	private Long offDutyTimes;
+
+	@FieldDescribe("正常签到次数")
+	@Column(name = "xresultNormal")
+	private Long resultNormal;
+
+	@FieldDescribe("迟到人数")
+	@Column(name = "xlateTimes")
+	private Long lateTimes;
+
+	@FieldDescribe("严重迟到人数")
+	@Column(name = "xSeriousLateTimes")
+	private Long seriousLateTimes;
+
+	@FieldDescribe("早退人数")
+	@Column(name = "xleaveEarlyTimes")
+	private Long leaveEarlyTimes;
+
+	@FieldDescribe("旷工人数")
+	@Column(name = "xAbsenteeismTimes")
+	private Long absenteeismTimes;
+
+	@FieldDescribe("未打卡人数")
+	@Column(name = "xNotSignedCount")
+	private Long notSignedCount;
+
+	public Long getResultNormal() {
+		return resultNormal;
+	}
+
+	public void setResultNormal(Long resultNormal) {
+		this.resultNormal = resultNormal;
+	}
+
+	public String getO2Unit() {
+		return o2Unit;
+	}
+
+	public void setO2Unit(String o2Unit) {
+		this.o2Unit = o2Unit;
+	}
+
+	public String getStatisticYear() {
+		return statisticYear;
+	}
+
+	public void setStatisticYear(String statisticYear) {
+		this.statisticYear = statisticYear;
+	}
+
+	public String getStatisticMonth() {
+		return statisticMonth;
+	}
+
+	public void setStatisticMonth(String statisticMonth) {
+		this.statisticMonth = statisticMonth;
+	}
+
+	public Long getWorkDayCount() {
+		return workDayCount;
+	}
+
+	public void setWorkDayCount(Long workDayCount) {
+		this.workDayCount = workDayCount;
+	}
+
+	public Long getOnDutyTimes() {
+		return onDutyTimes;
+	}
+
+	public void setOnDutyTimes(Long onDutyTimes) {
+		this.onDutyTimes = onDutyTimes;
+	}
+
+	public Long getOffDutyTimes() {
+		return offDutyTimes;
+	}
+
+	public void setOffDutyTimes(Long offDutyTimes) {
+		this.offDutyTimes = offDutyTimes;
+	}
+
+	public Long getLateTimes() {
+		return lateTimes;
+	}
+
+	public void setLateTimes(Long lateTimes) {
+		this.lateTimes = lateTimes;
+	}
+
+	public Long getSeriousLateTimes() {
+		return seriousLateTimes;
+	}
+
+	public void setSeriousLateTimes(Long seriousLateTimes) {
+		this.seriousLateTimes = seriousLateTimes;
+	}
+
+	public Long getLeaveEarlyTimes() {
+		return leaveEarlyTimes;
+	}
+
+	public void setLeaveEarlyTimes(Long leaveEarlyTimes) {
+		this.leaveEarlyTimes = leaveEarlyTimes;
+	}
+
+	public Long getAbsenteeismTimes() {
+		return absenteeismTimes;
+	}
+
+	public void setAbsenteeismTimes(Long absenteeismTimes) {
+		this.absenteeismTimes = absenteeismTimes;
+	}
+
+	public Long getNotSignedCount() {
+		return notSignedCount;
+	}
+
+	public void setNotSignedCount(Long notSignedCount) {
+		this.notSignedCount = notSignedCount;
+	}
+}

+ 59 - 1
o2server/x_base_core_project/src/main/java/com/x/base/core/project/config/Qiyeweixin.java

@@ -44,6 +44,13 @@ public class Qiyeweixin extends ConfigObject {
 	private Boolean messageEnable;
 	@FieldDescribe("企业微信扫码登录")
 	private Boolean scanLoginEnable;
+	@FieldDescribe("是否启用考勤信息")
+	private Boolean attendanceSyncEnable;
+
+	@FieldDescribe("企业微信考勤打卡应用id")
+	private String attendanceSyncAgentId;
+	@FieldDescribe("企业微信考勤打卡应用secret")
+	private String attendanceSyncSecret;
 
 	public static Qiyeweixin defaultInstance() {
 		return new Qiyeweixin();
@@ -61,6 +68,7 @@ public class Qiyeweixin extends ConfigObject {
 	public static final String default_messageRedirectPortal = "";
 	public static final Boolean default_messageEanble = false;
 	public static final Boolean default_scanLoginEnable = false;
+	public static final Boolean default_attendanceSyncEnable = false;
 
 	public Qiyeweixin() {
 		this.enable = default_enable;
@@ -75,9 +83,14 @@ public class Qiyeweixin extends ConfigObject {
 		this.workUrl = default_workUrl;
 		this.messageRedirectPortal = default_messageRedirectPortal;
 		this.scanLoginEnable = default_scanLoginEnable;
-
+		this.attendanceSyncEnable = default_attendanceSyncEnable;
+		this.attendanceSyncAgentId = "";
+		this.attendanceSyncSecret = "";
 	}
 
+	private static String cacheAttendanceAccessToken;
+	private static Date cacheAttendanceAccessTokenDate;
+
 	private static String cachedCorpAccessToken;
 	private static Date cachedCorpAccessTokenDate;
 
@@ -142,6 +155,26 @@ public class Qiyeweixin extends ConfigObject {
 		}
 	}
 
+	public String attendanceAccessToken() throws Exception {
+		if ((StringUtils.isNotEmpty(cacheAttendanceAccessToken) && (null != cacheAttendanceAccessTokenDate))
+				&& (cacheAttendanceAccessTokenDate.after(new Date()))) {
+			return cacheAttendanceAccessToken;
+		} else {
+			String address = default_apiAddress + "/cgi-bin/gettoken?corpid=" + this.getCorpId() + "&corpsecret="
+					+ this.getAttendanceSyncSecret();
+			CorpAccessTokenResp resp = HttpConnection.getAsObject(address, null, CorpAccessTokenResp.class);
+			if (resp.getErrcode() != 0) {
+				throw new ExceptionQiyeweixinCorpAccessToken(resp.getErrcode(), resp.getErrmsg());
+			}
+			cacheAttendanceAccessToken = resp.getAccess_token();
+			Calendar cal = Calendar.getInstance();
+			cal.add(Calendar.MINUTE, 90);
+
+			cacheAttendanceAccessTokenDate = cal.getTime();
+			return cacheAttendanceAccessToken;
+		}
+	}
+
 	public String syncAccessToken() throws Exception {
 		if ((StringUtils.isNotEmpty(cachedSyncAccessToken) && (null != cachedSyncAccessTokenDate))
 				&& (cachedSyncAccessTokenDate.after(new Date()))) {
@@ -386,6 +419,31 @@ public class Qiyeweixin extends ConfigObject {
 		this.scanLoginEnable = scanLoginEnable;
 	}
 
+
+	public Boolean getAttendanceSyncEnable() {
+		return attendanceSyncEnable;
+	}
+
+	public void setAttendanceSyncEnable(Boolean attendanceSyncEnable) {
+		this.attendanceSyncEnable = attendanceSyncEnable;
+	}
+
+	public String getAttendanceSyncAgentId() {
+		return attendanceSyncAgentId;
+	}
+
+	public void setAttendanceSyncAgentId(String attendanceSyncAgentId) {
+		this.attendanceSyncAgentId = attendanceSyncAgentId;
+	}
+
+	public String getAttendanceSyncSecret() {
+		return attendanceSyncSecret;
+	}
+
+	public void setAttendanceSyncSecret(String attendanceSyncSecret) {
+		this.attendanceSyncSecret = attendanceSyncSecret;
+	}
+
 	public void save() throws Exception {
 		File file = new File(Config.base(), Config.PATH_CONFIG_QIYEWEIXIN);
 		FileUtils.write(file, XGsonBuilder.toJson(this), DefaultCharset.charset);

+ 28 - 0
o2server/x_base_core_project/src/main/java/com/x/base/core/project/tools/DateTools.java

@@ -609,5 +609,33 @@ public class DateTools {
 		return dayForWeek; 
 	}
 
+
+	/**
+	 * 指定的日期,添加指定的天数
+	 *
+	 * @param date
+	 * @param dayCount
+	 * @return
+	 */
+	public static Date addDay(Date date, int dayCount) {
+		Calendar calendar = Calendar.getInstance();
+		calendar.setTime(date);
+		calendar.add(Calendar.DAY_OF_YEAR, dayCount);// 指定的时间上加上n天
+		date = calendar.getTime();
+		return date;
+	}
+
+	public static void main(String[] args) {
+		try {
+			Date today = new Date();
+			today = DateTools.floorDate(today, null);
+			Date sevenDayBefore = DateTools.addDay(today, -7);
+			System.out.println(DateTools.format(today));
+			System.out.println(DateTools.format(sevenDayBefore));
+		} catch (Exception e) {
+			e.printStackTrace();
+		}
+
+	}
 	
 }

+ 2 - 1
o2server/x_base_core_project/src/main/java/com/x/base/core/project/x_attendance_assemble_control.java

@@ -15,7 +15,8 @@ import com.x.base.core.project.annotation.ModuleType;
 		"com.x.attendance.entity.StatisticTopUnitForDay", "com.x.attendance.entity.StatisticTopUnitForMonth",
 		"com.x.attendance.entity.StatisticUnitForDay", "com.x.attendance.entity.StatisticUnitForMonth",
 		"com.x.attendance.entity.AttendanceDingtalkDetail", "com.x.attendance.entity.AttendanceQywxDetail",
-		"com.x.attendance.entity.DingdingQywxSyncRecord"}, storeJars = {
+		"com.x.attendance.entity.DingdingQywxSyncRecord", "com.x.attendance.entity.StatisticDingdingPersonForMonth",
+		"com.x.attendance.entity.StatisticDingdingUnitForDay", "com.x.attendance.entity.StatisticDingdingUnitForMonth"}, storeJars = {
 				"x_attendance_core_entity", "x_organization_core_express", "x_organization_core_entity" })
 public class x_attendance_assemble_control extends Deployable {
 }

+ 74 - 57
o2web/source/x_component_Attendance/$Main/navi.json

@@ -2,141 +2,158 @@
   {
     "title": "我的考勤月报",
     "id": "10",
-    "action" : "openMyIndex"
+    "action": "openMyIndex"
   },
   {
     "title": "我的考勤统计",
     "id": "11",
-    "action" : "openMyDetail"
+    "action": "openMyDetail"
   },
   {
     "title": "我的申诉申请",
     "id": "12",
-    "action" : "openMyAppealDeal"
+    "action": "openMyAppealDeal"
   },
   {
     "title": "员工考勤申诉审批",
     "id": "13",
-    "action" : "openAppealDeal"
+    "action": "openAppealDeal"
   },
   {
-    "access" :"admin_dept",
-    "type" :"sep"
+    "access": "admin_dept",
+    "type": "sep"
   },
   {
-    "access" :"admin_dept",
+    "access": "admin_dept",
     "title": "部门考勤月报",
     "id": "15",
-    "action" : "openUnitIndex"
+    "action": "openUnitIndex"
   },
   {
-    "access" :"admin_dept",
+    "access": "admin_dept",
     "title": "个人考勤统计",
     "id": "16",
-    "action" : "openPeopleDetail"
+    "action": "openPeopleDetail"
   },
   {
-    "access" :"admin_dept",
+    "access": "admin_dept",
     "title": "部门考勤统计",
     "id": "17",
-
-    "action" : "openUnitDetail"
+    "action": "openUnitDetail"
   },
   {
-    "access" :"admin",
+    "access": "admin",
     "title": "公司考勤统计",
     "id": "18",
-    "action" : "openTopUnitDetail"
+    "action": "openTopUnitDetail"
   },
   {
-    "access" :"admin",
-    "type" :"sep"
+    "access": "dingding",
+    "title": "钉钉部门考勤月报",
+    "id": "151",
+    "action": "openUnitDingdingIndex"
   },
   {
-    "access" :"admin",
+    "access": "dingding",
+    "title": "钉钉个人考勤统计",
+    "id": "161",
+    "action": "openDingdingPeopleDetail"
+  },
+  {
+    "access": "dingding",
+    "title": "钉钉部门考勤统计",
+    "id": "171",
+    "action": "openDingdingUnitDetail"
+  },
+  {
+    "access": "admin",
+    "type": "sep"
+  },
+  {
+    "access": "admin",
     "title": "维护",
     "id": "20",
     "sub": [
       {
-        "access" :"admin",
+        "access": "admin",
         "title": "数据导入",
         "id": "20.3",
-        "action" : "openImporting"
+        "action": "openImporting"
       },
       {
-        "access" :"admin",
+        "access": "admin",
         "title": "导入错误信息",
         "id": "20.4",
-        "action" : "openImportedInvalidInfor"
+        "action": "openImportedInvalidInfor"
       },
       {
-        "access" :"admin",
+        "access": "admin",
         "title": "非正常出勤数据导出",
         "id": "20.42",
-        "action" : "openAbnormalExport"
+        "action": "openAbnormalExport"
       },
       {
-        "access" :"admin",
+        "access": "admin",
         "title": "员工休假记录",
         "id": "20.1",
-        "action" : "openSelfHoliday"
+        "action": "openSelfHoliday"
       }
     ]
   },
   {
-    "access" :"admin",
+    "access": "admin",
     "title": "权限和人员",
     "id": "30",
     "sub": [
       {
-        "id" : "30.10",
-        "access" : "admin",
-        "title" : "管理员设置",
-        "action" : "openPermissionSetting"
+        "id": "30.10",
+        "access": "admin",
+        "title": "管理员设置",
+        "action": "openPermissionSetting"
       },
       {
-        "id" : "30.20",
-        "access" : "admin",
-        "title" : "考勤人员配置",
-        "action" : "openPersonSetting"
+        "id": "30.20",
+        "access": "admin",
+        "title": "考勤人员配置",
+        "action": "openPersonSetting"
       }
     ]
   },
   {
-    "access" :"admin",
+    "access": "admin",
     "title": "配置",
     "id": "40",
     "sub": [
       {
-        "id" : "40.10",
-        "access" : "admin",
-        "title" : "排班设置",
-        "action" : "openScheduleSetting"
+        "id": "40.10",
+        "access": "admin",
+        "title": "排班设置",
+        "action": "openScheduleSetting"
       },
       {
-        "id" : "40.20",
-        "access" : "admin",
-        "title" : "统计周期设置",
-        "action" : "openStaticsCycleExplorer"
+        "id": "40.20",
+        "access": "admin",
+        "title": "统计周期设置",
+        "action": "openStaticsCycleExplorer"
       },
       {
-        "id" : "40.15",
-        "access" : "admin",
-        "title" : "法定假期",
-        "action" : "openHolidaySetting"
+        "id": "40.15",
+        "access": "admin",
+        "title": "法定假期",
+        "action": "openHolidaySetting"
       },
       {
-        "id" : "40.35",
-        "access" : "admin",
-        "title" : "工作场所设置",
-        "action" : "openAddressSetting"
+        "id": "40.35",
+        "access": "admin",
+        "title": "工作场所设置",
+        "action": "openAddressSetting"
       },
       {
-        "id" : "40.25",
-        "access" : "admin",
-        "target" : "_blank",
-        "title" : "申诉设置",
-        "action" : "openAppSetting"
+        "id": "40.25",
+        "access": "admin",
+        "target": "_blank",
+        "title": "申诉设置",
+        "action": "openAppSetting"
       }
     ]
   }

+ 42 - 0
o2web/source/x_component_Attendance/$PeopleDetail/listItem_dingding.json

@@ -0,0 +1,42 @@
+[
+  {
+    "title": "姓名",
+    "headStyles": "normalThNode",
+    "contentStyles": "normalTdNode",
+    "item": "function( d ){ return d.o2User.split('@')[0] }",
+    "name": "o2User",
+    "width": "20%"
+  },
+  {
+    "title": "日期",
+    "headStyles": "normalThNode",
+    "contentStyles": "normalTdNode",
+    "item": "function(d){ var date = new Date(d.userCheckTime);     return date.getFullYear()+'-'+(date.getMonth()+1)+'-'+date.getDate();}",
+    "name": "userCheckTime",
+    "width": "20%"
+  },
+  {
+    "title": "打卡类型",
+    "headStyles": "normalThNode",
+    "contentStyles": "normalTdNode",
+    "item": "function( data ){ if(data.checkType == 'OffDuty'){return '下班打卡';}else{return '上班打卡';}  }",
+    "name": "checkType",
+    "width": "20%"
+  },
+  {
+    "title": "打卡结果",
+    "headStyles": "normalThNode",
+    "contentStyles": "normalTdNode",
+    "item": "function( data ){ if(data.timeResult == 'Normal'){return '正常';}else if(data.timeResult == 'Early'){return '早退';}else if(data.timeResult == 'Late'){return '迟到';}else if(data.timeResult == 'SeriousLate'){return '严重迟到';}else if(data.timeResult == 'Absenteeism'){return '旷工迟到';}else {return '未打卡';}  }",
+    "name": "timeResult",
+    "width": "20%"
+  },
+  {
+    "title": "打卡时间",
+    "headStyles": "normalThNode",
+    "contentStyles": "normalTdNode",
+    "item": "function(d){ var date = new Date(d.userCheckTime);     return date.getHours()+':'+date.getMinutes()+':'+date.getSeconds();}",
+    "name": "userCheckTime",
+    "width": "20%"
+  }
+]

+ 83 - 0
o2web/source/x_component_Attendance/$PeopleDetail/listItem_dingding_detailStatic.json

@@ -0,0 +1,83 @@
+[
+  {
+    "title": "人员",
+    "headStyles": "normalThNode",
+    "contentStyles": "normalTdNode",
+    "item": "function( d ){ return d.o2User.split('@')[0] }",
+    "name": "o2User",
+    "width": "9%"
+  },
+  {
+    "title": "月份",
+    "headStyles": "normalThNode",
+    "contentStyles": "normalTdNode",
+    "item": "function( data ){ return data.statisticYear + '-' + data.statisticMonth  }",
+    "name": "statisticMonth",
+    "width": "9%"
+  },
+  {
+    "title": "上班打卡次数",
+    "headStyles": "normalThNode",
+    "contentStyles": "normalTdNode",
+    "item": "onDutyTimes",
+    "name": "onDutyTimes",
+    "width": "9%"
+  },
+  {
+    "title": "下班打卡次数",
+    "headStyles": "normalThNode",
+    "contentStyles": "normalTdNode",
+    "item": "offDutyTimes",
+    "name": "offDutyTimes",
+    "width": "9%"
+  },
+  {
+    "title": "出勤人天数",
+    "headStyles": "normalThNode",
+    "contentStyles": "normalTdNode",
+    "item": "workDayCount",
+    "name": "workDayCount",
+    "width": "9%"
+  },
+  {
+    "title": "迟到次数",
+    "headStyles": "normalThNode",
+    "contentStyles": "normalTdNode",
+    "item": "lateTimes",
+    "name": "lateTimes",
+    "width": "9%"
+  },
+  {
+    "title": "早退次数",
+    "headStyles": "normalThNode",
+    "contentStyles": "normalTdNode",
+    "item": "leaveEarlyTimes",
+    "name": "leaveEarlyTimes",
+    "width": "9%"
+  },
+  {
+    "title": "未打卡次数",
+    "headStyles": "normalThNode",
+    "contentStyles": "normalTdNode",
+    "item": "notSignedCount",
+    "name": "notSignedCount",
+    "width": "9%"
+  },
+  {
+    "title": "严重迟到次数",
+    "headStyles": "normalThNode",
+    "contentStyles": "normalTdNode",
+    "item": "seriousLateTimes",
+    "name": "seriousLateTimes",
+    "width": "9%"
+  },
+  {
+    "title": "旷工次数",
+    "headStyles": "normalThNode",
+    "contentStyles": "normalTdNode",
+    "item": "absenteeismTimes",
+    "name": "absenteeismTimes",
+    "width": "9%"
+  }
+   
+]

+ 42 - 0
o2web/source/x_component_Attendance/$UnitDetail/listItem_dingding.json

@@ -0,0 +1,42 @@
+[
+  {
+    "title": "姓名",
+    "headStyles": "normalThNode",
+    "contentStyles": "normalTdNode",
+    "item": "function( d ){ return d.o2User.split('@')[0] }",
+    "name": "o2User",
+    "width": "20%"
+  },
+  {
+    "title": "日期",
+    "headStyles": "normalThNode",
+    "contentStyles": "normalTdNode",
+    "item": "function(d){ var date = new Date(d.userCheckTime);     return date.getFullYear()+'-'+(date.getMonth()+1)+'-'+date.getDate();}",
+    "name": "userCheckTime",
+    "width": "20%"
+  },
+  {
+    "title": "打卡类型",
+    "headStyles": "normalThNode",
+    "contentStyles": "normalTdNode",
+    "item": "function( data ){ if(data.checkType == 'OffDuty'){return '下班打卡';}else{return '上班打卡';}  }",
+    "name": "checkType",
+    "width": "20%"
+  },
+  {
+    "title": "打卡结果",
+    "headStyles": "normalThNode",
+    "contentStyles": "normalTdNode",
+    "item": "function( data ){ if(data.timeResult == 'Normal'){return '正常';}else if(data.timeResult == 'Early'){return '早退';}else if(data.timeResult == 'Late'){return '迟到';}else if(data.timeResult == 'SeriousLate'){return '严重迟到';}else if(data.timeResult == 'Absenteeism'){return '旷工迟到';}else {return '未打卡';}  }",
+    "name": "timeResult",
+    "width": "20%"
+  },
+  {
+    "title": "打卡时间",
+    "headStyles": "normalThNode",
+    "contentStyles": "normalTdNode",
+    "item": "function(d){ var date = new Date(d.userCheckTime);     return date.getHours()+':'+date.getMinutes()+':'+date.getSeconds();}",
+    "name": "userCheckTime",
+    "width": "20%"
+  }
+]

+ 83 - 0
o2web/source/x_component_Attendance/$UnitDetail/listItem_dingding_detailStatic.json

@@ -0,0 +1,83 @@
+[
+  {
+    "title": "人员",
+    "headStyles": "normalThNode",
+    "contentStyles": "normalTdNode",
+    "item": "function( d ){ return d.o2User.split('@')[0] }",
+    "name": "o2User",
+    "width": "9%"
+  },
+  {
+    "title": "月份",
+    "headStyles": "normalThNode",
+    "contentStyles": "normalTdNode",
+    "item": "function( data ){ return data.statisticYear + '-' + data.statisticMonth  }",
+    "name": "statisticMonth",
+    "width": "9%"
+  },
+  {
+    "title": "上班打卡次数",
+    "headStyles": "normalThNode",
+    "contentStyles": "normalTdNode",
+    "item": "onDutyTimes",
+    "name": "onDutyTimes",
+    "width": "9%"
+  },
+  {
+    "title": "下班打卡次数",
+    "headStyles": "normalThNode",
+    "contentStyles": "normalTdNode",
+    "item": "offDutyTimes",
+    "name": "offDutyTimes",
+    "width": "9%"
+  },
+  {
+    "title": "出勤人天数",
+    "headStyles": "normalThNode",
+    "contentStyles": "normalTdNode",
+    "item": "workDayCount",
+    "name": "workDayCount",
+    "width": "9%"
+  },
+  {
+    "title": "迟到次数",
+    "headStyles": "normalThNode",
+    "contentStyles": "normalTdNode",
+    "item": "lateTimes",
+    "name": "lateTimes",
+    "width": "9%"
+  },
+  {
+    "title": "早退次数",
+    "headStyles": "normalThNode",
+    "contentStyles": "normalTdNode",
+    "item": "leaveEarlyTimes",
+    "name": "leaveEarlyTimes",
+    "width": "9%"
+  },
+  {
+    "title": "未打卡次数",
+    "headStyles": "normalThNode",
+    "contentStyles": "normalTdNode",
+    "item": "notSignedCount",
+    "name": "notSignedCount",
+    "width": "9%"
+  },
+  {
+    "title": "严重迟到次数",
+    "headStyles": "normalThNode",
+    "contentStyles": "normalTdNode",
+    "item": "seriousLateTimes",
+    "name": "seriousLateTimes",
+    "width": "9%"
+  },
+  {
+    "title": "旷工次数",
+    "headStyles": "normalThNode",
+    "contentStyles": "normalTdNode",
+    "item": "absenteeismTimes",
+    "name": "absenteeismTimes",
+    "width": "9%"
+  }
+   
+]

+ 208 - 161
o2web/source/x_component_Attendance/Main.js

@@ -1,5 +1,5 @@
 MWF.xApplication.Attendance = MWF.xApplication.Attendance || {};
-MWF.require("MWF.widget.O2Identity", null,false);
+MWF.require("MWF.widget.O2Identity", null, false);
 //MWF.xDesktop.requireApp("Attendance", "Actions.RestActions", null, false);
 MWF.xDesktop.requireApp("Attendance", "Common", null, false);
 MWF.xDesktop.requireApp("Template", "MDomItem", null, false);
@@ -12,7 +12,7 @@ MWF.xApplication.Attendance.Main = new Class({
 	Implements: [Options, Events],
 
 	options: {
-		"curNaviId" : null,
+		"curNaviId": null,
 		"style": "default",
 		"name": "Attendance",
 		"icon": "icon.png",
@@ -22,10 +22,10 @@ MWF.xApplication.Attendance.Main = new Class({
 		"isMax": true,
 		"title": MWF.xApplication.Attendance.LP.title
 	},
-	onQueryLoad: function(){
+	onQueryLoad: function () {
 		this.lp = MWF.xApplication.Attendance.LP;
 	},
-	loadApplication: function(callback){
+	loadApplication: function (callback) {
 		if (!this.options.isRefresh) {
 			this.maxSize(function () {
 				this.loadLayout();
@@ -34,9 +34,10 @@ MWF.xApplication.Attendance.Main = new Class({
 			this.loadLayout();
 		}
 	},
-	loadLayout: function(){
-		this.manageUnits =[];
+	loadLayout: function () {
+		this.manageUnits = [];
 		this.manageTopUnits = [];
+		this.enableType = "";
 
 		this.restActions = MWF.Actions.get("x_attendance_assemble_control");
 		this.personActions = MWF.Actions.get("x_organization_assemble_personal");
@@ -45,263 +46,306 @@ MWF.xApplication.Attendance.Main = new Class({
 		this.createNode();
 		this.loadApplicationContent();
 	},
-	isAdmin: function(){
-		return this.isTopUnitManager() || MWF.AC.isAttendanceManager() || MWF.AC.isAdministrator() 
+	isAdmin: function () {
+		return this.isTopUnitManager() || MWF.AC.isAttendanceManager() || MWF.AC.isAdministrator()
 	},
-	isUnitManager : function(){
+	isUnitManager: function () {
 		return this.manageUnits.length > 0;
 	},
-	isTopUnitManager : function(){
+	isTopUnitManager: function () {
 		return this.manageTopUnits.length > 0;
 	},
-	getNameFlag : function(name){
+	getNameFlag: function (name) {
 		var t = typeOf(name);
-		if (t==="array"){
+		if (t === "array") {
 			var v = [];
-			name.each(function(id){
-				v.push((typeOf(id)==="object") ? (id.distinguishedName || id.id || id.unique || id.name) : id);
+			name.each(function (id) {
+				v.push((typeOf(id) === "object") ? (id.distinguishedName || id.id || id.unique || id.name) : id);
 			});
 			return v;
-		}else{
-			return [(t==="object") ? (name.distinguishedName || name.id || name.unique || name.name) : name];
+		} else {
+			return [(t === "object") ? (name.distinguishedName || name.id || name.unique || name.name) : name];
 		}
 	},
-	loadController: function(callback){
-		this.restActions.listPermission( function( json ){
+	loadController: function (callback) {
+		this.restActions.listPermission(function (json) {
 			json.data = json.data || [];
-			json.data.each(function(item){
-				if( item.adminLevel == "COMPANY" && item.adminName == layout.desktop.session.user.distinguishedName){
-					this.manageTopUnits.push( item.unitName )
-				}else if( item.adminLevel == "DEPT" && item.adminName == layout.desktop.session.user.distinguishedName ){
-					this.manageUnits.push( item.unitName )
+			json.data.each(function (item) {
+				if (item.adminLevel == "COMPANY" && item.adminName == layout.desktop.session.user.distinguishedName) {
+					this.manageTopUnits.push(item.unitName)
+				} else if (item.adminLevel == "DEPT" && item.adminName == layout.desktop.session.user.distinguishedName) {
+					this.manageUnits.push(item.unitName)
 				}
 			}.bind(this));
-			if(callback)callback(json);
+			if (callback) callback(json);
 		}.bind(this));
 	},
-	createNode: function(){
+	loadEnableType: function (callback) {
+		var action = o2.Actions.load("x_attendance_assemble_control");
+		action.AttendanceSettingAction.enableType(//平台封装好的方法
+			function (json) { //服务调用成功的回调函数, json为服务传回的数据
+				if (json.data && json.data.value) {
+					debugger;
+					this.enableType = json.data.value;
+				}
+				if (callback) callback(json);
+			}.bind(this));
+	},
+	createNode: function () {
 		this.content.setStyle("overflow", "hidden");
 		this.node = new Element("div", {
-			"styles": {"width": "100%", "height": "100%", "overflow": "hidden"}
+			"styles": { "width": "100%", "height": "100%", "overflow": "hidden" }
 		}).inject(this.content);
 	},
-	loadApplicationContent: function(){
-		this.loadController(function(){
-			this.loaNavi();
+	loadApplicationContent: function () {
+		this.loadController(function () {
+			this.loadEnableType(function () {
+				this.loaNavi();
+			}.bind(this));
 		}.bind(this));
 		//this.loadApplicationLayout();
 	},
-	loaNavi: function(callback){
+	loaNavi: function (callback) {
 		this.naviNode = new Element("div.naviNode", {
 			"styles": this.css.naviNode
 		}).inject(this.node);
 
-		var curNavi = { "id" : "" };
-		if( this.status ){
+		var curNavi = { "id": "" };
+		if (this.status) {
 			curNavi.id = this.status.id
 		}
-		if( this.options.curNaviId ){
+		if (this.options.curNaviId) {
 			curNavi.id = this.options.curNaviId;
 		}
-		this.navi = new MWF.xApplication.Attendance.Navi(this, this.naviNode, curNavi );
+		this.navi = new MWF.xApplication.Attendance.Navi(this, this.naviNode, curNavi);
 	},
-	clearContent: function(){
-		if (this.explorerContent){
-			if (this.explorer && this.explorer.destroy ){
+	clearContent: function () {
+		if (this.explorerContent) {
+			if (this.explorer && this.explorer.destroy) {
 				this.explorer.destroy();
 			}
 			this.explorerContent.destroy();
 			this.explorerContent = null;
 		}
 	},
-	openMyIndex : function(){
-		MWF.xDesktop.requireApp("Attendance", "MyIndex", function(){
+	openMyIndex: function () {
+		MWF.xDesktop.requireApp("Attendance", "MyIndex", function () {
+			this.clearContent();
+			this.explorerContent = new Element("div", {
+				"styles": this.css.rightContentNode
+			}).inject(this.node);
+			this.explorer = new MWF.xApplication.Attendance.MyIndex(this.explorerContent, this, this.restActions, { "isAdmin": this.isAdmin() });
+			this.explorer.load();
+		}.bind(this));
+	},
+	openMyDetail: function () {
+		MWF.xDesktop.requireApp("Attendance", "MyDetail", function () {
+			this.clearContent();
+			this.explorerContent = new Element("div", {
+				"styles": this.css.rightContentNode
+			}).inject(this.node);
+			this.explorer = new MWF.xApplication.Attendance.MyDetail(this.explorerContent, this, this.restActions, { "isAdmin": this.isAdmin() });
+			this.explorer.load();
+		}.bind(this));
+	},
+	openUnitIndex: function () {
+		MWF.xDesktop.requireApp("Attendance", "UnitIndex", function () {
+			this.clearContent();
+			this.explorerContent = new Element("div", {
+				"styles": this.css.rightContentNode
+			}).inject(this.node);
+			this.explorer = new MWF.xApplication.Attendance.UnitIndex(this.explorerContent, this, this.restActions, { "isAdmin": this.isAdmin() });
+			this.explorer.load();
+		}.bind(this));
+	},
+	openUnitDingdingIndex: function () {
+		MWF.xDesktop.requireApp("Attendance", "UnitDingdingIndex", function () {
 			this.clearContent();
 			this.explorerContent = new Element("div", {
 				"styles": this.css.rightContentNode
 			}).inject(this.node);
-			this.explorer = new MWF.xApplication.Attendance.MyIndex(this.explorerContent, this, this.restActions,{"isAdmin":this.isAdmin() } );
+			this.explorer = new MWF.xApplication.Attendance.UnitDingdingIndex(this.explorerContent, this, this.restActions, { "isAdmin": this.isAdmin() });
 			this.explorer.load();
 		}.bind(this));
 	},
-	openMyDetail : function(){
-		MWF.xDesktop.requireApp("Attendance", "MyDetail", function(){
+	openUnitDetail: function () {
+		MWF.xDesktop.requireApp("Attendance", "UnitDetail", function () {
 			this.clearContent();
 			this.explorerContent = new Element("div", {
 				"styles": this.css.rightContentNode
 			}).inject(this.node);
-			this.explorer = new MWF.xApplication.Attendance.MyDetail(this.explorerContent, this, this.restActions,{"isAdmin":this.isAdmin() } );
+			this.explorer = new MWF.xApplication.Attendance.UnitDetail(this.explorerContent, this, this.restActions, { "isAdmin": this.isAdmin() });
 			this.explorer.load();
 		}.bind(this));
 	},
-	openUnitIndex : function(){
-		MWF.xDesktop.requireApp("Attendance", "UnitIndex", function(){
+	openDingdingUnitDetail: function () {
+		MWF.xDesktop.requireApp("Attendance", "UnitDingdingDetail", function () {
 			this.clearContent();
 			this.explorerContent = new Element("div", {
 				"styles": this.css.rightContentNode
 			}).inject(this.node);
-			this.explorer = new MWF.xApplication.Attendance.UnitIndex(this.explorerContent, this, this.restActions,{"isAdmin":this.isAdmin() } );
+			this.explorer = new MWF.xApplication.Attendance.UnitDingdingDetail(this.explorerContent, this, this.restActions, { "isAdmin": this.isAdmin() });
 			this.explorer.load();
 		}.bind(this));
 	},
-	openUnitDetail : function(){
-		MWF.xDesktop.requireApp("Attendance", "UnitDetail", function(){
+	openPeopleDetail: function () {
+		MWF.xDesktop.requireApp("Attendance", "PeopleDetail", function () {
 			this.clearContent();
 			this.explorerContent = new Element("div", {
 				"styles": this.css.rightContentNode
 			}).inject(this.node);
-			this.explorer = new MWF.xApplication.Attendance.UnitDetail(this.explorerContent, this, this.restActions,{"isAdmin":this.isAdmin() } );
+			this.explorer = new MWF.xApplication.Attendance.PeopleDetail(this.explorerContent, this, this.restActions, { "isAdmin": this.isAdmin() });
 			this.explorer.load();
 		}.bind(this));
 	},
-	openPeopleDetail : function(){
-		MWF.xDesktop.requireApp("Attendance", "PeopleDetail", function(){
+	openDingdingPeopleDetail: function () {
+		MWF.xDesktop.requireApp("Attendance", "PeopleDingdingDetail", function () {
 			this.clearContent();
 			this.explorerContent = new Element("div", {
 				"styles": this.css.rightContentNode
 			}).inject(this.node);
-			this.explorer = new MWF.xApplication.Attendance.PeopleDetail(this.explorerContent, this, this.restActions,{"isAdmin":this.isAdmin() } );
+			this.explorer = new MWF.xApplication.Attendance.PeopleDingdingDetail(this.explorerContent, this, this.restActions, { "isAdmin": this.isAdmin() });
 			this.explorer.load();
 		}.bind(this));
 	},
-	openTopUnitDetail : function(){
-		MWF.xDesktop.requireApp("Attendance", "TopUnitDetail", function(){
+	openTopUnitDetail: function () {
+		MWF.xDesktop.requireApp("Attendance", "TopUnitDetail", function () {
 			this.clearContent();
 			this.explorerContent = new Element("div", {
 				"styles": this.css.rightContentNode
 			}).inject(this.node);
-			this.explorer = new MWF.xApplication.Attendance.TopUnitDetail(this.explorerContent, this, this.restActions,{"isAdmin":this.isAdmin() } );
+			this.explorer = new MWF.xApplication.Attendance.TopUnitDetail(this.explorerContent, this, this.restActions, { "isAdmin": this.isAdmin() });
 			this.explorer.load();
 		}.bind(this));
 	},
-	openSelfHoliday : function(){
-		MWF.xDesktop.requireApp("Attendance", "SelfHoliday", function(){
+	openSelfHoliday: function () {
+		MWF.xDesktop.requireApp("Attendance", "SelfHoliday", function () {
 			this.clearContent();
 			this.explorerContent = new Element("div", {
 				"styles": this.css.rightContentNode
 			}).inject(this.node);
-			this.explorer = new MWF.xApplication.Attendance.SelfHoliday(this.explorerContent, this, this.restActions,{"isAdmin":this.isAdmin() } );
+			this.explorer = new MWF.xApplication.Attendance.SelfHoliday(this.explorerContent, this, this.restActions, { "isAdmin": this.isAdmin() });
 			this.explorer.load();
 		}.bind(this));
 	},
-	openMyAppealDeal : function(){
-		MWF.xDesktop.requireApp("Attendance", "MyAppeal", function(){
+	openMyAppealDeal: function () {
+		MWF.xDesktop.requireApp("Attendance", "MyAppeal", function () {
 			this.clearContent();
 			this.explorerContent = new Element("div", {
 				"styles": this.css.rightContentNode
 			}).inject(this.node);
-			this.explorer = new MWF.xApplication.Attendance.MyAppeal(this.explorerContent, this, this.restActions,{"isAdmin":this.isAdmin() } );
+			this.explorer = new MWF.xApplication.Attendance.MyAppeal(this.explorerContent, this, this.restActions, { "isAdmin": this.isAdmin() });
 			this.explorer.load();
 		}.bind(this));
 	},
-	openAppealDeal : function(){
-		MWF.xDesktop.requireApp("Attendance", "AppealExplorer", function(){
+	openAppealDeal: function () {
+		MWF.xDesktop.requireApp("Attendance", "AppealExplorer", function () {
 			this.clearContent();
 			this.explorerContent = new Element("div", {
 				"styles": this.css.rightContentNode
 			}).inject(this.node);
-			this.explorer = new MWF.xApplication.Attendance.AppealExplorer(this.explorerContent, this, this.restActions,{"isAdmin":this.isAdmin() } );
+			this.explorer = new MWF.xApplication.Attendance.AppealExplorer(this.explorerContent, this, this.restActions, { "isAdmin": this.isAdmin() });
 			this.explorer.load();
 		}.bind(this));
 	},
-	openImporting : function(){
-		MWF.xDesktop.requireApp("Attendance", "ImportExplorer", function(){
+	openImporting: function () {
+		MWF.xDesktop.requireApp("Attendance", "ImportExplorer", function () {
 			this.clearContent();
 			this.explorerContent = new Element("div", {
 				"styles": this.css.rightContentNode
 			}).inject(this.node);
-			this.explorer = new MWF.xApplication.Attendance.ImportExplorer(this.explorerContent, this, this.restActions,{"isAdmin":this.isAdmin() } );
+			this.explorer = new MWF.xApplication.Attendance.ImportExplorer(this.explorerContent, this, this.restActions, { "isAdmin": this.isAdmin() });
 			this.explorer.load();
 		}.bind(this));
 	},
-	openImportedInvalidInfor : function(){
-		MWF.xDesktop.requireApp("Attendance", "InvalidInfor", function(){
+	openImportedInvalidInfor: function () {
+		MWF.xDesktop.requireApp("Attendance", "InvalidInfor", function () {
 			this.clearContent();
 			this.explorerContent = new Element("div", {
 				"styles": this.css.rightContentNode
 			}).inject(this.node);
-			this.explorer = new MWF.xApplication.Attendance.InvalidInfor(this.explorerContent, this, this.restActions,{"isAdmin":this.isAdmin() } );
+			this.explorer = new MWF.xApplication.Attendance.InvalidInfor(this.explorerContent, this, this.restActions, { "isAdmin": this.isAdmin() });
 			this.explorer.load();
 		}.bind(this));
 	},
-	openAbnormalExport : function(){
-		MWF.xDesktop.requireApp("Attendance", "AbnormalExport", function(){
+	openAbnormalExport: function () {
+		MWF.xDesktop.requireApp("Attendance", "AbnormalExport", function () {
 			this.clearContent();
 			this.explorerContent = new Element("div", {
 				"styles": this.css.rightContentNode
 			}).inject(this.node);
-			this.explorer = new MWF.xApplication.Attendance.AbnormalExport(this.explorerContent, this, this.restActions,{"isAdmin":this.isAdmin() } );
+			this.explorer = new MWF.xApplication.Attendance.AbnormalExport(this.explorerContent, this, this.restActions, { "isAdmin": this.isAdmin() });
 			this.explorer.load();
 		}.bind(this));
 	},
-	openScheduleSetting: function(){
-		MWF.xDesktop.requireApp("Attendance", "ScheduleExplorer", function(){
+	openScheduleSetting: function () {
+		MWF.xDesktop.requireApp("Attendance", "ScheduleExplorer", function () {
 			this.clearContent();
 			this.explorerContent = new Element("div", {
 				"styles": this.css.rightContentNode
 			}).inject(this.node);
-			this.explorer = new MWF.xApplication.Attendance.ScheduleExplorer(this.explorerContent, this, this.restActions,{"isAdmin":this.isAdmin() } );
+			this.explorer = new MWF.xApplication.Attendance.ScheduleExplorer(this.explorerContent, this, this.restActions, { "isAdmin": this.isAdmin() });
 			this.explorer.load();
 		}.bind(this));
 	},
-	openPermissionSetting: function(){
-		MWF.xDesktop.requireApp("Attendance", "PermissionExplorer", function(){
+	openPermissionSetting: function () {
+		MWF.xDesktop.requireApp("Attendance", "PermissionExplorer", function () {
 			this.clearContent();
 			this.explorerContent = new Element("div", {
 				"styles": this.css.rightContentNode
 			}).inject(this.node);
-			this.explorer = new MWF.xApplication.Attendance.PermissionExplorer(this.explorerContent, this, this.restActions,{"isAdmin":this.isAdmin() } );
+			this.explorer = new MWF.xApplication.Attendance.PermissionExplorer(this.explorerContent, this, this.restActions, { "isAdmin": this.isAdmin() });
 			this.explorer.load();
 		}.bind(this));
 	},
-	openHolidaySetting : function(){
-		MWF.xDesktop.requireApp("Attendance", "HolidayExplorer", function(){
+	openHolidaySetting: function () {
+		MWF.xDesktop.requireApp("Attendance", "HolidayExplorer", function () {
 			this.clearContent();
 			this.explorerContent = new Element("div", {
 				"styles": this.css.rightContentNode
 			}).inject(this.node);
-			this.explorer = new MWF.xApplication.Attendance.HolidayExplorer(this.explorerContent, this, this.restActions,{"isAdmin":this.isAdmin() } );
+			this.explorer = new MWF.xApplication.Attendance.HolidayExplorer(this.explorerContent, this, this.restActions, { "isAdmin": this.isAdmin() });
 			this.explorer.load();
 		}.bind(this));
 	},
-	openStaticsCycleExplorer : function(){
-		MWF.xDesktop.requireApp("Attendance", "StatisticsCycle", function(){
+	openStaticsCycleExplorer: function () {
+		MWF.xDesktop.requireApp("Attendance", "StatisticsCycle", function () {
 			this.clearContent();
 			this.explorerContent = new Element("div", {
 				"styles": this.css.rightContentNode
 			}).inject(this.node);
-			this.explorer = new MWF.xApplication.Attendance.StatisticsCycle(this.explorerContent, this, this.restActions,{"isAdmin":this.isAdmin() } );
+			this.explorer = new MWF.xApplication.Attendance.StatisticsCycle(this.explorerContent, this, this.restActions, { "isAdmin": this.isAdmin() });
 			this.explorer.load();
 		}.bind(this));
 	},
-	openAppSetting : function(){
-		MWF.xDesktop.requireApp("Attendance", "AppSetting", function(){
-			var setting = new MWF.xApplication.Attendance.AppSetting(this,this.restActions);
+	openAppSetting: function () {
+		MWF.xDesktop.requireApp("Attendance", "AppSetting", function () {
+			var setting = new MWF.xApplication.Attendance.AppSetting(this, this.restActions);
 			setting.edit();
 		}.bind(this));
 	},
-	openAddressSetting : function(){
-		MWF.xDesktop.requireApp("Attendance", "AddressExplorer", function(){
+	openAddressSetting: function () {
+		MWF.xDesktop.requireApp("Attendance", "AddressExplorer", function () {
 			this.clearContent();
 			this.explorerContent = new Element("div", {
 				"styles": this.css.rightContentNode
 			}).inject(this.node);
-			this.explorer = new MWF.xApplication.Attendance.AddressExplorer(this.explorerContent, this, this.restActions,{"isAdmin":this.isAdmin() } );
+			this.explorer = new MWF.xApplication.Attendance.AddressExplorer(this.explorerContent, this, this.restActions, { "isAdmin": this.isAdmin() });
 			this.explorer.load();
 		}.bind(this));
 	},
-	openPersonSetting : function(){
-		MWF.xDesktop.requireApp("Attendance", "PersonSetting", function(){
+	openPersonSetting: function () {
+		MWF.xDesktop.requireApp("Attendance", "PersonSetting", function () {
 			this.clearContent();
 			this.explorerContent = new Element("div", {
 				"styles": this.css.rightContentNode
 			}).inject(this.node);
-			this.explorer = new MWF.xApplication.Attendance.PersonSetting(this.explorerContent, this, this.restActions,{"isAdmin":this.isAdmin() } );
+			this.explorer = new MWF.xApplication.Attendance.PersonSetting(this.explorerContent, this, this.restActions, { "isAdmin": this.isAdmin() });
 			this.explorer.load();
 		}.bind(this));
 	},
-	recordStatus: function(){
-		return this.navi && this.navi.currentItem ?  this.navi.currentItem.retrieve("data") : {};
+	recordStatus: function () {
+		return this.navi && this.navi.currentItem ? this.navi.currentItem.retrieve("data") : {};
 	}
 });
 
@@ -309,10 +353,10 @@ MWF.xApplication.Attendance.Main = new Class({
 
 MWF.xApplication.Attendance.Navi = new Class({
 	Implements: [Options, Events],
-	options : {
-		"id" : ""
+	options: {
+		"id": ""
 	},
-	initialize: function(app, node, options){
+	initialize: function (app, node, options) {
 		this.setOptions(options);
 		this.app = app;
 		this.node = $(node);
@@ -324,61 +368,64 @@ MWF.xApplication.Attendance.Navi = new Class({
 		this.elements = [];
 		this.load();
 	},
-	load: function(){
-		this.scrollNode = new Element("div.naviScrollNode", { "styles" : this.css.naviScrollNode }).inject( this.node );
-		this.areaNode = new Element("div.naviAreaNode", { "styles" : this.css.naviAreaNode }).inject( this.scrollNode );
+	load: function () {
+		this.scrollNode = new Element("div.naviScrollNode", { "styles": this.css.naviScrollNode }).inject(this.node);
+		this.areaNode = new Element("div.naviAreaNode", { "styles": this.css.naviAreaNode }).inject(this.scrollNode);
 
 		this.setNodeScroll();
 
-		var naviUrl = this.app.path+"navi.json";
-		MWF.getJSON(naviUrl, function(json){
-			json.each(function(navi){
-				if( navi.access && navi.access == "admin" ){
-					if( this.app.isAdmin() )this.createNaviNode(navi);
-				}else if( navi.access && navi.access == "admin_dept" ){
-					if( this.app.isUnitManager() || this.app.isAdmin() )this.createNaviNode(navi);
-				}else{
+		var naviUrl = this.app.path + "navi.json";
+		MWF.getJSON(naviUrl, function (json) {
+			json.each(function (navi) {
+				if (navi.access && navi.access == "admin") {
+					if (this.app.isAdmin()) this.createNaviNode(navi);
+				} else if (navi.access && navi.access == "admin_dept") {
+					if (this.app.isUnitManager() || this.app.isAdmin()) this.createNaviNode(navi);
+				} else if (navi.access && navi.access == "dingding") { //启用钉钉考勤同步后
+					debugger;
+					if ((this.app.isUnitManager() || this.app.isAdmin()) && (this.app.enableType == "dingding")) this.createNaviNode(navi);
+				} else {
 					this.createNaviNode(navi);
 				}
 			}.bind(this));
-			if( this.options.id == "" )this.elements[0].click();
+			if (this.options.id == "") this.elements[0].click();
 
 			this.setContentSize();
 
 			this.app.addEvent("resize", this.setContentSize.bind(this));
 		}.bind(this));
 	},
-	setNodeScroll: function(){
-		MWF.require("MWF.widget.DragScroll", function(){
+	setNodeScroll: function () {
+		MWF.require("MWF.widget.DragScroll", function () {
 			new MWF.widget.DragScroll(this.scrollNode);
 		}.bind(this));
-		MWF.require("MWF.widget.ScrollBar", function(){
-			new MWF.widget.ScrollBar(this.scrollNode, {"indent": false});
+		MWF.require("MWF.widget.ScrollBar", function () {
+			new MWF.widget.ScrollBar(this.scrollNode, { "indent": false });
 		}.bind(this));
 	},
-	createNaviNode :function(data){
-		if( data.type == "sep" ){
+	createNaviNode: function (data) {
+		if (data.type == "sep") {
 			var flag = true;
-			if( data.access == "admin" ){
-				if( !this.app.isAdmin() )flag = false;
-			}else if( data.access && data.access == "admin_dept" ){
-				if( !this.app.isUnitManager() && !this.app.isAdmin() )flag = false;
+			if (data.access == "admin") {
+				if (!this.app.isAdmin()) flag = false;
+			} else if (data.access && data.access == "admin_dept") {
+				if (!this.app.isUnitManager() && !this.app.isAdmin()) flag = false;
 			}
-			if( flag ){
+			if (flag) {
 				new Element("div", { "styles": this.css.viewNaviSepartorNode }).inject(this.areaNode);
 			}
-		}else if( data.sub && data.sub.length > 0 ){
+		} else if (data.sub && data.sub.length > 0) {
 			this.createNaviMenuNode(data);
-		}else{
+		} else {
 			this.menus[data.id] = {};
 			this.createNaviItemNode(data, data.id);
 		}
 	},
-	createNaviMenuNode :function(data){
-		if( data.access == "admin" ){
-			if( !this.app.isAdmin() )return;
-		}else if(data.access == "admin_dept"){
-			if( !this.app.isUnitManager() && !this.app.isAdmin() )return;
+	createNaviMenuNode: function (data) {
+		if (data.access == "admin") {
+			if (!this.app.isAdmin()) return;
+		} else if (data.access == "admin_dept") {
+			if (!this.app.isUnitManager() && !this.app.isAdmin()) return;
 		}
 		var _self = this;
 		var menuNode = new Element("div", {
@@ -387,7 +434,7 @@ MWF.xApplication.Attendance.Navi = new Class({
 		menuNode.store("data", data);
 		menuNode.store("type", "menu");
 
-		var textNode =  new Element("div", {
+		var textNode = new Element("div", {
 			"styles": this.css.naviMenuTextNode,
 			"text": data.title
 		});
@@ -399,54 +446,54 @@ MWF.xApplication.Attendance.Navi = new Class({
 		this.elements.push(menuNode);
 
 		menuNode.addEvents({
-			"mouseover": function(){ if (_self.currentMenu!=this) this.setStyles(_self.app.css.naviMenuNode_over);},
-			"mouseout": function(){if (_self.currentMenu!=this) this.setStyles(_self.app.css.naviMenuNode);},
-			"mousedown": function(){if (_self.currentMenu!=this) this.setStyles(_self.app.css.naviMenuNode_down);},
-			"mouseup": function(){if (_self.currentMenu!=this) this.setStyles(_self.app.css.naviMenuNode_over);},
-			"click": function(){
+			"mouseover": function () { if (_self.currentMenu != this) this.setStyles(_self.app.css.naviMenuNode_over); },
+			"mouseout": function () { if (_self.currentMenu != this) this.setStyles(_self.app.css.naviMenuNode); },
+			"mousedown": function () { if (_self.currentMenu != this) this.setStyles(_self.app.css.naviMenuNode_down); },
+			"mouseup": function () { if (_self.currentMenu != this) this.setStyles(_self.app.css.naviMenuNode_over); },
+			"click": function () {
 				//if (_self.currentNavi!=this) _self.doAction.apply(_self, [this]);
 				_self.clickMenu.apply(_self, [this]);
 			}
 		});
 
-		data.sub.each(function( d ){
-			this.createNaviItemNode( d, data.id, menuNode  )
+		data.sub.each(function (d) {
+			this.createNaviItemNode(d, data.id, menuNode)
 		}.bind(this))
 	},
-	clickMenu: function(naviNode) {
+	clickMenu: function (naviNode) {
 		var navi = naviNode.retrieve("data");
 		var action = navi.action;
 
 		this.closeCurrentMenu();
-		if( this.menus[navi.id].itemNodes ) {
-			this.menus[navi.id].itemNodes.each( function(itemNode){
-				itemNode.setStyle("display","block");
+		if (this.menus[navi.id].itemNodes) {
+			this.menus[navi.id].itemNodes.each(function (itemNode) {
+				itemNode.setStyle("display", "block");
 			})
 		}
 
 		var type = naviNode.retrieve("type");
 		if (!navi.target || navi.target != "_blank") {
-			naviNode.setStyles( this.css.naviMenuNode_current );
+			naviNode.setStyles(this.css.naviMenuNode_current);
 			this.currentMenu = naviNode;
 		}
 	},
-	closeCurrentMenu:function(){
-		if( this.currentMenu ) {
+	closeCurrentMenu: function () {
+		if (this.currentMenu) {
 			var data = this.currentMenu.retrieve("data");
 			if (this.menus[data.id].itemNodes) {
 				this.menus[data.id].itemNodes.each(function (itemNode) {
 					itemNode.setStyle("display", "none");
 				})
 			}
-			this.currentMenu.setStyles( this.css.naviMenuNode);
+			this.currentMenu.setStyles(this.css.naviMenuNode);
 		}
 	},
-	createNaviItemNode : function( data,menuId ){
+	createNaviItemNode: function (data, menuId) {
 
-		if( data.access == "admin" ){
-			if( !this.app.isAdmin() )return;
-		}else if( data.access && data.access == "admin_dept" ){
-			if( !this.app.isUnitManager() && !this.app.isAdmin() )return;
+		if (data.access == "admin") {
+			if (!this.app.isAdmin()) return;
+		} else if (data.access && data.access == "admin_dept") {
+			if (!this.app.isUnitManager() && !this.app.isAdmin()) return;
 		}
 
 		var _self = this;
@@ -456,13 +503,13 @@ MWF.xApplication.Attendance.Navi = new Class({
 		var itemNode = new Element("div", {
 			"styles": this.css.naviItemNode
 		});
-		itemNode.setStyle("display","block");
+		itemNode.setStyle("display", "block");
 
 		items.push(itemNode);
 		itemNode.store("data", data);
 		itemNode.store("type", "item");
 
-		var textNode =  new Element("div", {
+		var textNode = new Element("div", {
 			"styles": this.css.naviItemTextNode,
 			"text": data.title
 		});
@@ -474,20 +521,20 @@ MWF.xApplication.Attendance.Navi = new Class({
 		this.items[data.id] = itemNode;
 
 		itemNode.addEvents({
-			"mouseover": function(){ if (_self.currentItem!=this) this.setStyles(_self.app.css.naviItemNode_over);},
-			"mouseout": function(){if (_self.currentItem!=this) this.setStyles(_self.app.css.naviItemNode);},
-			"mousedown": function(){if (_self.currentItem!=this) this.setStyles(_self.app.css.naviItemNode_down);},
-			"mouseup": function(){if (_self.currentItem!=this) this.setStyles(_self.app.css.naviItemNode_over);},
-			"click": function(){
+			"mouseover": function () { if (_self.currentItem != this) this.setStyles(_self.app.css.naviItemNode_over); },
+			"mouseout": function () { if (_self.currentItem != this) this.setStyles(_self.app.css.naviItemNode); },
+			"mousedown": function () { if (_self.currentItem != this) this.setStyles(_self.app.css.naviItemNode_down); },
+			"mouseup": function () { if (_self.currentItem != this) this.setStyles(_self.app.css.naviItemNode_over); },
+			"click": function () {
 				_self.clickItem.apply(_self, [this]);
 			}
 		});
 
-		if( data.id == this.options.id ){
+		if (data.id == this.options.id) {
 			itemNode.click();
 		}
 	},
-	clickItem : function(naviNode) {
+	clickItem: function (naviNode) {
 		var navi = naviNode.retrieve("data");
 		var action = navi.action;
 
@@ -502,9 +549,9 @@ MWF.xApplication.Attendance.Navi = new Class({
 			this.app[navi.action].call(this.app, navi);
 		}
 	},
-	setContentSize : function(){
+	setContentSize: function () {
 		var size = this.app.content.getSize();
-		this.scrollNode.setStyle("height", size.y - 5 );
+		this.scrollNode.setStyle("height", size.y - 5);
 	}
 	//loadCalendar: function () {
 	//	var calendarArea = new Element("div#calendarArea",{

+ 455 - 0
o2web/source/x_component_Attendance/PeopleDingdingDetail.js

@@ -0,0 +1,455 @@
+MWF.xDesktop.requireApp("Attendance", "Explorer", null, false);
+MWF.xDesktop.requireApp("Selector", "package", null, false);
+
+MWF.xApplication.Attendance.PeopleDingdingDetail = new Class({
+    Extends: MWF.widget.Common,
+    Implements: [Options, Events],
+    options: {
+        "style": "default"
+    },
+    initialize: function (node, app, actions, options) {
+        this.setOptions(options);
+        this.app = app;
+        this.path = "/x_component_Attendance/$PeopleDetail/";
+        this.cssPath = "/x_component_Attendance/$PeopleDetail/" + this.options.style + "/css.wcss";
+        this._loadCss();
+
+        this.actions = actions;
+        this.node = $(node);
+    },
+    load: function () {
+        this.loadTab();
+    },
+    loadTab: function () {
+
+        this.tabNode = new Element("div", { "styles": this.css.tabNode }).inject(this.node);
+        this.detailArea = new Element("div", { "styles": this.css.tabPageContainer }).inject(this.tabNode);
+        //this.selfHolidayArea = new Element("div",{"styles" : this.css.tabPageContainer }).inject(this.tabNode)
+        this.detailStaticArea = new Element("div", { "styles": this.css.tabPageContainer }).inject(this.tabNode);
+        //this.selfHolidayStaticArea = new Element("div",{"styles" : this.css.tabPageContainer }).inject(this.tabNode)
+
+        MWF.require("MWF.widget.Tab", function () {
+
+            this.tabs = new MWF.widget.Tab(this.tabNode, { "style": "attendance" });
+            this.tabs.load();
+
+            this.detailPage = this.tabs.addTab(this.detailArea, "个人打卡明细", false);
+            this.detailPage.contentNodeArea.set("class", "detailPage");
+            this.detailPage.addEvent("show", function () {
+                if (!this.detailExplorer) {
+                    this.detailExplorer = new MWF.xApplication.Attendance.PeopleDingdingDetail.Explorer(this.detailArea, this);
+                    this.detailExplorer.load();
+                }
+            }.bind(this));
+
+
+            this.detailStaticPage = this.tabs.addTab(this.detailStaticArea, "个人打卡率统计", false);
+            this.detailStaticPage.contentNodeArea.set("class", "detailStaticPage");
+            this.detailStaticPage.addEvent("show", function () {
+                if (!this.detailStaticExplorer) {
+                    this.detailStaticExplorer = new MWF.xApplication.Attendance.PeopleDingdingDetail.DetailStaticExplorer(this.detailStaticArea, this);
+                    this.detailStaticExplorer.load();
+                }
+            }.bind(this));
+
+
+            this.tabs.pages[0].showTab();
+        }.bind(this));
+    }
+});
+
+MWF.xApplication.Attendance.PeopleDingdingDetail.Explorer = new Class({
+    Extends: MWF.xApplication.Attendance.Explorer,
+    Implements: [Options, Events],
+
+    initialize: function (node, parent, options) {
+        this.setOptions(options);
+        this.parent = parent;
+        this.app = parent.app;
+        this.css = parent.css;
+        this.path = parent.path;
+
+        this.actions = parent.actions;
+        this.node = $(node);
+
+
+        this.initData();
+        if (!this.peopleActions) this.peopleActions = new MWF.xAction.org.express.RestActions();
+    },
+    initData: function () {
+        this.toolItemNodes = [];
+    },
+    reload: function () {
+        this.node.empty();
+        this.load();
+    },
+    load: function () {
+        this.loadFilter();
+        this.loadContentNode();
+        this.setNodeScroll();
+    },
+    loadFilter: function () {
+        this.fileterNode = new Element("div.fileterNode", {
+            "styles": this.css.fileterNode
+        }).inject(this.node);
+
+        var html = "<table width='100%' bordr='0' cellpadding='5' cellspacing='0' styles='filterTable'>" +
+            "<tr>" +
+            "    <td styles='filterTableValue' lable='person'></td>" +
+            "    <td styles='filterTableTitle' item='person'></td>" +
+            "    <td styles='filterTableTitle' lable='year'></td>" +
+            "    <td styles='filterTableValue' item='year'></td>" +
+            "    <td styles='filterTableTitle' lable='month'></td>" +
+            "    <td styles='filterTableValue' item='month'></td>" +
+            "    <td styles='filterTableTitle' lable='day'></td>" +
+            "    <td styles='filterTableValue' item='day'></td>" +
+            "    <td styles='filterTableTitle' lable='checkType'></td>" +
+            "    <td styles='filterTableValue' item='checkType'></td>" +
+            "    <td styles='filterTableTitle' lable='timeResult'></td>" +
+            "    <td styles='filterTableValue' item='timeResult'></td>" +
+            "    <td styles='filterTableValue' item='action'></td>" +
+            "</tr>" +
+            "</table>";
+        this.fileterNode.set("html", html);
+
+        MWF.xDesktop.requireApp("Template", "MForm", function () {
+            this.form = new MForm(this.fileterNode, {}, {
+                isEdited: true,
+                itemTemplate: {
+                    person: { text: "人员", type: "org", orgType: "person", notEmpty: true, style: { "min-width": "100px" } },
+                    year: {
+                        text: "年度",
+                        "type": "select",
+                        "selectValue": function () {
+                            var years = [];
+                            var year = new Date().getFullYear();
+                            for (var i = 0; i < 6; i++) {
+                                years.push(year--);
+                            }
+                            return years;
+                        },
+                        "event": {
+                            "change": function (item, ev) {
+                                var values = this.getDateSelectValue();
+                                item.form.getItem("day").resetItemOptions(values, values)
+                            }.bind(this)
+                        }
+                    },
+                    month: {
+                        text: "月份",
+                        "type": "select",
+                        "defaultValue": function () {
+                            var month = (new Date().getMonth() + 1).toString();
+                            return month.length == 1 ? "0" + month : month;
+                        },
+                        "selectValue": ["", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12"],
+                        "event": {
+                            "change": function (item, ev) {
+                                var values = this.getDateSelectValue();
+                                item.form.getItem("day").resetItemOptions(values, values)
+                            }.bind(this)
+                        }
+                    },
+                    day: { text: "日期", "type": "select", "selectValue": this.getDateSelectValue.bind(this) },
+                    checkType: { text: "打卡类型", "type": "select", "selectValue": ["", "OnDuty", "OffDuty"], "selectText": ["", "上班打卡", "下班打卡"] },
+                    timeResult: { text: "打卡结果", "type": "select", "selectValue": ["", "Normal", "Early", "Late", "SeriousLate", "Absenteeism", "NotSigned"], "selectText": ["", "正常", "早退", "迟到", "严重迟到", "旷工迟到", "未打卡"] },
+                    action: {
+                        "value": "查询", type: "button", className: "filterButton", event: {
+                            click: function () {
+                                var result = this.form.getResult(true, ",", true, true, false);
+                                if (!result) return;
+                                if (result.day && result.day != "") {
+                                    result.q_date = result.year + "-" + result.month + "-" + result.day;
+                                }
+                                this.loadView(result);
+                            }.bind(this)
+                        }
+                    }
+                }
+            }, this.app, this.css);
+            this.form.load();
+        }.bind(this), true);
+    },
+    getDateSelectValue: function () {
+        if (this.form) {
+            var year = parseInt(this.form.getItem("year").getValue());
+            var month = parseInt(this.form.getItem("month").getValue()) - 1;
+        } else {
+            var year = (new Date()).getFullYear();
+            var month = (new Date()).getMonth();
+        }
+        var date = new Date(year, month, 1);
+        var days = [];
+        days.push("");
+        while (date.getMonth() === month) {
+            var d = date.getDate().toString();
+            if (d.length == 1) d = "0" + d;
+            days.push(d);
+            date.setDate(date.getDate() + 1);
+        }
+        return days;
+    },
+
+    loadContentNode: function () {
+        this.elementContentNode = new Element("div", {
+            "styles": this.css.elementContentNode
+        }).inject(this.node);
+        this.app.addEvent("resize", function () { this.setContentSize(); }.bind(this));
+
+    },
+    loadView: function (filterData) {
+        this.elementContentNode.empty();
+        if (this.view) delete this.view;
+        this.view = new MWF.xApplication.Attendance.PeopleDingdingDetail.View(this.elementContentNode, this.app, this);
+        this.view.filterData = filterData;
+        this.view.listItemUrl = this.path + "listItem_dingding.json";
+        this.view.load();
+        this.setContentSize();
+    },
+    setContentSize: function () {
+        var tabNodeSize = this.parent.tabs ? this.parent.tabs.tabNodeContainer.getSize() : { "x": 0, "y": 0 };
+        var fileterNodeSize = this.fileterNode ? this.fileterNode.getSize() : { "x": 0, "y": 0 };
+        var nodeSize = this.parent.node.getSize();
+
+        var pt = this.elementContentNode.getStyle("padding-top").toFloat();
+        var pb = this.elementContentNode.getStyle("padding-bottom").toFloat();
+        //var filterSize = this.filterNode.getSize();
+
+        var height = nodeSize.y - tabNodeSize.y - pt - pb - fileterNodeSize.y - 20;
+        this.elementContentNode.setStyle("height", "" + height + "px");
+
+        this.pageCount = (height / 40).toInt() + 5;
+
+        if (this.view && this.view.items.length < this.pageCount) {
+            this.view.loadElementList(this.pageCount - this.view.items.length);
+        }
+    }
+});
+
+MWF.xApplication.Attendance.PeopleDingdingDetail.SelfHoliday = new Class({
+    Extends: MWF.xApplication.Attendance.PeopleDingdingDetail.Explorer,
+
+    loadView: function (filterData) {
+        this.elementContentNode.empty();
+        if (this.view) delete this.view;
+        this.view = new MWF.xApplication.Attendance.PeopleDingdingDetail.SelfHolidayView(this.elementContentNode, this.app, this);
+        this.view.filterData = filterData;
+        this.view.load();
+        this.setContentSize();
+    }
+});
+
+
+MWF.xApplication.Attendance.PeopleDingdingDetail.DetailStaticExplorer = new Class({
+    Extends: MWF.xApplication.Attendance.PeopleDingdingDetail.Explorer,
+    loadFilter: function () {
+        this.fileterNode = new Element("div.fileterNode", {
+            "styles": this.css.fileterNode
+        }).inject(this.node);
+
+        var html = "<table width='100%' bordr='0' cellpadding='5' cellspacing='0' style='width: 460px;font-size: 14px;color:#666'>" +
+            "<tr>" +
+            "    <td styles='filterTableValue' lable='q_empName'></td>" +
+            "    <td styles='filterTableTitle' item='q_empName'></td>" +
+            "    <td styles='filterTableTitle' lable='cycleYear'></td>" +
+            "    <td styles='filterTableValue' item='cycleYear'></td>" +
+            "    <td styles='filterTableTitle' lable='cycleMonth'></td>" +
+            "    <td styles='filterTableValue' item='cycleMonth'></td>" +
+            "    <td styles='filterTableValue' item='action'></td>" +
+            "</tr>" +
+            "</table>";
+        this.fileterNode.set("html", html);
+
+        MWF.xDesktop.requireApp("Template", "MForm", function () {
+            this.form = new MForm(this.fileterNode, {}, {
+                isEdited: true,
+                itemTemplate: {
+                    q_empName: { text: "人员", type: "org", orgType: "person", notEmpty: true, style: { "min-width": "100px" } },
+                    cycleYear: {
+                        text: "年度",
+                        "type": "select",
+                        "selectValue": function () {
+                            var years = [];
+                            var year = new Date().getFullYear();
+                            for (var i = 0; i < 6; i++) {
+                                years.push(year--);
+                            }
+                            return years;
+                        }
+                    },
+                    cycleMonth: {
+                        text: "月份",
+                        "type": "select",
+                        "defaultValue": function () {
+                            var month = (new Date().getMonth() + 1).toString();
+                            return month.length == 1 ? "0" + month : month;
+                        },
+                        "selectValue": ["", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12"]
+                    },
+                    action: {
+                        "value": "查询", type: "button", className: "filterButton", event: {
+                            click: function () {
+                                var result = this.form.getResult(true, ",", true, true, false);
+                                if (!result) return;
+                                this.loadView(result);
+                            }.bind(this)
+                        }
+                    }
+                }
+            }, this.app, this.css);
+            this.form.load();
+        }.bind(this), true);
+    },
+
+    loadView: function (filterData) {
+        this.elementContentNode.empty();
+        if (this.view) delete this.view;
+        this.view = new MWF.xApplication.Attendance.PeopleDingdingDetail.DetailStaticView(this.elementContentNode, this.app, this);
+        this.view.filterData = filterData;
+        this.view.listItemUrl = this.path + "listItem_dingding_detailStatic.json";
+        this.view.load();
+        this.setContentSize();
+    }
+});
+
+
+MWF.xApplication.Attendance.PeopleDingdingDetail.SelfHolidayStaticExplorer = new Class({
+    Extends: MWF.xApplication.Attendance.PeopleDingdingDetail.Explorer,
+
+    loadView: function (filterData) {
+        this.elementContentNode.empty();
+        if (this.view) delete this.view;
+        this.view = new MWF.xApplication.Attendance.PeopleDingdingDetail.SelfHolidayStaticView(this.elementContentNode, this.app, this);
+        this.view.filterData = filterData;
+        this.view.load();
+        this.setContentSize();
+    }
+});
+
+MWF.xApplication.Attendance.PeopleDingdingDetail.View = new Class({
+    Extends: MWF.xApplication.Attendance.Explorer.View,
+    _createItem: function (data) {
+        return new MWF.xApplication.Attendance.PeopleDingdingDetail.Document(this.table, data, this.explorer, this);
+    },
+
+    _getCurrentPageData: function (callback, count) {
+        if (!count) count = 20;
+        var id = (this.items.length) ? this.items[this.items.length - 1].data.id : "(0)";
+        var filter = this.filterData || {};
+
+        var action = o2.Actions.load("x_attendance_assemble_control");
+        action.DingdingAttendanceAction.listNextDingdingAttendance(id, count, filter, function (json) {
+            if (callback) callback(json);
+        }.bind(this));
+    },
+    _removeDocument: function (documentData, all) {
+
+    },
+    _createDocument: function () {
+
+    },
+    _openDocument: function (documentData) {
+
+    }
+
+});
+
+MWF.xApplication.Attendance.PeopleDingdingDetail.SelfHolidayView = new Class({
+    Extends: MWF.xApplication.Attendance.Explorer.View,
+    _createItem: function (data) {
+        return new MWF.xApplication.Attendance.PeopleDingdingDetail.SelfHolidayDocument(this.table, data, this.explorer, this);
+    },
+
+    _getCurrentPageData: function (callback, count) {
+        var filter = this.filterData || {};
+        this.actions.listDetailFilter(filter, function (json) {
+            if (callback) callback(json);
+        }.bind(this))
+    },
+    _removeDocument: function (documentData, all) {
+
+    },
+    _createDocument: function () {
+
+    },
+    _openDocument: function (documentData) {
+
+    }
+
+});
+
+
+MWF.xApplication.Attendance.PeopleDingdingDetail.DetailStaticView = new Class({
+    Extends: MWF.xApplication.Attendance.Explorer.View,
+    _createItem: function (data) {
+        return new MWF.xApplication.Attendance.PeopleDingdingDetail.DetailStaticDocument(this.table, data, this.explorer, this);
+    },
+
+    _getCurrentPageData: function (callback, count) {
+        var filter = this.filterData || {};
+        var action = o2.Actions.load("x_attendance_assemble_control");
+        action.DingdingAttendanceStatisticAction.personMonth(filter.q_empName, filter.cycleYear, filter.cycleMonth, function (json) {
+            // var data = json.data;
+            // data.sort(function (a, b) {
+            //     return parseInt(b.statisticYear + b.statisticMonth) - parseInt(a.statisticYear + a.statisticMonth)
+            // });
+            // json.data = data;
+            if (callback) callback(json);
+        }.bind(this))
+    },
+    _removeDocument: function (documentData, all) {
+
+    },
+    _createDocument: function () {
+
+    },
+    _openDocument: function (documentData) {
+
+    }
+
+});
+
+MWF.xApplication.Attendance.PeopleDingdingDetail.SelfHolidayStaticView = new Class({
+    Extends: MWF.xApplication.Attendance.Explorer.View,
+    _createItem: function (data) {
+        return new MWF.xApplication.Attendance.PeopleDingdingDetail.SelfHolidayStaticDocument(this.table, data, this.explorer, this);
+    },
+
+    _getCurrentPageData: function (callback, count) {
+        var filter = this.filterData || {};
+        this.actions.listDetailFilter(filter, function (json) {
+            if (callback) callback(json);
+        }.bind(this))
+    },
+    _removeDocument: function (documentData, all) {
+
+    },
+    _createDocument: function () {
+
+    },
+    _openDocument: function (documentData) {
+
+    }
+
+});
+
+MWF.xApplication.Attendance.PeopleDingdingDetail.Document = new Class({
+    Extends: MWF.xApplication.Attendance.Explorer.Document
+
+});
+
+MWF.xApplication.Attendance.PeopleDingdingDetail.SelfHolidayDocument = new Class({
+    Extends: MWF.xApplication.Attendance.Explorer.Document
+
+});
+
+
+MWF.xApplication.Attendance.PeopleDingdingDetail.DetailStaticDocument = new Class({
+    Extends: MWF.xApplication.Attendance.Explorer.Document
+
+});
+
+MWF.xApplication.Attendance.PeopleDingdingDetail.SelfHolidayStaticDocument = new Class({
+    Extends: MWF.xApplication.Attendance.Explorer.Document
+
+});

+ 372 - 0
o2web/source/x_component_Attendance/UnitDingdingDetail.js

@@ -0,0 +1,372 @@
+MWF.xDesktop.requireApp("Attendance", "Explorer", null, false);
+MWF.xDesktop.requireApp("Selector", "package", null, false);
+
+MWF.xApplication.Attendance.UnitDingdingDetail = new Class({
+    Extends: MWF.widget.Common,
+    Implements: [Options, Events],
+    options: {
+        "style": "default"
+    },
+    initialize: function (node, app, actions, options) {
+        this.setOptions(options);
+        this.app = app;
+        this.path = "/x_component_Attendance/$UnitDetail/";
+        this.cssPath = "/x_component_Attendance/$UnitDetail/" + this.options.style + "/css.wcss";
+        this._loadCss();
+
+        this.actions = actions;
+        this.node = $(node);
+    },
+    load: function () {
+        this.loadTab();
+    },
+    loadTab: function () {
+
+        this.tabNode = new Element("div", { "styles": this.css.tabNode }).inject(this.node);
+        this.detailArea = new Element("div", { "styles": this.css.tabPageContainer }).inject(this.tabNode);
+        //this.selfHolidayArea = new Element("div",{"styles" : this.css.tabPageContainer }).inject(this.tabNode)
+        this.detailStaticArea = new Element("div", { "styles": this.css.tabPageContainer }).inject(this.tabNode);
+        //this.selfHolidayStaticArea = new Element("div",{"styles" : this.css.tabPageContainer }).inject(this.tabNode)
+
+        MWF.require("MWF.widget.Tab", function () {
+
+            this.tabs = new MWF.widget.Tab(this.tabNode, { "style": "attendance" });
+            this.tabs.load();
+
+            this.detailPage = this.tabs.addTab(this.detailArea, "部门打卡明细", false);
+            this.detailPage.contentNodeArea.set("class", "detailPage");
+            this.detailPage.addEvent("show", function () {
+                if (!this.detailExplorer) {
+                    this.detailExplorer = new MWF.xApplication.Attendance.UnitDingdingDetail.Explorer(this.detailArea, this);
+                    this.detailExplorer.load();
+                }
+            }.bind(this));
+
+
+            this.detailStaticPage = this.tabs.addTab(this.detailStaticArea, "部门打卡率统计", false);
+            this.detailStaticPage.contentNodeArea.set("class", "detailStaticPage");
+            this.detailStaticPage.addEvent("show", function () {
+                if (!this.detailStaticExplorer) {
+                    this.detailStaticExplorer = new MWF.xApplication.Attendance.UnitDingdingDetail.DetailStaticExplorer(this.detailStaticArea, this);
+                    this.detailStaticExplorer.load();
+                }
+            }.bind(this));
+
+            this.tabs.pages[0].showTab();
+        }.bind(this));
+    }
+});
+
+MWF.xApplication.Attendance.UnitDingdingDetail.Explorer = new Class({
+    Extends: MWF.xApplication.Attendance.Explorer,
+    Implements: [Options, Events],
+
+    initialize: function (node, parent, options) {
+        this.setOptions(options);
+        this.parent = parent;
+        this.app = parent.app;
+        this.lp = this.app.lp;
+        this.css = parent.css;
+        this.path = parent.path;
+
+        this.actions = parent.actions;
+        this.node = $(node);
+
+        this.initData();
+        if (!this.peopleActions) this.peopleActions = new MWF.xAction.org.express.RestActions();
+    },
+    initData: function () {
+        this.toolItemNodes = [];
+    },
+    reload: function () {
+        this.node.empty();
+        this.load();
+    },
+    load: function () {
+        this.loadFilter();
+        this.loadContentNode();
+        this.setNodeScroll();
+    },
+    loadFilter: function () {
+        this.fileterNode = new Element("div.fileterNode", {
+            "styles": this.css.fileterNode
+        }).inject(this.node);
+
+        var html = "<table width='100%' bordr='0' cellpadding='5' cellspacing='0' styles='filterTable'>" +
+            "<tr>" +
+            "    <td styles='filterTableValue' lable='unit'></td>" +
+            "    <td styles='filterTableTitle' item='unit'></td>" +
+            "    <td styles='filterTableTitle' lable='year'></td>" +
+            "    <td styles='filterTableValue' item='year'></td>" +
+            "    <td styles='filterTableTitle' lable='month'></td>" +
+            "    <td styles='filterTableValue' item='month'></td>" +
+            "    <td styles='filterTableTitle' lable='day'></td>" +
+            "    <td styles='filterTableValue' item='day'></td>" +
+            "    <td styles='filterTableTitle' lable='checkType'></td>" +
+            "    <td styles='filterTableValue' item='checkType'></td>" +
+            "    <td styles='filterTableTitle' lable='timeResult'></td>" +
+            "    <td styles='filterTableValue' item='timeResult'></td>" +
+            "    <td styles='filterTableValue' item='action'></td>" +
+            "</tr>" +
+            "</table>";
+        this.fileterNode.set("html", html);
+
+        MWF.xDesktop.requireApp("Template", "MForm", function () {
+            this.form = new MForm(this.fileterNode, {}, {
+                isEdited: true,
+                itemTemplate: {
+                    unit: { text: "部门", type: "org", orgType: "unit", notEmpty: true, style: { "min-width": "200px" } },
+                    year: {
+                        text: "年度",
+                        "type": "select",
+                        "selectValue": function () {
+                            var years = [];
+                            var year = new Date().getFullYear();
+                            for (var i = 0; i < 6; i++) {
+                                years.push(year--);
+                            }
+                            return years;
+                        },
+                        "event": {
+                            "change": function (item, ev) {
+                                var values = this.getDateSelectValue();
+                                item.form.getItem("day").resetItemOptions(values, values)
+                            }.bind(this)
+                        }
+                    },
+                    month: {
+                        text: "月份",
+                        "type": "select",
+                        "defaultValue": function () {
+                            var month = (new Date().getMonth() + 1).toString();
+                            return month.length == 1 ? "0" + month : month;
+                        },
+                        "selectValue": ["", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12"],
+                        "event": {
+                            "change": function (item, ev) {
+                                var values = this.getDateSelectValue();
+                                item.form.getItem("day").resetItemOptions(values, values)
+                            }.bind(this)
+                        }
+                    },
+                    day: { text: "日期", "type": "select", "selectValue": this.getDateSelectValue.bind(this) },
+                    checkType: { text: "打卡类型", "type": "select", "selectValue": ["", "OnDuty", "OffDuty"], "selectText": ["", "上班打卡", "下班打卡"] },
+                    timeResult: { text: "打卡结果", "type": "select", "selectValue": ["", "Normal", "Early", "Late", "SeriousLate", "Absenteeism", "NotSigned"], "selectText": ["", "正常", "早退", "迟到", "严重迟到", "旷工迟到", "未打卡"] },
+                    action: {
+                        "value": "查询", type: "button", className: "filterButton", event: {
+                            click: function () {
+                                var result = this.form.getResult(true, ",", true, true, false);
+                                if (!result) return;
+                                if (result.day && result.day != "") {
+                                    result.q_date = result.year + "-" + result.month + "-" + result.day;
+                                }
+                                this.loadView(result);
+                            }.bind(this)
+                        }
+                    }
+                }
+            }, this.app, this.css);
+            this.form.load();
+        }.bind(this), true);
+    },
+    getDateSelectValue: function () {
+        if (this.form) {
+            var year = parseInt(this.form.getItem("year").getValue());
+            var month = parseInt(this.form.getItem("month").getValue()) - 1;
+        } else {
+            var year = (new Date()).getFullYear();
+            var month = (new Date()).getMonth();
+        }
+        var date = new Date(year, month, 1);
+        var days = [];
+        days.push("");
+        while (date.getMonth() === month) {
+            var d = date.getDate().toString();
+            if (d.length == 1) d = "0" + d;
+            days.push(d);
+            date.setDate(date.getDate() + 1);
+        }
+        return days;
+    },
+    loadContentNode: function () {
+        this.elementContentNode = new Element("div", {
+            "styles": this.css.elementContentNode
+        }).inject(this.node);
+        this.app.addEvent("resize", function () { this.setContentSize(); }.bind(this));
+
+    },
+    loadView: function (filterData) {
+        this.elementContentNode.empty();
+        if (this.view) delete this.view;
+        this.view = new MWF.xApplication.Attendance.UnitDingdingDetail.View(this.elementContentNode, this.app, this);
+        this.view.filterData = filterData;
+        this.view.listItemUrl = this.path + "listItem_dingding.json";
+        this.view.load();
+        this.setContentSize();
+    },
+    setContentSize: function () {
+        var tabNodeSize = this.parent.tabs ? this.parent.tabs.tabNodeContainer.getSize() : { "x": 0, "y": 0 };
+        var fileterNodeSize = this.fileterNode ? this.fileterNode.getSize() : { "x": 0, "y": 0 };
+        var nodeSize = this.parent.node.getSize();
+
+        var pt = this.elementContentNode.getStyle("padding-top").toFloat();
+        var pb = this.elementContentNode.getStyle("padding-bottom").toFloat();
+        //var filterSize = this.filterNode.getSize();
+
+        var height = nodeSize.y - tabNodeSize.y - pt - pb - fileterNodeSize.y - 20;
+        this.elementContentNode.setStyle("height", "" + height + "px");
+
+        this.pageCount = (height / 40).toInt() + 5;
+
+        if (this.view && this.view.items.length < this.pageCount) {
+            this.view.loadElementList(this.pageCount - this.view.items.length);
+        }
+    }
+});
+
+
+
+MWF.xApplication.Attendance.UnitDingdingDetail.DetailStaticExplorer = new Class({
+    Extends: MWF.xApplication.Attendance.UnitDingdingDetail.Explorer,
+
+    loadFilter: function () {
+        this.fileterNode = new Element("div.fileterNode", {
+            "styles": this.css.fileterNode
+        }).inject(this.node);
+
+        var html = "<table width='100%' bordr='0' cellpadding='5' cellspacing='0' style='width: 660px;font-size: 14px;color:#666'>" +
+            "<tr>" +
+            "    <td styles='filterTableValue' lable='q_unitName'></td>" +
+            "    <td styles='filterTableTitle' item='q_unitName'></td>" +
+            "    <td styles='filterTableTitle' lable='cycleYear'></td>" +
+            "    <td styles='filterTableValue' item='cycleYear'></td>" +
+            "    <td styles='filterTableTitle' lable='cycleMonth'></td>" +
+            "    <td styles='filterTableValue' item='cycleMonth'></td>" +
+            "    <td styles='filterTableValue' item='action'></td>" +
+            "</tr>" +
+            "</table>";
+        this.fileterNode.set("html", html);
+
+        MWF.xDesktop.requireApp("Template", "MForm", function () {
+            this.form = new MForm(this.fileterNode, {}, {
+                isEdited: true,
+                itemTemplate: {
+                    q_unitName: { text: "部门", type: "org", orgType: "unit", notEmpty: true, style: { "min-width": "200px" } },
+                    cycleYear: {
+                        text: "年度",
+                        "type": "select",
+                        "selectValue": function () {
+                            var years = [];
+                            var year = new Date().getFullYear();
+                            for (var i = 0; i < 6; i++) {
+                                years.push(year--);
+                            }
+                            return years;
+                        }
+                    },
+                    cycleMonth: {
+                        text: "月份", notEmpty: true,
+                        "type": "select",
+                        "defaultValue": function () {
+                            var month = (new Date().getMonth() + 1).toString();
+                            return month.length == 1 ? "0" + month : month;
+                        },
+                        "selectValue": ["", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12"]
+                    },
+                    action: {
+                        "value": "查询", type: "button", className: "filterButton", event: {
+                            click: function () {
+                                var result = this.form.getResult(true, ",", true, true, false);
+                                if (!result) return;
+                                this.loadView(result);
+                            }.bind(this)
+                        }
+                    }
+                }
+            }, this.app, this.css);
+            this.form.load();
+        }.bind(this), true);
+    },
+
+    loadView: function (filterData) {
+        this.elementContentNode.empty();
+        if (this.view) delete this.view;
+        this.view = new MWF.xApplication.Attendance.UnitDingdingDetail.DetailStaticView(this.elementContentNode, this.app, this);
+        this.view.filterData = filterData;
+        this.view.listItemUrl = this.path + "listItem_dingding_detailStatic.json";
+        this.view.load();
+        this.setContentSize();
+    }
+});
+
+
+
+MWF.xApplication.Attendance.UnitDingdingDetail.View = new Class({
+    Extends: MWF.xApplication.Attendance.Explorer.View,
+    _createItem: function (data) {
+        return new MWF.xApplication.Attendance.UnitDingdingDetail.Document(this.table, data, this.explorer, this);
+    },
+
+    _getCurrentPageData: function (callback, count) {
+        if (!count) count = 20;
+        var id = (this.items.length) ? this.items[this.items.length - 1].data.id : "(0)";
+        var filter = this.filterData || {};
+
+        var action = o2.Actions.load("x_attendance_assemble_control");
+        action.DingdingAttendanceAction.listNextDingdingAttendance(id, count, filter, function (json) {
+            if (callback) callback(json);
+        }.bind(this));
+    },
+    _removeDocument: function (documentData, all) {
+
+    },
+    _createDocument: function () {
+
+    },
+    _openDocument: function (documentData) {
+
+    }
+
+});
+
+
+
+
+MWF.xApplication.Attendance.UnitDingdingDetail.DetailStaticView = new Class({
+    Extends: MWF.xApplication.Attendance.Explorer.View,
+    _createItem: function (data) {
+        return new MWF.xApplication.Attendance.UnitDingdingDetail.DetailStaticDocument(this.table, data, this.explorer, this);
+    },
+
+    _getCurrentPageData: function (callback, count) {
+        var filter = this.filterData || {};
+        var action = o2.Actions.load("x_attendance_assemble_control");
+        action.DingdingAttendanceStatisticAction.personMonthWithUnit(filter.q_unitName, filter.cycleYear, filter.cycleMonth, function (json) {
+            if (callback) callback(json);
+        }.bind(this));
+    },
+    _removeDocument: function (documentData, all) {
+
+    },
+    _createDocument: function () {
+
+    },
+    _openDocument: function (documentData) {
+
+    }
+
+});
+
+
+
+MWF.xApplication.Attendance.UnitDingdingDetail.Document = new Class({
+    Extends: MWF.xApplication.Attendance.Explorer.Document
+
+});
+
+
+MWF.xApplication.Attendance.UnitDingdingDetail.DetailStaticDocument = new Class({
+    Extends: MWF.xApplication.Attendance.Explorer.Document
+
+});
+

+ 501 - 0
o2web/source/x_component_Attendance/UnitDingdingIndex.js

@@ -0,0 +1,501 @@
+MWF.xApplication.Attendance = MWF.xApplication.Attendance || {};
+
+MWF.require("MWF.xAction.org.express.RestActions", null,false);
+MWF.xDesktop.requireApp("Attendance", "lp."+MWF.language, null, false);
+MWF.xDesktop.requireApp("Attendance", "Common", null, false);
+
+MWF.xApplication.Attendance.UnitDingdingIndex = new Class({
+    Extends: MWF.widget.Common,
+    Implements: [Options, Events],
+    options: {
+        "style": "default"
+    },
+    statusColor : {
+        "resultNormal" : "#9acd32", //绿色,正常
+        "leaveEarlyTimes":"#4f94cd", //蓝色,早退
+        "lateTimes":"#fede03", //黄色,迟到
+        "notSignedCount":"#ee807f", //粉红色,未签到
+        "seriousLateTimes" : "#dec674",//严重迟到
+        "absenteeismTimes" : "#fedcbd"//矿工
+    },
+    initialize: function(node, app, actions, options){
+        this.setOptions(options);
+        this.app = app;
+        this.lp = app.lp;
+        this.path = "/x_component_Attendance/$UnitIndex/";
+        this.cssPath = "/x_component_Attendance/$UnitIndex/"+this.options.style+"/css.wcss";
+        this._loadCss();
+
+        this.actions = actions;
+        this.node = $(node);
+
+        this.setDate();
+
+        this.today = new Date();
+
+        this.userName = layout.desktop.session.user.distinguishedName;
+        this.data = {};
+    },
+    setDate : function( date ){
+        this.date = date || new Date();
+        this.year = this.date.getFullYear().toString();
+        var month = this.date.getMonth()+1;
+        this.month = month.toString().length == 2 ? month : "0"+month;
+    },
+    reload: function(){
+        this.node.empty();
+        this.load();
+    },
+    load: function(){
+        this.loadTitleNode();
+        this.loadContent();
+    },
+    loadTitleNode : function(){
+        var text = this.date.format(this.app.lp.dateFormatMonth);
+
+        this.titleNode = new Element("div.titleNode",{
+            "styles" : this.css.titleNode
+        }).inject(this.node);
+        this.titleLeftArrowNode = new Element("div",{
+            "styles" : this.css.titleLeftArrowNode
+        }).inject(this.titleNode);
+        this.titleTextNode = new Element("div",{
+            "styles" : this.css.titleTextNode,
+            "text" : text
+        }).inject(this.titleNode);
+        this.titleRightArrowNode = new Element("div",{
+            "styles" : this.css.titleRightArrowNode
+        }).inject(this.titleNode);
+
+        this.titleLeftArrowNode.addEvents({
+            "mouseover": function(){this.titleLeftArrowNode.setStyles(this.css.titleLeftArrowNode_over);}.bind(this),
+            "mouseout": function(){this.titleLeftArrowNode.setStyles(this.css.titleLeftArrowNode);}.bind(this),
+            "mousedown": function(){this.titleLeftArrowNode.setStyles(this.css.titleLeftArrowNode_down);}.bind(this),
+            "mouseup": function(){this.titleLeftArrowNode.setStyles(this.css.titleLeftArrowNode_over);}.bind(this),
+            "click": function(){this.changeMonthPrev();}.bind(this)
+        });
+        this.titleRightArrowNode.addEvents({
+            "mouseover": function(){this.titleRightArrowNode.setStyles(this.css.titleRightArrowNode_over);}.bind(this),
+            "mouseout": function(){this.titleRightArrowNode.setStyles(this.css.titleRightArrowNode);}.bind(this),
+            "mousedown": function(){this.titleRightArrowNode.setStyles(this.css.titleRightArrowNode_down);}.bind(this),
+            "mouseup": function(){this.titleRightArrowNode.setStyles(this.css.titleRightArrowNode_over);}.bind(this),
+            "click": function(){this.changeMonthNext();}.bind(this)
+        });
+        this.titleTextNode.addEvents({
+            "mouseover": function(){this.titleTextNode.setStyles(this.css.titleTextNode_over);}.bind(this),
+            "mouseout": function(){this.titleTextNode.setStyles(this.css.titleTextNode);}.bind(this),
+            "mousedown": function(){this.titleTextNode.setStyles(this.css.titleTextNode_down);}.bind(this),
+            "mouseup": function(){this.titleTextNode.setStyles(this.css.titleTextNode_over);}.bind(this),
+            "click": function(){this.changeMonthSelect();}.bind(this)
+        });
+
+        this.loadUnitNode();
+    },
+    changeMonthPrev: function(){
+        this.date.decrement("month", 1);
+        this.setDate( this.date );
+        var text = this.date.format(this.app.lp.dateFormatMonth);
+        this.titleTextNode.set("text", text);
+        this.reloadContent();
+    },
+    changeMonthNext: function(){
+        this.date.increment("month", 1);
+        this.setDate( this.date );
+        var text = this.date.format(this.app.lp.dateFormatMonth);
+        this.titleTextNode.set("text", text);
+        this.reloadContent();
+    },
+    changeMonthSelect: function(){
+        if (!this.monthSelector) this.createMonthSelector();
+        this.monthSelector.show();
+    },
+    createMonthSelector: function(){
+        this.monthSelector = new MWF.xApplication.Attendance.MonthSelector(this.date, this);
+    },
+    changeMonthTo: function(d){
+        this.setDate( d )
+        var text = this.date.format(this.app.lp.dateFormatMonth);
+        this.titleTextNode.set("text", text);
+        this.reloadContent();
+    },
+    changeUnitTo : function( d ){
+        this.unit = d;
+        this.titleUnitActionTextNode.set("text", d.split("@")[0]);
+        this.reloadContent();
+    },
+    loadUnitNode: function(){
+        this.listUnitWithPerson( function( unit ){
+            this.unit = unit;
+            this.units = [];
+            var flag = true;
+            if( this.app.isTopUnitManager() ){
+                var data = {"unitList": this.app.getNameFlag( this.app.manageTopUnits )};
+                this.app.orgActions.listUnitSubDirect( function( json ){
+                    json.data.each(function( d ){
+                        this.units.push( d.distinguishedName )
+                    }.bind(this))
+                }.bind(this), null , data, false )
+            }else if( this.app.isUnitManager() ){
+                this.units = this.app.manageUnits;
+            }
+            this.unit = this.units[0] || this.unit;
+            if( this.units.length > 1 ){ //(this.units.length==1 && this.units[0]!=this.unit )
+                this.titleUnitAreaNode = new Element("div.titleUnitAreaNode",{
+                    "styles" : this.css.titleUnitAreaNode
+                }).inject(this.titleNode)
+
+                this.titleUnitActionNode = new Element("div",{
+                    "styles" : this.css.titleUnitActionNode
+                }).inject(this.titleUnitAreaNode)
+
+                this.titleUnitActionTextNode = new Element("div",{
+                    "styles" : this.css.titleUnitActionTextNode,
+                    "text" : this.unit.split("@")[0]
+                }).inject(this.titleUnitActionNode);
+
+                this.titleUnitActionIconNode = new Element("div",{
+                    "styles" : this.css.titleUnitActionIconNode
+                }).inject(this.titleUnitActionNode);
+
+                this.titleUnitActionNode.addEvents({
+                    "mouseover": function(){
+                        this.titleUnitActionTextNode.setStyles(this.css.titleUnitActionTextNode_over);
+                        this.titleUnitActionIconNode.setStyles(this.css.titleUnitActionIconNode_over);
+                    }.bind(this),
+                    "mouseout": function(){
+                        this.titleUnitActionTextNode.setStyles(this.css.titleUnitActionTextNode);
+                        this.titleUnitActionIconNode.setStyles(this.css.titleUnitActionIconNode);
+                    }.bind(this),
+                    "click" : function( ev ){
+                        this.switchUnit( ev.target );
+                        ev.stopPropagation();
+                    }.bind(this)
+                })
+            }else{
+                this.titleUnitNode = new Element("div",{
+                    "styles" : this.css.titleUnitNode,
+                    "text" : this.unit.split("@")[0]
+                }).inject(this.titleNode);
+            }
+        }.bind(this) )
+    },
+    listUnitWithPerson : function( callback ){
+        var data = {"personList": this.app.getNameFlag(this.userName)};
+        this.app.orgActions.listUnitWithPerson( function( json ){
+            if( json.data.length > 0 ){
+                if(callback)callback( json.data[0].distinguishedName );
+            }else{
+                if(callback)callback();
+            }
+        }.bind(this), null, data , false )
+    },
+    switchUnit : function( el ){
+        var _self = this;
+        var node = this.titleUnitListNode;
+        var parentNode = el.getParent();
+        if(node){
+            if(  node.getStyle("display") == "block" ){
+                node.setStyle("display","none");
+            }else{
+                node.setStyle("display","block");
+                node.position({
+                    relativeTo: this.titleUnitActionNode,
+                    position: 'bottomCenter',
+                    edge: 'upperCenter'
+                });
+            }
+        }else{
+            node = this.titleUnitListNode = new Element("div",{
+                "styles" :  this.css.titleUnitListNode
+            }).inject(this.node);
+            this.app.content.addEvent("click",function(){
+                _self.titleUnitListNode.setStyle("display","none");
+            });
+            this.units.each(function( d ){
+                var dNode = new Element("div",{
+                    "text" : d.split('@')[0],
+                    "styles" : this.css.titleUnitSelectNode
+                }).inject(node);
+                dNode.store("unit", d );
+                dNode.addEvents({
+                    "mouseover" : function(){ this.setStyles(_self.css.titleUnitSelectNode_over); },
+                    "mouseout" : function(){  this.setStyles(_self.css.titleUnitSelectNode); },
+                    "click" : function(e){
+                        _self.titleUnitListNode.setStyle("display","none");
+                        this.setStyles(_self.css.titleUnitSelectNode);
+                        _self.changeUnitTo( this.retrieve("unit") );
+                        e.stopPropagation();
+                    }
+                })
+            }.bind(this));
+            node.position({
+                relativeTo: this.titleUnitActionNode,
+                position: 'bottomCenter',
+                edge: 'upperCenter'
+            });
+        }
+    },
+    reloadContent : function(){
+        this.pieChartArea.empty();
+        this.barChartArea.empty();
+        this.lineChartArea.empty();
+        this.loadData(function(){
+            this.loadStatusColorNode();
+            this.loadPieChart();
+            this.loadBarChart();
+        }.bind(this));
+        this.loadDetail();
+    },
+    loadContent : function(){
+        this.loadContentNode();
+        this.loadData(function(){
+            this.loadStatusColorNode();
+            this.loadPieChart();
+            this.loadBarChart();
+        }.bind(this))
+        this.loadDetail();
+        this.setNodeScroll();
+        this.setContentSize();
+    },
+    reloadChart : function(){
+        this.pieChartArea.empty();
+        this.barChartArea.empty();
+        this.lineChartArea.empty();
+        this.loadPieChart();
+        this.loadBarChart();
+    },
+    loadContentNode: function(){
+        this.elementContentNode = new Element("div.elementContentNode", {
+            "styles": this.css.elementContentNode
+        }).inject(this.node);
+        this.app.addEvent("resize", function(){
+            this.setContentSize();
+            this.reloadChart();
+        }.bind(this));
+
+        this.elementContentListNode = new Element("div.elementContentListNode", {
+            "styles": this.css.elementContentListNode
+        }).inject(this.elementContentNode);
+
+        this.topContentArea = new Element("div.topContentArea",{
+            "styles" : this.css.topContentArea
+        }).inject(this.elementContentListNode)
+
+
+        this.pieChartArea = new Element("div.pieChartArea",{
+            "styles" : this.css.pieChartArea
+        }).inject(this.topContentArea)
+
+        this.statusColorArea = new Element("div.statusColorArea",{
+            "styles" : this.css.statusColorArea
+        }).inject(this.topContentArea)
+
+        this.barChartArea = new Element("div.barChartArea",{
+            "styles" : this.css.barChartArea
+        }).inject(this.topContentArea)
+
+        this.middleContentArea = new Element("div.middleContentArea",{
+            "styles" : this.css.middleContentArea
+        }).inject(this.elementContentListNode)
+
+        this.lineChartArea = new Element("div.lineChartArea",{
+            "styles" : this.css.lineChartArea
+        }).inject(this.middleContentArea)
+
+        this.bottomContentArea = new Element("div.middleContentArea",{
+            "styles" : this.css.bottomContentArea
+        }).inject(this.elementContentListNode)
+
+        this.detailArea = new Element("div.lineChartArea",{
+            "styles" : this.css.detailArea
+        }).inject(this.bottomContentArea)
+
+    },
+    loadData : function( callback, unit, year, month, async ){
+        if( !unit  )unit = this.unit;
+        if( !year )year = this.year;
+        if( !month )month = this.month;
+        if( this.data[ unit + year + month ] ) {
+            if(callback)callback();
+        }else{
+            var action = o2.Actions.load("x_attendance_assemble_control");
+            action.DingdingAttendanceStatisticAction.unitMonth(unit, year, month, function(json){
+                var d = json.data || {};
+                var data = this.data[ unit + year + month ] = {};
+                var totals = data.totalData = {
+                    resultNormal: d.resultNormal || 0,
+                    lateTimes: d.lateTimes || 0,
+                    leaveEarlyTimes: d.leaveEarlyTimes || 0,
+                    absenteeismTimes: d.absenteeismTimes || 0,
+                    seriousLateTimes: d.seriousLateTimes || 0,
+                    notSignedCount: d.notSignedCount || 0
+                }
+
+                var total = 0;
+                for( var n in totals  ){
+                    total += totals[n];
+                }
+                data.rateData = {
+                    resultNormal : (!totals.resultNormal || !total) ? 0 : ((totals.resultNormal/total * 100).toFixed(2) + "%"),
+                    lateTimes : (!totals.lateTimes || !total) ? 0 : ((totals.lateTimes/total * 100).toFixed(2)  + "%"),
+                    leaveEarlyTimes : (!totals.leaveEarlyTimes || !total) ? 0 : ((totals.leaveEarlyTimes/total * 100).toFixed(2)  + "%"),
+                    absenteeismTimes : (!totals.absenteeismTimes || !total) ? 0 : ((totals.absenteeismTimes/total * 100).toFixed(2)  + "%"),
+                    seriousLateTimes : (!totals.seriousLateTimes || !total) ? 0 : ((totals.seriousLateTimes/total * 100).toFixed(2) + "%"),
+                    notSignedCount : (!totals.notSignedCount || !total) ? 0 : ((totals.notSignedCount/total* 100).toFixed(2)  + "%")
+                }
+                if(callback)callback();
+
+            }.bind(this), null, async);
+        }
+    },
+    loadStatusColorNode : function(){
+        this.statusColorArea.empty();
+
+        this.statusColorTable = new Element("table",{
+            "styles" : this.css.statusColorTable
+        }).inject(this.statusColorArea)
+
+        var totalData = this.data[ this.unit+this.year + this.month].totalData;
+        var rateData = this.data[ this.unit+this.year + this.month].rateData;
+
+        for(var status in this.statusColor){
+
+            var tr = new Element("tr",{
+                "styles" : this.css.statusColorTr
+            }).inject(this.statusColorTable)
+            var td = new Element("td",{
+                "styles" : this.css.statusColorTd
+            }).inject(tr)
+            td.setStyle("background-color",this.statusColor[status]);
+
+            var tr = new Element("tr",{
+                "styles" : this.css.statusTextTr
+            }).inject(this.statusColorTable)
+            var td = new Element("td",{
+                "styles" : this.css.statusTextTd,
+                "text" : this.lp[status] +totalData[status]+ this.lp.day +"("+rateData[status]+")"
+            }).inject(tr)
+        }
+    },
+    loadPieChart : function(){
+        this.pieChartNode = new Element("div.pieChartNode",{
+            "styles" : this.css.pieChartNode
+        }).inject(this.pieChartArea)
+
+
+        var data = this.data[this.unit+ this.year + this.month].totalData;
+        this.pieChart = new MWF.xApplication.Attendance.Echarts(this.pieChartNode, this, data);
+        this.pieChart.loadUnitPieChart();
+    },
+    loadBarChart : function(){
+        this.barChartNode = new Element("div.barChartNode",{
+            "styles" : this.css.barChartNode
+        }).inject(this.barChartArea);
+
+        var date = new Date( this.date.getFullYear() , this.date.getMonth(), this.date.getDate() );
+        date.decrement("month", 1);
+        var year_1 = date.getFullYear().toString();
+        var month_1 = date.format( this.lp.dateFormatOnlyMonth );
+        var data_1 = this.data[ this.unit + year_1 + month_1 ];
+
+        date.decrement("month", 1);
+        var year_2 = date.getFullYear().toString();
+        var month_2 = date.format( this.lp.dateFormatOnlyMonth );
+        var data_2 = this.data[ this.unit + year_2 + month_2 ];
+
+        if( !data_1 ){
+            this.loadData( null, this.unit, year_1, month_1, false )
+        }
+        if( !data_2 ){
+            this.loadData( null, this.unit, year_2, month_2, false)
+        }
+
+        var d = [{
+            year : year_2,
+            month : month_2,
+            data : this.data[this.unit+ year_2 + month_2].totalData
+        },{
+            year : year_1,
+            month : month_1,
+            data : this.data[this.unit+ year_1 + month_1].totalData
+        },{
+            year : this.year,
+            month : this.month,
+            data : this.data[this.unit+ this.year + this.month].totalData
+        }];
+
+        this.barChart = new MWF.xApplication.Attendance.Echarts(this.barChartNode, this, d );
+        this.barChart.loadUnitBarChart();
+    },
+    loadDetail : function(){
+        this.detailArea.empty();
+        this.detailNode = new Element("div",{
+            "styles" : this.css.detailNode
+        }).inject(this.detailArea);
+
+        this.detailTitleNode = new Element("div",{
+            "styles" : this.css.detailTitleNode,
+            "text" : this.lp.attendanceStatisic
+        }).inject(this.detailNode)
+
+        var table = new Element("table", {
+            "width" : "100%", "border" : "0", "cellpadding" : "5", "cellspacing" : "0",  "styles" : this.css.table, "class" : "editTable"
+        }).inject( this.detailNode );
+
+        var tr = new Element("tr", { "styles" : this.css.listHeadNode }).inject(table);
+        var td = new Element("td", {  "styles" : this.css.tableTitle, "text" : this.lp.name  }).inject(tr);
+        var td = new Element("td", {  "styles" : this.css.tableTitle, "text" : this.lp.onDutyTimes  }).inject(tr);
+        var td = new Element("td", {  "styles" : this.css.tableTitle, "text" : this.lp.offDutyTimes   }).inject(tr);
+        var td = new Element("td", {  "styles" : this.css.tableTitle, "text" : this.lp.resultNormal  }).inject(tr);
+        var td = new Element("td", {  "styles" : this.css.tableTitle, "text" : this.lp.lateTimes  }).inject(tr);
+        var td = new Element("td", {  "styles" : this.css.tableTitle, "text" : this.lp.seriousLateTimes  }).inject(tr);
+        var td = new Element("td", {  "styles" : this.css.tableTitle, "text" : this.lp.leaveEarlyTimes  }).inject(tr);
+
+        var td = new Element("td", {  "styles" : this.css.tableTitle, "text" : this.lp.absenteeismTimes  }).inject(tr);
+        var td = new Element("td", {  "styles" : this.css.tableTitle, "text" : this.lp.notSignedCount  }).inject(tr);
+        var action = o2.Actions.load("x_attendance_assemble_control");
+        action.DingdingAttendanceStatisticAction.personMonthWithUnit(this.unit, this.year, this.month, function(json){
+            var data = json.data || [];
+            data.sort( function(a, b){
+               return b.workDayCount - a.workDayCount;
+            });
+            data.each(function( d ){
+                var tr = new Element("tr").inject(table);
+                var td = new Element("td", { "styles" : this.css.tableValue , "text": d.o2User.split("@")[0] }).inject(tr);
+                var td = new Element("td", { "styles" : this.css.tableValue , "text": d.onDutyTimes }).inject(tr);
+                var td = new Element("td", { "styles" : this.css.tableValue , "text": d.offDutyTimes }).inject(tr);
+                var td = new Element("td", { "styles" : this.css.tableValue , "text": d.resultNormal }).inject(tr);
+                var td = new Element("td", { "styles" : this.css.tableValue , "text": d.lateTimes }).inject(tr);
+                var td = new Element("td", { "styles" : this.css.tableValue , "text": d.seriousLateTimes }).inject(tr);
+                var td = new Element("td", { "styles" : this.css.tableValue , "text": d.leaveEarlyTimes }).inject(tr);
+                var td = new Element("td", { "styles" : this.css.tableValue , "text": d.absenteeismTimes }).inject(tr);
+                var td = new Element("td", { "styles" : this.css.tableValue , "text": d.notSignedCount }).inject(tr);
+            }.bind(this))
+
+        }.bind(this));
+        
+    },
+    setContentSize: function(){
+        var toolbarSize = this.toolbarNode ? this.toolbarNode.getSize() : {"x":0,"y":0};
+        var titlebarSize = this.titleNode ? this.titleNode.getSize() : {"x":0,"y":0};
+        var nodeSize = this.node.getSize();
+        var pt = this.elementContentNode.getStyle("padding-top").toFloat();
+        var pb = this.elementContentNode.getStyle("padding-bottom").toFloat();
+        //var filterSize = this.filterNode.getSize();
+        var filterConditionSize = this.filterConditionNode ? this.filterConditionNode.getSize() : {"x":0,"y":0};
+
+        var height = nodeSize.y-toolbarSize.y-pt-pb-filterConditionSize.y-titlebarSize.y-10;
+        this.elementContentNode.setStyle("height", ""+height+"px");
+
+    },
+    setNodeScroll: function(){
+        var _self = this;
+        MWF.require("MWF.widget.ScrollBar", function(){
+            new MWF.widget.ScrollBar(this.elementContentNode, {
+                "indent": false,"style":"xApp_TaskList", "where": "before", "distance": 30, "friction": 4,	"axis": {"x": false, "y": true},
+                "onScroll": function(y){
+                }
+            });
+        }.bind(this));
+    }
+});

+ 5 - 0
o2web/source/x_component_Attendance/lp/zh-cn.js

@@ -47,6 +47,11 @@ MWF.xApplication.Attendance.LP = {
 	"leaveEarlyTimes" : "早退次数",
 	"lackOfTimeCount" : "工时不足人次",
 	"abNormalDutyCount" : "异常打卡人次",
+	"resultNormal" : "正常打卡次数",
+	"seriousLateTimes": "严重迟到次数",
+	"leaveEarlyTimes": "早退次数",
+	"absenteeismTimes": "矿工次数",
+	"notSignedCount": "未打卡次数",
 
 	"topUnitAttendanceDetail" : "公司出勤明细",
 	"topUnitAttendanceStatic" : "公司出勤率统计",