!864 CRM-合同:完善 TODO

Merge pull request !864 from puhui999/develop
This commit is contained in:
芋道源码 2024-02-04 01:13:25 +00:00 committed by Gitee
commit bb9173f7f0
No known key found for this signature in database
GPG Key ID: 173E9B9CA92EEF8F
31 changed files with 464 additions and 127 deletions

View File

@ -35,6 +35,22 @@ public class MoneyUtils {
return calculateRatePrice(price, rate, 0, RoundingMode.FLOOR).intValue(); return calculateRatePrice(price, rate, 0, RoundingMode.FLOOR).intValue();
} }
/**
* 计算百分比金额
*
* @param price 金额单位分
* @param count 数量
* @param percent 折扣单位分列如 60.2%则传入 6020
* @return 商品总价
*/
public static Integer calculator(Integer price, Integer count, Integer percent) {
price = price * count;
if (percent == null) {
return price;
}
return MoneyUtils.calculateRatePriceFloor(price, (double) (percent / 100));
}
/** /**
* 计算百分比金额 * 计算百分比金额
* *

View File

@ -0,0 +1,26 @@
package cn.iocoder.yudao.module.bpm.api.listener;
import cn.iocoder.yudao.module.bpm.api.listener.dto.BpmResultListenerRespDTO;
/**
* 业务流程实例的结果发生变化的监听器 Api
*
* @author HUIHUI
*/
public interface BpmResultListenerApi {
/**
* 监听的流程定义 Key
*
* @return 返回监听的流程定义 Key
*/
String getProcessDefinitionKey();
/**
* 处理事件
*
* @param event 事件
*/
void onEvent(BpmResultListenerRespDTO event);
}

View File

@ -0,0 +1,31 @@
package cn.iocoder.yudao.module.bpm.api.listener.dto;
import lombok.Data;
/**
* 业务流程实例的结果 Response DTO
*
* @author HUIHUI
*/
@Data
public class BpmResultListenerRespDTO {
/**
* 流程实例的编号
*/
private String id;
/**
* 流程实例的 key
*/
private String processDefinitionKey;
/**
* 流程实例的结果
*/
private Integer result;
/**
* 流程实例对应的业务标识
* 例如说请假
*/
private String businessKey;
}

View File

@ -43,7 +43,7 @@ public class BpmModelController {
return success(model); return success(model);
} }
// TODO @puhui999这个接口的目的是啥呀 // TODO @puhui999这个接口的目的是啥呀业务表单预览流程🤣
@GetMapping("/get-by-key") @GetMapping("/get-by-key")
@Operation(summary = "获得模型") @Operation(summary = "获得模型")
@Parameter(name = "key", description = "流程标识", required = true, example = "oa_leave") @Parameter(name = "key", description = "流程标识", required = true, example = "oa_leave")

View File

@ -0,0 +1,35 @@
package cn.iocoder.yudao.module.bpm.framework.bpm.listener;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.bpm.api.listener.BpmResultListenerApi;
import cn.iocoder.yudao.module.bpm.api.listener.dto.BpmResultListenerRespDTO;
import cn.iocoder.yudao.module.bpm.framework.bpm.core.event.BpmProcessInstanceResultEvent;
import jakarta.annotation.Resource;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;
import java.util.List;
/**
* 业务流程结果监听器实现类
*
* @author HUIHUI
*/
@Component
public class BpmServiceResultListener implements ApplicationListener<BpmProcessInstanceResultEvent> {
@Resource
private List<BpmResultListenerApi> bpmResultListenerApis;
@Override
public final void onApplicationEvent(BpmProcessInstanceResultEvent event) {
bpmResultListenerApis.forEach(bpmResultListenerApi -> {
if (!StrUtil.equals(event.getProcessDefinitionKey(), bpmResultListenerApi.getProcessDefinitionKey())) {
return;
}
bpmResultListenerApi.onEvent(BeanUtils.toBean(event, BpmResultListenerRespDTO.class));
});
}
}

View File

