| | |
| | | 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.util.List; |
| | | 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 |
| | | @AllArgsConstructor |
| | | @Slf4j |
| | | public class CustomerServiceImpl extends ServiceImpl<CustomerMapper, Customer> 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 客户详情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); |
| | | } |
| | | |
| | | return dto; |
| | | } |
| | | |
| | | /** |
| | | * 查询客户档案列表 |
| | | * |
| | | * @param customer 客户档案 |
| | | * @return 客户档案 |
| | | */ |
| | | @Override |
| | | public List<Customer> selectCustomerList(Customer customer) { |
| | | return customerMapper.selectList(new LambdaQueryWrapper<>()); |
| | | public IPage<Customer> selectCustomerList(Page<Customer> page, Customer customer) { |
| | | // 1. 处理空值场景(参数校验) |
| | | if (page == null) { |
| | | page = Page.of(1, 10); // 默认第1页,每页10条数据 |
| | | } |
| | | if (customer == null) { |
| | | customer = new Customer(); // 避免空对象导致的NPE |
| | | } |
| | | |
| | | // 2. 构建查询条件(增强空值安全) |
| | | LambdaQueryWrapper<Customer> 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<Customer> customerPage = customerMapper.selectPage(page, queryWrapper); |
| | | |
| | | // 4. 数据处理(增强空值安全 & 代码可读性) |
| | | List<Customer> 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<CustomerFollowUp>() |
| | | .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<Customer> resultPage = new Page<>(customerPage.getCurrent(), customerPage.getSize(), customerPage.getTotal()); |
| | | resultPage.setRecords(processedList); |
| | | |
| | | return customerPage; // 返回包含分页信息的IPage对象 |
| | | } |
| | | |
| | | /** |
| | |
| | | */ |
| | | @Override |
| | | public int insertCustomer(Customer customer) { |
| | | LoginUser loginUser = SecurityUtils.getLoginUser(); |
| | | Long tenantId = loginUser.getTenantId(); |
| | | customer.setTenantId(tenantId); |
| | | return customerMapper.insert(customer); |
| | | } |
| | | |
| | |
| | | */ |
| | | @Override |
| | | public int updateCustomer(Customer customer) { |
| | | LoginUser loginUser = SecurityUtils.getLoginUser(); |
| | | Long tenantId = loginUser.getTenantId(); |
| | | customer.setTenantId(tenantId); |
| | | 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 |
| | | public AjaxResult importData(MultipartFile file) { |
| | | try { |
| | | ExcelUtil<Customer> util = new ExcelUtil<Customer>(Customer.class); |
| | | List<Customer> 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<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()); |
| | | } |
| | | |
| | | /** |
| | | * 删除客户档案信息 |
| | | * |
| | | * @param id 客户档案主键 |
| | | * @return 结果 |
| | | * 下划线命名转驼峰命名 |
| | | */ |
| | | @Override |
| | | public int deleteCustomerById(Long id) { |
| | | return customerMapper.deleteById(id); |
| | | 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<Customer> list = parseCustomer(dataArr); |
| | | if (!list.isEmpty()) { |
| | | // 处理更新或新增 |
| | | int affected = processSaveOrUpdate(list); |
| | | log.info("客户数据同步完成,共同步 {} 条数据", affected); |
| | | } |
| | | |
| | | } catch (Exception e) { |
| | | log.error("同步客户信息异常", e); |
| | | } finally { |
| | | // 释放锁 |
| | | syncLock.unlock(); |
| | | } |
| | | } |
| | | |
| | | private List<Customer> parseCustomer(JSONArray dataArr) { |
| | | List<Customer> 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<Customer> list) { |
| | | if (list == null || list.isEmpty()) { |
| | | return 0; |
| | | } |
| | | int affected = 0; |
| | | |
| | | for (Customer customer : list) { |
| | | |
| | | LambdaQueryWrapper<Customer> 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; |
| | | } |
| | | } |