Browse Source

feat(opportunity): 票14 自定义视图排序接线(补底稿定义2漏项)

底稿 issues/14 定义2 把「排序字段+方向」列为自定义视图组成部分,实体/DTO
已存 sortField/sortDirection,但上一版查询未接排序(列表恒按 createTime)。补齐:
- OpportunitySavedViewFilter.orderByClause(field,direction):字段走同一字段池
  白名单→主表列名,方向只认 asc/desc(大小写无关,其余/空→DESC);字段不在池/空→null
- ViewQueryImpl.applySavedView 返回排序片段,pageOpportunities 有则
  wrapper.last(ORDER BY ...) 替代默认 createTime 倒序,无则回落默认;
  RECENT 路径为时间序不接排序(时间序优先)
- 移除不再直接引用的 SavedView import
- 补 3 排序单测(asc/desc默认/未知字段回落)

mvn -pl crm-opportunity -am test 147 全绿。
master
luoweijian 2 weeks ago
parent
commit
911511d8f5
  1. 18
      crm-opportunity/src/main/java/com/crm/opportunity/query/impl/OpportunitySavedViewFilter.java
  2. 36
      crm-opportunity/src/main/java/com/crm/opportunity/query/impl/OpportunityViewQueryImpl.java
  3. 24
      crm-opportunity/src/test/java/com/crm/opportunity/query/impl/OpportunitySavedViewFilterTest.java

18
crm-opportunity/src/main/java/com/crm/opportunity/query/impl/OpportunitySavedViewFilter.java

@ -86,4 +86,22 @@ public class OpportunitySavedViewFilter {
case IS_NOT_EMPTY -> wrapper.apply("(" + column + " IS NOT NULL AND " + column + " <> '')");
}
}
/**
* 自定义视图排序翻译 14 定义 2排序字段 + 方向排序字段同属字段池
* <p>返回可直接拼入 {@code ORDER BY} 的列片段 {@code "owner_user_id DESC"}
* 字段不在字段池白名单 {@code null}调用方回落默认排序方向只认 asc/desc
* 大小写无关其余 / 默认 DESC列名走白名单常量方向只产 ASC/DESC 字面量无注入面</p>
*/
public String orderByClause(String sortField, String sortDirection) {
if (StrUtil.isBlank(sortField)) {
return null;
}
String column = FIELD_COLUMN.get(sortField);
if (column == null) {
return null; // 排序字段不在字段池 → 回落默认排序
}
boolean asc = "asc".equalsIgnoreCase(StrUtil.trimToEmpty(sortDirection));
return column + (asc ? " ASC" : " DESC");
}
}

36
crm-opportunity/src/main/java/com/crm/opportunity/query/impl/OpportunityViewQueryImpl.java

