Przeglądaj źródła

提交数据权限

chenxiaofei 2 miesięcy temu
rodzic
commit
2b0e74244f
22 zmienionych plików z 1309 dodań i 8 usunięć
  1. 29 0
      sckw-modules-api/sckw-system-api/src/main/java/com/sckw/system/api/feign/DataPermissionFeignService.java
  2. 37 0
      sckw-modules-api/sckw-system-api/src/main/java/com/sckw/system/api/model/dto/req/DataPermissionFilterReqDto.java
  3. 107 0
      sckw-modules-api/sckw-system-api/src/main/java/com/sckw/system/api/model/dto/res/DataPermissionDTO.java
  4. 53 0
      sckw-modules/sckw-system/src/main/java/com/sckw/system/controller/KwsDataPermissionController.java
  5. 39 0
      sckw-modules/sckw-system/src/main/java/com/sckw/system/controller/server/DataPermissionServerController.java
  6. 44 0
      sckw-modules/sckw-system/src/main/java/com/sckw/system/dao/KwsDataPermissionDao.java
  7. 11 0
      sckw-modules/sckw-system/src/main/java/com/sckw/system/dao/KwsMenuDao.java
  8. 27 0
      sckw-modules/sckw-system/src/main/java/com/sckw/system/model/KwsDataPermission.java
  9. 7 0
      sckw-modules/sckw-system/src/main/java/com/sckw/system/model/KwsRole.java
  10. 39 0
      sckw-modules/sckw-system/src/main/java/com/sckw/system/model/vo/req/DataPermissionReqVo.java
  11. 11 0
      sckw-modules/sckw-system/src/main/java/com/sckw/system/model/vo/req/EditRoleReqVo.java
  12. 7 0
      sckw-modules/sckw-system/src/main/java/com/sckw/system/model/vo/req/FindMenuTreeReqVo.java
  13. 45 0
      sckw-modules/sckw-system/src/main/java/com/sckw/system/model/vo/res/DataPermissionEntTreeResVo.java
  14. 49 0
      sckw-modules/sckw-system/src/main/java/com/sckw/system/model/vo/res/DataPermissionResVo.java
  15. 11 0
      sckw-modules/sckw-system/src/main/java/com/sckw/system/model/vo/res/RoleResVo.java
  16. 329 0
      sckw-modules/sckw-system/src/main/java/com/sckw/system/service/KwsDataPermissionService.java
  17. 36 0
      sckw-modules/sckw-system/src/main/java/com/sckw/system/service/KwsMenuService.java
  18. 259 0
      sckw-modules/sckw-system/src/main/java/com/sckw/system/utils/DataPermissionHelper.java
  19. 122 0
      sckw-modules/sckw-system/src/main/resources/mapper/KwsDataPermissionDao.xml
  20. 13 0
      sckw-modules/sckw-system/src/main/resources/mapper/KwsMenuDao.xml
  21. 7 8
      sckw-modules/sckw-transport/src/main/java/com/sckw/transport/service/KwtAcceptCarriageOrderService.java
  22. 27 0
      sql/2026/03/2026_03_26_data_permission_create.sql

+ 29 - 0
sckw-modules-api/sckw-system-api/src/main/java/com/sckw/system/api/feign/DataPermissionFeignService.java

@@ -0,0 +1,29 @@
+package com.sckw.system.api.feign;
+
+import com.sckw.system.api.model.dto.req.DataPermissionFilterReqDto;
+import com.sckw.system.api.model.dto.res.DataPermissionDTO;
+import org.springframework.cloud.openfeign.FeignClient;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+
+/**
+ * 数据权限远程Feign接口
+ * <p>
+ * 供其他模块获取当前用户的数据权限过滤条件(企业数据权限 + 个人数据权限并集),
+ * 适用于贸易合同、贸易订单、物流订单、运单等业务数据查询过滤。
+ * @author cxf
+ * @date 2026-03-31
+ */
+@FeignClient(name = "sckw-ng-system", contextId = "DataPermissionFeignService")
+public interface DataPermissionFeignService {
+
+    /**
+     * 获取数据权限过滤条件
+     *
+     * @param req 查询参数(用户ID、角色ID、是否平台管理员)
+     * @return 数据权限过滤DTO
+     */
+    @PostMapping("/server/dataPermission/filter")
+    DataPermissionDTO getDataPermissionFilter(@RequestBody DataPermissionFilterReqDto req);
+
+}

+ 37 - 0
sckw-modules-api/sckw-system-api/src/main/java/com/sckw/system/api/model/dto/req/DataPermissionFilterReqDto.java

@@ -0,0 +1,37 @@
+package com.sckw.system.api.model.dto.req;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * 获取数据权限过滤条件请求体
+ *
+ * @author cxf
+ * @date 2026-04-07
+ */
+@Data
+public class DataPermissionFilterReqDto implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 当前用户ID
+     */
+    private Long userId;
+
+    /**
+     * 当前使用的角色ID(可为null,null时取用户所有角色的并集)
+     */
+    private Long roleId;
+
+    /**
+     * 是否平台管理员
+     */
+    @JsonProperty("isManager")
+    private boolean manager;
+
+}

+ 107 - 0
sckw-modules-api/sckw-system-api/src/main/java/com/sckw/system/api/model/dto/res/DataPermissionDTO.java

@@ -0,0 +1,107 @@
+package com.sckw.system.api.model.dto.res;
+
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * 数据权限过滤DTO
+ * <p>
+ * 供其他模块通过Dubbo远程调用获取当前用户的数据权限过滤条件,
+ * 用于贸易合同、贸易订单、物流订单、运单等业务数据查询过滤。
+ * <p>
+ * 规则说明:
+ * <ul>
+ *     <li>企业数据权限:限制可见的企业范围(角色配置的企业ID列表)</li>
+ *     <li>个人数据权限:在企业范围内,仅查看本人作为销售人员的数据</li>
+ *     <li>两者为并集(组合)关系:开启个人数据权限后,
+ *         查询条件为 ent_id IN (已选企业) AND salesman_id = 当前用户</li>
+ *     <li>多角色场景:各角色的企业ID取并集,任一角色开启个人数据权限则整体开启</li>
+ * </ul>
+ * <p>
+ * @author cxf
+ * @date 2026-03-31
+ */
+@Data
+public class DataPermissionDTO implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 当前用户ID
+     */
+    private Long userId;
+
+    /**
+     * 是否可见全部数据(未配置任何数据权限时为true)
+     */
+    private boolean allVisible;
+
+    /**
+     * 可见的企业ID集合(企业数据权限配置的企业列表)
+     */
+    private Set<Long> visibleEntIds;
+
+    /**
+     * 是否开启个人数据权限
+     * <p>
+     * 开启后,在企业范围内仅查看本人作为销售人员的贸易合同及其衍生数据
+     * (贸易订单、物流订单、运单)
+     */
+    private boolean personalDataEnabled;
+
+    /**
+     * 将数据权限过滤条件应用到查询参数Map中
+     * <p>
+     * 注入的参数:
+     * <ul>
+     *     <li><b>dataPermEntIds</b> (List&lt;Long&gt;) — 可访问的企业ID列表,SQL: ent_id IN (...)</li>
+     *     <li><b>dataPermUserId</b> (Long) — 当前用户ID,SQL: salesman_id = #{dataPermUserId}</li>
+     * </ul>
+     * <p>
+     * Mapper XML标准写法(适用于贸易合同/订单/物流/运单等表,t为表别名):
+     * <pre>
+     * &lt;!-- 企业数据权限过滤 --&gt;
+     * &lt;if test="dataPermEntIds != null and dataPermEntIds.size() > 0"&gt;
+     *     AND t.ent_id IN
+     *     &lt;foreach collection="dataPermEntIds" item="item" open="(" close=")" separator=","&gt;
+     *         #{item}
+     *     &lt;/foreach&gt;
+     * &lt;/if&gt;
+     * &lt;!-- 个人数据权限过滤(销售人员=当前用户) --&gt;
+     * &lt;if test="dataPermUserId != null"&gt;
+     *     AND t.salesman_id = #{dataPermUserId}
+     * &lt;/if&gt;
+     * </pre>
+     *
+     * @param params 查询参数Map
+     */
+    public void applyTo(Map<String, Object> params) {
+        if (this.allVisible && !this.personalDataEnabled) {
+            return;
+        }
+
+        if (!this.allVisible && visibleEntIds != null && !visibleEntIds.isEmpty()) {
+            params.put("dataPermEntIds", new ArrayList<>(this.visibleEntIds));
+        }
+
+        if (this.personalDataEnabled) {
+            params.put("dataPermUserId", this.userId);
+        }
+    }
+
+    /**
+     * 判断是否需要数据权限过滤
+     *
+     * @return true=存在数据权限限制,需要过滤
+     */
+    public boolean needFilter() {
+        return !this.allVisible || this.personalDataEnabled;
+    }
+
+}

