|
|
@@ -10,6 +10,8 @@ import com.malk.server.workhours.TimeCardApprovalStartRequest;
|
|
|
import com.malk.server.workhours.WHConf;
|
|
|
import com.malk.service.aliwork.YDClient;
|
|
|
import com.malk.service.aliwork.YDService;
|
|
|
+import com.malk.service.dingtalk.DDClient;
|
|
|
+import com.malk.service.dingtalk.DDClient_Contacts;
|
|
|
import com.malk.utils.UtilMap;
|
|
|
import lombok.extern.slf4j.Slf4j;
|
|
|
import org.apache.commons.lang3.StringUtils;
|
|
|
@@ -47,6 +49,12 @@ public class ReSubmitApprovalService {
|
|
|
@Autowired
|
|
|
private WHConf whConf;
|
|
|
|
|
|
+ @Autowired
|
|
|
+ private DDClient ddClient;
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private DDClient_Contacts ddClient_contacts;
|
|
|
+
|
|
|
/**
|
|
|
* 前端 v2.35 常量:发起时明细「审批意见」默认「同意」
|
|
|
*/
|
|
|
@@ -67,6 +75,9 @@ public class ReSubmitApprovalService {
|
|
|
*/
|
|
|
private static final String DEFAULT_MANAGER_ID = "634735129";
|
|
|
|
|
|
+ private static final String PERSONNEL_EMPLOYEE = "employeeField_mkow4ydp";
|
|
|
+ private static final String PERSONNEL_MANAGER = "employeeField_mh8xhqc3";
|
|
|
+
|
|
|
// ===== 汇总表字段(与 ApprovalWriteBackService 一致) =====
|
|
|
private static final String S_SUBMITTER = "employeeField_mmacxew4";
|
|
|
private static final String S_SUBMITTER_ID = "textField_mmbffvd9";
|
|
|
@@ -228,6 +239,7 @@ public class ReSubmitApprovalService {
|
|
|
}
|
|
|
List<Map> allRecords = queryAll(whConf.getFormUuidWorkHoursSummary(), search);
|
|
|
ApprovalCoverage approvalCoverage = loadApprovalCoverage(monthText);
|
|
|
+ Map<String, String> projectManagerCache = new HashMap<>();
|
|
|
|
|
|
// 2) 按 (submitterUid) 分组员工月度记录
|
|
|
Map<String, List<Map>> perSubmitter = new LinkedHashMap<>();
|
|
|
@@ -253,7 +265,8 @@ public class ReSubmitApprovalService {
|
|
|
String uid = e.getKey();
|
|
|
List<Map> records = e.getValue();
|
|
|
try {
|
|
|
- CollectResult cr = collectApprovalData(uid, monthText, records, approvalCoverage);
|
|
|
+ CollectResult cr = collectApprovalData(
|
|
|
+ uid, monthText, records, approvalCoverage, projectManagerCache);
|
|
|
if (cr.managerGroups.isEmpty() && cr.otherRows.isEmpty()) {
|
|
|
skipped++;
|
|
|
continue;
|
|
|
@@ -410,8 +423,11 @@ public class ReSubmitApprovalService {
|
|
|
String deptManagerId;
|
|
|
}
|
|
|
|
|
|
- private CollectResult collectApprovalData(String uid, String monthText, List<Map> records,
|
|
|
- ApprovalCoverage approvalCoverage) {
|
|
|
+ private CollectResult collectApprovalData(String uid,
|
|
|
+ String monthText,
|
|
|
+ List<Map> records,
|
|
|
+ ApprovalCoverage approvalCoverage,
|
|
|
+ Map<String, String> projectManagerCache) {
|
|
|
CollectResult cr = new CollectResult();
|
|
|
Set<String> seenBillableKeys = new HashSet<>();
|
|
|
Set<String> seenNonBillableKeys = new HashSet<>();
|
|
|
@@ -447,7 +463,8 @@ public class ReSubmitApprovalService {
|
|
|
continue;
|
|
|
}
|
|
|
String pmRaw = firstIdOrText(row, S_BIL_MANAGER);
|
|
|
- final String pm = StringUtils.isBlank(pmRaw) ? DEFAULT_MANAGER_ID : pmRaw;
|
|
|
+ final String pm = projectManagerCache.computeIfAbsent(
|
|
|
+ StringUtils.defaultString(pmRaw), this::resolveProjectManager);
|
|
|
ManagerGroup mg = cr.managerGroups.computeIfAbsent(pm, k -> {
|
|
|
ManagerGroup m = new ManagerGroup();
|
|
|
m.managerId = pm;
|
|
|
@@ -481,7 +498,8 @@ public class ReSubmitApprovalService {
|
|
|
continue;
|
|
|
}
|
|
|
String pmRaw = firstIdOrText(row, S_NON_MANAGER);
|
|
|
- final String pm = StringUtils.isBlank(pmRaw) ? DEFAULT_MANAGER_ID : pmRaw;
|
|
|
+ final String pm = projectManagerCache.computeIfAbsent(
|
|
|
+ StringUtils.defaultString(pmRaw), this::resolveProjectManager);
|
|
|
ManagerGroup mg = cr.managerGroups.computeIfAbsent(pm, k -> {
|
|
|
ManagerGroup m = new ManagerGroup();
|
|
|
m.managerId = pm;
|
|
|
@@ -530,6 +548,81 @@ public class ReSubmitApprovalService {
|
|
|
return cr;
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * prd 项目经理离职时,改由人员档案中的在职主管审批;仅在整条替补链路不可用时兜底 Raymond。
|
|
|
+ */
|
|
|
+ String resolveProjectManager(String managerId) {
|
|
|
+ if (StringUtils.isBlank(managerId) || DEFAULT_MANAGER_ID.equals(managerId)) {
|
|
|
+ return DEFAULT_MANAGER_ID;
|
|
|
+ }
|
|
|
+
|
|
|
+ String accessToken;
|
|
|
+ try {
|
|
|
+ accessToken = ddClient.getAccessToken();
|
|
|
+ } catch (Exception ex) {
|
|
|
+ log.warn("[补发起] 获取钉钉 accessToken 失败,项目经理兜底 Raymond pm={} err={}",
|
|
|
+ managerId, ex.getMessage());
|
|
|
+ return DEFAULT_MANAGER_ID;
|
|
|
+ }
|
|
|
+ if (isUserInService(accessToken, managerId)) {
|
|
|
+ return managerId;
|
|
|
+ }
|
|
|
+
|
|
|
+ String supervisorId;
|
|
|
+ try {
|
|
|
+ supervisorId = queryPersonnelManagerId(managerId);
|
|
|
+ } catch (Exception ex) {
|
|
|
+ log.warn("[补发起] 查询离职 PM 人员档案主管失败,兜底 Raymond pm={} err={}",
|
|
|
+ managerId, ex.getMessage());
|
|
|
+ return DEFAULT_MANAGER_ID;
|
|
|
+ }
|
|
|
+ if (StringUtils.isBlank(supervisorId)) {
|
|
|
+ log.warn("[补发起] 离职 PM 未配置人员档案主管,兜底 Raymond pm={}", managerId);
|
|
|
+ return DEFAULT_MANAGER_ID;
|
|
|
+ }
|
|
|
+ if (DEFAULT_MANAGER_ID.equals(supervisorId)
|
|
|
+ || isUserInService(accessToken, supervisorId)) {
|
|
|
+ log.info("[补发起] 离职 PM 改由人员档案主管审批 pm={} supervisor={}",
|
|
|
+ managerId, supervisorId);
|
|
|
+ return supervisorId;
|
|
|
+ }
|
|
|
+ log.warn("[补发起] 离职 PM 的人员档案主管也不在职,兜底 Raymond pm={} supervisor={}",
|
|
|
+ managerId, supervisorId);
|
|
|
+ return DEFAULT_MANAGER_ID;
|
|
|
+ }
|
|
|
+
|
|
|
+ private boolean isUserInService(String accessToken, String userId) {
|
|
|
+ try {
|
|
|
+ Map user = ddClient_contacts.getUserInfoById(accessToken, userId);
|
|
|
+ return user != null && StringUtils.isNotBlank(str(user, "userid"));
|
|
|
+ } catch (Exception ex) {
|
|
|
+ log.info("[补发起] 钉钉用户不在职或查询失败 userId={} err={}", userId, ex.getMessage());
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private String queryPersonnelManagerId(String userId) {
|
|
|
+ Map<String, Object> search = new HashMap<>();
|
|
|
+ search.put(PERSONNEL_EMPLOYEE, Collections.singletonList(userId));
|
|
|
+ DDR_New response = ydClient.queryData(YDParam.builder()
|
|
|
+ .appType(whConf.getYidaAppType())
|
|
|
+ .systemToken(whConf.getYidaSystemToken())
|
|
|
+ .formUuid(whConf.getFormUuidPersonnel())
|
|
|
+ .searchFieldJson(JSON.toJSONString(search))
|
|
|
+ .currentPage(1)
|
|
|
+ .pageSize(1)
|
|
|
+ .build(), YDConf.FORM_QUERY.retrieve_search_form);
|
|
|
+ List<Map> records = response == null ? null : (List<Map>) response.getData();
|
|
|
+ if (records == null || records.isEmpty()) {
|
|
|
+ return "";
|
|
|
+ }
|
|
|
+ Object formData = records.get(0).get("formData");
|
|
|
+ if (!(formData instanceof Map)) {
|
|
|
+ return "";
|
|
|
+ }
|
|
|
+ return firstEmployeeId((Map) formData, PERSONNEL_MANAGER);
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* 加载指定月份两类审批单的完整明细覆盖索引。
|
|
|
* fixme 流程列表内联子表恰好 50 行时必须 queryDetails 拉全,否则会把第 51 行以后误判为漏单。
|
|
|
@@ -1461,6 +1554,15 @@ public class ReSubmitApprovalService {
|
|
|
return "";
|
|
|
}
|
|
|
|
|
|
+ private String firstEmployeeId(Map m, String fieldPrefix) {
|
|
|
+ Object idArr = m == null ? null : m.get(fieldPrefix + "_id");
|
|
|
+ if (!(idArr instanceof List) || ((List) idArr).isEmpty()) {
|
|
|
+ return "";
|
|
|
+ }
|
|
|
+ Object first = ((List) idArr).get(0);
|
|
|
+ return first == null ? "" : String.valueOf(first);
|
|
|
+ }
|
|
|
+
|
|
|
private String firstNonBlank(String... vals) {
|
|
|
for (String v : vals) {
|
|
|
if (StringUtils.isNotBlank(v)) {
|