MES本地审批共用钉钉审批等配置

This commit is contained in:
geht
2026-06-10 16:33:44 +08:00
parent c4447b91dd
commit 617d47a3db
19 changed files with 980 additions and 36 deletions

View File

@@ -944,6 +944,42 @@ jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/service/impl/MesXslMixingSpecServiceImpl.java
jeecgboot-vue3/src/views/xslmes/mesXslMixingSpec/components/MesXslMixingSpecModal.vue
-- author:GHT---date:20260610--for: 【IM审批通用化】IM工作通知公众号(同事列表置顶+审批消息统一推送) -----
jeecg-boot/jeecg-module-system/jeecg-system-start/src/main/resources/flyway/sql/mysql/V3.9.2_147__sys_im_work_notify_user.sql
jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/im/service/impl/SysImChatServiceImpl.java
jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/im/vo/SysImContactVO.java
jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/im/service/ISysImChatService.java
jeecgboot-vue3/src/views/system/im/ImChat.vue
jeecgboot-vue3/src/views/system/im/imCache.ts
jeecgboot-vue3/src/views/system/im/ImCreateGroupModal.vue
-- author:GHT---date:20260610--for: 【IM审批通用化】审批待办IM发送修复(admin自审重复成员+独立事务防审批回滚) -----
jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/im/service/impl/SysImChatServiceImpl.java
-- author:GHT---date:20260610--for: 【IM审批通用化】IM卡片字段取值修复(下划线列名+valueMode与钉钉发起对齐) -----
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/dingtalk/service/DingTplImCardBuilder.java
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/dingtalk/service/DingTplBindFieldValueResolver.java
-- author:GHT---date:20260610--for: 【IM审批通用化】流转中实例支持补发IM审批卡片(审批台账入口) -----
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/approval/service/IMesXslApprovalHandleService.java
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/approval/service/impl/MesXslApprovalHandleServiceImpl.java
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/approval/controller/MesXslApprovalHandleController.java
jeecgboot-vue3/src/views/approval/flow/approvalHandle.api.ts
jeecgboot-vue3/src/views/xslmes/approval/mesXslApprovalRecord/MesXslApprovalRecordList.vue
-- author:GHT---date:20260610--for: 【IM审批通用化】发起人=处理人时审批待办改由admin代发IM消息 -----
jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/im/service/ISysImChatService.java
jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/im/service/impl/SysImChatServiceImpl.java
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/approval/service/impl/MesXslApprovalHandleServiceImpl.java
-- author:GHT---date:20260610--for: 【IM审批通用化】IM卡片复用钉钉模板字段+MES回调补齐stageKey走集成方案 -----
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/dingtalk/service/DingTplImCardBuilder.java
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/dingtalk/service/IMesXslDingTplBindService.java
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/dingtalk/service/impl/MesXslDingTplBindServiceImpl.java
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/approval/service/impl/MesXslApprovalHandleServiceImpl.java
jeecgboot-vue3/src/views/system/im/imBizRecordMessage.ts
jeecgboot-vue3/src/views/system/im/ImBizRecordMessageContent.vue
-- author:GHT---date:20260610--for: 【混炼示方】TCU温度条件新增是否附加/重量字段 -----
jeecg-boot/jeecg-module-system/jeecg-system-start/src/main/resources/flyway/sql/mysql/V3.9.2_146__mes_xsl_mixing_spec_tcu_attach.sql
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/entity/MesXslMixingSpecTcu.java

View File

@@ -108,4 +108,20 @@ public class MesXslApprovalHandleController {
return Result.OK(approvalHandleService.pendingList(user));
}
//update-end---author:GHT ---date:2026-05-29 for【QH-MES审批流完善】待办列表-----
//update-begin---author:GHT ---date:20260610 for【IM审批通用化】补发IM审批卡片(历史流转中实例)-----------
@Operation(summary = "审批办理-补发IM审批卡片(流转中)")
@PostMapping("/resendCard")
public Result<String> resendCard(@RequestBody Map<String, Object> body) {
String instanceId = body.get("instanceId") == null ? null : String.valueOf(body.get("instanceId"));
String bizTable = body.get("bizTable") == null ? null : String.valueOf(body.get("bizTable"));
String bizDataId = body.get("bizDataId") == null ? null : String.valueOf(body.get("bizDataId"));
if (oConvertUtils.isEmpty(instanceId)
&& (oConvertUtils.isEmpty(bizTable) || oConvertUtils.isEmpty(bizDataId))) {
return Result.error("请提供 instanceId 或 bizTable+bizDataId");
}
LoginUser user = (LoginUser) SecurityUtils.getSubject().getPrincipal();
return approvalHandleService.resendCard(instanceId, bizTable, bizDataId, user);
}
//update-end---author:GHT ---date:20260610 for【IM审批通用化】补发IM审批卡片(历史流转中实例)-----------
}

