gongchunyi
6 小时以前 ff41f9a13e981b543c114336058150c940110393
src/main/java/com/ruoyi/basic/service/impl/CustomerServiceImpl.java
@@ -7,27 +7,28 @@
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.basic.dto.CustomerDto;
import com.ruoyi.basic.dto.CustomerImportDto;
import com.ruoyi.basic.mapper.CustomerMapper;
import com.ruoyi.basic.pojo.Customer;
import com.ruoyi.basic.service.ICustomerService;
import com.ruoyi.basic.pojo.CustomerRegions;
import com.ruoyi.basic.service.*;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.framework.security.LoginUser;
import com.ruoyi.framework.web.domain.AjaxResult;
import com.ruoyi.project.system.domain.SysUser;
import com.ruoyi.sales.mapper.SalesLedgerMapper;
import com.ruoyi.sales.pojo.SalesLedger;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.web.multipart.MultipartFile;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.*;
import java.util.stream.Collectors;
@@ -41,8 +42,17 @@
@AllArgsConstructor
@Slf4j
public class CustomerServiceImpl extends ServiceImpl<CustomerMapper, Customer> implements ICustomerService {
    private static final Set<String> ALLOWED_CUSTOMER_TYPES = new HashSet<>(Arrays.asList("零售客户", "进销商客户"));
    private final SalesLedgerMapper salesLedgerMapper;
    private CustomerMapper customerMapper;
    private CustomerFollowUpService customerFollowUpService;
    private CustomerFollowUpFileService customerFollowUpFileService;
    private CustomerReturnVisitService customerReturnVisitService;
    private final ICustomerRegionsService customerRegionsService;
    /**
     * 查询客户档案
@@ -56,46 +66,128 @@
    }
    /**
     * 查询客户档案列表
     * 查询客户详情(含跟进记录和附件)
     *
     * @param customer 客户档案
     * @return 客户档案
     * @param id 客户档案主键
     * @return 客户详情DTO
     */
    @Override
    public IPage<Customer> selectCustomerList(Page page, Customer customer) {
        // 1. 处理空值场景(参数校验)
        if (page == null) {
            page = Page.of(1, 10); // 默认第1页,每页10条数据
        }
    public CustomerDto selectCustomerDetailById(Long id) {
        Customer customer = customerMapper.selectById(id);
        if (customer == null) {
            customer = new Customer(); // 避免空对象导致的NPE
            return null;
        }
        // 2. 构建查询条件(增强空值安全)
        CustomerDto dto = new CustomerDto();
        BeanUtils.copyProperties(customer, dto);
        // 查询跟进记录
//        List<CustomerFollowUp> followUpList = customerFollowUpService.list(
//                new LambdaQueryWrapper<CustomerFollowUp>()
//                        .eq(CustomerFollowUp::getCustomerId, id)
//                        .orderByDesc(CustomerFollowUp::getFollowUpTime)
//        );
//
//        if (!CollectionUtils.isEmpty(followUpList)) {
//            List<CustomerFollowUpDto> followUpDtoList = followUpList.stream().map(followUp -> {
//                CustomerFollowUpDto followUpDto = new CustomerFollowUpDto();
//                BeanUtils.copyProperties(followUp, followUpDto);
//
//                // 查询附件
//                List<CustomerFollowUpFile> fileList = customerFollowUpFileService.list(
//                        new LambdaQueryWrapper<CustomerFollowUpFile>()
//                                .eq(CustomerFollowUpFile::getFollowUpId, followUp.getId())
//                );
//                followUpDto.setFileList(fileList);
//
//                return followUpDto;
//            }).collect(Collectors.toList());
//
//            dto.setFollowUpList(followUpDtoList);
//        }
        //  地区名称
        CustomerRegions customerRegions = customerRegionsService.getById(customer.getRegionsId());
        if (customerRegions != null) {
            dto.setRegionsName(customerRegions.getRegionsName());
        }
        return dto;
    }
    /**
     * 查询客户档案列表
     *
     * @param page     分页对象
     * @param customer 客户查询条件
     * @return 客户档案分页列表
     */
    @Override
    public IPage<CustomerDto> selectCustomerList(Page<Customer> page, Customer customer) {
        if (page == null) page = Page.of(1, 10);
        if (customer == null) customer = new Customer();
        LambdaQueryWrapper<Customer> queryWrapper = new LambdaQueryWrapper<>();
        String customerName = customer.getCustomerName();
        String customerType = customer.getCustomerType();
        Long regionsId = customer.getRegionsId();
        if (StringUtils.isNotBlank(customerName)) {
            queryWrapper.like(Customer::getCustomerName, customerName);
        }
        if (StringUtils.isNotBlank(customerType)) {
            queryWrapper.eq(Customer::getCustomerType, customerType);
        }
        // 3. 执行分页查询(保留分页元数据)
        if (regionsId != null) {
            // 调用 regionsService 获取当前地区及其所有后代的 ID 集合
            List<Long> allRegionsIds = customerRegionsService.regionsChildrenIds(regionsId);
            if (!CollectionUtils.isEmpty(allRegionsIds)) {
                queryWrapper.in(Customer::getRegionsId, allRegionsIds);
            } else {
                queryWrapper.eq(Customer::getRegionsId, regionsId);
            }
        }
        IPage<Customer> customerPage = customerMapper.selectPage(page, queryWrapper);
        // 4. 数据处理(增强空值安全 & 代码可读性)
        List<Customer> processedList = customerPage.getRecords().stream()
                .filter(Objects::nonNull) // 过滤空对象(避免后续操作NPE)
                .peek(c -> {
                    // 安全获取字段,避免null值拼接
        List<CustomerDto> dtoList = customerPage.getRecords().stream()
                .filter(Objects::nonNull)
                .map(c -> {
                    CustomerDto dto = new CustomerDto();
                    BeanUtils.copyProperties(c, dto);
                    // 地址电话拼接
                    String address = StringUtils.defaultString(c.getCompanyAddress(), "");
                    String phone = StringUtils.defaultString(c.getCompanyPhone(), "");
                    c.setAddressPhone(address + "(" + phone + ")"); // 优化字符串拼接
                    dto.setAddressPhone(address + "(" + phone + ")");
                    // 填充地区名称
                    if (c.getRegionsId() != null) {
                        CustomerRegions regions = customerRegionsService.getById(c.getRegionsId());
                        if (regions != null) {
                            dto.setRegionsName(regions.getRegionsName());
                        }
                    }
                    // 查询最新的跟进记录
//                    CustomerFollowUp followUp = customerFollowUpService.getOne(
//                            new LambdaQueryWrapper<CustomerFollowUp>()
//                                    .eq(CustomerFollowUp::getCustomerId, c.getId())
//                                    .orderByDesc(CustomerFollowUp::getFollowUpTime)
//                                    .last("LIMIT 1")
//                    );
//
//                    if (followUp != null) {
//                        dto.setFollowUpLevel(followUp.getFollowUpLevel());
//                        dto.setFollowUpTime(Date.from(followUp.getFollowUpTime().atZone(ZoneId.systemDefault()).toInstant()));
//                    }
                    return dto;
                })
                .collect(Collectors.toList());
        // 5. 更新分页结果中的数据(保持分页信息完整)
        customerPage.setRecords(processedList);
        IPage<CustomerDto> resultPage = new Page<>(customerPage.getCurrent(), customerPage.getSize(), customerPage.getTotal());
        resultPage.setRecords(dtoList);
        return customerPage; // 返回包含分页信息的IPage对象
        return resultPage;
    }
    /**
@@ -109,6 +201,7 @@
        LoginUser loginUser = SecurityUtils.getLoginUser();
        Long tenantId = loginUser.getTenantId();
        customer.setTenantId(tenantId);
        validateCustomerNameUnique(customer, null);
        return customerMapper.insert(customer);
    }
@@ -123,6 +216,7 @@
        LoginUser loginUser = SecurityUtils.getLoginUser();
        Long tenantId = loginUser.getTenantId();
        customer.setTenantId(tenantId);
        validateCustomerNameUnique(customer, customer.getId());
        return customerMapper.updateById(customer);
    }
@@ -133,12 +227,19 @@
     * @return 结果
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public int deleteCustomerByIds(Long[] ids) {
        List<Long> idList = Arrays.asList(ids);
        List<SalesLedger> salesLedgers = salesLedgerMapper.selectList(new QueryWrapper<SalesLedger>().lambda().in(SalesLedger::getCustomerId, idList));
        if (!salesLedgers.isEmpty()) {
            throw new RuntimeException("客户档案下有销售合同,请先删除销售合同");
        }
        //  删除客户的同时也需要删除对应的客户跟随、附件和回访提醒
        for (Long id : ids) {
            customerFollowUpService.deleteByCustomerId(id);
            customerReturnVisitService.deleteByCustomerId(id);
        }
        return customerMapper.deleteBatchIds(idList);
    }
@@ -155,20 +256,130 @@
    }
    @Override
    public AjaxResult importData(MultipartFile file) {
    @Transactional(rollbackFor = Exception.class)
    public void importData(MultipartFile file) {
        List<CustomerImportDto> userList;
        try {
            ExcelUtil<Customer> util = new ExcelUtil<Customer>(Customer.class);
            List<Customer> userList = util.importExcel(file.getInputStream());
            if(CollectionUtils.isEmpty(userList)){
                return AjaxResult.warn("模板错误或导入数据为空");
            ExcelUtil<CustomerImportDto> util = new ExcelUtil<>(CustomerImportDto.class);
            userList = util.importExcel(file.getInputStream());
            if (CollectionUtils.isEmpty(userList)) {
                throw new ServiceException("模板错误或导入数据为空");
            }
            this.saveOrUpdateBatch(userList);
            return AjaxResult.success(true);
        }catch (Exception e){
            e.printStackTrace();
            return AjaxResult.error("导入失败");
        } catch (Exception e) {
            log.error("客户导入失败: {}", e.getMessage());
            throw new ServiceException("导入失败");
        }
        List<Customer> customers = new ArrayList<>();
        Customer customer;
        int rowIndex = 1;
        Set<String> importedCustomerRegionNames = new HashSet<>();
        for (CustomerImportDto user : userList) {
            customer = new Customer();
            if (user == null) {
                throw new ServiceException("导入失败,第【" + rowIndex + "】行数据不能为空");
            }
            if (StringUtils.isEmpty(user.getCustomerName())) {
                throw new ServiceException("导入失败,第" + rowIndex + "行数据【客户名称】不能为空");
            }
            String customerName = user.getCustomerName().trim();
            customer.setCustomerName(customerName);
            String customerType = user.getCustomerType() == null ? null : user.getCustomerType().trim();
            if (!StringUtils.isEmpty(customerType) && !ALLOWED_CUSTOMER_TYPES.contains(customerType)) {
                throw new ServiceException("导入失败,第" + rowIndex + "行数据【客户分类】仅支持:零售客户、进销商客户");
            }
            customer.setCustomerType(customerType);
            customer.setTaxpayerIdentificationNumber(user.getTaxpayerIdentificationNumber());
            customer.setCompanyAddress(user.getCompanyAddress());
            customer.setCompanyPhone(user.getCompanyPhone());
            customer.setContactPerson(user.getContactPerson());
            customer.setContactPhone(user.getContactPhone());
            customer.setMaintainer(user.getMaintainer());
            customer.setMaintenanceTime(user.getMaintenanceTime());
            customer.setBasicBankAccount(user.getBasicBankAccount());
            customer.setBankAccount(user.getBankAccount());
            customer.setBankCode(user.getBankCode());
            if (StringUtils.isEmpty(user.getCustomRegions())) {
                throw new ServiceException("导入失败,第" + rowIndex + "行数据【客户地区】不能为空");
            }
            Long regionsId = resolveRegionId(user.getCustomRegions(), rowIndex);
            customer.setRegionsId(regionsId);
            String importUniqueKey = customerName + "_" + (customer.getRegionsId() == null ? "NULL" : customer.getRegionsId());
            if (importedCustomerRegionNames.contains(importUniqueKey)) {
                throw new ServiceException("导入失败,第" + rowIndex + "行数据【同地区客户名称】重复");
            }
            importedCustomerRegionNames.add(importUniqueKey);
            validateCustomerNameUnique(customer, null);
            customers.add(customer);
            rowIndex++;
        }
        if (CollectionUtils.isEmpty(customers)) {
            throw new ServiceException("导入失败,未识别到可导入的数据");
        }
        this.saveBatch(customers);
    }
    /**
     * 按 “xx-xx-xx” 层级解析地区,不存在则按父级逐级创建。
     */
    private Long resolveRegionId(String regionsPath, int rowIndex) {
        if (StringUtils.isEmpty(regionsPath)) {
            throw new ServiceException("导入失败,第" + rowIndex + "行数据【客户地区】不能为空");
        }
        String normalizedPath = regionsPath.trim().replace("—", "-").replace("-", "-");
        if (!normalizedPath.matches("^[^-]+(?:-[^-]+)*$")) {
            throw new ServiceException("导入失败,第" + rowIndex + "行数据【客户地区】格式错误,仅支持:xxx 或 xxx-xxx-xxx");
        }
        String[] regionParts = normalizedPath.split("-");
        Long parentId = 0L;
        Long currentId = null;
        for (String rawPart : regionParts) {
            String regionName = rawPart == null ? null : rawPart.trim();
            if (StringUtils.isEmpty(regionName)) {
                throw new ServiceException("导入失败,第" + rowIndex + "行数据【客户地区】格式错误");
            }
            CustomerRegions region = customerRegionsService.getOne(new LambdaQueryWrapper<CustomerRegions>()
                    .eq(CustomerRegions::getRegionsName, regionName)
                    .last("limit 1"));
            if (region == null) {
                region = new CustomerRegions();
                region.setParentId(parentId);
                region.setRegionsName(regionName);
                if (!customerRegionsService.save(region)) {
                    throw new ServiceException("导入失败,第" + rowIndex + "行数据【客户地区】创建失败");
                }
            }
            currentId = region.getId();
            parentId = currentId;
        }
        return currentId;
    }
    private void validateCustomerNameUnique(Customer customer, Long excludeId) {
        if (customer == null || StringUtils.isEmpty(customer.getCustomerName())) {
            throw new ServiceException("客户名称不能为空");
        }
        if (customer.getRegionsId() == null) {
            throw new ServiceException("客户地区不能为空");
        }
        String customerName = customer.getCustomerName().trim();
        LambdaQueryWrapper<Customer> queryWrapper = new LambdaQueryWrapper<Customer>().eq(Customer::getCustomerName, customerName);
        if (customer.getRegionsId() == null) {
            queryWrapper.isNull(Customer::getRegionsId);
        } else {
            queryWrapper.eq(Customer::getRegionsId, customer.getRegionsId());
        }
        if (customer.getTenantId() != null) {
            queryWrapper.eq(Customer::getTenantId, customer.getTenantId());
        }
        if (excludeId != null) {
            queryWrapper.ne(Customer::getId, excludeId);
        }
        Long exists = customerMapper.selectCount(queryWrapper);
        if (exists != null && exists > 0) {
            throw new ServiceException("同地区下客户名称已存在,请勿重复");
        }
        customer.setCustomerName(customerName);
    }
    @Override