|
@@ -0,0 +1,477 @@
|
|
|
|
|
+package com.malk.service.workhours;
|
|
|
|
|
+
|
|
|
|
|
+import com.alibaba.fastjson.JSON;
|
|
|
|
|
+import com.alibaba.fastjson.JSONObject;
|
|
|
|
|
+import com.malk.server.aliwork.YDConf;
|
|
|
|
|
+import com.malk.server.aliwork.YDParam;
|
|
|
|
|
+import com.malk.server.dingtalk.DDR_New;
|
|
|
|
|
+import com.malk.server.workhours.WHConf;
|
|
|
|
|
+import com.malk.service.aliwork.YDClient;
|
|
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
|
|
+import org.springframework.beans.factory.annotation.Autowired;
|
|
|
|
|
+import org.springframework.stereotype.Service;
|
|
|
|
|
+
|
|
|
|
|
+import java.time.DayOfWeek;
|
|
|
|
|
+import java.time.Instant;
|
|
|
|
|
+import java.time.LocalDate;
|
|
|
|
|
+import java.time.ZoneId;
|
|
|
|
|
+import java.time.format.DateTimeFormatter;
|
|
|
|
|
+import java.util.*;
|
|
|
|
|
+
|
|
|
|
|
+@Slf4j
|
|
|
|
|
+@Service
|
|
|
|
|
+public class WorkHoursCalcService {
|
|
|
|
|
+
|
|
|
|
|
+ @Autowired
|
|
|
|
|
+ private YDClient ydClient;
|
|
|
|
|
+
|
|
|
|
|
+ @Autowired
|
|
|
|
|
+ private WHConf whConf;
|
|
|
|
|
+
|
|
|
|
|
+ private static final int DAILY_HOURS = 8;
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 主入口:计算指定月份每个工作日的应填报工时并写入宜搭(按天维度,每条记录8h)
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param targetMonth 目标月份,null 则默认当前月
|
|
|
|
|
+ */
|
|
|
|
|
+ public void calculateAndSyncMonthlyHours(LocalDate targetMonth) {
|
|
|
|
|
+ if (targetMonth == null) {
|
|
|
|
|
+ targetMonth = LocalDate.now();
|
|
|
|
|
+ }
|
|
|
|
|
+ int year = targetMonth.getYear();
|
|
|
|
|
+ int month = targetMonth.getMonthValue();
|
|
|
|
|
+ log.info("开始计算{}年{}月应填报工时(按天维度)", year, month);
|
|
|
|
|
+
|
|
|
|
|
+ // 1. 查询项目档案,提取所有员工及经理映射
|
|
|
|
|
+ Map<String, String> employeeManagerMap = queryAllEmployeesFromProjects();
|
|
|
|
|
+ log.info("从项目档案获取到{}名员工", employeeManagerMap.size());
|
|
|
|
|
+ if (employeeManagerMap.isEmpty()) {
|
|
|
|
|
+ log.warn("项目档案中未查询到任何员工,跳过");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 2. 查询节假日规则
|
|
|
|
|
+ Map<LocalDate, String> holidayRules = queryHolidayRules(String.valueOf(year));
|
|
|
|
|
+ log.info("{}年节假日规则共{}条", year, holidayRules.size());
|
|
|
|
|
+
|
|
|
|
|
+ // 3. 计算当月所有工作日列表
|
|
|
|
|
+ List<LocalDate> workingDays = getWorkingDays(year, month, holidayRules);
|
|
|
|
|
+ log.info("{}年{}月工作日{}天", year, month, 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);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ log.info("应填报工时写入完成: 成功{}条, 失败{}条({}名员工 × {}个工作日)",
|
|
|
|
|
+ successCount, failCount, employeeManagerMap.size(), workingDays.size());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 增量同步:查询最近 N 天内修改过的项目档案,仅同步这些项目中的员工
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param daysBack 回溯天数(默认2天)
|
|
|
|
|
+ */
|
|
|
|
|
+ public void incrementalSync(int daysBack) {
|
|
|
|
|
+ LocalDate today = LocalDate.now();
|
|
|
|
|
+ int year = today.getYear();
|
|
|
|
|
+ int month = today.getMonthValue();
|
|
|
|
|
+ LocalDate fromDate = today.minusDays(daysBack);
|
|
|
|
|
+ // modifiedToTimeGMT 默认0点,需要加1天确保包含当天
|
|
|
|
|
+ LocalDate toDate = today.plusDays(1);
|
|
|
|
|
+
|
|
|
|
|
+ log.info("开始增量同步: 查询{}~{}修改的项目档案, 同步{}年{}月数据",
|
|
|
|
|
+ fromDate, toDate, year, month);
|
|
|
|
|
+
|
|
|
|
|
+ // 1. 查询最近修改的项目档案,提取变动员工
|
|
|
|
|
+ Map<String, String> employeeManagerMap = queryEmployeesFromRecentProjects(fromDate, toDate);
|
|
|
|
|
+ log.info("增量: 从最近修改的项目档案获取到{}名员工", employeeManagerMap.size());
|
|
|
|
|
+ if (employeeManagerMap.isEmpty()) {
|
|
|
|
|
+ log.info("无项目档案变动,增量同步跳过");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 2~5 同全量逻辑
|
|
|
|
|
+ Map<LocalDate, String> holidayRules = queryHolidayRules(String.valueOf(year));
|
|
|
|
|
+ List<LocalDate> workingDays = getWorkingDays(year, month, holidayRules);
|
|
|
|
|
+ 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);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ 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;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 查询项目档案,提取所有唯一员工及对应经理
|
|
|
|
|
+ *
|
|
|
|
|
+ * @return Map<employeeUserId, managerUserId>
|
|
|
|
|
+ */
|
|
|
|
|
+ @SuppressWarnings("unchecked")
|
|
|
|
|
+ private Map<String, String> queryAllEmployeesFromProjects() {
|
|
|
|
|
+ Map<String, String> employeeManagerMap = new LinkedHashMap<>();
|
|
|
|
|
+ String appType = whConf.getYidaAppType();
|
|
|
|
|
+ String systemToken = whConf.getYidaSystemToken();
|
|
|
|
|
+
|
|
|
|
|
+ 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.getFormUuidProject())
|
|
|
|
|
+ .currentPage(currentPage)
|
|
|
|
|
+ .pageSize(pageSize)
|
|
|
|
|
+ .build(), YDConf.FORM_QUERY.retrieve_list_all);
|
|
|
|
|
+
|
|
|
|
|
+ totalCount = result.getTotalCount();
|
|
|
|
|
+ List<Map> dataList = (List<Map>) result.getData();
|
|
|
|
|
+ if (dataList == null || dataList.isEmpty()) break;
|
|
|
|
|
+
|
|
|
|
|
+ for (Map item : dataList) {
|
|
|
|
|
+ Map<String, Object> formData = (Map<String, Object>) item.get("formData");
|
|
|
|
|
+ if (formData == null) continue;
|
|
|
|
|
+
|
|
|
|
|
+ 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 : "");
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ currentPage++;
|
|
|
|
|
+ } while ((long) (currentPage - 1) * pageSize < totalCount);
|
|
|
|
|
+
|
|
|
|
|
+ return employeeManagerMap;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 查询指定时间范围内修改过的项目档案,提取员工及对应经理
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param fromDate 修改开始日期(含)
|
|
|
|
|
+ * @param toDate 修改结束日期(不含,API 默认0点,需+1天)
|
|
|
|
|
+ * @return Map<employeeUserId, managerUserId>
|
|
|
|
|
+ */
|
|
|
|
|
+ @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");
|
|
|
|
|
+
|
|
|
|
|
+ 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.getFormUuidProject())
|
|
|
|
|
+ .currentPage(currentPage)
|
|
|
|
|
+ .pageSize(pageSize)
|
|
|
|
|
+ .modifiedFromTimeGMT(fromDate.format(fmt))
|
|
|
|
|
+ .modifiedToTimeGMT(toDate.format(fmt))
|
|
|
|
|
+ .build(), YDConf.FORM_QUERY.retrieve_list_all);
|
|
|
|
|
+
|
|
|
|
|
+ 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;
|
|
|
|
|
+
|
|
|
|
|
+ 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 : "");
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ currentPage++;
|
|
|
|
|
+ } while ((long) (currentPage - 1) * pageSize < totalCount);
|
|
|
|
|
+
|
|
|
|
|
+ return employeeManagerMap;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 查询节假日规则(按年份)
|
|
|
|
|
+ *
|
|
|
|
|
+ * @return Map<日期, 类型("调休"/"加班")>
|
|
|
|
|
+ */
|
|
|
|
|
+ @SuppressWarnings("unchecked")
|
|
|
|
|
+ private Map<LocalDate, String> queryHolidayRules(String year) {
|
|
|
|
|
+ Map<LocalDate, String> rules = new HashMap<>();
|
|
|
|
|
+ String appType = whConf.getYidaAppType();
|
|
|
|
|
+ String systemToken = whConf.getYidaSystemToken();
|
|
|
|
|
+
|
|
|
|
|
+ Map<String, String> searchField = new HashMap<>();
|
|
|
|
|
+ searchField.put("textField_mn76yxgb", year);
|
|
|
|
|
+
|
|
|
|
|
+ List<Map> dataList = (List<Map>) ydClient.queryData(YDParam.builder()
|
|
|
|
|
+ .appType(appType)
|
|
|
|
|
+ .systemToken(systemToken)
|
|
|
|
|
+ .formUuid(whConf.getFormUuidHoliday())
|
|
|
|
|
+ .searchFieldJson(JSON.toJSONString(searchField))
|
|
|
|
|
+ .pageSize(YDConf.PAGE_SIZE_LIMIT)
|
|
|
|
|
+ .build(), YDConf.FORM_QUERY.retrieve_search_form).getData();
|
|
|
|
|
+
|
|
|
|
|
+ if (dataList == null) return rules;
|
|
|
|
|
+
|
|
|
|
|
+ for (Map item : dataList) {
|
|
|
|
|
+ Map<String, Object> formData = (Map<String, Object>) item.get("formData");
|
|
|
|
|
+ if (formData == null) continue;
|
|
|
|
|
+
|
|
|
|
|
+ Object dateObj = formData.get("datefield_lKuSAJ2y");
|
|
|
|
|
+ Object typeObj = formData.get("radiofield_sEPqCTex");
|
|
|
|
|
+
|
|
|
|
|
+ if (dateObj != null && typeObj != null) {
|
|
|
|
|
+ LocalDate date = parseToLocalDate(dateObj);
|
|
|
|
|
+ if (date != null) {
|
|
|
|
|
+ rules.put(date, typeObj.toString());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return rules;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 一次性查询人员档案全量,构建 userId → 员工信息 映射
|
|
|
|
|
+ * ppExt: 预加载到内存,后续通过 Map.get() 匹配,避免逐个员工重复查询 API
|
|
|
|
|
+ */
|
|
|
|
|
+ @SuppressWarnings("unchecked")
|
|
|
|
|
+ private Map<String, Map<String, Object>> queryAllPersonnelDetails() {
|
|
|
|
|
+ Map<String, Map<String, Object>> personnelMap = new HashMap<>();
|
|
|
|
|
+ String appType = whConf.getYidaAppType();
|
|
|
|
|
+ String systemToken = whConf.getYidaSystemToken();
|
|
|
|
|
+
|
|
|
|
|
+ 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)
|
|
|
|
|
+ .build(), YDConf.FORM_QUERY.retrieve_search_form);
|
|
|
|
|
+
|
|
|
|
|
+ totalCount = result.getTotalCount();
|
|
|
|
|
+ List<Map> dataList = (List<Map>) result.getData();
|
|
|
|
|
+ if (dataList == null || dataList.isEmpty()) break;
|
|
|
|
|
+
|
|
|
|
|
+ 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"));
|
|
|
|
|
+ info.put("employeeField_mh8xhqc3", formData.get("employeeField_mh8xhqc3_id"));
|
|
|
|
|
+ personnelMap.put(empId, info);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ currentPage++;
|
|
|
|
|
+ } while ((long) (currentPage - 1) * pageSize < totalCount);
|
|
|
|
|
+
|
|
|
|
|
+ return personnelMap;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 按员工+日期 upsert 写入单日应填报工时(8h)
|
|
|
|
|
+ */
|
|
|
|
|
+ private void upsertDailyHours(String employeeId, String managerId, LocalDate workDay,
|
|
|
|
|
+ Map<String, Object> personnelInfo) {
|
|
|
|
|
+ String appType = whConf.getYidaAppType();
|
|
|
|
|
+ String systemToken = whConf.getYidaSystemToken();
|
|
|
|
|
+ long dayTimestamp = workDay.atStartOfDay(ZoneId.systemDefault()).toInstant().toEpochMilli();
|
|
|
|
|
+
|
|
|
|
|
+ // 构建写入数据
|
|
|
|
|
+ JSONObject formData = new JSONObject();
|
|
|
|
|
+ formData.put("employeeField_mmd8onl4", Arrays.asList(employeeId));
|
|
|
|
|
+ formData.put("dateField_mmd8onl5", dayTimestamp);
|
|
|
|
|
+ formData.put("numberField_mmd8onl6", DAILY_HOURS);
|
|
|
|
|
+
|
|
|
|
|
+ // 经理:优先项目档案子表经理,兜底人员档案主管
|
|
|
|
|
+ if (managerId != null && !managerId.isEmpty()) {
|
|
|
|
|
+ formData.put("employeeField_mh8xhqc3", Arrays.asList(managerId));
|
|
|
|
|
+ } else if (personnelInfo.get("employeeField_mh8xhqc3") instanceof List) {
|
|
|
|
|
+ formData.put("employeeField_mh8xhqc3", personnelInfo.get("employeeField_mh8xhqc3"));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 人员档案补充字段
|
|
|
|
|
+ if (!personnelInfo.isEmpty()) {
|
|
|
|
|
+ putIfNotNull(formData, "textField_mh8xhqc1", personnelInfo.get("textField_mh8xhqc1"));
|
|
|
|
|
+ putIfNotNull(formData, "radioField_mkow4ydo", personnelInfo.get("radioField_mkow4ydo"));
|
|
|
|
|
+ // 部门选择器字段:需要数组格式 ["deptId"],与成员字段格式一致
|
|
|
|
|
+ Object deptValue = personnelInfo.get("departmentSelectField_mkow4ydr");
|
|
|
|
|
+ if (deptValue != null) {
|
|
|
|
|
+ if (deptValue instanceof List) {
|
|
|
|
|
+ formData.put("departmentSelectField_mkow4ydr", deptValue);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ String deptStr = String.valueOf(deptValue).trim();
|
|
|
|
|
+ if (!deptStr.isEmpty()) {
|
|
|
|
|
+ formData.put("departmentSelectField_mkow4ydr", Arrays.asList(deptStr));
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ putIfNotNull(formData, "textField_mmekrcji", personnelInfo.get("textField_mmekrcji"));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // fixme: 日期组件在 searchCondition 中必须使用数组格式 [start, end]
|
|
|
|
|
+ JSONObject searchCondition = new JSONObject();
|
|
|
|
|
+ searchCondition.put("employeeField_mmd8onl4", employeeId);
|
|
|
|
|
+ searchCondition.put("dateField_mmd8onl5", Arrays.asList(String.valueOf(dayTimestamp), String.valueOf(dayTimestamp)));
|
|
|
|
|
+
|
|
|
|
|
+ YDParam param = YDParam.builder()
|
|
|
|
|
+ .appType(appType)
|
|
|
|
|
+ .systemToken(systemToken)
|
|
|
|
|
+ .formUuid(whConf.getFormUuidRequiredHours())
|
|
|
|
|
+ .searchCondition(JSON.toJSONString(searchCondition))
|
|
|
|
|
+ .formDataJson(formData.toJSONString())
|
|
|
|
|
+ .noExecuteExpression(false)
|
|
|
|
|
+ .build();
|
|
|
|
|
+
|
|
|
|
|
+ Object result = ydClient.operateData(param, YDConf.FORM_OPERATION.upsert);
|
|
|
|
|
+ log.debug("员工{} {} 写入8h,结果: {}", employeeId, workDay, JSON.toJSONString(result));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // ==================== 工具方法 ====================
|
|
|
|
|
+
|
|
|
|
|
+ private void putIfNotNull(JSONObject target, String key, Object value) {
|
|
|
|
|
+ if (value != null && !"".equals(String.valueOf(value).trim())) {
|
|
|
|
|
+ target.put(key, value);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 从 formData 中提取员工字段的 userId
|
|
|
|
|
+ * ppExt: 员工字段返回格式可能为 userId字符串、JSON数组字符串 或 _id 后缀字段
|
|
|
|
|
+ */
|
|
|
|
|
+ @SuppressWarnings("unchecked")
|
|
|
|
|
+ private String extractEmployeeId(Map<String, Object> data, String fieldId) {
|
|
|
|
|
+ Object idValue = data.get(fieldId + "_id");
|
|
|
|
|
+ if (idValue == null) {
|
|
|
|
|
+ idValue = data.get(fieldId);
|
|
|
|
|
+ }
|
|
|
|
|
+ if (idValue == null) return null;
|
|
|
|
|
+
|
|
|
|
|
+ if (idValue instanceof List) {
|
|
|
|
|
+ List list = (List) idValue;
|
|
|
|
|
+ return list.isEmpty() ? null : String.valueOf(list.get(0));
|
|
|
|
|
+ }
|
|
|
|
|
+ String str = String.valueOf(idValue);
|
|
|
|
|
+ if (str.startsWith("[")) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ List<String> list = JSON.parseArray(str, String.class);
|
|
|
|
|
+ return list.isEmpty() ? null : list.get(0);
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return str;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 将日期对象转为 LocalDate(支持时间戳和日期字符串)
|
|
|
|
|
+ */
|
|
|
|
|
+ private LocalDate parseToLocalDate(Object dateObj) {
|
|
|
|
|
+ if (dateObj == null) return null;
|
|
|
|
|
+ try {
|
|
|
|
|
+ String dateStr = String.valueOf(dateObj).trim();
|
|
|
|
|
+ if (dateStr.isEmpty()) return null;
|
|
|
|
|
+
|
|
|
|
|
+ if (dateStr.matches("\\d+")) {
|
|
|
|
|
+ long timestamp = Long.parseLong(dateStr);
|
|
|
|
|
+ return Instant.ofEpochMilli(timestamp).atZone(ZoneId.systemDefault()).toLocalDate();
|
|
|
|
|
+ }
|
|
|
|
|
+ if (dateStr.length() >= 10) {
|
|
|
|
|
+ return LocalDate.parse(dateStr.substring(0, 10));
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ log.warn("解析日期失败: {}", dateObj, e);
|
|
|
|
|
+ }
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|