|
@@ -7,6 +7,8 @@ import com.malk.server.aliwork.YDParam;
|
|
|
import com.malk.server.dingtalk.DDR_New;
|
|
import com.malk.server.dingtalk.DDR_New;
|
|
|
import com.malk.server.workhours.WHConf;
|
|
import com.malk.server.workhours.WHConf;
|
|
|
import com.malk.service.aliwork.YDClient;
|
|
import com.malk.service.aliwork.YDClient;
|
|
|
|
|
+import com.malk.service.dingtalk.DDClient;
|
|
|
|
|
+import com.malk.service.dingtalk.DDClient_Contacts;
|
|
|
import lombok.extern.slf4j.Slf4j;
|
|
import lombok.extern.slf4j.Slf4j;
|
|
|
import org.springframework.beans.factory.annotation.Autowired;
|
|
import org.springframework.beans.factory.annotation.Autowired;
|
|
|
import org.springframework.stereotype.Service;
|
|
import org.springframework.stereotype.Service;
|
|
@@ -17,6 +19,8 @@ import java.time.LocalDate;
|
|
|
import java.time.ZoneId;
|
|
import java.time.ZoneId;
|
|
|
import java.time.format.DateTimeFormatter;
|
|
import java.time.format.DateTimeFormatter;
|
|
|
import java.util.*;
|
|
import java.util.*;
|
|
|
|
|
+import java.util.concurrent.*;
|
|
|
|
|
+import java.util.concurrent.atomic.AtomicInteger;
|
|
|
|
|
|
|
|
@Slf4j
|
|
@Slf4j
|
|
|
@Service
|
|
@Service
|
|
@@ -28,14 +32,26 @@ public class WorkHoursCalcService {
|
|
|
@Autowired
|
|
@Autowired
|
|
|
private WHConf whConf;
|
|
private WHConf whConf;
|
|
|
|
|
|
|
|
|
|
+ @Autowired
|
|
|
|
|
+ private DDClient ddClient;
|
|
|
|
|
+
|
|
|
|
|
+ @Autowired
|
|
|
|
|
+ private DDClient_Contacts ddClient_contacts;
|
|
|
|
|
+
|
|
|
private static final int DAILY_HOURS = 8;
|
|
private static final int DAILY_HOURS = 8;
|
|
|
|
|
+ private static final int THREAD_POOL_SIZE = 10;
|
|
|
|
|
+ private static final int MAX_RETRY = 2;
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
* 主入口:计算指定月份每个工作日的应填报工时并写入宜搭(按天维度,每条记录8h)
|
|
* 主入口:计算指定月份每个工作日的应填报工时并写入宜搭(按天维度,每条记录8h)
|
|
|
*
|
|
*
|
|
|
* @param targetMonth 目标月份,null 则默认当前月
|
|
* @param targetMonth 目标月份,null 则默认当前月
|
|
|
*/
|
|
*/
|
|
|
- public void calculateAndSyncMonthlyHours(LocalDate targetMonth) {
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * @return Map{employeeCount, workingDays, success, fail}
|
|
|
|
|
+ */
|
|
|
|
|
+ public Map<String, Object> calculateAndSyncMonthlyHours(LocalDate targetMonth) {
|
|
|
|
|
+ Map<String, Object> stats = new LinkedHashMap<>();
|
|
|
if (targetMonth == null) {
|
|
if (targetMonth == null) {
|
|
|
targetMonth = LocalDate.now();
|
|
targetMonth = LocalDate.now();
|
|
|
}
|
|
}
|
|
@@ -43,51 +59,40 @@ public class WorkHoursCalcService {
|
|
|
int month = targetMonth.getMonthValue();
|
|
int month = targetMonth.getMonthValue();
|
|
|
log.info("开始计算{}年{}月应填报工时(按天维度)", year, month);
|
|
log.info("开始计算{}年{}月应填报工时(按天维度)", year, month);
|
|
|
|
|
|
|
|
- // 1. 查询项目档案,提取所有员工及经理映射
|
|
|
|
|
- Map<String, String> employeeManagerMap = queryAllEmployeesFromProjects();
|
|
|
|
|
- log.info("从项目档案获取到{}名员工", employeeManagerMap.size());
|
|
|
|
|
- if (employeeManagerMap.isEmpty()) {
|
|
|
|
|
- log.warn("项目档案中未查询到任何员工,跳过");
|
|
|
|
|
- return;
|
|
|
|
|
|
|
+ // 1. 查询人员档案全量,作为员工数据源 + 信息补充
|
|
|
|
|
+ Map<String, Map<String, Object>> personnelMap = queryAllPersonnelDetails();
|
|
|
|
|
+ log.info("人员档案共{}条", personnelMap.size());
|
|
|
|
|
+ stats.put("employeeCount", personnelMap.size());
|
|
|
|
|
+ if (personnelMap.isEmpty()) {
|
|
|
|
|
+ log.warn("人员档案中未查询到任何员工,跳过");
|
|
|
|
|
+ return stats;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 2. 查询节假日规则
|
|
|
|
|
|
|
+ // 2. 查询直属主管(仅内部员工调用钉钉 API)
|
|
|
|
|
+ Map<String, String> managerMap = queryManagerMap(personnelMap);
|
|
|
|
|
+ log.info("获取到{}名员工的直属主管", managerMap.size());
|
|
|
|
|
+ stats.put("managerCount", managerMap.size());
|
|
|
|
|
+
|
|
|
|
|
+ // 3. 查询节假日规则
|
|
|
Map<LocalDate, String> holidayRules = queryHolidayRules(String.valueOf(year));
|
|
Map<LocalDate, String> holidayRules = queryHolidayRules(String.valueOf(year));
|
|
|
log.info("{}年节假日规则共{}条", year, holidayRules.size());
|
|
log.info("{}年节假日规则共{}条", year, holidayRules.size());
|
|
|
|
|
|
|
|
- // 3. 计算当月所有工作日列表
|
|
|
|
|
|
|
+ // 4. 计算当月所有工作日列表
|
|
|
List<LocalDate> workingDays = getWorkingDays(year, month, holidayRules);
|
|
List<LocalDate> workingDays = getWorkingDays(year, month, holidayRules);
|
|
|
log.info("{}年{}月工作日{}天", year, month, workingDays.size());
|
|
log.info("{}年{}月工作日{}天", year, month, workingDays.size());
|
|
|
|
|
+ stats.put("workingDays", workingDays.size());
|
|
|
|
|
|
|
|
- // 4. 一次性查询人员档案全量,内存中按 userId 匹配
|
|
|
|
|
- Map<String, Map<String, Object>> personnelMap = queryAllPersonnelDetails();
|
|
|
|
|
- log.info("人员档案共{}条", personnelMap.size());
|
|
|
|
|
-
|
|
|
|
|
- // 5. 按员工 × 工作日逐条 upsert
|
|
|
|
|
- int successCount = 0;
|
|
|
|
|
- int failCount = 0;
|
|
|
|
|
- for (Map.Entry<String, String> entry : employeeManagerMap.entrySet()) {
|
|
|
|
|
- String employeeId = entry.getKey();
|
|
|
|
|
- String managerId = entry.getValue();
|
|
|
|
|
- // 从预加载的人员档案集合中 find 匹配,避免重复查询
|
|
|
|
|
- Map<String, Object> personnelInfo = personnelMap.getOrDefault(employeeId, Collections.emptyMap());
|
|
|
|
|
-
|
|
|
|
|
- for (LocalDate workDay : workingDays) {
|
|
|
|
|
- try {
|
|
|
|
|
- upsertDailyHours(employeeId, managerId, workDay, personnelInfo);
|
|
|
|
|
- successCount++;
|
|
|
|
|
- } catch (Exception e) {
|
|
|
|
|
- failCount++;
|
|
|
|
|
- log.error("员工{} {} 写入失败", employeeId, workDay, e);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ // 5. 多线程并发写入:按员工维度分任务
|
|
|
|
|
+ int[] counts = concurrentUpsert(personnelMap, managerMap, workingDays);
|
|
|
|
|
+ stats.put("success", counts[0]);
|
|
|
|
|
+ stats.put("fail", counts[1]);
|
|
|
log.info("应填报工时写入完成: 成功{}条, 失败{}条({}名员工 × {}个工作日)",
|
|
log.info("应填报工时写入完成: 成功{}条, 失败{}条({}名员工 × {}个工作日)",
|
|
|
- successCount, failCount, employeeManagerMap.size(), workingDays.size());
|
|
|
|
|
|
|
+ counts[0], counts[1], personnelMap.size(), workingDays.size());
|
|
|
|
|
+ return stats;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
- * 增量同步:查询最近 N 天内修改过的项目档案,仅同步这些项目中的员工
|
|
|
|
|
|
|
+ * 增量同步:查询最近 N 天内修改过的人员档案,仅同步变动员工的当月数据
|
|
|
*
|
|
*
|
|
|
* @param daysBack 回溯天数(默认2天)
|
|
* @param daysBack 回溯天数(默认2天)
|
|
|
*/
|
|
*/
|
|
@@ -99,174 +104,177 @@ public class WorkHoursCalcService {
|
|
|
// modifiedToTimeGMT 默认0点,需要加1天确保包含当天
|
|
// modifiedToTimeGMT 默认0点,需要加1天确保包含当天
|
|
|
LocalDate toDate = today.plusDays(1);
|
|
LocalDate toDate = today.plusDays(1);
|
|
|
|
|
|
|
|
- log.info("开始增量同步: 查询{}~{}修改的项目档案, 同步{}年{}月数据",
|
|
|
|
|
|
|
+ log.info("开始增量同步: 查询{}~{}修改的人员档案, 同步{}年{}月数据",
|
|
|
fromDate, toDate, year, month);
|
|
fromDate, toDate, year, month);
|
|
|
|
|
|
|
|
- // 1. 查询最近修改的项目档案,提取变动员工
|
|
|
|
|
- Map<String, String> employeeManagerMap = queryEmployeesFromRecentProjects(fromDate, toDate);
|
|
|
|
|
- log.info("增量: 从最近修改的项目档案获取到{}名员工", employeeManagerMap.size());
|
|
|
|
|
- if (employeeManagerMap.isEmpty()) {
|
|
|
|
|
- log.info("无项目档案变动,增量同步跳过");
|
|
|
|
|
|
|
+ // 1. 查询最近修改的人员档案
|
|
|
|
|
+ Map<String, Map<String, Object>> personnelMap = queryRecentPersonnelDetails(fromDate, toDate);
|
|
|
|
|
+ log.info("增量: 最近修改的人员档案{}条", personnelMap.size());
|
|
|
|
|
+ if (personnelMap.isEmpty()) {
|
|
|
|
|
+ log.info("无人员档案变动,增量同步跳过");
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 2~5 同全量逻辑
|
|
|
|
|
|
|
+ // 2. 查询直属主管(仅内部员工)
|
|
|
|
|
+ Map<String, String> managerMap = queryManagerMap(personnelMap);
|
|
|
|
|
+
|
|
|
|
|
+ // 3~4 查询节假日和工作日
|
|
|
Map<LocalDate, String> holidayRules = queryHolidayRules(String.valueOf(year));
|
|
Map<LocalDate, String> holidayRules = queryHolidayRules(String.valueOf(year));
|
|
|
List<LocalDate> workingDays = getWorkingDays(year, month, holidayRules);
|
|
List<LocalDate> workingDays = getWorkingDays(year, month, holidayRules);
|
|
|
log.info("{}年{}月工作日{}天", year, month, workingDays.size());
|
|
log.info("{}年{}月工作日{}天", year, month, workingDays.size());
|
|
|
|
|
|
|
|
- Map<String, Map<String, Object>> personnelMap = queryAllPersonnelDetails();
|
|
|
|
|
-
|
|
|
|
|
- int successCount = 0;
|
|
|
|
|
- int failCount = 0;
|
|
|
|
|
- for (Map.Entry<String, String> entry : employeeManagerMap.entrySet()) {
|
|
|
|
|
- String employeeId = entry.getKey();
|
|
|
|
|
- String managerId = entry.getValue();
|
|
|
|
|
- Map<String, Object> personnelInfo = personnelMap.getOrDefault(employeeId, Collections.emptyMap());
|
|
|
|
|
-
|
|
|
|
|
- for (LocalDate workDay : workingDays) {
|
|
|
|
|
- try {
|
|
|
|
|
- upsertDailyHours(employeeId, managerId, workDay, personnelInfo);
|
|
|
|
|
- successCount++;
|
|
|
|
|
- } catch (Exception e) {
|
|
|
|
|
- failCount++;
|
|
|
|
|
- log.error("增量-员工{} {} 写入失败", employeeId, workDay, e);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ // 5. 多线程并发写入
|
|
|
|
|
+ int[] counts = concurrentUpsert(personnelMap, managerMap, workingDays);
|
|
|
log.info("增量同步完成: 成功{}条, 失败{}条({}名员工 × {}个工作日)",
|
|
log.info("增量同步完成: 成功{}条, 失败{}条({}名员工 × {}个工作日)",
|
|
|
- successCount, failCount, employeeManagerMap.size(), workingDays.size());
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * 获取指定月份的所有工作日列表
|
|
|
|
|
- */
|
|
|
|
|
- private List<LocalDate> getWorkingDays(int year, int month, Map<LocalDate, String> holidayRules) {
|
|
|
|
|
- List<LocalDate> workingDays = new ArrayList<>();
|
|
|
|
|
- int daysInMonth = LocalDate.of(year, month, 1).lengthOfMonth();
|
|
|
|
|
-
|
|
|
|
|
- for (int day = 1; day <= daysInMonth; day++) {
|
|
|
|
|
- LocalDate current = LocalDate.of(year, month, day);
|
|
|
|
|
- DayOfWeek dow = current.getDayOfWeek();
|
|
|
|
|
- boolean isWeekend = (dow == DayOfWeek.SATURDAY || dow == DayOfWeek.SUNDAY);
|
|
|
|
|
- String holidayType = holidayRules.get(current);
|
|
|
|
|
-
|
|
|
|
|
- if ("调休".equals(holidayType)) {
|
|
|
|
|
- continue;
|
|
|
|
|
- } else if ("加班".equals(holidayType)) {
|
|
|
|
|
- workingDays.add(current);
|
|
|
|
|
- } else if (!isWeekend) {
|
|
|
|
|
- workingDays.add(current);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- return workingDays;
|
|
|
|
|
|
|
+ counts[0], counts[1], personnelMap.size(), workingDays.size());
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
- * 查询项目档案,提取所有唯一员工及对应经理
|
|
|
|
|
- *
|
|
|
|
|
- * @return Map<employeeUserId, managerUserId>
|
|
|
|
|
|
|
+ * 批量删除应填报工时全部数据(验证时使用,非日常流程)
|
|
|
*/
|
|
*/
|
|
|
@SuppressWarnings("unchecked")
|
|
@SuppressWarnings("unchecked")
|
|
|
- private Map<String, String> queryAllEmployeesFromProjects() {
|
|
|
|
|
- Map<String, String> employeeManagerMap = new LinkedHashMap<>();
|
|
|
|
|
|
|
+ public void deleteAllRequiredHours() {
|
|
|
String appType = whConf.getYidaAppType();
|
|
String appType = whConf.getYidaAppType();
|
|
|
String systemToken = whConf.getYidaSystemToken();
|
|
String systemToken = whConf.getYidaSystemToken();
|
|
|
|
|
|
|
|
int currentPage = 1;
|
|
int currentPage = 1;
|
|
|
int pageSize = YDConf.PAGE_SIZE_LIMIT;
|
|
int pageSize = YDConf.PAGE_SIZE_LIMIT;
|
|
|
- long totalCount;
|
|
|
|
|
|
|
+ int totalDeleted = 0;
|
|
|
|
|
+ int consecutiveFailures = 0;
|
|
|
|
|
+ final int MAX_CONSECUTIVE_FAILURES = 3;
|
|
|
|
|
|
|
|
- do {
|
|
|
|
|
|
|
+ while (true) {
|
|
|
DDR_New result = ydClient.queryData(YDParam.builder()
|
|
DDR_New result = ydClient.queryData(YDParam.builder()
|
|
|
.appType(appType)
|
|
.appType(appType)
|
|
|
.systemToken(systemToken)
|
|
.systemToken(systemToken)
|
|
|
- .formUuid(whConf.getFormUuidProject())
|
|
|
|
|
|
|
+ .formUuid(whConf.getFormUuidRequiredHours())
|
|
|
.currentPage(currentPage)
|
|
.currentPage(currentPage)
|
|
|
.pageSize(pageSize)
|
|
.pageSize(pageSize)
|
|
|
- .build(), YDConf.FORM_QUERY.retrieve_list_all);
|
|
|
|
|
|
|
+ .build(), YDConf.FORM_QUERY.retrieve_search_form);
|
|
|
|
|
|
|
|
- totalCount = result.getTotalCount();
|
|
|
|
|
|
|
+ long totalCount = result.getTotalCount();
|
|
|
List<Map> dataList = (List<Map>) result.getData();
|
|
List<Map> dataList = (List<Map>) result.getData();
|
|
|
if (dataList == null || dataList.isEmpty()) break;
|
|
if (dataList == null || dataList.isEmpty()) break;
|
|
|
|
|
|
|
|
|
|
+ List<String> instanceIds = new ArrayList<>();
|
|
|
for (Map item : dataList) {
|
|
for (Map item : dataList) {
|
|
|
- Map<String, Object> formData = (Map<String, Object>) item.get("formData");
|
|
|
|
|
- if (formData == null) continue;
|
|
|
|
|
|
|
+ Object instId = item.get("formInstanceId");
|
|
|
|
|
+ if (instId != null) {
|
|
|
|
|
+ instanceIds.add(String.valueOf(instId));
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- Object subTableObj = formData.get("tableField_mkowyn6d");
|
|
|
|
|
- if (subTableObj instanceof List) {
|
|
|
|
|
- List<Map<String, Object>> subTable = (List<Map<String, Object>>) subTableObj;
|
|
|
|
|
- for (Map<String, Object> row : subTable) {
|
|
|
|
|
- String empId = extractEmployeeId(row, "employeeField_mmbfe0ij");
|
|
|
|
|
- String mgrId = extractEmployeeId(row, "employeeField_mkoxpswf");
|
|
|
|
|
- if (empId != null && !empId.isEmpty()) {
|
|
|
|
|
- employeeManagerMap.putIfAbsent(empId, mgrId != null ? mgrId : "");
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ if (!instanceIds.isEmpty()) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ ydClient.operateData(YDParam.builder()
|
|
|
|
|
+ .appType(appType)
|
|
|
|
|
+ .systemToken(systemToken)
|
|
|
|
|
+ .formUuid(whConf.getFormUuidRequiredHours())
|
|
|
|
|
+ .formInstanceIdList(instanceIds)
|
|
|
|
|
+ .build(), YDConf.FORM_OPERATION.delete_batch);
|
|
|
|
|
+ totalDeleted += instanceIds.size();
|
|
|
|
|
+ consecutiveFailures = 0;
|
|
|
|
|
+ log.info("批量删除: 已删除{}条, 共{}条", totalDeleted, totalCount);
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ consecutiveFailures++;
|
|
|
|
|
+ log.error("批量删除失败(连续第{}次), 当前页{}", consecutiveFailures, currentPage, e);
|
|
|
|
|
+ if (consecutiveFailures >= MAX_CONSECUTIVE_FAILURES) {
|
|
|
|
|
+ log.error("连续失败{}次,终止批量删除", MAX_CONSECUTIVE_FAILURES);
|
|
|
|
|
+ break;
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
- currentPage++;
|
|
|
|
|
- } while ((long) (currentPage - 1) * pageSize < totalCount);
|
|
|
|
|
|
|
|
|
|
- return employeeManagerMap;
|
|
|
|
|
|
|
+ // 删除后总数变化,始终查第1页
|
|
|
|
|
+ if (totalDeleted >= totalCount) break;
|
|
|
|
|
+ }
|
|
|
|
|
+ log.info("应填报工时数据清空完成, 共删除{}条", totalDeleted);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ // ==================== 多线程并发写入 ====================
|
|
|
|
|
+
|
|
|
/**
|
|
/**
|
|
|
- * 查询指定时间范围内修改过的项目档案,提取员工及对应经理
|
|
|
|
|
|
|
+ * 按员工维度多线程并发 upsert,每个线程处理一个员工的所有工作日
|
|
|
*
|
|
*
|
|
|
- * @param fromDate 修改开始日期(含)
|
|
|
|
|
- * @param toDate 修改结束日期(不含,API 默认0点,需+1天)
|
|
|
|
|
- * @return Map<employeeUserId, managerUserId>
|
|
|
|
|
|
|
+ * @return int[]{successCount, failCount}
|
|
|
*/
|
|
*/
|
|
|
- @SuppressWarnings("unchecked")
|
|
|
|
|
- private Map<String, String> queryEmployeesFromRecentProjects(LocalDate fromDate, LocalDate toDate) {
|
|
|
|
|
- Map<String, String> employeeManagerMap = new LinkedHashMap<>();
|
|
|
|
|
- String appType = whConf.getYidaAppType();
|
|
|
|
|
- String systemToken = whConf.getYidaSystemToken();
|
|
|
|
|
- DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd");
|
|
|
|
|
|
|
+ private int[] concurrentUpsert(Map<String, Map<String, Object>> personnelMap,
|
|
|
|
|
+ Map<String, String> managerMap,
|
|
|
|
|
+ List<LocalDate> workingDays) {
|
|
|
|
|
+ AtomicInteger successCount = new AtomicInteger(0);
|
|
|
|
|
+ AtomicInteger failCount = new AtomicInteger(0);
|
|
|
|
|
+ ExecutorService executor = Executors.newFixedThreadPool(THREAD_POOL_SIZE);
|
|
|
|
|
+ try {
|
|
|
|
|
+ List<Future<?>> futures = new ArrayList<>();
|
|
|
|
|
+
|
|
|
|
|
+ for (Map.Entry<String, Map<String, Object>> entry : personnelMap.entrySet()) {
|
|
|
|
|
+ String empId = entry.getKey();
|
|
|
|
|
+ Map<String, Object> info = entry.getValue();
|
|
|
|
|
+ String mgrId = managerMap.get(empId);
|
|
|
|
|
+
|
|
|
|
|
+ futures.add(executor.submit(() -> {
|
|
|
|
|
+ for (LocalDate workDay : workingDays) {
|
|
|
|
|
+ boolean written = false;
|
|
|
|
|
+ for (int retry = 0; retry <= MAX_RETRY; retry++) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ upsertDailyHours(empId, mgrId, workDay, info);
|
|
|
|
|
+ written = true;
|
|
|
|
|
+ break;
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ if (retry < MAX_RETRY) {
|
|
|
|
|
+ log.warn("员工{} {} 写入失败(第{}次重试)", empId, workDay, retry + 1);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ log.error("员工{} {} 写入失败(已重试{}次)", empId, workDay, MAX_RETRY, e);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ if (written) {
|
|
|
|
|
+ successCount.incrementAndGet();
|
|
|
|
|
+ } else {
|
|
|
|
|
+ failCount.incrementAndGet();
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }));
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- int currentPage = 1;
|
|
|
|
|
- int pageSize = YDConf.PAGE_SIZE_LIMIT;
|
|
|
|
|
- long totalCount;
|
|
|
|
|
|
|
+ for (Future<?> f : futures) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ f.get();
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ log.error("线程执行异常", e);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ executor.shutdown();
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- do {
|
|
|
|
|
- DDR_New result = ydClient.queryData(YDParam.builder()
|
|
|
|
|
- .appType(appType)
|
|
|
|
|
- .systemToken(systemToken)
|
|
|
|
|
- .formUuid(whConf.getFormUuidProject())
|
|
|
|
|
- .currentPage(currentPage)
|
|
|
|
|
- .pageSize(pageSize)
|
|
|
|
|
- .modifiedFromTimeGMT(fromDate.format(fmt))
|
|
|
|
|
- .modifiedToTimeGMT(toDate.format(fmt))
|
|
|
|
|
- .build(), YDConf.FORM_QUERY.retrieve_list_all);
|
|
|
|
|
|
|
+ return new int[]{successCount.get(), failCount.get()};
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- totalCount = result.getTotalCount();
|
|
|
|
|
- List<Map> dataList = (List<Map>) result.getData();
|
|
|
|
|
- if (dataList == null || dataList.isEmpty()) break;
|
|
|
|
|
|
|
+ // ==================== 数据查询 ====================
|
|
|
|
|
|
|
|
- log.info("增量: 第{}页查到{}条修改的项目档案", currentPage, dataList.size());
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 获取指定月份的所有工作日列表
|
|
|
|
|
+ */
|
|
|
|
|
+ private List<LocalDate> getWorkingDays(int year, int month, Map<LocalDate, String> holidayRules) {
|
|
|
|
|
+ List<LocalDate> workingDays = new ArrayList<>();
|
|
|
|
|
+ int daysInMonth = LocalDate.of(year, month, 1).lengthOfMonth();
|
|
|
|
|
|
|
|
- for (Map item : dataList) {
|
|
|
|
|
- Map<String, Object> formData = (Map<String, Object>) item.get("formData");
|
|
|
|
|
- if (formData == null) continue;
|
|
|
|
|
|
|
+ for (int day = 1; day <= daysInMonth; day++) {
|
|
|
|
|
+ LocalDate current = LocalDate.of(year, month, day);
|
|
|
|
|
+ DayOfWeek dow = current.getDayOfWeek();
|
|
|
|
|
+ boolean isWeekend = (dow == DayOfWeek.SATURDAY || dow == DayOfWeek.SUNDAY);
|
|
|
|
|
+ String holidayType = holidayRules.get(current);
|
|
|
|
|
|
|
|
- Object subTableObj = formData.get("tableField_mkowyn6d");
|
|
|
|
|
- if (subTableObj instanceof List) {
|
|
|
|
|
- List<Map<String, Object>> subTable = (List<Map<String, Object>>) subTableObj;
|
|
|
|
|
- for (Map<String, Object> row : subTable) {
|
|
|
|
|
- String empId = extractEmployeeId(row, "employeeField_mmbfe0ij");
|
|
|
|
|
- String mgrId = extractEmployeeId(row, "employeeField_mkoxpswf");
|
|
|
|
|
- if (empId != null && !empId.isEmpty()) {
|
|
|
|
|
- employeeManagerMap.putIfAbsent(empId, mgrId != null ? mgrId : "");
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ if ("调休".equals(holidayType)) {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ } else if ("加班".equals(holidayType)) {
|
|
|
|
|
+ workingDays.add(current);
|
|
|
|
|
+ } else if (!isWeekend) {
|
|
|
|
|
+ workingDays.add(current);
|
|
|
}
|
|
}
|
|
|
- currentPage++;
|
|
|
|
|
- } while ((long) (currentPage - 1) * pageSize < totalCount);
|
|
|
|
|
-
|
|
|
|
|
- return employeeManagerMap;
|
|
|
|
|
|
|
+ }
|
|
|
|
|
+ return workingDays;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
@@ -348,7 +356,6 @@ public class WorkHoursCalcService {
|
|
|
info.put("textField_mh8xhqc1", formData.get("textField_mh8xhqc1"));
|
|
info.put("textField_mh8xhqc1", formData.get("textField_mh8xhqc1"));
|
|
|
info.put("departmentSelectField_mkow4ydr", formData.get("departmentSelectField_mkow4ydr_id"));
|
|
info.put("departmentSelectField_mkow4ydr", formData.get("departmentSelectField_mkow4ydr_id"));
|
|
|
info.put("textField_mmekrcji", formData.get("textField_mmekrcji"));
|
|
info.put("textField_mmekrcji", formData.get("textField_mmekrcji"));
|
|
|
- info.put("employeeField_mh8xhqc3", formData.get("employeeField_mh8xhqc3_id"));
|
|
|
|
|
personnelMap.put(empId, info);
|
|
personnelMap.put(empId, info);
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
@@ -358,6 +365,100 @@ public class WorkHoursCalcService {
|
|
|
return personnelMap;
|
|
return personnelMap;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 查询指定时间范围内修改过的人员档案(增量用)
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param fromDate 修改开始日期(含)
|
|
|
|
|
+ * @param toDate 修改结束日期(不含,API 默认0点,需+1天)
|
|
|
|
|
+ */
|
|
|
|
|
+ @SuppressWarnings("unchecked")
|
|
|
|
|
+ private Map<String, Map<String, Object>> queryRecentPersonnelDetails(LocalDate fromDate, LocalDate toDate) {
|
|
|
|
|
+ Map<String, Map<String, Object>> personnelMap = new HashMap<>();
|
|
|
|
|
+ String appType = whConf.getYidaAppType();
|
|
|
|
|
+ String systemToken = whConf.getYidaSystemToken();
|
|
|
|
|
+ DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd");
|
|
|
|
|
+
|
|
|
|
|
+ int currentPage = 1;
|
|
|
|
|
+ int pageSize = YDConf.PAGE_SIZE_LIMIT;
|
|
|
|
|
+ long totalCount;
|
|
|
|
|
+
|
|
|
|
|
+ do {
|
|
|
|
|
+ DDR_New result = ydClient.queryData(YDParam.builder()
|
|
|
|
|
+ .appType(appType)
|
|
|
|
|
+ .systemToken(systemToken)
|
|
|
|
|
+ .formUuid(whConf.getFormUuidPersonnel())
|
|
|
|
|
+ .currentPage(currentPage)
|
|
|
|
|
+ .pageSize(pageSize)
|
|
|
|
|
+ .modifiedFromTimeGMT(fromDate.format(fmt))
|
|
|
|
|
+ .modifiedToTimeGMT(toDate.format(fmt))
|
|
|
|
|
+ .build(), YDConf.FORM_QUERY.retrieve_search_form);
|
|
|
|
|
+
|
|
|
|
|
+ totalCount = result.getTotalCount();
|
|
|
|
|
+ List<Map> dataList = (List<Map>) result.getData();
|
|
|
|
|
+ if (dataList == null || dataList.isEmpty()) break;
|
|
|
|
|
+
|
|
|
|
|
+ log.info("增量: 第{}页查到{}条修改的人员档案", currentPage, dataList.size());
|
|
|
|
|
+
|
|
|
|
|
+ for (Map item : dataList) {
|
|
|
|
|
+ Map<String, Object> formData = (Map<String, Object>) item.get("formData");
|
|
|
|
|
+ if (formData == null) continue;
|
|
|
|
|
+
|
|
|
|
|
+ String empId = extractEmployeeId(formData, "employeeField_mkow4ydp");
|
|
|
|
|
+ if (empId != null && !empId.isEmpty()) {
|
|
|
|
|
+ Map<String, Object> info = new HashMap<>();
|
|
|
|
|
+ info.put("radioField_mkow4ydo", formData.get("radioField_mkow4ydo"));
|
|
|
|
|
+ info.put("textField_mh8xhqc1", formData.get("textField_mh8xhqc1"));
|
|
|
|
|
+ info.put("departmentSelectField_mkow4ydr", formData.get("departmentSelectField_mkow4ydr_id"));
|
|
|
|
|
+ info.put("textField_mmekrcji", formData.get("textField_mmekrcji"));
|
|
|
|
|
+ personnelMap.put(empId, info);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ currentPage++;
|
|
|
|
|
+ } while ((long) (currentPage - 1) * pageSize < totalCount);
|
|
|
|
|
+
|
|
|
|
|
+ return personnelMap;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 批量查询直属主管(仅内部员工调用钉钉 API,外部员工跳过避免无效查询)
|
|
|
|
|
+ *
|
|
|
|
|
+ * @return Map<employeeId, managerUserId>
|
|
|
|
|
+ */
|
|
|
|
|
+ @SuppressWarnings("unchecked")
|
|
|
|
|
+ private Map<String, String> queryManagerMap(Map<String, Map<String, Object>> personnelMap) {
|
|
|
|
|
+ String accessToken = ddClient.getAccessToken();
|
|
|
|
|
+ Map<String, String> managerMap = new HashMap<>();
|
|
|
|
|
+ int skipCount = 0;
|
|
|
|
|
+
|
|
|
|
|
+ for (Map.Entry<String, Map<String, Object>> entry : personnelMap.entrySet()) {
|
|
|
|
|
+ String empId = entry.getKey();
|
|
|
|
|
+ Object attr = entry.getValue().get("radioField_mkow4ydo");
|
|
|
|
|
+
|
|
|
|
|
+ // 仅内部员工查询直属主管,外部员工跳过
|
|
|
|
|
+ if (!"内部".equals(String.valueOf(attr))) {
|
|
|
|
|
+ skipCount++;
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ Map userInfo = ddClient_contacts.getUserInfoById(accessToken, empId);
|
|
|
|
|
+ if (userInfo != null && userInfo.get("manager_userid") != null) {
|
|
|
|
|
+ String mgrId = String.valueOf(userInfo.get("manager_userid"));
|
|
|
|
|
+ if (!mgrId.isEmpty() && !"null".equals(mgrId)) {
|
|
|
|
|
+ managerMap.put(empId, mgrId);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ log.warn("获取员工{}直属主管失败: {}", empId, e.getMessage());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ log.info("直属主管查询完成: 内部员工{}人, 外部跳过{}人", managerMap.size(), skipCount);
|
|
|
|
|
+ return managerMap;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // ==================== 数据写入 ====================
|
|
|
|
|
+
|
|
|
/**
|
|
/**
|
|
|
* 按员工+日期 upsert 写入单日应填报工时(8h)
|
|
* 按员工+日期 upsert 写入单日应填报工时(8h)
|
|
|
*/
|
|
*/
|
|
@@ -373,11 +474,9 @@ public class WorkHoursCalcService {
|
|
|
formData.put("dateField_mmd8onl5", dayTimestamp);
|
|
formData.put("dateField_mmd8onl5", dayTimestamp);
|
|
|
formData.put("numberField_mmd8onl6", DAILY_HOURS);
|
|
formData.put("numberField_mmd8onl6", DAILY_HOURS);
|
|
|
|
|
|
|
|
- // 经理:优先项目档案子表经理,兜底人员档案主管
|
|
|
|
|
|
|
+ // 直属主管(来自钉钉用户详情 manager_userid)
|
|
|
if (managerId != null && !managerId.isEmpty()) {
|
|
if (managerId != null && !managerId.isEmpty()) {
|
|
|
formData.put("employeeField_mh8xhqc3", Arrays.asList(managerId));
|
|
formData.put("employeeField_mh8xhqc3", Arrays.asList(managerId));
|
|
|
- } else if (personnelInfo.get("employeeField_mh8xhqc3") instanceof List) {
|
|
|
|
|
- formData.put("employeeField_mh8xhqc3", personnelInfo.get("employeeField_mh8xhqc3"));
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// 人员档案补充字段
|
|
// 人员档案补充字段
|