فهرست منبع

Merge remote-tracking branch 'origin/master'

# Conflicts:
#	ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/RuoyiCustomerApplication.java
tjxt 3 ماه پیش
والد
کامیت
7310878430
100فایلهای تغییر یافته به همراه4965 افزوده شده و 118 حذف شده
  1. 5 4
      pom.xml
  2. 10 1
      ruoyi-api/ruoyi-api-customer/src/main/java/org/dromara/customer/api/RemoteCustomerService.java
  3. 25 0
      ruoyi-api/ruoyi-api-customer/src/main/java/org/dromara/customer/api/domain/CustomerApiVo.java
  4. 31 0
      ruoyi-api/ruoyi-api-product/src/main/java/org/dromara/product/api/RemoteProductProgramService.java
  5. 28 0
      ruoyi-api/ruoyi-api-product/src/main/java/org/dromara/product/api/domain/ProductProgramApiVo.java
  6. 18 0
      ruoyi-api/ruoyi-api-system/src/main/java/org/dromara/system/api/RemoteCreditLevelService.java
  7. 8 0
      ruoyi-api/ruoyi-api-system/src/main/java/org/dromara/system/api/RemoteDeptService.java
  8. 0 2
      ruoyi-api/ruoyi-api-system/src/main/java/org/dromara/system/api/domain/vo/RemoteDeptVo.java
  9. 2 0
      ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/interceptor/PlatformDataScopeInterceptor.java
  10. 3 1
      ruoyi-modules/ruoyi-customer/pom.xml
  11. 20 0
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/controller/CustomerInfoController.java
  12. 106 0
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/controller/CustomerProgramController.java
  13. 22 0
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/controller/CustomerSalesInfoController.java
  14. 57 0
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/domain/CustomerProgram.java
  15. 5 0
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/domain/MaintenanceServerTime.java
  16. 7 7
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/domain/bo/CustomerInfoBo.java
  17. 34 0
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/domain/bo/CustomerListBo.java
  18. 53 0
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/domain/bo/CustomerProgramBo.java
  19. 5 0
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/domain/bo/MaintenanceServerTimeBo.java
  20. 45 0
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/domain/bo/MessagePublishCustomerBo.java
  21. 6 0
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/domain/vo/CustomerDeptVo.java
  22. 55 0
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/domain/vo/CustomerListVo.java
  23. 81 0
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/domain/vo/CustomerProgramVo.java
  24. 6 0
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/domain/vo/MaintenanceServerTimeVo.java
  25. 80 0
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/domain/vo/MessagePublishCustomerVo.java
  26. 27 0
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/dubbo/RemoteCustomerServiceImpl.java
  27. 1 1
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/mapper/CustomerBusinessInfoMapper.java
  28. 1 1
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/mapper/CustomerContactMapper.java
  29. 1 1
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/mapper/CustomerContractMapper.java
  30. 17 0
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/mapper/CustomerInfoMapper.java
  31. 1 1
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/mapper/CustomerInvoiceInfoMapper.java
  32. 15 0
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/mapper/CustomerProgramMapper.java
  33. 1 1
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/mapper/CustomerSalesInfoMapper.java
  34. 1 1
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/mapper/CustomerShippingAddressMapper.java
  35. 22 0
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/service/ICustomerInfoService.java
  36. 70 0
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/service/ICustomerProgramService.java
  37. 18 0
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/service/ICustomerSalesInfoService.java
  38. 49 23
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/service/impl/CustomerDeptServiceImpl.java
  39. 66 0
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/service/impl/CustomerInfoServiceImpl.java
  40. 191 0
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/service/impl/CustomerProgramServiceImpl.java
  41. 46 0
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/service/impl/CustomerSalesInfoServiceImpl.java
  42. 92 0
      ruoyi-modules/ruoyi-customer/src/main/resources/mapper/customer/CustomerInfoMapper.xml
  43. 7 0
      ruoyi-modules/ruoyi-customer/src/main/resources/mapper/customer/CustomerProgramMapper.xml
  44. 5 0
      ruoyi-modules/ruoyi-product/pom.xml
  45. 2 0
      ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/RuoyiProductApplication.java
  46. 12 0
      ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/controller/ProductAssociateController.java
  47. 26 3
      ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/controller/ProductBaseController.java
  48. 118 0
      ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/controller/ProductOperationInfoController.java
  49. 22 0
      ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/controller/ProductProgramLinkController.java
  50. 8 0
      ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/controller/ProductSpecsController.java
  51. 106 0
      ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/controller/ProductWarehouseInventoryController.java
  52. 1 0
      ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/domain/ProductExtend.java
  53. 82 0
      ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/domain/ProductOperationInfo.java
  54. 87 0
      ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/domain/ProductWarehouseInventory.java
  55. 4 0
      ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/domain/bo/ProductBlacklistBo.java
  56. 82 0
      ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/domain/bo/ProductOperationInfoBo.java
  57. 87 0
      ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/domain/bo/ProductWarehouseInventoryBo.java
  58. 115 0
      ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/domain/vo/ProductBaseSimpleVo.java
  59. 39 6
      ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/domain/vo/ProductBlacklistVo.java
  60. 94 0
      ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/domain/vo/ProductOperationInfoVo.java
  61. 85 0
      ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/domain/vo/ProductOperationVo.java
  62. 111 0
      ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/domain/vo/ProductWarehouseInventoryVo.java
  63. 64 0
      ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/domain/vo/RecommendProductVo.java
  64. 57 0
      ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/dubbo/RemoteProductProgramServiceImpl.java
  65. 32 0
      ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/mapper/ProductBaseMapper.java
  66. 8 0
      ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/mapper/ProductBlacklistMapper.java
  67. 15 0
      ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/mapper/ProductOperationInfoMapper.java
  68. 30 0
      ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/mapper/ProductWarehouseInventoryMapper.java
  69. 8 0
      ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/service/IProductAssociateService.java
  70. 30 1
      ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/service/IProductBaseService.java
  71. 78 0
      ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/service/IProductOperationInfoService.java
  72. 17 0
      ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/service/IProductProgramLinkService.java
  73. 8 0
      ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/service/IProductSpecsService.java
  74. 70 0
      ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/service/IProductWarehouseInventoryService.java
  75. 136 9
      ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/service/impl/ProductAssociateServiceImpl.java
  76. 113 31
      ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/service/impl/ProductBaseServiceImpl.java
  77. 33 7
      ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/service/impl/ProductBlacklistServiceImpl.java
  78. 14 1
      ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/service/impl/ProductGiftFloorLinkServiceImpl.java
  79. 154 0
      ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/service/impl/ProductOperationInfoServiceImpl.java
  80. 34 0
      ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/service/impl/ProductProgramLinkServiceImpl.java
  81. 127 9
      ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/service/impl/ProductSpecsServiceImpl.java
  82. 37 1
      ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/service/impl/ProductThemeGroupLinkServiceImpl.java
  83. 140 0
      ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/service/impl/ProductWarehouseInventoryServiceImpl.java
  84. 178 1
      ruoyi-modules/ruoyi-product/src/main/resources/mapper/product/ProductBaseMapper.xml
  85. 41 0
      ruoyi-modules/ruoyi-product/src/main/resources/mapper/product/ProductBlacklistMapper.xml
  86. 2 1
      ruoyi-modules/ruoyi-product/src/main/resources/mapper/product/ProductGiftFloorLinkMapper.xml
  87. 2 1
      ruoyi-modules/ruoyi-product/src/main/resources/mapper/product/ProductIndustrialFloorLinkMapper.xml
  88. 7 0
      ruoyi-modules/ruoyi-product/src/main/resources/mapper/product/ProductOperationInfoMapper.xml
  89. 40 0
      ruoyi-modules/ruoyi-product/src/main/resources/mapper/product/ProductWarehouseInventoryMapper.xml
  90. 55 3
      ruoyi-modules/ruoyi-resource/src/main/java/org/dromara/resource/controller/SysSmsController.java
  91. 106 0
      ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysAdaptSceneController.java
  92. 106 0
      ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysAddressAreaController.java
  93. 106 0
      ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysOperationMessageController.java
  94. 106 0
      ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysPriceRangeController.java
  95. 106 0
      ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysProjectTypeController.java
  96. 106 0
      ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysPurchaseCategoryController.java
  97. 106 0
      ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysRuleCategoryController.java
  98. 106 0
      ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysRuleCenterController.java
  99. 106 0
      ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysServiceTimeController.java
  100. 57 0
      ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysAdaptScene.java

+ 5 - 4
pom.xml

@@ -93,20 +93,21 @@
                 <nacos.password>nacos</nacos.password>
                 <logstash.address>127.0.0.1:4560</logstash.address>
             </properties>
+
         </profile>
         <profile>
-            <id>hrx</id>
+            <id>zl</id>
             <properties>
                 <!-- 环境标识,需要与配置文件的名称相对应 -->
-                <profiles.active>5346d34f-9daf-4259-bc2f-5219f814987f</profiles.active>
-                <nacos.server>127.0.0.1:8848</nacos.server>
+                <profiles.active>zl</profiles.active>
+                <nacos.server>169.254.38.213:8848</nacos.server>
                 <nacos.discovery.group>DEFAULT_GROUP</nacos.discovery.group>
                 <nacos.config.group>DEFAULT_GROUP</nacos.config.group>
                 <nacos.username>nacos</nacos.username>
                 <nacos.password>nacos</nacos.password>
                 <logstash.address>127.0.0.1:4560</logstash.address>
             </properties>
-            <activation>
+             <activation>
                 <!-- 默认环境 -->
                 <activeByDefault>true</activeByDefault>
             </activation>

+ 10 - 1
ruoyi-api/ruoyi-api-customer/src/main/java/org/dromara/customer/api/RemoteCustomerService.java

@@ -1,10 +1,19 @@
 package org.dromara.customer.api;
 
+import org.dromara.customer.api.domain.CustomerApiVo;
+
 import java.util.Map;
 import java.util.Set;
 
 public interface RemoteCustomerService {
 
-    /*根据ids查询客户名称*/
+    /**
+     * 根据ids查询客户名称
+     */
     Map<Long, String> selectCustomerNameByIds(Set<Long> ids);
+
+    /**
+     * 根据ids查询客户信息(编号+名称)
+     */
+    Map<Long, CustomerApiVo> selectCustomerByIds(Set<Long> ids);
 }

+ 25 - 0
ruoyi-api/ruoyi-api-customer/src/main/java/org/dromara/customer/api/domain/CustomerApiVo.java

@@ -0,0 +1,25 @@
+package org.dromara.customer.api.domain;
+
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * 客户API视图对象
+ */
+@Data
+public class CustomerApiVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /** 客户ID */
+    private Long id;
+
+    /** 客户编号 */
+    private String customerNo;
+
+    /** 客户名称 */
+    private String customerName;
+}

+ 31 - 0
ruoyi-api/ruoyi-api-product/src/main/java/org/dromara/product/api/RemoteProductProgramService.java

@@ -0,0 +1,31 @@
+package org.dromara.product.api;
+
+import org.dromara.product.api.domain.ProductProgramApiVo;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * 产品方案远程服务接口
+ *
+ * @author LionLi
+ * @date 2026-01-09
+ */
+public interface RemoteProductProgramService {
+
+    /**
+     * 根据ID批量查询方案信息
+     *
+     * @param ids 方案ID集合
+     * @return Map<ID, 方案信息>
+     */
+    Map<Long, ProductProgramApiVo> selectProgramByIds(Set<Long> ids);
+
+    /**
+     * 查询所有方案列表(用于下拉选择)
+     *
+     * @return 方案列表
+     */
+    List<ProductProgramApiVo> selectAllProgramList();
+}

+ 28 - 0
ruoyi-api/ruoyi-api-product/src/main/java/org/dromara/product/api/domain/ProductProgramApiVo.java

@@ -0,0 +1,28 @@
+package org.dromara.product.api.domain;
+
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * 产品方案API视图对象
+ *
+ * @author LionLi
+ * @date 2026-01-09
+ */
+@Data
+public class ProductProgramApiVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /** 方案ID */
+    private Long id;
+
+    /** 方案编号 */
+    private String programNo;
+
+    /** 方案标题 */
+    private String title;
+}

+ 18 - 0
ruoyi-api/ruoyi-api-system/src/main/java/org/dromara/system/api/RemoteCreditLevelService.java

@@ -0,0 +1,18 @@
+package org.dromara.system.api;
+
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * 信用等级远程服务接口
+ */
+public interface RemoteCreditLevelService {
+
+    /**
+     * 根据ID批量查询信用等级名称
+     *
+     * @param ids 信用等级ID集合
+     * @return Map<ID, 名称>
+     */
+    Map<Long, String> selectCreditLevelNameByIds(Set<Long> ids);
+}

+ 8 - 0
ruoyi-api/ruoyi-api-system/src/main/java/org/dromara/system/api/RemoteDeptService.java

@@ -43,6 +43,14 @@ public interface RemoteDeptService {
 
     RemoteDeptVo insertDept(RemoteDeptVo vo);
 
+    /**
+     * 更新部门信息
+     *
+     * @param vo 部门信息
+     * @return 更新后的部门信息
+     */
+    RemoteDeptVo updateDept(RemoteDeptVo vo);
+
     List<RemoteDeptVo> selectDeptByIds(List<Long> deptIds);
 
     /**

+ 0 - 2
ruoyi-api/ruoyi-api-system/src/main/java/org/dromara/system/api/domain/vo/RemoteDeptVo.java

@@ -34,8 +34,6 @@ public class RemoteDeptVo implements Serializable {
      */
     private String deptName;
 
-    private Long customerId;
-
     private String status;
 
     private Integer orderNum;

+ 2 - 0
ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/interceptor/PlatformDataScopeInterceptor.java

@@ -61,6 +61,8 @@ public class PlatformDataScopeInterceptor implements Interceptor {
         "invoice_type",
         "customer_info",
         "customer_dept",
+        "customer_contact",
+        "customer_contract",
         "com_bank",
         "settlement_level",
         "settlement_method",

+ 3 - 1
ruoyi-modules/ruoyi-customer/pom.xml

@@ -117,9 +117,11 @@
             <groupId>org.dromara</groupId>
             <artifactId>ruoyi-api-customer</artifactId>
         </dependency>
+
+        <!-- RuoYi Api Product -->
         <dependency>
             <groupId>org.dromara</groupId>
-            <artifactId>ruoyi-api-customer</artifactId>
+            <artifactId>ruoyi-api-product</artifactId>
         </dependency>
 
     </dependencies>

+ 20 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/controller/CustomerInfoController.java

@@ -8,8 +8,12 @@ import jakarta.validation.constraints.*;
 import cn.dev33.satoken.annotation.SaCheckPermission;
 import org.dromara.customer.domain.CustomerSalesInfo;
 import org.dromara.customer.domain.bo.CustomerSalesInfoBo;
+import org.dromara.customer.domain.bo.CustomerListBo;
+import org.dromara.customer.domain.bo.MessagePublishCustomerBo;
 import org.dromara.customer.domain.dto.SetCustomerInfoTagDto;
 import org.dromara.customer.domain.vo.ContractVo;
+import org.dromara.customer.domain.vo.CustomerListVo;
+import org.dromara.customer.domain.vo.MessagePublishCustomerVo;
 import org.springframework.web.bind.annotation.*;
 import org.springframework.validation.annotation.Validated;
 import org.dromara.common.idempotent.annotation.RepeatSubmit;
@@ -49,6 +53,14 @@ public class CustomerInfoController extends BaseController {
         return customerInfoService.queryPageList(bo, pageQuery);
     }
 
+    /**
+     * 消息发布-查询客户列表
+     */
+    @GetMapping("/messagePublishList")
+    public TableDataInfo<MessagePublishCustomerVo> messagePublishList(MessagePublishCustomerBo bo, PageQuery pageQuery) {
+        return customerInfoService.queryMessagePublishCustomerPage(bo, pageQuery);
+    }
+
     /**
      * 查询合同管理
      */
@@ -57,6 +69,14 @@ public class CustomerInfoController extends BaseController {
         return customerInfoService.queryContractPageList(bo, pageQuery);
     }
 
+    /**
+     * 客户列表-分页查询
+     */
+    @GetMapping("/customerList")
+    public TableDataInfo<CustomerListVo> customerList(CustomerListBo bo, PageQuery pageQuery) {
+        return customerInfoService.queryCustomerListPage(bo, pageQuery);
+    }
+
     /**
      * 导出客户信息列表
      */

+ 106 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/controller/CustomerProgramController.java

@@ -0,0 +1,106 @@
+package org.dromara.customer.controller;
+
+import java.util.List;
+
+import lombok.RequiredArgsConstructor;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.validation.constraints.*;
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.validation.annotation.Validated;
+import org.dromara.common.idempotent.annotation.RepeatSubmit;
+import org.dromara.common.log.annotation.Log;
+import org.dromara.common.web.core.BaseController;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.core.validate.AddGroup;
+import org.dromara.common.core.validate.EditGroup;
+import org.dromara.common.log.enums.BusinessType;
+import org.dromara.common.excel.utils.ExcelUtil;
+import org.dromara.customer.domain.vo.CustomerProgramVo;
+import org.dromara.customer.domain.bo.CustomerProgramBo;
+import org.dromara.customer.service.ICustomerProgramService;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+
+/**
+ * 客户专属方案关联
+ * 前端访问路由地址为:/customer/program
+ *
+ * @author LionLi
+ * @date 2026-01-09
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/program")
+public class CustomerProgramController extends BaseController {
+
+    private final ICustomerProgramService customerProgramService;
+
+    /**
+     * 查询客户专属方案关联列表
+     */
+    @SaCheckPermission("customer:program:list")
+    @GetMapping("/list")
+    public TableDataInfo<CustomerProgramVo> list(CustomerProgramBo bo, PageQuery pageQuery) {
+        return customerProgramService.queryPageList(bo, pageQuery);
+    }
+
+    /**
+     * 导出客户专属方案关联列表
+     */
+    @SaCheckPermission("customer:program:export")
+    @Log(title = "客户专属方案关联", businessType = BusinessType.EXPORT)
+    @PostMapping("/export")
+    public void export(CustomerProgramBo bo, HttpServletResponse response) {
+        List<CustomerProgramVo> list = customerProgramService.queryList(bo);
+        ExcelUtil.exportExcel(list, "客户专属方案关联", CustomerProgramVo.class, response);
+    }
+
+    /**
+     * 获取客户专属方案关联详细信息
+     *
+     * @param id 主键
+     */
+    @SaCheckPermission("customer:program:query")
+    @GetMapping("/{id}")
+    public R<CustomerProgramVo> getInfo(@NotNull(message = "主键不能为空")
+                                     @PathVariable("id") Long id) {
+        return R.ok(customerProgramService.queryById(id));
+    }
+
+    /**
+     * 新增客户专属方案关联
+     */
+    @SaCheckPermission("customer:program:add")
+    @Log(title = "客户专属方案关联", businessType = BusinessType.INSERT)
+    @RepeatSubmit()
+    @PostMapping()
+    public R<Void> add(@Validated(AddGroup.class) @RequestBody CustomerProgramBo bo) {
+        return toAjax(customerProgramService.insertByBo(bo));
+    }
+
+    /**
+     * 修改客户专属方案关联
+     */
+    @SaCheckPermission("customer:program:edit")
+    @Log(title = "客户专属方案关联", businessType = BusinessType.UPDATE)
+    @RepeatSubmit()
+    @PutMapping()
+    public R<Void> edit(@Validated(EditGroup.class) @RequestBody CustomerProgramBo bo) {
+        return toAjax(customerProgramService.updateByBo(bo));
+    }
+
+    /**
+     * 删除客户专属方案关联
+     *
+     * @param ids 主键串
+     */
+    @SaCheckPermission("customer:program:remove")
+    @Log(title = "客户专属方案关联", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{ids}")
+    public R<Void> remove(@NotEmpty(message = "主键不能为空")
+                          @PathVariable("ids") Long[] ids) {
+        return toAjax(customerProgramService.deleteWithValidByIds(List.of(ids), true));
+    }
+}

+ 22 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/controller/CustomerSalesInfoController.java

@@ -1,6 +1,7 @@
 package org.dromara.customer.controller;
 
 import java.util.List;
+import java.util.Map;
 
 import lombok.RequiredArgsConstructor;
 import jakarta.servlet.http.HttpServletResponse;
@@ -97,4 +98,25 @@ public class CustomerSalesInfoController extends BaseController {
                           @PathVariable("ids") Long[] ids) {
         return toAjax(customerSalesInfoService.deleteWithValidByIds(List.of(ids), true));
     }
+
+    /**
+     * 分配业务人员
+     */
+    @Log(title = "分配业务人员", businessType = BusinessType.UPDATE)
+    @RepeatSubmit()
+    @PutMapping("/assignStaff")
+    public R<Void> assignStaff(@RequestBody Map<String, Object> params) {
+        Long customerId = Long.valueOf(params.get("customerId").toString());
+        Long salesPersonId = params.get("salesPersonId") != null ? Long.valueOf(params.get("salesPersonId").toString()) : null;
+        Long serviceStaffId = params.get("serviceStaffId") != null ? Long.valueOf(params.get("serviceStaffId").toString()) : null;
+        return toAjax(customerSalesInfoService.assignStaff(customerId, salesPersonId, serviceStaffId));
+    }
+
+    /**
+     * 根据客户ID获取销售信息
+     */
+    @GetMapping("/getByCustomerId/{customerId}")
+    public R<CustomerSalesInfoVo> getByCustomerId(@PathVariable("customerId") Long customerId) {
+        return R.ok(customerSalesInfoService.queryByCustomerId(customerId));
+    }
 }

+ 57 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/domain/CustomerProgram.java

@@ -0,0 +1,57 @@
+package org.dromara.customer.domain;
+
+import org.dromara.common.tenant.core.TenantEntity;
+import com.baomidou.mybatisplus.annotation.*;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.io.Serial;
+
+/**
+ * 客户专属方案关联对象 customer_program
+ *
+ * @author LionLi
+ * @date 2026-01-09
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@TableName("customer_program")
+public class CustomerProgram extends TenantEntity {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 主键ID
+     */
+    @TableId(value = "id")
+    private Long id;
+
+    /**
+     * 客户ID(关联customer_info.id)
+     */
+    private Long customerId;
+
+    /**
+     * 专属方案ID(关联product_program.id)
+     */
+    private Long programId;
+
+    /**
+     * 状态(0正常 1停用)
+     */
+    private String status;
+
+    /**
+     * 删除标志(0代表存在 2代表删除)
+     */
+    @TableLogic
+    private String delFlag;
+
+    /**
+     * 备注
+     */
+    private String remark;
+
+
+}

+ 5 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/domain/MaintenanceServerTime.java

