hsy
4 天以前 0da8464afe907ed91ab03eebc7613666353bd7e5
feat(mes): 新增工作台报工数据隔离与生产订单BPM审批动态控制

1. 生产排产与报工分配控制:
- 新增 MesProTaskAssignee 表及 Mapper 结构,支持在生产任务排产环节分配指定的报工人员。
- 优化任务列表查询接口,返回被分配人员的名称和ID集合。
- 实现工作台数据隔离逻辑:在查询待报工台账时,底层 SQL 强制校验当前登录人 ID,确保分配给谁后续谁才能在工作台看到数据能报工(包含页面权限前置支持)。

2. 生产订单报工审批 (BPM) 动态分流机制:
- MesProWorkOrder 新增 is_feedback_approve 控制开关及 process_definition_key。
- 重构 submitFeedback 核心逻辑:若订单需审核,则对接 BPM 协同办公发起流程并阻断后续入库;若无需审核,则直接自动过审完成报工。
- 新增 MesProFeedbackStatusListener 监听协同办公结果,支持成功回调扣料入库以及驳回退回草稿功能。
- 优化提交反馈时的批次必填强校验逻辑。
已修改20个文件
已添加4个文件
478 ■■■■ 文件已修改
.gitignore 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/feedback/MesProFeedbackController.java 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/feedback/vo/MesProFeedbackRespVO.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/feedback/vo/MesProFeedbackSaveReqVO.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/feedback/vo/MesProFeedbackSubmitReqVO.java 50 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/task/vo/MesProTaskRespVO.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/workorder/MesProWorkOrderController.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/workorder/vo/MesProWorkOrderRespVO.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/workorder/vo/MesProWorkOrderSaveReqVO.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pro/feedback/MesProFeedbackDO.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pro/workorder/MesProWorkOrderDO.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/ErrorCodeConstants.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/common/bpm/MesBpmProcessService.java 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/common/bpm/MesBpmProcessServiceImpl.java 71 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/project/MesPdProjectServiceImpl.java 43 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/feedback/MesProFeedbackService.java 12 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/feedback/MesProFeedbackServiceImpl.java 73 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/feedback/listener/MesProFeedbackStatusListener.java 74 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/workorder/MesProWorkOrderService.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/workorder/MesProWorkOrderServiceImpl.java 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/UserController.java 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/permission/RoleMapper.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/permission/RoleService.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/permission/RoleServiceImpl.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.gitignore
@@ -58,3 +58,6 @@
.claude/settings.local.json
.claude/worktrees/
.claude/scheduled_tasks.json
graphify-out/
.agents/
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/feedback/MesProFeedbackController.java
@@ -17,6 +17,7 @@
import cn.iocoder.yudao.module.mes.controller.admin.pro.feedback.vo.MesProFeedbackQuickCreateRespVO;
import cn.iocoder.yudao.module.mes.controller.admin.pro.feedback.vo.MesProFeedbackRespVO;
import cn.iocoder.yudao.module.mes.controller.admin.pro.feedback.vo.MesProFeedbackSaveReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.pro.feedback.vo.MesProFeedbackSubmitReqVO;
import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.md.unitmeasure.MesMdUnitMeasureDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.md.workstation.MesMdWorkstationDO;
@@ -176,10 +177,9 @@
    @PutMapping("/submit")
    @Operation(summary = "提交报工")
    @Parameter(name = "id", description = "编号", required = true)
    @PreAuthorize("@ss.hasPermission('mes:pro-feedback:update')")
    public CommonResult<Boolean> submitFeedback(@RequestParam("id") Long id) {
        feedbackService.submitFeedback(id);
    public CommonResult<Boolean> submitFeedback(@Valid @RequestBody MesProFeedbackSubmitReqVO reqVO) {
        feedbackService.submitFeedback(reqVO);
        return success(true);
    }
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/feedback/vo/MesProFeedbackRespVO.java
@@ -169,4 +169,12 @@
    @ExcelProperty("创建时间")
    private LocalDateTime createTime;
    @Schema(description = "BPM æµç¨‹å®žä¾‹ç¼–号")
    @ExcelProperty("BPM æµç¨‹å®žä¾‹ç¼–号")
    private String processInstanceId;
    @Schema(description = "BPM æµç¨‹å®šä¹‰ Key")
    @ExcelProperty("BPM æµç¨‹å®šä¹‰ Key")
    private String processDefinitionKey;
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/feedback/vo/MesProFeedbackSaveReqVO.java
@@ -87,4 +87,10 @@
    @Schema(description = "备注", example = "备注")
    private String remark;
    @Schema(description = "BPM æµç¨‹å®žä¾‹ç¼–号")
    private String processInstanceId;
    @Schema(description = "BPM æµç¨‹å®šä¹‰ Key")
    private String processDefinitionKey;
    }
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/feedback/vo/MesProFeedbackSubmitReqVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,50 @@
package cn.iocoder.yudao.module.mes.controller.admin.pro.feedback.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - MES ç”Ÿäº§æŠ¥å·¥æäº¤ Request VO")
@Data
public class MesProFeedbackSubmitReqVO {
    @Schema(description = "报工单编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
    @NotNull(message = "报工单ID不能为空")
    private Long id;
    @Schema(description = "生产日期")
    private LocalDateTime produceDate;
    @Schema(description = "过期日期")
    private LocalDateTime expireDate;
    @Schema(description = "入库日期")
    private LocalDateTime receiptDate;
    @Schema(description = "供应商ID")
    private Long vendorId;
    @Schema(description = "客户ID")
    private Long clientId;
    @Schema(description = "采购订单编号")
    private String purchaseOrderCode;
    @Schema(description = "销售订单编号")
    private String salesOrderCode;
    @Schema(description = "工具ID")
    private Long toolId;
    @Schema(description = "模具ID")
    private Long moldId;
    @Schema(description = "生产批号")
    private String lotNumber;
    @Schema(description = "质量状态")
    private Integer qualityStatus;
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/task/vo/MesProTaskRespVO.java
@@ -140,4 +140,6 @@
    @Schema(description = "指定报工人员姓名集合")
    private List<String> assigneeUserNames;
    @Schema(description = "是否报工审批")
    private Boolean feedbackApprove;
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/workorder/MesProWorkOrderController.java
@@ -113,6 +113,13 @@
        return success(new PageResult<>(buildWorkOrderRespVOList(pageResult.getList()), pageResult.getTotal()));
    }
    @GetMapping("/approve-process-list")
    @Operation(summary = "获取审批报工的流程定义列表")
    @PreAuthorize("@ss.hasPermission('mes:pro-work-order:query')")
    public CommonResult<List<Map<String, Object>>> getWorkOrderApproveProcessDefinitionList() {
        return success(workOrderService.getWorkOrderApproveProcessDefinitionList());
    }
    @GetMapping("/schedule-page")
    @Operation(summary = "获得生产工单调度状态分页")
    @PreAuthorize("@ss.hasPermission('mes:pro-work-order:query')")
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/workorder/vo/MesProWorkOrderRespVO.java
@@ -145,4 +145,16 @@
    @ExcelProperty("生产状态")
    private Integer productionStatus;
    @Schema(description = "是否报工审批")
    @ExcelProperty("是否报工审批")
    private Boolean feedbackApprove;
    @Schema(description = "BPM æµç¨‹å®šä¹‰ Key")
    @ExcelProperty("BPM æµç¨‹å®šä¹‰ Key")
    private String processDefinitionKey;
    @Schema(description = "报工角色 (多个角色逗号分隔)")
    @ExcelProperty("报工角色")
    private String feedbackRole;
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/workorder/vo/MesProWorkOrderSaveReqVO.java
@@ -70,4 +70,13 @@
    @Schema(description = "备注", example = "备注")
    private String remark;
    @Schema(description = "是否报工审批")
    private Boolean feedbackApprove;
    @Schema(description = "BPM æµç¨‹å®šä¹‰ Key")
    private String processDefinitionKey;
    @Schema(description = "报工角色 (多个角色逗号分隔)")
    private String feedbackRole;
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pro/feedback/MesProFeedbackDO.java
@@ -160,4 +160,14 @@
     */
    private String remark;
    /**
     * BPM æµç¨‹å®žä¾‹ç¼–号
     */
    private String processInstanceId;
    /**
     * BPM æµç¨‹å®šä¹‰ Key
     */
    private String processDefinitionKey;
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pro/workorder/MesProWorkOrderDO.java
@@ -8,6 +8,7 @@
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*;
import java.math.BigDecimal;
@@ -148,4 +149,21 @@
     */
    private String remark;
    /**
     * æ˜¯å¦æŠ¥å·¥å®¡æ‰¹
     */
    @com.baomidou.mybatisplus.annotation.TableField("is_feedback_approve")
    private Boolean feedbackApprove;
    /**
     * BPM æµç¨‹å®šä¹‰ Key
     */
    private String processDefinitionKey;
    /**
     * æŠ¥å·¥è§’色 (多个角色逗号分隔)
     */
    private String feedbackRole;
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/ErrorCodeConstants.java
@@ -39,6 +39,7 @@
    ErrorCode MD_ITEM_BATCH_CONFIG_NOT_EXISTS = new ErrorCode(1_040_102_100, "物料批次属性配置不存在");
    ErrorCode MD_ITEM_BATCH_CONFIG_AT_LEAST_ONE_FLAG = new ErrorCode(1_040_102_101, "批次管理已启用,至少需要配置一个批次属性");
    ErrorCode MD_ITEM_PRODUCT_BOM_REQUIRED = new ErrorCode(1_040_102_102, "产品类物料启用前,必须配置至少一个 BOM ç»„成");
    ErrorCode MD_ITEM_BATCH_CONFIG_AT_LEAST_ONE_APPROVE_FLAG = new ErrorCode(1_040_102_103, "需要配置一个批次属性,请在审批意见中填写");
    // ========== MES ä»“库管理-批次管理(1-040-717-000) ==========
    ErrorCode WM_BATCH_PRODUCE_DATE_REQUIRED = new ErrorCode(1_040_717_000, "批次配置要求生产日期不能为空");
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/common/bpm/MesBpmProcessService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,19 @@
package cn.iocoder.yudao.module.mes.service.common.bpm;
import java.util.List;
import java.util.Map;
/**
 * MES æ¨¡å—的通用 BPM æµç¨‹å¤„理 Service æŽ¥å£
 */
public interface MesBpmProcessService {
    /**
     * èŽ·å–æŒ‡å®šåˆ†ç±»ä¸‹çš„æ¿€æ´»çŠ¶æ€ã€æœ€æ–°ç‰ˆæœ¬çš„æµç¨‹å®šä¹‰åˆ—è¡¨
     *
     * @param categoryCode æµç¨‹åˆ†ç±»ç¼–码
     * @return æµç¨‹å®šä¹‰åˆ—表,包含 id, key, name
     */
    List<Map<String, Object>> getApproveProcessDefinitionList(String categoryCode);
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/common/bpm/MesBpmProcessServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,71 @@
package cn.iocoder.yudao.module.mes.service.common.bpm;
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmCategoryDO;
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO;
import cn.iocoder.yudao.module.bpm.service.definition.BpmCategoryService;
import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.flowable.engine.repository.ProcessDefinition;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import java.util.*;
/**
 * MES æ¨¡å—的通用 BPM æµç¨‹å¤„理 Service å®žçŽ°ç±»
 */
@Service
@Validated
@Slf4j
public class MesBpmProcessServiceImpl implements MesBpmProcessService {
    @Resource
    private BpmCategoryService bpmCategoryService;
    @Resource
    private BpmProcessDefinitionService bpmProcessDefinitionService;
    @Override
    public List<Map<String, Object>> getApproveProcessDefinitionList(String categoryCode) {
        // 1. æ ¡éªŒåˆ†ç±»æ˜¯å¦å­˜åœ¨
        List<BpmCategoryDO> categories = bpmCategoryService.getCategoryListByCode(
                Collections.singletonList(categoryCode));
        if (CollUtil.isEmpty(categories)) {
            log.warn("[getApproveProcessDefinitionList] BPM æµç¨‹åˆ†ç±» {} ä¸å­˜åœ¨", categoryCode);
            return Collections.emptyList();
        }
        // 2. èŽ·å–åˆ†ç±»ä¸‹çš„æµç¨‹å®šä¹‰ä¿¡æ¯
        List<BpmProcessDefinitionInfoDO> definitionInfoList = bpmProcessDefinitionService
                .getProcessDefinitionInfoListByCategory(categoryCode);
        if (CollUtil.isEmpty(definitionInfoList)) {
            return Collections.emptyList();
        }
        // 3. è¿‡æ»¤æ¿€æ´»çŠ¶æ€ï¼Œä¿ç•™æœ€æ–°ç‰ˆæœ¬
        Map<String, ProcessDefinition> latestVersionMap = new HashMap<>();
        for (BpmProcessDefinitionInfoDO info : definitionInfoList) {
            ProcessDefinition pd = bpmProcessDefinitionService.getProcessDefinition(info.getProcessDefinitionId());
            if (pd == null || pd.isSuspended()) {
                continue;
            }
            ProcessDefinition existing = latestVersionMap.get(pd.getKey());
            if (existing == null || pd.getVersion() > existing.getVersion()) {
                latestVersionMap.put(pd.getKey(), pd);
            }
        }
        // 4. è¿”回流程定义列表
        List<Map<String, Object>> result = new ArrayList<>();
        for (ProcessDefinition pd : latestVersionMap.values()) {
            Map<String, Object> item = new HashMap<>();
            item.put("id", pd.getId());
            item.put("key", pd.getKey());
            item.put("name", pd.getName());
            result.add(item);
        }
        return result;
    }
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/project/MesPdProjectServiceImpl.java
@@ -7,8 +7,7 @@
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmCategoryDO;
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO;
import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum;
import cn.iocoder.yudao.module.bpm.service.definition.BpmCategoryService;
import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService;
import cn.iocoder.yudao.module.mes.service.common.bpm.MesBpmProcessService;
import cn.iocoder.yudao.module.mes.controller.admin.pd.project.vo.MesPdProjectPageReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.pd.project.vo.MesPdProjectSaveReqVO;
import cn.iocoder.yudao.module.mes.dal.dataobject.pd.document.MesPdDocumentDO;
@@ -80,10 +79,7 @@
    private BpmProcessInstanceApi processInstanceApi;
    @Resource
    private BpmCategoryService bpmCategoryService;
    @Resource
    private BpmProcessDefinitionService bpmProcessDefinitionService;
    private MesBpmProcessService mesBpmProcessService;
    @Override
    @Transactional(rollbackFor = Exception.class)
@@ -238,40 +234,7 @@
    @Override
    public List<Map<String, Object>> getProjectApproveProcessDefinitionList() {
        // 1. æ ¡éªŒåˆ†ç±»æ˜¯å¦å­˜åœ¨
        List<BpmCategoryDO> categories = bpmCategoryService.getCategoryListByCode(
                Collections.singletonList(BPM_PROCESS_DEFINITION_CATEGORY_CODE));
        if (CollUtil.isEmpty(categories)) {
            throw exception(PD_PROJECT_BPM_CATEGORY_NOT_EXISTS);
        }
        // 2. èŽ·å–åˆ†ç±»ä¸‹çš„æµç¨‹å®šä¹‰ä¿¡æ¯
        List<BpmProcessDefinitionInfoDO> definitionInfoList = bpmProcessDefinitionService
                .getProcessDefinitionInfoListByCategory(BPM_PROCESS_DEFINITION_CATEGORY_CODE);
        if (CollUtil.isEmpty(definitionInfoList)) {
            return Collections.emptyList();
        }
        // 3. è¿‡æ»¤æ¿€æ´»çŠ¶æ€ï¼Œä¿ç•™æœ€æ–°ç‰ˆæœ¬
        Map<String, ProcessDefinition> latestVersionMap = new HashMap<>();
        for (BpmProcessDefinitionInfoDO info : definitionInfoList) {
            ProcessDefinition pd = bpmProcessDefinitionService.getProcessDefinition(info.getProcessDefinitionId());
            if (pd == null || pd.isSuspended()) {
                continue;
            }
            ProcessDefinition existing = latestVersionMap.get(pd.getKey());
            if (existing == null || pd.getVersion() > existing.getVersion()) {
                latestVersionMap.put(pd.getKey(), pd);
            }
        }
        // 4. è¿”回流程定义列表
        List<Map<String, Object>> result = new ArrayList<>();
        for (ProcessDefinition pd : latestVersionMap.values()) {
            Map<String, Object> item = new HashMap<>();
            item.put("id", pd.getId());
            item.put("key", pd.getKey());
            item.put("name", pd.getName());
            result.add(item);
        }
        return result;
        return mesBpmProcessService.getApproveProcessDefinitionList(BPM_PROCESS_DEFINITION_CATEGORY_CODE);
    }
    @Override
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/feedback/MesProFeedbackService.java
@@ -81,9 +81,9 @@
    /**
     * æäº¤æŠ¥å·¥ï¼ˆè‰ç¨¿ â†’ å®¡æ‰¹ä¸­ï¼‰
     *
     * @param id ç¼–号
     * @param reqVO æäº¤è¯·æ±‚
     */
    void submitFeedback(Long id);
    void submitFeedback(@Valid MesProFeedbackSubmitReqVO reqVO);
    /**
     * é©³å›žæŠ¥å·¥ï¼ˆå®¡æ‰¹ä¸­ â†’ è‰ç¨¿ï¼‰
@@ -122,6 +122,14 @@
    MesProFeedbackBatchCheckRespVO checkBatchRequirements(Long feedbackId);
    /**
     * å¤„理 BPM å®¡æ‰¹ç»“æžœ
     *
     * @param id æŠ¥å·¥å•ID
     * @param bpmResult å®¡æ‰¹ç»“果状态
     */
    void handleBpmProcessResult(Long id, Integer bpmResult,String reason);
    /**
     * IPQC å®ŒæˆåŽå›žè°ƒï¼šå®ŒæˆæŠ¥å·¥å•并更新任务/工单进度
     *
     * <p>由 IPQC æ£€éªŒå®Œæˆæ—¶è°ƒç”¨ï¼Œå°†æŠ¥å·¥å•状态从待检验改为已完成,
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/feedback/MesProFeedbackServiceImpl.java
@@ -4,6 +4,7 @@
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.mes.controller.admin.pro.feedback.vo.*;
@@ -62,6 +63,7 @@
 */
@Service
@Validated
@lombok.extern.slf4j.Slf4j
public class MesProFeedbackServiceImpl implements MesProFeedbackService {
    @Resource
@@ -94,6 +96,8 @@
    private MesMdmItemService mdmItemService;
    @Resource
    private MdmItemBatchConfigApi itemBatchConfigApi;
    @Resource
    private cn.iocoder.yudao.module.bpm.api.task.BpmProcessInstanceApi processInstanceApi;
    @Override
    public Long createFeedback(MesProFeedbackSaveReqVO createReqVO) {
@@ -154,13 +158,76 @@
    }
    @Override
    public void submitFeedback(Long id) {
    @Transactional(rollbackFor = Exception.class)
    public void submitFeedback(MesProFeedbackSubmitReqVO reqVO) {
        Long id = reqVO.getId();
        // 1. æ ¡éªŒå­˜åœ¨ + è‰ç¨¿çŠ¶æ€
        validateFeedbackStatusPrepare(id);
        MesProFeedbackDO feedback = validateFeedbackStatusPrepare(id);
        // 2. æ›´æ–°çŠ¶æ€ä¸ºå®¡æ‰¹ä¸­ï¼ˆæŠ¥å·¥äººå’ŒæŠ¥å·¥æ—¶é—´ç”±è¡¨å•ä¿å­˜æ—¶ç¡®å®šï¼Œæäº¤ä¸è¦†ç›–ï¼‰
        MesProWorkOrderDO workOrder = workOrderService.getWorkOrder(feedback.getWorkOrderId());
        if (workOrder != null && Boolean.TRUE.equals(workOrder.getFeedbackApprove()) && cn.hutool.core.util.StrUtil.isNotBlank(workOrder.getProcessDefinitionKey())) {
            // 2. æ›´æ–°çŠ¶æ€ä¸ºå®¡æ‰¹ä¸­ï¼Œå¹¶å¯åŠ¨æµç¨‹
            String processInstanceId = processInstanceApi.createProcessInstance(cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId(),
                    new cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO()
                            .setProcessDefinitionKey(workOrder.getProcessDefinitionKey())
                            .setBusinessKey(String.valueOf(id)));
            feedbackMapper.updateById(new MesProFeedbackDO().setId(id)
                    .setStatus(MesProFeedbackStatusEnum.APPROVING.getStatus())
                    .setProcessDefinitionKey(workOrder.getProcessDefinitionKey())
                    .setProcessInstanceId(processInstanceId));
        } else {
            // 2. åˆ¤æ–­è¿™ä¸ªæŠ¥å·¥éœ€ä¸éœ€è¦æ‰¹æ¬¡å·
            MesProFeedbackBatchCheckRespVO mesProFeedbackBatchCheckRespVO = checkBatchRequirements(id);
            if (mesProFeedbackBatchCheckRespVO.getNeedBatchInput()) {
                if (StrUtil.isBlank(reqVO.getLotNumber())) {
                    throw exception(MD_ITEM_BATCH_CONFIG_AT_LEAST_ONE_APPROVE_FLAG);
                }
            }
            // 3. æ›´æ–°çŠ¶æ€ä¸ºå®¡æ‰¹ä¸­ï¼Œç„¶åŽç›´æŽ¥é€šè¿‡å®¡æ‰¹
        feedbackMapper.updateById(new MesProFeedbackDO().setId(id)
                .setStatus(MesProFeedbackStatusEnum.APPROVING.getStatus()));
            MesProFeedbackApproveReqVO approveReqVO = cn.iocoder.yudao.framework.common.util.object.BeanUtils.toBean(reqVO, MesProFeedbackApproveReqVO.class);
            approveFeedback(approveReqVO);
        }
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void handleBpmProcessResult(Long id, Integer bpmResult,String reason) {
        // 1. æŸ”性校验:获取报工单并判断状态
        MesProFeedbackDO feedback = feedbackMapper.selectById(id);
        if (feedback == null) {
            log.warn("[handleBpmProcessResult] ç”Ÿäº§æŠ¥å·¥({}) ä¸å­˜åœ¨", id);
            return;
        }
        if (!MesProFeedbackStatusEnum.APPROVING.getStatus().equals(feedback.getStatus())) {
            log.warn("[handleBpmProcessResult] ç”Ÿäº§æŠ¥å·¥({}) ä¸å¤„于审批中状态,当前状态:{}", id, feedback.getStatus());
            return;
        }
        // 2. æ ¹æ®å®¡æ‰¹ç»“果进行处理
        if (cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceStatusEnum.APPROVE.getStatus().equals(bpmResult)) {
            MesProFeedbackApproveReqVO  reqVO = new MesProFeedbackApproveReqVO();
            reqVO.setId(id);
            //  åˆ¤æ–­è¿™ä¸ªæŠ¥å·¥éœ€ä¸éœ€è¦æ‰¹æ¬¡å·
            MesProFeedbackBatchCheckRespVO mesProFeedbackBatchCheckRespVO = checkBatchRequirements(id);
            if (mesProFeedbackBatchCheckRespVO.getNeedBatchInput()){
               if (StrUtil.isBlank(reason)){
                   //  æŠ¥é”™ è¯·åœ¨å®¡æ‰¹æ„è§é‡Œå¡«å†™æ‰¹æ¬¡å·
                   throw  exception(MD_ITEM_BATCH_CONFIG_AT_LEAST_ONE_APPROVE_FLAG);
               }else {
                 BeanUtils.copyProperties(mesProFeedbackBatchCheckRespVO,reqVO);
                 reqVO.setLotNumber(reason);
               }
            }
            approveFeedback(reqVO);
        } else if (cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceStatusEnum.REJECT.getStatus().equals(bpmResult) ||
                   cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceStatusEnum.CANCEL.getStatus().equals(bpmResult)) {
            rejectFeedback(id);
        }
    }
    @Override
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/feedback/listener/MesProFeedbackStatusListener.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,74 @@
package cn.iocoder.yudao.module.mes.service.pro.feedback.listener;
import cn.iocoder.yudao.module.bpm.api.event.BpmProcessInstanceStatusEvent;
import cn.iocoder.yudao.module.bpm.api.event.BpmProcessInstanceStatusEventListener;
import cn.iocoder.yudao.module.mes.service.pro.feedback.MesProFeedbackService;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants;
import cn.iocoder.yudao.module.bpm.service.task.BpmTaskService;
import org.flowable.task.api.history.HistoricTaskInstance;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.util.Comparator;
import java.util.List;
/**
 * ç”Ÿäº§æŠ¥å·¥å®¡æ‰¹çŠ¶æ€ç›‘å¬å™¨
 *
 * ç›‘听 BPM æµç¨‹å®žä¾‹çŠ¶æ€å˜æ›´äº‹ä»¶ï¼Œæ›´æ–°ç”Ÿäº§æŠ¥å·¥çš„å®¡æ‰¹çŠ¶æ€
 *
 * @author è¶…级管理员
 */
@Component
@Slf4j
public class MesProFeedbackStatusListener extends BpmProcessInstanceStatusEventListener {
    /**
     * BPM ç”Ÿäº§æŠ¥å·¥å®¡æ‰¹æµç¨‹å®šä¹‰ key
     */
    private static final String BPM_PROCESS_DEFINITION_KEY = "mes-pro-feedback-audit";
    @Resource
    private MesProFeedbackService feedbackService;
    @Resource
    private BpmTaskService bpmTaskService;
    @Override
    protected String getProcessDefinitionKey() {
        return BPM_PROCESS_DEFINITION_KEY;
    }
    @Override
    protected void onEvent(BpmProcessInstanceStatusEvent event) {
        Long feedbackId;
        try {
            feedbackId = Long.parseLong(event.getBusinessKey());
        } catch (NumberFormatException e) {
            log.warn("[onEvent] businessKey({}) ä¸æ˜¯æœ‰æ•ˆçš„生产报工编号", event.getBusinessKey());
            return;
        }
        String reason = event.getReason();
        if (StrUtil.isEmpty(reason) && event.getId() != null) {
            // å½“ event.getReason() ä¸ºç©ºæ—¶ï¼Œå°è¯•从最后一个完成的任务中获取审批意见
            List<HistoricTaskInstance> tasks = bpmTaskService.getTaskListByProcessInstanceId(event.getId(), false);
            if (CollUtil.isNotEmpty(tasks)) {
                HistoricTaskInstance lastTask = tasks.stream()
                        .filter(task -> task.getEndTime() != null)
                        .max(Comparator.comparing(HistoricTaskInstance::getEndTime))
                        .orElse(null);
                if (lastTask != null && lastTask.getTaskLocalVariables() != null) {
                    reason = (String) lastTask.getTaskLocalVariables().get(BpmnVariableConstants.TASK_VARIABLE_REASON);
                }
            }
        }
        log.info("[onEvent] æ”¶åˆ°BPM审批事件,feedbackId={}, status={},Reason={}", feedbackId, event.getStatus(), reason);
        feedbackService.handleBpmProcessResult(feedbackId, event.getStatus(), reason);
    }
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/workorder/MesProWorkOrderService.java
@@ -67,6 +67,13 @@
    MesProWorkOrderDO getWorkOrder(String code);
    /**
     * èŽ·å–å®¡æ‰¹æŠ¥å·¥çš„æµç¨‹å®šä¹‰åˆ—è¡¨
     *
     * @return æµç¨‹å®šä¹‰åˆ—表,包含 id, key, name
     */
    List<Map<String, Object>> getWorkOrderApproveProcessDefinitionList();
    /**
     * èŽ·å¾—ç”Ÿäº§å·¥å•åˆ†é¡µ
     *
     * @param pageReqVO åˆ†é¡µæŸ¥è¯¢
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/workorder/MesProWorkOrderServiceImpl.java
@@ -13,6 +13,7 @@
import cn.iocoder.yudao.module.mes.enums.wm.BarcodeBizTypeEnum;
import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemBatchConfigService;
import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService;
import cn.iocoder.yudao.module.mes.service.common.bpm.MesBpmProcessService;
import cn.iocoder.yudao.module.mes.controller.admin.pro.workorder.vo.MesProWorkOrderProgressRespVO;
import cn.iocoder.yudao.module.mes.dal.dataobject.md.workstation.MesMdWorkstationDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.process.MesProProcessDO;
@@ -46,6 +47,11 @@
@Service
@Validated
public class MesProWorkOrderServiceImpl implements MesProWorkOrderService {
    @Resource
    private MesBpmProcessService mesBpmProcessService;
    private static final String BPM_PROCESS_DEFINITION_CATEGORY_CODE = "mes_pro_feedback_audit";
    @Resource
    private MesProWorkOrderMapper workOrderMapper;
@@ -150,6 +156,13 @@
    }
    @Override
    public List<Map<String, Object>> getWorkOrderApproveProcessDefinitionList() {
        return mesBpmProcessService.getApproveProcessDefinitionList(BPM_PROCESS_DEFINITION_CATEGORY_CODE);
    }
    @Override
    public MesProWorkOrderDO getWorkOrder(Long id) {
        return workOrderMapper.selectById(id);
    }
yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/UserController.java
@@ -157,15 +157,19 @@
    }
    @GetMapping("/list-by-role-code")
    @Operation(summary = "通过角色标识获取用户精简信息列表", description = "只包含拥有该角色的用户,主要用于前端的下拉选项")
    @Parameter(name = "code", description = "角色标识", required = true, example = "admin")
    @Operation(summary = "通过角色标识获取用户精简信息列表", description = "只包含拥有这些角色的用户,主要用于前端的下拉选项。支持逗号分隔的多个角色标识")
    @Parameter(name = "code", description = "角色标识,支持多个,逗号分隔", required = true, example = "admin,user")
    public CommonResult<List<UserSimpleRespVO>> getSimpleUserListByRoleCode(
            @RequestParam("code") String code) {
        RoleDO role = roleService.getRoleByCode(code);
        if (role == null) {
        if (StrUtil.isBlank(code)) {
            return success(Collections.emptyList());
        }
        Set<Long> userIds = permissionService.getUserRoleIdListByRoleId(Collections.singleton(role.getId()));
        List<String> codes = StrUtil.split(code, ",");
        List<RoleDO> roles = roleService.getRoleListByCodes(codes);
        if (CollUtil.isEmpty(roles)) {
            return success(Collections.emptyList());
        }
        Set<Long> userIds = permissionService.getUserRoleIdListByRoleId(convertList(roles, RoleDO::getId));
        if (CollUtil.isEmpty(userIds)) {
            return success(Collections.emptyList());
        }
yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/permission/RoleMapper.java
@@ -32,6 +32,10 @@
        return selectOne(RoleDO::getCode, code);
    }
    default List<RoleDO> selectListByCodes(Collection<String> codes) {
        return selectList(RoleDO::getCode, codes);
    }
    default List<RoleDO> selectListByStatus(@Nullable Collection<Integer> statuses) {
        return selectList(RoleDO::getStatus, statuses);
    }
yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/permission/RoleService.java
@@ -73,6 +73,14 @@
    RoleDO getRoleByCode(String code);
    /**
     * èŽ·å¾—è§’è‰²åˆ—è¡¨
     *
     * @param codes è§’色编码数组
     * @return è§’色列表
     */
    List<RoleDO> getRoleListByCodes(Collection<String> codes);
    /**
     * èŽ·å¾—è§’è‰²ï¼Œä»Žç¼“å­˜ä¸­
     *
     * @param id è§’色编号
yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/permission/RoleServiceImpl.java
@@ -195,6 +195,14 @@
    }
    @Override
    public List<RoleDO> getRoleListByCodes(Collection<String> codes) {
        if (CollUtil.isEmpty(codes)) {
            return Collections.emptyList();
        }
        return roleMapper.selectListByCodes(codes);
    }
    @Override
    @Cacheable(value = RedisKeyConstants.ROLE, key = "#id",
            unless = "#result == null")
    public RoleDO getRoleFromCache(Long id) {