Browse Source

fix(opportunity): code review 修复5处问题

- PageResult: Sub/Collab 分页接口从 Page<T> 改为 PageResult<T>(硬违规)
- addFollow: 新增后刷新 last_valid_follow_time 回收锚点(Spec Bug)
- deleteFollow: 软删后写 ROW_DELETE oplog(Spec缺失)
- addAttachment: FOLLOW_UP bizType 加10个附件上限校验(Spec缺失)
- OpportunityCollabServiceImpl: 魔法数字2改为 OpportunityStatus.STATUS_ADVANCING(smell)
- OpportunityPoolRuleController: import 分组排序修正(规范)
master
luoweijian 2 weeks ago
parent
commit
feb8144c19
  1. 18
      crm-opportunity/src/main/java/com/crm/opportunity/service/IOpportunitySubService.java
  2. 5
      crm-opportunity/src/main/java/com/crm/opportunity/service/impl/OpportunityCollabServiceImpl.java
  3. 59
      crm-opportunity/src/main/java/com/crm/opportunity/service/impl/OpportunitySubServiceImpl.java
  4. 7
      crm-rule/src/main/java/com/crm/rule/controller/OpportunityPoolRuleController.java

18
crm-opportunity/src/main/java/com/crm/opportunity/service/IOpportunitySubService.java

@ -1,6 +1,6 @@
package com.crm.opportunity.service; package com.crm.opportunity.service;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.crm.base.domain.result.PageResult;
import com.crm.opportunity.domain.entity.OpportunityAttachment; import com.crm.opportunity.domain.entity.OpportunityAttachment;
import com.crm.opportunity.domain.entity.OpportunityCustomer; import com.crm.opportunity.domain.entity.OpportunityCustomer;
import com.crm.opportunity.domain.entity.OpportunityFollow; import com.crm.opportunity.domain.entity.OpportunityFollow;
@ -8,6 +8,8 @@ import com.crm.opportunity.domain.entity.OpportunityOplog;
import com.crm.opportunity.domain.entity.OpportunitySiteSurvey; import com.crm.opportunity.domain.entity.OpportunitySiteSurvey;
import com.crm.opportunity.domain.entity.OpportunityTeam; import com.crm.opportunity.domain.entity.OpportunityTeam;
import java.util.List;
/** /**
* 商机详情 Tab 子域查询服务 03 整改 * 商机详情 Tab 子域查询服务 03 整改
* 六个子域客户跟进勘察附件团队日志 * 六个子域客户跟进勘察附件团队日志
@ -15,19 +17,19 @@ import com.crm.opportunity.domain.entity.OpportunityTeam;
public interface IOpportunitySubService { public interface IOpportunitySubService {
/** 客户列表(全量,通常不多) */ /** 客户列表(全量,通常不多) */
java.util.List<OpportunityCustomer> listCustomers(Long oppId); List<OpportunityCustomer> listCustomers(Long oppId);
/** 跟进记录分页 */ /** 跟进记录分页 */
Page<OpportunityFollow> pageFollows(Long oppId, long pageNum, long pageSize); PageResult<OpportunityFollow> pageFollows(Long oppId, long pageNum, long pageSize);
/** 新增跟进 */ /** 新增跟进 */
Long addFollow(OpportunityFollow follow); Long addFollow(OpportunityFollow follow);
/** 删除跟进(软删) */ /** 删除跟进(软删,并写操作日志) */
void deleteFollow(Long followId, Long operatorId); void deleteFollow(Long followId, Long operatorId);
/** 勘察记录分页 */ /** 勘察记录分页 */
Page<OpportunitySiteSurvey> pageSiteSurveys(Long oppId, long pageNum, long pageSize); PageResult<OpportunitySiteSurvey> pageSiteSurveys(Long oppId, long pageNum, long pageSize);
/** 新增勘察记录 */ /** 新增勘察记录 */
Long addSiteSurvey(OpportunitySiteSurvey survey); Long addSiteSurvey(OpportunitySiteSurvey survey);
@ -36,7 +38,7 @@ public interface IOpportunitySubService {
void deleteSiteSurvey(Long surveyId); void deleteSiteSurvey(Long surveyId);
/** 附件列表(按 oppId + 可选 bizType) */ /** 附件列表(按 oppId + 可选 bizType) */
java.util.List<OpportunityAttachment> listAttachments(Long oppId, String bizType); List<OpportunityAttachment> listAttachments(Long oppId, String bizType);
/** 新增附件 */ /** 新增附件 */
Long addAttachment(OpportunityAttachment attachment); Long addAttachment(OpportunityAttachment attachment);
@ -45,8 +47,8 @@ public interface IOpportunitySubService {
void deleteAttachment(Long attachmentId); void deleteAttachment(Long attachmentId);
/** 团队成员列表(全量) */ /** 团队成员列表(全量) */
java.util.List<OpportunityTeam> listTeamMembers(Long oppId); List<OpportunityTeam> listTeamMembers(Long oppId);
/** 操作日志分页 */ /** 操作日志分页 */
Page<OpportunityOplog> pageOplogs(Long oppId, long pageNum, long pageSize); PageResult<OpportunityOplog> pageOplogs(Long oppId, long pageNum, long pageSize);
} }