@@ -32,6 +32,11 @@ public class MaintenanceServerTime extends TenantEntity {
      */
     private String timeName;
 
+    /**
+     * 类型值
+     */
+    private Integer typeValue;
+
     /**
      * 状态(0正常 1停用)
      */

+ 7 - 7
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/domain/bo/CustomerInfoBo.java

@@ -33,13 +33,13 @@ public class CustomerInfoBo extends BaseEntity {
     /**
      * 客户编号
      */
-    @NotBlank(message = "客户编号不能为空", groups = {AddGroup.class, EditGroup.class})
+//    @NotBlank(message = "客户编号不能为空", groups = {AddGroup.class, EditGroup.class})
     private String customerNo;
 
     /**
      * 所属公司
      */
-    @NotNull(message = "所属公司不能为空", groups = {AddGroup.class, EditGroup.class})
+//    @NotNull(message = "所属公司不能为空", groups = {AddGroup.class, EditGroup.class})
     private Long belongCompanyId;
 
     private Long belongDeptId;
@@ -57,25 +57,25 @@ public class CustomerInfoBo extends BaseEntity {
     /**
      * 工商名称
      */
-    @NotBlank(message = "工商名称不能为空", groups = {AddGroup.class, EditGroup.class})
+//    @NotBlank(message = "工商名称不能为空", groups = {AddGroup.class, EditGroup.class})
     private String businessCustomerName;
 
     /**
      * 企业简称
      */
-    @NotBlank(message = "企业简称不能为空", groups = {AddGroup.class, EditGroup.class})
+//    @NotBlank(message = "企业简称不能为空", groups = {AddGroup.class, EditGroup.class})
     private String shortName;
 
     /**
      * 开票类型
      */
-    @NotNull(message = "开票类型不能为空", groups = {AddGroup.class, EditGroup.class})
+//    @NotNull(message = "开票类型不能为空", groups = {AddGroup.class, EditGroup.class})
     private Long invoiceTypeId;
 
     /**
      * 企业规模
      */
-    @NotNull(message = "企业规模不能为空", groups = {AddGroup.class, EditGroup.class})
+//    @NotNull(message = "企业规模不能为空", groups = {AddGroup.class, EditGroup.class})
     private Long enterpriseScaleId;
 
     /**
@@ -86,7 +86,7 @@ public class CustomerInfoBo extends BaseEntity {
     /**
      * 行业类别
      */
-    @NotNull(message = "行业类别不能为空", groups = {AddGroup.class, EditGroup.class})
+//    @NotNull(message = "行业类别不能为空", groups = {AddGroup.class, EditGroup.class})
     private Long industryCategoryId;
 
     /**

+ 34 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/domain/bo/CustomerListBo.java

@@ -0,0 +1,34 @@
+package org.dromara.customer.domain.bo;
+
+import lombok.Data;
+import org.dromara.common.mybatis.core.domain.BaseEntity;
+
+import java.math.BigDecimal;
+
+/**
+ * 客户列表查询条件
+ */
+@Data
+public class CustomerListBo extends BaseEntity {
+
+    /** 客户ID */
+    private Long id;
+
+    /** 客户编号 */
+    private String customerNo;
+
+    /** 行业类别ID */
+    private Long industryCategoryId;
+
+    /** 年度销售最小值 */
+    private BigDecimal yearSalesMin;
+
+    /** 年度销售最大值 */
+    private BigDecimal yearSalesMax;
+
+    /** 业务员ID */
+    private Long salesPersonId;
+
+    /** 客户等级ID */
+    private Long customerLevelId;
+}

+ 53 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/domain/bo/CustomerProgramBo.java

@@ -0,0 +1,53 @@
+package org.dromara.customer.domain.bo;
+
+import org.dromara.customer.domain.CustomerProgram;
+import org.dromara.common.mybatis.core.domain.BaseEntity;
+import org.dromara.common.core.validate.AddGroup;
+import org.dromara.common.core.validate.EditGroup;
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import jakarta.validation.constraints.*;
+
+/**
+ * 客户专属方案关联业务对象 customer_program
+ *
+ * @author LionLi
+ * @date 2026-01-09
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@AutoMapper(target = CustomerProgram.class, reverseConvertGenerate = false)
+public class CustomerProgramBo extends BaseEntity {
+
+    /**
+     * 主键ID
+     */
+    private Long id;
+
+    /**
+     * 客户ID(关联customer_info.id)
+     */
+//    @NotNull(message = "客户ID(关联customer_info.id)不能为空", groups = { AddGroup.class, EditGroup.class })
+    private Long customerId;
+
+    /**
+     * 专属方案ID(关联product_program.id)
+     */
+//    @NotNull(message = "专属方案ID(关联product_program.id)不能为空", groups = { AddGroup.class, EditGroup.class })
+    private Long programId;
+
+    /**
+     * 状态(0正常 1停用)
+     */
+//    @NotBlank(message = "状态(0正常 1停用)不能为空", groups = { AddGroup.class, EditGroup.class })
+    private String status;
+
+    /**
+     * 备注
+     */
+//    @NotBlank(message = "备注不能为空", groups = { AddGroup.class, EditGroup.class })
+    private String remark;
+
+
+}

+ 5 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/domain/bo/MaintenanceServerTimeBo.java

@@ -31,6 +31,11 @@ public class MaintenanceServerTimeBo extends BaseEntity {
     @NotBlank(message = "时间类型不能为空", groups = { AddGroup.class, EditGroup.class })
     private String timeName;
 
+    /**
+     * 类型值(月数)
+     */
+    private Integer typeValue;
+
     /**
      * 状态(0正常 1停用)
      */

+ 45 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/domain/bo/MessagePublishCustomerBo.java

@@ -0,0 +1,45 @@
+package org.dromara.customer.domain.bo;
+
+import lombok.Data;
+import org.dromara.common.mybatis.core.domain.BaseEntity;
+
+import java.io.Serial;
+import java.util.List;
+
+/**
+ * 消息发布-客户查询条件Bo
+ *
+ * @author yoe
+ * @date 2026-01-06
+ */
+@Data
+public class MessagePublishCustomerBo extends BaseEntity {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 行业分类ID
+     */
+    private Long industryCategoryId;
+
+    /**
+     * 采购体量(客户等级ID)
+     */
+    private Long customerLevelId;
+
+    /**
+     * 企业规模ID
+     */
+    private Long enterpriseScaleId;
+
+    /**
+     * 采购标签ID
+     */
+    private Long tagId;
+
+    /**
+     * 平台标识
+     */
+    private String platformCode;
+}

+ 6 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/domain/vo/CustomerDeptVo.java

@@ -149,5 +149,11 @@ public class CustomerDeptVo implements Serializable {
     @ExcelProperty(value = "备注")
     private String remark;
 
+    /**
+     * 创建时间
+     */
+    @ExcelProperty(value = "创建时间")
+    private Date createTime;
+
 
 }

+ 55 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/domain/vo/CustomerListVo.java

@@ -0,0 +1,55 @@
+package org.dromara.customer.domain.vo;
+
+import lombok.Data;
+import java.io.Serial;
+import java.io.Serializable;
+import java.math.BigDecimal;
+
+/**
+ * 客户列表视图对象
+ */
+@Data
+public class CustomerListVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /** 客户ID */
+    private Long id;
+
+    /** 客户编号 */
+    private String customerNo;
+
+    /** 客户名称 */
+    private String customerName;
+
+    /** 行业类别ID */
+    private Long industryCategoryId;
+
+    /** 行业名称 */
+    private String industryName;
+
+    /** 年销售预估(万) */
+    private BigDecimal yearSalesEstimate;
+
+    /** 信用额度 */
+    private BigDecimal creditAmount;
+
+    /** 信用等级ID(关联credit_level表) */
+    private Long creditLevelId;
+
+    /** 信用等级名称 */
+    private String creditLevelName;
+
+    /** 应收账款 */
+    private BigDecimal accountsReceivable;
+
+    /** 业务员ID */
+    private Long salesPersonId;
+
+    /** 业务员名称 */
+    private String salesPersonName;
+
+    /** 状态 */
+    private String status;
+}

+ 81 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/domain/vo/CustomerProgramVo.java

@@ -0,0 +1,81 @@
+package org.dromara.customer.domain.vo;
+
+import org.dromara.customer.domain.CustomerProgram;
+import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
+import cn.idev.excel.annotation.ExcelProperty;
+import org.dromara.common.excel.annotation.ExcelDictFormat;
+import org.dromara.common.excel.convert.ExcelDictConvert;
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.Date;
+
+
+
+/**
+ * 客户专属方案关联视图对象 customer_program
+ *
+ * @author LionLi
+ * @date 2026-01-09
+ */
+@Data
+@ExcelIgnoreUnannotated
+@AutoMapper(target = CustomerProgram.class)
+public class CustomerProgramVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 主键ID
+     */
+    @ExcelProperty(value = "主键ID")
+    private Long id;
+
+    /**
+     * 客户ID(关联customer_info.id)
+     */
+    @ExcelProperty(value = "客户ID")
+    private Long customerId;
+
+    /**
+     * 客户编号(远程查询回填)
+     */
+    @ExcelProperty(value = "客户编号")
+    private String customerNo;
+
+    /**
+     * 专属方案ID(关联product_program.id)
+     */
+    @ExcelProperty(value = "专属方案ID")
+    private Long programId;
+
+    /**
+     * 方案编号(远程查询回填)
+     */
+    @ExcelProperty(value = "方案编号")
+    private String programNo;
+
+    /**
+     * 方案名称(远程查询回填)
+     */
+    @ExcelProperty(value = "方案名称")
+    private String programName;
+
+    /**
+     * 状态(0正常 1停用)
+     */
+    @ExcelProperty(value = "状态", converter = ExcelDictConvert.class)
+    @ExcelDictFormat(readConverterExp = "0=正常,1=停用")
+    private String status;
+
+    /**
+     * 备注
+     */
+    @ExcelProperty(value = "备注")
+    private String remark;
+
+
+}

+ 6 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/domain/vo/MaintenanceServerTimeVo.java

@@ -40,6 +40,12 @@ public class MaintenanceServerTimeVo implements Serializable {
     @ExcelProperty(value = "时间类型")
     private String timeName;
 
+    /**
+     * 类型值(月数)
+     */
+    @ExcelProperty(value = "类型值")
+    private Integer typeValue;
+
     /**
      * 状态(0正常 1停用)
      */

+ 80 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/domain/vo/MessagePublishCustomerVo.java

@@ -0,0 +1,80 @@
+package org.dromara.customer.domain.vo;
+
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.math.BigDecimal;
+
+/**
+ * 消息发布-客户列表VO
+ *
+ * @author yoe
+ * @date 2026-01-06
+ */
+@Data
+public class MessagePublishCustomerVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 客户ID
+     */
+    private Long id;
+
+    /**
+     * 企业ID(客户编号)
+     */
+    private String customerNo;
+
+    /**
+     * 企业名称
+     */
+    private String companyName;
+
+    /**
+     * 行业类别ID
+     */
+    private Long industryCategoryId;
+
+    /**
+     * 行业类别名称
+     */
+    private String industryCategoryName;
+
+    /**
+     * 员工数(暂无数据)
+     */
+    private Integer employeeCount;
+
+    /**
+     * 账期(天)
+     */
+    private Integer accountPeriod;
+
+    /**
+     * 信用额度
+     */
+    private BigDecimal creditAmount;
+
+    /**
+     * 剩余额度
+     */
+    private BigDecimal remainingQuota;
+
+    /**
+     * 应收账款
+     */
+    private BigDecimal accountsReceivable;
+
+    /**
+     * 状态(0正常 1停用)
+     */
+    private String status;
+
+    /**
+     * 主联系人手机号
+     */
+    private String phone;
+}

+ 27 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/dubbo/RemoteCustomerServiceImpl.java

@@ -1,14 +1,21 @@
 package org.dromara.customer.dubbo;
 
+import cn.hutool.core.collection.CollUtil;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.dubbo.config.annotation.DubboService;
 import org.dromara.customer.api.RemoteCustomerService;
+import org.dromara.customer.api.domain.CustomerApiVo;
+import org.dromara.customer.domain.CustomerInfo;
+import org.dromara.customer.mapper.CustomerInfoMapper;
 import org.dromara.customer.service.ICustomerInfoService;
 import org.springframework.stereotype.Service;
 
+import java.util.Collections;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.stream.Collectors;
 
 @Slf4j
 @Service
@@ -17,9 +24,29 @@ import java.util.Set;
 public class RemoteCustomerServiceImpl implements RemoteCustomerService {
 
     private final ICustomerInfoService customerInfoService;
+    private final CustomerInfoMapper customerInfoMapper;
 
     @Override
     public Map<Long, String> selectCustomerNameByIds(Set<Long> ids) {
         return customerInfoService.selectCustomerNameByIds(ids);
     }
+
+    @Override
+    public Map<Long, CustomerApiVo> selectCustomerByIds(Set<Long> ids) {
+        if (CollUtil.isEmpty(ids)) {
+            return Collections.emptyMap();
+        }
+        List<CustomerInfo> list = customerInfoMapper.selectBatchIds(ids);
+        return list.stream().collect(Collectors.toMap(
+            CustomerInfo::getId,
+            c -> {
+                CustomerApiVo vo = new CustomerApiVo();
+                vo.setId(c.getId());
+                vo.setCustomerNo(c.getCustomerNo());
+                vo.setCustomerName(c.getCustomerName() != null ? c.getCustomerName() : c.getBusinessCustomerName());
+                return vo;
+            },
+            (v1, v2) -> v1
+        ));
+    }
 }

+ 1 - 1
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/mapper/CustomerBusinessInfoMapper.java

@@ -1,6 +1,6 @@
 package org.dromara.customer.mapper;
 
-import org.apache.dubbo.remoting.http12.rest.Param;
+import org.apache.ibatis.annotations.Param;
 import org.dromara.customer.domain.CustomerBusinessInfo;
 import org.dromara.customer.domain.vo.CustomerBusinessInfoVo;
 import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;

+ 1 - 1
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/mapper/CustomerContactMapper.java

@@ -1,6 +1,6 @@
 package org.dromara.customer.mapper;
 
-import org.apache.dubbo.remoting.http12.rest.Param;
+import org.apache.ibatis.annotations.Param;
 import org.dromara.customer.domain.CustomerContact;
 import org.dromara.customer.domain.CustomerInvoiceInfo;
 import org.dromara.customer.domain.vo.CustomerContactVo;

+ 1 - 1
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/mapper/CustomerContractMapper.java

@@ -1,6 +1,6 @@
 package org.dromara.customer.mapper;
 
-import org.apache.dubbo.remoting.http12.rest.Param;
+import org.apache.ibatis.annotations.Param;
 import org.dromara.customer.domain.CustomerContract;
 import org.dromara.customer.domain.vo.CustomerContractVo;
 import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;

+ 17 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/mapper/CustomerInfoMapper.java

@@ -1,8 +1,14 @@
 package org.dromara.customer.mapper;
 
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import org.apache.ibatis.annotations.Param;
 import org.dromara.customer.domain.CustomerInfo;
+import org.dromara.customer.domain.bo.CustomerListBo;
+import org.dromara.customer.domain.bo.MessagePublishCustomerBo;
 import org.dromara.customer.domain.vo.CustomerInfoVo;
 import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
+import org.dromara.customer.domain.vo.CustomerListVo;
+import org.dromara.customer.domain.vo.MessagePublishCustomerVo;
 
 /**
  * 客户信息Mapper接口
@@ -12,4 +18,15 @@ import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
  */
 public interface CustomerInfoMapper extends BaseMapperPlus<CustomerInfo, CustomerInfoVo> {
 
+    /**
+     * 消息发布-分页查询客户列表
+     */
+    Page<MessagePublishCustomerVo> selectMessagePublishCustomerPage(@Param("page") Page<MessagePublishCustomerVo> page,
+                                                                     @Param("bo") MessagePublishCustomerBo bo);
+
+    /**
+     * 客户列表-分页查询
+     */
+    Page<CustomerListVo> selectCustomerListPage(@Param("page") Page<CustomerListVo> page,
+                                                 @Param("bo") CustomerListBo bo);
 }

+ 1 - 1
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/mapper/CustomerInvoiceInfoMapper.java

@@ -1,6 +1,6 @@
 package org.dromara.customer.mapper;
 
-import org.apache.dubbo.remoting.http12.rest.Param;
+import org.apache.ibatis.annotations.Param;
 import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
 import org.dromara.customer.domain.CustomerInvoiceInfo;
 import org.dromara.customer.domain.vo.CustomerInvoiceInfoVo;

+ 15 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/mapper/CustomerProgramMapper.java

@@ -0,0 +1,15 @@
+package org.dromara.customer.mapper;
+
+import org.dromara.customer.domain.CustomerProgram;
+import org.dromara.customer.domain.vo.CustomerProgramVo;
+import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
+
+/**
+ * 客户专属方案关联Mapper接口
+ *
+ * @author LionLi
+ * @date 2026-01-09
+ */
+public interface CustomerProgramMapper extends BaseMapperPlus<CustomerProgram, CustomerProgramVo> {
+
+}

+ 1 - 1
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/mapper/CustomerSalesInfoMapper.java

@@ -1,6 +1,6 @@
 package org.dromara.customer.mapper;
 
-import org.apache.dubbo.remoting.http12.rest.Param;
+import org.apache.ibatis.annotations.Param;
 import org.dromara.customer.domain.CustomerSalesInfo;
 import org.dromara.customer.domain.vo.CustomerBusinessInfoVo;
 import org.dromara.customer.domain.vo.CustomerSalesInfoVo;

+ 1 - 1
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/mapper/CustomerShippingAddressMapper.java

@@ -1,6 +1,6 @@
 package org.dromara.customer.mapper;
 
-import org.apache.dubbo.remoting.http12.rest.Param;
+import org.apache.ibatis.annotations.Param;
 import org.apache.ibatis.annotations.Update;
 import org.dromara.customer.domain.CustomerShippingAddress;
 import org.dromara.customer.domain.vo.CustomerShippingAddressVo;

+ 22 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/service/ICustomerInfoService.java

@@ -2,11 +2,15 @@ package org.dromara.customer.service;
 
 import com.baomidou.mybatisplus.extension.service.IService;
 import org.dromara.customer.domain.CustomerInfo;
+import org.dromara.customer.domain.bo.CustomerListBo;
+import org.dromara.customer.domain.bo.MessagePublishCustomerBo;
 import org.dromara.customer.domain.vo.ContractVo;
 import org.dromara.customer.domain.vo.CustomerInfoVo;
 import org.dromara.customer.domain.bo.CustomerInfoBo;
 import org.dromara.common.mybatis.core.page.TableDataInfo;
 import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.customer.domain.vo.CustomerListVo;
+import org.dromara.customer.domain.vo.MessagePublishCustomerVo;
 
 import java.math.BigDecimal;
 import java.util.Collection;
@@ -48,6 +52,24 @@ public interface ICustomerInfoService extends IService<CustomerInfo> {
      */
     TableDataInfo<ContractVo> queryContractPageList(CustomerInfoBo bo, PageQuery pageQuery);
 
+    /**
+     * 消息发布-分页查询客户列表
+     *
+     * @param bo        查询条件
+     * @param pageQuery 分页参数
+     * @return 客户列表
+     */
+    TableDataInfo<MessagePublishCustomerVo> queryMessagePublishCustomerPage(MessagePublishCustomerBo bo, PageQuery pageQuery);
+
+    /**
+     * 客户列表-分页查询
+     *
+     * @param bo        查询条件
+     * @param pageQuery 分页参数
+     * @return 客户列表
+     */
+    TableDataInfo<CustomerListVo> queryCustomerListPage(CustomerListBo bo, PageQuery pageQuery);
+
     /**
      * 查询符合条件的客户信息列表
      *

+ 70 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/service/ICustomerProgramService.java

@@ -0,0 +1,70 @@
+package org.dromara.customer.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import org.dromara.customer.domain.CustomerProgram;
+import org.dromara.customer.domain.vo.CustomerProgramVo;
+import org.dromara.customer.domain.bo.CustomerProgramBo;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.mybatis.core.page.PageQuery;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * 客户专属方案关联Service接口
+ *
+ * @author LionLi
+ * @date 2026-01-09
+ */
+public interface ICustomerProgramService extends IService<CustomerProgram>{
+
+    /**
+     * 查询客户专属方案关联
+     *
+     * @param id 主键
+     * @return 客户专属方案关联
+     */
+    CustomerProgramVo queryById(Long id);
+
+    /**
+     * 分页查询客户专属方案关联列表
+     *
+     * @param bo        查询条件
+     * @param pageQuery 分页参数
+     * @return 客户专属方案关联分页列表
+     */
+    TableDataInfo<CustomerProgramVo> queryPageList(CustomerProgramBo bo, PageQuery pageQuery);
+
+    /**
+     * 查询符合条件的客户专属方案关联列表
+     *
+     * @param bo 查询条件
+     * @return 客户专属方案关联列表
+     */
+    List<CustomerProgramVo> queryList(CustomerProgramBo bo);
+
+    /**
+     * 新增客户专属方案关联
+     *
+     * @param bo 客户专属方案关联
+     * @return 是否新增成功
+     */
+    Boolean insertByBo(CustomerProgramBo bo);
+
+    /**
+     * 修改客户专属方案关联
+     *
+     * @param bo 客户专属方案关联
+     * @return 是否修改成功
+     */
+    Boolean updateByBo(CustomerProgramBo bo);
+
+    /**
+     * 校验并批量删除客户专属方案关联信息
+     *
+     * @param ids     待删除的主键集合
+     * @param isValid 是否进行有效性校验
+     * @return 是否删除成功
+     */
+    Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
+}

+ 18 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/service/ICustomerSalesInfoService.java

@@ -67,4 +67,22 @@ public interface ICustomerSalesInfoService extends IService<CustomerSalesInfo>{
      * @return 是否删除成功
      */
     Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
+
+    /**
+     * 分配业务人员
+     *
+     * @param customerId 客户ID
+     * @param salesPersonId 销售人员ID
+     * @param serviceStaffId 服务人员ID
+     * @return 是否成功
+     */
+    Boolean assignStaff(Long customerId, Long salesPersonId, Long serviceStaffId);
+
+    /**
+     * 根据客户ID查询销售信息
+     *
+     * @param customerId 客户ID
+     * @return 销售信息
+     */
+    CustomerSalesInfoVo queryByCustomerId(Long customerId);
 }

+ 49 - 23
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/service/impl/CustomerDeptServiceImpl.java

@@ -68,7 +68,37 @@ public class CustomerDeptServiceImpl extends ServiceImpl<CustomerDeptMapper, Cus
     @Override
     public List<CustomerDeptVo> queryList(CustomerDeptBo bo) {
         LambdaQueryWrapper<CustomerDept> lqw = buildQueryWrapper(bo);
-        return baseMapper.selectVoList(lqw);
+        List<CustomerDeptVo> list = baseMapper.selectVoList(lqw);
+        
+        if (CollUtil.isEmpty(list)) {
+            return list;
+        }
+        
+        // 关联查询 sys_dept 获取部门名称等信息
+        List<Long> deptIds = list.stream()
+            .map(CustomerDeptVo::getDeptId)
+            .filter(id -> id != null)
+            .collect(Collectors.toList());
+        
+        if (CollUtil.isEmpty(deptIds)) {
+            return list;
+        }
+        
+        List<RemoteDeptVo> sysDepts = remoteDeptService.selectDeptByIds(deptIds);
+        Map<Long, RemoteDeptVo> deptMap = sysDepts.stream()
+            .collect(Collectors.toMap(RemoteDeptVo::getDeptId, d -> d, (e, r) -> e));
+        
+        // 补充部门名称等信息
+        for (CustomerDeptVo vo : list) {
+            RemoteDeptVo sysDept = deptMap.get(vo.getDeptId());
+            if (sysDept != null) {
+                vo.setDeptName(sysDept.getDeptName());
+                vo.setParentId(sysDept.getParentId());
+                vo.setStatus(sysDept.getStatus());
+            }
+        }
+        
+        return list;
     }
 
     @Override
@@ -138,7 +168,7 @@ public class CustomerDeptServiceImpl extends ServiceImpl<CustomerDeptMapper, Cus
         lqw.eq(StringUtils.isNotBlank(bo.getDeptManage()), CustomerDept::getDeptManage, bo.getDeptManage());
         lqw.eq(StringUtils.isNotBlank(bo.getIsLimit()), CustomerDept::getIsLimit, bo.getIsLimit());
         lqw.eq(StringUtils.isNotBlank(bo.getSelectYear()), CustomerDept::getSelectYear, bo.getSelectYear());
-        lqw.eq(StringUtils.isNotBlank(bo.getPlatformCode()), CustomerDept::getPlatformCode, bo.getPlatformCode());
+        // lqw.eq(StringUtils.isNotBlank(bo.getPlatformCode()), CustomerDept::getPlatformCode, bo.getPlatformCode());
         return lqw;
     }
 
@@ -189,33 +219,29 @@ public class CustomerDeptServiceImpl extends ServiceImpl<CustomerDeptMapper, Cus
 
     @Override
     public Boolean updateByBo(CustomerDeptBo bo) {
-
         if (bo == null || bo.getDeptId() == null || bo.getDeptId() <= 0) {
             return false;
         }
-        RemoteDeptVo remoteDeptVo = remoteDeptService.selectDeptById(bo.getDeptId());
-        if (remoteDeptVo == null) {
-            return false;
-        }
-
-        MapstructUtils.convert(bo, CustomerInfo.class);
-
-
-        // 1. 更新主表
-        CustomerInfo entity = MapstructUtils.convert(bo, CustomerInfo.class);
-//        boolean mainUpdated = baseMapper.updateById(entity) > 0;
-//        if (!mainUpdated) {
-//            return false;
-//        }
-//
-//        Long customerId = bo.getId();
-
-
-        return true;
+        
+        // 更新 customer_dept 表(业务扩展字段)
+        // 注意:部门名称、上级部门等基础信息存在 sys_dept 表,暂不支持修改
+        CustomerDept dept = new CustomerDept();
+        dept.setDeptId(bo.getDeptId());
+        dept.setCustomerId(bo.getCustomerId());
+        dept.setYearlyBudget(bo.getYearlyBudget());
+        dept.setMonthLimit(bo.getMonthLimit());
+        dept.setUsedBudget(bo.getUsedBudget());
+        dept.setBindStatus(bo.getBindStatus());
+        dept.setBindAddress(bo.getBindAddress());
+        
+        return baseMapper.updateById(dept) > 0;
     }
 
     @Override
     public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
-        return null;
+        if (CollUtil.isEmpty(ids)) {
+            return false;
+        }
+        return baseMapper.deleteBatchIds(ids) > 0;
     }
 }

+ 66 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/service/impl/CustomerInfoServiceImpl.java

@@ -53,6 +53,9 @@ public class CustomerInfoServiceImpl extends ServiceImpl<CustomerInfoMapper, Cus
     @DubboReference
     private RemoteComCompanyService remoteComCompanyService;
 
+    @DubboReference
+    private RemoteCreditLevelService remoteCreditLevelService;
+
     private final CustomerInfoMapper baseMapper;
     private final CustomerBusinessInfoMapper customerBusinessInfoMapper;
     private final CustomerInvoiceInfoMapper customerInvoiceInfoMapper;
@@ -385,6 +388,69 @@ public class CustomerInfoServiceImpl extends ServiceImpl<CustomerInfoMapper, Cus
         return vo;
     }
 
