liu
8 天以前 6d1bf32bc0b51dc12048fe6bb70badfbd31d67ac
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessServiceImpl.java
@@ -3,9 +3,15 @@
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil;
import cn.iocoder.yudao.framework.common.exception.ServiceException;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
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.CrmBusinessImportExcelVO;
import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessImportReqVO;
import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessImportRespVO;
import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessPageReqVO;
import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessSaveReqVO;
import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessTransferReqVO;
@@ -17,8 +23,10 @@
import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessStatusDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactBusinessDO;
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.dal.mysql.business.CrmBusinessMapper;
import cn.iocoder.yudao.module.crm.dal.mysql.business.CrmBusinessProductMapper;
import cn.iocoder.yudao.module.crm.dal.mysql.customer.CrmCustomerMapper;
import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum;
import cn.iocoder.yudao.module.crm.framework.permission.core.annotations.CrmPermission;
@@ -43,14 +51,17 @@
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
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.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.*;
import static cn.iocoder.yudao.module.crm.enums.LogRecordConstants.*;
@@ -67,6 +78,8 @@
    private CrmBusinessMapper businessMapper;
    @Resource
    private CrmBusinessProductMapper businessProductMapper;
    @Resource
    private CrmCustomerMapper customerMapper;
    @Resource
    private CrmBusinessStatusService businessStatusService;
@@ -113,6 +126,9 @@
        permissionService.createPermission(new CrmPermissionCreateReqBO().setUserId(business.getOwnerUserId())
                .setBizType(CrmBizTypeEnum.CRM_BUSINESS.getType()).setBizId(business.getId())
                .setLevel(CrmPermissionLevelEnum.OWNER.getLevel()));
        // 3.1 客户负责人自动拥有该商机的读写权限
        permissionService.createPermissionForCustomerOwner(CrmBizTypeEnum.CRM_BUSINESS.getType(),
                business.getId(), business.getCustomerId(), business.getOwnerUserId());
        // 4. 在联系人的详情页,如果直接【新建商机】,则需要关联下
        if (createReqVO.getContactId() != null) {
@@ -134,8 +150,9 @@
        updateReqVO.setOwnerUserId(null).setStatusTypeId(null); // 不允许更新的字段
        // 1.1 校验存在
        CrmBusinessDO oldBusiness = validateBusinessExists(updateReqVO.getId());
        // 1.2 校验产品项的有效性
        List<CrmBusinessProductDO> businessProducts = validateBusinessProducts(updateReqVO.getProducts());
        // 1.2 校验产品项的有效性(传入旧产品列表用于合并 null 字段)
        List<CrmBusinessProductDO> oldProducts = businessProductMapper.selectListByBusinessId(updateReqVO.getId());
        List<CrmBusinessProductDO> businessProducts = validateBusinessProducts(updateReqVO.getProducts(), oldProducts);
        // 1.3 校验关联字段
        validateRelationDataExists(updateReqVO);
@@ -147,7 +164,7 @@
        updateBusinessProduct(updateObj.getId(), businessProducts);
        // 3. 记录操作日志上下文
        updateReqVO.setOwnerUserId(oldBusiness.getOwnerUserId()); // 避免操作日志出现“删除负责人”的情况
        updateReqVO.setOwnerUserId(oldBusiness.getOwnerUserId()); // 避免操作日志出现"删除负责人"的情况
        LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(oldBusiness, CrmBusinessSaveReqVO.class));
        LogRecordContext.putVariable("businessName", oldBusiness.getName());
    }
