From 84f3b230d20b8db543861835500197b55318b7f8 Mon Sep 17 00:00:00 2001
From: puhui999 <puhui999@163.com>
Date: Mon, 5 Feb 2024 00:14:50 +0800
Subject: [PATCH] =?UTF-8?q?CRM-=E5=90=88=E5=90=8C:=20=E6=96=B0=E5=A2=9E?=
 =?UTF-8?q?=E5=90=88=E5=90=8C=E5=85=B3=E8=81=94=E5=95=86=E5=93=81?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../CrmBusinessStatusTypeController.java      |   4 +-
 .../vo/business/CrmBusinessSaveReqVO.java     |  31 +++--
 .../product/CrmBusinessProductPageReqVO.java  |  15 ---
 .../vo/product/CrmBusinessProductRespVO.java  |  11 --
 .../product/CrmBusinessProductSaveReqVO.java  |  49 --------
 .../admin/contract/CrmContractController.java |  68 +++++-----
 .../CrmBusinessStatusConvert.java             |   2 +-
 .../CrmBusinessStatusTypeConvert.java         |   2 +-
 .../CrmBusinessProductConvert.java            |  21 ----
 .../convert/contract/CrmContractConvert.java  |  21 +++-
 .../business/CrmBusinessProductDO.java        |  18 ---
 .../contract/CrmContractProductDO.java        |  63 ++++++++++
 .../dal/dataobject/contract/package-info.java |   4 -
 .../business/CrmBusinessProductMapper.java    |   7 ++
 .../contract/CrmContractProductMapper.java    |  31 +++++
 .../dal/mysql/product/CrmProductMapper.java   |   9 --
 .../business/CrmBusinessProductService.java   |  57 ---------
 .../CrmBusinessProductServiceImpl.java        |  55 --------
 .../business/CrmBusinessServiceImpl.java      | 119 +++++++++---------
 .../service/contract/CrmContractService.java  |   9 ++
 .../contract/CrmContractServiceImpl.java      |  51 ++++----
 .../product/CrmProductServiceImpl.java        |   2 +-
 .../ErpProductUnitServiceImplTest.java        |  25 ++--
 .../product/ProductServiceImplTest.java       |  25 ++--
 24 files changed, 302 insertions(+), 397 deletions(-)
 delete mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/product/CrmBusinessProductPageReqVO.java
 delete mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/product/CrmBusinessProductRespVO.java
 delete mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/product/CrmBusinessProductSaveReqVO.java
 rename yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/{businessstatus => business}/CrmBusinessStatusConvert.java (92%)
 rename yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/{businessstatustype => business}/CrmBusinessStatusTypeConvert.java (96%)
 delete mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/businessproduct/CrmBusinessProductConvert.java
 create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/contract/CrmContractProductDO.java
 delete mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/contract/package-info.java
 create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/contract/CrmContractProductMapper.java
 delete mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessProductService.java
 delete mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessProductServiceImpl.java

diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/CrmBusinessStatusTypeController.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/CrmBusinessStatusTypeController.java
index 925fa4953..337590044 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/CrmBusinessStatusTypeController.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/CrmBusinessStatusTypeController.java
@@ -14,8 +14,8 @@ import cn.iocoder.yudao.module.crm.controller.admin.business.vo.type.CrmBusiness
 import cn.iocoder.yudao.module.crm.controller.admin.business.vo.type.CrmBusinessStatusTypeQueryVO;
 import cn.iocoder.yudao.module.crm.controller.admin.business.vo.type.CrmBusinessStatusTypeRespVO;
 import cn.iocoder.yudao.module.crm.controller.admin.business.vo.type.CrmBusinessStatusTypeSaveReqVO;
-import cn.iocoder.yudao.module.crm.convert.businessstatus.CrmBusinessStatusConvert;
-import cn.iocoder.yudao.module.crm.convert.businessstatustype.CrmBusinessStatusTypeConvert;
+import cn.iocoder.yudao.module.crm.convert.business.CrmBusinessStatusConvert;
+import cn.iocoder.yudao.module.crm.convert.business.CrmBusinessStatusTypeConvert;
 import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessStatusDO;
 import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessStatusTypeDO;
 import cn.iocoder.yudao.module.crm.service.business.CrmBusinessStatusService;
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/business/CrmBusinessSaveReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/business/CrmBusinessSaveReqVO.java
index 672450c4a..c3b167ef1 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/business/CrmBusinessSaveReqVO.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/business/CrmBusinessSaveReqVO.java
@@ -1,18 +1,18 @@
 package cn.iocoder.yudao.module.crm.controller.admin.business.vo.business;
 
 import cn.iocoder.yudao.framework.common.validation.InEnum;
-import cn.iocoder.yudao.module.crm.controller.admin.business.vo.product.CrmBusinessProductSaveReqVO;
 import cn.iocoder.yudao.module.crm.enums.business.CrmBizEndStatus;
 import cn.iocoder.yudao.module.crm.framework.operatelog.core.CrmCustomerParseFunction;
 import com.mzt.logapi.starter.annotation.DiffLogField;
 import io.swagger.v3.oas.annotations.media.Schema;
 import jakarta.validation.constraints.NotNull;
+import lombok.AllArgsConstructor;
 import lombok.Data;
+import lombok.NoArgsConstructor;
 import org.springframework.format.annotation.DateTimeFormat;
 
 import java.math.BigDecimal;
 import java.time.LocalDateTime;