View File

@@ -57,6 +57,14 @@ public interface IMesXslApprovalHandleService {
Result<String> urge(String instanceId, LoginUser user);
//update-end---author:GHT ---date:2026-05-29 for【QH-MES审批流完善】催办接口-----
//update-begin---author:GHT ---date:20260610 for【IM审批通用化】流转中实例补发IM审批卡片-----------
/**
* 补发当前节点审批卡片(用于历史数据未收到 IM 消息等场景)。
* instanceId 与 (bizTable+bizDataId) 二选一;仅审批中且 MES 通道实例可补发。
*/
Result<String> resendCard(String instanceId, String bizTable, String bizDataId, LoginUser user);
//update-end---author:GHT ---date:20260610 for【IM审批通用化】流转中实例补发IM审批卡片-----------
//update-begin---author:GHT ---date:2026-05-29 for【QH-MES审批流完善】待办列表查询-----
/**
* 查询当前用户的待办审批列表(状态为审批中且当前处理人包含该用户)。

View File

@@ -23,6 +23,7 @@ import org.jeecg.modules.xslmes.approval.service.IMesXslApprovalHandleService;
import org.jeecg.modules.xslmes.approval.integration.entity.MesXslIntegrationPlan;
import org.jeecg.modules.xslmes.approval.integration.service.IMesXslIntegrationPlanService;
import org.jeecg.modules.xslmes.approval.service.IMesXslApprovalInstanceService;
import org.jeecg.modules.xslmes.dingtalk.service.DingTplImCardBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
@@ -100,6 +101,11 @@ public class MesXslApprovalHandleServiceImpl implements IMesXslApprovalHandleSer
@Autowired
private IMesXslIntegrationPlanService integrationPlanService;
//update-end---author:GHT ---date:20260605 for【XSLMES-20260605-K8R2】驳回回退改由集成方案 onReject 驱动-----------
//update-begin---author:GHT ---date:20260610 for【IM审批通用化】IM卡片字段与钉钉模板绑定对齐-----------
@Autowired
private DingTplImCardBuilder dingTplImCardBuilder;
//update-end---author:GHT ---date:20260610 for【IM审批通用化】IM卡片字段与钉钉模板绑定对齐-----------
//update-end---author:GHT ---date:2026-05-29 for【QH-MES审批流设计】审批与业务单据联动回调-----
// ==================== 发起后进入首节点 ====================
@@ -691,11 +697,11 @@ public class MesXslApprovalHandleServiceImpl implements IMesXslApprovalHandleSer
oConvertUtils.getString(inst.getApplyUserName(), inst.getApplyUser()), inst.getFlowName(), actionLabel);
msgType = "text";
}
SysUser applicant = getUserSafely(inst.getApplyUser());
String fromId = applicant == null ? null : applicant.getId();
//update-begin---author:GHT ---date:20260610 for【IM审批通用化】待办卡片由系统账号发给处理人,支持发起人=处理人-----------
for (String uname : handlerUsernames) {
sendOne(fromId, uname, inst.getTenantId(), content, msgType);
sendApprovalHandlerNotify(uname, inst.getTenantId(), content, msgType);
}
//update-end---author:GHT ---date:20260610 for【IM审批通用化】待办卡片由系统账号发给处理人,支持发起人=处理人-----------
}
/** 抄送通知(无办理按钮) */
@@ -755,6 +761,24 @@ public class MesXslApprovalHandleServiceImpl implements IMesXslApprovalHandleSer
}
}
//update-begin---author:GHT ---date:20260610 for【IM审批通用化】审批待办IM通知-----------
private void sendApprovalHandlerNotify(String toUsername, Integer tenantId, String content, String msgType) {
String uname = toUsername == null ? "" : toUsername.trim();
if (oConvertUtils.isEmpty(uname)) {
return;
}
try {
SysUser to = sysUserService.getUserByName(uname);
if (to == null) {
return;
}
sysImChatService.sendApprovalHandlerMessage(to.getId(), tenantId, content, msgType);
} catch (Exception e) {
log.warn("发送审批待办IM消息失败 to={}", uname, e);
}
}
//update-end---author:GHT ---date:20260610 for【IM审批通用化】审批待办IM通知-----------
/** 构建 biz_record 卡片 JSON含 instanceId / actionLabel / canApprove与前端 ImBizRecordPayload 对齐 v=2 */
private String buildCardJson(MesXslApprovalInstance inst, String actionLabel, boolean canApprove, String routePath) {
//update-begin---author:GHT ---date:2026-05-29 for【QH-MES审批流设计】区分审批卡片与抄送通知卡片-----
@@ -766,6 +790,15 @@ public class MesXslApprovalHandleServiceImpl implements IMesXslApprovalHandleSer
*/
private String buildCardJson(MesXslApprovalInstance inst, String actionLabel, boolean canApprove, String routePath, boolean approvalCard) {
//update-end---author:GHT ---date:2026-05-29 for【QH-MES审批流设计】区分审批卡片与抄送通知卡片-----
//update-begin---author:GHT ---date:20260610 for【IM审批通用化】优先按钉钉模板绑定构建卡片字段-----------
MesXslApprovalFlow flow = flowService.getById(inst.getFlowId());
JSONObject dingPayload = dingTplImCardBuilder.buildCardPayload(
inst, flow, actionLabel, canApprove, routePath, approvalCard);
if (dingPayload != null) {
return dingPayload.toJSONString();
}
//update-end---author:GHT ---date:20260610 for【IM审批通用化】优先按钉钉模板绑定构建卡片字段-----------
JSONArray fields = new JSONArray();
addField(fields, "审批流", inst.getFlowName());
addField(fields, "单据", safeTitle(inst));
@@ -1353,10 +1386,26 @@ public class MesXslApprovalHandleServiceImpl implements IMesXslApprovalHandleSer
if (user != null) {
ctx.setOperatorUsername(user.getUsername());
ctx.setOperatorName(oConvertUtils.getString(user.getRealname(), user.getUsername()));
ctx.setOperatorTime(new Date());
} else {
ctx.setOperatorUsername("system");
ctx.setOperatorName("系统");
}
//update-begin---author:GHT ---date:20260610 for【IM审批通用化】MES通道补齐stageKey与钉钉回调共用集成方案-----------
if (oConvertUtils.isNotEmpty(nodeId) && oConvertUtils.isNotEmpty(inst.getFlowId())) {
MesXslApprovalFlow flow = flowService.getById(inst.getFlowId());
if (flow != null && oConvertUtils.isNotEmpty(flow.getFlowConfig())) {
JSONObject root = safeParse(flow.getFlowConfig());
JSONObject node = root == null ? null : findNodeById(root, nodeId);
if (node != null) {
JSONObject props = node.getJSONObject("props");
if (props != null && props.containsKey("stageKey")) {
ctx.setStageKey(props.getString("stageKey"));
}
}
}
}
//update-end---author:GHT ---date:20260610 for【IM审批通用化】MES通道补齐stageKey与钉钉回调共用集成方案-----------
return ctx;
}
@@ -1455,7 +1504,7 @@ public class MesXslApprovalHandleServiceImpl implements IMesXslApprovalHandleSer
if (oConvertUtils.isEmpty(uname)) {
continue;
}
sendOne(user.getId(), uname, inst.getTenantId(),
sendApprovalHandlerNotify(uname, inst.getTenantId(),
"【催办提醒】" + applicantName + " 催促您处理「" + safeTitle(inst) + "」,请尽快审批。", "text");
}
urgeTimeMap.put(instanceId, System.currentTimeMillis());
@@ -1463,6 +1512,77 @@ public class MesXslApprovalHandleServiceImpl implements IMesXslApprovalHandleSer
}
//update-end---author:GHT ---date:2026-05-29 for【QH-MES审批流完善】催办发起人向当前处理人发催办提醒-----
//update-begin---author:GHT ---date:20260610 for【IM审批通用化】流转中实例补发IM审批卡片-----------
@Override
public Result<String> resendCard(String instanceId, String bizTable, String bizDataId, LoginUser user) {
if (user == null) {
return Result.error("请先登录");
}
MesXslApprovalInstance inst = resolveRunningInstance(instanceId, bizTable, bizDataId);
if (inst == null) {
return Result.error("未找到审批中的 MES 审批实例");
}
if (!"0".equals(inst.getStatus())) {
return Result.error("该审批已结束,无法补发卡片");
}
if (!canResendCard(user, inst)) {
return Result.error("仅发起人或当前处理人可以补发审批卡片");
}
if (oConvertUtils.isEmpty(inst.getCurrentHandlers())) {
return Result.error("当前无待处理人,无法补发");
}
MesXslApprovalFlow flow = flowService.getById(inst.getFlowId());
if (flow == null) {
return Result.error("审批流不存在");
}
List<String> handlers = new ArrayList<>();
for (String uname : inst.getCurrentHandlers().split(",")) {
if (oConvertUtils.isNotEmpty(uname)) {
handlers.add(uname.trim());
}
}
if (handlers.isEmpty()) {
return Result.error("当前无待处理人,无法补发");
}
String actionLabel = oConvertUtils.getString(inst.getCurrentNodeName(), "审批");
sendApprovalCard(inst, flow, actionLabel, handlers);
return Result.OK("已向 " + handlers.size() + " 位处理人补发审批卡片,请在 IM 中查看与 admin 的会话");
}
private MesXslApprovalInstance resolveRunningInstance(String instanceId, String bizTable, String bizDataId) {
if (oConvertUtils.isNotEmpty(instanceId)) {
MesXslApprovalInstance inst = instanceService.getById(instanceId);
return inst == null || !"0".equals(inst.getStatus()) ? null : inst;
}
if (oConvertUtils.isEmpty(bizTable) || oConvertUtils.isEmpty(bizDataId)
|| !IDENTIFIER.matcher(bizTable).matches()) {
return null;
}
return instanceService.lambdaQuery()
.eq(MesXslApprovalInstance::getBizTable, bizTable)
.eq(MesXslApprovalInstance::getBizDataId, bizDataId)
.eq(MesXslApprovalInstance::getStatus, "0")
.orderByDesc(MesXslApprovalInstance::getCreateTime)
.last("LIMIT 1")
.one();
}
private boolean canResendCard(LoginUser user, MesXslApprovalInstance inst) {
if (user.getUsername().equals(inst.getApplyUser())) {
return true;
}
if (oConvertUtils.isEmpty(inst.getCurrentHandlers())) {
return false;
}
for (String uname : inst.getCurrentHandlers().split(",")) {
if (user.getUsername().equals(uname == null ? "" : uname.trim())) {
return true;
}
}
return false;
}
//update-end---author:GHT ---date:20260610 for【IM审批通用化】流转中实例补发IM审批卡片-----------
// ==================== 待办列表 ====================
//update-begin---author:GHT ---date:2026-05-29 for【QH-MES审批流完善】待办列表查询当前用户的待处理审批实例-----

