钉钉审批功能完善、混炼示方新增是否附加料

This commit is contained in:
geht
2026-06-10 15:41:02 +08:00
parent de48bd2324
commit 39a9bd83f1
37 changed files with 2461 additions and 166 deletions

View File

@@ -893,3 +893,60 @@ 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/MesXslMixingSpec.data.ts
jeecgboot-vue3/src/views/xslmes/approval/integration/components/VisualActionEditor.vue
-- author:GHT---date:20260610--for: 【MESToDing审批配置】钉钉模板列表操作列绑定审批流程弹窗 -----
jeecgboot-vue3/src/views/xslmes/dingtalk/mesXslDingProcessTpl/components/BindApprovalFlowModal.vue
jeecgboot-vue3/src/views/xslmes/dingtalk/mesXslDingProcessTpl/MesXslDingProcessTplList.vue
-- author:GHT---date:20260610--for: 【MESToDing审批配置】模板名称 MES↔钉钉 双向同步 -----
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/dingtalk/controller/MesXslDingProcessTplController.java
jeecgboot-vue3/src/views/xslmes/dingtalk/mesXslDingProcessTpl/MesXslDingProcessTpl.api.ts
jeecgboot-vue3/src/views/xslmes/dingtalk/mesXslDingProcessTpl/components/DingTplDesigner.vue
-- author:GHT---date:20260610--for: 【钉钉审批模板绑定】字段绑定支持原值/显示文本 -----
jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/print/vo/PrintBizFieldItemVO.java
jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/print/util/PrintBizEntityFieldIntrospector.java
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/dingtalk/service/DingTplBindFieldValueResolver.java
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/dingtalk/controller/MesXslDingTplBindController.java
jeecgboot-vue3/src/views/xslmes/dingtalk/dingTplBind/dingTplFieldValue.ts
jeecgboot-vue3/src/views/xslmes/dingtalk/dingTplBind/dingTplBind.api.ts
jeecgboot-vue3/src/views/xslmes/dingtalk/dingTplBind/index.vue
jeecgboot-vue3/src/components/DingTplLaunch/DingBindLaunchModal.vue
-- author:GHT---date:20260610--for: 【审批流设计】节点内生成集成方案并配置动作 -----
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/approval/integration/service/IntegrationPlanGenerator.java
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/approval/integration/controller/MesXslIntegrationPlanController.java
jeecgboot-vue3/src/views/xslmes/approval/integration/MesXslIntegrationPlan.api.ts
jeecgboot-vue3/src/views/xslmes/approval/integration/components/MesXslIntegrationActionDrawer.vue
jeecgboot-vue3/src/views/approval/flow/components/FlowDesign.vue
jeecgboot-vue3/src/views/approval/flow/components/NodeConfigDrawer.vue
-- author:GHT---date:20260610--for: 【审批注册中心】物理表名改为数据库表下拉选择 -----
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/approval/integration/controller/MesXslBizDocRegistryController.java
jeecgboot-vue3/src/views/xslmes/approval/integration/MesXslBizDocRegistry.api.ts
jeecgboot-vue3/src/views/xslmes/approval/integration/MesXslBizDocRegistry.data.ts
-- author:GHT---date:20260610--for: 【配合示方】审批进度展示改为关联痕迹表 -----
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/approval/integration/advice/ApprovalTraceResponseAdvice.java
jeecgboot-vue3/src/views/xslmes/approval/integration/traceRecordHelper.ts
jeecgboot-vue3/src/views/xslmes/mesXslFormulaSpec/MesXslFormulaSpec.data.ts
jeecgboot-vue3/src/views/xslmes/mesXslFormulaSpec/components/MesXslFormulaSpecModal.vue
-- author:GHT---date:20260610--for: 【混炼示方】页脚签章区展示痕迹表审批人/时间 -----
jeecgboot-vue3/src/views/xslmes/approval/integration/traceRecordHelper.ts
jeecgboot-vue3/src/views/xslmes/mesXslMixingSpec/components/MesXslMixingSpecModal.vue
-- author:GHT---date:20260610--for: 【配合示方】移除手写痕迹列,改由 useListPage 统一注入 6 列 -----
jeecgboot-vue3/src/views/xslmes/mesXslFormulaSpec/MesXslFormulaSpec.data.ts
-- author:GHT---date:20260610--for: 【混炼示方】页脚起草人/变更人展示姓名 -----
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/entity/MesXslMixingSpec.java
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: 【混炼示方】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
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/MesXslMixingSpec.data.ts
jeecgboot-vue3/src/views/xslmes/mesXslMixingSpec/components/MesXslMixingSpecModal.vue

View File

