Kaynağa Gözat

Merge remote-tracking branch 'origin/dev_20260630' into dev_20260630

xucaiqin 5 gün önce
ebeveyn
işleme
ec977e108b
92 değiştirilmiş dosya ile 3643 ekleme ve 86 silme
  1. 7 1
      sckw-auth/pom.xml
  2. 512 11
      sckw-auth/src/main/java/com/sckw/auth/service/impl/AuthServiceImpl.java
  3. 213 0
      sckw-auth/src/test/java/com/sckw/auth/service/impl/AuthServiceImplTest.java
  4. 1 0
      sckw-common/sckw-common-core/src/main/java/com/sckw/core/common/enums/enums/DictTypeEnum.java
  5. 27 0
      sckw-modules-api/sckw-contract-api/src/main/java/com/sckw/contract/api/RemoteContractService.java
  6. 5 0
      sckw-modules-api/sckw-contract-api/src/main/java/com/sckw/contract/api/model/dto/res/ContractTradeOrderDto.java
  7. 5 0
      sckw-modules-api/sckw-fleet-api/src/main/java/com/sckw/fleet/api/model/vo/RTruckVo.java
  8. 7 0
      sckw-modules-api/sckw-system-api/src/main/java/com/sckw/system/api/RemoteSystemService.java
  9. 8 0
      sckw-modules-api/sckw-system-api/src/main/java/com/sckw/system/api/RemoteUserService.java
  10. 51 0
      sckw-modules-api/sckw-system-api/src/main/java/com/sckw/system/api/model/dto/res/AppTabBarMenuResDto.java
  11. 5 0
      sckw-modules-api/sckw-transport-api/src/main/java/com/sckw/transport/api/model/param/AddLogisticOrderParam.java
  12. 6 1
      sckw-modules/sckw-contract/pom.xml
  13. 263 0
      sckw-modules/sckw-contract/src/main/java/com/sckw/contract/dubbo/RemoteContractServiceImpl.java
  14. 5 0
      sckw-modules/sckw-contract/src/main/java/com/sckw/contract/model/dto/res/QueryListResDto.java
  15. 7 1
      sckw-modules/sckw-contract/src/main/java/com/sckw/contract/model/entity/KwcContractTrade.java
  16. 6 0
      sckw-modules/sckw-contract/src/main/java/com/sckw/contract/model/vo/req/QueryListReqVo.java
  17. 6 0
      sckw-modules/sckw-contract/src/main/java/com/sckw/contract/model/vo/req/QueryTradeReq.java
  18. 13 1
      sckw-modules/sckw-contract/src/main/java/com/sckw/contract/model/vo/res/QueryListResVo.java
  19. 12 0
      sckw-modules/sckw-contract/src/main/java/com/sckw/contract/repository/KwcContractProxyRepository.java
  20. 17 0
      sckw-modules/sckw-contract/src/main/java/com/sckw/contract/repository/KwcContractProxyUnitRepository.java
  21. 3 1
      sckw-modules/sckw-contract/src/main/java/com/sckw/contract/repository/KwcContractTradeRepository.java
  22. 222 5
      sckw-modules/sckw-contract/src/main/java/com/sckw/contract/service/operateService/KwcContractTradeService.java
  23. 15 3
      sckw-modules/sckw-contract/src/main/resources/mapper/KwcContractTradeMapper.xml
  24. 46 0
      sckw-modules/sckw-contract/src/test/java/com/sckw/contract/service/operateService/KwcContractTradeServiceTest.java
  25. 4 4
      sckw-modules/sckw-file/src/main/resources/bootstrap-cxf.yml
  26. 7 1
      sckw-modules/sckw-fleet/pom.xml
  27. 2 0
      sckw-modules/sckw-fleet/src/main/java/com/sckw/fleet/dubbo/RemoteFleetServiceImpl.java
  28. 6 0
      sckw-modules/sckw-fleet/src/main/java/com/sckw/fleet/model/KwfTruck.java
  29. 7 0
      sckw-modules/sckw-fleet/src/main/java/com/sckw/fleet/model/reponse/TruckDetailResp.java
  30. 7 0
      sckw-modules/sckw-fleet/src/main/java/com/sckw/fleet/model/reponse/TruckResp.java
  31. 7 0
      sckw-modules/sckw-fleet/src/main/java/com/sckw/fleet/model/request/TruckSaveParam.java
  32. 7 1
      sckw-modules/sckw-fleet/src/main/java/com/sckw/fleet/service/KwfTruckService.java
  33. 35 0
      sckw-modules/sckw-fleet/src/test/java/com/sckw/fleet/model/TruckMaxTransportDistanceTest.java
  34. 4 4
      sckw-modules/sckw-operation/src/main/resources/bootstrap-cxf.yml
  35. 6 1
      sckw-modules/sckw-order/pom.xml
  36. 6 0
      sckw-modules/sckw-order/src/main/java/com/sckw/order/model/KwoTradeOrder.java
  37. 5 0
      sckw-modules/sckw-order/src/main/java/com/sckw/order/model/dto/OrderListResDTO.java
  38. 18 0
      sckw-modules/sckw-order/src/main/java/com/sckw/order/model/dto/TradeOrderListSelectDTO.java
  39. 6 0
      sckw-modules/sckw-order/src/main/java/com/sckw/order/model/vo/req/TradeOrderListStatisticParam.java
  40. 10 0
      sckw-modules/sckw-order/src/main/java/com/sckw/order/model/vo/res/OrderListResVO.java
  41. 264 2
      sckw-modules/sckw-order/src/main/java/com/sckw/order/serivce/KwoTradeOrderService.java
  42. 24 1
      sckw-modules/sckw-order/src/main/resources/mapper/KwoTradeOrderMapper.xml
  43. 125 0
      sckw-modules/sckw-order/src/test/java/com/sckw/order/serivce/KwoTradeOrderServiceTest.java
  44. 5 0
      sckw-modules/sckw-product/pom.xml
  45. 3 0
      sckw-modules/sckw-product/src/main/java/com/sckw/product/model/vo/res/BuildingMaterialsMarketList.java
  46. 4 0
      sckw-modules/sckw-product/src/main/java/com/sckw/product/model/vo/res/GoodsDetailVo.java
  47. 28 3
      sckw-modules/sckw-product/src/main/java/com/sckw/product/service/KwpGoodsService.java
  48. 4 4
      sckw-modules/sckw-product/src/main/resources/bootstrap-cxf.yml
  49. 30 0
      sckw-modules/sckw-product/src/test/java/com/sckw/product/model/vo/res/BuildingMaterialsMarketListTest.java
  50. 30 0
      sckw-modules/sckw-product/src/test/java/com/sckw/product/model/vo/res/GoodsDetailVoTest.java
  51. 5 0
      sckw-modules/sckw-system/pom.xml
  52. 2 2
      sckw-modules/sckw-system/src/main/java/com/sckw/system/controller/KwsMenuController.java
  53. 37 0
      sckw-modules/sckw-system/src/main/java/com/sckw/system/dubbo/RemoteSystemServiceImpl.java
  54. 73 1
      sckw-modules/sckw-system/src/main/java/com/sckw/system/dubbo/RemoteUserServiceImpl.java
  55. 7 1
      sckw-modules/sckw-system/src/main/java/com/sckw/system/model/KwsMenu.java
  56. 3 0
      sckw-modules/sckw-system/src/main/java/com/sckw/system/model/vo/res/AppMenuPermItemResVo.java
  57. 5 0
      sckw-modules/sckw-system/src/main/java/com/sckw/system/model/vo/res/FindMenuTreeResVo.java
  58. 5 0
      sckw-modules/sckw-system/src/main/java/com/sckw/system/model/vo/res/MenuDetailResVo.java
  59. 16 0
      sckw-modules/sckw-system/src/main/java/com/sckw/system/service/KwsMenuService.java
  60. 0 1
      sckw-modules/sckw-system/src/main/resources/bootstrap.yml
  61. 14 0
      sckw-modules/sckw-system/src/main/resources/mapper/KwsMenuDao.xml
  62. 57 0
      sckw-modules/sckw-system/src/test/java/com/sckw/system/model/KwsMenuFieldMappingTest.java
  63. 1 0
      sckw-modules/sckw-transport/src/main/java/com/sckw/transport/dubbo/TransportServiceImpl.java
  64. 2 0
      sckw-modules/sckw-transport/src/main/java/com/sckw/transport/handler/TakingOrderHandler.java
  65. 7 1
      sckw-modules/sckw-transport/src/main/java/com/sckw/transport/model/KwtLogisticsOrder.java
  66. 7 1
      sckw-modules/sckw-transport/src/main/java/com/sckw/transport/model/KwtWaybillOrder.java
  67. 7 0
      sckw-modules/sckw-transport/src/main/java/com/sckw/transport/model/KwtWaybillOrderSubtask.java
  68. 4 0
      sckw-modules/sckw-transport/src/main/java/com/sckw/transport/model/dto/AddLogisticOrderDTO.java
  69. 11 0
      sckw-modules/sckw-transport/src/main/java/com/sckw/transport/model/param/LogisticsOrderResp.java
  70. 10 0
      sckw-modules/sckw-transport/src/main/java/com/sckw/transport/model/param/QueryLogisticsOrderReq.java
  71. 11 0
      sckw-modules/sckw-transport/src/main/java/com/sckw/transport/model/param/WaybillOrderReq.java
  72. 12 0
      sckw-modules/sckw-transport/src/main/java/com/sckw/transport/model/param/WaybillOrderResp.java
  73. 17 1
      sckw-modules/sckw-transport/src/main/java/com/sckw/transport/repository/KwtLogisticsOrderRepository.java
  74. 13 1
      sckw-modules/sckw-transport/src/main/java/com/sckw/transport/repository/KwtWaybillOrderRepository.java
  75. 2 0
      sckw-modules/sckw-transport/src/main/java/com/sckw/transport/repository/KwtWaybillOrderSubtaskRepository.java
  76. 86 0
      sckw-modules/sckw-transport/src/main/java/com/sckw/transport/service/AutoDispatchDistanceUtils.java
  77. 5 0
      sckw-modules/sckw-transport/src/main/java/com/sckw/transport/service/KwtAcceptCarriageOrderService.java
  78. 336 5
      sckw-modules/sckw-transport/src/main/java/com/sckw/transport/service/KwtLogisticsConsignmentService.java
  79. 434 23
      sckw-modules/sckw-transport/src/main/java/com/sckw/transport/service/KwtWaybillOrderV1Service.java
  80. 2 0
      sckw-modules/sckw-transport/src/main/resources/mapper/KwtLogisticsOrderMapper.xml
  81. 2 1
      sckw-modules/sckw-transport/src/main/resources/mapper/KwtWaybillOrderMapper.xml
  82. 4 3
      sckw-modules/sckw-transport/src/main/resources/mapper/KwtWaybillOrderSubtaskMapper.xml
  83. 64 0
      sckw-modules/sckw-transport/src/test/java/com/sckw/transport/service/KwtAcceptCarriageOrderServiceTest.java
  84. 74 0
      sckw-modules/sckw-transport/src/test/java/com/sckw/transport/service/KwtLogisticsConsignmentServiceTest.java
  85. 159 0
      sckw-modules/sckw-transport/src/test/java/com/sckw/transport/service/KwtWaybillOrderV1ServiceTest.java
  86. 2 0
      sql/2026/06/2026_06_01_add_trade_contract_agent_flag.sql
  87. 2 0
      sql/2026/06/2026_06_02_add_logistics_order_agent_flag.sql
  88. 2 0
      sql/2026/06/2026_06_02_add_trade_order_agent_flag.sql
  89. 5 0
      sql/2026/06/2026_06_02_add_waybill_order_agent_flag.sql
  90. 2 0
      sql/2026/06/2026_06_07_fleet_truck_max_transport_distance.sql
  91. 2 0
      sql/2026/06/2026_06_09_add_kws_menu_not_selected_icon_path.sql
  92. 18 0
      sql/2026/06/2026_06_11_app_tabbar_menu_permission.sql

+ 7 - 1
sckw-auth/pom.xml

@@ -76,6 +76,12 @@
             <version>4.5.0</version>
         </dependency>
 
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <scope>test</scope>
+        </dependency>
+
 
     </dependencies>
     <build>
@@ -102,4 +108,4 @@
             </plugin>
         </plugins>
     </build>
-</project>
+</project>

+ 512 - 11
sckw-auth/src/main/java/com/sckw/auth/service/impl/AuthServiceImpl.java

@@ -10,6 +10,7 @@ import com.sckw.auth.model.vo.res.LoginResVo1;
 import com.sckw.auth.service.IAuthService;
 import com.sckw.auth.util.AsyncFactory;
 import com.sckw.core.common.enums.enums.DictEnum;
+import com.sckw.core.common.enums.enums.DictTypeEnum;
 import com.sckw.core.exception.SystemException;
 import com.sckw.core.model.constant.Global;
 import com.sckw.core.model.constant.NumberConstant;