@@ -216,6 +233,18 @@
    }
    private List<CrmBusinessProductDO> validateBusinessProducts(List<CrmBusinessSaveReqVO.BusinessProduct> list) {
        return validateBusinessProducts(list, Collections.emptyList());
    }
    /**
     * 校验产品项的有效性(更新场景合并旧数据)
     *
     * @param list       前端传入的产品列表
     * @param oldProducts 已有产品列表(用于合并 null 字段)
     * @return 处理后的产品 DO 列表
     */
    private List<CrmBusinessProductDO> validateBusinessProducts(List<CrmBusinessSaveReqVO.BusinessProduct> list,
                                                                  List<CrmBusinessProductDO> oldProducts) {
        // 1. 校验物料存在
        Set<Long> itemIds = convertSet(list, CrmBusinessSaveReqVO.BusinessProduct::getItemId);
        Map<Long, MdmItemRespDTO> itemMap = mdmItemApi.getItemMap(itemIds);
@@ -224,15 +253,50 @@
                throw exception(PRODUCT_NOT_EXISTS);
            }
        }
        // 2. 转化为 CrmBusinessProductDO 列表
        return convertList(list, o -> BeanUtils.toBean(o, CrmBusinessProductDO.class,
                item -> item.setTotalPrice(MoneyUtils.priceMultiply(item.getBusinessPrice(), item.getCount()))));
        // 2. 构建旧产品 Map(按 id 索引)
        Map<Long, CrmBusinessProductDO> oldProductMap = convertMap(oldProducts, CrmBusinessProductDO::getId);
        // 3. 转化为 CrmBusinessProductDO 列表(合并旧数据)
        return convertList(list, o -> {
            CrmBusinessProductDO product = BeanUtils.toBean(o, CrmBusinessProductDO.class);
            // 更新场景:如果产品已有 id 且有旧数据,则合并 null 字段
            if (o.getId() != null && oldProductMap.containsKey(o.getId())) {
                CrmBusinessProductDO old = oldProductMap.get(o.getId());
                if (product.getBusinessPrice() == null) {
                    product.setBusinessPrice(old.getBusinessPrice());
                }
                if (product.getItemPrice() == null) {
                    product.setItemPrice(old.getItemPrice());
                }
                if (product.getCount() == null) {
                    product.setCount(old.getCount());
                }
                if (product.getItemUnitId() == null) {
                    product.setItemUnitId(old.getItemUnitId());
                }
            } else {
                // 新产品:清除前端生成的临时 ID,让数据库 AUTO_INCREMENT 接管
                product.setId(null);
            }
            // 优先使用前端传入的 totalPrice,否则通过 businessPrice * count 计算
            if (o.getTotalPrice() != null) {
                product.setTotalPrice(o.getTotalPrice());
            } else {
                product.setTotalPrice(MoneyUtils.priceMultiply(product.getBusinessPrice(), product.getCount()));
            }
            return product;
        });
    }
    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));
        // totalProductPrice:优先使用前端传入值,否则从产品汇总
        if (business.getTotalProductPrice() == null) {
            business.setTotalProductPrice(getSumValue(businessProducts, CrmBusinessProductDO::getTotalPrice, BigDecimal::add, BigDecimal.ZERO));
        }
        // totalPrice:优先使用前端传入值,否则按折扣计算
        if (business.getTotalPrice() == null) {
            BigDecimal discountPrice = MoneyUtils.priceMultiplyPercent(business.getTotalProductPrice(), business.getDiscountPercent());
            business.setTotalPrice(business.getTotalProductPrice().subtract(discountPrice));
        }
    }
    @Override