@@ -84,6 +84,9 @@ public class ApprovalTraceResponseAdvice implements ResponseBodyAdvice<Object> {
}
String path = extractServletPath(request);
CacheEntry entry = resolveEntry(path);
if (entry == null) {
entry = resolveEntryByQueryById(path);
}
if (entry == null) {
return body;
}
@@ -93,11 +96,15 @@ public class ApprovalTraceResponseAdvice implements ResponseBodyAdvice<Object> {
List<?> records = null;
IPage page = null;
boolean singleEntity = false;
if (data instanceof IPage) {
page = (IPage) data;
records = page.getRecords();
} else if (data instanceof List) {
records = (List<?>) data;
} else if (data != null && extractId(data) != null) {
records = Collections.singletonList(data);
singleEntity = true;
}
if (records == null || records.isEmpty()) {
@@ -118,6 +125,8 @@ public class ApprovalTraceResponseAdvice implements ResponseBodyAdvice<Object> {
if (page != null) {
((Page) page).setRecords(enriched);
} else if (singleEntity) {
result.setResult(enriched.get(0));
} else {
result.setResult(enriched);
}
@@ -141,6 +150,37 @@ public class ApprovalTraceResponseAdvice implements ResponseBodyAdvice<Object> {
return pathToEntryCache.get(path);
}
/** queryById 与 list 同模块时,按 list 路径匹配注册中心配置 */
private CacheEntry resolveEntryByQueryById(String path) {
if (oConvertUtils.isEmpty(path) || !path.endsWith("/queryById")) {
return null;
}
ensureCacheLoaded();
String listPath = path.substring(0, path.length() - "/queryById".length()) + "/list";
return pathToEntryCache.get(listPath);
}
private String extractId(Object r) {
if (r == null) {
return null;
}
Object id = null;
if (r instanceof Map) {
id = ((Map<?, ?>) r).get("id");
} else {
try {
id = r.getClass().getMethod("getId").invoke(r);
} catch (Exception ignored) {
// 无 getId 方法
}
}
if (id == null) {
return null;
}
String idStr = String.valueOf(id);
return oConvertUtils.isNotEmpty(idStr) ? idStr : null;
}
private void ensureCacheLoaded() {
long now = System.currentTimeMillis();
if (now - cacheLoadTime > CACHE_TTL_MS) {

View File

@@ -14,8 +14,14 @@ import org.jeecg.common.system.query.QueryGenerator;
import org.jeecg.modules.xslmes.approval.integration.entity.MesXslBizDocRegistry;
import org.jeecg.modules.xslmes.approval.integration.service.IMesXslBizDocRegistryService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.web.bind.annotation.*;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* 审批注册中心
*
@@ -30,6 +36,8 @@ public class MesXslBizDocRegistryController extends JeecgController<MesXslBizDoc
@Autowired
private IMesXslBizDocRegistryService service;
@Autowired
private JdbcTemplate jdbcTemplate;
@Operation(summary = "审批注册-分页列表")
@RequiresPermissions("xslmes:mes_xsl_biz_doc_registry:list")
@@ -81,4 +89,37 @@ public class MesXslBizDocRegistryController extends JeecgController<MesXslBizDoc
MesXslBizDocRegistry entity = service.getById(id);
return entity != null ? Result.OK(entity) : Result.error("未找到对应数据");
}
//update-begin---author:GHT ---date:20260610 for【审批注册中心】物理表名下拉选择查询当前库表清单-----------
@Operation(summary = "查询当前数据库物理表(供注册中心下拉选择)")
@RequiresPermissions("xslmes:mes_xsl_biz_doc_registry:list")
@GetMapping("/dbTables")
public Result<List<Map<String, String>>> listDbTables(
@RequestParam(name = "keyword", required = false) String keyword) {
StringBuilder sql = new StringBuilder(
"SELECT TABLE_NAME tableName, IFNULL(TABLE_COMMENT,'') tableComment "
+ "FROM information_schema.tables "
+ "WHERE table_schema = (SELECT DATABASE()) AND table_type = 'BASE TABLE' ");
List<Object> args = new ArrayList<>();
if (keyword != null && !keyword.isBlank()) {
String like = "%" + keyword.trim() + "%";
sql.append("AND (TABLE_NAME LIKE ? OR TABLE_COMMENT LIKE ?) ");
args.add(like);
args.add(like);
}
sql.append("ORDER BY TABLE_NAME");
List<Map<String, Object>> rows = jdbcTemplate.queryForList(sql.toString(), args.toArray());
List<Map<String, String>> options = new ArrayList<>(rows.size());
for (Map<String, Object> row : rows) {
String tableName = String.valueOf(row.get("tableName"));
String comment = String.valueOf(row.get("tableComment"));
Map<String, String> opt = new LinkedHashMap<>();
opt.put("value", tableName);
opt.put("comment", comment);
opt.put("label", comment.isBlank() ? tableName : tableName + "" + comment + "");
options.add(opt);
}
return Result.OK(options);
}
//update-end---author:GHT ---date:20260610 for【审批注册中心】物理表名下拉选择查询当前库表清单-----------
}

View File

@@ -160,6 +160,25 @@ public class MesXslIntegrationPlanController extends JeecgController<MesXslInteg
IntegrationPlanGenerator.parseStageOverrides(nodeBindings));
//update-end---author:GHT ---date:20260605 for【XSLMES-20260605-K8R2】生成时支持手选识别环节-----------
}
//update-begin---author:GHT ---date:2026-06-10 for【审批流设计】单节点生成集成方案-----------
@Operation(summary = "为单个审批节点生成集成方案(流程设计器内使用)")
@RequiresPermissions("xslmes:mes_xsl_integration_plan:edit")
@PostMapping("/generateForNode")
@Transactional(rollbackFor = Exception.class)
public Result<Map<String, Object>> generateForNode(@RequestBody Map<String, Object> body) {
if (body == null) {
return Result.error("请求体不能为空");
}
String sourceTable = body.get("sourceTable") != null ? String.valueOf(body.get("sourceTable")) : null;
String flowId = body.get("flowId") != null ? String.valueOf(body.get("flowId")) : null;
String nodeId = body.get("nodeId") != null ? String.valueOf(body.get("nodeId")) : null;
String stageKey = body.get("stageKey") != null ? String.valueOf(body.get("stageKey")) : null;
String flowConfig = body.get("flowConfig") != null ? String.valueOf(body.get("flowConfig")) : null;
boolean overwriteDraft = Boolean.TRUE.equals(body.get("overwriteDraft"));
return planGenerator.generateForNode(sourceTable, flowId, nodeId, stageKey, flowConfig, overwriteDraft);
}
//update-end---author:GHT ---date:2026-06-10 for【审批流设计】单节点生成集成方案-----------
//update-end---author:GHT ---date:20260605 for【XSLMES-20260605-K8R2】按审批流程节点生成默认集成方案-----------
//update-begin---author:GHT ---date:2026-06-05 for【审核集成Phase0】新增表字段元数据查询接口可视化配置向导用-----------

View File

@@ -106,4 +106,78 @@ public final class IntegrationActionConfigHelper {
return null;
}
//update-end---author:GHT ---date:20260609 for【驳回回退】targetStage 按 containsKey 解析字典键值(含 0-----------
//update-begin---author:GHT ---date:20260610 for【关联表痕迹同步】解析 SQL_UPDATE 动作是否同步目标表痕迹-----------
/** 关联表动作是否开启痕迹同步actionConfig.syncTrace */
public static boolean resolveSyncTrace(MesXslIntegrationAction action) {
if (action == null || oConvertUtils.isEmpty(action.getActionConfig())) {
return false;
}
try {
JSONObject cfg = JSONObject.parseObject(action.getActionConfig());
return cfg.getBooleanValue("syncTrace");
} catch (Exception ignored) {
return false;
}
}
/** 可视化配置中的目标表名 */
public static String resolveTargetTable(MesXslIntegrationAction action) {
if (action == null || oConvertUtils.isEmpty(action.getActionConfig())) {
return null;
}
try {
JSONObject cfg = JSONObject.parseObject(action.getActionConfig());
String table = cfg.getString("targetTable");
return oConvertUtils.isEmpty(table) ? null : table.trim();
} catch (Exception ignored) {
return null;
}
}
/** 关联条件:触发表字段 */
public static String resolveLinkSourceField(MesXslIntegrationAction action) {
return resolveLinkField(action, "sourceField");
}
/** 关联条件:目标表字段 */
public static String resolveLinkTargetField(MesXslIntegrationAction action) {
return resolveLinkField(action, "targetField");
}
/** 状态修改动作的新状态值(驳回回退时作为痕迹清空目标) */
public static String resolveStatusConfigNewValue(MesXslIntegrationAction action) {
if (action == null || oConvertUtils.isEmpty(action.getActionConfig())) {
return null;
}
try {
JSONObject cfg = JSONObject.parseObject(action.getActionConfig());
JSONObject statusConfig = cfg.getJSONObject("statusConfig");
if (statusConfig == null) {
return null;
}
String v = statusConfig.getString("newValue");
return oConvertUtils.isEmpty(v) ? null : v.trim();
} catch (Exception ignored) {
return null;
}
}
private static String resolveLinkField(MesXslIntegrationAction action, String fieldKey) {
if (action == null || oConvertUtils.isEmpty(action.getActionConfig())) {
return null;
}
try {
JSONObject cfg = JSONObject.parseObject(action.getActionConfig());
JSONObject link = cfg.getJSONObject("linkCondition");
if (link == null) {
return null;
}
String v = link.getString(fieldKey);
return oConvertUtils.isEmpty(v) ? null : v.trim();
} catch (Exception ignored) {
return null;
}
}
//update-end---author:GHT ---date:20260610 for【关联表痕迹同步】解析 SQL_UPDATE 动作是否同步目标表痕迹-----------
}

View File

