From 83299113e7cfb87e068c3e87ab47be3e72dfb7d0 Mon Sep 17 00:00:00 2001
From: luoweijian <1329394916@qq.com>
Date: Tue, 25 Aug 2026 22:11:09 +0800
Subject: [PATCH] =?UTF-8?q?refactor(lead/opportunity):=20ConvertibleLeadCa?=
=?UTF-8?q?talogPort=20=E6=94=B6=E7=AA=84=E8=B7=A8=E5=9F=9F=E5=A5=91?=
=?UTF-8?q?=E7=BA=A6=EF=BC=88=E6=9E=B6=E6=9E=84=E5=AE=A1=E6=9F=A5=E5=80=99?=
=?UTF-8?q?=E9=80=892=EF=BC=89?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- crm-lead 新增 port/inbound:ConvertibleLeadCatalogPort(单方法)+
ConvertibleLeadView(随端口同包)+ ConvertibleLeadCatalogPortImpl
(逻辑自 LeadServiceImpl 原样迁入,票 12 口径不变)
- 删 ILeadService.listConvertibleLeads + LeadServiceImpl 实现 +
domain/dto/ConvertibleLeadView + LeadConvertibleQueryTest
(宽服务接口不再挂为外域服务的方法,商机侧不再 import ILeadService)
- 商机侧 4 处改注入端口:CreateServiceImpl / IOpportunityCreateService /
CreateController / 测试;占用过滤(查 opportunity.source_lead_id)留商机侧
- 验证:crm-lead 119 + crm-opportunity 165 全绿
---
.../inbound/ConvertibleLeadCatalogPort.java | 26 ++++++++++++++
.../inbound}/ConvertibleLeadView.java | 8 +++--
.../impl/ConvertibleLeadCatalogPortImpl.java | 36 +++++++++++++++++++
.../com/crm/lead/service/ILeadService.java | 10 ------
.../lead/service/impl/LeadServiceImpl.java | 11 ------
.../ConvertibleLeadCatalogPortImplTest.java} | 33 ++++++++---------
.../OpportunityCreateController.java | 2 +-
.../service/IOpportunityCreateService.java | 12 ++++---
.../impl/OpportunityCreateServiceImpl.java | 14 ++++----
.../OpportunityCreateServiceImplTest.java | 11 +++---
10 files changed, 102 insertions(+), 61 deletions(-)
create mode 100644 crm-lead/src/main/java/com/crm/lead/port/inbound/ConvertibleLeadCatalogPort.java
rename crm-lead/src/main/java/com/crm/lead/{domain/dto => port/inbound}/ConvertibleLeadView.java (57%)
create mode 100644 crm-lead/src/main/java/com/crm/lead/port/inbound/impl/ConvertibleLeadCatalogPortImpl.java
rename crm-lead/src/test/java/com/crm/lead/{service/impl/LeadConvertibleQueryTest.java => port/inbound/ConvertibleLeadCatalogPortImplTest.java} (66%)
diff --git a/crm-lead/src/main/java/com/crm/lead/port/inbound/ConvertibleLeadCatalogPort.java b/crm-lead/src/main/java/com/crm/lead/port/inbound/ConvertibleLeadCatalogPort.java
new file mode 100644
index 0000000..9c9cb1d
--- /dev/null
+++ b/crm-lead/src/main/java/com/crm/lead/port/inbound/ConvertibleLeadCatalogPort.java
@@ -0,0 +1,26 @@
+package com.crm.lead.port.inbound;
+
+import java.util.List;
+
+/**
+ * 可转商机候选线索目录入站端口(票 12 数据源 / Candidate #2:商机侧消费的窄查询契约)。
+ *
+ *
商机侧新建入口(oppSource=线索转入)的【关联线索】下拉数据源。此前商机侧 import
+ * 线索域宽服务接口 {@code ILeadService}(20+ 方法)仅为此一处查询——本端口把契约收窄到
+ * 单方法,宽接口退回线索域内部(ADR-0031,对称 {@code port/outbound} 的
+ * {@code OpportunityCreationPort}:出站契约 lead 声明他域实现,入站契约本域实现他域调用)。
+ *
+ * 职责边界(同 ADR-0025):准入 = 归属用户名下 {@code status IN (已领取 3 / 跟进中 4)}
+ * (与转商机前置对称,线索域判定);「未被商机占用」过滤由商机侧补(本端口不知
+ * opportunity 表)。
+ */
+public interface ConvertibleLeadCatalogPort {
+
+ /**
+ * 列出该用户可关联到商机的候选线索。
+ *
+ * @param ownerUserId 归属用户 id(商机侧传当前登录人)
+ * @return 轻量投影(id/名称/电话),无候选时返回空列表
+ */
+ List listConvertibleLeads(Long ownerUserId);
+}
diff --git a/crm-lead/src/main/java/com/crm/lead/domain/dto/ConvertibleLeadView.java b/crm-lead/src/main/java/com/crm/lead/port/inbound/ConvertibleLeadView.java
similarity index 57%
rename from crm-lead/src/main/java/com/crm/lead/domain/dto/ConvertibleLeadView.java
rename to crm-lead/src/main/java/com/crm/lead/port/inbound/ConvertibleLeadView.java
index c76b197..296ce78 100644
--- a/crm-lead/src/main/java/com/crm/lead/domain/dto/ConvertibleLeadView.java
+++ b/crm-lead/src/main/java/com/crm/lead/port/inbound/ConvertibleLeadView.java
@@ -1,11 +1,13 @@
-package com.crm.lead.domain.dto;
+package com.crm.lead.port.inbound;
/**
* 可转商机候选线索视图(票 12:商机侧新建入口「关联线索」下拉数据源)。
*
* 轻量投影,仅够下拉展示 + 回填:线索 id + 名称 + 电话。候选准入
- * (status IN 已领取/跟进中)由 {@code ILeadService.listConvertibleLeads} 在线索域判定;
- * 「未被商机占用」过滤由商机侧补(查 opportunity.source_lead_id)。
+ * (status IN 已领取/跟进中)由 {@link ConvertibleLeadCatalogPort} 在线索域判定;
+ * 「未被商机占用」过滤由商机侧补(查 opportunity.source_lead_id)。
+ * 随端口同包(ADR-0031,对称 port/outbound 契约 cmd/exception 同包先例)——
+ * 本包类型是线索域对商机侧发布的契约面,商机侧不得 import 线索域 service/domain 内部包。
*
* @param id 线索 id(回填 source_lead_id)
* @param leadName 线索名称(下拉展示)
diff --git a/crm-lead/src/main/java/com/crm/lead/port/inbound/impl/ConvertibleLeadCatalogPortImpl.java b/crm-lead/src/main/java/com/crm/lead/port/inbound/impl/ConvertibleLeadCatalogPortImpl.java
new file mode 100644
index 0000000..425b6e3
--- /dev/null
+++ b/crm-lead/src/main/java/com/crm/lead/port/inbound/impl/ConvertibleLeadCatalogPortImpl.java
@@ -0,0 +1,36 @@
+package com.crm.lead.port.inbound.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.crm.lead.constant.LeadConstants;
+import com.crm.lead.domain.entity.Lead;
+import com.crm.lead.mapper.LeadMapper;
+import com.crm.lead.port.inbound.ConvertibleLeadCatalogPort;
+import com.crm.lead.port.inbound.ConvertibleLeadView;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+/**
+ * 可转商机候选线索目录端口实现(逻辑自 {@code LeadServiceImpl.listConvertibleLeads}
+ * 原样迁入,票 12 口径不变:归属 + status IN (已领取 3 / 跟进中 4))。
+ *
+ * 窄查询契约的实现(ADR-0031):直查 {@code LeadMapper},不复用宽服务接口;
+ * 「未被商机占用」过滤留商机侧(本实现不知 opportunity 表)。
+ */
+@Service
+@RequiredArgsConstructor
+public class ConvertibleLeadCatalogPortImpl implements ConvertibleLeadCatalogPort {
+
+ private final LeadMapper leadMapper;
+
+ @Override
+ public List listConvertibleLeads(Long ownerUserId) {
+ List leads = leadMapper.selectList(new LambdaQueryWrapper()
+ .eq(Lead::getOwnerUserId, ownerUserId)
+ .in(Lead::getStatus, LeadConstants.STATUS_CLAIMED, LeadConstants.STATUS_FOLLOWING));
+ return leads.stream()
+ .map(l -> new ConvertibleLeadView(l.getId(), l.getLeadName(), l.getPhone()))
+ .toList();
+ }
+}
diff --git a/crm-lead/src/main/java/com/crm/lead/service/ILeadService.java b/crm-lead/src/main/java/com/crm/lead/service/ILeadService.java
index 0b60cc2..13b9ff8 100644
--- a/crm-lead/src/main/java/com/crm/lead/service/ILeadService.java
+++ b/crm-lead/src/main/java/com/crm/lead/service/ILeadService.java
@@ -2,7 +2,6 @@ package com.crm.lead.service;
import com.crm.base.domain.result.PageResult;
import com.crm.base.domain.result.BatchResult;
-import com.crm.lead.domain.dto.ConvertibleLeadView;
import com.crm.lead.domain.dto.LeadDTO;
import com.crm.lead.domain.dto.LeadBatchFailItem;
import com.crm.lead.domain.dto.LeadFeedbackDTO;
@@ -34,15 +33,6 @@ public interface ILeadService {
/** 视图统计卡片(与 {@link #pageLeads} 同一套筛选口径,口径随视图,ADR-0023) */
LeadStatsDTO countStats(LeadPageParam param);
- /**
- * 可转商机候选线索(票 12:商机侧新建入口【关联线索】下拉数据源)。
- * 准入 = 该用户名下 {@code status IN (已领取 3 / 跟进中 4)} 的线索(对齐转商机准入)。
- * “未被商机占用”过滤由商机侧补(本方法不知 opportunity 表)。
- *
- * @param ownerUserId 归属用户 id(商机侧传当前登录人)
- */
- List listConvertibleLeads(Long ownerUserId);
-
// ==================== 创建/编辑/删除 ====================
/**
diff --git a/crm-lead/src/main/java/com/crm/lead/service/impl/LeadServiceImpl.java b/crm-lead/src/main/java/com/crm/lead/service/impl/LeadServiceImpl.java
index f1f2532..c80a475 100644
--- a/crm-lead/src/main/java/com/crm/lead/service/impl/LeadServiceImpl.java
+++ b/crm-lead/src/main/java/com/crm/lead/service/impl/LeadServiceImpl.java
@@ -14,7 +14,6 @@ import com.crm.lead.creation.LeadCreateContext;
import com.crm.lead.creation.LeadCreationPlanner;
import com.crm.lead.deadline.DeadlineBudget;
import com.crm.lead.deadline.LeadDeadlines;
-import com.crm.lead.domain.dto.ConvertibleLeadView;
import com.crm.lead.domain.dto.LeadDTO;
import com.crm.lead.domain.dto.LeadBatchFailItem;
import com.crm.lead.domain.dto.LeadFeedbackDTO;
@@ -105,16 +104,6 @@ public class LeadServiceImpl extends BaseServiceImpl
return leadViewQuery.countStats(param);
}
- @Override
- public List listConvertibleLeads(Long ownerUserId) {
- List leads = getBaseMapper().selectList(new LambdaQueryWrapper()
- .eq(Lead::getOwnerUserId, ownerUserId)
- .in(Lead::getStatus, LeadConstants.STATUS_CLAIMED, LeadConstants.STATUS_FOLLOWING));
- return leads.stream()
- .map(l -> new ConvertibleLeadView(l.getId(), l.getLeadName(), l.getPhone()))
- .toList();
- }
-
// ==================== 创建/编辑/删除 ====================
@Override
diff --git a/crm-lead/src/test/java/com/crm/lead/service/impl/LeadConvertibleQueryTest.java b/crm-lead/src/test/java/com/crm/lead/port/inbound/ConvertibleLeadCatalogPortImplTest.java
similarity index 66%
rename from crm-lead/src/test/java/com/crm/lead/service/impl/LeadConvertibleQueryTest.java
rename to crm-lead/src/test/java/com/crm/lead/port/inbound/ConvertibleLeadCatalogPortImplTest.java
index 961273e..d49edee 100644
--- a/crm-lead/src/test/java/com/crm/lead/service/impl/LeadConvertibleQueryTest.java
+++ b/crm-lead/src/test/java/com/crm/lead/port/inbound/ConvertibleLeadCatalogPortImplTest.java
@@ -1,43 +1,38 @@
-package com.crm.lead.service.impl;
+package com.crm.lead.port.inbound;
import com.crm.lead.constant.LeadConstants;
-import com.crm.lead.domain.dto.ConvertibleLeadView;
import com.crm.lead.domain.entity.Lead;
import com.crm.lead.mapper.LeadMapper;
+import com.crm.lead.port.inbound.impl.ConvertibleLeadCatalogPortImpl;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
-import org.mockito.ArgumentCaptor;
+import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
-import org.springframework.test.util.ReflectionTestUtils;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
/**
- * 票 12:可转商机候选线索查询({@link LeadServiceImpl#listConvertibleLeads})规格验证。
+ * 票 12 / Candidate #2:可转商机候选线索目录端口
+ * ({@link ConvertibleLeadCatalogPortImpl},逻辑自 LeadServiceImpl 原样迁入)规格验证。
* 准入 = 该用户名下 status IN (已领取 3 / 跟进中 4);投影为轻量 view。
*/
@ExtendWith(MockitoExtension.class)
-@DisplayName("票12 可转商机候选线索查询")
-class LeadConvertibleQueryTest {
+@DisplayName("票12 可转商机候选线索目录端口")
+class ConvertibleLeadCatalogPortImplTest {
private static final Long OWNER = 100L;
- @Mock private LeadMapper leadMapper;
+ @Mock
+ private LeadMapper leadMapper;
- private LeadServiceImpl service() {
- // 仅测 listConvertibleLeads:其余构造依赖本方法不触及,传 null 即可
- LeadServiceImpl svc = new LeadServiceImpl(
- null, null, null, null, null, null, null, null, null, null, null);
- ReflectionTestUtils.setField(svc, "baseMapper", leadMapper);
- return svc;
- }
+ @InjectMocks
+ private ConvertibleLeadCatalogPortImpl catalog;
private Lead lead(Long id, String name, String phone) {
Lead l = new Lead();
@@ -54,7 +49,7 @@ class LeadConvertibleQueryTest {
lead(1L, "线索甲", "13800000001"),
lead(2L, "线索乙", "13800000002")));
- List views = service().listConvertibleLeads(OWNER);
+ List views = catalog.listConvertibleLeads(OWNER);
assertThat(views).extracting(ConvertibleLeadView::id).containsExactly(1L, 2L);
assertThat(views).extracting(ConvertibleLeadView::leadName).containsExactly("线索甲", "线索乙");
@@ -62,7 +57,7 @@ class LeadConvertibleQueryTest {
}
@Test
- @DisplayName("准入状态常量对齐:STATUS_CLAIMED=3 / STATUS_FOLLOWING=4")
+ @DisplayName("准入状态常量对齐:STATUS_CLAIMED=3 / STATUS_FOLLOWING=4(与转商机前置对称)")
void statusConstantsAlignWithConvertPrecondition() {
assertThat(LeadConstants.STATUS_CLAIMED).isEqualTo(3);
assertThat(LeadConstants.STATUS_FOLLOWING).isEqualTo(4);
@@ -73,6 +68,6 @@ class LeadConvertibleQueryTest {
void listConvertibleLeads_emptyWhenNoCandidates() {
when(leadMapper.selectList(any())).thenReturn(List.of());
- assertThat(service().listConvertibleLeads(OWNER)).isEmpty();
+ assertThat(catalog.listConvertibleLeads(OWNER)).isEmpty();
}
}
diff --git a/crm-opportunity/src/main/java/com/crm/opportunity/controller/OpportunityCreateController.java b/crm-opportunity/src/main/java/com/crm/opportunity/controller/OpportunityCreateController.java
index 29c9cb3..252ee89 100644
--- a/crm-opportunity/src/main/java/com/crm/opportunity/controller/OpportunityCreateController.java
+++ b/crm-opportunity/src/main/java/com/crm/opportunity/controller/OpportunityCreateController.java
@@ -2,7 +2,7 @@ package com.crm.opportunity.controller;
import com.crm.base.domain.result.Result;
import com.crm.base.security.SecurityUtils;
-import com.crm.lead.domain.dto.ConvertibleLeadView;
+import com.crm.lead.port.inbound.ConvertibleLeadView;
import com.crm.opportunity.domain.dto.CreateOpportunityRequest;
import com.crm.opportunity.service.IOpportunityCreateService;
import io.swagger.v3.oas.annotations.Operation;
diff --git a/crm-opportunity/src/main/java/com/crm/opportunity/service/IOpportunityCreateService.java b/crm-opportunity/src/main/java/com/crm/opportunity/service/IOpportunityCreateService.java
index 307f576..97e7118 100644
--- a/crm-opportunity/src/main/java/com/crm/opportunity/service/IOpportunityCreateService.java
+++ b/crm-opportunity/src/main/java/com/crm/opportunity/service/IOpportunityCreateService.java
@@ -1,5 +1,6 @@
package com.crm.opportunity.service;
+import com.crm.lead.port.inbound.ConvertibleLeadView;
import com.crm.opportunity.domain.dto.CreateOpportunityRequest;
import java.util.List;
@@ -7,9 +8,9 @@ import java.util.List;
/**
* 商机侧直接新建商机服务(票 12:新建入口 A3-1-1-2-1)。
*
- * 与线索侧转商机 port 并列的第二条建商机入口。核心建商机逻辑(主表 + 阶段落位 +
- * 初始日志 + 团队成员,票 02/16)两条入口对称;本入口额外负责 oppSource 驱动的
- * 关联线索必填校验 + source_lead_id 唯一性预检。
+ * 与线索侧转商机 port 并列的第二条建档入口。核心建档逻辑收敛于深模块 OpportunityIntake
+ * (ADR-0030);本入口额外负责 oppSource 驱动的关联线索必填校验;候选线索准入由
+ * crm-lead 入站端口 {@code ConvertibleLeadCatalogPort} 提供(ADR-0031),本侧补占用过滤。
*/
public interface IOpportunityCreateService {
@@ -23,9 +24,10 @@ public interface IOpportunityCreateService {
Long createOpportunity(CreateOpportunityRequest request, Long ownerUserId);
/**
- * 可关联线索下拉候选(票 12):该用户 status IN(3,4) 且未被任何商机占用的线索。
+ * 可关联线索下拉候选(票 12):该用户 status IN(3,4) 且未被任何商机占用的线索
+ * (准入来自 {@code ConvertibleLeadCatalogPort},占用过滤在本侧)。
*
* @param ownerUserId 当前登录人
*/
- List listConvertibleLeads(Long ownerUserId);
+ List listConvertibleLeads(Long ownerUserId);
}
diff --git a/crm-opportunity/src/main/java/com/crm/opportunity/service/impl/OpportunityCreateServiceImpl.java b/crm-opportunity/src/main/java/com/crm/opportunity/service/impl/OpportunityCreateServiceImpl.java
index 29cfa65..cda4e67 100644
--- a/crm-opportunity/src/main/java/com/crm/opportunity/service/impl/OpportunityCreateServiceImpl.java
+++ b/crm-opportunity/src/main/java/com/crm/opportunity/service/impl/OpportunityCreateServiceImpl.java
@@ -2,8 +2,8 @@ package com.crm.opportunity.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.crm.base.domain.exception.BusinessErrorException;
-import com.crm.lead.domain.dto.ConvertibleLeadView;
-import com.crm.lead.service.ILeadService;
+import com.crm.lead.port.inbound.ConvertibleLeadCatalogPort;
+import com.crm.lead.port.inbound.ConvertibleLeadView;
import com.crm.opportunity.domain.dto.CreateOpportunityRequest;
import com.crm.opportunity.domain.entity.Opportunity;
import com.crm.opportunity.intake.IntakeSource;
@@ -34,9 +34,9 @@ import java.util.stream.Collectors;
* 事务:本层不再标 @Transactional——{@code open} 自带 REQUIRED 事务保证原子性。
* ownerDeptId 传 null:直接新建不带部门快照,阶段模板走默认兜底(票 15),与收敛前行为一致。
*
- * 候选线索(票 12 架构甲):准入(status IN 3/4)由 crm-lead
- * {@link ILeadService#listConvertibleLeads} 在线索域判定;「未被商机占用」由本侧补
- * (查 opportunity.source_lead_id),职责边界干净。此能力属于 Candidate #2 范围,维持原状。
+ * 候选线索(票 12 架构甲):准入(status IN 3/4)由 crm-lead 入站端口
+ * {@link ConvertibleLeadCatalogPort} 在线索域判定(ADR-0031,不再 import 宽服务接口);
+ * 「未被商机占用」由本侧补(查 opportunity.source_lead_id),职责边界干净。
*/
@Service
@RequiredArgsConstructor
@@ -44,7 +44,7 @@ public class OpportunityCreateServiceImpl implements IOpportunityCreateService {
private final OpportunityIntake intake;
private final OpportunityMapper oppMapper;
- private final ILeadService leadService;
+ private final ConvertibleLeadCatalogPort convertibleLeadCatalog;
@Override
public Long createOpportunity(CreateOpportunityRequest request, Long ownerUserId) {
@@ -80,7 +80,7 @@ public class OpportunityCreateServiceImpl implements IOpportunityCreateService {
@Override
public List listConvertibleLeads(Long ownerUserId) {
- List candidates = leadService.listConvertibleLeads(ownerUserId);
+ List candidates = convertibleLeadCatalog.listConvertibleLeads(ownerUserId);
if (candidates.isEmpty()) {
return candidates;
}
diff --git a/crm-opportunity/src/test/java/com/crm/opportunity/service/impl/OpportunityCreateServiceImplTest.java b/crm-opportunity/src/test/java/com/crm/opportunity/service/impl/OpportunityCreateServiceImplTest.java
index aa77d2d..b0872fc 100644
--- a/crm-opportunity/src/test/java/com/crm/opportunity/service/impl/OpportunityCreateServiceImplTest.java
+++ b/crm-opportunity/src/test/java/com/crm/opportunity/service/impl/OpportunityCreateServiceImplTest.java
@@ -3,8 +3,8 @@ package com.crm.opportunity.service.impl;
import com.baomidou.mybatisplus.core.MybatisConfiguration;
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
import com.crm.base.domain.exception.BusinessErrorException;
-import com.crm.lead.domain.dto.ConvertibleLeadView;
-import com.crm.lead.service.ILeadService;
+import com.crm.lead.port.inbound.ConvertibleLeadCatalogPort;
+import com.crm.lead.port.inbound.ConvertibleLeadView;
import com.crm.opportunity.domain.dto.CreateOpportunityRequest;
import com.crm.opportunity.domain.entity.Opportunity;
import com.crm.opportunity.intake.IntakeSource;
@@ -37,7 +37,8 @@ import static org.mockito.Mockito.when;
* 覆盖:oppSource=线索转入时 sourceLeadId 必填校验(不进深模块)、
* {@code CreateOpportunityRequest} → {@code OpportunityIntakeSpec} 全字段映射、
* {@code OpportunityIntakeException} → {@code BusinessErrorException} seam 翻译、
- * 候选线索剔除已占用。建档五步机制与唯一性防护的断言已上移
+ * 候选线索剔除已占用(准入来自 {@code ConvertibleLeadCatalogPort},ADR-0031)。
+ * 建档五步机制与唯一性防护的断言已上移
* {@code OpportunityIntakeImplTest},本类不再重复。
*/
@ExtendWith(MockitoExtension.class)
@@ -51,7 +52,7 @@ class OpportunityCreateServiceImplTest {
@Mock private OpportunityIntake intake;
@Mock private OpportunityMapper oppMapper;
- @Mock private ILeadService leadService;
+ @Mock private ConvertibleLeadCatalogPort convertibleLeadCatalog;
@InjectMocks private OpportunityCreateServiceImpl service;
@@ -131,7 +132,7 @@ class OpportunityCreateServiceImplTest {
@Test
@DisplayName("候选线索:剔除已被商机占用的 source_lead_id")
void listConvertibleLeads_excludesTaken() {
- when(leadService.listConvertibleLeads(OWNER)).thenReturn(List.of(
+ when(convertibleLeadCatalog.listConvertibleLeads(OWNER)).thenReturn(List.of(
new ConvertibleLeadView(1L, "线索甲", "138"),
new ConvertibleLeadView(2L, "线索乙", "139")));
// 商机侧查已占用:lead 2 已被占用