From 65fc1c5ffb5d9581cef99451f7e27a237132615d Mon Sep 17 00:00:00 2001 From: luoweijian <1329394916@qq.com> Date: Tue, 25 Aug 2026 13:59:41 +0800 Subject: [PATCH] =?UTF-8?q?feat(opportunity):=20=E7=A5=A810=20=E8=AF=BB?= =?UTF-8?q?=E4=BE=A7=E5=9B=9E=E6=98=BE=E5=AD=97=E5=85=B8/=E9=83=A8?= =?UTF-8?q?=E9=97=A8/=E5=8C=BA=E5=88=92=E5=90=8D=20(=E5=AF=B9=E7=A7=B0=20c?= =?UTF-8?q?rm-lead)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 注入 DictQueryService/ISysDeptService/ISysRegionService - fillDictNames: opp_source/industry_code 按 (groupCode,code) 批量查名 (PRD §10 契约) - fillDeptNames: owner_dept_id → SysDept.deptName - fillRegionNames: province_code/city_code → SysRegion.name - DTO 仅 oppSourceName/industryName 有 Name 位, oppType/bidForm/localityType 无位不填 - 新增回显名测试, 130 全绿 --- .scratch/opportunity-module/map.md | 4 +- .../query/impl/OpportunityViewQueryImpl.java | 89 +++++++++++++++++-- .../impl/OpportunityViewQueryImplTest.java | 57 ++++++++++++ 3 files changed, 141 insertions(+), 9 deletions(-) diff --git a/.scratch/opportunity-module/map.md b/.scratch/opportunity-module/map.md index d973bbe..a286ff7 100644 --- a/.scratch/opportunity-module/map.md +++ b/.scratch/opportunity-module/map.md @@ -30,7 +30,7 @@ | 11 | createCmd customerId | ⛔ 卡 A4 | | 12 | 商机侧新增入口关联线索校验 | ⛔ 实质卡(建商机入口 Service 未建=依赖票 02/11 → A4) | | 13 | 商机-客户关联子表 | ⚠ 部分(实体 `OpportunityCustomer`+约束已落;读写入口/下拉选客户卡 A4) | -| 14 | 自定义视图 + 内置视图族 | 🔨 底座已落(team `participated` / focus `followed` / view_log `recent` 三表 + `TeamMemberPermission` 枚举已建);**视图组织 + crm-preference 自定义视图待开工**(依赖 10,接在 10 之后) | +| 14 | 自定义视图 + 内置视图族 | ✅ 主体已落(底座三表 team/focus/view_log + `TeamMemberPermission` 枚举;**内置六视图**在 crm-opportunity 枚举/ViewQuery 硬编码,票 10 主体已落;**自定义视图 = crm-preference 平台能力已落**:`UserSavedView` 实体 + Mapper + `SavedView`/`SavedViewCondition` DTO + `SavedViewService` 契约 list/save/delete/setDefault + Impl(默认视图单值互斥) + Controller `/api/preference/view` + 16 单测);**剩商机侧接线尾巴**:销售机会工作区读默认视图 / 把 filter_json 条件翻译成 OpportunityPageParam 过滤(字段池 = 票 10 PageParam 可筛选列,依赖票 10 回显尾巴一起收) | **前沿小结**:商机模块**结构层已全部落地**(主表 + 状态机 + 三族规则 + 六子表 + 视图数据源三表 team/focus/view_log)。剩余:① **票 10 查询层 + 票 14 视图组织/自定义视图 = 可独立开工的读侧大票(不卡 A4)** → 建议每票清上下文单独 `/implement`(底座表已备);② **票 02/11/12/13 读写入口 = 卡 A4 客户模块**(建商机核心逻辑需写关联客户主标记子表 customer_id),挂起等 A4。 @@ -87,7 +87,7 @@ -- **自定义视图实现落地(票 14 传出)** — `crm-preference` 新契约方法签名 `list/save/delete/setDefault` + `user_saved_view` 表 DDL;商机列表**可筛选列字段池清单**(字段池归商机模块,实现阶段列全)。均为实现阶段工作,本 spec-only effort 不展开。 +- **自定义视图实现落地(票 14 传出)** — ✅ **crm-preference 侧已落**:`user_saved_view` 表(`UserSavedView` 实体,uk(user,scope,view_id))+ 契约 `list/save/delete/setDefault`(Impl 默认视图单值互斥 + 16 单测)+ Controller `/api/preference/view`。**剩商机侧接线尾巴**:销售机会工作区进入时读默认视图、把 `filter_json` 条件列表翻译成 `OpportunityPageParam` 过滤(可筛选列字段池 = 票 10 `OpportunityPageParam` 已实现的列)—— 与票 10 回显尾巴(字典名 / 节点名)一起收。 - **客户关联子表的形态** — 已钉死为 **票 13**(见下 Decisions);仅“下拉选客户”数据源 + 客户联系人是否本子表冗余仍挂 **A4 客户模块(未建)** 边界(票 11 占位)。 - **商机批量操作与统计接口** — 对称线索 G1–G5(批量领取/分配/关注、列表统计卡片)。等主表 + 四视图定稿后再切成票。 diff --git a/crm-opportunity/src/main/java/com/crm/opportunity/query/impl/OpportunityViewQueryImpl.java b/crm-opportunity/src/main/java/com/crm/opportunity/query/impl/OpportunityViewQueryImpl.java index dde0a87..7e0e41a 100644 --- a/crm-opportunity/src/main/java/com/crm/opportunity/query/impl/OpportunityViewQueryImpl.java +++ b/crm-opportunity/src/main/java/com/crm/opportunity/query/impl/OpportunityViewQueryImpl.java @@ -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 回显。
* - *票 10 §二数值计算列(节点停留/最近跟进/方案卡状态/方案预算)已批量 join 填充(无 N+1)。
+ *票 10 §二数值计算列(节点停留/最近跟进/方案卡状态/方案预算)已批量 join 填充(无 N+1); + * 字典/部门/区划名回显(DictQueryService/SysDeptService/SysRegionService,对称 crm-lead fillDisplayFields) + * 已落。
* - *TODO(后续步骤):① 字典/部门/区划名回显(DictQueryService/SysDeptService/SysRegionService, - * 对称 crm-lead fillDisplayFields);② 当前/下一节点名(依赖 crm-rule 阶段模板服务);③ RECENT 视图按 - * {@code opportunity_view_log.last_view_time} 倒序取上限(当前 RECENT 先按 create_time 兜底排序,待接 - * view_log join);④ PARTICIPATED 团队白名单可见性叠加详情链路。
+ *TODO(后续步骤):① 当前/下一节点名(依赖 crm-rule 阶段模板服务);② PARTICIPATED + * 团队白名单可见性叠加详情链路。RECENT 视图已按 {@code opportunity_view_log.last_view_time} 倒序取上限 + * (见 {@link #pageRecent})。
*/ @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)。 - *TODO 后续:字典/部门/区划名 + 当前/下一节点名(依赖 crm-rule 模板服务)。
+ * 出参回显字段填充:followed + 票 10 §二数值计算列(批量,无 N+1)+ 字典/部门/区划名。 + *字典名对称 crm-lead {@code fillDictNames}:按 (groupCode, code) 批量查(同页去重); + * 部门名 = {@code owner_dept_id} → SysDept;区划名 = province/city → SysRegion。
*/ private void fillDisplayFields(ListgroupCode 契约见 PRD §10 字典清单({@code opp_source} / {@code industry_code})。 + * 走 {@link DictQueryService#getItem}(含停用项,扛历史数据回显,Caffeine 缓存), + * 按 (分组, code) 去重后逐个查,同一页内重复值只查一次。DTO 仅暴露 + * {@code oppSourceName}/{@code industryName} 两回显位(oppType/bidForm/localityType 无 Name 位,不填)。
+ */ + private void fillDictNames(List