@@ -417,4 +481,99 @@
        return businessMapper.selectPage(pageVO);
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public CrmBusinessImportRespVO importBusinessList(List<CrmBusinessImportExcelVO> importBusinesses,
                                                      CrmBusinessImportReqVO importReqVO) {
        // 1. 校验非空
        importBusinesses = filterList(importBusinesses, item -> StrUtil.isNotBlank(item.getName()));
        if (CollUtil.isEmpty(importBusinesses)) {
            throw exception(BUSINESS_IMPORT_LIST_IS_EMPTY);
        }
        // 2. 校验负责人(为空则默认当前操作人)
        Long ownerUserId = importReqVO.getOwnerUserId() != null ? importReqVO.getOwnerUserId() : getLoginUserId();
        adminUserApi.validateUser(ownerUserId);
        // 3. 默认商机状态组 + 状态
        Long defaultStatusTypeId = businessStatusService.getBusinessStatusTypeList().get(0).getId();
        Long defaultStatusId = businessStatusService.getBusinessStatusListByTypeId(defaultStatusTypeId).get(0).getId();
        // 4. 逐条处理
        CrmBusinessImportRespVO respVO = CrmBusinessImportRespVO.builder()
                .createBusinessNames(new ArrayList<>())
                .updateBusinessNames(new ArrayList<>())
                .failureBusinessNames(new LinkedHashMap<>())
                .build();
        importBusinesses.forEach(importBusiness -> {
            try {
                // 4.1 解析客户
                CrmCustomerDO customer = customerMapper.selectByCustomerName(importBusiness.getCustomerName());
                if (customer == null) {
                    throw exception(BUSINESS_IMPORT_CUSTOMER_NOT_EXISTS, importBusiness.getCustomerName());
                }
                // 4.2 判断是否存在
                CrmBusinessDO existBusiness = businessMapper.selectByNameAndCustomerId(
                        importBusiness.getName(), customer.getId());
                if (existBusiness == null) {
                    // 4.2.1 新增
                    CrmBusinessDO business = BeanUtils.toBean(importBusiness, CrmBusinessDO.class)
                            .setCustomerId(customer.getId()).setOwnerUserId(ownerUserId)
                            .setStatusTypeId(defaultStatusTypeId).setStatusId(defaultStatusId);
                    if (business.getDiscountPercent() == null) {
                        business.setDiscountPercent(BigDecimal.ZERO);
                    }
                    calculateTotalPrice(business, Collections.emptyList());
                    businessMapper.insert(business);
                    // 创建数据权限
                    permissionService.createPermission(new CrmPermissionCreateReqBO()
                            .setBizType(CrmBizTypeEnum.CRM_BUSINESS.getType()).setBizId(business.getId())
                            .setUserId(ownerUserId).setLevel(CrmPermissionLevelEnum.OWNER.getLevel()));
                    // 客户负责人自动拥有该商机的读写权限
                    permissionService.createPermissionForCustomerOwner(CrmBizTypeEnum.CRM_BUSINESS.getType(),
                            business.getId(), customer.getId(), ownerUserId);
                    respVO.getCreateBusinessNames().add(importBusiness.getName());
                    // 记录操作日志
                    getSelf().importBusinessLog(business, false);
                    return;
                }
                // 4.2.2 已存在,判断是否允许更新
                if (!importReqVO.getUpdateSupport()) {
                    respVO.getFailureBusinessNames().put(importBusiness.getName(),
                            StrUtil.format(BUSINESS_NAME_EXISTS.getMsg(), importBusiness.getName()));
                    return;
                }
                CrmBusinessDO updateBusiness = BeanUtils.toBean(importBusiness, CrmBusinessDO.class)
                        .setId(existBusiness.getId()).setCustomerId(customer.getId());
                businessMapper.updateById(updateBusiness);
                respVO.getUpdateBusinessNames().add(importBusiness.getName());
                // 记录操作日志
                getSelf().importBusinessLog(updateBusiness, true);
            } catch (ServiceException ex) {
                respVO.getFailureBusinessNames().put(importBusiness.getName(), ex.getMessage());
            }
        });
        return respVO;
    }
    /**
     * 记录导入商机时的操作日志
     *
     * @param business 商机信息
     * @param isUpdate 是否更新;true - 更新,false - 新增
     */
    @LogRecord(type = CRM_BUSINESS_TYPE, subType = CRM_BUSINESS_IMPORT_SUB_TYPE, bizNo = "{{#business.id}}",
            success = CRM_BUSINESS_IMPORT_SUCCESS)
    public void importBusinessLog(CrmBusinessDO business, boolean isUpdate) {
        LogRecordContext.putVariable("business", business);
        LogRecordContext.putVariable("isUpdate", isUpdate);
    }
    /**
     * 获得自身的代理对象,解决 AOP 生效问题
     *
     * @return 自己
     */
    private CrmBusinessServiceImpl getSelf() {
        return SpringUtil.getBean(getClass());
    }
}