mirror of
				https://gitee.com/hhyykk/ipms-sjy.git
				synced 2025-11-01 02:38:43 +08:00 
			
		
		
		
	✨ CRM:完善商机的列表
This commit is contained in:
		| @@ -3,6 +3,8 @@ package cn.iocoder.yudao.module.crm.controller.admin.business; | ||||
| import cn.hutool.core.collection.CollUtil; | ||||
| import cn.iocoder.yudao.framework.common.pojo.CommonResult; | ||||
| import cn.iocoder.yudao.framework.common.pojo.PageResult; | ||||
| import cn.iocoder.yudao.framework.common.util.collection.MapUtils; | ||||
| import cn.iocoder.yudao.framework.common.util.number.NumberUtils; | ||||
| import cn.iocoder.yudao.framework.common.util.object.BeanUtils; | ||||
| import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils; | ||||
| import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog; | ||||
| @@ -10,15 +12,21 @@ import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusi | ||||
| import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessRespVO; | ||||
| 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.convert.business.CrmBusinessConvert; | ||||
| 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.business.CrmBusinessStatusDO; | ||||
| import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessStatusTypeDO; | ||||
| 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.CrmBusinessService; | ||||
| import cn.iocoder.yudao.module.crm.service.business.CrmBusinessStatusService; | ||||
| import cn.iocoder.yudao.module.crm.service.business.CrmBusinessStatusTypeService; | ||||
| import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService; | ||||
| import cn.iocoder.yudao.module.crm.service.product.CrmProductService; | ||||
| import cn.iocoder.yudao.module.system.api.dept.DeptApi; | ||||
| import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO; | ||||
| import cn.iocoder.yudao.module.system.api.user.AdminUserApi; | ||||
| import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; | ||||
| import io.swagger.v3.oas.annotations.Operation; | ||||
| import io.swagger.v3.oas.annotations.Parameter; | ||||
| import io.swagger.v3.oas.annotations.tags.Tag; | ||||
| @@ -30,13 +38,15 @@ import org.springframework.validation.annotation.Validated; | ||||
| import org.springframework.web.bind.annotation.*; | ||||
|  | ||||
| import java.io.IOException; | ||||
| import java.util.Collections; | ||||
| import java.util.List; | ||||
| import java.util.Map; | ||||
| import java.util.stream.Stream; | ||||
|  | ||||
| import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; | ||||
| import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; | ||||
| import static cn.iocoder.yudao.framework.common.pojo.PageParam.PAGE_SIZE_NONE; | ||||
| import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; | ||||
| import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; | ||||
| import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; | ||||
| import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT; | ||||
| import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; | ||||
| import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.CUSTOMER_NOT_EXISTS; | ||||
| @@ -55,6 +65,13 @@ public class CrmBusinessController { | ||||
|     private CrmBusinessStatusTypeService businessStatusTypeService; | ||||
|     @Resource | ||||
|     private CrmBusinessStatusService businessStatusService; | ||||
|     @Resource | ||||
|     private CrmProductService productService; | ||||
|  | ||||
|     @Resource | ||||
|     private AdminUserApi adminUserApi; | ||||
|     @Resource | ||||
|     private DeptApi deptApi; | ||||
|  | ||||
|     @PostMapping("/create") | ||||
|     @Operation(summary = "创建商机") | ||||
| @@ -86,9 +103,25 @@ public class CrmBusinessController { | ||||
|     @PreAuthorize("@ss.hasPermission('crm:business:query')") | ||||
|     public CommonResult<CrmBusinessRespVO> getBusiness(@RequestParam("id") Long id) { | ||||
|         CrmBusinessDO business = businessService.getBusiness(id); | ||||
|         return success(BeanUtils.toBean(business, CrmBusinessRespVO.class)); | ||||
|         return success(buildBusinessDetail(business)); | ||||
|     } | ||||
|  | ||||
|     private CrmBusinessRespVO buildBusinessDetail(CrmBusinessDO business) { | ||||
|         if (business == null) { | ||||
|             return null; | ||||
|         } | ||||
|         CrmBusinessRespVO businessVO = buildBusinessDetailList(Collections.singletonList(business)).get(0); | ||||
|         // 拼接产品项 | ||||
|         List<CrmBusinessProductDO> businessProducts = businessService.getBusinessProductListByBusinessId(businessVO.getId()); | ||||
|         Map<Long, CrmProductDO> productMap = productService.getProductMap( | ||||
|                 convertSet(businessProducts, CrmBusinessProductDO::getProductId)); | ||||
|         businessVO.setProducts(BeanUtils.toBean(businessProducts, CrmBusinessRespVO.Product.class, businessProductVO -> | ||||
|                 MapUtils.findAndThen(productMap, businessProductVO.getProductId(), | ||||
|                         product -> businessProductVO.setProductNo(product.getNo()).setProductUnit(product.getUnit())))); | ||||
|         return businessVO; | ||||
|     } | ||||
|  | ||||
|     // TODO 芋艿:处理下 | ||||
|     @GetMapping("/list-by-ids") | ||||
|     @Operation(summary = "获得商机列表") | ||||
|     @Parameter(name = "ids", description = "编号", required = true, example = "[1024]") | ||||
| @@ -97,6 +130,7 @@ public class CrmBusinessController { | ||||
|         return success(BeanUtils.toBean(businessService.getBusinessList(ids, getLoginUserId()), CrmBusinessRespVO.class)); | ||||
|     } | ||||
|  | ||||
|     // TODO 芋艿:处理下 | ||||
|     @GetMapping("/simple-all-list") | ||||
|     @Operation(summary = "获得联系人的精简列表") | ||||
|     @PreAuthorize("@ss.hasPermission('crm:contact:query')") | ||||
| @@ -113,7 +147,7 @@ public class CrmBusinessController { | ||||
|     @PreAuthorize("@ss.hasPermission('crm:business:query')") | ||||
|     public CommonResult<PageResult<CrmBusinessRespVO>> getBusinessPage(@Valid CrmBusinessPageReqVO pageVO) { | ||||
|         PageResult<CrmBusinessDO> pageResult = businessService.getBusinessPage(pageVO, getLoginUserId()); | ||||
|         return success(buildBusinessDetailPageResult(pageResult)); | ||||
|         return success(new PageResult<>(buildBusinessDetailList(pageResult.getList()), pageResult.getTotal())); | ||||
|     } | ||||
|  | ||||
|     @GetMapping("/page-by-customer") | ||||
| @@ -123,7 +157,7 @@ public class CrmBusinessController { | ||||
|             throw exception(CUSTOMER_NOT_EXISTS); | ||||
|         } | ||||
|         PageResult<CrmBusinessDO> pageResult = businessService.getBusinessPageByCustomerId(pageReqVO); | ||||
|         return success(buildBusinessDetailPageResult(pageResult)); | ||||
|         return success(new PageResult<>(buildBusinessDetailList(pageResult.getList()), pageResult.getTotal())); | ||||
|     } | ||||
|  | ||||
|     @GetMapping("/page-by-contact") | ||||
| @@ -131,7 +165,7 @@ public class CrmBusinessController { | ||||
|     @PreAuthorize("@ss.hasPermission('crm:business:query')") | ||||
|     public CommonResult<PageResult<CrmBusinessRespVO>> getBusinessContactPage(@Valid CrmBusinessPageReqVO pageReqVO) { | ||||
|         PageResult<CrmBusinessDO> pageResult = businessService.getBusinessPageByContact(pageReqVO); | ||||
|         return success(buildBusinessDetailPageResult(pageResult)); | ||||
|         return success(new PageResult<>(buildBusinessDetailList(pageResult.getList()), pageResult.getTotal())); | ||||
|     } | ||||
|  | ||||
|     @GetMapping("/export-excel") | ||||
| @@ -141,29 +175,43 @@ public class CrmBusinessController { | ||||
|     public void exportBusinessExcel(@Valid CrmBusinessPageReqVO exportReqVO, | ||||
|                                     HttpServletResponse response) throws IOException { | ||||
|         exportReqVO.setPageSize(PAGE_SIZE_NONE); | ||||
|         PageResult<CrmBusinessDO> pageResult = businessService.getBusinessPage(exportReqVO, getLoginUserId()); | ||||
|         List<CrmBusinessDO> list = businessService.getBusinessPage(exportReqVO, getLoginUserId()).getList(); | ||||
|         // 导出 Excel | ||||
|         ExcelUtils.write(response, "商机.xls", "数据", CrmBusinessRespVO.class, | ||||
|                 buildBusinessDetailPageResult(pageResult).getList()); | ||||
|                 buildBusinessDetailList(list)); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 构建详细的商机分页结果 | ||||
|      * | ||||
|      * @param pageResult 简单的商机分页结果 | ||||
|      * @return 详细的商机分页结果 | ||||
|      */ | ||||
|     private PageResult<CrmBusinessRespVO> buildBusinessDetailPageResult(PageResult<CrmBusinessDO> pageResult) { | ||||
|         if (CollUtil.isEmpty(pageResult.getList())) { | ||||
|             return PageResult.empty(pageResult.getTotal()); | ||||
|     private List<CrmBusinessRespVO> buildBusinessDetailList(List<CrmBusinessDO> list) { | ||||
|         if (CollUtil.isEmpty(list)) { | ||||
|             return Collections.emptyList(); | ||||
|         } | ||||
|         List<CrmBusinessStatusTypeDO> statusTypeList = businessStatusTypeService.getBusinessStatusTypeList( | ||||
|                 convertSet(pageResult.getList(), CrmBusinessDO::getStatusTypeId)); | ||||
|         List<CrmBusinessStatusDO> statusList = businessStatusService.getBusinessStatusList( | ||||
|                 convertSet(pageResult.getList(), CrmBusinessDO::getStatusId)); | ||||
|         List<CrmCustomerDO> customerList = customerService.getCustomerList( | ||||
|                 convertSet(pageResult.getList(), CrmBusinessDO::getCustomerId)); | ||||
|         return CrmBusinessConvert.INSTANCE.convertPage(pageResult, customerList, statusTypeList, statusList); | ||||
|         // 1.1 获取客户列表 | ||||
|         Map<Long, CrmCustomerDO> customerMap = customerService.getCustomerMap( | ||||
|                 convertSet(list, CrmBusinessDO::getCustomerId)); | ||||
|         // 1.2 获取创建人、负责人列表 | ||||
|         Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(convertListByFlatMap(list, | ||||
|                 contact -> Stream.of(NumberUtils.parseLong(contact.getCreator()), contact.getOwnerUserId()))); | ||||
|         Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(convertSet(userMap.values(), AdminUserRespDTO::getDeptId)); | ||||
|         // 1.3 获得商机状态组 | ||||
|         Map<Long, CrmBusinessStatusTypeDO> statusTypeMap = businessStatusTypeService.getBusinessStatusTypeMap( | ||||
|                 convertSet(list, CrmBusinessDO::getStatusTypeId)); | ||||
|         Map<Long, CrmBusinessStatusDO> statusMap = businessStatusService.getBusinessStatusMap( | ||||
|                 convertSet(list, CrmBusinessDO::getStatusId)); | ||||
|         // 2. 拼接数据 | ||||
|         return BeanUtils.toBean(list, CrmBusinessRespVO.class, businessVO -> { | ||||
|             // 2.1 设置客户名称 | ||||
|             MapUtils.findAndThen(customerMap, businessVO.getCustomerId(), customer -> businessVO.setCustomerName(customer.getName())); | ||||
|             // 2.2 设置创建人、负责人名称 | ||||
|             MapUtils.findAndThen(userMap, NumberUtils.parseLong(businessVO.getCreator()), | ||||
|                     user -> businessVO.setCreatorName(user.getNickname())); | ||||
|             MapUtils.findAndThen(userMap, businessVO.getOwnerUserId(), user -> { | ||||
|                 businessVO.setOwnerUserName(user.getNickname()); | ||||
|                 MapUtils.findAndThen(deptMap, user.getDeptId(), dept -> businessVO.setOwnerUserDeptName(dept.getName())); | ||||
|             }); | ||||
|             // 2.3 设置商机状态 | ||||
|             MapUtils.findAndThen(statusTypeMap, businessVO.getStatusTypeId(), statusType -> businessVO.setStatusTypeName(statusType.getName())); | ||||
|             MapUtils.findAndThen(statusMap, businessVO.getStatusId(), status -> businessVO.setStatusName(status.getName())); | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     @PutMapping("/transfer") | ||||
|   | ||||
| @@ -1,75 +0,0 @@ | ||||
| package cn.iocoder.yudao.module.crm.controller.admin.business.vo.business; | ||||
|  | ||||
| import com.alibaba.excel.annotation.ExcelProperty; | ||||
| import lombok.Data; | ||||
|  | ||||
| import java.math.BigDecimal; | ||||
| import java.time.LocalDateTime; | ||||
| import java.util.Set; | ||||
|  | ||||
| /** | ||||
|  * 商机 Excel VO | ||||
|  * | ||||
|  * @author ljlleo | ||||
|  */ | ||||
| @Data | ||||
| public class CrmBusinessExcelVO { | ||||
|  | ||||
|     @ExcelProperty("主键") | ||||
|     private Long id; | ||||
|  | ||||
|     @ExcelProperty("商机名称") | ||||
|     private String name; | ||||
|  | ||||
|     @ExcelProperty("商机状态类型编号") | ||||
|     private Long statusTypeId; | ||||
|  | ||||
|     @ExcelProperty("商机状态编号") | ||||
|     private Long statusId; | ||||
|  | ||||
|     @ExcelProperty("下次联系时间") | ||||
|     private LocalDateTime contactNextTime; | ||||
|  | ||||
|     @ExcelProperty("客户编号") | ||||
|     private Long customerId; | ||||
|  | ||||
|     @ExcelProperty("预计成交日期") | ||||
|     private LocalDateTime dealTime; | ||||
|  | ||||
|     @ExcelProperty("商机金额") | ||||
|     private BigDecimal price; | ||||
|  | ||||
|     @ExcelProperty("整单折扣") | ||||
|     private BigDecimal discountPercent; | ||||
|  | ||||
|     @ExcelProperty("产品总金额") | ||||
|     private BigDecimal productPrice; | ||||
|  | ||||
|     @ExcelProperty("备注") | ||||
|     private String remark; | ||||
|  | ||||
|     @ExcelProperty("负责人的用户编号") | ||||
|     private Long ownerUserId; | ||||
|  | ||||
|     @ExcelProperty("创建时间") | ||||
|     private LocalDateTime createTime; | ||||
|  | ||||
|     @ExcelProperty("只读权限的用户编号数组") | ||||
|     private Set<Long> roUserIds; | ||||
|  | ||||
|     @ExcelProperty("读写权限的用户编号数组") | ||||
|     private Set<Long> rwUserIds; | ||||
|  | ||||
|     @ExcelProperty("1赢单2输单3无效") | ||||
|     private Integer endStatus; | ||||
|  | ||||
|     @ExcelProperty("结束时的备注") | ||||
|     private String endRemark; | ||||
|  | ||||
|     @ExcelProperty("最后跟进时间") | ||||
|     private LocalDateTime contactLastTime; | ||||
|  | ||||
|     @ExcelProperty("跟进状态") | ||||
|     private Integer followUpStatus; | ||||
|  | ||||
| } | ||||
| @@ -1,69 +1,144 @@ | ||||
| package cn.iocoder.yudao.module.crm.controller.admin.business.vo.business; | ||||
|  | ||||
| import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; | ||||
| import com.alibaba.excel.annotation.ExcelProperty; | ||||
| import io.swagger.v3.oas.annotations.media.Schema; | ||||
| import jakarta.validation.constraints.NotNull; | ||||
| import lombok.AllArgsConstructor; | ||||
| import lombok.Data; | ||||
| import org.springframework.format.annotation.DateTimeFormat; | ||||
| import lombok.NoArgsConstructor; | ||||
|  | ||||
| import java.math.BigDecimal; | ||||
| import java.time.LocalDateTime; | ||||
| import java.util.List; | ||||
|  | ||||
| import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; | ||||
|  | ||||
| @Schema(description = "管理后台 - 商机 Response VO") | ||||
| @Schema(description = "管理后台 - CRM 商机 Response VO") | ||||
| @Data | ||||
| @ExcelIgnoreUnannotated | ||||
| public class CrmBusinessRespVO { | ||||
|  | ||||
|     @Schema(description = "主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "32129") | ||||
|     @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "32129") | ||||
|     @ExcelProperty("编号") | ||||
|     private Long id; | ||||
|  | ||||
|     @Schema(description = "商机名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "李四") | ||||
|     @NotNull(message = "商机名称不能为空") | ||||
|     @ExcelProperty("商机名称") | ||||
|     private String name; | ||||
|  | ||||
|     @Schema(description = "商机状态类型编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "25714") | ||||
|     @NotNull(message = "商机状态类型不能为空") | ||||
|     private Long statusTypeId; | ||||
|  | ||||
|     @Schema(description = "商机状态编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "30320") | ||||
|     @NotNull(message = "商机状态不能为空") | ||||
|     private Long statusId; | ||||
|  | ||||
|     @Schema(description = "下次联系时间") | ||||
|     @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) | ||||
|     private LocalDateTime contactNextTime; | ||||
|  | ||||
|     @Schema(description = "客户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10299") | ||||
|     @NotNull(message = "客户不能为空") | ||||
|     private Long customerId; | ||||
|  | ||||
|     @Schema(description = "预计成交日期") | ||||
|     @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) | ||||
|     private LocalDateTime dealTime; | ||||
|  | ||||
|     @Schema(description = "商机金额", example = "12371") | ||||
|     private Integer price; | ||||
|  | ||||
|     // TODO @ljileo:折扣使用 Integer 类型,存储时,默认 * 100;展示的时候,前端需要 / 100;避免精度丢失问题 | ||||
|     @Schema(description = "整单折扣") | ||||
|     private Integer discountPercent; | ||||
|  | ||||
|     @Schema(description = "产品总金额", example = "12025") | ||||
|     private BigDecimal productPrice; | ||||
|  | ||||
|     @Schema(description = "备注", example = "随便") | ||||
|     private String remark; | ||||
|  | ||||
|     @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) | ||||
|     private LocalDateTime createTime; | ||||
|  | ||||
|     @Schema(description = "客户名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "李四") | ||||
|     @ExcelProperty("客户名称") | ||||
|     private String customerName; | ||||
|  | ||||
|     @Schema(description = "跟进状态", requiredMode = Schema.RequiredMode.REQUIRED, example ="true") | ||||
|     @ExcelProperty("跟进状态") | ||||
|     private Boolean followUpStatus; | ||||
|  | ||||
|     @Schema(description = "最后跟进时间") | ||||
|     @ExcelProperty("最后跟进时间") | ||||
|     private LocalDateTime contactLastTime; | ||||
|  | ||||
|     @Schema(description = "下次联系时间") | ||||
|     @ExcelProperty("下次联系时间") | ||||
|     private LocalDateTime contactNextTime; | ||||
|  | ||||
|     @Schema(description = "负责人的用户编号", example = "25682") | ||||
|     @ExcelProperty("负责人的用户编号") | ||||
|     private Long ownerUserId; | ||||
|     @Schema(description = "负责人名字", example = "25682") | ||||
|     @ExcelProperty("负责人名字") | ||||
|     private String ownerUserName; | ||||
|     @Schema(description = "负责人部门") | ||||
|     @ExcelProperty("负责人部门") | ||||
|     private String ownerUserDeptName; | ||||
|  | ||||
|     @Schema(description = "商机状态类型编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "25714") | ||||
|     private Long statusTypeId; | ||||
|     @Schema(description = "状态类型名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "进行中") | ||||
|     @ExcelProperty("商机状态类型") | ||||
|     private String statusTypeName; | ||||
|  | ||||
|     @Schema(description = "商机状态编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "30320") | ||||
|     private Long statusId; | ||||
|     @Schema(description = "状态名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "跟进中") | ||||
|     @ExcelProperty("商机状态") | ||||
|     private String statusName; | ||||
|  | ||||
|     @Schema | ||||
|     @ExcelProperty("1赢单2输单3无效") | ||||
|     private Integer endStatus; | ||||
|  | ||||
|     @ExcelProperty("结束时的备注") | ||||
|     private String endRemark; | ||||
|  | ||||
|     @Schema(description = "预计成交日期") | ||||
|     @ExcelProperty("预计成交日期") | ||||
|     private LocalDateTime dealTime; | ||||
|  | ||||
|     @Schema(description = "产品总金额", example = "12025") | ||||
|     @ExcelProperty("产品总金额") | ||||
|     private BigDecimal totalProductPrice; | ||||
|  | ||||
|     @Schema(description = "整单折扣") | ||||
|     @ExcelProperty("整单折扣") | ||||
|     private BigDecimal discountPercent; | ||||
|  | ||||
|     @Schema(description = "商机总金额", example = "12371") | ||||
|     @ExcelProperty("商机总金额") | ||||
|     private BigDecimal totalPrice; | ||||
|  | ||||
|     @Schema(description = "备注", example = "随便") | ||||
|     @ExcelProperty("备注") | ||||
|     private String remark; | ||||
|  | ||||
|     @Schema(description = "创建人", example = "1024") | ||||
|     @ExcelProperty("创建人") | ||||
|     private String creator; | ||||
|     @Schema(description = "创建人名字", example = "芋道源码") | ||||
|     @ExcelProperty("创建人名字") | ||||
|     private String creatorName; | ||||
|  | ||||
|     @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) | ||||
|     @ExcelProperty("创建时间") | ||||
|     private LocalDateTime createTime; | ||||
|  | ||||
|     @Schema(description = "更新时间", requiredMode = Schema.RequiredMode.REQUIRED) | ||||
|     @ExcelProperty("更新时间") | ||||
|     private LocalDateTime updateTime; | ||||
|  | ||||
|     @Schema(description = "产品列表") | ||||
|     private List<Product> products; | ||||
|  | ||||
|     @Schema(description = "产品列表") | ||||
|     @Data | ||||
|     @NoArgsConstructor | ||||
|     @AllArgsConstructor | ||||
|     public static class Product { | ||||
|  | ||||
|         @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "888") | ||||
|         private Long id; | ||||
|  | ||||
|         @Schema(description = "产品编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "20529") | ||||
|         private Long productId; | ||||
|         @Schema(description = "产品名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "李四") | ||||
|         private String productName; | ||||
|         @Schema(description = "产品条码", requiredMode = Schema.RequiredMode.REQUIRED, example = "20529") | ||||
|         private String productNo; | ||||
|         @Schema(description = "产品单位", requiredMode = Schema.RequiredMode.REQUIRED, example = "李四") | ||||
|         private Integer productUnit; | ||||
|  | ||||
|         @Schema(description = "产品单价", requiredMode = Schema.RequiredMode.REQUIRED, example = "123.00") | ||||
|         private BigDecimal productPrice; | ||||
|  | ||||
|         @Schema(description = "合同价格", requiredMode = Schema.RequiredMode.REQUIRED, example = "123.00") | ||||
|         private BigDecimal businessPrice; | ||||
|  | ||||
|         @Schema(description = "产品数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "8911") | ||||
|         private Integer count; | ||||
|  | ||||
|         @Schema(description = "总计价格", requiredMode = Schema.RequiredMode.REQUIRED, example = "123.00") | ||||
|         private BigDecimal totalPrice; | ||||
|  | ||||
|     } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -1,8 +1,7 @@ | ||||
| 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.enums.business.CrmBizEndStatus; | ||||
| import cn.iocoder.yudao.module.crm.framework.operatelog.core.CrmCustomerParseFunction; | ||||
| import cn.iocoder.yudao.module.crm.framework.operatelog.core.SysAdminUserParseFunction; | ||||
| import com.mzt.logapi.starter.annotation.DiffLogField; | ||||
| import io.swagger.v3.oas.annotations.media.Schema; | ||||
| import jakarta.validation.constraints.NotNull; | ||||
| @@ -29,75 +28,68 @@ public class CrmBusinessSaveReqVO { | ||||
|     @NotNull(message = "商机名称不能为空") | ||||
|     private String name; | ||||
|  | ||||
|     @Schema(description = "商机状态类型编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "25714") | ||||
|     @DiffLogField(name = "商机状态") | ||||
|     @NotNull(message = "商机状态类型不能为空") | ||||
|     private Long statusTypeId; | ||||
|  | ||||
|     @Schema(description = "商机状态编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "30320") | ||||
|     @DiffLogField(name = "商机状态") | ||||
|     @NotNull(message = "商机状态不能为空") | ||||
|     private Long statusId; | ||||
|     @Schema(description = "客户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10299") | ||||
|     @DiffLogField(name = "客户", function = CrmCustomerParseFunction.NAME) | ||||
|     @NotNull(message = "客户不能为空") | ||||
|     private Long customerId; | ||||
|  | ||||
|     @Schema(description = "下次联系时间") | ||||
|     @DiffLogField(name = "下次联系时间") | ||||
|     @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) | ||||
|     private LocalDateTime contactNextTime; | ||||
|  | ||||
|     @Schema(description = "客户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10299") | ||||
|     @DiffLogField(name = "客户", function = CrmCustomerParseFunction.NAME) | ||||
|     @NotNull(message = "客户不能为空") | ||||
|     private Long customerId; | ||||
|     @Schema(description = "负责人用户编号", example = "14334") | ||||
|     @NotNull(message = "负责人不能为空") | ||||
|     @DiffLogField(name = "负责人", function = SysAdminUserParseFunction.NAME) | ||||
|     private Long ownerUserId; | ||||
|  | ||||
|     @Schema(description = "商机状态类型编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "25714") | ||||
|     @DiffLogField(name = "商机状态") | ||||
|     @NotNull(message = "商机状态类型不能为空") | ||||
|     private Long statusTypeId; | ||||
|  | ||||
|     @Schema(description = "预计成交日期") | ||||
|     @DiffLogField(name = "预计成交日期") | ||||
|     @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) | ||||
|     private LocalDateTime dealTime; | ||||
|  | ||||
|     @Schema(description = "商机金额", example = "12371") | ||||
|     @DiffLogField(name = "商机金额") | ||||
|     private Integer price; | ||||
|  | ||||
|     @Schema(description = "整单折扣") | ||||
|     @DiffLogField(name = "整单折扣") | ||||
|     private Integer discountPercent; | ||||
|  | ||||
|     @Schema(description = "产品总金额", example = "12025") | ||||
|     @DiffLogField(name = "产品总金额") | ||||
|     private BigDecimal productPrice; | ||||
|     @NotNull(message = "整单折扣不能为空") | ||||
|     private BigDecimal discountPercent; | ||||
|  | ||||
|     @Schema(description = "备注", example = "随便") | ||||
|     @DiffLogField(name = "备注") | ||||
|     private String remark; | ||||
|  | ||||
|     @Schema(description = "结束状态", example = "1") | ||||
|     @InEnum(CrmBizEndStatus.class) | ||||
|     private Integer endStatus; | ||||
|  | ||||
|     @Schema(description = "联系人编号", example = "110") | ||||
|     private Long contactId; // 使用场景,在【联系人详情】添加商机时,如果需要关联两者,需要传递 contactId 字段 | ||||
|  | ||||
|     // TODO @puhui999:传递 items 就行啦; | ||||
|     @Schema(description = "产品列表") | ||||
|     private List<CrmBusinessProductItem> productItems; | ||||
|     private List<Product> products; | ||||
|  | ||||
|     @Schema(description = "产品列表") | ||||
|     @Data | ||||
|     @NoArgsConstructor | ||||
|     @AllArgsConstructor | ||||
|     public static class CrmBusinessProductItem { | ||||
|     public static class Product { | ||||
|  | ||||
|         @Schema(description = "产品编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "20529") | ||||
|         @NotNull(message = "产品编号不能为空") | ||||
|         private Long id; | ||||
|         private Long productId; | ||||
|  | ||||
|         @Schema(description = "产品单价", requiredMode = Schema.RequiredMode.REQUIRED, example = "123.00") | ||||
|         @NotNull(message = "产品单价不能为空") | ||||
|         private BigDecimal productPrice; | ||||
|  | ||||
|         @Schema(description = "合同价格", requiredMode = Schema.RequiredMode.REQUIRED, example = "123.00") | ||||
|         @NotNull(message = "合同价格不能为空") | ||||
|         private BigDecimal businessPrice; | ||||
|  | ||||
|         @Schema(description = "产品数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "8911") | ||||
|         @NotNull(message = "产品数量不能为空") | ||||
|         private Integer count; | ||||
|  | ||||
|         @Schema(description = "产品折扣") | ||||
|         private Integer discountPercent; | ||||
|  | ||||
|     } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -179,7 +179,7 @@ public class CrmContractController { | ||||
|         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)); | ||||
|             productList = productService.getProductList(convertSet(contractProductList, CrmContractProductDO::getProductId)); | ||||
|         } | ||||
|         return CrmContractConvert.INSTANCE.convertList(contractList, userMap, customerList, contactMap, businessMap, contractProductMap, productList); | ||||
|     } | ||||
|   | ||||
| @@ -1,16 +1,17 @@ | ||||
| package cn.iocoder.yudao.module.crm.controller.admin.product; | ||||
|  | ||||
| import cn.hutool.core.collection.CollUtil; | ||||
| import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; | ||||
| import cn.iocoder.yudao.framework.common.pojo.CommonResult; | ||||
| import cn.iocoder.yudao.framework.common.pojo.PageParam; | ||||
| import cn.iocoder.yudao.framework.common.pojo.PageResult; | ||||
| import cn.iocoder.yudao.framework.common.util.collection.SetUtils; | ||||
| import cn.iocoder.yudao.framework.common.util.collection.MapUtils; | ||||
| import cn.iocoder.yudao.framework.common.util.object.BeanUtils; | ||||
| import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils; | ||||
| import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog; | ||||
| import cn.iocoder.yudao.module.crm.controller.admin.product.vo.product.CrmProductPageReqVO; | ||||
| import cn.iocoder.yudao.module.crm.controller.admin.product.vo.product.CrmProductRespVO; | ||||
| import cn.iocoder.yudao.module.crm.controller.admin.product.vo.product.CrmProductSaveReqVO; | ||||
| import cn.iocoder.yudao.module.crm.convert.product.CrmProductConvert; | ||||
| import cn.iocoder.yudao.module.crm.dal.dataobject.product.CrmProductCategoryDO; | ||||
| import cn.iocoder.yudao.module.crm.dal.dataobject.product.CrmProductDO; | ||||
| import cn.iocoder.yudao.module.crm.service.product.CrmProductCategoryService; | ||||
| @@ -34,10 +35,9 @@ import java.util.Map; | ||||
| 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.convertSet; | ||||
| import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSetByFlatMap; | ||||
| import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; | ||||
| import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT; | ||||
| import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; | ||||
| import static java.util.Collections.singletonList; | ||||
|  | ||||
| @Tag(name = "管理后台 - CRM 产品") | ||||
| @RestController | ||||
| @@ -49,6 +49,7 @@ public class CrmProductController { | ||||
|     private CrmProductService productService; | ||||
|     @Resource | ||||
|     private CrmProductCategoryService productCategoryService; | ||||
|  | ||||
|     @Resource | ||||
|     private AdminUserApi adminUserApi; | ||||
|  | ||||
| @@ -82,21 +83,30 @@ public class CrmProductController { | ||||
|     @PreAuthorize("@ss.hasPermission('crm:product:query')") | ||||
|     public CommonResult<CrmProductRespVO> getProduct(@RequestParam("id") Long id) { | ||||
|         CrmProductDO product = productService.getProduct(id); | ||||
|         return success(buildProductDetail(product)); | ||||
|     } | ||||
|  | ||||
|     private CrmProductRespVO buildProductDetail(CrmProductDO product) { | ||||
|         if (product == null) { | ||||
|             return success(null); | ||||
|             return null; | ||||
|         } | ||||
|         Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap( | ||||
|                 SetUtils.asSet(Long.valueOf(product.getCreator()), product.getOwnerUserId())); | ||||
|         CrmProductCategoryDO category = productCategoryService.getProductCategory(product.getCategoryId()); | ||||
|         return success(CrmProductConvert.INSTANCE.convert(product, userMap, category)); | ||||
|         return buildProductDetailList(singletonList(product)).get(0); | ||||
|     } | ||||
|  | ||||
|     @GetMapping("/simple-list") | ||||
|     @Operation(summary = "获得产品精简列表", description = "只包含被开启的产品,主要用于前端的下拉选项") | ||||
|     public CommonResult<List<CrmProductRespVO>> getProductSimpleList() { | ||||
|         List<CrmProductDO> list = productService.getProductListByStatus(CommonStatusEnum.ENABLE.getStatus()); | ||||
|         return success(convertList(list, product -> new CrmProductRespVO().setId(product.getId()).setName(product.getName()) | ||||
|                 .setUnit(product.getUnit()).setNo(product.getNo()).setPrice(product.getPrice()))); | ||||
|     } | ||||
|  | ||||
|     @GetMapping("/page") | ||||
|     @Operation(summary = "获得产品分页") | ||||
|     @PreAuthorize("@ss.hasPermission('crm:product:query')") | ||||
|     public CommonResult<PageResult<CrmProductRespVO>> getProductPage(@Valid CrmProductPageReqVO pageVO) { | ||||
|         PageResult<CrmProductDO> pageResult = productService.getProductPage(pageVO, getLoginUserId()); | ||||
|         return success(new PageResult<>(getProductDetailList(pageResult.getList()), pageResult.getTotal())); | ||||
|         PageResult<CrmProductDO> pageResult = productService.getProductPage(pageVO); | ||||
|         return success(new PageResult<>(buildProductDetailList(pageResult.getList()), pageResult.getTotal())); | ||||
|     } | ||||
|  | ||||
|     @GetMapping("/export-excel") | ||||
| @@ -106,21 +116,30 @@ public class CrmProductController { | ||||
|     public void exportProductExcel(@Valid CrmProductPageReqVO exportReqVO, | ||||
|                                    HttpServletResponse response) throws IOException { | ||||
|         exportReqVO.setPageSize(PageParam.PAGE_SIZE_NONE); | ||||
|         List<CrmProductDO> list = productService.getProductPage(exportReqVO, getLoginUserId()).getList(); | ||||
|         List<CrmProductDO> list = productService.getProductPage(exportReqVO).getList(); | ||||
|         // 导出 Excel | ||||
|         ExcelUtils.write(response, "产品.xls", "数据", CrmProductRespVO.class, | ||||
|                 getProductDetailList(list)); | ||||
|                 buildProductDetailList(list)); | ||||
|     } | ||||
|  | ||||
|     private List<CrmProductRespVO> getProductDetailList(List<CrmProductDO> list) { | ||||
|     private List<CrmProductRespVO> buildProductDetailList(List<CrmProductDO> list) { | ||||
|         if (CollUtil.isEmpty(list)) { | ||||
|             return Collections.emptyList(); | ||||
|         } | ||||
|         // 1.1 获得用户信息 | ||||
|         Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap( | ||||
|                 convertSetByFlatMap(list, user -> Stream.of(Long.valueOf(user.getCreator()), user.getOwnerUserId()))); | ||||
|         List<CrmProductCategoryDO> productCategoryList = productCategoryService.getProductCategoryList( | ||||
|         // 1.2 获得分类信息 | ||||
|         Map<Long, CrmProductCategoryDO> categoryMap = productCategoryService.getProductCategoryMap( | ||||
|                 convertSet(list, CrmProductDO::getCategoryId)); | ||||
|         return CrmProductConvert.INSTANCE.convertList(list, userMap, productCategoryList); | ||||
|         // 2. 拼接数据 | ||||
|         return BeanUtils.toBean(list, CrmProductRespVO.class, productVO -> { | ||||
|             // 2.1 设置用户信息 | ||||
|             MapUtils.findAndThen(userMap, productVO.getOwnerUserId(), user -> productVO.setOwnerUserName(user.getNickname())); | ||||
|             MapUtils.findAndThen(userMap, Long.valueOf(productVO.getCreator()), user -> productVO.setCreatorName(user.getNickname())); | ||||
|             // 2.2 设置分类名称 | ||||
|             MapUtils.findAndThen(categoryMap, productVO.getCategoryId(), category -> productVO.setCategoryName(category.getName())); | ||||
|         }); | ||||
|     } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -8,6 +8,7 @@ import com.alibaba.excel.annotation.ExcelProperty; | ||||
| import io.swagger.v3.oas.annotations.media.Schema; | ||||
| import lombok.Data; | ||||
|  | ||||
| import java.math.BigDecimal; | ||||
| import java.time.LocalDateTime; | ||||
|  | ||||
| @Schema(description = "管理后台 - CRM 产品 Response VO") | ||||
| @@ -34,7 +35,7 @@ public class CrmProductRespVO { | ||||
|  | ||||
|     @Schema(description = "价格, 单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "8911") | ||||
|     @ExcelProperty("价格,单位:分") | ||||
|     private Long price; | ||||
|     private BigDecimal price; | ||||
|  | ||||
|     @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "上架") | ||||
|     @ExcelProperty(value = "单位", converter = DictConvert.class) | ||||
|   | ||||
| @@ -7,6 +7,8 @@ import io.swagger.v3.oas.annotations.media.Schema; | ||||
| import jakarta.validation.constraints.NotNull; | ||||
| import lombok.Data; | ||||
|  | ||||
| import java.math.BigDecimal; | ||||
|  | ||||
| @Schema(description = "管理后台 - CRM 产品创建/修改 Request VO") | ||||
| @Data | ||||
| public class CrmProductSaveReqVO { | ||||
| @@ -28,10 +30,10 @@ public class CrmProductSaveReqVO { | ||||
|     @DiffLogField(name = "单位", function = CrmProductUnitParseFunction.NAME) | ||||
|     private Integer unit; | ||||
|  | ||||
|     @Schema(description = "价格, 单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "8911") | ||||
|     @Schema(description = "价格", requiredMode = Schema.RequiredMode.REQUIRED, example = "8911") | ||||
|     @NotNull(message = "价格不能为空") | ||||
|     @DiffLogField(name = "价格") | ||||
|     private Long price; | ||||
|     private BigDecimal price; | ||||
|  | ||||
|     @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "上架") | ||||
|     @NotNull(message = "状态不能为空") | ||||
|   | ||||
| @@ -1,14 +1,8 @@ | ||||
| package cn.iocoder.yudao.module.crm.convert.business; | ||||
|  | ||||
| import cn.iocoder.yudao.framework.common.pojo.PageResult; | ||||
| 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.business.vo.business.CrmBusinessRespVO; | ||||
| import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessTransferReqVO; | ||||
| import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO; | ||||
| 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.dal.dataobject.customer.CrmCustomerDO; | ||||
| import cn.iocoder.yudao.module.crm.service.followup.bo.CrmUpdateFollowUpReqBO; | ||||
| import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionTransferReqBO; | ||||
| import org.mapstruct.Mapper; | ||||
| @@ -16,9 +10,6 @@ import org.mapstruct.Mapping; | ||||
| import org.mapstruct.factory.Mappers; | ||||
|  | ||||
| import java.util.List; | ||||
| import java.util.Map; | ||||
|  | ||||
| import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; | ||||
|  | ||||
| /** | ||||
|  * 商机 Convert | ||||
| @@ -33,20 +24,6 @@ public interface CrmBusinessConvert { | ||||
|     @Mapping(target = "bizId", source = "reqVO.id") | ||||
|     CrmPermissionTransferReqBO convert(CrmBusinessTransferReqVO reqVO, Long userId); | ||||
|  | ||||
|     default PageResult<CrmBusinessRespVO> convertPage(PageResult<CrmBusinessDO> pageResult, List<CrmCustomerDO> customerList, | ||||
|                                                       List<CrmBusinessStatusTypeDO> statusTypeList, List<CrmBusinessStatusDO> statusList) { | ||||
|         PageResult<CrmBusinessRespVO> voPageResult = BeanUtils.toBean(pageResult, CrmBusinessRespVO.class); | ||||
|         // 拼接关联字段 | ||||
|         Map<Long, String> customerMap = convertMap(customerList, CrmCustomerDO::getId, CrmCustomerDO::getName); | ||||
|         Map<Long, String> statusTypeMap = convertMap(statusTypeList, CrmBusinessStatusTypeDO::getId, CrmBusinessStatusTypeDO::getName); | ||||
|         Map<Long, String> statusMap = convertMap(statusList, CrmBusinessStatusDO::getId, CrmBusinessStatusDO::getName); | ||||
|         voPageResult.getList().forEach(type -> type | ||||
|                 .setCustomerName(customerMap.get(type.getCustomerId())) | ||||
|                 .setStatusTypeName(statusTypeMap.get(type.getStatusTypeId())) | ||||
|                 .setStatusName(statusMap.get(type.getStatusId()))); | ||||
|         return voPageResult; | ||||
|     } | ||||
|  | ||||
|     @Mapping(target = "id", source = "reqBO.bizId") | ||||
|     CrmBusinessDO convert(CrmUpdateFollowUpReqBO reqBO); | ||||
|  | ||||
|   | ||||
| @@ -1,46 +0,0 @@ | ||||
| package cn.iocoder.yudao.module.crm.convert.product; | ||||
|  | ||||
| import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; | ||||
| import cn.iocoder.yudao.framework.common.util.collection.MapUtils; | ||||
| import cn.iocoder.yudao.framework.common.util.object.BeanUtils; | ||||
| import cn.iocoder.yudao.module.crm.controller.admin.product.vo.product.CrmProductRespVO; | ||||
| import cn.iocoder.yudao.module.crm.dal.dataobject.product.CrmProductCategoryDO; | ||||
| import cn.iocoder.yudao.module.crm.dal.dataobject.product.CrmProductDO; | ||||
| import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; | ||||
| import org.mapstruct.Mapper; | ||||
| import org.mapstruct.factory.Mappers; | ||||
|  | ||||
| import java.util.List; | ||||
| import java.util.Map; | ||||
| import java.util.Optional; | ||||
|  | ||||
| import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; | ||||
|  | ||||
| /** | ||||
|  * 产品 Convert | ||||
|  * | ||||
|  * @author ZanGe丶 | ||||
|  */ | ||||
| @Mapper | ||||
| public interface CrmProductConvert { | ||||
|  | ||||
|     CrmProductConvert INSTANCE = Mappers.getMapper(CrmProductConvert.class); | ||||
|  | ||||
|     default List<CrmProductRespVO> convertList(List<CrmProductDO> list, | ||||
|                                                Map<Long, AdminUserRespDTO> userMap, | ||||
|                                                List<CrmProductCategoryDO> categoryList) { | ||||
|         Map<Long, CrmProductCategoryDO> categoryMap = convertMap(categoryList, CrmProductCategoryDO::getId); | ||||
|         return CollectionUtils.convertList(list, | ||||
|                 product -> convert(product, userMap, categoryMap.get(product.getCategoryId()))); | ||||
|     } | ||||
|  | ||||
|     default CrmProductRespVO convert(CrmProductDO product, | ||||
|                                      Map<Long, AdminUserRespDTO> userMap, CrmProductCategoryDO category) { | ||||
|         CrmProductRespVO productVO = BeanUtils.toBean(product, CrmProductRespVO.class); | ||||
|         Optional.ofNullable(category).ifPresent(c -> productVO.setCategoryName(c.getName())); | ||||
|         MapUtils.findAndThen(userMap, productVO.getOwnerUserId(), user -> productVO.setOwnerUserName(user.getNickname())); | ||||
|         MapUtils.findAndThen(userMap, Long.valueOf(productVO.getCreator()), user -> productVO.setCreatorName(user.getNickname())); | ||||
|         return productVO; | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -8,10 +8,11 @@ import com.baomidou.mybatisplus.annotation.TableId; | ||||
| import com.baomidou.mybatisplus.annotation.TableName; | ||||
| import lombok.*; | ||||
|  | ||||
| import java.math.BigDecimal; | ||||
| import java.time.LocalDateTime; | ||||
|  | ||||
| /** | ||||
|  * 商机 DO | ||||
|  * CRM 商机 DO | ||||
|  * | ||||
|  * @author ljlleo | ||||
|  */ | ||||
| @@ -26,7 +27,7 @@ import java.time.LocalDateTime; | ||||
| public class CrmBusinessDO extends BaseDO { | ||||
|  | ||||
|     /** | ||||
|      * 主键 | ||||
|      * 编号 | ||||
|      */ | ||||
|     @TableId | ||||
|     private Long id; | ||||
| @@ -34,6 +35,33 @@ public class CrmBusinessDO extends BaseDO { | ||||
|      * 商机名称 | ||||
|      */ | ||||
|     private String name; | ||||
|     /** | ||||
|      * 客户编号 | ||||
|      * | ||||
|      * 关联 {@link CrmCustomerDO#getId()} | ||||
|      */ | ||||
|     private Long customerId; | ||||
|  | ||||
|     /** | ||||
|      * 跟进状态 | ||||
|      */ | ||||
|     private Boolean followUpStatus; | ||||
|     /** | ||||
|      * 最后跟进时间 | ||||
|      */ | ||||
|     private LocalDateTime contactLastTime; | ||||
|     /** | ||||
|      * 下次联系时间 | ||||
|      */ | ||||
|     private LocalDateTime contactNextTime; | ||||
|  | ||||
|     /** | ||||
|      * 负责人的用户编号 | ||||
|      * | ||||
|      * 关联 AdminUserDO 的 id 字段 | ||||
|      */ | ||||
|     private Long ownerUserId; | ||||
|  | ||||
|     /** | ||||
|      * 商机状态类型编号 | ||||
|      * | ||||
| @@ -46,39 +74,6 @@ public class CrmBusinessDO extends BaseDO { | ||||
|      * 关联 {@link CrmBusinessStatusDO#getId()} | ||||
|      */ | ||||
|     private Long statusId; | ||||
|     /** | ||||
|      * 下次联系时间 | ||||
|      */ | ||||
|     private LocalDateTime contactNextTime; | ||||
|     /** | ||||
|      * 客户编号 | ||||
|      * | ||||
|      * TODO @ljileo:这个字段,后续要写下关联的实体哈 | ||||
|      * 关联 {@link CrmCustomerDO#getId()} | ||||
|      */ | ||||
|     private Long customerId; | ||||
|     /** | ||||
|      * 预计成交日期 | ||||
|      */ | ||||
|     private LocalDateTime dealTime; | ||||
|     /** | ||||
|      * 商机金额 | ||||
|      * | ||||
|      */ | ||||
|     private Integer price; | ||||
|     /** | ||||
|      * 整单折扣 | ||||
|      * | ||||
|      */ | ||||
|     private Integer discountPercent; | ||||
|     /** | ||||
|      * 产品总金额,单位:分 | ||||
|      */ | ||||
|     private Integer productPrice; | ||||
|     /** | ||||
|      * 备注 | ||||
|      */ | ||||
|     private String remark; | ||||
|     /** | ||||
|      * 结束状态 | ||||
|      * | ||||
| @@ -89,20 +84,28 @@ public class CrmBusinessDO extends BaseDO { | ||||
|      * 结束时的备注 | ||||
|      */ | ||||
|     private String endRemark; | ||||
|     /** | ||||
|      * 最后跟进时间 | ||||
|      */ | ||||
|     private LocalDateTime contactLastTime; | ||||
|     /** | ||||
|      * 跟进状态 | ||||
|      */ | ||||
|     private Boolean followUpStatus; | ||||
|  | ||||
|     /** | ||||
|      * 负责人的用户编号 | ||||
|      * | ||||
|      * 关联 AdminUserDO 的 id 字段 | ||||
|      * 预计成交日期 | ||||
|      */ | ||||
|     private Long ownerUserId; | ||||
|     private LocalDateTime dealTime; | ||||
|     /** | ||||
|      * 产品总金额,单位:元 | ||||
|      * | ||||
|      * productPrice = ∑({@link CrmBusinessProductDO#getTotalPrice()}) | ||||
|      */ | ||||
|     private BigDecimal totalProductPrice; | ||||
|     /** | ||||
|      * 整单折扣,百分比 | ||||
|      */ | ||||
|     private BigDecimal discountPercent; | ||||
|     /** | ||||
|      * 商机总金额,单位:元 | ||||
|      */ | ||||
|     private BigDecimal totalPrice; | ||||
|     /** | ||||
|      * 备注 | ||||
|      */ | ||||
|     private String remark; | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -7,9 +7,13 @@ import com.baomidou.mybatisplus.annotation.TableId; | ||||
| import com.baomidou.mybatisplus.annotation.TableName; | ||||
| import lombok.*; | ||||
|  | ||||
| import java.math.BigDecimal; | ||||
|  | ||||
| /** | ||||
|  * 商机产品关联表 DO | ||||
|  * | ||||
|  * CrmBusinessDO : CrmBusinessProductDO = 1 : N | ||||
|  * | ||||
|  * @author lzxhqs | ||||
|  */ | ||||
| @TableName("crm_business_product") | ||||
| @@ -40,24 +44,24 @@ public class CrmBusinessProductDO extends BaseDO { | ||||
|      */ | ||||
|     private Long productId; | ||||
|     /** | ||||
|      * 产品单价 | ||||
|      * 产品单价,单位:元 | ||||
|      * | ||||
|      * 冗余 {@link CrmProductDO#getPrice()} | ||||
|      */ | ||||
|     private Integer price; | ||||
|     private BigDecimal productPrice; | ||||
|     /** | ||||
|      * 销售价格, 单位:分 | ||||
|      * 合同价格, 单位:元 | ||||
|      */ | ||||
|     private Integer salesPrice; | ||||
|     private BigDecimal businessPrice; | ||||
|     /** | ||||
|      * 数量 | ||||
|      */ | ||||
|     private Integer count; | ||||
|     private BigDecimal count; | ||||
|     /** | ||||
|      * 折扣 | ||||
|      * 总计价格,单位:元 | ||||
|      * | ||||
|      * totalPrice = businessPrice * count | ||||
|      */ | ||||
|     private Integer discountPercent; | ||||
|     /** | ||||
|      * 总计价格(折扣后价格) | ||||
|      */ | ||||
|     private Integer totalPrice; | ||||
|     private BigDecimal totalPrice; | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -11,7 +11,7 @@ import lombok.*; | ||||
| import java.time.LocalDateTime; | ||||
|  | ||||
| /** | ||||
|  * 线索 DO | ||||
|  * CRM 线索 DO | ||||
|  * | ||||
|  * @author Wanwan | ||||
|  */ | ||||
|   | ||||
| @@ -8,6 +8,8 @@ import com.baomidou.mybatisplus.annotation.TableId; | ||||
| import com.baomidou.mybatisplus.annotation.TableName; | ||||
| import lombok.*; | ||||
|  | ||||
| import java.math.BigDecimal; | ||||
|  | ||||
| /** | ||||
|  * CRM 产品 DO | ||||
|  * | ||||
| @@ -43,9 +45,9 @@ public class CrmProductDO extends BaseDO { | ||||
|      */ | ||||
|     private Integer unit; | ||||
|     /** | ||||
|      * 价格,单位:分 | ||||
|      * 价格,单位:元 | ||||
|      */ | ||||
|     private Integer price; | ||||
|     private BigDecimal price; | ||||
|     /** | ||||
|      * 状态 | ||||
|      * | ||||
|   | ||||
| @@ -1,4 +0,0 @@ | ||||
| /** | ||||
|  * 商机(销售机会) | ||||
|  */ | ||||
| package cn.iocoder.yudao.module.crm.dal.mysql.business; | ||||
| @@ -1,4 +0,0 @@ | ||||
| /** | ||||
|  * 联系人 | ||||
|  */ | ||||
| package cn.iocoder.yudao.module.crm.dal.mysql.contact; | ||||
| @@ -5,10 +5,10 @@ import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; | ||||
| 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; | ||||
| 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.List; | ||||
|  | ||||
| /** | ||||
|  * CRM 产品 Mapper | ||||
|  * | ||||
| @@ -17,21 +17,23 @@ import org.apache.ibatis.annotations.Mapper; | ||||
| @Mapper | ||||
| public interface CrmProductMapper extends BaseMapperX<CrmProductDO> { | ||||
|  | ||||
|     default PageResult<CrmProductDO> selectPage(CrmProductPageReqVO reqVO, Long userId) { | ||||
|         MPJLambdaWrapperX<CrmProductDO> query = new MPJLambdaWrapperX<>(); | ||||
|         // 拼接数据权限的查询条件 | ||||
|         CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_PRODUCT.getType(), | ||||
|                 CrmProductDO::getId, userId, null, Boolean.FALSE); | ||||
|         // 拼接自身的查询条件 | ||||
|         query.selectAll(CrmProductDO.class) | ||||
|     default PageResult<CrmProductDO> selectPage(CrmProductPageReqVO reqVO) { | ||||
|         return selectPage(reqVO, new MPJLambdaWrapperX<CrmProductDO>() | ||||
|                 .likeIfPresent(CrmProductDO::getName, reqVO.getName()) | ||||
|                 .eqIfPresent(CrmProductDO::getStatus, reqVO.getStatus()) | ||||
|                 .orderByDesc(CrmProductDO::getId); | ||||
|         return selectJoinPage(reqVO, CrmProductDO.class, query); | ||||
|                 .orderByDesc(CrmProductDO::getId)); | ||||
|     } | ||||
|  | ||||
|     default CrmProductDO selectByNo(String no) { | ||||
|         return selectOne(CrmProductDO::getNo, no); | ||||
|     } | ||||
|  | ||||
|     default Long selectCountByCategoryId(Long categoryId) { | ||||
|         return selectCount(CrmProductDO::getCategoryId, categoryId); | ||||
|     } | ||||
|  | ||||
|     default List<CrmProductDO> selectListByStatus(Integer status) { | ||||
|         return selectList(CrmProductDO::getStatus, status); | ||||
|     } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -5,6 +5,7 @@ import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusi | ||||
| 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.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.customer.CrmCustomerDO; | ||||
| import cn.iocoder.yudao.module.crm.service.business.bo.CrmBusinessUpdateProductReqBO; | ||||
| @@ -90,6 +91,14 @@ public interface CrmBusinessService { | ||||
|      */ | ||||
|     List<CrmBusinessDO> getBusinessList(Collection<Long> ids); | ||||
|  | ||||
|     /** | ||||
|      * 获得指定商机编号的产品列表 | ||||
|      * | ||||
|      * @param businessId 商机编号 | ||||
|      * @return 商机产品列表 | ||||
|      */ | ||||
|     List<CrmBusinessProductDO> getBusinessProductListByBusinessId(Long businessId); | ||||
|  | ||||
|     /** | ||||
|      * 获得商机分页 | ||||
|      * | ||||
|   | ||||
| @@ -2,9 +2,7 @@ package cn.iocoder.yudao.module.crm.service.business; | ||||
|  | ||||
| import cn.hutool.core.collection.CollUtil; | ||||
| 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; | ||||
| @@ -14,7 +12,6 @@ import cn.iocoder.yudao.module.crm.convert.business.CrmBusinessConvert; | ||||
| 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; | ||||
| @@ -36,14 +33,14 @@ import org.springframework.stereotype.Service; | ||||
| import org.springframework.transaction.annotation.Transactional; | ||||
| import org.springframework.validation.annotation.Validated; | ||||
|  | ||||
| import java.math.BigDecimal; | ||||
| 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.*; | ||||
| import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.*; | ||||
| 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.module.crm.enums.LogRecordConstants.*; | ||||
|  | ||||
| /** | ||||
| @@ -75,27 +72,29 @@ public class CrmBusinessServiceImpl implements CrmBusinessService { | ||||
|     @LogRecord(type = CRM_BUSINESS_TYPE, subType = CRM_BUSINESS_CREATE_SUB_TYPE, bizNo = "{{#business.id}}", | ||||
|             success = CRM_BUSINESS_CREATE_SUCCESS) | ||||
|     public Long createBusiness(CrmBusinessSaveReqVO createReqVO, Long userId) { | ||||
|         createReqVO.setId(null); | ||||
|         // 1. 插入商机 | ||||
|         // 1.1 校验产品项的有效性 | ||||
|         List<CrmBusinessProductDO> businessProducts = validateBusinessProducts(createReqVO.getProducts()); | ||||
|         // 1.2 TODO 芋艿:校验关联字 | ||||
|  | ||||
|         // 2.1 插入商机 | ||||
|         CrmBusinessDO business = BeanUtils.toBean(createReqVO, CrmBusinessDO.class).setOwnerUserId(userId); | ||||
|         calculateTotalPrice(business, businessProducts); | ||||
|         businessMapper.insert(business); | ||||
|         // 1.2 插入商机关联商品 | ||||
|         if (CollUtil.isNotEmpty(createReqVO.getProductItems())) { // 如果有的话 | ||||
|             List<CrmBusinessProductDO> productList = buildBusinessProductList(createReqVO.getProductItems(), business.getId()); | ||||
|             businessProductMapper.insertBatch(productList); | ||||
|             // 更新合同商品总金额 | ||||
|             businessMapper.updateById(new CrmBusinessDO().setId(business.getId()).setProductPrice( | ||||
|                     getSumValue(productList, CrmBusinessProductDO::getTotalPrice, Integer::sum))); | ||||
|         // 2.2 插入商机关联商品 | ||||
|         if (CollUtil.isNotEmpty(businessProducts)) { | ||||
|             businessProducts.forEach(item -> item.setBusinessId(business.getId())); | ||||
|             businessProductMapper.insertBatch(businessProducts); | ||||
|         } | ||||
|  | ||||
|         // TODO @puhui999:在联系人的详情页,如果直接【新建商机】,则需要关联下。这里要搞个 CrmContactBusinessDO 表 | ||||
|         createContactBusiness(business.getId(), createReqVO.getContactId()); | ||||
|  | ||||
|         // 2. 创建数据权限 | ||||
|         // 3. 创建数据权限 | ||||
|         // 设置当前操作的人为负责人 | ||||
|         permissionService.createPermission(new CrmPermissionCreateReqBO().setBizType(CrmBizTypeEnum.CRM_BUSINESS.getType()) | ||||
|                 .setBizId(business.getId()).setUserId(userId).setLevel(CrmPermissionLevelEnum.OWNER.getLevel())); | ||||
|  | ||||
|         // 3. 记录操作日志上下文 | ||||
|         // 4. 记录操作日志上下文 | ||||
|         LogRecordContext.putVariable("business", business); | ||||
|         return business.getId(); | ||||
|     } | ||||
| @@ -114,15 +113,18 @@ public class CrmBusinessServiceImpl implements CrmBusinessService { | ||||
|             success = CRM_BUSINESS_UPDATE_SUCCESS) | ||||
|     @CrmPermission(bizType = CrmBizTypeEnum.CRM_BUSINESS, bizId = "#updateReqVO.id", level = CrmPermissionLevelEnum.WRITE) | ||||
|     public void updateBusiness(CrmBusinessSaveReqVO updateReqVO) { | ||||
|         // 1. 校验存在 | ||||
|         // 1.1 校验存在 | ||||
|         CrmBusinessDO oldBusiness = validateBusinessExists(updateReqVO.getId()); | ||||
|         // 1.2 校验产品项的有效性 | ||||
|         List<CrmBusinessProductDO> businessProducts = validateBusinessProducts(updateReqVO.getProducts()); | ||||
|         // 1.3 TODO 芋艿:校验关联字 | ||||
|  | ||||
|         // 2.1 更新商机 | ||||
|         CrmBusinessDO updateObj = BeanUtils.toBean(updateReqVO, CrmBusinessDO.class); | ||||
|         calculateTotalPrice(updateObj, businessProducts); | ||||
|         businessMapper.updateById(updateObj); | ||||
|         // 2.2 更新商机关联商品 | ||||
|         List<CrmBusinessProductDO> productList = buildBusinessProductList(updateReqVO.getProductItems(), updateObj.getId()); | ||||
|         updateBusinessProduct(productList, updateObj.getId()); | ||||
|         updateBusinessProduct(updateObj.getId(), businessProducts); | ||||
|  | ||||
|         // TODO @商机待定:如果状态发生变化,插入商机状态变更记录表 | ||||
|         // 3. 记录操作日志上下文 | ||||
| @@ -130,6 +132,37 @@ public class CrmBusinessServiceImpl implements CrmBusinessService { | ||||
|         LogRecordContext.putVariable("businessName", oldBusiness.getName()); | ||||
|     } | ||||
|  | ||||
|     private void updateBusinessProduct(Long id, List<CrmBusinessProductDO> newList) { | ||||
|         List<CrmBusinessProductDO> oldList = businessProductMapper.selectListByBusinessId(id); | ||||
|         List<List<CrmBusinessProductDO>> diffList = diffList(oldList, newList, // id 不同,就认为是不同的记录 | ||||
|                 (oldVal, newVal) -> oldVal.getId().equals(newVal.getId())); | ||||
|         if (CollUtil.isNotEmpty(diffList.get(0))) { | ||||
|             diffList.get(0).forEach(o -> o.setBusinessId(id)); | ||||
|             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> validateBusinessProducts(List<CrmBusinessSaveReqVO.Product> list) { | ||||
|         // 1. 校验产品存在 | ||||
|          productService.validProductList(convertSet(list, CrmBusinessSaveReqVO.Product::getProductId)); | ||||
|         // 2. 转化为 CrmBusinessProductDO 列表 | ||||
|         return convertList(list, o -> BeanUtils.toBean(o, CrmBusinessProductDO.class, item -> { | ||||
|             item.setTotalPrice(MoneyUtils.priceMultiply(item.getBusinessPrice(), item.getCount())); | ||||
|         })); | ||||
|     } | ||||
|  | ||||
|     private void calculateTotalPrice(CrmBusinessDO business, List<CrmBusinessProductDO> businessProducts) { | ||||
|         business.setTotalProductPrice(getSumValue(businessProducts, CrmBusinessProductDO::getTotalPrice, BigDecimal::add, BigDecimal.ZERO)); | ||||
|         BigDecimal discountPrice = MoneyUtils.priceMultiplyPercent(business.getTotalProductPrice(), business.getDiscountPercent()); | ||||
|         business.setTotalPrice(business.getTotalProductPrice().subtract(discountPrice)); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void updateBusinessFollowUpBatch(List<CrmUpdateFollowUpReqBO> updateFollowUpReqBOList) { | ||||
|         businessMapper.updateBatch(CrmBusinessConvert.INSTANCE.convertList(updateFollowUpReqBOList)); | ||||
| @@ -155,44 +188,6 @@ 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> buildBusinessProductList(List<CrmBusinessSaveReqVO.CrmBusinessProductItem> productItems, | ||||
|                                                                 Long businessId) { | ||||
|         // 校验商品存在 | ||||
|         Set<Long> productIds = convertSet(productItems, 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(productItems, 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())); | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 删除校验合同是关联合同 | ||||
|      * | ||||
| @@ -235,10 +230,10 @@ public class CrmBusinessServiceImpl implements CrmBusinessService { | ||||
|  | ||||
|     @Override | ||||
|     public void updateBusinessProduct(CrmBusinessUpdateProductReqBO updateProductReqBO) { | ||||
|         // 更新商机关联商品 | ||||
|         List<CrmBusinessProductDO> productList = buildBusinessProductList( | ||||
|                 BeanUtils.toBean(updateProductReqBO.getProductItems(), CrmBusinessSaveReqVO.CrmBusinessProductItem.class), updateProductReqBO.getId()); | ||||
|         updateBusinessProduct(productList, updateProductReqBO.getId()); | ||||
|         // 更新商机关联商品 TODO yunai | ||||
| //        List<CrmBusinessProductDO> productList = buildBusinessProductList( | ||||
| //                BeanUtils.toBean(updateProductReqBO.getProductItems(), CrmBusinessSaveReqVO.Product.class), updateProductReqBO.getId()); | ||||
| //        updateBusinessProduct(productList, updateProductReqBO.getId()); | ||||
|     } | ||||
|  | ||||
|     //======================= 查询相关 ======================= | ||||
| @@ -265,6 +260,11 @@ public class CrmBusinessServiceImpl implements CrmBusinessService { | ||||
|         return businessMapper.selectBatchIds(ids); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public List<CrmBusinessProductDO> getBusinessProductListByBusinessId(Long businessId) { | ||||
|         return businessProductMapper.selectListByBusinessId(businessId); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public PageResult<CrmBusinessDO> getBusinessPage(CrmBusinessPageReqVO pageReqVO, Long userId) { | ||||
|         return businessMapper.selectPage(pageReqVO, userId); | ||||
|   | ||||
| @@ -5,11 +5,13 @@ import cn.iocoder.yudao.module.crm.controller.admin.business.vo.status.CrmBusine | ||||
| import cn.iocoder.yudao.module.crm.controller.admin.business.vo.status.CrmBusinessStatusQueryVO; | ||||
| import cn.iocoder.yudao.module.crm.controller.admin.business.vo.status.CrmBusinessStatusSaveReqVO; | ||||
| import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessStatusDO; | ||||
|  | ||||
| import jakarta.validation.Valid; | ||||
|  | ||||
| import java.util.Collection; | ||||
| import java.util.List; | ||||
| import java.util.Map; | ||||
|  | ||||
| import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; | ||||
|  | ||||
| /** | ||||
|  * 商机状态 Service 接口 | ||||
| @@ -74,4 +76,14 @@ public interface CrmBusinessStatusService { | ||||
|      */ | ||||
|     List<CrmBusinessStatusDO> getBusinessStatusList(Collection<Long> ids); | ||||
|  | ||||
|     /** | ||||
|      * 获得商机状态 Map | ||||
|      * | ||||
|      * @param ids 编号数组 | ||||
|      * @return 商机状态 Map | ||||
|      */ | ||||
|     default Map<Long, CrmBusinessStatusDO> getBusinessStatusMap(Collection<Long> ids) { | ||||
|         return convertMap(getBusinessStatusList(ids), CrmBusinessStatusDO::getId); | ||||
|     } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -1,5 +1,6 @@ | ||||
| package cn.iocoder.yudao.module.crm.service.business; | ||||
|  | ||||
| import cn.hutool.core.collection.CollUtil; | ||||
| import cn.iocoder.yudao.framework.common.pojo.PageResult; | ||||
| import cn.iocoder.yudao.framework.common.util.object.BeanUtils; | ||||
| import cn.iocoder.yudao.module.crm.controller.admin.business.vo.status.CrmBusinessStatusPageReqVO; | ||||
| @@ -13,6 +14,7 @@ import org.springframework.validation.annotation.Validated; | ||||
| import jakarta.annotation.Resource; | ||||
|  | ||||
| import java.util.Collection; | ||||
| import java.util.Collections; | ||||
| import java.util.List; | ||||
|  | ||||
| import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; | ||||
| @@ -80,6 +82,9 @@ public class CrmBusinessStatusServiceImpl implements CrmBusinessStatusService { | ||||
|  | ||||
|     @Override | ||||
|     public List<CrmBusinessStatusDO> getBusinessStatusList(Collection<Long> ids) { | ||||
|         if (CollUtil.isEmpty(ids)) { | ||||
|             return Collections.emptyList(); | ||||
|         } | ||||
|         return businessStatusMapper.selectBatchIds(ids); | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -9,6 +9,9 @@ import jakarta.validation.Valid; | ||||
|  | ||||
| import java.util.Collection; | ||||
| import java.util.List; | ||||
| import java.util.Map; | ||||
|  | ||||
| import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; | ||||
|  | ||||
| /** | ||||
|  * 商机状态类型 Service 接口 | ||||
| @@ -72,4 +75,14 @@ public interface CrmBusinessStatusTypeService { | ||||
|      */ | ||||
|     List<CrmBusinessStatusTypeDO> getBusinessStatusTypeList(Collection<Long> ids); | ||||
|  | ||||
|     /** | ||||
|      * 获得商机状态类型 Map | ||||
|      * | ||||
|      * @param ids 编号数组 | ||||
|      * @return 商机状态类型 Map | ||||
|      */ | ||||
|     default Map<Long, CrmBusinessStatusTypeDO> getBusinessStatusTypeMap(Collection<Long> ids) { | ||||
|         return convertMap(getBusinessStatusTypeList(ids), CrmBusinessStatusTypeDO::getId); | ||||
|     } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -3,7 +3,6 @@ package cn.iocoder.yudao.module.crm.service.business; | ||||
| import cn.hutool.core.collection.CollUtil; | ||||
| import cn.iocoder.yudao.framework.common.pojo.PageResult; | ||||
| import cn.iocoder.yudao.framework.common.util.object.BeanUtils; | ||||
| import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; | ||||
| import cn.iocoder.yudao.module.crm.controller.admin.business.vo.type.CrmBusinessStatusTypePageReqVO; | ||||
| import cn.iocoder.yudao.module.crm.controller.admin.business.vo.type.CrmBusinessStatusTypeQueryVO; | ||||
| import cn.iocoder.yudao.module.crm.controller.admin.business.vo.type.CrmBusinessStatusTypeSaveReqVO; | ||||
| @@ -11,19 +10,18 @@ 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.dal.mysql.business.CrmBusinessStatusMapper; | ||||
| import cn.iocoder.yudao.module.crm.dal.mysql.business.CrmBusinessStatusTypeMapper; | ||||
| import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; | ||||
| import jakarta.annotation.Resource; | ||||
| import org.springframework.stereotype.Service; | ||||
| import org.springframework.transaction.annotation.Transactional; | ||||
| import org.springframework.validation.annotation.Validated; | ||||
|  | ||||
| import jakarta.annotation.Resource; | ||||
|  | ||||
| import java.util.Collection; | ||||
| import java.util.Collections; | ||||
| import java.util.List; | ||||
|  | ||||
| import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; | ||||
| import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.BUSINESS_STATUS_TYPE_NOT_EXISTS; | ||||
| import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.BUSINESS_STATUS_TYPE_NAME_EXISTS; | ||||
| import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.BUSINESS_STATUS_TYPE_NOT_EXISTS; | ||||
|  | ||||
| /** | ||||
|  * 商机状态类型 Service 实现类 | ||||
| @@ -126,6 +124,9 @@ public class CrmBusinessStatusTypeServiceImpl implements CrmBusinessStatusTypeSe | ||||
|  | ||||
|     @Override | ||||
|     public List<CrmBusinessStatusTypeDO> getBusinessStatusTypeList(Collection<Long> ids) { | ||||
|         if (CollUtil.isEmpty(ids)) { | ||||
|             return Collections.emptyList(); | ||||
|         } | ||||
|         return businessStatusTypeMapper.selectBatchIds(ids); | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -184,7 +184,8 @@ public class CrmContractServiceImpl implements CrmContractService { | ||||
|             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())); | ||||
|                     // TODO 芋艿:这里临时注释掉 | ||||
|                     .setTotalPrice(MoneyUtils.calculator(null, productItem.getCount(), productItem.getDiscountPercent())); | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -3,10 +3,13 @@ package cn.iocoder.yudao.module.crm.service.product; | ||||
| import cn.iocoder.yudao.module.crm.controller.admin.product.vo.category.CrmProductCategoryCreateReqVO; | ||||
| import cn.iocoder.yudao.module.crm.controller.admin.product.vo.category.CrmProductCategoryListReqVO; | ||||
| import cn.iocoder.yudao.module.crm.dal.dataobject.product.CrmProductCategoryDO; | ||||
|  | ||||
| import jakarta.validation.Valid; | ||||
|  | ||||
| import java.util.Collection; | ||||
| import java.util.List; | ||||
| import java.util.Map; | ||||
|  | ||||
| import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; | ||||
|  | ||||
| /** | ||||
|  * CRM 产品分类 Service 接口 | ||||
| @@ -61,4 +64,14 @@ public interface CrmProductCategoryService { | ||||
|      */ | ||||
|     List<CrmProductCategoryDO> getProductCategoryList(Collection<Long> ids); | ||||
|  | ||||
|     /** | ||||
|      * 获得产品分类 Map | ||||
|      * | ||||
|      * @param ids 编号数组 | ||||
|      * @return 产品分类 Map | ||||
|      */ | ||||
|     default Map<Long, CrmProductCategoryDO> getProductCategoryMap(Collection<Long> ids) { | ||||
|         return convertMap(getProductCategoryList(ids), CrmProductCategoryDO::getId); | ||||
|     } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -8,6 +8,9 @@ import jakarta.validation.Valid; | ||||
|  | ||||
| import java.util.Collection; | ||||
| import java.util.List; | ||||
| import java.util.Map; | ||||
|  | ||||
| import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; | ||||
|  | ||||
| /** | ||||
|  * CRM 产品 Service 接口 | ||||
| @@ -54,28 +57,46 @@ public interface CrmProductService { | ||||
|      */ | ||||
|     List<CrmProductDO> getProductList(Collection<Long> ids); | ||||
|  | ||||
|     /** | ||||
|      * 获得产品 Map | ||||
|      * | ||||
|      * @param ids 编号 | ||||
|      * @return 产品 Map | ||||
|      */ | ||||
|     default Map<Long, CrmProductDO> getProductMap(Collection<Long> ids) { | ||||
|         return convertMap(getProductList(ids), CrmProductDO::getId); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 获得产品分页 | ||||
|      * | ||||
|      * @param pageReqVO 分页查询 | ||||
|      * @return 产品分页 | ||||
|      */ | ||||
|     PageResult<CrmProductDO> getProductPage(CrmProductPageReqVO pageReqVO, Long userId); | ||||
|     PageResult<CrmProductDO> getProductPage(CrmProductPageReqVO pageReqVO); | ||||
|  | ||||
|     /** | ||||
|      * 获得产品 | ||||
|      * 获得产品数量 | ||||
|      * | ||||
|      * @param categoryId 分类编号 | ||||
|      * @return 产品 | ||||
|      */ | ||||
|     CrmProductDO getProductByCategoryId(Long categoryId); | ||||
|     Long getProductByCategoryId(Long categoryId); | ||||
|  | ||||
|     /** | ||||
|      * 获得产品列表 | ||||
|      * 获得指定状态的产品列表 | ||||
|      * | ||||
|      * @param ids 产品编号 | ||||
|      * @param status 状态 | ||||
|      * @return 产品列表 | ||||
|      */ | ||||
|     List<CrmProductDO> getProductListByIds(Collection<Long> ids); | ||||
|     List<CrmProductDO> getProductListByStatus(Integer status); | ||||
|  | ||||
|     /** | ||||
|      * 校验产品们的有效性 | ||||
|      * | ||||
|      * @param ids 编号数组 | ||||
|      * @return 产品列表 | ||||
|      */ | ||||
|     List<CrmProductDO> validProductList(Collection<Long> ids); | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| package cn.iocoder.yudao.module.crm.service.product; | ||||
|  | ||||
| import cn.hutool.core.collection.CollUtil; | ||||
| import cn.hutool.core.collection.ListUtil; | ||||
| import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; | ||||
| import cn.iocoder.yudao.framework.common.pojo.PageResult; | ||||
| import cn.iocoder.yudao.framework.common.util.object.BeanUtils; | ||||
| import cn.iocoder.yudao.module.crm.controller.admin.product.vo.product.CrmProductPageReqVO; | ||||
| @@ -15,7 +15,6 @@ import cn.iocoder.yudao.module.crm.framework.permission.core.annotations.CrmPerm | ||||
| import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService; | ||||
| import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionCreateReqBO; | ||||
| import cn.iocoder.yudao.module.system.api.user.AdminUserApi; | ||||
| import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; | ||||
| import com.mzt.logapi.context.LogRecordContext; | ||||
| import com.mzt.logapi.service.impl.DiffParseFunction; | ||||
| import com.mzt.logapi.starter.annotation.LogRecord; | ||||
| @@ -27,8 +26,10 @@ import org.springframework.validation.annotation.Validated; | ||||
| import java.util.Collection; | ||||
| import java.util.Collections; | ||||
| import java.util.List; | ||||
| import java.util.Map; | ||||
|  | ||||
| import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; | ||||
| import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; | ||||
| import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.*; | ||||
| import static cn.iocoder.yudao.module.crm.enums.LogRecordConstants.*; | ||||
|  | ||||
| @@ -138,25 +139,41 @@ public class CrmProductServiceImpl implements CrmProductService { | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public List<CrmProductDO> getProductList(Collection<Long> ids) { | ||||
|     public PageResult<CrmProductDO> getProductPage(CrmProductPageReqVO pageReqVO) { | ||||
|         return productMapper.selectPage(pageReqVO); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public Long getProductByCategoryId(Long categoryId) { | ||||
|         return productMapper.selectCountByCategoryId(categoryId); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public List<CrmProductDO> getProductListByStatus(Integer status) { | ||||
|         return productMapper.selectListByStatus(status); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public List<CrmProductDO> validProductList(Collection<Long> ids) { | ||||
|         if (CollUtil.isEmpty(ids)) { | ||||
|             return ListUtil.empty(); | ||||
|             return Collections.emptyList(); | ||||
|         } | ||||
|         return productMapper.selectBatchIds(ids); | ||||
|         List<CrmProductDO> list = productMapper.selectBatchIds(ids); | ||||
|         Map<Long, CrmProductDO> productMap = convertMap(list, CrmProductDO::getId); | ||||
|         for (Long id : ids) { | ||||
|             CrmProductDO product = productMap.get(id); | ||||
|             if (productMap.get(id) == null) { | ||||
|                 throw exception(PRODUCT_NOT_EXISTS); | ||||
|             } | ||||
|             if (CommonStatusEnum.isDisable(product.getStatus())) { | ||||
|                 throw exception(PRODUCT_NOT_ENABLE, product.getName()); | ||||
|             } | ||||
|         } | ||||
|         return list; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public PageResult<CrmProductDO> getProductPage(CrmProductPageReqVO pageReqVO, Long userId) { | ||||
|         return productMapper.selectPage(pageReqVO, userId); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public CrmProductDO getProductByCategoryId(Long categoryId) { | ||||
|         return productMapper.selectOne(new LambdaQueryWrapper<CrmProductDO>().eq(CrmProductDO::getCategoryId, categoryId)); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public List<CrmProductDO> getProductListByIds(Collection<Long> ids) { | ||||
|     public List<CrmProductDO> getProductList(Collection<Long> ids) { | ||||
|         if (CollUtil.isEmpty(ids)) { | ||||
|             return Collections.emptyList(); | ||||
|         } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 YunaiV
					YunaiV