+ 53 - 0
sckw-modules/sckw-system/src/main/java/com/sckw/system/controller/KwsDataPermissionController.java

@@ -0,0 +1,53 @@
+package com.sckw.system.controller;
+
+import com.sckw.core.web.constant.HttpStatus;
+import com.sckw.core.web.response.HttpResult;
+import com.sckw.system.model.vo.req.DataPermissionReqVo;
+import com.sckw.system.service.KwsDataPermissionService;
+import jakarta.validation.Valid;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+/**
+ * 数据权限配置
+ *
+ * @author system
+ * @date 2026-03-26
+ */
+@RestController
+@RequestMapping("/kwsDataPermission")
+public class KwsDataPermissionController {
+
+    @Autowired
+    private KwsDataPermissionService kwsDataPermissionService;
+
+    /**
+     * 查询角色的数据权限配置
+     *
+     * @param roleId 角色ID
+     */
+    @GetMapping("/detail")
+    public HttpResult detail(@RequestParam Long roleId) {
+        return HttpResult.ok(kwsDataPermissionService.getDataPermission(roleId));
+    }
+
+    /**
+     * 保存/更新角色数据权限
+     */
+    @PostMapping("/save")
+    public HttpResult save(@Valid @RequestBody DataPermissionReqVo reqVo) {
+        kwsDataPermissionService.saveDataPermission(reqVo);
+        return HttpResult.ok(HttpStatus.MSG_005);
+    }
+
+    /**
+     * 查询数据权限企业树
+     * 客户端:当前企业+下级企业
+     * 平台端:所有入驻企业
+     */
+    @GetMapping("/entTree")
+    public HttpResult entTree() {
+        return HttpResult.ok(kwsDataPermissionService.getEntTree());
+    }
+
+}

+ 39 - 0
sckw-modules/sckw-system/src/main/java/com/sckw/system/controller/server/DataPermissionServerController.java

@@ -0,0 +1,39 @@
+package com.sckw.system.controller.server;
+
+import com.sckw.system.api.model.dto.req.DataPermissionFilterReqDto;
+import com.sckw.system.api.model.dto.res.DataPermissionDTO;
+import com.sckw.system.utils.DataPermissionHelper;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * 数据权限内部Feign接口(服务端)
+ * <p>
+ * 供其他微服务模块通过Feign调用,获取数据权限过滤条件。
+ * 对应Feign客户端:{@link com.sckw.system.api.feign.DataPermissionFeignService}
+ *
+ * @author cxf
+ * @date 2026-03-31
+ */
+@RestController
+@RequestMapping("/server/dataPermission")
+public class DataPermissionServerController {
+
+    @Autowired
+    private DataPermissionHelper dataPermissionHelper;
+
+    /**
+     * 获取数据权限过滤条件(企业数据权限 + 个人数据权限并集)
+     *
+     * @param req 查询参数(用户ID、角色ID、是否平台管理员)
+     * @return 数据权限过滤DTO
+     */
+    @PostMapping("/filter")
+    public DataPermissionDTO getDataPermissionFilter(@RequestBody DataPermissionFilterReqDto req) {
+        return dataPermissionHelper.buildPermissionDTO(req.getUserId(), req.getRoleId(), req.isManager());
+    }
+
+}

+ 44 - 0
sckw-modules/sckw-system/src/main/java/com/sckw/system/dao/KwsDataPermissionDao.java

@@ -0,0 +1,44 @@
+package com.sckw.system.dao;
+
+import com.sckw.system.model.KwsDataPermission;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+/**
+ * 角色数据权限(企业维度)Mapper
+ *
+ * @author cxf
+ * @date 2026-03-26
+ */
+@Mapper
+public interface KwsDataPermissionDao {
+
+    int insert(KwsDataPermission record);
+
+    int update(KwsDataPermission record);
+
+    int saveBatch(@Param("list") List<KwsDataPermission> list);
+
+    /**
+     * 根据角色ID查询数据权限
+     */
+    List<KwsDataPermission> selectByRoleId(@Param("roleId") Long roleId);
+
+    /**
+     * 根据多个角色ID查询数据权限
+     */
+    List<KwsDataPermission> selectByRoleIds(@Param("list") List<Long> roleIds);
+
+    /**
+     * 根据角色ID逻辑删除数据权限
+     */
+    int deleteByRoleId(@Param("roleId") Long roleId);
+
+    /**
+     * 根据多个角色ID逻辑删除数据权限
+     */
+    int deleteByRoleIds(@Param("list") List<Long> roleIds);
+
+}

+ 11 - 0
sckw-modules/sckw-system/src/main/java/com/sckw/system/dao/KwsMenuDao.java

@@ -5,6 +5,8 @@ import com.sckw.system.model.pojo.FindMenuTreePojo;
 import com.sckw.system.model.vo.req.FindMenuTreeReqVo;
 import com.sckw.system.model.vo.res.KwsMenuResVo;
 import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+
 import java.util.List;
 import java.util.Map;
 
@@ -77,4 +79,13 @@ public interface KwsMenuDao {
      * @date: 2023/7/10
      */
     List<KwsMenu> queryChildMenu(KwsMenu kwsMenuParam);
+
+    /**
+     * 根据多个父级ID批量查询子菜单
+     *
+     * @param parentIds 父级菜单ID列表
+     * @param type      菜单类型(可选),传null则不限类型
+     * @return 子菜单列表
+     */
+    List<KwsMenu> selectByParentIds(@Param("parentIds") List<Long> parentIds, @Param("type") Integer type);
 }

+ 27 - 0
sckw-modules/sckw-system/src/main/java/com/sckw/system/model/KwsDataPermission.java

