|
|
|
@ -4,9 +4,13 @@ import cn.hutool.core.collection.CollUtil; |
|
|
|
import cn.hutool.core.util.StrUtil; |
|
|
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
|
|
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
|
|
|
import com.crm.auth.domain.entity.SysDept; |
|
|
|
import com.crm.auth.service.ISysDeptService; |
|
|
|
import com.crm.base.domain.result.PageResult; |
|
|
|
import com.crm.base.security.SecurityUtils; |
|
|
|
import com.crm.base.utils.PageConverter; |
|
|
|
import com.crm.dict.domain.entity.DictItem; |
|
|
|
import com.crm.dict.service.DictQueryService; |
|
|
|
import com.crm.opportunity.domain.dto.OpportunityDTO; |
|
|
|
import com.crm.opportunity.domain.entity.Opportunity; |
|
|
|
import com.crm.opportunity.domain.entity.OpportunityCustomer; |
|
|
|
@ -26,6 +30,8 @@ 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.rule.domain.dto.SysRegionDTO; |
|
|
|
import com.crm.rule.service.ISysRegionService; |
|
|
|
import lombok.RequiredArgsConstructor; |
|
|
|
import org.springframework.stereotype.Component; |
|
|
|
|
|
|
|
@ -33,6 +39,8 @@ import java.time.LocalDate; |
|
|
|
import java.time.LocalDateTime; |
|
|
|
import java.time.temporal.ChronoUnit; |
|
|
|
import java.util.HashMap; |
|
|
|
import java.util.HashSet; |
|
|
|
import java.util.LinkedHashSet; |
|
|
|
import java.util.List; |
|
|
|
import java.util.Map; |
|
|
|
import java.util.Set; |
|
|
|
@ -45,12 +53,13 @@ import java.util.stream.Collectors; |
|
|
|
* {@link #applyViewFilters};{@code @DataScope}({@code Opportunity} 实体注解)经拦截器对 wrapper 自动 |
|
|
|
* 追加部门天花板。当前步已落:视图 filter + 公共筛选 + 分页 + followed 回显。</p> |
|
|
|
* |
|
|
|
* <p>票 10 §二数值计算列(节点停留/最近跟进/方案卡状态/方案预算)已批量 join 填充(无 N+1)。</p> |
|
|
|
* <p>票 10 §二数值计算列(节点停留/最近跟进/方案卡状态/方案预算)已批量 join 填充(无 N+1); |
|
|
|
* 字典/部门/区划名回显(DictQueryService/SysDeptService/SysRegionService,对称 crm-lead fillDisplayFields) |
|
|
|
* 已落。</p> |
|
|
|
* |
|
|
|
* <p><b>TODO(后续步骤)</b>:① 字典/部门/区划名回显(DictQueryService/SysDeptService/SysRegionService, |
|
|
|
* 对称 crm-lead fillDisplayFields);② 当前/下一节点名(依赖 crm-rule 阶段模板服务);③ RECENT 视图按 |
|
|
|
* {@code opportunity_view_log.last_view_time} 倒序取上限(当前 RECENT 先按 create_time 兜底排序,待接 |
|
|
|
* view_log join);④ PARTICIPATED 团队白名单可见性叠加详情链路。</p> |
|
|
|
* <p><b>TODO(后续步骤)</b>:① 当前/下一节点名(依赖 crm-rule 阶段模板服务);② PARTICIPATED |
|
|
|
* 团队白名单可见性叠加详情链路。RECENT 视图已按 {@code opportunity_view_log.last_view_time} 倒序取上限 |
|
|
|
* (见 {@link #pageRecent})。</p> |
|
|
|
*/ |
|
|
|
@Component |
|
|
|
@RequiredArgsConstructor |
|
|
|
@ -63,6 +72,9 @@ public class OpportunityViewQueryImpl implements OpportunityViewQuery { |
|
|
|
private final OpportunityCustomerMapper customerMapper; |
|
|
|
private final OpportunitySchemeCardMapper schemeCardMapper; |
|
|
|
private final OpportunityViewLogMapper viewLogMapper; |
|
|
|
private final DictQueryService dictQueryService; |
|
|
|
private final ISysDeptService sysDeptService; |
|
|
|
private final ISysRegionService sysRegionService; |
|
|
|
|
|
|
|
/** RECENT 视图上限(票 10 P2-2 默认 50;view_log 已被后台 Job 裁剪,此处再兜底截断)。 */ |
|
|
|
private static final int RECENT_LIMIT = 50; |
|
|
|
@ -172,8 +184,9 @@ public class OpportunityViewQueryImpl implements OpportunityViewQuery { |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* 出参回显字段填充:followed + 票 10 §二数值计算列(批量,无 N+1)。 |
|
|
|
* <p>TODO 后续:字典/部门/区划名 + 当前/下一节点名(依赖 crm-rule 模板服务)。</p> |
|
|
|
* 出参回显字段填充:followed + 票 10 §二数值计算列(批量,无 N+1)+ 字典/部门/区划名。 |
|
|
|
* <p>字典名对称 crm-lead {@code fillDictNames}:按 (groupCode, code) 批量查(同页去重); |
|
|
|
* 部门名 = {@code owner_dept_id} → SysDept;区划名 = province/city → SysRegion。</p> |
|
|
|
*/ |
|
|
|
private void fillDisplayFields(List<OpportunityDTO> dtos, Long currentUserId) { |
|
|
|
if (CollUtil.isEmpty(dtos)) { |
|
|
|
@ -192,6 +205,68 @@ public class OpportunityViewQueryImpl implements OpportunityViewQuery { |
|
|
|
fillStageStayDays(dtos, ids); |
|
|
|
fillLastFollowTime(dtos, ids); |
|
|
|
fillSchemeCardColumns(dtos, ids); |
|
|
|
fillDictNames(dtos); |
|
|
|
fillDeptNames(dtos); |
|
|
|
fillRegionNames(dtos); |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* 字典名称快照回显(对称 crm-lead {@code fillDictNames}):商机来源 / 行业。 |
|
|
|
* <p>groupCode 契约见 PRD §10 字典清单({@code opp_source} / {@code industry_code})。 |
|
|
|
* 走 {@link DictQueryService#getItem}(含停用项,扛历史数据回显,Caffeine 缓存), |
|
|
|
* 按 (分组, code) 去重后逐个查,同一页内重复值只查一次。DTO 仅暴露 |
|
|
|
* {@code oppSourceName}/{@code industryName} 两回显位(oppType/bidForm/localityType 无 Name 位,不填)。</p> |
|
|
|
*/ |
|
|
|
private void fillDictNames(List<OpportunityDTO> dtos) { |
|
|
|
Map<String, String> sourceNames = dictNames("opp_source", |
|
|
|
dtos.stream().map(OpportunityDTO::getOppSource).collect(Collectors.toSet())); |
|
|
|
Map<String, String> industryNames = dictNames("industry_code", |
|
|
|
dtos.stream().map(OpportunityDTO::getIndustryCode).collect(Collectors.toSet())); |
|
|
|
dtos.forEach(d -> { |
|
|
|
d.setOppSourceName(sourceNames.get(d.getOppSource())); |
|
|
|
d.setIndustryName(industryNames.get(d.getIndustryCode())); |
|
|
|
}); |
|
|
|
} |
|
|
|
|
|
|
|
private Map<String, String> dictNames(String groupCode, Set<String> codes) { |
|
|
|
Map<String, String> names = new HashMap<>(); |
|
|
|
codes.stream().filter(StrUtil::isNotBlank).distinct().forEach(code -> { |
|
|
|
DictItem item = dictQueryService.getItem(groupCode, code); |
|
|
|
if (item != null) { |
|
|
|
names.put(code, item.getName()); |
|
|
|
} |
|
|
|
}); |
|
|
|
return names; |
|
|
|
} |
|
|
|
|
|
|
|
/** 负责人部门名回显(对称 crm-lead {@code fillDeptNames}):owner_dept_id → SysDept.deptName。 */ |
|
|
|
private void fillDeptNames(List<OpportunityDTO> dtos) { |
|
|
|
Set<Long> deptIds = dtos.stream().map(OpportunityDTO::getOwnerDeptId) |
|
|
|
.filter(java.util.Objects::nonNull).collect(Collectors.toCollection(LinkedHashSet::new)); |
|
|
|
if (deptIds.isEmpty()) { |
|
|
|
return; |
|
|
|
} |
|
|
|
Map<Long, String> deptNameMap = sysDeptService.listByIds(deptIds).stream() |
|
|
|
.collect(Collectors.toMap(SysDept::getId, SysDept::getDeptName, (a, b) -> a)); |
|
|
|
dtos.forEach(d -> d.setOwnerDeptName(deptNameMap.get(d.getOwnerDeptId()))); |
|
|
|
} |
|
|
|
|
|
|
|
/** 省/市区划名回显(对称 crm-lead 省市名):province_code/city_code → SysRegion.name。 */ |
|
|
|
private void fillRegionNames(List<OpportunityDTO> dtos) { |
|
|
|
Set<String> regionCodes = new HashSet<>(); |
|
|
|
dtos.forEach(d -> { |
|
|
|
if (StrUtil.isNotBlank(d.getProvinceCode())) regionCodes.add(d.getProvinceCode()); |
|
|
|
if (StrUtil.isNotBlank(d.getCityCode())) regionCodes.add(d.getCityCode()); |
|
|
|
}); |
|
|
|
if (regionCodes.isEmpty()) { |
|
|
|
return; |
|
|
|
} |
|
|
|
Map<String, String> regionNameMap = sysRegionService.listByCodes(regionCodes).stream() |
|
|
|
.collect(Collectors.toMap(SysRegionDTO::getCode, SysRegionDTO::getName, (a, b) -> a)); |
|
|
|
dtos.forEach(d -> { |
|
|
|
d.setProvinceName(regionNameMap.get(d.getProvinceCode())); |
|
|
|
d.setCityName(regionNameMap.get(d.getCityCode())); |
|
|
|
}); |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
|