View File

@@ -3,6 +3,7 @@ package org.jeecg.modules.xslmes.dingtalk.service;
import java.util.Map;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.jeecg.common.util.oConvertUtils;
import org.jeecg.modules.print.vo.PrintBizFieldItemVO;
import org.jeecg.modules.system.service.ISysDictService;
import org.springframework.beans.factory.annotation.Autowired;
@@ -79,7 +80,7 @@ public class DingTplBindFieldValueResolver {
return null;
}
if (cur instanceof Map) {
cur = ((Map<String, Object>) cur).get(p);
cur = getMapValue((Map<String, Object>) cur, p);
} else {
return null;
}
@@ -87,6 +88,32 @@ public class DingTplBindFieldValueResolver {
return cur;
}
/** JDBC 行多为下划线列名,绑定配置为驼峰字段名,双向兼容取值 */
@SuppressWarnings("unchecked")
private Object getMapValue(Map<String, Object> map, String key) {
if (map == null || StringUtils.isBlank(key)) {
return null;
}
Object val = map.get(key);
if (val != null) {
return val;
}
String underline = oConvertUtils.camelToUnderline(key);
if (!underline.equals(key)) {
val = map.get(underline);
if (val != null) {
return val;
}
}
if (key.contains("_")) {
String camel = oConvertUtils.camelName(key);
if (!camel.equals(key)) {
val = map.get(camel);
}
}
return val;
}
@SuppressWarnings("unchecked")
private String getDictTextFromRow(Object rowData, String bizField) {
if (!(rowData instanceof Map) || StringUtils.isBlank(bizField)) {
@@ -95,13 +122,13 @@ public class DingTplBindFieldValueResolver {
Map<String, Object> map = (Map<String, Object>) rowData;
String[] parts = bizField.split("\\.");
if (parts.length == 1) {
Object v = map.get(parts[0] + "_dictText");
Object v = getMapValue(map, parts[0] + "_dictText");
return v != null ? String.valueOf(v) : null;
}
String parentPath = String.join(".", java.util.Arrays.copyOf(parts, parts.length - 1));
Object parent = getNestedValue(rowData, parentPath);
if (parent instanceof Map) {
Object v = ((Map<?, ?>) parent).get(parts[parts.length - 1] + "_dictText");
Object v = getMapValue((Map<String, Object>) parent, parts[parts.length - 1] + "_dictText");
return v != null ? String.valueOf(v) : null;
}
return null;

View File

@@ -0,0 +1,339 @@
package org.jeecg.modules.xslmes.dingtalk.service;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import java.text.SimpleDateFormat;
import java.util.Collections;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.jeecg.common.util.oConvertUtils;
import org.jeecg.modules.print.catalog.IPrintBizEntityFieldCatalogProvider;
import org.jeecg.modules.print.service.IPrintBizPermEntityService;
import org.jeecg.modules.print.util.PrintBizDetailPropertyScanner;
import org.jeecg.modules.print.util.PrintBizEntityFieldIntrospector;
import org.jeecg.modules.print.vo.PrintBizDetailSlotVO;
import org.jeecg.modules.print.vo.PrintBizFieldItemVO;
import org.jeecg.modules.print.vo.PrintBizTypeVO;
import org.jeecg.modules.xslmes.approval.entity.MesXslApprovalFlow;
import org.jeecg.modules.xslmes.approval.entity.MesXslApprovalInstance;
import org.jeecg.modules.xslmes.dingtalk.entity.MesXslDingProcessTpl;
import org.jeecg.modules.xslmes.dingtalk.entity.MesXslDingTplBind;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
/**
* 按钉钉审批模板绑定配置构建 IM 聊天审批卡片字段,与钉钉发起审批表单展示对齐。
*/
@Slf4j
@Service
public class DingTplImCardBuilder {
private static final Pattern IDENTIFIER = Pattern.compile("^[A-Za-z0-9_]+$");
private static final String CARD_STYLE_DING = "ding";
/** 与前端 dingTplFieldValue.defaultValueMode 一致:下拉类控件默认原值 */
private static final Set<String> DD_SELECT_COMPONENTS = Set.of(
"DDSelectField", "DDMultiSelectField", "DepartmentField", "InnerContactField");
@Autowired
private IMesXslDingTplBindService bindService;
@Autowired
private IMesXslDingProcessTplService tplService;
@Autowired
private DingTplBindFieldValueResolver fieldValueResolver;
@Autowired
private IPrintBizPermEntityService printBizPermEntityService;
@Autowired
private JdbcTemplate jdbcTemplate;
@Autowired(required = false)
private IPrintBizEntityFieldCatalogProvider fieldCatalogProvider;
/**
* 若存在启用的钉钉模板绑定,则按绑定字段构建完整 biz_record 载荷;否则返回 null 由调用方降级。
*/
public JSONObject buildCardPayload(MesXslApprovalInstance inst, MesXslApprovalFlow flow,
String actionLabel, boolean canApprove, String routePath,
boolean approvalCard) {
if (inst == null || oConvertUtils.isEmpty(routePath)) {
return null;
}
MesXslDingTplBind bind = bindService.resolveActiveByRoutePath(routePath);
if (bind == null || oConvertUtils.isEmpty(bind.getFieldMappingJson())) {
return null;
}
MesXslDingProcessTpl tpl = tplService.getById(bind.getTemplateId());
if (tpl == null || !"1".equals(tpl.getStatus())) {
return null;
}
Map<String, Object> rowData = loadBizRow(inst.getBizTable(), inst.getBizDataId());
if (rowData.isEmpty()) {
return null;
}
Map<String, PrintBizFieldItemVO> metaMap = buildFieldMetaMap(bind.getBizCode());
JSONArray fields = buildDingStyleFields(bind.getFieldMappingJson(), rowData, metaMap);
appendApprovalMetaFields(fields, inst, flow, approvalCard);
JSONObject item = buildItem(inst, routePath, fields, actionLabel, canApprove, approvalCard);
JSONArray items = new JSONArray();
items.add(item);
JSONObject payload = new JSONObject();
payload.put("v", 2);
payload.put("cardStyle", CARD_STYLE_DING);
payload.put("templateId", bind.getTemplateId());
payload.put("templateName", oConvertUtils.getString(bind.getTemplateName(), tpl.getTplName()));
payload.put("pageTitle", oConvertUtils.getString(inst.getBizTableName(), flow != null ? flow.getFlowName() : inst.getFlowName()));
payload.put("pagePath", routePath);
payload.put("rowKey", "id");
payload.put("items", items);
return payload;
}
private JSONArray buildDingStyleFields(String mappingJson, Map<String, Object> rowData,
Map<String, PrintBizFieldItemVO> metaMap) {
JSONArray fields = new JSONArray();
JSONArray mappings;
try {
mappings = JSON.parseArray(mappingJson);
} catch (Exception e) {
log.warn("解析钉钉模板绑定 JSON 失败: {}", e.getMessage());
return fields;
}
if (mappings == null || mappings.isEmpty()) {
return fields;
}
for (int i = 0; i < mappings.size(); i++) {
JSONObject mapping = mappings.getJSONObject(i);
if (mapping == null) {
continue;
}
String componentName = mapping.getString("componentName");
if ("TableField".equals(componentName)) {
continue;
}
String label = mapping.getString("componentLabel");
String bizField = mapping.getString("bizField");
if (oConvertUtils.isEmpty(label) || oConvertUtils.isEmpty(bizField)) {
if ("TextNote".equals(componentName) && oConvertUtils.isNotEmpty(label)) {
addField(fields, label, "");
}
continue;
}
PrintBizFieldItemVO meta = metaMap.get(bizField.trim());
String valueMode = resolveValueMode(mapping, meta);
Object val = fieldValueResolver.resolveValue(rowData, bizField.trim(), valueMode, meta);
addField(fields, label, formatValue(val));
}
return fields;
}
private void appendApprovalMetaFields(JSONArray fields, MesXslApprovalInstance inst,
MesXslApprovalFlow flow, boolean approvalCard) {
addField(fields, "审批流", oConvertUtils.getString(inst.getFlowName(),
flow != null ? flow.getFlowName() : ""));
addField(fields, approvalCard ? "当前节点" : "知会",
oConvertUtils.getString(inst.getCurrentNodeName(), approvalCard ? "审批" : "抄送"));
addField(fields, "状态", statusText(inst.getStatus()));
}
private JSONObject buildItem(MesXslApprovalInstance inst, String routePath, JSONArray fields,
String actionLabel, boolean canApprove, boolean approvalCard) {
JSONObject item = new JSONObject();
item.put("recordId", inst.getBizDataId());
item.put("fields", fields);
StringBuilder body = new StringBuilder();
for (int i = 0; i < fields.size(); i++) {
JSONObject f = fields.getJSONObject(i);
if (i > 0) {
body.append("\n");
}
body.append(f.getString("label")).append(": ").append(f.getString("value"));
}
item.put("body", body.toString());
String sep = routePath.contains("?") ? "&" : "?";
item.put("linkPath", routePath + sep + "imRecordId=" + inst.getBizDataId());
if (approvalCard) {
item.put("instanceId", inst.getId());
item.put("canApprove", canApprove);
item.put("nodeId", inst.getCurrentNodeId());
if (oConvertUtils.isNotEmpty(actionLabel)) {
item.put("actionLabel", actionLabel);
}
}
return item;
}
private Map<String, Object> loadBizRow(String table, String bizDataId) {
if (oConvertUtils.isEmpty(table) || oConvertUtils.isEmpty(bizDataId)
|| !IDENTIFIER.matcher(table).matches()) {
return Collections.emptyMap();
}
try {
List<Map<String, Object>> rows = jdbcTemplate.queryForList(
"SELECT * FROM `" + table + "` WHERE id = ? LIMIT 1", bizDataId);
if (rows.isEmpty()) {
return Collections.emptyMap();
}
//update-begin---author:GHT ---date:20260610 for【IM审批通用化】JDBC列名下划线转驼峰,与绑定字段对齐-----------
return normalizeRowKeys(rows.get(0));
//update-end---author:GHT ---date:20260610 for【IM审批通用化】JDBC列名下划线转驼峰,与绑定字段对齐-----------
} catch (Exception e) {
log.warn("加载业务单据失败 table={} id={}", table, bizDataId, e);
return Collections.emptyMap();
}
}
private Map<String, Object> normalizeRowKeys(Map<String, Object> raw) {
Map<String, Object> normalized = new LinkedHashMap<>();
if (raw == null) {
return normalized;
}
for (Map.Entry<String, Object> e : raw.entrySet()) {
String key = e.getKey();
if (oConvertUtils.isEmpty(key)) {
continue;
}
normalized.put(key, e.getValue());
if (key.contains("_")) {
String camel = oConvertUtils.camelName(key);
normalized.putIfAbsent(camel, e.getValue());
}
}
return normalized;
}
/** 与钉钉发起弹窗 defaultValueMode 对齐 */
private String resolveValueMode(JSONObject mapping, PrintBizFieldItemVO meta) {
String mode = mapping.getString("valueMode");
if (oConvertUtils.isNotEmpty(mode)) {
return mode.trim();
}
if (meta == null || oConvertUtils.isEmpty(meta.getTranslateKind())
|| "NONE".equalsIgnoreCase(meta.getTranslateKind())) {
return "raw";
}
String componentName = mapping.getString("componentName");
if (oConvertUtils.isNotEmpty(componentName) && DD_SELECT_COMPONENTS.contains(componentName)) {
return "raw";
}
return "text";
}
private Map<String, PrintBizFieldItemVO> buildFieldMetaMap(String bizCode) {
if (oConvertUtils.isEmpty(bizCode)) {
return Collections.emptyMap();
}
Map<String, PrintBizFieldItemVO> map = new LinkedHashMap<>();
for (PrintBizFieldItemVO f : listMainFieldsEnriched(bizCode)) {
if (f != null && StringUtils.isNotBlank(f.getFieldKey())) {
map.put(f.getFieldKey(), f);
}
}
Class<?> mainCls = resolveEntityClass(bizCode);
if (mainCls != null && fieldCatalogProvider != null && fieldCatalogProvider.hasCatalogForBiz(bizCode)) {
for (PrintBizDetailSlotVO slot : fieldCatalogProvider.listDetailSlots(bizCode)) {
mergeDetailMeta(map, bizCode, slot.getPropertyName(), slot.getSlotKind());
}
} else if (mainCls != null) {
for (PrintBizDetailSlotVO slot : PrintBizDetailPropertyScanner.listSlots(mainCls)) {
mergeDetailMeta(map, bizCode, slot.getPropertyName(), slot.getSlotKind());
}
}
return map;
}
private List<PrintBizFieldItemVO> listMainFieldsEnriched(String bizCode) {
Class<?> cls = resolveEntityClass(bizCode);
List<PrintBizFieldItemVO> fields;
if (fieldCatalogProvider != null && fieldCatalogProvider.hasCatalogForBiz(bizCode)) {
fields = fieldCatalogProvider.listMainFields(bizCode);
} else if (cls != null) {
fields = PrintBizEntityFieldIntrospector.listFields(cls);
} else {
return Collections.emptyList();
}
if (cls != null && fields != null && !fields.isEmpty()) {
PrintBizEntityFieldIntrospector.enrichDictMeta(fields, cls, null);
}
return fields != null ? fields : Collections.emptyList();
}
private void mergeDetailMeta(Map<String, PrintBizFieldItemVO> map, String bizCode, String prop, String kind) {
List<PrintBizFieldItemVO> detailFields;
if (fieldCatalogProvider != null && fieldCatalogProvider.hasCatalogForBiz(bizCode)) {
detailFields = fieldCatalogProvider.listPrefixedDetailFields(bizCode, prop, kind);
} else {
Class<?> mainCls = resolveEntityClass(bizCode);
detailFields = mainCls == null
? Collections.emptyList()
: PrintBizDetailPropertyScanner.listPrefixedDetailFields(mainCls, prop, kind);
}
Class<?> itemCls = resolveDetailItemClass(bizCode, prop, kind);
if (itemCls != null && detailFields != null && !detailFields.isEmpty()) {
PrintBizEntityFieldIntrospector.enrichDictMeta(detailFields, itemCls, null);
}
if (detailFields != null) {
for (PrintBizFieldItemVO f : detailFields) {
if (f != null && StringUtils.isNotBlank(f.getFieldKey())) {
map.put(f.getFieldKey(), f);
}
}
}
}
private Class<?> resolveEntityClass(String bizCode) {
PrintBizTypeVO vo = printBizPermEntityService.resolveBizTypeVo(bizCode);
if (vo == null || StringUtils.isBlank(vo.getDescription())) {
return null;
}
return PrintBizEntityFieldIntrospector.tryLoadClass(vo.getDescription().trim());
}
private Class<?> resolveDetailItemClass(String bizCode, String detailProperty, String slotKind) {
Class<?> mainCls = resolveEntityClass(bizCode);
if (mainCls == null) {
return null;
}
return PrintBizDetailPropertyScanner.resolveItemClassForSlot(mainCls, detailProperty, slotKind);
}
private void addField(JSONArray fields, String label, String value) {
JSONObject f = new JSONObject();
f.put("label", label);
f.put("value", oConvertUtils.getString(value, ""));
fields.add(f);
}
private String formatValue(Object val) {
if (val == null) {
return "";
}
if (val instanceof Date) {
return new SimpleDateFormat("yyyy-MM-dd").format((Date) val);
}
return String.valueOf(val);
}
private String statusText(String status) {
if ("1".equals(status)) {
return "已通过";
}
if ("2".equals(status)) {
return "已驳回";
}
if ("3".equals(status)) {
return "已撤销";
}
return "审批中";
}
}

View File

@@ -13,4 +13,7 @@ public interface IMesXslDingTplBindService extends IService<MesXslDingTplBind> {
/** 按业务编码查询绑定记录(未删除的第一条) */
MesXslDingTplBind getByBizCode(String bizCode);
/** 按前端路由解析启用的钉钉模板绑定(模板停用则返回 null */
MesXslDingTplBind resolveActiveByRoutePath(String routePath);
}

View File

@@ -2,11 +2,18 @@ package org.jeecg.modules.xslmes.dingtalk.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.apache.commons.lang3.StringUtils;
import org.jeecg.modules.xslmes.dingtalk.entity.MesXslDingProcessTpl;
import org.jeecg.modules.xslmes.dingtalk.entity.MesXslDingTplBind;
import org.jeecg.modules.xslmes.dingtalk.mapper.MesXslDingTplBindMapper;
import org.jeecg.modules.xslmes.dingtalk.service.IMesXslDingProcessTplService;
import org.jeecg.modules.xslmes.dingtalk.service.IMesXslDingTplBindService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* 钉钉审批模板绑定 ServiceImpl
*
@@ -17,8 +24,43 @@ import org.springframework.stereotype.Service;
public class MesXslDingTplBindServiceImpl extends ServiceImpl<MesXslDingTplBindMapper, MesXslDingTplBind>
implements IMesXslDingTplBindService {
@Autowired
private JdbcTemplate jdbcTemplate;
@Autowired
private IMesXslDingProcessTplService tplService;
@Override
public MesXslDingTplBind getByBizCode(String bizCode) {
return getOne(new QueryWrapper<MesXslDingTplBind>().eq("biz_code", bizCode).last("LIMIT 1"));
}
//update-begin---author:GHT ---date:20260610 for【IM审批通用化】按路由解析启用的钉钉模板绑定-----------
@Override
public MesXslDingTplBind resolveActiveByRoutePath(String routePath) {
if (StringUtils.isBlank(routePath)) {
return null;
}
String path = routePath.trim();
String sql = "SELECT id FROM sys_permission WHERE url = ? AND del_flag = 0 LIMIT 1";
List<String> ids;
try {
ids = jdbcTemplate.queryForList(sql, String.class, path);
} catch (Exception e) {
return null;
}
if (ids.isEmpty()) {
return null;
}
MesXslDingTplBind bind = getByBizCode(ids.get(0));
if (bind == null || StringUtils.isBlank(bind.getTemplateId())) {
return null;
}
MesXslDingProcessTpl tpl = tplService.getById(bind.getTemplateId());
if (tpl == null || !"1".equals(tpl.getStatus())) {
return null;
}
return bind;
}
//update-end---author:GHT ---date:20260610 for【IM审批通用化】按路由解析启用的钉钉模板绑定-----------
}