diff --git a/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/doc/代码修改日志 b/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/doc/代码修改日志 index 07b7171a..20e28ec2 100644 --- a/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/doc/代码修改日志 +++ b/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/doc/代码修改日志 @@ -1248,7 +1248,7 @@ jeecgboot-vue3/src/views/xslmes/mesXslDryingProcessSwitch/MesXslDryingProcessSwi jeecgboot-vue3/src/views/xslmes/mesXslDryingProcessSwitch/MesXslDryingProcessSwitch.api.ts -- author:jiangxh---date:20260708--for: 【MES】烘胶流程开启(总开关+二次确认,不校验烘胶房日期) --- --- author:xsl---date:20260720--for: APPԭͼ졿ֳֶ˰ѯ/ͼ/¼ӿҳ --- +-- author:xsl---date:20260720--for: ��APPԭ�����ͼ졿�ֳֶ˰������ѯ/�ͼ�/¼��������ӿ���ҳ�� --- jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/vo/MesXslRawMaterialInspectAppScanVo.java jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/controller/MesXslRawMaterialInspectAppController.java jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/service/IMesXslRawMaterialInspectRecordService.java @@ -1257,3 +1257,14 @@ JeecgUniapp-master/src/service/xslmes/rawMaterialInspect.ts JeecgUniapp-master/src/pages/rawMaterialInspect/rawMaterialInspect.vue JeecgUniapp-master/src/pages/rawMaterialInspect/inspectResult.vue JeecgUniapp-master/src/common/work.ts + +-- author:GHT---date:20260730--for: XSLMES-20260730-DING-PULLֶ̨ȡǿܼ --- +jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/approval/integration/engine/IntegrationForceReplayHolder.java +jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/approval/integration/engine/IntegrationOrchestrator.java +jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/dingtalk/stream/DingApprovalManualPullService.java +jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/dingtalk/stream/DingBpmsEventProcessor.java +jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/approval/controller/MesXslApprovalRecordController.java +jeecg-module-system/jeecg-system-start/src/main/resources/flyway/sql/mysql/V3.9.2_164__mes_xsl_approval_record_pull_ding_result.sql +jeecgboot-vue3/src/views/xslmes/approval/mesXslApprovalRecord/MesXslApprovalRecordList.vue +jeecgboot-vue3/src/views/xslmes/approval/mesXslApprovalRecord/MesXslApprovalRecord.api.ts +-- author:GHT---date:20260730--for: XSLMES-20260730-DING-PULLֶ̨ȡǿܼ --- diff --git a/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/approval/controller/MesXslApprovalRecordController.java b/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/approval/controller/MesXslApprovalRecordController.java index 98fa62a2..047dfd5b 100644 --- a/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/approval/controller/MesXslApprovalRecordController.java +++ b/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/approval/controller/MesXslApprovalRecordController.java @@ -13,8 +13,10 @@ import org.jeecg.common.system.base.controller.JeecgController; import org.jeecg.common.system.query.QueryGenerator; import org.jeecg.modules.xslmes.approval.entity.MesXslApprovalRecord; import org.jeecg.modules.xslmes.approval.service.IMesXslApprovalRecordService; +import org.jeecg.modules.xslmes.dingtalk.stream.DingApprovalManualPullService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @@ -35,6 +37,9 @@ public class MesXslApprovalRecordController extends JeecgController pullDingResult(@RequestParam(name = "id") String id) { + return dingApprovalManualPullService.pullAndForceReplay(id); + } + //update-end---author:GHT ---date:20260730 for:【XSLMES-20260730-DING-PULL】台账手动拉取钉钉结果并强制重跑集成----- + @RequiresPermissions("xslmes:mes_xsl_approval_record:exportXls") @RequestMapping(value = "/exportXls") public ModelAndView exportXls(HttpServletRequest request, MesXslApprovalRecord model) { diff --git a/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/approval/integration/engine/IntegrationForceReplayHolder.java b/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/approval/integration/engine/IntegrationForceReplayHolder.java new file mode 100644 index 00000000..d229f264 --- /dev/null +++ b/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/approval/integration/engine/IntegrationForceReplayHolder.java @@ -0,0 +1,25 @@ +package org.jeecg.modules.xslmes.approval.integration.engine; + +/** + * 手动强制重跑集成时的线程标记:旁路幂等跳过,允许假 success 后再次执行。 + */ +public final class IntegrationForceReplayHolder { + + private static final ThreadLocal FORCE = ThreadLocal.withInitial(() -> Boolean.FALSE); + + private IntegrationForceReplayHolder() { + } + + public static boolean isForced() { + return Boolean.TRUE.equals(FORCE.get()); + } + + public static void runForced(Runnable action) { + FORCE.set(Boolean.TRUE); + try { + action.run(); + } finally { + FORCE.remove(); + } + } +} diff --git a/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/approval/integration/engine/IntegrationOrchestrator.java b/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/approval/integration/engine/IntegrationOrchestrator.java index 207a503a..03743625 100644 --- a/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/approval/integration/engine/IntegrationOrchestrator.java +++ b/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/approval/integration/engine/IntegrationOrchestrator.java @@ -223,11 +223,16 @@ public class IntegrationOrchestrator { IIntegrationActionExecutor executor = findExecutor(action.getActionType()); String response = executor.execute(ctx, action); long ms = System.currentTimeMillis() - t0; - writeLog(ctx, action, idempotentKey, "success", null, response, snapshot, null, ms); - successCount++; - //update-begin---author:GHT ---date:20260608 for:【审核集成】动作成功后刷新源单快照,供后续动作使用最新字段----------- - refreshSourceRecord(ctx); - //update-end---author:GHT ---date:20260608 for:【审核集成】动作成功后刷新源单快照,供后续动作使用最新字段----------- + //update-begin---author:GHT ---date:20260730 for:【XSLMES-20260730-DING-PULL】条件不符的「跳过」不记 success,避免假成功堵死重试----------- + if (oConvertUtils.isNotEmpty(response) && response.startsWith("跳过:")) { + writeLog(ctx, action, idempotentKey, "skipped", null, response, snapshot, null, ms); + log.info("[集成引擎] 动作条件跳过 action={} response={}", action.getActionName(), response); + } else { + writeLog(ctx, action, idempotentKey, "success", null, response, snapshot, null, ms); + successCount++; + refreshSourceRecord(ctx); + } + //update-end---author:GHT ---date:20260730 for:【XSLMES-20260730-DING-PULL】条件不符的「跳过」不记 success,避免假成功堵死重试----------- } catch (Exception e) { long ms = System.currentTimeMillis() - t0; String errMsg = e.getMessage(); @@ -386,8 +391,26 @@ public class IntegrationOrchestrator { /** * 回退类动作:历史 success 但源单 status 仍未到 targetStage 时,允许再次执行。 + * 环节同步:历史 success 但源单仍未到 statusAfter 时,允许再次执行(修复假成功堵死)。 + * 手动强制重跑:一律旁路。 */ private boolean shouldBypassIdempotentSkip(IntegrationContext ctx, MesXslIntegrationAction action) { + //update-begin---author:GHT ---date:20260730 for:【XSLMES-20260730-DING-PULL】强制重跑+环节同步未达目标旁路幂等----------- + if (IntegrationForceReplayHolder.isForced()) { + return true; + } + if ("REGISTRY_STAGE_SYNC".equals(action.getActionType())) { + String statusAfter = resolveSyncStatusAfter(ctx, action); + if (oConvertUtils.isEmpty(statusAfter)) { + return false; + } + String currentStatus = readSourceStatus(ctx); + if (oConvertUtils.isEmpty(currentStatus)) { + return true; + } + return !statusAfter.equals(currentStatus); + } + //update-end---author:GHT ---date:20260730 for:【XSLMES-20260730-DING-PULL】强制重跑+环节同步未达目标旁路幂等----------- if (!"REGISTRY_STAGE_REVERT".equals(action.getActionType())) { return false; } @@ -399,6 +422,13 @@ public class IntegrationOrchestrator { return !targetStage.equals(currentStatus); } + //update-begin---author:GHT ---date:20260730 for:【XSLMES-20260730-DING-PULL】解析环节同步目标状态----------- + private String resolveSyncStatusAfter(IntegrationContext ctx, MesXslIntegrationAction action) { + String stage = IntegrationActionConfigHelper.resolveStage(action, ctx.getPlan()); + return IntegrationActionConfigHelper.resolveStatusAfter(action, stage); + } + //update-end---author:GHT ---date:20260730 for:【XSLMES-20260730-DING-PULL】解析环节同步目标状态----------- + private String resolveRevertTargetStage(MesXslIntegrationAction action) { String target = IntegrationActionConfigHelper.resolveTargetStage(action); if (oConvertUtils.isNotEmpty(target)) { diff --git a/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/dingtalk/stream/DingApprovalManualPullService.java b/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/dingtalk/stream/DingApprovalManualPullService.java new file mode 100644 index 00000000..0bee3b58 --- /dev/null +++ b/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/dingtalk/stream/DingApprovalManualPullService.java @@ -0,0 +1,143 @@ +package org.jeecg.modules.xslmes.dingtalk.stream; + +import com.alibaba.fastjson2.JSONObject; +import lombok.extern.slf4j.Slf4j; +import org.jeecg.common.api.vo.Result; +import org.jeecg.common.util.oConvertUtils; +import org.jeecg.modules.xslmes.approval.constant.ApprovalRecordConstants; +import org.jeecg.modules.xslmes.approval.entity.MesXslApprovalRecord; +import org.jeecg.modules.xslmes.approval.integration.engine.IntegrationForceReplayHolder; +import org.jeecg.modules.xslmes.approval.service.IMesXslApprovalRecordService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +/** + * 审批台账手动拉取钉钉结果并强制重跑集成。 + *

