Эх сурвалжийг харах

提交630贸易订单物流运单物流订单权限过滤相关逻辑

chenxiaofei 2 долоо хоног өмнө
parent
commit
ad3de76da1

+ 8 - 0
sckw-modules-api/sckw-contract-api/src/main/java/com/sckw/contract/api/RemoteContractService.java

@@ -148,6 +148,14 @@ public interface RemoteContractService {
     Long queryLogisticsCount(Long entId,Integer type);
     ContractCountVo contractCount();
 
+    /**
+     * 根据供应商企业ID查询代理企业ID集合。
+     *
+     * @param supplyEntId 供应商企业ID
+     * @return 代理企业ID集合
+     */
+    List<Long> queryProxyEntIdsBySupplyId(Long supplyEntId);
+
     List<ContractVo> tradeList();
 
     List<ContractVo> logisticsList();

+ 64 - 0
sckw-modules/sckw-contract/src/main/java/com/sckw/contract/dubbo/RemoteContractServiceImpl.java

@@ -10,6 +10,8 @@ import com.sckw.contract.api.model.dto.req.ContractAuditPara;
 import com.sckw.contract.api.model.dto.res.*;
 import com.sckw.contract.api.model.vo.*;
 import com.sckw.contract.dao.*;
+import com.sckw.contract.model.KwcContractProxy;
+import com.sckw.contract.model.KwcContractProxyUnit;
 import com.sckw.contract.model.dto.res.QueryContractValidCountResDto;
 import com.sckw.contract.model.entity.*;
 import com.sckw.contract.model.vo.req.ContractDetailReq;
@@ -87,6 +89,8 @@ public class RemoteContractServiceImpl implements RemoteContractService {
     private final KwcContractTradeService kwcContractTradeService;
     private final KwcContractLogisticsScoreRepository contractLogisticsScoreRepository;
     private final KwcContractLogisticsService kwcContractLogisticsService;
+    private final KwcContractProxyRepository kwcContractProxyRepository;
+    private final KwcContractProxyUnitRepository kwcContractProxyUnitRepository;
 
     @Override
     public Map<Long, ContractCommonInfoResDto> queryContractBaseInfo(List<Long> contractIds) {
@@ -547,6 +551,66 @@ public class RemoteContractServiceImpl implements RemoteContractService {
         return contractCountVo;
     }
 
+
+    /**
+     * 根据供应商企业ID查询关联的代理企业ID集合。
+     * <p>
+     * 业务逻辑说明:
+     * 1. 根据供应商ID查询所有的代理合同(KwcContractProxy)。
+     * 2. 提取这些代理合同的ID集合。
+     * 3. 根据合同ID集合和单位类型(unitType=2,通常代表代理方/被委托方),查询对应的合同单元(KwcContractProxyUnit)。
+     * 4. 从合同单元中提取企业ID(EntId),去重后返回。
+     *
+     * @param supplyEntId 供应商企业ID
+     * @return 代理企业ID列表,若未找到则返回空列表
+     */
+    @Override
+    public List<Long> queryProxyEntIdsBySupplyId(Long supplyEntId) {
+        // 参数校验:供应商ID不能为空
+        if (Objects.isNull(supplyEntId)) {
+            log.warn("查询代理企业ID失败,输入参数供应商企业ID为空");
+            return Collections.emptyList();
+        }
+
+        log.debug("开始查询供应商[{}]对应的代理企业ID", supplyEntId);
+
+        // 第一步:根据供应商ID查询代理合同列表
+        List<KwcContractProxy> proxyContracts = kwcContractProxyRepository.queryBySupplyId(supplyEntId);
+        if (CollectionUtils.isEmpty(proxyContracts)) {
+            log.debug("供应商[{}]未找到任何关联的代理合同", supplyEntId);
+            return Collections.emptyList();
+        }
+
+        // 提取代理合同ID集合,用于后续查询合同单元
+        Set<Long> contractIds = proxyContracts.stream()
+                .map(KwcContractProxy::getId)
+                .filter(Objects::nonNull)
+                .collect(Collectors.toSet());
+
+        if (CollectionUtils.isEmpty(contractIds)) {
+            log.warn("供应商[{}]关联的代理合同ID提取为空", supplyEntId);
+            return Collections.emptyList();
+        }
+
+        // 第二步:根据合同ID集合和单位类型(2-代理方)查询合同单元
+        // 注意:这里的硬编码 2 建议后续改为枚举或常量,此处保持原逻辑
+        List<KwcContractProxyUnit> proxyUnits = kwcContractProxyUnitRepository.queryByContractIdsAndUnitType(contractIds, 2);
+        if (CollectionUtils.isEmpty(proxyUnits)) {
+            log.debug("供应商[{}]对应的代理合同[{}]中未找到类型为2的合同单元", supplyEntId, contractIds);
+            return Collections.emptyList();
+        }
+
+        // 第三步:提取企业ID,过滤空值并去重
+        List<Long> proxyEntIds = proxyUnits.stream()
+                .map(KwcContractProxyUnit::getEntId)
+                .filter(Objects::nonNull)
+                .distinct()
+                .collect(Collectors.toList());
+
+        log.info("成功查询到供应商[{}]对应的{}个代理企业ID", supplyEntId, proxyEntIds.size());
+        return proxyEntIds;
+    }
+
     @Override
     public List<ContractVo> tradeList() {
         List<Long> ids = new ArrayList<>();

+ 12 - 0
sckw-modules/sckw-contract/src/main/java/com/sckw/contract/repository/KwcContractProxyRepository.java

@@ -59,4 +59,16 @@ public class KwcContractProxyRepository extends ServiceImpl<KwcContractProxyMapp
                 .eq(KwcContractProxy::getDelFlag, 0)
                 .in(KwcContractProxy::getId, ids));
     }
+
+    /**
+     * 根据供应商企业ID查询代理合同。
+     *
+     * @param supplyId 供应商企业ID
+     * @return 代理合同集合
+     */
+    public List<KwcContractProxy> queryBySupplyId(Long supplyId) {
+        return list(Wrappers.<KwcContractProxy>lambdaQuery()
+                .eq(KwcContractProxy::getDelFlag, 0)
+                .eq(KwcContractProxy::getSupplyId, supplyId));
+    }
 }

+ 17 - 0
sckw-modules/sckw-contract/src/main/java/com/sckw/contract/repository/KwcContractProxyUnitRepository.java

@@ -25,6 +25,23 @@ public class KwcContractProxyUnitRepository extends ServiceImpl<KwcContractProxy
                 .in(KwcContractProxyUnit::getContractId, contractIds));
     }
 