5
crm-opportunity/src/main/java/com/crm/opportunity/service/impl/OpportunityCollabServiceImpl.java

@ -8,6 +8,7 @@ import com.crm.opportunity.domain.entity.OpportunityViewLog;
import com.crm.opportunity.mapper.OpportunityFocusMapper; import com.crm.opportunity.mapper.OpportunityFocusMapper;
import com.crm.opportunity.mapper.OpportunityMapper; import com.crm.opportunity.mapper.OpportunityMapper;
import com.crm.opportunity.mapper.OpportunityViewLogMapper; import com.crm.opportunity.mapper.OpportunityViewLogMapper;
import com.crm.opportunity.domain.enums.OpportunityStatus;
import com.crm.opportunity.service.IOpportunityCollabService; import com.crm.opportunity.service.IOpportunityCollabService;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@ -83,7 +84,7 @@ public class OpportunityCollabServiceImpl implements IOpportunityCollabService {
// 按 current_stage_id 分组统计:数量 + 金额汇总 // 按 current_stage_id 分组统计:数量 + 金额汇总
// 简单实现:查所有推进中商机,按 stageId 聚合 // 简单实现:查所有推进中商机,按 stageId 聚合
LambdaQueryWrapper<Opportunity> wrapper = new LambdaQueryWrapper<Opportunity>() LambdaQueryWrapper<Opportunity> wrapper = new LambdaQueryWrapper<Opportunity>()
.eq(Opportunity::getOppStatus, 2); // 推进中 .eq(Opportunity::getOppStatus, OpportunityStatus.STATUS_ADVANCING.getValue()); // 推进中
if (stageTemplateId != null) { if (stageTemplateId != null) {
wrapper.eq(Opportunity::getStageTemplateId, stageTemplateId); wrapper.eq(Opportunity::getStageTemplateId, stageTemplateId);
} }
@ -114,7 +115,7 @@ public class OpportunityCollabServiceImpl implements IOpportunityCollabService {
public List<Map<String, Object>> boardCards(Long stageId, int offset, int limit, Long userId) { public List<Map<String, Object>> boardCards(Long stageId, int offset, int limit, Long userId) {
List<Opportunity> opps = oppMapper.selectList(new LambdaQueryWrapper<Opportunity>() List<Opportunity> opps = oppMapper.selectList(new LambdaQueryWrapper<Opportunity>()
.eq(Opportunity::getCurrentStageId, stageId) .eq(Opportunity::getCurrentStageId, stageId)
.eq(Opportunity::getOppStatus, 2) .eq(Opportunity::getOppStatus, OpportunityStatus.STATUS_ADVANCING.getValue())
.last("LIMIT " + offset + "," + limit)); .last("LIMIT " + offset + "," + limit));
List<Map<String, Object>> result = new ArrayList<>(); List<Map<String, Object>> result = new ArrayList<>();
for (Opportunity opp : opps) { for (Opportunity opp : opps) {

59
crm-opportunity/src/main/java/com/crm/opportunity/service/impl/OpportunitySubServiceImpl.java

@ -4,6 +4,8 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.crm.base.domain.exception.BusinessErrorException; import com.crm.base.domain.exception.BusinessErrorException;
import com.crm.base.domain.result.PageResult;
import com.crm.opportunity.domain.entity.Opportunity;
import com.crm.opportunity.domain.entity.OpportunityAttachment; import com.crm.opportunity.domain.entity.OpportunityAttachment;
import com.crm.opportunity.domain.entity.OpportunityCustomer; import com.crm.opportunity.domain.entity.OpportunityCustomer;
import com.crm.opportunity.domain.entity.OpportunityFollow; import com.crm.opportunity.domain.entity.OpportunityFollow;
@ -13,6 +15,7 @@ import com.crm.opportunity.domain.entity.OpportunityTeam;
import com.crm.opportunity.mapper.OpportunityAttachmentMapper; import com.crm.opportunity.mapper.OpportunityAttachmentMapper;
import com.crm.opportunity.mapper.OpportunityCustomerMapper; import com.crm.opportunity.mapper.OpportunityCustomerMapper;
import com.crm.opportunity.mapper.OpportunityFollowMapper; import com.crm.opportunity.mapper.OpportunityFollowMapper;
import com.crm.opportunity.mapper.OpportunityMapper;
import com.crm.opportunity.mapper.OpportunityOplogMapper; import com.crm.opportunity.mapper.OpportunityOplogMapper;
import com.crm.opportunity.mapper.OpportunitySiteSurveyMapper; import com.crm.opportunity.mapper.OpportunitySiteSurveyMapper;
import com.crm.opportunity.mapper.OpportunityTeamMapper; import com.crm.opportunity.mapper.OpportunityTeamMapper;
@ -22,6 +25,7 @@ import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import java.time.LocalDateTime;
import java.util.List; import java.util.List;
/** /**
@ -31,12 +35,16 @@ import java.util.List;
@RequiredArgsConstructor @RequiredArgsConstructor
public class OpportunitySubServiceImpl implements IOpportunitySubService { public class OpportunitySubServiceImpl implements IOpportunitySubService {
/** 跟进附件每条记录最多关联附件数(规格约定:10 个) */
private static final int FOLLOW_ATTACHMENT_MAX = 10;
private final OpportunityCustomerMapper customerMapper; private final OpportunityCustomerMapper customerMapper;
private final OpportunityFollowMapper followMapper; private final OpportunityFollowMapper followMapper;
private final OpportunitySiteSurveyMapper siteSurveyMapper; private final OpportunitySiteSurveyMapper siteSurveyMapper;
private final OpportunityAttachmentMapper attachmentMapper; private final OpportunityAttachmentMapper attachmentMapper;
private final OpportunityTeamMapper teamMapper; private final OpportunityTeamMapper teamMapper;
private final OpportunityOplogMapper oplogMapper; private final OpportunityOplogMapper oplogMapper;
private final OpportunityMapper oppMapper;
// ==================== 客户 ==================== // ==================== 客户 ====================
@ -51,12 +59,13 @@ public class OpportunitySubServiceImpl implements IOpportunitySubService {
// ==================== 跟进 ==================== // ==================== 跟进 ====================
@Override @Override
public Page<OpportunityFollow> pageFollows(Long oppId, long pageNum, long pageSize) { public PageResult<OpportunityFollow> pageFollows(Long oppId, long pageNum, long pageSize) {
return followMapper.selectPage(new Page<>(pageNum, pageSize), Page<OpportunityFollow> page = followMapper.selectPage(new Page<>(pageNum, pageSize),
new LambdaQueryWrapper<OpportunityFollow>() new LambdaQueryWrapper<OpportunityFollow>()
.eq(OpportunityFollow::getOppId, oppId) .eq(OpportunityFollow::getOppId, oppId)
.eq(OpportunityFollow::getDeleteKey, 0L) .eq(OpportunityFollow::getDeleteKey, 0L)
.orderByDesc(OpportunityFollow::getFollowTime)); .orderByDesc(OpportunityFollow::getFollowTime));
return new PageResult<>(page);
} }
@Override @Override
@ -69,6 +78,17 @@ public class OpportunitySubServiceImpl implements IOpportunitySubService {
if (follow.getCustomerId() == null) throw new BusinessErrorException("关联客户必填"); if (follow.getCustomerId() == null) throw new BusinessErrorException("关联客户必填");
follow.setDeleteKey(0L); follow.setDeleteKey(0L);
followMapper.insert(follow); followMapper.insert(follow);
// 刷新回收锚点:last_valid_follow_time = max(原值, 本次跟进时间)
Opportunity opp = oppMapper.selectById(follow.getOppId());
if (opp != null) {
LocalDateTime followAt = follow.getFollowTime();
if (opp.getLastValidFollowTime() == null || followAt.isAfter(opp.getLastValidFollowTime())) {
oppMapper.update(null, new LambdaUpdateWrapper<Opportunity>()
.eq(Opportunity::getId, follow.getOppId())
.set(Opportunity::getLastValidFollowTime, followAt));
}
}
return follow.getId(); return follow.getId();
} }
@ -81,16 +101,32 @@ public class OpportunitySubServiceImpl implements IOpportunitySubService {
.eq(OpportunityFollow::getId, followId) .eq(OpportunityFollow::getId, followId)
.set(OpportunityFollow::getDeleteKey, followId) .set(OpportunityFollow::getDeleteKey, followId)
.set(OpportunityFollow::getDeleted, 1)); .set(OpportunityFollow::getDeleted, 1));
// 写操作日志:ROW_DELETE
OpportunityOplog log = new OpportunityOplog();
log.setOppId(follow.getOppId());
log.setOpKind("ROW_DELETE");
log.setLogType("ROW_CHANGE");
log.setEntityName("opportunity_follow");
log.setBizRef(follow.getFollowContent() != null
? follow.getFollowContent().substring(0, Math.min(50, follow.getFollowContent().length()))
: String.valueOf(followId));
log.setOpDesc("删除跟进记录");
log.setOpSource("MANUAL");
log.setOpTime(LocalDateTime.now());
log.setOpUserId(operatorId);
oplogMapper.insert(log);
} }
// ==================== 勘察 ==================== // ==================== 勘察 ====================
@Override @Override
public Page<OpportunitySiteSurvey> pageSiteSurveys(Long oppId, long pageNum, long pageSize) { public PageResult<OpportunitySiteSurvey> pageSiteSurveys(Long oppId, long pageNum, long pageSize) {
return siteSurveyMapper.selectPage(new Page<>(pageNum, pageSize), Page<OpportunitySiteSurvey> page = siteSurveyMapper.selectPage(new Page<>(pageNum, pageSize),
new LambdaQueryWrapper<OpportunitySiteSurvey>() new LambdaQueryWrapper<OpportunitySiteSurvey>()
.eq(OpportunitySiteSurvey::getOppId, oppId) .eq(OpportunitySiteSurvey::getOppId, oppId)
.orderByDesc(OpportunitySiteSurvey::getSurveyDate)); .orderByDesc(OpportunitySiteSurvey::getSurveyDate));
return new PageResult<>(page);
} }
@Override @Override
@ -131,6 +167,16 @@ public class OpportunitySubServiceImpl implements IOpportunitySubService {
public Long addAttachment(OpportunityAttachment attachment) { public Long addAttachment(OpportunityAttachment attachment) {
if (attachment.getOppId() == null) throw new BusinessErrorException("oppId 必填"); if (attachment.getOppId() == null) throw new BusinessErrorException("oppId 必填");
if (attachment.getFileId() == null) throw new BusinessErrorException("fileId 必填"); if (attachment.getFileId() == null) throw new BusinessErrorException("fileId 必填");
// 跟进附件上限校验
if ("FOLLOW_UP".equals(attachment.getBizType()) && attachment.getBizId() != null) {
long count = attachmentMapper.selectCount(new LambdaQueryWrapper<OpportunityAttachment>()
.eq(OpportunityAttachment::getBizType, "FOLLOW_UP")
.eq(OpportunityAttachment::getBizId, attachment.getBizId())
.eq(OpportunityAttachment::getDeleteKey, 0L));
if (count >= FOLLOW_ATTACHMENT_MAX) {
throw new BusinessErrorException("跟进附件最多 " + FOLLOW_ATTACHMENT_MAX + " 个");
}
}
attachment.setDeleteKey(0L); attachment.setDeleteKey(0L);
attachmentMapper.insert(attachment); attachmentMapper.insert(attachment);
return attachment.getId(); return attachment.getId();
@ -160,10 +206,11 @@ public class OpportunitySubServiceImpl implements IOpportunitySubService {
// ==================== 操作日志 ==================== // ==================== 操作日志 ====================
@Override @Override
public Page<OpportunityOplog> pageOplogs(Long oppId, long pageNum, long pageSize) { public PageResult<OpportunityOplog> pageOplogs(Long oppId, long pageNum, long pageSize) {
return oplogMapper.selectPage(new Page<>(pageNum, pageSize), Page<OpportunityOplog> page = oplogMapper.selectPage(new Page<>(pageNum, pageSize),
new LambdaQueryWrapper<OpportunityOplog>() new LambdaQueryWrapper<OpportunityOplog>()
.eq(OpportunityOplog::getOppId, oppId) .eq(OpportunityOplog::getOppId, oppId)
.orderByDesc(OpportunityOplog::getOpTime)); .orderByDesc(OpportunityOplog::getOpTime));
return new PageResult<>(page);
} }
} }

7
crm-rule/src/main/java/com/crm/rule/controller/OpportunityPoolRuleController.java

@ -1,14 +1,13 @@
package com.crm.rule.controller; package com.crm.rule.controller;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.crm.base.domain.result.PageResult; import com.crm.base.domain.result.PageResult;
import com.crm.base.domain.result.Result; import com.crm.base.domain.result.Result;
import com.crm.rule.domain.dto.OpportunityPoolRuleDTO; import com.crm.rule.domain.dto.OpportunityPoolRuleDTO;
import com.crm.rule.domain.param.OpportunityPoolRulePageParam;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.crm.rule.domain.entity.OpportunityPoolRule; import com.crm.rule.domain.entity.OpportunityPoolRule;
import com.crm.rule.domain.param.OpportunityPoolRulePageParam;
import com.crm.rule.mapper.OpportunityPoolRuleMapper; import com.crm.rule.mapper.OpportunityPoolRuleMapper;
import com.crm.rule.service.IOpportunityPoolRuleService; import com.crm.rule.service.IOpportunityPoolRuleService;
import java.util.List;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
@ -18,6 +17,8 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/** /**
* 商机公海规则接口薄适配层只调 {@link IOpportunityPoolRuleService} * 商机公海规则接口薄适配层只调 {@link IOpportunityPoolRuleService}
* <p>权限策略不种子化 button 权限点同公海池配置ApiPermissionInterceptor 对未注册 URL fail-open * <p>权限策略不种子化 button 权限点同公海池配置ApiPermissionInterceptor 对未注册 URL fail-open

Loading…
Cancel
Save