+    /**
+     * 消息发布-分页查询客户列表
+     *
+     * @param bo        查询条件
+     * @param pageQuery 分页参数
+     * @return 客户列表
+     */
+    @Override
+    public TableDataInfo<MessagePublishCustomerVo> queryMessagePublishCustomerPage(MessagePublishCustomerBo bo, PageQuery pageQuery) {
+        // 设置平台标识
+        if (StringUtils.isBlank(bo.getPlatformCode())) {
+            bo.setPlatformCode(PlatformContext.getPlatform());
+        }
+        Page<MessagePublishCustomerVo> page = baseMapper.selectMessagePublishCustomerPage(pageQuery.build(), bo);
+        return TableDataInfo.build(page);
+    }
+
+    /**
+     * 客户列表-分页查询
+     *
+     * @param bo        查询条件
+     * @param pageQuery 分页参数
+     * @return 客户列表
+     */
+    @Override
+    public TableDataInfo<CustomerListVo> queryCustomerListPage(CustomerListBo bo, PageQuery pageQuery) {
+        // 设置平台标识(暂时注释掉)
+        // if (StringUtils.isBlank(bo.getPlatformCode())) {
+        //     bo.setPlatformCode(PlatformContext.getPlatform());
+        // }
+        Page<CustomerListVo> page = baseMapper.selectCustomerListPage(pageQuery.build(), bo);
+
+        // 补充业务员名称和信用等级名称
+        List<CustomerListVo> records = page.getRecords();
+        if (CollUtil.isNotEmpty(records)) {
+            // 收集业务员ID
+            Set<Long> staffIds = records.stream()
+                .map(CustomerListVo::getSalesPersonId)
+                .filter(Objects::nonNull)
+                .collect(Collectors.toSet());
+
+            // 收集信用等级ID(从 credit_management_id 字段)
+            Set<Long> creditLevelIds = records.stream()
+                .map(CustomerListVo::getCreditLevelId)
+                .filter(Objects::nonNull)
+                .collect(Collectors.toSet());
+
+            // 批量查询业务员名称
+            if (!staffIds.isEmpty()) {
+                Map<Long, String> staffMap = remoteComStaffService.selectStaffNameByIds(staffIds);
+                records.forEach(vo -> vo.setSalesPersonName(staffMap.get(vo.getSalesPersonId())));
+            }
+
+            // 批量查询信用等级名称
+            if (!creditLevelIds.isEmpty()) {
+                Map<Long, String> creditLevelMap = remoteCreditLevelService.selectCreditLevelNameByIds(creditLevelIds);
+                records.forEach(vo -> vo.setCreditLevelName(creditLevelMap.get(vo.getCreditLevelId())));
+            }
+        }
+
+        return TableDataInfo.build(page);
+    }
+
     /**
      * 查询符合条件的客户信息列表
      *

+ 191 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/service/impl/CustomerProgramServiceImpl.java

@@ -0,0 +1,191 @@
+package org.dromara.customer.service.impl;
+
+import cn.hutool.core.collection.CollUtil;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import org.apache.dubbo.config.annotation.DubboReference;
+import org.dromara.common.core.utils.MapstructUtils;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.dromara.customer.domain.CustomerInfo;
+import org.dromara.customer.mapper.CustomerInfoMapper;
+import org.dromara.product.api.RemoteProductProgramService;
+import org.dromara.product.api.domain.ProductProgramApiVo;
+import org.springframework.stereotype.Service;
+import org.dromara.customer.domain.bo.CustomerProgramBo;
+import org.dromara.customer.domain.vo.CustomerProgramVo;
+import org.dromara.customer.domain.CustomerProgram;
+import org.dromara.customer.mapper.CustomerProgramMapper;
+import org.dromara.customer.service.ICustomerProgramService;
+
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * 客户专属方案关联Service业务层处理
+ *
+ * @author LionLi
+ * @date 2026-01-09
+ */
+@Slf4j
+@RequiredArgsConstructor
+@Service
+public class CustomerProgramServiceImpl extends ServiceImpl<CustomerProgramMapper, CustomerProgram> implements ICustomerProgramService {
+
+    private final CustomerProgramMapper baseMapper;
+    private final CustomerInfoMapper customerInfoMapper;
+
+    @DubboReference
+    private RemoteProductProgramService remoteProductProgramService;
+
+    /**
+     * 查询客户专属方案关联
+     *
+     * @param id 主键
+     * @return 客户专属方案关联
+     */
+    @Override
+    public CustomerProgramVo queryById(Long id){
+        return baseMapper.selectVoById(id);
+    }
+
+    /**
+     * 分页查询客户专属方案关联列表
+     *
+     * @param bo        查询条件
+     * @param pageQuery 分页参数
+     * @return 客户专属方案关联分页列表
+     */
+    @Override
+    public TableDataInfo<CustomerProgramVo> queryPageList(CustomerProgramBo bo, PageQuery pageQuery) {
+        LambdaQueryWrapper<CustomerProgram> lqw = buildQueryWrapper(bo);
+        Page<CustomerProgramVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
+
+        // 补充客户编号和方案信息
+        List<CustomerProgramVo> records = result.getRecords();
+        if (CollUtil.isNotEmpty(records)) {
+            // 收集客户ID
+            Set<Long> customerIds = records.stream()
+                .map(CustomerProgramVo::getCustomerId)
+                .filter(Objects::nonNull)
+                .collect(Collectors.toSet());
+
+            // 收集方案ID
+            Set<Long> programIds = records.stream()
+                .map(CustomerProgramVo::getProgramId)
+                .filter(Objects::nonNull)
+                .collect(Collectors.toSet());
+
+            // 查询客户编号
+            Map<Long, String> customerNoMap = Collections.emptyMap();
+            if (!customerIds.isEmpty()) {
+                List<CustomerInfo> customerList = customerInfoMapper.selectBatchIds(customerIds);
+                customerNoMap = customerList.stream().collect(Collectors.toMap(
+                    CustomerInfo::getId,
+                    CustomerInfo::getCustomerNo,
+                    (v1, v2) -> v1
+                ));
+            }
+
+            // 远程查询方案信息
+            Map<Long, ProductProgramApiVo> programMap = Collections.emptyMap();
+            if (!programIds.isEmpty()) {
+                programMap = remoteProductProgramService.selectProgramByIds(programIds);
+            }
+
+            // 回填数据
+            final Map<Long, String> finalCustomerNoMap = customerNoMap;
+            final Map<Long, ProductProgramApiVo> finalProgramMap = programMap;
+            records.forEach(vo -> {
+                vo.setCustomerNo(finalCustomerNoMap.get(vo.getCustomerId()));
+                ProductProgramApiVo programVo = finalProgramMap.get(vo.getProgramId());
+                if (programVo != null) {
+                    vo.setProgramNo(programVo.getProgramNo());
+                    vo.setProgramName(programVo.getTitle());
+                }
+            });
+        }
+
+        return TableDataInfo.build(result);
+    }
+
+    /**
+     * 查询符合条件的客户专属方案关联列表
+     *
+     * @param bo 查询条件
+     * @return 客户专属方案关联列表
+     */
+    @Override
+    public List<CustomerProgramVo> queryList(CustomerProgramBo bo) {
+        LambdaQueryWrapper<CustomerProgram> lqw = buildQueryWrapper(bo);
+        return baseMapper.selectVoList(lqw);
+    }
+
+    private LambdaQueryWrapper<CustomerProgram> buildQueryWrapper(CustomerProgramBo bo) {
+        Map<String, Object> params = bo.getParams();
+        LambdaQueryWrapper<CustomerProgram> lqw = Wrappers.lambdaQuery();
+        lqw.orderByAsc(CustomerProgram::getId);
+        lqw.eq(bo.getCustomerId() != null, CustomerProgram::getCustomerId, bo.getCustomerId());
+        lqw.eq(bo.getProgramId() != null, CustomerProgram::getProgramId, bo.getProgramId());
+        lqw.eq(StringUtils.isNotBlank(bo.getStatus()), CustomerProgram::getStatus, bo.getStatus());
+        lqw.eq(StringUtils.isNotBlank(bo.getPlatformCode()), CustomerProgram::getPlatformCode, bo.getPlatformCode());
+        return lqw;
+    }
+
+    /**
+     * 新增客户专属方案关联
+     *
+     * @param bo 客户专属方案关联
+     * @return 是否新增成功
+     */
+    @Override
+    public Boolean insertByBo(CustomerProgramBo bo) {
+        CustomerProgram add = MapstructUtils.convert(bo, CustomerProgram.class);
+        validEntityBeforeSave(add);
+        boolean flag = baseMapper.insert(add) > 0;
+        if (flag) {
+            bo.setId(add.getId());
+        }
+        return flag;
+    }
+
+    /**
+     * 修改客户专属方案关联
+     *
+     * @param bo 客户专属方案关联
+     * @return 是否修改成功
+     */
+    @Override
+    public Boolean updateByBo(CustomerProgramBo bo) {
+        CustomerProgram update = MapstructUtils.convert(bo, CustomerProgram.class);
+        validEntityBeforeSave(update);
+        return baseMapper.updateById(update) > 0;
+    }
+
+    /**
+     * 保存前的数据校验
+     */
+    private void validEntityBeforeSave(CustomerProgram entity){
+        //TODO 做一些数据校验,如唯一约束
+    }
+
+    /**
+     * 校验并批量删除客户专属方案关联信息
+     *
+     * @param ids     待删除的主键集合
+     * @param isValid 是否进行有效性校验
+     * @return 是否删除成功
+     */
+    @Override
+    public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
+        if(isValid){
+            //TODO 做一些业务上的校验,判断是否需要校验
+        }
+        return baseMapper.deleteByIds(ids) > 0;
+    }
+}

+ 46 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/service/impl/CustomerSalesInfoServiceImpl.java

@@ -144,4 +144,50 @@ public class CustomerSalesInfoServiceImpl extends ServiceImpl<CustomerSalesInfoM
         }
         return baseMapper.deleteByIds(ids) > 0;
     }
+
+    /**
+     * 分配业务人员
+     *
+     * @param customerId 客户ID
+     * @param salesPersonId 销售人员ID
+     * @param serviceStaffId 服务人员ID
+     * @return 是否成功
+     */
+    @Override
+    public Boolean assignStaff(Long customerId, Long salesPersonId, Long serviceStaffId) {
+        // 按 customerId 查询是否已有记录
+        CustomerSalesInfo existing = baseMapper.selectOne(
+            Wrappers.<CustomerSalesInfo>lambdaQuery()
+                .eq(CustomerSalesInfo::getCustomerId, customerId)
+        );
+        
+        if (existing != null) {
+            // 有记录,更新
+            existing.setSalesPersonId(salesPersonId);
+            existing.setServiceStaffId(serviceStaffId);
+            return baseMapper.updateById(existing) > 0;
+        } else {
+            // 没有记录,新增
+            CustomerSalesInfo salesInfo = new CustomerSalesInfo();
+            salesInfo.setCustomerId(customerId);
+            salesInfo.setSalesPersonId(salesPersonId);
+            salesInfo.setServiceStaffId(serviceStaffId);
+            salesInfo.setStatus("0");
+            return baseMapper.insert(salesInfo) > 0;
+        }
+    }
+
+    /**
+     * 根据客户ID查询销售信息
+     *
+     * @param customerId 客户ID
+     * @return 销售信息
+     */
+    @Override
+    public CustomerSalesInfoVo queryByCustomerId(Long customerId) {
+        return baseMapper.selectVoOne(
+            Wrappers.<CustomerSalesInfo>lambdaQuery()
+                .eq(CustomerSalesInfo::getCustomerId, customerId)
+        );
+    }
 }

+ 92 - 0
ruoyi-modules/ruoyi-customer/src/main/resources/mapper/customer/CustomerInfoMapper.xml

@@ -4,4 +4,96 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
 "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
 <mapper namespace="org.dromara.customer.mapper.CustomerInfoMapper">
 
+    <!-- 消息发布-分页查询客户列表 -->
+    <select id="selectMessagePublishCustomerPage" resultType="org.dromara.customer.domain.vo.MessagePublishCustomerVo">
+        SELECT
+            ci.id,
+            ci.customer_no AS customerNo,
+            ci.company_name AS companyName,
+            ci.industry_category_id AS industryCategoryId,
+            ic.industry_category_name AS industryCategoryName,
+            0 AS employeeCount,
+            csi.account_period AS accountPeriod,
+            csi.credit_amount AS creditAmount,
+            csi.remaining_quota AS remainingQuota,
+            csi.accounts_receivable AS accountsReceivable,
+            ci.status,
+            cc.phone AS phone
+        FROM customer_info ci
+        LEFT JOIN customer_sales_info csi ON ci.id = csi.customer_id AND csi.del_flag = '0'
+        LEFT JOIN industry_category ic ON ci.industry_category_id = ic.id AND ic.del_flag = '0'
+        LEFT JOIN customer_contact cc ON ci.id = cc.customer_id AND cc.is_primary = '0' AND cc.del_flag = '0'
+        <where>
+            ci.del_flag = '0'
+            <if test="bo.platformCode != null and bo.platformCode != ''">
+                AND ci.platform_code = #{bo.platformCode}
+            </if>
+            <if test="bo.industryCategoryId != null">
+                AND ci.industry_category_id = #{bo.industryCategoryId}
+            </if>
+            <if test="bo.customerLevelId != null">
+                AND ci.customer_level_id = #{bo.customerLevelId}
+            </if>
+            <if test="bo.enterpriseScaleId != null">
+                AND ci.enterprise_scale_id = #{bo.enterpriseScaleId}
+            </if>
+            <if test="bo.tagId != null">
+                AND ci.id IN (
+                    SELECT cit.customer_id FROM customer_info_tag cit
+                    WHERE cit.tag_id = #{bo.tagId}
+                    <if test="bo.platformCode != null and bo.platformCode != ''">
+                        AND cit.platform_code = #{bo.platformCode}
+                    </if>
+                )
+            </if>
+        </where>
+        ORDER BY ci.id DESC
+    </select>
+
+    <!-- 客户列表-分页查询 -->
+    <select id="selectCustomerListPage" resultType="org.dromara.customer.domain.vo.CustomerListVo">
+        SELECT
+            ci.id,
+            ci.customer_no AS customerNo,
+            IFNULL(ci.customer_name, ci.business_customer_name) AS customerName,
+            ci.industry_category_id AS industryCategoryId,
+            ic.industry_category_name AS industryName,
+            csi.credit_amount AS creditAmount,
+            csi.credit_management_id AS creditLevelId,
+            csi.accounts_receivable AS accountsReceivable,
+            csi.sales_person_id AS salesPersonId,
+            ci.status
+        FROM customer_info ci
+        LEFT JOIN customer_sales_info csi ON ci.id = csi.customer_id AND csi.del_flag = '0'
+        LEFT JOIN industry_category ic ON ci.industry_category_id = ic.id AND ic.del_flag = '0'
+        <where>
+            ci.del_flag = '0'
+            <if test="bo.platformCode != null and bo.platformCode != ''">
+                AND ci.platform_code = #{bo.platformCode}
+            </if>
+            <if test="bo.id != null">
+                AND ci.id = #{bo.id}
+            </if>
+            <if test="bo.customerNo != null and bo.customerNo != ''">
+                AND ci.customer_no LIKE CONCAT('%', #{bo.customerNo}, '%')
+            </if>
+            <if test="bo.industryCategoryId != null">
+                AND ci.industry_category_id = #{bo.industryCategoryId}
+            </if>
+            <if test="bo.customerLevelId != null">
+                AND ci.customer_level_id = #{bo.customerLevelId}
+            </if>
+            <if test="bo.salesPersonId != null">
+                AND csi.sales_person_id = #{bo.salesPersonId}
+            </if>
+            <if test="bo.yearSalesMin != null">
+                AND csi.credit_amount &gt;= #{bo.yearSalesMin}
+            </if>
+            <if test="bo.yearSalesMax != null">
+                AND csi.credit_amount &lt;= #{bo.yearSalesMax}
+            </if>
+        </where>
+        ORDER BY ci.id DESC
+    </select>
+
 </mapper>

+ 7 - 0
ruoyi-modules/ruoyi-customer/src/main/resources/mapper/customer/CustomerProgramMapper.xml

@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="org.dromara.customer.mapper.CustomerProgramMapper">
+
+</mapper>

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

@@ -114,6 +114,11 @@
             <artifactId>ruoyi-api-product</artifactId>
         </dependency>
 
+        <dependency>
+            <groupId>org.dromara</groupId>
+            <artifactId>ruoyi-api-customer</artifactId>
+        </dependency>
+
 
     </dependencies>
 

+ 2 - 0
ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/RuoyiProductApplication.java

@@ -1,11 +1,13 @@
 package org.dromara.product;
 
+import org.apache.dubbo.config.spring.context.annotation.EnableDubbo;
 import org.springframework.boot.SpringApplication;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
 import org.springframework.boot.context.metrics.buffering.BufferingApplicationStartup;
 
 import java.math.BigDecimal;
 
+@EnableDubbo
 @SpringBootApplication
 public class RuoyiProductApplication {
 

+ 12 - 0
ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/controller/ProductAssociateController.java

@@ -69,6 +69,18 @@ public class ProductAssociateController extends BaseController {
         return R.ok(productAssociateService.queryById(id));
     }
 
+    /**
+     * 根据产品ID获取关联信息
+     *
+     * @param productId 产品ID
+     */
+    //@SaCheckPermission("product:associate:query")
+    @GetMapping("/product/{productId}")
+    public R<ProductAssociateVo> getByProductId(@NotNull(message = "产品ID不能为空")
+                                     @PathVariable("productId") Long productId) {
+        return R.ok(productAssociateService.queryByProductId(productId));
+    }
+
     /**
      * 新增产品关联
      */

+ 26 - 3
ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/controller/ProductBaseController.java

@@ -191,7 +191,6 @@ public class ProductBaseController extends BaseController {
     public TableDataInfo<SiteProductVo> siteProductList(SiteProductBo bo, PageQuery pageQuery) {
         return productBaseService.querySiteProductPageList(bo, pageQuery);
     }
-
     /**
      * 商品审核
      *
@@ -207,11 +206,35 @@ public class ProductBaseController extends BaseController {
     }
 
     /**
-    * 商品上下架 状态变更
-    * */
+     * 商品上下架 状态变更
+     * */
     @PostMapping("/shelfReview")
     public R<Void> shelfReview(@Validated @RequestBody ProductBaseBo bo) {
         productBaseService.shelfReview(bo);
         return R.ok();
     }
+
+    /**
+     * 查询推荐商品列表(联表查询分类名称,用于客户运营-推荐商品页面)
+     */
+    @GetMapping("/recommendProduct/list")
+    public TableDataInfo<RecommendProductVo> recommendProductList(ProductBaseBo bo, PageQuery pageQuery) {
+        return productBaseService.queryRecommendProductPageList(bo, pageQuery);
+    }
+
+    /**
+     * 查询商品运营列表(联表查询,用于客户运营-商品运营页面)
+     */
+    @GetMapping("/operation/list")
+    public TableDataInfo<ProductOperationVo> operationList(ProductBaseBo bo, PageQuery pageQuery) {
+        return productBaseService.queryProductOperationPageList(bo, pageQuery);
+    }
+
+    /**
+     * 查询商品简化列表(用于选择弹窗,字段精简)
+     */
+    @GetMapping("/simpleList")
+    public TableDataInfo<ProductBaseSimpleVo> simpleList(ProductBaseBo bo, PageQuery pageQuery) {
+        return productBaseService.querySimplePageList(bo, pageQuery);
+    }
 }

+ 118 - 0
ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/controller/ProductOperationInfoController.java

@@ -0,0 +1,118 @@
+package org.dromara.product.controller;
+
+import java.util.List;
+
+import lombok.RequiredArgsConstructor;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.validation.constraints.*;
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.validation.annotation.Validated;
+import org.dromara.common.idempotent.annotation.RepeatSubmit;
+import org.dromara.common.log.annotation.Log;
+import org.dromara.common.web.core.BaseController;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.core.validate.AddGroup;
+import org.dromara.common.core.validate.EditGroup;
+import org.dromara.common.log.enums.BusinessType;
+import org.dromara.common.excel.utils.ExcelUtil;
+import org.dromara.product.domain.vo.ProductOperationInfoVo;
+import org.dromara.product.domain.bo.ProductOperationInfoBo;
+import org.dromara.product.service.IProductOperationInfoService;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+
+/**
+ * 产品运营信息
+ * 前端访问路由地址为:/product/operationInfo
+ *
+ * @author LionLi
+ * @date 2026-01-08
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/operationInfo")
+public class ProductOperationInfoController extends BaseController {
+
+    private final IProductOperationInfoService productOperationInfoService;
+
+    /**
+     * 查询产品运营信息列表
+     */
+    @SaCheckPermission("product:operationInfo:list")
+    @GetMapping("/list")
+    public TableDataInfo<ProductOperationInfoVo> list(ProductOperationInfoBo bo, PageQuery pageQuery) {
+        return productOperationInfoService.queryPageList(bo, pageQuery);
+    }
+
+    /**
+     * 导出产品运营信息列表
+     */
+    @SaCheckPermission("product:operationInfo:export")
+    @Log(title = "产品运营信息", businessType = BusinessType.EXPORT)
+    @PostMapping("/export")
+    public void export(ProductOperationInfoBo bo, HttpServletResponse response) {
+        List<ProductOperationInfoVo> list = productOperationInfoService.queryList(bo);
+        ExcelUtil.exportExcel(list, "产品运营信息", ProductOperationInfoVo.class, response);
+    }
+
+    /**
+     * 获取产品运营信息详细信息
+     *
+     * @param id 主键
+     */
+    @SaCheckPermission("product:operationInfo:query")
+    @GetMapping("/{id}")
+    public R<ProductOperationInfoVo> getInfo(@NotNull(message = "主键不能为空")
+                                     @PathVariable("id") Long id) {
+        return R.ok(productOperationInfoService.queryById(id));
+    }
+
+    /**
+     * 根据产品ID获取运营信息
+     *
+     * @param productId 产品ID
+     */
+    @SaCheckPermission("product:operationInfo:query")
+    @GetMapping("/product/{productId}")
+    public R<ProductOperationInfoVo> getByProductId(@NotNull(message = "产品ID不能为空")
+                                     @PathVariable("productId") Long productId) {
+        return R.ok(productOperationInfoService.queryByProductId(productId));
+    }
+
+    /**
+     * 新增产品运营信息
+     */
+    @SaCheckPermission("product:operationInfo:add")
+    @Log(title = "产品运营信息", businessType = BusinessType.INSERT)
+    @RepeatSubmit()
+    @PostMapping()
+    public R<Void> add(@Validated(AddGroup.class) @RequestBody ProductOperationInfoBo bo) {
+        return toAjax(productOperationInfoService.insertByBo(bo));
+    }
+
+    /**
+     * 修改产品运营信息
+     */
+    @SaCheckPermission("product:operationInfo:edit")
+    @Log(title = "产品运营信息", businessType = BusinessType.UPDATE)
+    @RepeatSubmit()
+    @PutMapping()
+    public R<Void> edit(@Validated(EditGroup.class) @RequestBody ProductOperationInfoBo bo) {
+        return toAjax(productOperationInfoService.updateByBo(bo));
+    }
+
+    /**
+     * 删除产品运营信息
+     *
+     * @param ids 主键串
+     */
+    @SaCheckPermission("product:operationInfo:remove")
+    @Log(title = "产品运营信息", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{ids}")
+    public R<Void> remove(@NotEmpty(message = "主键不能为空")
+                          @PathVariable("ids") Long[] ids) {
+        return toAjax(productOperationInfoService.deleteWithValidByIds(List.of(ids), true));
+    }
+}

+ 22 - 0
ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/controller/ProductProgramLinkController.java

@@ -1,6 +1,7 @@
 package org.dromara.product.controller;
 
 import java.util.List;
+import java.util.Map;
 
 import lombok.RequiredArgsConstructor;
 import jakarta.servlet.http.HttpServletResponse;
@@ -103,4 +104,25 @@ public class ProductProgramLinkController extends BaseController {
                           @PathVariable("ids") Long[] ids) {
         return toAjax(productProgramLinkService.deleteWithValidByIds(List.of(ids), true));
     }
+
+    /**
+     * 根据产品ID查询已关联的方案ID列表
+     */
+    @GetMapping("/product/{productId}")
+    public R<List<Long>> getProgramIdsByProductId(@PathVariable("productId") Long productId) {
+        return R.ok(productProgramLinkService.queryProgramIdsByProductId(productId));
+    }
+
+    /**
+     * 批量保存产品方案关联
+     */
+    @Log(title = "项目方案关联", businessType = BusinessType.UPDATE)
+    @PostMapping("/saveBatch")
+    public R<Void> saveBatch(@RequestBody Map<String, Object> params) {
+        Long productId = Long.valueOf(params.get("productId").toString());
+        List<Long> programIds = ((List<?>) params.get("programIds")).stream()
+            .map(id -> Long.valueOf(id.toString()))
+            .toList();
+        return toAjax(productProgramLinkService.saveBatch(productId, programIds));
+    }
 }

+ 8 - 0
ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/controller/ProductSpecsController.java

@@ -103,4 +103,12 @@ public class ProductSpecsController extends BaseController {
                           @PathVariable("ids") Long[] ids) {
         return toAjax(productSpecsService.deleteWithValidByIds(List.of(ids), true));
     }
+
+    /**
+     * 根据产品ID查询规格关联信息
+     */
+    @GetMapping("/product/{productId}")
+    public R<ProductSpecsVo> getByProductId(@PathVariable("productId") String productId) {
+        return R.ok(productSpecsService.queryByProductId(productId));
+    }
 }

+ 106 - 0
ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/controller/ProductWarehouseInventoryController.java

@@ -0,0 +1,106 @@
+package org.dromara.product.controller;
+
+import java.util.List;
+
+import lombok.RequiredArgsConstructor;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.validation.constraints.*;
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.validation.annotation.Validated;
+import org.dromara.common.idempotent.annotation.RepeatSubmit;
+import org.dromara.common.log.annotation.Log;
+import org.dromara.common.web.core.BaseController;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.core.validate.AddGroup;
+import org.dromara.common.core.validate.EditGroup;
+import org.dromara.common.log.enums.BusinessType;
+import org.dromara.common.excel.utils.ExcelUtil;
+import org.dromara.product.domain.vo.ProductWarehouseInventoryVo;
+import org.dromara.product.domain.bo.ProductWarehouseInventoryBo;
+import org.dromara.product.service.IProductWarehouseInventoryService;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+
+/**
+ * 仓库库存明细
+ * 前端访问路由地址为:/product/warehouseInventory
+ *
+ * @author LionLi
+ * @date 2025-12-31
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/warehouseInventory")
+public class ProductWarehouseInventoryController extends BaseController {
+
+    private final IProductWarehouseInventoryService productWarehouseInventoryService;
+
+    /**
+     * 查询仓库库存明细列表
+     */
+    @SaCheckPermission("product:warehouseInventory:list")
+    @GetMapping("/list")
+    public TableDataInfo<ProductWarehouseInventoryVo> list(ProductWarehouseInventoryBo bo, PageQuery pageQuery) {
+        return productWarehouseInventoryService.queryPageList(bo, pageQuery);
+    }
+
+    /**
+     * 导出仓库库存明细列表
+     */
+    @SaCheckPermission("product:warehouseInventory:export")
+    @Log(title = "仓库库存明细", businessType = BusinessType.EXPORT)
+    @PostMapping("/export")
+    public void export(ProductWarehouseInventoryBo bo, HttpServletResponse response) {
+        List<ProductWarehouseInventoryVo> list = productWarehouseInventoryService.queryList(bo);
+        ExcelUtil.exportExcel(list, "仓库库存明细", ProductWarehouseInventoryVo.class, response);
+    }
+
+    /**
+     * 获取仓库库存明细详细信息
+     *
+     * @param id 主键
+     */
+    @SaCheckPermission("product:warehouseInventory:query")
+    @GetMapping("/{id}")
+    public R<ProductWarehouseInventoryVo> getInfo(@NotNull(message = "主键不能为空")
+                                     @PathVariable("id") Long id) {
+        return R.ok(productWarehouseInventoryService.queryById(id));
+    }
+
+    /**
+     * 新增仓库库存明细
+     */
+    @SaCheckPermission("product:warehouseInventory:add")
+    @Log(title = "仓库库存明细", businessType = BusinessType.INSERT)
+    @RepeatSubmit()
+    @PostMapping()
+    public R<Void> add(@Validated(AddGroup.class) @RequestBody ProductWarehouseInventoryBo bo) {
+        return toAjax(productWarehouseInventoryService.insertByBo(bo));
+    }
+
+    /**
+     * 修改仓库库存明细
+     */
+    @SaCheckPermission("product:warehouseInventory:edit")
+    @Log(title = "仓库库存明细", businessType = BusinessType.UPDATE)
+    @RepeatSubmit()
+    @PutMapping()
+    public R<Void> edit(@Validated(EditGroup.class) @RequestBody ProductWarehouseInventoryBo bo) {
+        return toAjax(productWarehouseInventoryService.updateByBo(bo));
+    }
+
+    /**
+     * 删除仓库库存明细
+     *
+     * @param ids 主键串
+     */
+    @SaCheckPermission("product:warehouseInventory:remove")
+    @Log(title = "仓库库存明细", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{ids}")
+    public R<Void> remove(@NotEmpty(message = "主键不能为空")
+                          @PathVariable("ids") Long[] ids) {
+        return toAjax(productWarehouseInventoryService.deleteWithValidByIds(List.of(ids), true));
+    }
+}

+ 1 - 0
ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/domain/ProductExtend.java