@@ -0,0 +1,178 @@
package org.jeecg.modules.xslmes.approval.integration.engine;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import lombok.extern.slf4j.Slf4j;
import org.jeecg.common.util.oConvertUtils;
import org.jeecg.modules.xslmes.approval.callback.ApprovalCallbackContext;
import org.jeecg.modules.xslmes.approval.integration.entity.MesXslIntegrationAction;
import org.jeecg.modules.xslmes.approval.integration.entity.MesXslIntegrationPlan;
import org.jeecg.modules.xslmes.approval.integration.service.IApprovalTraceSyncService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
/**
* 关联表 SQL_UPDATE 动作执行后的审批痕迹同步/清空。
*/
@Slf4j
@Component
public class RelatedTableTraceSyncHelper {
@Autowired
private IApprovalTraceSyncService approvalTraceSyncService;
@Autowired
private JdbcTemplate jdbcTemplate;
//update-begin---author:GHT ---date:20260610 for【关联表痕迹同步】SQL_UPDATE 成功后按动作配置同步目标表痕迹-----------
/**
* SQL_UPDATE 成功后,按动作配置将主表审批人/时间写入或清空关联表痕迹。
*
* @param affectedRows SQL 实际影响行数,零行时跳过
*/
public void syncAfterSqlUpdate(IntegrationContext ctx, MesXslIntegrationAction action, int affectedRows) {
if (ctx == null || action == null || !IntegrationActionConfigHelper.resolveSyncTrace(action)) {
return;
}
if (affectedRows <= 0) {
log.info("[关联表痕迹] 跳过SQL 零行更新 action={}", action.getActionName());
return;
}
String targetTable = IntegrationActionConfigHelper.resolveTargetTable(action);
String sourceField = IntegrationActionConfigHelper.resolveLinkSourceField(action);
String targetField = IntegrationActionConfigHelper.resolveLinkTargetField(action);
if (oConvertUtils.isEmpty(targetTable) || oConvertUtils.isEmpty(sourceField) || oConvertUtils.isEmpty(targetField)) {
log.warn("[关联表痕迹] 跳过:未配置目标表或关联条件 action={}", action.getActionName());
return;
}
RegistryStageFieldHelper.assertIdentifier(targetTable);
RegistryStageFieldHelper.assertIdentifier(sourceField);
RegistryStageFieldHelper.assertIdentifier(targetField);
String linkValue = resolveLinkValue(ctx, sourceField);
if (oConvertUtils.isEmpty(linkValue)) {
log.warn("[关联表痕迹] 跳过:触发表关联字段为空 action={} sourceField={}", action.getActionName(), sourceField);
return;
}
List<String> targetIds = listTargetBizIds(targetTable, targetField, linkValue);
if (targetIds.isEmpty()) {
log.warn("[关联表痕迹] 跳过:未匹配到目标表记录 action={} table={} {}={}",
action.getActionName(), targetTable, targetField, linkValue);
return;
}
if (isRejectLikePhase(ctx)) {
syncRevertTrace(ctx, action, targetTable, targetIds);
} else {
syncPassTrace(ctx, action, targetTable, targetIds);
}
}
private void syncPassTrace(IntegrationContext ctx, MesXslIntegrationAction action,
String targetTable, List<String> targetIds) {
String stage = resolveTraceStage(ctx, action);
if (oConvertUtils.isEmpty(stage)) {
log.warn("[关联表痕迹] 跳过:无法解析审批环节 action={}", action.getActionName());
return;
}
String stageErr = approvalTraceSyncService.checkStageAllowed(targetTable, stage);
if (stageErr != null) {
log.warn("[关联表痕迹] 跳过:{} action={}", stageErr, action.getActionName());
return;
}
String operator = resolveOperator(ctx);
Date operatorTime = resolveOperatorTime(ctx);
for (String targetId : targetIds) {
approvalTraceSyncService.syncStage(targetTable, targetId, stage, operator, operatorTime);
}
log.info("[关联表痕迹] 写入完成 action={} table={} stage={} operator={} count={}",
action.getActionName(), targetTable, stage, operator, targetIds.size());
}
private void syncRevertTrace(IntegrationContext ctx, MesXslIntegrationAction action,
String targetTable, List<String> targetIds) {
// 驳回场景取状态修改动作的「新状态」,与 SQL SET 值一致,用于痕迹回退粒度对齐
String revertTarget = IntegrationActionConfigHelper.resolveStatusConfigNewValue(action);
if (oConvertUtils.isEmpty(revertTarget)) {
log.warn("[关联表痕迹] 驳回清空跳过状态修改未配置「新状态」action={}", action.getActionName());
return;
}
for (String targetId : targetIds) {
approvalTraceSyncService.revertToStage(targetTable, targetId, revertTarget);
}
log.info("[关联表痕迹] 驳回清空完成 action={} table={} targetStage={} count={}",
action.getActionName(), targetTable, revertTarget, targetIds.size());
}
private boolean isRejectLikePhase(IntegrationContext ctx) {
if (ctx.getTriggerPhase() == TriggerPhase.ON_REJECT) {
return true;
}
ApprovalCallbackContext ac = ctx.getApprovalCtx();
if (ac == null || ac.getAction() == null) {
return false;
}
return ac.getAction() == ApprovalCallbackContext.Action.REJECTED
|| ac.getAction() == ApprovalCallbackContext.Action.CANCELLED;
}
private String resolveTraceStage(IntegrationContext ctx, MesXslIntegrationAction action) {
ApprovalCallbackContext ac = ctx.getApprovalCtx();
if (ac != null && oConvertUtils.isNotEmpty(ac.getStageKey())) {
return ac.getStageKey().trim();
}
MesXslIntegrationPlan plan = ctx.getPlan();
if (plan != null && oConvertUtils.isNotEmpty(plan.getTriggerStage())) {
return plan.getTriggerStage().trim();
}
return IntegrationActionConfigHelper.resolveStage(action, plan);
}
private String resolveOperator(IntegrationContext ctx) {
ApprovalCallbackContext ac = ctx.getApprovalCtx();
if (ac != null && oConvertUtils.isNotEmpty(ac.getOperatorName())) {
return ac.getOperatorName();
}
if (ac != null && oConvertUtils.isNotEmpty(ac.getOperatorUsername())) {
return ac.getOperatorUsername();
}
return "系统";
}
private Date resolveOperatorTime(IntegrationContext ctx) {
ApprovalCallbackContext ac = ctx.getApprovalCtx();
if (ac != null && ac.getOperatorTime() != null) {
return ac.getOperatorTime();
}
return new Date();
}
private String resolveLinkValue(IntegrationContext ctx, String sourceField) {
if ("id".equalsIgnoreCase(sourceField)) {
return ctx.getSourceBizId();
}
Map<String, Object> rec = ctx.getSourceRecord();
if (rec == null || !rec.containsKey(sourceField)) {
return null;
}
Object v = rec.get(sourceField);
return v == null ? null : String.valueOf(v).trim();
}
private List<String> listTargetBizIds(String targetTable, String targetField, String linkValue) {
String sql = "SELECT id FROM `" + targetTable + "` WHERE `" + targetField + "` = ?";
List<Map<String, Object>> rows = jdbcTemplate.queryForList(sql, linkValue);
List<String> ids = new ArrayList<>();
for (Map<String, Object> row : rows) {
Object id = row.get("id");
if (id != null && oConvertUtils.isNotEmpty(String.valueOf(id))) {
ids.add(String.valueOf(id));
}
}
return ids;
}
//update-end---author:GHT ---date:20260610 for【关联表痕迹同步】SQL_UPDATE 成功后按动作配置同步目标表痕迹-----------
}

View File