@@ -56,6 +57,20 @@ public class AuthServiceImpl implements IAuthService {
     @DubboReference(version = "1.0.0", group = "design", check = false)
     private RemoteFleetService fleetService;
 
+    /**
+     * APP模块权限逻辑菜单关键字,仅用于 {@link #applyAppModulePermissionsByConfig} 控制模块显隐,
+     * 不应出现在底部导航 TabBar 中。
+     */
+    private static final String[] APP_MODULE_LOGIC_MENU_KEYWORDS = {
+            "\u5c55\u793a\u8ba2\u5355\u7edf\u8ba1", "orderStatistics", "order-statistics", "order_statistics",
+            "\u5c55\u793a\u9500\u552e\u7edf\u8ba1", "salesStatistics", "sales-statistics", "sales_statistics",
+            "\u5c55\u793a\u94b1\u5305", "wallet",
+            "\u5c55\u793a\u5730\u5740", "address",
+            "\u5c55\u793a\u5f85\u5c65\u7ea6\u4fdd\u8bc1\u91d1", "PENDING_PERFORMANCE_BALANCE", "pendingPerformanceBalance", "pending-performance-balance",
+            "\u5c55\u793a\u9884\u4ed8\u4f59\u989d", "PREPAY_BALANCE", "prepayBalance", "prepay-balance",
+            "\u5c55\u793a\u5f85\u4ed8\u8fd0\u8d39", "PENDING_FREIGHT", "pendingFreight", "pending-freight"
+    };
+
     @Override
     public HttpResult login(LoginBase loginBase) {
         if (StringUtils.isNotBlank(loginBase.getCaptcha())) {
@@ -168,11 +183,11 @@ public class AuthServiceImpl implements IAuthService {
         loginRes.setClientType(loginBase.getClientType());
         loginRes.setSystemType(loginBase.getSystemType());
         loginRes.setToken(token);
-        List<LoginResVo1.TabBarItem> tabBar = buildAppTabBar(loginRes, loginBase, null, 1, loginRes.getEntTypes());
+        List<LoginResVo1.TabBarItem> tabBar = buildAppTabBarByConfig(loginRes, loginBase, null, 1, loginRes.getEntTypes());
         if (org.apache.commons.collections4.CollectionUtils.isNotEmpty(tabBar)) {
             loginRes.setTabBar(tabBar);
         }
-        applyAppModulePermissions(loginRes, loginBase, null);
+        applyAppModulePermissionsByConfig(loginRes, loginBase);
         loginRes.setRefreshToken(refreshToken);
         return HttpResult.ok(loginRes);
     }
@@ -262,11 +277,11 @@ public class AuthServiceImpl implements IAuthService {
         loginRes.setDriverId(user.getDriverId());
         loginRes.setRoleName(user.getRoleName());
         loginRes.setRoleList(user.getRoleInfoDto());
-        List<LoginResVo1.TabBarItem> tabBar = buildAppTabBar(loginRes, loginBase, user.getRoleName(), 0, loginRes.getEntTypes());
+        List<LoginResVo1.TabBarItem> tabBar = buildAppTabBarByConfig(loginRes, loginBase, user.getRoleName(), 0, loginRes.getEntTypes());
         if (org.apache.commons.collections4.CollectionUtils.isNotEmpty(tabBar)) {
             loginRes.setTabBar(tabBar);
         }
-        applyAppModulePermissions(loginRes, loginBase, user.getRoleName());
+        applyAppModulePermissionsByConfig(loginRes, loginBase);
         if (user.getSystemType().equals(SystemTypeEnum.MANAGE.getCode())) {
             loginRes.setValid(true);
         } else {
@@ -352,12 +367,12 @@ public class AuthServiceImpl implements IAuthService {
         loginRes.setRoleId(user.getRoleId());
         loginRes.setRoleName(user.getRoleName());
         loginRes.setRoleList(user.getRoleInfoDto());
-        List<LoginResVo1.TabBarItem> tabBar = buildAppTabBar(loginRes, loginBase, user.getRoleName(), 0, loginRes.getEntTypes());
+        List<LoginResVo1.TabBarItem> tabBar = buildAppTabBarByConfig(loginRes, loginBase, user.getRoleName(), 0, loginRes.getEntTypes());
         if (org.apache.commons.collections4.CollectionUtils.isNotEmpty(tabBar)) {
             loginRes.setTabBar(tabBar);
 
         }
-        applyAppModulePermissions(loginRes, loginBase, user.getRoleName());
+        applyAppModulePermissionsByConfig(loginRes, loginBase);
         if (user.getSystemType().equals(SystemTypeEnum.MANAGE.getCode())) {
             loginRes.setValid(true);
         } else {
@@ -523,7 +538,7 @@ public class AuthServiceImpl implements IAuthService {
         loginRes.setRoleId(user.getRoleId());
         LoginBase loginBase = new LoginBase();
         loginBase.setClientType(clientType);
-        applyAppModulePermissions(loginRes, loginBase, user.getRoleName());
+        applyAppModulePermissionsByConfig(loginRes, loginBase);
         if (user.getSystemType().equals(SystemTypeEnum.MANAGE.getCode())) {
             loginRes.setValid(true);
         } else {
@@ -859,11 +874,11 @@ public class AuthServiceImpl implements IAuthService {
         loginRes.setToken(newToken);
         loginRes.setRefreshToken(newRefreshToken);
         loginRes.setDriverId(driver.getId());
-        List<LoginResVo1.TabBarItem> tabBar = buildAppTabBar(loginRes, loginBase, null, 1, loginRes.getEntTypes());
+        List<LoginResVo1.TabBarItem> tabBar = buildAppTabBarByConfig(loginRes, loginBase, null, 1, loginRes.getEntTypes());
         if (org.apache.commons.collections4.CollectionUtils.isNotEmpty(tabBar)) {
             loginRes.setTabBar(tabBar);
         }
-        applyAppModulePermissions(loginRes, loginBase, null);
+        applyAppModulePermissionsByConfig(loginRes, loginBase);
         return loginRes;
     }
 
@@ -897,11 +912,11 @@ public class AuthServiceImpl implements IAuthService {
         loginRes.setDriverId(user.getDriverId());
         loginRes.setRoleName(user.getRoleName());
         loginRes.setRoleList(user.getRoleInfoDto());
-        List<LoginResVo1.TabBarItem> tabBar = buildAppTabBar(loginRes, loginBase, user.getRoleName(), 1, loginRes.getEntTypes());
+        List<LoginResVo1.TabBarItem> tabBar = buildAppTabBarByConfig(loginRes, loginBase, user.getRoleName(), 1, loginRes.getEntTypes());
         if (org.apache.commons.collections4.CollectionUtils.isNotEmpty(tabBar)) {
             loginRes.setTabBar(tabBar);
         }
-        applyAppModulePermissions(loginRes, loginBase, user.getRoleName());
+        applyAppModulePermissionsByConfig(loginRes, loginBase);
         if (user.getSystemType().equals(SystemTypeEnum.MANAGE.getCode())) {
             loginRes.setValid(true);
         } else {
@@ -917,6 +932,492 @@ public class AuthServiceImpl implements IAuthService {
                 || Objects.equals(clientType, ClientTypeEnum.android.getValue())
                 || Objects.equals(clientType, ClientTypeEnum.mobile.getValue());
     }
+    
+
+    /**
+     * 根据角色ID字典配置控制APP模块权限。
+     * <p>
+     * 该方法主要用于在登录或刷新Token时,根据当前用户的角色配置,动态设置APP端需要展示的模块(如订单统计、销售统计、钱包、地址管理等)。
+     * 仅在APP端登录时生效,PC/H5端不展示这些特定模块配置。
+     * </p>
+     *
+     * @param loginRes  登录返回信息对象,用于设置模块显示状态及钱包子项
+     * @param loginBase 登录请求基础信息,用于判断客户端类型
+     */
+    private void applyAppModulePermissionsByConfig(LoginResVo1 loginRes, LoginBase loginBase) {
+        // 1. 参数非空校验:若登录结果或登录参数为空,直接返回,避免NPE
+        if (Objects.isNull(loginRes) || Objects.isNull(loginBase)) {
+            log.debug("登录返回对象或登录参数为空,跳过APP模块权限配置");
+            return;
+        }
+
+        // 2. 初始化默认状态:默认隐藏所有APP工作台模块,遵循“最小权限原则”,避免无菜单权限时前端误展示。
+        loginRes.setShowOrderStatisticsModule(Boolean.FALSE);
+        loginRes.setShowSalesStatisticsModule(Boolean.FALSE);
+        loginRes.setShowWalletModule(Boolean.FALSE);
+        loginRes.setShowAddressModule(Boolean.FALSE);
+        loginRes.setWalletModuleItems(Collections.emptyList());
+
+        // 3. 客户端类型校验:仅对APP端(iOS/Android/Mobile)进行模块权限配置,PC/H5端直接返回
+        if (!isAppLogin(loginBase)) {
+            log.debug("非APP端登录,跳过APP模块权限配置。clientType: {}", loginBase.getClientType());
+            return;
+        }
+
+        // 4. 获取角色ID并加载对应的APP菜单配置
+        Long roleId = loginRes.getRoleId();
+        log.debug("开始加载APP模块权限配置,userId: {}, roleId: {}", loginRes.getId(), roleId);
+        
+        // 通过远程服务查询该角色在APP端的菜单权限列表
+        List<AppTabBarMenuResDto> appMenus = loadAppRoleMenus(roleId);
+
+        // 5. 逐项判断模块权限:
+        // 使用多关键字匹配(中文名、英文名、下划线格式、驼峰格式等),提高匹配的兼容性和健壮性。
+        
+        // 5.1 订单统计模块:检查菜单中是否包含“展示订单统计”或相关标识(orderStatistics等),决定前端是否显示该模块
+        boolean hasOrderStatistics = hasAppModuleMenu(appMenus, "\u5c55\u793a\u8ba2\u5355\u7edf\u8ba1", "orderStatistics", "order-statistics", "order_statistics");
+        log.debug("订单统计模块权限判断结果: {}", hasOrderStatistics);
+        
+        // 5.2 销售统计模块:检查菜单中是否包含“展示销售统计”或相关标识(salesStatistics等),决定前端是否显示该模块
+        boolean hasSalesStatistics = hasAppModuleMenu(appMenus, "\u5c55\u793a\u9500\u552e\u7edf\u8ba1", "salesStatistics", "sales-statistics", "sales_statistics");
+        log.debug("销售统计模块权限判断结果: {}", hasSalesStatistics);
+        
+        // 5.3 钱包模块:检查菜单中是否包含“展示钱包”或相关标识(wallet),决定前端是否显·示钱包入口
+        boolean hasWallet = hasAppModuleMenu(appMenus, "\u5c55\u793a\u94b1\u5305", "wallet");
+        log.debug("钱包模块权限判断结果: {}", hasWallet);
+        
+        // 5.4 地址管理模块:检查菜单中是否包含“展示地址”或相关标识(address),决定前端是否显示地址管理入口
+        boolean hasAddress = hasAppModuleMenu(appMenus, "\u5c55\u793a\u5730\u5740", "address");
+        log.debug("地址管理模块权限判断结果: {}", hasAddress);
+
+        // 5.5 钱包子项 - 待履约保证金:检查菜单中是否包含“展示待履约保证金”或相关标识,用于控制钱包内子项显示
+        boolean hasPendingPerformance = hasAppModuleMenu(appMenus, "\u5c55\u793a\u5f85\u5c65\u7ea6\u4fdd\u8bc1\u91d1", "PENDING_PERFORMANCE_BALANCE", "pendingPerformanceBalance", "pending-performance-balance");
+        log.debug("待履约保证金子项权限判断结果: {}", hasPendingPerformance);
+
+        // 5.6 钱包子项 - 预付余额:检查菜单中是否包含“展示预付余额”或相关标识,用于控制钱包内子项显示
+        boolean hasPrepayBalance = hasAppModuleMenu(appMenus, "\u5c55\u793a\u9884\u4ed8\u4f59\u989d", "PREPAY_BALANCE", "prepayBalance", "prepay-balance");
+        log.debug("预付余额子项权限判断结果: {}", hasPrepayBalance);
+        
+        // 5.7 钱包子项 - 待付运费:检查菜单中是否包含“展示待付运费”或相关标识,用于控制钱包内子项显示
+        boolean hasPendingFreight = hasAppModuleMenu(appMenus, "\u5c55\u793a\u5f85\u4ed8\u8fd0\u8d39", "PENDING_FREIGHT", "pendingFreight", "pending-freight");
+        log.debug("待付运费子项权限判断结果: {}", hasPendingFreight);
+
+        // 6. 应用权限配置到返回对象
+        loginRes.setShowOrderStatisticsModule(hasOrderStatistics);
+        loginRes.setShowSalesStatisticsModule(hasSalesStatistics);
+        loginRes.setShowWalletModule(hasWallet);
+        loginRes.setShowAddressModule(hasAddress);
+        
+        // 若拥有钱包模块权限,则进一步构建钱包内部的子项列表
+        if (hasWallet) {
+            List<String> walletItems = buildWalletModuleItems(hasPendingPerformance, hasPrepayBalance, hasPendingFreight);
+            loginRes.setWalletModuleItems(walletItems);
+            log.debug("钱包模块开启,子项配置: {}", walletItems);
+        }
+
+        // 7. 记录最终配置结果日志,便于后续排查权限问题
+        log.info("APP模块权限配置完成 - userId: {}, roleId: {}, orderStats: {}, salesStats: {}, wallet: {}, address: {}",
+                loginRes.getId(), roleId, hasOrderStatistics, hasSalesStatistics, hasWallet, hasAddress);
+    }
+
+
+    /**
+     * 根据菜单授权结果构建钱包模块子项。
+     *
+     * @param hasPendingPerformance 是否包含待履约保证金菜单
+     * @param hasPrepayBalance      是否包含预付余额菜单
+     * @param hasPendingFreight     是否包含待付运费菜单
+     * @return 钱包模块子项编码列表
+     */
+    List<String> buildWalletModuleItems(boolean hasPendingPerformance, boolean hasPrepayBalance, boolean hasPendingFreight) {
+        List<String> walletItems = new ArrayList<>();
+        if (hasPendingPerformance) {
+            walletItems.add("PENDING_PERFORMANCE_BALANCE");
+        }
+        if (hasPrepayBalance) {
+            walletItems.add("PREPAY_BALANCE");
+        }
+        if (hasPendingFreight || hasPendingPerformance || hasPrepayBalance) {
+            walletItems.add("PENDING_FREIGHT");
+        }
+        return walletItems;
+    }
+
+    /**
+     * 加载角色APP菜单。
+     *
+     * @param roleId 角色ID
+     * @return APP菜单列表
+     */
+    /**
+     * 加载指定角色的APP底部导航菜单配置。
+     * <p>
+     * 该方法通过远程服务查询角色对应的APP TabBar菜单权限,用于动态构建APP端底部导航栏。
+     * 若角色ID为空、查询结果为空或发生异常,均返回空列表,确保前端展示默认状态或不展示导航栏,避免影响登录流程。
+     * </p>
+     *
+     * @param roleId 角色ID,用于查询对应的APP底部导航配置
+     * @return APP底部导航菜单项列表,若无配置或查询失败则返回空列表
+     */
+    List<AppTabBarMenuResDto> loadAppRoleMenus(Long roleId) {
+        // 1. 参数校验:若角色ID为空,直接返回空列表,避免无效远程调用
+        if (Objects.isNull(roleId)) {
+            log.debug("角色ID为空,跳过APP角色菜单查询");
+            return Collections.emptyList();
+        }
+
+        try {
+            // 2. 记录开始查询日志,便于追踪性能和问题
+            log.debug("开始查询APP角色菜单,roleId: {}", roleId);
+
+            // 3. 远程调用系统服务,获取角色对应的APP底部导航菜单配置
+            List<AppTabBarMenuResDto> menus = remoteUserService.queryAppTabBarMenuByRoleId(roleId);
+
+            // 4. 结果校验:若返回结果为空或无数据,记录调试日志并返回空列表
+            if (org.apache.commons.collections4.CollectionUtils.isEmpty(menus)) {
+                log.debug("角色未配置APP底部导航菜单,roleId: {}", roleId);
+                return Collections.emptyList();
+            }
+
+            // 5. 记录成功查询日志,包含菜单数量信息
+            log.info("APP角色菜单查询成功,roleId: {}, 菜单数量: {}", roleId, menus.size());
+            return menus;
+
+        } catch (Exception e) {
+            // 6. 异常处理:记录错误日志,防止因远程服务异常导致登录流程中断
+            // 返回空列表,前端可根据此情况展示默认导航或隐藏导航栏
+            log.error("查询APP角色菜单失败,roleId: {}", roleId, e);
+            return Collections.emptyList();
+        }
+    }
+
+    /**
+     * 判断角色菜单中是否包含指定APP模块。
+     *
+     * @param menus    APP菜单列表
+     * @param keywords 模块关键字
+     * @return true-包含,false-不包含
+     */
+    boolean hasAppModuleMenu(List<AppTabBarMenuResDto> menus, String... keywords) {
+        if (org.apache.commons.collections4.CollectionUtils.isEmpty(menus) || Objects.isNull(keywords)) {
+            return false;
+        }
+        return menus.stream()
+                .filter(Objects::nonNull)
+                .anyMatch(menu -> matchesAppModuleMenuKeywords(menu, keywords));
+    }
+
+    /**
+     * 判断菜单是否为模块权限逻辑菜单(仅控制模块显隐,不作为 TabBar 展示项)。
+     * <p>
+     * 该方法通过检查菜单的名称、权限标识或URL是否包含预定义的“逻辑菜单关键字”,
+     * 来识别该菜单是否属于仅用于控制APP端特定模块(如订单统计、钱包等)显示/隐藏的辅助菜单。
+     * 此类菜单不应出现在底部导航栏(TabBar)中。
+     * </p>
+     *
+     * @param menu APP菜单对象
+     * @return true-是逻辑菜单(不展示在TabBar),false-非逻辑菜单(可展示在TabBar)
+     */
+    boolean isAppModuleLogicMenu(AppTabBarMenuResDto menu) {
+        // 1. 空值校验:若菜单对象为空,直接返回false
+        if (Objects.isNull(menu)) {
+            return false;
+        }
+
+        // 2. 关键字匹配:检查菜单是否命中预定义的逻辑菜单关键字集合
+        boolean isLogicMenu = matchesAppModuleMenuKeywords(menu, APP_MODULE_LOGIC_MENU_KEYWORDS);
+        
+        // 3. 记录调试日志,便于排查哪些菜单被识别为逻辑菜单
+        if (isLogicMenu) {
+            log.debug("识别到APP模块逻辑菜单 - 菜单名称: {}, URL: {}", menu.getName(), menu.getUrl());
+        }
+        
+        return isLogicMenu;
+    }
+
+    /**
+     * 判断菜单是否匹配指定的关键字列表。
+     * <p>
+     * 该方法会遍历给定的关键字数组,检查菜单的名称(name)、权限标识(perms)或URL(url)
+     * 是否包含任意一个非空关键字(忽略大小写)。只要有一个字段匹配成功,即返回true。
+     * </p>
+     *
+     * @param menu     APP菜单对象
+     * @param keywords 待匹配的关键字数组
+     * @return true-匹配成功,false-未匹配
+     */
+    private boolean matchesAppModuleMenuKeywords(AppTabBarMenuResDto menu, String... keywords) {
+        // 1. 参数校验:若菜单对象或关键字数组为空,直接返回false
+        if (Objects.isNull(menu) || Objects.isNull(keywords)) {
+            return false;
+        }
+
+        // 2. 流式处理关键字进行匹配
+        return Arrays.stream(keywords)
+                // 过滤掉空白关键字,避免无效匹配
+                .filter(org.apache.commons.lang3.StringUtils::isNotBlank)
+                // 只要有一个关键字在菜单的 name、perms 或 url 中出现(忽略大小写),即判定为匹配
+                .anyMatch(keyword -> containsIgnoreCase(menu.getName(), keyword)
+                        || containsIgnoreCase(menu.getPerms(), keyword)
+                        || containsIgnoreCase(menu.getUrl(), keyword));
+    }
+
+    private boolean containsIgnoreCase(String source, String keyword) {
+        return org.apache.commons.lang3.StringUtils.isNotBlank(source)
+                && org.apache.commons.lang3.StringUtils.containsIgnoreCase(source, keyword);
+    }
+
+    private List<LoginResVo1.TabBarItem> buildAppTabBarByConfig(LoginResVo1 loginRes, LoginBase loginBase,
+                                                                String roleName, int flag, String entTypeNames) {
+        // 优先使用登录结果中的角色名称,若为空则使用传入的参数
+        if (Objects.isNull(loginRes)) {
+            log.debug("登录返回对象为空,跳过APP底部导航菜单查询");
+            return List.of();
+        }
+        roleName = org.apache.commons.lang3.StringUtils.defaultIfBlank(loginRes.getRoleName(), roleName);
+        
+        // 记录关键上下文信息,便于排查角色匹配问题
+        log.info("构建APP TabBar - 角色ID:{},角色名称:{},企业类型:{}", loginRes.getRoleId(), roleName, entTypeNames);
+
+        // 非APP端登录(如PC、H5等)不展示底部导航
+        // 校验客户端类型:仅对APP端(iOS/Android/Mobile)构建底部导航,PC/H5端返回空列表
+        if (!isAppLogin(loginBase)) {
+            log.debug("非APP端登录(clientType: {}),跳过APP底部导航构建", loginBase.getClientType());
+            return List.of();
+        }
+
+        Long roleId = loginRes.getRoleId();
+        log.info("开始构建APP底部导航 - userId: {}, roleId: {}", loginRes.getId(), roleId);
+
+        // 从角色菜单权限加载APP底部导航,替代原有的硬编码字典配置,实现动态配置
+        List<LoginResVo1.TabBarItem> tabBarItems = buildAppTabBarByRoleMenus(roleId);
+        
+        log.info("APP底部导航构建完成 - userId: {}, roleId: {}, 菜单数量: {}", 
+                loginRes.getId(), roleId, tabBarItems != null ? tabBarItems.size() : 0);
+        
+        return tabBarItems;
+    }
+
+
+    /**
+     * 根据角色ID查询并构建APP底部导航栏菜单项。
+     * <p>
+     * 该方法通过远程服务获取指定角色配置的APP底部导航菜单数据,
+     * 并将其转换为前端所需的TabBarItem对象列表。
+     * 若角色ID为空、未配置菜单或查询异常,则返回空列表,确保前端展示默认状态或不展示底部导航。
+     * </p>
+     *
+     * @param roleId 角色ID,用于查询对应的APP底部导航配置
+     * @return APP底部导航菜单项列表,若无配置或查询失败则返回空列表
+     */
+    List<LoginResVo1.TabBarItem> buildAppTabBarByRoleMenus(Long roleId) {
+        // 1. 参数校验:若角色ID为空,直接返回空列表,避免无效远程调用
+        if (Objects.isNull(roleId)) {
+            log.debug("角色ID为空,跳过APP底部导航菜单查询");
+            return List.of();
+        }
+
+        try {
+            // 2. 记录开始查询日志,便于追踪性能和问题
+            log.debug("开始查询APP底部导航菜单,roleId: {}", roleId);
+
+            // 3. 远程调用系统服务,获取角色对应的APP底部导航菜单配置
+            List<AppTabBarMenuResDto> menus = remoteUserService.queryAppTabBarMenuByRoleId(roleId);
+
+            // 4. 结果校验:若返回结果为空或无数据,记录调试日志并返回空列表
+            if (org.apache.commons.collections4.CollectionUtils.isEmpty(menus)) {
+                log.debug("角色未配置APP底部导航菜单,roleId: {}", roleId);
+                return List.of();
+            }
+
+            // 5. 数据转换:将DTO列表转换为前端所需的TabBarItem对象列表
+            // 过滤掉null对象及模块权限逻辑菜单(仅用于控制模块显隐,不作为底部导航展示)
+            List<LoginResVo1.TabBarItem> tabBarItems = menus.stream()
+                    .filter(Objects::nonNull)
+                    .filter(menu -> !isAppModuleLogicMenu(menu))
+                    .map(menu -> buildTabBarItem(
+                            // 菜单名称,若为空则默认为空字符串
+                            org.apache.commons.lang3.StringUtils.defaultString(menu.getName()),
+                            // 选中图标路径,若为空则默认为空字符串
+                            org.apache.commons.lang3.StringUtils.defaultString(menu.getIcon()),
+                            // 未选中图标路径,若为空则默认为空字符串
+                            org.apache.commons.lang3.StringUtils.defaultString(menu.getNotSelectedIconPath()),
+                            // 页面路由地址,若为空则默认为空字符串
+                            org.apache.commons.lang3.StringUtils.defaultString(menu.getUrl())))
+                    .toList();
+
+            // 6. 记录成功构建日志,包含菜单数量信息
+            log.info("APP底部导航菜单构建成功,roleId: {}, 菜单数量: {}", roleId, tabBarItems.size());
+            return tabBarItems;
+
+        } catch (Exception e) {
+            // 7. 异常处理:记录错误日志,防止因远程服务异常导致登录流程中断
+            // 返回空列表,前端可根据此情况展示默认导航或隐藏导航栏
+            log.error("查询APP底部导航菜单失败,roleId: {}", roleId, e);
+            return List.of();
+        }
+    }
+
+    /**
+     * 加载APP模块权限角色配置。
+     *
+     * @param roleId 角色ID
+     * @return APP角色配置
+     */
+    private AppRoleConfig loadAppRoleConfig(Long roleId) {
+        // 1. 参数校验:若角色ID为空,直接返回空配置,避免无效查询
+        if (Objects.isNull(roleId)) {
+            log.debug("角色ID为空,返回空APP角色配置");
+            return AppRoleConfig.empty();
+        }
+
+        try {
+            // 2. 记录开始加载日志,便于追踪性能和问题
+            log.debug("开始加载APP角色权限配置,roleId: {}", roleId);
+
+            // 3. 远程调用系统服务,获取APP角色权限相关的字典数据
+            // 字典类型由 DictTypeEnum.APP_ROLE_PERMISSION 定义:value 存角色ID列表,label 存 AppRoleType 枚举名。
+            List<SysDictResDto> dictList = systemService.queryDictDbByType(DictTypeEnum.APP_ROLE_PERMISSION.getType());
+
+            // 4. 数据预处理与转换
+            // 过滤掉 null 对象及 value 为空的无效字典项
+            // 将 List 转换为 Map,Key 为字典项的 value (即 AppRoleType 枚举名),Value 为字典对象本身
+            // 注意:若存在重复 Key,保留第一个出现的值 (first, second) -> first
+            Map<String, SysDictResDto> dictMap = dictList.stream()
+                    .filter(Objects::nonNull)
+                    .filter(item -> org.apache.commons.lang3.StringUtils.isNotBlank(item.getLabel()))
+                    .collect(Collectors.toMap(SysDictResDto::getLabel, item -> item, (first, second) -> first));
+
+            // 5. 构建并返回角色配置对象
+            AppRoleConfig config = AppRoleConfig.fromDict(roleId, dictMap);
+            log.debug("APP角色权限配置加载完成,roleId: {}, 匹配到的角色类型: {}", roleId, config.roleTypes);
+            return config;
+
+        } catch (Exception e) {
+            // 6. 异常处理:记录警告日志并返回空配置,确保登录流程不因配置加载失败而中断
+            // 使用 warn 级别是因为这属于非核心业务异常,但需要运维关注
+            log.warn("读取APP角色权限字典配置失败,roleId: {},错误信息: {}", roleId, e.getMessage(), e);
+            return AppRoleConfig.empty();
+        }
+    }
+
+    /**
+     * APP端角色类型枚举。
+     * 用于定义APP底部导航栏(TabBar)及模块权限控制的角色标识,
+     * 对应字典表 app_role_permission 中的 value 值。
+     */
+    enum AppRoleType {
+        /** 销售/供应商 */
+        SELLER,
+        /** 财务 */
+        FINANCE,
+        /** 采购 */
+        PURCHASE,
+        /** 门卫 */
+        DOOR_KEEPER,
+        /** 叉车司机 */
+        FORKLIFT_DRIVER,
+        /** 买家 */
+        BUYER,
+        /** 司机 */
+        DRIVER,
+        /** 物流人员 */
+        LOGISTICS,
+        /** 供应商管理员 */
+        SUPPLIER_ADMIN,
+        /** 物流商管理员 */
+        LOGISTICS_ADMIN,
+        /** 采购商管理员 */
+        PURCHASE_ADMIN
+    }
+
+    static class AppRoleConfig {
+        private final Set<AppRoleType> roleTypes;
+
+        private AppRoleConfig(Set<AppRoleType> roleTypes) {
+            this.roleTypes = roleTypes;
+        }
+
+        static AppRoleConfig empty() {
+            return new AppRoleConfig(Collections.emptySet());
+        }
+
+        /**
+         * 根据角色ID和字典映射构建APP角色配置。
+         * <p>
+         * 该方法遍历字典项,查找value字段中包含当前roleId的配置项,
+         * 并将其label字段解析为对应的AppRoleType枚举值。
+         * </p>
+         *
+         * @param roleId   当前用户的角色ID
+         * @param dictMap  APP角色权限字典映射,Key为AppRoleType枚举名,Value为字典对象
+         * @return {@link AppRoleConfig} 包含匹配到的角色类型集合
+         */
+        static AppRoleConfig fromDict(Long roleId, Map<String, SysDictResDto> dictMap) {
+            // 1. 参数校验:若角色ID为空或字典数据为空,直接返回空配置
+            if (Objects.isNull(roleId) || dictMap == null || dictMap.isEmpty()) {
+                log.debug("构建APP角色配置失败:roleId为空或字典数据为空");
+                return empty();
+            }
+
+            String roleIdValue = String.valueOf(roleId);
+            log.debug("开始构建APP角色配置,roleId: {}", roleIdValue);
+
+            // 2. 流式处理字典数据,提取匹配的角色类型
+            Set<AppRoleType> roleTypes = dictMap.values().stream()
+                    // 过滤掉null对象
+                    .filter(Objects::nonNull)
+                    // 核心逻辑:判断字典项的value(角色ID列表字符串)是否包含当前roleId
+                    .filter(item -> {
+                        boolean matched = matchRoleId(roleIdValue, item.getValue());
+                        if (matched) {
+                            log.debug("字典项匹配成功 - Label: {}, Value: {}", item.getLabel(), item.getValue());
+                        }
+                        return matched;
+                    })
+                    // 获取字典项的Label,即AppRoleType的枚举名称
+                    .map(SysDictResDto::getLabel)
+                    // 将字符串转换为AppRoleType枚举对象
+                    .map(AppRoleConfig::parseRoleType)
+                    // 过滤掉转换失败的Optional.empty()
+                    .filter(Optional::isPresent)
+                    // 解包Optional获取枚举值
+                    .map(Optional::get)
+                    // 收集为Set集合,去重
+                    .collect(Collectors.toSet());
+
+            log.info("APP角色配置构建完成,roleId: {}, 匹配到的角色类型数量: {}, 类型列表: {}", 
+                    roleIdValue, roleTypes.size(), roleTypes);
+            
+            return new AppRoleConfig(roleTypes);
+        }
+
+        boolean match(AppRoleType roleType) {
+            return roleTypes.contains(roleType);
+        }
+
+        private static boolean matchRoleId(String roleId, String roleIds) {
+            if (org.apache.commons.lang3.StringUtils.isBlank(roleId) || org.apache.commons.lang3.StringUtils.isBlank(roleIds)) {
+                return false;
+            }
+            return Arrays.stream(roleIds.split(","))
+                    .map(String::trim)
+                    .filter(org.apache.commons.lang3.StringUtils::isNotBlank)
+                    .anyMatch(roleId::equals);
+        }
+
+        private static Optional<AppRoleType> parseRoleType(String roleType) {
+            if (org.apache.commons.lang3.StringUtils.isBlank(roleType)) {
+                return Optional.empty();
+            }
+            try {
+                return Optional.of(AppRoleType.valueOf(roleType.trim().toUpperCase(Locale.ROOT)));
+            } catch (Exception e) {
+                log.warn("APP角色权限字典value配置错误,value:{}", roleType);
+                return Optional.empty();
+            }
+        }
+    }
 
     private void applyAppModulePermissions(LoginResVo1 loginRes, LoginBase loginBase, String roleName) {
         roleName = loginRes.getRoleName();

+ 213 - 0
sckw-auth/src/test/java/com/sckw/auth/service/impl/AuthServiceImplTest.java

@@ -0,0 +1,213 @@
+package com.sckw.auth.service.impl;
+
+import com.sckw.auth.model.vo.req.LoginBase;
+import com.sckw.auth.model.vo.res.LoginResVo1;
+import com.sckw.system.api.model.dto.res.SysDictResDto;
+import com.sckw.system.api.RemoteUserService;
+import com.sckw.system.api.model.dto.res.AppTabBarMenuResDto;
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * AuthServiceImpl单元测试。
+ */
+public class AuthServiceImplTest {
+
+    /**
+     * 验证APP角色权限可通过字典中的角色ID匹配,不依赖角色名称。
+     */
+    @Test
+    public void appRoleConfigShouldMatchRoleIdFromDict() {
+        Map<String, SysDictResDto> dictMap = new HashMap<>();
+        dictMap.put("SELLER", buildDict("1001,1002", "SELLER"));
+        dictMap.put("FINANCE", buildDict("2001", "FINANCE"));
+
+        AuthServiceImpl.AppRoleConfig appRoleConfig = AuthServiceImpl.AppRoleConfig.fromDict(1002L, dictMap);
+
+        Assert.assertTrue(appRoleConfig.match(AuthServiceImpl.AppRoleType.SELLER));
+        Assert.assertFalse(appRoleConfig.match(AuthServiceImpl.AppRoleType.FINANCE));
+    }
+
+    /**
+     * 验证未配置当前角色ID时不命中任何APP角色类型。
+     */
+    @Test
+    public void appRoleConfigShouldNotMatchWhenRoleIdMissing() {
+        Map<String, SysDictResDto> dictMap = new HashMap<>();
+        dictMap.put("PURCHASE", buildDict("3001", "PURCHASE"));
+
+        AuthServiceImpl.AppRoleConfig appRoleConfig = AuthServiceImpl.AppRoleConfig.fromDict(9999L, dictMap);
+
+        Assert.assertFalse(appRoleConfig.match(AuthServiceImpl.AppRoleType.PURCHASE));
+    }
+
+    /**
+     * 验证APP底部导航通过角色菜单权限返回,并正确映射前端需要的字段。
+     */
+    @Test
+    public void appTabBarShouldBuildFromRoleMenus() throws Exception {
+        AuthServiceImpl authService = new AuthServiceImpl();
+        setRemoteUserService(authService, buildRemoteUserServiceProxy());
+
+        List<com.sckw.auth.model.vo.res.LoginResVo1.TabBarItem> tabBarItems =
+                authService.buildAppTabBarByRoleMenus(1001L);
+
+        Assert.assertEquals(1, tabBarItems.size());
+        com.sckw.auth.model.vo.res.LoginResVo1.TabBarItem item = tabBarItems.get(0);
+        Assert.assertEquals("首页", item.getText());
+        Assert.assertEquals("/static/icon/home-selected.png", item.getIconPath());
+        Assert.assertEquals("/static/icon/home.png", item.getNotSelectedIconPath());
+        Assert.assertEquals("/pages/home/index", item.getPagePath());
+    }
+
+    /**
+     * 验证APP模块开关受角色菜单结果控制:没有销售统计菜单时,不返回销售统计模块。
+     */
+    @Test
+    public void appModulePermissionsShouldFollowRoleMenus() throws Exception {
+        AuthServiceImpl authService = new AuthServiceImpl();
+        setRemoteUserService(authService, buildRemoteUserServiceProxy(buildOrderStatisticsMenu()));
+
+        LoginResVo1 loginRes = new LoginResVo1();
+        loginRes.setId(1L);
+        loginRes.setRoleId(9999L);
+        LoginBase loginBase = new LoginBase();
+        loginBase.setClientType("app");
+
+        Method method = AuthServiceImpl.class.getDeclaredMethod(
+                "applyAppModulePermissionsByConfig", LoginResVo1.class, LoginBase.class);
+        method.setAccessible(true);
+        method.invoke(authService, loginRes, loginBase);
+
+        Assert.assertTrue(loginRes.getShowOrderStatisticsModule());
+        Assert.assertFalse(loginRes.getShowSalesStatisticsModule());
+        Assert.assertFalse(loginRes.getShowWalletModule());
+        Assert.assertFalse(loginRes.getShowAddressModule());
+    }
+
+    /**
+     * 验证模块权限逻辑菜单不会出现在底部导航 TabBar 中。
+     */
+    @Test
+    public void appTabBarShouldExcludeModuleLogicMenus() throws Exception {
+        AuthServiceImpl authService = new AuthServiceImpl();
+        AppTabBarMenuResDto homeMenu = new AppTabBarMenuResDto();
+        homeMenu.setName("首页");
+        homeMenu.setIcon("/static/icon/home-selected.png");
+        homeMenu.setNotSelectedIconPath("/static/icon/home.png");
+        homeMenu.setUrl("/pages/home/index");
+        setRemoteUserService(authService, buildRemoteUserServiceProxy(
+                homeMenu, buildOrderStatisticsMenu(), buildWalletMenu(), buildAddressMenu()));
+
+        List<LoginResVo1.TabBarItem> tabBarItems = authService.buildAppTabBarByRoleMenus(1001L);
+
+        Assert.assertEquals(1, tabBarItems.size());
+        Assert.assertEquals("首页", tabBarItems.get(0).getText());
+    }
+
+    /**
+     * 验证钱包、地址模块只由菜单结果控制,并按菜单子项返回钱包展示项。
+     */
+    @Test
+    public void appModulePermissionsShouldBuildWalletItemsFromMenus() throws Exception {
+        AuthServiceImpl authService = new AuthServiceImpl();
+        setRemoteUserService(authService, buildRemoteUserServiceProxy(
+                buildWalletMenu(), buildPrepayBalanceMenu(), buildPendingFreightMenu(), buildAddressMenu()));
+
+        LoginResVo1 loginRes = new LoginResVo1();
+        loginRes.setId(1L);
+        loginRes.setRoleId(9999L);
+        LoginBase loginBase = new LoginBase();
+        loginBase.setClientType("app");
+
+        Method method = AuthServiceImpl.class.getDeclaredMethod(
+                "applyAppModulePermissionsByConfig", LoginResVo1.class, LoginBase.class);
+        method.setAccessible(true);
+        method.invoke(authService, loginRes, loginBase);
+
+        Assert.assertFalse(loginRes.getShowOrderStatisticsModule());
+        Assert.assertFalse(loginRes.getShowSalesStatisticsModule());
+        Assert.assertTrue(loginRes.getShowWalletModule());
+        Assert.assertTrue(loginRes.getShowAddressModule());
+        Assert.assertEquals(Arrays.asList("PREPAY_BALANCE", "PENDING_FREIGHT"), loginRes.getWalletModuleItems());
+    }
+
+    private SysDictResDto buildDict(String value, String label) {
+        SysDictResDto dict = new SysDictResDto();
+        dict.setValue(value);
+        dict.setLabel(label);
+        return dict;
+    }
+
+    private RemoteUserService buildRemoteUserServiceProxy() {
+        AppTabBarMenuResDto menu = new AppTabBarMenuResDto();
+        menu.setName("首页");
+        menu.setIcon("/static/icon/home-selected.png");
+        menu.setNotSelectedIconPath("/static/icon/home.png");
+        menu.setUrl("/pages/home/index");
+        return buildRemoteUserServiceProxy(menu);
+    }
+
+    private AppTabBarMenuResDto buildOrderStatisticsMenu() {
+        AppTabBarMenuResDto menu = new AppTabBarMenuResDto();
+        menu.setName("\u8ba2\u5355\u7edf\u8ba1");
+        menu.setPerms("orderStatistics");
+        menu.setUrl("/pages/order/statistics");
+        return menu;
+    }
+
+    private AppTabBarMenuResDto buildWalletMenu() {
+        AppTabBarMenuResDto menu = new AppTabBarMenuResDto();
+        menu.setName("\u94b1\u5305");
+        menu.setPerms("wallet");
+        return menu;
+    }
+
+    private AppTabBarMenuResDto buildPrepayBalanceMenu() {
+        AppTabBarMenuResDto menu = new AppTabBarMenuResDto();
+        menu.setName("\u9884\u4ed8\u4f59\u989d");
+        menu.setPerms("PREPAY_BALANCE");
+        return menu;
+    }
+
+    private AppTabBarMenuResDto buildPendingFreightMenu() {
+        AppTabBarMenuResDto menu = new AppTabBarMenuResDto();
+        menu.setName("\u5f85\u4ed8\u8fd0\u8d39");
+        menu.setPerms("PENDING_FREIGHT");
+        return menu;
+    }
+
+    private AppTabBarMenuResDto buildAddressMenu() {
+        AppTabBarMenuResDto menu = new AppTabBarMenuResDto();
+        menu.setName("\u5730\u5740");
+        menu.setPerms("address");
+        return menu;
+    }
+
+    private RemoteUserService buildRemoteUserServiceProxy(AppTabBarMenuResDto... menus) {
+        return (RemoteUserService) Proxy.newProxyInstance(
+                RemoteUserService.class.getClassLoader(),
+                new Class[]{RemoteUserService.class},
+                (proxy, method, args) -> {
+                    if ("queryAppTabBarMenuByRoleId".equals(method.getName())) {
+                        return Arrays.asList(menus);
+                    }
+                    return null;
+                });
+    }
+
+    private void setRemoteUserService(AuthServiceImpl authService, RemoteUserService remoteUserService) throws Exception {
+        Field field = AuthServiceImpl.class.getDeclaredField("remoteUserService");
+        field.setAccessible(true);
+        field.set(authService, remoteUserService);
+    }
+}

+ 1 - 0
sckw-common/sckw-common-core/src/main/java/com/sckw/core/common/enums/enums/DictTypeEnum.java

@@ -11,6 +11,7 @@ import lombok.Getter;
 @Getter
 @AllArgsConstructor
 public enum DictTypeEnum {
+    APP_ROLE_PERMISSION("app_role_permission", "APP角色权限配置"),
     MSG_CATEGORY("msg_category", "消息分类"),
     MSG_STATUS("msg_status", "消息状态"),
     SEND_SMS_TYPE("send_sms_type", "发送短信类型"),

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

@@ -77,8 +77,27 @@ public interface RemoteContractService {
      */
     TradeContractGoodsDto queryTradeContractNew(Long entId, Long goodsId, LocalDateTime time);
 
+    /**
+     * 查询当前企业作为采购方或销售方时,指定商品最新有效贸易合同签约价。
+     *
+     * @param entId   当前企业ID
+     * @param goodsId 商品ID
+     * @param time    当前时间
+     * @return 最新有效贸易合同商品签约价
+     */
+    TradeContractGoodsDto queryTradeContractNewByEnt(Long entId, Long goodsId, LocalDateTime time);
+
     List<Long> queryNewSignGoods(Long entId, LocalDateTime time);
 
+    /**
+     * 查询当前企业作为采购方或销售方时,已签约且有效的贸易合同商品ID集合。
+     *
+     * @param entId 当前企业ID
+     * @param time  当前时间
+     * @return 商品ID集合
+     */
+    List<Long> queryNewSignGoodsByEnt(Long entId, LocalDateTime time);
+
     /**
      * 查询签约的商品
      *
@@ -148,6 +167,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();

+ 5 - 0
sckw-modules-api/sckw-contract-api/src/main/java/com/sckw/contract/api/model/dto/res/ContractTradeOrderDto.java

@@ -29,4 +29,9 @@ public class ContractTradeOrderDto implements Serializable {
      * 企业类型(1-供应 2-采购)
      */
     private Integer entType;
+
+    /**
+     * 下拉框输入匹配关键字,支持按“合同名称-采购企业名称”模糊匹配。
+     */
+    private String keyword;
 }

+ 5 - 0
sckw-modules-api/sckw-fleet-api/src/main/java/com/sckw/fleet/api/model/vo/RTruckVo.java

@@ -100,4 +100,9 @@ public class RTruckVo implements Serializable {
      * 能源类型
      */
     private Integer energyType;
+
+    /**
+     * 最大运输距离,单位:公里;为空表示车辆未维护该限制。
+     */
+    private BigDecimal maxTransportDistance;
 }

+ 7 - 0
sckw-modules-api/sckw-system-api/src/main/java/com/sckw/system/api/RemoteSystemService.java

@@ -43,6 +43,13 @@ public interface RemoteSystemService {
      */
     List<SysDictResDto> queryDictByType(String type);
 
+    /**
+     * @param type 字典类型
+     * @return SysDictResDto
+     * @desc 根据字典类型直接查询数据库,不读取Redis缓存,适用于运行期频繁调整的配置
+     */
+    List<SysDictResDto> queryDictDbByType(String type);
+
     /**
      * @param type 类型
      * @return SysDictResDto

+ 8 - 0
sckw-modules-api/sckw-system-api/src/main/java/com/sckw/system/api/RemoteUserService.java

@@ -107,6 +107,14 @@ public interface RemoteUserService {
      */
     List<UserAccessMenuInfoResDto> queryRoleMenu(Long roleId);
 
+    /**
+     * 根据角色ID查询APP底部导航菜单。
+     *
+     * @param roleId 角色ID
+     * @return APP底部导航菜单列表
+     */
+    List<AppTabBarMenuResDto> queryAppTabBarMenuByRoleId(Long roleId);
+
 
     /**
      * @param userLoginReqDto 用户登录信息

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

@@ -0,0 +1,51 @@
+package com.sckw.system.api.model.dto.res;
+
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * APP底部导航菜单返回对象。
+ */
+@Data
+public class AppTabBarMenuResDto implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 753470578366262433L;
+
+    /**
+     * 菜单ID
+     */
+    private Long id;
+
+    /**
+     * 菜单名称
+     */
+    private String name;
+
+    /**
+     * 页面路径
+     */
+    private String url;
+
+    /**
+     * 权限标识
+     */
+    private String perms;
+
+    /**
+     * 选中菜单图标路径
+     */
+    private String icon;
+
+    /**
+     * 未选中菜单图标路径
+     */
+    private String notSelectedIconPath;
+
+    /**
+     * 排序
+     */
+    private Integer sort;
+}

+ 5 - 0
sckw-modules-api/sckw-transport-api/src/main/java/com/sckw/transport/api/model/param/AddLogisticOrderParam.java

@@ -260,4 +260,9 @@ public class AddLogisticOrderParam implements Serializable {
      */
     private String goodsUnit;
 
+    /**
+     * 是否代理属性:0-否,1-是
+     */
+    private Integer agentFlag;
+
 }

+ 6 - 1
sckw-modules/sckw-contract/pom.xml

@@ -88,6 +88,11 @@
             <groupId>org.springframework</groupId>
             <artifactId>spring-test</artifactId>
         </dependency>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <scope>test</scope>
+        </dependency>
         <dependency>
             <groupId>com.sckw</groupId>
             <artifactId>sckw-common-log</artifactId>
@@ -150,4 +155,4 @@
             </plugin>
         </plugins>
     </build>
-</project>
+</project>

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

@@ -13,6 +13,8 @@ import com.sckw.contract.api.model.vo.*;
 import com.sckw.contract.dao.*;
 import com.sckw.contract.model.KwcContractProxy;
 import com.sckw.contract.model.KwcContractProxyGoods;
+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;
@@ -47,6 +49,7 @@ import org.springframework.data.redis.core.RedisTemplate;
 
 import java.math.BigDecimal;
 import java.time.LocalDateTime;
+import java.time.ZoneId;
 import java.util.*;
 import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
@@ -94,6 +97,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) {
@@ -303,11 +308,209 @@ public class RemoteContractServiceImpl implements RemoteContractService {
         return kwcContractTradeMapper.queryNewSignPrice(entId, goodsId, time);
     }
 
+    /**
+     * 根据企业ID、商品ID和指定时间,查询该企业在当时有效的最新贸易合同中的商品价格。
+     * <p>
+     * 业务逻辑:
+     * 1. 参数校验:确保 entId, goodsId, time 不为空。
+     * 2. 查找关联合同:查询该企业(作为供应商或采购商)参与的所有未删除的贸易合同单元,获取合同ID列表。
+     * 3. 筛选商品关联:在上述合同ID列表中,查找包含指定商品ID且未删除的合同商品记录。
+     * 4. 构建映射:将合同商品记录以 contractId 为 key 建立映射,方便后续快速查找。
+     * 5. 确定有效合同:在筛选出的合同中,查找满足以下条件的唯一最新合同:
+     *    - 状态为已签约 (SIGNED)
+     *    - 未删除
+     *    - 指定时间在合同有效期内 (startTime <= time < endTime 或 endTime 为空)
+     *    - 按创建时间倒序排列,取第一条(即最近创建的符合条件的合同)
+     * 6. 返回结果:如果找到有效合同,返回对应的合同ID、商品ID和价格;否则返回 null。
+     *
+     * @param entId   企业ID
+     * @param goodsId 商品ID
+     * @param time    查询时间点
+     * @return 贸易合同商品信息DTO,包含合同ID、商品ID和价格;若无匹配数据则返回 null
+     */
+    @Override
+    public TradeContractGoodsDto queryTradeContractNewByEnt(Long entId, Long goodsId, LocalDateTime time) {
+        // 1. 参数校验
+        if (Objects.isNull(entId) || Objects.isNull(goodsId) || Objects.isNull(time)) {
+            log.warn("查询企业商品最新签约价参数异常,entId:{},goodsId:{},time:{}", entId, goodsId, time);
+            return null;
+        }
+
+        log.debug("开始查询企业[{}]商品[{}]在时间[{}]的最新签约价", entId, goodsId, time);
+
+        // 将 LocalDateTime 转换为 Date 用于数据库查询
+        Date queryTime = Date.from(time.atZone(ZoneId.systemDefault()).toInstant());
+
+        // 2. 查找该企业参与的所有有效贸易合同ID
+        // 条件:未删除、企业ID匹配、单位类型为供应商或采购商
+        List<Long> contractIdsByEnt = kwcContractTradeUnitMapper.selectList(new LambdaQueryWrapper<KwcContractTradeUnit>()
+                        .eq(KwcContractTradeUnit::getDelFlag, Global.NO)
+                        .eq(KwcContractTradeUnit::getEntId, entId)
+                        .in(KwcContractTradeUnit::getUnitType, List.of(CooperateTypeEnum.SUPPLIER.getCode(), CooperateTypeEnum.PURCHASER.getCode())))
+                .stream()
+                .map(KwcContractTradeUnit::getContractId)
+                .filter(Objects::nonNull)
+                .distinct()
+                .toList();
+
+        if (CollectionUtils.isEmpty(contractIdsByEnt)) {
+            log.debug("企业[{}]未找到任何关联的贸易合同单元", entId);
+            return null;
+        }
+        log.debug("企业[{}]关联的合同ID数量: {}", entId, contractIdsByEnt.size());
+
+        // 3. 在这些合同中查找包含指定商品的记录
+        List<KwcContractTradeGoods> contractGoods = kwcContractTradeGoodsMapper.selectList(new LambdaQueryWrapper<KwcContractTradeGoods>()
+                .eq(KwcContractTradeGoods::getDelFlag, Global.NO)
+                .eq(KwcContractTradeGoods::getGoodsId, goodsId)
+                .in(KwcContractTradeGoods::getContractId, contractIdsByEnt));
+
+        if (CollectionUtils.isEmpty(contractGoods)) {
+            log.debug("在企业[{}]的合同中找到商品[{}]的记录为空", entId, goodsId);
+            return null;
+        }
+
+        // 4. 构建合同ID到合同商品对象的映射,以便后续通过合同ID快速获取商品信息
+        Map<Long, KwcContractTradeGoods> contractGoodsMap = contractGoods.stream()
+                .filter(item -> Objects.nonNull(item.getContractId()))
+                .collect(Collectors.toMap(KwcContractTradeGoods::getContractId, item -> item, (oldValue, newValue) -> oldValue));
+
+        if (CollectionUtils.isEmpty(contractGoodsMap)) {
+            log.warn("构建合同商品映射失败,contractGoodsMap为空");
+            return null;
+        }
+
+        // 5. 查询在指定时间点有效的最新已签约合同
+        // 条件:
+        // - 未删除
+        // - 状态为已签约
+        // - 合同ID在之前筛选出的包含该商品的合同ID集合中
+        // - 合同开始时间 <= 查询时间
+        // - 合同结束时间 > 查询时间 或 结束时间为空(表示长期有效)
+        // - 按创建时间倒序,取最新的一条
+        KwcContractTrade contractTrade = kwcContractTradeMapper.selectOne(new LambdaQueryWrapper<KwcContractTrade>()
+                .eq(KwcContractTrade::getDelFlag, Global.NO)
+                .eq(KwcContractTrade::getStatus, ContractStatusEnum.SIGNED.getCode())
+                .in(KwcContractTrade::getId, contractGoodsMap.keySet())
+                .lt(KwcContractTrade::getStartTime, queryTime)
+                .and(wrapper -> wrapper.gt(KwcContractTrade::getEndTime, queryTime).or().isNull(KwcContractTrade::getEndTime))
+                .orderByDesc(KwcContractTrade::getCreateTime)
+                .last("limit 1"));
+
+        if (Objects.isNull(contractTrade)) {
+            log.debug("未找到企业[{}]商品[{}]在时间[{}]的有效已签约合同", entId, goodsId, time);
+            return null;
+        }
+        log.info("找到企业[{}]商品[{}]的有效合同ID: {}", entId, goodsId, contractTrade.getId());
+
+        // 6. 从映射中获取对应的商品信息并组装返回对象
+        KwcContractTradeGoods newestContractGoods = contractGoodsMap.get(contractTrade.getId());
+        if (Objects.isNull(newestContractGoods)) {
+            log.error("数据不一致:合同ID[{}]在contractGoodsMap中不存在", contractTrade.getId());
+            return null;
+        }
+
+        TradeContractGoodsDto tradeContractGoodsDto = new TradeContractGoodsDto();
+        tradeContractGoodsDto.setContractId(contractTrade.getId());
+        tradeContractGoodsDto.setGoodsId(goodsId);
+        tradeContractGoodsDto.setPrice(newestContractGoods.getPrice());
+
+        log.debug("查询成功,返回合同ID: {}, 价格: {}", tradeContractGoodsDto.getContractId(), tradeContractGoodsDto.getPrice());
+        return tradeContractGoodsDto;
+    }
+
     @Override
     public List<Long> queryNewSignGoods(Long entId, LocalDateTime time) {
         return kwcContractTradeMapper.queryNewSignGoods(entId, time);
     }
 
+
+    /**
+     * 查询指定企业在给定时间点已签约且有效的贸易合同关联的商品ID列表。
+     * <p>
+     * 业务逻辑:
+     * 1. 参数校验:确保 entId 和 time 不为空。
+     * 2. 查找关联合同:查询该企业(作为供应商 SUPPLIER 或采购商 PURCHASER)参与的所有未删除的贸易合同单元,获取合同ID列表。
+     * 3. 筛选有效合同:在上述合同ID列表中,筛选出满足以下条件的合同:
+     *    - 未删除 (delFlag = 0)
+     *    - 状态为已签约 (status = SIGNED)
+     *    - 指定时间在合同有效期内 (startTime <= time < endTime 或 endTime 为空表示长期有效)
+     * 4. 提取商品ID:根据筛选出的有效合同ID,查询对应的合同商品记录,提取所有未删除的商品ID并去重。
+     *
+     * @param entId 企业ID
+     * @param time  查询时间点
+     * @return 商品ID列表,若无匹配数据则返回空列表
+     */
+    @Override
+    public List<Long> queryNewSignGoodsByEnt(Long entId, LocalDateTime time) {
+        // 1. 参数校验
+        if (Objects.isNull(entId) || Objects.isNull(time)) {
+            log.warn("查询企业已签约商品参数异常,entId:{},time:{}", entId, time);
+            return Collections.emptyList();
+        }
+
+        log.debug("开始查询企业[{}]在时间[{}]的已签约商品列表", entId, time);
+
+        // 将 LocalDateTime 转换为 Date 用于数据库查询
+        Date queryTime = Date.from(time.atZone(ZoneId.systemDefault()).toInstant());
+
+        // 2. 查找该企业参与的所有贸易合同ID
+        // 条件:未删除、企业ID匹配、单位类型为供应商或采购商
+        List<Long> contractIdsByEnt = kwcContractTradeUnitMapper.selectList(new LambdaQueryWrapper<KwcContractTradeUnit>()
+                        .eq(KwcContractTradeUnit::getDelFlag, Global.NO)
+                        .eq(KwcContractTradeUnit::getEntId, entId)
+                        .in(KwcContractTradeUnit::getUnitType, List.of(CooperateTypeEnum.SUPPLIER.getCode(), CooperateTypeEnum.PURCHASER.getCode())))
+                .stream()
+                .map(KwcContractTradeUnit::getContractId)
+                .filter(Objects::nonNull)
+                .distinct()
+                .toList();
+
+        if (CollectionUtils.isEmpty(contractIdsByEnt)) {
+            log.debug("企业[{}]未找到任何关联的贸易合同单元", entId);
+            return Collections.emptyList();
+        }
+        log.debug("企业[{}]关联的合同ID数量: {}", entId, contractIdsByEnt.size());
+
+        // 3. 筛选在指定时间点有效的已签约合同
+        // 条件:
+        // - 未删除
+        // - 状态为已签约
+        // - 合同ID在之前筛选出的集合中
+        // - 合同开始时间 <= 查询时间
+        // - 合同结束时间 > 查询时间 或 结束时间为空(表示长期有效)
+        List<Long> validContractIds = kwcContractTradeMapper.selectList(new LambdaQueryWrapper<KwcContractTrade>()
+                        .eq(KwcContractTrade::getDelFlag, Global.NO)
+                        .eq(KwcContractTrade::getStatus, ContractStatusEnum.SIGNED.getCode())
+                        .in(KwcContractTrade::getId, contractIdsByEnt)
+                        .lt(KwcContractTrade::getStartTime, queryTime)
+                        .and(wrapper -> wrapper.gt(KwcContractTrade::getEndTime, queryTime).or().isNull(KwcContractTrade::getEndTime)))
+                .stream()
+                .map(KwcContractTrade::getId)
+                .filter(Objects::nonNull)
+                .distinct()
+                .toList();
+
+        if (CollectionUtils.isEmpty(validContractIds)) {
+            log.debug("企业[{}]在时间[{}]没有有效的已签约合同", entId, time);
+            return Collections.emptyList();
+        }
+        log.info("企业[{}]在时间[{}]找到{}个有效的已签约合同", entId, time, validContractIds.size());
+
+        // 4. 根据有效合同ID查询关联的商品ID
+        List<Long> goodsIds = kwcContractTradeGoodsMapper.selectList(new LambdaQueryWrapper<KwcContractTradeGoods>()
+                        .eq(KwcContractTradeGoods::getDelFlag, Global.NO)
+                        .in(KwcContractTradeGoods::getContractId, validContractIds))
+                .stream()
+                .map(KwcContractTradeGoods::getGoodsId)
+                .filter(Objects::nonNull)
+                .distinct()
+                .toList();
+
+        log.debug("查询成功,企业[{}]在时间[{}]关联的商品ID数量: {}", entId, time, goodsIds.size());
+        return goodsIds;
+    }
+
     @Override
     public List<Long> querySignTradeContract(Long entId, LocalDateTime time) {
         return kwcContractTradeMapper.querySignTradeContract(entId, time);
@@ -554,6 +757,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<>();

+ 5 - 0
sckw-modules/sckw-contract/src/main/java/com/sckw/contract/model/dto/res/QueryListResDto.java

@@ -178,4 +178,9 @@ public class QueryListResDto {
      */
     private Integer unloadWay;
 
+    /**
+     * 是否代理属性:0-否,1-是
+     */
+    private Integer agentFlag;
+
 }

+ 7 - 1
sckw-modules/sckw-contract/src/main/java/com/sckw/contract/model/entity/KwcContractTrade.java

@@ -173,4 +173,10 @@ public class KwcContractTrade implements Serializable {
     @TableField("dispatch_way")
     private Integer dispatchWay;
 
-}
+    /**
+     * 是否代理属性:0-否,1-是
+     */
+    @TableField("agent_flag")
+    private Integer agentFlag;
+
+}

+ 6 - 0
sckw-modules/sckw-contract/src/main/java/com/sckw/contract/model/vo/req/QueryListReqVo.java

@@ -123,5 +123,11 @@ public class QueryListReqVo extends PageReq implements Serializable {
     @Schema(description = "派车方式(1-手动派车、2-自动派车)")
     private Integer dispatchWay;
 
+    /**
+     * 是否代理属性:0-否,1-是
+     */
+    @Schema(description = "是否代理属性:0-否,1-是")
+    private Integer agentFlag;
+
 
 }

+ 6 - 0
sckw-modules/sckw-contract/src/main/java/com/sckw/contract/model/vo/req/QueryTradeReq.java

@@ -55,4 +55,10 @@ public class QueryTradeReq extends PageReq implements Serializable {
      */
     @Schema(description = "派车方式")
     private Integer dispatchWay;
+
+    /**
+     * 是否代理属性:0-否,1-是
+     */
+    @Schema(description = "是否代理属性:0-否,1-是")
+    private Integer agentFlag;
 }

+ 13 - 1
sckw-modules/sckw-contract/src/main/java/com/sckw/contract/model/vo/res/QueryListResVo.java

@@ -238,7 +238,19 @@ public class QueryListResVo implements Serializable {
     @Schema(description = "派车方式秒速")
     private String dispatchWayDesc;
 
+    /**
+     * 是否代理属性:0-否,1-是
+     */
+    @Schema(description = "是否代理属性:0-否,1-是")
+    private Integer agentFlag;
+
+    /**
+     * 是否代理属性描述
+     */
+    @Schema(description = "是否代理属性描述")
+    private String agentFlagDesc;
+
 
 
 
-}
+}

+ 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);

+ 3 - 1
sckw-modules/sckw-contract/src/main/java/com/sckw/contract/repository/KwcContractTradeRepository.java

@@ -63,7 +63,8 @@ public class KwcContractTradeRepository extends ServiceImpl<KwcContractTradeMapp
 
     public List<KwcContractTrade> queryTradeListByPageList(String contractCode,
                                                          String contractName, String supplementCode,
-                                                            Set<Long> contractIds, Integer status, Integer dispatchWay, int pageNum, int pageSize) {
+                                                            Set<Long> contractIds, Integer status, Integer dispatchWay,
+                                                            Integer agentFlag, int pageNum, int pageSize) {
         return list( Wrappers.<KwcContractTrade>lambdaQuery()
                 .eq(KwcContractTrade::getDelFlag, 0)
                 .like(StringUtils.isNotBlank(contractCode),KwcContractTrade::getContractNo, contractCode)
@@ -72,6 +73,7 @@ public class KwcContractTradeRepository extends ServiceImpl<KwcContractTradeMapp
                 .like(StringUtils.isNotBlank(supplementCode),KwcContractTrade::getContractPid, supplementCode)
                 .in(CollectionUtils.isNotEmpty(contractIds),KwcContractTrade::getId, contractIds)
                 .eq(Objects.nonNull(dispatchWay),KwcContractTrade::getDispatchWay, dispatchWay)
+                .eq(Objects.nonNull(agentFlag),KwcContractTrade::getAgentFlag, agentFlag)
                 .orderByDesc(KwcContractTrade::getUpdateTime)
                 .orderByDesc(KwcContractTrade::getId));
 

+ 222 - 5
sckw-modules/sckw-contract/src/main/java/com/sckw/contract/service/operateService/KwcContractTradeService.java

@@ -92,6 +92,11 @@ import java.util.stream.Collectors;
 @RequiredArgsConstructor
 public class KwcContractTradeService {
 
+    /**
+     * 供应单位代理属性类型
+     */
+    static final int SUPPLY_AGENT_ENT_TYPE = 4;
+
 
     @Autowired
     private KwcContractTradeMapper kwcContractTradeMapper;
@@ -199,6 +204,7 @@ public class KwcContractTradeService {
         queryTradeReq.setSupplyEntId(reqVo.getSupplyEntId());
         queryTradeReq.setDispatchWay(reqVo.getDispatchWay());
         queryTradeReq.setStatus(reqVo.getStatus());
+        queryTradeReq.setAgentFlag(reqVo.getAgentFlag());
         queryTradeReq.setPageNum(reqVo.getPageNum());
         queryTradeReq.setPageSize(reqVo.getPageSize());
 
@@ -232,6 +238,7 @@ public class KwcContractTradeService {
         for (QueryListResDto queryListResDto : queryListResDtos) {
             QueryListResVo queryListResVo = new QueryListResVo();
             BeanUtils.copyProperties(queryListResDto, queryListResVo);
+            queryListResVo.setAgentFlagDesc(Objects.equals(queryListResDto.getAgentFlag(), Global.YES) ? "是" : "否");
 
 
             if (Objects.nonNull(queryListResDto.getUnitType())) {
@@ -313,6 +320,7 @@ public class KwcContractTradeService {
         KwcContractTrade kwcContractTrade = getKwcContractTrade(baseInfo, reqVo.getGoodsInfo(), reqVo.getRemark(), reqVo.getPid());
         long contactId = new IdWorker(1L).nextId();
         kwcContractTrade.setId(contactId);
+        kwcContractTrade.setAgentFlag(querySupplyAgentFlag(baseInfo.getProvideEntId()));
         EntCacheResDto entCacheResDto = remoteSystemService.queryEntTreeById(LoginUserHolder.getEntId());
         if (Objects.nonNull(entCacheResDto) && StringUtils.isNotBlank(entCacheResDto.getEntTypes())) {
             if (org.apache.commons.lang3.StringUtils.equals(entCacheResDto.getEntTypes(), String.valueOf(EntTypeEnum.SUPPLIER.getCode()))) {
@@ -489,6 +497,82 @@ public class KwcContractTradeService {
         return kwcContractTrade;
     }
 
+    /**
+     * 根据供应单位企业属性计算贸易合同代理标记。
+     *
+     * @param supplyEntId 供应单位ID
+     * @return 0-否,1-是
+     */
+    /**
+     * 查询供应单位是否具备代理属性。
+     * <p>
+     * 通过远程服务获取企业属性信息,判断该企业是否被标记为“供应单位代理”。
+     * 如果具备代理属性,返回 Global.YES (1),否则返回 Global.NO (0)。
+     * </p>
+     *
+     * @param supplyEntId 供应单位ID
+     * @return 代理标识:1-是代理,0-非代理
+     * @throws BusinessException 当供应单位ID为空或查询远程服务异常时抛出
+     */
+    private Integer querySupplyAgentFlag(Long supplyEntId) {
+        // 参数校验:供应单位ID不能为空
+        if (Objects.isNull(supplyEntId)) {
+            log.warn("查询供应单位代理属性失败,原因:供应单位ID为空");
+            throw new BusinessException("供应单位不能为空");
+        }
+
+        try {
+            log.debug("开始查询供应单位代理属性,supplyEntId: {}", supplyEntId);
+            
+            // 调用远程系统服务查询企业属性类型列表
+            List<EntTypeResDto> entTypeList = remoteSystemService.queryEntTypeByIds(Collections.singleton(supplyEntId));
+            
+            // 判断是否包含代理属性类型
+            boolean isAgent = hasSupplyAgentAttribute(entTypeList, supplyEntId);
+            Integer result = isAgent ? Global.YES : Global.NO;
+            
+            log.debug("供应单位代理属性查询完成,supplyEntId: {}, 结果: {}", supplyEntId, result);
+            return result;
+            
+        } catch (BusinessException e) {
+            // 业务异常直接抛出,避免重复包装
+            throw e;
+        } catch (Exception e) {
+            // 捕获其他异常(如网络超时、远程服务错误等),记录详细日志并抛出通用业务异常
+            log.error("查询供应单位代理属性发生系统异常,supplyEntId: {}", supplyEntId, e);
+            throw new BusinessException("查询供应单位代理属性失败");
+        }
+    }
+
+    /**
+     * 判断指定供应单位是否具备代理属性。
+     * <p>
+     * 遍历企业属性列表,检查是否存在与给定 supplyEntId 匹配且类型为 {@link #SUPPLY_AGENT_ENT_TYPE} 的记录。
+     * </p>
+     *
+     * @param entTypeList 企业属性集合,由远程服务返回
+     * @param supplyEntId 待检查的供应单位ID
+     * @return true-具备代理属性,false-不具备代理属性或参数无效
+     */
+    static boolean hasSupplyAgentAttribute(List<EntTypeResDto> entTypeList, Long supplyEntId) {
+        // 前置条件检查:ID不为空且列表非空
+        if (Objects.isNull(supplyEntId) || CollectionUtils.isEmpty(entTypeList)) {
+            return false;
+        }
+
+        // 流式处理:过滤空对象 -> 匹配企业ID -> 检查是否为代理类型
+        boolean isMatch = entTypeList.stream()
+                .filter(Objects::nonNull) // 过滤列表中的null元素
+                .filter(item -> Objects.equals(item.getEntId(), supplyEntId)) // 匹配指定的企业ID
+                .anyMatch(item -> Objects.equals(item.getType(), SUPPLY_AGENT_ENT_TYPE)); // 检查类型是否为代理类型(4)
+
+        if (isMatch) {
+            log.debug("检测到供应单位具备代理属性,supplyEntId: {}", supplyEntId);
+        }
+        
+        return isMatch;
+    }
+
     /**
      * @param reqVo 补充入参
      * @desc: 补充合同
@@ -812,6 +896,7 @@ public class KwcContractTradeService {
         kwcContractTrade.setSalesmanId(baseInfo.getSalesmanId());
         kwcContractTrade.setSalesmanPhone(baseInfo.getSalesmanPhone());
         kwcContractTrade.setDispatchWay(baseInfo.getDispatchWay());
+        kwcContractTrade.setAgentFlag(querySupplyAgentFlag(baseInfo.getProvideEntId()));
         List<TradeGoodsInfoReqVo> goodsInfo = reqVo.getGoodsInfo();
         BigDecimal amountTotal = new BigDecimal(Global.NUMERICAL_ZERO);
         if (CollectionUtils.isNotEmpty(goodsInfo)) {
@@ -1351,6 +1436,7 @@ public class KwcContractTradeService {
         kwcContractTrade.setSalesmanId(baseInfo.getSalesmanId());
         kwcContractTrade.setSalesmanPhone(baseInfo.getSalesmanPhone());
         kwcContractTrade.setDispatchWay(baseInfo.getDispatchWay());
+        kwcContractTrade.setAgentFlag(querySupplyAgentFlag(baseInfo.getProvideEntId()));
 
 
         BigDecimal amountTotal = new BigDecimal(Global.NUMERICAL_ZERO);
@@ -1674,7 +1760,7 @@ public class KwcContractTradeService {
                 req.getContractCode(),
                 req.getContractName(),
                 req.getSupplementCode(),
-                contractIds, req.getStatus(), req.getDispatchWay(), req.getPageNum(), req.getPageSize());
+                contractIds, req.getStatus(), req.getDispatchWay(), req.getAgentFlag(), req.getPageNum(), req.getPageSize());
         // List<KwcContractTrade> records = page.getRecords();
         if (org.apache.commons.collections4.CollectionUtils.isEmpty(records)) {
             return Collections.emptyList();
@@ -1842,7 +1928,7 @@ public class KwcContractTradeService {
                 req.getContractCode(),
                 req.getContractName(),
                 req.getSupplementCode(),
-                contractIds, req.getStatus(), req.getDispatchWay(), 0, 0);
+                contractIds, req.getStatus(), req.getDispatchWay(), req.getAgentFlag(), 0, 0);
         if (org.apache.commons.collections4.CollectionUtils.isEmpty(records)) {
             return 0L;
         }
@@ -1965,6 +2051,8 @@ public class KwcContractTradeService {
         queryListResVo.setStatusName(ContractStatusEnum.getNameByCode(t.getStatus()));
         queryListResVo.setDispatchWay(t.getDispatchWay());
         queryListResVo.setDispatchWayDesc(DispatchWayEnums.getDesc(t.getDispatchWay()));
+        queryListResVo.setAgentFlag(t.getAgentFlag());
+        queryListResVo.setAgentFlagDesc(Objects.equals(t.getAgentFlag(), Global.YES) ? "是" : "否");
 
         KwcContractTradeUnit purchaseEnt = finalContractUnitTypeKeyAndUnitMap.getOrDefault(t.getId() + "-" + CooperateTypeEnum.PURCHASER.getCode(),
                 new KwcContractTradeUnit());
@@ -2197,6 +2285,13 @@ public class KwcContractTradeService {
                 tradeUnits1.stream().collect(Collectors.toMap(x -> x.getContractId() +
                                 "_" + x.getUnitType(), Function.identity(),
                         (x, y) -> x));
+        // 判断当前查询的企业类型是否为供应方(供应商)
+        if (Objects.equals(contractTradeOrderDto.getEntType(), CooperateTypeEnum.SUPPLIER.getCode())) {
+            log.debug("当前查询类型为供应方,执行销售员签约贸易合同查询逻辑。entId: {}, goodsId: {}", 
+                    contractTradeOrderDto.getEntId(), contractTradeOrderDto.getGoodsId());
+            // 调用专用方法查询当前登录用户作为销售员且已签约的贸易合同信息
+            return querySalesmanSignedTradeOrder(contractTradeOrderDto, contractIds, conTractTradeUnitMap);
+        }
         contractIds = tradeUnits1.stream()
                 .filter(x -> Objects.equals(x.getEntId(), LoginUserHolder.getEntId()) && Objects.equals(x.getUnitType(),
                         2))
@@ -2223,9 +2318,128 @@ public class KwcContractTradeService {
         kwcContractTrades = kwcContractTrades.stream()
                 .filter(x -> Objects.equals(x.getStatus(), ContractStatusEnum.SIGNED.getCode()))
                 .collect(Collectors.toList());
-        return kwcContractTrades.stream()
+        List<ContractTradeOrderInfo> result = kwcContractTrades.stream()
                 .map(x -> getContractTradeOrderInfo(x, conTractTradeUnitMap, contractTradeOrderDto.getEntType(), contractGoodsMap))
                 .collect(Collectors.toList());
+        return filterContractTradeOrderInfo(result, contractTradeOrderDto.getKeyword());
+    }
+
+
+    /**
+     * 查询当前登录用户作为销售员且已签约的贸易合同订单信息。
+     * <p>
+     * 该方法主要用于供应商端下拉选择场景,确保只返回当前销售员本人负责且状态为“已签约”的合同数据。
+     * </p>
+     *
+     * @param contractTradeOrderDto 查询条件DTO,包含商品ID、企业类型等关键过滤条件
+     * @param contractIds           待筛选的合同ID集合(通常基于当前企业查询出的关联合同)
+     * @param conTractTradeUnitMap  合同与单位信息的映射关系,Key格式为 "contractId_unitType",用于后续组装返回对象
+     * @return 过滤后的贸易合同订单信息列表,若无符合条件数据则返回空列表
+     */
+    private List<ContractTradeOrderInfo> querySalesmanSignedTradeOrder(ContractTradeOrderDto contractTradeOrderDto,
+                                                                       Set<Long> contractIds,
+                                                                       Map<String, KwcContractTradeUnit> conTractTradeUnitMap) {
+        // 1. 前置校验:若合同ID集合为空,直接返回空列表,避免无效数据库查询
+        if (org.apache.commons.collections4.CollectionUtils.isEmpty(contractIds)) {
+            log.debug("查询销售员签约合同失败:合同ID集合为空");
+            return Collections.emptyList();
+        }
+
+        // 2. 查询指定合同集合下包含特定商品的合同商品信息
+        Long goodsId = contractTradeOrderDto.getGoodsId();
+        log.debug("开始查询销售员签约合同,合同IDs数量: {}, 商品ID: {}", contractIds.size(), goodsId);
+        
+        List<KwcContractTradeGoods> kwcContractGoods = kwcContractTradeGoodsRepository.queryByContractIdsAndGoodsId(contractIds, goodsId);
+        if (org.apache.commons.collections4.CollectionUtils.isEmpty(kwcContractGoods)) {
+            log.debug("未找到包含指定商品的合同商品信息,商品ID: {}", goodsId);
+            return Collections.emptyList();
+        }
+
+        // 3. 构建合同ID到合同商品信息的映射,方便后续快速查找价格、数量等详情
+        // 注意:这里使用 (x, y) -> x 处理可能的重复Key,保留第一个匹配项
+        Map<Long, KwcContractTradeGoods> contractGoodsMap = kwcContractGoods.stream()
+                .collect(Collectors.toMap(KwcContractTradeGoods::getContractId, Function.identity(), (x, y) -> x));
+
+        // 4. 过滤出状态为“已签约”的合同ID集合
+        // 只有已签约的合同才允许在下拉框中展示供业务操作
+        Set<Long> signedContractIdList = kwcContractGoods.stream()
+                .filter(goods -> Objects.equals(goods.getStatus(), ContractStatusEnum.SIGNED.getCode()))
+                .map(KwcContractTradeGoods::getContractId)
+                .collect(Collectors.toSet());
+
+        if (org.apache.commons.collections4.CollectionUtils.isEmpty(signedContractIdList)) {
+            log.debug("未找到状态为已签约的合同商品,商品ID: {}", goodsId);
+            return Collections.emptyList();
+        }
+
+        // 5. 根据已签约的合同ID集合,批量查询贸易合同主表信息
+        List<KwcContractTrade> kwcContractTrades = kwcContractTradeRepository.findByContractIds(signedContractIdList);
+        if (org.apache.commons.collections4.CollectionUtils.isEmpty(kwcContractTrades)) {
+            log.warn("根据合同ID查询贸易合同主表信息为空,合同IDs: {}", signedContractIdList);
+            return Collections.emptyList();
+        }
+
+        // 6. 获取当前登录用户ID,用于权限过滤(仅查看本人销售的合同)
+        Long currentUserId = LoginUserHolder.getUserId();
+        log.debug("当前登录用户ID: {},开始过滤本人销售的已签约合同", currentUserId);
+
+        // 7. 流式处理:
+        //    - 再次确认合同状态为已签约(双重保险)
+        //    - 核心过滤: salesmanId 必须等于当前登录用户ID
+        //    - 组装返回对象:利用之前构建的 map 填充详细信息
+        List<ContractTradeOrderInfo> result = kwcContractTrades.stream()
+                .filter(trade -> Objects.equals(trade.getStatus(), ContractStatusEnum.SIGNED.getCode()))
+                .filter(trade -> {
+                    boolean isMySale = Objects.equals(trade.getSalesmanId(), currentUserId);
+                    if (!isMySale) {
+                        log.trace("合同ID: {} 被过滤,因为销售员ID: {} 不匹配当前用户: {}", 
+                                trade.getId(), trade.getSalesmanId(), currentUserId);
+                    }
+                    return isMySale;
+                })
+                .map(trade -> getContractTradeOrderInfo(trade, conTractTradeUnitMap, contractTradeOrderDto.getEntType(), contractGoodsMap))
+                .collect(Collectors.toList());
+
+        log.info("查询销售员签约合同完成,原始合同数: {}, 过滤后结果数: {}", kwcContractTrades.size(), result.size());
+
+        // 8. 最后根据关键字(如合同名称/企业名称)进行模糊匹配过滤
+        return filterContractTradeOrderInfo(result, contractTradeOrderDto.getKeyword());
+    }
+
+
+    /**
+     * 按下拉展示名称进行关键字过滤。
+     * <p>
+     * 该方法用于对贸易合同订单信息进行模糊匹配过滤,支持忽略大小写。
+     * 主要用于前端下拉框搜索场景,根据用户输入的关键字筛选出名称中包含该关键字的合同记录。
+     * </p>
+     *
+     * @param result  待过滤的贸易合同订单信息列表
+     * @param keyword 搜索关键字,若为空或空白则不进行过滤
+     * @return 过滤后的贸易合同订单信息列表;若原列表为空或关键字为空,则返回原列表
+     */
+    private static List<ContractTradeOrderInfo> filterContractTradeOrderInfo(List<ContractTradeOrderInfo> result, String keyword) {
+        // 前置校验:如果结果集为空或关键字为空/空白,直接返回原结果集,避免不必要的流处理
+        if (org.apache.commons.collections4.CollectionUtils.isEmpty(result) || org.apache.commons.lang3.StringUtils.isBlank(keyword)) {
+            return result;
+        }
+
+        log.debug("开始对贸易合同订单进行关键字过滤,关键字: {}, 原始数据量: {}", keyword, result.size());
+
+        // 执行过滤逻辑:使用 StringUtils.containsIgnoreCase 实现不区分大小写的包含匹配
+        List<ContractTradeOrderInfo> filteredList = result.stream()
+                .filter(item -> {
+                    // 防御性编程:确保 item 和 item.getName() 不为 null,避免 NPE
+                    if (item == null || item.getName() == null) {
+                        return false;
+                    }
+                    return org.apache.commons.lang3.StringUtils.containsIgnoreCase(item.getName(), keyword);
+                })
+                .collect(Collectors.toList());
+
+        log.debug("贸易合同订单关键字过滤完成,过滤后数据量: {}", filteredList.size());
+        
+        return filteredList;
     }
 
     private static void checkParam(Long contractTradeOrderDto, Long contractTradeOrderDto1, Integer contractTradeOrderDto2) {
@@ -2247,9 +2461,12 @@ public class KwcContractTradeService {
         contractTradeOrderInfo.setId(c.getId());
         KwcContractTradeUnit unit = conTractTradeUnitMap.getOrDefault(c.getId() + "_" + entType,
                 new KwcContractTradeUnit());
+        KwcContractTradeUnit purchaseUnit = conTractTradeUnitMap.getOrDefault(c.getId() + "_" + CooperateTypeEnum.PURCHASER.getCode(),
+                new KwcContractTradeUnit());
         contractTradeOrderInfo.setEntId(unit.getEntId());
         contractTradeOrderInfo.setContractNo(c.getContractNo());
-        contractTradeOrderInfo.setName(c.getName());
+        //合同名称-企业名称
+        contractTradeOrderInfo.setName(String.format("%s-%s", Objects.toString(c.getName(), ""), Objects.toString(purchaseUnit.getFirmName(), "")));
         contractTradeOrderInfo.setSigningWay(c.getSigningWay());
         contractTradeOrderInfo.setTrading(c.getTrading());
         contractTradeOrderInfo.setStartTime(c.getStartTime());
@@ -2332,7 +2549,7 @@ public class KwcContractTradeService {
                 req.getContractCode(),
                 req.getContractName(),
                 req.getSupplementCode(),
-                contractIds, req.getStatus(), req.getDispatchWay(), 0, 0);
+                contractIds, req.getStatus(), req.getDispatchWay(), req.getAgentFlag(), 0, 0);
 
         // List<KwcContractTrade> kwcContractTrades = kwcContractTradeRepository.queryTradeListByPageList(LoginUserHolder.getEntId());
 

+ 15 - 3
sckw-modules/sckw-contract/src/main/resources/mapper/KwcContractTradeMapper.xml

@@ -27,7 +27,8 @@
                a.performed_amount performedAmount,
                f.name             contractPname,
                a.unload_way       unloadWay,
-               a.signing_way      signingWay
+               a.signing_way      signingWay,
+               a.agent_flag       agentFlag
         from kwc_contract_trade a
             left join kwc_contract_trade_unit b
         on a.id = b.contract_id
@@ -107,6 +108,9 @@
             and b.unit_type = 1
             and b.id = #{supplyEntId}
         </if>
+        <if test="agentFlag != null">
+            and a.agent_flag = #{agentFlag}
+        </if>
         order by a.create_time desc
     </select>
 
@@ -159,7 +163,8 @@
                e.firm_name        purchaseEntName,
                a.contract_pid     contractPid,
                a.performed_amount performedAmount,
-               f.name             contractPname
+               f.name             contractPname,
+               a.agent_flag       agentFlag
         from kwc_contract_trade a
                  left join kwc_contract_trade_unit b on a.id = b.contract_id and b.del_flag = 0 and b.unit_type = 1
                  left join kwc_contract_trade_unit e on a.id = e.contract_id and e.del_flag = 0 and e.unit_type = 2
@@ -203,6 +208,9 @@
         <if test="reqVo.signingWay != null">
             and a.signing_way = #{reqVo.signingWay}
         </if>
+        <if test="reqVo.agentFlag != null">
+            and a.agent_flag = #{reqVo.agentFlag}
+        </if>
         order by a.create_time desc
     </select>
 
@@ -231,7 +239,8 @@
                f.name             contractPname,
                f.contract_no      contractPidNo,
                a.unload_way       unloadWay,
-               a.signing_way      signingWay
+               a.signing_way      signingWay,
+               a.agent_flag       agentFlag
         from kwc_contract_trade a
                  left join kwc_contract_trade_unit b
                            on a.id = b.contract_id
@@ -274,6 +283,9 @@
             and b.unit_type = 1
             and b.id = #{supplyEntId}
         </if>
+        <if test="agentFlag != null">
+            and a.agent_flag = #{agentFlag}
+        </if>
         <if test="allEnt != null and allEnt.size() > 0">
             and a.ent_id in
             <foreach collection="allEnt" separator="," open="(" close=")" item="item">

+ 46 - 0
sckw-modules/sckw-contract/src/test/java/com/sckw/contract/service/operateService/KwcContractTradeServiceTest.java

@@ -0,0 +1,46 @@
+package com.sckw.contract.service.operateService;
+
+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;
+
+/**
+ * 贸易合同服务单元测试。
+ */
+public class KwcContractTradeServiceTest {
+
+    /**
+     * 验证供应单位存在类型为4的企业属性时,识别为代理属性。
+     */
+    @Test
+    public void hasSupplyAgentAttributeWhenSupplyEntTypeIsFour() {
+        EntTypeResDto supplierType = new EntTypeResDto();
+        supplierType.setEntId(1001L);
+        supplierType.setType(1);
+
+        EntTypeResDto agentType = new EntTypeResDto();
+        agentType.setEntId(1001L);
+        agentType.setType(4);
+
+        boolean result = KwcContractTradeService.hasSupplyAgentAttribute(Arrays.asList(supplierType, agentType), 1001L);
+
+        Assert.assertTrue(result);
+    }
+
+    /**
+     * 验证其他企业即使存在类型为4的企业属性,也不会误判当前供应单位。
+     */
+    @Test
+    public void hasSupplyAgentAttributeWhenTypeFourBelongsToOtherEnt() {
+        EntTypeResDto agentType = new EntTypeResDto();
+        agentType.setEntId(2002L);
+        agentType.setType(4);
+
+        boolean result = KwcContractTradeService.hasSupplyAgentAttribute(Collections.singletonList(agentType), 1001L);
+
+        Assert.assertFalse(result);
+    }
+}

+ 4 - 4
sckw-modules/sckw-file/src/main/resources/bootstrap-cxf.yml

@@ -3,16 +3,16 @@ spring:
     nacos:
       discovery:
         # 服务注册地址
-        server-addr: @nacos.server@
+        server-addr: 118.116.4.155:8848
         # 命名空间
-        namespace: @nacos.namespace@
+        namespace: sckw-ng-service-platform-xf
         # 共享配置
         group: sckw-ng-service-platform
       config:
         # 配置中心地址
-        server-addr: @nacos.server@
+        server-addr: 118.116.4.155:8848
         # 命名空间
-        namespace: @nacos.namespace@
+        namespace: sckw-ng-service-platform-xf
         # 共享配置
         group: sckw-ng-service-platform
         # 配置文件格式

+ 7 - 1
sckw-modules/sckw-fleet/pom.xml

@@ -113,6 +113,12 @@
             <version>4.5.0</version>
         </dependency>
 
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <scope>test</scope>
+        </dependency>
+
     </dependencies>
 
     <build>
@@ -134,4 +140,4 @@
         </plugins>
     </build>
 
-</project>
+</project>

+ 2 - 0
sckw-modules/sckw-fleet/src/main/java/com/sckw/fleet/dubbo/RemoteFleetServiceImpl.java

@@ -172,6 +172,7 @@ public class RemoteFleetServiceImpl implements RemoteFleetService {
             truckVo.setTotalWeight(truck.getTotalWeight());
             truckVo.setBusinessStatus(truck.getBusinessStatus());
             truckVo.setTruckType(StringUtils.objectStr(truck.getType()));
+            truckVo.setMaxTransportDistance(truck.getMaxTransportDistance());
             truckMap.put(truckVo.getTruckNo(), truckVo);
         }
         return truckMap;
@@ -370,6 +371,7 @@ public class RemoteFleetServiceImpl implements RemoteFleetService {
         rTruckVo.setCarAxis(carAxisInfo.getName());
         rTruckVo.setLegalLoad(Objects.equals(TruckTypeEnum.TRUCK.getCode(), truck.getType()) ? carAxisInfo.getLegalLoad() : carAxisInfo.getTractorLegalLoad());
         rTruckVo.setEnergyType(truck.getEnergyType());
+        rTruckVo.setMaxTransportDistance(truck.getMaxTransportDistance());
         return rTruckVo;
     }
     /**

+ 6 - 0
sckw-modules/sckw-fleet/src/main/java/com/sckw/fleet/model/KwfTruck.java

@@ -196,6 +196,12 @@ public class KwfTruck extends BaseModel {
      */
     private Integer blacklist;
 
+    /**
+     * 最大运输距离,单位:公里
+     */
+    @TableField("max_transport_distance")
+    private BigDecimal maxTransportDistance;
+
 
 
 

+ 7 - 0
sckw-modules/sckw-fleet/src/main/java/com/sckw/fleet/model/reponse/TruckDetailResp.java

@@ -7,6 +7,7 @@ import lombok.Data;
 
 import java.io.Serial;
 import java.io.Serializable;
+import java.math.BigDecimal;
 import java.util.Date;
 
 /**
@@ -131,6 +132,12 @@ public class TruckDetailResp implements Serializable {
     @Schema(description = "发动机号")
     private String positionDevice;
 
+    /**
+     * 最大运输距离,单位:公里
+     */
+    @Schema(description = "最大运输距离,单位:公里")
+    private BigDecimal maxTransportDistance;
+
     /**
      * 车辆行驶证信息
      */

+ 7 - 0
sckw-modules/sckw-fleet/src/main/java/com/sckw/fleet/model/reponse/TruckResp.java

@@ -10,6 +10,7 @@ import lombok.experimental.Accessors;
 
 import java.io.Serial;
 import java.io.Serializable;
+import java.math.BigDecimal;
 import java.util.Date;
 
 /**
@@ -84,6 +85,12 @@ public class TruckResp implements Serializable {
     @Schema(description = "关联司机")
     private String driverName;
 
+    /**
+     * 最大运输距离,单位:公里
+     */
+    @Schema(description = "最大运输距离,单位:公里")
+    private BigDecimal maxTransportDistance;
+
     /**
      * 黑名单
      */

+ 7 - 0
sckw-modules/sckw-fleet/src/main/java/com/sckw/fleet/model/request/TruckSaveParam.java

@@ -8,6 +8,7 @@ import lombok.Data;
 
 import java.io.Serial;
 import java.io.Serializable;
+import java.math.BigDecimal;
 import java.util.Date;
 
 /**
@@ -101,6 +102,12 @@ public class TruckSaveParam implements Serializable {
     @Schema(description = "发动机号")
     private String positionDevice;
 
+    /**
+     * 最大运输距离,单位:公里
+     */
+    @Schema(description = "最大运输距离,单位:公里")
+    private BigDecimal maxTransportDistance;
+
     /**
      * 车辆行驶证信息
      */

+ 7 - 1
sckw-modules/sckw-fleet/src/main/java/com/sckw/fleet/service/KwfTruckService.java

@@ -1986,7 +1986,7 @@ public class KwfTruckService {
         if (CollectionUtils.isEmpty(r)){
             throw new BusinessException("车牌识别失败");
         }
-        return result;
+        return r;
     }
 
     /**
@@ -2217,6 +2217,7 @@ public class KwfTruckService {
             truckResp.setBlacklist(truck.getBlacklist());
             truckResp.setRemark(truck.getRemark());
             truckResp.setAnnualInspectionTime(truck.getAnnualInspectionTime());
+            truckResp.setMaxTransportDistance(truck.getMaxTransportDistance());
             //查询车辆轴数
             TmsTruckAxleNum axleNum = getCarAxisInfo(truck.getCarAxis());
             truckResp.setCarAxis(truck.getCarAxis());
@@ -2267,6 +2268,7 @@ public class KwfTruckService {
         truckDetailResp.setAnnualInspectionTime(truck.getAnnualInspectionTime());
         truckDetailResp.setTransportValidityTime(truck.getTransportValidityTime());
         truckDetailResp.setPositionDevice(truck.getPositionDevice());
+        truckDetailResp.setMaxTransportDistance(truck.getMaxTransportDistance());
         truckDetailResp.setTruckLicense(truck.getTruckLicense());
         truckDetailResp.setTransportLicense(truck.getTransportLicense());
         truckDetailResp.setEnvironmentalList(truck.getEnvironmentalList());
@@ -2324,6 +2326,7 @@ public class KwfTruckService {
             truck.setAnnualInspectionTime(param.getAnnualInspectionTime());
             truck.setTransportValidityTime(param.getTransportValidityTime());
             truck.setPositionDevice(param.getPositionDevice());
+            truck.setMaxTransportDistance(param.getMaxTransportDistance());
             truck.setTruckLicense(param.getTruckLicense());
             truck.setTransportLicense(param.getTransportLicense());
             truck.setEnvironmentalList(param.getEnvironmentalList());
@@ -2355,6 +2358,7 @@ public class KwfTruckService {
             truck.setAnnualInspectionTime(param.getAnnualInspectionTime());
             truck.setTransportValidityTime(param.getTransportValidityTime());
             truck.setPositionDevice(param.getPositionDevice());
+            truck.setMaxTransportDistance(param.getMaxTransportDistance());
             truck.setTruckLicense(param.getTruckLicense());
             truck.setTransportLicense(param.getTransportLicense());
             truck.setEnvironmentalList(param.getEnvironmentalList());
@@ -2395,6 +2399,7 @@ public class KwfTruckService {
             truck.setAnnualInspectionTime(param.getAnnualInspectionTime());
             truck.setTransportValidityTime(param.getTransportValidityTime());
             truck.setPositionDevice(param.getPositionDevice());
+            truck.setMaxTransportDistance(param.getMaxTransportDistance());
             truck.setTruckLicense(param.getTruckLicense());
             truck.setTransportLicense(param.getTransportLicense());
             truck.setEnvironmentalList(param.getEnvironmentalList());
@@ -2426,6 +2431,7 @@ public class KwfTruckService {
             truck.setAnnualInspectionTime(param.getAnnualInspectionTime());
             truck.setTransportValidityTime(param.getTransportValidityTime());
             truck.setPositionDevice(param.getPositionDevice());
+            truck.setMaxTransportDistance(param.getMaxTransportDistance());
             truck.setTruckLicense(param.getTruckLicense());
             truck.setTransportLicense(param.getTransportLicense());
             truck.setEnvironmentalList(param.getEnvironmentalList());

+ 35 - 0
sckw-modules/sckw-fleet/src/test/java/com/sckw/fleet/model/TruckMaxTransportDistanceTest.java

@@ -0,0 +1,35 @@
+package com.sckw.fleet.model;
+
+import com.sckw.fleet.model.reponse.TruckDetailResp;
+import com.sckw.fleet.model.reponse.TruckResp;
+import com.sckw.fleet.model.request.TruckSaveParam;
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.math.BigDecimal;
+
+/**
+ * 车辆最大运输距离接口字段单元测试。
+ */
+public class TruckMaxTransportDistanceTest {
+
+    /**
+     * 校验新增、详情、列表接口对象均支持最大运输距离字段读写。
+     */
+    @Test
+    public void shouldReadAndWriteMaxTransportDistance() {
+        BigDecimal maxTransportDistance = new BigDecimal("1200.50");
+
+        TruckSaveParam saveParam = new TruckSaveParam();
+        TruckDetailResp detailResp = new TruckDetailResp();
+        TruckResp truckResp = new TruckResp();
+
+        saveParam.setMaxTransportDistance(maxTransportDistance);
+        detailResp.setMaxTransportDistance(maxTransportDistance);
+        truckResp.setMaxTransportDistance(maxTransportDistance);
+
+        Assert.assertEquals(maxTransportDistance, saveParam.getMaxTransportDistance());
+        Assert.assertEquals(maxTransportDistance, detailResp.getMaxTransportDistance());
+        Assert.assertEquals(maxTransportDistance, truckResp.getMaxTransportDistance());
+    }
+}

+ 4 - 4
sckw-modules/sckw-operation/src/main/resources/bootstrap-cxf.yml

@@ -3,16 +3,16 @@ spring:
     nacos:
       discovery:
         # 服务注册地址
-        server-addr: @nacos.server@
+        server-addr: 118.116.4.155:8848
         # 命名空间
-        namespace: @nacos.namespace@
+        namespace: sckw-ng-service-platform-xf
         # 共享配置
         group: sckw-ng-service-platform
       config:
         # 配置中心地址
-        server-addr: @nacos.server@
+        server-addr: 118.116.4.155:8848
         # 命名空间
-        namespace: @nacos.namespace@
+        namespace: sckw-ng-service-platform-xf
         # 共享配置
         group: sckw-ng-service-platform
         # 配置文件格式

+ 6 - 1
sckw-modules/sckw-order/pom.xml

@@ -130,6 +130,11 @@
             <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
             <version>2.6.0</version>
         </dependency>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <scope>test</scope>
+        </dependency>
         <dependency>
             <groupId>com.github.xiaoymin</groupId>
             <artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
@@ -164,4 +169,4 @@
         </plugins>
     </build>
 
-</project>
+</project>

+ 6 - 0
sckw-modules/sckw-order/src/main/java/com/sckw/order/model/KwoTradeOrder.java

@@ -154,5 +154,11 @@ public class KwoTradeOrder extends BaseModel implements Serializable {
     @TableField("add_vehicles")
     private Integer addVehicles;
 
+    /**
+     * 是否代理属性:0-否,1-是
+     */
+    @TableField("agent_flag")
+    private Integer agentFlag;
+
 
 }

+ 5 - 0
sckw-modules/sckw-order/src/main/java/com/sckw/order/model/dto/OrderListResDTO.java

@@ -156,6 +156,11 @@ public class OrderListResDTO {
      */
     private Integer addVehicles;
 
+    /**
+     * 是否代理属性:0-否,1-是
+     */
+    private Integer agentFlag;
+
     /**
      * 装货地址信息
      */

+ 18 - 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;
@@ -150,4 +151,21 @@ public class TradeOrderListSelectDTO {
      * 数据权限是否强制返回空结果
      */
     private Boolean dataPermissionForceEmpty;
+    /**
+     * 是否代理属性:0-否,1-是
+     */
+    @Schema(description = "是否代理属性:0-否,1-是")
+    private Integer agentFlag;
+
+    /**
+     * 代理关系可见供应企业ID集合:代理商仅包含自身,供应商包含自身及代理商。
+     */
+    @JsonIgnore
+    private List<Long> proxyScopeEntIds;
+
+    /**
+     * 代理关系过滤是否强制返回空结果。
+     */
+    @JsonIgnore
+    private Boolean proxyScopeForceEmpty;
 }

+ 6 - 0
sckw-modules/sckw-order/src/main/java/com/sckw/order/model/vo/req/TradeOrderListStatisticParam.java

@@ -90,4 +90,10 @@ public class TradeOrderListStatisticParam {
      */
     private Integer status;
 
+    /**
+     * 是否代理属性:0-否,1-是
+     */
+    @Schema(description = "是否代理属性:0-否,1-是")
+    private Integer agentFlag;
+
 }

+ 10 - 0
sckw-modules/sckw-order/src/main/java/com/sckw/order/model/vo/res/OrderListResVO.java

@@ -100,6 +100,16 @@ public class OrderListResVO {
      */
     private Integer addVehicles;
 
+    /**
+     * 是否代理属性:0-否,1-是
+     */
+    private Integer agentFlag;
+
+    /**
+     * 是否代理属性描述
+     */
+    private String agentFlagDesc;
+
     /**
      * 合同主键
      */

+ 264 - 2
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;
 
 /**
@@ -119,6 +120,15 @@ import java.util.stream.Collectors;
 @RequiredArgsConstructor
 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;
     @DubboReference(version = "1.0.0", group = "design", check = false)
@@ -1621,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());
@@ -1628,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
      */
@@ -1750,6 +1812,7 @@ public class KwoTradeOrderService {
             vo.setActualPrice(actualAmount.multiply(unitPrice).setScale(2, RoundingMode.HALF_UP)).setUnitPrice(setScale(e.getUnitPrice())).setPrice(setScale(e.getPrice())).setUnit(e.getUnit()).setUnitLabel(unitType == null ? e.getUnit() : (unitType.get(e.getUnit()) == null ? e.getUnit() : unitType.get(e.getUnit()).getLabel())).setAmount(setScale(vo.getAmount())).setEntrustAmount(setScale(e.getEntrustAmount())).setActualAmount(setScale(e.getActualAmount())).setCreateByName(Objects.isNull(userCache) ? null : userCache.getName());
             vo.setDeliveryTypeLabel(DictEnum.getLabel(DictTypeEnum.LOAD_UNLOAD_WAY.getType(), String.valueOf(e.getChargeType())));
             vo.setAddVehicles(e.getAddVehicles());
+            vo.setAgentFlagDesc(Objects.equals(e.getAgentFlag(), Global.YES) ? "是" : "否");
             if (Objects.equals(e.getChargeType(), 1)) { //按装货量
                 vo.setDealAmount(e.getLoadAmount());
             } else {
@@ -1801,6 +1864,8 @@ public class KwoTradeOrderService {
             }
         }
         String keywords = params.getKeywords();
+        applyAgentKeywordCondition(dto);
+        keywords = dto.getKeywords();
         if (StringUtils.isNotBlank(keywords)) {
             List<SysDictResDto> sysDictResDtos = remoteSystemService.queryGoodsDict(keywords);
             List<String> collect = new ArrayList<>();
@@ -1812,6 +1877,21 @@ public class KwoTradeOrderService {
         return dto;
     }
 
+    /**
+     * 处理贸易订单列表代理关键词。
+     * keywords包含“代理”时,转换为代理属性过滤;订单表关键词字段不会存储该语义,
+     * 继续参与订单号、单位名称、商品名称模糊匹配会导致代理订单被错误过滤。
+     *
+     * @param dto 贸易订单列表查询DTO
+     */
+    static void applyAgentKeywordCondition(TradeOrderListSelectDTO dto) {
+        if (Objects.isNull(dto) || StringUtils.isBlank(dto.getKeywords()) || !dto.getKeywords().contains("代理")) {
+            return;
+        }
+        dto.setAgentFlag(Global.YES);
+        dto.setKeywords(null);
+    }
+
     /**
      * @desc: 设置两位小数
      * @author: yzc
@@ -2177,6 +2257,185 @@ public class KwoTradeOrderService {
         return kwoTradeOrderMapper.querySaleOrder(start, end);
     }
 
+    /**
+     * 根据贸易合同ID查询订单的代理属性标识。
+     * <p>
+     * 逻辑说明:
+     * 1. 校验贸易合同ID是否为空。
+     * 2. 调用远程服务查询该合同下的所有单位信息。
+     * 3. 筛选出类型为“供应方”(unitType="1")的单位,并获取其企业ID。
+     * 4. 根据供应方企业ID,进一步查询该企业是否具备“代理”属性。
+     *
+     * @param tradeContractId 贸易合同ID
+     * @return 代理属性标识:1-是代理,0-非代理
+     * @throws BusinessException 当合同ID为空、未找到供应单位或查询过程异常时抛出
+     */
+    private Integer queryOrderAgentFlag(Long tradeContractId) {
+        // 参数校验:贸易合同ID不能为空
+        if (Objects.isNull(tradeContractId)) {
+            throw new BusinessException("贸易合同ID不能为空");
+        }
+
+        try {
+            // 记录开始查询日志
+            log.debug("开始查询贸易合同代理属性,tradeContractId={}", tradeContractId);
+
+            // 1. 调用远程服务获取合同关联的单位列表
+            List<TradeContractUnitDto> unitList = remoteContractService.queryContractUnitByContractId(tradeContractId);
+
+            // 2. 从单位列表中筛选出供应方(unitType="1")的企业ID
+            Long supplyEntId = Optional.ofNullable(unitList)
+                    .orElseGet(Collections::emptyList)
+                    .stream()
+                    .filter(Objects::nonNull)
+                    // 过滤出单位类型为供应方的记录 (假设 "1" 代表供应方)
+                    .filter(item -> StrUtil.equals("1", item.getUnitType()))
+                    .map(TradeContractUnitDto::getEntId)
+                    .filter(Objects::nonNull)
+                    .findFirst()
+                    .orElseThrow(() -> {
+                        log.warn("贸易合同中未找到有效的供应单位,tradeContractId={}", tradeContractId);
+                        return new BusinessException("贸易合同供应单位不存在");
+                    });
+
+            log.debug("找到供应单位ID,supplyEntId={},准备查询代理属性", supplyEntId);
+
+            // 3. 根据供应方企业ID查询其是否具备代理属性
+            Integer agentFlag = querySupplyAgentFlag(supplyEntId);
+            
+            log.debug("贸易合同代理属性查询完成,tradeContractId={}, agentFlag={}", tradeContractId, agentFlag);
+            return agentFlag;
+
+        } catch (Exception e) {
+            // 捕获其他未知异常,记录详细堆栈日志,并抛出通用业务异常
+            log.error("查询贸易订单代理属性发生系统异常,tradeContractId={}", tradeContractId, e);
+            throw new BusinessException("查询贸易订单代理属性失败");
+        }
+    }
+
+
+    /**
+     * 查询供应单位企业类型,判断是否包含代理属性。
+     * <p>
+     * 业务逻辑:
+     * 1. 校验供应单位ID有效性。
+     * 2. 调用远程系统服务获取该企业的类型信息列表。
+     * 3. 通过辅助方法 {@link #hasSupplyAgentAttribute(List, Long)} 判断企业类型中是否包含“供应代理”标识(类型为4)。
+     * 4. 返回全局常量标识:Global.YES (1) 表示是代理,Global.NO (0) 表示非代理。
+     *
+     * @param supplyEntId 供应单位ID
+     * @return 代理属性标识:1-是代理,0-非代理
+     * @throws BusinessException 当供应单位ID为空或查询过程发生异常时抛出
+     */
+    private Integer querySupplyAgentFlag(Long supplyEntId) {
+        // 参数校验:确保供应单位ID不为空
+        if (Objects.isNull(supplyEntId)) {
+            log.warn("查询供应单位代理属性失败,原因:供应单位ID为空");
+            throw new BusinessException("供应单位不能为空");
+        }
+
+        try {
+            // 记录调试日志,开始查询流程
+            log.debug("开始查询供应单位代理属性,supplyEntId={}", supplyEntId);
+
+            // 调用远程系统服务,根据企业ID查询企业类型信息
+            // 使用 Collections.singleton 包装单个ID以符合接口参数要求
+            List<EntTypeResDto> entTypeList = remoteSystemService.queryEntTypeByIds(Collections.singleton(supplyEntId));
+
+            // 判断该企业是否具有供应代理属性
+            boolean isAgent = hasSupplyAgentAttribute(entTypeList, supplyEntId);
+            
+            // 记录结果日志
+            log.debug("供应单位代理属性查询完成,supplyEntId={}, isAgent={}", supplyEntId, isAgent);
+
+            // 根据判断结果返回对应的全局常量标识
+            return isAgent ? Global.YES : Global.NO;
+        } catch (Exception e) {
+            // 捕获异常,记录错误日志(包含堆栈信息以便排查)
+            log.error("查询供应单位代理属性发生系统异常,supplyEntId={}", supplyEntId, e);
+            // 抛出统一的业务异常,避免暴露底层技术细节
+            throw new BusinessException("查询供应单位代理属性失败");
+        }
+    }
+
+    /**
+     * 组装代理关系可见企业范围。
+     *
+     * @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));
+    }
+
+    /**
+     * 判断指定供应企业是否具有代理属性。
+     *
+     * @param entTypeList 企业类型信息列表
+     * @param supplyEntId 待校验的供应企业ID
+     * @return true-该企业具有代理属性,false-该企业不具有代理属性或参数无效
+     */
+    static boolean hasSupplyAgentAttribute(List<EntTypeResDto> entTypeList, Long supplyEntId) {
+        // 参数预校验:确保企业ID和企业类型列表非空
+        if (Objects.isNull(supplyEntId) || CollUtil.isEmpty(entTypeList)) {
+            log.debug("判断供应单位代理属性:参数无效,supplyEntId={}, entTypeListSize={}", supplyEntId, CollUtil.size(entTypeList));
+            return false;
+        }
+
+        // 流式处理:过滤非空对象 -> 匹配企业ID -> 判断是否为代理类型
+        boolean isAgent = entTypeList.stream()
+                .filter(Objects::nonNull)
+                .filter(item -> Objects.equals(item.getEntId(), supplyEntId))
+                .anyMatch(item -> {
+                    boolean match = Objects.equals(item.getType(), SUPPLY_AGENT_ENT_TYPE);
+                    if (match) {
+                        log.debug("找到供应单位代理属性匹配项,entId={}, type={}", item.getEntId(), item.getType());
+                    }
+                    return match;
+                });
+
+        log.debug("供应单位代理属性判断结果,supplyEntId={}, isAgent={}", supplyEntId, isAgent);
+        return isAgent;
+    }
+
     /**
      * 新增贸易订单
      *
@@ -2190,6 +2449,7 @@ public class KwoTradeOrderService {
         KwpGoods goodsById = goodsInfoService.getGoodsById(tradeOrderParam.getGoodsId());
 
         TradeContractResDto tradeContractResDto = checkPara(tradeOrderParam, order, goodsById);
+        order.setAgentFlag(queryOrderAgentFlag(tradeOrderParam.getTradeContractId()));
 
         // ====== 下单前校验 start ======
 
@@ -2814,6 +3074,7 @@ public class KwoTradeOrderService {
         addLogisticOrderParam.setEntId(LoginUserHolder.getEntId());
         addLogisticOrderParam.setUserId(LoginUserHolder.getUserId());
         addLogisticOrderParam.setMeasurementWay(tradeContractResDto.getMeasurementWay());
+        addLogisticOrderParam.setAgentFlag(kwoTradeOrder.getAgentFlag());
         log.info("创建物流订单:{}", JSONObject.toJSONString(addLogisticOrderParam));
         Integer result = transportRemoteService.addLogisticOrder(addLogisticOrderParam);
         //自动派单运力不足
@@ -3261,6 +3522,7 @@ public class KwoTradeOrderService {
         addLogisticOrderParam.setEntId(LoginUserHolder.getEntId());
         addLogisticOrderParam.setUserId(LoginUserHolder.getUserId());
         addLogisticOrderParam.setDispatchWay(DispatchWayEnums.MANUAL_DISPATCH.getCode());
+        addLogisticOrderParam.setAgentFlag(kwoTradeOrder.getAgentFlag());
         log.info("创建物流订单:{}", JSONObject.toJSONString(addLogisticOrderParam));
         transportRemoteService.addLogisticOrder(addLogisticOrderParam);
 

+ 24 - 1
sckw-modules/sckw-order/src/main/resources/mapper/KwoTradeOrderMapper.xml

@@ -31,6 +31,7 @@
         <result column="createBy" property="createBy"/>
         <result column="createTime" property="createTime"/>
         <result column="addVehicles" property="addVehicles"/>
+        <result column="agentFlag" property="agentFlag"/>
 
         <collection property="loadAddresses" ofType="com.sckw.order.model.dto.LoadAddressDTO">
             <id column="loadAddressId" property="loadAddressId"/>
@@ -76,7 +77,8 @@
         a.is_agent       as isAgent,
         a.create_by      AS createBy,
         a.create_time    AS createTime,
-        a.add_vehicles   AS addVehicles
+        a.add_vehicles   AS addVehicles,
+        a.agent_flag     AS agentFlag
     </sql>
 
     <select id="tradeOrderSelect" resultMap="mainEntityMap">
@@ -90,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">
@@ -155,6 +172,9 @@
             <if test="query.saleEntId != null">
                 and e.ent_id = #{query.saleEntId}
             </if>
+            <if test="query.agentFlag != null">
+                and a.agent_flag = #{query.agentFlag}
+            </if>
             <if test="query.status != null">
                 and a.status = #{query.status}
             </if>
@@ -526,6 +546,9 @@
             <if test="query.saleEntId != null">
                 and e.ent_id = #{query.saleEntId}
             </if>
+            <if test="query.agentFlag != null">
+                and a.agent_flag = #{query.agentFlag}
+            </if>
 <!--            <if test="query.dataPermissionForceEmpty != null and query.dataPermissionForceEmpty">-->
 <!--                and 1 = 0-->
 <!--            </if>-->

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

@@ -0,0 +1,125 @@
+package com.sckw.order.serivce;
+
+import com.sckw.order.model.dto.TradeOrderListSelectDTO;
+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 KwoTradeOrderServiceTest {
+
+    /**
+     * 供应单位存在类型4时,识别为代理属性。
+     */
+    @Test
+    public void hasSupplyAgentAttributeWhenSupplyEntHasAgentType() {
+        EntTypeResDto agentType = new EntTypeResDto();
+        agentType.setEntId(1001L);
+        agentType.setType(KwoTradeOrderService.SUPPLY_AGENT_ENT_TYPE);
+
+        boolean result = KwoTradeOrderService.hasSupplyAgentAttribute(Collections.singletonList(agentType), 1001L);
+
+        Assert.assertTrue(result);
+    }
+
+    /**
+     * 只有其他企业存在类型4时,当前供应单位不识别为代理属性。
+     */
+    @Test
+    public void hasSupplyAgentAttributeWhenAgentTypeBelongsToOtherEnt() {
+        EntTypeResDto otherEntAgentType = new EntTypeResDto();
+        otherEntAgentType.setEntId(2002L);
+        otherEntAgentType.setType(KwoTradeOrderService.SUPPLY_AGENT_ENT_TYPE);
+
+        EntTypeResDto supplyNormalType = new EntTypeResDto();
+        supplyNormalType.setEntId(1001L);
+        supplyNormalType.setType(1);
+
+        boolean result = KwoTradeOrderService.hasSupplyAgentAttribute(
+                Arrays.asList(otherEntAgentType, supplyNormalType),
+                1001L
+        );
+
+        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());
+    }
+
+    /**
+     * keywords包含“代理”时,应转换为代理属性过滤,并清空原关键词。
+     */
+    @Test
+    public void applyAgentKeywordConditionWhenKeywordsContainsAgent() {
+        TradeOrderListSelectDTO dto = new TradeOrderListSelectDTO();
+        dto.setKeywords("代理");
+
+        KwoTradeOrderService.applyAgentKeywordCondition(dto);
+
+        Assert.assertEquals(Integer.valueOf(1), dto.getAgentFlag());
+        Assert.assertNull(dto.getKeywords());
+    }
+
+    /**
+     * keywords不包含“代理”时,不应影响原关键词查询逻辑。
+     */
+    @Test
+    public void applyAgentKeywordConditionWhenKeywordsNotContainsAgent() {
+        TradeOrderListSelectDTO dto = new TradeOrderListSelectDTO();
+        dto.setKeywords("钢材");
+
+        KwoTradeOrderService.applyAgentKeywordCondition(dto);
+
+        Assert.assertNull(dto.getAgentFlag());
+        Assert.assertEquals("钢材", dto.getKeywords());
+    }
+}

+ 5 - 0
sckw-modules/sckw-product/pom.xml

@@ -118,6 +118,11 @@
             <artifactId>sckw-contract-api</artifactId>
             <version>${basic.version}</version>
         </dependency>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 
     <build>

+ 3 - 0
sckw-modules/sckw-product/src/main/java/com/sckw/product/model/vo/res/BuildingMaterialsMarketList.java

@@ -91,4 +91,7 @@ public class BuildingMaterialsMarketList {
 
     @Schema(description = "是否签约")
     private boolean signFlag;
+
+    @Schema(description = "是否展示立即下单按钮")
+    private boolean showOrderButtonFlag;
 }

+ 4 - 0
sckw-modules/sckw-product/src/main/java/com/sckw/product/model/vo/res/GoodsDetailVo.java

@@ -179,6 +179,10 @@ public class GoodsDetailVo {
 
     @Schema(description = "是否签约")
     private boolean signFlag;
+
+    @Schema(description = "是否展示立即下单按钮")
+    private boolean showOrderButtonFlag;
+
     @Schema(description = "供应商信息")
     private GoodsEntInfo goodsEntInfo;
 

+ 28 - 3
sckw-modules/sckw-product/src/main/java/com/sckw/product/service/KwpGoodsService.java

@@ -415,12 +415,15 @@ public class KwpGoodsService {
         detail.setImages(images).setPrice(price).setGoodsEntInfo(goodsEntInfo).setAttributes(attributes).setAddressInfo(addressInfo);
         Long entId = LoginUserHolder.getEntId();
         detail.setSignFlag(false);
+        detail.setShowOrderButtonFlag(false);
         if (Objects.nonNull(entId)) {
-            TradeContractGoodsDto tradeContractResDto = remoteContractService.queryTradeContractNew(entId, id, LocalDateTime.now());
+            LocalDateTime now = LocalDateTime.now();
+            TradeContractGoodsDto tradeContractResDto = remoteContractService.queryTradeContractNew(entId, id, now);
             if (Objects.nonNull(tradeContractResDto)) {
                 detail.setSignPrice(tradeContractResDto.getPrice());
                 detail.setSignFlag(true);
             }
+            detail.setShowOrderButtonFlag(Objects.nonNull(queryOrderButtonTradeContract(entId, id, now, tradeContractResDto)));
         }
         return detail;
     }
@@ -1091,7 +1094,7 @@ public class KwpGoodsService {
         Long entId = LoginUserHolder.getEntId();
         if (Objects.nonNull(entId)) {
             if (Objects.nonNull(params.getSign())) {//查询签约或未签约商品
-                List<Long> longs = remoteContractService.queryNewSignGoods(entId, LocalDateTime.now());
+                List<Long> longs = remoteContractService.queryNewSignGoodsByEnt(entId, LocalDateTime.now());
                 if (Objects.equals(params.getSign(), 1)) {
                     if (CollUtil.isNotEmpty(longs)) {
                         wrapper.in(KwpGoods::getId, longs);
@@ -1147,11 +1150,13 @@ public class KwpGoodsService {
             List<KwpGoodsPriceRange> priceRanges = priceRangeMap.get(e.getId());
             materials.setGoodsTypeLabel(CollectionUtils.isNotEmpty(productNameMap) ? productNameMap.get(e.getGoodsType()) : null).setUnitLabel(CollectionUtils.isNotEmpty(unitMap) ? unitMap.get(e.getUnit()) : null).setSpec(CollectionUtils.isNotEmpty(specMap) ? specMap.get(e.getSpec()) : null).setAddressName(Objects.isNull(address) ? null : address.getCityName()).setDetailAddress(Objects.isNull(address) ? null : address.getDetailAddress()).setPrice(CollectionUtils.isEmpty(priceRanges) ? null : priceRanges.get(0).getPrice()).setThumb(FileUtils.splice(e.getThumb())).setSupplyEnt(entMap.get(e.getSupplyEntId()));
             materials.setSignFlag(false);
+            materials.setShowOrderButtonFlag(false);
             if (Objects.nonNull(entId)) {
-                TradeContractGoodsDto tradeContractResDto = remoteContractService.queryTradeContractNew(entId, e.getId(), LocalDateTime.now());
+                TradeContractGoodsDto tradeContractResDto = remoteContractService.queryTradeContractNewByEnt(entId, e.getId(), LocalDateTime.now());
                 if (Objects.nonNull(tradeContractResDto)) {
                     materials.setSignPrice(tradeContractResDto.getPrice());
                     materials.setSignFlag(true);
+                    materials.setShowOrderButtonFlag(true);
                 }
             }
             result.add(materials);
@@ -1561,18 +1566,38 @@ public class KwpGoodsService {
             List<KwpGoodsPriceRange> priceRanges = priceRangeMap.get(e.getId());
             materials.setGoodsTypeLabel(CollectionUtils.isNotEmpty(productNameMap) ? productNameMap.get(e.getGoodsType()) : null).setUnitLabel(CollectionUtils.isNotEmpty(unitMap) ? unitMap.get(e.getUnit()) : null).setSpec(CollectionUtils.isNotEmpty(goodsMap) ? goodsMap.get(e.getSpec()) : null).setAddressName(Objects.isNull(address) ? null : address.getCityName()).setDetailAddress(Objects.isNull(address) ? null : address.getDetailAddress()).setPrice(CollectionUtils.isEmpty(priceRanges) ? null : priceRanges.get(0).getPrice()).setThumb(FileUtils.splice(e.getThumb())).setSupplyEnt(entMap.get(e.getSupplyEntId()));
             materials.setSignFlag(false);
+            materials.setShowOrderButtonFlag(false);
             if (Objects.nonNull(entId)) {
                 TradeContractGoodsDto tradeContractResDto = remoteContractService.queryTradeContractNew(entId, e.getId(), LocalDateTime.now());
                 if (Objects.nonNull(tradeContractResDto)) {
                     materials.setSignPrice(tradeContractResDto.getPrice());
                     materials.setSignFlag(true);
                 }
+                materials.setShowOrderButtonFlag(Objects.nonNull(queryOrderButtonTradeContract(entId, e.getId(), LocalDateTime.now(), tradeContractResDto)));
             }
             result.add(materials);
         });
         return result;
     }
 
+    /**
+     * 查询当前企业是否存在可用于展示立即下单按钮的有效贸易合同商品。
+     *
+     * @param entId             当前登录企业ID
+     * @param goodsId           商品ID
+     * @param time              当前时间
+     * @param buyerContractGoods 当前企业作为买方时已查询到的合同商品
+     * @return 可展示立即下单按钮的贸易合同商品,未签有效合同则返回null
+     */
+    private TradeContractGoodsDto queryOrderButtonTradeContract(Long entId, Long goodsId, LocalDateTime time, TradeContractGoodsDto buyerContractGoods) {
+        if (Objects.isNull(entId) || Objects.isNull(goodsId) || Objects.isNull(time)) {
+            return null;
+        }
+        return Objects.nonNull(buyerContractGoods)
+                ? buyerContractGoods
+                : remoteContractService.queryTradeContractNewByEnt(entId, goodsId, time);
+    }
+
     public List<BuildingMaterialsMarketList> hootGoods() {
         LocalDateTime now = LocalDateTime.now();
         LocalDateTime start = now.minusDays(30);

+ 4 - 4
sckw-modules/sckw-product/src/main/resources/bootstrap-cxf.yml

@@ -3,16 +3,16 @@ spring:
     nacos:
       discovery:
         # 服务注册地址
-        server-addr: @nacos.server@
+        server-addr: 118.116.4.155:8848
         # 命名空间
-        namespace: @nacos.namespace@
+        namespace: sckw-ng-service-platform-xf
         # 共享配置
         group: sckw-ng-service-platform
       config:
         # 配置中心地址
-        server-addr: @nacos.server@
+        server-addr: 118.116.4.155:8848
         # 命名空间
-        namespace: @nacos.namespace@
+        namespace: sckw-ng-service-platform-xf
         # 共享配置
         group: sckw-ng-service-platform
         # 配置文件格式

+ 30 - 0
sckw-modules/sckw-product/src/test/java/com/sckw/product/model/vo/res/BuildingMaterialsMarketListTest.java

@@ -0,0 +1,30 @@
+package com.sckw.product.model.vo.res;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.math.BigDecimal;
+
+/**
+ * 建材市场商品列表响应单元测试。
+ */
+public class BuildingMaterialsMarketListTest {
+
+    /**
+     * 校验新增已签约展示标识默认不展示,且支持链式赋值。
+     */
+    @Test
+    public void showSignedFlagShouldDefaultFalseAndSupportSetter() {
+        BuildingMaterialsMarketList goods = new BuildingMaterialsMarketList();
+
+        Assert.assertFalse(goods.isShowOrderButtonFlag());
+
+        goods.setSignPrice(new BigDecimal("12.34"))
+                .setSignFlag(true)
+                .setShowOrderButtonFlag(true);
+
+        Assert.assertTrue(goods.isSignFlag());
+        Assert.assertTrue(goods.isShowOrderButtonFlag());
+        Assert.assertEquals(new BigDecimal("12.34"), goods.getSignPrice());
+    }
+}

+ 30 - 0
sckw-modules/sckw-product/src/test/java/com/sckw/product/model/vo/res/GoodsDetailVoTest.java

@@ -0,0 +1,30 @@
+package com.sckw.product.model.vo.res;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.math.BigDecimal;
+
+/**
+ * 商品详情响应单元测试。
+ */
+public class GoodsDetailVoTest {
+
+    /**
+     * 校验立即下单按钮展示标识默认不展示,并支持链式赋值。
+     */
+    @Test
+    public void showOrderButtonFlagShouldDefaultFalseAndSupportSetter() {
+        GoodsDetailVo detailVo = new GoodsDetailVo();
+
+        Assert.assertFalse(detailVo.isShowOrderButtonFlag());
+
+        detailVo.setSignPrice(new BigDecimal("12.34"))
+                .setSignFlag(true)
+                .setShowOrderButtonFlag(true);
+
+        Assert.assertTrue(detailVo.isSignFlag());
+        Assert.assertTrue(detailVo.isShowOrderButtonFlag());
+        Assert.assertEquals(new BigDecimal("12.34"), detailVo.getSignPrice());
+    }
+}

+ 5 - 0
sckw-modules/sckw-system/pom.xml

@@ -111,6 +111,11 @@
             <groupId>com.sckw</groupId>
             <artifactId>sckw-order-api</artifactId>
         </dependency>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <scope>test</scope>
+        </dependency>
 
     </dependencies>
     <build>

+ 2 - 2
sckw-modules/sckw-system/src/main/java/com/sckw/system/controller/KwsMenuController.java

@@ -66,7 +66,7 @@ public class KwsMenuController {
 
     /**
      * @param params {clientType:终端类型、parentId:父级id、name:菜单名称、url:跳转地址、perms:权限标识、
-     *               type:类型:0目录/1菜单/2按钮、icon:图标、sort:排序、level:层级、custom:是否定制、
+     *               type:类型:0目录/1菜单/2按钮、icon:图标、notSelectedIconPath:未选中菜单图标路径、sort:排序、level:层级、custom:是否定制、
      *               isMain:是否主账号、usingRoles:适用企业类型(1供应商,2采购商,34PL物流,43PL物流)}
      * @return
      * @description 新增
@@ -81,7 +81,7 @@ public class KwsMenuController {
 
     /**
      * @param params {id:主键,clientType:终端类型、parentId:父级id、name:菜单名称、url:跳转地址、perms:权限标识、
-     *               type:类型:0目录/1菜单/2按钮、icon:图标、sort:排序、level:层级、custom:是否定制、
+     *               type:类型:0目录/1菜单/2按钮、icon:图标、notSelectedIconPath:未选中菜单图标路径、sort:排序、level:层级、custom:是否定制、
      *               isMain:是否主账号、usingRoles:适用企业类型(1供应商,2采购商,34PL物流,43PL物流)}
      * @desc 更新
      * @author zk

+ 37 - 0
sckw-modules/sckw-system/src/main/java/com/sckw/system/dubbo/RemoteSystemServiceImpl.java

@@ -27,6 +27,7 @@ import com.sckw.system.service.KwsUserService;
 import com.sckw.system.service.SysAreaService;
 import jakarta.annotation.Resource;
 import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
 import org.apache.dubbo.config.annotation.DubboService;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
@@ -44,6 +45,7 @@ import java.util.stream.Collectors;
  */
 @DubboService(group = "design", version = "1.0.0")
 @RequiredArgsConstructor
+@Slf4j
 public class RemoteSystemServiceImpl implements RemoteSystemService {
 
     @Autowired
@@ -138,6 +140,41 @@ public class RemoteSystemServiceImpl implements RemoteSystemService {
         return JSONObject.parseArray(dictCache, SysDictResDto.class);
     }
 
+    /**
+     * 根据字典类型直接查询数据库,不使用Redis缓存。
+     * 适用于需要获取最新字典数据或缓存失效的场景。
+     *
+     * @param type 字典类型
+     * @return 字典列表,如果类型为空或未找到数据则返回空列表
+     */
+    @Override
+    public List<SysDictResDto> queryDictDbByType(String type) {
+        // 参数校验:如果字典类型为空,直接返回空列表
+        if (StringUtils.isBlank(type)) {
+            return Collections.emptyList();
+        }
+
+        // 记录调试日志,便于追踪查询请求
+        log.debug("开始从数据库查询字典数据,类型: {}", type);
+
+        // 直接从数据库查询指定类型的字典数据
+        List<SysDict> sysDictList = sysDictDao.queryByType(type);
+
+        // 如果查询结果为空,记录日志并返回空列表
+        if (CollectionUtils.isEmpty(sysDictList)) {
+            log.debug("数据库中未找到类型为 {} 的字典数据", type);
+            return Collections.emptyList();
+        }
+
+        // 将实体对象转换为DTO对象
+        List<SysDictResDto> result = BeanUtils.copyToList(sysDictList, SysDictResDto.class);
+        
+        // 记录成功日志,包含查询到的数据数量
+        log.debug("成功从数据库查询到类型为 {} 的字典数据,数量: {}", type, result.size());
+        
+        return result;
+    }
+
     @Override
     public Map<String, Map<String, String>> queryDictByType(List<String> list) {
         if (CollectionUtils.isEmpty(list)) {

+ 73 - 1
sckw-modules/sckw-system/src/main/java/com/sckw/system/dubbo/RemoteUserServiceImpl.java

@@ -28,6 +28,7 @@ import com.sckw.system.model.vo.res.KwsUserResVo;
 import com.sckw.system.model.vo.res.KwsUserSystemTypeVo;
 import com.sckw.system.service.*;
 import jakarta.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
 import org.apache.dubbo.config.annotation.DubboService;
 import org.springframework.transaction.annotation.Transactional;
 
@@ -39,7 +40,7 @@ import java.util.stream.Collectors;
  * @desc 远程接口
  * @date 2023/6/12
  */
-
+@Slf4j
 @DubboService(group = "design", version = "1.0.0")
 public class RemoteUserServiceImpl implements RemoteUserService {
 
@@ -81,6 +82,9 @@ public class RemoteUserServiceImpl implements RemoteUserService {
     @Resource
     private KwsRoleDao kwsRoleDao;
 
+    @Resource
+    private KwsMenuRightsDao kwsMenuRightsDao;
+
     @Resource
     private CommonService commonService;
 
@@ -124,6 +128,74 @@ public class RemoteUserServiceImpl implements RemoteUserService {
         return BeanUtils.copyToList(kwsMenuResVos, UserAccessMenuInfoResDto.class);
     }
 
+    /**
+     * 根据角色ID查询APP端底部导航栏(TabBar)菜单列表
+     * <p>
+     * 业务逻辑:
+     * 1. 校验角色ID有效性
+     * 2. 查询该角色关联的菜单权限ID
+     * 3. 根据菜单ID查询菜单详情
+     * 4. 过滤出符合APP端TabBar展示的菜单(类型=1, 客户端类型=3, 未删除)
+     * 5. 按排序字段排序并转换为DTO返回
+     *
+     * @param roleId 角色ID
+     * @return APP端TabBar菜单列表
+     */
+    @Override
+    public List<AppTabBarMenuResDto> queryAppTabBarMenuByRoleId(Long roleId) {
+        // 1. 参数校验
+        if (Objects.isNull(roleId)) {
+            log.warn("查询APP TabBar菜单失败,角色ID为空");
+            return Collections.emptyList();
+        }
+
+        // 2. 查询角色关联的菜单权限
+        List<KwsMenuRights> menuRightsList = kwsMenuRightsDao.selectByRoleIds(Collections.singletonList(roleId));
+        if (CollectionUtils.isEmpty(menuRightsList)) {
+            log.debug("角色ID: {} 未配置任何菜单权限", roleId);
+            return Collections.emptyList();
+        }
+
+        // 3. 提取有效的菜单ID列表
+        List<Long> menuIds = menuRightsList.stream()
+                .map(KwsMenuRights::getMenuId)
+                .filter(Objects::nonNull)
+                .distinct()
+                .toList();
+
+        if (CollectionUtils.isEmpty(menuIds)) {
+            log.warn("角色ID: {} 关联的菜单权限中无有效菜单ID", roleId);
+            return Collections.emptyList();
+        }
+
+        // 4. 批量查询菜单详情
+        List<KwsMenu> menus = kwsMenuService.selectByKeys(menuIds);
+        if (CollectionUtils.isEmpty(menus)) {
+            log.warn("根据菜单IDs: {} 未查询到对应的菜单信息", menuIds);
+            return Collections.emptyList();
+        }
+
+        // 5. 过滤、排序并转换DTO
+        // 过滤条件:
+        // - type == 1 (菜单类型)
+        // - clientType == 3 (APP端)
+        // - delFlag == 0 (未删除)
+        List<AppTabBarMenuResDto> result = menus.stream()
+                .filter(menu -> Objects.equals(menu.getType(), Global.NUMERICAL_ONE))
+                .filter(menu -> Objects.equals(menu.getClientType(), Global.NUMERICAL_THREE))
+                .filter(menu -> Objects.equals(menu.getDelFlag(), Global.NO))
+                .sorted(Comparator.comparing(KwsMenu::getSort, Comparator.nullsLast(Integer::compareTo)))
+                .map(menu -> {
+                    AppTabBarMenuResDto dto = new AppTabBarMenuResDto();
+                    BeanUtils.copyProperties(menu, dto);
+                    return dto;
+                })
+                .toList();
+
+        log.debug("角色ID: {} 查询到 {} 个APP TabBar菜单", roleId, result.size());
+        return result;
+    }
+
     @Override
     public void forgetPassword(ForgetPasswordReqDto reqDto) throws SystemException {
         com.sckw.system.model.vo.req.ForgetPasswordReqVo forgetPasswordReqVo = new ForgetPasswordReqVo();

+ 7 - 1
sckw-modules/sckw-system/src/main/java/com/sckw/system/model/KwsMenu.java

@@ -60,6 +60,12 @@ public class KwsMenu extends BaseModel {
      */
     private String icon;
 
+    /**
+     * 未选中菜单图标路径
+     */
+    @Size(max = 255, message = "未选中菜单图标路径长度不能超过255")
+    private String notSelectedIconPath;
+
     /**
      * 排序
      */
@@ -90,4 +96,4 @@ public class KwsMenu extends BaseModel {
      */
     private String usingRoles;
 
-}
+}

+ 3 - 0
sckw-modules/sckw-system/src/main/java/com/sckw/system/model/vo/res/AppMenuPermItemResVo.java

@@ -46,6 +46,9 @@ public class AppMenuPermItemResVo implements Serializable {
     @Schema(description = "菜单图标")
     private String icon;
 
+    @Schema(description = "未选中菜单图标路径")
+    private String notSelectedIconPath;
+
     @Schema(description = "排序")
     private Integer sort;
 

+ 5 - 0
sckw-modules/sckw-system/src/main/java/com/sckw/system/model/vo/res/FindMenuTreeResVo.java

@@ -105,6 +105,11 @@ public class FindMenuTreeResVo implements Serializable {
      */
     private String icon;
 
+    /**
+     * 未选中菜单图标路径
+     */
+    private String notSelectedIconPath;
+
     /**
      * 排序
      */

+ 5 - 0
sckw-modules/sckw-system/src/main/java/com/sckw/system/model/vo/res/MenuDetailResVo.java

@@ -102,6 +102,11 @@ public class MenuDetailResVo implements Serializable {
      */
     private String icon;
 
+    /**
+     * 未选中菜单图标路径
+     */
+    private String notSelectedIconPath;
+
     /**
      * 排序
      */

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

@@ -379,6 +379,9 @@ public class KwsMenuService {
         if (v.getIcon() == null) {
             v.setIcon("");
         }
+        if (v.getNotSelectedIconPath() == null) {
+            v.setNotSelectedIconPath("");
+        }
         if (v.getSort() == null) {
             v.setSort(0);
         }
@@ -860,6 +863,19 @@ public class KwsMenuService {
         return kwsMenuDao.selectAll();
     }
 
+    /**
+     * 根据菜单ID集合批量查询菜单。
+     *
+     * @param menuIds 菜单ID集合
+     * @return 菜单列表
+     */
+    public List<KwsMenu> selectByKeys(List<Long> menuIds) {
+        if (CollectionUtils.isEmpty(menuIds)) {
+            return Collections.emptyList();
+        }
+        return kwsMenuDao.selectByKeys(menuIds);
+    }
+
     @Transactional(rollbackFor = {})
     public void moveMenu(MoveMenuReqVo reqVo) {
         KwsMenu kwsMenu = kwsMenuDao.selectByKey(reqVo.getId());

+ 0 - 1
sckw-modules/sckw-system/src/main/resources/bootstrap.yml

@@ -6,7 +6,6 @@ spring:
     name: sckw-ng-system
   profiles:
     active: @profiles.active@
-#    active: local
 #    active: test
   main:
     allow-bean-definition-overriding: true

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

@@ -10,6 +10,7 @@
     <result column="perms" jdbcType="VARCHAR" property="perms" />
     <result column="type" jdbcType="INTEGER" property="type" />
     <result column="icon" jdbcType="VARCHAR" property="icon" />
+    <result column="not_selected_icon_path" jdbcType="VARCHAR" property="notSelectedIconPath" />
     <result column="sort" jdbcType="INTEGER" property="sort" />
     <result column="level" jdbcType="INTEGER" property="level" />
     <result column="custom" jdbcType="INTEGER" property="custom" />
@@ -34,6 +35,7 @@
         sm.perms,
         sm.type,
         sm.icon,
+        sm.not_selected_icon_path,
         sm.sort,
         sm.level,
         sm.custom,
@@ -86,6 +88,9 @@
       <if test="icon != null">
         icon,
       </if>
+      <if test="notSelectedIconPath != null">
+        not_selected_icon_path,
+      </if>
       <if test="sort != null">
         sort,
       </if>
@@ -154,6 +159,9 @@
       <if test="icon != null">
         #{icon,jdbcType=VARCHAR},
       </if>
+      <if test="notSelectedIconPath != null">
+        #{notSelectedIconPath,jdbcType=VARCHAR},
+      </if>
       <if test="sort != null">
         #{sort,jdbcType=INTEGER},
       </if>
@@ -220,6 +228,9 @@
       <if test="icon != null">
         icon = #{icon,jdbcType=VARCHAR},
       </if>
+      <if test="notSelectedIconPath != null">
+        not_selected_icon_path = #{notSelectedIconPath,jdbcType=VARCHAR},
+      </if>
       <if test="sort != null">
         sort = #{sort,jdbcType=INTEGER},
       </if>
@@ -378,6 +389,9 @@
     <if test="icon != null">
       and icon = #{icon,jdbcType=VARCHAR}
     </if>
+    <if test="notSelectedIconPath != null">
+      and not_selected_icon_path = #{notSelectedIconPath,jdbcType=VARCHAR}
+    </if>
     <if test="sort != null">
       and sort = #{sort,jdbcType=INTEGER}
     </if>

+ 57 - 0
sckw-modules/sckw-system/src/test/java/com/sckw/system/model/KwsMenuFieldMappingTest.java

@@ -0,0 +1,57 @@
+package com.sckw.system.model;
+
+import com.sckw.system.model.vo.res.AppMenuPermItemResVo;
+import org.junit.Test;
+
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * 菜单未选中图标字段映射测试。
+ */
+public class KwsMenuFieldMappingTest {
+
+    /**
+     * 验证新增字段可在菜单实体和返回对象中正常读写。
+     */
+    @Test
+    public void shouldReadAndWriteNotSelectedIconPath() {
+        String notSelectedIconPath = "/static/menu/home-unselected.png";
+
+        KwsMenu kwsMenu = new KwsMenu();
+        kwsMenu.setNotSelectedIconPath(notSelectedIconPath);
+
+        AppMenuPermItemResVo resVo = new AppMenuPermItemResVo();
+        resVo.setNotSelectedIconPath(kwsMenu.getNotSelectedIconPath());
+
+        assertEquals(notSelectedIconPath, kwsMenu.getNotSelectedIconPath());
+        assertEquals(notSelectedIconPath, resVo.getNotSelectedIconPath());
+    }
+
+    /**
+     * 验证新增字段已配置到 MyBatis 新增、更新和查询映射中。
+     *
+     * @throws Exception 读取 XML 资源失败时抛出异常
+     */
+    @Test
+    public void shouldContainNotSelectedIconPathSqlMapping() throws Exception {
+        URL mapperUrl = Thread.currentThread()
+                .getContextClassLoader()
+                .getResource("mapper/KwsMenuDao.xml");
+        assertNotNull(mapperUrl);
+
+        String xml = Files.readString(Paths.get(mapperUrl.toURI()), StandardCharsets.UTF_8);
+
+        assertTrue(xml.contains("property=\"notSelectedIconPath\""));
+        assertTrue(xml.contains("sm.not_selected_icon_path"));
+        assertTrue(xml.contains("not_selected_icon_path,"));
+        assertTrue(xml.contains("#{notSelectedIconPath,jdbcType=VARCHAR}"));
+        assertTrue(xml.contains("not_selected_icon_path = #{notSelectedIconPath,jdbcType=VARCHAR}"));
+    }
+}

+ 1 - 0
sckw-modules/sckw-transport/src/main/java/com/sckw/transport/dubbo/TransportServiceImpl.java

@@ -1331,6 +1331,7 @@ public class TransportServiceImpl implements TransportRemoteService {
         addLogisticOrderDTO.setActualDisPatch(param.getActualDisPatch());
         addLogisticOrderDTO.setGoodsUnit(param.getGoodsUnit());
         addLogisticOrderDTO.setMeasurementWay(param.getMeasurementWay());
+        addLogisticOrderDTO.setAgentFlag(param.getAgentFlag());
         return addLogisticOrderDTO;
     }
 

+ 2 - 0
sckw-modules/sckw-transport/src/main/java/com/sckw/transport/handler/TakingOrderHandler.java

@@ -407,6 +407,7 @@ public class TakingOrderHandler extends AbstractWaybillOrderHandler<OrderCircula
         waybillOrder.setCreateBy(param.getDriverId());
         waybillOrder.setUpdateBy(param.getDriverId());
         waybillOrder.setDispatchWay(logOrder.getDispatchWay());
+        waybillOrder.setAgentFlag(logOrder.getAgentFlag());
         waybillOrderRepository.save(waybillOrder);
         log.info("创建物流运单成功,运单ID:{}", waybillOrder.getId());
 
@@ -430,6 +431,7 @@ public class TakingOrderHandler extends AbstractWaybillOrderHandler<OrderCircula
         subtask.setWOrderNo(waybillOrder.getWOrderNo());
         subtask.setUnit("吨"); //TODO DONGLANG
 
+        subtask.setAgentFlag(waybillOrder.getAgentFlag());
         subtask.setEntrustAmount(truckLoadVolume);
         subtask.setStatus(CarWaybillV1Enum.PENDING_VEHICLE.getCode());
         subtask.setCreateBy(param.getDriverId());

+ 7 - 1
sckw-modules/sckw-transport/src/main/java/com/sckw/transport/model/KwtLogisticsOrder.java

@@ -254,9 +254,15 @@ public class KwtLogisticsOrder implements Serializable {
     @TableField("measurement_way")
     private Integer measurementWay;
 
+    /**
+     * 是否代理属性:0-否,1-是
+     */
+    @TableField("agent_flag")
+    private Integer agentFlag;
+
     private static final long serialVersionUID = 1L;
 
     @TableField(exist = false)
     private String yearAndMonth;
 
-}
+}

+ 7 - 1
sckw-modules/sckw-transport/src/main/java/com/sckw/transport/model/KwtWaybillOrder.java

@@ -167,6 +167,12 @@ public class KwtWaybillOrder implements Serializable {
     @TableField("task_end_time")
     private Date taskEndTime;
 
+    /**
+     * 是否代理属性:0-否,1-是
+     */
+    @TableField("agent_flag")
+    private Integer agentFlag;
+
     private static final long serialVersionUID = 1L;
 
     @TableField(exist = false)
@@ -185,4 +191,4 @@ public class KwtWaybillOrder implements Serializable {
     //用于存储动态状态
     @TableField(exist = false)
     private Integer targetStatus;
-}
+}

+ 7 - 0
sckw-modules/sckw-transport/src/main/java/com/sckw/transport/model/KwtWaybillOrderSubtask.java

@@ -1,5 +1,6 @@
 package com.sckw.transport.model;
 
+import com.baomidou.mybatisplus.annotation.TableField;
 import com.baomidou.mybatisplus.annotation.TableName;
 import com.sckw.core.model.base.BaseModel;
 import lombok.Data;
@@ -90,4 +91,10 @@ public class KwtWaybillOrderSubtask extends BaseModel {
      * 卸货凭证操作人
      */
     private Long unloadOperator;
+
+    /**
+     * 是否代理属性:0-否,1-是
+     */
+    @TableField("agent_flag")
+    private Integer agentFlag;
 }

+ 4 - 0
sckw-modules/sckw-transport/src/main/java/com/sckw/transport/model/dto/AddLogisticOrderDTO.java

@@ -256,4 +256,8 @@ public class AddLogisticOrderDTO implements Serializable {
      * 商品单位
      */
     private String goodsUnit;
+    /**
+     * 是否代理属性:0-否,1-是
+     */
+    private Integer agentFlag;
 }

+ 11 - 0
sckw-modules/sckw-transport/src/main/java/com/sckw/transport/model/param/LogisticsOrderResp.java

@@ -199,4 +199,15 @@ public class LogisticsOrderResp implements Serializable {
     @Schema(description = "订单余量")
     private BigDecimal remainingAmount;
 
+    /**
+     * 是否代理属性:0-否,1-是
+     */
+    @Schema(description = "是否代理属性:0-否,1-是")
+    private Integer agentFlag;
+
+    /**
+     * 是否代理属性描述
+     */
+    @Schema(description = "是否代理属性描述")
+    private String agentFlagDesc;
 }

+ 10 - 0
sckw-modules/sckw-transport/src/main/java/com/sckw/transport/model/param/QueryLogisticsOrderReq.java

@@ -37,6 +37,11 @@ public class QueryLogisticsOrderReq extends PageReq implements Serializable {
 
     @Schema(description = "贸易订单号")
     private String tradeOrderNo;
+    /**
+     * 关键字段查询:支持物流订单编号、商品名称、代理关键字。
+     */
+    @Schema(description = "关键字段查询:支持物流订单编号、商品名称、代理关键字")
+    private String keywords;
     /**
      * 商品名称
      */
@@ -77,4 +82,9 @@ public class QueryLogisticsOrderReq extends PageReq implements Serializable {
      */
     @Schema(description = "订单状态")
     private String orderStatus;
+    /**
+     * 是否代理属性:0-否,1-是
+     */
+    @Schema(description = "是否代理属性:0-否,1-是")
+    private Integer agentFlag;
 }

+ 11 - 0
sckw-modules/sckw-transport/src/main/java/com/sckw/transport/model/param/WaybillOrderReq.java

@@ -42,6 +42,11 @@ public class WaybillOrderReq extends PageReq implements Serializable {
 
     @Schema(description = "贸易订单号")
     private String tradeOrderNo;
+    /**
+     * 关键字段查询:支持物流运单编号、商品名称、代理关键字。
+     */
+    @Schema(description = "关键字段查询:支持物流运单编号、商品名称、代理关键字")
+    private String keywords;
     /**
      * 商品名称
      */
@@ -102,4 +107,10 @@ public class WaybillOrderReq extends PageReq implements Serializable {
      */
     @Schema(description = "状态")
     private String status;
+
+    /**
+     * 是否代理属性:0-否,1-是
+     */
+    @Schema(description = "是否代理属性:0-否,1-是")
+    private Integer agentFlag;
 }

+ 12 - 0
sckw-modules/sckw-transport/src/main/java/com/sckw/transport/model/param/WaybillOrderResp.java

@@ -179,4 +179,16 @@ public class WaybillOrderResp implements Serializable {
     @Schema(description = "毛重")
     private BigDecimal grossAmount;
 
+    /**
+     * 是否代理属性:0-否,1-是
+     */
+    @Schema(description = "是否代理属性:0-否,1-是")
+    private Integer agentFlag;
+
+    /**
+     * 是否代理属性描述
+     */
+    @Schema(description = "是否代理属性描述")
+    private String agentFlagDesc;
+
 }

+ 17 - 1
sckw-modules/sckw-transport/src/main/java/com/sckw/transport/repository/KwtLogisticsOrderRepository.java

@@ -43,6 +43,7 @@ public class KwtLogisticsOrderRepository extends ServiceImpl<KwtLogisticsOrderMa
         public IPage<KwtLogisticsOrder> queryByPage(Set<Long> logisticsOrderIds, Long  tradeOrderId, String orderNo,
                                                     String tradeOrderNo,
                                                     String status,
+                                                    Integer agentFlag,
                                                     Date startTime, Date endTime, int pageNum, int pageSize) {
         return page(new Page<>(pageNum, pageSize),
                 Wrappers.<KwtLogisticsOrder>lambdaQuery()
@@ -50,6 +51,7 @@ public class KwtLogisticsOrderRepository extends ServiceImpl<KwtLogisticsOrderMa
                         .in(CollectionUtils.isNotEmpty(logisticsOrderIds), KwtLogisticsOrder::getId, logisticsOrderIds)
                         .eq(Objects.nonNull(tradeOrderId),KwtLogisticsOrder::getTOrderId, tradeOrderId)
                         .eq(StringUtils.isNotBlank(status),KwtLogisticsOrder::getStatus, status)
+                        .eq(Objects.nonNull(agentFlag), KwtLogisticsOrder::getAgentFlag, agentFlag)
                         .like(StringUtils.isNotBlank(orderNo),KwtLogisticsOrder::getLOrderNo, orderNo)
                         .like(StringUtils.isNotBlank(tradeOrderNo),KwtLogisticsOrder::getTOrderNo, tradeOrderNo)
                         .ge(Objects.nonNull(startTime),KwtLogisticsOrder::getCreateTime, startTime)
@@ -72,6 +74,18 @@ public class KwtLogisticsOrderRepository extends ServiceImpl<KwtLogisticsOrderMa
                 .last("limit 1"));
     }
 
+    /**
+     * 根据物流订单编号模糊查询物流订单列表。
+     *
+     * @param logisticsOrderNo 物流订单编号关键字
+     * @return 物流订单列表
+     */
+    public List<KwtLogisticsOrder> queryListByLogisticsOrderNo(String logisticsOrderNo) {
+        return list(Wrappers.<KwtLogisticsOrder>lambdaQuery()
+                .eq(KwtLogisticsOrder::getDelFlag, 0)
+                .like(StringUtils.isNotBlank(logisticsOrderNo), KwtLogisticsOrder::getLOrderNo, logisticsOrderNo));
+    }
+
     public List<KwtLogisticsOrder> queryByLogisticsOrderIds(List<Long> logisOrderIds) {
         return list(Wrappers.<KwtLogisticsOrder>lambdaQuery()
                 .eq(KwtLogisticsOrder::getDelFlag,0)
@@ -108,12 +122,14 @@ public class KwtLogisticsOrderRepository extends ServiceImpl<KwtLogisticsOrderMa
                 .eq(KwtLogisticsOrder::getBillingMode, billingMethod));
     }
 
-    public List<KwtLogisticsOrder> queryList( Set<Long> logOrderIds, Long tradeOrderId, String orderNo, String orderStatus, String startTime, String endTime) {
+    public List<KwtLogisticsOrder> queryList( Set<Long> logOrderIds, Long tradeOrderId, String orderNo, String orderStatus,
+                                             Integer agentFlag, String startTime, String endTime) {
         return list(Wrappers.<KwtLogisticsOrder>lambdaQuery()
                 .eq(KwtLogisticsOrder::getDelFlag,0)
                 .in(CollectionUtils.isNotEmpty(logOrderIds), KwtLogisticsOrder::getId, logOrderIds)
                 .eq(Objects.nonNull(tradeOrderId),KwtLogisticsOrder::getTOrderId, tradeOrderId)
                 .eq(StringUtils.isNotBlank(orderStatus),KwtLogisticsOrder::getStatus, orderStatus)
+                .eq(Objects.nonNull(agentFlag), KwtLogisticsOrder::getAgentFlag, agentFlag)
                 .ge(StringUtils.isNotBlank(startTime),KwtLogisticsOrder::getLoadTime, startTime)
                 .le(StringUtils.isNotBlank(startTime),KwtLogisticsOrder::getUnloadTime, endTime)
                 .like(StringUtils.isNotBlank(orderNo),KwtLogisticsOrder::getLOrderNo, orderNo)

+ 13 - 1
sckw-modules/sckw-transport/src/main/java/com/sckw/transport/repository/KwtWaybillOrderRepository.java

@@ -126,6 +126,18 @@ public class KwtWaybillOrderRepository extends ServiceImpl<KwtWaybillOrderMapper
                 .last("limit 1"));
     }
 
+    /**
+     * 根据物流运单编号模糊查询运单列表。
+     *
+     * @param wOrderNo 物流运单编号关键字
+     * @return 运单列表
+     */
+    public List<KwtWaybillOrder> queryListByWOrderNo(String wOrderNo) {
+        return list(Wrappers.<KwtWaybillOrder>lambdaQuery()
+                .eq(KwtWaybillOrder::getDelFlag, 0)
+                .like(StringUtils.isNotBlank(wOrderNo), KwtWaybillOrder::getWOrderNo, wOrderNo));
+    }
+
     public List<KwtWaybillOrder> queryWaybillOrderByEntId(Long entId) {
         return list(Wrappers.<KwtWaybillOrder>lambdaQuery()
                 .eq(KwtWaybillOrder::getDelFlag,0)
@@ -341,4 +353,4 @@ public class KwtWaybillOrderRepository extends ServiceImpl<KwtWaybillOrderMapper
         return page(page,wrapper);
     }
 
-}
+}

+ 2 - 0
sckw-modules/sckw-transport/src/main/java/com/sckw/transport/repository/KwtWaybillOrderSubtaskRepository.java

@@ -68,12 +68,14 @@ public class KwtWaybillOrderSubtaskRepository extends ServiceImpl<KwtWaybillOrde
     public IPage<KwtWaybillOrderSubtask> queryByPage( Set<Long> logOrderIds, Set<Long> billOrderIds,
                                                      Integer status,
                                                       List<Integer> waybillOrderStatus,
+                                                     Integer agentFlag,
                                                      int pageNum,
                                                      int pageSize) {
         return page(new Page<>(pageNum, pageSize),
                 Wrappers.<KwtWaybillOrderSubtask>lambdaQuery()
                         .eq(BaseModel::getDelFlag,0)
                         .eq(Objects.nonNull( status),KwtWaybillOrderSubtask::getStatus, status)
+                        .eq(Objects.nonNull(agentFlag), KwtWaybillOrderSubtask::getAgentFlag, agentFlag)
                         .notIn(CollectionUtils.isNotEmpty( waybillOrderStatus),KwtWaybillOrderSubtask::getStatus, waybillOrderStatus)
                         .in(CollectionUtils.isNotEmpty(logOrderIds),KwtWaybillOrderSubtask::getLOrderId, logOrderIds)
                         .in(CollectionUtils.isNotEmpty(billOrderIds),KwtWaybillOrderSubtask::getWOrderId, billOrderIds)

+ 86 - 0
sckw-modules/sckw-transport/src/main/java/com/sckw/transport/service/AutoDispatchDistanceUtils.java

@@ -0,0 +1,86 @@
+package com.sckw.transport.service;
+
+import com.sckw.fleet.api.model.vo.RTruckVo;
+import lombok.extern.slf4j.Slf4j;
+
+import java.math.BigDecimal;
+
+
+/**
+ * 自动派车距离筛选工具类。
+ */
+@Slf4j
+final class AutoDispatchDistanceUtils {
+
+    private AutoDispatchDistanceUtils() {
+    }
+
+
+    /**
+     * 校验车辆最大运输距离是否满足订单任务距离要求。
+     * <p>
+     * 校验逻辑如下:
+     * 1. 若车辆信息为空,视为不满足条件。
+     * 2. 若车辆未设置最大运输距离(null),视为无限制,满足条件。
+     * 3. 若订单距离为空、空白或解析失败,视为无效数据或无限制,默认满足条件。
+     * 4. 若订单距离小于等于0,视为无效数据,默认满足条件。
+     * 5. 比较车辆最大运输距离与订单距离,若车辆最大距离 >= 订单距离,则满足条件。
+     * </p>
+     *
+     * @param truck       车辆信息对象
+     * @param orderDistance 订单任务距离字符串,单位:公里
+     * @return true-满足或不限制,false-车辆最大运输距离小于订单任务距离
+     */
+    static boolean isTransportDistanceAvailable(RTruckVo truck, String orderDistance) {
+        // 1. 基础非空校验:车辆对象不能为空
+        if (truck == null) {
+            log.warn("自动派车距离校验失败:车辆信息为空");
+            return false;
+        }
+
+        // 2. 获取车辆最大运输距离
+        BigDecimal maxTransportDistance = truck.getMaxTransportDistance();
+
+        // 3. 若车辆未配置最大运输距离,默认认为无限制,校验通过
+        if (maxTransportDistance == null) {
+            log.debug("自动派车距离校验:车辆[{}]未配置最大运输距离,默认通过", truck.getTruckNo());
+            return true;
+        }
+
+        // 4. 校验订单距离参数有效性
+        if (orderDistance == null || orderDistance.trim().isEmpty()) {
+            log.debug("自动派车距离校验:订单距离为空,默认通过");
+            return true;
+        }
+
+        try {
+            // 5. 解析订单距离并校验数值合法性
+            String trimmedDistance = orderDistance.trim();
+            BigDecimal taskDistance = new BigDecimal(trimmedDistance);
+
+            // 6. 若订单距离小于等于0,视为异常数据或无实际距离要求,默认通过
+            if (taskDistance.compareTo(BigDecimal.ZERO) <= 0) {
+                log.debug("自动派车距离校验:订单距离[{}]小于等于0,默认通过", trimmedDistance);
+                return true;
+            }
+
+            // 7. 核心比对:车辆最大运输距离必须大于等于订单任务距离
+            boolean isAvailable = maxTransportDistance.compareTo(taskDistance) >= 0;
+            
+            if (!isAvailable) {
+                log.info("自动派车距离校验不通过:车辆[{}]最大运输距离[{}]km < 订单距离[{}]km", 
+                        truck.getTruckNo(), maxTransportDistance, taskDistance);
+            } else {
+                log.debug("自动派车距离校验通过:车辆[{}]最大运输距离[{}]km >= 订单距离[{}]km", 
+                        truck.getTruckNo(), maxTransportDistance, taskDistance);
+            }
+            
+            return isAvailable;
+
+        } catch (Exception e) {
+            // 8. 异常处理:若距离字符串格式错误,记录日志并默认通过(避免因数据脏读导致派车失败,可根据业务需求调整为返回false)
+            log.warn("自动派车距离校验:订单距离格式非法[{}],异常信息:{},默认通过", orderDistance, e.getMessage());
+            return true;
+        }
+    }
+}

+ 5 - 0
sckw-modules/sckw-transport/src/main/java/com/sckw/transport/service/KwtAcceptCarriageOrderService.java

@@ -3382,11 +3382,15 @@ public class KwtAcceptCarriageOrderService {
         }
         List<String> axleNumStrId = Arrays.asList(goodsById.getCarAxis().split(","));
         List<RTruckVo> truckVoList = fleetService.findTruckByEntIds(logEntId);
+        String orderDistance = getDistance(orderDTO);
+        log.info("物流订单距离:{}", orderDistance);
         //过滤满足轴数的车辆
         List<RTruckVo> truckVoFilterList = truckVoList.stream()
                 .filter(Objects::nonNull)
                 .filter(truck -> truck.getCarAxisId() != null)
                 .filter(truck ->    axleNumStrId.contains(String.valueOf(truck.getCarAxisId())))
+                // 自动派车按订单任务距离筛选车辆;车辆未维护最大运输距离时视为不限制。
+                .filter(truck -> AutoDispatchDistanceUtils.isTransportDistanceAvailable(truck, orderDistance))
                 .collect(Collectors.toList());
 
         //过滤满足车辆最大可派任务数的车辆
@@ -3706,6 +3710,7 @@ public class KwtAcceptCarriageOrderService {
         kwtLogisticsOrder.setDispatchWay(orderDTO.getDispatchWay());
         kwtLogisticsOrder.setDistance(distance);
         kwtLogisticsOrder.setMeasurementWay(orderDTO.getMeasurementWay());
+        kwtLogisticsOrder.setAgentFlag(orderDTO.getAgentFlag());
         saveLogisticsOrderList.add(kwtLogisticsOrder);
     }
 

+ 336 - 5
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) {
@@ -2342,7 +2352,7 @@ public class KwtLogisticsConsignmentService {
      */
     private boolean shouldReturnEmptyResult(Set<Long> logOrderIds, Set<Long> entList, QueryLogisticsOrderReq req) {
         return CollectionUtils.isEmpty(logOrderIds) &&
-                (!org.apache.commons.lang3.StringUtils.isAllBlank(req.getContractId(), req.getGoodsName(), req.getConsignCompanyId(), req.getCarriageCompanyId())
+                (!org.apache.commons.lang3.StringUtils.isAllBlank(req.getContractId(), req.getGoodsName(), req.getConsignCompanyId(), req.getCarriageCompanyId(), req.getKeywords())
                         || org.apache.commons.collections4.CollectionUtils.isNotEmpty(entList));
     }
 
@@ -2359,7 +2369,7 @@ public class KwtLogisticsConsignmentService {
         }
         return logisticsOrderRepository.queryByPage(
                 logOrderIds, optimizedReq.getTradeOrderId(), optimizedReq.getOrderNo(),optimizedReq.getTradeOrderNo(),
-                optimizedReq.getOrderStatus(), startDate,
+                optimizedReq.getOrderStatus(), optimizedReq.getAgentFlag(), startDate,
                 endDate, optimizedReq.getPageNum(), optimizedReq.getPageSize());
     }
 
@@ -2977,20 +2987,204 @@ 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) {
+        applyAgentKeywordCondition(req);
         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())) {
@@ -3044,7 +3238,141 @@ public class KwtLogisticsConsignmentService {
             }
         }
 
-        return logOrderIds;
+        // 判断是否存在限制物流订单ID范围的条件(如企业列表、合同ID、商品名称)
+        // 如果存在这些条件,后续的关键字搜索必须与这些条件的结果取交集(AND关系)
+        boolean hasLogOrderIdRestrictedCondition = org.apache.commons.collections4.CollectionUtils.isNotEmpty(entList)
+                || StringUtils.isNotBlank(req.getContractId())
+                || StringUtils.isNotBlank(req.getGoodsName());
+        
+        log.debug("关键字过滤前置检查: hasLogOrderIdRestrictedCondition={}, 当前logOrderIds大小={}", 
+                hasLogOrderIdRestrictedCondition, logOrderIds.size());
+                
+        return applyKeywordFilterToLogOrderIds(req, logOrderIds, hasLogOrderIdRestrictedCondition);
+    }
+
+    /**
+     * 处理物流订单关键字段中的代理关键字。
+     * 业务规则:当keywords包含“代理”时,将其转换为代理属性过滤(agentFlag=1),并清空keywords。
+     * 原因:物流订单编号、商品名称等字段不存储“代理”语义,若继续参与模糊匹配会导致代理订单被错误过滤或漏选。
+     *
+     * @param req 物流订单分页查询请求
+     */
+    static void applyAgentKeywordCondition(QueryLogisticsOrderReq req) {
+        if (Objects.isNull(req)
+                || org.apache.commons.lang3.StringUtils.isBlank(req.getKeywords())
+                || !req.getKeywords().contains("代理")) {
+            return;
+        }
+        log.info("检测到关键字包含'代理',转换为代理标识过滤。原关键字: {}", req.getKeywords());
+        req.setAgentFlag(Global.YES);
+        req.setKeywords(null);
+    }
+
+    /**
+     * 根据关键字段过滤物流订单ID。
+     * 逻辑说明:
+     * 1. 关键字段支持物流订单编号和商品名称,这两类条件之间为 OR 关系。
+     * 2. 关键字搜索结果与原有筛选条件(企业、合同、商品等)之间为 AND 关系。
+     *
+     * @param req                         物流订单分页查询请求
+     * @param logOrderIds                 已经由原有条件筛选出的物流订单ID集合
+     * @param hasLogOrderIdRestrictedCondition 是否已经存在物流订单ID范围限制(即是否有其他AND条件)
+     * @return 过滤后的物流订单ID集合
+     */
+    private Set<Long> applyKeywordFilterToLogOrderIds(QueryLogisticsOrderReq req,
+                                                       Set<Long> logOrderIds,
+                                                       boolean hasLogOrderIdRestrictedCondition) {
+        // 如果没有关键字,直接返回原有的ID集合
+        if (Objects.isNull(req) || org.apache.commons.lang3.StringUtils.isBlank(req.getKeywords())) {
+            log.debug("无关键字过滤条件,直接返回原有物流订单ID集合");
+            return logOrderIds;
+        }
+
+        log.info("开始执行关键字过滤,关键字: {}, 原有ID数量: {}", req.getKeywords(), logOrderIds.size());
+        
+        // 查询命中关键字的物流订单ID集合
+        Set<Long> keywordLogOrderIds = queryLogisticsOrderIdsByKeywords(req.getKeywords());
+        log.debug("关键字搜索命中ID数量: {}", keywordLogOrderIds.size());
+        
+        // 合并结果
+        return mergeKeywordLogOrderIds(logOrderIds, keywordLogOrderIds, hasLogOrderIdRestrictedCondition);
+    }
+
+    /**
+     * 查询关键字段命中的物流订单ID。
+     * 搜索范围:
+     * 1. 物流订单编号 (LOrderNo)
+     * 2. 商品名称 (GoodsName)
+     * 两者结果为并集 (OR)。
+     *
+     * @param keywords 搜索关键字
+     * @return 命中的物流订单ID集合
+     */
+    private Set<Long> queryLogisticsOrderIdsByKeywords(String keywords) {
+        Set<Long> keywordLogOrderIds = Sets.newLinkedHashSet();
+        
+        // 1. 根据物流订单编号模糊查询
+        List<KwtLogisticsOrder> logisticsOrders = logisticsOrderRepository.queryListByLogisticsOrderNo(keywords);
+        if (org.apache.commons.collections4.CollectionUtils.isNotEmpty(logisticsOrders)) {
+            Set<Long> orderIds = logisticsOrders.stream()
+                    .map(KwtLogisticsOrder::getId)
+                    .filter(Objects::nonNull)
+                    .collect(Collectors.toSet());
+            keywordLogOrderIds.addAll(orderIds);
+            log.debug("通过订单号匹配到 {} 个订单", orderIds.size());
+        }
+        
+        // 2. 根据商品名称模糊查询
+        List<KwtLogisticsOrderGoods> logisticsOrderGoodsList = logisticsOrderGoodsRepository.queryByGoodsName(keywords);
+        if (org.apache.commons.collections4.CollectionUtils.isNotEmpty(logisticsOrderGoodsList)) {
+            Set<Long> goodsOrderIds = logisticsOrderGoodsList.stream()
+                    .map(KwtLogisticsOrderGoods::getLOrderId)
+                    .filter(Objects::nonNull)
+                    .collect(Collectors.toSet());
+            keywordLogOrderIds.addAll(goodsOrderIds);
+            log.debug("通过商品名称匹配到 {} 个订单", goodsOrderIds.size());
+        }
+        
+        return keywordLogOrderIds;
+    }
+
+    /**
+     * 合并关键字段命中的物流订单ID。
+     * 合并策略:
+     * 1. 如果关键字未命中任何数据,返回空集合(因为关键字是必填过滤项时,无命中即无结果)。
+     * 2. 如果原有条件未限制ID范围(即没有其他AND条件),则直接返回关键字命中的ID集合。
+     * 3. 如果原有条件已限制ID范围,则取交集(原有ID AND 关键字命中ID)。
+     *
+     * @param logOrderIds                 原有条件命中的物流订单ID集合
+     * @param keywordLogOrderIds          关键字段命中的物流订单ID集合
+     * @param hasLogOrderIdRestrictedCondition 是否存在原有物流订单ID范围限制
+     * @return 合并后的物流订单ID集合
+     */
+    static Set<Long> mergeKeywordLogOrderIds(Set<Long> logOrderIds,
+                                             Set<Long> keywordLogOrderIds,
+                                             boolean hasLogOrderIdRestrictedCondition) {
+        // 情况1:关键字未命中任何数据,返回空集合
+        if (org.apache.commons.collections4.CollectionUtils.isEmpty(keywordLogOrderIds)) {
+            log.debug("关键字未命中任何数据,返回空集合");
+            return Sets.newHashSet();
+        }
+        
+        // 情况2:原有条件未限制ID范围,且原有ID集合为空,直接返回关键字命中的ID
+        // 注意:这里假设如果hasLogOrderIdRestrictedCondition为false,说明用户只输入了关键字,没有其他筛选条件
+        if (org.apache.commons.collections4.CollectionUtils.isEmpty(logOrderIds) && !hasLogOrderIdRestrictedCondition) {
+            log.debug("无其他限制条件,直接返回关键字命中ID集合,数量: {}", keywordLogOrderIds.size());
+            return Sets.newHashSet(keywordLogOrderIds);
+        }
+        
+        // 情况3:取交集
+        // 只有同时满足原有条件和关键字条件的订单才保留
+        Set<Long> result = Sets.newHashSet(logOrderIds);
+        int beforeSize = result.size();
+        result.retainAll(keywordLogOrderIds);
+        log.debug("执行交集操作,原有ID数: {}, 关键字命中数: {}, 交集后剩余: {}", 
+                beforeSize, keywordLogOrderIds.size(), result.size());
+        
+        return result;
     }
 
     private Long getAllEnt(String entId) {
@@ -3072,6 +3400,8 @@ public class KwtLogisticsConsignmentService {
         LogisticsOrderResp logisticsOrderResp = new LogisticsOrderResp();
         logisticsOrderResp.setLogisticsOrderId(String.valueOf(kwtLogisticsOrder.getId()));
         logisticsOrderResp.setLogisticsOrderNo(kwtLogisticsOrder.getLOrderNo());
+        logisticsOrderResp.setAgentFlag(kwtLogisticsOrder.getAgentFlag());
+        logisticsOrderResp.setAgentFlagDesc(Objects.equals(kwtLogisticsOrder.getAgentFlag(), NumberConstant.ONE) ? "是" : "否");
         KwtLogisticsOrderUnit consignCompany = finalLogOrderIdAndUnitTypeKeyAndUnitMap.getOrDefault(kwtLogisticsOrder.getId() + "-" + NumberConstant.ONE,
                 new KwtLogisticsOrderUnit());
         logisticsOrderResp.setConsignCompanyId(String.valueOf(consignCompany.getId()));
@@ -4059,6 +4389,7 @@ public class KwtLogisticsConsignmentService {
             return orderStatusStatisticsResp;
         }
         List<KwtLogisticsOrder> logisticsOrders = logisticsOrderRepository.queryList(logOrderIds, req.getTradeOrderId(), req.getOrderNo(), req.getOrderStatus(),
+                req.getAgentFlag(),
                 req.getStartTime(), req.getEndTime());
 
         if (Objects.isNull(logisticsOrders)) {

+ 434 - 23
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 统计
@@ -2988,7 +2997,7 @@ public class KwtWaybillOrderV1Service {
 
     public PageDataResult<WaybillOrderResp> findBillOrderListPage(WaybillOrderReq req) {
         log.info("查询订单列表请求参数:{}", JSON.toJSONString(req));
-
+        applyAgentKeywordCondition(req);
         // 处理状态参数
         Integer status = parseStatus(req.getStatus());
 
@@ -3050,7 +3059,7 @@ public class KwtWaybillOrderV1Service {
                 logOrderIds.size(), billOrderIds.size(), status);
         List<Integer> waybillOrderStatus = getWaybillOrderStatus();
         IPage<KwtWaybillOrderSubtask> page = waybillOrderSubtaskRepository.queryByPage(
-                logOrderIds, billOrderIds, status,waybillOrderStatus, req.getPageNum(), req.getPageSize());
+                logOrderIds, billOrderIds, status, waybillOrderStatus, req.getAgentFlag(), req.getPageNum(), req.getPageSize());
 
         List<KwtWaybillOrderSubtask> records = page.getRecords();
         if (CollectionUtils.isEmpty(records)) {
@@ -3221,7 +3230,8 @@ public class KwtWaybillOrderV1Service {
     private boolean shouldReturnEmptyResultForWaybill(WaybillOrderReq req, Set<Long> billOrderIds) {
         boolean hasWaybillFilter = Stream.of(
                         req.getWaybillNo(), req.getTruckNo(), req.getDriverName(), req.getPhone(),
-                        req.getLoadStartTime(), req.getLoadEndTime(), req.getUnloadStartTime(), req.getUnloadEndTime())
+                        req.getLoadStartTime(), req.getLoadEndTime(), req.getUnloadStartTime(), req.getUnloadEndTime(),
+                        req.getKeywords())
                 .anyMatch(StringUtils::isNotBlank);
 
         return CollectionUtils.isEmpty(billOrderIds) && hasWaybillFilter;
@@ -3605,12 +3615,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 +3900,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集合
+     * 根据当前企业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集合,保持插入顺序
      */
-    private Set<Long> getLogOrderIdsByEntIds(Set<Long> entIds,String consignorId,String carrierId,Long entId) {
+    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;
+    }
+
+    /**
+     * 判断指定企业是否具备指定企业类型。
+     * <p>
+     * 该方法用于校验给定的企业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) || 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)){
@@ -3973,18 +4186,162 @@ public class KwtWaybillOrderV1Service {
         }
     }
 
+
+    /**
+     * 处理运单列表关键字段中的代理关键字。
+     * <p>
+     * 业务逻辑:
+     * 1. 检查请求参数及关键字是否包含“代理”语义。
+     * 2. 若包含,则将请求对象的代理标识设置为“是”(Global.YES)。
+     * 3. 清空关键字字段,避免后续模糊查询将“代理”作为普通文本匹配,导致数据过滤错误。
+     * </p>
+     *
+     * @param req 运单分页查询请求对象
+     */
+    static void applyAgentKeywordCondition(WaybillOrderReq req) {
+        // 参数校验:请求为空、关键字为空或不包含“代理”字样,直接返回
+        if (Objects.isNull(req)
+                || StringUtils.isBlank(req.getKeywords())
+                || !req.getKeywords().contains("代理")) {
+            return;
+        }
+        
+        log.debug("检测到关键字包含'代理',设置代理标识并清空关键字");
+        // 设置代理标识为真
+        req.setAgentFlag(Global.YES);
+        // 清空关键字,防止后续作为普通文本进行模糊匹配
+        req.setKeywords(null);
+    }
+
+    /**
+     * 根据关键字段过滤运单ID。
+     * <p>
+     * 业务逻辑:
+     * 1. 支持通过物流运单编号和商品名称进行检索。
+     * 2. 运单编号与商品名称之间的检索条件为 OR 关系(并集)。
+     * 3. 关键字检索结果与原有其他筛选条件(如时间、车辆等)的结果为 AND 关系(交集)。
+     * </p>
+     *
+     * @param req                             运单分页查询请求
+     * @param billOrderIds                    已由原有非关键字条件筛选出的运单ID集合
+     * @param hasBillOrderRestrictedCondition 标记是否已经应用了其他限制条件(用于判断初始集合是否为空时的处理逻辑)
+     * @return 最终过滤后的运单ID集合
+     */
+    private Set<Long> applyKeywordFilterToBillOrderIds(WaybillOrderReq req,
+                                                        Set<Long> billOrderIds,
+                                                        boolean hasBillOrderRestrictedCondition) {
+        // 若无请求或无关键字,直接返回原有ID集合
+        if (Objects.isNull(req) || StringUtils.isBlank(req.getKeywords())) {
+            return billOrderIds;
+        }
+        
+        log.debug("开始执行关键字过滤,关键字: {}", req.getKeywords());
+        
+        // 1. 查询命中关键字的运单ID集合
+        Set<Long> keywordBillOrderIds = queryBillOrderIdsByKeywords(req.getKeywords());
+        log.debug("关键字查询到的运单ID数量: {}", CollectionUtils.isEmpty(keywordBillOrderIds) ? 0 : keywordBillOrderIds.size());
+        
+        // 2. 合并结果(取交集或并集,取决于是否有其他限制条件)
+        return mergeKeywordBillOrderIds(billOrderIds, keywordBillOrderIds, hasBillOrderRestrictedCondition);
+    }
+
+    /**
+     * 查询关键字段命中的运单ID。
+     * <p>
+     * 检索策略:
+     * 1. 通过运单号模糊查询主运单ID。
+     * 2. 通过商品名称查询关联的物流订单,进而找到关联的子运单及主运单ID。
+     * 3. 两者结果取并集。
+     * </p>
+     *
+     * @param keywords 搜索关键字(运单号或商品名)
+     * @return 命中的运单ID集合
+     */
+    private Set<Long> queryBillOrderIdsByKeywords(String keywords) {
+        Set<Long> keywordBillOrderIds = Sets.newLinkedHashSet();
+        
+        // 1. 根据运单号模糊查询
+        List<KwtWaybillOrder> waybillOrders = kwtWaybillOrderRepository.queryListByWOrderNo(keywords);
+        if (CollectionUtils.isNotEmpty(waybillOrders)) {
+            Set<Long> orderIdsByNo = waybillOrders.stream()
+                    .map(KwtWaybillOrder::getId)
+                    .filter(Objects::nonNull)
+                    .collect(Collectors.toSet());
+            keywordBillOrderIds.addAll(orderIdsByNo);
+            log.debug("通过运单号查询到ID数量: {}", orderIdsByNo.size());
+        }
+        
+        // 2. 根据商品名称查询
+        // 先获取包含该商品的物流订单ID
+        Set<Long> logOrderIdsByGoods = getLogOrderIdsByGoodsName(keywords);
+        if (CollectionUtils.isNotEmpty(logOrderIdsByGoods)) {
+            // 再根据物流订单ID查询对应的子运单,从而获取主运单ID
+            List<KwtWaybillOrderSubtask> subtasks = waybillOrderSubtaskRepository.queryByLogIds(new ArrayList<>(logOrderIdsByGoods));
+            if (CollectionUtils.isNotEmpty(subtasks)) {
+                Set<Long> orderIdsByGoods = subtasks.stream()
+                        .map(KwtWaybillOrderSubtask::getWOrderId)
+                        .filter(Objects::nonNull)
+                        .collect(Collectors.toSet());
+                keywordBillOrderIds.addAll(orderIdsByGoods);
+                log.debug("通过商品名称查询到ID数量: {}", orderIdsByGoods.size());
+            }
+        }
+        
+        return keywordBillOrderIds;
+    }
+
+    /**
+     * 合并关键字段命中的运单ID。
+     * <p>
+     * 合并逻辑:
+     * 1. 若关键字查询结果为空,返回空集合(表示无匹配数据)。
+     * 2. 若原有条件未限制范围(hasBillOrderRestrictedCondition=false)且原有ID集合为空,则直接返回关键字查询结果。
+     * 3. 否则,取原有ID集合与关键字ID集合的交集,确保同时满足所有筛选条件。
+     * </p>
+     *
+     * @param billOrderIds                    原有条件命中的运单ID集合
+     * @param keywordBillOrderIds             关键字段命中的运单ID集合
+     * @param hasBillOrderRestrictedCondition 是否存在原有运单ID范围限制(如已按时间、车辆等筛选过)
+     * @return 合并后的运单ID集合
+     */
+    static Set<Long> mergeKeywordBillOrderIds(Set<Long> billOrderIds,
+                                              Set<Long> keywordBillOrderIds,
+                                              boolean hasBillOrderRestrictedCondition) {
+        // 如果关键字没查到任何数据,整体结果为空
+        if (CollectionUtils.isEmpty(keywordBillOrderIds)) {
+            return Sets.newHashSet();
+        }
+        
+        // 如果之前没有施加其他限制条件,且之前的ID集合也为空,说明这是第一个有效过滤条件,直接返回关键字结果
+        if (CollectionUtils.isEmpty(billOrderIds) && !hasBillOrderRestrictedCondition) {
+            return Sets.newHashSet(keywordBillOrderIds);
+        }
+        
+        // 取交集:保留既在原有结果中,又命中关键字的ID
+        Set<Long> result = Sets.newHashSet(billOrderIds);
+        result.retainAll(keywordBillOrderIds);
+        log.debug("关键字过滤合并完成,最终ID数量: {}", result.size());
+        return result;
+    }
+
     @NotNull
     private Set<Long> getBillOrderIds(WaybillOrderReq req) {
         Set<Long> billOrderIds = Sets.newHashSet();
         KwtWaybillOrder order = getKwtWaybillOrder(req);
+        Set<Long> loadConditionBillOrderIds = Sets.newHashSet();
+        Set<Long> unloadConditionBillOrderIds = Sets.newHashSet();
+        boolean hasMergedBillOrderCondition = false;
         boolean b = Stream.of(req.getWaybillNo(), req.getTruckNo(), req.getDriverName(), req.getPhone()).anyMatch(StringUtils::isNotBlank);
         if (b){
+            Set<Long> conditionBillOrderIds = Sets.newHashSet();
             List<KwtWaybillOrder> waybillOrders = kwtWaybillOrderRepository.queryByCondition(order);
             if (org.apache.commons.collections4.CollectionUtils.isNotEmpty(waybillOrders)){
-                billOrderIds.addAll(waybillOrders.stream()
+                conditionBillOrderIds.addAll(waybillOrders.stream()
                         .map(KwtWaybillOrder::getId)
                         .collect(Collectors.toSet()));
             }
+            billOrderIds = mergeConditionBillOrderIds(billOrderIds, conditionBillOrderIds, hasMergedBillOrderCondition);
+            hasMergedBillOrderCondition = true;
         }
         if (org.apache.commons.lang3.StringUtils.isNoneBlank(req.getLoadStartTime(),req.getLoadEndTime())){
             Date loadStartTime = DateUtils.formatDate(req.getLoadStartTime());
@@ -3992,7 +4349,7 @@ public class KwtWaybillOrderV1Service {
             List<KwtWaybillOrderTicket> tickets = kwtWaybillOrderTicketRepository.queryByOperateTime(loadStartTime,
                     loadEndTime,1);
             if (org.apache.commons.collections4.CollectionUtils.isNotEmpty( tickets)){
-                billOrderIds.addAll(tickets.stream()
+                loadConditionBillOrderIds.addAll(tickets.stream()
                         .map(KwtWaybillOrderTicket::getWOrderId)
                         .collect(Collectors.toSet()));
             }
@@ -4004,7 +4361,7 @@ public class KwtWaybillOrderV1Service {
             List<KwtWaybillOrderTicket> tickets = kwtWaybillOrderTicketRepository.queryByOperateTime(unloadStartTime,
                     unloadEndTime,2);
             if (org.apache.commons.collections4.CollectionUtils.isNotEmpty( tickets)){
-                billOrderIds.addAll(tickets.stream()
+                unloadConditionBillOrderIds.addAll(tickets.stream()
                         .map(KwtWaybillOrderTicket::getWOrderId)
                         .collect(Collectors.toSet()));
             }
@@ -4015,13 +4372,12 @@ public class KwtWaybillOrderV1Service {
             Date startDate = getStartDate(req.getUnloadStartTime());
             Date endDate = getEndDate(req.getUnloadEndTime());
             List<KwtWaybillOrderNode> nodes = kwtWaybillOrderNodeRepository.queryByOperateTime(startDate, endDate,CarWaybillV1Enum.COMPLETION_LOADING.getCode());
-            Set<Long> billOrderIds2 = Optional.ofNullable(nodes)
+            unloadConditionBillOrderIds.addAll(Optional.ofNullable(nodes)
                     .orElse(List.of()).stream()
                     .map(KwtWaybillOrderNode::getWOrderId)
-                    .collect(Collectors.toSet());
-            if (org.apache.commons.collections4.CollectionUtils.isNotEmpty( billOrderIds2)){
-                billOrderIds.addAll(billOrderIds2);
-            }
+                    .collect(Collectors.toSet()));
+            billOrderIds = mergeConditionBillOrderIds(billOrderIds, unloadConditionBillOrderIds, hasMergedBillOrderCondition);
+            hasMergedBillOrderCondition = true;
         }
 
         if (org.apache.commons.lang3.StringUtils.isNotBlank(req.getLoadStartTime()) &&
@@ -4030,15 +4386,67 @@ public class KwtWaybillOrderV1Service {
             Date startDate = getStartDate(req.getLoadStartTime());
             Date endDate = getEndDate(req.getLoadEndTime());
             List<KwtWaybillOrderNode> nodes = kwtWaybillOrderNodeRepository.queryByOperateTime(startDate, endDate,CarWaybillV1Enum.EXIT_COMPLETED.getCode());
-            Set<Long> billOrderIds2 = Optional.ofNullable(nodes)
+            loadConditionBillOrderIds.addAll(Optional.ofNullable(nodes)
                     .orElse(List.of()).stream()
                     .map(KwtWaybillOrderNode::getWOrderId)
-                    .collect(Collectors.toSet());
-            if (org.apache.commons.collections4.CollectionUtils.isNotEmpty( billOrderIds2)){
-                billOrderIds.addAll(billOrderIds2);
-            }
+                    .collect(Collectors.toSet()));
+            billOrderIds = mergeConditionBillOrderIds(billOrderIds, loadConditionBillOrderIds, hasMergedBillOrderCondition);
+            hasMergedBillOrderCondition = true;
         }
-        return billOrderIds;
+        boolean hasBillOrderRestrictedCondition = Stream.of(
+                req.getWaybillNo(), req.getTruckNo(), req.getDriverName(), req.getPhone(),
+                req.getLoadStartTime(), req.getLoadEndTime(), req.getUnloadStartTime(), req.getUnloadEndTime()
+        ).anyMatch(StringUtils::isNotBlank);
+        return applyKeywordFilterToBillOrderIds(req, billOrderIds, hasBillOrderRestrictedCondition);
+    }
+
+
+    /**
+     * 合并单个已生效查询条件的运单ID集合。
+     * <p>
+     * 业务逻辑说明:
+     * 1. 多个筛选条件(如运单号、车牌、司机、时间等)之间为 AND 关系,即取交集。
+     * 2. 如果当前条件未匹配到任何数据(conditionOrderIds为空),则整体结果必为空,直接返回空集合,避免无效查询。
+     * 3. 如果是第一个生效的条件(!hasMergedCondition),则直接返回当前条件的结果集作为初始集合。
+     * 4. 如果之前已有生效条件(hasMergedCondition=true),则将当前条件结果与已有结果取交集。
+     * 5. 如果已有结果集为空,说明之前的条件已过滤掉所有数据,直接返回空集合。
+     * </p>
+     *
+     * @param billOrderIds       已合并条件命中的运单ID集合(前序条件的交集结果)
+     * @param conditionOrderIds  当前单一条件命中的运单ID集合
+     * @param hasMergedCondition 标记是否已经合并过其他有效条件
+     * @return 多个条件之间取交集后的运单ID集合
+     */
+    static Set<Long> mergeConditionBillOrderIds(Set<Long> billOrderIds,
+                                                Set<Long> conditionOrderIds,
+                                                boolean hasMergedCondition) {
+        // 1. 校验当前条件结果:若当前条件无命中数据,则整体查询结果应为空,防止后续逻辑误判或全量扫描。
+        if (CollectionUtils.isEmpty(conditionOrderIds)) {
+            log.debug("当前查询条件未匹配到任何运单ID,返回空集合");
+            return Sets.newHashSet();
+        }
+
+        // 2. 处理首个有效条件:若此前未合并过任何条件,则当前条件结果即为初始结果集。
+        if (!hasMergedCondition) {
+            log.debug("首次合并有效条件,直接采用当前条件结果,ID数量: {}", conditionOrderIds.size());
+            return Sets.newHashSet(conditionOrderIds);
+        }
+
+        // 3. 校验前序结果:若前序条件已导致结果为空,则无需再进行交集运算,直接返回空。
+        if (CollectionUtils.isEmpty(billOrderIds)) {
+            log.debug("前序条件已过滤为空,无需继续合并,返回空集合");
+            return Sets.newHashSet();
+        }
+
+        // 4. 执行交集运算:保留既在前序结果中,又满足当前条件的运单ID。
+        Set<Long> result = Sets.newHashSet(billOrderIds);
+        result.retainAll(conditionOrderIds);
+        
+        // 5. 记录日志:监控交集运算后的数据量变化,便于排查数据过滤逻辑。
+        log.debug("运单多条件交集合并完成,当前条件ID数量: {}, 前序ID数量: {}, 合并后剩余ID数量: {}", 
+                conditionOrderIds.size(), billOrderIds.size(), result.size());
+        
+        return result;
     }
 
     private static Date getStartDate(String date) {
@@ -4098,6 +4506,9 @@ public class KwtWaybillOrderV1Service {
         waybillOrderResp.setLogisticsOrderId(String.valueOf(record.getLOrderId()));
         KwtLogisticsOrder order = finalLogIdAndOrderMap.getOrDefault(record.getLOrderId(), new KwtLogisticsOrder());
         waybillOrderResp.setLogisticsOrderNo(order.getLOrderNo());
+        Integer agentFlag = Objects.nonNull(record.getAgentFlag()) ? record.getAgentFlag() : billOrder.getAgentFlag();
+        waybillOrderResp.setAgentFlag(agentFlag);
+        waybillOrderResp.setAgentFlagDesc(Objects.equals(agentFlag, Global.YES) ? "是" : "否");
         KwtLogisticsOrderUnit consignUnit = finalLogOrderIdUnitTypeKeyAndUnitMap.getOrDefault(record.getLOrderId() + "-" + NumberConstant.ONE,
                 new KwtLogisticsOrderUnit());
         waybillOrderResp.setConsignUnitId(String.valueOf(consignUnit.getEntId()));

+ 2 - 0
sckw-modules/sckw-transport/src/main/resources/mapper/KwtLogisticsOrderMapper.xml

@@ -42,6 +42,7 @@
         <result column="update_by" jdbcType="BIGINT" property="updateBy"/>
         <result column="update_time" jdbcType="TIMESTAMP" property="updateTime"/>
         <result column="del_flag" jdbcType="INTEGER" property="delFlag"/>
+        <result column="agent_flag" jdbcType="INTEGER" property="agentFlag"/>
     </resultMap>
     <sql id="Base_Column_List">
         id
@@ -84,6 +85,7 @@
         create_time,
         update_by,
         update_time,
+        agent_flag,
         del_flag
     </sql>
     <select id="selectOrderList" resultType="com.sckw.transport.model.dto.LogisticsOrderDto">

+ 2 - 1
sckw-modules/sckw-transport/src/main/resources/mapper/KwtWaybillOrderMapper.xml

@@ -25,12 +25,13 @@
         <result column="update_by" jdbcType="BIGINT" property="updateBy"/>
         <result column="update_time" jdbcType="TIMESTAMP" property="updateTime"/>
         <result column="del_flag" jdbcType="INTEGER" property="delFlag"/>
+        <result column="agent_flag" jdbcType="INTEGER" property="agentFlag"/>
     </resultMap>
     <sql id="Base_Column_List">
         id
         , ent_id, l_order_id, w_order_no, truck_no, driver_id, entrust_amount, unload_amount,
     load_amount, deficit_amount, remark, `status`, create_by, create_time, update_by,
-    update_time, del_flag,start_time,end_time
+    update_time, del_flag,start_time,end_time,agent_flag
     </sql>
 
     <select id="selectWaybillOrderCarList" resultType="com.sckw.transport.model.dto.OrderCarDTO">

+ 4 - 3
sckw-modules/sckw-transport/src/main/resources/mapper/KwtWaybillOrderSubtaskMapper.xml

@@ -7,7 +7,7 @@
         id, ent_id entId, l_order_id lOrderId, w_order_id wOrderId, w_order_no wOrderNo, unit,
         entrust_amount entrustAmount, unload_amount unloadAmount, unload_time unloadTime,
         load_amount loadAmount, load_time loadTime, deficit_amount deficitAmount, deficit_price deficitPrice,
-        remark, status, create_by createBy, create_time createTime, update_by updateBy, update_time updateTime
+        agent_flag agentFlag, remark, status, create_by createBy, create_time createTime, update_by updateBy, update_time updateTime
         from kwt_waybill_order_subtask
         where del_flag = 0
         <if test="wOrderId != null">
@@ -20,7 +20,7 @@
         id, ent_id entId, l_order_id lOrderId, w_order_id wOrderId, w_order_no wOrderNo, unit,
         entrust_amount entrustAmount, unload_amount unloadAmount, unload_time unloadTime,
         load_amount loadAmount, load_time loadTime, deficit_amount deficitAmount, deficit_price deficitPrice,
-        remark, status, create_by createBy, create_time createTime, update_by updateBy, update_time updateTime
+        agent_flag agentFlag, remark, status, create_by createBy, create_time createTime, update_by updateBy, update_time updateTime
         from kwt_waybill_order_subtask
         where del_flag = 0
         <if test="wOrderId != null and wOrderId != ''">
@@ -43,6 +43,7 @@
                 wos.load_amount loadAmount,
                 wos.deficit_amount deficitAmount,
                 wos.deficit_price deficitPrice,
+                wos.agent_flag agentFlag,
                 wos.id wSubtaskId,
                 wo.w_order_no wOrderNo,
                 wo.driver_id driverId,
@@ -220,4 +221,4 @@
             )
         </if>
     </select>
-</mapper>
+</mapper>

+ 64 - 0
sckw-modules/sckw-transport/src/test/java/com/sckw/transport/service/KwtAcceptCarriageOrderServiceTest.java

@@ -0,0 +1,64 @@
+package com.sckw.transport.service;
+
+import com.sckw.fleet.api.model.vo.RTruckVo;
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.math.BigDecimal;
+
+/**
+ * 自动派车车辆最大运输距离筛选单元测试。
+ */
+public class KwtAcceptCarriageOrderServiceTest {
+
+    /**
+     * 车辆未维护最大运输距离时,应视为不限制运输距离。
+     */
+    @Test
+    public void isTransportDistanceAvailableWhenMaxDistanceIsNull() {
+        RTruckVo truck = new RTruckVo();
+
+        boolean result = AutoDispatchDistanceUtils.isTransportDistanceAvailable(truck, "300.50");
+
+        Assert.assertTrue(result);
+    }
+
+    /**
+     * 车辆最大运输距离大于订单任务距离时,应允许自动派车。
+     */
+    @Test
+    public void isTransportDistanceAvailableWhenMaxDistanceGreaterThanOrderDistance() {
+        RTruckVo truck = new RTruckVo();
+        truck.setMaxTransportDistance(new BigDecimal("500.00"));
+
+        boolean result = AutoDispatchDistanceUtils.isTransportDistanceAvailable(truck, "300.50");
+
+        Assert.assertTrue(result);
+    }
+
+    /**
+     * 车辆最大运输距离等于订单任务距离时,应允许自动派车。
+     */
+    @Test
+    public void isTransportDistanceAvailableWhenMaxDistanceEqualsOrderDistance() {
+        RTruckVo truck = new RTruckVo();
+        truck.setMaxTransportDistance(new BigDecimal("300.50"));
+
+        boolean result = AutoDispatchDistanceUtils.isTransportDistanceAvailable(truck, "300.50");
+
+        Assert.assertTrue(result);
+    }
+
+    /**
+     * 车辆最大运输距离小于订单任务距离时,应过滤该车辆。
+     */
+    @Test
+    public void isTransportDistanceAvailableWhenMaxDistanceLessThanOrderDistance() {
+        RTruckVo truck = new RTruckVo();
+        truck.setMaxTransportDistance(new BigDecimal("200.00"));
+
+        boolean result = AutoDispatchDistanceUtils.isTransportDistanceAvailable(truck, "300.50");
+
+        Assert.assertFalse(result);
+    }
+}

+ 74 - 0
sckw-modules/sckw-transport/src/test/java/com/sckw/transport/service/KwtLogisticsConsignmentServiceTest.java

@@ -0,0 +1,74 @@
+package com.sckw.transport.service;
+
+import com.sckw.transport.model.param.QueryLogisticsOrderReq;
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.util.Set;
+
+/**
+ * 物流订单分页查询关键字段单元测试。
+ */
+public class KwtLogisticsConsignmentServiceTest {
+
+    /**
+     * keywords包含“代理”时,应转换为代理属性过滤并清空关键字段。
+     */
+    @Test
+    public void applyAgentKeywordConditionWhenKeywordsContainsAgent() {
+        QueryLogisticsOrderReq req = new QueryLogisticsOrderReq();
+        req.setKeywords("代理");
+
+        KwtLogisticsConsignmentService.applyAgentKeywordCondition(req);
+
+        Assert.assertEquals(Integer.valueOf(1), req.getAgentFlag());
+        Assert.assertNull(req.getKeywords());
+    }
+
+    /**
+     * keywords不包含“代理”时,应保留原关键字段。
+     */
+    @Test
+    public void applyAgentKeywordConditionWhenKeywordsNotContainsAgent() {
+        QueryLogisticsOrderReq req = new QueryLogisticsOrderReq();
+        req.setKeywords("煤炭");
+
+        KwtLogisticsConsignmentService.applyAgentKeywordCondition(req);
+
+        Assert.assertNull(req.getAgentFlag());
+        Assert.assertEquals("煤炭", req.getKeywords());
+    }
+
+    /**
+     * 无原有物流订单ID范围限制时,关键字段命中的ID作为最终范围。
+     */
+    @Test
+    public void mergeKeywordLogOrderIdsWhenNoRestrictedCondition() {
+        Set<Long> result = KwtLogisticsConsignmentService.mergeKeywordLogOrderIds(
+                Set.of(), Set.of(1001L, 1002L), false);
+
+        Assert.assertEquals(Set.of(1001L, 1002L), result);
+    }
+
+    /**
+     * 已存在原有物流订单ID范围限制时,关键字段命中的ID与原范围取交集。
+     */
+    @Test
+    public void mergeKeywordLogOrderIdsWhenHasRestrictedCondition() {
+        Set<Long> result = KwtLogisticsConsignmentService.mergeKeywordLogOrderIds(
+                Set.of(1001L, 1002L), Set.of(1002L, 1003L), true);
+
+        Assert.assertEquals(Set.of(1002L), result);
+    }
+
+    /**
+     * 关键字段无命中时,返回空范围,避免后续查询全量。
+     */
+    @Test
+    public void mergeKeywordLogOrderIdsWhenKeywordNotMatched() {
+        Set<Long> result = KwtLogisticsConsignmentService.mergeKeywordLogOrderIds(
+                Set.of(1001L), Set.of(), true);
+
+        Assert.assertTrue(result.isEmpty());
+    }
+}

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

@@ -0,0 +1,159 @@
+package com.sckw.transport.service;
+
+import com.sckw.system.api.model.dto.res.EntTypeResDto;
+import com.sckw.transport.model.param.WaybillOrderReq;
+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());
+    }
+
+    /**
+     * keywords包含“代理”时,应转换为代理属性过滤并清空关键字段。
+     */
+    @Test
+    public void applyAgentKeywordConditionWhenKeywordsContainsAgent() {
+        WaybillOrderReq req = new WaybillOrderReq();
+        req.setKeywords("代理");
+
+        KwtWaybillOrderV1Service.applyAgentKeywordCondition(req);
+
+        Assert.assertEquals(Integer.valueOf(1), req.getAgentFlag());
+        Assert.assertNull(req.getKeywords());
+    }
+
+    /**
+     * keywords不包含“代理”时,应保留原关键字段。
+     */
+    @Test
+    public void applyAgentKeywordConditionWhenKeywordsNotContainsAgent() {
+        WaybillOrderReq req = new WaybillOrderReq();
+        req.setKeywords("煤炭");
+
+        KwtWaybillOrderV1Service.applyAgentKeywordCondition(req);
+
+        Assert.assertNull(req.getAgentFlag());
+        Assert.assertEquals("煤炭", req.getKeywords());
+    }
+
+    /**
+     * 无原有运单ID范围限制时,关键字段命中的ID作为最终范围。
+     */
+    @Test
+    public void mergeKeywordBillOrderIdsWhenNoRestrictedCondition() {
+        Set<Long> result = KwtWaybillOrderV1Service.mergeKeywordBillOrderIds(
+                Set.of(), Set.of(1001L, 1002L), false);
+
+        Assert.assertEquals(Set.of(1001L, 1002L), result);
+    }
+
+    /**
+     * 已存在原有运单ID范围限制时,关键字段命中的ID与原范围取交集。
+     */
+    @Test
+    public void mergeKeywordBillOrderIdsWhenHasRestrictedCondition() {
+        Set<Long> result = KwtWaybillOrderV1Service.mergeKeywordBillOrderIds(
+                Set.of(1001L, 1002L), Set.of(1002L, 1003L), true);
+
+        Assert.assertEquals(Set.of(1002L), result);
+    }
+
+    /**
+     * 关键字段无命中时,返回空范围,避免后续查询全量。
+     */
+    @Test
+    public void mergeKeywordBillOrderIdsWhenKeywordNotMatched() {
+        Set<Long> result = KwtWaybillOrderV1Service.mergeKeywordBillOrderIds(
+                Set.of(1001L), Set.of(), true);
+
+        Assert.assertTrue(result.isEmpty());
+    }
+
+    /**
+     * 多个运单查询条件同时生效时,应按条件结果取交集。
+     */
+    @Test
+    public void mergeConditionBillOrderIdsWhenMultipleConditions() {
+        Set<Long> firstConditionResult = KwtWaybillOrderV1Service.mergeConditionBillOrderIds(
+                Set.of(), Set.of(1001L, 1002L, 1003L), false);
+
+        Set<Long> result = KwtWaybillOrderV1Service.mergeConditionBillOrderIds(
+                firstConditionResult, Set.of(1002L, 1003L, 1004L), true);
+
+        Assert.assertEquals(Set.of(1002L, 1003L), result);
+    }
+
+    /**
+     * 任一已填写条件无命中时,整体运单ID范围应为空。
+     */
+    @Test
+    public void mergeConditionBillOrderIdsWhenConditionNotMatched() {
+        Set<Long> result = KwtWaybillOrderV1Service.mergeConditionBillOrderIds(
+                Set.of(1001L, 1002L), Set.of(), true);
+
+        Assert.assertTrue(result.isEmpty());
+    }
+
+    /**
+     * 前置条件已无命中时,后续条件命中也不能重新放大结果集。
+     */
+    @Test
+    public void mergeConditionBillOrderIdsWhenPreviousConditionEmpty() {
+        Set<Long> result = KwtWaybillOrderV1Service.mergeConditionBillOrderIds(
+                Set.of(), Set.of(1001L, 1002L), true);
+
+        Assert.assertTrue(result.isEmpty());
+    }
+}

+ 2 - 0
sql/2026/06/2026_06_01_add_trade_contract_agent_flag.sql

@@ -0,0 +1,2 @@
+ALTER TABLE kwc_contract_trade
+    ADD COLUMN agent_flag tinyint NOT NULL DEFAULT 0 COMMENT '是否代理属性:0-否,1-是';

+ 2 - 0
sql/2026/06/2026_06_02_add_logistics_order_agent_flag.sql

@@ -0,0 +1,2 @@
+ALTER TABLE kwt_logistics_order
+    ADD COLUMN agent_flag tinyint NOT NULL DEFAULT 0 COMMENT '是否代理属性:0-否,1-是';

+ 2 - 0
sql/2026/06/2026_06_02_add_trade_order_agent_flag.sql

@@ -0,0 +1,2 @@
+ALTER TABLE kwo_trade_order
+    ADD COLUMN agent_flag tinyint NOT NULL DEFAULT 0 COMMENT '是否代理属性:0-否,1-是';

+ 5 - 0
sql/2026/06/2026_06_02_add_waybill_order_agent_flag.sql

@@ -0,0 +1,5 @@
+ALTER TABLE kwt_waybill_order
+    ADD COLUMN agent_flag tinyint NOT NULL DEFAULT 0 COMMENT '是否代理属性:0-否,1-是';
+
+ALTER TABLE kwt_waybill_order_subtask
+    ADD COLUMN agent_flag tinyint NOT NULL DEFAULT 0 COMMENT '是否代理属性:0-否,1-是';

+ 2 - 0
sql/2026/06/2026_06_07_fleet_truck_max_transport_distance.sql

@@ -0,0 +1,2 @@
+ALTER TABLE `kwf_truck`
+    ADD COLUMN `max_transport_distance` decimal(12, 2) NULL COMMENT '最大运输距离,单位:公里' AFTER `position_device`;

+ 2 - 0
sql/2026/06/2026_06_09_add_kws_menu_not_selected_icon_path.sql

@@ -0,0 +1,2 @@
+ALTER TABLE `kws_menu`
+    ADD COLUMN `not_selected_icon_path` varchar(255) NOT NULL DEFAULT  '' COMMENT '未选中菜单图标路径' AFTER `icon`;

+ 18 - 0
sql/2026/06/2026_06_11_app_tabbar_menu_permission.sql

@@ -0,0 +1,18 @@
+INSERT INTO sckw_ng_system.kws_menu (id, client_type, parent_id, name, type, url, perms, icon, not_selected_icon_path, sort, level, custom, links, is_display, is_main, using_roles, remark, status, create_by, create_time, update_by, update_time, del_flag) VALUES (545625956564340736, 3, 0, '投诉', 1, '/pages/complaint/index', 'app:tabbar:complaint', '/static/tabbar/complaint_select.png', '/static/tabbar/complaint.png', 152, 1, 0, null, null, 0, '', null, 0, 156383116720607232, '2026-06-11 13:58:45', 156383116720607232, '2026-06-11 13:58:45', 0);
+INSERT INTO sckw_ng_system.kws_menu (id, client_type, parent_id, name, type, url, perms, icon, not_selected_icon_path, sort, level, custom, links, is_display, is_main, using_roles, remark, status, create_by, create_time, update_by, update_time, del_flag) VALUES (545624857677664256, 3, 0, '商城', 1, '/pages/mall/index', 'app:tabbar:mall', '/static/tabbar/mall_select.png', '/static/tabbar/mall.png', 142, 1, 0, null, null, 0, '', null, 0, 156383116720607232, '2026-06-11 13:54:23', 156383116720607232, '2026-06-11 13:54:23', 0);
+INSERT INTO sckw_ng_system.kws_menu (id, client_type, parent_id, name, type, url, perms, icon, not_selected_icon_path, sort, level, custom, links, is_display, is_main, using_roles, remark, status, create_by, create_time, update_by, update_time, del_flag) VALUES (545585295257505792, 3, 0, '展示待付运费', 1, '/pages/app-permission/pending-freight', 'PENDING_FREIGHT', null, '', 161, 1, 0, null, null, 0, '', null, 0, 156383116720607232, '2026-06-11 11:17:10', 156383116720607232, '2026-06-11 11:17:10', 0);
+INSERT INTO sckw_ng_system.kws_menu (id, client_type, parent_id, name, type, url, perms, icon, not_selected_icon_path, sort, level, custom, links, is_display, is_main, using_roles, remark, status, create_by, create_time, update_by, update_time, del_flag) VALUES (545585189032562688, 3, 0, '展示预付余额', 1, '/pages/app-permission/prepay-balance', 'PREPAY_BALANCE', null, '', 160, 1, 0, null, null, 0, '', null, 0, 156383116720607232, '2026-06-11 11:16:45', 156383116720607232, '2026-06-11 11:16:45', 0);
+INSERT INTO sckw_ng_system.kws_menu (id, client_type, parent_id, name, type, url, perms, icon, not_selected_icon_path, sort, level, custom, links, is_display, is_main, using_roles, remark, status, create_by, create_time, update_by, update_time, del_flag) VALUES (545585072720318464, 3, 0, '展示待履约保证金', 1, '/pages/app-permission/pending-performance-balance', 'PENDING_PERFORMANCE_BALANCE', null, '', 159, 1, 0, null, null, 0, '', null, 0, 156383116720607232, '2026-06-11 11:16:17', 156383116720607232, '2026-06-11 11:16:17', 0);
+INSERT INTO sckw_ng_system.kws_menu (id, client_type, parent_id, name, type, url, perms, icon, not_selected_icon_path, sort, level, custom, links, is_display, is_main, using_roles, remark, status, create_by, create_time, update_by, update_time, del_flag) VALUES (545584913374515200, 3, 0, '展示地址', 1, '/pages/app-permission/address', 'app:module:address', null, '', 158, 1, 0, null, null, 0, '', null, 0, 156383116720607232, '2026-06-11 11:15:39', 156383116720607232, '2026-06-11 11:15:39', 0);
+INSERT INTO sckw_ng_system.kws_menu (id, client_type, parent_id, name, type, url, perms, icon, not_selected_icon_path, sort, level, custom, links, is_display, is_main, using_roles, remark, status, create_by, create_time, update_by, update_time, del_flag) VALUES (545584735045292032, 3, 0, '展示钱包', 1, '/pages/app-permission/wallet', 'app:module:wallet', null, '', 157, 1, 0, null, null, 0, '', null, 0, 156383116720607232, '2026-06-11 11:14:57', 156383116720607232, '2026-06-11 11:14:57', 0);
+INSERT INTO sckw_ng_system.kws_menu (id, client_type, parent_id, name, type, url, perms, icon, not_selected_icon_path, sort, level, custom, links, is_display, is_main, using_roles, remark, status, create_by, create_time, update_by, update_time, del_flag) VALUES (545584639574544384, 3, 0, '展示销售统计', 1, '/pages/app-permission/sales-statistics', 'app:module:salesStatistics', '', '', 156, 1, 0, null, null, 0, '', null, 0, 156383116720607232, '2026-06-11 11:14:34', 156383116720607232, '2026-06-11 11:14:34', 0);
+INSERT INTO sckw_ng_system.kws_menu (id, client_type, parent_id, name, type, url, perms, icon, not_selected_icon_path, sort, level, custom, links, is_display, is_main, using_roles, remark, status, create_by, create_time, update_by, update_time, del_flag) VALUES (545584503880421376, 3, 0, '展示订单统计', 1, '/pages/app-permission/order-statistics', 'app:module:orderStatistics', null, '', 155, 1, 0, null, null, 0, '', null, 0, 156383116720607232, '2026-06-11 11:14:02', 156383116720607232, '2026-06-11 11:14:02', 0);
+INSERT INTO sckw_ng_system.kws_menu (id, client_type, parent_id, name, type, url, perms, icon, not_selected_icon_path, sort, level, custom, links, is_display, is_main, using_roles, remark, status, create_by, create_time, update_by, update_time, del_flag) VALUES (545584153811226624, 3, 0, '管理', 1, '/pages/manage/index', 'app:tabbar:driver:manage', '/static/tabbar/manage_select.png', '/static/tabbar/manage.png', 153, 1, 0, null, null, 0, '', null, 0, 156383116720607232, '2026-06-11 11:12:38', 156383116720607232, '2026-06-11 11:12:38', 0);
+INSERT INTO sckw_ng_system.kws_menu (id, client_type, parent_id, name, type, url, perms, icon, not_selected_icon_path, sort, level, custom, links, is_display, is_main, using_roles, remark, status, create_by, create_time, update_by, update_time, del_flag) VALUES (545583945744388096, 3, 0, '司机任务', 1, '/pages/index/index', 'app:tabbar:driver:task', '/static/tabbar/task_select.png', '/static/tabbar/task.png', 152, 1, 0, null, null, 0, '', null, 0, 156383116720607232, '2026-06-11 11:11:49', 156383116720607232, '2026-06-11 11:11:49', 0);
+INSERT INTO sckw_ng_system.kws_menu (id, client_type, parent_id, name, type, url, perms, icon, not_selected_icon_path, sort, level, custom, links, is_display, is_main, using_roles, remark, status, create_by, create_time, update_by, update_time, del_flag) VALUES (545583742417113088, 3, 0, '铲车任务', 1, '/pages/forklift/index', 'app:tabbar:forklift:task', '/static/tabbar/task_select.png', '/static/tabbar/task.png', 151, 1, 0, null, null, 0, '', null, 0, 156383116720607232, '2026-06-11 11:11:00', 156383116720607232, '2026-06-11 11:11:00', 0);
+INSERT INTO sckw_ng_system.kws_menu (id, client_type, parent_id, name, type, url, perms, icon, not_selected_icon_path, sort, level, custom, links, is_display, is_main, using_roles, remark, status, create_by, create_time, update_by, update_time, del_flag) VALUES (545583470085148672, 3, 0, '往来车辆', 1, '/pages/doorkeeper/index', 'app:tabbar:doorKeeper:car', '/static/tabbar/car_select.png', '/static/tabbar/car.png', 150, 1, 0, null, null, 0, '', null, 0, 156383116720607232, '2026-06-11 11:09:55', 156383116720607232, '2026-06-11 11:09:55', 0);
+INSERT INTO sckw_ng_system.kws_menu (id, client_type, parent_id, name, type, url, perms, icon, not_selected_icon_path, sort, level, custom, links, is_display, is_main, using_roles, remark, status, create_by, create_time, update_by, update_time, del_flag) VALUES (545583282163552256, 3, 0, '个人中心', 1, '/pages/myCenter/index', 'app:tabbar:my', '/static/tabbar/my_select.png', '/static/tabbar/my.png', 154, 1, 0, null, null, 0, '', null, 0, 156383116720607232, '2026-06-11 11:09:10', 156383116720607232, '2026-06-11 11:09:10', 0);
+INSERT INTO sckw_ng_system.kws_menu (id, client_type, parent_id, name, type, url, perms, icon, not_selected_icon_path, sort, level, custom, links, is_display, is_main, using_roles, remark, status, create_by, create_time, update_by, update_time, del_flag) VALUES (496654622425878528, 3, 0, '物流运单', 1, '/pages/logistics/waybill/index', 'app:tabbar:waybill', '/static/tabbar/waybill_select.png', '/static/tabbar/waybill.png', 146, 1, 0, null, null, 0, '', null, 0, 156383116720607232, '2026-01-27 10:44:09', 156383116720607232, '2026-06-11 11:08:00', 0);
+INSERT INTO sckw_ng_system.kws_menu (id, client_type, parent_id, name, type, url, perms, icon, not_selected_icon_path, sort, level, custom, links, is_display, is_main, using_roles, remark, status, create_by, create_time, update_by, update_time, del_flag) VALUES (496654536237125632, 3, 0, '物流订单', 1, '/pages/logistics/order/index', 'app:tabbar:logOrder', '/static/tabbar/logOrder_select.png', '/static/tabbar/logOrder.png', 145, 1, 0, null, null, 0, '', null, 0, 156383116720607232, '2026-01-27 10:43:48', 156383116720607232, '2026-06-11 11:07:21', 0);
+INSERT INTO sckw_ng_system.kws_menu (id, client_type, parent_id, name, type, url, perms, icon, not_selected_icon_path, sort, level, custom, links, is_display, is_main, using_roles, remark, status, create_by, create_time, update_by, update_time, del_flag) VALUES (496654386416586752, 3, 0, '商城商品', 1, '/pages/mall/index', 'app:tabbar:mall', '/static/tabbar/mall_select.png', '/static/tabbar/mall.png', 144, 1, 0, null, null, 0, '', null, 0, 156383116720607232, '2026-01-27 10:43:13', 156383116720607232, '2026-06-11 11:06:41', 0);
+INSERT INTO sckw_ng_system.kws_menu (id, client_type, parent_id, name, type, url, perms, icon, not_selected_icon_path, sort, level, custom, links, is_display, is_main, using_roles, remark, status, create_by, create_time, update_by, update_time, del_flag) VALUES (489392333326913536, 3, 0, '贸易订单', 1, '/pages/tradeOrder/index', 'app:tabbar:trade', '/static/tabbar/trade_select.png', '/static/tabbar/trade.png', 143, 1, 0, null, null, 0, '', null, 0, 156383116720607232, '2026-01-07 09:46:24', 156383116720607232, '2026-06-11 11:05:06', 0);