| | |
| | | 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.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.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.List; |
| | | import java.util.*; |
| | | import java.util.stream.Collectors; |
| | | |
| | | |
| | | /** |
| | |
| | | */ |
| | | @Service |
| | | @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; |
| | | |
| | | /** |
| | | * 查询客户档案 |
| | |
| | | */ |
| | | @Override |
| | | public Customer selectCustomerById(Long id) { |
| | | return customerMapper.selectCustomerById(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<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 customer 客户档案 |
| | | * @return 客户档案 |
| | | * @param page 分页对象 |
| | | * @param customer 客户查询条件 |
| | | * @return 客户档案分页列表 |
| | | */ |
| | | @Override |
| | | public List<Customer> selectCustomerList(Customer customer) { |
| | | return customerMapper.selectCustomerList(customer); |
| | | 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); |
| | | } |
| | | |
| | | 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); |
| | | |
| | | 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(), ""); |
| | | 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()); |
| | | |
| | | IPage<CustomerDto> resultPage = new Page<>(customerPage.getCurrent(), customerPage.getSize(), customerPage.getTotal()); |
| | | resultPage.setRecords(dtoList); |
| | | |
| | | return resultPage; |
| | | } |
| | | |
| | | /** |
| | |
| | | */ |
| | | @Override |
| | | public int insertCustomer(Customer customer) { |
| | | return customerMapper.insertCustomer(customer); |
| | | LoginUser loginUser = SecurityUtils.getLoginUser(); |
| | | Long tenantId = loginUser.getTenantId(); |
| | | customer.setTenantId(tenantId); |
| | | validateCustomerNameUnique(customer, null); |
| | | return customerMapper.insert(customer); |
| | | } |
| | | |
| | | /** |
| | |
| | | */ |
| | | @Override |
| | | public int updateCustomer(Customer customer) { |
| | | return customerMapper.updateCustomer(customer); |
| | | LoginUser loginUser = SecurityUtils.getLoginUser(); |
| | | Long tenantId = loginUser.getTenantId(); |
| | | customer.setTenantId(tenantId); |
| | | validateCustomerNameUnique(customer, customer.getId()); |
| | | return customerMapper.updateById(customer); |
| | | } |
| | | |
| | | /** |
| | |
| | | * @return 结果 |
| | | */ |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public int deleteCustomerByIds(Long[] ids) { |
| | | return customerMapper.deleteCustomerByIds(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); |
| | | } |
| | | |
| | | @Override |
| | | public List<Customer> selectCustomerListByIds(Long[] ids) { |
| | | LambdaQueryWrapper<Customer> queryWrapper = new LambdaQueryWrapper<>(); |
| | | queryWrapper.in(Customer::getId, Arrays.asList(ids)); |
| | | return customerMapper.selectList(queryWrapper); |
| | | } |
| | | |
| | | @Override |
| | | public List<Customer> selectCustomerLists(Customer customer) { |
| | | return customerMapper.selectList(null); |
| | | } |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public void importData(MultipartFile file) { |
| | | List<CustomerImportDto> userList; |
| | | try { |
| | | ExcelUtil<CustomerImportDto> 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<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); |
| | | } |
| | | |
| | | /** |
| | | * 删除客户档案信息 |
| | | * |
| | | * @param id 客户档案主键 |
| | | * @return 结果 |
| | | * 按 “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 |
| | | public int deleteCustomerById(Long id) { |
| | | return customerMapper.deleteCustomerById(id); |
| | | public List<Map<String, Object>> customerList(Customer customer) { |
| | | LambdaQueryWrapper<Customer> queryWrapper = Wrappers.lambdaQuery(); |
| | | queryWrapper.select(Customer::getId, Customer::getCustomerName, Customer::getTaxpayerIdentificationNumber); |
| | | |
| | | // 获取原始查询结果 |
| | | List<Map<String, Object>> 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(); |
| | | } |
| | | } |