@@ -160,6 +160,7 @@ public class ProductExtend extends TenantEntity {
     /**
      * 数据来源
      */
+    @TableField(exist = false)
     private String dataSource;
 
     /**

+ 82 - 0
ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/domain/ProductOperationInfo.java

@@ -0,0 +1,82 @@
+package org.dromara.product.domain;
+
+import org.dromara.common.tenant.core.TenantEntity;
+import com.baomidou.mybatisplus.annotation.*;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.io.Serial;
+
+/**
+ * 产品运营信息对象 product_operation_info
+ *
+ * @author LionLi
+ * @date 2026-01-08
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@TableName("product_operation_info")
+public class ProductOperationInfo extends TenantEntity {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 主键ID
+     */
+    @TableId(value = "id")
+    private Long id;
+
+    /**
+     * 产品ID,关联product_base.id
+     */
+    private Long productId;
+
+    /**
+     * 详细页标题
+     */
+    private String detailTitle;
+
+    /**
+     * 商品关键词,空格分隔
+     */
+    private String productKeywords;
+
+    /**
+     * 详细页描述
+     */
+    private String detailDescription;
+
+    /**
+     * 商品推荐标签,逗号分隔(recommend=推荐,hot=热销,special=特价,explosive=爆款)
+     */
+    private String productRecommend;
+
+    /**
+     * 适配价格区间,逗号分隔
+     */
+    private String priceInterval;
+
+    /**
+     * 标签TAG管理,逗号分隔
+     */
+    private String labelManage;
+
+    /**
+     * 状态(0正常 1停用)
+     */
+    private String status;
+
+    /**
+     * 删除标志(0代表存在 2代表删除)
+     */
+    @TableLogic
+    private String delFlag;
+
+    /**
+     * 备注
+     */
+    private String remark;
+
+
+}

+ 87 - 0
ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/domain/ProductWarehouseInventory.java

@@ -0,0 +1,87 @@
+package org.dromara.product.domain;
+
+import org.dromara.common.tenant.core.TenantEntity;
+import com.baomidou.mybatisplus.annotation.*;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.io.Serial;
+
+/**
+ * 仓库库存明细对象 product_warehouse_inventory
+ *
+ * @author LionLi
+ * @date 2025-12-31
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@TableName("product_warehouse_inventory")
+public class ProductWarehouseInventory extends TenantEntity {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * ID
+     */
+    @TableId(value = "id")
+    private Long id;
+
+    /**
+     * 商品ID
+     */
+    private Long productId;
+
+    /**
+     * SKU ID
+     */
+    private Long skuId;
+
+    /**
+     * 规格型号
+     */
+    private String specModel;
+
+    /**
+     * 仓库ID
+     */
+    private Long warehouseId;
+
+    /**
+     * 仓库编号
+     */
+    private String warehouseNo;
+
+    /**
+     * 仓库名称
+     */
+    private String warehouseName;
+
+    /**
+     * 可用库存
+     */
+    private Long nowInventory;
+
+    /**
+     * 锁定库存
+     */
+    private Long lockInventory;
+
+    /**
+     * 状态(0正常 1停用)
+     */
+    private String status;
+
+    /**
+     * 删除标志(0代表存在 2代表删除)
+     */
+    @TableLogic
+    private String delFlag;
+
+    /**
+     * 备注
+     */
+    private String remark;
+
+
+}

+ 4 - 0
ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/domain/bo/ProductBlacklistBo.java

@@ -48,5 +48,9 @@ public class ProductBlacklistBo extends BaseEntity {
     //@NotBlank(message = "备注不能为空", groups = { AddGroup.class, EditGroup.class })
     private String remark;
 
+    /**
+     * 黑名单类型:product=商品黑名单,category=分类黑名单
+     */
+    private String blacklistType;
 
 }

+ 82 - 0
ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/domain/bo/ProductOperationInfoBo.java

@@ -0,0 +1,82 @@
+package org.dromara.product.domain.bo;
+
+import org.dromara.product.domain.ProductOperationInfo;
+import org.dromara.common.mybatis.core.domain.BaseEntity;
+import org.dromara.common.core.validate.AddGroup;
+import org.dromara.common.core.validate.EditGroup;
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import jakarta.validation.constraints.*;
+
+/**
+ * 产品运营信息业务对象 product_operation_info
+ *
+ * @author LionLi
+ * @date 2026-01-08
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@AutoMapper(target = ProductOperationInfo.class, reverseConvertGenerate = false)
+public class ProductOperationInfoBo extends BaseEntity {
+
+    /**
+     * 主键ID
+     */
+    private Long id;
+
+    /**
+     * 产品ID,关联product_base.id
+     */
+    private Long productId;
+
+    /**
+     * 详细页标题
+     */
+//    @NotBlank(message = "详细页标题不能为空", groups = { AddGroup.class, EditGroup.class })
+    private String detailTitle;
+
+    /**
+     * 商品关键词,空格分隔
+     */
+//    @NotBlank(message = "商品关键词,空格分隔不能为空", groups = { AddGroup.class, EditGroup.class })
+    private String productKeywords;
+
+    /**
+     * 详细页描述
+     */
+//    @NotBlank(message = "详细页描述不能为空", groups = { AddGroup.class, EditGroup.class })
+    private String detailDescription;
+
+    /**
+     * 商品推荐标签,逗号分隔(recommend=推荐,hot=热销,special=特价,explosive=爆款)
+     */
+//    @NotBlank(message = "商品推荐标签,逗号分隔(recommend=推荐,hot=热销,special=特价,explosive=爆款)不能为空", groups = { AddGroup.class, EditGroup.class })
+    private String productRecommend;
+
+    /**
+     * 适配价格区间,逗号分隔
+     */
+//    @NotBlank(message = "适配价格区间,逗号分隔不能为空", groups = { AddGroup.class, EditGroup.class })
+    private String priceInterval;
+
+    /**
+     * 标签TAG管理,逗号分隔
+     */
+//    @NotBlank(message = "标签TAG管理,逗号分隔不能为空", groups = { AddGroup.class, EditGroup.class })
+    private String labelManage;
+
+    /**
+     * 状态(0正常 1停用)
+     */
+//    @NotBlank(message = "状态(0正常 1停用)不能为空", groups = { AddGroup.class, EditGroup.class })
+    private String status;
+
+    /**
+     * 备注
+     */
+//    @NotBlank(message = "备注不能为空", groups = { AddGroup.class, EditGroup.class })
+    private String remark;
+
+
+}

+ 87 - 0
ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/domain/bo/ProductWarehouseInventoryBo.java

@@ -0,0 +1,87 @@
+package org.dromara.product.domain.bo;
+
+import org.dromara.product.domain.ProductWarehouseInventory;
+import org.dromara.common.mybatis.core.domain.BaseEntity;
+import org.dromara.common.core.validate.AddGroup;
+import org.dromara.common.core.validate.EditGroup;
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import jakarta.validation.constraints.*;
+
+/**
+ * 仓库库存明细业务对象 product_warehouse_inventory
+ *
+ * @author LionLi
+ * @date 2025-12-31
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@AutoMapper(target = ProductWarehouseInventory.class, reverseConvertGenerate = false)
+public class ProductWarehouseInventoryBo extends BaseEntity {
+
+    /**
+     * ID
+     */
+    private Long id;
+
+    /**
+     * 商品ID
+     */
+    private Long productId;
+
+    /**
+     * SKU ID
+     */
+//    @NotNull(message = "SKU ID不能为空", groups = { AddGroup.class, EditGroup.class })
+    private Long skuId;
+
+    /**
+     * 规格型号
+     */
+//    @NotBlank(message = "规格型号不能为空", groups = { AddGroup.class, EditGroup.class })
+    private String specModel;
+
+    /**
+     * 仓库ID
+     */
+    private Long warehouseId;
+
+    /**
+     * 仓库编号
+     */
+//    @NotBlank(message = "仓库编号不能为空", groups = { AddGroup.class, EditGroup.class })
+    private String warehouseNo;
+
+    /**
+     * 仓库名称
+     */
+//    @NotBlank(message = "仓库名称不能为空", groups = { AddGroup.class, EditGroup.class })
+    private String warehouseName;
+
+    /**
+     * 可用库存
+     */
+//    @NotNull(message = "可用库存不能为空", groups = { AddGroup.class, EditGroup.class })
+    private Long nowInventory;
+
+    /**
+     * 锁定库存
+     */
+//    @NotNull(message = "锁定库存不能为空", groups = { AddGroup.class, EditGroup.class })
+    private Long lockInventory;
+
+    /**
+     * 状态(0正常 1停用)
+     */
+//    @NotBlank(message = "状态(0正常 1停用)不能为空", groups = { AddGroup.class, EditGroup.class })
+    private String status;
+
+    /**
+     * 备注
+     */
+//    @NotBlank(message = "备注不能为空", groups = { AddGroup.class, EditGroup.class })
+    private String remark;
+
+
+}

+ 115 - 0
ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/domain/vo/ProductBaseSimpleVo.java

@@ -0,0 +1,115 @@
+package org.dromara.product.domain.vo;
+
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.math.BigDecimal;
+
+/**
+ * 产品基础信息简化视图对象(用于选择列表)
+ *
+ * @author LionLi
+ * @date 2025-01-09
+ */
+@Data
+public class ProductBaseSimpleVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 主键ID
+     */
+    private Long id;
+
+    /**
+     * 产品编号
+     */
+    private String productNo;
+
+    /**
+     * 产品名称
+     */
+    private String itemName;
+
+    /**
+     * 产品图片
+     */
+    private String productImage;
+
+    /**
+     * 市场价
+     */
+    private BigDecimal marketPrice;
+
+    /**
+     * 会员价格
+     */
+    private BigDecimal memberPrice;
+
+    /**
+     * 规格型号
+     */
+    private String specification;
+
+    /**
+     * 顶级分类id
+     */
+    private Long topCategoryId;
+
+    /**
+     * 顶级分类名称
+     */
+    private String topCategoryName;
+
+    /**
+     * 中级分类id
+     */
+    private Long mediumCategoryId;
+
+    /**
+     * 中级分类名称
+     */
+    private String mediumCategoryName;
+
+    /**
+     * 底层分类id
+     */
+    private Long bottomCategoryId;
+
+    /**
+     * 底层分类名称
+     */
+    private String bottomCategoryName;
+
+    /**
+     * 首页推荐:1=推荐,0=不推荐
+     */
+    private String homeRecommended;
+
+    /**
+     * 分类推荐:1=推荐,0=不推荐
+     */
+    private String categoryRecommendation;
+
+    /**
+     * 购物车推荐:1=推荐,0=不推荐
+     */
+    private String cartRecommendation;
+
+    /**
+     * 推荐产品顺序
+     */
+    private Long recommendedProductOrder;
+
+    /**
+     * 是否热门:1=是,0=否
+     */
+    private String isPopular;
+
+    /**
+     * 商品状态:1=上架,0=下架
+     */
+    private String productStatus;
+}

+ 39 - 6
ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/domain/vo/ProductBlacklistVo.java

@@ -37,24 +37,57 @@ public class ProductBlacklistVo implements Serializable {
     /**
      * 客户id(可为空,表示全局黑名单)
      */
-    @ExcelProperty(value = "客户id", converter = ExcelDictConvert.class)
-    @ExcelDictFormat(readConverterExp = "可=为空,表示全局黑名单")
+    @ExcelProperty(value = "客户ID")
     private Long customerId;
 
+    /**
+     * 客户编号
+     */
+    @ExcelProperty(value = "客户编号")
+    private String customerNo;
+
+    /**
+     * 客户名称
+     */
+    @ExcelProperty(value = "客户名称")
+    private String customerName;
+
     /**
      * 产品id(可为空,表示该客户对整个分类禁用)
      */
-    @ExcelProperty(value = "产品id", converter = ExcelDictConvert.class)
-    @ExcelDictFormat(readConverterExp = "可=为空,表示该客户对整个分类禁用")
+    @ExcelProperty(value = "商品ID")
     private Long productId;
 
+    /**
+     * 商品编号
+     */
+    @ExcelProperty(value = "商品编号")
+    private String productNo;
+
+    /**
+     * 商品名称
+     */
+    @ExcelProperty(value = "商品名称")
+    private String productName;
+
     /**
      * 产品分类id(必填)
      */
-    @ExcelProperty(value = "产品分类id", converter = ExcelDictConvert.class)
-    @ExcelDictFormat(readConverterExp = "必=填")
+    @ExcelProperty(value = "分类ID")
     private Long productCategoryId;
 
+    /**
+     * 分类编号
+     */
+    @ExcelProperty(value = "分类编号")
+    private String categoryNo;
+
+    /**
+     * 分类名称
+     */
+    @ExcelProperty(value = "分类名称")
+    private String categoryName;
+
     /**
      * 备注
      */

+ 94 - 0
ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/domain/vo/ProductOperationInfoVo.java

@@ -0,0 +1,94 @@
+package org.dromara.product.domain.vo;
+
+import org.dromara.product.domain.ProductOperationInfo;
+import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
+import cn.idev.excel.annotation.ExcelProperty;
+import org.dromara.common.excel.annotation.ExcelDictFormat;
+import org.dromara.common.excel.convert.ExcelDictConvert;
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.Date;
+
+
+
+/**
+ * 产品运营信息视图对象 product_operation_info
+ *
+ * @author LionLi
+ * @date 2026-01-08
+ */
+@Data
+@ExcelIgnoreUnannotated
+@AutoMapper(target = ProductOperationInfo.class)
+public class ProductOperationInfoVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 主键ID
+     */
+    @ExcelProperty(value = "主键ID")
+    private Long id;
+
+    /**
+     * 产品ID,关联product_base.id
+     */
+    @ExcelProperty(value = "产品ID,关联product_base.id")
+    private Long productId;
+
+    /**
+     * 详细页标题
+     */
+    @ExcelProperty(value = "详细页标题")
+    private String detailTitle;
+
+    /**
+     * 商品关键词,空格分隔
+     */
+    @ExcelProperty(value = "商品关键词,空格分隔")
+    private String productKeywords;
+
+    /**
+     * 详细页描述
+     */
+    @ExcelProperty(value = "详细页描述")
+    private String detailDescription;
+
+    /**
+     * 商品推荐标签,逗号分隔(recommend=推荐,hot=热销,special=特价,explosive=爆款)
+     */
+    @ExcelProperty(value = "商品推荐标签,逗号分隔", converter = ExcelDictConvert.class)
+    @ExcelDictFormat(readConverterExp = "r=ecommend=推荐,hot=热销,special=特价,explosive=爆款")
+    private String productRecommend;
+
+    /**
+     * 适配价格区间,逗号分隔
+     */
+    @ExcelProperty(value = "适配价格区间,逗号分隔")
+    private String priceInterval;
+
+    /**
+     * 标签TAG管理,逗号分隔
+     */
+    @ExcelProperty(value = "标签TAG管理,逗号分隔")
+    private String labelManage;
+
+    /**
+     * 状态(0正常 1停用)
+     */
+    @ExcelProperty(value = "状态", converter = ExcelDictConvert.class)
+    @ExcelDictFormat(readConverterExp = "0=正常,1=停用")
+    private String status;
+
+    /**
+     * 备注
+     */
+    @ExcelProperty(value = "备注")
+    private String remark;
+
+
+}

+ 85 - 0
ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/domain/vo/ProductOperationVo.java

@@ -0,0 +1,85 @@
+package org.dromara.product.domain.vo;
+
+import lombok.Data;
+import java.io.Serial;
+import java.io.Serializable;
+import java.math.BigDecimal;
+
+/**
+ * 商品运营VO
+ */
+@Data
+public class ProductOperationVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /** 主键ID */
+    private Long id;
+
+    /** 产品编号 */
+    private String productNo;
+
+    /** 商品名称 */
+    private String itemName;
+
+    /** 商品图片 */
+    private String productImage;
+
+    /** 品牌ID */
+    private Long brandId;
+
+    /** 品牌名称 */
+    private String brandName;
+
+    /** 规格 */
+    private String specification;
+
+    /** 顶级分类ID */
+    private Long topCategoryId;
+
+    /** 顶级分类名称 */
+    private String topCategoryName;
+
+    /** 中级分类ID */
+    private Long mediumCategoryId;
+
+    /** 中级分类名称 */
+    private String mediumCategoryName;
+
+    /** 底层分类ID */
+    private Long bottomCategoryId;
+
+    /** 底层分类名称 */
+    private String bottomCategoryName;
+
+    /** 单位名称 */
+    private String unitName;
+
+    /** 起订量 */
+    private Long minOrderQuantity;
+
+    /** 市场价 */
+    private BigDecimal marketPrice;
+
+    /** 会员价 */
+    private BigDecimal memberPrice;
+
+    /** 最低售价 */
+    private BigDecimal minSellingPrice;
+
+    /** 供应商数量 */
+    private Integer supplierCount;
+
+    /** 库存总数 */
+    private Long totalInventory;
+
+    /** 可销库存 */
+    private Long nowInventory;
+
+    /** 虚拟库存 */
+    private Long virtualInventory;
+
+    /** 商品状态 */
+    private String productStatus;
+}

+ 111 - 0
ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/domain/vo/ProductWarehouseInventoryVo.java

@@ -0,0 +1,111 @@
+package org.dromara.product.domain.vo;
+
+import org.dromara.product.domain.ProductWarehouseInventory;
+import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
+import cn.idev.excel.annotation.ExcelProperty;
+import org.dromara.common.excel.annotation.ExcelDictFormat;
+import org.dromara.common.excel.convert.ExcelDictConvert;
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.Date;
+
+
+
+/**
+ * 仓库库存明细视图对象 product_warehouse_inventory
+ *
+ * @author LionLi
+ * @date 2025-12-31
+ */
+@Data
+@ExcelIgnoreUnannotated
+@AutoMapper(target = ProductWarehouseInventory.class)
+public class ProductWarehouseInventoryVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * ID
+     */
+    @ExcelProperty(value = "ID")
+    private Long id;
+
+    /**
+     * 商品ID
+     */
+    @ExcelProperty(value = "商品ID")
+    private Long productId;
+
+    /**
+     * 商品编号
+     */
+    @ExcelProperty(value = "商品编号")
+    private String productNo;
+
+    /**
+     * 商品名称
+     */
+    @ExcelProperty(value = "商品名称")
+    private String productName;
+
+    /**
+     * SKU ID
+     */
+    @ExcelProperty(value = "SKU ID")
+    private Long skuId;
+
+    /**
+     * 规格型号
+     */
+    @ExcelProperty(value = "规格型号")
+    private String specModel;
+
+    /**
+     * 仓库ID
+     */
+    @ExcelProperty(value = "仓库ID")
+    private Long warehouseId;
+
+    /**
+     * 仓库编号
+     */
+    @ExcelProperty(value = "仓库编号")
+    private String warehouseNo;
+
+    /**
+     * 仓库名称
+     */
+    @ExcelProperty(value = "仓库名称")
+    private String warehouseName;
+
+    /**
+     * 可用库存
+     */
+    @ExcelProperty(value = "可用库存")
+    private Long nowInventory;
+
+    /**
+     * 锁定库存
+     */
+    @ExcelProperty(value = "锁定库存")
+    private Long lockInventory;
+
+    /**
+     * 状态(0正常 1停用)
+     */
+    @ExcelProperty(value = "状态", converter = ExcelDictConvert.class)
+    @ExcelDictFormat(readConverterExp = "0=正常,1=停用")
+    private String status;
+
+    /**
+     * 备注
+     */
+    @ExcelProperty(value = "备注")
+    private String remark;
+
+
+}

+ 64 - 0
ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/domain/vo/RecommendProductVo.java

@@ -0,0 +1,64 @@
+package org.dromara.product.domain.vo;
+
+import lombok.Data;
+import java.io.Serial;
+import java.io.Serializable;
+import java.math.BigDecimal;
+
+/**
+ * 推荐商品视图对象(精简版)
+ *
+ * @author yoe
+ * @date 2026-01-08
+ */
+@Data
+public class RecommendProductVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /** 主键ID */
+    private Long id;
+
+    /** 产品编号 */
+    private String productNo;
+
+    /** 产品名称 */
+    private String itemName;
+
+    /** 产品图片URL */
+    private String productImage;
+
+    /** 一级分类名称 */
+    private String topCategoryName;
+
+    /** 二级分类名称 */
+    private String mediumCategoryName;
+
+    /** 三级分类名称 */
+    private String bottomCategoryName;
+
+    /** 平台价(最低销售价) */
+    private BigDecimal minSellingPrice;
+
+    /** 市场价 */
+    private BigDecimal marketPrice;
+
+    /** 首页推荐:1=推荐,0=不推荐 */
+    private String homeRecommended;
+
+    /** 分类推荐:1=推荐,0=不推荐 */
+    private String categoryRecommendation;
+
+    /** 购物车推荐:1=推荐,0=不推荐 */
+    private String cartRecommendation;
+
+    /** 是否爆款:1=是,0=否 */
+    private String isPopular;
+
+    /** 推荐排序 */
+    private Long recommendedProductOrder;
+
+    /** 商品状态:1=上架,0=下架 */
+    private String productStatus;
+}

+ 57 - 0
ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/dubbo/RemoteProductProgramServiceImpl.java

@@ -0,0 +1,57 @@
+package org.dromara.product.dubbo;
+
+import cn.hutool.core.collection.CollUtil;
+import lombok.RequiredArgsConstructor;
+import org.apache.dubbo.config.annotation.DubboService;
+import org.dromara.product.api.RemoteProductProgramService;
+import org.dromara.product.api.domain.ProductProgramApiVo;
+import org.dromara.product.domain.ProductProgram;
+import org.dromara.product.mapper.ProductProgramMapper;
+import org.springframework.stereotype.Service;
+
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * 产品方案远程服务实现
+ *
+ * @author LionLi
+ * @date 2026-01-09
+ */
+@RequiredArgsConstructor
+@Service
+@DubboService
+public class RemoteProductProgramServiceImpl implements RemoteProductProgramService {
+
+    private final ProductProgramMapper productProgramMapper;
+
+    @Override
+    public Map<Long, ProductProgramApiVo> selectProgramByIds(Set<Long> ids) {
+        if (CollUtil.isEmpty(ids)) {
+            return Collections.emptyMap();
+        }
+        List<ProductProgram> list = productProgramMapper.selectBatchIds(ids);
+        return list.stream().collect(Collectors.toMap(
+            ProductProgram::getId,
+            this::convertToApiVo,
+            (v1, v2) -> v1
+        ));
+    }
+
+    @Override
+    public List<ProductProgramApiVo> selectAllProgramList() {
+        List<ProductProgram> list = productProgramMapper.selectList(null);
+        if (CollUtil.isEmpty(list)) {
+            return Collections.emptyList();
+        }
+        return list.stream().map(this::convertToApiVo).collect(Collectors.toList());
+    }
+
+    private ProductProgramApiVo convertToApiVo(ProductProgram program) {
+        ProductProgramApiVo vo = new ProductProgramApiVo();
+        vo.setId(program.getId());
+        vo.setProgramNo(program.getProgramNo());
+        vo.setTitle(program.getTitle());
+        return vo;
+    }
+}

+ 32 - 0
ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/mapper/ProductBaseMapper.java

@@ -7,8 +7,12 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import org.apache.ibatis.annotations.Param;
 import org.apache.ibatis.cursor.Cursor;
 import org.dromara.product.domain.ProductBase;
+import org.dromara.product.domain.bo.ProductBaseBo;
 import org.dromara.product.domain.bo.SiteProductBo;
 import org.dromara.product.domain.vo.ProductBaseVo;
+import org.dromara.product.domain.vo.ProductBaseSimpleVo;
+import org.dromara.product.domain.vo.ProductOperationVo;
+import org.dromara.product.domain.vo.RecommendProductVo;
 import org.dromara.product.domain.vo.SiteProductVo;
 import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
 import org.dromara.product.domain.vo.StatusCountVo;
@@ -32,6 +36,33 @@ public interface ProductBaseMapper extends BaseMapperPlus<ProductBase, ProductBa
      */
     Page<SiteProductVo> selectSiteProductPage(@Param("page") Page<SiteProductVo> page, @Param("bo") SiteProductBo bo);
 
+    /**
+     * 分页查询推荐商品列表(联表查询分类名称)
+     *
+     * @param page 分页参数
+     * @param bo   查询条件
+     * @return 推荐商品分页列表
+     */
+    Page<RecommendProductVo> selectRecommendProductPage(@Param("page") Page<RecommendProductVo> page, @Param("bo") ProductBaseBo bo);
+
+    /**
+     * 分页查询商品运营列表(联表查询)
+     *
+     * @param page 分页参数
+     * @param bo   查询条件
+     * @return 商品运营分页列表
+     */
+    Page<ProductOperationVo> selectProductOperationPage(@Param("page") Page<ProductOperationVo> page, @Param("bo") ProductBaseBo bo);
+
+    /**
+     * 分页查询商品简化列表(用于选择弹窗)
+     *
+     * @param page 分页参数
+     * @param bo   查询条件
+     * @return 商品简化分页列表
+     */
+    Page<ProductBaseSimpleVo> selectSimplePage(@Param("page") Page<ProductBaseSimpleVo> page, @Param("bo") ProductBaseBo bo);
+
     /**
      * 查询所有产品列表(联表查询)
      * 注意:此方法会一次性加载所有数据到内存,不适合大数据量场景
@@ -67,4 +98,5 @@ public interface ProductBaseMapper extends BaseMapperPlus<ProductBase, ProductBa
      * @return 商品状态统计信息
      */
     StatusCountVo selectProductStatusCount();
+
 }

+ 8 - 0
ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/mapper/ProductBlacklistMapper.java

@@ -1,6 +1,9 @@
 package org.dromara.product.mapper;
 
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import org.apache.ibatis.annotations.Param;
 import org.dromara.product.domain.ProductBlacklist;
+import org.dromara.product.domain.bo.ProductBlacklistBo;
 import org.dromara.product.domain.vo.ProductBlacklistVo;
 import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
 
@@ -12,4 +15,9 @@ import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
  */
 public interface ProductBlacklistMapper extends BaseMapperPlus<ProductBlacklist, ProductBlacklistVo> {
 
+    /**
+     * 分页查询黑名单列表(关联商品和分类)
+     */
+    Page<ProductBlacklistVo> selectBlacklistPage(@Param("page") Page<ProductBlacklistVo> page,
+                                                  @Param("bo") ProductBlacklistBo bo);
 }

+ 15 - 0
ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/mapper/ProductOperationInfoMapper.java

@@ -0,0 +1,15 @@
+package org.dromara.product.mapper;
+
+import org.dromara.product.domain.ProductOperationInfo;
+import org.dromara.product.domain.vo.ProductOperationInfoVo;
+import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
+
+/**
+ * 产品运营信息Mapper接口
+ *
+ * @author LionLi
+ * @date 2026-01-08
+ */
+public interface ProductOperationInfoMapper extends BaseMapperPlus<ProductOperationInfo, ProductOperationInfoVo> {
+
+}

+ 30 - 0
ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/mapper/ProductWarehouseInventoryMapper.java

