package com.ruoyi.basic.service.impl; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; 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.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.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.*; import java.util.stream.Collectors; /** * 客户档案Service业务层处理 * * @author ruoyi * @date 2025-05-07 */ @Service @AllArgsConstructor @Slf4j public class CustomerServiceImpl extends ServiceImpl implements ICustomerService { private static final Set 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; /** * 查询客户档案 * * @param id 客户档案主键 * @return 客户档案 */ @Override public Customer selectCustomerById(Long id) { return customerMapper.selectById(id); } /** * 查询客户详情(含跟进记录和附件) * * @param id 客户档案主键 * @return 客户详情DTO */ @Override public CustomerDto selectCustomerDetailById(Long id) { Customer customer = customerMapper.selectById(id); if (customer == null) { return null; } CustomerDto dto = new CustomerDto(); BeanUtils.copyProperties(customer, dto); // 查询跟进记录 // List followUpList = customerFollowUpService.list( // new LambdaQueryWrapper() // .eq(CustomerFollowUp::getCustomerId, id) // .orderByDesc(CustomerFollowUp::getFollowUpTime) // ); // // if (!CollectionUtils.isEmpty(followUpList)) { // List followUpDtoList = followUpList.stream().map(followUp -> { // CustomerFollowUpDto followUpDto = new CustomerFollowUpDto(); // BeanUtils.copyProperties(followUp, followUpDto); // // // 查询附件 // List fileList = customerFollowUpFileService.list( // new LambdaQueryWrapper() // .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 selectCustomerList(Page page, Customer customer) { if (page == null) page = Page.of(1, 10); if (customer == null) customer = new Customer(); LambdaQueryWrapper 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); } if (regionsId != null) { // 调用 regionsService 获取当前地区及其所有后代的 ID 集合 List allRegionsIds = customerRegionsService.regionsChildrenIds(regionsId); if (!CollectionUtils.isEmpty(allRegionsIds)) { queryWrapper.in(Customer::getRegionsId, allRegionsIds); } else { queryWrapper.eq(Customer::getRegionsId, regionsId); } } IPage customerPage = customerMapper.selectPage(page, queryWrapper); List 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(), ""); 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() // .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()); IPage resultPage = new Page<>(customerPage.getCurrent(), customerPage.getSize(), customerPage.getTotal()); resultPage.setRecords(dtoList); return resultPage; } /** * 新增客户档案 * * @param customer 客户档案 * @return 结果 */ @Override public int insertCustomer(Customer customer) { LoginUser loginUser = SecurityUtils.getLoginUser(); Long tenantId = loginUser.getTenantId(); customer.setTenantId(tenantId); validateCustomerNameUnique(customer, null); return customerMapper.insert(customer); } /** * 修改客户档案 * * @param customer 客户档案 * @return 结果 */ @Override public int updateCustomer(Customer customer) { LoginUser loginUser = SecurityUtils.getLoginUser(); Long tenantId = loginUser.getTenantId(); customer.setTenantId(tenantId); validateCustomerNameUnique(customer, customer.getId()); return customerMapper.updateById(customer); } /** * 批量删除客户档案 * * @param ids 需要删除的客户档案主键 * @return 结果 */ @Override @Transactional(rollbackFor = Exception.class) public int deleteCustomerByIds(Long[] ids) { List idList = Arrays.asList(ids); List salesLedgers = salesLedgerMapper.selectList(new QueryWrapper().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); } @Override public List selectCustomerListByIds(Long[] ids) { LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.in(Customer::getId, Arrays.asList(ids)); return customerMapper.selectList(queryWrapper); } @Override public List selectCustomerLists(Customer customer) { return customerMapper.selectList(null); } @Override @Transactional(rollbackFor = Exception.class) public void importData(MultipartFile file) { List userList; try { ExcelUtil util = new ExcelUtil<>(CustomerImportDto.class); userList = util.importExcel(file.getInputStream()); if (CollectionUtils.isEmpty(userList)) { throw new ServiceException("模板错误或导入数据为空"); } } catch (Exception e) { log.error("客户导入失败: {}", e.getMessage()); throw new ServiceException("导入失败"); } List customers = new ArrayList<>(); Customer customer; int rowIndex = 1; Set 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() .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 queryWrapper = new LambdaQueryWrapper().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 public List> customerList(Customer customer) { LambdaQueryWrapper queryWrapper = Wrappers.lambdaQuery(); queryWrapper.select(Customer::getId, Customer::getCustomerName, Customer::getTaxpayerIdentificationNumber); // 获取原始查询结果 List> result = customerMapper.selectMaps(queryWrapper); // 将下划线命名转换为驼峰命名 return result.stream().map(map -> map.entrySet().stream() .collect(Collectors.toMap( entry -> underlineToCamel(entry.getKey()), Map.Entry::getValue)) ).collect(Collectors.toList()); } /** * 下划线命名转驼峰命名 */ private String underlineToCamel(String param) { if (param == null || "".equals(param.trim())) { return ""; } int len = param.length(); StringBuilder sb = new StringBuilder(len); for (int i = 0; i < len; i++) { char c = param.charAt(i); if (c == '_') { if (++i < len) { sb.append(Character.toUpperCase(param.charAt(i))); } } else { sb.append(Character.toLowerCase(c)); } } return sb.toString(); } }