Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ public OpportunityDetailResponse get(@PathVariable String id) {
@RequiresPermissions(value = {PermissionConstants.OPPORTUNITY_MANAGEMENT_UPDATE, PermissionConstants.OPPORTUNITY_MANAGEMENT_RESIGN}, logical = Logical.OR)
@Operation(summary = "更新商机阶段")
public void updateStage(@RequestBody OpportunityStageRequest request) {
opportunityService.updateStage(request, OrganizationContext.getOrganizationId());
opportunityService.updateStage(request, OrganizationContext.getOrganizationId(), SessionUtils.getUserId());
}

@PostMapping("/batch/update")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package cn.cordys.crm.opportunity.controller;

import cn.cordys.common.constants.PermissionConstants;
import cn.cordys.context.OrganizationContext;
import cn.cordys.crm.opportunity.dto.response.OpportunityStageHistoryResponse;
import cn.cordys.crm.opportunity.service.OpportunityStageHistoryService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;


@Tag(name = "商机阶段变更历史")
@RestController
@RequestMapping("/opportunity/stage/history")
public class OpportunityStageHistoryController {
@Resource
private OpportunityStageHistoryService opportunityStageHistoryService;

@GetMapping("/list/{opportunityId}")
@RequiresPermissions(PermissionConstants.OPPORTUNITY_MANAGEMENT_READ)
@Operation(summary = "商机阶段变更历史列表")
public List<OpportunityStageHistoryResponse> list(@PathVariable String opportunityId) {
return opportunityStageHistoryService.list(opportunityId, OrganizationContext.getOrganizationId());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package cn.cordys.crm.opportunity.domain;

import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.persistence.Table;
import lombok.Data;


@Data
@Table(name = "opportunity_stage_history")
public class OpportunityStageHistory {

@Schema(description = "id")
private String id;

@Schema(description = "商机id")
private String opportunityId;

@Schema(description = "原阶段id")
private String fromStage;

@Schema(description = "新阶段id")
private String toStage;

@Schema(description = "变更时间")
private Long changeTime;

@Schema(description = "操作人")
private String operator;

@Schema(description = "失败原因id(当变更为失败阶段时)")
private String failureReasonId;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package cn.cordys.crm.opportunity.dto.response;

import cn.cordys.crm.opportunity.domain.OpportunityStageHistory;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;


@Data
public class OpportunityStageHistoryResponse extends OpportunityStageHistory {
@Schema(description = "原阶段名称")
private String fromStageName;

@Schema(description = "新阶段名称")
private String toStageName;

@Schema(description = "操作人名称")
private String operatorName;

@Schema(description = "失败原因名称")
private String failureReasonName;
}
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,8 @@ public class OpportunityService {
private ExtOpportunityStageConfigMapper extOpportunityStageConfigMapper;
@Resource
private DataScopeService dataScopeService;
@Resource
private OpportunityStageHistoryService opportunityStageHistoryService;

public PagerWithOption<List<OpportunityListResponse>> list(OpportunityPageRequest request, String userId, String orgId,
DeptDataPermissionDTO deptDataPermission, Boolean source) {
Expand Down Expand Up @@ -397,10 +399,10 @@ public void delete(String id, String userId, String orgId) {
Optional.ofNullable(opportunity).ifPresentOrElse(item -> {
opportunityMapper.deleteByPrimaryKey(opportunity.getId());
opportunityFieldService.deleteByResourceId(opportunity.getId());
opportunityStageHistoryService.deleteByOpportunityId(opportunity.getId());
}, () -> {
throw new GenericException("opportunity_not_found");
});
// 添加日志上下文
OperationLogContext.setResourceName(opportunity.getName());

commonNoticeSendService.sendNotice(NotificationConstants.Module.OPPORTUNITY,
Expand All @@ -414,7 +416,6 @@ public void delete(String id, String userId, String orgId) {
*/
public void transfer(OpportunityTransferRequest request, String userId, String orgId) {
List<StageConfigResponse> stageConfigList = extOpportunityStageConfigMapper.getStageConfigList(orgId);
// 过滤出成功阶段
StageConfigResponse successConfig = stageConfigList.stream().filter(config ->
Strings.CI.equals(config.getType(), OpportunityStageType.END.name()) && Strings.CI.equals(config.getRate(), "100")
).findFirst().get();
Expand All @@ -428,16 +429,32 @@ public void transfer(OpportunityTransferRequest request, String userId, String o
}
List<String> ids = opportunityList.stream().map(Opportunity::getId).toList();

long nextPos = getNextPos(orgId, stageConfigList.getFirst().getId());
String firstStageId = stageConfigList.getFirst().getId();
Map<String, String> oldStageMap = opportunityList.stream()
.collect(Collectors.toMap(Opportunity::getId, Opportunity::getStage));

long nextPos = getNextPos(orgId, firstStageId);
SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
ExtOpportunityMapper batchUpdateMapper = sqlSession.getMapper(ExtOpportunityMapper.class);
for (int i = 0; i < ids.size(); i++) {
batchUpdateMapper.transfer(request.getOwner(), userId, ids.get(i), System.currentTimeMillis(), nextPos + i, stageConfigList.getFirst().getId());
batchUpdateMapper.transfer(request.getOwner(), userId, ids.get(i), System.currentTimeMillis(), nextPos + i, firstStageId);
}
sqlSession.flushStatements();
SqlSessionUtils.closeSqlSession(sqlSession, sqlSessionFactory);

// 记录日志
opportunityList.forEach(opportunity -> {
String oldStage = oldStageMap.get(opportunity.getId());
if (!Strings.CI.equals(oldStage, firstStageId)) {
opportunityStageHistoryService.add(
opportunity.getId(),
oldStage,
firstStageId,
userId,
null
);
}
});

List<LogDTO> logs = new ArrayList<>();
opportunityList.forEach(opportunity -> {
Customer originCustomer = new Customer();
Expand Down Expand Up @@ -479,6 +496,7 @@ public void batchDelete(List<String> ids, String userId, String orgId) {
}
opportunityMapper.deleteByIds(toDoIds);
opportunityFieldService.deleteByResourceIds(toDoIds);
opportunityStageHistoryService.deleteByOpportunityIds(toDoIds);
List<LogDTO> logs = new ArrayList<>();
opportunityList.forEach(opportunity -> {
LogDTO logDTO = new LogDTO(opportunity.getOrganizationId(), opportunity.getId(), userId, LogType.DELETE, LogModule.OPPORTUNITY_INDEX, opportunity.getName());
Expand All @@ -487,7 +505,6 @@ public void batchDelete(List<String> ids, String userId, String orgId) {
});
logService.batchAdd(logs);

// 消息通知
opportunityList.forEach(opportunity ->
commonNoticeSendService.sendNotice(NotificationConstants.Module.OPPORTUNITY,
NotificationConstants.Event.BUSINESS_DELETED, opportunity.getName(), userId,
Expand Down Expand Up @@ -633,14 +650,19 @@ public List<OpportunityDetailResponse> batchGetSimpleByIds(List<String> ids) {
*
* @param request
* @param orgId
* @param operatorId
*/
@OperationLog(module = LogModule.OPPORTUNITY_INDEX, type = LogType.UPDATE, resourceId = "{#request.id}")
public void updateStage(OpportunityStageRequest request, String orgId) {
public void updateStage(OpportunityStageRequest request, String orgId, String operatorId) {
final Opportunity oldOpportunity = opportunityMapper.selectByPrimaryKey(request.getId());
if (oldOpportunity == null) {
throw new GenericException(Translator.get("opportunity_not_found"));
}

if (Strings.CI.equals(oldOpportunity.getStage(), request.getStage())) {
return;
}

final List<StageConfigResponse> stageConfigList = extOpportunityStageConfigMapper.getStageConfigList(orgId);

final Optional<StageConfigResponse> successOpt = stageConfigList.stream()
Expand Down Expand Up @@ -676,6 +698,14 @@ public void updateStage(OpportunityStageRequest request, String orgId) {

opportunityMapper.update(newOpportunity);

opportunityStageHistoryService.add(
request.getId(),
oldOpportunity.getStage(),
request.getStage(),
operatorId,
isFailStage ? request.getFailureReason() : null
);

final Map<String, String> originalVal = new HashMap<>(1);
originalVal.put("stage", stageMap.get(oldOpportunity.getStage()));
final Map<String, String> modifiedVal = new HashMap<>(1);
Expand Down Expand Up @@ -840,14 +870,15 @@ public void batchUpdate(ResourceBatchEditRequest request, String userId, String
* @param userId
*/
public void sort(OpportunitySortRequest request, String userId) {
//拖拽节点
Opportunity opportunity = opportunityMapper.selectByPrimaryKey(request.getDragNodeId());
if (opportunity == null) {
throw new GenericException(Translator.get("opportunity_not_found"));
}

boolean stageChanged = !Strings.CI.equals(opportunity.getStage(), request.getStage());

Long pos = DEFAULT_POS;
if (StringUtils.isNotBlank(request.getDropNodeId())) {
//放入节点
Opportunity dropNode = opportunityMapper.selectByPrimaryKey(request.getDropNodeId());
pos = dropNode.getPos();
if (request.getDropPosition() == -1) {
Expand All @@ -866,6 +897,16 @@ public void sort(OpportunitySortRequest request, String userId) {
dragOpportunity.setUpdateUser(userId);
dragOpportunity.setUpdateTime(System.currentTimeMillis());
opportunityMapper.updateById(dragOpportunity);

if (stageChanged) {
opportunityStageHistoryService.add(
request.getDragNodeId(),
opportunity.getStage(),
request.getStage(),
userId,
null
);
}
}

public List<ChartResult> chart(ChartAnalysisRequest request, String userId, String orgId, DeptDataPermissionDTO deptDataPermission) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
package cn.cordys.crm.opportunity.service;

import cn.cordys.common.dto.UserDeptDTO;
import cn.cordys.common.service.BaseService;
import cn.cordys.common.uid.IDGenerator;
import cn.cordys.common.util.BeanUtils;
import cn.cordys.crm.opportunity.domain.OpportunityStageHistory;
import cn.cordys.crm.opportunity.dto.response.OpportunityStageHistoryResponse;
import cn.cordys.crm.opportunity.dto.response.StageConfigResponse;
import cn.cordys.crm.opportunity.mapper.ExtOpportunityStageConfigMapper;
import cn.cordys.crm.system.constants.DictModule;
import cn.cordys.crm.system.domain.Dict;
import cn.cordys.crm.system.domain.DictConfig;
import cn.cordys.mybatis.BaseMapper;
import cn.cordys.mybatis.lambda.LambdaQueryWrapper;
import jakarta.annotation.Resource;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;
import java.util.stream.Collectors;


@Service
@Transactional(rollbackFor = Exception.class)
public class OpportunityStageHistoryService {
@Resource
private BaseMapper<OpportunityStageHistory> opportunityStageHistoryMapper;
@Resource
private BaseMapper<DictConfig> dictConfigMapper;
@Resource
private BaseMapper<Dict> dictMapper;
@Resource
private BaseService baseService;
@Resource
private ExtOpportunityStageConfigMapper extOpportunityStageConfigMapper;


public List<OpportunityStageHistoryResponse> list(String opportunityId, String orgId) {
LambdaQueryWrapper<OpportunityStageHistory> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(OpportunityStageHistory::getOpportunityId, opportunityId);
wrapper.orderByDesc(OpportunityStageHistory::getChangeTime);
List<OpportunityStageHistory> historyList = opportunityStageHistoryMapper.selectListByLambda(wrapper);
return buildListData(orgId, historyList);
}

private List<OpportunityStageHistoryResponse> buildListData(String orgId, List<OpportunityStageHistory> historyList) {
if (CollectionUtils.isEmpty(historyList)) {
return List.of();
}

Set<String> operatorIds = new HashSet<>();
Set<String> stageIds = new HashSet<>();
Set<String> reasonIds = new HashSet<>();

for (OpportunityStageHistory history : historyList) {
operatorIds.add(history.getOperator());
stageIds.add(history.getFromStage());
stageIds.add(history.getToStage());
if (StringUtils.isNotBlank(history.getFailureReasonId())) {
reasonIds.add(history.getFailureReasonId());
}
}

Map<String, String> operatorNameMap = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
operatorNameMap.putAll(baseService.getUserNameMap(operatorIds));

List<StageConfigResponse> stageConfigList = extOpportunityStageConfigMapper.getStageConfigList(orgId);
Map<String, String> stageNameMap = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
for (StageConfigResponse config : stageConfigList) {
stageNameMap.put(config.getId(), config.getName());
}

Map<String, String> reasonNameMap = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
if (!reasonIds.isEmpty()) {
List<Dict> dictList = dictMapper.selectByIds(new ArrayList<>(reasonIds));
for (Dict dict : dictList) {
reasonNameMap.put(dict.getId(), dict.getName());
}
}

return historyList
.stream()
.map(item -> {
OpportunityStageHistoryResponse response =
BeanUtils.copyBean(new OpportunityStageHistoryResponse(), item);
response.setOperatorName(getNameIgnoreCase(operatorNameMap, item.getOperator()));
response.setFromStageName(getNameIgnoreCase(stageNameMap, item.getFromStage()));
response.setToStageName(getNameIgnoreCase(stageNameMap, item.getToStage()));
if (StringUtils.isNotBlank(item.getFailureReasonId())) {
response.setFailureReasonName(getNameIgnoreCase(reasonNameMap, item.getFailureReasonId()));
}
return response;
}).toList();
}

private String getNameIgnoreCase(Map<String, String> nameMap, String key) {
if (StringUtils.isBlank(key)) {
return null;
}
return nameMap.get(key);
}

public void add(String opportunityId, String fromStage, String toStage, String operatorId, String failureReasonId) {
OpportunityStageHistory history = new OpportunityStageHistory();
history.setId(IDGenerator.nextStr());
history.setOpportunityId(opportunityId);
history.setFromStage(fromStage);
history.setToStage(toStage);
history.setChangeTime(System.currentTimeMillis());
history.setOperator(operatorId);
history.setFailureReasonId(failureReasonId);
opportunityStageHistoryMapper.insert(history);
}

public void deleteByOpportunityId(String opportunityId) {
LambdaQueryWrapper<OpportunityStageHistory> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(OpportunityStageHistory::getOpportunityId, opportunityId);
opportunityStageHistoryMapper.deleteByLambda(wrapper);
}

public void deleteByOpportunityIds(List<String> opportunityIds) {
if (CollectionUtils.isEmpty(opportunityIds)) {
return;
}
LambdaQueryWrapper<OpportunityStageHistory> wrapper = new LambdaQueryWrapper<>();
wrapper.in(OpportunityStageHistory::getOpportunityId, opportunityIds);
opportunityStageHistoryMapper.deleteByLambda(wrapper);
}
}
Loading