@@ -0,0 +1,30 @@
+package org.dromara.product.mapper;
+
+import org.apache.ibatis.annotations.Param;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.product.domain.ProductWarehouseInventory;
+import org.dromara.product.domain.bo.ProductWarehouseInventoryBo;
+import org.dromara.product.domain.vo.ProductWarehouseInventoryVo;
+import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+
+import java.util.List;
+
+/**
+ * 仓库库存明细Mapper接口
+ *
+ * @author LionLi
+ * @date 2025-12-31
+ */
+public interface ProductWarehouseInventoryMapper extends BaseMapperPlus<ProductWarehouseInventory, ProductWarehouseInventoryVo> {
+
+    /**
+     * 查询仓库库存明细列表(关联商品信息)
+     */
+    Page<ProductWarehouseInventoryVo> selectWarehouseInventoryList(Page<ProductWarehouseInventoryVo> page, @Param("bo") ProductWarehouseInventoryBo bo);
+
+    /**
+     * 查询仓库库存明细列表(不分页)
+     */
+    List<ProductWarehouseInventoryVo> selectWarehouseInventoryList(@Param("bo") ProductWarehouseInventoryBo bo);
+}

+ 8 - 0
ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/service/IProductAssociateService.java

@@ -26,6 +26,14 @@ public interface IProductAssociateService extends IService<ProductAssociate>{
      */
     ProductAssociateVo queryById(Long id);
 
+    /**
+     * 根据产品ID查询关联信息
+     *
+     * @param productId 产品ID
+     * @return 产品关联
+     */
+    ProductAssociateVo queryByProductId(Long productId);
+
     /**
      * 分页查询产品关联列表
      *

+ 30 - 1
ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/service/IProductBaseService.java

@@ -1,9 +1,11 @@
 package org.dromara.product.service;
 
 import com.baomidou.mybatisplus.extension.service.IService;
-import org.dromara.product.api.domain.ProductVo;
 import org.dromara.product.domain.ProductBase;
 import org.dromara.product.domain.vo.ProductBaseVo;
+import org.dromara.product.domain.vo.ProductBaseSimpleVo;
+import org.dromara.product.domain.vo.ProductOperationVo;
+import org.dromara.product.domain.vo.RecommendProductVo;
 import org.dromara.product.domain.vo.SiteProductVo;
 import org.dromara.product.domain.bo.ProductBaseBo;
 import org.dromara.product.domain.bo.SiteProductBo;
@@ -88,6 +90,33 @@ public interface IProductBaseService extends IService<ProductBase>{
      */
     ProductBaseVo updateProductShelfState(Long productId);
 
+    /**
+     * 分页查询推荐商品列表(联表查询分类名称)
+     *
+     * @param bo        查询条件
+     * @param pageQuery 分页参数
+     * @return 推荐商品分页列表
+     */
+    TableDataInfo<RecommendProductVo> queryRecommendProductPageList(ProductBaseBo bo, PageQuery pageQuery);
+
+    /**
+     * 分页查询商品运营列表(联表查询)
+     *
+     * @param bo        查询条件
+     * @param pageQuery 分页参数
+     * @return 商品运营分页列表
+     */
+    TableDataInfo<ProductOperationVo> queryProductOperationPageList(ProductBaseBo bo, PageQuery pageQuery);
+
+    /**
+     * 分页查询商品简化列表(用于选择弹窗)
+     *
+     * @param bo        查询条件
+     * @param pageQuery 分页参数
+     * @return 商品简化分页列表
+     */
+    TableDataInfo<ProductBaseSimpleVo> querySimplePageList(ProductBaseBo bo, PageQuery pageQuery);
+
     /**
      * 获取商品状态数量
      *

+ 78 - 0
ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/service/IProductOperationInfoService.java

@@ -0,0 +1,78 @@
+package org.dromara.product.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import org.dromara.product.domain.ProductOperationInfo;
+import org.dromara.product.domain.vo.ProductOperationInfoVo;
+import org.dromara.product.domain.bo.ProductOperationInfoBo;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.mybatis.core.page.PageQuery;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * 产品运营信息Service接口
+ *
+ * @author LionLi
+ * @date 2026-01-08
+ */
+public interface IProductOperationInfoService extends IService<ProductOperationInfo>{
+
+    /**
+     * 查询产品运营信息
+     *
+     * @param id 主键
+     * @return 产品运营信息
+     */
+    ProductOperationInfoVo queryById(Long id);
+
+    /**
+     * 根据产品ID查询运营信息
+     *
+     * @param productId 产品ID
+     * @return 产品运营信息
+     */
+    ProductOperationInfoVo queryByProductId(Long productId);
+
+    /**
+     * 分页查询产品运营信息列表
+     *
+     * @param bo        查询条件
+     * @param pageQuery 分页参数
+     * @return 产品运营信息分页列表
+     */
+    TableDataInfo<ProductOperationInfoVo> queryPageList(ProductOperationInfoBo bo, PageQuery pageQuery);
+
+    /**
+     * 查询符合条件的产品运营信息列表
+     *
+     * @param bo 查询条件
+     * @return 产品运营信息列表
+     */
+    List<ProductOperationInfoVo> queryList(ProductOperationInfoBo bo);
+
+    /**
+     * 新增产品运营信息
+     *
+     * @param bo 产品运营信息
+     * @return 是否新增成功
+     */
+    Boolean insertByBo(ProductOperationInfoBo bo);
+
+    /**
+     * 修改产品运营信息
+     *
+     * @param bo 产品运营信息
+     * @return 是否修改成功
+     */
+    Boolean updateByBo(ProductOperationInfoBo bo);
+
+    /**
+     * 校验并批量删除产品运营信息信息
+     *
+     * @param ids     待删除的主键集合
+     * @param isValid 是否进行有效性校验
+     * @return 是否删除成功
+     */
+    Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
+}

+ 17 - 0
ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/service/IProductProgramLinkService.java

@@ -67,4 +67,21 @@ public interface IProductProgramLinkService extends IService<ProductProgramLink>
      * @return 是否删除成功
      */
     Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
+
+    /**
+     * 根据产品ID查询已关联的方案ID列表
+     *
+     * @param productId 产品ID
+     * @return 方案ID列表
+     */
+    List<Long> queryProgramIdsByProductId(Long productId);
+
+    /**
+     * 批量保存产品方案关联(先删后增)
+     *
+     * @param productId  产品ID
+     * @param programIds 方案ID列表
+     * @return 是否成功
+     */
+    Boolean saveBatch(Long productId, List<Long> programIds);
 }

+ 8 - 0
ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/service/IProductSpecsService.java

@@ -67,4 +67,12 @@ public interface IProductSpecsService extends IService<ProductSpecs>{
      * @return 是否删除成功
      */
     Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
+
+    /**
+     * 根据产品ID查询规格关联信息
+     *
+     * @param productId 产品ID
+     * @return 规格关联信息
+     */
+    ProductSpecsVo queryByProductId(String productId);
 }

+ 70 - 0
ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/service/IProductWarehouseInventoryService.java

@@ -0,0 +1,70 @@
+package org.dromara.product.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import org.dromara.product.domain.ProductWarehouseInventory;
+import org.dromara.product.domain.vo.ProductWarehouseInventoryVo;
+import org.dromara.product.domain.bo.ProductWarehouseInventoryBo;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.mybatis.core.page.PageQuery;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * 仓库库存明细Service接口
+ *
+ * @author LionLi
+ * @date 2025-12-31
+ */
+public interface IProductWarehouseInventoryService extends IService<ProductWarehouseInventory>{
+
+    /**
+     * 查询仓库库存明细
+     *
+     * @param id 主键
+     * @return 仓库库存明细
+     */
+    ProductWarehouseInventoryVo queryById(Long id);
+
+    /**
+     * 分页查询仓库库存明细列表
+     *
+     * @param bo        查询条件
+     * @param pageQuery 分页参数
+     * @return 仓库库存明细分页列表
+     */
+    TableDataInfo<ProductWarehouseInventoryVo> queryPageList(ProductWarehouseInventoryBo bo, PageQuery pageQuery);
+
+    /**
+     * 查询符合条件的仓库库存明细列表
+     *
+     * @param bo 查询条件
+     * @return 仓库库存明细列表
+     */
+    List<ProductWarehouseInventoryVo> queryList(ProductWarehouseInventoryBo bo);
+
+    /**
+     * 新增仓库库存明细
+     *
+     * @param bo 仓库库存明细
+     * @return 是否新增成功
+     */
+    Boolean insertByBo(ProductWarehouseInventoryBo bo);
+
+    /**
+     * 修改仓库库存明细
+     *
+     * @param bo 仓库库存明细
+     * @return 是否修改成功
+     */
+    Boolean updateByBo(ProductWarehouseInventoryBo bo);
+
+    /**
+     * 校验并批量删除仓库库存明细信息
+     *
+     * @param ids     待删除的主键集合
+     * @param isValid 是否进行有效性校验
+     * @return 是否删除成功
+     */
+    Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
+}

+ 136 - 9
ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/service/impl/ProductAssociateServiceImpl.java

@@ -20,6 +20,9 @@ import org.dromara.product.service.IProductAssociateService;
 import java.util.List;
 import java.util.Map;
 import java.util.Collection;
+import java.util.Set;
+import java.util.HashSet;
+import java.util.Arrays;
 
 /**
  * 产品关联Service业务层处理
@@ -45,6 +48,20 @@ public class ProductAssociateServiceImpl  extends ServiceImpl<ProductAssociateMa
         return baseMapper.selectVoById(id);
     }
 
+    /**
+     * 根据产品ID查询关联信息
+     *
+     * @param productId 产品ID
+     * @return 产品关联
+     */
+    @Override
+    public ProductAssociateVo queryByProductId(Long productId){
+        LambdaQueryWrapper<ProductAssociate> lqw = Wrappers.lambdaQuery();
+        lqw.eq(ProductAssociate::getProductId, productId);
+        lqw.last("LIMIT 1"); // 防止多条记录报错
+        return baseMapper.selectVoOne(lqw);
+    }
+
     /**
      * 分页查询产品关联列表
      *
@@ -91,13 +108,20 @@ public class ProductAssociateServiceImpl  extends ServiceImpl<ProductAssociateMa
      */
     @Override
     public Boolean insertByBo(ProductAssociateBo bo) {
-        ProductAssociate add = MapstructUtils.convert(bo, ProductAssociate.class);
-        validEntityBeforeSave(add);
-        boolean flag = baseMapper.insert(add) > 0;
-        if (flag) {
-            bo.setId(add.getId());
+        // 保存当前商品的关联
+        boolean result = saveOrUpdateAssociate(bo.getProductId(), bo.getProductIds(), bo.getIsUnidirectional(), bo.getRelatedTitle(), bo.getRemark());
+        
+        // 如果是双向关联(isUnidirectional = "0"),给被关联的商品也添加关联
+        if ("0".equals(bo.getIsUnidirectional()) && StringUtils.isNotBlank(bo.getProductIds())) {
+            String[] targetIds = bo.getProductIds().split(",");
+            for (String targetId : targetIds) {
+                if (StringUtils.isNotBlank(targetId)) {
+                    addBidirectionalAssociate(Long.parseLong(targetId.trim()), bo.getProductId());
+                }
+            }
         }
-        return flag;
+        
+        return result;
     }
 
     /**
@@ -108,9 +132,112 @@ public class ProductAssociateServiceImpl  extends ServiceImpl<ProductAssociateMa
      */
     @Override
     public Boolean updateByBo(ProductAssociateBo bo) {
-        ProductAssociate update = MapstructUtils.convert(bo, ProductAssociate.class);
-        validEntityBeforeSave(update);
-        return baseMapper.updateById(update) > 0;
+        // 先获取旧的关联数据,用于处理双向关联的移除
+        ProductAssociate oldAssociate = baseMapper.selectById(bo.getId());
+        
+        // 保存当前商品的关联
+        boolean result = saveOrUpdateAssociate(bo.getProductId(), bo.getProductIds(), bo.getIsUnidirectional(), bo.getRelatedTitle(), bo.getRemark());
+        
+        // 如果是双向关联,处理新增和移除
+        if ("0".equals(bo.getIsUnidirectional())) {
+            Set<String> oldIds = new HashSet<>();
+            Set<String> newIds = new HashSet<>();
+            
+            if (oldAssociate != null && StringUtils.isNotBlank(oldAssociate.getProductIds())) {
+                oldIds.addAll(Arrays.asList(oldAssociate.getProductIds().split(",")));
+            }
+            if (StringUtils.isNotBlank(bo.getProductIds())) {
+                newIds.addAll(Arrays.asList(bo.getProductIds().split(",")));
+            }
+            
+            // 新增的关联:在newIds中但不在oldIds中
+            for (String newId : newIds) {
+                if (StringUtils.isNotBlank(newId) && !oldIds.contains(newId)) {
+                    addBidirectionalAssociate(Long.parseLong(newId.trim()), bo.getProductId());
+                }
+            }
+            
+            // 移除的关联:在oldIds中但不在newIds中
+            for (String oldId : oldIds) {
+                if (StringUtils.isNotBlank(oldId) && !newIds.contains(oldId)) {
+                    removeBidirectionalAssociate(Long.parseLong(oldId.trim()), bo.getProductId());
+                }
+            }
+        }
+        
+        return result;
+    }
+    
+    /**
+     * 保存或更新关联记录
+     */
+    private boolean saveOrUpdateAssociate(Long productId, String productIds, String isUnidirectional, String relatedTitle, String remark) {
+        LambdaQueryWrapper<ProductAssociate> lqw = Wrappers.lambdaQuery();
+        lqw.eq(ProductAssociate::getProductId, productId);
+        ProductAssociate existing = baseMapper.selectOne(lqw);
+        
+        if (existing != null) {
+            existing.setProductIds(productIds);
+            existing.setIsUnidirectional(isUnidirectional);
+            existing.setRelatedTitle(relatedTitle);
+            existing.setRemark(remark);
+            return baseMapper.updateById(existing) > 0;
+        }
+        
+        ProductAssociate add = new ProductAssociate();
+        add.setProductId(productId);
+        add.setProductIds(productIds);
+        add.setIsUnidirectional(isUnidirectional);
+        add.setRelatedTitle(relatedTitle);
+        add.setRemark(remark);
+        return baseMapper.insert(add) > 0;
+    }
+    
+    /**
+     * 给目标商品添加双向关联(把sourceProductId加到目标商品的关联列表中)
+     */
+    private void addBidirectionalAssociate(Long targetProductId, Long sourceProductId) {
+        LambdaQueryWrapper<ProductAssociate> lqw = Wrappers.lambdaQuery();
+        lqw.eq(ProductAssociate::getProductId, targetProductId);
+        ProductAssociate targetAssociate = baseMapper.selectOne(lqw);
+        
+        String sourceIdStr = String.valueOf(sourceProductId);
+        
+        if (targetAssociate != null) {
+            // 已有记录,追加关联
+            Set<String> ids = new HashSet<>();
+            if (StringUtils.isNotBlank(targetAssociate.getProductIds())) {
+                ids.addAll(Arrays.asList(targetAssociate.getProductIds().split(",")));
+            }
+            if (!ids.contains(sourceIdStr)) {
+                ids.add(sourceIdStr);
+                targetAssociate.setProductIds(String.join(",", ids));
+                baseMapper.updateById(targetAssociate);
+            }
+        } else {
+            // 没有记录,新建
+            ProductAssociate newAssociate = new ProductAssociate();
+            newAssociate.setProductId(targetProductId);
+            newAssociate.setProductIds(sourceIdStr);
+            newAssociate.setIsUnidirectional("0"); // 双向
+            baseMapper.insert(newAssociate);
+        }
+    }
+    
+    /**
+     * 从目标商品移除双向关联(把sourceProductId从目标商品的关联列表中移除)
+     */
+    private void removeBidirectionalAssociate(Long targetProductId, Long sourceProductId) {
+        LambdaQueryWrapper<ProductAssociate> lqw = Wrappers.lambdaQuery();
+        lqw.eq(ProductAssociate::getProductId, targetProductId);
+        ProductAssociate targetAssociate = baseMapper.selectOne(lqw);
+        
+        if (targetAssociate != null && StringUtils.isNotBlank(targetAssociate.getProductIds())) {
+            Set<String> ids = new HashSet<>(Arrays.asList(targetAssociate.getProductIds().split(",")));
+            ids.remove(String.valueOf(sourceProductId));
+            targetAssociate.setProductIds(String.join(",", ids));
+            baseMapper.updateById(targetAssociate);
+        }
     }
 
     /**

+ 113 - 31
ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/service/impl/ProductBaseServiceImpl.java

@@ -31,6 +31,9 @@ import org.springframework.transaction.annotation.Transactional;
 import org.dromara.product.domain.bo.ProductBaseBo;
 import org.dromara.product.domain.bo.SiteProductBo;
 import org.dromara.product.domain.vo.ProductBaseVo;
+import org.dromara.product.domain.vo.ProductBaseSimpleVo;
+import org.dromara.product.domain.vo.ProductOperationVo;
+import org.dromara.product.domain.vo.RecommendProductVo;
 import org.dromara.product.domain.vo.SiteProductVo;
 import org.dromara.product.domain.ProductBase;
 import org.dromara.product.domain.ProductExtend;
@@ -85,7 +88,7 @@ import java.util.stream.Collectors;
 @Slf4j
 @RequiredArgsConstructor
 @Service
-public class ProductBaseServiceImpl  extends ServiceImpl<ProductBaseMapper, ProductBase> implements IProductBaseService, ApplicationRunner {
+public class ProductBaseServiceImpl extends ServiceImpl<ProductBaseMapper, ProductBase> implements IProductBaseService,ApplicationRunner {
 
     //产品基础信息Mapper
     private final ProductBaseMapper baseMapper;
@@ -604,9 +607,9 @@ public class ProductBaseServiceImpl  extends ServiceImpl<ProductBaseMapper, Prod
         priceInventory.setTaxRate(bo.getTaxRate());
         priceInventory.setMinOrderQuantity(bo.getMinOrderQuantity());
         // 设置默认库存值
-        priceInventory.setTotalInventory(bo.getTotalInventory());
-        priceInventory.setNowInventory(bo.getNowInventory());
-        priceInventory.setVirtualInventory(bo.getVirtualInventory());
+        priceInventory.setTotalInventory(0L);
+        priceInventory.setNowInventory(0L);
+        priceInventory.setVirtualInventory(0L);
 
         priceInventoryMapper.insert(priceInventory);
     }
@@ -630,33 +633,53 @@ public class ProductBaseServiceImpl  extends ServiceImpl<ProductBaseMapper, Prod
      * 更新产品扩展信息
      */
     private void updateProductExtend(ProductBaseBo bo, Long productId) {
-        // 先删除旧的扩展信息
+        // 检查是否存在扩展信息
         LambdaQueryWrapper<ProductExtend> wrapper = Wrappers.lambdaQuery();
         wrapper.eq(ProductExtend::getProductId, productId);
-        extendMapper.delete(wrapper);
+        ProductExtend existing = extendMapper.selectOne(wrapper);
 
-        // 重新保存
-        saveProductExtend(bo, productId);
+        if (existing != null) {
+            // 存在则更新(只更新非空字段)
+            if (bo.getInvoiceName() != null) existing.setInvoiceName(bo.getInvoiceName());
+            if (bo.getInvoiceSpec() != null) existing.setInvoiceSpecs(bo.getInvoiceSpec());
+            if (bo.getUpcBarcode() != null) existing.setBarCoding(bo.getUpcBarcode());
+            if (bo.getWeight() != null) existing.setProductWeight(bo.getWeight());
+            if (bo.getWeightUnit() != null) existing.setWeightUnit(bo.getWeightUnit());
+            if (bo.getVolume() != null) existing.setProductVolume(bo.getVolume());
+            if (bo.getVolumeUnit() != null) existing.setVolumeUnit(bo.getVolumeUnit());
+            if (bo.getAfterSalesService() != null) existing.setAfterSalesService(bo.getAfterSalesService());
+            if (bo.getReferenceLink() != null) existing.setReferenceLink(bo.getReferenceLink());
+            if (bo.getCustomizable() != null) existing.setIsCustomize(bo.getCustomizable() ? "1" : "0");
+            if (bo.getCustomDescription() != null) existing.setCustomDescription(bo.getCustomDescription());
+            if (bo.getServiceGuarantee() != null) existing.setServiceGuarantee(bo.getServiceGuarantee());
+            if (bo.getFreeInstallation() != null) existing.setIsInstallService(bo.getFreeInstallation());
+            if (bo.getSalesVolume() != null) existing.setSalesVolume(bo.getSalesVolume());
+            extendMapper.updateById(existing);
+        } else {
+            // 不存在则新增
+            saveProductExtend(bo, productId);
+        }
     }
 
     /**
      * 更新产品价格库存信息
      */
     private void updateProductPriceInventory(ProductBaseBo bo, Long productId) {
+        // 如果没有传价格相关信息,不做任何操作
+        if (bo.getMidRangePrice() == null && bo.getStandardPrice() == null &&
+            bo.getCertificatePrice() == null && bo.getPurchasePrice() == null &&
+            bo.getEstimatedPurchasePrice() == null) {
+            return;
+        }
+
         ProductPriceInventory existing = priceInventoryMapper.selectById(productId);
         if (existing != null) {
-            // 更新现有记录
-            existing.setMarketPrice(bo.getMidRangePrice());
-            existing.setMemberPrice(bo.getStandardPrice());
-            existing.setMinSellingPrice(bo.getCertificatePrice());
-            existing.setPurchasingPrice(bo.getPurchasePrice());
-            existing.setMaxPurchasePrice(bo.getEstimatedPurchasePrice());
-            existing.setCurrency(bo.getCurrency());
-            existing.setTaxRate(bo.getTaxRate());
-            existing.setMinOrderQuantity(bo.getMinOrderQuantity());
-            existing.setVirtualInventory(bo.getVirtualInventory());
-            existing.setNowInventory(bo.getNowInventory());
-
+            // 更新非空字段
+            if (bo.getMidRangePrice() != null) existing.setMarketPrice(bo.getMidRangePrice());
+            if (bo.getStandardPrice() != null) existing.setMemberPrice(bo.getStandardPrice());
+            if (bo.getCertificatePrice() != null) existing.setMinSellingPrice(bo.getCertificatePrice());
+            if (bo.getPurchasePrice() != null) existing.setPurchasingPrice(bo.getPurchasePrice());
+            if (bo.getEstimatedPurchasePrice() != null) existing.setMaxPurchasePrice(bo.getEstimatedPurchasePrice());
             priceInventoryMapper.updateById(existing);
         } else {
             // 创建新记录
@@ -668,13 +691,24 @@ public class ProductBaseServiceImpl  extends ServiceImpl<ProductBaseMapper, Prod
      * 更新产品属性关联信息
      */
     private void updateProductClassification(ProductBaseBo bo, Long productId) {
-        // 先删除旧的分类关联
+        // 如果没有传分类信息,不做任何操作
+        if (bo.getBottomCategoryId() == null && bo.getAttributesList() == null) {
+            return;
+        }
+
         LambdaQueryWrapper<ProductClassification> wrapper = Wrappers.lambdaQuery();
         wrapper.eq(ProductClassification::getProductId, productId);
-        classificationMapper.delete(wrapper);
+        ProductClassification existing = classificationMapper.selectOne(wrapper);
 
-        // 重新保存
-        saveProductClassification(bo, productId);
+        if (existing != null) {
+            // 存在则更新非空字段
+            if (bo.getBottomCategoryId() != null) existing.setCategoryId(bo.getBottomCategoryId());
+            if (bo.getAttributesList() != null) existing.setAttributesList(bo.getAttributesList());
+            classificationMapper.updateById(existing);
+        } else if (bo.getBottomCategoryId() != null) {
+            // 不存在且有分类ID才新增
+            saveProductClassification(bo, productId);
+        }
     }
 
     /**
@@ -895,14 +929,22 @@ public class ProductBaseServiceImpl  extends ServiceImpl<ProductBaseMapper, Prod
      * 更新产品定制信息
      */
     private void updateProductCustomization(ProductBaseBo bo, Long productId) {
-        // 先删除旧的定制信息
-        LambdaQueryWrapper<ProductCustomization> wrapper = Wrappers.lambdaQuery();
-        wrapper.eq(ProductCustomization::getProductId, productId);
-        customizationMapper.delete(wrapper);
+        // 如果没有传定制相关信息,不做任何操作
+        if (bo.getCustomizable() == null && bo.getCustomDetailsJson() == null) {
+            return;
+        }
 
-        // 如果可定制,则保存新的定制信息
-        if (bo.getCustomizable() != null && bo.getCustomizable()) {
-            saveProductCustomization(bo, productId);
+        // 只有明确传了定制详情JSON才更新定制信息
+        if (bo.getCustomDetailsJson() != null) {
+            // 先删除旧的定制信息
+            LambdaQueryWrapper<ProductCustomization> wrapper = Wrappers.lambdaQuery();
+            wrapper.eq(ProductCustomization::getProductId, productId);
+            customizationMapper.delete(wrapper);
+
+            // 如果可定制,则保存新的定制信息
+            if (Boolean.TRUE.equals(bo.getCustomizable())) {
+                saveProductCustomization(bo, productId);
+            }
         }
     }
 
@@ -1032,4 +1074,44 @@ public class ProductBaseServiceImpl  extends ServiceImpl<ProductBaseMapper, Prod
             .eq(ProductExtend::getProductId, bo.getId())
         );
     }
+
+    /**
+     * 分页查询推荐商品列表(联表查询分类名称)
+     *
+     * @param bo        查询条件
+     * @param pageQuery 分页参数
+     * @return 推荐商品分页列表
+     */
+    @Override
+    public TableDataInfo<RecommendProductVo> queryRecommendProductPageList(ProductBaseBo bo, PageQuery pageQuery) {
+        Page<RecommendProductVo> page = baseMapper.selectRecommendProductPage(pageQuery.build(), bo);
+        return TableDataInfo.build(page);
+    }
+
+    /**
+     * 分页查询商品运营列表(联表查询)
+     *
+     * @param bo        查询条件
+     * @param pageQuery 分页参数
+     * @return 商品运营分页列表
+     */
+    @Override
+    public TableDataInfo<ProductOperationVo> queryProductOperationPageList(ProductBaseBo bo, PageQuery pageQuery) {
+        Page<ProductOperationVo> page = baseMapper.selectProductOperationPage(pageQuery.build(), bo);
+        return TableDataInfo.build(page);
+    }
+
+    /**
+     * 分页查询商品简化列表(用于选择弹窗)
+     *
+     * @param bo        查询条件
+     * @param pageQuery 分页参数
+     * @return 商品简化分页列表
+     */
+    @Override
+    public TableDataInfo<ProductBaseSimpleVo> querySimplePageList(ProductBaseBo bo, PageQuery pageQuery) {
+        Page<ProductBaseSimpleVo> page = baseMapper.selectSimplePage(pageQuery.build(), bo);
+        return TableDataInfo.build(page);
+    }
 }
+

+ 33 - 7
ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/service/impl/ProductBlacklistServiceImpl.java

@@ -1,6 +1,8 @@
 package org.dromara.product.service.impl;
 
+import cn.hutool.core.collection.CollUtil;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import org.apache.dubbo.config.annotation.DubboReference;
 import org.dromara.common.core.utils.MapstructUtils;
 import org.dromara.common.core.utils.StringUtils;
 import org.dromara.common.mybatis.core.page.TableDataInfo;
@@ -10,6 +12,8 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
+import org.dromara.customer.api.RemoteCustomerService;
+import org.dromara.customer.api.domain.CustomerApiVo;
 import org.springframework.stereotype.Service;
 import org.dromara.product.domain.bo.ProductBlacklistBo;
 import org.dromara.product.domain.vo.ProductBlacklistVo;
@@ -17,9 +21,8 @@ import org.dromara.product.domain.ProductBlacklist;
 import org.dromara.product.mapper.ProductBlacklistMapper;
 import org.dromara.product.service.IProductBlacklistService;
 
-import java.util.List;
-import java.util.Map;
-import java.util.Collection;
+import java.util.*;
+import java.util.stream.Collectors;
 
 /**
  * 产品黑名单Service业务层处理
@@ -30,10 +33,13 @@ import java.util.Collection;
 @Slf4j
 @RequiredArgsConstructor
 @Service
-public class ProductBlacklistServiceImpl  extends ServiceImpl<ProductBlacklistMapper, ProductBlacklist> implements IProductBlacklistService {
+public class ProductBlacklistServiceImpl extends ServiceImpl<ProductBlacklistMapper, ProductBlacklist> implements IProductBlacklistService {
 
     private final ProductBlacklistMapper baseMapper;
 
+    @DubboReference
+    private RemoteCustomerService remoteCustomerService;
+
     /**
      * 查询产品黑名单
      *
@@ -54,9 +60,29 @@ public class ProductBlacklistServiceImpl  extends ServiceImpl<ProductBlacklistMa
      */
     @Override
     public TableDataInfo<ProductBlacklistVo> queryPageList(ProductBlacklistBo bo, PageQuery pageQuery) {
-        LambdaQueryWrapper<ProductBlacklist> lqw = buildQueryWrapper(bo);
-        Page<ProductBlacklistVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
-        return TableDataInfo.build(result);
+        Page<ProductBlacklistVo> page = baseMapper.selectBlacklistPage(pageQuery.build(), bo);
+
+        // 补充客户编号和名称(远程调用)
+        List<ProductBlacklistVo> records = page.getRecords();
+        if (CollUtil.isNotEmpty(records)) {
+            Set<Long> customerIds = records.stream()
+                .map(ProductBlacklistVo::getCustomerId)
+                .filter(Objects::nonNull)
+                .collect(Collectors.toSet());
+
+            if (!customerIds.isEmpty()) {
+                Map<Long, CustomerApiVo> customerMap = remoteCustomerService.selectCustomerByIds(customerIds);
+                records.forEach(vo -> {
+                    CustomerApiVo customer = customerMap.get(vo.getCustomerId());
+                    if (customer != null) {
+                        vo.setCustomerNo(customer.getCustomerNo());
+                        vo.setCustomerName(customer.getCustomerName());
+                    }
+                });
+            }
+        }
+
+        return TableDataInfo.build(page);
     }
 
     /**

+ 14 - 1
ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/service/impl/ProductGiftFloorLinkServiceImpl.java

@@ -16,6 +16,7 @@ import org.dromara.product.domain.vo.ProductGiftFloorLinkVo;
 import org.dromara.product.domain.ProductGiftFloorLink;
 import org.dromara.product.mapper.ProductGiftFloorLinkMapper;
 import org.dromara.product.service.IProductGiftFloorLinkService;
+import org.dromara.common.core.exception.ServiceException;
 
 import java.util.List;
 import java.util.Map;
@@ -117,7 +118,19 @@ public class ProductGiftFloorLinkServiceImpl  extends ServiceImpl<ProductGiftFlo
      * 保存前的数据校验
      */
     private void validEntityBeforeSave(ProductGiftFloorLink entity){
-        //TODO 做一些数据校验,如唯一约束
+        // 校验同一楼层下商品是否已存在
+        if (entity.getFloorId() != null && entity.getProductId() != null) {
+            LambdaQueryWrapper<ProductGiftFloorLink> lqw = Wrappers.lambdaQuery();
+            lqw.eq(ProductGiftFloorLink::getFloorId, entity.getFloorId());
+            lqw.eq(ProductGiftFloorLink::getProductId, entity.getProductId());
+            // 如果是更新操作,排除自身
+            if (entity.getId() != null) {
+                lqw.ne(ProductGiftFloorLink::getId, entity.getId());
+            }
+            if (baseMapper.exists(lqw)) {
+                throw new ServiceException("该商品已存在于当前楼层,请勿重复添加");
+            }
+        }
     }
 
     /**

+ 154 - 0
ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/service/impl/ProductOperationInfoServiceImpl.java

@@ -0,0 +1,154 @@
+package org.dromara.product.service.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import org.dromara.common.core.utils.MapstructUtils;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.dromara.product.domain.bo.ProductOperationInfoBo;
+import org.dromara.product.domain.vo.ProductOperationInfoVo;
+import org.dromara.product.domain.ProductOperationInfo;
+import org.dromara.product.mapper.ProductOperationInfoMapper;
+import org.dromara.product.service.IProductOperationInfoService;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Collection;
+
+/**
+ * 产品运营信息Service业务层处理
+ *
+ * @author LionLi
+ * @date 2026-01-08
+ */
+@Slf4j
+@RequiredArgsConstructor
+@Service
+public class ProductOperationInfoServiceImpl  extends ServiceImpl<ProductOperationInfoMapper, ProductOperationInfo> implements IProductOperationInfoService {
+
+    private final ProductOperationInfoMapper baseMapper;
+
+    /**
+     * 查询产品运营信息
+     *
+     * @param id 主键
+     * @return 产品运营信息
+     */
+    @Override
+    public ProductOperationInfoVo queryById(Long id){
+        return baseMapper.selectVoById(id);
+    }
+
+    /**
+     * 根据产品ID查询运营信息
+     *
+     * @param productId 产品ID
+     * @return 产品运营信息
+     */
+    @Override
+    public ProductOperationInfoVo queryByProductId(Long productId){
+        LambdaQueryWrapper<ProductOperationInfo> lqw = Wrappers.lambdaQuery();
+        lqw.eq(ProductOperationInfo::getProductId, productId);
+        return baseMapper.selectVoOne(lqw);
+    }
+
+    /**
+     * 分页查询产品运营信息列表
+     *
+     * @param bo        查询条件
+     * @param pageQuery 分页参数
+     * @return 产品运营信息分页列表
+     */
+    @Override
+    public TableDataInfo<ProductOperationInfoVo> queryPageList(ProductOperationInfoBo bo, PageQuery pageQuery) {
+        LambdaQueryWrapper<ProductOperationInfo> lqw = buildQueryWrapper(bo);
+        Page<ProductOperationInfoVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
+        return TableDataInfo.build(result);
+    }
+
+    /**
+     * 查询符合条件的产品运营信息列表
+     *
+     * @param bo 查询条件
+     * @return 产品运营信息列表
+     */
+    @Override
+    public List<ProductOperationInfoVo> queryList(ProductOperationInfoBo bo) {
+        LambdaQueryWrapper<ProductOperationInfo> lqw = buildQueryWrapper(bo);
+        return baseMapper.selectVoList(lqw);
+    }
+
+    private LambdaQueryWrapper<ProductOperationInfo> buildQueryWrapper(ProductOperationInfoBo bo) {
+        Map<String, Object> params = bo.getParams();
+        LambdaQueryWrapper<ProductOperationInfo> lqw = Wrappers.lambdaQuery();
+        lqw.orderByAsc(ProductOperationInfo::getId);
+        lqw.eq(bo.getProductId() != null, ProductOperationInfo::getProductId, bo.getProductId());
+        lqw.eq(StringUtils.isNotBlank(bo.getDetailTitle()), ProductOperationInfo::getDetailTitle, bo.getDetailTitle());
+        lqw.eq(StringUtils.isNotBlank(bo.getProductKeywords()), ProductOperationInfo::getProductKeywords, bo.getProductKeywords());
+        lqw.eq(StringUtils.isNotBlank(bo.getDetailDescription()), ProductOperationInfo::getDetailDescription, bo.getDetailDescription());
+        lqw.eq(StringUtils.isNotBlank(bo.getProductRecommend()), ProductOperationInfo::getProductRecommend, bo.getProductRecommend());
+        lqw.eq(StringUtils.isNotBlank(bo.getPriceInterval()), ProductOperationInfo::getPriceInterval, bo.getPriceInterval());
+        lqw.eq(StringUtils.isNotBlank(bo.getLabelManage()), ProductOperationInfo::getLabelManage, bo.getLabelManage());
+        lqw.eq(StringUtils.isNotBlank(bo.getStatus()), ProductOperationInfo::getStatus, bo.getStatus());
+        lqw.eq(StringUtils.isNotBlank(bo.getPlatformCode()), ProductOperationInfo::getPlatformCode, bo.getPlatformCode());
+        return lqw;
+    }
+
+    /**
+     * 新增产品运营信息
+     *
+     * @param bo 产品运营信息
+     * @return 是否新增成功
+     */
+    @Override
+    public Boolean insertByBo(ProductOperationInfoBo bo) {
+        ProductOperationInfo add = MapstructUtils.convert(bo, ProductOperationInfo.class);
+        validEntityBeforeSave(add);
+        boolean flag = baseMapper.insert(add) > 0;
+        if (flag) {
+            bo.setId(add.getId());
+        }
+        return flag;
+    }
+
+    /**
+     * 修改产品运营信息
+     *
+     * @param bo 产品运营信息
+     * @return 是否修改成功
+     */
+    @Override
+    public Boolean updateByBo(ProductOperationInfoBo bo) {
+        ProductOperationInfo update = MapstructUtils.convert(bo, ProductOperationInfo.class);
+        validEntityBeforeSave(update);
+        return baseMapper.updateById(update) > 0;
+    }
+
+    /**
+     * 保存前的数据校验
+     */
+    private void validEntityBeforeSave(ProductOperationInfo entity){
+        //TODO 做一些数据校验,如唯一约束
+    }
+
+    /**
+     * 校验并批量删除产品运营信息信息
+     *
+     * @param ids     待删除的主键集合
+     * @param isValid 是否进行有效性校验
+     * @return 是否删除成功
+     */
+    @Override
+    public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
+        if(isValid){
+            //TODO 做一些业务上的校验,判断是否需要校验
+        }
+        return baseMapper.deleteByIds(ids) > 0;
+    }
+}