@@ -0,0 +1,27 @@
+package com.sckw.system.model;
+
+import com.sckw.core.model.base.BaseModel;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * 角色数据权限(企业维度)
+ *
+ * @author cxf
+ * @date 2026-03-26
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class KwsDataPermission extends BaseModel {
+
+    /**
+     * 角色ID
+     */
+    private Long roleId;
+
+    /**
+     * 企业ID(可访问的企业)
+     */
+    private Long entId;
+
+}

+ 7 - 0
sckw-modules/sckw-system/src/main/java/com/sckw/system/model/KwsRole.java

@@ -31,4 +31,11 @@ public class KwsRole extends BaseModel {
     @TableField("admin_flag")
     private Integer adminFlag;
 
+    /**
+     * 个人数据权限:0关闭 1开启
+     * 开启后仅查看本人作为销售人员的贸易合同/订单/运单数据
+     */
+    @TableField("personal_data_flag")
+    private Integer personalDataFlag;
+
 }

+ 39 - 0
sckw-modules/sckw-system/src/main/java/com/sckw/system/model/vo/req/DataPermissionReqVo.java

@@ -0,0 +1,39 @@
+package com.sckw.system.model.vo.req;
+
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 数据权限配置请求VO
+ *
+ * @author cxf
+ * @date 2026-03-26
+ */
+@Data
+public class DataPermissionReqVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 角色ID
+     */
+    @NotNull(message = "角色ID不能为空")
+    private Long roleId;
+
+    /**
+     * 可访问的企业ID列表
+     */
+    private List<Long> entIds;
+
+    /**
+     * 个人数据权限:0关闭 1开启
+     * 开启后仅查看本人作为销售人员的贸易合同/订单/运单数据
+     */
+    private Integer personalDataFlag;
+
+}

+ 11 - 0
sckw-modules/sckw-system/src/main/java/com/sckw/system/model/vo/req/EditRoleReqVo.java

@@ -7,6 +7,7 @@ import lombok.Data;
 
 import java.io.Serial;
 import java.io.Serializable;
+import java.util.List;
 
 /**
  * @author czh
@@ -61,4 +62,14 @@ public class EditRoleReqVo implements Serializable {
      */
     private Boolean isManage;
     private Integer adminFlag;
+
+    /**
+     * 数据权限-可访问的企业ID列表(新增企业维度数据权限)
+     */
+    private List<Long> entIds;
+
+    /**
+     * 个人数据权限:0关闭 1开启
+     */
+    private Integer personalDataFlag;
 }

+ 7 - 0
sckw-modules/sckw-system/src/main/java/com/sckw/system/model/vo/req/FindMenuTreeReqVo.java

@@ -40,4 +40,11 @@ public class FindMenuTreeReqVo implements Serializable {
      */
     private Integer type;
 
+    /**
+     * 是否包含按钮级别权限
+     * <p>
+     * true时返回的菜单树包含按钮节点(type=2),用于角色权限配置页面
+     */
+    private Boolean includeButton;
+
 }

+ 45 - 0
sckw-modules/sckw-system/src/main/java/com/sckw/system/model/vo/res/DataPermissionEntTreeResVo.java

@@ -0,0 +1,45 @@
+package com.sckw.system.model.vo.res;
+
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import com.sckw.core.utils.LongToStringUtils;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 数据权限企业树节点
+ *
+ * @author cxf
+ * @date 2026-03-26
+ */
+@Data
+public class DataPermissionEntTreeResVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 企业ID
+     */
+    @JsonSerialize(using = LongToStringUtils.class)
+    private Long id;
+
+    /**
+     * 上级企业ID
+     */
+    @JsonSerialize(using = LongToStringUtils.class)
+    private Long pid;
+
+    /**
+     * 企业名称
+     */
+    private String firmName;
+
+    /**
+     * 子企业列表
+     */
+    private List<DataPermissionEntTreeResVo> children;
+
+}

+ 49 - 0
sckw-modules/sckw-system/src/main/java/com/sckw/system/model/vo/res/DataPermissionResVo.java

@@ -0,0 +1,49 @@
+package com.sckw.system.model.vo.res;
+
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import com.sckw.core.utils.LongToStringUtils;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 数据权限配置响应VO
+ *
+ * @author cxf
+ * @date 2026-03-26
+ */
+@Data
+public class DataPermissionResVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 角色ID
+     */
+    @JsonSerialize(using = LongToStringUtils.class)
+    private Long roleId;
+
+    /**
+     * 角色名称
+     */
+    private String roleName;
+
+    /**
+     * 个人数据权限:0关闭 1开启
+     */
+    private Integer personalDataFlag;
+
+    /**
+     * 已配置的企业ID列表
+     */
+    private List<Long> entIds;
+
+    /**
+     * 已配置的企业名称列表(用于前端展示)
+     */
+    private List<String> entNames;
+
+}

+ 11 - 0
sckw-modules/sckw-system/src/main/java/com/sckw/system/model/vo/res/RoleResVo.java

@@ -8,6 +8,7 @@ import lombok.Data;
 import java.io.Serial;
 import java.io.Serializable;
 import java.util.Date;
+import java.util.List;
 
 /**
  * @author czh
@@ -87,5 +88,15 @@ public class RoleResVo implements Serializable {
     private String menuIds;
     private Integer adminFlag;
 
+    /**
+     * 个人数据权限:0关闭 1开启
+     */
+    private Integer personalDataFlag;
+
+    /**
+     * 数据权限-可访问的企业ID列表
+     */
+    private List<Long> entIdList;
+
 
 }

+ 329 - 0
sckw-modules/sckw-system/src/main/java/com/sckw/system/service/KwsDataPermissionService.java

