fix:项目信息推送CRM
parent
9439b82081
commit
8784a143f1
|
|
@ -417,6 +417,10 @@ export default {
|
|||
this.$emit('update:visible', false);
|
||||
},
|
||||
cancel() {
|
||||
if (this.$listeners['update:visible']) {
|
||||
this.handleClose();
|
||||
return;
|
||||
}
|
||||
if (this.projectId && this.orderId === null) {
|
||||
this.$router.go(-1);
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -69,3 +69,6 @@ unis:
|
|||
endHour: 96
|
||||
mail:
|
||||
enabled: false
|
||||
opportunity:
|
||||
integration:
|
||||
base-url: http://192.168.2.250:8080
|
||||
|
|
|
|||
|
|
@ -66,3 +66,6 @@ spring:
|
|||
wall:
|
||||
config:
|
||||
multi-statement-allow: true
|
||||
opportunity:
|
||||
integration:
|
||||
base-url: https://crm.unissense.top
|
||||
|
|
|
|||
|
|
@ -56,12 +56,12 @@ public class ProjectInfo extends BaseEntity
|
|||
*/
|
||||
@Excel(name = "项目阶段", dictType = "project_stage")
|
||||
private String projectStage;
|
||||
/** 建设类型 */
|
||||
private String constructionType;
|
||||
/** 项目把握度 */
|
||||
@Excel(name = "项目把握度")
|
||||
private String projectGraspDegree;
|
||||
/** 汇智支撑人员id */
|
||||
|
||||
private String hzSupportUser;
|
||||
@Excel(name = "汇智负责人")
|
||||
private String hzSupportUserName;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,58 @@
|
|||
package com.ruoyi.sip.dto.integration;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class OpportunityUpdateRequestDto {
|
||||
/**
|
||||
* 商机编号
|
||||
*/
|
||||
private String opportunityCode;
|
||||
/**
|
||||
* 商机名称
|
||||
*/
|
||||
private String opportunityName;
|
||||
/**
|
||||
* 运作方
|
||||
*/
|
||||
private String operatorName;
|
||||
/**
|
||||
* 商机金额
|
||||
*/
|
||||
private BigDecimal amount;
|
||||
/**
|
||||
* 预计结单日期,格式 YYYY-MM-DD
|
||||
*/
|
||||
private String expectedCloseDate;
|
||||
/**
|
||||
* 把握度,建议传 A、B、C
|
||||
*/
|
||||
private String confidencePct;
|
||||
/**
|
||||
* 项目阶段
|
||||
*/
|
||||
private String stage;
|
||||
/**
|
||||
* 建设类型
|
||||
*/
|
||||
private String opportunityType;
|
||||
/**
|
||||
* 售前 ID
|
||||
*/
|
||||
private String preSalesId;
|
||||
/**
|
||||
* 售前姓名
|
||||
*/
|
||||
private String preSalesName;
|
||||
/**
|
||||
* 竞品名称
|
||||
*/
|
||||
private String competitorName;
|
||||
/**
|
||||
* 是否归档
|
||||
*/
|
||||
private Boolean archived;
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
package com.ruoyi.sip.service;
|
||||
|
||||
import com.ruoyi.sip.dto.integration.OpportunityUpdateRequestDto;
|
||||
|
||||
public interface IOpportunityIntegrationService {
|
||||
|
||||
Long updateOpportunity(OpportunityUpdateRequestDto requestDto);
|
||||
|
||||
}
|
||||
|
|
@ -78,4 +78,6 @@ public interface IProjectInfoService
|
|||
|
||||
ProjectInfo insertProjectInfoForApi(ApiProjectAddDto dto);
|
||||
|
||||
void scheduleOpportunityUpdateByProjectId(Long projectId);
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,143 @@
|
|||
package com.ruoyi.sip.service.impl;
|
||||
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.ruoyi.common.exception.ServiceException;
|
||||
import com.ruoyi.common.utils.StringUtils;
|
||||
import com.ruoyi.sip.dto.integration.OpportunityUpdateRequestDto;
|
||||
import com.ruoyi.sip.service.IOpportunityIntegrationService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.client.HttpStatusCodeException;
|
||||
import org.springframework.web.client.RestClientException;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.Map;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
public class OpportunityIntegrationServiceImpl implements IOpportunityIntegrationService {
|
||||
|
||||
@Value("${opportunity.integration.base-url:http://localhost:8080}")
|
||||
private String baseUrl;
|
||||
|
||||
@Value("${opportunity.integration.update-path:/api/opportunities/integration/update}")
|
||||
private String updatePath;
|
||||
|
||||
@Value("${opportunity.integration.secret:f0eb247f84db4e328fb27ce8ff6e7be96e73a53a7e9c4793395ad10d999e0d77}")
|
||||
private String secret;
|
||||
|
||||
private final RestTemplate restTemplate = new RestTemplate();
|
||||
|
||||
@Override
|
||||
public Long updateOpportunity(OpportunityUpdateRequestDto requestDto) {
|
||||
validateRequest(requestDto);
|
||||
JSONObject payload = buildPayload(requestDto);
|
||||
log.info("调用商机更新接口请求体, opportunityCode:{}, payload:{}", requestDto.getOpportunityCode(), payload.toJSONString());
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.APPLICATION_JSON);
|
||||
headers.add("X-Internal-Secret", secret);
|
||||
HttpEntity<JSONObject> requestEntity = new HttpEntity<>(payload, headers);
|
||||
ResponseEntity<Map> responseEntity;
|
||||
try {
|
||||
responseEntity = restTemplate.exchange(buildUrl(), HttpMethod.PUT, requestEntity, Map.class);
|
||||
} catch (HttpStatusCodeException e) {
|
||||
throw new ServiceException("调用商机更新接口失败: HTTP " + e.getRawStatusCode() + " " + e.getStatusText() + ", 响应: " + e.getResponseBodyAsString());
|
||||
} catch (RestClientException e) {
|
||||
throw new ServiceException("调用商机更新接口失败: " + e.getMessage());
|
||||
}
|
||||
Map responseBody = responseEntity.getBody();
|
||||
if (responseBody == null) {
|
||||
throw new ServiceException("调用商机更新接口失败: 响应为空");
|
||||
}
|
||||
String code = String.valueOf(responseBody.get("code"));
|
||||
if (!"0".equals(code)) {
|
||||
throw new ServiceException(String.valueOf(responseBody.get("msg")));
|
||||
}
|
||||
Object data = responseBody.get("data");
|
||||
log.info("商机更新接口调用成功, opportunityCode:{}, data:{}", requestDto.getOpportunityCode(), data);
|
||||
if (data == null) {
|
||||
return null;
|
||||
}
|
||||
if (data instanceof Number) {
|
||||
return ((Number) data).longValue();
|
||||
}
|
||||
try {
|
||||
return Long.valueOf(String.valueOf(data));
|
||||
} catch (NumberFormatException e) {
|
||||
throw new ServiceException("调用商机更新接口失败: 返回 data 不是数字");
|
||||
}
|
||||
}
|
||||
|
||||
private String buildUrl() {
|
||||
String trimmedBaseUrl = baseUrl.endsWith("/") ? baseUrl.substring(0, baseUrl.length() - 1) : baseUrl;
|
||||
String trimmedPath = updatePath.startsWith("/") ? updatePath : "/" + updatePath;
|
||||
return trimmedBaseUrl + trimmedPath;
|
||||
}
|
||||
|
||||
private void validateRequest(OpportunityUpdateRequestDto requestDto) {
|
||||
if (requestDto == null) {
|
||||
throw new ServiceException("请求参数不能为空");
|
||||
}
|
||||
if (StringUtils.isEmpty(requestDto.getOpportunityCode())) {
|
||||
throw new ServiceException("opportunityCode 不能为空");
|
||||
}
|
||||
if (StringUtils.isEmpty(secret)) {
|
||||
throw new ServiceException("opportunity.integration.secret 未配置,无法调用商机更新接口");
|
||||
}
|
||||
if (!hasAtLeastOneUpdateField(requestDto)) {
|
||||
throw new ServiceException("至少传入一个需要更新的字段");
|
||||
}
|
||||
}
|
||||
|
||||
private boolean hasAtLeastOneUpdateField(OpportunityUpdateRequestDto requestDto) {
|
||||
for (Field field : OpportunityUpdateRequestDto.class.getDeclaredFields()) {
|
||||
if ("opportunityCode".equals(field.getName())) {
|
||||
continue;
|
||||
}
|
||||
field.setAccessible(true);
|
||||
Object value;
|
||||
try {
|
||||
value = field.get(requestDto);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new ServiceException("参数读取失败: " + field.getName());
|
||||
}
|
||||
if (value instanceof String) {
|
||||
if (StringUtils.isNotEmpty((String) value)) {
|
||||
return true;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (value != null) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private JSONObject buildPayload(OpportunityUpdateRequestDto requestDto) {
|
||||
JSONObject payload = new JSONObject();
|
||||
for (Field field : OpportunityUpdateRequestDto.class.getDeclaredFields()) {
|
||||
field.setAccessible(true);
|
||||
Object value;
|
||||
try {
|
||||
value = field.get(requestDto);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new ServiceException("参数读取失败: " + field.getName());
|
||||
}
|
||||
if (value instanceof String && StringUtils.isEmpty((String) value)) {
|
||||
continue;
|
||||
}
|
||||
if (value != null) {
|
||||
payload.put(field.getName(), value);
|
||||
}
|
||||
}
|
||||
return payload;
|
||||
}
|
||||
}
|
||||
|
|
@ -26,6 +26,7 @@ import com.ruoyi.sip.dto.ApiProjectAddDto;
|
|||
import com.ruoyi.sip.dto.HomepageQueryDto;
|
||||
import com.ruoyi.sip.dto.StatisticsDetailDto;
|
||||
import com.ruoyi.sip.dto.StatisticsDto;
|
||||
import com.ruoyi.sip.dto.integration.OpportunityUpdateRequestDto;
|
||||
import com.ruoyi.sip.mapper.ProjectInfoMapper;
|
||||
import com.ruoyi.sip.service.*;
|
||||
import liquibase.hub.model.Project;
|
||||
|
|
@ -39,6 +40,8 @@ import org.hibernate.validator.internal.constraintvalidators.bv.time.future.Futu
|
|||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.transaction.support.TransactionSynchronization;
|
||||
import org.springframework.transaction.support.TransactionSynchronizationManager;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
|
|
@ -53,6 +56,7 @@ import java.time.Period;
|
|||
import java.time.ZoneId;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
|
@ -88,6 +92,8 @@ public class ProjectInfoServiceImpl implements IProjectInfoService {
|
|||
private IProjectUserCollectInfoService projectUserCollectInfoService;
|
||||
@Autowired
|
||||
private IProjectOrderInfoService orderInfoService;
|
||||
@Autowired
|
||||
private IOpportunityIntegrationService opportunityIntegrationService;
|
||||
private static final String PROJECT_CODE_PREFIX = "V";
|
||||
private static final Integer PROJECT_CODE_LENGTH = 6;
|
||||
|
||||
|
|
@ -281,7 +287,7 @@ public class ProjectInfoServiceImpl implements IProjectInfoService {
|
|||
}
|
||||
//变更属地校验
|
||||
List<ProjectOrderInfo> projectOrderInfos = orderInfoService.selectProjectOrderInfoByProjectId(Collections.singletonList(projectInfo.getId()));
|
||||
if (!isApi && !oldProjectInfo.getAgentCode().equals(projectInfo.getAgentCode())) {
|
||||
if (!isApi && StringUtils.isNotEmpty(oldProjectInfo.getAgentCode()) && !oldProjectInfo.getAgentCode().equals(projectInfo.getAgentCode())) {
|
||||
//查询订单信息 如果有抛出异常
|
||||
if (CollUtil.isNotEmpty(projectOrderInfos)) {
|
||||
throw new ServiceException("该项目存在订单流转,无法更改代表处");
|
||||
|
|
@ -313,9 +319,85 @@ public class ProjectInfoServiceImpl implements IProjectInfoService {
|
|||
if(CollUtil.isEmpty(projectInfos)){
|
||||
quotationService.unBind(oldProjectInfo.getQuotationId());
|
||||
}
|
||||
//项目信息异步推送CRM
|
||||
scheduleOpportunityUpdateByProjectId(projectInfo.getId());
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void scheduleOpportunityUpdateByProjectId(Long projectId) {
|
||||
if (projectId == null) {
|
||||
return;
|
||||
}
|
||||
scheduleOpportunityUpdate(this.selectProjectInfoById(projectId));
|
||||
}
|
||||
|
||||
/**
|
||||
* 项目信息异步推送CRM
|
||||
* @param projectInfo
|
||||
*/
|
||||
private void scheduleOpportunityUpdate(ProjectInfo projectInfo) {
|
||||
OpportunityUpdateRequestDto requestDto = buildOpportunityUpdateRequest(projectInfo);
|
||||
if (requestDto == null) {
|
||||
return;
|
||||
}
|
||||
Runnable task = () -> {
|
||||
try {
|
||||
opportunityIntegrationService.updateOpportunity(requestDto);
|
||||
} catch (Exception e) {
|
||||
log.error("项目更新后同步商机失败, projectId:{}, opportunityCode:{}", projectInfo.getId(), requestDto.getOpportunityCode(), e);
|
||||
}
|
||||
};
|
||||
if (TransactionSynchronizationManager.isSynchronizationActive()) {
|
||||
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
|
||||
@Override
|
||||
public void afterCommit() {
|
||||
CompletableFuture.runAsync(task);
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
CompletableFuture.runAsync(task);
|
||||
}
|
||||
|
||||
private OpportunityUpdateRequestDto buildOpportunityUpdateRequest(ProjectInfo projectInfo) {
|
||||
if (projectInfo == null || StringUtils.isEmpty(projectInfo.getProjectCode())) {
|
||||
return null;
|
||||
}
|
||||
OpportunityUpdateRequestDto requestDto = new OpportunityUpdateRequestDto();
|
||||
requestDto.setOpportunityCode(projectInfo.getProjectCode());
|
||||
requestDto.setOpportunityName(projectInfo.getProjectName());
|
||||
requestDto.setOperatorName(projectInfo.getOperateInstitution());
|
||||
requestDto.setAmount(projectInfo.getEstimatedAmount());
|
||||
requestDto.setExpectedCloseDate(formatterDate(projectInfo.getEstimatedOrderTime(), DateUtils.YYYY_MM_DD));
|
||||
requestDto.setConfidencePct(projectInfo.getProjectGraspDegree());
|
||||
requestDto.setStage(projectInfo.getProjectStage());
|
||||
requestDto.setOpportunityType(projectInfo.getConstructionType());
|
||||
requestDto.setPreSalesId(projectInfo.getHzSupportUser());
|
||||
requestDto.setPreSalesName(projectInfo.getHzSupportUserName());
|
||||
requestDto.setCompetitorName(projectInfo.getCompetitor());
|
||||
Boolean canGenerate = projectInfo.getCanGenerate();
|
||||
if (canGenerate == null && projectInfo.getId() != null) {
|
||||
canGenerate = CollUtil.isNotEmpty(orderInfoService.selectProjectOrderInfoByProjectId(Collections.singletonList(projectInfo.getId())));
|
||||
}
|
||||
requestDto.setArchived(canGenerate);
|
||||
return hasOpportunityUpdateField(requestDto) ? requestDto : null;
|
||||
}
|
||||
|
||||
private boolean hasOpportunityUpdateField(OpportunityUpdateRequestDto requestDto) {
|
||||
return StringUtils.isNotEmpty(requestDto.getOpportunityName())
|
||||
|| StringUtils.isNotEmpty(requestDto.getOperatorName())
|
||||
|| requestDto.getAmount() != null
|
||||
|| StringUtils.isNotEmpty(requestDto.getExpectedCloseDate())
|
||||
|| StringUtils.isNotEmpty(requestDto.getConfidencePct())
|
||||
|| StringUtils.isNotEmpty(requestDto.getStage())
|
||||
|| StringUtils.isNotEmpty(requestDto.getOpportunityType())
|
||||
|| requestDto.getPreSalesId() != null
|
||||
|| StringUtils.isNotEmpty(requestDto.getPreSalesName())
|
||||
|| StringUtils.isNotEmpty(requestDto.getCompetitorName())
|
||||
|| requestDto.getArchived() != null;
|
||||
}
|
||||
|
||||
private void recordOperationLogs(ProjectInfo projectInfo, ProjectInfo oldProjectInfo) {
|
||||
StringBuilder logContent = new StringBuilder();
|
||||
//简略信息变更
|
||||
|
|
@ -453,6 +535,13 @@ public class ProjectInfoServiceImpl implements IProjectInfoService {
|
|||
return DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS, date);
|
||||
}
|
||||
|
||||
private String formatterDate(Date date, String pattern) {
|
||||
if (date == null) {
|
||||
return null;
|
||||
}
|
||||
return DateUtils.parseDateToStr(pattern, date);
|
||||
}
|
||||
|
||||
/**
|
||||
* 比较产品信息列表并生成日志内容
|
||||
*
|
||||
|
|
|
|||
|
|
@ -147,6 +147,9 @@ public class ProjectOrderInfoServiceImpl implements IProjectOrderInfoService, To
|
|||
@Autowired
|
||||
@Lazy
|
||||
private ProjectOrderInfoToolProvider projectOrderInfoToolProvider;
|
||||
@Autowired
|
||||
@Lazy
|
||||
private IProjectInfoService projectInfoService;
|
||||
/**
|
||||
* 查询订单管理
|
||||
*
|
||||
|
|
@ -324,6 +327,7 @@ public class ProjectOrderInfoServiceImpl implements IProjectOrderInfoService, To
|
|||
}
|
||||
//修改项目预计下单时间
|
||||
projectInfoMapper.updateOrderTimeById(projectOrderInfo.getProjectId());
|
||||
projectInfoService.scheduleOpportunityUpdateByProjectId(projectOrderInfo.getProjectId());
|
||||
return i;
|
||||
}
|
||||
|
||||
|
|
@ -518,7 +522,24 @@ public class ProjectOrderInfoServiceImpl implements IProjectOrderInfoService, To
|
|||
*/
|
||||
@Override
|
||||
public int deleteProjectOrderInfoByIds(String ids) {
|
||||
return projectOrderInfoMapper.deleteProjectOrderInfoByIds(Convert.toStrArray(ids));
|
||||
String[] idArray = Convert.toStrArray(ids);
|
||||
Set<Long> projectIds = new HashSet<>();
|
||||
for (String idStr : idArray) {
|
||||
if (StringUtils.isEmpty(idStr)) {
|
||||
continue;
|
||||
}
|
||||
ProjectOrderInfo orderInfo = projectOrderInfoMapper.selectProjectOrderInfoById(Long.valueOf(idStr));
|
||||
if (orderInfo != null && orderInfo.getProjectId() != null) {
|
||||
projectIds.add(orderInfo.getProjectId());
|
||||
}
|
||||
}
|
||||
int rows = projectOrderInfoMapper.deleteProjectOrderInfoByIds(idArray);
|
||||
if (rows > 0) {
|
||||
for (Long projectId : projectIds) {
|
||||
projectInfoService.scheduleOpportunityUpdateByProjectId(projectId);
|
||||
}
|
||||
}
|
||||
return rows;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
Loading…
Reference in New Issue