@ -12,6 +12,7 @@ public interface ErrorCodeConstants {
// ========== 合同管理 1-020-000-000 ========== // ========== 合同管理 1-020-000-000 ==========
ErrorCode CONTRACT_NOT_EXISTS = new ErrorCode(1_020_000_000, "合同不存在"); ErrorCode CONTRACT_NOT_EXISTS = new ErrorCode(1_020_000_000, "合同不存在");
ErrorCode CONTRACT_UPDATE_FAIL_EDITING_PROHIBITED = new ErrorCode(1_020_000_001, "更新合同失败,原因:禁止编辑"); ErrorCode CONTRACT_UPDATE_FAIL_EDITING_PROHIBITED = new ErrorCode(1_020_000_001, "更新合同失败,原因:禁止编辑");
ErrorCode CONTRACT_SUBMIT_FAIL_NOT_DRAFT = new ErrorCode(1_020_000_002, "合同提交审核失败,原因:合同没处在未提交状态");
// ========== 线索管理 1-020-001-000 ========== // ========== 线索管理 1-020-001-000 ==========
ErrorCode CLUE_NOT_EXISTS = new ErrorCode(1_020_001_000, "线索不存在"); ErrorCode CLUE_NOT_EXISTS = new ErrorCode(1_020_001_000, "线索不存在");

View File

@ -39,6 +39,8 @@ public interface LogRecordConstants {
String CRM_CUSTOMER_POOL_SUCCESS = "将客户【{{#customerName}}】放入了公海"; String CRM_CUSTOMER_POOL_SUCCESS = "将客户【{{#customerName}}】放入了公海";
String CRM_CUSTOMER_RECEIVE_SUB_TYPE = "{{#ownerUserName != null ? '分配客户' : '领取客户'}}"; String CRM_CUSTOMER_RECEIVE_SUB_TYPE = "{{#ownerUserName != null ? '分配客户' : '领取客户'}}";
String CRM_CUSTOMER_RECEIVE_SUCCESS = "{{#ownerUserName != null ? '将客户【' + #customer.name + '】分配给【' + #ownerUserName + '】' : '领取客户【' + #customer.name + '】'}}"; String CRM_CUSTOMER_RECEIVE_SUCCESS = "{{#ownerUserName != null ? '将客户【' + #customer.name + '】分配给【' + #ownerUserName + '】' : '领取客户【' + #customer.name + '】'}}";
String CRM_CUSTOMER_IMPORT_SUB_TYPE = "{{#isUpdate ? '导入并更新客户' : '导入客户'}}";
String CRM_CUSTOMER_IMPORT_SUCCESS = "{{#isUpdate ? '导入并更新了客户【'+ #customer.name +'】' : '导入了客户【'+ #customer.name +'】'}}";
// ======================= CRM_CUSTOMER_LIMIT_CONFIG 客户限制配置 ======================= // ======================= CRM_CUSTOMER_LIMIT_CONFIG 客户限制配置 =======================

View File

@ -169,7 +169,7 @@ public class CrmBusinessController {
@PutMapping("/transfer") @PutMapping("/transfer")
@Operation(summary = "商机转移") @Operation(summary = "商机转移")
@PreAuthorize("@ss.hasPermission('crm:business:update')") @PreAuthorize("@ss.hasPermission('crm:business:update')")
public CommonResult<Boolean> transfer(@Valid @RequestBody CrmBusinessTransferReqVO reqVO) { public CommonResult<Boolean> transferBusiness(@Valid @RequestBody CrmBusinessTransferReqVO reqVO) {
businessService.transferBusiness(reqVO, getLoginUserId()); businessService.transferBusiness(reqVO, getLoginUserId());
return success(true); return success(true);
} }

View File

@ -91,7 +91,7 @@ public class CrmClueController {
@PutMapping("/transfer") @PutMapping("/transfer")
@Operation(summary = "线索转移") @Operation(summary = "线索转移")
@PreAuthorize("@ss.hasPermission('crm:clue:update')") @PreAuthorize("@ss.hasPermission('crm:clue:update')")
public CommonResult<Boolean> transfer(@Valid @RequestBody CrmClueTransferReqVO reqVO) { public CommonResult<Boolean> transferClue(@Valid @RequestBody CrmClueTransferReqVO reqVO) {
clueService.transferClue(reqVO, getLoginUserId()); clueService.transferClue(reqVO, getLoginUserId());
return success(true); return success(true);
} }

View File

@ -102,7 +102,7 @@ public class CrmContactController {
List<CrmCustomerDO> customerList = customerService.getCustomerList( List<CrmCustomerDO> customerList = customerService.getCustomerList(
Collections.singletonList(contact.getCustomerId())); Collections.singletonList(contact.getCustomerId()));
// 3. 直属上级 // 3. 直属上级
List<CrmContactDO> parentContactList = contactService.getContactList( List<CrmContactDO> parentContactList = contactService.getContactListByIds(
Collections.singletonList(contact.getParentId()), getLoginUserId()); Collections.singletonList(contact.getParentId()), getLoginUserId());
return success(CrmContactConvert.INSTANCE.convert(contact, userMap, customerList, parentContactList)); return success(CrmContactConvert.INSTANCE.convert(contact, userMap, customerList, parentContactList));
} }
@ -112,7 +112,7 @@ public class CrmContactController {
@Parameter(name = "ids", description = "编号", required = true, example = "[1024]") @Parameter(name = "ids", description = "编号", required = true, example = "[1024]")
@PreAuthorize("@ss.hasPermission('crm:contact:query')") @PreAuthorize("@ss.hasPermission('crm:contact:query')")
public CommonResult<List<CrmContactRespVO>> getContactListByIds(@RequestParam("ids") List<Long> ids) { public CommonResult<List<CrmContactRespVO>> getContactListByIds(@RequestParam("ids") List<Long> ids) {
return success(BeanUtils.toBean(contactService.getContactList(ids, getLoginUserId()), CrmContactRespVO.class)); return success(BeanUtils.toBean(contactService.getContactListByIds(ids, getLoginUserId()), CrmContactRespVO.class));
} }
@GetMapping("/simple-all-list") @GetMapping("/simple-all-list")
@ -170,7 +170,7 @@ public class CrmContactController {
Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(convertListByFlatMap(contactList, Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(convertListByFlatMap(contactList,
contact -> Stream.of(NumberUtils.parseLong(contact.getCreator()), contact.getOwnerUserId()))); contact -> Stream.of(NumberUtils.parseLong(contact.getCreator()), contact.getOwnerUserId())));
// 3. 直属上级 // 3. 直属上级
List<CrmContactDO> parentContactList = contactService.getContactList( List<CrmContactDO> parentContactList = contactService.getContactListByIds(
convertSet(contactList, CrmContactDO::getParentId), getLoginUserId()); convertSet(contactList, CrmContactDO::getParentId), getLoginUserId());
return CrmContactConvert.INSTANCE.convertPage(pageResult, userMap, crmCustomerDOList, parentContactList); return CrmContactConvert.INSTANCE.convertPage(pageResult, userMap, crmCustomerDOList, parentContactList);
} }
@ -178,7 +178,7 @@ public class CrmContactController {
@PutMapping("/transfer") @PutMapping("/transfer")
@Operation(summary = "联系人转移") @Operation(summary = "联系人转移")
@PreAuthorize("@ss.hasPermission('crm:contact:update')") @PreAuthorize("@ss.hasPermission('crm:contact:update')")
public CommonResult<Boolean> transfer(@Valid @RequestBody CrmContactTransferReqVO reqVO) { public CommonResult<Boolean> transferContact(@Valid @RequestBody CrmContactTransferReqVO reqVO) {
contactService.transferContact(reqVO, getLoginUserId()); contactService.transferContact(reqVO, getLoginUserId());
return success(true); return success(true);
} }

View File

@ -10,10 +10,18 @@ import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog; 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.controller.admin.contract.vo.*;
import cn.iocoder.yudao.module.crm.convert.contract.CrmContractConvert; 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.CrmContractDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO; 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; import cn.iocoder.yudao.module.crm.service.contract.CrmContractService;
import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService; 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.user.AdminUserApi; import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
@ -28,13 +36,14 @@ import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import java.io.IOException; import java.io.IOException;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.stream.Stream; import java.util.stream.Stream;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertListByFlatMap; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; 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.operatelog.core.enums.OperateTypeEnum.EXPORT;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
@ -48,6 +57,15 @@ public class CrmContractController {
private CrmContractService contractService; private CrmContractService contractService;
@Resource @Resource
private CrmCustomerService customerService; private CrmCustomerService customerService;
@Resource
private CrmContactService contactService;
@Resource
private CrmBusinessService businessService;
@Resource
@Lazy
private CrmBusinessProductService businessProductService;
@Resource
private CrmProductService productService;
@Resource @Resource
private AdminUserApi adminUserApi; private AdminUserApi adminUserApi;
@ -82,7 +100,19 @@ public class CrmContractController {
@PreAuthorize("@ss.hasPermission('crm:contract:query')") @PreAuthorize("@ss.hasPermission('crm:contract:query')")
public CommonResult<CrmContractRespVO> getContract(@RequestParam("id") Long id) { public CommonResult<CrmContractRespVO> getContract(@RequestParam("id") Long id) {
CrmContractDO contract = contractService.getContract(id); CrmContractDO contract = contractService.getContract(id);
return success(BeanUtils.toBean(contract, CrmContractRespVO.class)); List<CrmContractRespVO> respVOList = buildContractDetail(Collections.singletonList(contract));
CrmContractRespVO respVO = respVOList.getFirst();
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);
} }
@GetMapping("/page") @GetMapping("/page")
@ -90,15 +120,15 @@ public class CrmContractController {
@PreAuthorize("@ss.hasPermission('crm:contract:query')") @PreAuthorize("@ss.hasPermission('crm:contract:query')")
public CommonResult<PageResult<CrmContractRespVO>> getContractPage(@Valid CrmContractPageReqVO pageVO) { public CommonResult<PageResult<CrmContractRespVO>> getContractPage(@Valid CrmContractPageReqVO pageVO) {
PageResult<CrmContractDO> pageResult = contractService.getContractPage(pageVO, getLoginUserId()); PageResult<CrmContractDO> pageResult = contractService.getContractPage(pageVO, getLoginUserId());
return success(buildContractDetailPage(pageResult)); return success(BeanUtils.toBean(pageResult, CrmContractRespVO.class).setList(buildContractDetail(pageResult.getList())));
} }
@GetMapping("/page-by-customer") @GetMapping("/page-by-customer")
@Operation(summary = "获得联系人分页,基于指定客户") @Operation(summary = "获得合同分页,基于指定客户")
public CommonResult<PageResult<CrmContractRespVO>> getContractPageByCustomer(@Valid CrmContractPageReqVO pageVO) { public CommonResult<PageResult<CrmContractRespVO>> getContractPageByCustomer(@Valid CrmContractPageReqVO pageVO) {
Assert.notNull(pageVO.getCustomerId(), "客户编号不能为空"); Assert.notNull(pageVO.getCustomerId(), "客户编号不能为空");
PageResult<CrmContractDO> pageResult = contractService.getContractPageByCustomerId(pageVO); PageResult<CrmContractDO> pageResult = contractService.getContractPageByCustomerId(pageVO);
return success(buildContractDetailPage(pageResult)); return success(BeanUtils.toBean(pageResult, CrmContractRespVO.class).setList(buildContractDetail(pageResult.getList())));
} }
@GetMapping("/export-excel") @GetMapping("/export-excel")
@ -114,40 +144,43 @@ public class CrmContractController {
} }
/** /**
* 构建详细的合同分页结果 * 构建详细的合同结果
* *
* @param pageResult 简单的合同分页结果 * @param contractList 原始合同信息
* @return 细的合同分页结果 * @return 细的合同结果
*/ */
private PageResult<CrmContractRespVO> buildContractDetailPage(PageResult<CrmContractDO> pageResult) { private List<CrmContractRespVO> buildContractDetail(List<CrmContractDO> contractList) {
List<CrmContractDO> contactList = pageResult.getList(); if (CollUtil.isEmpty(contractList)) {
if (CollUtil.isEmpty(contactList)) { return Collections.emptyList();
return PageResult.empty(pageResult.getTotal());
} }
// 1. 获取客户列表 // 1. 获取客户列表
List<CrmCustomerDO> customerList = customerService.getCustomerList( List<CrmCustomerDO> customerList = customerService.getCustomerList(
convertSet(contactList, CrmContractDO::getCustomerId)); convertSet(contractList, CrmContractDO::getCustomerId));
// 2. 获取创建人负责人列表 // 2. 获取创建人负责人列表
Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(convertListByFlatMap(contactList, Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(convertListByFlatMap(contractList,
contact -> Stream.of(NumberUtils.parseLong(contact.getCreator()), contact.getOwnerUserId()))); contact -> Stream.of(NumberUtils.parseLong(contact.getCreator()), contact.getOwnerUserId())));
return CrmContractConvert.INSTANCE.convertPage(pageResult, userMap, customerList); // 3. 获取联系人
Map<Long, CrmContactDO> contactMap = convertMap(contactService.getContactListByIds(convertSet(contractList,
CrmContractDO::getContactId)), CrmContactDO::getId);
// 4. 获取商机
Map<Long, CrmBusinessDO> businessMap = convertMap(businessService.getBusinessList(convertSet(contractList,
CrmContractDO::getBusinessId)), CrmBusinessDO::getId);
return CrmContractConvert.INSTANCE.convertList(contractList, userMap, customerList, contactMap, businessMap);
} }
// TODO @puhui999transferContract
@PutMapping("/transfer") @PutMapping("/transfer")
@Operation(summary = "合同转移") @Operation(summary = "合同转移")
@PreAuthorize("@ss.hasPermission('crm:contract:update')") @PreAuthorize("@ss.hasPermission('crm:contract:update')")
public CommonResult<Boolean> transfer(@Valid @RequestBody CrmContractTransferReqVO reqVO) { public CommonResult<Boolean> transferContract(@Valid @RequestBody CrmContractTransferReqVO reqVO) {
contractService.transferContract(reqVO, getLoginUserId()); contractService.transferContract(reqVO, getLoginUserId());
return success(true); return success(true);
} }
// TODO @puhui999方法名不对哈要不改成 submit提交审核的意思 @PutMapping("/submit")
@PutMapping("/approve") @Operation(summary = "提交合同审批")
@Operation(summary = "发起合同审批流程")
@PreAuthorize("@ss.hasPermission('crm:contract:update')") @PreAuthorize("@ss.hasPermission('crm:contract:update')")
public CommonResult<Boolean> transfer(@RequestParam("id") Long id) { public CommonResult<Boolean> submitContract(@RequestParam("id") Long id) {
contractService.handleApprove(id, getLoginUserId()); contractService.submitContract(id, getLoginUserId());
return success(true); return success(true);
} }

View File

@ -3,10 +3,13 @@ package cn.iocoder.yudao.module.crm.controller.admin.contract.vo;
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
import com.alibaba.excel.annotation.ExcelProperty; import com.alibaba.excel.annotation.ExcelProperty;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Data; import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.format.annotation.DateTimeFormat; import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime; 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; import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@ -26,10 +29,16 @@ public class CrmContractRespVO {
@Schema(description = "客户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "18336") @Schema(description = "客户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "18336")
@ExcelProperty("客户编号") @ExcelProperty("客户编号")
private Long customerId; private Long customerId;
@Schema(description = "客户名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "18336")
@ExcelProperty("客户名称")
private String customerName;
@Schema(description = "商机编号", example = "10864") @Schema(description = "商机编号", example = "10864")
@ExcelProperty("商机编号") @ExcelProperty("商机编号")
private Long businessId; private Long businessId;
@Schema(description = "商机名称", example = "10864")
@ExcelProperty("商机名称")
private String businessName;
@Schema(description = "工作流编号", example = "1043") @Schema(description = "工作流编号", example = "1043")
@ExcelProperty("工作流编号") @ExcelProperty("工作流编号")
@ -74,10 +83,16 @@ public class CrmContractRespVO {
@Schema(description = "联系人编号", example = "18546") @Schema(description = "联系人编号", example = "18546")
@ExcelProperty("联系人编号") @ExcelProperty("联系人编号")
private Long contactId; private Long contactId;
@Schema(description = "联系人编号", example = "18546")
@ExcelProperty("联系人编号")
private String contactName;
@Schema(description = "公司签约人", example = "14036") @Schema(description = "公司签约人", example = "14036")
@ExcelProperty("公司签约人") @ExcelProperty("公司签约人")
private Long signUserId; private Long signUserId;
@Schema(description = "公司签约人", example = "14036")
@ExcelProperty("公司签约人")
private String signUserName;
@Schema(description = "最后跟进时间") @Schema(description = "最后跟进时间")
@ExcelProperty("最后跟进时间") @ExcelProperty("最后跟进时间")
@ -101,10 +116,6 @@ public class CrmContractRespVO {
@ExcelProperty("创建人名字") @ExcelProperty("创建人名字")
private String creatorName; private String creatorName;
@Schema(description = "客户名字", example = "test")
@ExcelProperty("客户名字")
private String customerName;
@Schema(description = "负责人", example = "test") @Schema(description = "负责人", example = "test")
@ExcelProperty("负责人") @ExcelProperty("负责人")
private String ownerUserName; private String ownerUserName;
@ -113,4 +124,36 @@ public class CrmContractRespVO {
@ExcelProperty("审批状态") @ExcelProperty("审批状态")
private Integer auditStatus; private Integer auditStatus;
@Schema(description = "产品列表")
private List<CrmContractProductItemRespVO> productItems;
@Schema(description = "产品列表")
@Data
@NoArgsConstructor
@AllArgsConstructor
public static class CrmContractProductItemRespVO {
@Schema(description = "产品编号", example = "20529")
private Long id;
@Schema(description = "产品名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "8911")
private String name;
@Schema(description = "产品编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "8911")
private String no;
@Schema(description = "单位", requiredMode = Schema.RequiredMode.REQUIRED, example = "8911")
private Integer unit;
@Schema(description = "价格,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "8911")
private Integer price;
@Schema(description = "产品数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "8911")
private Integer count;
@Schema(description = "产品折扣")
private Integer discountPercent;
}
} }

View File

@ -89,14 +89,11 @@ public class CrmContractSaveReqVO {
@DiffLogField(name = "备注") @DiffLogField(name = "备注")
private String remark; private String remark;
// TODO @puhui999这个字段按道理不用传递
@Schema(description = "审批状态", example = "1")
private Integer auditStatus;
@Schema(description = "产品列表") @Schema(description = "产品列表")
private List<CrmContractProductItem> productItems; private List<CrmContractProductItem> productItems;
@Schema(description = "商品属性") @Schema(description = "产品列表")
@Data @Data
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor

View File

@ -235,7 +235,7 @@ public class CrmCustomerController {
@PutMapping("/transfer") @PutMapping("/transfer")
@Operation(summary = "转移客户") @Operation(summary = "转移客户")
@PreAuthorize("@ss.hasPermission('crm:customer:update')") @PreAuthorize("@ss.hasPermission('crm:customer:update')")
public CommonResult<Boolean> transfer(@Valid @RequestBody CrmCustomerTransferReqVO reqVO) { public CommonResult<Boolean> transferCustomer(@Valid @RequestBody CrmCustomerTransferReqVO reqVO) {
customerService.transferCustomer(reqVO, getLoginUserId()); customerService.transferCustomer(reqVO, getLoginUserId());
return success(true); return success(true);
} }

View File

@ -75,7 +75,7 @@ public class CrmFollowUpRecordController {
public CommonResult<PageResult<CrmFollowUpRecordRespVO>> getFollowUpRecordPage(@Valid CrmFollowUpRecordPageReqVO pageReqVO) { public CommonResult<PageResult<CrmFollowUpRecordRespVO>> getFollowUpRecordPage(@Valid CrmFollowUpRecordPageReqVO pageReqVO) {
PageResult<CrmFollowUpRecordDO> pageResult = followUpRecordService.getFollowUpRecordPage(pageReqVO); PageResult<CrmFollowUpRecordDO> pageResult = followUpRecordService.getFollowUpRecordPage(pageReqVO);
/// 拼接数据 /// 拼接数据
Map<Long, CrmContactDO> contactMap = convertMap(contactService.getContactList( Map<Long, CrmContactDO> contactMap = convertMap(contactService.getContactListByIds(
convertSetByFlatMap(pageResult.getList(), item -> item.getContactIds().stream())), CrmContactDO::getId); convertSetByFlatMap(pageResult.getList(), item -> item.getContactIds().stream())), CrmContactDO::getId);
Map<Long, CrmBusinessDO> businessMap = convertMap(businessService.getBusinessList( Map<Long, CrmBusinessDO> businessMap = convertMap(businessService.getBusinessList(
convertSetByFlatMap(pageResult.getList(), item -> item.getBusinessIds().stream())), CrmBusinessDO::getId); convertSetByFlatMap(pageResult.getList(), item -> item.getBusinessIds().stream())), CrmBusinessDO::getId);

View File

@ -1,9 +1,10 @@
package cn.iocoder.yudao.module.crm.convert.contract; package cn.iocoder.yudao.module.crm.convert.contract;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils; 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.CrmContractRespVO;
import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.CrmContractTransferReqVO; 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.CrmContractDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO; import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionTransferReqBO; import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionTransferReqBO;
@ -31,17 +32,21 @@ public interface CrmContractConvert {
@Mapping(target = "bizId", source = "reqVO.id") @Mapping(target = "bizId", source = "reqVO.id")
CrmPermissionTransferReqBO convert(CrmContractTransferReqVO reqVO, Long userId); CrmPermissionTransferReqBO convert(CrmContractTransferReqVO reqVO, Long userId);
default PageResult<CrmContractRespVO> convertPage(PageResult<CrmContractDO> pageResult, Map<Long, AdminUserRespDTO> userMap, default List<CrmContractRespVO> convertList(List<CrmContractDO> contractList, Map<Long, AdminUserRespDTO> userMap,
List<CrmCustomerDO> customerList) { List<CrmCustomerDO> customerList, Map<Long, CrmContactDO> contactMap,
PageResult<CrmContractRespVO> voPageResult = BeanUtils.toBean(pageResult, CrmContractRespVO.class); Map<Long, CrmBusinessDO> businessMap) {
List<CrmContractRespVO> respVOList = BeanUtils.toBean(contractList, CrmContractRespVO.class);
// 拼接关联字段 // 拼接关联字段
Map<Long, CrmCustomerDO> customerMap = convertMap(customerList, CrmCustomerDO::getId); Map<Long, CrmCustomerDO> customerMap = convertMap(customerList, CrmCustomerDO::getId);
voPageResult.getList().forEach(contract -> { respVOList.forEach(contract -> {
findAndThen(userMap, contract.getOwnerUserId(), user -> contract.setOwnerUserName(user.getNickname())); findAndThen(userMap, contract.getOwnerUserId(), user -> contract.setOwnerUserName(user.getNickname()));
findAndThen(userMap, Long.parseLong(contract.getCreator()), user -> contract.setCreatorName(user.getNickname())); findAndThen(userMap, Long.parseLong(contract.getCreator()), user -> contract.setCreatorName(user.getNickname()));
findAndThen(userMap, contract.getSignUserId(), user -> contract.setSignUserName(user.getNickname()));
findAndThen(customerMap, contract.getCustomerId(), customer -> contract.setCustomerName(customer.getName())); findAndThen(customerMap, contract.getCustomerId(), customer -> contract.setCustomerName(customer.getName()));
findAndThen(contactMap, contract.getContactId(), contact -> contract.setContactName(contact.getName()));
findAndThen(businessMap, contract.getBusinessId(), business -> contract.setBusinessName(business.getName()));
}); });
return voPageResult; return respVOList;
} }
} }

View File

@ -1,6 +1,7 @@
package cn.iocoder.yudao.module.crm.dal.dataobject.business; package cn.iocoder.yudao.module.crm.dal.dataobject.business;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; 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.dal.dataobject.product.CrmProductDO;
import cn.iocoder.yudao.module.crm.enums.DictTypeConstants; import cn.iocoder.yudao.module.crm.enums.DictTypeConstants;
import com.baomidou.mybatisplus.annotation.KeySequence; import com.baomidou.mybatisplus.annotation.KeySequence;
@ -40,6 +41,12 @@ public class CrmBusinessProductDO extends BaseDO {
* 关联 {@link CrmProductDO#getId()} * 关联 {@link CrmProductDO#getId()}
*/ */
private Long productId; private Long productId;
/**
* 合同编号
*
* 关联 {@link CrmContractDO#getId()}
*/
private Long contractId;
/** /**
* 产品单价 * 产品单价

View File

@ -45,7 +45,7 @@ public class CrmProductDO extends BaseDO {
/** /**
* 价格单位 * 价格单位
*/ */
private Long price; private Integer price;
/** /**
* 状态 * 状态
* *

View File

@ -101,10 +101,9 @@ public interface CrmCustomerMapper extends BaseMapperX<CrmCustomerDO> {
return selectJoinPage(pageReqVO, CrmCustomerDO.class, query); return selectJoinPage(pageReqVO, CrmCustomerDO.class, query);
} }
default List<CrmCustomerDO> selectListByLockAndDealStatusAndNotPool(Boolean lockStatus, Boolean dealStatus) { default List<CrmCustomerDO> selectListByLockAndNotPool(Boolean lockStatus) {
return selectList(new LambdaQueryWrapper<CrmCustomerDO>() return selectList(new LambdaQueryWrapper<CrmCustomerDO>()
.eq(CrmCustomerDO::getLockStatus, lockStatus) .eq(CrmCustomerDO::getLockStatus, lockStatus)
.eq(CrmCustomerDO::getDealStatus, dealStatus)
.gt(CrmCustomerDO::getOwnerUserId, 0)); .gt(CrmCustomerDO::getOwnerUserId, 0));
} }

View File

@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.crm.dal.mysql.product;
import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; 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.framework.mybatis.core.query.MPJLambdaWrapperX;
import cn.iocoder.yudao.module.crm.controller.admin.product.vo.product.CrmProductPageReqVO; 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.dal.dataobject.product.CrmProductDO;
@ -9,6 +10,9 @@ import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
import cn.iocoder.yudao.module.crm.util.CrmQueryWrapperUtils; import cn.iocoder.yudao.module.crm.util.CrmQueryWrapperUtils;
import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Mapper;
import java.util.Collection;
import java.util.List;
/** /**
* CRM 产品 Mapper * CRM 产品 Mapper
* *
@ -34,4 +38,8 @@ public interface CrmProductMapper extends BaseMapperX<CrmProductDO> {
return selectOne(CrmProductDO::getNo, no); return selectOne(CrmProductDO::getNo, no);
} }
default List<CrmProductDO> selectListByIds(Collection<Long> ids) {
return selectList(new LambdaQueryWrapperX<CrmProductDO>().in(CrmProductDO::getId, ids));
}
} }

View File

@ -5,6 +5,7 @@ import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessProductDO;
import java.util.List; import java.util.List;
// TODO @lzxhqs方法名上带下 BusinessProduct主要考虑不精简的原因是因为一个逻辑可能会出现一些超越它自身方法省略不容易懂 // TODO @lzxhqs方法名上带下 BusinessProduct主要考虑不精简的原因是因为一个逻辑可能会出现一些超越它自身方法省略不容易懂
/** /**
* 商机产品关联表 Service 接口 * 商机产品关联表 Service 接口
* *
@ -14,31 +15,45 @@ public interface CrmBusinessProductService {
/** /**
* 批量新增商机产品关联数据 * 批量新增商机产品关联数据
*
* @param list 商机产品集合 * @param list 商机产品集合
*/ */
void insertBatch(List<CrmBusinessProductDO> list); void createBusinessProductBatch(List<CrmBusinessProductDO> list);
/** /**
* 根据商机id获取商机产品关联数据集合 * 根据商机id获取商机产品关联数据集合
*
* @param businessId 商机id * @param businessId 商机id
*/ */
List<CrmBusinessProductDO> selectListByBusinessId(Long businessId); List<CrmBusinessProductDO> getBusinessProductListByBusinessId(Long businessId);
/** /**
* 批量更新商机产品表 * 批量更新商机产品表
*
* @param list 商机产品数据集合 * @param list 商机产品数据集合
*/ */
void updateBatch(List<CrmBusinessProductDO> list); void updateBusinessProductBatch(List<CrmBusinessProductDO> list);
/** /**
* 批量删除 * 批量删除
*
* @param list 需要删除的商机产品集合 * @param list 需要删除的商机产品集合
*/ */
void deleteBatch(List<CrmBusinessProductDO> list); void deleteBusinessProductBatch(List<CrmBusinessProductDO> list);
/** /**
*根据商机id删除商机产品关联数据 * 根据商机id删除商机产品关联数据
*
* @param businessId 商机id * @param businessId 商机id
*/ */
void deleteByBusinessId(Long businessId); void deleteBusinessProductByBusinessId(Long businessId);
/**
* 获得合同关联的商品列表
*
* @param contractId 合同编号
* @return 关联的商品列表
*/
List<CrmBusinessProductDO> getBusinessProductListByContractId(Long contractId);
} }

View File

@ -22,29 +22,34 @@ public class CrmBusinessProductServiceImpl implements CrmBusinessProductService
private CrmBusinessProductMapper businessProductMapper; private CrmBusinessProductMapper businessProductMapper;
@Override @Override
public void insertBatch(List<CrmBusinessProductDO> list) { public void createBusinessProductBatch(List<CrmBusinessProductDO> list) {
businessProductMapper.insertBatch(list); businessProductMapper.insertBatch(list);
} }
@Override @Override
public List<CrmBusinessProductDO> selectListByBusinessId(Long businessId) { public List<CrmBusinessProductDO> getBusinessProductListByBusinessId(Long businessId) {
return businessProductMapper.selectList(CrmBusinessProductDO::getBusinessId,businessId); return businessProductMapper.selectList(CrmBusinessProductDO::getBusinessId, businessId);
} }
@Override @Override
public void updateBatch(List<CrmBusinessProductDO> list) { public void updateBusinessProductBatch(List<CrmBusinessProductDO> list) {
businessProductMapper.updateBatch(list); businessProductMapper.updateBatch(list);
} }
// TODO @lzxhqs这个方法可以直接调用 deleteList 方法然后传递 ids 就好了 // TODO @lzxhqs这个方法可以直接调用 deleteList 方法然后传递 ids 就好了
@Override @Override
public void deleteBatch(List<CrmBusinessProductDO> list) { public void deleteBusinessProductBatch(List<CrmBusinessProductDO> list) {
businessProductMapper.deleteBatchIds(CollectionUtils.convertList(list, CrmBusinessProductDO::getId)); businessProductMapper.deleteBatchIds(CollectionUtils.convertList(list, CrmBusinessProductDO::getId));
} }
@Override @Override
public void deleteByBusinessId(Long businessId) { public void deleteBusinessProductByBusinessId(Long businessId) {
businessProductMapper.deleteByBusinessId(businessId); businessProductMapper.deleteByBusinessId(businessId);
} }
@Override
public List<CrmBusinessProductDO> getBusinessProductListByContractId(Long contractId) {
return businessProductMapper.selectList(CrmBusinessProductDO::getContractId, contractId);
}
} }

View File

@ -120,20 +120,20 @@ public class CrmBusinessServiceImpl implements CrmBusinessService {
CrmBusinessProductConvert.INSTANCE.convert(product).setBusinessId(businessId)); CrmBusinessProductConvert.INSTANCE.convert(product).setBusinessId(businessId));
if (Boolean.TRUE.equals(updateFlag)) { if (Boolean.TRUE.equals(updateFlag)) {
// 根据商机 id从商机产品关联表中获取已存在的数据集合 // 根据商机 id从商机产品关联表中获取已存在的数据集合
List<CrmBusinessProductDO> oldProducts = businessProductService.selectListByBusinessId(businessId); List<CrmBusinessProductDO> oldProducts = businessProductService.getBusinessProductListByBusinessId(businessId);
List<List<CrmBusinessProductDO>> diffList = CollectionUtils.diffList(oldProducts, list, (oldValue, newValue) -> List<List<CrmBusinessProductDO>> diffList = CollectionUtils.diffList(oldProducts, list, (oldValue, newValue) ->
ObjectUtil.equal(oldValue.getProductId(), newValue.getProductId())); ObjectUtil.equal(oldValue.getProductId(), newValue.getProductId()));
if (CollUtil.isNotEmpty(diffList.getFirst())) { if (CollUtil.isNotEmpty(diffList.getFirst())) {
businessProductService.insertBatch(diffList.getFirst()); businessProductService.createBusinessProductBatch(diffList.getFirst());
} }
if (CollUtil.isNotEmpty(diffList.get(1))) { if (CollUtil.isNotEmpty(diffList.get(1))) {
businessProductService.updateBatch(diffList.get(1)); businessProductService.updateBusinessProductBatch(diffList.get(1));
} }
if (CollUtil.isNotEmpty(diffList.get(2))) { if (CollUtil.isNotEmpty(diffList.get(2))) {
businessProductService.deleteBatch(diffList.get(2)); businessProductService.deleteBusinessProductBatch(diffList.get(2));
} }
} else { } else {
businessProductService.insertBatch(list); businessProductService.createBusinessProductBatch(list);
} }
} }
@ -154,7 +154,7 @@ public class CrmBusinessServiceImpl implements CrmBusinessService {
if (CollUtil.isNotEmpty(updateReqVO.getProducts())) { if (CollUtil.isNotEmpty(updateReqVO.getProducts())) {
createBusinessProducts(updateReqVO.getProducts(), updateReqVO.getId(), true); createBusinessProducts(updateReqVO.getProducts(), updateReqVO.getId(), true);
} else { } else {
businessProductService.deleteByBusinessId(updateReqVO.getId()); businessProductService.deleteBusinessProductByBusinessId(updateReqVO.getId());
} }
// TODO @商机待定如果状态发生变化插入商机状态变更记录表 // TODO @商机待定如果状态发生变化插入商机状态变更记录表
@ -196,7 +196,7 @@ public class CrmBusinessServiceImpl implements CrmBusinessService {
*/ */
private void validateContractExists(Long businessId) { private void validateContractExists(Long businessId) {
// TODO @lzxhqs保持风格的统一selectCountByBusinessId 改成 getContractCountByBusinessId另外可以不用声明 count因为就一次性使用直接把 197 198 合并成一行 // TODO @lzxhqs保持风格的统一selectCountByBusinessId 改成 getContractCountByBusinessId另外可以不用声明 count因为就一次性使用直接把 197 198 合并成一行
Long count = contractService.selectCountByBusinessId(businessId); Long count = contractService.getContractCountByBusinessId(businessId);
if (count > 0) { if (count > 0) {
throw exception(BUSINESS_CONTRACT_EXISTS); throw exception(BUSINESS_CONTRACT_EXISTS);
} }

View File

@ -80,7 +80,7 @@ public interface CrmContactService {
* @param userId 用户编号 * @param userId 用户编号
* @return 联系人列表 * @return 联系人列表
*/ */
List<CrmContactDO> getContactList(Collection<Long> ids, Long userId); List<CrmContactDO> getContactListByIds(Collection<Long> ids, Long userId);
/** /**
* 获得联系人列表 * 获得联系人列表
@ -88,7 +88,7 @@ public interface CrmContactService {
* @param ids 编号 * @param ids 编号
* @return 联系人列表 * @return 联系人列表
*/ */
List<CrmContactDO> getContactList(Collection<Long> ids); List<CrmContactDO> getContactListByIds(Collection<Long> ids);
/** /**
* 获得联系人列表 * 获得联系人列表

View File

@ -25,6 +25,7 @@ import com.mzt.logapi.context.LogRecordContext;
import com.mzt.logapi.service.impl.DiffParseFunction; import com.mzt.logapi.service.impl.DiffParseFunction;
import com.mzt.logapi.starter.annotation.LogRecord; import com.mzt.logapi.starter.annotation.LogRecord;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
@ -56,6 +57,7 @@ public class CrmContactServiceImpl implements CrmContactService {
@Resource @Resource
private CrmPermissionService permissionService; private CrmPermissionService permissionService;
@Resource @Resource
@Lazy
private CrmContractService contractService; private CrmContractService contractService;
@Resource @Resource
private CrmContactBusinessService contactBusinessService; private CrmContactBusinessService contactBusinessService;
@ -208,7 +210,7 @@ public class CrmContactServiceImpl implements CrmContactService {
} }
@Override @Override
public List<CrmContactDO> getContactList(Collection<Long> ids, Long userId) { public List<CrmContactDO> getContactListByIds(Collection<Long> ids, Long userId) {
if (CollUtil.isEmpty(ids)) { if (CollUtil.isEmpty(ids)) {
return ListUtil.empty(); return ListUtil.empty();
} }
@ -216,7 +218,7 @@ public class CrmContactServiceImpl implements CrmContactService {
} }
@Override @Override
public List<CrmContactDO> getContactList(Collection<Long> ids) { public List<CrmContactDO> getContactListByIds(Collection<Long> ids) {
if (CollUtil.isEmpty(ids)) { if (CollUtil.isEmpty(ids)) {
return ListUtil.empty(); return ListUtil.empty();
} }

View File

@ -1,6 +1,7 @@
package cn.iocoder.yudao.module.crm.service.contract; package cn.iocoder.yudao.module.crm.service.contract;
import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.bpm.api.listener.dto.BpmResultListenerRespDTO;
import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.CrmContractPageReqVO; import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.CrmContractPageReqVO;
import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.CrmContractSaveReqVO; 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.controller.admin.contract.vo.CrmContractTransferReqVO;
@ -63,8 +64,14 @@ public interface CrmContractService {
* @param id 合同编号 * @param id 合同编号
* @param userId 用户编号 * @param userId 用户编号
*/ */
void handleApprove(Long id, Long userId); void submitContract(Long id, Long userId);
/**
* 更新合同流程审批结果
*
* @param event 审批结果
*/
void updateContractAuditStatus(BpmResultListenerRespDTO event);
/** /**
* 获得合同 * 获得合同
* *
@ -118,13 +125,12 @@ public interface CrmContractService {
*/ */
Long getContractCountByCustomerId(Long customerId); Long getContractCountByCustomerId(Long customerId);
// TODO @puhui999要不改成 getContractCountByBusinessId
/** /**
* 根据商机ID获取关联客户的合同数量 * 根据商机ID获取关联客户的合同数量
* *
* @param businessId 商机编号 * @param businessId 商机编号
* @return 数量 * @return 数量
*/ */
Long selectCountByBusinessId(Long businessId); Long getContractCountByBusinessId(Long businessId);
} }

View File

@ -2,10 +2,13 @@ package cn.iocoder.yudao.module.crm.service.contract;
import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.ListUtil; import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.ObjUtil; import cn.hutool.core.util.ObjUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.number.MoneyUtils; import cn.iocoder.yudao.framework.common.util.number.MoneyUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
import cn.iocoder.yudao.module.bpm.api.listener.dto.BpmResultListenerRespDTO;
import cn.iocoder.yudao.module.bpm.api.task.BpmProcessInstanceApi; import cn.iocoder.yudao.module.bpm.api.task.BpmProcessInstanceApi;
import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO; import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO;
import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.CrmContractPageReqVO; import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.CrmContractPageReqVO;
@ -87,8 +90,11 @@ public class CrmContractServiceImpl implements CrmContractService {
CrmContractDO contract = BeanUtils.toBean(createReqVO, CrmContractDO.class).setId(null); CrmContractDO contract = BeanUtils.toBean(createReqVO, CrmContractDO.class).setId(null);
contractMapper.insert(contract); contractMapper.insert(contract);
// 1.2 插入商机关联商品 // 1.2 插入商机关联商品
List<CrmBusinessProductDO> businessProduct = convertBusinessProductList(createReqVO); if (CollUtil.isNotEmpty(createReqVO.getProductItems())) { // 如果有的话
businessProductService.insertBatch(businessProduct); List<CrmBusinessProductDO> businessProduct = convertBusinessProductList(createReqVO, contract.getId());
businessProductService.createBusinessProductBatch(businessProduct);
// 更新合同商品总金额
}
// 2. 创建数据权限 // 2. 创建数据权限
crmPermissionService.createPermission(new CrmPermissionCreateReqBO().setUserId(userId) crmPermissionService.createPermission(new CrmPermissionCreateReqBO().setUserId(userId)
@ -106,9 +112,11 @@ public class CrmContractServiceImpl implements CrmContractService {
success = CRM_CONTRACT_UPDATE_SUCCESS) success = CRM_CONTRACT_UPDATE_SUCCESS)
@CrmPermission(bizType = CrmBizTypeEnum.CRM_CONTRACT, bizId = "#updateReqVO.id", level = CrmPermissionLevelEnum.WRITE) @CrmPermission(bizType = CrmBizTypeEnum.CRM_CONTRACT, bizId = "#updateReqVO.id", level = CrmPermissionLevelEnum.WRITE)
public void updateContract(CrmContractSaveReqVO updateReqVO) { public void updateContract(CrmContractSaveReqVO updateReqVO) {
// TODO @合同待定只有草稿审批中可以编辑 Assert.notNull(updateReqVO.getId(), "合同编号不能为空");
if (ObjUtil.notEqual(updateReqVO.getAuditStatus(), CrmAuditStatusEnum.DRAFT.getStatus()) || CrmContractDO contract = validateContractExists(updateReqVO.getId());
ObjUtil.notEqual(updateReqVO.getAuditStatus(), CrmAuditStatusEnum.PROCESS.getStatus())) { // 只有草稿审批中可以编辑
if (!ObjectUtils.equalsAny(contract.getAuditStatus(), CrmAuditStatusEnum.DRAFT.getStatus(),
CrmAuditStatusEnum.PROCESS.getStatus())) {
throw exception(CONTRACT_UPDATE_FAIL_EDITING_PROHIBITED); throw exception(CONTRACT_UPDATE_FAIL_EDITING_PROHIBITED);
} }
validateRelationDataExists(updateReqVO); validateRelationDataExists(updateReqVO);
@ -117,21 +125,41 @@ public class CrmContractServiceImpl implements CrmContractService {
// 更新合同 // 更新合同
CrmContractDO updateObj = BeanUtils.toBean(updateReqVO, CrmContractDO.class); CrmContractDO updateObj = BeanUtils.toBean(updateReqVO, CrmContractDO.class);
contractMapper.updateById(updateObj); contractMapper.updateById(updateObj);
// 更新合同关联商品
// TODO puhui999: @芋艿合同变更关联的商机后商品怎么处理 updateContractProduct(updateReqVO, updateObj.getId());
// TODO @puhui999和商品 spusku 编辑一样新增的插入修改的更新删除的删除
//List<CrmBusinessProductDO> businessProduct = convertBusinessProductList(updateReqVO);
//businessProductService.selectListByBusinessId()
//diffList()
// 3. 记录操作日志上下文 // 3. 记录操作日志上下文
LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(oldContract, CrmContractSaveReqVO.class)); LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(oldContract, CrmContractSaveReqVO.class));
LogRecordContext.putVariable("contractName", oldContract.getName()); LogRecordContext.putVariable("contractName", oldContract.getName());
} }
private void updateContractProduct(CrmContractSaveReqVO updateReqVO, Long contractId) {
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;
}
newObj.setId(oldObj.getId()); // 设置一下老的编号更新时需要使用
return true;
});
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));
}
}
// TODO @合同待定缺一个取消合同的接口只有草稿审批中可以取消CrmAuditStatusEnum // TODO @合同待定缺一个取消合同的接口只有草稿审批中可以取消CrmAuditStatusEnum
private List<CrmBusinessProductDO> convertBusinessProductList(CrmContractSaveReqVO reqVO) { private List<CrmBusinessProductDO> convertBusinessProductList(CrmContractSaveReqVO reqVO, Long contractId) {
// 校验商品存在 // 校验商品存在
Set<Long> productIds = convertSet(reqVO.getProductItems(), CrmContractSaveReqVO.CrmContractProductItem::getId); Set<Long> productIds = convertSet(reqVO.getProductItems(), CrmContractSaveReqVO.CrmContractProductItem::getId);
List<CrmProductDO> productList = productService.getProductList(productIds); List<CrmProductDO> productList = productService.getProductList(productIds);
@ -140,29 +168,14 @@ public class CrmContractServiceImpl implements CrmContractService {
} }
Map<Long, CrmProductDO> productMap = convertMap(productList, CrmProductDO::getId); Map<Long, CrmProductDO> productMap = convertMap(productList, CrmProductDO::getId);
return convertList(reqVO.getProductItems(), productItem -> { return convertList(reqVO.getProductItems(), productItem -> {
// TODO @puhui999这里可以改成直接 return不用弄一个 businessProduct 变量哈 CrmProductDO product = productMap.get(productItem.getId());
CrmBusinessProductDO businessProduct = BeanUtils.toBean(productMap.get(productItem.getId()), CrmBusinessProductDO.class); return BeanUtils.toBean(product, CrmBusinessProductDO.class)
businessProduct.setId(null).setBusinessId(reqVO.getBusinessId()).setProductId(productItem.getId()) .setId(null).setBusinessId(reqVO.getBusinessId()).setProductId(productItem.getId()).setContractId(contractId)
.setCount(productItem.getCount()).setDiscountPercent(productItem.getDiscountPercent()).setTotalPrice(calculator(businessProduct)); .setCount(productItem.getCount()).setDiscountPercent(productItem.getDiscountPercent())
return businessProduct; .setTotalPrice(MoneyUtils.calculator(product.getPrice(), productItem.getCount(), productItem.getDiscountPercent()));
}); });
} }
/**
* 计算商品总价
*
* @param businessProduct 关联商品
* @return 商品总价
*/
// TODO @puhui999这个逻辑的计算是不是可以封装到 calculateRatePriceFloor
private Integer calculator(CrmBusinessProductDO businessProduct) {
int price = businessProduct.getPrice() * businessProduct.getCount();
if (businessProduct.getDiscountPercent() == null) {
return price;
}
return MoneyUtils.calculateRatePriceFloor(price, (double) (businessProduct.getDiscountPercent() / 100));
}
/** /**
* 校验关联数据是否存在 * 校验关联数据是否存在
* *
@ -235,9 +248,12 @@ public class CrmContractServiceImpl implements CrmContractService {
@Override @Override
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
public void handleApprove(Long id, Long userId) { public void submitContract(Long id, Long userId) {
// TODO @puhui999需要做状态检查 // 1. 校验合同是否在审批
CrmContractDO contract = validateContractExists(id);
if (ObjUtil.notEqual(contract.getAuditStatus(), CrmAuditStatusEnum.DRAFT.getStatus())) {
throw exception(CONTRACT_SUBMIT_FAIL_NOT_DRAFT);
}
// 创建合同审批流程实例 // 创建合同审批流程实例
String processInstanceId = bpmProcessInstanceApi.createProcessInstance(userId, new BpmProcessInstanceCreateReqDTO() String processInstanceId = bpmProcessInstanceApi.createProcessInstance(userId, new BpmProcessInstanceCreateReqDTO()
.setProcessDefinitionKey(CONTRACT_APPROVE).setBusinessKey(String.valueOf(id))); .setProcessDefinitionKey(CONTRACT_APPROVE).setBusinessKey(String.valueOf(id)));
@ -247,6 +263,12 @@ public class CrmContractServiceImpl implements CrmContractService {
.setAuditStatus(CrmAuditStatusEnum.PROCESS.getStatus())); .setAuditStatus(CrmAuditStatusEnum.PROCESS.getStatus()));
} }
@Override
public void updateContractAuditStatus(BpmResultListenerRespDTO event) {
contractMapper.updateById(new CrmContractDO().setId(Long.parseLong(event.getBusinessKey()))
.setAuditStatus(event.getResult()));
}
//======================= 查询相关 ======================= //======================= 查询相关 =======================
@Override @Override
@ -285,7 +307,7 @@ public class CrmContractServiceImpl implements CrmContractService {
} }
@Override @Override
public Long selectCountByBusinessId(Long businessId) { public Long getContractCountByBusinessId(Long businessId) {
return contractMapper.selectCountByBusinessId(businessId); return contractMapper.selectCountByBusinessId(businessId);
} }
// TODO @合同待定需要新增一个 ContractConfigDO 合同配置重点是到期提醒 // TODO @合同待定需要新增一个 ContractConfigDO 合同配置重点是到期提醒

View File

@ -1,5 +1,59 @@
package cn.iocoder.yudao.module.crm.service.contract.listener; package cn.iocoder.yudao.module.crm.service.contract.listener;
public class CrmContractResultListener { import cn.hutool.core.util.ObjUtil;
// TODO puhui999: @芋艿 艿艿写一下这个没研究明白哈哈 import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
import cn.iocoder.yudao.module.bpm.api.listener.BpmResultListenerApi;
import cn.iocoder.yudao.module.bpm.api.listener.dto.BpmResultListenerRespDTO;
import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceResultEnum;
import cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum;
import cn.iocoder.yudao.module.crm.service.contract.CrmContractService;
import cn.iocoder.yudao.module.crm.service.contract.CrmContractServiceImpl;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Component;
/**
* 合同审批的结果的监听器实现类
*
* @author HUIHUI
*/
@Component
public class CrmContractResultListener implements BpmResultListenerApi {
@Resource
private CrmContractService contractService;
@Override
public String getProcessDefinitionKey() {
return CrmContractServiceImpl.CONTRACT_APPROVE;
}
@Override
public void onEvent(BpmResultListenerRespDTO event) {
boolean currentTaskFinish = isEndResult(event.getResult());
if (!currentTaskFinish) {
return;
}
if (ObjUtil.equal(event.getResult(), BpmProcessInstanceResultEnum.APPROVE.getResult())) {
event.setResult(CrmAuditStatusEnum.APPROVE.getStatus());
}
if (ObjUtil.equal(event.getResult(), BpmProcessInstanceResultEnum.REJECT.getResult())) {
event.setResult(CrmAuditStatusEnum.REJECT.getStatus());
}
if (ObjUtil.equal(event.getResult(), BpmProcessInstanceResultEnum.CANCEL.getResult())) {
event.setResult(CrmAuditStatusEnum.CANCEL.getStatus());
}
contractService.updateContractAuditStatus(event);
}
/**
* 判断该结果是否处于 End 最终结果
*
* @param result 结果
* @return 是否
*/
public static boolean isEndResult(Integer result) {
return ObjectUtils.equalsAny(result, BpmProcessInstanceResultEnum.APPROVE.getResult(),
BpmProcessInstanceResultEnum.REJECT.getResult(), BpmProcessInstanceResultEnum.CANCEL.getResult());
}
} }

View File

@ -93,8 +93,7 @@ public class CrmCustomerServiceImpl implements CrmCustomerService {
validateCustomerExceedOwnerLimit(createReqVO.getOwnerUserId(), 1); validateCustomerExceedOwnerLimit(createReqVO.getOwnerUserId(), 1);
// 2. 插入客户 // 2. 插入客户
CrmCustomerDO customer = BeanUtils.toBean(createReqVO, CrmCustomerDO.class).setOwnerUserId(userId) CrmCustomerDO customer = initCustomer(createReqVO, userId);
.setLockStatus(false).setDealStatus(false).setContactLastTime(LocalDateTime.now());
customerMapper.insert(customer); customerMapper.insert(customer);
// 3. 创建数据权限 // 3. 创建数据权限
@ -232,7 +231,6 @@ public class CrmCustomerServiceImpl implements CrmCustomerService {
return customer.getId(); return customer.getId();
} }
// TODO @puhui999操作日志
@Override @Override
public CrmCustomerImportRespVO importCustomerList(List<CrmCustomerImportExcelVO> importCustomers, public CrmCustomerImportRespVO importCustomerList(List<CrmCustomerImportExcelVO> importCustomers,
Boolean isUpdateSupport, Long userId) { Boolean isUpdateSupport, Long userId) {
@ -253,14 +251,14 @@ public class CrmCustomerServiceImpl implements CrmCustomerService {
// 判断如果不存在在进行插入 // 判断如果不存在在进行插入
CrmCustomerDO existCustomer = customerMapper.selectByCustomerName(importCustomer.getName()); CrmCustomerDO existCustomer = customerMapper.selectByCustomerName(importCustomer.getName());
if (existCustomer == null) { if (existCustomer == null) {
// TODO @puhui999可以搞个 initCustomer 方法这样可以把 create 和导入复用下这个方法 CrmCustomerDO customer = initCustomer(importCustomer, userId);
CrmCustomerDO customer = BeanUtils.toBean(importCustomer, CrmCustomerDO.class).setOwnerUserId(userId)
.setLockStatus(false).setDealStatus(false).setContactLastTime(LocalDateTime.now());
customerMapper.insert(customer); customerMapper.insert(customer);
respVO.getCreateCustomerNames().add(importCustomer.getName()); respVO.getCreateCustomerNames().add(importCustomer.getName());
// 创建数据权限 // 创建数据权限
permissionService.createPermission(new CrmPermissionCreateReqBO().setBizType(CrmBizTypeEnum.CRM_CUSTOMER.getType()) permissionService.createPermission(new CrmPermissionCreateReqBO().setBizType(CrmBizTypeEnum.CRM_CUSTOMER.getType())
.setBizId(customer.getId()).setUserId(userId).setLevel(CrmPermissionLevelEnum.OWNER.getLevel())); // 设置当前操作的人为负责人 .setBizId(customer.getId()).setUserId(userId).setLevel(CrmPermissionLevelEnum.OWNER.getLevel())); // 设置当前操作的人为负责人
// 记录操作日志
getSelf().importCustomerLog(customer, false);
return; return;
} }
// 如果存在判断是否允许更新 // 如果存在判断是否允许更新
@ -273,10 +271,25 @@ public class CrmCustomerServiceImpl implements CrmCustomerService {
updateCustomer.setId(existCustomer.getId()); updateCustomer.setId(existCustomer.getId());
customerMapper.updateById(updateCustomer); customerMapper.updateById(updateCustomer);
respVO.getUpdateCustomerNames().add(importCustomer.getName()); respVO.getUpdateCustomerNames().add(importCustomer.getName());
// 记录操作日志
getSelf().importCustomerLog(updateCustomer, true);
}); });
return respVO; return respVO;
} }
private static CrmCustomerDO initCustomer(Object customer, Long userId) {
return BeanUtils.toBean(customer, CrmCustomerDO.class).setOwnerUserId(userId)
.setLockStatus(false).setDealStatus(false).setContactLastTime(LocalDateTime.now());
}
@LogRecord(type = CRM_CUSTOMER_TYPE, subType = CRM_CUSTOMER_IMPORT_SUB_TYPE, bizNo = "{{#customer.id}}",
success = CRM_CUSTOMER_IMPORT_SUCCESS)
public void importCustomerLog(CrmCustomerDO customer, boolean isUpdate) {
// 记录操作日志上下文
LogRecordContext.putVariable("customer", customer);
LogRecordContext.putVariable("isUpdate", isUpdate);
}
private void validateCustomerForCreate(CrmCustomerImportExcelVO importCustomer) { private void validateCustomerForCreate(CrmCustomerImportExcelVO importCustomer) {
// 校验客户名称不能为空 // 校验客户名称不能为空
if (StrUtil.isEmptyIfStr(importCustomer.getName())) { if (StrUtil.isEmptyIfStr(importCustomer.getName())) {
@ -367,15 +380,15 @@ public class CrmCustomerServiceImpl implements CrmCustomerService {
if (poolConfig == null || !poolConfig.getEnabled()) { if (poolConfig == null || !poolConfig.getEnabled()) {
return 0; return 0;
} }
// 1.1 获取没有锁定的不在公海的客户且没有成交的 // 1.1 获取没有锁定的不在公海的客户
List<CrmCustomerDO> notDealCustomerList = customerMapper.selectListByLockAndDealStatusAndNotPool(Boolean.FALSE, Boolean.FALSE); List<CrmCustomerDO> customerList = customerMapper.selectListByLockAndNotPool(Boolean.FALSE);
// 1.2 获取没有锁定的不在公海的客户且成交的
// TODO @puhui999下面也搞到 sql 里去哈 or 查询问题不大的
List<CrmCustomerDO> dealCustomerList = customerMapper.selectListByLockAndDealStatusAndNotPool(Boolean.FALSE, Boolean.TRUE);
List<CrmCustomerDO> poolCustomerList = new ArrayList<>(); List<CrmCustomerDO> poolCustomerList = new ArrayList<>();
poolCustomerList.addAll(filterList(notDealCustomerList, customer -> poolCustomerList.addAll(filterList(customerList, customer ->
(poolConfig.getDealExpireDays() - LocalDateTimeUtils.between(customer.getCreateTime())) <= 0)); !customer.getDealStatus() && (poolConfig.getDealExpireDays() - LocalDateTimeUtils.between(customer.getCreateTime())) <= 0));
poolCustomerList.addAll(filterList(dealCustomerList, customer -> { poolCustomerList.addAll(filterList(customerList, customer -> {
if (!customer.getDealStatus()) { // 这里只处理成交的
return false;
}
LocalDateTime lastTime = ObjUtil.defaultIfNull(customer.getContactLastTime(), customer.getCreateTime()); LocalDateTime lastTime = ObjUtil.defaultIfNull(customer.getContactLastTime(), customer.getCreateTime());
return (poolConfig.getContactExpireDays() - LocalDateTimeUtils.between(lastTime)) <= 0; return (poolConfig.getContactExpireDays() - LocalDateTimeUtils.between(lastTime)) <= 0;
})); }));

View File

@ -69,4 +69,13 @@ public interface CrmProductService {
* @return 产品 * @return 产品
*/ */
CrmProductDO getProductByCategoryId(Long categoryId); CrmProductDO getProductByCategoryId(Long categoryId);
/**
* 获得产品列表
*
* @param ids 产品编号
* @return 产品列表
*/
List<CrmProductDO> getProductListByIds(Collection<Long> ids);
} }

View File

@ -155,4 +155,12 @@ public class CrmProductServiceImpl implements CrmProductService {
return productMapper.selectOne(new LambdaQueryWrapper<CrmProductDO>().eq(CrmProductDO::getCategoryId, categoryId)); return productMapper.selectOne(new LambdaQueryWrapper<CrmProductDO>().eq(CrmProductDO::getCategoryId, categoryId));
} }
@Override
public List<CrmProductDO> getProductListByIds(Collection<Long> ids) {
if (CollUtil.isEmpty(ids)) {
return Collections.emptyList();
}
return productMapper.selectListByIds(ids);
}
} }