package com.ruoyi.basic.service.impl; import com.alibaba.fastjson2.JSONArray; import com.alibaba.fastjson2.JSONObject; 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.CustomerFollowUpDto; import com.ruoyi.basic.mapper.CustomerMapper; import com.ruoyi.basic.pojo.Customer; import com.ruoyi.basic.pojo.CustomerFollowUp; import com.ruoyi.basic.pojo.CustomerFollowUpFile; import com.ruoyi.basic.service.CustomerFollowUpFileService; import com.ruoyi.basic.service.CustomerFollowUpService; import com.ruoyi.basic.service.CustomerReturnVisitService; import com.ruoyi.basic.service.ICustomerService; import com.ruoyi.common.utils.SecurityUtils; import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.poi.ExcelUtil; import com.ruoyi.framework.config.AliDingConfig; import com.ruoyi.framework.security.LoginUser; import com.ruoyi.framework.util.AliDingUtils; import com.ruoyi.framework.web.domain.AjaxResult; import com.ruoyi.production.enums.MaterialConfigTypeEnum; import com.ruoyi.production.pojo.ProductMaterial; import com.ruoyi.production.pojo.ProductMaterialSku; import com.ruoyi.productionPlan.enums.DataSourceTypeEnum; import com.ruoyi.productionPlan.pojo.ProductionPlan; 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.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.CollectionUtils; import org.springframework.web.multipart.MultipartFile; import java.time.Instant; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.ZoneId; import java.util.*; import java.util.concurrent.locks.ReentrantLock; import java.util.stream.Collectors; /** * 客户档案Service业务层处理 * * @author ruoyi * @date 2025-05-07 */ @Service @AllArgsConstructor @Slf4j public class CustomerServiceImpl extends ServiceImpl implements ICustomerService { private final SalesLedgerMapper salesLedgerMapper; private CustomerMapper customerMapper; private CustomerFollowUpService customerFollowUpService; private CustomerFollowUpFileService customerFollowUpFileService; private CustomerReturnVisitService customerReturnVisitService; private AliDingConfig aliDingConfig; /** * 同步锁,防止手动和定时任务同时执行 */ private final ReentrantLock syncLock = new ReentrantLock(); /** * 查询客户档案 * * @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); } return dto; } /** * 查询客户档案列表 * * @param customer 客户档案 * @return 客户档案 */ @Override public IPage selectCustomerList(Page page, Customer customer) { // 1. 处理空值场景(参数校验) if (page == null) { page = Page.of(1, 10); // 默认第1页,每页10条数据 } if (customer == null) { customer = new Customer(); // 避免空对象导致的NPE } // 2. 构建查询条件(增强空值安全) LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); String customerName = customer.getCustomerName(); String customerType = customer.getCustomerType(); if (StringUtils.isNotBlank(customerName)) { queryWrapper.like(Customer::getCustomerName, customerName); } if (StringUtils.isNotBlank(customerType)) { queryWrapper.like(Customer::getCustomerType, customerType); } // 3. 执行分页查询(保留分页元数据) IPage customerPage = customerMapper.selectPage(page, queryWrapper); // 4. 数据处理(增强空值安全 & 代码可读性) List processedList = customerPage.getRecords().stream() .filter(Objects::nonNull) // 过滤空对象(避免后续操作NPE) .peek(c -> { // 安全获取字段,避免null值拼接 String address = StringUtils.defaultString(c.getCompanyAddress(), ""); String phone = StringUtils.defaultString(c.getCompanyPhone(), ""); c.setAddressPhone(address + "(" + phone + ")"); // 查询最新的跟进记录 CustomerFollowUp followUp = customerFollowUpService.getOne( new LambdaQueryWrapper() .eq(CustomerFollowUp::getCustomerId, c.getId()) .orderByDesc(CustomerFollowUp::getFollowUpTime) .last("LIMIT 1") ); if (followUp != null) { c.setFollowUpLevel(followUp.getFollowUpLevel()); c.setFollowUpTime(followUp.getFollowUpTime()); } }) .collect(Collectors.toList()); // 5. 更新分页结果中的数据(保持分页信息完整) IPage resultPage = new Page<>(customerPage.getCurrent(), customerPage.getSize(), customerPage.getTotal()); resultPage.setRecords(processedList); return customerPage; // 返回包含分页信息的IPage对象 } /** * 新增客户档案 * * @param customer 客户档案 * @return 结果 */ @Override public int insertCustomer(Customer customer) { LoginUser loginUser = SecurityUtils.getLoginUser(); Long tenantId = loginUser.getTenantId(); customer.setTenantId(tenantId); return customerMapper.insert(customer); } /** * 修改客户档案 * * @param customer 客户档案 * @return 结果 */ @Override public int updateCustomer(Customer customer) { LoginUser loginUser = SecurityUtils.getLoginUser(); Long tenantId = loginUser.getTenantId(); customer.setTenantId(tenantId); 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 public AjaxResult importData(MultipartFile file) { try { ExcelUtil util = new ExcelUtil(Customer.class); List userList = util.importExcel(file.getInputStream()); if (CollectionUtils.isEmpty(userList)) { return AjaxResult.warn("模板错误或导入数据为空"); } this.saveOrUpdateBatch(userList); return AjaxResult.success(true); } catch (Exception e) { e.printStackTrace(); return AjaxResult.error("导入失败"); } } @Override public void syncCustomerJob() { syncCustomerData(2); } @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(); } /** * 同步数据 */ @Transactional(rollbackFor = Exception.class) public void syncCustomerData(Integer dataSyncType) { if (!syncLock.tryLock()) { log.warn("同步正在进行中,本次 {} 同步请求被跳过", dataSyncType == 1 ? "手动" : "定时任务"); return; } try { JSONArray searchConditions = new JSONArray(); JSONObject statusCondition = new JSONObject(); statusCondition.put("key", "processInstanceStatus"); JSONArray statusValueArray = new JSONArray(); statusValueArray.add("COMPLETED"); statusCondition.put("value", statusValueArray); statusCondition.put("type", "ARRAY"); statusCondition.put("operator", "in"); statusCondition.put("componentName", "SelectField"); searchConditions.add(statusCondition); JSONObject resultCondition = new JSONObject(); resultCondition.put("key", "processApprovedResult"); JSONArray resultValueArray = new JSONArray(); resultValueArray.add("agree"); resultCondition.put("value", resultValueArray); resultCondition.put("type", "ARRAY"); resultCondition.put("operator", "in"); resultCondition.put("componentName", "SelectField"); searchConditions.add(resultCondition); String searchFieldJson = searchConditions.toJSONString(); JSONArray dataArr = AliDingUtils.getFormDataList(aliDingConfig, aliDingConfig.getCustomerCodeFormUuid(), searchFieldJson, this, Customer::getFormModifiedTime); if (dataArr.isEmpty()) { log.info("没有更多新数据需要同步"); return; } // 解析并保存数据 List list = parseCustomer(dataArr); if (!list.isEmpty()) { // 处理更新或新增 int affected = processSaveOrUpdate(list); log.info("客户数据同步完成,共同步 {} 条数据", affected); } } catch (Exception e) { log.error("同步客户信息异常", e); } finally { // 释放锁 syncLock.unlock(); } } private List parseCustomer(JSONArray dataArr) { List list = new ArrayList<>(); for (int i = 0; i < dataArr.size(); i++) { JSONObject item = dataArr.getJSONObject(i); String formInstanceId = item.getString("formInstanceId"); JSONObject formData = item.getJSONObject("formData"); Customer customer = new Customer(); customer.setCustomerName(formData.getString("textField_l7fwg8uh")); customer.setTaxpayerIdentificationNumber(formData.getString("textField_l88df8ae")); customer.setCompanyAddress(formData.getString("textField_l7fwg8uj")); customer.setCompanyPhone(formData.getString("textField_lb38bng2")); customer.setContactPerson(formData.getString("textField_l7fwg8ut")); customer.setContactPhone(formData.getString("textField_l7fwg8uu")); customer.setMaintainer("管理员账号"); LocalDateTime dateTime = AliDingUtils.parseUtcTime(item.getString("modifiedTimeGMT")); ZoneId zoneId = ZoneId.of("Asia/Shanghai"); Date date = Date.from(dateTime.atZone(zoneId).toInstant()); customer.setMaintenanceTime(date); customer.setFormInstanceId(formInstanceId); customer.setFormModifiedTime(dateTime); list.add(customer); } return list; } private int processSaveOrUpdate(List list) { if (list == null || list.isEmpty()) { return 0; } int affected = 0; for (Customer customer : list) { LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); wrapper.eq(Customer::getFormInstanceId, customer.getFormInstanceId()); Customer exist = this.getOne(wrapper); if (exist == null) { this.save(customer); affected++; log.info("新增客户信息 {}", customer.getCustomerName()); } else { if (exist.getFormModifiedTime() == null || !exist.getFormModifiedTime().equals(customer.getFormModifiedTime())) { customer.setId(exist.getId()); this.updateById(customer); affected++; log.info("更新客户信息 {}", customer.getCustomerName()); } } } return affected; } }