+ * 用于 Stream 接收节点未在线导致漏推、或集成假 success 后幂等卡死的人工修复。 + */ +@Slf4j +@Service +public class DingApprovalManualPullService { + + private static final String LOG_TAG = DingTalkStreamSdkRunner.LOG_TAG; + + @Autowired + private IMesXslApprovalRecordService approvalRecordService; + + @Autowired + private DingTalkWorkflowService workflowService; + + @Autowired + private DingBpmsEventProcessor eventProcessor; + + //update-begin---author:GHT ---date:20260730 for:【XSLMES-20260730-DING-PULL】台账手动拉取钉钉审批结果并强制重跑集成----- + /** + * 按台账 ID 拉取钉钉实例,强制重放已同意中间节点集成,终态时再触发 onInstanceChange。 + */ + public Result pullAndForceReplay(String recordId) { + if (oConvertUtils.isEmpty(recordId)) { + return Result.error("台账ID不能为空"); + } + MesXslApprovalRecord record = approvalRecordService.getById(recordId); + if (record == null) { + return Result.error("未找到审批台账"); + } + if (!ApprovalRecordConstants.CHANNEL_DINGTALK.equals(record.getChannel())) { + return Result.error("仅支持钉钉通道台账"); + } + if (oConvertUtils.isEmpty(record.getExternalInstanceId())) { + return Result.error("台账无钉钉实例ID,无法拉取"); + } + if (oConvertUtils.isEmpty(record.getBizTable()) || oConvertUtils.isEmpty(record.getBizDataId())) { + return Result.error("台账缺少业务表或业务ID"); + } + + String instanceId = record.getExternalInstanceId(); + JSONObject instance = workflowService.getProcessInstance(instanceId); + if (instance == null) { + return Result.error("拉取钉钉审批实例失败,请稍后重试"); + } + + String dingStatus = instance.getString("status"); + String dingResult = instance.getString("result"); + log.info("{} 手动拉取开始 recordId={} instanceId={} dingStatus={} dingResult={}", + LOG_TAG, recordId, instanceId, dingStatus, dingResult); + + int[] nodeHolder = {0}; + boolean[] terminalHolder = {false}; + String[] errHolder = {null}; + + IntegrationForceReplayHolder.runForced(() -> { + try { + nodeHolder[0] = eventProcessor.forceReconcileAgreedNodes(record, instance); + JSONObject synthetic = buildSyntheticEvent(instance, instanceId); + if (synthetic != null) { + eventProcessor.onInstanceChange(synthetic); + terminalHolder[0] = true; + } + } catch (Exception e) { + log.error("{} 手动拉取强制重跑失败 recordId={} instanceId={}: {}", + LOG_TAG, recordId, instanceId, e.getMessage(), e); + errHolder[0] = e.getMessage(); + } + }); + + if (errHolder[0] != null) { + return Result.error("拉取成功但重跑集成失败: " + errHolder[0]); + } + + StringBuilder msg = new StringBuilder("已拉取钉钉结果并强制重跑集成"); + msg.append("(钉钉状态=").append(dingStatus); + if (oConvertUtils.isNotEmpty(dingResult)) { + msg.append(",结果=").append(dingResult); + } + msg.append(")"); + msg.append(";中间节点重放 ").append(nodeHolder[0]).append(" 个"); + if (terminalHolder[0]) { + msg.append(";已触发终态回调"); + } else if ("RUNNING".equalsIgnoreCase(dingStatus)) { + msg.append(";钉钉仍流转中,仅补偿已同意节点"); + } else { + msg.append(";未映射到可处理终态,请核对钉钉实例"); + } + log.info("{} 手动拉取完成 recordId={} {}", LOG_TAG, recordId, msg); + return Result.OK(msg.toString()); + } + + /** + * 将钉钉实例状态映射为 onInstanceChange 所需的合成事件(与补偿扫描一致)。 + */ + private JSONObject buildSyntheticEvent(JSONObject instance, String processInstanceId) { + String dingStatus = instance.getString("status"); + String dingResult = instance.getString("result"); + + if ("RUNNING".equalsIgnoreCase(dingStatus)) { + return null; + } + + JSONObject event = new JSONObject(); + event.put("processInstanceId", processInstanceId); + + if ("COMPLETED".equalsIgnoreCase(dingStatus)) { + if ("agree".equalsIgnoreCase(dingResult)) { + event.put("type", "finish"); + event.put("result", "agree"); + } else if ("refuse".equalsIgnoreCase(dingResult)) { + event.put("type", "finish"); + event.put("result", "refuse"); + } else { + event.put("type", "finish"); + event.put("result", oConvertUtils.isEmpty(dingResult) ? "unknown" : dingResult); + } + return event; + } + + if ("TERMINATED".equalsIgnoreCase(dingStatus) || "CANCELED".equalsIgnoreCase(dingStatus)) { + event.put("type", "terminate"); + return event; + } + return null; + } + //update-end---author:GHT ---date:20260730 for:【XSLMES-20260730-DING-PULL】台账手动拉取钉钉审批结果并强制重跑集成----- +} diff --git a/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/dingtalk/stream/DingBpmsEventProcessor.java b/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/dingtalk/stream/DingBpmsEventProcessor.java index 164013e1..5db5db50 100644 --- a/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/dingtalk/stream/DingBpmsEventProcessor.java +++ b/jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/dingtalk/stream/DingBpmsEventProcessor.java @@ -491,10 +491,22 @@ public class DingBpmsEventProcessor { * @return 本次补偿触发的节点数 */ public int reconcileIntermediateNodes(MesXslApprovalRecord record, JSONObject instance) { + return reconcileAgreedNodes(record, instance, false); + } + + //update-begin---author:GHT ---date:20260730 for:【XSLMES-20260730-DING-PULL】强制重放已同意中间节点(不限台账流转态)----- + /** + * 手动拉取场景:忽略台账是否已终态、忽略历史集成 success,强制按序重放已同意节点。 + */ + public int forceReconcileAgreedNodes(MesXslApprovalRecord record, JSONObject instance) { + return reconcileAgreedNodes(record, instance, true); + } + + private int reconcileAgreedNodes(MesXslApprovalRecord record, JSONObject instance, boolean force) { if (record == null || instance == null) { return 0; } - if (!ApprovalRecordConstants.STATUS_RUNNING.equals(record.getStatus())) { + if (!force && !ApprovalRecordConstants.STATUS_RUNNING.equals(record.getStatus())) { return 0; } MesXslApprovalFlow flow = loadFlow(record.getFlowId()); @@ -504,8 +516,8 @@ public class DingBpmsEventProcessor { List pairs = instanceStageExtractor.alignMesNodesWithTasks(instance, flow.getFlowConfig()); if (pairs.isEmpty()) { - log.info("{} 中间节点补偿:无节点对齐 instanceId={} recordId={}", - LOG_TAG, record.getExternalInstanceId(), record.getId()); + log.info("{} 中间节点补偿:无节点对齐 instanceId={} recordId={} force={}", + LOG_TAG, record.getExternalInstanceId(), record.getId(), force); return 0; } @@ -519,7 +531,7 @@ public class DingBpmsEventProcessor { } String planId = resolveNodeIntegrationPlanId(pair.getMesNode()); - if (oConvertUtils.isNotEmpty(planId) && hasIntegrationSuccess(record.getId(), planId)) { + if (!force && oConvertUtils.isNotEmpty(planId) && hasIntegrationSuccess(record.getId(), planId)) { continue; } if (oConvertUtils.isEmpty(planId)) { @@ -529,8 +541,8 @@ public class DingBpmsEventProcessor { JSONObject mesNode = pair.getMesNode(); String nodeName = mesNode != null ? mesNode.getString("name") : null; - log.info("{} 中间节点补偿:触发集成 instanceId={} recordId={} nodeIndex={} nodeName={} planId={}", - LOG_TAG, record.getExternalInstanceId(), record.getId(), nodeIndex, nodeName, planId); + log.info("{} 中间节点补偿:触发集成 instanceId={} recordId={} nodeIndex={} nodeName={} planId={} force={}", + LOG_TAG, record.getExternalInstanceId(), record.getId(), nodeIndex, nodeName, planId, force); try { fireCompensatedNodeApproved(record, instance, pair, nodeIndex, decision); @@ -542,6 +554,7 @@ public class DingBpmsEventProcessor { } return compensated; } + //update-end---author:GHT ---date:20260730 for:【XSLMES-20260730-DING-PULL】强制重放已同意中间节点(不限台账流转态)----- private void fireCompensatedNodeApproved(MesXslApprovalRecord record, JSONObject instance, NodePair pair, int nodeIndex, NodeTaskDecision decision) { diff --git a/jeecg-boot/jeecg-module-system/jeecg-system-biz/docs/代码修改日志 b/jeecg-boot/jeecg-module-system/jeecg-system-biz/docs/代码修改日志 index 606f54c1..1a5ec7ff 100644 --- a/jeecg-boot/jeecg-module-system/jeecg-system-biz/docs/代码修改日志 +++ b/jeecg-boot/jeecg-module-system/jeecg-system-biz/docs/代码修改日志 @@ -602,7 +602,7 @@ jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules jeecgboot-vue3/src/views/xslmes/mesXslRubberQuickTestRecord/MesXslRubberQuickTestRecord.data.ts jeecgboot-vue3/src/views/xslmes/mesXslRubberQuickTestRecord/components/MesXslRubberQuickTestRecordModal.vue --- author:xsl---date:20260720--for: IMͬͷҲ WebSocketͬ˺ PC/APP ͬʾ --- +-- author:xsl---date:20260720--for: ��IM���ͬ�������ͷ�Ҳ���� WebSocket��ͬ�˺� PC/APP ����ͬ����ʾ --- jeecg-boot/jeecg-module-system/jeecg-system-biz/src/main/java/org/jeecg/modules/im/service/impl/SysImChatServiceImpl.java jeecgboot-vue3/src/views/system/im/imCache.ts jeecgboot-vue3/src/views/system/im/ImChat.vue diff --git a/jeecg-boot/jeecg-module-system/jeecg-system-start/src/main/resources/flyway/sql/mysql/V3.9.2_164__mes_xsl_approval_record_pull_ding_result.sql b/jeecg-boot/jeecg-module-system/jeecg-system-start/src/main/resources/flyway/sql/mysql/V3.9.2_164__mes_xsl_approval_record_pull_ding_result.sql new file mode 100644 index 00000000..c99b8bd3 --- /dev/null +++ b/jeecg-boot/jeecg-module-system/jeecg-system-start/src/main/resources/flyway/sql/mysql/V3.9.2_164__mes_xsl_approval_record_pull_ding_result.sql @@ -0,0 +1,18 @@ +-- ============================================================ +-- 审批台账:手动拉取钉钉审批结果权限 +-- author: GHT date: 2026-07-30 +-- for: 【XSLMES-20260730-DING-PULL】 +-- ============================================================ + +INSERT INTO sys_permission(id, parent_id, name, url, component, is_route, component_name, redirect, menu_type, perms, perms_type, sort_no, always_show, icon, is_leaf, keep_alive, hidden, hide_tab, description, create_by, create_time, update_by, update_time, del_flag, rule_flag, status, internal_or_external) +SELECT '178046026420823', '178046026420820', '拉取钉钉审批结果', NULL, NULL, 0, NULL, NULL, 2, 'xslmes:mes_xsl_approval_record:pullDingResult', '1', NULL, 0, NULL, 1, 0, 0, 0, NULL, 'admin', '2026-07-30 00:00:00', NULL, NULL, 0, 0, '1', 0 +FROM DUAL +WHERE NOT EXISTS (SELECT 1 FROM sys_permission WHERE id = '178046026420823'); + +INSERT INTO sys_role_permission (id, role_id, permission_id, data_rule_ids, operate_date, operate_ip) +SELECT REPLACE(UUID(),'-',''), 'f6817f48af4fb3af11b9e8bf182f618b', '178046026420823', NULL, '2026-07-30 00:00:00', '127.0.0.1' +FROM DUAL +WHERE NOT EXISTS ( + SELECT 1 FROM sys_role_permission + WHERE role_id = 'f6817f48af4fb3af11b9e8bf182f618b' AND permission_id = '178046026420823' +); diff --git a/jeecgboot-vue3/src/views/xslmes/approval/mesXslApprovalRecord/MesXslApprovalRecord.api.ts b/jeecgboot-vue3/src/views/xslmes/approval/mesXslApprovalRecord/MesXslApprovalRecord.api.ts index d7390b88..a48e1297 100644 --- a/jeecgboot-vue3/src/views/xslmes/approval/mesXslApprovalRecord/MesXslApprovalRecord.api.ts +++ b/jeecgboot-vue3/src/views/xslmes/approval/mesXslApprovalRecord/MesXslApprovalRecord.api.ts @@ -4,6 +4,7 @@ enum Api { list = '/xslmes/mesXslApprovalRecord/list', queryById = '/xslmes/mesXslApprovalRecord/queryById', exportXls = '/xslmes/mesXslApprovalRecord/exportXls', + pullDingResult = '/xslmes/mesXslApprovalRecord/pullDingResult', } export const getExportUrl = Api.exportXls; @@ -11,3 +12,7 @@ export const getExportUrl = Api.exportXls; export const list = (params) => defHttp.get({ url: Api.list, params }); export const queryById = (params: { id: string }) => defHttp.get({ url: Api.queryById, params }); + +/** 手动拉取钉钉审批结果并强制重跑集成(含钉钉API与多节点集成,超时放宽) */ +export const pullDingResult = (params: { id: string }) => + defHttp.post({ url: Api.pullDingResult, params, timeout: 120 * 1000 }, { joinParamsToUrl: true }); diff --git a/jeecgboot-vue3/src/views/xslmes/approval/mesXslApprovalRecord/MesXslApprovalRecordList.vue b/jeecgboot-vue3/src/views/xslmes/approval/mesXslApprovalRecord/MesXslApprovalRecordList.vue index eef0040d..db32768a 100644 --- a/jeecgboot-vue3/src/views/xslmes/approval/mesXslApprovalRecord/MesXslApprovalRecordList.vue +++ b/jeecgboot-vue3/src/views/xslmes/approval/mesXslApprovalRecord/MesXslApprovalRecordList.vue @@ -19,6 +19,16 @@ onClick: handleDetail.bind(null, record), auth: 'xslmes:mes_xsl_approval_record:list', }, + { + label: '拉取钉钉结果', + color: 'success', + ifShow: record.channel === 'DINGTALK' && !!record.externalInstanceId, + popConfirm: { + title: '将拉取钉钉最新审批结果并强制重跑集成方案,确认继续?', + confirm: handlePullDingResult.bind(null, record), + }, + auth: 'xslmes:mes_xsl_approval_record:list', + }, { label: '补发IM卡片', color: 'warning', @@ -43,7 +53,7 @@ import { useListPage } from '/@/hooks/system/useListPage'; import MesXslApprovalRecordDetailModal from './components/MesXslApprovalRecordDetailModal.vue'; import { columns, searchFormSchema } from './MesXslApprovalRecord.data'; - import { list, getExportUrl } from './MesXslApprovalRecord.api'; + import { list, getExportUrl, pullDingResult } from './MesXslApprovalRecord.api'; import { resendApprovalCard } from '/@/views/approval/flow/approvalHandle.api'; import { useMessage } from '/@/hooks/web/useMessage'; @@ -63,7 +73,7 @@ showAdvancedButton: true, }, actionColumn: { - width: 180, + width: 280, fixed: 'right', }, }, @@ -73,7 +83,7 @@ }, }); - const [registerTable] = tableContext; + const [registerTable, { reload }] = tableContext; function handleDetail(record: Recordable) { openModal(true, { record }); @@ -87,4 +97,17 @@ createMessage.error(e?.message || '补发失败'); } } + + async function handlePullDingResult(record: Recordable) { + const hide = createMessage.loading('正在拉取钉钉结果并重跑集成,请稍候…', 0); + try { + const msg = await pullDingResult({ id: record.id }); + createMessage.success(msg || '拉取并重跑完成'); + reload(); + } catch (e: any) { + createMessage.error(e?.message || '拉取钉钉结果失败'); + } finally { + hide(); + } + }