2 Commits 0d31443033 ... f6cf11c4e2

Author SHA1 Message Date
  malk f6cf11c4e2 feat(workhours): 审批回写同步状态字段接入(v0.7.1) 1 week ago
  malk 21a98a61b8 fix(workhours): 审批回写修 resolveDetailRows NPE + 加同步状态字段(v0.6+v0.7) 1 week ago

+ 17 - 0
mjava-akdsbeisen/src/main/java/com/malk/server/workhours/ApprovalWriteBackResult.java

@@ -54,4 +54,21 @@ public class ApprovalWriteBackResult {
      * 处理失败的记录数
      */
     private int failRecords;
+
+    /**
+     * 同步状态:成功 / 部分成功 / 失败
+     * <p>
+     * 计算口径:failCount==0 → 成功;failCount<syncTotal → 部分成功;failCount==syncTotal → 失败
+     */
+    private String syncStatus;
+
+    /**
+     * 同步总数 = 需要处理的汇总表「人+天」日记录组数 (== groups)
+     */
+    private int syncTotal;
+
+    /**
+     * 失败数量 = 线程执行/重试耗尽后的失败组数 (== failRecords,独立字段便于排查/对外可见)
+     */
+    private int failCount;
 }

+ 14 - 0
mjava-akdsbeisen/src/main/java/com/malk/server/workhours/WHConf.java

@@ -27,4 +27,18 @@ public class WHConf {
     private String yidaAppType;
 
     private String yidaSystemToken;
+
+    // prd 审批回写同步状态字段:写回原审批单(工时审批 / 其他工时审批 各 3 个 fieldId)
+    // fixme 6 个 fieldId 任一为空则跳过该类别的同步字段回写(向后兼容,允许业务方分阶段建字段)
+    private String approvalSyncStatusField;        // 工时审批 - 同步状态(单选/文本)
+
+    private String approvalSyncTotalField;         // 工时审批 - 同步总数(数字)
+
+    private String approvalSyncFailField;          // 工时审批 - 失败数量(数字)
+
+    private String otherApprovalSyncStatusField;   // 其他工时审批 - 同步状态(单选/文本)
+
+    private String otherApprovalSyncTotalField;    // 其他工时审批 - 同步总数(数字)
+
+    private String otherApprovalSyncFailField;     // 其他工时审批 - 失败数量(数字)
 }

+ 62 - 4
mjava-akdsbeisen/src/main/java/com/malk/service/workhours/ApprovalWriteBackService.java

@@ -13,6 +13,7 @@ import com.malk.utils.UtilMap;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.lang3.StringUtils;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.scheduling.annotation.Async;
 import org.springframework.stereotype.Service;
 
 import java.util.*;