@@ -3,6 +3,7 @@ package org.jeecg.modules.xslmes.approval.integration.engine.executor;
import lombok.extern.slf4j.Slf4j;
import org.jeecg.common.util.oConvertUtils;
import org.jeecg.modules.xslmes.approval.integration.engine.IntegrationContext;
import org.jeecg.modules.xslmes.approval.integration.engine.RelatedTableTraceSyncHelper;
import org.jeecg.modules.xslmes.approval.integration.engine.VariableResolver;
import org.jeecg.modules.xslmes.approval.integration.entity.MesXslIntegrationAction;
import org.springframework.beans.factory.annotation.Autowired;
@@ -32,6 +33,9 @@ public class SqlUpdateActionExecutor implements IIntegrationActionExecutor {
@Autowired
private JdbcTemplate jdbcTemplate;
@Autowired
private RelatedTableTraceSyncHelper relatedTableTraceSyncHelper;
@Override
public String supportActionType() {
return "SQL_UPDATE";
@@ -61,6 +65,9 @@ public class SqlUpdateActionExecutor implements IIntegrationActionExecutor {
}
//update-end---author:GHT ---date:20260608 for【审核集成】SQL_UPDATE零行时输出可诊断提示-----------
log.info("[集成引擎][SQL_UPDATE] 完成 action={} {}", action.getActionName(), result);
//update-begin---author:GHT ---date:20260610 for【关联表痕迹同步】SQL 成功后按动作配置写入/清空目标表痕迹-----------
relatedTableTraceSyncHelper.syncAfterSqlUpdate(ctx, action, affected);
//update-end---author:GHT ---date:20260610 for【关联表痕迹同步】SQL 成功后按动作配置写入/清空目标表痕迹-----------
return result;
}

View File

@@ -143,6 +143,121 @@ public class IntegrationPlanGenerator {
result.put("planCodes", planCodes);
return Result.OK("生成完成:新增 " + created + " 个方案,跳过 " + skipped + " 个已存在方案", result);
}
//update-begin---author:GHT ---date:2026-06-10 for【审批流设计】单节点生成集成方案并返回方案ID-----------
/**
* 为流程设计器中当前审批节点生成(或复用)集成方案,便于生成后直接配置动作。
*/
@Transactional(rollbackFor = Exception.class)
public Result<Map<String, Object>> generateForNode(String sourceTable, String flowId, String nodeId,
String stageKey, String flowConfigJson, boolean overwriteDraft) {
if (oConvertUtils.isEmpty(sourceTable) || oConvertUtils.isEmpty(flowId) || oConvertUtils.isEmpty(nodeId)) {
return Result.error("缺少业务表、审批流或节点信息");
}
if (oConvertUtils.isEmpty(stageKey)) {
return Result.error("请先在节点上绑定审批环节(校对/审核/批准)");
}
MesXslBizDocRegistry registry = registryService.findActiveByTableName(sourceTable);
if (registry == null) {
return Result.error("业务表未在审批注册中心启用: " + sourceTable);
}
MesXslApprovalFlow flow = resolveFlow(sourceTable, flowId);
String configJson = oConvertUtils.isNotEmpty(flowConfigJson) ? flowConfigJson : flow.getFlowConfig();
if (oConvertUtils.isEmpty(configJson)) {
return Result.error("审批流程未设计,请先保存或完成流程节点配置");
}
List<String> enabledStages = orderedEnabledStages(registry);
List<FlowNode> flowNodes = parseApproverNodes(configJson);
boolean nodeFound = flowNodes.stream().anyMatch(n -> nodeId.equals(n.nodeId));
if (!nodeFound) {
return Result.error("当前节点不在流程配置中,请确认流程设计已包含该节点");
}
List<StatusDictItem> statusChain = loadStatusChain(registry);
String initialStatus = resolveInitialStatus(statusChain, enabledStages);
Map<String, String> overrides = Map.of(nodeId, stageKey.trim());
List<StageBinding> bindings = bindAllFlowNodes(flowNodes, registry, enabledStages, statusChain, initialStatus, overrides);
StageBinding binding = bindings.stream()
.filter(b -> nodeId.equals(b.nodeId))
.findFirst()
.orElse(null);
if (binding == null || !binding.stageConfigured) {
String reason = binding != null ? binding.unconfiguredReason : "节点环节未配置";
return Result.error(reason);
}
String codePrefix = planCodePrefix(registry);
String displayName = oConvertUtils.isNotEmpty(registry.getDisplayName()) ? registry.getDisplayName() : sourceTable;
String planCode = codePrefix + "_reg_" + binding.stage;
String phase = "onNodeApprove";
MesXslIntegrationPlan existing = planService.lambdaQuery()
.eq(MesXslIntegrationPlan::getPlanCode, planCode)
.one();
if (existing != null) {
if ("0".equals(existing.getStatus()) && overwriteDraft) {
actionService.removeByPlanId(existing.getId());
planService.removeById(existing.getId());
} else {
return buildNodeGenerateResult(existing, false, phase, binding);
}
}
Map<String, Object> actionConfig = new LinkedHashMap<>();
actionConfig.put("visualType", "REGISTRY_STAGE_SYNC");
actionConfig.put("stage", binding.stage);
actionConfig.put("expectedFrom", binding.expectedFrom);
if (oConvertUtils.isNotEmpty(binding.statusAfter)) {
actionConfig.put("statusAfter", binding.statusAfter);
}
MesXslIntegrationPlan plan = new MesXslIntegrationPlan();
plan.setPlanCode(planCode);
plan.setPlanName(displayName + "-" + binding.stageLabel + "通过(流程生成)");
plan.setSourceTable(sourceTable);
plan.setRegistryId(registry.getId());
plan.setTriggerPhase(phase);
plan.setTriggerStage(binding.stage);
plan.setExecMode("async");
plan.setStatus("0");
plan.setRemark("按审批流程节点「" + binding.nodeName + "」自动生成");
Result<String> validate = planService.normalizeAndValidate(plan);
if (!validate.isSuccess()) {
return Result.error("方案校验失败: " + validate.getMessage());
}
planService.save(plan);
MesXslIntegrationAction action = new MesXslIntegrationAction();
action.setPlanId(plan.getId());
action.setActionName(binding.stageLabel + "环节同步");
action.setActionType("REGISTRY_STAGE_SYNC");
action.setActionConfig(JSON.toJSONString(actionConfig));
action.setExecOrder(1);
action.setOnFail("stop");
action.setEnabled(1);
actionService.save(action);
return buildNodeGenerateResult(plan, true, phase, binding);
}
private Result<Map<String, Object>> buildNodeGenerateResult(MesXslIntegrationPlan plan, boolean created,
String phase, StageBinding binding) {
Map<String, Object> out = new LinkedHashMap<>();
out.put("planId", plan.getId());
out.put("planCode", plan.getPlanCode());
out.put("planName", plan.getPlanName());
out.put("sourceTable", plan.getSourceTable());
out.put("triggerPhase", phase);
out.put("triggerStage", plan.getTriggerStage());
out.put("status", plan.getStatus());
out.put("created", created);
out.put("nodeName", binding.nodeName);
out.put("stageLabel", binding.stageLabel);
String msg = created ? "已生成集成方案,请配置动作并发布" : "该环节已有集成方案,可直接配置动作";
return Result.OK(msg, out);
}
//update-end---author:GHT ---date:2026-06-10 for【审批流设计】单节点生成集成方案并返回方案ID-----------
//update-end---author:GHT ---date:20260605 for【XSLMES-20260605-K8R2】按流程生成默认集成方案与动作-----------
private Map<String, Object> buildPreview(String sourceTable, String flowId, Map<String, String> stageOverrides) {
@@ -385,7 +500,10 @@ public class IntegrationPlanGenerator {
return;
}
if ("approver".equals(node.getString("type"))) {
JSONObject props = node.getJSONObject("properties");
JSONObject props = node.getJSONObject("props");
if (props == null) {
props = node.getJSONObject("properties");
}
if (props == null) {
props = new JSONObject();
}

View File

@@ -36,7 +36,9 @@ import org.jeecg.modules.system.entity.SysThirdAccount;
import org.jeecg.modules.system.service.ISysThirdAccountService;
import org.jeecg.modules.system.service.impl.ThirdAppDingtalkServiceImpl;
import org.jeecg.modules.xslmes.dingtalk.entity.MesXslDingProcessTpl;
import org.jeecg.modules.xslmes.dingtalk.entity.MesXslDingTplBind;
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.web.bind.annotation.*;
@@ -80,6 +82,9 @@ public class MesXslDingProcessTplController extends JeecgController<MesXslDingPr
@Autowired
private IMesXslApprovalGateService approvalGateService;
@Autowired
private IMesXslDingTplBindService dingTplBindService;
//update-end---author:GHT ---date:2026-06-04 for【MESToDing审批配置】绑定MES审批流审批人来源-----------
@Operation(summary = "钉钉审批模板配置-分页列表查询")
@@ -142,10 +147,59 @@ public class MesXslDingProcessTplController extends JeecgController<MesXslDingPr
@RequiresPermissions("xslmes:mes_xsl_ding_process_tpl:edit")
@RequestMapping(value = "/edit", method = {RequestMethod.PUT, RequestMethod.POST})
public Result<String> edit(@RequestBody MesXslDingProcessTpl mesXslDingProcessTpl) {
if (oConvertUtils.isEmpty(mesXslDingProcessTpl.getId())) {
return Result.error("缺少模板ID");
}
//update-begin---author:GHT ---date:20260610 for【MESToDing审批配置】MES改模板名同步到钉钉-----------
MesXslDingProcessTpl old = mesXslDingProcessTplService.getById(mesXslDingProcessTpl.getId());
mesXslDingProcessTplService.updateById(mesXslDingProcessTpl);
return Result.OK("编辑成功!");
String msg = "编辑成功!";
if (old != null && oConvertUtils.isNotEmpty(mesXslDingProcessTpl.getTplName())) {
String newName = mesXslDingProcessTpl.getTplName().trim();
String oldName = oConvertUtils.isEmpty(old.getTplName()) ? "" : old.getTplName().trim();
if (!newName.equals(oldName)) {
refreshBindTemplateName(mesXslDingProcessTpl.getId(), newName);
String processCode = oConvertUtils.isNotEmpty(mesXslDingProcessTpl.getProcessCode())
? mesXslDingProcessTpl.getProcessCode() : old.getProcessCode();
if (oConvertUtils.isNotEmpty(processCode)) {
MesXslDingProcessTpl latest = mesXslDingProcessTplService.getById(mesXslDingProcessTpl.getId());
if (latest != null && oConvertUtils.isEmpty(latest.getProcessCode())) {
latest.setProcessCode(processCode);
}
Result<String> pushResult = pushTemplateMetaToDingtalk(latest);
if (!pushResult.isSuccess()) {
return Result.OK("编辑成功,但同步钉钉模板名称失败:" + pushResult.getMessage());
}
msg = "编辑成功,已同步钉钉模板名称";
}
}
}
return Result.OK(msg);
//update-end---author:GHT ---date:20260610 for【MESToDing审批配置】MES改模板名同步到钉钉-----------
}
//update-begin---author:GHT ---date:20260610 for【钉钉审批模板】操作列停用/启用,停用后业务页不显示钉钉审批按钮-----------
@AutoLog(value = "钉钉审批模板配置-切换启用状态")
@Operation(summary = "钉钉审批模板配置-切换启用/停用")
@RequiresPermissions("xslmes:mes_xsl_ding_process_tpl:edit")
@PostMapping(value = "/toggleStatus")
public Result<String> toggleStatus(@RequestParam(name = "id") String id) {
if (oConvertUtils.isEmpty(id)) {
return Result.error("缺少模板ID");
}
MesXslDingProcessTpl tpl = mesXslDingProcessTplService.getById(id);
if (tpl == null) {
return Result.error("未找到对应模板");
}
String newStatus = "1".equals(tpl.getStatus()) ? "0" : "1";
MesXslDingProcessTpl update = new MesXslDingProcessTpl();
update.setId(id);
update.setStatus(newStatus);
mesXslDingProcessTplService.updateById(update);
return Result.OK("1".equals(newStatus) ? "已启用" : "已停用");
}
//update-end---author:GHT ---date:20260610 for【钉钉审批模板】操作列停用/启用,停用后业务页不显示钉钉审批按钮-----------
@AutoLog(value = "钉钉审批模板配置-通过id删除")
@Operation(summary = "钉钉审批模板配置-通过id删除")
@RequiresPermissions("xslmes:mes_xsl_ding_process_tpl:delete")
@@ -245,11 +299,15 @@ public class MesXslDingProcessTplController extends JeecgController<MesXslDingPr
}
List<Map<String, Object>> list = new ArrayList<>();
Map<String, String> dingNameByCode = new LinkedHashMap<>();
if (processList != null) {
for (int i = 0; i < processList.size(); i++) {
JSONObject item = processList.getJSONObject(i);
String code = item.getString("process_code");
String name = oConvertUtils.getString(item.getString("name"), "").trim();
if (oConvertUtils.isNotEmpty(code) && oConvertUtils.isNotEmpty(name)) {
dingNameByCode.put(code, name);
}
Map<String, Object> row = new LinkedHashMap<>();
row.put("processCode", code);
row.put("name", name);
@@ -263,6 +321,9 @@ public class MesXslDingProcessTplController extends JeecgController<MesXslDingPr
list.add(row);
}
}
//update-begin---author:GHT ---date:20260610 for【MESToDing审批配置】从钉钉同步时回写已导入模板名称-----------
syncLocalTplNamesFromDingMap(dingNameByCode);
//update-end---author:GHT ---date:20260610 for【MESToDing审批配置】从钉钉同步时回写已导入模板名称-----------
//update-end---author:GHT ---date:2026-06-04 for【MESToDing审批配置】同步列表已导入判定含 processCode 与同名本地草稿-----
return Result.OK(list);
} catch (Exception e) {
@@ -358,6 +419,9 @@ public class MesXslDingProcessTplController extends JeecgController<MesXslDingPr
detail.put("schemaError", "AccessToken 获取失败,请检查钉钉应用配置");
return Result.OK(detail);
}
if (oConvertUtils.isEmpty(tpl.getProcessCode())) {
return Result.OK(detail);
}
try {
String url = "https://api.dingtalk.com/v1.0/workflow/forms/schemas/processCodes"
+ "?processCode=" + tpl.getProcessCode();
@@ -373,6 +437,7 @@ public class MesXslDingProcessTplController extends JeecgController<MesXslDingPr
JSONObject ddResp = JSONObject.parseObject(respBody);
if (ddResp.containsKey("code")) {
detail.put("schemaError", ddResp.getString("message") + " (code=" + ddResp.getString("code") + ")");
mergeDingTemplateName(detail, tpl, accessToken, null, ddResp);
return Result.OK(detail);
}
@@ -486,9 +551,11 @@ public class MesXslDingProcessTplController extends JeecgController<MesXslDingPr
}
detail.put("dingFields", dingFields);
detail.put("dingFieldsCount", dingFields.size());
mergeDingTemplateName(detail, tpl, accessToken, root, ddResp);
} catch (Exception e) {
log.warn("钉钉 Schema 接口异常 processCode={}: {}", tpl.getProcessCode(), e.getMessage());
detail.put("schemaError", "接口异常: " + e.getMessage());
mergeDingTemplateName(detail, tpl, accessToken, null, null);
}
return Result.OK(detail);
}
@@ -571,38 +638,15 @@ public class MesXslDingProcessTplController extends JeecgController<MesXslDingPr
if (tpl == null) return Result.error("未找到对应模板配置");
if (oConvertUtils.isEmpty(tpl.getProcessCode())) return Result.error("该记录尚无 processCode请先创建模板");
String accessToken = dingtalkService.getAccessToken();
if (oConvertUtils.isEmpty(accessToken)) return Result.error("AccessToken 获取失败");
List<DingFormComponent> components = buildFormComponentList(tpl.getFormFields());
if (components.isEmpty()) return Result.error("请先在字段映射中配置至少一个字段");
// 带 processCode → 钉钉更新已有模板官方文档POST同一接口有processCode=更新)
DingFormUpdateRequest req = new DingFormUpdateRequest()
.setProcessCode(tpl.getProcessCode())
.setName(tpl.getTplName())
.setDescription(oConvertUtils.isNotEmpty(tpl.getRemark()) ? tpl.getRemark() : "")
.setFormComponents(components);
try {
String reqJson = JSON.toJSONString(req);
log.info("【钉钉更新模板】processCode={} 请求体: {}", tpl.getProcessCode(), reqJson);
String respBody = callDingApi("POST", "https://api.dingtalk.com/v1.0/workflow/forms", accessToken, reqJson);
JSONObject resp = JSONObject.parseObject(respBody);
log.info("【钉钉更新模板】响应: {}", respBody);
if (resp.containsKey("code")) {
return Result.error("钉钉返回错误: " + resp.getString("message") + " (code=" + resp.getString("code") + ")");
}
Map<String, Object> result = new LinkedHashMap<>();
result.put("processCode", tpl.getProcessCode());
result.put("rawResponse", resp);
return Result.OK("钉钉审批模板更新成功", result);
} catch (Exception e) {
log.error("更新钉钉模板异常", e);
return Result.error("请求异常: " + e.getMessage());
//update-begin---author:GHT ---date:20260610 for【MESToDing审批配置】更新钉钉模板复用统一推送逻辑-----------
Result<String> pushResult = pushTemplateMetaToDingtalk(tpl);
if (!pushResult.isSuccess()) {
return Result.error(pushResult.getMessage());
}
Map<String, Object> result = new LinkedHashMap<>();
result.put("processCode", tpl.getProcessCode());
return Result.OK("钉钉审批模板更新成功", result);
//update-end---author:GHT ---date:20260610 for【MESToDing审批配置】更新钉钉模板复用统一推送逻辑-----------
}
//update-end---author:GHT ---date:2026-06-03 for【MESToDing审批配置】创建/更新钉钉审批模板-----
@@ -851,6 +895,184 @@ public class MesXslDingProcessTplController extends JeecgController<MesXslDingPr
return mesXslDingProcessTplService.getOne(qw, false);
}
//update-begin---author:GHT ---date:20260610 for【MESToDing审批配置】模板名称 MES↔钉钉 双向同步-----------
/** 将本地模板名称写入 tpl 表,并同步绑定表 template_name */
private void applyLocalTplName(String tplId, String tplName) {
if (oConvertUtils.isEmpty(tplId) || oConvertUtils.isEmpty(tplName)) {
return;
}
String name = tplName.trim();
MesXslDingProcessTpl update = new MesXslDingProcessTpl();
update.setId(tplId);
update.setTplName(name);
mesXslDingProcessTplService.updateById(update);
refreshBindTemplateName(tplId, name);
}
/** 同步更新模板绑定表中的 template_name */
private void refreshBindTemplateName(String templateId, String tplName) {
if (oConvertUtils.isEmpty(templateId)) {
return;
}
MesXslDingTplBind bindUpdate = new MesXslDingTplBind();
bindUpdate.setTemplateName(tplName);
dingTplBindService.update(bindUpdate, new QueryWrapper<MesXslDingTplBind>().eq("template_id", templateId));
}
/** 从钉钉拉取模板名称并回写本地getTemplateDetail / 设计器打开时) */
private void mergeDingTemplateName(Map<String, Object> detail, MesXslDingProcessTpl tpl,
String accessToken, JSONObject schemaRoot, JSONObject schemaResp) {
if (tpl == null || oConvertUtils.isEmpty(tpl.getProcessCode())) {
detail.put("dingNameSynced", false);
return;
}
String dingName = extractDingTemplateName(schemaRoot);
if (oConvertUtils.isEmpty(dingName)) {
dingName = extractDingTemplateName(schemaResp);
}
if (oConvertUtils.isEmpty(dingName)) {
dingName = fetchDingTemplateNameByProcessCode(tpl.getProcessCode(), accessToken);
}
detail.put("dingTplName", oConvertUtils.isNotEmpty(dingName) ? dingName : tpl.getTplName());
String localName = oConvertUtils.isEmpty(tpl.getTplName()) ? "" : tpl.getTplName().trim();
if (oConvertUtils.isNotEmpty(dingName) && !dingName.equals(localName)) {
applyLocalTplName(tpl.getId(), dingName);
detail.put("tplName", dingName);
detail.put("dingNameSynced", true);
} else {
detail.put("dingNameSynced", false);
}
}
/** 按 processCode 批量回写本地模板名称(从钉钉同步列表) */
private void syncLocalTplNamesFromDingMap(Map<String, String> dingNameByCode) {
if (dingNameByCode == null || dingNameByCode.isEmpty()) {
return;
}
for (MesXslDingProcessTpl local : mesXslDingProcessTplService.list()) {
if (oConvertUtils.isEmpty(local.getProcessCode())) {
continue;
}
String dingName = dingNameByCode.get(local.getProcessCode());
if (oConvertUtils.isEmpty(dingName)) {
continue;
}
String localName = oConvertUtils.isEmpty(local.getTplName()) ? "" : local.getTplName().trim();
if (!dingName.equals(localName)) {
applyLocalTplName(local.getId(), dingName);
log.info("【模板名称同步】processCode={} 本地「{}」→ 钉钉「{}」", local.getProcessCode(), localName, dingName);
}
}
}
/** 从钉钉 JSON 响应中提取模板名称 */
private String extractDingTemplateName(JSONObject obj) {
if (obj == null) {
return null;
}
for (String key : Arrays.asList("name", "processName", "flowTitle", "title", "templateName")) {
String val = obj.getString(key);
if (oConvertUtils.isNotEmpty(val)) {
return val.trim();
}
}
return null;
}
/** 通过可见审批列表按 processCode 查找钉钉模板名称 */
private String fetchDingTemplateNameByProcessCode(String processCode, String accessToken) {
if (oConvertUtils.isEmpty(processCode) || oConvertUtils.isEmpty(accessToken)) {
return null;
}
try {
LoginUser loginUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
int tenantId = oConvertUtils.getInt(TenantContext.getTenant(), CommonConstant.TENANT_ID_DEFAULT_VALUE);
String dtUserId = resolveDtUserId(loginUser.getUsername(), loginUser.getPhone(), accessToken, tenantId);
if (oConvertUtils.isEmpty(dtUserId)) {
return null;
}
Map<String, String> nameMap = fetchVisibleProcessNameMap(dtUserId, accessToken);
return nameMap.get(processCode);
} catch (Exception e) {
log.warn("按 processCode 拉取钉钉模板名称失败 processCode={}: {}", processCode, e.getMessage());
return null;
}
}
/** 拉取当前用户可见的钉钉审批模板 processCode → name 映射 */
private Map<String, String> fetchVisibleProcessNameMap(String dtUserId, String accessToken) throws Exception {
Map<String, String> map = new LinkedHashMap<>();
String url = "https://oapi.dingtalk.com/topapi/process/listbyuserid?access_token=" + accessToken;
JSONObject reqBody = new JSONObject();
reqBody.put("userid", dtUserId);
reqBody.put("offset", 0);
reqBody.put("size", 100);
HttpClient httpClient = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(10)).build();
HttpRequest httpReq = HttpRequest.newBuilder()
.uri(URI.create(url))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(reqBody.toJSONString()))
.timeout(Duration.ofSeconds(10))
.build();
String respBody = httpClient.send(httpReq, HttpResponse.BodyHandlers.ofString()).body();
JSONObject ddResp = JSONObject.parseObject(respBody);
if (ddResp.getIntValue("errcode") != 0) {
return map;
}
JSONObject result = ddResp.getJSONObject("result");
JSONArray processList = result == null ? null : result.getJSONArray("process_list");
if (processList == null) {
return map;
}
for (int i = 0; i < processList.size(); i++) {
JSONObject item = processList.getJSONObject(i);
String code = item.getString("process_code");
String name = oConvertUtils.getString(item.getString("name"), "").trim();
if (oConvertUtils.isNotEmpty(code) && oConvertUtils.isNotEmpty(name)) {
map.put(code, name);
}
}
return map;
}
/** 将本地模板元数据(名称/描述/表单)推送到钉钉 */
private Result<String> pushTemplateMetaToDingtalk(MesXslDingProcessTpl tpl) {
if (tpl == null) {
return Result.error("模板不存在");
}
if (oConvertUtils.isEmpty(tpl.getProcessCode())) {
return Result.error("该记录尚无 processCode");
}
String accessToken = dingtalkService.getAccessToken();
if (oConvertUtils.isEmpty(accessToken)) {
return Result.error("AccessToken 获取失败");
}
List<DingFormComponent> components = buildFormComponentList(tpl.getFormFields());
if (components.isEmpty()) {
return Result.error("请先在表单设计中配置至少一个字段后再同步到钉钉");
}
DingFormUpdateRequest req = new DingFormUpdateRequest()
.setProcessCode(tpl.getProcessCode())
.setName(tpl.getTplName())
.setDescription(oConvertUtils.isNotEmpty(tpl.getRemark()) ? tpl.getRemark() : "")
.setFormComponents(components);
try {
String reqJson = JSON.toJSONString(req);
log.info("【钉钉更新模板】processCode={} name={} 请求体: {}", tpl.getProcessCode(), tpl.getTplName(), reqJson);
String respBody = callDingApi("POST", "https://api.dingtalk.com/v1.0/workflow/forms", accessToken, reqJson);
JSONObject resp = JSONObject.parseObject(respBody);
log.info("【钉钉更新模板】响应: {}", respBody);
if (resp.containsKey("code")) {
return Result.error("钉钉返回错误: " + resp.getString("message") + " (code=" + resp.getString("code") + ")");
}
return Result.OK("同步成功");
} catch (Exception e) {
log.error("推送钉钉模板元数据异常 processCode={}", tpl.getProcessCode(), e);
return Result.error("请求异常: " + e.getMessage());
}
}
//update-end---author:GHT ---date:20260610 for【MESToDing审批配置】模板名称 MES↔钉钉 双向同步-----------
/** 统一 HTTP 调用钉钉 v1.0 接口 */
private String callDingApi(String method, String url, String accessToken, String jsonBody) throws Exception {
HttpClient httpClient = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(10)).build();
@@ -1103,6 +1325,9 @@ public class MesXslDingProcessTplController extends JeecgController<MesXslDingPr
if (tpl == null) {
return Result.error("未找到对应模板配置");
}
if (!"1".equals(tpl.getStatus())) {
return Result.error("该审批模板已停用,无法发起钉钉审批");
}
if (oConvertUtils.isEmpty(tpl.getProcessCode())) {
return Result.error("该模板尚无 processCode请先在钉钉管理后台创建审批模板");
}

