Merge remote-tracking branch 'origin/20260519-3.9.2版本-葛昊天分支'
This commit is contained in:
@@ -466,3 +466,58 @@ jeecgboot-vue3/src/views/xslmes/mesXslRubberQuickTestRecord/MesXslRubberQuickTes
|
||||
jeecgboot-vue3/src/views/xslmes/mesXslRubberQuickTestRecord/MesXslRubberQuickTestRecord.data.ts
|
||||
jeecgboot-vue3/src/views/xslmes/mesXslRubberQuickTestRecord/components/MesXslRubberQuickTestRecordModal.vue
|
||||
jeecgboot-vue3/src/views/mes/material/MesMaterialList.vue
|
||||
|
||||
-- author:GHT---date:20260529--for: 【QH-MES审批流设计】我的租户下新增审批流设计,钉钉式可视化拖拽设计(先选单据再设计流程),本期实现设计器 ---
|
||||
jeecg-boot/jeecg-module-system/jeecg-system-start/src/main/resources/flyway/sql/mysql/V3.9.2_111__mes_xsl_approval_flow.sql
|
||||
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/approval/entity/MesXslApprovalFlow.java
|
||||
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/approval/mapper/MesXslApprovalFlowMapper.java
|
||||
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/approval/service/IMesXslApprovalFlowService.java
|
||||
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/approval/service/impl/MesXslApprovalFlowServiceImpl.java
|
||||
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/approval/controller/MesXslApprovalFlowController.java
|
||||
jeecgboot-vue3/src/views/approval/flow/approvalFlow.api.ts
|
||||
jeecgboot-vue3/src/views/approval/flow/approvalFlow.data.ts
|
||||
jeecgboot-vue3/src/views/approval/flow/ApprovalFlowList.vue
|
||||
jeecgboot-vue3/src/views/approval/flow/ApprovalFlowModal.vue
|
||||
jeecgboot-vue3/src/views/approval/flow/components/flowTypes.ts
|
||||
jeecgboot-vue3/src/views/approval/flow/components/FlowNode.vue
|
||||
jeecgboot-vue3/src/views/approval/flow/components/NodeConfigDrawer.vue
|
||||
jeecgboot-vue3/src/views/approval/flow/components/FlowDesign.vue
|
||||
jeecgboot-vue3/src/views/approval/flow/components/flow.less
|
||||
|
||||
-- author:GHT---date:20260529--for: 【QH-MES审批流设计】发起审批运行时:全局悬浮按钮(选单据类型->选单据->发起),本期仅发起(生成审批实例+解析首节点处理人),不办理/不回写业务表 ---
|
||||
jeecg-boot/jeecg-module-system/jeecg-system-start/src/main/resources/flyway/sql/mysql/V3.9.2_112__mes_xsl_approval_instance.sql
|
||||
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/approval/entity/MesXslApprovalFlow.java
|
||||
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/approval/entity/MesXslApprovalInstance.java
|
||||
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/approval/mapper/MesXslApprovalInstanceMapper.java
|
||||
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/approval/service/IMesXslApprovalInstanceService.java
|
||||
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/approval/service/impl/MesXslApprovalInstanceServiceImpl.java
|
||||
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/approval/controller/MesXslApprovalLaunchController.java
|
||||
jeecgboot-vue3/src/views/approval/flow/approvalFlow.data.ts
|
||||
jeecgboot-vue3/src/views/approval/flow/launch.api.ts
|
||||
jeecgboot-vue3/src/components/ApprovalLaunch/index.vue
|
||||
jeecgboot-vue3/src/layouts/default/index.vue
|
||||
|
||||
-- author:GHT---date:20260529--for: 【QH-MES审批流设计】发起审批悬浮按钮仅在配置了审批流的功能页显示:审批流定义增加route_path(功能页路由),前端按当前路由匹配后才显示按钮 ---
|
||||
jeecg-boot/jeecg-module-system/jeecg-system-start/src/main/resources/flyway/sql/mysql/V3.9.2_113__mes_xsl_approval_flow_route.sql
|
||||
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/approval/entity/MesXslApprovalFlow.java
|
||||
jeecgboot-vue3/src/views/approval/flow/approvalFlow.data.ts
|
||||
jeecgboot-vue3/src/components/ApprovalLaunch/index.vue
|
||||
|
||||
-- author:GHT---date:20260529--for: 【QH-MES审批流设计】取消手填功能页路由:publishedList按单据表名自动反查sys_permission菜单url填入routePath,设计表单去掉路由字段 ---
|
||||
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/approval/controller/MesXslApprovalLaunchController.java
|
||||
jeecgboot-vue3/src/views/approval/flow/approvalFlow.data.ts
|
||||
|
||||
-- author:GHT---date:20260529--for: 【QH-MES审批流设计】发起审批支持列表多选联动:useListPage自动同步选中行到全局上下文,悬浮按钮发起弹窗自动带入选中单据并批量发起 ---
|
||||
jeecgboot-vue3/src/components/ApprovalLaunch/useApprovalSelection.ts
|
||||
jeecgboot-vue3/src/hooks/system/useListPage.ts
|
||||
jeecgboot-vue3/src/components/ApprovalLaunch/index.vue
|
||||
jeecgboot-vue3/src/views/approval/flow/launch.api.ts
|
||||
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/approval/controller/MesXslApprovalLaunchController.java
|
||||
|
||||
-- author:GHT---date:20260529--for: 【QH-MES审批流设计】发起审批与IM聊天结合: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/controller/MesXslApprovalLaunchController.java
|
||||
|
||||
-- author:GHT---date:20260529--for: 【QH-MES审批流设计】审批IM消息升级为可跳转业务卡片(biz_record):点击可定位到对应单据,无法定位功能页时退回纯文本 ---
|
||||
jeecg-boot/jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/approval/controller/MesXslApprovalLaunchController.java
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
package org.jeecg.modules.xslmes.approval.callback;
|
||||
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
|
||||
/**
|
||||
* 审批动作领域事件。
|
||||
* 与 {@link IApprovalBizCallback} 等价的另一种接入方式:
|
||||
* 偏好松耦合的业务可使用 {@code @EventListener} 监听本事件(同步、同事务)。
|
||||
*
|
||||
* @author GHT
|
||||
* @date 2026-05-29 for:【QH-MES审批流设计】审批与业务单据联动回调
|
||||
*/
|
||||
public class ApprovalActionEvent extends ApplicationEvent {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final ApprovalCallbackContext context;
|
||||
|
||||
public ApprovalActionEvent(Object source, ApprovalCallbackContext context) {
|
||||
super(source);
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
public ApprovalCallbackContext getContext() {
|
||||
return context;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
package org.jeecg.modules.xslmes.approval.callback;
|
||||
|
||||
import com.alibaba.fastjson2.JSONArray;
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.jeecg.common.constant.CommonConstant;
|
||||
import org.jeecg.common.util.oConvertUtils;
|
||||
import org.jeecg.modules.xslmes.approval.entity.MesXslApprovalInstance;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
|
||||
/**
|
||||
* 审批节点「回调接口」HTTP 执行器。
|
||||
*
|
||||
* 审批到对应时机时,读取节点配置中录制好的业务接口(url+method),
|
||||
* 以「当前审批处理人」的登录态(透传当前请求的 X-Access-Token)内部调用该接口,
|
||||
* 自动带上单据ID(覆盖 id 参数),从而真实执行业务页面按钮背后的逻辑。
|
||||
*
|
||||
* 限制:自动流转 / 无人值守节点(无登录态请求上下文)时降级跳过并记录日志。
|
||||
*
|
||||
* @author GHT
|
||||
* @date 2026-05-29 for:【QH-MES审批流设计】节点回调接口内部调用执行
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class ApprovalActionHttpExecutor {
|
||||
|
||||
@Value("${server.port:8080}")
|
||||
private int serverPort;
|
||||
|
||||
@Value("${server.servlet.context-path:}")
|
||||
private String contextPath;
|
||||
|
||||
private final RestTemplate restTemplate = new RestTemplate();
|
||||
|
||||
/**
|
||||
* 执行某节点在指定时机配置的所有回调接口。
|
||||
*
|
||||
* @param node 流程节点 JSON(含 props.callbackActions)
|
||||
* @param phase 时机:onNodeApprove / onApprove / onReject
|
||||
* @param inst 审批实例
|
||||
*/
|
||||
public void run(JSONObject node, String phase, MesXslApprovalInstance inst) {
|
||||
if (node == null || inst == null) {
|
||||
return;
|
||||
}
|
||||
JSONObject propsObj = node.getJSONObject("props");
|
||||
if (propsObj == null) {
|
||||
return;
|
||||
}
|
||||
JSONObject callbackActions = propsObj.getJSONObject("callbackActions");
|
||||
if (callbackActions == null) {
|
||||
return;
|
||||
}
|
||||
JSONArray actions = callbackActions.getJSONArray(phase);
|
||||
if (actions == null || actions.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
String token = currentToken();
|
||||
for (int i = 0; i < actions.size(); i++) {
|
||||
JSONObject action = actions.getJSONObject(i);
|
||||
if (action == null) {
|
||||
continue;
|
||||
}
|
||||
String url = action.getString("url");
|
||||
if (oConvertUtils.isEmpty(url)) {
|
||||
continue;
|
||||
}
|
||||
if (oConvertUtils.isEmpty(token)) {
|
||||
// 无登录态(自动流转/无人值守) -> 降级跳过
|
||||
log.warn("[审批回调] 无当前处理人登录态,跳过接口调用 phase={}, url={}, bizId={}", phase, url, inst.getBizDataId());
|
||||
continue;
|
||||
}
|
||||
String method = oConvertUtils.getString(action.getString("method"), "POST").toUpperCase();
|
||||
invoke(method, url, action.getJSONObject("body"), inst.getBizDataId(), token);
|
||||
}
|
||||
}
|
||||
|
||||
private void invoke(String method, String url, JSONObject recordedBody, String bizDataId, String token) {
|
||||
String fullUrl = buildFullUrl(url);
|
||||
HttpMethod httpMethod = HttpMethod.valueOf(method);
|
||||
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.APPLICATION_JSON);
|
||||
headers.add(CommonConstant.X_ACCESS_TOKEN, token);
|
||||
headers.add(HttpHeaders.AUTHORIZATION, token);
|
||||
|
||||
Object bodyToSend = null;
|
||||
if ("GET".equals(method) || "DELETE".equals(method)) {
|
||||
// 查询/删除:单据ID拼到 url
|
||||
String sep = fullUrl.contains("?") ? "&" : "?";
|
||||
fullUrl = fullUrl + sep + "id=" + bizDataId + "&dataId=" + bizDataId;
|
||||
} else {
|
||||
// 写操作:合并录制的 body,并用单据ID覆盖 id
|
||||
JSONObject body = recordedBody == null ? new JSONObject() : new JSONObject(recordedBody);
|
||||
body.put("id", bizDataId);
|
||||
bodyToSend = body;
|
||||
}
|
||||
|
||||
try {
|
||||
HttpEntity<Object> entity = new HttpEntity<>(bodyToSend, headers);
|
||||
ResponseEntity<String> resp = restTemplate.exchange(fullUrl, httpMethod, entity, String.class);
|
||||
String respBody = resp.getBody();
|
||||
if (!resp.getStatusCode().is2xxSuccessful()) {
|
||||
throw new RuntimeException("回调接口返回非2xx:" + resp.getStatusCode());
|
||||
}
|
||||
// Jeecg 统一返回 {success:false,...} 视为业务失败
|
||||
if (oConvertUtils.isNotEmpty(respBody)) {
|
||||
try {
|
||||
JSONObject r = JSONObject.parseObject(respBody);
|
||||
if (r != null && r.containsKey("success") && Boolean.FALSE.equals(r.getBoolean("success"))) {
|
||||
throw new RuntimeException("回调接口业务失败:" + oConvertUtils.getString(r.getString("message"), "未知错误"));
|
||||
}
|
||||
} catch (RuntimeException re) {
|
||||
throw re;
|
||||
} catch (Exception ignore) {
|
||||
// 非JSON响应不强校验
|
||||
}
|
||||
}
|
||||
log.info("[审批回调] 已调用业务接口成功 {} {} bizId={}", method, fullUrl, bizDataId);
|
||||
} catch (RuntimeException e) {
|
||||
log.error("[审批回调] 调用业务接口失败 {} {} bizId={}", method, fullUrl, bizDataId, e);
|
||||
// 抛出以回滚整个审批动作,保证审批与业务一致
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
/** 构建内部调用绝对地址:http://127.0.0.1:port + context-path + url */
|
||||
private String buildFullUrl(String url) {
|
||||
String ctx = oConvertUtils.getString(contextPath, "");
|
||||
if (oConvertUtils.isNotEmpty(ctx) && !ctx.startsWith("/")) {
|
||||
ctx = "/" + ctx;
|
||||
}
|
||||
String path = url.startsWith("/") ? url : "/" + url;
|
||||
// 录制到的路径已含 context-path 时不重复拼接
|
||||
if (oConvertUtils.isNotEmpty(ctx) && (path.equals(ctx) || path.startsWith(ctx + "/"))) {
|
||||
ctx = "";
|
||||
}
|
||||
return "http://127.0.0.1:" + serverPort + ctx + path;
|
||||
}
|
||||
|
||||
/** 取当前请求的登录 token(处理人身份) */
|
||||
private String currentToken() {
|
||||
try {
|
||||
ServletRequestAttributes attrs = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
|
||||
if (attrs == null) {
|
||||
return null;
|
||||
}
|
||||
HttpServletRequest request = attrs.getRequest();
|
||||
String token = request.getHeader(CommonConstant.X_ACCESS_TOKEN);
|
||||
if (oConvertUtils.isEmpty(token)) {
|
||||
token = request.getHeader(HttpHeaders.AUTHORIZATION);
|
||||
}
|
||||
return token;
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
package org.jeecg.modules.xslmes.approval.callback;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
import org.jeecg.modules.xslmes.approval.entity.MesXslApprovalInstance;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 审批回调上下文。
|
||||
* 由审批引擎在「节点通过 / 最终通过 / 驳回」时构建并传给业务回调,
|
||||
* 业务模块据此调用自身已有的审核/回写接口,实现审批与业务功能的统一联动。
|
||||
*
|
||||
* @author GHT
|
||||
* @date 2026-05-29 for:【QH-MES审批流设计】审批与业务单据联动回调
|
||||
*/
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public class ApprovalCallbackContext implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/** 回调动作类型 */
|
||||
public enum Action {
|
||||
/** 单个审批节点通过(中间态,每个节点都会触发一次) */
|
||||
NODE_APPROVED,
|
||||
/** 整个流程最终通过 */
|
||||
APPROVED,
|
||||
/** 被驳回(任一节点驳回即终止) */
|
||||
REJECTED
|
||||
}
|
||||
|
||||
/** 回调动作 */
|
||||
private Action action;
|
||||
|
||||
/** 审批实例ID */
|
||||
private String instanceId;
|
||||
|
||||
/** 审批流定义ID */
|
||||
private String flowId;
|
||||
|
||||
/** 审批流名称 */
|
||||
private String flowName;
|
||||
|
||||
/** 业务单据表名 */
|
||||
private String bizTable;
|
||||
|
||||
/** 业务单据中文名 */
|
||||
private String bizTableName;
|
||||
|
||||
/** 业务单据记录ID(业务表主键) */
|
||||
private String bizDataId;
|
||||
|
||||
/** 业务单据展示标题 */
|
||||
private String bizTitle;
|
||||
|
||||
/** 当前/刚处理的节点ID */
|
||||
private String nodeId;
|
||||
|
||||
/** 当前/刚处理的节点名称 */
|
||||
private String nodeName;
|
||||
|
||||
/** 操作人 username(系统自动处理时为 null/system) */
|
||||
private String operatorUsername;
|
||||
|
||||
/** 操作人姓名 */
|
||||
private String operatorName;
|
||||
|
||||
/** 审批意见 / 驳回理由 */
|
||||
private String comment;
|
||||
|
||||
/** 发起人 username */
|
||||
private String applyUser;
|
||||
|
||||
/** 是否为流程最终结束(APPROVED/REJECTED 时为 true) */
|
||||
private boolean finalResult;
|
||||
|
||||
/** 完整审批实例(供业务读取租户、发起信息等) */
|
||||
private transient MesXslApprovalInstance instance;
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
package org.jeecg.modules.xslmes.approval.callback;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.jeecg.common.util.oConvertUtils;
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 审批业务回调分发器。
|
||||
* 统一在审批引擎流转关键点调用:
|
||||
* <ol>
|
||||
* <li>按业务表名路由到对应的 {@link IApprovalBizCallback} 实现(强类型,业务直接调用自身接口);</li>
|
||||
* <li>同时发布 {@link ApprovalActionEvent} 供 {@code @EventListener} 松耦合监听。</li>
|
||||
* </ol>
|
||||
* 回调与审批状态变更同事务执行,回调异常将向上抛出以回滚整个审批动作。
|
||||
*
|
||||
* @author GHT
|
||||
* @date 2026-05-29 for:【QH-MES审批流设计】审批与业务单据联动回调
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class ApprovalCallbackDispatcher {
|
||||
|
||||
/** 监听所有业务表的通配符 */
|
||||
private static final String ANY_TABLE = "*";
|
||||
|
||||
private final ObjectProvider<List<IApprovalBizCallback>> callbacksProvider;
|
||||
private final ApplicationEventPublisher eventPublisher;
|
||||
|
||||
public ApprovalCallbackDispatcher(ObjectProvider<List<IApprovalBizCallback>> callbacksProvider,
|
||||
ApplicationEventPublisher eventPublisher) {
|
||||
this.callbacksProvider = callbacksProvider;
|
||||
this.eventPublisher = eventPublisher;
|
||||
}
|
||||
|
||||
/** 节点通过(中间态) */
|
||||
public void fireNodeApproved(ApprovalCallbackContext ctx) {
|
||||
ctx.setAction(ApprovalCallbackContext.Action.NODE_APPROVED);
|
||||
ctx.setFinalResult(false);
|
||||
dispatch(ctx);
|
||||
}
|
||||
|
||||
/** 流程最终通过 */
|
||||
public void fireApproved(ApprovalCallbackContext ctx) {
|
||||
ctx.setAction(ApprovalCallbackContext.Action.APPROVED);
|
||||
ctx.setFinalResult(true);
|
||||
dispatch(ctx);
|
||||
}
|
||||
|
||||
/** 驳回 */
|
||||
public void fireRejected(ApprovalCallbackContext ctx) {
|
||||
ctx.setAction(ApprovalCallbackContext.Action.REJECTED);
|
||||
ctx.setFinalResult(true);
|
||||
dispatch(ctx);
|
||||
}
|
||||
|
||||
private void dispatch(ApprovalCallbackContext ctx) {
|
||||
if (ctx == null || oConvertUtils.isEmpty(ctx.getBizTable())) {
|
||||
return;
|
||||
}
|
||||
// 1) 强类型回调:按表路由 + 通配
|
||||
for (IApprovalBizCallback cb : matchedCallbacks(ctx.getBizTable())) {
|
||||
invoke(cb, ctx);
|
||||
}
|
||||
// 2) 领域事件:松耦合监听(同步、同事务)
|
||||
try {
|
||||
eventPublisher.publishEvent(new ApprovalActionEvent(this, ctx));
|
||||
} catch (RuntimeException e) {
|
||||
log.error("审批领域事件处理失败 table={}, bizId={}, action={}", ctx.getBizTable(), ctx.getBizDataId(), ctx.getAction(), e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
private List<IApprovalBizCallback> matchedCallbacks(String bizTable) {
|
||||
List<IApprovalBizCallback> all = callbacksProvider.getIfAvailable();
|
||||
List<IApprovalBizCallback> matched = new ArrayList<>();
|
||||
if (all == null) {
|
||||
return matched;
|
||||
}
|
||||
for (IApprovalBizCallback cb : all) {
|
||||
String support = cb.supportTable();
|
||||
if (ANY_TABLE.equals(support) || (support != null && support.equalsIgnoreCase(bizTable))) {
|
||||
matched.add(cb);
|
||||
}
|
||||
}
|
||||
return matched;
|
||||
}
|
||||
|
||||
private void invoke(IApprovalBizCallback cb, ApprovalCallbackContext ctx) {
|
||||
try {
|
||||
switch (ctx.getAction()) {
|
||||
case NODE_APPROVED:
|
||||
cb.onNodeApproved(ctx);
|
||||
break;
|
||||
case APPROVED:
|
||||
cb.onApproved(ctx);
|
||||
break;
|
||||
case REJECTED:
|
||||
cb.onRejected(ctx);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} catch (RuntimeException e) {
|
||||
log.error("审批业务回调执行失败 callback={}, table={}, bizId={}, action={}",
|
||||
cb.getClass().getSimpleName(), ctx.getBizTable(), ctx.getBizDataId(), ctx.getAction(), e);
|
||||
// 抛出以回滚整个审批动作,保证审批与业务数据一致
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package org.jeecg.modules.xslmes.approval.callback;
|
||||
|
||||
/**
|
||||
* 业务审批回调扩展点(SPI)。
|
||||
*
|
||||
* 各业务模块按需实现本接口并声明 {@link #supportTable()}(绑定的业务表名),
|
||||
* 审批引擎会在审批流转的关键节点自动回调对应实现,业务模块在回调里
|
||||
* 调用自己「已有的」审核/回写接口(如更新审核状态、扣减库存、生成下游单据等),
|
||||
* 从而把审批流程与业务单据的功能统一串联起来。
|
||||
*
|
||||
* <p>事务说明:回调与审批状态变更处于同一事务内,
|
||||
* 若回调抛出异常,整个审批动作回滚(审批失败),保证审批与业务数据一致。</p>
|
||||
*
|
||||
* <p>使用方式:实现类标注为 Spring Bean(@Component / @Service)即可被自动收集。</p>
|
||||
*
|
||||
* <pre>{@code
|
||||
* @Component
|
||||
* public class RubberStdApprovalCallback implements IApprovalBizCallback {
|
||||
* @Override public String supportTable() { return "mes_xsl_rubber_quick_test_std"; }
|
||||
* @Override public void onApproved(ApprovalCallbackContext ctx) {
|
||||
* // 调用业务自身已有接口完成回写
|
||||
* stdService.lambdaUpdate()
|
||||
* .eq(MesXslRubberQuickTestStd::getId, ctx.getBizDataId())
|
||||
* .set(MesXslRubberQuickTestStd::getAuditStatus, "1").update();
|
||||
* }
|
||||
* }
|
||||
* }</pre>
|
||||
*
|
||||
* @author GHT
|
||||
* @date 2026-05-29 for:【QH-MES审批流设计】审批与业务单据联动回调
|
||||
*/
|
||||
public interface IApprovalBizCallback {
|
||||
|
||||
/**
|
||||
* 绑定的业务表名(与审批流定义 bizTable 一致)。
|
||||
* 返回 "*" 表示监听所有业务表。
|
||||
*/
|
||||
String supportTable();
|
||||
|
||||
/**
|
||||
* 单个审批节点通过(中间态)。每经过一个审批节点通过都会触发一次。
|
||||
* 适合更新中间状态(如「审核中」「已校对」等)。
|
||||
* 默认空实现,业务按需重写。
|
||||
*/
|
||||
default void onNodeApproved(ApprovalCallbackContext ctx) {
|
||||
// 默认不处理
|
||||
}
|
||||
|
||||
/**
|
||||
* 整个审批流程最终通过。适合执行终态业务(如置为「已批准」、生效、扣库存等)。
|
||||
* 默认空实现,业务按需重写。
|
||||
*/
|
||||
default void onApproved(ApprovalCallbackContext ctx) {
|
||||
// 默认不处理
|
||||
}
|
||||
|
||||
/**
|
||||
* 审批被驳回(流程终止)。适合回退业务状态(如置回「草稿」、释放占用等)。
|
||||
* 默认空实现,业务按需重写。
|
||||
*/
|
||||
default void onRejected(ApprovalCallbackContext ctx) {
|
||||
// 默认不处理
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package org.jeecg.modules.xslmes.approval.callback.impl;
|
||||
|
||||
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.callback.IApprovalBizCallback;
|
||||
import org.jeecg.modules.xslmes.common.XslMesBizConstants;
|
||||
import org.jeecg.modules.xslmes.entity.MesXslRubberQuickTestStd;
|
||||
import org.jeecg.modules.xslmes.service.IMesXslRubberQuickTestStdService;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 胶料快检实验标准 审批回调示例。
|
||||
* 演示如何把审批流转结果联动到业务单据已有功能:
|
||||
* 审批最终通过 -> 审核状态置「已批准」;驳回 -> 回退「草稿」。
|
||||
*
|
||||
* 业务在回调里直接调用自身的 service(此处用 lambdaUpdate 更新审核状态字段),
|
||||
* 与原有「批准/反审核」逻辑保持统一。
|
||||
*
|
||||
* @author GHT
|
||||
* @date 2026-05-29 for:【QH-MES审批流设计】审批与业务单据联动回调-示例
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class RubberQuickTestStdApprovalCallback implements IApprovalBizCallback {
|
||||
|
||||
private final IMesXslRubberQuickTestStdService stdService;
|
||||
|
||||
public RubberQuickTestStdApprovalCallback(IMesXslRubberQuickTestStdService stdService) {
|
||||
this.stdService = stdService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String supportTable() {
|
||||
return "mes_xsl_rubber_quick_test_std";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onApproved(ApprovalCallbackContext ctx) {
|
||||
updateAuditStatus(ctx.getBizDataId(), XslMesBizConstants.RUBBER_QUICK_TEST_STD_AUDIT_APPROVED);
|
||||
log.info("[审批联动] 实验标准 {} 审批通过,审核状态置为已批准", ctx.getBizDataId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRejected(ApprovalCallbackContext ctx) {
|
||||
updateAuditStatus(ctx.getBizDataId(), XslMesBizConstants.RUBBER_QUICK_TEST_STD_AUDIT_DRAFT);
|
||||
log.info("[审批联动] 实验标准 {} 被驳回,审核状态回退为草稿", ctx.getBizDataId());
|
||||
}
|
||||
|
||||
private void updateAuditStatus(String bizDataId, String auditStatus) {
|
||||
if (oConvertUtils.isEmpty(bizDataId)) {
|
||||
return;
|
||||
}
|
||||
stdService.lambdaUpdate()
|
||||
.eq(MesXslRubberQuickTestStd::getId, bizDataId)
|
||||
.set(MesXslRubberQuickTestStd::getAuditStatus, auditStatus)
|
||||
.update();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,433 @@
|
||||
package org.jeecg.modules.xslmes.approval.controller;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.shiro.authz.annotation.RequiresPermissions;
|
||||
import org.jeecg.common.api.vo.Result;
|
||||
import org.jeecg.common.aspect.annotation.AutoLog;
|
||||
import org.jeecg.common.system.api.ISysBaseAPI;
|
||||
import org.jeecg.common.system.base.controller.JeecgController;
|
||||
import org.jeecg.common.system.query.QueryGenerator;
|
||||
import org.jeecg.common.system.vo.DictModel;
|
||||
import org.jeecg.common.util.oConvertUtils;
|
||||
import org.jeecg.modules.xslmes.approval.entity.MesXslApprovalFlow;
|
||||
import org.jeecg.modules.xslmes.approval.service.IMesXslApprovalFlowService;
|
||||
import org.jeecg.modules.xslmes.common.MesXslTenantUtils;
|
||||
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.Arrays;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* MES 审批流设计
|
||||
*
|
||||
* @author GHT
|
||||
* @date 2026-05-29 for:【QH-MES审批流设计】新增审批流可视化设计
|
||||
*/
|
||||
@Tag(name = "MES审批流设计")
|
||||
@RestController
|
||||
@RequestMapping("/xslmes/approvalFlow")
|
||||
@Slf4j
|
||||
public class MesXslApprovalFlowController extends JeecgController<MesXslApprovalFlow, IMesXslApprovalFlowService> {
|
||||
|
||||
@Autowired
|
||||
private IMesXslApprovalFlowService mesXslApprovalFlowService;
|
||||
|
||||
@Autowired
|
||||
private ISysBaseAPI sysBaseAPI;
|
||||
|
||||
//update-begin---author:GHT ---date:2026-05-29 for:【QH-MES审批流设计】当前页字段解析-----
|
||||
@Autowired
|
||||
private JdbcTemplate jdbcTemplate;
|
||||
|
||||
/** 合法标识符(表名/字段名)白名单校验,防 SQL 注入 */
|
||||
private static final Pattern IDENTIFIER = Pattern.compile("^[A-Za-z0-9_]+$");
|
||||
|
||||
/**
|
||||
* 审批阶段关键字配置(有序):key=阶段标识,name=阶段中文,nodeType=对应节点类型,keywords=列注释匹配关键字。
|
||||
* 解析顺序即默认流程顺序:校对 -> 审核 -> 审批 -> 分发 -> 抄送。
|
||||
*/
|
||||
private static final String[][] STAGE_DEFS = new String[][]{
|
||||
{"proofread", "校对", "approver", "校对"},
|
||||
{"review", "审核", "approver", "审核|审查"},
|
||||
{"approve", "审批", "approver", "审批|批准|核准"},
|
||||
{"distribute", "分发", "approver", "分发|发放"},
|
||||
{"cc", "抄送", "cc", "抄送"},
|
||||
};
|
||||
//update-end---author:GHT ---date:2026-05-29 for:【QH-MES审批流设计】当前页字段解析-----
|
||||
|
||||
/**
|
||||
* 根据所选单据表名翻译字典,回填单据中文名
|
||||
*/
|
||||
private void fillBizTableName(MesXslApprovalFlow flow) {
|
||||
if (oConvertUtils.isEmpty(flow.getBizTable())) {
|
||||
return;
|
||||
}
|
||||
List<DictModel> items = sysBaseAPI.getDictItems("mes_xsl_approval_biz_doc");
|
||||
if (items != null) {
|
||||
for (DictModel item : items) {
|
||||
if (flow.getBizTable().equals(item.getValue())) {
|
||||
flow.setBizTableName(item.getText());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Operation(summary = "审批流设计-分页列表查询")
|
||||
@RequiresPermissions("approval:flow:list")
|
||||
@GetMapping(value = "/list")
|
||||
public Result<IPage<MesXslApprovalFlow>> queryPageList(
|
||||
MesXslApprovalFlow mesXslApprovalFlow,
|
||||
@RequestParam(name = "pageNo", defaultValue = "1") Integer pageNo,
|
||||
@RequestParam(name = "pageSize", defaultValue = "10") Integer pageSize,
|
||||
HttpServletRequest req) {
|
||||
QueryWrapper<MesXslApprovalFlow> queryWrapper = QueryGenerator.initQueryWrapper(mesXslApprovalFlow, req.getParameterMap());
|
||||
// 列表查询不返回大字段 flowConfig,避免传输冗余
|
||||
queryWrapper.select(MesXslApprovalFlow.class, info -> !"flow_config".equals(info.getColumn()));
|
||||
queryWrapper.orderByDesc("create_time");
|
||||
Page<MesXslApprovalFlow> page = new Page<>(pageNo, pageSize);
|
||||
IPage<MesXslApprovalFlow> pageList = mesXslApprovalFlowService.page(page, queryWrapper);
|
||||
return Result.OK(pageList);
|
||||
}
|
||||
|
||||
@AutoLog(value = "审批流设计-添加")
|
||||
@Operation(summary = "审批流设计-添加")
|
||||
@RequiresPermissions("approval:flow:add")
|
||||
@PostMapping(value = "/add")
|
||||
public Result<String> add(@RequestBody MesXslApprovalFlow mesXslApprovalFlow) {
|
||||
if (oConvertUtils.isEmpty(mesXslApprovalFlow.getFlowName())) {
|
||||
return Result.error("审批流名称不能为空");
|
||||
}
|
||||
if (oConvertUtils.isEmpty(mesXslApprovalFlow.getBizTable())) {
|
||||
return Result.error("请先选择绑定单据");
|
||||
}
|
||||
if (oConvertUtils.isEmpty(mesXslApprovalFlow.getStatus())) {
|
||||
mesXslApprovalFlow.setStatus("0");
|
||||
}
|
||||
fillBizTableName(mesXslApprovalFlow);
|
||||
mesXslApprovalFlowService.save(mesXslApprovalFlow);
|
||||
return Result.OK("添加成功!");
|
||||
}
|
||||
|
||||
@AutoLog(value = "审批流设计-编辑")
|
||||
@Operation(summary = "审批流设计-编辑")
|
||||
@RequiresPermissions("approval:flow:edit")
|
||||
@RequestMapping(value = "/edit", method = {RequestMethod.PUT, RequestMethod.POST})
|
||||
public Result<String> edit(@RequestBody MesXslApprovalFlow mesXslApprovalFlow) {
|
||||
if (oConvertUtils.isEmpty(mesXslApprovalFlow.getId())) {
|
||||
return Result.error("主键不能为空");
|
||||
}
|
||||
fillBizTableName(mesXslApprovalFlow);
|
||||
mesXslApprovalFlowService.updateById(mesXslApprovalFlow);
|
||||
return Result.OK("编辑成功!");
|
||||
}
|
||||
|
||||
@AutoLog(value = "审批流设计-保存流程设计")
|
||||
@Operation(summary = "审批流设计-保存流程设计")
|
||||
@RequiresPermissions("approval:flow:design")
|
||||
@PostMapping(value = "/saveDesign")
|
||||
public Result<String> saveDesign(@RequestBody MesXslApprovalFlow mesXslApprovalFlow) {
|
||||
if (oConvertUtils.isEmpty(mesXslApprovalFlow.getId())) {
|
||||
return Result.error("主键不能为空");
|
||||
}
|
||||
MesXslApprovalFlow update = new MesXslApprovalFlow();
|
||||
update.setId(mesXslApprovalFlow.getId());
|
||||
update.setFlowConfig(mesXslApprovalFlow.getFlowConfig());
|
||||
// 设计保存时若仍为草稿则置为已发布
|
||||
if (oConvertUtils.isNotEmpty(mesXslApprovalFlow.getStatus())) {
|
||||
update.setStatus(mesXslApprovalFlow.getStatus());
|
||||
}
|
||||
mesXslApprovalFlowService.updateById(update);
|
||||
return Result.OK("流程设计已保存!");
|
||||
}
|
||||
|
||||
@AutoLog(value = "审批流设计-发布/停用")
|
||||
@Operation(summary = "审批流设计-发布/停用")
|
||||
@RequiresPermissions("approval:flow:design")
|
||||
@PostMapping(value = "/updateStatus")
|
||||
public Result<String> updateStatus(
|
||||
@RequestParam(name = "id") String id,
|
||||
@RequestParam(name = "status") String status) {
|
||||
if (!"0".equals(status) && !"1".equals(status) && !"2".equals(status)) {
|
||||
return Result.error("状态参数非法");
|
||||
}
|
||||
boolean ok = mesXslApprovalFlowService.lambdaUpdate()
|
||||
.eq(MesXslApprovalFlow::getId, id)
|
||||
.set(MesXslApprovalFlow::getStatus, status)
|
||||
.update();
|
||||
return ok ? Result.OK("操作成功") : Result.error("操作失败");
|
||||
}
|
||||
|
||||
@AutoLog(value = "审批流设计-删除")
|
||||
@Operation(summary = "审批流设计-删除")
|
||||
@RequiresPermissions("approval:flow:delete")
|
||||
@DeleteMapping(value = "/delete")
|
||||
public Result<String> delete(@RequestParam(name = "id") String id) {
|
||||
mesXslApprovalFlowService.removeById(id);
|
||||
return Result.OK("删除成功!");
|
||||
}
|
||||
|
||||
@AutoLog(value = "审批流设计-批量删除")
|
||||
@Operation(summary = "审批流设计-批量删除")
|
||||
@RequiresPermissions("approval:flow:delete")
|
||||
@DeleteMapping(value = "/deleteBatch")
|
||||
public Result<String> deleteBatch(@RequestParam(name = "ids") String ids) {
|
||||
mesXslApprovalFlowService.removeByIds(Arrays.asList(ids.split(",")));
|
||||
return Result.OK("批量删除成功!");
|
||||
}
|
||||
|
||||
@Operation(summary = "审批流设计-通过id查询")
|
||||
@GetMapping(value = "/queryById")
|
||||
public Result<MesXslApprovalFlow> queryById(@RequestParam(name = "id") String id) {
|
||||
MesXslApprovalFlow entity = mesXslApprovalFlowService.getById(id);
|
||||
if (entity == null) {
|
||||
return Result.error("未找到对应数据");
|
||||
}
|
||||
return Result.OK(entity);
|
||||
}
|
||||
|
||||
//update-begin---author:GHT ---date:2026-05-29 for:【QH-MES审批流设计】全局审批流程设计悬浮按钮-当前页字段解析-----
|
||||
/**
|
||||
* 设计上下文:供全局"审批流程设计"悬浮按钮调用。
|
||||
* 1) 根据当前功能页路由反查绑定的业务表;
|
||||
* 2) 解析该表的字段,识别"校对/审核/审批/分发/抄送"等阶段字段(不存在不报错,存在即解析);
|
||||
* 3) 取/建该业务表的草稿审批流,返回流程记录(含id)供直接进入设计器。
|
||||
*
|
||||
* @param routePath 当前功能页前端路由(如 /xslmes/mesXslFormulaSpec/MesXslFormulaSpecList)
|
||||
*/
|
||||
@Operation(summary = "审批流设计-当前页设计上下文")
|
||||
@RequiresPermissions("approval:flow:design")
|
||||
@GetMapping(value = "/designContext")
|
||||
public Result<Map<String, Object>> designContext(@RequestParam(name = "routePath") String routePath) {
|
||||
Map<String, Object> data = new LinkedHashMap<>();
|
||||
data.put("routePath", routePath);
|
||||
data.put("bizTable", null);
|
||||
data.put("bizTableName", null);
|
||||
data.put("stages", new ArrayList<>());
|
||||
data.put("flow", null);
|
||||
|
||||
// 1) 路由 -> 业务表名(无法反查时不报错,返回空上下文,前端提示)
|
||||
String table = resolveTableByRoutePath(routePath);
|
||||
if (oConvertUtils.isEmpty(table) || !tableExists(table)) {
|
||||
return Result.OK(data);
|
||||
}
|
||||
data.put("bizTable", table);
|
||||
|
||||
// 2) 业务表中文名:优先字典,其次表注释
|
||||
String bizTableName = resolveBizTableName(table);
|
||||
data.put("bizTableName", bizTableName);
|
||||
|
||||
// 3) 解析阶段字段
|
||||
data.put("stages", parseStageFields(table));
|
||||
|
||||
// 4) 取/建草稿审批流
|
||||
Integer tenantId = MesXslTenantUtils.resolveTenantId(null);
|
||||
MesXslApprovalFlow flow = findFlowByTable(table, tenantId);
|
||||
if (flow == null) {
|
||||
flow = new MesXslApprovalFlow();
|
||||
flow.setFlowName((oConvertUtils.isNotEmpty(bizTableName) ? bizTableName : table) + "审批流");
|
||||
flow.setBizTable(table);
|
||||
flow.setBizTableName(bizTableName);
|
||||
flow.setRoutePath(routePath);
|
||||
flow.setStatus("0");
|
||||
if (tenantId != null) {
|
||||
flow.setTenantId(tenantId);
|
||||
}
|
||||
mesXslApprovalFlowService.save(flow);
|
||||
} else if (oConvertUtils.isEmpty(flow.getRoutePath())) {
|
||||
// 历史数据未记录路由,回填一次便于后续发起按钮匹配
|
||||
mesXslApprovalFlowService.lambdaUpdate()
|
||||
.eq(MesXslApprovalFlow::getId, flow.getId())
|
||||
.set(MesXslApprovalFlow::getRoutePath, routePath)
|
||||
.update();
|
||||
flow.setRoutePath(routePath);
|
||||
}
|
||||
data.put("flow", flow);
|
||||
return Result.OK(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据前端路由反查业务表名。
|
||||
* 约定:jeecg 代码生成的列表组件名为 表名驼峰 + List,sys_permission.component 形如
|
||||
* xslmes/mesXslFormulaSpec/MesXslFormulaSpecList,url 即路由。
|
||||
* 反查:url=routePath -> component -> 末段组件名去掉 List -> 驼峰转下划线得到表名。
|
||||
*/
|
||||
private String resolveTableByRoutePath(String routePath) {
|
||||
if (oConvertUtils.isEmpty(routePath)) {
|
||||
return null;
|
||||
}
|
||||
String path = routePath.trim().replaceAll("/+$", "");
|
||||
String sql = "SELECT component FROM sys_permission WHERE menu_type IN (0,1) "
|
||||
+ "AND (del_flag = 0 OR del_flag IS NULL) AND url = ? "
|
||||
+ "ORDER BY menu_type DESC LIMIT 1";
|
||||
String component;
|
||||
try {
|
||||
List<String> list = jdbcTemplate.queryForList(sql, String.class, path);
|
||||
if (list.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
component = list.get(0);
|
||||
} catch (Exception e) {
|
||||
log.warn("反查菜单组件失败 routePath={}", routePath, e);
|
||||
return null;
|
||||
}
|
||||
if (oConvertUtils.isEmpty(component)) {
|
||||
return null;
|
||||
}
|
||||
// 取组件路径末段:xslmes/mesXslFormulaSpec/MesXslFormulaSpecList -> MesXslFormulaSpecList
|
||||
String comp = component.contains("/") ? component.substring(component.lastIndexOf('/') + 1) : component;
|
||||
if (comp.endsWith("List")) {
|
||||
comp = comp.substring(0, comp.length() - "List".length());
|
||||
}
|
||||
return camelToUnderline(comp);
|
||||
}
|
||||
|
||||
/** 驼峰转下划线小写:MesXslFormulaSpec -> mes_xsl_formula_spec */
|
||||
private String camelToUnderline(String camel) {
|
||||
if (oConvertUtils.isEmpty(camel)) {
|
||||
return null;
|
||||
}
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < camel.length(); i++) {
|
||||
char c = camel.charAt(i);
|
||||
if (Character.isUpperCase(c)) {
|
||||
if (i > 0) {
|
||||
sb.append('_');
|
||||
}
|
||||
sb.append(Character.toLowerCase(c));
|
||||
} else {
|
||||
sb.append(c);
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/** 校验表是否存在于当前库 */
|
||||
private boolean tableExists(String table) {
|
||||
if (!IDENTIFIER.matcher(table).matches()) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
Integer cnt = jdbcTemplate.queryForObject(
|
||||
"SELECT COUNT(1) FROM information_schema.tables WHERE table_schema = (SELECT DATABASE()) AND table_name = ?",
|
||||
Integer.class, table);
|
||||
return cnt != null && cnt > 0;
|
||||
} catch (Exception e) {
|
||||
log.warn("校验表存在失败 table={}", table, e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/** 业务表中文名:优先字典 mes_xsl_approval_biz_doc,其次表注释 */
|
||||
private String resolveBizTableName(String table) {
|
||||
List<DictModel> items = sysBaseAPI.getDictItems("mes_xsl_approval_biz_doc");
|
||||
if (items != null) {
|
||||
for (DictModel item : items) {
|
||||
if (table.equals(item.getValue())) {
|
||||
return item.getText();
|
||||
}
|
||||
}
|
||||
}
|
||||
try {
|
||||
List<String> comments = jdbcTemplate.queryForList(
|
||||
"SELECT table_comment FROM information_schema.tables WHERE table_schema = (SELECT DATABASE()) AND table_name = ?",
|
||||
String.class, table);
|
||||
if (!comments.isEmpty() && oConvertUtils.isNotEmpty(comments.get(0))) {
|
||||
return comments.get(0);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("查询表注释失败 table={}", table, e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析表字段,识别审批阶段字段。每个阶段最多取一个字段(优先列注释含"人/员"的人员字段)。
|
||||
* 返回有序列表:[{stageKey, stageName, nodeType, field, fieldComment}]
|
||||
*/
|
||||
private List<Map<String, Object>> parseStageFields(String table) {
|
||||
List<Map<String, Object>> stages = new ArrayList<>();
|
||||
List<Map<String, Object>> columns;
|
||||
try {
|
||||
columns = jdbcTemplate.queryForList(
|
||||
"SELECT column_name AS name, column_comment AS comment FROM information_schema.columns "
|
||||
+ "WHERE table_schema = (SELECT DATABASE()) AND table_name = ? ORDER BY ordinal_position",
|
||||
table);
|
||||
} catch (Exception e) {
|
||||
log.warn("查询表字段失败 table={}", table, e);
|
||||
return stages;
|
||||
}
|
||||
for (String[] def : STAGE_DEFS) {
|
||||
String stageKey = def[0];
|
||||
String stageName = def[1];
|
||||
String nodeType = def[2];
|
||||
String[] keywords = def[3].split("\\|");
|
||||
Map<String, Object> hit = matchStageColumn(columns, keywords);
|
||||
if (hit != null) {
|
||||
Map<String, Object> stage = new LinkedHashMap<>();
|
||||
stage.put("stageKey", stageKey);
|
||||
stage.put("stageName", stageName);
|
||||
stage.put("nodeType", nodeType);
|
||||
stage.put("field", hit.get("name"));
|
||||
stage.put("fieldComment", hit.get("comment"));
|
||||
stages.add(stage);
|
||||
}
|
||||
}
|
||||
return stages;
|
||||
}
|
||||
|
||||
/** 在列集合中按关键字匹配阶段字段,优先返回注释含"人/员"的人员字段 */
|
||||
private Map<String, Object> matchStageColumn(List<Map<String, Object>> columns, String[] keywords) {
|
||||
Map<String, Object> firstMatch = null;
|
||||
for (Map<String, Object> col : columns) {
|
||||
String comment = col.get("comment") == null ? "" : String.valueOf(col.get("comment"));
|
||||
if (oConvertUtils.isEmpty(comment)) {
|
||||
continue;
|
||||
}
|
||||
boolean matched = false;
|
||||
for (String kw : keywords) {
|
||||
if (comment.contains(kw)) {
|
||||
matched = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!matched) {
|
||||
continue;
|
||||
}
|
||||
if (firstMatch == null) {
|
||||
firstMatch = col;
|
||||
}
|
||||
// 人员字段优先(如"校对人""审核员")
|
||||
if (comment.contains("人") || comment.contains("员")) {
|
||||
return col;
|
||||
}
|
||||
}
|
||||
return firstMatch;
|
||||
}
|
||||
|
||||
/** 按业务表+租户查找审批流(取最近一条) */
|
||||
private MesXslApprovalFlow findFlowByTable(String table, Integer tenantId) {
|
||||
QueryWrapper<MesXslApprovalFlow> qw = new QueryWrapper<>();
|
||||
qw.eq("biz_table", table);
|
||||
if (tenantId != null) {
|
||||
qw.eq("tenant_id", tenantId);
|
||||
}
|
||||
qw.orderByDesc("create_time");
|
||||
qw.last("LIMIT 1");
|
||||
return mesXslApprovalFlowService.getOne(qw, false);
|
||||
}
|
||||
//update-end---author:GHT ---date:2026-05-29 for:【QH-MES审批流设计】全局审批流程设计悬浮按钮-当前页字段解析-----
|
||||
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
package org.jeecg.modules.xslmes.approval.controller;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.shiro.SecurityUtils;
|
||||
import org.jeecg.common.api.vo.Result;
|
||||
import org.jeecg.common.system.vo.LoginUser;
|
||||
import org.jeecg.common.util.oConvertUtils;
|
||||
import org.jeecg.modules.xslmes.approval.service.IMesXslApprovalHandleService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* MES 审批办理(运行时-流转)。
|
||||
* 供 IM 审批卡片按钮调用:查看详情 / 审批通过 / 驳回。
|
||||
*
|
||||
* @author GHT
|
||||
* @date 2026-05-29 for:【QH-MES审批流设计】审批办理/流转
|
||||
*/
|
||||
@Tag(name = "MES审批办理")
|
||||
@RestController
|
||||
@RequestMapping("/xslmes/approvalHandle")
|
||||
@Slf4j
|
||||
public class MesXslApprovalHandleController {
|
||||
|
||||
@Autowired
|
||||
private IMesXslApprovalHandleService approvalHandleService;
|
||||
|
||||
@Operation(summary = "审批办理-单据详情")
|
||||
@GetMapping("/detail")
|
||||
public Result<Map<String, Object>> detail(@RequestParam("instanceId") String instanceId) {
|
||||
LoginUser user = (LoginUser) SecurityUtils.getSubject().getPrincipal();
|
||||
Map<String, Object> data = approvalHandleService.detail(instanceId, user);
|
||||
if (data == null || data.isEmpty()) {
|
||||
return Result.error("审批实例不存在");
|
||||
}
|
||||
return Result.OK(data);
|
||||
}
|
||||
|
||||
@Operation(summary = "审批办理-实时状态(卡片置灰判断)")
|
||||
@GetMapping("/status")
|
||||
public Result<Map<String, Object>> status(@RequestParam("instanceId") String instanceId) {
|
||||
LoginUser user = (LoginUser) SecurityUtils.getSubject().getPrincipal();
|
||||
Map<String, Object> data = approvalHandleService.statusInfo(instanceId, user);
|
||||
return Result.OK(data);
|
||||
}
|
||||
|
||||
@Operation(summary = "审批办理-通过")
|
||||
@PostMapping("/approve")
|
||||
public Result<String> approve(@RequestBody Map<String, Object> body) {
|
||||
String instanceId = (String) body.get("instanceId");
|
||||
String comment = body.get("comment") == null ? null : String.valueOf(body.get("comment"));
|
||||
if (oConvertUtils.isEmpty(instanceId)) {
|
||||
return Result.error("缺少审批实例ID");
|
||||
}
|
||||
LoginUser user = (LoginUser) SecurityUtils.getSubject().getPrincipal();
|
||||
return approvalHandleService.approve(instanceId, comment, user);
|
||||
}
|
||||
|
||||
@Operation(summary = "审批办理-驳回")
|
||||
@PostMapping("/reject")
|
||||
public Result<String> reject(@RequestBody Map<String, Object> body) {
|
||||
String instanceId = (String) body.get("instanceId");
|
||||
String reason = body.get("reason") == null ? null : String.valueOf(body.get("reason"));
|
||||
if (oConvertUtils.isEmpty(instanceId)) {
|
||||
return Result.error("缺少审批实例ID");
|
||||
}
|
||||
LoginUser user = (LoginUser) SecurityUtils.getSubject().getPrincipal();
|
||||
return approvalHandleService.reject(instanceId, reason, user);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,242 @@
|
||||
package org.jeecg.modules.xslmes.approval.controller;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.shiro.SecurityUtils;
|
||||
import org.jeecg.common.api.vo.Result;
|
||||
import org.jeecg.common.system.vo.LoginUser;
|
||||
import org.jeecg.common.util.oConvertUtils;
|
||||
import org.jeecg.modules.xslmes.approval.entity.MesXslApprovalFlow;
|
||||
import org.jeecg.modules.xslmes.approval.entity.MesXslApprovalInstance;
|
||||
import org.jeecg.modules.xslmes.approval.service.IMesXslApprovalFlowService;
|
||||
import org.jeecg.modules.xslmes.approval.service.IMesXslApprovalHandleService;
|
||||
import org.jeecg.modules.xslmes.approval.service.IMesXslApprovalInstanceService;
|
||||
import org.jeecg.modules.xslmes.common.MesXslTenantUtils;
|
||||
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.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* MES 审批发起(运行时-本期仅发起)
|
||||
* 供全局"发起审批"悬浮按钮调用:选单据类型 -> 选单据记录 -> 发起。
|
||||
*
|
||||
* @author GHT
|
||||
* @date 2026-05-29 for:【QH-MES审批流设计】发起审批运行时
|
||||
*/
|
||||
@Tag(name = "MES审批发起")
|
||||
@RestController
|
||||
@RequestMapping("/xslmes/approvalLaunch")
|
||||
@Slf4j
|
||||
public class MesXslApprovalLaunchController {
|
||||
|
||||
/** 合法标识符(表名/字段名)白名单校验,防 SQL 注入 */
|
||||
private static final Pattern IDENTIFIER = Pattern.compile("^[A-Za-z0-9_]+$");
|
||||
|
||||
@Autowired
|
||||
private IMesXslApprovalFlowService approvalFlowService;
|
||||
|
||||
@Autowired
|
||||
private IMesXslApprovalInstanceService approvalInstanceService;
|
||||
|
||||
//update-begin---author:GHT ---date:2026-05-29 for:【QH-MES审批流设计】发起改用流转引擎进入首节点-----
|
||||
@Autowired
|
||||
private IMesXslApprovalHandleService approvalHandleService;
|
||||
//update-end---author:GHT ---date:2026-05-29 for:【QH-MES审批流设计】发起改用流转引擎进入首节点-----
|
||||
|
||||
@Autowired
|
||||
private JdbcTemplate jdbcTemplate;
|
||||
|
||||
/**
|
||||
* 已发布审批流列表(按租户隔离),即"可发起的单据类型"。
|
||||
* 同时按"功能模块(单据表)"自动反查其菜单路由填入 routePath,供前端控制悬浮按钮仅在该功能页显示,无需手工配置。
|
||||
*/
|
||||
@Operation(summary = "发起审批-已发布审批流列表")
|
||||
@GetMapping("/publishedList")
|
||||
public Result<List<MesXslApprovalFlow>> publishedList() {
|
||||
QueryWrapper<MesXslApprovalFlow> qw = new QueryWrapper<>();
|
||||
qw.eq("status", "1");
|
||||
Integer tenantId = MesXslTenantUtils.resolveTenantId(null);
|
||||
if (tenantId != null) {
|
||||
qw.eq("tenant_id", tenantId);
|
||||
}
|
||||
qw.orderByDesc("create_time");
|
||||
List<MesXslApprovalFlow> list = approvalFlowService.list(qw);
|
||||
// 未手工指定 route_path 时,按单据表名自动反查菜单路由
|
||||
for (MesXslApprovalFlow flow : list) {
|
||||
if (oConvertUtils.isEmpty(flow.getRoutePath())) {
|
||||
flow.setRoutePath(resolveRoutePathByTable(flow.getBizTable()));
|
||||
}
|
||||
}
|
||||
return Result.OK(list);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据单据表名反查对应功能菜单的前端路由。
|
||||
* 约定:jeecg 代码生成的列表组件名为 表名驼峰 + List(如 mes_xsl_formula_spec -> MesXslFormulaSpecList),
|
||||
* 对应 sys_permission.component 形如 xslmes/mesXslFormulaSpec/MesXslFormulaSpecList,取其 url 即路由。
|
||||
*/
|
||||
private String resolveRoutePathByTable(String table) {
|
||||
if (oConvertUtils.isEmpty(table) || !IDENTIFIER.matcher(table).matches()) {
|
||||
return null;
|
||||
}
|
||||
StringBuilder comp = new StringBuilder();
|
||||
for (String p : table.split("_")) {
|
||||
if (oConvertUtils.isEmpty(p)) {
|
||||
continue;
|
||||
}
|
||||
comp.append(Character.toUpperCase(p.charAt(0))).append(p.substring(1));
|
||||
}
|
||||
comp.append("List");
|
||||
String sql = "SELECT url FROM sys_permission WHERE menu_type IN (0,1) "
|
||||
+ "AND (del_flag = 0 OR del_flag IS NULL) "
|
||||
+ "AND (component LIKE ? OR component LIKE ?) "
|
||||
+ "ORDER BY menu_type DESC LIMIT 1";
|
||||
try {
|
||||
List<String> urls = jdbcTemplate.queryForList(sql, String.class, "%/" + comp, "%" + comp);
|
||||
return urls.isEmpty() ? null : urls.get(0);
|
||||
} catch (Exception e) {
|
||||
log.warn("反查菜单路由失败 table={}", table, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据审批流绑定的单据表,查询业务单据记录(id + 标题),供发起时选择
|
||||
*/
|
||||
@Operation(summary = "发起审批-业务单据记录列表")
|
||||
@GetMapping("/bizRecords")
|
||||
public Result<List<Map<String, Object>>> bizRecords(
|
||||
@RequestParam("flowId") String flowId,
|
||||
@RequestParam(value = "keyword", required = false) String keyword) {
|
||||
MesXslApprovalFlow flow = approvalFlowService.getById(flowId);
|
||||
if (flow == null) {
|
||||
return Result.error("审批流不存在");
|
||||
}
|
||||
String table = flow.getBizTable();
|
||||
String titleField = flow.getTitleField();
|
||||
if (oConvertUtils.isEmpty(table) || !IDENTIFIER.matcher(table).matches()) {
|
||||
return Result.error("单据表名非法");
|
||||
}
|
||||
boolean hasTitle = oConvertUtils.isNotEmpty(titleField) && IDENTIFIER.matcher(titleField).matches();
|
||||
|
||||
StringBuilder sql = new StringBuilder("SELECT id, ");
|
||||
sql.append(hasTitle ? titleField : "id").append(" AS title FROM ").append(table);
|
||||
List<Object> args = new ArrayList<>();
|
||||
if (hasTitle && oConvertUtils.isNotEmpty(keyword)) {
|
||||
sql.append(" WHERE ").append(titleField).append(" LIKE CONCAT('%', ?, '%')");
|
||||
args.add(keyword);
|
||||
}
|
||||
// 主键一定存在,按 id 倒序近似最新;限制条数防全表
|
||||
sql.append(" ORDER BY id DESC LIMIT 100");
|
||||
try {
|
||||
List<Map<String, Object>> list = jdbcTemplate.queryForList(sql.toString(), args.toArray());
|
||||
return Result.OK(list);
|
||||
} catch (Exception e) {
|
||||
log.error("查询业务单据失败 table={}, field={}", table, titleField, e);
|
||||
return Result.error("查询业务单据失败:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发起审批:根据审批流定义创建审批实例,解析首个审批节点处理人
|
||||
*/
|
||||
@Operation(summary = "发起审批-发起")
|
||||
@PostMapping("/launch")
|
||||
public Result<String> launch(@RequestBody Map<String, Object> body) {
|
||||
String flowId = (String) body.get("flowId");
|
||||
String bizDataId = (String) body.get("bizDataId");
|
||||
String bizTitle = (String) body.get("bizTitle");
|
||||
if (oConvertUtils.isEmpty(flowId) || oConvertUtils.isEmpty(bizDataId)) {
|
||||
return Result.error("请选择审批流和单据");
|
||||
}
|
||||
MesXslApprovalFlow flow = approvalFlowService.getById(flowId);
|
||||
if (flow == null) {
|
||||
return Result.error("审批流不存在");
|
||||
}
|
||||
if (!"1".equals(flow.getStatus())) {
|
||||
return Result.error("该审批流未发布,无法发起");
|
||||
}
|
||||
|
||||
LoginUser loginUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
|
||||
//update-begin---author:GHT ---date:2026-05-29 for:【QH-MES审批流设计】发起后由引擎进入首节点(解析处理人/建进度/发可办理卡片)-----
|
||||
MesXslApprovalInstance inst = buildInstance(flow, bizDataId, bizTitle, loginUser);
|
||||
approvalInstanceService.save(inst);
|
||||
approvalHandleService.enterFirstNode(inst, loginUser);
|
||||
//update-end---author:GHT ---date:2026-05-29 for:【QH-MES审批流设计】发起后由引擎进入首节点(解析处理人/建进度/发可办理卡片)-----
|
||||
return Result.OK("发起成功!");
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量发起审批:用于列表多选后一次性发起
|
||||
*/
|
||||
@Operation(summary = "发起审批-批量发起")
|
||||
@PostMapping("/launchBatch")
|
||||
public Result<String> launchBatch(@RequestBody Map<String, Object> body) {
|
||||
String flowId = (String) body.get("flowId");
|
||||
Object itemsObj = body.get("items");
|
||||
if (oConvertUtils.isEmpty(flowId) || !(itemsObj instanceof List) || ((List<?>) itemsObj).isEmpty()) {
|
||||
return Result.error("请选择审批流和单据");
|
||||
}
|
||||
MesXslApprovalFlow flow = approvalFlowService.getById(flowId);
|
||||
if (flow == null) {
|
||||
return Result.error("审批流不存在");
|
||||
}
|
||||
if (!"1".equals(flow.getStatus())) {
|
||||
return Result.error("该审批流未发布,无法发起");
|
||||
}
|
||||
|
||||
LoginUser loginUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
|
||||
//update-begin---author:GHT ---date:2026-05-29 for:【QH-MES审批流设计】批量发起逐条进入首节点(支持按单据字段解析处理人/会签)-----
|
||||
int count = 0;
|
||||
for (Object o : (List<?>) itemsObj) {
|
||||
if (!(o instanceof Map)) {
|
||||
continue;
|
||||
}
|
||||
Map<?, ?> item = (Map<?, ?>) o;
|
||||
String bizDataId = item.get("bizDataId") == null ? null : String.valueOf(item.get("bizDataId"));
|
||||
String bizTitle = item.get("bizTitle") == null ? null : String.valueOf(item.get("bizTitle"));
|
||||
if (oConvertUtils.isEmpty(bizDataId)) {
|
||||
continue;
|
||||
}
|
||||
MesXslApprovalInstance inst = buildInstance(flow, bizDataId, bizTitle, loginUser);
|
||||
approvalInstanceService.save(inst);
|
||||
approvalHandleService.enterFirstNode(inst, loginUser);
|
||||
count++;
|
||||
}
|
||||
if (count == 0) {
|
||||
return Result.error("没有有效的单据数据");
|
||||
}
|
||||
return Result.OK("已发起 " + count + " 条审批!");
|
||||
//update-end---author:GHT ---date:2026-05-29 for:【QH-MES审批流设计】批量发起逐条进入首节点(支持按单据字段解析处理人/会签)-----
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建一条审批实例(基础字段;处理人解析/卡片由流转引擎完成)
|
||||
*/
|
||||
private MesXslApprovalInstance buildInstance(MesXslApprovalFlow flow, String bizDataId, String bizTitle, LoginUser loginUser) {
|
||||
MesXslApprovalInstance inst = new MesXslApprovalInstance();
|
||||
inst.setFlowId(flow.getId());
|
||||
inst.setFlowName(flow.getFlowName());
|
||||
inst.setBizTable(flow.getBizTable());
|
||||
inst.setBizTableName(flow.getBizTableName());
|
||||
inst.setBizDataId(bizDataId);
|
||||
inst.setBizTitle(oConvertUtils.isNotEmpty(bizTitle) ? bizTitle : bizDataId);
|
||||
inst.setStatus("0");
|
||||
if (loginUser != null) {
|
||||
inst.setApplyUser(loginUser.getUsername());
|
||||
inst.setApplyUserName(loginUser.getRealname());
|
||||
}
|
||||
inst.setApplyTime(new Date());
|
||||
inst.setTenantId(MesXslTenantUtils.resolveTenantId(flow.getTenantId()));
|
||||
// 处理人解析、节点进度初始化、卡片发送统一由 IMesXslApprovalHandleService.enterFirstNode 完成
|
||||
return inst;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package org.jeecg.modules.xslmes.approval.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableLogic;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.experimental.Accessors;
|
||||
import org.jeecg.common.aspect.annotation.Dict;
|
||||
import org.jeecg.common.system.base.entity.JeecgEntity;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* MES 审批流定义
|
||||
* 钉钉式可视化审批流设计,绑定 MES 业务单据。
|
||||
*
|
||||
* @author GHT
|
||||
* @date 2026-05-29 for:【QH-MES审批流设计】新增审批流可视化设计
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
@Accessors(chain = true)
|
||||
@TableName("mes_xsl_approval_flow")
|
||||
@Schema(description = "MES审批流定义")
|
||||
public class MesXslApprovalFlow extends JeecgEntity implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description = "审批流名称")
|
||||
private String flowName;
|
||||
|
||||
@Schema(description = "绑定单据表名")
|
||||
@Dict(dicCode = "mes_xsl_approval_biz_doc")
|
||||
private String bizTable;
|
||||
|
||||
@Schema(description = "绑定单据中文名(冗余展示)")
|
||||
private String bizTableName;
|
||||
|
||||
@Schema(description = "单据标题字段名(发起选单据时展示)")
|
||||
private String titleField;
|
||||
|
||||
@Schema(description = "对应功能页面前端路由(发起审批悬浮按钮仅在该页显示)")
|
||||
private String routePath;
|
||||
|
||||
@Schema(description = "流程设计JSON(钉钉式节点树)")
|
||||
private String flowConfig;
|
||||
|
||||
@Schema(description = "状态:0草稿 1已发布 2已停用")
|
||||
@Dict(dicCode = "mes_xsl_approval_flow_status")
|
||||
private String status;
|
||||
|
||||
@Schema(description = "排序")
|
||||
private Double sortNo;
|
||||
|
||||
@Schema(description = "备注")
|
||||
private String remark;
|
||||
|
||||
@Schema(description = "逻辑删除:0正常 1已删除")
|
||||
@TableLogic
|
||||
private Integer delFlag;
|
||||
|
||||
@Schema(description = "租户ID")
|
||||
private Integer tenantId;
|
||||
|
||||
@Schema(description = "所属部门编码")
|
||||
private String sysOrgCode;
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
package org.jeecg.modules.xslmes.approval.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableLogic;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.experimental.Accessors;
|
||||
import org.jeecg.common.aspect.annotation.Dict;
|
||||
import org.jeecg.common.system.base.entity.JeecgEntity;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* MES 审批实例(本期仅记录发起,不含办理)
|
||||
*
|
||||
* @author GHT
|
||||
* @date 2026-05-29 for:【QH-MES审批流设计】发起审批运行时
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
@Accessors(chain = true)
|
||||
@TableName("mes_xsl_approval_instance")
|
||||
@Schema(description = "MES审批实例")
|
||||
public class MesXslApprovalInstance extends JeecgEntity implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description = "审批流定义ID")
|
||||
private String flowId;
|
||||
|
||||
@Schema(description = "审批流名称")
|
||||
private String flowName;
|
||||
|
||||
@Schema(description = "业务单据表名")
|
||||
private String bizTable;
|
||||
|
||||
@Schema(description = "业务单据中文名")
|
||||
private String bizTableName;
|
||||
|
||||
@Schema(description = "业务单据记录ID")
|
||||
private String bizDataId;
|
||||
|
||||
@Schema(description = "业务单据展示标题")
|
||||
private String bizTitle;
|
||||
|
||||
@Schema(description = "当前节点ID")
|
||||
private String currentNodeId;
|
||||
|
||||
@Schema(description = "当前节点名称")
|
||||
private String currentNodeName;
|
||||
|
||||
@Schema(description = "当前处理人(username逗号分隔)")
|
||||
private String currentHandlers;
|
||||
|
||||
@Schema(description = "当前处理人展示文本")
|
||||
private String currentHandlersText;
|
||||
|
||||
//update-begin---author:GHT ---date:2026-05-29 for:【QH-MES审批流设计】审批办理/流转(会签/或签/依次)进度与历史-----
|
||||
@Schema(description = "当前节点处理进度JSON(nodeId/mode/tasks)")
|
||||
private String nodeProgress;
|
||||
|
||||
@Schema(description = "审批历史JSON数组")
|
||||
private String history;
|
||||
//update-end---author:GHT ---date:2026-05-29 for:【QH-MES审批流设计】审批办理/流转(会签/或签/依次)进度与历史-----
|
||||
|
||||
@Schema(description = "状态:0审批中 1已通过 2已驳回 3已撤销")
|
||||
@Dict(dicCode = "mes_xsl_approval_instance_status")
|
||||
private String status;
|
||||
|
||||
@Schema(description = "发起人username")
|
||||
private String applyUser;
|
||||
|
||||
@Schema(description = "发起人姓名")
|
||||
private String applyUserName;
|
||||
|
||||
@Schema(description = "发起时间")
|
||||
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date applyTime;
|
||||
|
||||
@Schema(description = "备注")
|
||||
private String remark;
|
||||
|
||||
@Schema(description = "逻辑删除:0正常 1已删除")
|
||||
@TableLogic
|
||||
private Integer delFlag;
|
||||
|
||||
@Schema(description = "租户ID")
|
||||
private Integer tenantId;
|
||||
|
||||
@Schema(description = "所属部门编码")
|
||||
private String sysOrgCode;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package org.jeecg.modules.xslmes.approval.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.jeecg.modules.xslmes.approval.entity.MesXslApprovalFlow;
|
||||
|
||||
/**
|
||||
* MES 审批流定义 Mapper
|
||||
*
|
||||
* @author GHT
|
||||
* @date 2026-05-29 for:【QH-MES审批流设计】新增审批流可视化设计
|
||||
*/
|
||||
@Mapper
|
||||
public interface MesXslApprovalFlowMapper extends BaseMapper<MesXslApprovalFlow> {
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package org.jeecg.modules.xslmes.approval.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.jeecg.modules.xslmes.approval.entity.MesXslApprovalInstance;
|
||||
|
||||
/**
|
||||
* MES 审批实例 Mapper
|
||||
*
|
||||
* @author GHT
|
||||
* @date 2026-05-29 for:【QH-MES审批流设计】发起审批运行时
|
||||
*/
|
||||
@Mapper
|
||||
public interface MesXslApprovalInstanceMapper extends BaseMapper<MesXslApprovalInstance> {
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package org.jeecg.modules.xslmes.approval.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import org.jeecg.modules.xslmes.approval.entity.MesXslApprovalFlow;
|
||||
|
||||
/**
|
||||
* MES 审批流定义 Service
|
||||
*
|
||||
* @author GHT
|
||||
* @date 2026-05-29 for:【QH-MES审批流设计】新增审批流可视化设计
|
||||
*/
|
||||
public interface IMesXslApprovalFlowService extends IService<MesXslApprovalFlow> {
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package org.jeecg.modules.xslmes.approval.service;
|
||||
|
||||
import org.jeecg.common.api.vo.Result;
|
||||
import org.jeecg.common.system.vo.LoginUser;
|
||||
import org.jeecg.modules.xslmes.approval.entity.MesXslApprovalInstance;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* MES 审批办理/流转引擎。
|
||||
* 负责:发起后进入首节点、审批通过/驳回的流转推进(支持会签/或签/依次)、查看单据全字段详情。
|
||||
*
|
||||
* @author GHT
|
||||
* @date 2026-05-29 for:【QH-MES审批流设计】审批办理/流转
|
||||
*/
|
||||
public interface IMesXslApprovalHandleService {
|
||||
|
||||
/**
|
||||
* 发起后进入首个审批节点:解析处理人、初始化节点进度、发送审批卡片。
|
||||
* 实例需已先行 save(带 id)。
|
||||
*/
|
||||
void enterFirstNode(MesXslApprovalInstance inst, LoginUser applyUser);
|
||||
|
||||
/**
|
||||
* 审批通过:标记当前处理人任务完成,按节点 multiMode 判断是否流转到下一节点。
|
||||
*/
|
||||
Result<String> approve(String instanceId, String comment, LoginUser user);
|
||||
|
||||
/**
|
||||
* 驳回:任一处理人驳回即终止流程,通知发起人。
|
||||
*/
|
||||
Result<String> reject(String instanceId, String reason, LoginUser user);
|
||||
|
||||
/**
|
||||
* 查看单据全部字段 + 审批进度/历史,供 IM 卡片"查看详情"弹窗使用。
|
||||
*/
|
||||
Map<String, Object> detail(String instanceId, LoginUser user);
|
||||
|
||||
/**
|
||||
* 轻量状态查询:供 IM 卡片实时判断是否仍可办理(旧节点卡片置灰)。
|
||||
* 返回 status/statusText/currentNodeId/currentNodeName/canApprove。
|
||||
*/
|
||||
Map<String, Object> statusInfo(String instanceId, LoginUser user);
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package org.jeecg.modules.xslmes.approval.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import org.jeecg.modules.xslmes.approval.entity.MesXslApprovalInstance;
|
||||
|
||||
/**
|
||||
* MES 审批实例 Service
|
||||
*
|
||||
* @author GHT
|
||||
* @date 2026-05-29 for:【QH-MES审批流设计】发起审批运行时
|
||||
*/
|
||||
public interface IMesXslApprovalInstanceService extends IService<MesXslApprovalInstance> {
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package org.jeecg.modules.xslmes.approval.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import org.jeecg.modules.xslmes.approval.entity.MesXslApprovalFlow;
|
||||
import org.jeecg.modules.xslmes.approval.mapper.MesXslApprovalFlowMapper;
|
||||
import org.jeecg.modules.xslmes.approval.service.IMesXslApprovalFlowService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* MES 审批流定义 ServiceImpl
|
||||
*
|
||||
* @author GHT
|
||||
* @date 2026-05-29 for:【QH-MES审批流设计】新增审批流可视化设计
|
||||
*/
|
||||
@Service
|
||||
public class MesXslApprovalFlowServiceImpl extends ServiceImpl<MesXslApprovalFlowMapper, MesXslApprovalFlow> implements IMesXslApprovalFlowService {
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,17 @@
|
||||
package org.jeecg.modules.xslmes.approval.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import org.jeecg.modules.xslmes.approval.entity.MesXslApprovalInstance;
|
||||
import org.jeecg.modules.xslmes.approval.mapper.MesXslApprovalInstanceMapper;
|
||||
import org.jeecg.modules.xslmes.approval.service.IMesXslApprovalInstanceService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* MES 审批实例 ServiceImpl
|
||||
*
|
||||
* @author GHT
|
||||
* @date 2026-05-29 for:【QH-MES审批流设计】发起审批运行时
|
||||
*/
|
||||
@Service
|
||||
public class MesXslApprovalInstanceServiceImpl extends ServiceImpl<MesXslApprovalInstanceMapper, MesXslApprovalInstance> implements IMesXslApprovalInstanceService {
|
||||
}
|
||||
@@ -48,6 +48,21 @@ public interface ISysImChatService {
|
||||
|
||||
SysImMessageVO sendMessage(String userId, Integer tenantId, SysImSendMessageDTO dto);
|
||||
|
||||
//update-begin---author:GHT ---date:2026-05-29 for:【QH-MES审批流设计】系统单聊消息(绕过同部门校验,供审批等系统通知场景)-----
|
||||
/**
|
||||
* 系统消息:以 fromUser 身份给 toUser 发送单聊消息。
|
||||
* 与 sendMessage 区别:自动获取/创建单聊会话,且不做"同部门/同租户聊天"校验,专供审批通知等系统场景。
|
||||
*
|
||||
* @param fromUserId 发送人用户ID(一般为业务发起人)
|
||||
* @param toUserId 接收人用户ID
|
||||
* @param tenantId 租户ID
|
||||
* @param content 消息内容
|
||||
* @param msgType 消息类型 text/biz_record 等,空则按 text
|
||||
* @return 消息VO,发送失败返回 null
|
||||
*/
|
||||
SysImMessageVO sendSystemSingleMessage(String fromUserId, String toUserId, Integer tenantId, String content, String msgType);
|
||||
//update-end---author:GHT ---date:2026-05-29 for:【QH-MES审批流设计】系统单聊消息(绕过同部门校验,供审批等系统通知场景)-----
|
||||
|
||||
|
||||
|
||||
void markRead(String userId, String conversationId);
|
||||
|
||||
@@ -473,6 +473,57 @@ public class SysImChatServiceImpl implements ISysImChatService {
|
||||
}
|
||||
//update-end---author:cursor ---date:20260528 for:【IM聊天-OA】发送消息-----------
|
||||
|
||||
//update-begin---author:GHT ---date:2026-05-29 for:【QH-MES审批流设计】系统单聊消息(绕过同部门校验)-----
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public SysImMessageVO sendSystemSingleMessage(String fromUserId, String toUserId, Integer tenantId, String content, String msgType) {
|
||||
if (oConvertUtils.isEmpty(fromUserId) || oConvertUtils.isEmpty(toUserId) || oConvertUtils.isEmpty(content)) {
|
||||
return null;
|
||||
}
|
||||
// 不给自己发送
|
||||
if (fromUserId.equals(toUserId)) {
|
||||
return null;
|
||||
}
|
||||
Integer tenant = tenantId == null ? 0 : tenantId;
|
||||
Date now = new Date();
|
||||
// 获取或创建单聊会话(系统通知场景,不做同部门校验)
|
||||
String pairKey = buildPairKey(fromUserId, toUserId);
|
||||
SysImConversation conversation = conversationMapper.selectOne(new LambdaQueryWrapper<SysImConversation>()
|
||||
.eq(SysImConversation::getTenantId, tenant)
|
||||
.eq(SysImConversation::getUserPairKey, pairKey));
|
||||
if (conversation == null) {
|
||||
conversation = new SysImConversation();
|
||||
conversation.setConvType(CONV_TYPE_SINGLE);
|
||||
conversation.setUserPairKey(pairKey);
|
||||
conversation.setTenantId(tenant);
|
||||
conversation.setCreateBy(fromUserId);
|
||||
conversation.setCreateTime(now);
|
||||
conversation.setUpdateTime(now);
|
||||
conversationMapper.insert(conversation);
|
||||
createMember(conversation.getId(), fromUserId, now);
|
||||
createMember(conversation.getId(), toUserId, now);
|
||||
}
|
||||
// 写入消息
|
||||
SysImMessage message = new SysImMessage();
|
||||
message.setConversationId(conversation.getId());
|
||||
message.setSenderId(fromUserId);
|
||||
message.setContent(content.trim());
|
||||
message.setMsgType(oConvertUtils.isEmpty(msgType) ? MSG_TYPE_TEXT : msgType);
|
||||
message.setTenantId(tenant);
|
||||
message.setCreateTime(now);
|
||||
messageMapper.insert(message);
|
||||
// 更新会话摘要与未读、推送
|
||||
conversation.setLastContent(truncate(resolveLastContent(message.getMsgType(), message.getContent()), 200));
|
||||
conversation.setLastTime(now);
|
||||
conversation.setUpdateTime(now);
|
||||
conversationMapper.updateById(conversation);
|
||||
memberMapper.incrementUnreadExceptSender(conversation.getId(), fromUserId);
|
||||
SysImMessageVO messageVo = toMessageVo(message, fromUserId);
|
||||
pushChatMessage(conversation.getId(), fromUserId, messageVo, conversation.getConvType());
|
||||
return messageVo;
|
||||
}
|
||||
//update-end---author:GHT ---date:2026-05-29 for:【QH-MES审批流设计】系统单聊消息(绕过同部门校验)-----
|
||||
|
||||
//update-begin---author:cursor ---date:20260528 for:【IM聊天-OA】标记已读-----------
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
-- 【QH-MES审批流设计】审批流定义表 + 可审批单据字典 + 我的租户菜单与授权
|
||||
SET NAMES utf8mb4;
|
||||
|
||||
-- 1) 审批流定义表
|
||||
CREATE TABLE IF NOT EXISTS `mes_xsl_approval_flow` (
|
||||
`id` varchar(32) NOT NULL COMMENT '主键',
|
||||
`flow_name` varchar(100) NOT NULL COMMENT '审批流名称',
|
||||
`biz_table` varchar(100) NOT NULL COMMENT '绑定单据表名',
|
||||
`biz_table_name` varchar(200) DEFAULT NULL COMMENT '绑定单据中文名(冗余展示)',
|
||||
`flow_config` longtext COMMENT '流程设计JSON(钉钉式节点树)',
|
||||
`status` varchar(1) DEFAULT '0' COMMENT '状态 0草稿 1已发布 2已停用',
|
||||
`sort_no` double DEFAULT '0' COMMENT '排序',
|
||||
`remark` varchar(500) DEFAULT NULL COMMENT '备注',
|
||||
`del_flag` int DEFAULT '0' COMMENT '逻辑删除 0正常 1已删除',
|
||||
`tenant_id` int DEFAULT NULL COMMENT '租户ID',
|
||||
`sys_org_code` varchar(64) DEFAULT NULL COMMENT '所属部门编码',
|
||||
`create_by` varchar(50) DEFAULT NULL COMMENT '创建人',
|
||||
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
|
||||
`update_by` varchar(50) DEFAULT NULL COMMENT '更新人',
|
||||
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_appr_flow_tenant_biz` (`tenant_id`, `biz_table`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='MES审批流定义表';
|
||||
|
||||
-- 2) 可审批单据字典(单据来源:MES现有业务表) item_value=表名 item_text=单据中文名
|
||||
INSERT IGNORE INTO `sys_dict` (`id`, `dict_name`, `dict_code`, `description`, `del_flag`, `create_by`, `create_time`, `type`, `tenant_id`)
|
||||
VALUES ('1995000000000000310', '可审批单据', 'mes_xsl_approval_biz_doc', 'MES审批流可绑定的业务单据', 0, 'admin', NOW(), 0, 0);
|
||||
|
||||
INSERT IGNORE INTO `sys_dict_item` (`id`, `dict_id`, `item_text`, `item_value`, `description`, `sort_order`, `status`, `create_by`, `create_time`)
|
||||
VALUES
|
||||
('1995000000000000311', '1995000000000000310', '配合示方', 'mes_xsl_formula_spec', '配合示方主表', 1, 1, 'admin', NOW()),
|
||||
('1995000000000000312', '1995000000000000310', '混炼示方', 'mes_xsl_mixing_spec', '混炼示方主表', 2, 1, 'admin', NOW()),
|
||||
('1995000000000000313', '1995000000000000310', '密炼PS编制', 'mes_xsl_mixer_ps_compile', '密炼PS编制', 3, 1, 'admin', NOW()),
|
||||
('1995000000000000314', '1995000000000000310', '胶料快检标准', 'mes_xsl_rubber_quick_test_std', '胶料快检实验标准', 4, 1, 'admin', NOW()),
|
||||
('1995000000000000315', '1995000000000000310', '原料入场记录', 'mes_xsl_raw_material_entry', '原料入场记录', 5, 1, 'admin', NOW());
|
||||
|
||||
-- 2.1) 审批流状态字典
|
||||
INSERT IGNORE INTO `sys_dict` (`id`, `dict_name`, `dict_code`, `description`, `del_flag`, `create_by`, `create_time`, `type`, `tenant_id`)
|
||||
VALUES ('1995000000000000320', '审批流状态', 'mes_xsl_approval_flow_status', 'MES审批流定义状态', 0, 'admin', NOW(), 0, 0);
|
||||
|
||||
INSERT IGNORE INTO `sys_dict_item` (`id`, `dict_id`, `item_text`, `item_value`, `description`, `sort_order`, `status`, `create_by`, `create_time`)
|
||||
VALUES
|
||||
('1995000000000000321', '1995000000000000320', '草稿', '0', '草稿', 1, 1, 'admin', NOW()),
|
||||
('1995000000000000322', '1995000000000000320', '已发布', '1', '已发布', 2, 1, 'admin', NOW()),
|
||||
('1995000000000000323', '1995000000000000320', '已停用', '2', '已停用', 3, 1, 'admin', NOW());
|
||||
|
||||
-- 3) 我的租户 -> 审批流设计 菜单(parent_id=我的租户 1674708136602542082)
|
||||
INSERT IGNORE 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`
|
||||
) VALUES (
|
||||
'1995000000000000301', '1674708136602542082', '审批流设计', '/approval/ApprovalFlowList',
|
||||
'approval/flow/ApprovalFlowList', 1, 'ApprovalFlowList', NULL,
|
||||
1, NULL, '0', 4.00, 0, 'ant-design:partition-outlined', 0, 1,
|
||||
0, 0, '租户审批流可视化设计', 'admin', NOW(), 'admin', NOW(),
|
||||
0, 0, '1', 0
|
||||
);
|
||||
|
||||
-- 按钮权限
|
||||
INSERT IGNORE INTO `sys_permission` (`id`, `parent_id`, `name`, `menu_type`, `perms`, `perms_type`, `sort_no`, `is_route`, `is_leaf`, `hidden`, `status`, `del_flag`, `create_by`, `create_time`)
|
||||
VALUES ('1995000000000000302', '1995000000000000301', '查询', 2, 'approval:flow:list', '1', 1.00, 0, 1, 0, '1', 0, 'admin', NOW());
|
||||
|
||||
INSERT IGNORE INTO `sys_permission` (`id`, `parent_id`, `name`, `menu_type`, `perms`, `perms_type`, `sort_no`, `is_route`, `is_leaf`, `hidden`, `status`, `del_flag`, `create_by`, `create_time`)
|
||||
VALUES ('1995000000000000303', '1995000000000000301', '新增', 2, 'approval:flow:add', '1', 2.00, 0, 1, 0, '1', 0, 'admin', NOW());
|
||||
|
||||
INSERT IGNORE INTO `sys_permission` (`id`, `parent_id`, `name`, `menu_type`, `perms`, `perms_type`, `sort_no`, `is_route`, `is_leaf`, `hidden`, `status`, `del_flag`, `create_by`, `create_time`)
|
||||
VALUES ('1995000000000000304', '1995000000000000301', '编辑', 2, 'approval:flow:edit', '1', 3.00, 0, 1, 0, '1', 0, 'admin', NOW());
|
||||
|
||||
INSERT IGNORE INTO `sys_permission` (`id`, `parent_id`, `name`, `menu_type`, `perms`, `perms_type`, `sort_no`, `is_route`, `is_leaf`, `hidden`, `status`, `del_flag`, `create_by`, `create_time`)
|
||||
VALUES ('1995000000000000305', '1995000000000000301', '删除', 2, 'approval:flow:delete', '1', 4.00, 0, 1, 0, '1', 0, 'admin', NOW());
|
||||
|
||||
INSERT IGNORE INTO `sys_permission` (`id`, `parent_id`, `name`, `menu_type`, `perms`, `perms_type`, `sort_no`, `is_route`, `is_leaf`, `hidden`, `status`, `del_flag`, `create_by`, `create_time`)
|
||||
VALUES ('1995000000000000306', '1995000000000000301', '设计/发布', 2, 'approval:flow:design', '1', 5.00, 0, 1, 0, '1', 0, 'admin', NOW());
|
||||
|
||||
-- 4) 授权给超级管理员 admin
|
||||
INSERT INTO `sys_role_permission` (`id`, `role_id`, `permission_id`, `data_rule_ids`, `operate_date`, `operate_ip`)
|
||||
SELECT REPLACE(UUID(), '-', ''), r.id, p.id, NULL, NOW(), '127.0.0.1'
|
||||
FROM `sys_role` r
|
||||
CROSS JOIN `sys_permission` p
|
||||
WHERE r.`role_code` = 'admin'
|
||||
AND p.`id` IN ('1995000000000000301','1995000000000000302','1995000000000000303','1995000000000000304','1995000000000000305','1995000000000000306')
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM `sys_role_permission` rp WHERE rp.`role_id` = r.`id` AND rp.`permission_id` = p.`id`
|
||||
);
|
||||
|
||||
-- 5) 授权给租户管理员 zuhuadmin(挂在"我的租户"下必须授权,否则租户管理员看不到)
|
||||
INSERT INTO `sys_role_permission` (`id`, `role_id`, `permission_id`, `data_rule_ids`, `operate_date`, `operate_ip`)
|
||||
SELECT REPLACE(UUID(), '-', ''), r.id, p.id, NULL, NOW(), '127.0.0.1'
|
||||
FROM `sys_role` r
|
||||
CROSS JOIN `sys_permission` p
|
||||
WHERE r.`role_code` = 'zuhuadmin'
|
||||
AND p.`id` IN ('1995000000000000301','1995000000000000302','1995000000000000303','1995000000000000304','1995000000000000305','1995000000000000306')
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM `sys_role_permission` rp WHERE rp.`role_id` = r.`id` AND rp.`permission_id` = p.`id`
|
||||
);
|
||||
@@ -0,0 +1,47 @@
|
||||
-- 【QH-MES审批流设计】审批实例表 + 审批流定义增加单据标题字段
|
||||
SET NAMES utf8mb4;
|
||||
|
||||
-- 1) 审批流定义表增加"单据标题字段名",发起时用于展示具体单据
|
||||
ALTER TABLE `mes_xsl_approval_flow`
|
||||
ADD COLUMN `title_field` varchar(100) DEFAULT NULL COMMENT '单据标题字段名(发起选单据时展示)' AFTER `biz_table_name`;
|
||||
|
||||
-- 2) 审批实例表(本期仅发起,记录实例与当前处理人)
|
||||
CREATE TABLE IF NOT EXISTS `mes_xsl_approval_instance` (
|
||||
`id` varchar(32) NOT NULL COMMENT '主键',
|
||||
`flow_id` varchar(32) NOT NULL COMMENT '审批流定义ID',
|
||||
`flow_name` varchar(100) DEFAULT NULL COMMENT '审批流名称',
|
||||
`biz_table` varchar(100) DEFAULT NULL COMMENT '业务单据表名',
|
||||
`biz_table_name` varchar(200) DEFAULT NULL COMMENT '业务单据中文名',
|
||||
`biz_data_id` varchar(64) DEFAULT NULL COMMENT '业务单据记录ID',
|
||||
`biz_title` varchar(300) DEFAULT NULL COMMENT '业务单据展示标题',
|
||||
`current_node_id` varchar(64) DEFAULT NULL COMMENT '当前节点ID',
|
||||
`current_node_name` varchar(100) DEFAULT NULL COMMENT '当前节点名称',
|
||||
`current_handlers` varchar(1000) DEFAULT NULL COMMENT '当前处理人(username逗号分隔)',
|
||||
`current_handlers_text` varchar(1000) DEFAULT NULL COMMENT '当前处理人展示文本',
|
||||
`status` varchar(2) DEFAULT '0' COMMENT '状态 0审批中 1已通过 2已驳回 3已撤销',
|
||||
`apply_user` varchar(50) DEFAULT NULL COMMENT '发起人username',
|
||||
`apply_user_name` varchar(100) DEFAULT NULL COMMENT '发起人姓名',
|
||||
`apply_time` datetime DEFAULT NULL COMMENT '发起时间',
|
||||
`remark` varchar(500) DEFAULT NULL COMMENT '备注',
|
||||
`del_flag` int DEFAULT '0' COMMENT '逻辑删除 0正常 1已删除',
|
||||
`tenant_id` int DEFAULT NULL COMMENT '租户ID',
|
||||
`sys_org_code` varchar(64) DEFAULT NULL COMMENT '所属部门编码',
|
||||
`create_by` varchar(50) DEFAULT NULL COMMENT '创建人',
|
||||
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
|
||||
`update_by` varchar(50) DEFAULT NULL COMMENT '更新人',
|
||||
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_appr_inst_tenant` (`tenant_id`, `flow_id`),
|
||||
KEY `idx_appr_inst_apply` (`apply_user`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='MES审批实例表';
|
||||
|
||||
-- 3) 审批实例状态字典
|
||||
INSERT IGNORE INTO `sys_dict` (`id`, `dict_name`, `dict_code`, `description`, `del_flag`, `create_by`, `create_time`, `type`, `tenant_id`)
|
||||
VALUES ('1995000000000000330', '审批实例状态', 'mes_xsl_approval_instance_status', 'MES审批实例状态', 0, 'admin', NOW(), 0, 0);
|
||||
|
||||
INSERT IGNORE INTO `sys_dict_item` (`id`, `dict_id`, `item_text`, `item_value`, `description`, `sort_order`, `status`, `create_by`, `create_time`)
|
||||
VALUES
|
||||
('1995000000000000331', '1995000000000000330', '审批中', '0', '审批中', 1, 1, 'admin', NOW()),
|
||||
('1995000000000000332', '1995000000000000330', '已通过', '1', '已通过', 2, 1, 'admin', NOW()),
|
||||
('1995000000000000333', '1995000000000000330', '已驳回', '2', '已驳回', 3, 1, 'admin', NOW()),
|
||||
('1995000000000000334', '1995000000000000330', '已撤销', '3', '已撤销', 4, 1, 'admin', NOW());
|
||||
@@ -0,0 +1,5 @@
|
||||
-- 【QH-MES审批流设计】审批流定义增加"功能页面路由",用于控制发起审批悬浮按钮仅在对应功能页显示
|
||||
SET NAMES utf8mb4;
|
||||
|
||||
ALTER TABLE `mes_xsl_approval_flow`
|
||||
ADD COLUMN `route_path` varchar(255) DEFAULT NULL COMMENT '对应功能页面前端路由(发起审批悬浮按钮仅在该页显示)' AFTER `title_field`;
|
||||
@@ -0,0 +1,6 @@
|
||||
-- 【QH-MES审批流设计】审批办理/流转:实例表增加当前节点处理进度与历史(支持会签/或签/依次)
|
||||
SET NAMES utf8mb4;
|
||||
|
||||
ALTER TABLE `mes_xsl_approval_instance`
|
||||
ADD COLUMN `node_progress` longtext COMMENT '当前节点处理进度JSON(nodeId/mode/tasks)' AFTER `current_handlers_text`,
|
||||
ADD COLUMN `history` longtext COMMENT '审批历史JSON数组' AFTER `node_progress`;
|
||||
Reference in New Issue
Block a user