@ -30,7 +30,6 @@ import com.crm.opportunity.mapper.OpportunitySchemeCardMapper;
import com.crm.opportunity.mapper.OpportunityStageHistoryMapper;
import com.crm.opportunity.mapper.OpportunityViewLogMapper;
import com.crm.opportunity.query.OpportunityViewQuery;
import com.crm.preference.domain.dto.SavedView;
import com.crm.preference.service.SavedViewService;
import com.crm.rule.domain.dto.SysRegionDTO;
import com.crm.rule.domain.entity.OpportunityStageNode;
@ -101,8 +100,13 @@ public class OpportunityViewQueryImpl implements OpportunityViewQuery {
LambdaQueryWrapper<Opportunity> wrapper = new LambdaQueryWrapper<>();
applyViewFilters(wrapper, param, currentUserId);
applySavedView(wrapper, param, currentUserId);
wrapper.orderByDesc(Opportunity::getCreateTime);
String savedViewOrderBy = applySavedView(wrapper, param, currentUserId);
if (StrUtil.isNotBlank(savedViewOrderBy)) {
// 自定义视图排序(票 14 定义 2)替代默认 createTime 倒序
wrapper.last("ORDER BY " + savedViewOrderBy);
} else {
wrapper.orderByDesc(Opportunity::getCreateTime);
}
Page<Opportunity> page = opportunityMapper.selectPage(PageConverter.toMpPage(param), wrapper);
PageResult<OpportunityDTO> result = new PageResult<>(page).convert(OpportunityDTO::fromEntity);
@ -131,6 +135,7 @@ public class OpportunityViewQueryImpl implements OpportunityViewQuery {
LambdaQueryWrapper<Opportunity> wrapper = new LambdaQueryWrapper<>();
wrapper.in(Opportunity::getId, orderedIds);
applyCommonFilters(wrapper, param);
// RECENT 为时间序(view_log 倒序),自定义视图仅叠加条件过滤,不接排序(时间序优先)
applySavedView(wrapper, param, currentUserId);
wrapper.last("order by field(id," + orderedIds.stream()
.map(String::valueOf).collect(Collectors.joining(",")) + ")");
@ -449,21 +454,26 @@ public class OpportunityViewQueryImpl implements OpportunityViewQuery {
}
/**
* 自定义视图条件叠加 14{@code param.savedViewId} 非空时读当前用户
* {@code opportunity.sales} scope 下该视图的检索条件翻译后 AND 叠在当前数据集之上
* <p>视图不存在已删 / 非本人 无额外条件等价写回未选自定义视图
* 排序sortField/sortDirection本期不入库级排序列表默认按 createTime留后续实现</p>
* 自定义视图叠加 14{@code param.savedViewId} 非空时读当前用户
* {@code opportunity.sales} scope 下该视图把检索条件翻译后 AND 叠在当前数据集之上
* 并返回排序列片段供调用方替代默认排序
* <p>视图不存在已删 / 非本人 savedViewId 返回 {@code null}无额外条件且回落默认排序</p>
*
* @return 自定义视图排序列片段 {@code "owner_user_id DESC"}无排序 / 排序字段不在字段池 {@code null}
*/
private void applySavedView(LambdaQueryWrapper<Opportunity> wrapper,
OpportunityPageParam param, Long currentUserId) {
private String applySavedView(LambdaQueryWrapper<Opportunity> wrapper,
OpportunityPageParam param, Long currentUserId) {
if (StrUtil.isBlank(param.getSavedViewId())) {
return;
return null;
}
savedViewService.list(currentUserId, SAVED_VIEW_SCOPE).stream()
return savedViewService.list(currentUserId, SAVED_VIEW_SCOPE).stream()
.filter(v -> param.getSavedViewId().equals(v.viewId()))
.findFirst()
.map(SavedView::conditions)
.ifPresent(conditions -> savedViewFilter.apply(wrapper, conditions));
.map(v -> {
savedViewFilter.apply(wrapper, v.conditions());
return savedViewFilter.orderByClause(v.sortField(), v.sortDirection());
})
.orElse(null);
}
private Long getCurrentUserId() {

24
crm-opportunity/src/test/java/com/crm/opportunity/query/impl/OpportunitySavedViewFilterTest.java

@ -125,6 +125,30 @@ class OpportunitySavedViewFilterTest {
assertThat(sql).contains("AND");
}
// ==================== 排序翻译 ====================
@Test
@DisplayName("orderByClause:字段在池 + asc → 列 ASC")
void orderBy_ascInPool() {
assertThat(filter.orderByClause("ownerUserId", "asc")).isEqualTo("owner_user_id ASC");
}
@Test
@DisplayName("orderByClause:desc / 方向空 / 非法方向 → 列 DESC(默认)")
void orderBy_descDefault() {
assertThat(filter.orderByClause("currentStageId", "desc")).isEqualTo("current_stage_id DESC");
assertThat(filter.orderByClause("currentStageId", null)).isEqualTo("current_stage_id DESC");
assertThat(filter.orderByClause("currentStageId", "nonsense")).isEqualTo("current_stage_id DESC");
}
@Test
@DisplayName("orderByClause:排序字段不在字段池 / 空 → null(回落默认排序)")
void orderBy_unknownOrBlank_null() {
assertThat(filter.orderByClause("secretColumn", "asc")).isNull();
assertThat(filter.orderByClause(null, "asc")).isNull();
assertThat(filter.orderByClause(" ", "asc")).isNull();
}
// ==================== 操作符枚举 ====================
@Test

Loading…
Cancel
Save