-import java.util.ArrayList;
 import java.util.List;
 
 import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@@ -58,7 +58,6 @@ public class CrmBusinessSaveReqVO {
     @DiffLogField(name = "商机金额")
     private Integer price;
 
-    // TODO @lzxhqs:折扣使用 Integer 类型,存储时,默认 * 100;展示的时候,前端需要 / 100;避免精度丢失问题
     @Schema(description = "整单折扣")
     @DiffLogField(name = "整单折扣")
     private Integer discountPercent;
@@ -75,11 +74,29 @@ public class CrmBusinessSaveReqVO {
     @InEnum(CrmBizEndStatus.class)
     private Integer endStatus;
 
-    // TODO @lzxhqs:不设置默认 new ArrayList<>();一般 pojo 不设置默认值哈
-    @Schema(description = "商机产品列表")
-    private List<CrmBusinessProductSaveReqVO> products = new ArrayList<>();
-
     @Schema(description = "联系人编号", example = "110")
     private Long contactId; // 使用场景,在【联系人详情】添加商机时,如果需要关联两者,需要传递 contactId 字段
 
+    @Schema(description = "产品列表")
+    private List<CrmBusinessProductItem> productItems;
+
+    @Schema(description = "产品列表")
+    @Data
+    @NoArgsConstructor
+    @AllArgsConstructor
+    public static class CrmBusinessProductItem {
+
+        @Schema(description = "产品编号", example = "20529")
+        @NotNull(message = "产品编号不能为空")
+        private Long id;
+
+        @Schema(description = "产品数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "8911")
+        @NotNull(message = "产品数量不能为空")
+        private Integer count;
+
+        @Schema(description = "产品折扣")
+        private Integer discountPercent;
+
+    }
+
 }
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/product/CrmBusinessProductPageReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/product/CrmBusinessProductPageReqVO.java
deleted file mode 100644
index 4804768a5..000000000
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/product/CrmBusinessProductPageReqVO.java
+++ /dev/null
@@ -1,15 +0,0 @@
-package cn.iocoder.yudao.module.crm.controller.admin.business.vo.product;
-
-import cn.iocoder.yudao.framework.common.pojo.PageParam;
-import io.swagger.v3.oas.annotations.media.Schema;
-import lombok.Data;
-import lombok.EqualsAndHashCode;
-import lombok.ToString;
-
-// TODO @lzxhqs:这个类,如果没用到,可以考虑删除哈
-@Schema(description = "管理后台 - 商机产品分页 Request VO")
-@Data
-@EqualsAndHashCode(callSuper = true)
-@ToString(callSuper = true)
-public class CrmBusinessProductPageReqVO extends PageParam {
-}
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/product/CrmBusinessProductRespVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/product/CrmBusinessProductRespVO.java
deleted file mode 100644
index d4996816f..000000000
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/product/CrmBusinessProductRespVO.java
+++ /dev/null
@@ -1,11 +0,0 @@
-package cn.iocoder.yudao.module.crm.controller.admin.business.vo.product;
-
-import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
-import io.swagger.v3.oas.annotations.media.Schema;
-import lombok.Data;
-
-@Schema(description = "管理后台 - 商机产品关联 Response VO")
-@Data
-@ExcelIgnoreUnannotated
-public class CrmBusinessProductRespVO {
-}
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/product/CrmBusinessProductSaveReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/product/CrmBusinessProductSaveReqVO.java
deleted file mode 100644
index f28f0f350..000000000
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/product/CrmBusinessProductSaveReqVO.java
+++ /dev/null
@@ -1,49 +0,0 @@
-package cn.iocoder.yudao.module.crm.controller.admin.business.vo.product;
-
-import io.swagger.v3.oas.annotations.media.Schema;
-import jakarta.validation.constraints.NotEmpty;
-import jakarta.validation.constraints.NotNull;
-import lombok.Data;
-
-import java.math.BigDecimal;
-
-@Schema(description = "管理后台 - CRM 商机产品关联表 创建/更新 Request VO")
-@Data
-public class CrmBusinessProductSaveReqVO {
-
-    @Schema(description = "主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "32129")
-    private Long id;
-
-    @Schema(description = "商机编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "30320")
-    @NotNull(message = "商机编号不能为空")
-    private Long businessId;
-
-    @Schema(description = "产品编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "30320")
-    @NotNull(message = "产品编号不能为空")
-    private Long productId;
-
-    @Schema(description = "产品单价", requiredMode = Schema.RequiredMode.REQUIRED, example = "30320")
-    @NotNull(message = "产品单价不能为空")
-    private BigDecimal price;
-
-    @Schema(description = "销售价格", requiredMode = Schema.RequiredMode.REQUIRED, example = "30320")
-    @NotNull(message = "销售价格不能为空")
-    private BigDecimal salesPrice;
-
-    @Schema(description = "数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "30320")
-    @NotNull(message = "数量不能为空")
-    private BigDecimal num;
-
-    @Schema(description = "折扣", requiredMode = Schema.RequiredMode.REQUIRED, example = "30320")
-    @NotNull(message = "折扣不能为空")
-    private BigDecimal discount;
-
-    @Schema(description = "小计(折扣后价格)", requiredMode = Schema.RequiredMode.REQUIRED, example = "30320")
-    @NotNull(message = "小计(折扣后价格)不能为空")
-    private BigDecimal subtotal;
-
-    @Schema(description = "单位", requiredMode = Schema.RequiredMode.REQUIRED, example = "30320")
-    @NotEmpty(message = "单位不能为空")
-    private String unit;
-
-}
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/CrmContractController.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/CrmContractController.java
index aaaccbd81..982ec274c 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/CrmContractController.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/CrmContractController.java
@@ -11,12 +11,11 @@ import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
 import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.*;
 import cn.iocoder.yudao.module.crm.convert.contract.CrmContractConvert;
 import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO;
-import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessProductDO;
 import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactDO;
 import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO;
+import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractProductDO;
 import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
 import cn.iocoder.yudao.module.crm.dal.dataobject.product.CrmProductDO;
-import cn.iocoder.yudao.module.crm.service.business.CrmBusinessProductService;
 import cn.iocoder.yudao.module.crm.service.business.CrmBusinessService;
 import cn.iocoder.yudao.module.crm.service.contact.CrmContactService;
 import cn.iocoder.yudao.module.crm.service.contract.CrmContractService;
@@ -30,7 +29,6 @@ import io.swagger.v3.oas.annotations.tags.Tag;
 import jakarta.annotation.Resource;
 import jakarta.servlet.http.HttpServletResponse;
 import jakarta.validation.Valid;
-import org.springframework.context.annotation.Lazy;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.*;
@@ -43,7 +41,6 @@ import java.util.stream.Stream;
 
 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
-import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen;
 import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT;
 import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
 
@@ -62,11 +59,7 @@ public class CrmContractController {
     @Resource
     private CrmBusinessService businessService;
     @Resource
-    @Lazy
-    private CrmBusinessProductService businessProductService;
-    @Resource
     private CrmProductService productService;
-
     @Resource
     private AdminUserApi adminUserApi;
 
@@ -105,22 +98,9 @@ public class CrmContractController {
             return success(null);
         }
 
-        // 2.1 拼接合同信息
+        // 2. 拼接合同信息
         List<CrmContractRespVO> respVOList = buildContractDetailList(Collections.singletonList(contract));
-        // 2.2 拼接产品信息
-        // TODO @puhui999:下面这块也可以搞到 convert 里哈;可以在 ContractDetailList 加个开关,是不是查询商品信息;ps:jdk21 的方法不太能去用,因为 jdk8 项目要兼容;
-        CrmContractRespVO respVO = respVOList.get(0);
-        List<CrmBusinessProductDO> businessProductList = businessProductService.getBusinessProductListByContractId(id);
-        Map<Long, CrmBusinessProductDO> businessProductMap = convertMap(businessProductList, CrmBusinessProductDO::getProductId);
-        List<CrmProductDO> productList = productService.getProductListByIds(convertSet(businessProductList, CrmBusinessProductDO::getProductId));
-        respVO.setProductItems(convertList(productList, product -> {
-            CrmContractRespVO.CrmContractProductItemRespVO productItemRespVO = BeanUtils.toBean(product, CrmContractRespVO.CrmContractProductItemRespVO.class);
-            findAndThen(businessProductMap, product.getId(), businessProduct -> {
-                productItemRespVO.setCount(businessProduct.getCount()).setDiscountPercent(businessProduct.getDiscountPercent());
-            });
-            return productItemRespVO;
-        }));
-        return success(respVO);
+        return success(respVOList.get(0));
     }
 
     @GetMapping("/page")
@@ -151,6 +131,22 @@ public class CrmContractController {
                 BeanUtils.toBean(pageResult.getList(), CrmContractExcelVO.class));
     }
 
+    @PutMapping("/transfer")
+    @Operation(summary = "合同转移")
+    @PreAuthorize("@ss.hasPermission('crm:contract:update')")
+    public CommonResult<Boolean> transferContract(@Valid @RequestBody CrmContractTransferReqVO reqVO) {
+        contractService.transferContract(reqVO, getLoginUserId());
+        return success(true);
+    }
+
+    @PutMapping("/submit")
+    @Operation(summary = "提交合同审批")
+    @PreAuthorize("@ss.hasPermission('crm:contract:update')")
+    public CommonResult<Boolean> submitContract(@RequestParam("id") Long id) {
+        contractService.submitContract(id, getLoginUserId());
+        return success(true);
+    }
+
     /**
      * 构建详细的合同结果
      *
@@ -173,23 +169,15 @@ public class CrmContractController {
         // 4. 获取商机
         Map<Long, CrmBusinessDO> businessMap = convertMap(businessService.getBusinessList(convertSet(contractList,
                 CrmContractDO::getBusinessId)), CrmBusinessDO::getId);
-        return CrmContractConvert.INSTANCE.convertList(contractList, userMap, customerList, contactMap, businessMap);
-    }
-
-    @PutMapping("/transfer")
-    @Operation(summary = "合同转移")
-    @PreAuthorize("@ss.hasPermission('crm:contract:update')")
-    public CommonResult<Boolean> transferContract(@Valid @RequestBody CrmContractTransferReqVO reqVO) {
-        contractService.transferContract(reqVO, getLoginUserId());
-        return success(true);
-    }
-
-    @PutMapping("/submit")
-    @Operation(summary = "提交合同审批")
-    @PreAuthorize("@ss.hasPermission('crm:contract:update')")
-    public CommonResult<Boolean> submitContract(@RequestParam("id") Long id) {
-        contractService.submitContract(id, getLoginUserId());
-        return success(true);
+        // 5. 获取合同关联的商品
+        Map<Long, CrmContractProductDO> contractProductMap = null;
+        List<CrmProductDO> productList = null;
+        if (contractList.size() == 1) {
+            List<CrmContractProductDO> contractProductList = contractService.getContractProductListByContractId(contractList.get(0).getId());
+            contractProductMap = convertMap(contractProductList, CrmContractProductDO::getProductId);
+            productList = productService.getProductListByIds(convertSet(contractProductList, CrmContractProductDO::getProductId));
+        }
+        return CrmContractConvert.INSTANCE.convertList(contractList, userMap, customerList, contactMap, businessMap, contractProductMap, productList);
     }
 
 }
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/businessstatus/CrmBusinessStatusConvert.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/business/CrmBusinessStatusConvert.java
similarity index 92%
rename from yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/businessstatus/CrmBusinessStatusConvert.java
rename to yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/business/CrmBusinessStatusConvert.java
index df2532b27..52186e3d9 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/businessstatus/CrmBusinessStatusConvert.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/business/CrmBusinessStatusConvert.java
@@ -1,4 +1,4 @@
-package cn.iocoder.yudao.module.crm.convert.businessstatus;
+package cn.iocoder.yudao.module.crm.convert.business;
 
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.module.crm.controller.admin.business.vo.status.CrmBusinessStatusRespVO;
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/businessstatustype/CrmBusinessStatusTypeConvert.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/business/CrmBusinessStatusTypeConvert.java
similarity index 96%
rename from yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/businessstatustype/CrmBusinessStatusTypeConvert.java
rename to yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/business/CrmBusinessStatusTypeConvert.java
index be203b580..4876fb537 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/businessstatustype/CrmBusinessStatusTypeConvert.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/business/CrmBusinessStatusTypeConvert.java
@@ -1,4 +1,4 @@
-package cn.iocoder.yudao.module.crm.convert.businessstatustype;
+package cn.iocoder.yudao.module.crm.convert.business;
 
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.module.crm.controller.admin.business.vo.type.CrmBusinessStatusTypeRespVO;
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/businessproduct/CrmBusinessProductConvert.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/businessproduct/CrmBusinessProductConvert.java
deleted file mode 100644
index 2fcd54d84..000000000
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/businessproduct/CrmBusinessProductConvert.java
+++ /dev/null
@@ -1,21 +0,0 @@
-package cn.iocoder.yudao.module.crm.convert.businessproduct;
-
-import cn.iocoder.yudao.module.crm.controller.admin.business.vo.product.CrmBusinessProductSaveReqVO;
-import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessProductDO;
-import org.mapstruct.Mapper;
-import org.mapstruct.factory.Mappers;
-
-// TODO @lzxhqs:看看是不是用 BeanUtils 替代了
-/**
- * @author lzxhqs
- * @version 1.0
- * @title CrmBusinessProductConvert
- * @description
- * @create 2024/1/12
- */
-@Mapper
-public interface CrmBusinessProductConvert {
-    CrmBusinessProductConvert INSTANCE = Mappers.getMapper(CrmBusinessProductConvert.class);
-
-    CrmBusinessProductDO convert(CrmBusinessProductSaveReqVO product);
-}
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/contract/CrmContractConvert.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/contract/CrmContractConvert.java
index c9247e6a5..444876040 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/contract/CrmContractConvert.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/contract/CrmContractConvert.java
@@ -1,12 +1,16 @@
 package cn.iocoder.yudao.module.crm.convert.contract;
 
+import cn.hutool.core.collection.CollUtil;
+import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
 import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.CrmContractRespVO;
 import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.CrmContractTransferReqVO;
 import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO;
 import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactDO;
 import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO;
+import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractProductDO;
 import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
+import cn.iocoder.yudao.module.crm.dal.dataobject.product.CrmProductDO;
 import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionTransferReqBO;
 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
 import org.mapstruct.Mapper;
@@ -34,7 +38,8 @@ public interface CrmContractConvert {
 
     default List<CrmContractRespVO> convertList(List<CrmContractDO> contractList, Map<Long, AdminUserRespDTO> userMap,
                                                 List<CrmCustomerDO> customerList, Map<Long, CrmContactDO> contactMap,
-                                                Map<Long, CrmBusinessDO> businessMap) {
+                                                Map<Long, CrmBusinessDO> businessMap, Map<Long, CrmContractProductDO> contractProductMap,
+                                                List<CrmProductDO> productList) {
         List<CrmContractRespVO> respVOList = BeanUtils.toBean(contractList, CrmContractRespVO.class);
         // 拼接关联字段
         Map<Long, CrmCustomerDO> customerMap = convertMap(customerList, CrmCustomerDO::getId);
@@ -46,7 +51,21 @@ public interface CrmContractConvert {
             findAndThen(contactMap, contract.getContactId(), contact -> contract.setContactName(contact.getName()));
             findAndThen(businessMap, contract.getBusinessId(), business -> contract.setBusinessName(business.getName()));
         });
+        if (CollUtil.isNotEmpty(respVOList) && respVOList.size() == 1) {
+            setContractRespVOProductItems(respVOList.get(0), contractProductMap, productList);
+        }
         return respVOList;
     }
 
+    default void setContractRespVOProductItems(CrmContractRespVO respVO, Map<Long, CrmContractProductDO> contractProductMap,
+                                               List<CrmProductDO> productList) {
+        respVO.setProductItems(CollectionUtils.convertList(productList, product -> {
+            CrmContractRespVO.CrmContractProductItemRespVO productItemRespVO = BeanUtils.toBean(product, CrmContractRespVO.CrmContractProductItemRespVO.class);
+            findAndThen(contractProductMap, product.getId(), contractProduct -> {
+                productItemRespVO.setCount(contractProduct.getCount()).setDiscountPercent(contractProduct.getDiscountPercent());
+            });
+            return productItemRespVO;
+        }));
+    }
+
 }
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/business/CrmBusinessProductDO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/business/CrmBusinessProductDO.java
index 77c5ba779..79d6a2a7b 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/business/CrmBusinessProductDO.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/business/CrmBusinessProductDO.java
@@ -1,9 +1,7 @@
 package cn.iocoder.yudao.module.crm.dal.dataobject.business;
 
 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
-import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO;
 import cn.iocoder.yudao.module.crm.dal.dataobject.product.CrmProductDO;
-import cn.iocoder.yudao.module.crm.enums.DictTypeConstants;
 import com.baomidou.mybatisplus.annotation.KeySequence;
 import com.baomidou.mybatisplus.annotation.TableId;
 import com.baomidou.mybatisplus.annotation.TableName;
@@ -41,19 +39,10 @@ public class CrmBusinessProductDO extends BaseDO {
      * 关联 {@link CrmProductDO#getId()}
      */
     private Long productId;
-    // TODO 芋艿:需要在看下 CRM
-    /**
-     * 合同编号
-     *
-     * 关联 {@link CrmContractDO#getId()}
-     */
-    private Long contractId;
-
     /**
      * 产品单价
      */
     private Integer price;
-
     /**
      * 销售价格, 单位:分
      */
@@ -71,11 +60,4 @@ public class CrmBusinessProductDO extends BaseDO {
      */
     private Integer totalPrice;
 
-    /**
-     * 单位
-     *
-     * 字典 {@link DictTypeConstants#CRM_PRODUCT_UNIT}
-     */
-    private Integer unit;
-
 }
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/contract/CrmContractProductDO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/contract/CrmContractProductDO.java
new file mode 100644
index 000000000..f0f506857
--- /dev/null
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/contract/CrmContractProductDO.java
@@ -0,0 +1,63 @@
+package cn.iocoder.yudao.module.crm.dal.dataobject.contract;
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import cn.iocoder.yudao.module.crm.dal.dataobject.product.CrmProductDO;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+
+/**
+ * 合同产品关联表 DO
+ *
+ * @author HUIHUI
+ */
+@TableName("crm_contract_product")
+@KeySequence("crm_contract_product_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class CrmContractProductDO extends BaseDO {
+
+    /**
+     * 主键
+     */
+    @TableId
+    private Long id;
+    /**
+     * 产品编号
+     *
+     * 关联 {@link CrmProductDO#getId()}
+     */
+    private Long productId;
+    /**
+     * 合同编号
+     *
+     * 关联 {@link CrmContractDO#getId()}
+     */
+    private Long contractId;
+    /**
+     * 产品单价
+     */
+    private Integer price;
+    /**
+     * 销售价格, 单位:分
+     */
+    private Integer salesPrice;
+    /**
+     * 数量
+     */
+    private Integer count;
+    /**
+     * 折扣
+     */
+    private Integer discountPercent;
+    /**
+     * 总计价格(折扣后价格)
+     */
+    private Integer totalPrice;
+
+}
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/contract/package-info.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/contract/package-info.java
deleted file mode 100644
index a981b5dfc..000000000
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/contract/package-info.java
+++ /dev/null
@@ -1,4 +0,0 @@
-/**
- * 合同
- */
-package cn.iocoder.yudao.module.crm.dal.dataobject.contract;
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/business/CrmBusinessProductMapper.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/business/CrmBusinessProductMapper.java
index 1730067fe..35fccdbdf 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/business/CrmBusinessProductMapper.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/business/CrmBusinessProductMapper.java
@@ -2,9 +2,12 @@ package cn.iocoder.yudao.module.crm.dal.mysql.business;
 
 
 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
 import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessProductDO;
 import org.apache.ibatis.annotations.Mapper;
 
+import java.util.List;
+
 /**
  * 商机产品 Mapper
  *
@@ -21,4 +24,8 @@ public interface CrmBusinessProductMapper extends BaseMapperX<CrmBusinessProduct
         return selectOne(CrmBusinessProductDO::getBusinessId, id);
     }
 
+    default List<CrmBusinessProductDO> selectListByBusinessId(Long businessId) {
+        return selectList(new LambdaQueryWrapperX<CrmBusinessProductDO>().eq(CrmBusinessProductDO::getBusinessId, businessId));
+    }
+
 }
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/contract/CrmContractProductMapper.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/contract/CrmContractProductMapper.java
new file mode 100644
index 000000000..fd6347a9e
--- /dev/null
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/contract/CrmContractProductMapper.java
@@ -0,0 +1,31 @@
+package cn.iocoder.yudao.module.crm.dal.mysql.contract;
+
+
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractProductDO;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.List;
+
+/**
+ * 合同产品 Mapper
+ *
+ * @author HUIHUI
+ */
+@Mapper
+public interface CrmContractProductMapper extends BaseMapperX<CrmContractProductDO> {
+
+    default void deleteByContractId(Long contractId) { // TODO @lzxhqs:第一个方法,和类之间最好空一行;
+        delete(CrmContractProductDO::getContractId, contractId);
+    }
+
+    default CrmContractProductDO selectByContractId(Long contractId) {
+        return selectOne(CrmContractProductDO::getContractId, contractId);
+    }
+
+    default List<CrmContractProductDO> selectListByContractId(Long contractId) {
+        return selectList(new LambdaQueryWrapperX<CrmContractProductDO>().eq(CrmContractProductDO::getContractId, contractId));
+    }
+
+}
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/product/CrmProductMapper.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/product/CrmProductMapper.java
index 8a7fa600b..30a07eec2 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/product/CrmProductMapper.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/product/CrmProductMapper.java
@@ -2,7 +2,6 @@ package cn.iocoder.yudao.module.crm.dal.mysql.product;
 
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
-import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
 import cn.iocoder.yudao.framework.mybatis.core.query.MPJLambdaWrapperX;
 import cn.iocoder.yudao.module.crm.controller.admin.product.vo.product.CrmProductPageReqVO;
 import cn.iocoder.yudao.module.crm.dal.dataobject.product.CrmProductDO;
@@ -10,9 +9,6 @@ import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
 import cn.iocoder.yudao.module.crm.util.CrmQueryWrapperUtils;
 import org.apache.ibatis.annotations.Mapper;
 
-import java.util.Collection;
-import java.util.List;
-
 /**
  * CRM 产品 Mapper
  *
@@ -38,9 +34,4 @@ public interface CrmProductMapper extends BaseMapperX<CrmProductDO> {
         return selectOne(CrmProductDO::getNo, no);
     }
 
-    // TODO @puhui999:selectBatchIds
-    default List<CrmProductDO> selectListByIds(Collection<Long> ids) {
-        return selectList(new LambdaQueryWrapperX<CrmProductDO>().in(CrmProductDO::getId, ids));
-    }
-
 }
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessProductService.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessProductService.java
deleted file mode 100644
index a68ac37b9..000000000
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessProductService.java
+++ /dev/null
@@ -1,57 +0,0 @@
-package cn.iocoder.yudao.module.crm.service.business;
-
-import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessProductDO;
-
-import java.util.List;
-
-/**
- * 商机产品关联表 Service 接口
- *
- * @author lzxhqs
- */
-public interface CrmBusinessProductService {
-
-    /**
-     * 批量新增商机产品关联数据
-     *
-     * @param list 商机产品集合
-     */
-    void createBusinessProductBatch(List<CrmBusinessProductDO> list);
-
-    /**
-     * 批量更新商机产品表
-     *
-     * @param list 商机产品数据集合
-     */
-    void updateBusinessProductBatch(List<CrmBusinessProductDO> list);
-
-    /**
-     * 批量删除
-     *
-     * @param list 需要删除的商机产品集合
-     */
-    void deleteBusinessProductBatch(List<CrmBusinessProductDO> list);
-
-    /**
-     * 根据商机编号,删除商机产品关联数据
-     *
-     * @param businessId 商机id
-     */
-    void deleteBusinessProductByBusinessId(Long businessId);
-
-    /**
-     * 根据商机编号,获取商机产品关联数据集合
-     *
-     * @param businessId 商机编号
-     */
-    List<CrmBusinessProductDO> getBusinessProductListByBusinessId(Long businessId);
-
-    /**
-     * 根据合同编号,获得合同关联的商品列表
-     *
-     * @param contractId 合同编号
-     * @return 关联的商品列表
-     */
-    List<CrmBusinessProductDO> getBusinessProductListByContractId(Long contractId);
-
-}
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessProductServiceImpl.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessProductServiceImpl.java
deleted file mode 100644
index 0762d4555..000000000
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessProductServiceImpl.java
+++ /dev/null
@@ -1,55 +0,0 @@
-package cn.iocoder.yudao.module.crm.service.business;
-
-import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
-import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessProductDO;
-import cn.iocoder.yudao.module.crm.dal.mysql.business.CrmBusinessProductMapper;
-import jakarta.annotation.Resource;
-import org.springframework.stereotype.Service;
-import org.springframework.validation.annotation.Validated;
-
-import java.util.List;
-
-/**
- * 商机产品关联表 Service 实现类
- *
- * @author lzxhqs
- */
-@Service
-@Validated
-public class CrmBusinessProductServiceImpl implements CrmBusinessProductService {
-
-    @Resource
-    private CrmBusinessProductMapper businessProductMapper;
-
-    @Override
-    public void createBusinessProductBatch(List<CrmBusinessProductDO> list) {
-        businessProductMapper.insertBatch(list);
-    }
-
-    @Override
-    public void updateBusinessProductBatch(List<CrmBusinessProductDO> list) {
-        businessProductMapper.updateBatch(list);
-    }
-
-    // TODO @puhui999:这个方法,可以直接调用 deleteList 方法,然后传递 ids 就好了;
-    @Override
-    public void deleteBusinessProductBatch(List<CrmBusinessProductDO> list) {
-        businessProductMapper.deleteBatchIds(CollectionUtils.convertList(list, CrmBusinessProductDO::getId));
-    }
-
-    @Override
-    public void deleteBusinessProductByBusinessId(Long businessId) {
-        businessProductMapper.deleteByBusinessId(businessId);
-    }
-
-    @Override
-    public List<CrmBusinessProductDO> getBusinessProductListByContractId(Long contractId) {
-        return businessProductMapper.selectList(CrmBusinessProductDO::getContractId, contractId);
-    }
-
-    @Override
-    public List<CrmBusinessProductDO> getBusinessProductListByBusinessId(Long businessId) {
-        return businessProductMapper.selectList(CrmBusinessProductDO::getBusinessId, businessId);
-    }
-
-}
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessServiceImpl.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessServiceImpl.java
index df7348cfe..7de0ca424 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessServiceImpl.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessServiceImpl.java
@@ -5,17 +5,18 @@ import cn.hutool.core.collection.ListUtil;
 import cn.hutool.core.util.ObjectUtil;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
+import cn.iocoder.yudao.framework.common.util.number.MoneyUtils;
 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
 import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessPageReqVO;
 import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessSaveReqVO;
 import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessTransferReqVO;
-import cn.iocoder.yudao.module.crm.controller.admin.business.vo.product.CrmBusinessProductSaveReqVO;
 import cn.iocoder.yudao.module.crm.convert.business.CrmBusinessConvert;
-import cn.iocoder.yudao.module.crm.convert.businessproduct.CrmBusinessProductConvert;
 import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO;
 import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessProductDO;
 import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactBusinessDO;
+import cn.iocoder.yudao.module.crm.dal.dataobject.product.CrmProductDO;
 import cn.iocoder.yudao.module.crm.dal.mysql.business.CrmBusinessMapper;
+import cn.iocoder.yudao.module.crm.dal.mysql.business.CrmBusinessProductMapper;
 import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
 import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum;
 import cn.iocoder.yudao.module.crm.framework.permission.core.annotations.CrmPermission;
@@ -24,6 +25,7 @@ import cn.iocoder.yudao.module.crm.service.contract.CrmContractService;
 import cn.iocoder.yudao.module.crm.service.followup.bo.CrmUpdateFollowUpReqBO;
 import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService;
 import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionCreateReqBO;
+import cn.iocoder.yudao.module.crm.service.product.CrmProductService;
 import com.mzt.logapi.context.LogRecordContext;
 import com.mzt.logapi.service.impl.DiffParseFunction;
 import com.mzt.logapi.starter.annotation.LogRecord;
@@ -35,11 +37,12 @@ import org.springframework.validation.annotation.Validated;
 
 import java.util.Collection;
 import java.util.List;
+import java.util.Map;
+import java.util.Set;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
-import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
-import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.BUSINESS_CONTRACT_EXISTS;
-import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.BUSINESS_NOT_EXISTS;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
+import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.*;
 import static cn.iocoder.yudao.module.crm.enums.LogRecordConstants.*;
 
 /**
@@ -53,9 +56,9 @@ public class CrmBusinessServiceImpl implements CrmBusinessService {
 
     @Resource
     private CrmBusinessMapper businessMapper;
-
     @Resource
-    private CrmBusinessProductService businessProductService;
+    private CrmBusinessProductMapper businessProductMapper;
+
     @Resource
     @Lazy // 延迟加载,避免循环依赖
     private CrmContractService contractService;
@@ -63,6 +66,8 @@ public class CrmBusinessServiceImpl implements CrmBusinessService {
     private CrmPermissionService permissionService;
     @Resource
     private CrmContactBusinessService contactBusinessService;
+    @Resource
+    private CrmProductService productService;
 
     @Override
     @Transactional(rollbackFor = Exception.class)
@@ -71,12 +76,15 @@ public class CrmBusinessServiceImpl implements CrmBusinessService {
     public Long createBusiness(CrmBusinessSaveReqVO createReqVO, Long userId) {
         createReqVO.setId(null);
         // 1. 插入商机
-        CrmBusinessDO business = BeanUtils.toBean(createReqVO, CrmBusinessDO.class)
-                .setOwnerUserId(userId);
+        CrmBusinessDO business = BeanUtils.toBean(createReqVO, CrmBusinessDO.class).setOwnerUserId(userId);
         businessMapper.insert(business);
-        // TODO 商机待定:插入商机与产品的关联表;校验商品存在
-        if (CollUtil.isNotEmpty(createReqVO.getProducts())) {
-            createBusinessProducts(createReqVO.getProducts(), business.getId(), false);
+        // 1.2 插入商机关联商品
+        if (CollUtil.isNotEmpty(createReqVO.getProductItems())) { // 如果有的话
+            List<CrmBusinessProductDO> productList = convertBusinessProductList(createReqVO, business.getId());
+            businessProductMapper.insertBatch(productList);
+            // 更新合同商品总金额
+            businessMapper.updateById(new CrmBusinessDO().setId(business.getId()).setProductPrice(
+                    getSumValue(productList, CrmBusinessProductDO::getTotalPrice, Integer::sum)));
         }
         // TODO 商机待定:在联系人的详情页,如果直接【新建商机】,则需要关联下。这里要搞个 CrmContactBusinessDO 表
         createContactBusiness(business.getId(), createReqVO.getContactId());
@@ -92,13 +100,6 @@ public class CrmBusinessServiceImpl implements CrmBusinessService {
     }
 
     // TODO @lzxhqs:CrmContactBusinessService 调用这个;这样逻辑才能收敛哈;
-    /**
-     * @param businessId 商机id
-     * @param contactId  联系人id
-     * @throws
-     * @description 联系人与商机的关联
-     * @author lzxhqs
-     */
     private void createContactBusiness(Long businessId, Long contactId) {
         CrmContactBusinessDO contactBusiness = new CrmContactBusinessDO();
         contactBusiness.setBusinessId(businessId);
@@ -106,37 +107,6 @@ public class CrmBusinessServiceImpl implements CrmBusinessService {
         contactBusinessService.insert(contactBusiness);
     }
 
-    // TODO @lzxhqs:这个方法注释格式不对;删除@description,然后把 插入商机产品关联表 作为方法注释;
-    /**
-     * 插入商机产品关联表
-     *
-     * @param products   产品集合
-     * @param businessId 商机id
-     * @param updateFlag 更新标识 true 代表更新
-     * @author lzxhqs
-     */
-    private void createBusinessProducts(List<CrmBusinessProductSaveReqVO> products, Long businessId, Boolean updateFlag) {
-        List<CrmBusinessProductDO> list = CollectionUtils.convertList(products, product ->
-                CrmBusinessProductConvert.INSTANCE.convert(product).setBusinessId(businessId));
-        if (Boolean.TRUE.equals(updateFlag)) {
-//            根据商机 id从商机产品关联表中获取已存在的数据集合
-            List<CrmBusinessProductDO> oldProducts = businessProductService.getBusinessProductListByBusinessId(businessId);
-            List<List<CrmBusinessProductDO>> diffList = CollectionUtils.diffList(oldProducts, list, (oldValue, newValue) ->
-                    ObjectUtil.equal(oldValue.getProductId(), newValue.getProductId()));
-            if (CollUtil.isNotEmpty(diffList.getFirst())) {
-                businessProductService.createBusinessProductBatch(diffList.getFirst());
-            }
-            if (CollUtil.isNotEmpty(diffList.get(1))) {
-                businessProductService.updateBusinessProductBatch(diffList.get(1));
-            }
-            if (CollUtil.isNotEmpty(diffList.get(2))) {
-                businessProductService.deleteBusinessProductBatch(diffList.get(2));
-            }
-        } else {
-            businessProductService.createBusinessProductBatch(list);
-        }
-    }
-
     @Override
     @Transactional(rollbackFor = Exception.class)
     @LogRecord(type = CRM_BUSINESS_TYPE, subType = CRM_BUSINESS_UPDATE_SUB_TYPE, bizNo = "{{#updateReqVO.id}}",
@@ -146,16 +116,12 @@ public class CrmBusinessServiceImpl implements CrmBusinessService {
         // 1. 校验存在
         CrmBusinessDO oldBusiness = validateBusinessExists(updateReqVO.getId());
 
-        // 2. 更新商机
+        // 2.1 更新商机
         CrmBusinessDO updateObj = BeanUtils.toBean(updateReqVO, CrmBusinessDO.class);
         businessMapper.updateById(updateObj);
-        // TODO 商机待定:插入商机与产品的关联表;校验商品存在
-        // TODO @lzxhqs:createBusinessProducts 可以抽成两个方法,一个新增;一个修改,修改需要把 businessProductService.deleteByBusinessId(updateReqVO.getId()); 一起处理进去;
-        if (CollUtil.isNotEmpty(updateReqVO.getProducts())) {
-            createBusinessProducts(updateReqVO.getProducts(), updateReqVO.getId(), true);
-        } else {
-            businessProductService.deleteBusinessProductByBusinessId(updateReqVO.getId());
-        }
+        // 2.2 更新商机关联商品
+        List<CrmBusinessProductDO> productList = convertBusinessProductList(updateReqVO, updateObj.getId());
+        updateBusinessProduct(productList, updateObj.getId());
 
         // TODO @商机待定:如果状态发生变化,插入商机状态变更记录表
         // 3. 记录操作日志上下文
@@ -188,6 +154,43 @@ public class CrmBusinessServiceImpl implements CrmBusinessService {
         LogRecordContext.putVariable("businessName", business.getName());
     }
 
+    private void updateBusinessProduct(List<CrmBusinessProductDO> newProductList, Long businessId) {
+        List<CrmBusinessProductDO> oldProducts = businessProductMapper.selectListByBusinessId(businessId);
+        List<List<CrmBusinessProductDO>> diffList = CollectionUtils.diffList(oldProducts, newProductList, (oldValue, newValue) -> {
+            boolean condition = ObjectUtil.equal(oldValue.getProductId(), newValue.getProductId());
+            if (condition) {
+                newValue.setId(oldValue.getId()); // 更新需要原始编号
+            }
+            return condition;
+        });
+        if (CollUtil.isNotEmpty(diffList.get(0))) {
+            businessProductMapper.insertBatch(diffList.get(0));
+        }
+        if (CollUtil.isNotEmpty(diffList.get(1))) {
+            businessProductMapper.updateBatch(diffList.get(1));
+        }
+        if (CollUtil.isNotEmpty(diffList.get(2))) {
+            businessProductMapper.deleteBatchIds(convertSet(diffList.get(2), CrmBusinessProductDO::getId));
+        }
+    }
+
+    private List<CrmBusinessProductDO> convertBusinessProductList(CrmBusinessSaveReqVO reqVO, Long businessId) {
+        // 校验商品存在
+        Set<Long> productIds = convertSet(reqVO.getProductItems(), CrmBusinessSaveReqVO.CrmBusinessProductItem::getId);
+        List<CrmProductDO> productList = productService.getProductList(productIds);
+        if (CollUtil.isEmpty(productIds) || productList.size() != productIds.size()) {
+            throw exception(PRODUCT_NOT_EXISTS);
+        }
+        Map<Long, CrmProductDO> productMap = convertMap(productList, CrmProductDO::getId);
+        return convertList(reqVO.getProductItems(), productItem -> {
+            CrmProductDO product = productMap.get(productItem.getId());
+            return BeanUtils.toBean(product, CrmBusinessProductDO.class)
+                    .setId(null).setProductId(productItem.getId()).setBusinessId(businessId)
+                    .setCount(productItem.getCount()).setDiscountPercent(productItem.getDiscountPercent())
+                    .setTotalPrice(MoneyUtils.calculator(product.getPrice(), productItem.getCount(), productItem.getDiscountPercent()));
+        });
+    }
+
     /**
      * 删除校验合同是关联合同
      *
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractService.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractService.java
index 3e79b73ed..0bd527ae0 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractService.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractService.java
@@ -6,6 +6,7 @@ import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.CrmContractPageR
 import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.CrmContractSaveReqVO;
 import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.CrmContractTransferReqVO;
 import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO;
+import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractProductDO;
 import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
 import cn.iocoder.yudao.module.crm.service.followup.bo.CrmUpdateFollowUpReqBO;
 import jakarta.validation.Valid;
@@ -134,4 +135,12 @@ public interface CrmContractService {
      */
     Long getContractCountByBusinessId(Long businessId);
 
+    /**
+     * 获取合同商品列表
+     *
+     * @param contactId 合同编号
+     * @return 合同商品列表
+     */
+    List<CrmContractProductDO> getContractProductListByContractId(Long contactId);
+
 }
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractServiceImpl.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractServiceImpl.java
index 4af609edd..0cf9065e4 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractServiceImpl.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractServiceImpl.java
@@ -15,15 +15,15 @@ import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.CrmContractPageR
 import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.CrmContractSaveReqVO;
 import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.CrmContractTransferReqVO;
 import cn.iocoder.yudao.module.crm.convert.contract.CrmContractConvert;
-import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessProductDO;
 import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO;
+import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractProductDO;
 import cn.iocoder.yudao.module.crm.dal.dataobject.product.CrmProductDO;
 import cn.iocoder.yudao.module.crm.dal.mysql.contract.CrmContractMapper;
+import cn.iocoder.yudao.module.crm.dal.mysql.contract.CrmContractProductMapper;
 import cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum;
 import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
 import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum;
 import cn.iocoder.yudao.module.crm.framework.permission.core.annotations.CrmPermission;
-import cn.iocoder.yudao.module.crm.service.business.CrmBusinessProductService;
 import cn.iocoder.yudao.module.crm.service.business.CrmBusinessService;
 import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService;
 import cn.iocoder.yudao.module.crm.service.followup.bo.CrmUpdateFollowUpReqBO;
@@ -63,12 +63,12 @@ public class CrmContractServiceImpl implements CrmContractService {
 
     @Resource
     private CrmContractMapper contractMapper;
+    @Resource
+    private CrmContractProductMapper contractProductMapper;
 
     @Resource
     private CrmPermissionService crmPermissionService;
     @Resource
-    private CrmBusinessProductService businessProductService;
-    @Resource
     private CrmProductService productService;
     @Resource
     private CrmCustomerService customerService;
@@ -89,11 +89,14 @@ public class CrmContractServiceImpl implements CrmContractService {
         // 1.1 插入合同
         CrmContractDO contract = BeanUtils.toBean(createReqVO, CrmContractDO.class).setId(null);
         contractMapper.insert(contract);
-        // 1.2 插入商机关联商品
+        // 1.2 插入合同关联商品
         if (CollUtil.isNotEmpty(createReqVO.getProductItems())) { // 如果有的话
-            List<CrmBusinessProductDO> businessProduct = convertBusinessProductList(createReqVO, contract.getId());
-            businessProductService.createBusinessProductBatch(businessProduct);
+            List<CrmContractProductDO> productList = convertContractProductList(createReqVO, contract.getId());
+            contractProductMapper.insertBatch(productList);
             // 更新合同商品总金额
+            contractMapper.updateById(new CrmContractDO().setId(contract.getId()).setProductPrice(
+                    getSumValue(productList, CrmContractProductDO::getTotalPrice, Integer::sum)));
+            // TODO @puhui999: 如果存在合同关联了商机则更新商机商品关联
         }
 
         // 2. 创建数据权限
@@ -137,29 +140,29 @@ public class CrmContractServiceImpl implements CrmContractService {
         if (CollUtil.isEmpty(updateReqVO.getProductItems())) {
             return;
         }
-        List<CrmBusinessProductDO> newProductList = convertBusinessProductList(updateReqVO, contractId);
-        List<CrmBusinessProductDO> oldProductList = businessProductService.getBusinessProductListByContractId(contractId);
-        List<List<CrmBusinessProductDO>> diffList = diffList(oldProductList, newProductList, (oldObj, newObj) -> {
-            if (ObjUtil.notEqual(oldObj.getProductId(), newObj.getProductId())) {
-                return false;
+        List<CrmContractProductDO> newProductList = convertContractProductList(updateReqVO, contractId);
+        List<CrmContractProductDO> oldProductList = contractProductMapper.selectListByContractId(contractId);
+        List<List<CrmContractProductDO>> diffList = diffList(oldProductList, newProductList, (oldObj, newObj) -> {
+            boolean equal = ObjUtil.equal(oldObj.getProductId(), newObj.getProductId());
+            if (equal) {
+                newObj.setId(oldObj.getId()); // 设置一下老的编号更新时需要使用
             }
-            newObj.setId(oldObj.getId()); // 设置一下老的编号更新时需要使用
-            return true;
+            return equal;
         });
-        if (CollUtil.isNotEmpty(diffList.getFirst())) {
-            businessProductService.createBusinessProductBatch(diffList.getFirst());
+        if (CollUtil.isNotEmpty(diffList.get(0))) {
+            contractProductMapper.insertBatch(diffList.get(0));
         }
         if (CollUtil.isNotEmpty(diffList.get(1))) {
-            businessProductService.updateBusinessProductBatch(diffList.get(1));
+            contractProductMapper.updateBatch(diffList.get(1));
         }
         if (CollUtil.isNotEmpty(diffList.get(2))) {
-            businessProductService.deleteBusinessProductBatch(diffList.get(2));
+            contractProductMapper.deleteBatchIds(convertList(diffList.get(2), CrmContractProductDO::getId));
         }
     }
 
     // TODO @合同待定:缺一个取消合同的接口;只有草稿、审批中可以取消;CrmAuditStatusEnum
 
-    private List<CrmBusinessProductDO> convertBusinessProductList(CrmContractSaveReqVO reqVO, Long contractId) {
+    private List<CrmContractProductDO> convertContractProductList(CrmContractSaveReqVO reqVO, Long contractId) {
         // 校验商品存在
         Set<Long> productIds = convertSet(reqVO.getProductItems(), CrmContractSaveReqVO.CrmContractProductItem::getId);
         List<CrmProductDO> productList = productService.getProductList(productIds);
@@ -169,8 +172,8 @@ public class CrmContractServiceImpl implements CrmContractService {
         Map<Long, CrmProductDO> productMap = convertMap(productList, CrmProductDO::getId);
         return convertList(reqVO.getProductItems(), productItem -> {
             CrmProductDO product = productMap.get(productItem.getId());
-            return BeanUtils.toBean(product, CrmBusinessProductDO.class)
-                    .setId(null).setBusinessId(reqVO.getBusinessId()).setProductId(productItem.getId()).setContractId(contractId)
+            return BeanUtils.toBean(product, CrmContractProductDO.class)
+                    .setId(null).setProductId(productItem.getId()).setContractId(contractId)
                     .setCount(productItem.getCount()).setDiscountPercent(productItem.getDiscountPercent())
                     .setTotalPrice(MoneyUtils.calculator(product.getPrice(), productItem.getCount(), productItem.getDiscountPercent()));
         });
@@ -313,5 +316,11 @@ public class CrmContractServiceImpl implements CrmContractService {
     public Long getContractCountByBusinessId(Long businessId) {
         return contractMapper.selectCountByBusinessId(businessId);
     }
+
+    @Override
+    public List<CrmContractProductDO> getContractProductListByContractId(Long contactId) {
+        return contractProductMapper.selectListByContractId(contactId);
+    }
+
     // TODO @合同待定:需要新增一个 ContractConfigDO 表,合同配置,重点是到期提醒;
 }
diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/product/CrmProductServiceImpl.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/product/CrmProductServiceImpl.java
index 55f8a3593..95205524e 100644
--- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/product/CrmProductServiceImpl.java
+++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/product/CrmProductServiceImpl.java
@@ -160,7 +160,7 @@ public class CrmProductServiceImpl implements CrmProductService {
         if (CollUtil.isEmpty(ids)) {
             return Collections.emptyList();
         }
-        return productMapper.selectListByIds(ids);
+        return productMapper.selectBatchIds(ids);
     }
 
 }
diff --git a/yudao-module-erp/yudao-module-erp-biz/src/test/java/cn/iocoder/yudao/module/erp/service/product/ErpProductUnitServiceImplTest.java b/yudao-module-erp/yudao-module-erp-biz/src/test/java/cn/iocoder/yudao/module/erp/service/product/ErpProductUnitServiceImplTest.java
index c4023df0e..669d88569 100644
--- a/yudao-module-erp/yudao-module-erp-biz/src/test/java/cn/iocoder/yudao/module/erp/service/product/ErpProductUnitServiceImplTest.java
+++ b/yudao-module-erp/yudao-module-erp-biz/src/test/java/cn/iocoder/yudao/module/erp/service/product/ErpProductUnitServiceImplTest.java
@@ -1,24 +1,23 @@
 package cn.iocoder.yudao.module.erp.service.product;
 
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
 import cn.iocoder.yudao.module.erp.controller.admin.product.vo.unit.ErpProductUnitPageReqVO;
 import cn.iocoder.yudao.module.erp.controller.admin.product.vo.unit.ErpProductUnitSaveReqVO;
-import org.junit.jupiter.api.Disabled;
-import org.junit.jupiter.api.Test;
-
-import jakarta.annotation.Resource;
-
-import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
-
 import cn.iocoder.yudao.module.erp.dal.dataobject.product.ErpProductUnitDO;
 import cn.iocoder.yudao.module.erp.dal.mysql.product.ErpProductUnitMapper;
-import cn.iocoder.yudao.framework.common.pojo.PageResult;
-
+import jakarta.annotation.Resource;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
 import org.springframework.context.annotation.Import;
 
-import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.*;
-import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*;
-import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.*;
-import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.*;
+import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildBetweenTime;
+import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId;
+import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
+import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException;
+import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId;
+import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
+import static cn.iocoder.yudao.module.erp.ErrorCodeConstants.PRODUCT_UNIT_NOT_EXISTS;
 import static org.junit.jupiter.api.Assertions.*;
 
 /**
diff --git a/yudao-module-erp/yudao-module-erp-biz/src/test/java/cn/iocoder/yudao/module/erp/service/product/ProductServiceImplTest.java b/yudao-module-erp/yudao-module-erp-biz/src/test/java/cn/iocoder/yudao/module/erp/service/product/ProductServiceImplTest.java
index 03298bcc3..89a7020ef 100644
--- a/yudao-module-erp/yudao-module-erp-biz/src/test/java/cn/iocoder/yudao/module/erp/service/product/ProductServiceImplTest.java
+++ b/yudao-module-erp/yudao-module-erp-biz/src/test/java/cn/iocoder/yudao/module/erp/service/product/ProductServiceImplTest.java
@@ -1,24 +1,23 @@
 package cn.iocoder.yudao.module.erp.service.product;
 
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
 import cn.iocoder.yudao.module.erp.controller.admin.product.vo.product.ProductPageReqVO;
 import cn.iocoder.yudao.module.erp.controller.admin.product.vo.product.ProductSaveReqVO;
-import org.junit.jupiter.api.Disabled;
-import org.junit.jupiter.api.Test;
-
-import jakarta.annotation.Resource;
-
-import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
-
 import cn.iocoder.yudao.module.erp.dal.dataobject.product.ErpProductDO;
 import cn.iocoder.yudao.module.erp.dal.mysql.product.ErpProductMapper;
-import cn.iocoder.yudao.framework.common.pojo.PageResult;
-
+import jakarta.annotation.Resource;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
 import org.springframework.context.annotation.Import;
 
-import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.*;
-import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*;
-import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.*;
-import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.*;
+import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildBetweenTime;
+import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId;
+import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
+import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException;
+import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId;
+import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
+import static cn.iocoder.yudao.module.erp.ErrorCodeConstants.PRODUCT_NOT_EXISTS;
 import static org.junit.jupiter.api.Assertions.*;
 
 /**