+    /**
+     * 根据代理合同ID集合和单位类型查询企业信息。
+     *
+     * @param contractIds 代理合同ID集合
+     * @param unitType 单位类型
+     * @return 代理合同企业信息集合
+     */
+    public List<KwcContractProxyUnit> queryByContractIdsAndUnitType(Set<Long> contractIds, Integer unitType) {
+        if (contractIds == null || contractIds.isEmpty()) {
+            return List.of();
+        }
+        return list(Wrappers.<KwcContractProxyUnit>lambdaQuery()
+                .eq(KwcContractProxyUnit::getDelFlag, 0)
+                .in(KwcContractProxyUnit::getContractId, contractIds)
+                .eq(KwcContractProxyUnit::getUnitType, unitType));
+    }
+
     @Transactional(rollbackFor = Exception.class)
     public void saveUnits(List<KwcContractProxyUnit> units) {
         saveBatch(units);

+ 13 - 0
sckw-modules/sckw-order/src/main/java/com/sckw/order/model/dto/TradeOrderListSelectDTO.java

@@ -1,5 +1,6 @@
 package com.sckw.order.model.dto;
 
+import com.fasterxml.jackson.annotation.JsonIgnore;
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Getter;
 import lombok.Setter;
@@ -155,4 +156,16 @@ public class TradeOrderListSelectDTO {
      */
     @Schema(description = "是否代理属性:0-否,1-是")
     private Integer agentFlag;
+
+    /**
+     * 代理关系可见供应企业ID集合:代理商仅包含自身,供应商包含自身及代理商。
+     */
+    @JsonIgnore
+    private List<Long> proxyScopeEntIds;
+
+    /**
+     * 代理关系过滤是否强制返回空结果。
+     */
+    @JsonIgnore
+    private Boolean proxyScopeForceEmpty;
 }

+ 110 - 11
sckw-modules/sckw-order/src/main/java/com/sckw/order/serivce/KwoTradeOrderService.java

@@ -107,6 +107,7 @@ import java.time.temporal.TemporalAdjusters;
 import java.util.*;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.function.Function;
+import java.util.function.Supplier;
 import java.util.stream.Collectors;
 
 /**
@@ -123,6 +124,10 @@ public class KwoTradeOrderService {
      * 供应单位代理属性类型。
      */
     static final int SUPPLY_AGENT_ENT_TYPE = 4;
+    /**
+     * 供应商企业类型。
+     */
+    static final int SUPPLIER_ENT_TYPE = 1;
 
     @DubboReference(version = "1.0.0", group = "design", check = false)
     private RemoteSystemService remoteSystemService;
@@ -1626,6 +1631,8 @@ public class KwoTradeOrderService {
         TradeOrderListSelectDTO dto = buildSelectParam(params);
         //数据权限过滤
         applyTradeOrderDataPermission(dto);
+        // 代理关系过滤:代理商仅查自身订单,供应商可查自身及其代理商订单。
+        applyProxyScopeToTradeOrderQuery(dto);
         PageHelper.startPage(params.getPage(), params.getPageSize());
         List<OrderListResDTO> list = kwoTradeOrderMapper.tradeOrderSelect(dto, dto.getGoodIds(), LoginUserHolder.getAuthUserIdList(), null);
         return buildResult(list, params.getPageSize());
@@ -1633,8 +1640,58 @@ public class KwoTradeOrderService {
     }
 
     /**
-     * 应用贸易订单数据权限过滤
-     * 根据当前用户的数据权限配置,设置DTO中的权限过滤参数
+     * 根据当前登录企业属性,为贸易订单列表追加代理关系可见范围。
+     * <p>
+     * 业务逻辑说明:
+     * 1. 获取当前登录用户的企业ID。
+     * 2. 查询该企业的类型信息(如:供应商、代理商等)。
+     * 3. 根据企业类型构建代理可见范围:
+     *    - 若为供应商:可见自身及下属代理商的订单。
+     *    - 若为代理商:仅可见自身的订单。
+     *    - 其他类型:不追加特殊过滤(由数据权限控制)。
+     * 4. 将计算出的可见企业ID集合设置到查询DTO中,供后续SQL过滤使用。
+     * 5. 若发生异常或企业ID为空,则强制结果为空,防止数据泄露。
+     *
+     * @param dto 贸易订单查询参数对象,用于承载过滤条件
+     */
+    private void applyProxyScopeToTradeOrderQuery(TradeOrderListSelectDTO dto) {
+        // 1. 获取当前登录上下文中的企业ID
+        Long currentEntId = LoginUserHolder.getEntId();
+        
+        // 2. 校验企业ID有效性,若为空则强制查询结果为空,确保数据安全
+        if (Objects.isNull(currentEntId)) {
+            log.warn("贸易订单代理关系过滤失败,当前登录企业ID为空");
+            dto.setProxyScopeForceEmpty(Boolean.TRUE);
+            return;
+        }
+
+        try {
+            // 3. 远程调用获取当前企业的类型信息列表
+            // 使用 Collections.singleton 包装单个ID以符合接口参数要求
+            List<EntTypeResDto> entTypeList = remoteSystemService.queryEntTypeByIds(Collections.singleton(currentEntId));
+            
+            // 4. 构建代理关系可见的企业ID集合
+            // 传入一个Supplier lambda表达式,仅在需要时(即当前企业为供应商时)才查询其关联的代理商ID,优化性能
+            Set<Long> proxyScopeEntIds = buildProxyScopeEntIds(currentEntId, entTypeList,
+                    () -> remoteContractService.queryProxyEntIdsBySupplyId(currentEntId));
+            
+            // 5. 若存在有效的可见范围,则设置到DTO中
+            if (CollUtil.isNotEmpty(proxyScopeEntIds)) {
+                dto.setProxyScopeEntIds(new ArrayList<>(proxyScopeEntIds));
+                log.debug("贸易订单代理关系过滤范围,currentEntId={}, scope={}", currentEntId, proxyScopeEntIds);
+            }
+            // 注意:若proxyScopeEntIds为空,表示当前企业不属于供应商或代理商角色,
+            // 此时不设置proxyScopeEntIds,后续逻辑将依赖常规的数据权限过滤。
+            
+        } catch (Exception e) {
+            // 6. 异常处理:记录错误日志,并强制查询结果为空,避免在系统异常时暴露全量数据
+            log.error("贸易订单代理关系过滤范围查询失败,currentEntId={}", currentEntId, e);
+            dto.setProxyScopeForceEmpty(Boolean.TRUE);
+        }
+    }
+
+    /**
+     * 应用贸易订单数据权限过滤。
      *
      * @param dto 贸易订单列表查询DTO
      */
@@ -2284,18 +2341,60 @@ public class KwoTradeOrderService {
         }
     }
 
+    /**
+     * 组装代理关系可见企业范围。
+     *
+     * @param currentEntId  当前登录企业ID
+     * @param entTypeList   企业类型集合
+     * @param proxySupplier 代理企业查询函数
+     * @return 可见企业ID集合,空集合表示当前企业不属于供应商或代理商,不追加过滤
+     */
+    static Set<Long> buildProxyScopeEntIds(Long currentEntId, List<EntTypeResDto> entTypeList,
+                                           Supplier<List<Long>> proxySupplier) {
+        if (Objects.isNull(currentEntId) || CollUtil.isEmpty(entTypeList)) {
+            return Collections.emptySet();
+        }
+        Set<Long> scopeEntIds = new LinkedHashSet<>();
+        if (hasEnterpriseType(entTypeList, currentEntId, SUPPLIER_ENT_TYPE)) {
+            scopeEntIds.add(currentEntId);
+            if (Objects.nonNull(proxySupplier)) {
+                List<Long> proxyEntIds = proxySupplier.get();
+                if (CollUtil.isNotEmpty(proxyEntIds)) {
+                    scopeEntIds.addAll(proxyEntIds.stream().filter(Objects::nonNull).collect(Collectors.toSet()));
+                }
+            }
+            return scopeEntIds;
+        }
+        if (hasEnterpriseType(entTypeList, currentEntId, SUPPLY_AGENT_ENT_TYPE)) {
+            scopeEntIds.add(currentEntId);
+        }
+        return scopeEntIds;
+    }
+
+    /**
+     * 判断指定企业是否具备指定企业类型。
+     *
+     * @param entTypeList 企业类型集合
+     * @param entId       企业ID
+     * @param entType     企业类型
+     * @return true-具备该类型,false-不具备
+     */
+    static boolean hasEnterpriseType(List<EntTypeResDto> entTypeList, Long entId, Integer entType) {
+        if (Objects.isNull(entId) || Objects.isNull(entType) || CollUtil.isEmpty(entTypeList)) {
+            return false;
+        }
+        return entTypeList.stream()
+                .filter(Objects::nonNull)
+                .filter(item -> Objects.equals(item.getEntId(), entId))
+                .anyMatch(item -> Objects.equals(item.getType(), entType));
+    }
+
     /**
      * 判断指定供应企业是否具有代理属性。
-     * <p>
-     * 业务逻辑:
-     * 1. 校验参数有效性:若供应企业ID为空或企业类型列表为空,直接返回false。
-     * 2. 遍历企业类型列表,筛选出与给定supplyEntId匹配的企业记录。
-     * 3. 检查匹配记录的企业类型(type)是否等于预设的供应单位代理属性类型(SUPPLY_AGENT_ENT_TYPE,值为4)。
-     * 4. 若存在至少一条匹配且类型为代理的记录,则返回true,否则返回false。
      *
-     * @param entTypeList  企业类型信息列表,来自远程系统服务查询结果
-     * @param supplyEntId  待校验的供应企业ID
-     * @return boolean     true-该企业具有代理属性; false-该企业不具有代理属性或参数无效
+     * @param entTypeList 企业类型信息列表
+     * @param supplyEntId 待校验的供应企业ID
+     * @return true-该企业具有代理属性,false-该企业不具有代理属性或参数无效
      */
     static boolean hasSupplyAgentAttribute(List<EntTypeResDto> entTypeList, Long supplyEntId) {
         // 参数预校验:确保企业ID和企业类型列表非空

+ 15 - 0
sckw-modules/sckw-order/src/main/resources/mapper/KwoTradeOrderMapper.xml

@@ -92,6 +92,21 @@
                  LEFT JOIN kwo_trade_order_contract f ON a.id = f.t_order_id AND f.del_flag = 0
         <where>
             a.del_flag = 0
+            <if test="query.proxyScopeForceEmpty != null and query.proxyScopeForceEmpty">
+                and 1 = 0
+            </if>
+            <if test="query.proxyScopeEntIds != null and query.proxyScopeEntIds.size() > 0">
+                and (
+                    e.ent_id in
+                    <foreach collection="query.proxyScopeEntIds" item="proxyScopeEntId" open="(" close=")" separator=",">
+                        #{proxyScopeEntId}
+                    </foreach>
+                    or e.top_ent_id in
+                    <foreach collection="query.proxyScopeEntIds" item="proxyScopeTopEntId" open="(" close=")" separator=",">
+                        #{proxyScopeTopEntId}
+                    </foreach>
+                )
+            </if>
 --          数据权限匹配
             <choose>
                 <when test="query.manager != null and !query.manager">

+ 47 - 0
sckw-modules/sckw-order/src/test/java/com/sckw/order/serivce/KwoTradeOrderServiceTest.java

@@ -6,6 +6,8 @@ import org.junit.Test;
 
 import java.util.Arrays;
 import java.util.Collections;
+import java.util.List;
+import java.util.Set;
 
 /**
  * 贸易订单服务单元测试。
@@ -46,4 +48,49 @@ public class KwoTradeOrderServiceTest {
 
         Assert.assertFalse(result);
     }
+
+    /**
+     * 当前企业为供应商时,可见范围应包含自身和代理企业。
+     */
+    @Test
+    public void buildProxyScopeEntIdsWhenCurrentEntIsSupplier() {
+        EntTypeResDto supplierType = new EntTypeResDto();
+        supplierType.setEntId(1001L);
+        supplierType.setType(KwoTradeOrderService.SUPPLIER_ENT_TYPE);
+
+        Set<Long> result = KwoTradeOrderService.buildProxyScopeEntIds(
+                1001L, Collections.singletonList(supplierType), () -> Arrays.asList(2001L, 2002L));
+
+        Assert.assertEquals(Set.of(1001L, 2001L, 2002L), result);
+    }
+
+    /**
+     * 当前企业为代理商时,可见范围只能包含自身。
+     */
+    @Test
+    public void buildProxyScopeEntIdsWhenCurrentEntIsAgent() {
+        EntTypeResDto agentType = new EntTypeResDto();
+        agentType.setEntId(2001L);
+        agentType.setType(KwoTradeOrderService.SUPPLY_AGENT_ENT_TYPE);
+
+        Set<Long> result = KwoTradeOrderService.buildProxyScopeEntIds(
+                2001L, Collections.singletonList(agentType), () -> List.of(3001L));
+
+        Assert.assertEquals(Set.of(2001L), result);
+    }
+
+    /**
+     * 当前企业不是供应商也不是代理商时,不追加代理关系过滤范围。
+     */
+    @Test
+    public void buildProxyScopeEntIdsWhenCurrentEntIsOtherType() {
+        EntTypeResDto purchaserType = new EntTypeResDto();
+        purchaserType.setEntId(3001L);
+        purchaserType.setType(2);
+
+        Set<Long> result = KwoTradeOrderService.buildProxyScopeEntIds(
+                3001L, Collections.singletonList(purchaserType), () -> List.of(4001L));
+
+        Assert.assertTrue(result.isEmpty());
+    }
 }

+ 195 - 2
sckw-modules/sckw-transport/src/main/java/com/sckw/transport/service/KwtLogisticsConsignmentService.java

@@ -54,6 +54,7 @@ import com.sckw.system.api.feign.DataPermissionFeignService;
 import com.sckw.system.api.model.dto.req.DataPermissionFilterReqDto;
 import com.sckw.system.api.model.dto.res.DataPermissionDTO;
 import com.sckw.system.api.model.dto.res.EntCacheResDto;
+import com.sckw.system.api.model.dto.res.EntTypeResDto;
 import com.sckw.system.api.model.dto.res.UserResDto;
 import com.sckw.transport.api.model.LogisticsBaseOrderVo;
 import com.sckw.transport.api.model.dto.McpLogisticsOrderVo;
@@ -91,6 +92,7 @@ import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.function.Function;
+import java.util.function.Supplier;
 import java.util.stream.Collectors;
 
 /**
@@ -2256,6 +2258,14 @@ public class KwtLogisticsConsignmentService {
 
     // 自定义线程池
     private static final ExecutorService QUERY_EXECUTOR = Executors.newFixedThreadPool(10);
+    /**
+     * 供应商企业类型。
+     */
+    private static final int SUPPLIER_ENT_TYPE = 1;
+    /**
+     * 代理商企业类型。
+     */
+    private static final int SUPPLY_AGENT_ENT_TYPE = 4;
 
 
     public PageDataResult<LogisticsOrderResp> queryLogisticsOrderByPage(QueryLogisticsOrderReq req) {
@@ -2977,20 +2987,203 @@ public class KwtLogisticsConsignmentService {
         return logisticsOrderIdAndUnitTypeKeyAndAddressMap;
     }
 
+
+    /**
+     * 获取当前用户基于代理关系可见的企业ID范围。
+     * <p>
+     * 逻辑说明:
+     * 1. 如果当前企业是供应商(SUPPLIER_ENT_TYPE),则可见范围包括:当前企业本身 + 该供应商关联的所有代理企业。
+     * 2. 如果当前企业是代理商(SUPPLY_AGENT_ENT_TYPE),则可见范围仅包括:当前企业本身。
+     * 3. 如果既不是供应商也不是代理商,或者查询异常,则根据策略返回空集合或仅包含当前企业的集合(容错处理)。
+     * </p>
+     *
+     * @return 可见企业ID集合
+     */
+    private Set<Long> getCurrentUserProxyScopeEntIds() {
+        // 1. 获取当前登录用户的所属企业ID
+        Long currentEntId = LoginUserHolder.getEntId();
+        
+        // 校验企业ID是否为空,若为空则无法进行权限过滤,记录警告并返回空集合
+        if (Objects.isNull(currentEntId)) {
+            log.warn("物流订单代理关系过滤失败,当前登录企业ID为空");
+            return Collections.emptySet();
+        }
+
+        log.debug("开始计算当前用户[entId:{}]的代理关系可见企业范围", currentEntId);
+
+        try {
+            // 2. 远程调用获取当前企业的类型信息
+            List<EntTypeResDto> entTypeList = remoteSystemService.queryEntTypeByIds(Collections.singleton(currentEntId));
+            
+            if (org.apache.commons.collections4.CollectionUtils.isEmpty(entTypeList)) {
+                log.warn("未查询到企业[entId:{}]的类型信息,默认仅可见自身", currentEntId);
+                return Collections.singleton(currentEntId);
+            }
+
+            // 3. 构建代理可见范围
+            // 使用 Supplier 延迟加载代理企业列表,避免不必要的远程调用
+            Set<Long> proxyScopeEntIds = buildProxyScopeEntIds(currentEntId, entTypeList,
+                    () -> contractService.queryProxyEntIdsBySupplyId(currentEntId));
+
+            log.debug("企业[entId:{}]的代理关系可见企业范围计算完成,结果数量: {}, IDs: {}", 
+                    currentEntId, proxyScopeEntIds.size(), proxyScopeEntIds);
+            
+            return proxyScopeEntIds;
+
+        } catch (Exception e) {
+            // 4. 异常处理:为了防止因远程服务波动导致业务完全不可用,采取降级策略
+            // 记录错误日志,并默认返回仅包含当前企业的集合,保证至少能看到自己的数据
+            log.error("物流订单代理关系过滤范围查询失败,currentEntId={}, 错误信息: {}", currentEntId, e.getMessage(), e);
+            return Collections.singleton(currentEntId);
+        }
+    }
+
+
+    /**
+     * 构建基于代理关系的企业可见范围ID集合。
+     * <p>
+     * 业务逻辑说明:
+     * 1. 如果当前企业是【供应商】(SUPPLIER_ENT_TYPE):
+     *    - 可见范围包含:当前企业自身 + 该供应商关联的所有代理企业(通过 proxySupplier 获取)。
+     * 2. 如果当前企业是【代理商】(SUPPLY_AGENT_ENT_TYPE):
+     *    - 可见范围仅包含:当前企业自身。
+     * 3. 其他情况或参数无效:
+     *    - 返回空集合。
+     * </p>
+     *
+     * @param currentEntId  当前登录用户所属的企业ID
+     * @param entTypeList   企业类型信息列表,用于判断当前企业的角色
+     * @param proxySupplier 懒加载 supplier,用于获取供应商关联的代理企业ID列表,避免不必要的远程调用
+     * @return 可见的企业ID集合,保持插入顺序
+     */
+    static Set<Long> buildProxyScopeEntIds(Long currentEntId, List<EntTypeResDto> entTypeList,
+                                           Supplier<List<Long>> proxySupplier) {
+        // 1. 基础参数校验
+        if (Objects.isNull(currentEntId) || org.apache.commons.collections4.CollectionUtils.isEmpty(entTypeList)) {
+            log.debug("构建代理可见范围失败:currentEntId为空或企业类型列表为空");
+            return Collections.emptySet();
+        }
+
+        Set<Long> scopeEntIds = new LinkedHashSet<>();
+        
+        // 2. 判断是否为供应商类型
+        if (hasEnterpriseType(entTypeList, currentEntId, SUPPLIER_ENT_TYPE)) {
+            log.debug("当前企业[{}]为供应商类型,开始构建供应商可见范围", currentEntId);
+            // 供应商可见自身
+            scopeEntIds.add(currentEntId);
+            
+            // 获取关联的代理企业
+            if (Objects.nonNull(proxySupplier)) {
+                try {
+                    List<Long> proxyEntIds = proxySupplier.get();
+                    if (org.apache.commons.collections4.CollectionUtils.isNotEmpty(proxyEntIds)) {
+                        // 过滤掉可能的null值并加入集合
+                        Set<Long> validProxyIds = proxyEntIds.stream()
+                                .filter(Objects::nonNull)
+                                .collect(Collectors.toSet());
+                        scopeEntIds.addAll(validProxyIds);
+                        log.debug("供应商[{}]关联的代理企业数量: {}", currentEntId, validProxyIds.size());
+                    } else {
+                        log.debug("供应商[{}]未关联任何代理企业", currentEntId);
+                    }
+                } catch (Exception e) {
+                    log.error("获取供应商[{}]关联代理企业失败", currentEntId, e);
+                    // 异常情况下,至少保证能看到自身数据,不抛出异常中断主流程
+                }
+            }
+            return scopeEntIds;
+        }
+
+        // 3. 判断是否为代理商类型
+        if (hasEnterpriseType(entTypeList, currentEntId, SUPPLY_AGENT_ENT_TYPE)) {
+            log.debug("当前企业[{}]为代理商类型,可见范围仅包含自身", currentEntId);
+            // 代理商仅可见自身
+            scopeEntIds.add(currentEntId);
+        } else {
+            log.debug("当前企业[{}]既非供应商也非代理商,或其他未知类型,可见范围为空", currentEntId);
+        }
+
+        return scopeEntIds;
+    }
+
+
+    /**
+     * 判断指定企业是否具备指定企业类型。
+     * <p>
+     * 该方法用于校验给定的企业ID列表中,是否存在某个企业同时满足指定的企业ID和企业类型。
+     * 通常用于权限控制或业务逻辑分支判断(如区分供应商与代理商)。
+     * </p>
+     *
+     * @param entTypeList 企业类型信息列表,包含企业ID和类型编码
+     * @param entId       待校验的目标企业ID
+     * @param entType     待校验的目标企业类型编码(如:1-供应商,4-代理商)
+     * @return true-该企业在列表中且具备指定类型;false-参数无效或未找到匹配项
+     */
+    static boolean hasEnterpriseType(List<EntTypeResDto> entTypeList, Long entId, Integer entType) {
+        // 1. 基础参数非空校验
+        // 如果目标企业ID、目标类型为空,或者传入的企业类型列表为空,直接返回false,避免后续空指针异常或无效计算
+        if (Objects.isNull(entId) || Objects.isNull(entType) || org.apache.commons.collections4.CollectionUtils.isEmpty(entTypeList)) {
+            log.debug("判断企业类型失败:参数为空 - entId: {}, entType: {}, listSize: {}", 
+                    entId, entType, entTypeList == null ? 0 : entTypeList.size());
+            return false;
+        }
+
+        // 2. 流式处理匹配逻辑
+        boolean matched = entTypeList.stream()
+                .filter(Objects::nonNull) // 过滤列表中的null元素,防止NPE
+                .filter(item -> Objects.equals(item.getEntId(), entId)) // 筛选出企业ID匹配的记录
+                .anyMatch(item -> Objects.equals(item.getType(), entType)); // 检查是否存在记录的类型与目标类型一致
+
+        // 3. 记录匹配结果日志,便于排查权限或业务逻辑问题
+        if (matched) {
+            log.debug("企业类型匹配成功:entId: {} 具备类型: {}", entId, entType);
+        } else {
+            log.debug("企业类型匹配失败:entId: {} 不具备类型: {} 或在列表中未找到", entId, entType);
+        }
+
+        return matched;
+    }
+
     @NotNull
     private Set<Long> getLogOrderIds(QueryLogisticsOrderReq req, Set<Long> entList, Long allEnt) {
         Set<Long> logOrderIds = Sets.newHashSet();
         if (Objects.nonNull(allEnt)) {
             entList.add(allEnt);
         }
+        // 1. 获取当前用户在代理关系下的可见企业ID范围(包含自身及关联的代理/供应商企业)
+        Set<Long> currentUserScopeEntIds = getCurrentUserProxyScopeEntIds();
+        log.debug("当前用户代理关系可见企业ID集合: {}", currentUserScopeEntIds);
+
+        // 2. 将代理关系可见的企业ID加入到查询条件集合中,扩大查询范围
+        if (org.apache.commons.collections4.CollectionUtils.isNotEmpty(currentUserScopeEntIds)) {
+            entList.addAll(currentUserScopeEntIds);
+            log.debug("已将代理关系可见企业ID加入查询集合,当前查询企业ID总数: {}", entList.size());
+        }
+
+        // 3. 根据所有相关企业的ID集合,批量查询物流订单单位信息(托运单位或承运单位)
         List<KwtLogisticsOrderUnit> logOrderUnits = logisticsOrderUnitRepository.queryByEntIds(entList);
+        log.debug("根据企业ID集合查询到的物流订单单位记录数: {}", 
+                org.apache.commons.collections4.CollectionUtils.isEmpty(logOrderUnits) ? 0 : logOrderUnits.size());
+
         if (org.apache.commons.collections4.CollectionUtils.isNotEmpty(logOrderUnits)) {
             Long entId = LoginUserHolder.getEntId();
-            // 获取物流订单ID集合
+            
+            // 4. 确定用于过滤物流订单单位的有效企业ID集合
+            // 如果存在代理关系可见企业,则使用该集合进行精确匹配;否则仅使用当前登录用户所属企业ID
+            Set<Long> finalCurrentUserScopeEntIds = org.apache.commons.collections4.CollectionUtils.isNotEmpty(currentUserScopeEntIds)
+                    ? currentUserScopeEntIds 
+                    : Collections.singleton(entId);
+            
+            log.debug("开始过滤物流订单单位,过滤条件企业ID集合: {}", finalCurrentUserScopeEntIds);
+
+            // 5. 从查询到的单位信息中提取物流订单ID
+            // 逻辑:只保留那些归属于“当前用户权限范围内企业”的单位所对应的物流订单ID
             Set<Long> logOrderIdList = logOrderUnits.stream()
-                    .filter(x -> Objects.equals(x.getEntId(), entId))
+                    .filter(unit -> finalCurrentUserScopeEntIds.contains(unit.getEntId()))
                     .map(KwtLogisticsOrderUnit::getLOrderId)
                     .collect(Collectors.toSet());
+            
+            log.debug("过滤后得到的物流订单ID数量: {}", logOrderIdList.size());
 
             // 根据托运单位和承运单位进行过滤
             if (StringUtils.isNotBlank(req.getConsignCompanyId())) {

+ 218 - 6
sckw-modules/sckw-transport/src/main/java/com/sckw/transport/service/KwtWaybillOrderV1Service.java

@@ -94,6 +94,7 @@ import java.util.*;
 import java.util.concurrent.*;
 import java.util.concurrent.atomic.AtomicReference;
 import java.util.function.Function;
+import java.util.function.Supplier;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
@@ -180,6 +181,14 @@ public class KwtWaybillOrderV1Service {
     private KwtLogisticsOrderAddressRepository kwtLogisticsOrderAddressRepository;
     // 定义超时时间常量
     private static final long PARALLEL_TIMEOUT_SECONDS = 60;
+    /**
+     * 供应商企业类型。
+     */
+    private static final int SUPPLIER_ENT_TYPE = 1;
+    /**
+     * 代理商企业类型。
+     */
+    private static final int SUPPLY_AGENT_ENT_TYPE = 4;
     /**
      * @param params 请求参数
      * @desc 统计
@@ -3605,12 +3614,39 @@ public class KwtWaybillOrderV1Service {
             return logOrderIds;
         }
         entIds.add(entId);
+        // 获取当前用户基于代理关系可见的企业ID范围(包含自身及关联代理企业)
+        Set<Long> currentUserScopeEntIds = getCurrentUserProxyScopeEntIds();
+        log.debug("获取当前用户代理范围企业ID完成,数量: {}", CollectionUtils.isEmpty(currentUserScopeEntIds) ? 0 : currentUserScopeEntIds.size());
+
+        // 将代理范围企业ID加入查询条件集合,确保数据权限覆盖
+        if (CollectionUtils.isNotEmpty(currentUserScopeEntIds)) {
+            entIds.addAll(currentUserScopeEntIds);
+            log.debug("已将代理范围企业ID加入查询集合,当前企业ID集合总数: {}", entIds.size());
+        }
+
+        // 如果企业ID集合不为空,根据企业ID、托运方/承运方ID过滤物流订单ID
         if (CollectionUtils.isNotEmpty(entIds)) {
-            Set<Long> orderIdsByEnt = getLogOrderIdsByEntIds(entIds,req.getConsignorId(),req.getCarrierId(),entId);
-            log.debug("根据企业ID查询到物流订单ID数量: {}", orderIdsByEnt.size());
+            log.debug("开始根据企业ID集合查询物流订单ID,企业ID集合: {}, 托运方ID: {}, 承运方ID: {}", 
+                    entIds, req.getConsignorId(), req.getCarrierId());
+            
+            Set<Long> orderIdsByEnt = getLogOrderIdsByEntIds(
+                    entIds, 
+                    req.getConsignorId(), 
+                    req.getCarrierId(), 
+                    entId, 
+                    currentUserScopeEntIds
+            );
+            
+            log.debug("根据企业ID及角色权限过滤后,获取到的物流订单ID数量: {}", orderIdsByEnt.size());
+            
             if (CollectionUtils.isNotEmpty(orderIdsByEnt)) {
                 logOrderIds.addAll(orderIdsByEnt);
+                log.debug("已将过滤后的物流订单ID加入结果集,当前物流订单ID总数: {}", logOrderIds.size());
+            } else {
+                log.debug("根据企业ID未查询到任何物流订单ID");
             }
+        } else {
+            log.debug("企业ID集合为空,跳过基于企业的物流订单ID查询");
         }
 
         // 如果指定了物流订单号,根据订单号查询
@@ -3863,16 +3899,192 @@ public class KwtWaybillOrderV1Service {
         return finalResult;
     }
 
+
+    /**
+     * 获取当前用户基于代理关系可见的企业ID范围。
+     * <p>
+     * 逻辑说明:
+     * 1. 获取当前登录用户的所属企业ID。
+     * 2. 查询该企业的类型信息。
+     * 3. 根据企业类型构建可见范围:
+     *    - 若为供应商(SUPPLIER_ENT_TYPE):可见自身及所有关联的代理企业。
+     *    - 若为代理商(SUPPLY_AGENT_ENT_TYPE):仅可见自身。
+     *    - 其他类型:返回空集合(无特殊代理权限)。
+     * 4. 异常处理:若查询失败,为保证业务连续性,默认返回当前企业ID,避免数据完全不可见。
+     * </p>
+     *
+     * @return 可见企业ID集合
+     */
+    private Set<Long> getCurrentUserProxyScopeEntIds() {
+        // 获取当前登录用户的企业ID
+        Long currentEntId = LoginUserHolder.getEntId();
+        
+        // 校验企业ID是否存在
+        if (Objects.isNull(currentEntId)) {
+            log.warn("运单代理关系过滤失败,当前登录企业ID为空");
+            return Collections.emptySet();
+        }
+        
+        log.debug("开始获取当前用户代理范围企业ID,currentEntId: {}", currentEntId);
+
+        try {
+            // 查询当前企业的类型信息
+            List<EntTypeResDto> entTypeList = remoteSystemService.queryEntTypeByIds(Collections.singleton(currentEntId));
+            log.debug("查询到企业类型信息数量: {}", CollectionUtils.isEmpty(entTypeList) ? 0 : entTypeList.size());
+
+            // 根据企业类型和代理关系构建可见企业ID集合
+            Set<Long> proxyScopeEntIds = buildProxyScopeEntIds(currentEntId, entTypeList,
+                    () -> {
+                        log.debug("当前企业为供应商,开始查询关联的代理企业ID");
+                        List<Long> proxyEntIds = remoteContractService.queryProxyEntIdsBySupplyId(currentEntId);
+                        log.debug("查询到关联代理企业ID数量: {}", CollectionUtils.isEmpty(proxyEntIds) ? 0 : proxyEntIds.size());
+                        return proxyEntIds;
+                    });
+            
+            log.info("获取当前用户代理范围企业ID完成,currentEntId: {}, 结果数量: {}", currentEntId, proxyScopeEntIds.size());
+            return proxyScopeEntIds;
+            
+        } catch (Exception e) {
+            // 记录异常日志,防止因远程服务调用失败导致主流程中断
+            log.error("运单代理关系过滤范围查询失败,currentEntId={}", currentEntId, e);
+            // 降级策略:返回当前企业ID,确保至少能看到本企业数据
+            return Collections.singleton(currentEntId);
+        }
+    }
+
+
+    /**
+     * 根据当前企业ID和企业类型,构建代理关系可见的企业ID范围集合。
+     * <p>
+     * 业务逻辑说明:
+     * 1. 如果当前企业是供应商(SUPPLIER_ENT_TYPE):
+     *    - 可见范围包含:自身企业ID + 所有关联的代理企业ID。
+     *    - 通过 proxySupplier 懒加载获取关联的代理企业列表,避免不必要的远程调用。
+     * 2. 如果当前企业是代理商(SUPPLY_AGENT_ENT_TYPE):
+     *    - 可见范围仅包含:自身企业ID。
+     * 3. 其他类型或异常情况:
+     *    - 返回空集合或仅包含自身的集合(取决于具体实现,此处非供应商/代理商返回空或仅自身,根据上下文通常代理商只看自己)。
+     * </p>
+     *
+     * @param currentEntId   当前登录用户所属的企业ID
+     * @param entTypeList    企业类型信息列表,用于判断当前企业的角色(供应商或代理商)
+     * @param proxySupplier  用于获取供应商关联代理企业ID的函数式接口,仅在确认为供应商时执行
+     * @return 可见的企业ID集合,保持插入顺序
+     */
+    static Set<Long> buildProxyScopeEntIds(Long currentEntId, List<EntTypeResDto> entTypeList,
+                                           Supplier<List<Long>> proxySupplier) {
+        // 参数校验:如果企业ID为空或类型列表为空,直接返回空集合,避免后续空指针异常
+        if (Objects.isNull(currentEntId) || CollectionUtils.isEmpty(entTypeList)) {
+            log.debug("构建代理范围企业ID失败:currentEntId为null或entTypeList为空");
+            return Collections.emptySet();
+        }
+
+        // 使用 LinkedHashSet 保持元素插入顺序,首先加入当前企业ID(如果需要)
+        Set<Long> scopeEntIds = new LinkedHashSet<>();
+
+        // 判断当前企业是否为供应商类型
+        if (hasEnterpriseType(entTypeList, currentEntId, SUPPLIER_ENT_TYPE)) {
+            log.debug("当前企业[{}]为供应商类型,开始构建可见企业范围", currentEntId);
+            // 供应商可见自身
+            scopeEntIds.add(currentEntId);
+            
+            // 如果提供了代理企业查询函数,则获取关联的代理企业
+            if (Objects.nonNull(proxySupplier)) {
+                try {
+                    List<Long> proxyEntIds = proxySupplier.get();
+                    if (CollectionUtils.isNotEmpty(proxyEntIds)) {
+                        // 过滤掉可能的null值,并添加到可见范围中
+                        Set<Long> validProxyIds = proxyEntIds.stream()
+                                .filter(Objects::nonNull)
+                                .collect(Collectors.toSet());
+                        scopeEntIds.addAll(validProxyIds);
+                        log.debug("供应商[{}]关联的代理企业数量: {}", currentEntId, validProxyIds.size());
+                    } else {
+                        log.debug("供应商[{}]未查询到关联的代理企业", currentEntId);
+                    }
+                } catch (Exception e) {
+                    // 捕获异常,防止因远程服务调用失败导致主流程中断,记录日志
+                    log.error("获取供应商[{}]关联代理企业失败", currentEntId, e);
+                }
+            }
+            // 供应商逻辑处理完毕,直接返回结果
+            return scopeEntIds;
+        }
+
+        // 判断当前企业是否为代理商类型
+        if (hasEnterpriseType(entTypeList, currentEntId, SUPPLY_AGENT_ENT_TYPE)) {
+            log.debug("当前企业[{}]为代理商类型,可见范围仅包含自身", currentEntId);
+            // 代理商仅可见自身
+            scopeEntIds.add(currentEntId);
+        } else {
+            log.debug("当前企业[{}]既不是供应商也不是代理商,可见范围为空", currentEntId);
+        }
+
+        return scopeEntIds;
+    }
+
     /**
-     * 根据企业ID集合获取物流订单ID集合
+     * 判断指定企业是否具备指定企业类型。
+     * <p>
+     * 该方法用于校验给定的企业ID在提供的企业类型列表中,是否存在匹配的目标企业类型。
+     * 主要用于构建代理关系可见范围时的类型判断逻辑(如区分供应商与代理商)。
+     * </p>
+     *
+     * @param entTypeList 企业类型信息列表,包含企业ID与类型的映射关系
+     * @param entId       待校验的企业ID
+     * @param entType     目标企业类型代码(如:1-供应商,4-代理商)
+     * @return true-该企业具备指定类型;false-不具备或参数无效
      */
-    private Set<Long> getLogOrderIdsByEntIds(Set<Long> entIds,String consignorId,String carrierId,Long entId) {
+    static boolean hasEnterpriseType(List<EntTypeResDto> entTypeList, Long entId, Integer entType) {
+        // 1. 基础参数校验:若企业ID、目标类型为空,或类型列表为空,直接返回false
+        if (Objects.isNull(entId) || Objects.isNull(entType) || CollectionUtils.isEmpty(entTypeList)) {
+            log.debug("判断企业类型失败:参数无效,entId={}, entType={}, listSize={}", 
+                    entId, entType, entTypeList == null ? 0 : entTypeList.size());
+            return false;
+        }
+
+        // 2. 流式处理查找匹配项
+        boolean result = entTypeList.stream()
+                .filter(Objects::nonNull) // 过滤列表中的空对象,防止NPE
+                .filter(item -> Objects.equals(item.getEntId(), entId)) // 筛选出指定企业ID的记录
+                .anyMatch(item -> Objects.equals(item.getType(), entType)); // 判断是否存在匹配的目标类型
+
+        // 3. 记录调试日志,便于追踪权限过滤逻辑
+        log.debug("判断企业类型结果:entId={}, targetEntType={}, result={}", entId, entType, result);
+        
+        return result;
+    }
+
+    /**
+     * 根据企业ID集合获取物流订单ID集合。
+     *
+     * @param entIds 企业ID集合
+     * @param consignorId 托运企业ID
+     * @param carrierId 承运企业ID
+     * @param entId 当前登录企业ID
+     * @param currentUserScopeEntIds 当前用户代理关系可见企业ID集合
+     * @return 物流订单ID集合
+     */
+    private Set<Long> getLogOrderIdsByEntIds(Set<Long> entIds, String consignorId, String carrierId,
+                                             Long entId, Set<Long> currentUserScopeEntIds) {
         List<KwtLogisticsOrderUnit> unitList = kwtLogisticsOrderUnitRepository.queryByEntIds(entIds);
         if (CollectionUtils.isEmpty(unitList)) {
             return Collections.emptySet();
         }
-        Set<Long> logOrderIds = unitList.stream().filter(x->Objects.equals(x.getEntId(),entId))
-                .map(KwtLogisticsOrderUnit::getLOrderId).collect(Collectors.toSet());
+        // 确定当前用户可见的企业ID范围:如果代理关系查询结果为空,则默认仅包含当前登录企业ID,确保数据权限过滤的有效性
+        Set<Long> finalCurrentUserScopeEntIds = CollectionUtils.isNotEmpty(currentUserScopeEntIds)
+                ? currentUserScopeEntIds : Collections.singleton(entId);
+        
+        log.debug("开始根据企业权限过滤物流订单单位,可见企业ID集合: {}, 待过滤单位数量: {}", 
+                finalCurrentUserScopeEntIds, unitList.size());
+
+        // 过滤出属于当前用户可见企业范围内的物流订单单位,并提取对应的物流订单ID集合
+        Set<Long> logOrderIds = unitList.stream()
+                .filter(unit -> finalCurrentUserScopeEntIds.contains(unit.getEntId()))
+                .map(KwtLogisticsOrderUnit::getLOrderId)
+                .collect(Collectors.toSet());
+
+        log.debug("企业权限过滤完成,最终获取到的物流订单ID数量: {}", logOrderIds.size());
 
 
         if (StringUtils.isNotBlank(consignorId)){

+ 61 - 0
sckw-modules/sckw-transport/src/test/java/com/sckw/transport/service/KwtWaybillOrderV1ServiceTest.java

@@ -0,0 +1,61 @@
+package com.sckw.transport.service;
+
+import com.sckw.system.api.model.dto.res.EntTypeResDto;
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * 运单服务代理关系范围单元测试。
+ */
+public class KwtWaybillOrderV1ServiceTest {
+
+    /**
+     * 当前企业为供应商时,可见范围应包含自身和代理企业。
+     */
+    @Test
+    public void buildProxyScopeEntIdsWhenCurrentEntIsSupplier() {
+        EntTypeResDto supplierType = new EntTypeResDto();
+        supplierType.setEntId(1001L);
+        supplierType.setType(1);
+
+        Set<Long> result = KwtWaybillOrderV1Service.buildProxyScopeEntIds(
+                1001L, Collections.singletonList(supplierType), () -> Arrays.asList(2001L, 2002L));
+
+        Assert.assertEquals(Set.of(1001L, 2001L, 2002L), result);
+    }
+
+    /**
+     * 当前企业为代理商时,可见范围只能包含自身。
+     */
+    @Test
+    public void buildProxyScopeEntIdsWhenCurrentEntIsAgent() {
+        EntTypeResDto agentType = new EntTypeResDto();
+        agentType.setEntId(2001L);
+        agentType.setType(4);
+
+        Set<Long> result = KwtWaybillOrderV1Service.buildProxyScopeEntIds(
+                2001L, Collections.singletonList(agentType), () -> List.of(3001L));
+
+        Assert.assertEquals(Set.of(2001L), result);
+    }
+
+    /**
+     * 当前企业不是供应商也不是代理商时,不追加代理关系过滤范围。
+     */
+    @Test
+    public void buildProxyScopeEntIdsWhenCurrentEntIsOtherType() {
+        EntTypeResDto purchaserType = new EntTypeResDto();
+        purchaserType.setEntId(3001L);
+        purchaserType.setType(2);
+
+        Set<Long> result = KwtWaybillOrderV1Service.buildProxyScopeEntIds(
+                3001L, Collections.singletonList(purchaserType), () -> List.of(4001L));
+
+        Assert.assertTrue(result.isEmpty());
+    }
+}