+ 34 - 0
ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/service/impl/ProductProgramLinkServiceImpl.java

@@ -132,4 +132,38 @@ public class ProductProgramLinkServiceImpl  extends ServiceImpl<ProductProgramLi
         }
         return baseMapper.deleteByIds(ids) > 0;
     }
+
+    /**
+     * 根据产品ID查询已关联的方案ID列表
+     */
+    @Override
+    public List<Long> queryProgramIdsByProductId(Long productId) {
+        LambdaQueryWrapper<ProductProgramLink> lqw = Wrappers.lambdaQuery();
+        lqw.eq(ProductProgramLink::getProductId, productId);
+        lqw.select(ProductProgramLink::getProgramId);
+        List<ProductProgramLink> list = baseMapper.selectList(lqw);
+        return list.stream().map(ProductProgramLink::getProgramId).toList();
+    }
+
+    /**
+     * 批量保存产品方案关联(先删后增)
+     */
+    @Override
+    public Boolean saveBatch(Long productId, List<Long> programIds) {
+        // 先删除该产品的所有关联
+        LambdaQueryWrapper<ProductProgramLink> lqw = Wrappers.lambdaQuery();
+        lqw.eq(ProductProgramLink::getProductId, productId);
+        baseMapper.delete(lqw);
+        
+        // 再批量新增
+        if (programIds != null && !programIds.isEmpty()) {
+            for (Long programId : programIds) {
+                ProductProgramLink link = new ProductProgramLink();
+                link.setProductId(productId);
+                link.setProgramId(programId);
+                baseMapper.insert(link);
+            }
+        }
+        return true;
+    }
 }

+ 127 - 9
ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/service/impl/ProductSpecsServiceImpl.java

@@ -20,6 +20,9 @@ import org.dromara.product.service.IProductSpecsService;
 import java.util.List;
 import java.util.Map;
 import java.util.Collection;
+import java.util.Set;
+import java.util.HashSet;
+import java.util.Arrays;
 
 /**
  * 产品规格关联Service业务层处理
@@ -90,13 +93,20 @@ public class ProductSpecsServiceImpl  extends ServiceImpl<ProductSpecsMapper, Pr
      */
     @Override
     public Boolean insertByBo(ProductSpecsBo bo) {
-        ProductSpecs add = MapstructUtils.convert(bo, ProductSpecs.class);
-        validEntityBeforeSave(add);
-        boolean flag = baseMapper.insert(add) > 0;
-        if (flag) {
-            bo.setId(add.getId());
+        // 保存当前商品的关联
+        boolean result = saveOrUpdateSpecs(bo.getProductId(), bo.getSpecsProductIds(), bo.getIsUnidirectional(), bo.getRemark());
+        
+        // 如果是双向关联(isUnidirectional = "0"),给被关联的商品也添加关联
+        if ("0".equals(bo.getIsUnidirectional()) && StringUtils.isNotBlank(bo.getSpecsProductIds())) {
+            String[] targetIds = bo.getSpecsProductIds().split(",");
+            for (String targetId : targetIds) {
+                if (StringUtils.isNotBlank(targetId)) {
+                    addBidirectionalSpecs(targetId.trim(), bo.getProductId());
+                }
+            }
         }
-        return flag;
+        
+        return result;
     }
 
     /**
@@ -107,9 +117,106 @@ public class ProductSpecsServiceImpl  extends ServiceImpl<ProductSpecsMapper, Pr
      */
     @Override
     public Boolean updateByBo(ProductSpecsBo bo) {
-        ProductSpecs update = MapstructUtils.convert(bo, ProductSpecs.class);
-        validEntityBeforeSave(update);
-        return baseMapper.updateById(update) > 0;
+        // 先获取旧的关联数据
+        ProductSpecs oldSpecs = baseMapper.selectById(bo.getId());
+        
+        // 保存当前商品的关联
+        boolean result = saveOrUpdateSpecs(bo.getProductId(), bo.getSpecsProductIds(), bo.getIsUnidirectional(), bo.getRemark());
+        
+        // 如果是双向关联,处理新增和移除
+        if ("0".equals(bo.getIsUnidirectional())) {
+            Set<String> oldIds = new HashSet<>();
+            Set<String> newIds = new HashSet<>();
+            
+            if (oldSpecs != null && StringUtils.isNotBlank(oldSpecs.getSpecsProductIds())) {
+                oldIds.addAll(Arrays.asList(oldSpecs.getSpecsProductIds().split(",")));
+            }
+            if (StringUtils.isNotBlank(bo.getSpecsProductIds())) {
+                newIds.addAll(Arrays.asList(bo.getSpecsProductIds().split(",")));
+            }
+            
+            // 新增的关联
+            for (String newId : newIds) {
+                if (StringUtils.isNotBlank(newId) && !oldIds.contains(newId)) {
+                    addBidirectionalSpecs(newId.trim(), bo.getProductId());
+                }
+            }
+            
+            // 移除的关联
+            for (String oldId : oldIds) {
+                if (StringUtils.isNotBlank(oldId) && !newIds.contains(oldId)) {
+                    removeBidirectionalSpecs(oldId.trim(), bo.getProductId());
+                }
+            }
+        }
+        
+        return result;
+    }
+    
+    /**
+     * 保存或更新规格关联记录
+     */
+    private boolean saveOrUpdateSpecs(String productId, String specsProductIds, String isUnidirectional, String remark) {
+        LambdaQueryWrapper<ProductSpecs> lqw = Wrappers.lambdaQuery();
+        lqw.eq(ProductSpecs::getProductId, productId);
+        ProductSpecs existing = baseMapper.selectOne(lqw);
+        
+        if (existing != null) {
+            existing.setSpecsProductIds(specsProductIds);
+            existing.setIsUnidirectional(isUnidirectional);
+            existing.setRemark(remark);
+            return baseMapper.updateById(existing) > 0;
+        }
+        
+        ProductSpecs add = new ProductSpecs();
+        add.setProductId(productId);
+        add.setSpecsProductIds(specsProductIds);
+        add.setIsUnidirectional(isUnidirectional);
+        add.setRemark(remark);
+        return baseMapper.insert(add) > 0;
+    }
+    
+    /**
+     * 给目标商品添加双向规格关联
+     */
+    private void addBidirectionalSpecs(String targetProductId, String sourceProductId) {
+        LambdaQueryWrapper<ProductSpecs> lqw = Wrappers.lambdaQuery();
+        lqw.eq(ProductSpecs::getProductId, targetProductId);
+        ProductSpecs targetSpecs = baseMapper.selectOne(lqw);
+        
+        if (targetSpecs != null) {
+            Set<String> ids = new HashSet<>();
+            if (StringUtils.isNotBlank(targetSpecs.getSpecsProductIds())) {
+                ids.addAll(Arrays.asList(targetSpecs.getSpecsProductIds().split(",")));
+            }
+            if (!ids.contains(sourceProductId)) {
+                ids.add(sourceProductId);
+                targetSpecs.setSpecsProductIds(String.join(",", ids));
+                baseMapper.updateById(targetSpecs);
+            }
+        } else {
+            ProductSpecs newSpecs = new ProductSpecs();
+            newSpecs.setProductId(targetProductId);
+            newSpecs.setSpecsProductIds(sourceProductId);
+            newSpecs.setIsUnidirectional("0");
+            baseMapper.insert(newSpecs);
+        }
+    }
+    
+    /**
+     * 从目标商品移除双向规格关联
+     */
+    private void removeBidirectionalSpecs(String targetProductId, String sourceProductId) {
+        LambdaQueryWrapper<ProductSpecs> lqw = Wrappers.lambdaQuery();
+        lqw.eq(ProductSpecs::getProductId, targetProductId);
+        ProductSpecs targetSpecs = baseMapper.selectOne(lqw);
+        
+        if (targetSpecs != null && StringUtils.isNotBlank(targetSpecs.getSpecsProductIds())) {
+            Set<String> ids = new HashSet<>(Arrays.asList(targetSpecs.getSpecsProductIds().split(",")));
+            ids.remove(sourceProductId);
+            targetSpecs.setSpecsProductIds(String.join(",", ids));
+            baseMapper.updateById(targetSpecs);
+        }
     }
 
     /**
@@ -133,4 +240,15 @@ public class ProductSpecsServiceImpl  extends ServiceImpl<ProductSpecsMapper, Pr
         }
         return baseMapper.deleteByIds(ids) > 0;
     }
+
+    /**
+     * 根据产品ID查询规格关联信息
+     */
+    @Override
+    public ProductSpecsVo queryByProductId(String productId) {
+        LambdaQueryWrapper<ProductSpecs> lqw = Wrappers.lambdaQuery();
+        lqw.eq(ProductSpecs::getProductId, productId);
+        lqw.last("LIMIT 1");
+        return baseMapper.selectVoOne(lqw);
+    }
 }

+ 37 - 1
ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/service/impl/ProductThemeGroupLinkServiceImpl.java

@@ -14,7 +14,9 @@ import org.springframework.stereotype.Service;
 import org.dromara.product.domain.bo.ProductThemeGroupLinkBo;
 import org.dromara.product.domain.vo.ProductThemeGroupLinkVo;
 import org.dromara.product.domain.ProductThemeGroupLink;
+import org.dromara.product.domain.ProductThemeGroup;
 import org.dromara.product.mapper.ProductThemeGroupLinkMapper;
+import org.dromara.product.mapper.ProductThemeGroupMapper;
 import org.dromara.product.service.IProductThemeGroupLinkService;
 
 import org.dromara.common.core.exception.ServiceException;
@@ -35,6 +37,7 @@ import java.util.Collection;
 public class ProductThemeGroupLinkServiceImpl  extends ServiceImpl<ProductThemeGroupLinkMapper, ProductThemeGroupLink> implements IProductThemeGroupLinkService {
 
     private final ProductThemeGroupLinkMapper baseMapper;
+    private final ProductThemeGroupMapper groupMapper;
 
     /**
      * 查询主题分组商品关联
@@ -98,6 +101,8 @@ public class ProductThemeGroupLinkServiceImpl  extends ServiceImpl<ProductThemeG
         boolean flag = baseMapper.insert(add) > 0;
         if (flag) {
             bo.setId(add.getId());
+            // 更新分组的商品数量
+            updateGroupProductCount(bo.getGroupId());
         }
         return flag;
     }
@@ -146,6 +151,37 @@ public class ProductThemeGroupLinkServiceImpl  extends ServiceImpl<ProductThemeG
         if(isValid){
             //TODO 做一些业务上的校验,判断是否需要校验
         }
-        return baseMapper.deleteByIds(ids) > 0;
+        // 先获取要删除的记录,拿到groupId
+        List<ProductThemeGroupLink> links = baseMapper.selectByIds(ids);
+        boolean flag = baseMapper.deleteByIds(ids) > 0;
+        if (flag) {
+            // 更新涉及的分组的商品数量
+            links.stream()
+                .map(ProductThemeGroupLink::getGroupId)
+                .distinct()
+                .forEach(this::updateGroupProductCount);
+        }
+        return flag;
+    }
+
+    /**
+     * 更新分组的商品数量
+     *
+     * @param groupId 分组ID
+     */
+    private void updateGroupProductCount(Long groupId) {
+        if (groupId == null) {
+            return;
+        }
+        // 统计该分组下的商品数量
+        Long count = baseMapper.selectCount(
+            Wrappers.<ProductThemeGroupLink>lambdaQuery()
+                .eq(ProductThemeGroupLink::getGroupId, groupId)
+        );
+        // 更新分组表的商品数量
+        ProductThemeGroup group = new ProductThemeGroup();
+        group.setId(groupId);
+        group.setProductCount(count);
+        groupMapper.updateById(group);
     }
 }

+ 140 - 0
ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/service/impl/ProductWarehouseInventoryServiceImpl.java

@@ -0,0 +1,140 @@
+package org.dromara.product.service.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import org.dromara.common.core.utils.MapstructUtils;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.dromara.product.domain.bo.ProductWarehouseInventoryBo;
+import org.dromara.product.domain.vo.ProductWarehouseInventoryVo;
+import org.dromara.product.domain.ProductWarehouseInventory;
+import org.dromara.product.mapper.ProductWarehouseInventoryMapper;
+import org.dromara.product.service.IProductWarehouseInventoryService;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Collection;
+
+/**
+ * 仓库库存明细Service业务层处理
+ *
+ * @author LionLi
+ * @date 2025-12-31
+ */
+@Slf4j
+@RequiredArgsConstructor
+@Service
+public class ProductWarehouseInventoryServiceImpl  extends ServiceImpl<ProductWarehouseInventoryMapper, ProductWarehouseInventory> implements IProductWarehouseInventoryService {
+
+    private final ProductWarehouseInventoryMapper baseMapper;
+
+    /**
+     * 查询仓库库存明细
+     *
+     * @param id 主键
+     * @return 仓库库存明细
+     */
+    @Override
+    public ProductWarehouseInventoryVo queryById(Long id){
+        return baseMapper.selectVoById(id);
+    }
+
+    /**
+     * 分页查询仓库库存明细列表
+     *
+     * @param bo        查询条件
+     * @param pageQuery 分页参数
+     * @return 仓库库存明细分页列表
+     */
+    @Override
+    public TableDataInfo<ProductWarehouseInventoryVo> queryPageList(ProductWarehouseInventoryBo bo, PageQuery pageQuery) {
+        Page<ProductWarehouseInventoryVo> result = baseMapper.selectWarehouseInventoryList(pageQuery.build(), bo);
+        return TableDataInfo.build(result);
+    }
+
+    /**
+     * 查询符合条件的仓库库存明细列表
+     *
+     * @param bo 查询条件
+     * @return 仓库库存明细列表
+     */
+    @Override
+    public List<ProductWarehouseInventoryVo> queryList(ProductWarehouseInventoryBo bo) {
+        return baseMapper.selectWarehouseInventoryList(bo);
+    }
+
+    private LambdaQueryWrapper<ProductWarehouseInventory> buildQueryWrapper(ProductWarehouseInventoryBo bo) {
+        Map<String, Object> params = bo.getParams();
+        LambdaQueryWrapper<ProductWarehouseInventory> lqw = Wrappers.lambdaQuery();
+        lqw.orderByAsc(ProductWarehouseInventory::getId);
+        lqw.eq(bo.getProductId() != null, ProductWarehouseInventory::getProductId, bo.getProductId());
+        lqw.eq(bo.getSkuId() != null, ProductWarehouseInventory::getSkuId, bo.getSkuId());
+        lqw.eq(StringUtils.isNotBlank(bo.getSpecModel()), ProductWarehouseInventory::getSpecModel, bo.getSpecModel());
+        lqw.eq(bo.getWarehouseId() != null, ProductWarehouseInventory::getWarehouseId, bo.getWarehouseId());
+        lqw.eq(StringUtils.isNotBlank(bo.getWarehouseNo()), ProductWarehouseInventory::getWarehouseNo, bo.getWarehouseNo());
+        lqw.like(StringUtils.isNotBlank(bo.getWarehouseName()), ProductWarehouseInventory::getWarehouseName, bo.getWarehouseName());
+        lqw.eq(bo.getNowInventory() != null, ProductWarehouseInventory::getNowInventory, bo.getNowInventory());
+        lqw.eq(bo.getLockInventory() != null, ProductWarehouseInventory::getLockInventory, bo.getLockInventory());
+        lqw.eq(StringUtils.isNotBlank(bo.getStatus()), ProductWarehouseInventory::getStatus, bo.getStatus());
+        lqw.eq(StringUtils.isNotBlank(bo.getPlatformCode()), ProductWarehouseInventory::getPlatformCode, bo.getPlatformCode());
+        return lqw;
+    }
+
+    /**
+     * 新增仓库库存明细
+     *
+     * @param bo 仓库库存明细
+     * @return 是否新增成功
+     */
+    @Override
+    public Boolean insertByBo(ProductWarehouseInventoryBo bo) {
+        ProductWarehouseInventory add = MapstructUtils.convert(bo, ProductWarehouseInventory.class);
+        validEntityBeforeSave(add);
+        boolean flag = baseMapper.insert(add) > 0;
+        if (flag) {
+            bo.setId(add.getId());
+        }
+        return flag;
+    }
+
+    /**
+     * 修改仓库库存明细
+     *
+     * @param bo 仓库库存明细
+     * @return 是否修改成功
+     */
+    @Override
+    public Boolean updateByBo(ProductWarehouseInventoryBo bo) {
+        ProductWarehouseInventory update = MapstructUtils.convert(bo, ProductWarehouseInventory.class);
+        validEntityBeforeSave(update);
+        return baseMapper.updateById(update) > 0;
+    }
+
+    /**
+     * 保存前的数据校验
+     */
+    private void validEntityBeforeSave(ProductWarehouseInventory entity){
+        //TODO 做一些数据校验,如唯一约束
+    }
+
+    /**
+     * 校验并批量删除仓库库存明细信息
+     *
+     * @param ids     待删除的主键集合
+     * @param isValid 是否进行有效性校验
+     * @return 是否删除成功
+     */
+    @Override
+    public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
+        if(isValid){
+            //TODO 做一些业务上的校验,判断是否需要校验
+        }
+        return baseMapper.deleteByIds(ids) > 0;
+    }
+}