@@ -125,6 +126,7 @@ public class ApprovalWriteBackService {
      * @param result            审批结果:0=拒绝/撤销,1=同意
      * @return 回写结果统计
      */
+    @Async
     public ApprovalWriteBackResult writeBack(String formInstanceId, int result) {
         McException.assertAccessException(StringUtils.isBlank(formInstanceId), "实例ID不能为空");
 
@@ -209,6 +211,13 @@ public class ApprovalWriteBackService {
             executor.shutdown();
         }
 
+        // 6. 同步状态汇总(syncTotal=分组数;failCount=失败组数)
+        // fixme 状态字面值对齐宜搭审批表 selectField_mq58cd5p 选项:"全部成功" / "部分失败" / "全部失败"
+        int syncTotal = groups.size();
+        int failCount = failRecords.get();
+        String syncStatus = (syncTotal == 0 || failCount == 0) ? "全部成功"
+                : (failCount < syncTotal ? "部分失败" : "全部失败");
+
         ApprovalWriteBackResult stats = ApprovalWriteBackResult.builder()
                 .formType(isOther ? "其他工时审批" : "工时审批")
                 .formInstanceId(formInstanceId)
@@ -218,12 +227,53 @@ public class ApprovalWriteBackService {
                 .hitRecords(hitRecords.get())
                 .updatedRows(updatedRows.get())
                 .missRows(missRows.get())
-                .failRecords(failRecords.get())
+                .failRecords(failCount)
+                .syncStatus(syncStatus)
+                .syncTotal(syncTotal)
+                .failCount(failCount)
                 .build();
         log.info("[审批回写] 完成 {}", stats);
+
+        // 7. 延迟 2s 等宜搭审批结束的内部同步索引/锁释放,再回写审批单同步字段,避免触发同步锁失败
+        sleep(2000);
+        writebackSyncFields(isOther, formInstanceId, syncStatus, syncTotal, failCount);
+
         return stats;
     }
 
+    /**
+     * 把同步状态/同步总数/失败数量回写到原审批单(fieldId 走 WHConf 配置)
+     * fixme 三个 fieldId 任一为空则跳过该类别回写,允许业务方分阶段在宜搭后台建字段
+     */
+    private void writebackSyncFields(boolean isOther, String formInstanceId,
+                                     String syncStatus, int syncTotal, int failCount) {
+        String statusField = isOther ? whConf.getOtherApprovalSyncStatusField() : whConf.getApprovalSyncStatusField();
+        String totalField = isOther ? whConf.getOtherApprovalSyncTotalField() : whConf.getApprovalSyncTotalField();
+        String failField = isOther ? whConf.getOtherApprovalSyncFailField() : whConf.getApprovalSyncFailField();
+        if (StringUtils.isAnyBlank(statusField, totalField, failField)) {
+            log.info("[审批回写] 跳过审批单同步字段回写(配置未启用) isOther={} formInstanceId={}", isOther, formInstanceId);
+            return;
+        }
+        try {
+            Map<String, Object> sync = new HashMap<>();
+            sync.put(statusField, syncStatus);
+            sync.put(totalField, syncTotal);
+            sync.put(failField, failCount);
+            ydClient.operateData(YDParam.builder()
+                    .appType(whConf.getYidaAppType())
+                    .systemToken(whConf.getYidaSystemToken())
+                    .formInstanceId(formInstanceId)
+                    .updateFormDataJson(JSON.toJSONString(sync))
+                    .ignoreEmpty(false)
+                    .useLatestVersion(true)
+                    .build(), YDConf.FORM_OPERATION.update);
+            log.info("[审批回写] 审批单同步字段已回写 isOther={} formInstanceId={} status={} total={} fail={}",
+                    isOther, formInstanceId, syncStatus, syncTotal, failCount);
+        } catch (Exception ex) {
+            log.error("[审批回写] 审批单同步字段回写失败 isOther={} formInstanceId={}", isOther, formInstanceId, ex);
+        }
+    }
+
     /**
      * 处理单条汇总表日记录
      *
@@ -378,20 +428,28 @@ public class ApprovalWriteBackService {
 
     /**
      * 子表取数控制:主表详情已内联返回子表时,行数 &lt;50 视为完整直接用;
-     * ==50(可能被宜搭截断)或内联缺失,才调 {@link YDService#queryDetails} 递归取全,避免无效请求。
+     * ==50(可能被宜搭截断)才调 {@link YDService#queryDetails} 递归取全,避免无效请求。
      * 复用 ydService 封装,不重复造分页轮子。
+     * <p>
+     * fixme inlineRows==null 表示审批单不含该子表(如其他工时审批单无 BILLABLE/NON_BILLABLE,或工时审批单某一子表未填):
+     * 直接返回空 list,不再调 queryDetails——基座 _queryDetails 对宜搭返回 data=null 的空子表查询会触发
+     * ArrayList.addAll(null) NPE。queryDetails 返回 null 同样兜底空 list。
      */
     private List<Map> resolveDetailRows(String formInstanceId, String tableId, List<Map> inlineRows) {
-        if (inlineRows != null && inlineRows.size() < 50) {
+        if (inlineRows == null) {
+            return Collections.emptyList();
+        }
+        if (inlineRows.size() < 50) {
             return inlineRows;
         }
-        return ydService.queryDetails(YDParam.builder()
+        List<Map> full = ydService.queryDetails(YDParam.builder()
                 .appType(whConf.getYidaAppType())
                 .systemToken(whConf.getYidaSystemToken())
                 .formInstanceId(formInstanceId)
                 .tableFieldId(tableId)
                 .pageNumber(1)
                 .build());
+        return full != null ? full : Collections.emptyList();
     }
 
     /**

+ 8 - 0
mjava-akdsbeisen/src/main/resources/application-dev.yml

@@ -76,3 +76,11 @@ workhours:
   formUuidOtherApproval: "FORM-4828E0E40CD34038825E8C6E25417B2718NF"   # 正式 FORM-BA14F6322DBD470F8CF84CCE131DEA31TIJS
   yidaAppType: "APP_ZQ3I7XO2RSHDJ4QDEVNB"
   yidaSystemToken: "FOD66381NOS25MERLN2UK92FY96Y21UMHD7LM36S"
+  # 审批回写同步状态字段:两类审批表 fieldId 一致(业务方建字段时同名同 ID)
+  # 同步状态 SelectField 选项必须为「全部成功 / 部分失败 / 全部失败」,与 ApprovalWriteBackService.syncStatus 计算口径对齐
+  approvalSyncStatusField: "selectField_mq58cd5p"
+  approvalSyncTotalField: "numberField_mq58cd5q"
+  approvalSyncFailField: "numberField_mq58cd5r"
+  otherApprovalSyncStatusField: "selectField_mq58cd5p"
+  otherApprovalSyncTotalField: "numberField_mq58cd5q"
+  otherApprovalSyncFailField: "numberField_mq58cd5r"