@@ -0,0 +1,329 @@
+package com.sckw.system.service;
+
+import com.sckw.core.exception.SystemException;
+import com.sckw.core.model.constant.Global;
+import com.sckw.core.utils.CollectionUtils;
+import com.sckw.core.utils.IdWorker;
+import com.sckw.core.web.constant.HttpStatus;
+import com.sckw.core.web.context.LoginUserHolder;
+import com.sckw.redis.utils.RedissonUtils;
+import com.sckw.system.dao.KwsDataPermissionDao;
+import com.sckw.system.dao.KwsEnterpriseDao;
+import com.sckw.system.dao.KwsRoleDao;
+import com.sckw.system.model.KwsDataPermission;
+import com.sckw.system.model.KwsEnterprise;
+import com.sckw.system.model.KwsRole;
+import com.sckw.system.model.vo.req.DataPermissionReqVo;
+import com.sckw.system.model.vo.res.DataPermissionEntTreeResVo;
+import com.sckw.system.model.vo.res.DataPermissionResVo;
+import com.sckw.system.model.vo.res.EntBaseInfo;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * 数据权限Service
+ *
+ * @author cxf
+ * @date 2026-03-26
+ */
+@Slf4j
+@Service
+public class KwsDataPermissionService {
+
+    /** Redis缓存key前缀:角色数据权限企业ID列表 */
+    private static final String REDIS_DATA_PERM_PREFIX = "dataPermission:role:";
+    /** Redis缓存key前缀:角色个人数据权限标识 */
+    private static final String REDIS_PERSONAL_FLAG_PREFIX = "dataPermission:personal:";
+    /** 缓存过期时间:2小时 */
+    private static final long CACHE_EXPIRE = 2 * 60 * 60L;
+
+    @Autowired
+    private KwsDataPermissionDao kwsDataPermissionDao;
+
+    @Autowired
+    private KwsRoleDao kwsRoleDao;
+
+    @Autowired
+    private KwsEnterpriseDao kwsEnterpriseDao;
+
+    /**
+     * 保存/更新角色数据权限(先删后插)
+     *
+     * @param reqVo 数据权限请求
+     */
+    @Transactional(rollbackFor = Exception.class)
+    public void saveDataPermission(DataPermissionReqVo reqVo) {
+        Long roleId = reqVo.getRoleId();
+        KwsRole kwsRole = kwsRoleDao.selectByKey(roleId);
+        if (Objects.isNull(kwsRole)) {
+            throw new SystemException(HttpStatus.CRUD_FAIL_CODE, "角色不存在");
+        }
+
+        // 1. 更新角色的个人数据权限标识
+        KwsRole updateRole = new KwsRole();
+        updateRole.setId(roleId);
+        if (Objects.nonNull(reqVo.getPersonalDataFlag())) {
+            kwsRole.setPersonalDataFlag(reqVo.getPersonalDataFlag());
+            updateRole.setPersonalDataFlag(reqVo.getPersonalDataFlag());
+            kwsRoleDao.updateById(updateRole);
+        }
+
+        // 2. 逻辑删除旧的数据权限
+        kwsDataPermissionDao.deleteByRoleId(roleId);
+
+        // 3. 批量插入新的数据权限
+        List<Long> entIds = reqVo.getEntIds();
+        if (CollectionUtils.isNotEmpty(entIds)) {
+            Long userId = LoginUserHolder.getUserId();
+            Date now = new Date();
+            List<KwsDataPermission> permList = new ArrayList<>();
+            for (Long entId : entIds) {
+                KwsDataPermission perm = new KwsDataPermission();
+                perm.setId(new IdWorker(1L).nextId());
+                perm.setRoleId(roleId);
+                perm.setEntId(entId);
+                perm.setStatus(Global.NO);
+                perm.setDelFlag(Global.NO);
+                perm.setCreateBy(userId);
+                perm.setCreateTime(now);
+                perm.setUpdateBy(userId);
+                perm.setUpdateTime(now);
+                permList.add(perm);
+            }
+            if (kwsDataPermissionDao.saveBatch(permList) < permList.size()) {
+                throw new SystemException(HttpStatus.CRUD_FAIL_CODE, HttpStatus.INSERT_FAIL);
+            }
+        }
+
+        // 4. 清除该角色的数据权限缓存,使权限立即生效
+        clearCache(roleId);
+    }
+
+    /**
+     * 查询角色的数据权限配置
+     *
+     * @param roleId 角色ID
+     * @return 数据权限响应
+     */
+    public DataPermissionResVo getDataPermission(Long roleId) {
+        KwsRole kwsRole = kwsRoleDao.selectByKey(roleId);
+        if (Objects.isNull(kwsRole)) {
+            throw new SystemException(HttpStatus.QUERY_FAIL_CODE, "角色不存在");
+        }
+
+        DataPermissionResVo resVo = new DataPermissionResVo();
+        resVo.setRoleId(roleId);
+        resVo.setRoleName(kwsRole.getName());
+        resVo.setPersonalDataFlag(kwsRole.getPersonalDataFlag() != null ? kwsRole.getPersonalDataFlag() : Global.NO);
+
+        List<KwsDataPermission> permList = kwsDataPermissionDao.selectByRoleId(roleId);
+        if (CollectionUtils.isNotEmpty(permList)) {
+            List<Long> entIds = permList.stream().map(KwsDataPermission::getEntId).toList();
+            resVo.setEntIds(entIds);
+            // 查询企业名称
+            List<KwsEnterprise> entList = kwsEnterpriseDao.selectAllByKeys(entIds);
+            if (CollectionUtils.isNotEmpty(entList)) {
+                resVo.setEntNames(entList.stream().map(KwsEnterprise::getFirmName).toList());
+            }
+        } else {
+            resVo.setEntIds(new ArrayList<>());
+            resVo.setEntNames(new ArrayList<>());
+        }
+
+        return resVo;
+    }
+
+    /**
+     * 查询数据权限企业树
+     * 客户端:展示当前账号所属企业及下级企业
+     * 平台端:展示所有入驻企业
+     *
+     * @return 企业树
+     */
+    public List<DataPermissionEntTreeResVo> getEntTree() {
+        List<DataPermissionEntTreeResVo> treeList;
+
+        if (LoginUserHolder.isManager()) {
+            // 平台端:展示所有入驻企业
+            List<KwsEnterprise> allEnts = kwsEnterpriseDao.findAllEnterprise();
+            treeList = buildEntTree(allEnts, 0L);
+        } else {
+            // 客户端:展示当前企业及下级企业(递归CTE查询)
+            Long entId = LoginUserHolder.getEntId();
+            List<EntBaseInfo> entBaseInfos = kwsEnterpriseDao.queryCte(entId);
+            if (CollectionUtils.isEmpty(entBaseInfos)) {
+                return new ArrayList<>();
+            }
+            treeList = buildEntTreeFromCte(entBaseInfos, entId);
+        }
+
+        return treeList;
+    }
+
+    /**
+     * 根据角色ID列表级联删除数据权限(角色删除时调用)
+     *
+     * @param roleIds 角色ID列表
+     */
+    @Transactional(rollbackFor = Exception.class)
+    public void deleteByRoleIds(List<Long> roleIds) {
+        if (CollectionUtils.isEmpty(roleIds)) {
+            return;
+        }
+        kwsDataPermissionDao.deleteByRoleIds(roleIds);
+        // 清除所有相关角色的缓存
+        for (Long roleId : roleIds) {
+            clearCache(roleId);
+        }
+    }
+
+    // ==================== 缓存相关 ====================
+
+    /**
+     * 从缓存获取角色可访问的企业ID列表,缓存未命中则查库并回填
+     *
+     * @param roleId 角色ID
+     * @return 可访问的企业ID集合
+     */
+    public Set<Long> getCachedEntIds(Long roleId) {
+        String cacheKey = REDIS_DATA_PERM_PREFIX + roleId;
+        try {
+            Object cached = RedissonUtils.get(cacheKey);
+            if (Objects.nonNull(cached) && cached instanceof Set) {
+                return (Set<Long>) cached;
+            }
+        } catch (Exception e) {
+            log.warn("读取数据权限缓存异常, roleId={}", roleId, e);
+        }
+
+        // 缓存未命中,查库
+        List<KwsDataPermission> permList = kwsDataPermissionDao.selectByRoleId(roleId);
+        Set<Long> entIdSet = CollectionUtils.isEmpty(permList)
+                ? Collections.emptySet()
+                : permList.stream().map(KwsDataPermission::getEntId).collect(Collectors.toSet());
+
+        try {
+            RedissonUtils.put(cacheKey, entIdSet, CACHE_EXPIRE);
+        } catch (Exception e) {
+            log.warn("写入数据权限缓存异常, roleId={}", roleId, e);
+        }
+        return entIdSet;
+    }
+
+    /**
+     * 从缓存获取角色的个人数据权限标识
+     *
+     * @param roleId 角色ID
+     * @return true=开启个人数据权限
+     */
+    public boolean getCachedPersonalFlag(Long roleId) {
+        String cacheKey = REDIS_PERSONAL_FLAG_PREFIX + roleId;
+        try {
+            Object cached = RedissonUtils.get(cacheKey);
+            if (Objects.nonNull(cached)) {
+                return Objects.equals(cached, Global.YES);
+            }
+        } catch (Exception e) {
+            log.warn("读取个人数据权限缓存异常, roleId={}", roleId, e);
+        }
+
+        KwsRole kwsRole = kwsRoleDao.selectByKey(roleId);
+        int flag = (kwsRole != null && kwsRole.getPersonalDataFlag() != null) ? kwsRole.getPersonalDataFlag() : Global.NO;
+        try {
+            RedissonUtils.put(cacheKey, flag, CACHE_EXPIRE);
+        } catch (Exception e) {
+            log.warn("写入个人数据权限缓存异常, roleId={}", roleId, e);
+        }
+        return Objects.equals(flag, Global.YES);
+    }
+
+    /**
+     * 清除角色数据权限缓存
+     */
+    public void clearCache(Long roleId) {
+        try {
+            RedissonUtils.delete(REDIS_DATA_PERM_PREFIX + roleId);
+            RedissonUtils.delete(REDIS_PERSONAL_FLAG_PREFIX + roleId);
+        } catch (Exception e) {
+            log.warn("清除数据权限缓存异常, roleId={}", roleId, e);
+        }
+    }
+
+    // ==================== 私有方法 ====================
+
+    /**
+     * 构建企业树(平台端,从全量企业列表构建)
+     */
+    private List<DataPermissionEntTreeResVo> buildEntTree(List<KwsEnterprise> entList, Long rootPid) {
+        if (CollectionUtils.isEmpty(entList)) {
+            return new ArrayList<>();
+        }
+        Map<Long, List<KwsEnterprise>> pidMap = entList.stream()
+                .collect(Collectors.groupingBy(e -> e.getPid() != null ? e.getPid() : 0L));
+
+        return buildChildren(pidMap, rootPid);
+    }
+
+    private List<DataPermissionEntTreeResVo> buildChildren(Map<Long, List<KwsEnterprise>> pidMap, Long parentId) {
+        List<KwsEnterprise> children = pidMap.get(parentId);
+        if (CollectionUtils.isEmpty(children)) {
+            return null;
+        }
+        List<DataPermissionEntTreeResVo> result = new ArrayList<>();
+        for (KwsEnterprise ent : children) {
+            DataPermissionEntTreeResVo node = new DataPermissionEntTreeResVo();
+            node.setId(ent.getId());
+            node.setPid(ent.getPid());
+            node.setFirmName(ent.getFirmName());
+            node.setChildren(buildChildren(pidMap, ent.getId()));
+            result.add(node);
+        }
+        return result;
+    }
+
+    /**
+     * 构建企业树(客户端,从CTE递归结果构建)
+     */
+    private List<DataPermissionEntTreeResVo> buildEntTreeFromCte(List<EntBaseInfo> entBaseInfos, Long rootEntId) {
+        Map<Long, List<EntBaseInfo>> pidMap = entBaseInfos.stream()
+                .collect(Collectors.groupingBy(e -> e.getPid() != null ? e.getPid() : 0L));
+
+        // 根节点是当前企业
+        List<DataPermissionEntTreeResVo> result = new ArrayList<>();
+        for (EntBaseInfo info : entBaseInfos) {
+            if (Objects.equals(info.getId(), rootEntId)) {
+                DataPermissionEntTreeResVo root = new DataPermissionEntTreeResVo();
+                root.setId(info.getId());
+                root.setPid(info.getPid());
+                root.setFirmName(info.getFirmName());
+                root.setChildren(buildCteChildren(pidMap, info.getId()));
+                result.add(root);
+                break;
+            }
+        }
+        return result;
+    }
+
+    private List<DataPermissionEntTreeResVo> buildCteChildren(Map<Long, List<EntBaseInfo>> pidMap, Long parentId) {
+        List<EntBaseInfo> children = pidMap.get(parentId);
+        if (CollectionUtils.isEmpty(children)) {
+            return null;
+        }
+        List<DataPermissionEntTreeResVo> result = new ArrayList<>();
+        for (EntBaseInfo info : children) {
+            DataPermissionEntTreeResVo node = new DataPermissionEntTreeResVo();
+            node.setId(info.getId());
+            node.setPid(info.getPid());
+            node.setFirmName(info.getFirmName());
+            node.setChildren(buildCteChildren(pidMap, info.getId()));
+            result.add(node);
+        }
+        return result;
+    }
+
+}