+ 178 - 1
ruoyi-modules/ruoyi-product/src/main/resources/mapper/product/ProductBaseMapper.xml

@@ -6,7 +6,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
 
     <!-- 站点产品列表查询(联表查询) -->
     <select id="selectSiteProductPage" resultType="org.dromara.product.domain.vo.SiteProductVo">
-        SELECT DISTINCT
+        SELECT
             b.id,
             b.product_no AS productNo,
             b.item_name AS productName,
@@ -31,6 +31,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             mc.category_name AS mediumCategoryName,
             b.bottom_category_id AS bottomCategoryId,
             bc.category_name AS bottomCategoryName,
+            GROUP_CONCAT(DISTINCT gcl.category_name) AS giftCategoryName,
             b.remark
         FROM product_base b
         LEFT JOIN product_price_inventory p ON b.id = p.product_id AND p.del_flag = '0'
@@ -38,6 +39,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         LEFT JOIN product_category tc ON b.top_category_id = tc.id AND tc.del_flag = '0'
         LEFT JOIN product_category mc ON b.medium_category_id = mc.id AND mc.del_flag = '0'
         LEFT JOIN product_category bc ON b.bottom_category_id = bc.id AND bc.del_flag = '0'
+        LEFT JOIN product_gift_category_link gcl ON b.product_no = gcl.product_no AND gcl.del_flag = '0'
         <where>
             b.del_flag = '0'
             <if test="bo.keyword != null and bo.keyword != ''">
@@ -65,6 +67,181 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
                 AND b.product_status = #{bo.productStatus}
             </if>
         </where>