View File

@@ -18,6 +18,7 @@ import org.jeecg.modules.print.vo.PrintBizFieldItemVO;
import org.jeecg.modules.print.vo.PrintBizTypeVO;
import org.jeecg.modules.xslmes.dingtalk.entity.MesXslDingTplBind;
import org.jeecg.modules.xslmes.dingtalk.entity.MesXslDingProcessTpl;
import org.jeecg.modules.xslmes.dingtalk.service.DingTplBindFieldValueResolver;
import org.jeecg.modules.xslmes.dingtalk.service.IMesXslDingTplBindService;
import org.jeecg.modules.xslmes.dingtalk.service.IMesXslDingProcessTplService;
import org.springframework.beans.factory.annotation.Autowired;
@@ -46,6 +47,8 @@ public class MesXslDingTplBindController {
@Autowired(required = false)
private IPrintBizEntityFieldCatalogProvider fieldCatalogProvider;
@Autowired private DingTplBindFieldValueResolver fieldValueResolver;
// ═══════════════════════ 菜单树 ═══════════════════════
/**
@@ -129,19 +132,7 @@ public class MesXslDingTplBindController {
if (StringUtils.isBlank(bizCode)) {
return Result.error("bizCode 不能为空");
}
String code = bizCode.trim();
if (fieldCatalogProvider != null && fieldCatalogProvider.hasCatalogForBiz(code)) {
return Result.OK(fieldCatalogProvider.listMainFields(code));
}
PrintBizTypeVO vo = printBizPermEntityService.resolveBizTypeVo(code);
if (vo == null || StringUtils.isBlank(vo.getDescription())) {
return Result.OK(Collections.emptyList());
}
Class<?> cls = PrintBizEntityFieldIntrospector.tryLoadClass(vo.getDescription().trim());
if (cls == null) {
return Result.OK(Collections.emptyList());
}
return Result.OK(PrintBizEntityFieldIntrospector.listFields(cls));
return Result.OK(listMainFieldsEnriched(bizCode.trim()));
}
@Operation(summary = "主实体上的明细槽位(供 TableField 绑定明细集合)")
@@ -177,20 +168,53 @@ public class MesXslDingTplBindController {
return Result.error("bizCode 与 detailProperty 不能为空");
}
String code = bizCode.trim();
String prop = detailProperty.trim();
String kind = slotKind.trim();
List<PrintBizFieldItemVO> fields;
if (fieldCatalogProvider != null && fieldCatalogProvider.hasCatalogForBiz(code)) {
return Result.OK(fieldCatalogProvider.listPrefixedDetailFields(code, detailProperty.trim(), slotKind.trim()));
fields = fieldCatalogProvider.listPrefixedDetailFields(code, prop, kind);
} else {
Class<?> cls = resolveEntityClass(code);
if (cls == null) {
return Result.OK(Collections.emptyList());
}
fields = PrintBizDetailPropertyScanner.listPrefixedDetailFields(cls, prop, kind);
}
PrintBizTypeVO vo = printBizPermEntityService.resolveBizTypeVo(code);
if (vo == null || StringUtils.isBlank(vo.getDescription())) {
return Result.OK(Collections.emptyList());
Class<?> itemCls = resolveDetailItemClass(code, prop, kind);
if (itemCls != null && fields != null && !fields.isEmpty()) {
PrintBizEntityFieldIntrospector.enrichDictMeta(fields, itemCls, prop);
}
Class<?> cls = PrintBizEntityFieldIntrospector.tryLoadClass(vo.getDescription().trim());
if (cls == null) {
return Result.OK(Collections.emptyList());
}
return Result.OK(PrintBizDetailPropertyScanner.listPrefixedDetailFields(cls, detailProperty.trim(), slotKind.trim()));
return Result.OK(fields != null ? fields : Collections.emptyList());
}
//update-begin---author:GHT ---date:2026-06-10 for【钉钉审批模板绑定】批量解析字段取值原值/显示文本)-----------
@Operation(summary = "批量解析绑定字段取值(字典/表字典显示文本)")
@PostMapping("/resolveFieldValues")
@RequiresPermissions("xslmes:mesXslDingTplBind:list")
public Result<Map<String, Object>> resolveFieldValues(@RequestBody ResolveFieldValuesRequest req) {
if (req == null || StringUtils.isBlank(req.getBizCode())) {
return Result.error("bizCode 不能为空");
}
if (req.getRowData() == null || req.getItems() == null || req.getItems().isEmpty()) {
return Result.OK(Collections.emptyMap());
}
String code = req.getBizCode().trim();
Map<String, PrintBizFieldItemVO> metaMap = buildFieldMetaMap(code);
Map<String, Object> out = new LinkedHashMap<>();
for (ResolveFieldItem item : req.getItems()) {
if (item == null || StringUtils.isBlank(item.getMapKey()) || StringUtils.isBlank(item.getBizField())) {
continue;
}
PrintBizFieldItemVO meta = metaMap.get(item.getBizField().trim());
Object val =
fieldValueResolver.resolveValue(
req.getRowData(), item.getBizField().trim(), item.getValueMode(), meta);
out.put(item.getMapKey(), val);
}
return Result.OK(out);
}
//update-end---author:GHT ---date:2026-06-10 for【钉钉审批模板绑定】批量解析字段取值原值/显示文本)-----------
// ═══════════════════════ 按路由检测绑定(全局悬浮按钮使用) ═══════════════════════
/**
@@ -216,6 +240,15 @@ public class MesXslDingTplBindController {
return Result.OK(null);
}
MesXslDingTplBind bind = bindService.getByBizCode(ids.get(0));
if (bind == null || StringUtils.isBlank(bind.getTemplateId())) {
return Result.OK(null);
}
//update-begin---author:GHT ---date:20260610 for【钉钉审批模板】模板停用时业务页不返回绑定隐藏钉钉审批按钮-----------
MesXslDingProcessTpl tpl = tplService.getById(bind.getTemplateId());
if (tpl == null || !"1".equals(tpl.getStatus())) {
return Result.OK(null);
}
//update-end---author:GHT ---date:20260610 for【钉钉审批模板】模板停用时业务页不返回绑定隐藏钉钉审批按钮-----------
return Result.OK(bind);
}
@@ -247,6 +280,9 @@ public class MesXslDingTplBindController {
if (tpl == null) {
return Result.error("选择的钉钉审批模板不存在");
}
if (!"1".equals(tpl.getStatus())) {
return Result.error("所选钉钉审批模板已停用,请先启用或更换其他模板");
}
MesXslDingTplBind existing = bindService.getByBizCode(req.getBizCode().trim());
if (existing != null) {
// 更新已有绑定
@@ -297,6 +333,82 @@ public class MesXslDingTplBindController {
return "[]";
}
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 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 Class<?> resolveDetailItemClass(String bizCode, String detailProperty, String slotKind) {
Class<?> mainCls = resolveEntityClass(bizCode);
if (mainCls == null) {
return null;
}
return PrintBizDetailPropertyScanner.resolveItemClassForSlot(mainCls, detailProperty, slotKind);
}
private Map<String, PrintBizFieldItemVO> buildFieldMetaMap(String bizCode) {
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 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, prop);
}
if (detailFields != null) {
for (PrintBizFieldItemVO f : detailFields) {
if (f != null && StringUtils.isNotBlank(f.getFieldKey())) {
map.put(f.getFieldKey(), f);
}
}
}
}
// ═══════════════════════ 内部 VO ═══════════════════════
@Data
@@ -316,4 +428,20 @@ public class MesXslDingTplBindController {
private String templateId;
private String fieldMappingJson;
}
@Data
public static class ResolveFieldValuesRequest {
private String bizCode;
private Map<String, Object> rowData;
private List<ResolveFieldItem> items;
}
@Data
public static class ResolveFieldItem {
/** 前端映射键,通常为 componentId */
private String mapKey;
private String bizField;
/** raw 或 text */
private String valueMode;
}
}