+ 36 - 0
sckw-modules/sckw-system/src/main/java/com/sckw/system/service/KwsMenuService.java

@@ -309,6 +309,10 @@ public class KwsMenuService {
             finalList = menuList;
         }
 
+        if (Boolean.TRUE.equals(reqVo.getIncludeButton())) {
+            finalList = supplementButtons(finalList);
+        }
+
         List<KwsMenuResVo> rootList = new ArrayList<>();
 
         //获取根节点数据
@@ -366,6 +370,38 @@ public class KwsMenuService {
         findMenuTreePojo.setRoleIds(Collections.singletonList(LoginUserHolder.getCurrentRoleId()));
     }
 
+    /**
+     * 补充按钮节点到菜单列表中
+     * <p>
+     * 查询所有菜单类型节点(type=1)的子按钮(type=2),
+     * 合并到列表中以便递归构建包含按钮的完整树。
+     */
+    private List<KwsMenuResVo> supplementButtons(List<KwsMenuResVo> menuList) {
+        List<Long> menuTypeIds = menuList.stream()
+                .filter(m -> Objects.equals(m.getType(), MenuTypeEnum.DIRECTORY.getCode()))
+                .map(KwsMenuResVo::getId)
+                .toList();
+        if (CollectionUtils.isEmpty(menuTypeIds)) {
+            return menuList;
+        }
+
+        List<KwsMenu> buttons = kwsMenuDao.selectByParentIds(menuTypeIds, MenuTypeEnum.BUTTON.getCode());
+        if (CollectionUtils.isEmpty(buttons)) {
+            return menuList;
+        }
+
+        Set<Long> existingIds = menuList.stream().map(KwsMenuResVo::getId).collect(Collectors.toSet());
+        List<KwsMenuResVo> result = new ArrayList<>(menuList);
+        for (KwsMenu btn : buttons) {
+            if (!existingIds.contains(btn.getId())) {
+                KwsMenuResVo vo = new KwsMenuResVo();
+                BeanUtils.copyProperties(btn, vo);
+                result.add(vo);
+            }
+        }
+        return result;
+    }
+
     /**
      * 递归获取下级菜单
      *

+ 259 - 0
sckw-modules/sckw-system/src/main/java/com/sckw/system/utils/DataPermissionHelper.java

@@ -0,0 +1,259 @@
+package com.sckw.system.utils;
+
+import com.sckw.core.utils.CollectionUtils;
+import com.sckw.core.web.context.LoginUserHolder;
+import com.sckw.system.api.model.dto.res.DataPermissionDTO;
+import com.sckw.system.model.KwsRole;
+import com.sckw.system.service.KwsDataPermissionService;
+import com.sckw.system.service.KwsRoleService;
+import lombok.Data;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.util.*;
+
+/**
+ * 数据权限过滤工具类
+ * <p>
+ * 用于业务查询拦截,在查询贸易合同/订单/运单等业务数据时,
+ * 根据当前用户角色的数据权限配置过滤可见数据范围。
+ * <p>
+ * 企业数据权限 + 个人数据权限为并集(组合)关系:
+ * <ul>
+ *     <li>企业数据权限:限制可见企业范围 → SQL: ent_id IN (已选企业)</li>
+ *     <li>个人数据权限开启后:在企业范围内仅查看销售人员=本人的数据
+ *         → SQL: ent_id IN (已选企业) AND salesman_id = 当前用户</li>
+ *     <li>覆盖范围:贸易合同及其衍生的贸易订单、物流订单、运单</li>
+ * </ul>
+ * <p>
+ *
+ * @author system
+ * @date 2026-03-26
+ */
+@Slf4j
+@Component
+public class DataPermissionHelper {
+
+    @Autowired
+    private KwsDataPermissionService kwsDataPermissionService;
+
+    @Autowired
+    private KwsRoleService kwsRoleService;
+
+    /**
+     * 获取当前登录用户的数据权限过滤条件
+     * <p>
+     * 规则:
+     * - 平台端管理员:仅管理平台自身创建的角色,数据权限按角色配置
+     * - 客户端主账号:可管理本企业+下级企业的角色
+     * - 所有用户:企业数据权限 + 个人数据权限为并集关系
+     * - 个人数据权限开启后:在已勾选企业范围内,仅查看销售人员=当前用户的数据
+     *
+     * @return 数据权限过滤条件
+     */
+    public DataPermissionFilter getPermissionFilter() {
+        DataPermissionFilter filter = new DataPermissionFilter();
+        Long userId = LoginUserHolder.getUserId();
+        filter.setUserId(userId);
+
+        // 平台管理员默认可见全部
+        if (LoginUserHolder.isManager()) {
+            Long roleId = LoginUserHolder.getCurrentRoleId();
+            if (Objects.isNull(roleId)) {
+                filter.setAllVisible(true);
+                return filter;
+            }
+            return buildFilterByRole(roleId, userId);
+        }
+
+        // 客户端用户:获取当前使用的角色
+        Long roleId = LoginUserHolder.getCurrentRoleId();
+        if (Objects.isNull(roleId)) {
+            // 无角色,查询用户所有角色取并集
+            List<KwsRole> roles = kwsRoleService.queryRoleByUserId(userId);
+            if (CollectionUtils.isEmpty(roles)) {
+                filter.setAllVisible(false);
+                filter.setVisibleEntIds(Collections.emptySet());
+                return filter;
+            }
+            return buildFilterByRoles(roles, userId);
+        }
+
+        return buildFilterByRole(roleId, userId);
+    }
+
+    /**
+     * 根据单个角色构建过滤条件
+     */
+    private DataPermissionFilter buildFilterByRole(Long roleId, Long userId) {
+        DataPermissionFilter filter = new DataPermissionFilter();
+        filter.setUserId(userId);
+
+        Set<Long> entIds = kwsDataPermissionService.getCachedEntIds(roleId);
+        boolean personalFlag = kwsDataPermissionService.getCachedPersonalFlag(roleId);
+
+        if (CollectionUtils.isEmpty(entIds)) {
+            // 未配置企业数据权限,默认可见全部(兼容旧数据)
+            filter.setAllVisible(true);
+        } else {
+            filter.setAllVisible(false);
+            filter.setVisibleEntIds(entIds);
+        }
+
+        filter.setPersonalDataEnabled(personalFlag);
+        return filter;
+    }
+
+    /**
+     * 根据多个角色构建过滤条件(取并集)
+     */
+    private DataPermissionFilter buildFilterByRoles(List<KwsRole> roles, Long userId) {
+        DataPermissionFilter filter = new DataPermissionFilter();
+        filter.setUserId(userId);
+
+        Set<Long> allEntIds = new HashSet<>();
+        boolean anyPersonalFlag = false;
+
+        for (KwsRole role : roles) {
+            Set<Long> entIds = kwsDataPermissionService.getCachedEntIds(role.getId());
+            if (CollectionUtils.isNotEmpty(entIds)) {
+                allEntIds.addAll(entIds);
+            }
+            if (kwsDataPermissionService.getCachedPersonalFlag(role.getId())) {
+                anyPersonalFlag = true;
+            }
+        }
+
+        if (allEntIds.isEmpty()) {
+            filter.setAllVisible(true);
+        } else {
+            filter.setAllVisible(false);
+            filter.setVisibleEntIds(allEntIds);
+        }
+
+        filter.setPersonalDataEnabled(anyPersonalFlag);
+        return filter;
+    }
+
+    /**
+     * 将过滤条件应用到查询参数Map中
+     * <p>
+     * 业务Mapper XML中使用:
+     * <pre>
+     * &lt;if test="dataPermEntIds != null and dataPermEntIds.size() > 0"&gt;
+     *     AND ent_id IN
+     *     &lt;foreach collection="dataPermEntIds" item="item" open="(" close=")" separator=","&gt;
+     *         #{item}
+     *     &lt;/foreach&gt;
+     * &lt;/if&gt;
+     * &lt;if test="dataPermUserId != null"&gt;
+     *     AND salesman_id = #{dataPermUserId}
+     * &lt;/if&gt;
+     * </pre>
+     *
+     * @param params 查询参数Map
+     */
+    public void applyFilter(Map<String, Object> params) {
+        DataPermissionFilter filter = getPermissionFilter();
+
+        if (filter.isAllVisible() && !filter.isPersonalDataEnabled()) {
+            return;
+        }
+
+        if (!filter.isAllVisible() && CollectionUtils.isNotEmpty(filter.getVisibleEntIds())) {
+            params.put("dataPermEntIds", new ArrayList<>(filter.getVisibleEntIds()));
+        }
+
+        if (filter.isPersonalDataEnabled()) {
+            params.put("dataPermUserId", filter.getUserId());
+        }
+    }
+
+    /**
+     * 判断当前用户是否有权查看指定企业的数据
+     *
+     * @param targetEntId 目标企业ID
+     * @return true=有权限
+     */
+    public boolean hasEntPermission(Long targetEntId) {
+        DataPermissionFilter filter = getPermissionFilter();
+        if (filter.isAllVisible()) {
+            return true;
+        }
+        return CollectionUtils.isNotEmpty(filter.getVisibleEntIds())
+                && filter.getVisibleEntIds().contains(targetEntId);
+    }
+
+    // ==================== 跨模块调用支持(Dubbo) ====================
+
+    /**
+     * 根据显式参数获取数据权限过滤条件(不依赖LoginUserHolder,适用于Feign远程调用)
+     *
+     * @param userId    用户ID
+     * @param roleId    当前使用的角色ID,null时取用户所有角色的并集
+     * @param isManager 是否平台管理员
+     * @return 数据权限过滤条件
+     */
+    public DataPermissionFilter getPermissionFilter(Long userId, Long roleId, boolean isManager) {
+        DataPermissionFilter filter = new DataPermissionFilter();
+        filter.setUserId(userId);
+
+        if (isManager) {
+            if (Objects.isNull(roleId)) {
+                filter.setAllVisible(true);
+                return filter;
+            }
+            return buildFilterByRole(roleId, userId);
+        }
+
+        if (Objects.isNull(roleId)) {
+            List<KwsRole> roles = kwsRoleService.queryRoleByUserId(userId);
+            if (CollectionUtils.isEmpty(roles)) {
+                filter.setAllVisible(false);
+                filter.setVisibleEntIds(Collections.emptySet());
+                return filter;
+            }
+            return buildFilterByRoles(roles, userId);
+        }
+
+        return buildFilterByRole(roleId, userId);
+    }
+
+    /**
+     * 构建数据权限DTO(供Feign远程接口返回给其他模块)
+     * <p>
+     * 其他模块通过 DataPermissionFeignService.getDataPermissionFilter() 间接调用本方法,
+     * 获取可序列化的 DataPermissionDTO,然后调用 dto.applyTo(params) 注入查询参数。
+     *
+     * @param userId    用户ID
+     * @param roleId    当前使用的角色ID
+     * @param isManager 是否平台管理员
+     * @return 可序列化的数据权限过滤DTO
+     */
+    public DataPermissionDTO buildPermissionDTO(Long userId, Long roleId, boolean isManager) {
+        DataPermissionFilter filter = getPermissionFilter(userId, roleId, isManager);
+        DataPermissionDTO dto = new DataPermissionDTO();
+        dto.setUserId(filter.getUserId());
+        dto.setAllVisible(filter.isAllVisible());
+        dto.setVisibleEntIds(filter.getVisibleEntIds());
+        dto.setPersonalDataEnabled(filter.isPersonalDataEnabled());
+        return dto;
+    }
+
+    /**
+     * 数据权限过滤条件
+     */
+    @Data
+    public static class DataPermissionFilter {
+        /** 当前用户ID */
+        private Long userId;
+        /** 是否可见全部数据(未配置数据权限时为true) */
+        private boolean allVisible;
+        /** 可见的企业ID集合 */
+        private Set<Long> visibleEntIds;
+        /** 是否开启个人数据权限(仅查看本人作为销售的数据) */
+        private boolean personalDataEnabled;
+    }
+
+}