+        GROUP BY b.id, b.product_no, b.item_name, b.product_image, b.is_self, 
+                 b.product_review_status, b.product_status, b.brand_id, 
+                 b.top_category_id, b.medium_category_id, b.bottom_category_id, b.remark,
+                 p.market_price, p.member_price, p.min_selling_price, p.purchasing_price,
+                 p.max_purchase_price, p.total_inventory, p.now_inventory, p.virtual_inventory,
+                 p.min_order_quantity, br.brand_name, tc.category_name, mc.category_name, bc.category_name
+        ORDER BY b.create_time DESC
+    </select>
+
+    <!-- 推荐商品列表查询(联表查询分类名称) -->
+    <select id="selectRecommendProductPage" resultType="org.dromara.product.domain.vo.RecommendProductVo">
+        SELECT
+            b.id,
+            b.product_no AS productNo,
+            b.item_name AS itemName,
+            b.product_image AS productImage,
+            tc.category_name AS topCategoryName,
+            mc.category_name AS mediumCategoryName,
+            bc.category_name AS bottomCategoryName,
+            b.product_status AS productStatus,
+            b.home_recommended AS homeRecommended,
+            b.category_recommendation AS categoryRecommendation,
+            b.cart_recommendation AS cartRecommendation,
+            b.is_popular AS isPopular,
+            b.recommended_product_order AS recommendedProductOrder,
+            p.market_price AS marketPrice,
+            p.min_selling_price AS minSellingPrice
+        FROM product_base b
+        LEFT JOIN product_price_inventory p ON b.id = p.product_id AND p.del_flag = '0'
+        LEFT JOIN product_category tc ON b.top_category_id = tc.id AND tc.del_flag = '0'
+        LEFT JOIN product_category mc ON b.medium_category_id = mc.id AND mc.del_flag = '0'
+        LEFT JOIN product_category bc ON b.bottom_category_id = bc.id AND bc.del_flag = '0'
+        <where>
+            b.del_flag = '0'
+            <if test="bo.productNo != null and bo.productNo != ''">
+                AND b.product_no LIKE CONCAT('%', #{bo.productNo}, '%')
+            </if>
+            <if test="bo.itemName != null and bo.itemName != ''">
+                AND b.item_name LIKE CONCAT('%', #{bo.itemName}, '%')
+            </if>
+            <if test="bo.brandId != null">
+                AND b.brand_id = #{bo.brandId}
+            </if>
+            <if test="bo.productStatus != null and bo.productStatus != ''">
+                AND b.product_status = #{bo.productStatus}
+            </if>
+            <if test="bo.topCategoryId != null">
+                AND b.top_category_id = #{bo.topCategoryId}
+            </if>
+            <if test="bo.mediumCategoryId != null">
+                AND b.medium_category_id = #{bo.mediumCategoryId}
+            </if>
+            <if test="bo.bottomCategoryId != null">
+                AND b.bottom_category_id = #{bo.bottomCategoryId}
+            </if>
+            <if test="bo.homeRecommended != null and bo.homeRecommended != ''">
+                AND b.home_recommended = #{bo.homeRecommended}
+            </if>
+            <if test="bo.categoryRecommendation != null and bo.categoryRecommendation != ''">
+                AND b.category_recommendation = #{bo.categoryRecommendation}
+            </if>
+            <if test="bo.cartRecommendation != null and bo.cartRecommendation != ''">
+                AND b.cart_recommendation = #{bo.cartRecommendation}
+            </if>
+        </where>
+        ORDER BY b.recommended_product_order ASC, b.create_time DESC
+    </select>
+
+    <!-- 商品运营列表查询(联表查询) -->
+    <select id="selectProductOperationPage" resultType="org.dromara.product.domain.vo.ProductOperationVo">
+        SELECT
+            b.id,
+            b.product_no AS productNo,
+            b.item_name AS itemName,
+            b.product_image AS productImage,
+            b.brand_id AS brandId,
+            br.brand_name AS brandName,
+            b.top_category_id AS topCategoryId,
+            tc.category_name AS topCategoryName,
+            b.medium_category_id AS mediumCategoryId,
+            mc.category_name AS mediumCategoryName,
+            b.bottom_category_id AS bottomCategoryId,
+            bc.category_name AS bottomCategoryName,
+            u.unit_name AS unitName,
+            p.min_order_quantity AS minOrderQuantity,
+            p.market_price AS marketPrice,
+            p.member_price AS memberPrice,
+            p.min_selling_price AS minSellingPrice,
+            p.total_inventory AS totalInventory,
+            p.now_inventory AS nowInventory,
+            p.virtual_inventory AS virtualInventory,
+            b.product_status AS productStatus
+        FROM product_base b
+        LEFT JOIN product_price_inventory p ON b.id = p.product_id AND p.del_flag = '0'
+        LEFT JOIN product_brand br ON b.brand_id = br.id AND br.del_flag = '0'
+        LEFT JOIN product_category tc ON b.top_category_id = tc.id AND tc.del_flag = '0'
+        LEFT JOIN product_category mc ON b.medium_category_id = mc.id AND mc.del_flag = '0'
+        LEFT JOIN product_category bc ON b.bottom_category_id = bc.id AND bc.del_flag = '0'
+        LEFT JOIN product_unit u ON b.unit_id = u.id AND u.del_flag = '0'
+        <where>
+            b.del_flag = '0'
+            <if test="bo.productNo != null and bo.productNo != ''">
+                AND b.product_no LIKE CONCAT('%', #{bo.productNo}, '%')
+            </if>
+            <if test="bo.itemName != null and bo.itemName != ''">
+                AND b.item_name LIKE CONCAT('%', #{bo.itemName}, '%')
+            </if>
+            <if test="bo.brandId != null">
+                AND b.brand_id = #{bo.brandId}
+            </if>
+            <if test="bo.productStatus != null and bo.productStatus != ''">
+                AND b.product_status = #{bo.productStatus}
+            </if>
+            <if test="bo.topCategoryId != null">
+                AND b.top_category_id = #{bo.topCategoryId}
+            </if>
+            <if test="bo.mediumCategoryId != null">
+                AND b.medium_category_id = #{bo.mediumCategoryId}
+            </if>
+            <if test="bo.bottomCategoryId != null">
+                AND b.bottom_category_id = #{bo.bottomCategoryId}
+            </if>
+        </where>
+        ORDER BY b.create_time DESC
+    </select>
+
+    <!-- 商品简化列表查询(用于选择弹窗) -->
+    <select id="selectSimplePage" resultType="org.dromara.product.domain.vo.ProductBaseSimpleVo">
+        SELECT
+            b.id,
+            b.product_no AS productNo,
+            b.item_name AS itemName,
+            b.product_image AS productImage,
+            p.market_price AS marketPrice,
+            p.member_price AS memberPrice,
+            e.specifications_code AS specification,
+            b.top_category_id AS topCategoryId,
+            tc.category_name AS topCategoryName,
+            b.medium_category_id AS mediumCategoryId,
+            mc.category_name AS mediumCategoryName,
+            b.bottom_category_id AS bottomCategoryId,
+            bc.category_name AS bottomCategoryName,
+            b.home_recommended AS homeRecommended,
+            b.category_recommendation AS categoryRecommendation,
+            b.cart_recommendation AS cartRecommendation,
+            b.recommended_product_order AS recommendedProductOrder,
+            b.is_popular AS isPopular,
+            b.product_status AS productStatus
+        FROM product_base b
+        LEFT JOIN product_price_inventory p ON b.id = p.product_id AND p.del_flag = '0'
+        LEFT JOIN product_extend e ON b.id = e.product_id AND e.del_flag = '0'
+        LEFT JOIN product_category tc ON b.top_category_id = tc.id AND tc.del_flag = '0'
+        LEFT JOIN product_category mc ON b.medium_category_id = mc.id AND mc.del_flag = '0'
+        LEFT JOIN product_category bc ON b.bottom_category_id = bc.id AND bc.del_flag = '0'
+        <where>
+            b.del_flag = '0'
+            <if test="bo.productNo != null and bo.productNo != ''">
+                AND b.product_no LIKE CONCAT('%', #{bo.productNo}, '%')
+            </if>
+            <if test="bo.itemName != null and bo.itemName != ''">
+                AND b.item_name LIKE CONCAT('%', #{bo.itemName}, '%')
+            </if>
+            <if test="bo.productStatus != null and bo.productStatus != ''">
+                AND b.product_status = #{bo.productStatus}
+            </if>
+            <if test="bo.topCategoryId != null">
+                AND b.top_category_id = #{bo.topCategoryId}
+            </if>
+            <if test="bo.mediumCategoryId != null">
+                AND b.medium_category_id = #{bo.mediumCategoryId}
+            </if>
+            <if test="bo.bottomCategoryId != null">
+                AND b.bottom_category_id = #{bo.bottomCategoryId}
+            </if>
+        </where>
         ORDER BY b.create_time DESC
     </select>
     <select id="selectAllList" resultType="org.dromara.product.domain.vo.ProductBaseVo">

+ 41 - 0
ruoyi-modules/ruoyi-product/src/main/resources/mapper/product/ProductBlacklistMapper.xml

@@ -4,4 +4,45 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
 "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
 <mapper namespace="org.dromara.product.mapper.ProductBlacklistMapper">
 
+    <!-- 分页查询黑名单列表(关联商品和分类) -->
+    <select id="selectBlacklistPage" resultType="org.dromara.product.domain.vo.ProductBlacklistVo">
+        SELECT
+            pb.id,
+            pb.customer_id AS customerId,
+            pb.product_id AS productId,
+            p.product_no AS productNo,
+            p.item_name AS productName,
+            pb.product_category_id AS productCategoryId,
+            pc.category_no AS categoryNo,
+            pc.category_name AS categoryName,
+            pb.remark
+        FROM product_blacklist pb
+        LEFT JOIN product_base p ON pb.product_id = p.id AND p.del_flag = '0'
+        LEFT JOIN product_category pc ON pb.product_category_id = pc.id AND pc.del_flag = '0'
+        <where>
+            pb.del_flag = '0'
+            <if test="bo.customerId != null">
+                AND pb.customer_id = #{bo.customerId}
+            </if>
+            <if test="bo.productId != null">
+                AND pb.product_id = #{bo.productId}
+            </if>
+            <if test="bo.productCategoryId != null">
+                AND pb.product_category_id = #{bo.productCategoryId}
+            </if>
+            <if test="bo.platformCode != null and bo.platformCode != ''">
+                AND pb.platform_code = #{bo.platformCode}
+            </if>
+            <!-- 商品黑名单:productId 不为空 -->
+            <if test="bo.blacklistType != null and bo.blacklistType == 'product'">
+                AND pb.product_id IS NOT NULL
+            </if>
+            <!-- 分类黑名单:productId 为空 -->
+            <if test="bo.blacklistType != null and bo.blacklistType == 'category'">
+                AND pb.product_id IS NULL
+            </if>
+        </where>
+        ORDER BY pb.id DESC
+    </select>
+
 </mapper>

+ 2 - 1
ruoyi-modules/ruoyi-product/src/main/resources/mapper/product/ProductGiftFloorLinkMapper.xml

@@ -16,9 +16,10 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             l.remark,
             p.item_name,
             p.product_image,
-            p.min_selling_price
+            ppi.min_selling_price
         FROM product_gift_floor_link l
         LEFT JOIN product_base p ON l.product_id = p.id
+        LEFT JOIN product_price_inventory ppi ON p.id = ppi.product_id
         WHERE l.del_flag = '0'
         <if test="floorId != null">
             AND l.floor_id = #{floorId}

+ 2 - 1
ruoyi-modules/ruoyi-product/src/main/resources/mapper/product/ProductIndustrialFloorLinkMapper.xml

@@ -19,9 +19,10 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             l.tenant_id,
             p.item_name,
             p.product_image,
-            p.min_selling_price
+            pi.min_selling_price
         FROM product_industrial_floor_link l
         LEFT JOIN product_base p ON l.product_id = p.id
+        LEFT JOIN product_price_inventory pi ON p.id = pi.product_id
         <where>
             l.del_flag = '0'
             <if test="floorId != null">

+ 7 - 0
ruoyi-modules/ruoyi-product/src/main/resources/mapper/product/ProductOperationInfoMapper.xml

@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="org.dromara.product.mapper.ProductOperationInfoMapper">
+
+</mapper>

+ 40 - 0
ruoyi-modules/ruoyi-product/src/main/resources/mapper/product/ProductWarehouseInventoryMapper.xml

@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="org.dromara.product.mapper.ProductWarehouseInventoryMapper">
+
+    <!-- 查询仓库库存明细列表(关联商品信息) -->
+    <select id="selectWarehouseInventoryList" resultType="org.dromara.product.domain.vo.ProductWarehouseInventoryVo">
+        SELECT
+            wi.id,
+            wi.product_id AS productId,
+            pb.product_no AS productNo,
+            pb.item_name AS productName,
+            wi.sku_id AS skuId,
+            wi.spec_model AS specModel,
+            wi.warehouse_id AS warehouseId,
+            wi.warehouse_no AS warehouseNo,
+            wi.warehouse_name AS warehouseName,
+            wi.now_inventory AS nowInventory,
+            wi.lock_inventory AS lockInventory,
+            wi.status,
+            wi.remark
+        FROM product_warehouse_inventory wi
+        LEFT JOIN product_base pb ON wi.product_id = pb.id AND pb.del_flag = '0'
+        <where>
+            wi.del_flag = '0'
+            <if test="bo.productId != null">
+                AND wi.product_id = #{bo.productId}
+            </if>
+            <if test="bo.warehouseId != null">
+                AND wi.warehouse_id = #{bo.warehouseId}
+            </if>
+            <if test="bo.warehouseNo != null and bo.warehouseNo != ''">
+                AND wi.warehouse_no = #{bo.warehouseNo}
+            </if>
+        </where>
+        ORDER BY wi.create_time DESC
+    </select>
+
+</mapper>

+ 55 - 3
ruoyi-modules/ruoyi-resource/src/main/java/org/dromara/resource/controller/SysSmsController.java

@@ -3,6 +3,8 @@ package org.dromara.resource.controller;
 
 import cn.hutool.core.util.RandomUtil;
 import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotEmpty;
+import lombok.Data;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.dromara.common.core.constant.Constants;
@@ -15,12 +17,11 @@ import org.dromara.sms4j.api.SmsBlend;
 import org.dromara.sms4j.api.entity.SmsResponse;
 import org.dromara.sms4j.core.factory.SmsFactory;
 import org.springframework.validation.annotation.Validated;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.bind.annotation.*;
 
 import java.time.Duration;
 import java.util.LinkedHashMap;
+import java.util.List;
 
 /**
  * 短信功能
@@ -58,4 +59,55 @@ public class SysSmsController extends BaseController {
         return R.ok();
     }
 
+    /**
+     * 群发短信
+     *
+     * @param request 群发短信请求
+     */
+    @PostMapping("/batch")
+    public R<Void> batchSend(@Validated @RequestBody SmsBatchRequest request) {
+        List<String> phones = request.getPhones();
+        String message = request.getMessage();
+        
+        if (phones.isEmpty()) {
+            return R.fail("手机号列表不能为空");
+        }
+        if (phones.size() > 1000) {
+            return R.fail("单次群发不能超过1000个手机号");
+        }
+        
+        log.info("群发短信 => 手机号数量: {}, 内容: {}", phones.size(), message);
+        
+        try {
+            SmsBlend smsBlend = SmsFactory.getSmsBlend("config1");
+            SmsResponse smsResponse = smsBlend.massTexting(phones, message);
+            if (!smsResponse.isSuccess()) {
+                log.error("群发短信失败 => {}", smsResponse);
+                return R.fail("发送失败: " + smsResponse.getData());
+            }
+            return R.ok();
+        } catch (Exception e) {
+            log.error("群发短信异常", e);
+            return R.fail("发送异常: " + e.getMessage());
+        }
+    }
+
+    /**
+     * 群发短信请求
+     */
+    @Data
+    public static class SmsBatchRequest {
+        /**
+         * 手机号列表
+         */
+        @NotEmpty(message = "手机号列表不能为空")
+        private List<String> phones;
+
+        /**
+         * 短信内容
+         */
+        @NotBlank(message = "短信内容不能为空")
+        private String message;
+    }
+
 }

+ 106 - 0
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysAdaptSceneController.java

@@ -0,0 +1,106 @@
+package org.dromara.system.controller;
+
+import java.util.List;
+
+import lombok.RequiredArgsConstructor;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.validation.constraints.*;
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.validation.annotation.Validated;
+import org.dromara.common.idempotent.annotation.RepeatSubmit;
+import org.dromara.common.log.annotation.Log;
+import org.dromara.common.web.core.BaseController;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.core.validate.AddGroup;
+import org.dromara.common.core.validate.EditGroup;
+import org.dromara.common.log.enums.BusinessType;
+import org.dromara.common.excel.utils.ExcelUtil;
+import org.dromara.system.domain.vo.SysAdaptSceneVo;
+import org.dromara.system.domain.bo.SysAdaptSceneBo;
+import org.dromara.system.service.ISysAdaptSceneService;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+
+/**
+ * 适配场景
+ * 前端访问路由地址为:/system/adaptScene
+ *
+ * @author LionLi
+ * @date 2026-01-05
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/adaptScene")
+public class SysAdaptSceneController extends BaseController {
+
+    private final ISysAdaptSceneService sysAdaptSceneService;
+
+    /**
+     * 查询适配场景列表
+     */
+    @SaCheckPermission("system:adaptScene:list")
+    @GetMapping("/list")
+    public TableDataInfo<SysAdaptSceneVo> list(SysAdaptSceneBo bo, PageQuery pageQuery) {
+        return sysAdaptSceneService.queryPageList(bo, pageQuery);
+    }
+
+    /**
+     * 导出适配场景列表
+     */
+    @SaCheckPermission("system:adaptScene:export")
+    @Log(title = "适配场景", businessType = BusinessType.EXPORT)
+    @PostMapping("/export")
+    public void export(SysAdaptSceneBo bo, HttpServletResponse response) {
+        List<SysAdaptSceneVo> list = sysAdaptSceneService.queryList(bo);
+        ExcelUtil.exportExcel(list, "适配场景", SysAdaptSceneVo.class, response);
+    }
+
+    /**
+     * 获取适配场景详细信息
+     *
+     * @param id 主键
+     */
+    @SaCheckPermission("system:adaptScene:query")
+    @GetMapping("/{id}")
+    public R<SysAdaptSceneVo> getInfo(@NotNull(message = "主键不能为空")
+                                     @PathVariable("id") Long id) {
+        return R.ok(sysAdaptSceneService.queryById(id));
+    }
+
+    /**
+     * 新增适配场景
+     */
+    @SaCheckPermission("system:adaptScene:add")
+    @Log(title = "适配场景", businessType = BusinessType.INSERT)
+    @RepeatSubmit()
+    @PostMapping()
+    public R<Void> add(@Validated(AddGroup.class) @RequestBody SysAdaptSceneBo bo) {
+        return toAjax(sysAdaptSceneService.insertByBo(bo));
+    }
+
+    /**
+     * 修改适配场景
+     */
+    @SaCheckPermission("system:adaptScene:edit")
+    @Log(title = "适配场景", businessType = BusinessType.UPDATE)
+    @RepeatSubmit()
+    @PutMapping()
+    public R<Void> edit(@Validated(EditGroup.class) @RequestBody SysAdaptSceneBo bo) {
+        return toAjax(sysAdaptSceneService.updateByBo(bo));
+    }
+
+    /**
+     * 删除适配场景
+     *
+     * @param ids 主键串
+     */
+    @SaCheckPermission("system:adaptScene:remove")
+    @Log(title = "适配场景", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{ids}")
+    public R<Void> remove(@NotEmpty(message = "主键不能为空")
+                          @PathVariable("ids") Long[] ids) {
+        return toAjax(sysAdaptSceneService.deleteWithValidByIds(List.of(ids), true));
+    }
+}

+ 106 - 0
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysAddressAreaController.java

@@ -0,0 +1,106 @@
+package org.dromara.system.controller;
+
+import java.util.List;
+
+import lombok.RequiredArgsConstructor;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.validation.constraints.*;
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.validation.annotation.Validated;
+import org.dromara.common.idempotent.annotation.RepeatSubmit;
+import org.dromara.common.log.annotation.Log;
+import org.dromara.common.web.core.BaseController;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.core.validate.AddGroup;
+import org.dromara.common.core.validate.EditGroup;
+import org.dromara.common.log.enums.BusinessType;
+import org.dromara.common.excel.utils.ExcelUtil;
+import org.dromara.system.domain.vo.SysAddressAreaVo;
+import org.dromara.system.domain.bo.SysAddressAreaBo;
+import org.dromara.system.service.ISysAddressAreaService;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+
+/**
+ * 地址区域
+ * 前端访问路由地址为:/system/addressArea
+ *
+ * @author LionLi
+ * @date 2026-01-05
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/addressArea")
+public class SysAddressAreaController extends BaseController {
+
+    private final ISysAddressAreaService sysAddressAreaService;
+
+    /**
+     * 查询地址区域列表
+     */
+    @SaCheckPermission("system:addressArea:list")
+    @GetMapping("/list")
+    public TableDataInfo<SysAddressAreaVo> list(SysAddressAreaBo bo, PageQuery pageQuery) {
+        return sysAddressAreaService.queryPageList(bo, pageQuery);
+    }
+
+    /**
+     * 导出地址区域列表
+     */
+    @SaCheckPermission("system:addressArea:export")
+    @Log(title = "地址区域", businessType = BusinessType.EXPORT)
+    @PostMapping("/export")
+    public void export(SysAddressAreaBo bo, HttpServletResponse response) {
+        List<SysAddressAreaVo> list = sysAddressAreaService.queryList(bo);
+        ExcelUtil.exportExcel(list, "地址区域", SysAddressAreaVo.class, response);
+    }
+
+    /**
+     * 获取地址区域详细信息
+     *
+     * @param id 主键
+     */
+    @SaCheckPermission("system:addressArea:query")
+    @GetMapping("/{id}")
+    public R<SysAddressAreaVo> getInfo(@NotNull(message = "主键不能为空")
+                                     @PathVariable("id") Long id) {
+        return R.ok(sysAddressAreaService.queryById(id));
+    }
+
+    /**
+     * 新增地址区域
+     */
+    @SaCheckPermission("system:addressArea:add")
+    @Log(title = "地址区域", businessType = BusinessType.INSERT)
+    @RepeatSubmit()
+    @PostMapping()
+    public R<Void> add(@Validated(AddGroup.class) @RequestBody SysAddressAreaBo bo) {
+        return toAjax(sysAddressAreaService.insertByBo(bo));
+    }
+
+    /**
+     * 修改地址区域
+     */
+    @SaCheckPermission("system:addressArea:edit")
+    @Log(title = "地址区域", businessType = BusinessType.UPDATE)
+    @RepeatSubmit()
+    @PutMapping()
+    public R<Void> edit(@Validated(EditGroup.class) @RequestBody SysAddressAreaBo bo) {
+        return toAjax(sysAddressAreaService.updateByBo(bo));
+    }
+
+    /**
+     * 删除地址区域
+     *
+     * @param ids 主键串
+     */
+    @SaCheckPermission("system:addressArea:remove")
+    @Log(title = "地址区域", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{ids}")
+    public R<Void> remove(@NotEmpty(message = "主键不能为空")
+                          @PathVariable("ids") Long[] ids) {
+        return toAjax(sysAddressAreaService.deleteWithValidByIds(List.of(ids), true));
+    }
+}

+ 106 - 0
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysOperationMessageController.java

@@ -0,0 +1,106 @@
+package org.dromara.system.controller;
+
+import java.util.List;
+
+import lombok.RequiredArgsConstructor;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.validation.constraints.*;
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.validation.annotation.Validated;
+import org.dromara.common.idempotent.annotation.RepeatSubmit;
+import org.dromara.common.log.annotation.Log;
+import org.dromara.common.web.core.BaseController;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.core.validate.AddGroup;
+import org.dromara.common.core.validate.EditGroup;
+import org.dromara.common.log.enums.BusinessType;
+import org.dromara.common.excel.utils.ExcelUtil;
+import org.dromara.system.domain.vo.SysOperationMessageVo;
+import org.dromara.system.domain.bo.SysOperationMessageBo;
+import org.dromara.system.service.ISysOperationMessageService;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+
+/**
+ * 运营消息
+ * 前端访问路由地址为:/system/operationMessage
+ *
+ * @author LionLi
+ * @date 2026-01-06
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/operationMessage")
+public class SysOperationMessageController extends BaseController {
+
+    private final ISysOperationMessageService sysOperationMessageService;
+
+    /**
+     * 查询运营消息列表
+     */
+    @SaCheckPermission("system:operationMessage:list")
+    @GetMapping("/list")
+    public TableDataInfo<SysOperationMessageVo> list(SysOperationMessageBo bo, PageQuery pageQuery) {
+        return sysOperationMessageService.queryPageList(bo, pageQuery);
+    }
+
+    /**
+     * 导出运营消息列表
+     */
+    @SaCheckPermission("system:operationMessage:export")
+    @Log(title = "运营消息", businessType = BusinessType.EXPORT)
+    @PostMapping("/export")
+    public void export(SysOperationMessageBo bo, HttpServletResponse response) {
+        List<SysOperationMessageVo> list = sysOperationMessageService.queryList(bo);
+        ExcelUtil.exportExcel(list, "运营消息", SysOperationMessageVo.class, response);
+    }
+
+    /**
+     * 获取运营消息详细信息
+     *
+     * @param id 主键
+     */
+    @SaCheckPermission("system:operationMessage:query")
+    @GetMapping("/{id}")
+    public R<SysOperationMessageVo> getInfo(@NotNull(message = "主键不能为空")
+                                     @PathVariable("id") Long id) {
+        return R.ok(sysOperationMessageService.queryById(id));
+    }
+
+    /**
+     * 新增运营消息
+     */
+    @SaCheckPermission("system:operationMessage:add")
+    @Log(title = "运营消息", businessType = BusinessType.INSERT)
+    @RepeatSubmit()
+    @PostMapping()
+    public R<Void> add(@Validated(AddGroup.class) @RequestBody SysOperationMessageBo bo) {
+        return toAjax(sysOperationMessageService.insertByBo(bo));
+    }
+
+    /**
+     * 修改运营消息
+     */
+    @SaCheckPermission("system:operationMessage:edit")
+    @Log(title = "运营消息", businessType = BusinessType.UPDATE)
+    @RepeatSubmit()
+    @PutMapping()
+    public R<Void> edit(@Validated(EditGroup.class) @RequestBody SysOperationMessageBo bo) {
+        return toAjax(sysOperationMessageService.updateByBo(bo));
+    }
+
+    /**
+     * 删除运营消息
+     *
+     * @param ids 主键串
+     */
+    @SaCheckPermission("system:operationMessage:remove")
+    @Log(title = "运营消息", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{ids}")
+    public R<Void> remove(@NotEmpty(message = "主键不能为空")
+                          @PathVariable("ids") Long[] ids) {
+        return toAjax(sysOperationMessageService.deleteWithValidByIds(List.of(ids), true));
+    }
+}

+ 106 - 0
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysPriceRangeController.java

@@ -0,0 +1,106 @@
+package org.dromara.system.controller;
+
+import java.util.List;
+
+import lombok.RequiredArgsConstructor;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.validation.constraints.*;
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.validation.annotation.Validated;
+import org.dromara.common.idempotent.annotation.RepeatSubmit;
+import org.dromara.common.log.annotation.Log;
+import org.dromara.common.web.core.BaseController;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.core.validate.AddGroup;
+import org.dromara.common.core.validate.EditGroup;
+import org.dromara.common.log.enums.BusinessType;
+import org.dromara.common.excel.utils.ExcelUtil;
+import org.dromara.system.domain.vo.SysPriceRangeVo;
+import org.dromara.system.domain.bo.SysPriceRangeBo;
+import org.dromara.system.service.ISysPriceRangeService;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+
+/**
+ * 价格区间
+ * 前端访问路由地址为:/system/priceRange
+ *
+ * @author LionLi
+ * @date 2026-01-05
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/priceRange")
+public class SysPriceRangeController extends BaseController {
+
+    private final ISysPriceRangeService sysPriceRangeService;
+
+    /**
+     * 查询价格区间列表
+     */
+    @SaCheckPermission("system:priceRange:list")
+    @GetMapping("/list")
+    public TableDataInfo<SysPriceRangeVo> list(SysPriceRangeBo bo, PageQuery pageQuery) {
+        return sysPriceRangeService.queryPageList(bo, pageQuery);
+    }
+
+    /**
+     * 导出价格区间列表
+     */
+    @SaCheckPermission("system:priceRange:export")
+    @Log(title = "价格区间", businessType = BusinessType.EXPORT)
+    @PostMapping("/export")
+    public void export(SysPriceRangeBo bo, HttpServletResponse response) {
+        List<SysPriceRangeVo> list = sysPriceRangeService.queryList(bo);
+        ExcelUtil.exportExcel(list, "价格区间", SysPriceRangeVo.class, response);
+    }
+
+    /**
+     * 获取价格区间详细信息
+     *
+     * @param id 主键
+     */
+    @SaCheckPermission("system:priceRange:query")
+    @GetMapping("/{id}")
+    public R<SysPriceRangeVo> getInfo(@NotNull(message = "主键不能为空")
+                                     @PathVariable("id") Long id) {
+        return R.ok(sysPriceRangeService.queryById(id));
+    }
+
+    /**
+     * 新增价格区间
+     */
+    @SaCheckPermission("system:priceRange:add")
+    @Log(title = "价格区间", businessType = BusinessType.INSERT)
+    @RepeatSubmit()
+    @PostMapping()
+    public R<Void> add(@Validated(AddGroup.class) @RequestBody SysPriceRangeBo bo) {
+        return toAjax(sysPriceRangeService.insertByBo(bo));
+    }
+
+    /**
+     * 修改价格区间
+     */
+    @SaCheckPermission("system:priceRange:edit")
+    @Log(title = "价格区间", businessType = BusinessType.UPDATE)
+    @RepeatSubmit()
+    @PutMapping()
+    public R<Void> edit(@Validated(EditGroup.class) @RequestBody SysPriceRangeBo bo) {
+        return toAjax(sysPriceRangeService.updateByBo(bo));
+    }
+
+    /**
+     * 删除价格区间
+     *
+     * @param ids 主键串
+     */
+    @SaCheckPermission("system:priceRange:remove")
+    @Log(title = "价格区间", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{ids}")
+    public R<Void> remove(@NotEmpty(message = "主键不能为空")
+                          @PathVariable("ids") Long[] ids) {
+        return toAjax(sysPriceRangeService.deleteWithValidByIds(List.of(ids), true));
+    }
+}

+ 106 - 0
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysProjectTypeController.java

@@ -0,0 +1,106 @@
+package org.dromara.system.controller;
+
+import java.util.List;
+
+import lombok.RequiredArgsConstructor;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.validation.constraints.*;
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.validation.annotation.Validated;
+import org.dromara.common.idempotent.annotation.RepeatSubmit;
+import org.dromara.common.log.annotation.Log;
+import org.dromara.common.web.core.BaseController;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.core.validate.AddGroup;
+import org.dromara.common.core.validate.EditGroup;
+import org.dromara.common.log.enums.BusinessType;
+import org.dromara.common.excel.utils.ExcelUtil;
+import org.dromara.system.domain.vo.SysProjectTypeVo;
+import org.dromara.system.domain.bo.SysProjectTypeBo;
+import org.dromara.system.service.ISysProjectTypeService;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+
+/**
+ * 项目类型
+ * 前端访问路由地址为:/system/projectType
+ *
+ * @author LionLi
+ * @date 2026-01-05
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/projectType")
+public class SysProjectTypeController extends BaseController {
+
+    private final ISysProjectTypeService sysProjectTypeService;
+
+    /**
+     * 查询项目类型列表
+     */
+    @SaCheckPermission("system:projectType:list")
+    @GetMapping("/list")
+    public TableDataInfo<SysProjectTypeVo> list(SysProjectTypeBo bo, PageQuery pageQuery) {
+        return sysProjectTypeService.queryPageList(bo, pageQuery);
+    }
+
+    /**
+     * 导出项目类型列表
+     */
+    @SaCheckPermission("system:projectType:export")
+    @Log(title = "项目类型", businessType = BusinessType.EXPORT)
+    @PostMapping("/export")
+    public void export(SysProjectTypeBo bo, HttpServletResponse response) {
+        List<SysProjectTypeVo> list = sysProjectTypeService.queryList(bo);
+        ExcelUtil.exportExcel(list, "项目类型", SysProjectTypeVo.class, response);
+    }
+
+    /**
+     * 获取项目类型详细信息
+     *
+     * @param id 主键
+     */
+    @SaCheckPermission("system:projectType:query")
+    @GetMapping("/{id}")
+    public R<SysProjectTypeVo> getInfo(@NotNull(message = "主键不能为空")
+                                     @PathVariable("id") Long id) {
+        return R.ok(sysProjectTypeService.queryById(id));
+    }
+
+    /**
+     * 新增项目类型
+     */
+    @SaCheckPermission("system:projectType:add")
+    @Log(title = "项目类型", businessType = BusinessType.INSERT)
+    @RepeatSubmit()
+    @PostMapping()
+    public R<Void> add(@Validated(AddGroup.class) @RequestBody SysProjectTypeBo bo) {
+        return toAjax(sysProjectTypeService.insertByBo(bo));
+    }
+
+    /**
+     * 修改项目类型
+     */
+    @SaCheckPermission("system:projectType:edit")
+    @Log(title = "项目类型", businessType = BusinessType.UPDATE)
+    @RepeatSubmit()
+    @PutMapping()
+    public R<Void> edit(@Validated(EditGroup.class) @RequestBody SysProjectTypeBo bo) {
+        return toAjax(sysProjectTypeService.updateByBo(bo));
+    }
+
+    /**
+     * 删除项目类型
+     *
+     * @param ids 主键串
+     */
+    @SaCheckPermission("system:projectType:remove")
+    @Log(title = "项目类型", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{ids}")
+    public R<Void> remove(@NotEmpty(message = "主键不能为空")
+                          @PathVariable("ids") Long[] ids) {
+        return toAjax(sysProjectTypeService.deleteWithValidByIds(List.of(ids), true));
+    }
+}

+ 106 - 0
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysPurchaseCategoryController.java

@@ -0,0 +1,106 @@
+package org.dromara.system.controller;
+
+import java.util.List;
+
+import lombok.RequiredArgsConstructor;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.validation.constraints.*;
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.validation.annotation.Validated;
+import org.dromara.common.idempotent.annotation.RepeatSubmit;
+import org.dromara.common.log.annotation.Log;
+import org.dromara.common.web.core.BaseController;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.core.validate.AddGroup;
+import org.dromara.common.core.validate.EditGroup;
+import org.dromara.common.log.enums.BusinessType;
+import org.dromara.common.excel.utils.ExcelUtil;
+import org.dromara.system.domain.vo.SysPurchaseCategoryVo;
+import org.dromara.system.domain.bo.SysPurchaseCategoryBo;
+import org.dromara.system.service.ISysPurchaseCategoryService;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+
+/**
+ * 采购分类
+ * 前端访问路由地址为:/system/purchaseCategory
+ *
+ * @author LionLi
+ * @date 2026-01-05
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/purchaseCategory")
+public class SysPurchaseCategoryController extends BaseController {
+
+    private final ISysPurchaseCategoryService sysPurchaseCategoryService;
+
+    /**
+     * 查询采购分类列表
+     */
+    @SaCheckPermission("system:purchaseCategory:list")
+    @GetMapping("/list")
+    public TableDataInfo<SysPurchaseCategoryVo> list(SysPurchaseCategoryBo bo, PageQuery pageQuery) {
+        return sysPurchaseCategoryService.queryPageList(bo, pageQuery);
+    }
+
+    /**
+     * 导出采购分类列表
+     */
+    @SaCheckPermission("system:purchaseCategory:export")
+    @Log(title = "采购分类", businessType = BusinessType.EXPORT)
+    @PostMapping("/export")
+    public void export(SysPurchaseCategoryBo bo, HttpServletResponse response) {
+        List<SysPurchaseCategoryVo> list = sysPurchaseCategoryService.queryList(bo);
+        ExcelUtil.exportExcel(list, "采购分类", SysPurchaseCategoryVo.class, response);
+    }
+
+    /**
+     * 获取采购分类详细信息
+     *
+     * @param id 主键
+     */
+    @SaCheckPermission("system:purchaseCategory:query")
+    @GetMapping("/{id}")
+    public R<SysPurchaseCategoryVo> getInfo(@NotNull(message = "主键不能为空")
+                                     @PathVariable("id") Long id) {
+        return R.ok(sysPurchaseCategoryService.queryById(id));
+    }
+
+    /**
+     * 新增采购分类
+     */
+    @SaCheckPermission("system:purchaseCategory:add")
+    @Log(title = "采购分类", businessType = BusinessType.INSERT)
+    @RepeatSubmit()
+    @PostMapping()
+    public R<Void> add(@Validated(AddGroup.class) @RequestBody SysPurchaseCategoryBo bo) {
+        return toAjax(sysPurchaseCategoryService.insertByBo(bo));
+    }
+
+    /**
+     * 修改采购分类
+     */
+    @SaCheckPermission("system:purchaseCategory:edit")
+    @Log(title = "采购分类", businessType = BusinessType.UPDATE)
+    @RepeatSubmit()
+    @PutMapping()
+    public R<Void> edit(@Validated(EditGroup.class) @RequestBody SysPurchaseCategoryBo bo) {
+        return toAjax(sysPurchaseCategoryService.updateByBo(bo));
+    }
+
+    /**
+     * 删除采购分类
+     *
+     * @param ids 主键串
+     */
+    @SaCheckPermission("system:purchaseCategory:remove")
+    @Log(title = "采购分类", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{ids}")
+    public R<Void> remove(@NotEmpty(message = "主键不能为空")
+                          @PathVariable("ids") Long[] ids) {
+        return toAjax(sysPurchaseCategoryService.deleteWithValidByIds(List.of(ids), true));
+    }
+}

+ 106 - 0
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysRuleCategoryController.java

@@ -0,0 +1,106 @@
+package org.dromara.system.controller;
+
+import java.util.List;
+
+import lombok.RequiredArgsConstructor;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.validation.constraints.*;
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.validation.annotation.Validated;
+import org.dromara.common.idempotent.annotation.RepeatSubmit;
+import org.dromara.common.log.annotation.Log;
+import org.dromara.common.web.core.BaseController;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.core.validate.AddGroup;
+import org.dromara.common.core.validate.EditGroup;
+import org.dromara.common.log.enums.BusinessType;
+import org.dromara.common.excel.utils.ExcelUtil;
+import org.dromara.system.domain.vo.SysRuleCategoryVo;
+import org.dromara.system.domain.bo.SysRuleCategoryBo;
+import org.dromara.system.service.ISysRuleCategoryService;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+
+/**
+ * 规则分类管理
+ * 前端访问路由地址为:/system/ruleCategory
+ *
+ * @author LionLi
+ * @date 2026-01-06
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/ruleCategory")
+public class SysRuleCategoryController extends BaseController {
+
+    private final ISysRuleCategoryService sysRuleCategoryService;
+
+    /**
+     * 查询规则分类管理列表
+     */
+    @SaCheckPermission("system:ruleCategory:list")
+    @GetMapping("/list")
+    public TableDataInfo<SysRuleCategoryVo> list(SysRuleCategoryBo bo, PageQuery pageQuery) {
+        return sysRuleCategoryService.queryPageList(bo, pageQuery);
+    }
+
+    /**
+     * 导出规则分类管理列表
+     */
+    @SaCheckPermission("system:ruleCategory:export")
+    @Log(title = "规则分类管理", businessType = BusinessType.EXPORT)
+    @PostMapping("/export")
+    public void export(SysRuleCategoryBo bo, HttpServletResponse response) {
+        List<SysRuleCategoryVo> list = sysRuleCategoryService.queryList(bo);
+        ExcelUtil.exportExcel(list, "规则分类管理", SysRuleCategoryVo.class, response);
+    }
+
+    /**
+     * 获取规则分类管理详细信息
+     *
+     * @param id 主键
+     */
+    @SaCheckPermission("system:ruleCategory:query")
+    @GetMapping("/{id}")
+    public R<SysRuleCategoryVo> getInfo(@NotNull(message = "主键不能为空")
+                                     @PathVariable("id") Long id) {
+        return R.ok(sysRuleCategoryService.queryById(id));
+    }
+
+    /**
+     * 新增规则分类管理
+     */
+    @SaCheckPermission("system:ruleCategory:add")
+    @Log(title = "规则分类管理", businessType = BusinessType.INSERT)
+    @RepeatSubmit()
+    @PostMapping()
+    public R<Void> add(@Validated(AddGroup.class) @RequestBody SysRuleCategoryBo bo) {
+        return toAjax(sysRuleCategoryService.insertByBo(bo));
+    }
+
+    /**
+     * 修改规则分类管理
+     */
+    @SaCheckPermission("system:ruleCategory:edit")
+    @Log(title = "规则分类管理", businessType = BusinessType.UPDATE)
+    @RepeatSubmit()
+    @PutMapping()
+    public R<Void> edit(@Validated(EditGroup.class) @RequestBody SysRuleCategoryBo bo) {
+        return toAjax(sysRuleCategoryService.updateByBo(bo));
+    }
+
+    /**
+     * 删除规则分类管理
+     *
+     * @param ids 主键串
+     */
+    @SaCheckPermission("system:ruleCategory:remove")
+    @Log(title = "规则分类管理", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{ids}")
+    public R<Void> remove(@NotEmpty(message = "主键不能为空")
+                          @PathVariable("ids") Long[] ids) {
+        return toAjax(sysRuleCategoryService.deleteWithValidByIds(List.of(ids), true));
+    }
+}

+ 106 - 0
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysRuleCenterController.java

@@ -0,0 +1,106 @@
+package org.dromara.system.controller;
+
+import java.util.List;
+
+import lombok.RequiredArgsConstructor;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.validation.constraints.*;
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.validation.annotation.Validated;
+import org.dromara.common.idempotent.annotation.RepeatSubmit;
+import org.dromara.common.log.annotation.Log;
+import org.dromara.common.web.core.BaseController;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.core.validate.AddGroup;
+import org.dromara.common.core.validate.EditGroup;
+import org.dromara.common.log.enums.BusinessType;
+import org.dromara.common.excel.utils.ExcelUtil;
+import org.dromara.system.domain.vo.SysRuleCenterVo;
+import org.dromara.system.domain.bo.SysRuleCenterBo;
+import org.dromara.system.service.ISysRuleCenterService;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+
+/**
+ * 规则中心
+ * 前端访问路由地址为:/system/ruleCenter
+ *
+ * @author LionLi
+ * @date 2026-01-06
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/ruleCenter")
+public class SysRuleCenterController extends BaseController {
+
+    private final ISysRuleCenterService sysRuleCenterService;
+
+    /**
+     * 查询规则中心列表
+     */
+    @SaCheckPermission("system:ruleCenter:list")
+    @GetMapping("/list")
+    public TableDataInfo<SysRuleCenterVo> list(SysRuleCenterBo bo, PageQuery pageQuery) {
+        return sysRuleCenterService.queryPageList(bo, pageQuery);
+    }
+
+    /**
+     * 导出规则中心列表
+     */
+    @SaCheckPermission("system:ruleCenter:export")
+    @Log(title = "规则中心", businessType = BusinessType.EXPORT)
+    @PostMapping("/export")
+    public void export(SysRuleCenterBo bo, HttpServletResponse response) {
+        List<SysRuleCenterVo> list = sysRuleCenterService.queryList(bo);
+        ExcelUtil.exportExcel(list, "规则中心", SysRuleCenterVo.class, response);
+    }
+
+    /**
+     * 获取规则中心详细信息
+     *
+     * @param id 主键
+     */
+    @SaCheckPermission("system:ruleCenter:query")
+    @GetMapping("/{id}")
+    public R<SysRuleCenterVo> getInfo(@NotNull(message = "主键不能为空")
+                                     @PathVariable("id") Long id) {
+        return R.ok(sysRuleCenterService.queryById(id));
+    }
+
+    /**
+     * 新增规则中心
+     */
+    @SaCheckPermission("system:ruleCenter:add")
+    @Log(title = "规则中心", businessType = BusinessType.INSERT)
+    @RepeatSubmit()
+    @PostMapping()
+    public R<Void> add(@Validated(AddGroup.class) @RequestBody SysRuleCenterBo bo) {
+        return toAjax(sysRuleCenterService.insertByBo(bo));
+    }
+
+    /**
+     * 修改规则中心
+     */
+    @SaCheckPermission("system:ruleCenter:edit")
+    @Log(title = "规则中心", businessType = BusinessType.UPDATE)
+    @RepeatSubmit()
+    @PutMapping()
+    public R<Void> edit(@Validated(EditGroup.class) @RequestBody SysRuleCenterBo bo) {
+        return toAjax(sysRuleCenterService.updateByBo(bo));
+    }
+
+    /**
+     * 删除规则中心
+     *
+     * @param ids 主键串
+     */
+    @SaCheckPermission("system:ruleCenter:remove")
+    @Log(title = "规则中心", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{ids}")
+    public R<Void> remove(@NotEmpty(message = "主键不能为空")
+                          @PathVariable("ids") Long[] ids) {
+        return toAjax(sysRuleCenterService.deleteWithValidByIds(List.of(ids), true));
+    }
+}

+ 106 - 0
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysServiceTimeController.java

@@ -0,0 +1,106 @@
+package org.dromara.system.controller;
+
+import java.util.List;
+
+import lombok.RequiredArgsConstructor;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.validation.constraints.*;
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.validation.annotation.Validated;
+import org.dromara.common.idempotent.annotation.RepeatSubmit;
+import org.dromara.common.log.annotation.Log;
+import org.dromara.common.web.core.BaseController;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.core.validate.AddGroup;
+import org.dromara.common.core.validate.EditGroup;
+import org.dromara.common.log.enums.BusinessType;
+import org.dromara.common.excel.utils.ExcelUtil;
+import org.dromara.system.domain.vo.SysServiceTimeVo;
+import org.dromara.system.domain.bo.SysServiceTimeBo;
+import org.dromara.system.service.ISysServiceTimeService;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+
+/**
+ * 服务时间
+ * 前端访问路由地址为:/system/serviceTime
+ *
+ * @author LionLi
+ * @date 2026-01-05
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/serviceTime")
+public class SysServiceTimeController extends BaseController {
+
+    private final ISysServiceTimeService sysServiceTimeService;
+
+    /**
+     * 查询服务时间列表
+     */
+    @SaCheckPermission("system:serviceTime:list")
+    @GetMapping("/list")
+    public TableDataInfo<SysServiceTimeVo> list(SysServiceTimeBo bo, PageQuery pageQuery) {
+        return sysServiceTimeService.queryPageList(bo, pageQuery);
+    }
+
+    /**
+     * 导出服务时间列表
+     */
+    @SaCheckPermission("system:serviceTime:export")
+    @Log(title = "服务时间", businessType = BusinessType.EXPORT)
+    @PostMapping("/export")
+    public void export(SysServiceTimeBo bo, HttpServletResponse response) {
+        List<SysServiceTimeVo> list = sysServiceTimeService.queryList(bo);
+        ExcelUtil.exportExcel(list, "服务时间", SysServiceTimeVo.class, response);
+    }
+
+    /**
+     * 获取服务时间详细信息
+     *
+     * @param id 主键
+     */
+    @SaCheckPermission("system:serviceTime:query")
+    @GetMapping("/{id}")
+    public R<SysServiceTimeVo> getInfo(@NotNull(message = "主键不能为空")
+                                     @PathVariable("id") Long id) {
+        return R.ok(sysServiceTimeService.queryById(id));
+    }
+
+    /**
+     * 新增服务时间
+     */
+    @SaCheckPermission("system:serviceTime:add")
+    @Log(title = "服务时间", businessType = BusinessType.INSERT)
+    @RepeatSubmit()
+    @PostMapping()
+    public R<Void> add(@Validated(AddGroup.class) @RequestBody SysServiceTimeBo bo) {
+        return toAjax(sysServiceTimeService.insertByBo(bo));
+    }
+
+    /**
+     * 修改服务时间
+     */
+    @SaCheckPermission("system:serviceTime:edit")
+    @Log(title = "服务时间", businessType = BusinessType.UPDATE)
+    @RepeatSubmit()
+    @PutMapping()
+    public R<Void> edit(@Validated(EditGroup.class) @RequestBody SysServiceTimeBo bo) {
+        return toAjax(sysServiceTimeService.updateByBo(bo));
+    }
+
+    /**
+     * 删除服务时间
+     *
+     * @param ids 主键串
+     */
+    @SaCheckPermission("system:serviceTime:remove")
+    @Log(title = "服务时间", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{ids}")
+    public R<Void> remove(@NotEmpty(message = "主键不能为空")
+                          @PathVariable("ids") Long[] ids) {
+        return toAjax(sysServiceTimeService.deleteWithValidByIds(List.of(ids), true));
+    }
+}

+ 57 - 0
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/SysAdaptScene.java

@@ -0,0 +1,57 @@
+package org.dromara.system.domain;
+
+import org.dromara.common.tenant.core.TenantEntity;
+import com.baomidou.mybatisplus.annotation.*;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.io.Serial;
+
+/**
+ * 适配场景对象 sys_adapt_scene
+ *
+ * @author LionLi
+ * @date 2026-01-05
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@TableName("sys_adapt_scene")
+public class SysAdaptScene extends TenantEntity {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * ID
+     */
+    @TableId(value = "id")
+    private Long id;
+
+    /**
+     * 场景编号
+     */
+    private String sceneCode;
+
+    /**
+     * 场景名称
+     */
+    private String sceneName;
+
+    /**
+     * 状态(0正常 1停用)
+     */
+    private String status;
+
+    /**
+     * 删除标志(0代表存在 2代表删除)
+     */
+    @TableLogic
+    private String delFlag;
+
+    /**
+     * 备注
+     */
+    private String remark;
+
+
+}

برخی فایل ها در این مقایسه diff نمایش داده نمی شوند زیرا تعداد فایل ها بسیار زیاد است