feat(order): 新增委托单级联删除服务
- 新增 InsOrderDeletionService,实现委托单及全部关联业务记录的级联删除
- 重构删除接口,增加权限校验和操作日志注解
- 委托单、报告、样品、样品接收、用章台账、过程报告等模块统一增加送样日期字段并按送样日期排序
- 委托单新增时校验送样日期不能为空
- 优化角色菜单权限,新增
已添加1个文件
已修改21个文件
272 ■■■■■ 文件已修改
cnas-process/src/main/java/com/ruoyi/process/dto/ProcessReportDto.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cnas-process/src/main/resources/mapper/ProcessReportMapper.xml 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
inspect-server/src/main/java/com/ruoyi/inspect/controller/InsOrderController.java 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
inspect-server/src/main/java/com/ruoyi/inspect/dto/ReportPageDto.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
inspect-server/src/main/java/com/ruoyi/inspect/dto/SampleReceiptPageDto.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
inspect-server/src/main/java/com/ruoyi/inspect/pojo/SealUsageLedger.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
inspect-server/src/main/java/com/ruoyi/inspect/service/InsOrderDeletionService.java 130 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
inspect-server/src/main/java/com/ruoyi/inspect/service/impl/InsOrderServiceImpl.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
inspect-server/src/main/java/com/ruoyi/inspect/vo/InsOrderPlanVO.java 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
inspect-server/src/main/resources/mapper/InsOrderMapper.xml 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
inspect-server/src/main/resources/mapper/InsReportMapper.xml 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
inspect-server/src/main/resources/mapper/InsSampleMapper.xml 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
inspect-server/src/main/resources/mapper/InsSampleReceiptMapper.xml 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
inspect-server/src/main/resources/mapper/SealUsageLedgerMapper.xml 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysLoginController.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysMenuController.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/SysPermissionService.java 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/TokenService.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/service/ISysMenuService.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/service/ISysRoleService.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysMenuServiceImpl.java 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysRoleServiceImpl.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cnas-process/src/main/java/com/ruoyi/process/dto/ProcessReportDto.java
@@ -11,6 +11,8 @@
@Data
//导出
public class ProcessReportDto extends ProcessReport {
    @Schema(description = "送样日期")
    private java.time.LocalDate sampleDate;
    private List<Integer> ids;
cnas-process/src/main/resources/mapper/ProcessReportMapper.xml
@@ -35,6 +35,7 @@
        cpr.update_user,
        cpr.update_time,
        cpr.inspection_order_id,
        io.sample_date,
        cpr.receive_date,
        cpr.receive_user,
        u1.name sendUserName
@@ -47,7 +48,7 @@
        <if test="ew.customSqlSegment != null and ew.customSqlSegment != ''">
            ${ew.customSqlSegment}
        </if>
        order by A.send_time desc, A.id desc
        order by (A.sample_date IS NULL) asc, A.sample_date desc, A.inspection_order_id desc, A.id desc
    </select>
    <select id="exportProcessReport" resultType="com.ruoyi.process.dto.ProcessReportDto">
        select ROW_NUMBER() OVER (ORDER BY cpr.id) AS indexs,
inspect-server/src/main/java/com/ruoyi/inspect/controller/InsOrderController.java
@@ -15,6 +15,9 @@
import com.ruoyi.inspect.pojo.InsProduct;
import com.ruoyi.inspect.pojo.InsSample;
import com.ruoyi.inspect.service.InsOrderService;
import com.ruoyi.inspect.service.InsOrderDeletionService;
import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.inspect.service.InsOrderTemplateService;
import com.ruoyi.inspect.service.InsProductService;
import com.ruoyi.inspect.service.InsSampleService;
@@ -40,6 +43,8 @@
public class InsOrderController {
    private InsOrderService insOrderService;
    private InsOrderDeletionService insOrderDeletionService;
    private InsSampleService insSampleService;
@@ -228,16 +233,12 @@
        return Result.success(insOrderService.updateInsOrder(insOrderUpdateDto));
    }
    @Operation(summary = "删除退回订单")
    @Operation(summary = "删除委托单及全部关联记录")
    @PreAuthorize("@ss.hasPermi('delete:insOrder')")
    @Log(title = "委托单及关联记录", businessType = BusinessType.DELETE)
    @DeleteMapping("/delInsOrder")
    public Result<?> delInsOrder(Integer insOrderId) {
        // æŸ¥è¯¢è®¢å•
        InsOrder order = insOrderService.getById(insOrderId);
        if (!(order.getState().equals(2) || order.getState().equals(3))) {
            throw new ErrorException("只有退回订单才能删除");
        }
        return Result.success(insOrderService.removeById(insOrderId));
    public Result<?> delInsOrder(@RequestParam Integer insOrderId) {
        return Result.success(insOrderDeletionService.deleteOrder(insOrderId));
    }
    @Operation(summary = "成品标签打印")
inspect-server/src/main/java/com/ruoyi/inspect/dto/ReportPageDto.java
@@ -8,6 +8,8 @@
@Data
public class ReportPageDto extends InsReport implements Serializable {
    @Schema(description = "送样日期")
    private java.time.LocalDate sampleDate;
    @Schema(description = "委托编号")
    private String entrustCode;
inspect-server/src/main/java/com/ruoyi/inspect/dto/SampleReceiptPageDto.java
@@ -10,6 +10,8 @@
@Data
@ExcelIgnoreUnannotated
public class SampleReceiptPageDto {
    @Schema(description = "送样日期")
    private java.time.LocalDate sampleDate;
    @Schema(description = "样品接收记录ID")
    private Integer receiptId;
inspect-server/src/main/java/com/ruoyi/inspect/pojo/SealUsageLedger.java
@@ -30,6 +30,9 @@
    private String sealIds;
    private String sealNames;
    private String projectName;
    @TableField(exist = false)
    @Schema(description = "送样日期")
    private java.time.LocalDate sampleDate;
    private Integer useUserId;
    private String useUserName;
    private Integer copies;
inspect-server/src/main/java/com/ruoyi/inspect/service/InsOrderDeletionService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,130 @@
package com.ruoyi.inspect.service;
import com.ruoyi.framework.exception.ErrorException;
import lombok.RequiredArgsConstructor;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Collections;
import java.util.List;
import java.util.Map;
/**
 * åˆ é™¤å§”托拥有的业务记录;标准、模板、设备、用户等公共资料不属于委托。
 */
@Service
@RequiredArgsConstructor
public class InsOrderDeletionService {
    private final NamedParameterJdbcTemplate jdbc;
    @Transactional(rollbackFor = Exception.class)
    public int deleteOrder(Integer orderId) {
        if (orderId == null || orderId <= 0) {
            throw new ErrorException("请选择要删除的委托单");
        }
        List<Map<String, Object>> orders = jdbc.queryForList(
                "SELECT type_source, ifs_inventory_id FROM ins_order WHERE id = :id FOR UPDATE",
                Collections.singletonMap("id", orderId));
        if (orders.isEmpty()) {
            throw new ErrorException("委托单不存在或已被删除");
        }
        Map<String, Object> order = orders.get(0);
        Object source = order.get("type_source");
        Object inventoryId = order.get("ifs_inventory_id");
        if ((source instanceof Number && ((Number) source).intValue() == 1)
                || (inventoryId != null && !inventoryId.toString().trim().isEmpty())) {
            throw new ErrorException("此入口仅允许删除委托下单中的委托单");
        }
        List<Long> orderIds = Collections.singletonList(orderId.longValue());
        List<Long> sampleIds = ids("ins_sample", "id", "ins_order_id", orderIds);
        List<Long> productIds = ids("ins_product", "id", "ins_sample_id", sampleIds);
        List<Long> reportIds = ids("ins_report", "id", "ins_order_id", orderIds);
        List<Long> inspectionIds = ids("cnas_inspection_order", "inspection_order_id", "ins_order_id", orderIds);
        List<Long> processSampleIds = ids("cnas_process_sample", "id", "inspection_order_id", inspectionIds);
        List<Long> satisfactionIds = ids("cnas_client_satisfaction", "client_satisfaction_id", "order_id", orderIds);
        List<Long> verifyIds = ids("ins_order_factory_verify", "factory_verify_id", "ins_order_id", orderIds);
        List<Long> warningIds = ids("ins_product_deviation_warning", "deviation_warning_id", "ins_order_id", orderIds);
        List<Long> evaluateIds = ids("cnas_quality_monitor_details_evaluate", "details_evaluate_id", "ins_order_id", orderIds);
        List<Long> retestIds = ids("ins_unqualified_retest_product", "id", "ins_product_id", productIds);
        List<Long> handlerIds = ids("ins_unqualified_handler", "id", "ins_order_id", orderIds);
        // å…ˆåˆ é™¤å¤–部业务模块中由本委托生成的记录,再删除检验主从表。
        delete("cnas_client_satisfaction_attachment", "client_satisfaction_id", satisfactionIds);
        delete("cnas_client_satisfaction", "client_satisfaction_id", satisfactionIds);
        delete("cnas_process_complain", "ins_report_id", reportIds);
        delete("cnas_quality_monitor_details_evaluate_file", "details_evaluate_id", evaluateIds);
        delete("cnas_quality_monitor_details_evaluate", "ins_order_id", orderIds);
        delete("cnas_process_deal", "ins_sample_id", sampleIds);
        delete("cnas_process_deal", "process_sample_id", processSampleIds);
        // æŠ¥å‘Šå‘送记录的历史字段 inspection_order_id å®žé™…存储 ins_order.id。
        delete("cnas_process_report", "inspection_order_id", orderIds);
        delete("cnas_process_order_device", "inspection_order_id", inspectionIds);
        delete("cnas_process_sample", "inspection_order_id", inspectionIds);
        delete("cnas_inspection_order_detail", "inspection_order_id", inspectionIds);
        delete("cnas_inspection_order", "inspection_order_id", inspectionIds);
        delete("auxiliary_output_working_hours", "order_id", orderIds);
        delete("auxiliary_output_working_hours_temporary", "order_id", orderIds);
        delete("auxiliary_output_working_hours", "sample_id", sampleIds);
        delete("auxiliary_output_working_hours_temporary", "sample_id", sampleIds);
        delete("auxiliary_output_working_hours", "ins_product_id", productIds);
        delete("auxiliary_output_working_hours_temporary", "ins_product_id", productIds);
        delete("ins_report_edit_session", "report_id", reportIds);
        delete("ins_report_return_record", "report_id", reportIds);
        delete("ins_report_return_record", "ins_order_id", orderIds);
        delete("seal_usage_ledger", "report_id", reportIds);
        delete("seal_usage_ledger", "ins_order_id", orderIds);
        delete("ins_report", "ins_order_id", orderIds);
        delete("ins_product_deviation_warning_detail", "deviation_warning_id", warningIds);
        delete("ins_product_deviation_warning_detail", "ins_order_id", orderIds);
        delete("ins_product_deviation_warning_detail", "ins_product_id", productIds);
        delete("ins_product_deviation_warning", "ins_order_id", orderIds);
        delete("ins_product_deviation_warning", "ins_product_id", productIds);
        delete("ins_order_rates", "ins_order_id", orderIds);
        delete("ins_order_rates", "ins_product_id", productIds);
        delete("ins_unqualified_retest_result", "retest_product_id", retestIds);
        delete("ins_unqualified_retest_product", "ins_product_id", productIds);
        delete("ins_unqualified_retest_product", "ins_sample_id", sampleIds);
        delete("ins_unqualified_handler_file", "unqualified_id", handlerIds);
        delete("ins_unqualified_handler", "ins_order_id", orderIds);
        delete("ins_product_result", "ins_product_id", productIds);
        delete("ins_product_user", "ins_product_id", productIds);
        delete("ins_product", "ins_sample_id", sampleIds);
        delete("warehouse_history", "ins_sample_id", sampleIds);
        delete("ins_sample_receipt", "ins_order_id", orderIds);
        // åŽ†å²å­—æ®µåä¸º ins_sample_id,实际存储 ins_order.id,不能按样品 ID åˆ é™¤ã€‚
        delete("ins_sample_user", "ins_sample_id", orderIds);
        delete("ins_sample", "ins_order_id", orderIds);
        delete("ins_order_factory_verify_item", "factory_verify_id", verifyIds);
        delete("ins_order_factory_verify", "ins_order_id", orderIds);
        delete("device_record", "ins_order_id", orderIds);
        delete("ins_order_file", "ins_order_id", orderIds);
        delete("ins_order_template_device", "ins_order_id", orderIds);
        delete("ins_order_standard_template", "ins_order_id", orderIds);
        delete("ins_order_state", "ins_order_id", orderIds);
        return delete("ins_order", "id", orderIds);
    }
    // è¡¨åã€åˆ—名仅来自本类固定的关联清单;所有业务 ID å‡ä½¿ç”¨ç»‘定参数。
    private List<Long> ids(String table, String key, String foreignKey, List<Long> parentIds) {
        if (parentIds.isEmpty()) {
            return Collections.emptyList();
        }
        return jdbc.queryForList("SELECT " + key + " FROM " + table + " WHERE " + foreignKey + " IN (:ids)",
                Collections.singletonMap("ids", parentIds), Long.class);
    }
    private int delete(String table, String foreignKey, List<Long> parentIds) {
        if (parentIds.isEmpty()) {
            return 0;
        }
        return jdbc.update("DELETE FROM " + table + " WHERE " + foreignKey + " IN (:ids)",
                Collections.singletonMap("ids", parentIds));
    }
}
inspect-server/src/main/java/com/ruoyi/inspect/service/impl/InsOrderServiceImpl.java
@@ -302,6 +302,9 @@
    @Override
    @Transactional(rollbackFor = Exception.class)
    public int addInsOrder(List<SampleProductDto> list, InsOrder insOrder) {
        if (insOrder == null || insOrder.getSampleDate() == null) {
            throw new ErrorException("送样日期不能为空");
        }
        // todo: ä¸‹å•判断抽样计划的唯一性
        if (insOrder.getQuarterItemId() != null) {
            Long quarterItemCount = insOrderMapper.selectCount(Wrappers.<InsOrder>lambdaQuery()
inspect-server/src/main/java/com/ruoyi/inspect/vo/InsOrderPlanVO.java
@@ -3,26 +3,43 @@
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDate;
@Data
public class InsOrderPlanVO{
@Schema(description = "检验任务列表信息")
public class InsOrderPlanVO {
    @Schema(description = "送样日期")
    private LocalDate sampleDate;
    @Schema(description = "委托单ID")
    private Integer id;
    @Schema(description = "委托编号")
    private String entrustCode;
    @Schema(description = "任务来源")
    private String taskSource;
    @Schema(description = "质量监控计划明细ID")
    private Integer qualityMonitorDetailsId;
    @Schema(description = "监控项目")
    private String monitorProject;
    @Schema(description = "计划期限")
    private String dueDate;
    @Schema(description = "监控目的")
    private String monitorPurpose;
    @Schema(description = "监控方式")
    private String monitorWay;
    @Schema(description = "样品规格")
    private String sampleSpec;
    @Schema(description = "标准编号")
    private String standardCode;
    @Schema(description = "评价人ID")
    private Integer evaluatorUserId;
    @Schema(description = "监控计划批准人ID")
    private Integer planRatifyUserId;
    @Schema(description = "质量监控评价状态")
    private Integer evaluateStatus;
    @Schema(description = "技术标准文本")
    private String technicalStandardText;
    @Schema(description = "样品名称")
@@ -49,6 +66,7 @@
    @Schema(description = "复核人")
    private String checkName;
    @Schema(description = "用户ID")
    private Integer userId;
    @Schema(description = "检验开始时间")
@@ -67,10 +85,13 @@
    private String reportReturnSource;
    @Schema(description = "子实验室")
    private String sonLaboratory;
    @Schema(description = "下单人ID")
    private Integer orderUserId;
    @Schema(description = "复核人ID")
    private Integer verifyUser;
    @Schema(description = "下单类别")
inspect-server/src/main/resources/mapper/InsOrderMapper.xml
@@ -202,11 +202,12 @@
            AND io.laboratory=#{laboratory}
        </if>
        GROUP BY io.id,type
        order by type desc,io.id desc
        order by (io.sample_date is null) asc, io.sample_date desc, io.id desc
        ) a
        <if test="ew.customSqlSegment != null and ew.customSqlSegment != ''">
            ${ew.customSqlSegment}
        </if>
        ORDER BY (a.sample_date IS NULL) ASC, a.sample_date DESC, a.id DESC
    </select>
    <select id="selectInsOrderParameter" resultType="com.ruoyi.inspect.pojo.InsOrder">
inspect-server/src/main/resources/mapper/InsReportMapper.xml
@@ -10,6 +10,7 @@
        select
        ir.*,
        io.entrust_code,
        io.sample_date,
        u.name write_user_name,
        u1.name ratify_user,
        u2.name examine_user,
@@ -59,7 +60,7 @@
        <if test="ew.customSqlSegment != null and ew.customSqlSegment != ''">
            ${ew.customSqlSegment}
        </if>
        ORDER BY a.create_time DESC
        ORDER BY (a.sample_date IS NULL) ASC, a.sample_date DESC, a.ins_order_id DESC, a.id DESC
    </select>
    <select id="getLaboratoryByName" resultType="java.lang.String">
        select s.address from seal s
inspect-server/src/main/resources/mapper/InsSampleMapper.xml
@@ -210,6 +210,7 @@
        io.entrust_code,
        io.type,
        io.appointed,
        io.sample_date,
        io.send_time,
        io.order_type,
        io.task_source,
@@ -322,7 +323,7 @@
        <if test="ew.customSqlSegment != null and ew.customSqlSegment != ''">
            ${ew.customSqlSegment}
        </if>
        ORDER BY send_time desc
        ORDER BY (A.sample_date IS NULL) ASC, A.sample_date DESC, A.id DESC, A.son_laboratory ASC
    </select>
inspect-server/src/main/resources/mapper/InsSampleReceiptMapper.xml
@@ -3,7 +3,7 @@
<mapper namespace="com.ruoyi.inspect.mapper.InsSampleReceiptMapper">
    <sql id="receiptColumns">
        r.id receipt_id, r.ins_order_id order_id, r.ins_sample_id sample_id, r.status,
        COALESCE(NULLIF(io.sample_view, ''), isa.sample) AS sample_name, isa.sample_code, io.entrust_code,
        COALESCE(NULLIF(io.sample_view, ''), isa.sample) AS sample_name, isa.sample_code, io.entrust_code, io.sample_date,
        COALESCE(iost.method_standard, sm.code) method_standard,
        ip.inspection_item, r.main_inspector_id, u.name main_inspector_name,
        COALESCE(isa.remark, io.remark) remark, r.reject_remark, r.receive_time,
@@ -43,7 +43,7 @@
            LIKE CONCAT('%', #{query.sampleName}, '%')
        </if>
        GROUP BY r.id
        ORDER BY r.create_time DESC
        ORDER BY (io.sample_date IS NULL) ASC, io.sample_date DESC, io.id DESC, r.id DESC
    </select>
    <select id="selectDetail" resultType="com.ruoyi.inspect.dto.SampleReceiptPageDto">
        SELECT <include refid="receiptColumns"/>
inspect-server/src/main/resources/mapper/SealUsageLedgerMapper.xml
@@ -5,7 +5,7 @@
    <sql id="LedgerColumns">
        ledger.id, ledger.report_id, ledger.report_code, ledger.ins_order_id,
        ledger.use_time, ledger.seal_ids, ledger.seal_names,
        COALESCE(io.sample_view, '') AS project_name,
        COALESCE(io.sample_view, '') AS project_name, io.sample_date,
        ledger.use_user_id, ledger.use_user_name, ledger.copies, ledger.version,
        ledger.create_user, ledger.create_time, ledger.update_user, ledger.update_time
    </sql>
@@ -39,7 +39,7 @@
        LEFT JOIN ins_order io ON io.id = ledger.ins_order_id
        WHERE 1 = 1
        <include refid="LedgerFilters"/>
        ORDER BY ledger.use_time DESC, ledger.id DESC
        ORDER BY (io.sample_date IS NULL) ASC, io.sample_date DESC, io.id DESC, ledger.id DESC
    </select>
    <select id="selectForExport" resultType="com.ruoyi.inspect.pojo.SealUsageLedger">
ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysLoginController.java
@@ -77,11 +77,9 @@
        Set<String> roles = permissionService.getRolePermission(user);
        // æƒé™é›†åˆ
        Set<String> permissions = permissionService.getMenuPermission(user);
        if (!loginUser.getPermissions().equals(permissions))
        {
            loginUser.setPermissions(permissions);
            tokenService.refreshToken(loginUser);
        }
        // å³ä½¿æƒé™å­—符串相同,角色身份及数据范围也可能变化,需要保存最新角色快照。
        loginUser.setPermissions(permissions);
        tokenService.refreshToken(loginUser);
        AjaxResult ajax = AjaxResult.success();
        ajax.put("user", user);
        ajax.put("roles", roles);
ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysMenuController.java
@@ -68,12 +68,14 @@
    /**
     * åŠ è½½å¯¹åº”è§’è‰²èœå•åˆ—è¡¨æ ‘
     */
    @GetMapping(value = "/roleMenuTreeselect/{roleId}")
    public AjaxResult roleMenuTreeselect(@PathVariable("roleId") Long roleId)
    @PreAuthorize("@ss.hasAnyPermi('system:role:add,system:role:edit')")
    @GetMapping(value = {"/roleMenuTreeselect", "/roleMenuTreeselect/{roleId}"})
    public AjaxResult roleMenuTreeselect(@PathVariable(value = "roleId", required = false) Long roleId)
    {
        List<SysMenu> menus = menuService.selectMenuList(getUserId());
        List<SysMenu> menus = menuService.selectRoleMenuList(getUserId());
        AjaxResult ajax = AjaxResult.success();
        ajax.put("checkedKeys", menuService.selectMenuListByRoleId(roleId));
        ajax.put("checkedKeys", roleId == null ? java.util.Collections.emptyList()
                : menuService.selectMenuListByRoleId(roleId));
        ajax.put("menus", menuService.buildMenuTreeSelect(menus));
        return ajax;
    }
@@ -162,4 +164,4 @@
        }
        return toAjax(menuService.deleteMenuById(menuId));
    }
}
}
ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/SysPermissionService.java
@@ -65,7 +65,9 @@
        }
        else
        {
            List<SysRole> roles = user.getRoles();
            // ç™»å½•后的角色分配可能已改变,不能继续使用 token ä¸­çš„角色快照。
            List<SysRole> roles = roleService.selectAssignedRolesByUserId(user.getUserId());
            user.setRoles(roles);
            if (!CollectionUtils.isEmpty(roles))
            {
                // å¤šè§’色设置permissions属性,以便数据权限匹配权限
ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/TokenService.java
@@ -254,15 +254,18 @@
                // ç®¡ç†å‘˜æ‹¥æœ‰æ‰€æœ‰æƒé™ï¼Œè·³è¿‡
                continue;
            }
            // åˆ¤æ–­è¯¥ç”¨æˆ·æ˜¯å¦æ‹¥æœ‰æ­¤è§’色
            boolean previouslyHadRole = loginUser.getUser().getRoles() != null
                    && loginUser.getUser().getRoles().stream().anyMatch(r -> roleId.equals(r.getRoleId()));
            // æƒé™è®¡ç®—会同时读取当前角色关系,覆盖新增角色未进入旧 token çš„æƒ…况。
            java.util.Set<String> permissions = permissionService.getMenuPermission(loginUser.getUser());
            boolean hasRole = loginUser.getUser().getRoles() != null
                    && loginUser.getUser().getRoles().stream().anyMatch(r -> roleId.equals(r.getRoleId()));
            if (!hasRole)
            if (!previouslyHadRole && !hasRole)
            {
                continue;
            }
            // åˆ·æ–°æƒé™ç¼“å­˜
            loginUser.setPermissions(permissionService.getMenuPermission(loginUser.getUser()));
            loginUser.setPermissions(permissions);
            refreshToken(loginUser);
            log.info("角色[{}]权限变更,已刷新在线用户[{}]的权限缓存", roleId, loginUser.getUsername());
        }
ruoyi-system/src/main/java/com/ruoyi/system/service/ISysMenuService.java
@@ -21,6 +21,9 @@
     */
    public List<SysMenu> selectMenuList(Long userId);
    /** è§’色授权使用的菜单列表,包含委托下单可分配的删除权限。 */
    public List<SysMenu> selectRoleMenuList(Long userId);
    /**
     * æ ¹æ®ç”¨æˆ·æŸ¥è¯¢ç³»ç»Ÿèœå•列表
     * 
ruoyi-system/src/main/java/com/ruoyi/system/service/ISysRoleService.java
@@ -28,6 +28,9 @@
     */
    public List<SysRole> selectRolesByUserId(Long userId);
    /** æŸ¥è¯¢ç”¨æˆ·å½“前实际拥有的角色,不包含角色选择框中的未选角色。 */
    public List<SysRole> selectAssignedRolesByUserId(Long userId);
    /**
     * æ ¹æ®ç”¨æˆ·ID查询角色权限
     * 
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysMenuServiceImpl.java
@@ -64,6 +64,27 @@
        return selectMenuList(new SysMenu(), userId);
    }
    @Override
    public List<SysMenu> selectRoleMenuList(Long userId)
    {
        List<SysMenu> menus = new ArrayList<>(selectMenuList(userId));
        Set<Long> visibleIds = menus.stream().map(SysMenu::getMenuId).collect(Collectors.toSet());
        // è§’色管理员可分配委托单删除权限,无需先拥有删除业务数据的权限。
        // ä»…在已有委托下单父菜单下补充该按钮,其他菜单仍遵循原有可见范围。
        for (SysMenu menu : menuMapper.selectMenuList(new SysMenu()))
        {
            if ("delete:insOrder".equals(menu.getPerms())
                    && "F".equals(menu.getMenuType())
                    && "0".equals(menu.getStatus())
                    && visibleIds.contains(menu.getParentId())
                    && visibleIds.add(menu.getMenuId()))
            {
                menus.add(menu);
            }
        }
        return menus;
    }
    /**
     * æŸ¥è¯¢ç³»ç»Ÿèœå•列表
     * 
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysRoleServiceImpl.java
@@ -86,6 +86,12 @@
        return roles;
    }
    @Override
    public List<SysRole> selectAssignedRolesByUserId(Long userId)
    {
        return roleMapper.selectRolePermissionByUserId(userId);
    }
    /**
     * æ ¹æ®ç”¨æˆ·ID查询权限
     *