|
|
@ -14,6 +14,7 @@ import com.crm.opportunity.domain.entity.OpportunityFocus; |
|
|
import com.crm.opportunity.domain.entity.OpportunityFollow; |
|
|
import com.crm.opportunity.domain.entity.OpportunityFollow; |
|
|
import com.crm.opportunity.domain.entity.OpportunitySchemeCard; |
|
|
import com.crm.opportunity.domain.entity.OpportunitySchemeCard; |
|
|
import com.crm.opportunity.domain.entity.OpportunityStageHistory; |
|
|
import com.crm.opportunity.domain.entity.OpportunityStageHistory; |
|
|
|
|
|
import com.crm.opportunity.domain.entity.OpportunityViewLog; |
|
|
import com.crm.opportunity.domain.enums.OpportunityStatus; |
|
|
import com.crm.opportunity.domain.enums.OpportunityStatus; |
|
|
import com.crm.opportunity.domain.enums.OpportunityViewType; |
|
|
import com.crm.opportunity.domain.enums.OpportunityViewType; |
|
|
import com.crm.opportunity.domain.param.OpportunityPageParam; |
|
|
import com.crm.opportunity.domain.param.OpportunityPageParam; |
|
|
@ -23,6 +24,7 @@ import com.crm.opportunity.mapper.OpportunityFollowMapper; |
|
|
import com.crm.opportunity.mapper.OpportunityMapper; |
|
|
import com.crm.opportunity.mapper.OpportunityMapper; |
|
|
import com.crm.opportunity.mapper.OpportunitySchemeCardMapper; |
|
|
import com.crm.opportunity.mapper.OpportunitySchemeCardMapper; |
|
|
import com.crm.opportunity.mapper.OpportunityStageHistoryMapper; |
|
|
import com.crm.opportunity.mapper.OpportunityStageHistoryMapper; |
|
|
|
|
|
import com.crm.opportunity.mapper.OpportunityViewLogMapper; |
|
|
import com.crm.opportunity.query.OpportunityViewQuery; |
|
|
import com.crm.opportunity.query.OpportunityViewQuery; |
|
|
import lombok.RequiredArgsConstructor; |
|
|
import lombok.RequiredArgsConstructor; |
|
|
import org.springframework.stereotype.Component; |
|
|
import org.springframework.stereotype.Component; |
|
|
@ -60,10 +62,20 @@ public class OpportunityViewQueryImpl implements OpportunityViewQuery { |
|
|
private final OpportunityFollowMapper followMapper; |
|
|
private final OpportunityFollowMapper followMapper; |
|
|
private final OpportunityCustomerMapper customerMapper; |
|
|
private final OpportunityCustomerMapper customerMapper; |
|
|
private final OpportunitySchemeCardMapper schemeCardMapper; |
|
|
private final OpportunitySchemeCardMapper schemeCardMapper; |
|
|
|
|
|
private final OpportunityViewLogMapper viewLogMapper; |
|
|
|
|
|
|
|
|
|
|
|
/** RECENT 视图上限(票 10 P2-2 默认 50;view_log 已被后台 Job 裁剪,此处再兜底截断)。 */ |
|
|
|
|
|
private static final int RECENT_LIMIT = 50; |
|
|
|
|
|
|
|
|
@Override |
|
|
@Override |
|
|
public PageResult<OpportunityDTO> pageOpportunities(OpportunityPageParam param) { |
|
|
public PageResult<OpportunityDTO> pageOpportunities(OpportunityPageParam param) { |
|
|
Long currentUserId = getCurrentUserId(); |
|
|
Long currentUserId = getCurrentUserId(); |
|
|
|
|
|
|
|
|
|
|
|
// RECENT 走 view_log 时间倒序独立路径(跨表排序,无法在主表 wrapper 内表达)
|
|
|
|
|
|
if (OpportunityViewType.fromValue(param.getViewType()) == OpportunityViewType.RECENT) { |
|
|
|
|
|
return pageRecent(param, currentUserId); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
LambdaQueryWrapper<Opportunity> wrapper = new LambdaQueryWrapper<>(); |
|
|
LambdaQueryWrapper<Opportunity> wrapper = new LambdaQueryWrapper<>(); |
|
|
applyViewFilters(wrapper, param, currentUserId); |
|
|
applyViewFilters(wrapper, param, currentUserId); |
|
|
wrapper.orderByDesc(Opportunity::getCreateTime); |
|
|
wrapper.orderByDesc(Opportunity::getCreateTime); |
|
|
@ -74,6 +86,36 @@ public class OpportunityViewQueryImpl implements OpportunityViewQuery { |
|
|
return result; |
|
|
return result; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* RECENT 最近访问视图:按 {@code opportunity_view_log.last_view_time} 倒序取上限 {@link #RECENT_LIMIT}, |
|
|
|
|
|
* 再按该顺序回主表(MySQL {@code ORDER BY FIELD} 保序)。公共筛选(statusIn 等)仍生效; |
|
|
|
|
|
* 分页在裁剪后的最近序列上做(recent 天然是短列表)。 |
|
|
|
|
|
*/ |
|
|
|
|
|
private PageResult<OpportunityDTO> pageRecent(OpportunityPageParam param, Long currentUserId) { |
|
|
|
|
|
List<Long> orderedIds = viewLogMapper.selectList( |
|
|
|
|
|
new LambdaQueryWrapper<OpportunityViewLog>() |
|
|
|
|
|
.eq(OpportunityViewLog::getUserId, currentUserId) |
|
|
|
|
|
.orderByDesc(OpportunityViewLog::getLastViewTime) |
|
|
|
|
|
.last("limit " + RECENT_LIMIT)) |
|
|
|
|
|
.stream().map(OpportunityViewLog::getOpportunityId).collect(Collectors.toList()); |
|
|
|
|
|
|
|
|
|
|
|
PageResult<OpportunityDTO> empty = new PageResult<>(new Page<>()); |
|
|
|
|
|
if (orderedIds.isEmpty()) { |
|
|
|
|
|
return empty; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
LambdaQueryWrapper<Opportunity> wrapper = new LambdaQueryWrapper<>(); |
|
|
|
|
|
wrapper.in(Opportunity::getId, orderedIds); |
|
|
|
|
|
applyCommonFilters(wrapper, param); |
|
|
|
|
|
wrapper.last("order by field(id," + orderedIds.stream() |
|
|
|
|
|
.map(String::valueOf).collect(Collectors.joining(",")) + ")"); |
|
|
|
|
|
|
|
|
|
|
|
Page<Opportunity> page = opportunityMapper.selectPage(PageConverter.toMpPage(param), wrapper); |
|
|
|
|
|
PageResult<OpportunityDTO> result = new PageResult<>(page).convert(OpportunityDTO::fromEntity); |
|
|
|
|
|
fillDisplayFields(result.getContent(), currentUserId); |
|
|
|
|
|
return result; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
/** |
|
|
/** |
|
|
* 六视图共享查询条件(viewType 数据集 + 公共筛选),不含排序/分页。 |
|
|
* 六视图共享查询条件(viewType 数据集 + 公共筛选),不含排序/分页。 |
|
|
* <p>{@code @DataScope} 经拦截器对本 wrapper 自动追加部门天花板(MANAGE/PUBLIC_POOL 严格生效)。</p> |
|
|
* <p>{@code @DataScope} 经拦截器对本 wrapper 自动追加部门天花板(MANAGE/PUBLIC_POOL 严格生效)。</p> |
|
|
@ -91,9 +133,8 @@ public class OpportunityViewQueryImpl implements OpportunityViewQuery { |
|
|
// 我关注的:opportunity_focus 命中当前用户
|
|
|
// 我关注的:opportunity_focus 命中当前用户
|
|
|
case FOLLOWED -> wrapper.apply( |
|
|
case FOLLOWED -> wrapper.apply( |
|
|
"id IN (SELECT opportunity_id FROM opportunity_focus WHERE user_id = {0})", currentUserId); |
|
|
"id IN (SELECT opportunity_id FROM opportunity_focus WHERE user_id = {0})", currentUserId); |
|
|
// 最近访问:view_log 命中当前用户(排序/去重/上限待后续 join,当前先出数据集)
|
|
|
// 最近访问:RECENT 走 pageRecent 独立路径(不入本 switch),此处兵底无额外过滤
|
|
|
case RECENT -> wrapper.apply( |
|
|
case RECENT -> { /* handled by pageRecent */ } |
|
|
"id IN (SELECT opportunity_id FROM opportunity_view_log WHERE user_id = {0})", currentUserId); |
|
|
|
|
|
// 商机公海:opp_status=待领取 且 owner 空(逻辑视图,票 05)
|
|
|
// 商机公海:opp_status=待领取 且 owner 空(逻辑视图,票 05)
|
|
|
case PUBLIC_POOL -> wrapper |
|
|
case PUBLIC_POOL -> wrapper |
|
|
.eq(Opportunity::getOppStatus, OpportunityStatus.STATUS_IN_POOL.getValue()) |
|
|
.eq(Opportunity::getOppStatus, OpportunityStatus.STATUS_IN_POOL.getValue()) |
|
|
@ -102,6 +143,11 @@ public class OpportunityViewQueryImpl implements OpportunityViewQuery { |
|
|
case MANAGE -> { /* @DataScope handles dept ceiling */ } |
|
|
case MANAGE -> { /* @DataScope handles dept ceiling */ } |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
applyCommonFilters(wrapper, param); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** 公共筛选(列表可筛选列 + keyword,票 14 字段池),与视图数据集正交;RECENT 独立路径亦复用。 */ |
|
|
|
|
|
private void applyCommonFilters(LambdaQueryWrapper<Opportunity> wrapper, OpportunityPageParam param) { |
|
|
// 公共筛选(列表可筛选列,票 14 字段池)
|
|
|
// 公共筛选(列表可筛选列,票 14 字段池)
|
|
|
wrapper.in(CollUtil.isNotEmpty(param.getStatusIn()), Opportunity::getOppStatus, param.getStatusIn()) |
|
|
wrapper.in(CollUtil.isNotEmpty(param.getStatusIn()), Opportunity::getOppStatus, param.getStatusIn()) |
|
|
.eq(StrUtil.isNotBlank(param.getOppSource()), Opportunity::getOppSource, param.getOppSource()) |
|
|
.eq(StrUtil.isNotBlank(param.getOppSource()), Opportunity::getOppSource, param.getOppSource()) |
|
|
|