Kaynağa Gözat

feat: 审批 h5优惠券具体参数详情

wuwenyi 3 gün önce
ebeveyn
işleme
60d3f6d254

+ 33 - 12
src/main/java/com/qqflow/engine/domain/ewechat/service/EnterpriseWechatMsgService.java

@@ -7,6 +7,7 @@ import com.qqflow.engine.domain.ewechat.config.EnterpriseWechatProperties;
 import com.qqflow.engine.domain.ewechat.mapper.EnterpriseWechatCorpMapper;
 import com.qqflow.engine.domain.ewechat.model.enterprise.req.EnterpriseWechatSendTextCardMsgReq;
 import com.qqflow.engine.domain.ewechat.model.enterprise.req.EnterpriseWechatSendTextMsgReq;
+import com.qqflow.engine.domain.ewechat.model.enterprise.rsp.EnterpriseWechatSendTextMsgRsp;
 import com.qqflow.engine.domain.ewechat.po.EnterpriseWechatCorp;
 import com.qqflow.engine.domain.system.entity.SysUser;
 import com.qqflow.engine.domain.system.entity.SysUserRole;
@@ -40,42 +41,62 @@ public class EnterpriseWechatMsgService {
     /** access_token 有效期 7200 秒,缓存 7000 秒留余量 */
     private static final Duration ACCESS_TOKEN_TTL = Duration.ofSeconds(7000);
 
-    /** 给指定企微用户ID列表发送文本消息 */
-    public void sendTextToUsers(List<String> wecomUserIds, String content) {
-        if (wecomUserIds == null || wecomUserIds.isEmpty()) return;
+    /** 给指定企微用户ID列表发送文本消息;返回是否被企微接口成功接收 */
+    public boolean sendTextToUsers(List<String> wecomUserIds, String content) {
+        if (wecomUserIds == null || wecomUserIds.isEmpty()) return false;
         String accessToken = this.getCorpAccessToken();
         if (accessToken == null) {
             log.warn("[企微消息] 无法获取 access_token");
-            return;
+            return false;
         }
         try {
             EnterpriseWechatSendTextMsgReq req = EnterpriseWechatSendTextMsgReq.toUsers(
                     wecomUserIds, wechatProperties.getAgentId(), content);
-            enterpriseWechatApi.sendTextMsg(accessToken, req);
-            log.info("[企微消息] 已发送 | 接收人数:{}", wecomUserIds.size());
+            EnterpriseWechatSendTextMsgRsp rsp = enterpriseWechatApi.sendTextMsg(accessToken, req);
+            return logSendResult(rsp, "文本消息", wecomUserIds.size());
         } catch (Exception e) {
             log.error("[企微消息] 发送失败", e);
+            return false;
         }
     }
 
-    /** 给指定企微用户ID列表发送文本卡片消息(可点击跳转,用于审批待办/催办) */
-    public void sendTextCardToUsers(List<String> wecomUserIds, String title, String description, String url, String btnText) {
-        if (wecomUserIds == null || wecomUserIds.isEmpty()) return;
+    /** 给指定企微用户ID列表发送文本卡片消息(可点击跳转,用于审批待办/催办);返回是否发送成功 */
+    public boolean sendTextCardToUsers(List<String> wecomUserIds, String title, String description, String url, String btnText) {
+        if (wecomUserIds == null || wecomUserIds.isEmpty()) return false;
         String accessToken = this.getCorpAccessToken();
         if (accessToken == null) {
             log.warn("[企微消息] 无法获取 access_token");
-            return;
+            return false;
         }
         try {
             EnterpriseWechatSendTextCardMsgReq req = EnterpriseWechatSendTextCardMsgReq.toUsers(
                     wecomUserIds, wechatProperties.getAgentId(), title, description, url, btnText);
-            enterpriseWechatApi.sendTextCardMsg(accessToken, req);
-            log.info("[企微消息] 已发送文本卡片 | 接收人数:{} | {}", wecomUserIds.size(), title);
+            EnterpriseWechatSendTextMsgRsp rsp = enterpriseWechatApi.sendTextCardMsg(accessToken, req);
+            return logSendResult(rsp, "文本卡片", wecomUserIds.size());
         } catch (Exception e) {
             log.error("[企微消息] 文本卡片发送失败", e);
+            return false;
         }
     }
 
+    /**
+     * 记录企微接口真实返回码:errcode=0 才算成功。
+     * 之前只看 HTTP 异常,errcode 非 0(如 60020 IP 不在白名单)会被当成发送成功。
+     */
+    private boolean logSendResult(EnterpriseWechatSendTextMsgRsp rsp, String scene, int userCount) {
+        if (rsp == null) {
+            log.error("[企微消息] {}无响应 | 接收人数:{}", scene, userCount);
+            return false;
+        }
+        if (rsp.getErrcode() == null || rsp.getErrcode() == 0) {
+            log.info("[企微消息] {}已发送 | 接收人数:{} | msgid:{}", scene, userCount, rsp.getMsgid());
+            return true;
+        }
+        log.error("[企微消息] {}发送失败 | 接收人数:{} | errcode={} errmsg={} invaliduser={}",
+                scene, userCount, rsp.getErrcode(), rsp.getErrmsg(), rsp.getInvaliduser());
+        return false;
+    }
+
     /** 给指定角色下的所有企微用户发送文本消息 */
     public void sendTextToRole(Long roleId, String content) {
         List<String> wecomUserIds = this.getRoleWecomUserIds(roleId);

+ 6 - 0
src/main/java/com/qqflow/engine/domain/flow/controller/ApprovalTaskController.java

@@ -59,6 +59,12 @@ public class ApprovalTaskController {
         return Result.ok(this.approvalTaskService.handledList(assigneeId, assigneeType, processName, pageNum, pageSize));
     }
 
+    @GetMapping("/{taskId}")
+    @Operation(summary = "审批任务详情(含实例表单快照)")
+    public Result<ApprovalTaskDTO> detail(@PathVariable Long taskId) {
+        return Result.ok(this.approvalTaskService.getDetail(taskId));
+    }
+
     @GetMapping("/{taskId}/next-nodes")
     @Operation(summary = "获取当前任务的下游节点列表(条件节点选分支用)")
     public Result<List<NextNodeDTO>> nextNodes(@PathVariable Long taskId) {

+ 3 - 0
src/main/java/com/qqflow/engine/domain/flow/service/ApprovalTaskService.java

@@ -17,6 +17,9 @@ public interface ApprovalTaskService {
 
     PageResult<ApprovalTaskDTO> handledList(Long assigneeId, String assigneeType, String processName, Integer pageNum, Integer pageSize);
 
+    /** 任务详情:任务 + 实例表单数据/流程名称(H5/PC 审批详情页用) */
+    ApprovalTaskDTO getDetail(Long taskId);
+
     void approve(ApproveTaskDTO dto);
 
     void reject(ApproveTaskDTO dto);

+ 40 - 0
src/main/java/com/qqflow/engine/domain/flow/service/impl/ApprovalTaskServiceImpl.java

@@ -148,6 +148,46 @@ public class ApprovalTaskServiceImpl implements ApprovalTaskService {
     }
 
     @Override
+    public ApprovalTaskDTO getDetail(Long taskId) {
+        ApprovalTask task = this.approvalTaskMapper.selectById(taskId);
+        if (task == null || Integer.valueOf(1).equals(task.getDeleted())) {
+            throw new BusinessException("任务不存在");
+        }
+        ProcessInstance instance = this.processInstanceMapper.selectById(task.getInstanceId());
+        if (instance == null) {
+            throw new BusinessException("流程实例不存在");
+        }
+        LoginUser loginUser = SecurityUtils.getLoginUser();
+        if (loginUser == null) {
+            throw new BusinessException("未登录");
+        }
+        Long userId = loginUser.getUserId();
+        String userType = loginUser.getUserType();
+        // 申请人、管理员、该实例的任务处理人/审批记录操作人均可查看详情
+        boolean visible = this.isApplicantOrAdmin(instance, loginUser, userId, userType)
+                || this.isTaskAssignee(instance.getId(), userId, userType)
+                || this.isRecordOperator(instance.getId(), userId, userType);
+        if (!visible) {
+            throw new BusinessException("无权查看该审批");
+        }
+        ApprovalTaskDTO dto = ApprovalTaskDTO.of(task);
+        dto.setInstanceTitle(instance.getTitle());
+        dto.setInstanceNo(instance.getInstanceNo());
+        // 详情页的表单/券快照来自实例,不能只依赖列表查询字段
+        dto.setFormData(instance.getFormData());
+        ProcessDefinition definition = this.processDefinitionMapper.selectById(instance.getProcessDefinitionId());
+        if (definition != null) {
+            dto.setDefinitionName(definition.getProcessName());
+            dto.setFormSchema(definition.getFormSchema());
+        }
+        this.fillUrgency(dto);
+        List<ApprovalTaskDTO> records = Collections.singletonList(dto);
+        this.approvalTaskDTOAssembler.fillAssigneeName(records);
+        this.approvalTaskDTOAssembler.fillSubNodeName(records);
+        return dto;
+    }
+
+    @Override
     @Transactional
     public void approve(ApproveTaskDTO dto) {
         ApprovalTask task = this.getPendingTaskForUpdate(dto.getTaskId());

+ 6 - 1
src/main/java/com/qqflow/engine/domain/flow/service/impl/WeComNotificationService.java

@@ -71,7 +71,12 @@ public class WeComNotificationService implements NotificationService {
         String url = h5Url();
         if (url != null) {
             // 文本卡片:券面摘要 + 可点击跳转企微 H5 待办(点击后免登)
-            wechatMsgService.sendTextCardToUsers(wecomUserIds, title, description, url + "/m/todo", "去审批");
+            boolean cardSent = wechatMsgService.sendTextCardToUsers(
+                    wecomUserIds, title, description, url + "/m/todo", "去审批");
+            if (!cardSent) {
+                // 卡片发送失败时降级纯文本,至少保证审批人收到待办提醒
+                wechatMsgService.sendTextToUsers(wecomUserIds, "【待办提醒】\n" + toPlainText(description));
+            }
         } else {
             wechatMsgService.sendTextToUsers(wecomUserIds, "【待办提醒】\n" + toPlainText(description));
         }

+ 4 - 2
src/main/resources/mapper/flow/ApprovalTaskMapper.xml

@@ -70,7 +70,8 @@
     </select>
 
     <select id="selectHandledList" resultMap="BaseResultMap">
-        SELECT DISTINCT t.*, pi.title as instance_title, pi.instance_no as instance_no, pd.process_name as process_name
+        SELECT DISTINCT t.*, pi.title as instance_title, pi.instance_no as instance_no, pi.form_data as form_data,
+               pd.process_name as process_name, pd.form_schema as form_schema
         FROM bpm_approval_task t
         INNER JOIN bpm_process_instance pi ON t.instance_id = pi.id
         LEFT JOIN bpm_process_definition pd ON pi.process_definition_id = pd.id
@@ -85,7 +86,8 @@
     </select>
 
     <select id="selectCcList" resultMap="BaseResultMap">
-        SELECT t.*, pi.title as instance_title, pi.instance_no as instance_no, pd.process_name as process_name
+        SELECT t.*, pi.title as instance_title, pi.instance_no as instance_no, pi.form_data as form_data,
+               pd.process_name as process_name, pd.form_schema as form_schema
         FROM bpm_approval_task t
         INNER JOIN bpm_process_instance pi ON t.instance_id = pi.id
         LEFT JOIN bpm_process_definition pd ON pi.process_definition_id = pd.id