|
|
@@ -0,0 +1,977 @@
|
|
|
+package com.example.integratedingtalkoa.service.impl;
|
|
|
+
|
|
|
+
|
|
|
+import com.example.integratedingtalkoa.config.DDConfig;
|
|
|
+import com.example.integratedingtalkoa.service.IntegrateCY;
|
|
|
+import com.example.integratedingtalkoa.util.TimeUtil;
|
|
|
+import com.google.gson.*;
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
+import org.springframework.beans.factory.annotation.Autowired;
|
|
|
+import org.springframework.stereotype.Service;
|
|
|
+
|
|
|
+import java.util.ArrayList;
|
|
|
+import java.util.Arrays;
|
|
|
+import java.util.Collections;
|
|
|
+import java.util.HashMap;
|
|
|
+import java.util.HashSet;
|
|
|
+import java.util.List;
|
|
|
+import java.util.Map;
|
|
|
+import java.util.Set;
|
|
|
+import java.util.function.Function;
|
|
|
+
|
|
|
+
|
|
|
+/**
|
|
|
+ * 钉钉 OA 与氚云业务集成服务实现类。
|
|
|
+ *
|
|
|
+ * <p>负责组装氚云 OpenApi 所需的业务参数,并调用氚云客户端完成外出单据的
|
|
|
+ * 新增、查询、批量创建、更新等操作。</p>
|
|
|
+ */
|
|
|
+@Slf4j
|
|
|
+@Service
|
|
|
+public class IntegrateCYImpl implements IntegrateCY {
|
|
|
+ private static final String BUSINESS_TRIP_PROCESS_CODE = "PROC-FF6Y696SO2-C63CMT87Q01PP2VN8V5D2-OVJZHHQI-45";
|
|
|
+ private static final String BUSINESS_TRIP_SCHEMA_CODE = "D000867smef05bd07fc4b43dfb179b72eb8fccf1b";
|
|
|
+ private static final String BUSINESS_TRIP_PROCESS_INSTANCE_ID_FIELD_CODE = "F0000018";
|
|
|
+ private static final String BUSINESS_TRIP_REASON_FIELD_CODE = "F0000004";
|
|
|
+ private static final String BUSINESS_TRIP_ITINERARY_FIELD_CODE = "D00086747ee2d03c3b3438ca4c41c7761451444";
|
|
|
+ private static final String BUSINESS_TRIP_DAYS_FIELD_CODE = "F0000001";
|
|
|
+ private static final String BUSINESS_TRIP_TEMPORARY_FIELD_CODE = "F0000017";
|
|
|
+ private static final String BUSINESS_TRIP_REMARK_FIELD_CODE = "F0000012";
|
|
|
+ private static final String BUSINESS_TRIP_COST_CENTER_FIELD_CODE = "F0000013";
|
|
|
+ private static final String BUSINESS_TRIP_INVOICE_TITLE_FIELD_CODE = "F0000014";
|
|
|
+ private static final String BUSINESS_TRIP_COMPANION_FIELD_CODE = "F0000015";
|
|
|
+ private static final String BUSINESS_TRIP_DEPARTMENT_FIELD_CODE = "F0000016";
|
|
|
+ private static final String BUSINESS_TRIP_APPLICANT_FIELD_CODE = "F0000019";
|
|
|
+ private static final String BUSINESS_TRIP_APPLY_DATE_FIELD_CODE = "F0000021";
|
|
|
+
|
|
|
+ private final Gson gson = new Gson();
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private h3yunClientImpl h3yunClientImpl;
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private DingTalkOAImpl dingTalkOAImpl;
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private DDConfig ddConfig;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 同步钉钉 OA 外出审批实例到氚云。
|
|
|
+ *
|
|
|
+ * <p>同步流程:</p>
|
|
|
+ * <ol>
|
|
|
+ * <li>获取钉钉 accessToken;</li>
|
|
|
+ * <li>查询最近一天已完成的外出审批实例 ID;</li>
|
|
|
+ * <li>根据实例 ID 查询钉钉审批详情;</li>
|
|
|
+ * <li>转换为氚云外出表单字段;</li>
|
|
|
+ * <li>根据钉钉审批实例 ID 查询氚云是否已存在;</li>
|
|
|
+ * <li>不存在则新增,已存在则更新。</li>
|
|
|
+ * </ol>
|
|
|
+ *
|
|
|
+ * @return 同步执行结果说明
|
|
|
+ */
|
|
|
+// public String syneDDOAProcessInstancesToH3yun() {
|
|
|
+//
|
|
|
+// //首先获取钉钉OA中的数据
|
|
|
+// String accessToken = dingTalkOAImpl.getAccessToken();
|
|
|
+//
|
|
|
+// Long nextToken = 0L;
|
|
|
+// while (nextToken != null) {
|
|
|
+// Map<String, Object> ddParams = new HashMap<>();
|
|
|
+// Long endTime = TimeUtil.getCurrentTimestamp();
|
|
|
+// ddParams.put("processCode", ddConfig.getWcProcessCode());
|
|
|
+// ddParams.put("startTime", TimeUtil.minusDays(endTime, 30));
|
|
|
+// ddParams.put("endTime", endTime);
|
|
|
+// ddParams.put("maxResults", 20L);
|
|
|
+// ddParams.put("statuses", Arrays.asList("COMPLETED"));
|
|
|
+//
|
|
|
+// String instanceIds = dingTalkOAImpl.getInstanceIds(ddParams, accessToken);
|
|
|
+// JsonParser jsonParser = new JsonParser();
|
|
|
+// JsonObject rootObject = jsonParser.parse(instanceIds).getAsJsonObject();
|
|
|
+// Boolean success = rootObject.get("success").getAsBoolean();
|
|
|
+// List<JsonElement> processInstanceIds = new ArrayList();
|
|
|
+// if (success) {
|
|
|
+// JsonObject result = rootObject.get("result").getAsJsonObject();
|
|
|
+// JsonArray listArray = result.get("list").getAsJsonArray();
|
|
|
+// nextToken = rootObject.get("nextToken").getAsJsonObject().getAsLong();
|
|
|
+//
|
|
|
+// listArray.forEach(processInstanceIds::add);
|
|
|
+// }
|
|
|
+//
|
|
|
+// List<Map<String, Object>> list = new ArrayList<>();
|
|
|
+// if (processInstanceIds.size() > 0) {
|
|
|
+// processInstanceIds.forEach(processInstanceId -> {
|
|
|
+// Map<String, Object> map = new HashMap<>();
|
|
|
+// String processInstances = dingTalkOAImpl.getProcessInstances(processInstanceId.toString(), accessToken);
|
|
|
+// Map<String, Object> processInstanceMap = parseGoOutJson(processInstances);
|
|
|
+// list.add(map);
|
|
|
+// });
|
|
|
+// }
|
|
|
+//
|
|
|
+// }
|
|
|
+//
|
|
|
+// //然后获取氚云中的OA数据
|
|
|
+// String SchemaCode = "D000867sm05feb5250b9c4d9b91cd489140ff5ef9";
|
|
|
+// List<Map<String, Object>> H3yunQueryList = new ArrayList<>();
|
|
|
+// //第0条开始,查询500条,如果循环则需要page = page +pageSize
|
|
|
+// int page = 0;
|
|
|
+// int pageSize = 500;
|
|
|
+//
|
|
|
+// while (true) {
|
|
|
+// Map<String, Object> filter = new HashMap<>();
|
|
|
+//
|
|
|
+// filter.put("FromRowNum", page*pageSize);
|
|
|
+// filter.put("ToRowNum", (page+1) * pageSize);
|
|
|
+// filter.put("RequireCount", false);
|
|
|
+// filter.put("SortByCollection", new ArrayList<>());
|
|
|
+// filter.put("ReturnItem", new ArrayList<>());
|
|
|
+//
|
|
|
+// Map<String, Object> matcher = new HashMap();
|
|
|
+// matcher.put("Type", "And");
|
|
|
+// filter.put("matcher", matcher);
|
|
|
+//
|
|
|
+// Map resultMap = h3yunClientImpl.LoadBizObjects(SchemaCode, filter);
|
|
|
+//
|
|
|
+// Boolean successful = (Boolean) resultMap.get("Successful");
|
|
|
+//
|
|
|
+// if (successful == null || !successful) {
|
|
|
+// throw new RuntimeException("氚云查询失败:" + resultMap.get("ErrorMessage"));
|
|
|
+// }
|
|
|
+// Map returnData = (Map) resultMap.get("ReturnData");
|
|
|
+//
|
|
|
+// if(returnData == null){
|
|
|
+// break;
|
|
|
+// }
|
|
|
+//
|
|
|
+//
|
|
|
+// List<Map<String,Object>> bizObjectArray = (List)returnData.get("BizObjectArray");
|
|
|
+// if (bizObjectArray == null || bizObjectArray.isEmpty()) {
|
|
|
+// break;
|
|
|
+// }
|
|
|
+// H3yunQueryList.addAll(bizObjectArray);
|
|
|
+// page++;
|
|
|
+// }
|
|
|
+//
|
|
|
+// //对比两边的数据
|
|
|
+//
|
|
|
+// return null;
|
|
|
+// }
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 按“钉钉外出单据列表 + 氚云外出单据列表”对比同步数据。
|
|
|
+ *
|
|
|
+ * <p>实现逻辑:</p>
|
|
|
+ * <ol>
|
|
|
+ * <li>分页查询钉钉外出审批实例 ID;</li>
|
|
|
+ * <li>根据实例 ID 查询审批详情,并解析标题、外出事由、开始时间、结束时间、时长、外出说明、是否临时申请、临时外出理由;</li>
|
|
|
+ * <li>分页查询氚云外出单据列表;</li>
|
|
|
+ * <li>以钉钉审批实例 ID 和氚云 F0000009 字段作为对比依据;</li>
|
|
|
+ * <li>氚云不存在则新增,已存在则根据 ObjectId 更新。</li>
|
|
|
+ * </ol>
|
|
|
+ *
|
|
|
+ * <p>注意:下面 F0000001 ~ F0000006 需要替换成你氚云外出表单里的真实字段编码。</p>
|
|
|
+ *
|
|
|
+ * @return 同步结果说明
|
|
|
+ */
|
|
|
+ public String syncGoOutProcessInstancesByCompare() {
|
|
|
+ Long endTime = TimeUtil.getCurrentTimestamp();
|
|
|
+ Long startTime = TimeUtil.minusDays(endTime, 5);
|
|
|
+ return syncGoOutProcessInstancesByCompare(startTime, endTime);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 同步最近 5 天内已通过的钉钉出差审批单。
|
|
|
+ *
|
|
|
+ * @return 同步结果说明
|
|
|
+ */
|
|
|
+ public String syncBusinessTripProcessInstances() {
|
|
|
+ Long endTime = TimeUtil.getCurrentTimestamp();
|
|
|
+ Long startTime = TimeUtil.minusDays(endTime, 5);
|
|
|
+ String accessToken = dingTalkOAImpl.getAccessToken();
|
|
|
+
|
|
|
+ List<Map<String, Object>> dingTalkBusinessTripList = queryDingTalkProcessInstanceList(
|
|
|
+ accessToken,
|
|
|
+ startTime,
|
|
|
+ endTime,
|
|
|
+ BUSINESS_TRIP_PROCESS_CODE,
|
|
|
+ this::parseBusinessTripJson
|
|
|
+ );
|
|
|
+ List<Map<String, Object>> h3yunBusinessTripList = queryH3yunGoOutList(BUSINESS_TRIP_SCHEMA_CODE);
|
|
|
+
|
|
|
+ Map<String, Map<String, Object>> h3yunDataMap = new HashMap<>();
|
|
|
+ for (Map<String, Object> h3yunData : h3yunBusinessTripList) {
|
|
|
+ String processInstanceId = getMapStringValue(h3yunData, BUSINESS_TRIP_PROCESS_INSTANCE_ID_FIELD_CODE);
|
|
|
+ if (processInstanceId != null && processInstanceId.trim().length() > 0) {
|
|
|
+ h3yunDataMap.put(processInstanceId, h3yunData);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ int createCount = 0;
|
|
|
+ int updateCount = 0;
|
|
|
+ for (Map<String, Object> dingTalkData : dingTalkBusinessTripList) {
|
|
|
+ String processInstanceId = getMapStringValue(dingTalkData, "processInstanceId");
|
|
|
+ if (processInstanceId == null || processInstanceId.trim().length() == 0) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ Map<String, Object> h3yunBizObject = buildBusinessTripBizObject(dingTalkData, processInstanceId);
|
|
|
+ Map<String, Object> existingData = h3yunDataMap.get(processInstanceId);
|
|
|
+ if (existingData == null) {
|
|
|
+ Map result = h3yunClientImpl.CreateBizObjects(
|
|
|
+ BUSINESS_TRIP_SCHEMA_CODE,
|
|
|
+ Collections.singletonList(h3yunBizObject)
|
|
|
+ );
|
|
|
+ assertH3yunOperationSuccessful(result, "新增氚云出差记录");
|
|
|
+ createCount++;
|
|
|
+ } else {
|
|
|
+ String objectId = getMapStringValue(existingData, "ObjectId");
|
|
|
+ if (objectId == null || objectId.trim().length() == 0) {
|
|
|
+ throw new RuntimeException("氚云出差记录缺少 ObjectId,钉钉流程ID=" + processInstanceId);
|
|
|
+ }
|
|
|
+ Map result = h3yunClientImpl.UpdateBizObject(BUSINESS_TRIP_SCHEMA_CODE, objectId, h3yunBizObject);
|
|
|
+ assertH3yunOperationSuccessful(result, "更新氚云出差记录");
|
|
|
+ updateCount++;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return "同步完成,钉钉查询到" + dingTalkBusinessTripList.size()
|
|
|
+ + "条,氚云查询到" + h3yunBusinessTripList.size()
|
|
|
+ + "条,新增" + createCount
|
|
|
+ + "条,更新" + updateCount + "条";
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 同步最近 5 天内的钉钉 OA 外出审批实例到氚云。
|
|
|
+ *
|
|
|
+ * <p>查询时间范围固定为当前时间向前 5 天。</p>
|
|
|
+ *
|
|
|
+ * @return 同步结果说明
|
|
|
+ */
|
|
|
+ public String syncAllGoOutProcessInstancesByCompare() {
|
|
|
+ Long finalEndTime = TimeUtil.getCurrentTimestamp();
|
|
|
+ Long firstStartTime = TimeUtil.minusDays(finalEndTime, 5);
|
|
|
+
|
|
|
+ StringBuilder resultBuilder = new StringBuilder();
|
|
|
+ int chunkIndex = 1;
|
|
|
+ Long currentStartTime = firstStartTime;
|
|
|
+
|
|
|
+ while (currentStartTime < finalEndTime) {
|
|
|
+ Long currentEndTime = TimeUtil.plusDays(currentStartTime, 30);
|
|
|
+ if (currentEndTime > finalEndTime) {
|
|
|
+ currentEndTime = finalEndTime;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (currentEndTime <= currentStartTime) {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ String result = syncGoOutProcessInstancesByCompare(currentStartTime, currentEndTime);
|
|
|
+ resultBuilder.append("第")
|
|
|
+ .append(chunkIndex)
|
|
|
+ .append("段:")
|
|
|
+ .append(TimeUtil.toDateTime(currentStartTime))
|
|
|
+ .append(" ~ ")
|
|
|
+ .append(TimeUtil.toDateTime(currentEndTime))
|
|
|
+ .append(",")
|
|
|
+ .append(result)
|
|
|
+ .append(System.lineSeparator());
|
|
|
+
|
|
|
+ currentStartTime = currentEndTime + 1;
|
|
|
+ chunkIndex++;
|
|
|
+ }
|
|
|
+
|
|
|
+ return resultBuilder.toString();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 按指定时间范围同步钉钉 OA 外出审批实例到氚云。
|
|
|
+ *
|
|
|
+ * @param startTime 查询开始时间,毫秒级时间戳
|
|
|
+ * @param endTime 查询结束时间,毫秒级时间戳
|
|
|
+ * @return 同步结果说明
|
|
|
+ */
|
|
|
+ private String syncGoOutProcessInstancesByCompare(Long startTime, Long endTime) {
|
|
|
+ String schemaCode = "D000867sm05feb5250b9c4d9b91cd489140ff5ef9";
|
|
|
+
|
|
|
+ String nameFieldCode = "Name";
|
|
|
+ String goOutReasonFieldCode = "F0000001";
|
|
|
+ String startTimeFieldCode = "F0000002";
|
|
|
+ String endTimeFieldCode = "F0000003";
|
|
|
+ String durationFieldCode = "F0000004";
|
|
|
+ String goOutDescriptionFieldCode = "F0000005";
|
|
|
+ String temporaryApplyFieldCode = "F0000006";
|
|
|
+ String temporaryReasonFieldCode = "F0000007";
|
|
|
+ String dingTalkProcessInstanceIdFieldCode = "F0000009";
|
|
|
+ String titleFieldCode = "F0000020";
|
|
|
+ String submitterFieldCode = "F0000021";
|
|
|
+
|
|
|
+ String accessToken = dingTalkOAImpl.getAccessToken();
|
|
|
+
|
|
|
+ List<Map<String, Object>> dingTalkGoOutList = queryDingTalkGoOutList(accessToken, startTime, endTime);
|
|
|
+ List<Map<String, Object>> h3yunGoOutList = queryH3yunGoOutList(schemaCode);
|
|
|
+
|
|
|
+ Map<String, Map<String, Object>> h3yunDataMap = new HashMap<>();
|
|
|
+ for (Map<String, Object> h3yunData : h3yunGoOutList) {
|
|
|
+ String dingTalkProcessInstanceId = getMapStringValue(h3yunData, dingTalkProcessInstanceIdFieldCode);
|
|
|
+ if (dingTalkProcessInstanceId != null && dingTalkProcessInstanceId.trim().length() > 0) {
|
|
|
+ h3yunDataMap.put(dingTalkProcessInstanceId, h3yunData);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ int createCount = 0;
|
|
|
+ int updateCount = 0;
|
|
|
+
|
|
|
+ for (Map<String, Object> dingTalkData : dingTalkGoOutList) {
|
|
|
+ String processInstanceId = getMapStringValue(dingTalkData, "processInstanceId");
|
|
|
+ if (processInstanceId == null || processInstanceId.trim().length() == 0) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ String title = getMapStringValue(dingTalkData, "title");
|
|
|
+ String submitter = getStringBeforeSubmit(title);
|
|
|
+
|
|
|
+ Map<String, Object> h3yunBizObject = new HashMap<>();
|
|
|
+ h3yunBizObject.put(nameFieldCode, title);
|
|
|
+ h3yunBizObject.put(titleFieldCode, title);
|
|
|
+ h3yunBizObject.put(submitterFieldCode, submitter);
|
|
|
+ h3yunBizObject.put(dingTalkProcessInstanceIdFieldCode, processInstanceId);
|
|
|
+ h3yunBizObject.put(goOutReasonFieldCode, dingTalkData.get("外出事由"));
|
|
|
+ h3yunBizObject.put(startTimeFieldCode, dingTalkData.get("开始时间"));
|
|
|
+ h3yunBizObject.put(endTimeFieldCode, dingTalkData.get("结束时间"));
|
|
|
+ h3yunBizObject.put(durationFieldCode, dingTalkData.get("时长"));
|
|
|
+ h3yunBizObject.put(goOutDescriptionFieldCode, dingTalkData.get("外出说明"));
|
|
|
+ h3yunBizObject.put(temporaryApplyFieldCode, dingTalkData.get("是否临时申请"));
|
|
|
+
|
|
|
+ if ("临时外出".equals(dingTalkData.get("是否临时申请"))) {
|
|
|
+ h3yunBizObject.put(temporaryReasonFieldCode, dingTalkData.get("临时外出理由"));
|
|
|
+ }
|
|
|
+
|
|
|
+ Map<String, Object> existH3yunData = h3yunDataMap.get(processInstanceId);
|
|
|
+ if (existH3yunData == null) {
|
|
|
+ Map<String, String> createMap = new HashMap<>();
|
|
|
+ for (Map.Entry<String, Object> entry : h3yunBizObject.entrySet()) {
|
|
|
+ createMap.put(entry.getKey(), entry.getValue() == null ? null : entry.getValue().toString());
|
|
|
+ }
|
|
|
+ h3yunClientImpl.CreateBizObject(schemaCode, createMap);
|
|
|
+ createCount++;
|
|
|
+ } else {
|
|
|
+ String objectId = getMapStringValue(existH3yunData, "ObjectId");
|
|
|
+ if (objectId != null && objectId.trim().length() > 0) {
|
|
|
+ h3yunClientImpl.UpdateBizObject(schemaCode, objectId, h3yunBizObject);
|
|
|
+ updateCount++;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return "同步完成,钉钉查询到" + dingTalkGoOutList.size()
|
|
|
+ + "条,氚云查询到" + h3yunGoOutList.size()
|
|
|
+ + "条,新增" + createCount
|
|
|
+ + "条,更新" + updateCount + "条";
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 分页查询钉钉外出审批详情列表。
|
|
|
+ *
|
|
|
+ * @param accessToken 钉钉 accessToken
|
|
|
+ * @param startTime 查询开始时间,毫秒级时间戳
|
|
|
+ * @param endTime 查询结束时间,毫秒级时间戳
|
|
|
+ * @return 解析后的钉钉外出审批详情列表
|
|
|
+ */
|
|
|
+ private List<Map<String, Object>> queryDingTalkGoOutList(String accessToken, Long startTime, Long endTime) {
|
|
|
+ return queryDingTalkProcessInstanceList(
|
|
|
+ accessToken,
|
|
|
+ startTime,
|
|
|
+ endTime,
|
|
|
+ ddConfig.getWcProcessCode(),
|
|
|
+ this::parseGoOutJson
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 分页查询指定审批模板下已完成的实例,并按实例 ID 获取审批详情。
|
|
|
+ */
|
|
|
+ private List<Map<String, Object>> queryDingTalkProcessInstanceList(
|
|
|
+ String accessToken,
|
|
|
+ Long startTime,
|
|
|
+ Long endTime,
|
|
|
+ String processCode,
|
|
|
+ Function<String, Map<String, Object>> detailParser) {
|
|
|
+ if (startTime == null || endTime == null) {
|
|
|
+ throw new RuntimeException("钉钉审批实例ID查询失败:startTime 或 endTime 不能为空");
|
|
|
+ }
|
|
|
+ if (endTime <= startTime) {
|
|
|
+ throw new RuntimeException("钉钉审批实例ID查询失败:结束时间不能小于或等于开始时间,startTime="
|
|
|
+ + TimeUtil.toDateTime(startTime) + ",endTime=" + TimeUtil.toDateTime(endTime));
|
|
|
+ }
|
|
|
+ if (endTime - startTime > 120L * 24 * 60 * 60 * 1000) {
|
|
|
+ throw new RuntimeException("钉钉审批实例ID查询失败:查询时间范围不能超过120天,startTime="
|
|
|
+ + TimeUtil.toDateTime(startTime) + ",endTime=" + TimeUtil.toDateTime(endTime));
|
|
|
+ }
|
|
|
+ Long earliestAllowedStartTime = TimeUtil.minusDays(TimeUtil.getCurrentTimestamp(), 365);
|
|
|
+ Long allowedTimeOffset = 60L * 1000;
|
|
|
+ if (startTime + allowedTimeOffset < earliestAllowedStartTime) {
|
|
|
+ throw new RuntimeException("钉钉审批实例ID查询失败:普通接口限制 startTime 距当前时间不能超过365天,startTime="
|
|
|
+ + TimeUtil.toDateTime(startTime) + ",最早允许时间=" + TimeUtil.toDateTime(earliestAllowedStartTime));
|
|
|
+ }
|
|
|
+ if (processCode == null || processCode.trim().length() == 0) {
|
|
|
+ throw new RuntimeException("钉钉审批实例ID查询失败:processCode 不能为空");
|
|
|
+ }
|
|
|
+ if (detailParser == null) {
|
|
|
+ throw new RuntimeException("钉钉审批实例ID查询失败:详情解析器不能为空");
|
|
|
+ }
|
|
|
+
|
|
|
+ List<Map<String, Object>> dingTalkGoOutList = new ArrayList<>();
|
|
|
+ Set<String> processedInstanceIds = new HashSet<>();
|
|
|
+
|
|
|
+ String nextToken = "0";
|
|
|
+ log.info("钉钉审批实例ID查询范围:{} ~ {},processCode={}",
|
|
|
+ TimeUtil.toDateTime(startTime), TimeUtil.toDateTime(endTime), processCode);
|
|
|
+
|
|
|
+ while (true) {
|
|
|
+ Map<String, Object> ddParams = new HashMap<>();
|
|
|
+ ddParams.put("processCode", processCode);
|
|
|
+ ddParams.put("startTime", startTime);
|
|
|
+ ddParams.put("endTime", endTime);
|
|
|
+ ddParams.put("nextToken", nextToken);
|
|
|
+ ddParams.put("maxResults", 20L);
|
|
|
+ ddParams.put("statuses", Arrays.asList("COMPLETED"));
|
|
|
+
|
|
|
+ String instanceIdsResult = dingTalkOAImpl.getInstanceIds(ddParams, accessToken);
|
|
|
+
|
|
|
+ JsonParser jsonParser = new JsonParser();
|
|
|
+ JsonObject rootObject = jsonParser.parse(instanceIdsResult).getAsJsonObject();
|
|
|
+
|
|
|
+ boolean success = rootObject.has("success")
|
|
|
+ && !rootObject.get("success").isJsonNull()
|
|
|
+ && rootObject.get("success").getAsBoolean();
|
|
|
+
|
|
|
+ if (!success) {
|
|
|
+ throw new RuntimeException("钉钉审批实例ID查询失败:" + instanceIdsResult);
|
|
|
+ }
|
|
|
+
|
|
|
+ JsonObject resultObject = rootObject.getAsJsonObject("result");
|
|
|
+ if (resultObject == null) {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ JsonArray listArray = resultObject.getAsJsonArray("list");
|
|
|
+ if (listArray != null) {
|
|
|
+ for (JsonElement item : listArray) {
|
|
|
+ if (item == null || item.isJsonNull()) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ String processInstanceId = item.getAsString();
|
|
|
+ if (processInstanceId == null || processInstanceId.trim().length() == 0) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ if (!processedInstanceIds.add(processInstanceId)) {
|
|
|
+ log.warn("钉钉审批实例 ID 重复,跳过重复同步:{}", processInstanceId);
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ String processInstanceDetail = dingTalkOAImpl.getProcessInstances(processInstanceId, accessToken);
|
|
|
+ Map<String, Object> processMap = detailParser.apply(processInstanceDetail);
|
|
|
+ if (processMap == null) {
|
|
|
+ throw new RuntimeException("钉钉审批详情解析失败,processInstanceId=" + processInstanceId);
|
|
|
+ }
|
|
|
+ // 审批实例 ID 列表接口返回的值才是后续查询与幂等写入的唯一依据。
|
|
|
+ processMap.put("processInstanceId", processInstanceId);
|
|
|
+ dingTalkGoOutList.add(processMap);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!resultObject.has("nextToken") || resultObject.get("nextToken").isJsonNull()) {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ String newNextToken = resultObject.get("nextToken").getAsString();
|
|
|
+ if (newNextToken == null
|
|
|
+ || newNextToken.trim().length() == 0
|
|
|
+ || "0".equals(newNextToken)
|
|
|
+ || nextToken.equals(newNextToken)) {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ nextToken = newNextToken;
|
|
|
+ }
|
|
|
+
|
|
|
+ return dingTalkGoOutList;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 分页查询氚云外出单据列表。
|
|
|
+ *
|
|
|
+ * @param schemaCode 氚云表单编码
|
|
|
+ * @return 氚云外出单据列表
|
|
|
+ */
|
|
|
+ private List<Map<String, Object>> queryH3yunGoOutList(String schemaCode) {
|
|
|
+ List<Map<String, Object>> h3yunQueryList = new ArrayList<>();
|
|
|
+
|
|
|
+ int page = 0;
|
|
|
+ int pageSize = 500;
|
|
|
+
|
|
|
+ while (true) {
|
|
|
+ Map<String, Object> filter = new HashMap<>();
|
|
|
+ filter.put("FromRowNum", page * pageSize);
|
|
|
+ filter.put("ToRowNum", (page + 1) * pageSize);
|
|
|
+ filter.put("RequireCount", false);
|
|
|
+ filter.put("SortByCollection", new ArrayList<>());
|
|
|
+ filter.put("ReturnItem", new ArrayList<>());
|
|
|
+
|
|
|
+ Map<String, Object> matcher = new HashMap<>();
|
|
|
+ matcher.put("Type", "And");
|
|
|
+ matcher.put("Matchers", new ArrayList<>());
|
|
|
+ filter.put("Matcher", matcher);
|
|
|
+
|
|
|
+ Map resultMap = h3yunClientImpl.LoadBizObjects(schemaCode, filter);
|
|
|
+
|
|
|
+ Boolean successful = (Boolean) resultMap.get("Successful");
|
|
|
+ if (successful == null || !successful) {
|
|
|
+ throw new RuntimeException("氚云查询失败:" + resultMap.get("ErrorMessage"));
|
|
|
+ }
|
|
|
+
|
|
|
+ Map returnData = (Map) resultMap.get("ReturnData");
|
|
|
+ if (returnData == null) {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ List<Map<String, Object>> bizObjectArray = (List<Map<String, Object>>) returnData.get("BizObjectArray");
|
|
|
+ if (bizObjectArray == null || bizObjectArray.isEmpty()) {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ h3yunQueryList.addAll(bizObjectArray);
|
|
|
+
|
|
|
+ if (bizObjectArray.size() < pageSize) {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ page++;
|
|
|
+ }
|
|
|
+
|
|
|
+ return h3yunQueryList;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 从 Map 中安全获取字符串值。
|
|
|
+ *
|
|
|
+ * @param map 数据 Map
|
|
|
+ * @param key 字段 key
|
|
|
+ * @return 字符串值
|
|
|
+ */
|
|
|
+ private String getMapStringValue(Map<String, Object> map, String key) {
|
|
|
+ if (map == null || key == null) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ Object value = map.get(key);
|
|
|
+ return value == null ? null : value.toString();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 构造氚云查询条件中的单个字段匹配项。
|
|
|
+ *
|
|
|
+ * @param type 匹配类型,字段条件通常为 Item
|
|
|
+ * @param name 氚云字段编码
|
|
|
+ * @param operator 查询操作符,例如 0 表示大于、2 表示等于
|
|
|
+ * @param value 查询条件值
|
|
|
+ * @return 单个字段匹配项 Map
|
|
|
+ */
|
|
|
+ private Map<String, Object> buildItemMatchers(String type,String name, Integer operator, Object value) {
|
|
|
+ Map<String, Object> item = new HashMap<>();
|
|
|
+ item.put("Type", type);
|
|
|
+ item.put("Name", name);
|
|
|
+ item.put("Operator", operator);
|
|
|
+ item.put("Value", value);
|
|
|
+ return item;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 构造氚云查询条件分组。
|
|
|
+ *
|
|
|
+ * <p>分组类型通常为 And 或 Or,内部通过 Matchers 存放具体字段匹配项。</p>
|
|
|
+ *
|
|
|
+ * @param type 分组类型,例如 And 或 Or
|
|
|
+ * @param itemMatcher 单个字段匹配项
|
|
|
+ * @return 查询条件分组 Map
|
|
|
+ */
|
|
|
+ private Map<String, Object> buildMatcherGroup(String type, Map<String, Object> itemMatcher) {
|
|
|
+ Map<String, Object> group = new HashMap<>();
|
|
|
+ group.put("Type", type);
|
|
|
+
|
|
|
+ List<Map<String, Object>> matchers = new ArrayList<>();
|
|
|
+ matchers.add(itemMatcher);
|
|
|
+
|
|
|
+ group.put("Matchers", matchers);
|
|
|
+ return group;
|
|
|
+ }
|
|
|
+
|
|
|
+ private Map<String, Object> buildBusinessTripBizObject(
|
|
|
+ Map<String, Object> dingTalkData,
|
|
|
+ String processInstanceId) {
|
|
|
+ Map<String, Object> h3yunBizObject = new HashMap<>();
|
|
|
+ h3yunBizObject.put("Name", getMapStringValue(dingTalkData, "title"));
|
|
|
+ h3yunBizObject.put(BUSINESS_TRIP_PROCESS_INSTANCE_ID_FIELD_CODE, processInstanceId);
|
|
|
+ h3yunBizObject.put(BUSINESS_TRIP_REASON_FIELD_CODE, dingTalkData.get("出差事由"));
|
|
|
+ h3yunBizObject.put(BUSINESS_TRIP_DAYS_FIELD_CODE, dingTalkData.get("出差天数"));
|
|
|
+ h3yunBizObject.put(BUSINESS_TRIP_TEMPORARY_FIELD_CODE, dingTalkData.get("是否临时申请"));
|
|
|
+ h3yunBizObject.put(BUSINESS_TRIP_REMARK_FIELD_CODE, dingTalkData.get("出差备注"));
|
|
|
+ h3yunBizObject.put(BUSINESS_TRIP_COST_CENTER_FIELD_CODE, dingTalkData.get("成本中心"));
|
|
|
+ h3yunBizObject.put(BUSINESS_TRIP_INVOICE_TITLE_FIELD_CODE, dingTalkData.get("发票抬头"));
|
|
|
+ h3yunBizObject.put(BUSINESS_TRIP_COMPANION_FIELD_CODE, dingTalkData.get("同行人"));
|
|
|
+ h3yunBizObject.put(BUSINESS_TRIP_DEPARTMENT_FIELD_CODE, dingTalkData.get("部门"));
|
|
|
+ h3yunBizObject.put(BUSINESS_TRIP_APPLICANT_FIELD_CODE, dingTalkData.get("申请人"));
|
|
|
+ h3yunBizObject.put(BUSINESS_TRIP_APPLY_DATE_FIELD_CODE, dingTalkData.get("申请日期"));
|
|
|
+
|
|
|
+ String itineraryValue = getMapStringValue(dingTalkData, "行程");
|
|
|
+ if (itineraryValue == null) {
|
|
|
+ itineraryValue = getMapStringValue(dingTalkData, "行程数据");
|
|
|
+ }
|
|
|
+ if (itineraryValue != null && itineraryValue.trim().length() > 0) {
|
|
|
+ h3yunBizObject.put(BUSINESS_TRIP_ITINERARY_FIELD_CODE, parseBusinessTripItineraries(itineraryValue));
|
|
|
+ }
|
|
|
+
|
|
|
+ return h3yunBizObject;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 解析钉钉出差审批详情中的主表字段和行程原始值。
|
|
|
+ */
|
|
|
+ public Map<String, Object> parseBusinessTripJson(String json) {
|
|
|
+ JsonObject rootObject = new JsonParser().parse(json).getAsJsonObject();
|
|
|
+ JsonObject resultObject = rootObject.getAsJsonObject("result");
|
|
|
+ if (resultObject == null) {
|
|
|
+ throw new RuntimeException("钉钉出差审批详情缺少 result");
|
|
|
+ }
|
|
|
+
|
|
|
+ Map<String, Object> resultMap = new HashMap<>();
|
|
|
+ String processInstanceId = getStringValue(resultObject, "processInstanceId");
|
|
|
+ if (processInstanceId == null) {
|
|
|
+ processInstanceId = getStringValue(resultObject, "procInstId");
|
|
|
+ }
|
|
|
+ if (processInstanceId == null) {
|
|
|
+ processInstanceId = getStringValue(resultObject, "id");
|
|
|
+ }
|
|
|
+ if (processInstanceId == null) {
|
|
|
+ processInstanceId = getStringValue(resultObject, "businessId");
|
|
|
+ }
|
|
|
+ resultMap.put("processInstanceId", processInstanceId);
|
|
|
+ String title = getStringValue(resultObject, "title");
|
|
|
+ resultMap.put("title", title);
|
|
|
+
|
|
|
+ String applicant = getStringValue(resultObject, "originatorUserName");
|
|
|
+ if (applicant == null || applicant.trim().length() == 0) {
|
|
|
+ applicant = getStringBeforeSubmit(title);
|
|
|
+ }
|
|
|
+ resultMap.put("申请人", applicant);
|
|
|
+ resultMap.put("申请日期", formatBusinessTripApplyDate(getStringValue(resultObject, "createTime")));
|
|
|
+
|
|
|
+ JsonArray formComponentValues = resultObject.getAsJsonArray("formComponentValues");
|
|
|
+ if (formComponentValues == null) {
|
|
|
+ throw new RuntimeException("钉钉出差审批详情缺少 formComponentValues");
|
|
|
+ }
|
|
|
+
|
|
|
+ for (JsonElement element : formComponentValues) {
|
|
|
+ if (element == null || element.isJsonNull() || !element.isJsonObject()) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ JsonObject item = element.getAsJsonObject();
|
|
|
+ String name = getStringValue(item, "name");
|
|
|
+ String value = getStringValue(item, "value");
|
|
|
+ if (name == null || value == null) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ if ("行程".equals(name) || "行程数据".equals(name)) {
|
|
|
+ resultMap.put(name, value);
|
|
|
+ } else {
|
|
|
+ resultMap.put(name, formatDingTalkFormValue(value));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return resultMap;
|
|
|
+ }
|
|
|
+
|
|
|
+ private String formatBusinessTripApplyDate(String createTime) {
|
|
|
+ if (createTime == null || createTime.trim().length() == 0) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ String value = createTime.trim();
|
|
|
+ try {
|
|
|
+ return TimeUtil.toDateTime(Long.parseLong(value)).substring(0, 10);
|
|
|
+ } catch (NumberFormatException e) {
|
|
|
+ return value.length() >= 10 ? value.substring(0, 10) : value;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ List<Map<String, Object>> parseBusinessTripItineraries(String itineraryValue) {
|
|
|
+ JsonElement itineraryElement;
|
|
|
+ try {
|
|
|
+ itineraryElement = new JsonParser().parse(itineraryValue);
|
|
|
+ } catch (Exception e) {
|
|
|
+ throw new RuntimeException("钉钉出差行程数据不是合法 JSON:" + itineraryValue, e);
|
|
|
+ }
|
|
|
+ if (!itineraryElement.isJsonArray()) {
|
|
|
+ throw new RuntimeException("钉钉出差行程数据不是数组:" + itineraryValue);
|
|
|
+ }
|
|
|
+
|
|
|
+ List<Map<String, Object>> itineraryRows = new ArrayList<>();
|
|
|
+ JsonArray sourceRows = itineraryElement.getAsJsonArray();
|
|
|
+ if (isNameValueArray(sourceRows)) {
|
|
|
+ Map<String, Object> targetRow = new HashMap<>();
|
|
|
+ for (JsonElement element : sourceRows) {
|
|
|
+ JsonObject field = element.getAsJsonObject();
|
|
|
+ putItineraryValue(targetRow, getStringValue(field, "name"), getStringValue(field, "value"));
|
|
|
+ }
|
|
|
+ addItineraryRow(itineraryRows, targetRow);
|
|
|
+ return itineraryRows;
|
|
|
+ }
|
|
|
+
|
|
|
+ for (JsonElement element : sourceRows) {
|
|
|
+ if (element == null || element.isJsonNull() || !element.isJsonObject()) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ JsonObject sourceRow = element.getAsJsonObject();
|
|
|
+ Map<String, Object> targetRow = new HashMap<>();
|
|
|
+ JsonArray rowValues = sourceRow.getAsJsonArray("rowValue");
|
|
|
+ if (rowValues != null) {
|
|
|
+ for (JsonElement rowValue : rowValues) {
|
|
|
+ if (rowValue == null || rowValue.isJsonNull() || !rowValue.isJsonObject()) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ JsonObject field = rowValue.getAsJsonObject();
|
|
|
+ putItineraryValue(targetRow, getStringValue(field, "label"), getStringValue(field, "value"));
|
|
|
+ }
|
|
|
+ addItineraryRow(itineraryRows, targetRow);
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ putItineraryValue(targetRow, "交通工具", getStringValue(sourceRow, "交通工具"));
|
|
|
+ putItineraryValue(targetRow, "单程往返", getStringValue(sourceRow, "单程往返"));
|
|
|
+ putItineraryValue(targetRow, "出发城市", getStringValue(sourceRow, "出发城市"));
|
|
|
+ putItineraryValue(targetRow, "出发地", getStringValue(sourceRow, "出发地"));
|
|
|
+ putItineraryValue(targetRow, "目的城市", getStringValue(sourceRow, "目的城市"));
|
|
|
+ putItineraryValue(targetRow, "目的地", getStringValue(sourceRow, "目的地"));
|
|
|
+ putItineraryValue(targetRow, "开始时间", getStringValue(sourceRow, "开始时间"));
|
|
|
+ putItineraryValue(targetRow, "结束时间", getStringValue(sourceRow, "结束时间"));
|
|
|
+ putItineraryValue(targetRow, "时长", getStringValue(sourceRow, "时长"));
|
|
|
+ addItineraryRow(itineraryRows, targetRow);
|
|
|
+ }
|
|
|
+ return itineraryRows;
|
|
|
+ }
|
|
|
+
|
|
|
+ private boolean isNameValueArray(JsonArray sourceRows) {
|
|
|
+ if (sourceRows.size() == 0) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ for (JsonElement element : sourceRows) {
|
|
|
+ if (element == null || element.isJsonNull() || !element.isJsonObject()) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ JsonObject field = element.getAsJsonObject();
|
|
|
+ if (!field.has("name") || !field.has("value")) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ private void putItineraryValue(Map<String, Object> targetRow, String dingTalkFieldName, String value) {
|
|
|
+ if (dingTalkFieldName == null || value == null) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ String h3yunFieldCode;
|
|
|
+ if ("交通工具".equals(dingTalkFieldName)) {
|
|
|
+ h3yunFieldCode = "F0000005";
|
|
|
+ } else if ("单程往返".equals(dingTalkFieldName)) {
|
|
|
+ h3yunFieldCode = "F0000006";
|
|
|
+ } else if ("出发城市".equals(dingTalkFieldName) || "出发地".equals(dingTalkFieldName)) {
|
|
|
+ h3yunFieldCode = "F0000007";
|
|
|
+ } else if ("目的城市".equals(dingTalkFieldName) || "目的地".equals(dingTalkFieldName)) {
|
|
|
+ h3yunFieldCode = "F0000008";
|
|
|
+ } else if ("开始时间".equals(dingTalkFieldName)) {
|
|
|
+ h3yunFieldCode = "F0000009";
|
|
|
+ } else if ("结束时间".equals(dingTalkFieldName)) {
|
|
|
+ h3yunFieldCode = "F0000010";
|
|
|
+ } else if ("时长".equals(dingTalkFieldName)) {
|
|
|
+ h3yunFieldCode = "F0000011";
|
|
|
+ } else {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ targetRow.put(h3yunFieldCode, formatDingTalkFormValue(value));
|
|
|
+ }
|
|
|
+
|
|
|
+ private void addItineraryRow(List<Map<String, Object>> itineraryRows, Map<String, Object> targetRow) {
|
|
|
+ if (targetRow.isEmpty()) {
|
|
|
+ throw new RuntimeException("钉钉出差行程行未匹配到可同步字段");
|
|
|
+ }
|
|
|
+ itineraryRows.add(targetRow);
|
|
|
+ }
|
|
|
+
|
|
|
+ private void assertH3yunOperationSuccessful(Map result, String action) {
|
|
|
+ if (result == null || !(result.get("Successful") instanceof Boolean)
|
|
|
+ || !((Boolean) result.get("Successful"))) {
|
|
|
+ Object errorMessage = result == null ? null : result.get("ErrorMessage");
|
|
|
+ throw new RuntimeException(action + "失败:" + errorMessage);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public Map<String, Object> parseGoOutJson(String json) {
|
|
|
+ Map<String, Object> resultMap = new HashMap<>();
|
|
|
+
|
|
|
+ JsonParser jsonParser = new JsonParser();
|
|
|
+ JsonObject rootObject = jsonParser.parse(json).getAsJsonObject();
|
|
|
+
|
|
|
+ JsonObject resultObject = rootObject.getAsJsonObject("result");
|
|
|
+
|
|
|
+ // 1. 获取钉钉详情中的唯一 ID 和 title
|
|
|
+ String processInstanceId = getStringValue(resultObject, "processInstanceId");
|
|
|
+ if (processInstanceId == null) {
|
|
|
+ processInstanceId = getStringValue(resultObject, "procInstId");
|
|
|
+ }
|
|
|
+ if (processInstanceId == null) {
|
|
|
+ processInstanceId = getStringValue(resultObject, "id");
|
|
|
+ }
|
|
|
+ if (processInstanceId == null) {
|
|
|
+ processInstanceId = getStringValue(resultObject, "businessId");
|
|
|
+ }
|
|
|
+ resultMap.put("processInstanceId", processInstanceId);
|
|
|
+
|
|
|
+ String title = getStringValue(resultObject, "title");
|
|
|
+ resultMap.put("title", title);
|
|
|
+
|
|
|
+ // 2. 获取 formComponentValues
|
|
|
+ JsonArray formComponentValues = resultObject.getAsJsonArray("formComponentValues");
|
|
|
+
|
|
|
+ String goOutReason = null;
|
|
|
+ String startTime = null;
|
|
|
+ String endTime = null;
|
|
|
+ String duration = null;
|
|
|
+ String goOutDescription = null;
|
|
|
+ String temporaryApply = null;
|
|
|
+ String temporaryReason = null;
|
|
|
+
|
|
|
+ for (JsonElement element : formComponentValues) {
|
|
|
+ JsonObject item = element.getAsJsonObject();
|
|
|
+
|
|
|
+ String name = getStringValue(item, "name");
|
|
|
+ String value = formatDingTalkFormValue(getStringValue(item, "value"));
|
|
|
+
|
|
|
+ if ("外出事由".equals(name)) {
|
|
|
+ goOutReason = value;
|
|
|
+ } else if ("开始时间".equals(name)) {
|
|
|
+ startTime = value;
|
|
|
+ } else if ("结束时间".equals(name)) {
|
|
|
+ endTime = value;
|
|
|
+ } else if ("时长".equals(name)) {
|
|
|
+ duration = value;
|
|
|
+ } else if ("外出说明".equals(name)) {
|
|
|
+ goOutDescription = value;
|
|
|
+ } else if ("是否临时申请".equals(name)) {
|
|
|
+ temporaryApply = value;
|
|
|
+ } else if ("临时外出理由".equals(name)) {
|
|
|
+ temporaryReason = value;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ resultMap.put("外出事由", goOutReason);
|
|
|
+ resultMap.put("开始时间", startTime);
|
|
|
+ resultMap.put("结束时间", endTime);
|
|
|
+ resultMap.put("时长", duration);
|
|
|
+ resultMap.put("外出说明", goOutDescription);
|
|
|
+ resultMap.put("是否临时申请", temporaryApply);
|
|
|
+
|
|
|
+ // 3. 如果是临时申请,才放入临时外出理由
|
|
|
+ if ("临时外出".equals(temporaryApply)) {
|
|
|
+ resultMap.put("临时外出理由", temporaryReason);
|
|
|
+ }
|
|
|
+
|
|
|
+ return resultMap;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 格式化钉钉表单组件值。
|
|
|
+ *
|
|
|
+ * <p>钉钉多选、单选等组件的 value 可能返回 JSON 数组字符串,例如 ["CRM客户拜访"]。
|
|
|
+ * 写入氚云普通文本字段时,需要转换为 CRM客户拜访;如果有多个选项,则使用英文逗号拼接。</p>
|
|
|
+ *
|
|
|
+ * @param value 钉钉表单组件原始 value
|
|
|
+ * @return 格式化后的文本值
|
|
|
+ */
|
|
|
+ private String formatDingTalkFormValue(String value) {
|
|
|
+ if (value == null) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ String trimValue = value.trim();
|
|
|
+ if (trimValue.length() == 0) {
|
|
|
+ return value;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!trimValue.startsWith("[") || !trimValue.endsWith("]")) {
|
|
|
+ return value;
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ JsonElement jsonElement = new JsonParser().parse(trimValue);
|
|
|
+ if (!jsonElement.isJsonArray()) {
|
|
|
+ return value;
|
|
|
+ }
|
|
|
+
|
|
|
+ JsonArray jsonArray = jsonElement.getAsJsonArray();
|
|
|
+ List<String> valueList = new ArrayList<>();
|
|
|
+ for (JsonElement element : jsonArray) {
|
|
|
+ if (element == null || element.isJsonNull()) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ valueList.add(element.getAsString());
|
|
|
+ }
|
|
|
+ return String.join(",", valueList);
|
|
|
+ } catch (Exception e) {
|
|
|
+ return value;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取 title 中“提交”前面的字符串。
|
|
|
+ *
|
|
|
+ * <p>例如:XX提交的外出申请,返回 XX。</p>
|
|
|
+ *
|
|
|
+ * @param title 钉钉审批实例标题
|
|
|
+ * @return “提交”前的字符串;如果 title 为空或不包含“提交”,返回 null
|
|
|
+ */
|
|
|
+ private String getStringBeforeSubmit(String title) {
|
|
|
+ if (title == null) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ int submitIndex = title.indexOf("提交");
|
|
|
+ if (submitIndex <= 0) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ String value = title.substring(0, submitIndex).trim();
|
|
|
+ return value.length() == 0 ? null : value;
|
|
|
+ }
|
|
|
+
|
|
|
+ private String getStringValue(JsonObject jsonObject, String key) {
|
|
|
+ if (jsonObject == null) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!jsonObject.has(key)) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (jsonObject.get(key).isJsonNull()) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ return jsonObject.get(key).getAsString();
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+}
|