Browse Source

feat: 优惠券流程卡片配置

wuwenyi 4 ngày trước cách đây
mục cha
commit
8f632a55ae

+ 1 - 0
src/main/java/com/qqflow/engine/domain/flow/listener/NotificationEventListener.java

@@ -24,6 +24,7 @@ public class NotificationEventListener {
     public void onTaskAssigned(TaskAssignedEvent event) {
         try {
             notificationService.notifyTaskAssigned(
+                    event.getInstanceId(),
                     event.getAssigneeIds(),
                     event.getAssigneeType(),
                     event.getProcessName(),

+ 3 - 1
src/main/java/com/qqflow/engine/domain/flow/service/NotificationService.java

@@ -13,13 +13,15 @@ public interface NotificationService {
     /**
      * 发送任务分配通知(待我处理)
      *
+     * @param instanceId    实例ID(用于读取表单快照,生成券审批卡片内容)
      * @param assigneeIds   接收人ID列表
      * @param assigneeType  接收人类型(USER / ROLE)
      * @param processName   流程名称
      * @param instanceTitle 实例标题
      * @param nodeName      当前节点名称
      */
-    void notifyTaskAssigned(List<Long> assigneeIds, String assigneeType, String processName, String instanceTitle, String nodeName);
+    void notifyTaskAssigned(Long instanceId, List<Long> assigneeIds, String assigneeType,
+                            String processName, String instanceTitle, String nodeName);
 
     /**
      * 发送任务完成通知(审批结果通知)

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

@@ -1,5 +1,7 @@
 package com.qqflow.engine.domain.flow.service.impl;
 
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
 import com.qqflow.engine.domain.ewechat.service.EnterpriseWechatMsgService;
 import com.qqflow.engine.domain.flow.enums.ApprovalAction;
 import com.qqflow.engine.domain.flow.mapper.ProcessInstanceMapper;
@@ -45,6 +47,7 @@ public class WeComNotificationService implements NotificationService {
     private final SysNotificationConfigService sysNotificationConfigService;
     private final EnterpriseWechatMsgService wechatMsgService;
     private final ProcessInstanceMapper processInstanceMapper;
+    private final ObjectMapper objectMapper;
 
     /**
      * H5 前端地址(逗号分隔:第一个为实际跳转地址,其余为免登 redirect 白名单);
@@ -54,7 +57,8 @@ public class WeComNotificationService implements NotificationService {
     private String h5BaseUrl;
 
     @Override
-    public void notifyTaskAssigned(List<Long> assigneeIds, String assigneeType, String processName, String instanceTitle, String nodeName) {
+    public void notifyTaskAssigned(Long instanceId, List<Long> assigneeIds, String assigneeType,
+                                   String processName, String instanceTitle, String nodeName) {
         if (isSuppressed() || !this.isEnabled()) return;
         List<String> wecomUserIds = new ArrayList<>();
         for (Long assigneeId : assigneeIds) {
@@ -62,17 +66,93 @@ public class WeComNotificationService implements NotificationService {
             if (wecomUserId != null) wecomUserIds.add(wecomUserId);
         }
         if (wecomUserIds.isEmpty()) return;
-        String description = String.format("流程:%s\n标题:%s\n节点:%s\n请及时处理。", processName, instanceTitle, nodeName);
+        String title = buildTaskTitle(instanceId, instanceTitle);
+        String description = buildTaskDescription(instanceId, processName, instanceTitle, nodeName);
         String url = h5Url();
         if (url != null) {
-            // 文本卡片:可点击跳转企微 H5 待办(点击后免登)
-            wechatMsgService.sendTextCardToUsers(wecomUserIds, textCardTitle("【待办审批】", instanceTitle),
-                    description, url + "/m/todo", "去审批");
+            // 文本卡片:券面摘要 + 可点击跳转企微 H5 待办(点击后免登)
+            wechatMsgService.sendTextCardToUsers(wecomUserIds, title, description, url + "/m/todo", "去审批");
         } else {
-            wechatMsgService.sendTextToUsers(wecomUserIds, "【待办提醒】\n" + description);
+            wechatMsgService.sendTextToUsers(wecomUserIds, "【待办提醒】\n" + toPlainText(description));
+        }
+    }
+
+    /**
+     * 待办卡片标题:券审批用「【优惠券审批】券名待您审批」,其它流程用「【待办审批】实例标题」
+     */
+    private String buildTaskTitle(Long instanceId, String instanceTitle) {
+        JsonNode task = loadScrmTask(instanceId);
+        if (task != null && task.path("taskType").asInt() == 10) {
+            String couponName = task.path("taskName").asText(instanceTitle);
+            return textCardTitle("【优惠券审批】", couponName + "待您审批");
+        }
+        return textCardTitle("【待办审批】", instanceTitle);
+    }
+
+    /**
+     * 待办卡片描述:券审批从 formData.scrmTask 取券名/力度/数量/推送方;
+     * 其它流程保持通用的流程/标题/节点描述。
+     */
+    private String buildTaskDescription(Long instanceId, String processName, String instanceTitle, String nodeName) {
+        JsonNode task = loadScrmTask(instanceId);
+        if (task != null && task.path("taskType").asInt() == 10) {
+            String couponName = task.path("taskName").asText(instanceTitle);
+            String face = findItemValue(task, "发行信息", "面额/折扣");
+            String count = findItemValue(task, "发行信息", "发行数量");
+            String submitter = task.path("submitUserName").asText("");
+            StringBuilder sb = new StringBuilder();
+            sb.append("<div class=\"gray\">券名称:").append(couponName).append("</div>");
+            if (StringUtils.hasText(face)) {
+                sb.append("<div class=\"gray\">优惠力度:").append(face).append("</div>");
+            }
+            if (StringUtils.hasText(count)) {
+                sb.append("<div class=\"gray\">发行数量:").append(count).append("</div>");
+            }
+            if (StringUtils.hasText(submitter)) {
+                sb.append("<div class=\"gray\">推送方:").append(submitter).append("</div>");
+            }
+            sb.append("<div class=\"gray\">当前节点:").append(nodeName).append("</div>");
+            return sb.toString();
+        }
+        return String.format("流程:%s\n标题:%s\n节点:%s\n请及时处理。", processName, instanceTitle, nodeName);
+    }
+
+    private JsonNode loadScrmTask(Long instanceId) {
+        if (instanceId == null) {
+            return null;
+        }
+        ProcessInstance instance = processInstanceMapper.selectById(instanceId);
+        if (instance == null || !StringUtils.hasText(instance.getFormData())) {
+            return null;
+        }
+        try {
+            JsonNode task = objectMapper.readTree(instance.getFormData()).path("scrmTask");
+            return task.isMissingNode() ? null : task;
+        } catch (Exception e) {
+            log.warn("[企微消息] 解析表单快照失败 instanceId={}", instanceId, e);
+            return null;
         }
     }
 
+    private String findItemValue(JsonNode task, String groupTitle, String label) {
+        for (JsonNode group : task.path("groups")) {
+            if (!groupTitle.equals(group.path("title").asText())) {
+                continue;
+            }
+            for (JsonNode item : group.path("items")) {
+                if (label.equals(item.path("label").asText())) {
+                    return item.path("value").asText();
+                }
+            }
+        }
+        return null;
+    }
+
+    /** textcard 的 HTML 描述降级为纯文本(未配置 H5 时使用) */
+    private String toPlainText(String description) {
+        return description.replace("<div class=\"gray\">", "").replace("</div>", "\n").trim();
+    }
+
     @Override
     public void notifyTaskCompleted(Long instanceId, String processName, String instanceTitle,
                                     String nodeName, String operatorName, String action) {

+ 6 - 1
src/main/resources/application-test.yml

@@ -45,4 +45,9 @@ enterprise-wechat:
   suite-id: dk0580416972e402b8
   suite-secret: 3aV48BTZHxDBeltZgdiRAUhttqsE5RxNs5pzElGhHHg
   agent-id: 1000095
-  parent-id: 0
+  parent-id: 0
+
+# 企微 H5 审批端(textcard 跳转地址)
+wecom:
+  # 未配置时审批消息会降级为纯文本;测试环境 H5 部署在 192.168.110.4/m/
+  h5-base-url: ${WECOM_H5_BASE_URL:http://192.168.110.4}