+ 122 - 0
sckw-modules/sckw-system/src/main/resources/mapper/KwsDataPermissionDao.xml

@@ -0,0 +1,122 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.sckw.system.dao.KwsDataPermissionDao">
+
+    <resultMap id="BaseResultMap" type="com.sckw.system.model.KwsDataPermission">
+        <id column="id" jdbcType="BIGINT" property="id"/>
+        <result column="role_id" jdbcType="BIGINT" property="roleId"/>
+        <result column="ent_id" jdbcType="BIGINT" property="entId"/>
+        <result column="remark" jdbcType="VARCHAR" property="remark"/>
+        <result column="status" jdbcType="INTEGER" property="status"/>
+        <result column="create_by" jdbcType="BIGINT" property="createBy"/>
+        <result column="create_time" jdbcType="TIMESTAMP" property="createTime"/>
+        <result column="update_by" jdbcType="BIGINT" property="updateBy"/>
+        <result column="update_time" jdbcType="TIMESTAMP" property="updateTime"/>
+        <result column="del_flag" jdbcType="INTEGER" property="delFlag"/>
+    </resultMap>
+
+    <sql id="Base_Column_List">
+        id, role_id, ent_id, remark, status, create_by, create_time, update_by, update_time, del_flag
+    </sql>
+
+    <select id="selectByRoleId" resultMap="BaseResultMap">
+        select
+        <include refid="Base_Column_List"/>
+        from kws_data_permission
+        where del_flag = 0
+          and status = 0
+          and role_id = #{roleId}
+    </select>
+
+    <select id="selectByRoleIds" resultMap="BaseResultMap">
+        select
+        <include refid="Base_Column_List"/>
+        from kws_data_permission
+        where del_flag = 0
+          and status = 0
+          and role_id in
+        <foreach collection="list" item="item" open="(" close=")" separator=",">
+            #{item}
+        </foreach>
+    </select>
+
+    <insert id="insert" parameterType="com.sckw.system.model.KwsDataPermission">
+        insert into kws_data_permission
+        <trim prefix="(" suffix=")" suffixOverrides=",">
+            <if test="id != null">id,</if>
+            <if test="roleId != null">role_id,</if>
+            <if test="entId != null">ent_id,</if>
+            <if test="remark != null">remark,</if>
+            <if test="status != null">status,</if>
+            <if test="createBy != null">create_by,</if>
+            <if test="createTime != null">create_time,</if>
+            <if test="updateBy != null">update_by,</if>
+            <if test="updateTime != null">update_time,</if>
+            <if test="delFlag != null">del_flag,</if>
+        </trim>
+        <trim prefix="values (" suffix=")" suffixOverrides=",">
+            <if test="id != null">#{id,jdbcType=BIGINT},</if>
+            <if test="roleId != null">#{roleId,jdbcType=BIGINT},</if>
+            <if test="entId != null">#{entId,jdbcType=BIGINT},</if>
+            <if test="remark != null">#{remark,jdbcType=VARCHAR},</if>
+            <if test="status != null">#{status,jdbcType=INTEGER},</if>
+            <if test="createBy != null">#{createBy,jdbcType=BIGINT},</if>
+            <if test="createTime != null">#{createTime,jdbcType=TIMESTAMP},</if>
+            <if test="updateBy != null">#{updateBy,jdbcType=BIGINT},</if>
+            <if test="updateTime != null">#{updateTime,jdbcType=TIMESTAMP},</if>
+            <if test="delFlag != null">#{delFlag,jdbcType=INTEGER},</if>
+        </trim>
+    </insert>
+
+    <insert id="saveBatch">
+        insert into kws_data_permission
+        (id, role_id, ent_id, remark, status, create_by, create_time, update_by, update_time, del_flag)
+        values
+        <foreach collection="list" item="item" separator=",">
+            (#{item.id,jdbcType=BIGINT},
+             #{item.roleId,jdbcType=BIGINT},
+             #{item.entId,jdbcType=BIGINT},
+             #{item.remark,jdbcType=VARCHAR},
+             #{item.status,jdbcType=INTEGER},
+             #{item.createBy,jdbcType=BIGINT},
+             #{item.createTime,jdbcType=TIMESTAMP},
+             #{item.updateBy,jdbcType=BIGINT},
+             #{item.updateTime,jdbcType=TIMESTAMP},
+             #{item.delFlag,jdbcType=INTEGER})
+        </foreach>
+    </insert>
+
+    <update id="update" parameterType="com.sckw.system.model.KwsDataPermission">
+        update kws_data_permission
+        <set>
+            <if test="roleId != null">role_id = #{roleId,jdbcType=BIGINT},</if>
+            <if test="entId != null">ent_id = #{entId,jdbcType=BIGINT},</if>
+            <if test="remark != null">remark = #{remark,jdbcType=VARCHAR},</if>
+            <if test="status != null">status = #{status,jdbcType=INTEGER},</if>
+            <if test="createBy != null">create_by = #{createBy,jdbcType=BIGINT},</if>
+            <if test="createTime != null">create_time = #{createTime,jdbcType=TIMESTAMP},</if>
+            <if test="updateBy != null">update_by = #{updateBy,jdbcType=BIGINT},</if>
+            <if test="updateTime != null">update_time = #{updateTime,jdbcType=TIMESTAMP},</if>
+            <if test="delFlag != null">del_flag = #{delFlag,jdbcType=INTEGER},</if>
+        </set>
+        where id = #{id,jdbcType=BIGINT}
+    </update>
+
+    <update id="deleteByRoleId">
+        update kws_data_permission
+        set del_flag = 1
+        where role_id = #{roleId}
+          and del_flag = 0
+    </update>
+
+    <update id="deleteByRoleIds">
+        update kws_data_permission
+        set del_flag = 1
+        where del_flag = 0
+          and role_id in
+        <foreach collection="list" item="item" open="(" close=")" separator=",">
+            #{item}
+        </foreach>
+    </update>
+
+</mapper>

+ 13 - 0
sckw-modules/sckw-system/src/main/resources/mapper/KwsMenuDao.xml

@@ -370,6 +370,19 @@
     order by sort
   </select>
 
+  <select id="selectByParentIds" resultType="com.sckw.system.model.KwsMenu">
+    select * from kws_menu
+    where del_flag = 0
+      and parent_id in
+    <foreach collection="parentIds" item="item" open="(" close=")" separator=",">
+      #{item}
+    </foreach>
+    <if test="type != null">
+      and type = #{type}
+    </if>
+    order by sort
+  </select>
+
   <select id="queryChildMenu" resultType="com.sckw.system.model.KwsMenu">
     select *
     from kws_menu

+ 7 - 8
sckw-modules/sckw-transport/src/main/java/com/sckw/transport/service/KwtAcceptCarriageOrderService.java

@@ -3192,9 +3192,7 @@ public class KwtAcceptCarriageOrderService {
     public Integer addLogisticOrder(AddLogisticOrderDTO orderDTO) {
         log.info("创建物流订单请求参数:{}",JSON.toJSONString(orderDTO));
         commonService.getRedisGenerateOrderNo();
-        /**生成订单编号*/
-        String lOrderNo = OrderGenerateSeqNoUtils.getSeqNo(RedisOrderGenerateEnum.LOGISTICS_ORDER, OrderRuleEnum.LOGISTICS_ORDER, "2", "0", "0");
-        log.info("生成物流订单编号:{}", lOrderNo);
+
         /**保存新建数据*/
 
         List<LogisticData> logisticInfo = orderDTO.getLogisticInfo();
@@ -3228,6 +3226,9 @@ public class KwtAcceptCarriageOrderService {
         Integer dispatched = 0;
         for (LogisticData x : logisticInfo) {
             Long lOrderId = new IdWorker(NumberConstant.ONE).nextId();
+            /**生成订单编号*/
+            String lOrderNo = OrderGenerateSeqNoUtils.getSeqNo(RedisOrderGenerateEnum.LOGISTICS_ORDER, OrderRuleEnum.LOGISTICS_ORDER, "2", "0", "0");
+            log.info("生成物流订单编号:{}", lOrderNo);
             //自动派单
             if (Objects.equals(DispatchWayEnums.AUTO_DISPATCH.getCode(), orderDTO.getDispatchWay())) {
                 // 剩余需派数
@@ -3287,7 +3288,7 @@ public class KwtAcceptCarriageOrderService {
         }
 
         // 批量保存并检查结果
-        saveBatch(saveLogisticsOrderList, saveAddressList, savelogOrderGoodsList, saveContractList, savelogOrderUnitList, savelogOrderCirculateList, lOrderNo);
+        saveBatch(saveLogisticsOrderList, saveAddressList, savelogOrderGoodsList, saveContractList, savelogOrderUnitList, savelogOrderCirculateList);
 
         //校验派车总量,处理未达标场景
         int unDispatched = actualDisPatch - dispatched;
@@ -3398,8 +3399,7 @@ public class KwtAcceptCarriageOrderService {
 
     private void saveBatch(List<KwtLogisticsOrder> saveLogisticsOrderList, List<KwtLogisticsOrderAddress> saveAddressList,
                            List<KwtLogisticsOrderGoods> savelogOrderGoodsList, List<KwtLogisticsOrderContract> saveContractList,
-                           List<KwtLogisticsOrderUnit> savelogOrderUnitList, List<KwtLogisticsOrderCirculate> savelogOrderCirculateList,
-                           String lOrderNo) {
+                           List<KwtLogisticsOrderUnit> savelogOrderUnitList, List<KwtLogisticsOrderCirculate> savelogOrderCirculateList) {
         log.info("开始批量保存物流订单数据,订单数量:{},地址数量:{},商品数量:{},合同数量:{},单位数量:{}, 派车数量:{}",
                 saveLogisticsOrderList.size(), saveAddressList.size(), savelogOrderGoodsList.size(),
                 saveContractList.size(), savelogOrderUnitList.size(), savelogOrderCirculateList.size());
@@ -3462,9 +3462,8 @@ public class KwtAcceptCarriageOrderService {
                 }
             }
 
-            log.info("物流订单批量保存成功,订单编号:{}", lOrderNo);
         } catch (Exception e) {
-            log.error("物流订单批量保存失败,订单编号:{},错误信息:{}", lOrderNo, e.getMessage(), e);
+            log.error("物流订单批量保存失败,错误信息:{}", e.getMessage(), e);
             throw new BusinessException("物流订单保存失败:" + e.getMessage());
         }
     }

+ 27 - 0
sql/2026/03/2026_03_26_data_permission_create.sql

@@ -0,0 +1,27 @@
+-- ============================================================
+-- 数据权限配置表 kws_data_permission
+-- 角色级企业数据权限,记录角色可访问的企业ID列表
+-- ============================================================
+CREATE TABLE IF NOT EXISTS `kws_data_permission` (
+    `id`          BIGINT       NOT NULL COMMENT '主键',
+    `role_id`     BIGINT       NOT NULL COMMENT '角色ID',
+    `ent_id`      BIGINT       NOT NULL COMMENT '企业ID(可访问的企业)',
+    `remark`      VARCHAR(200)          DEFAULT NULL COMMENT '备注',
+    `status`      INT                   DEFAULT 0 COMMENT '状态:0正常 1停用',
+    `create_by`   BIGINT                DEFAULT NULL COMMENT '创建人',
+    `create_time` DATETIME              DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    `update_by`   BIGINT                DEFAULT NULL COMMENT '更新人',
+    `update_time` DATETIME              DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+    `del_flag`    INT                   DEFAULT 0 COMMENT '删除标识:0正常 1删除',
+    PRIMARY KEY (`id`),
+    KEY `idx_role_id` (`role_id`),
+    KEY `idx_ent_id` (`ent_id`),
+    KEY `idx_role_ent` (`role_id`, `ent_id`)
+) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COMMENT = '角色数据权限(企业维度)';
+
+-- ============================================================
+-- kws_role 表新增个人数据权限标识字段
+-- personal_data_flag: 0=关闭 1=开启(开启后仅查看本人作为销售的数据)
+-- ============================================================
+ALTER TABLE `kws_role`
+    ADD COLUMN `personal_data_flag` INT DEFAULT 0 COMMENT '个人数据权限:0关闭 1开启' AFTER `admin_flag`;