View File

@@ -0,0 +1,109 @@
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.modules.print.vo.PrintBizFieldItemVO;
import org.jeecg.modules.system.service.ISysDictService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* 审批模板绑定字段取值:支持原值与字典/表字典显示文本。
*/
@Service
@Slf4j
public class DingTplBindFieldValueResolver {
@Autowired private ISysDictService sysDictService;
/**
* 按绑定配置解析字段值。
*
* @param rowData 业务行数据Map
* @param bizField 字段路径
* @param valueMode raw=原值text=显示文本
* @param meta 字段元数据(含 translateKind
*/
public Object resolveValue(
Object rowData, String bizField, String valueMode, PrintBizFieldItemVO meta) {
Object raw = getNestedValue(rowData, bizField);
if (!"text".equalsIgnoreCase(StringUtils.trimToEmpty(valueMode))
|| meta == null
|| StringUtils.isBlank(meta.getTranslateKind())
|| "NONE".equalsIgnoreCase(meta.getTranslateKind())) {
return raw;
}
String fromRow = getDictTextFromRow(rowData, bizField);
if (StringUtils.isNotBlank(fromRow)) {
return fromRow;
}
if (raw == null) {
return null;
}
String key = String.valueOf(raw);
if (StringUtils.isBlank(key)) {
return raw;
}
try {
if ("DICT".equalsIgnoreCase(meta.getTranslateKind())
&& StringUtils.isNotBlank(meta.getDictCode())) {
String text = sysDictService.queryDictTextByKey(meta.getDictCode(), key);
return StringUtils.isNotBlank(text) ? text : raw;
}
if ("TABLE".equalsIgnoreCase(meta.getTranslateKind())
&& StringUtils.isNotBlank(meta.getDictTable())) {
String text =
sysDictService.queryTableDictTextByKey(
meta.getDictTable(),
StringUtils.defaultString(meta.getDictText(), ""),
StringUtils.defaultIfBlank(meta.getDictCodeField(), "id"),
key);
return StringUtils.isNotBlank(text) ? text : raw;
}
} catch (Exception e) {
log.warn("审批绑定字段翻译失败 bizField={} key={}: {}", bizField, key, e.getMessage());
}
return raw;
}
@SuppressWarnings("unchecked")
private Object getNestedValue(Object obj, String path) {
if (obj == null || StringUtils.isBlank(path)) {
return null;
}
String[] parts = path.split("\\.");
Object cur = obj;
for (String p : parts) {
if (cur == null) {
return null;
}
if (cur instanceof Map) {
cur = ((Map<String, Object>) cur).get(p);
} else {
return null;
}
}
return cur;
}
@SuppressWarnings("unchecked")
private String getDictTextFromRow(Object rowData, String bizField) {
if (!(rowData instanceof Map) || StringUtils.isBlank(bizField)) {
return null;
}
Map<String, Object> map = (Map<String, Object>) rowData;
String[] parts = bizField.split("\\.");
if (parts.length == 1) {
Object v = map.get(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");
return v != null ? String.valueOf(v) : null;
}
return null;
}
}

View File

@@ -1,6 +1,7 @@
package org.jeecg.modules.xslmes.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
@@ -157,14 +158,35 @@ public class MesXslMixingSpec implements Serializable {
//update-end---author:cursor ---date:20260608 for【XSLMES-20260608-A01】混炼示方新增状态字段-----------
private String sysOrgCode;
//update-begin---author:GHT ---date:2026-06-10 for【混炼示方】queryById补充起草人/变更人姓名-----------
@Excel(name = "创建人", width = 12, dictTable = "sys_user", dicText = "realname", dicCode = "username")
@Dict(dictTable = "sys_user", dicText = "realname", dicCode = "username")
private String createBy;
/** queryById 等非分页接口补充创建人姓名DictAspect 仅翻译分页列表) */
@TableField(exist = false)
@Schema(description = "创建人姓名")
private String createBy_dictText;
@Dict(dictTable = "sys_user", dicText = "realname", dicCode = "username")
private String updateBy;
/** queryById 补充最后修改人姓名 */
@TableField(exist = false)
@Schema(description = "修改人姓名")
private String updateBy_dictText;
/** queryById 补充起草人姓名draftBy 存用户名时翻译) */
@TableField(exist = false)
@Schema(description = "起草人姓名")
private String draftBy_dictText;
//update-end---author:GHT ---date:2026-06-10 for【混炼示方】queryById补充起草人/变更人姓名-----------
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
private String updateBy;
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date updateTime;

View File

@@ -52,6 +52,14 @@ public class MesXslMixingSpecTcu implements Serializable {
@Schema(description = "药品称量位置(字典xslmes_mixing_drug_weigh_pos)")
private String drugWeighPos;
//update-begin---author:GHT ---date:2026-06-10 for【混炼示方】TCU温度条件新增是否附加/重量-----------
@Schema(description = "是否附加(字典yn)")
private String isAttach;
@Schema(description = "附加重量")
private BigDecimal attachWeight;
//update-end---author:GHT ---date:2026-06-10 for【混炼示方】TCU温度条件新增是否附加/重量-----------
@Schema(description = "租户ID")
private Integer tenantId;

View File

@@ -29,6 +29,8 @@ import org.jeecg.modules.xslmes.entity.MesXslMixingSpecDownStep;
import org.jeecg.modules.xslmes.entity.MesXslMixingSpecMaterial;
import org.jeecg.modules.xslmes.entity.MesXslMixingSpecStep;
import org.jeecg.modules.xslmes.entity.MesXslMixingSpecTcu;
import org.jeecg.modules.system.entity.SysUser;
import org.jeecg.modules.system.service.ISysUserService;
import org.jeecg.modules.xslmes.entity.MesXslMixerPsCompile;
import org.jeecg.modules.xslmes.mapper.MesXslMixingSpecDownStepMapper;
import org.jeecg.modules.xslmes.mapper.MesXslMixingSpecMapper;
@@ -50,6 +52,8 @@ public class MesXslMixingSpecServiceImpl extends ServiceImpl<MesXslMixingSpecMap
private static final String TCU_UP = "up_mixer";
private static final String TCU_DOWN = "down_mixer";
private static final String TCU_ATTACH_YES = "1";
private static final String TCU_ATTACH_NO = "0";
//update-begin---author:cursor ---date:20260601 for【XSLMES-20260601-A62】删除混炼示方时同步删除自动生成的B/F段胶料信息-----------
private static final Pattern GENERATED_B_RUBBER_SPEC_PATTERN = Pattern.compile("^B\\d", Pattern.CASE_INSENSITIVE);
//update-end---author:cursor ---date:20260601 for【XSLMES-20260601-A62】删除混炼示方时同步删除自动生成的B/F段胶料信息-----------
@@ -71,6 +75,9 @@ public class MesXslMixingSpecServiceImpl extends ServiceImpl<MesXslMixingSpecMap
@Resource
private IMesXslFormulaSpecEditLogService mesXslFormulaSpecEditLogService;
@Resource
private ISysUserService sysUserService;
@Override
@Transactional(rollbackFor = Exception.class)
public void saveMain(
@@ -235,6 +242,9 @@ public class MesXslMixingSpecServiceImpl extends ServiceImpl<MesXslMixingSpecMap
page.setStepList(queryStepByMainId(id));
page.setDownStepList(queryDownStepByMainId(id));
page.setTcuList(fillDefaultTcuRows(queryTcuByMainId(id)));
//update-begin---author:GHT ---date:2026-06-10 for【混炼示方】queryById补充起草人/变更人姓名-----------
fillUserDisplayText(page);
//update-end---author:GHT ---date:2026-06-10 for【混炼示方】queryById补充起草人/变更人姓名-----------
return page;
}
@@ -282,6 +292,33 @@ public class MesXslMixingSpecServiceImpl extends ServiceImpl<MesXslMixingSpecMap
return options;
}
//update-begin---author:GHT ---date:2026-06-10 for【混炼示方】queryById补充起草人/变更人姓名-----------
private void fillUserDisplayText(MesXslMixingSpecPage page) {
if (page == null) {
return;
}
page.setCreateBy_dictText(resolveUserRealname(page.getCreateBy()));
page.setUpdateBy_dictText(resolveUserRealname(page.getUpdateBy()));
String draftUsername = StringUtils.isNotBlank(page.getDraftBy()) ? page.getDraftBy() : page.getCreateBy();
String draftRealname = resolveUserRealname(draftUsername);
if (StringUtils.isBlank(draftRealname)) {
draftRealname = page.getCreateBy_dictText();
}
page.setDraftBy_dictText(draftRealname);
}
private String resolveUserRealname(String username) {
if (StringUtils.isBlank(username)) {
return null;
}
SysUser user = sysUserService.getUserByName(username);
if (user != null && StringUtils.isNotBlank(user.getRealname())) {
return user.getRealname();
}
return null;
}
//update-end---author:GHT ---date:2026-06-10 for【混炼示方】queryById补充起草人/变更人姓名-----------
private void normalizeMain(MesXslMixingSpec main) {
if (main.getDraftTime() == null) {
main.setDraftTime(new Date());
@@ -484,6 +521,9 @@ public class MesXslMixingSpecServiceImpl extends ServiceImpl<MesXslMixingSpecMap
if (TCU_DOWN.equals(row.getSectionType())) {
row.setDrugWeighPos(null);
}
//update-begin---author:GHT ---date:2026-06-10 for【混炼示方】TCU是否附加为否时清空重量-----------
normalizeTcuAttachFields(row);
//update-end---author:GHT ---date:2026-06-10 for【混炼示方】TCU是否附加为否时清空重量-----------
rows.add(row);
}
if (trace != null) {
@@ -663,13 +703,16 @@ public class MesXslMixingSpecServiceImpl extends ServiceImpl<MesXslMixingSpecMap
if (!hasUp) {
MesXslMixingSpecTcu up = new MesXslMixingSpecTcu();
up.setSectionType(TCU_UP);
up.setIsAttach(TCU_ATTACH_NO);
rows.add(0, up);
}
if (!hasDown) {
MesXslMixingSpecTcu down = new MesXslMixingSpecTcu();
down.setSectionType(TCU_DOWN);
down.setIsAttach(TCU_ATTACH_NO);
rows.add(down);
}
rows.forEach(this::normalizeTcuAttachFields);
rows.sort((a, b) -> {
int ai = TCU_UP.equals(a.getSectionType()) ? 0 : TCU_DOWN.equals(a.getSectionType()) ? 1 : 2;
int bi = TCU_UP.equals(b.getSectionType()) ? 0 : TCU_DOWN.equals(b.getSectionType()) ? 1 : 2;
@@ -681,6 +724,20 @@ public class MesXslMixingSpecServiceImpl extends ServiceImpl<MesXslMixingSpecMap
return rows;
}
//update-begin---author:GHT ---date:2026-06-10 for【混炼示方】TCU是否附加为否时清空重量-----------
private void normalizeTcuAttachFields(MesXslMixingSpecTcu row) {
if (row == null) {
return;
}
if (StringUtils.isBlank(row.getIsAttach())) {
row.setIsAttach(TCU_ATTACH_NO);
}
if (!TCU_ATTACH_YES.equals(row.getIsAttach())) {
row.setAttachWeight(null);
}
}
//update-end---author:GHT ---date:2026-06-10 for【混炼示方】TCU是否附加为否时清空重量-----------
//update-begin---author:cursor ---date:20260601 for【XSLMES-20260601-A62】删除混炼示方时同步删除自动生成的B/F段胶料信息-----------
/**
* 删除混炼示方后,若该 B/F 段胶示方编号已无其它混炼示方、未被其它示方明细引用、且未被配合示方选作胶料代号,