feat(opportunity): 新增全量商机查询内部接口
1. 新增OpportunityFullDTO封装全量商机精简字段 2. 实现listAllOpportunities接口及Mapper、SQL查询,返回全量商机不含数据权限过滤 3. 新增/api/opportunities/all内部接口,支持X-Internal-Secret鉴权 4. 配置放行接口路径到生产和开发环境配置文件 5. 补充完整接口文档说明main
parent
010d3052c8
commit
8477aac22f
|
|
@ -5,41 +5,13 @@
|
||||||
</component>
|
</component>
|
||||||
<component name="ChangeListManager">
|
<component name="ChangeListManager">
|
||||||
<list default="true" id="4c558d98-824e-4a48-ba48-bd2e6172f9f4" name="更改" comment="修改定位信息 0323">
|
<list default="true" id="4c558d98-824e-4a48-ba48-bd2e6172f9f4" name="更改" comment="修改定位信息 0323">
|
||||||
<change beforePath="$PROJECT_DIR$/backend/src/main/java/com/unis/crm/controller/OmsCallbackController.java" beforeDir="false" afterPath="$PROJECT_DIR$/backend/src/main/java/com/unis/crm/controller/OmsCallbackController.java" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/backend/src/main/java/com/unis/crm/controller/OpportunityController.java" beforeDir="false" afterPath="$PROJECT_DIR$/backend/src/main/java/com/unis/crm/controller/OpportunityController.java" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/backend/src/main/java/com/unis/crm/dto/opportunity/OpportunityDuplicateCheckDTO.java" beforeDir="false" afterPath="$PROJECT_DIR$/backend/src/main/java/com/unis/crm/dto/opportunity/OpportunityDuplicateCheckDTO.java" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/backend/src/main/java/com/unis/crm/llm/mapper/LlmMcpMapper.java" beforeDir="false" afterPath="$PROJECT_DIR$/backend/src/main/java/com/unis/crm/llm/mapper/LlmMcpMapper.java" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/backend/src/main/java/com/unis/crm/llm/security/McpDataPermissionInterceptor.java" beforeDir="false" afterPath="$PROJECT_DIR$/backend/src/main/java/com/unis/crm/llm/security/McpDataPermissionInterceptor.java" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/backend/src/main/java/com/unis/crm/llm/tools/CrmCheckinSearchToolProvider.java" beforeDir="false" afterPath="$PROJECT_DIR$/backend/src/main/java/com/unis/crm/llm/tools/CrmCheckinSearchToolProvider.java" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/backend/src/main/java/com/unis/crm/llm/tools/CrmDetailSearchToolProvider.java" beforeDir="false" afterPath="$PROJECT_DIR$/backend/src/main/java/com/unis/crm/llm/tools/CrmDetailSearchToolProvider.java" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/backend/src/main/java/com/unis/crm/llm/tools/CrmDictOptionsToolProvider.java" beforeDir="false" afterPath="$PROJECT_DIR$/backend/src/main/java/com/unis/crm/llm/tools/CrmDictOptionsToolProvider.java" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/backend/src/main/java/com/unis/crm/llm/tools/CrmEntityDetailToolProvider.java" beforeDir="false" afterPath="$PROJECT_DIR$/backend/src/main/java/com/unis/crm/llm/tools/CrmEntityDetailToolProvider.java" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/backend/src/main/java/com/unis/crm/llm/tools/CrmExpansionSearchToolProvider.java" beforeDir="false" afterPath="$PROJECT_DIR$/backend/src/main/java/com/unis/crm/llm/tools/CrmExpansionSearchToolProvider.java" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/backend/src/main/java/com/unis/crm/llm/tools/CrmFollowupSearchToolProvider.java" beforeDir="false" afterPath="$PROJECT_DIR$/backend/src/main/java/com/unis/crm/llm/tools/CrmFollowupSearchToolProvider.java" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/backend/src/main/java/com/unis/crm/llm/tools/CrmMetadataToolProvider.java" beforeDir="false" afterPath="$PROJECT_DIR$/backend/src/main/java/com/unis/crm/llm/tools/CrmMetadataToolProvider.java" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/backend/src/main/java/com/unis/crm/llm/tools/CrmOrgUserSearchToolProvider.java" beforeDir="false" afterPath="$PROJECT_DIR$/backend/src/main/java/com/unis/crm/llm/tools/CrmOrgUserSearchToolProvider.java" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/backend/src/main/java/com/unis/crm/llm/tools/CrmReportCatalogToolProvider.java" beforeDir="false" afterPath="$PROJECT_DIR$/backend/src/main/java/com/unis/crm/llm/tools/CrmReportCatalogToolProvider.java" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/backend/src/main/java/com/unis/crm/llm/tools/CrmReportQueryToolProvider.java" beforeDir="false" afterPath="$PROJECT_DIR$/backend/src/main/java/com/unis/crm/llm/tools/CrmReportQueryToolProvider.java" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/backend/src/main/java/com/unis/crm/llm/tools/CrmTodoSearchToolProvider.java" beforeDir="false" afterPath="$PROJECT_DIR$/backend/src/main/java/com/unis/crm/llm/tools/CrmTodoSearchToolProvider.java" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/backend/src/main/java/com/unis/crm/llm/tools/CrmUniversalSearchToolProvider.java" beforeDir="false" afterPath="$PROJECT_DIR$/backend/src/main/java/com/unis/crm/llm/tools/CrmUniversalSearchToolProvider.java" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/backend/src/main/java/com/unis/crm/llm/tools/CrmWorkTodayStatusToolProvider.java" beforeDir="false" afterPath="$PROJECT_DIR$/backend/src/main/java/com/unis/crm/llm/tools/CrmWorkTodayStatusToolProvider.java" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/backend/src/main/java/com/unis/crm/llm/tools/OpportunitySearchToolProvider.java" beforeDir="false" afterPath="$PROJECT_DIR$/backend/src/main/java/com/unis/crm/llm/tools/OpportunitySearchToolProvider.java" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/backend/src/main/java/com/unis/crm/llm/tools/UserProfileToolProvider.java" beforeDir="false" afterPath="$PROJECT_DIR$/backend/src/main/java/com/unis/crm/llm/tools/UserProfileToolProvider.java" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/backend/src/main/java/com/unis/crm/llm/tools/WorkReportSearchToolProvider.java" beforeDir="false" afterPath="$PROJECT_DIR$/backend/src/main/java/com/unis/crm/llm/tools/WorkReportSearchToolProvider.java" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/backend/src/main/java/com/unis/crm/llm/tools/support/PermissionedMcpToolProvider.java" beforeDir="false" afterPath="$PROJECT_DIR$/backend/src/main/java/com/unis/crm/llm/tools/support/PermissionedMcpToolProvider.java" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/backend/src/main/java/com/unis/crm/mapper/OpportunityMapper.java" beforeDir="false" afterPath="$PROJECT_DIR$/backend/src/main/java/com/unis/crm/mapper/OpportunityMapper.java" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/backend/src/main/java/com/unis/crm/mapper/OpportunityMapper.java" beforeDir="false" afterPath="$PROJECT_DIR$/backend/src/main/java/com/unis/crm/mapper/OpportunityMapper.java" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/backend/src/main/java/com/unis/crm/service/OpportunityService.java" beforeDir="false" afterPath="$PROJECT_DIR$/backend/src/main/java/com/unis/crm/service/OpportunityService.java" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/backend/src/main/java/com/unis/crm/service/OpportunityService.java" beforeDir="false" afterPath="$PROJECT_DIR$/backend/src/main/java/com/unis/crm/service/OpportunityService.java" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/backend/src/main/java/com/unis/crm/service/impl/OpportunityServiceImpl.java" beforeDir="false" afterPath="$PROJECT_DIR$/backend/src/main/java/com/unis/crm/service/impl/OpportunityServiceImpl.java" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/backend/src/main/java/com/unis/crm/service/impl/OpportunityServiceImpl.java" beforeDir="false" afterPath="$PROJECT_DIR$/backend/src/main/java/com/unis/crm/service/impl/OpportunityServiceImpl.java" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/backend/src/main/resources/application-prod.yml" beforeDir="false" afterPath="$PROJECT_DIR$/backend/src/main/resources/application-prod.yml" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/backend/src/main/resources/application-prod.yml" beforeDir="false" afterPath="$PROJECT_DIR$/backend/src/main/resources/application-prod.yml" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/backend/src/main/resources/application.yml" beforeDir="false" afterPath="$PROJECT_DIR$/backend/src/main/resources/application.yml" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/backend/src/main/resources/application.yml" beforeDir="false" afterPath="$PROJECT_DIR$/backend/src/main/resources/application.yml" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/backend/src/main/resources/mapper/llm/LlmMcpMapper.xml" beforeDir="false" afterPath="$PROJECT_DIR$/backend/src/main/resources/mapper/llm/LlmMcpMapper.xml" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/backend/src/main/resources/mapper/opportunity/OpportunityMapper.xml" beforeDir="false" afterPath="$PROJECT_DIR$/backend/src/main/resources/mapper/opportunity/OpportunityMapper.xml" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/backend/src/main/resources/mapper/opportunity/OpportunityMapper.xml" beforeDir="false" afterPath="$PROJECT_DIR$/backend/src/main/resources/mapper/opportunity/OpportunityMapper.xml" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/backend/src/test/java/com/unis/crm/llm/security/McpDataPermissionInterceptorTest.java" beforeDir="false" afterPath="$PROJECT_DIR$/backend/src/test/java/com/unis/crm/llm/security/McpDataPermissionInterceptorTest.java" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/backend/src/test/java/com/unis/crm/service/impl/OpportunityServiceImplTest.java" beforeDir="false" afterPath="$PROJECT_DIR$/backend/src/test/java/com/unis/crm/service/impl/OpportunityServiceImplTest.java" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/frontend/src/lib/auth.ts" beforeDir="false" afterPath="$PROJECT_DIR$/frontend/src/lib/auth.ts" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/frontend/src/pages/Opportunities.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/frontend/src/pages/Opportunities.tsx" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/frontend/src/pages/Work.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/frontend/src/pages/Work.tsx" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/sql/README.md" beforeDir="false" afterPath="$PROJECT_DIR$/sql/README.md" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/sql/init_full_pg17.sql" beforeDir="false" afterPath="$PROJECT_DIR$/sql/init_full_pg17.sql" afterDir="false" />
|
|
||||||
</list>
|
</list>
|
||||||
<option name="SHOW_DIALOG" value="false" />
|
<option name="SHOW_DIALOG" value="false" />
|
||||||
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||||
|
|
@ -139,7 +111,8 @@
|
||||||
<workItem from="1789033867432" duration="17000" />
|
<workItem from="1789033867432" duration="17000" />
|
||||||
<workItem from="1789440226191" duration="937000" />
|
<workItem from="1789440226191" duration="937000" />
|
||||||
<workItem from="1789459774783" duration="30000" />
|
<workItem from="1789459774783" duration="30000" />
|
||||||
<workItem from="1789529046094" duration="3889000" />
|
<workItem from="1789529046094" duration="5499000" />
|
||||||
|
<workItem from="1789969706373" duration="858000" />
|
||||||
</task>
|
</task>
|
||||||
<task id="LOCAL-00001" summary="修改定位信息 0323">
|
<task id="LOCAL-00001" summary="修改定位信息 0323">
|
||||||
<option name="closed" value="true" />
|
<option name="closed" value="true" />
|
||||||
|
|
|
||||||
|
|
@ -2,18 +2,23 @@ package com.unis.crm.controller;
|
||||||
|
|
||||||
import com.unis.crm.common.ApiResponse;
|
import com.unis.crm.common.ApiResponse;
|
||||||
import com.unis.crm.common.CurrentUserUtils;
|
import com.unis.crm.common.CurrentUserUtils;
|
||||||
|
import com.unis.crm.common.UnauthorizedException;
|
||||||
|
import com.unis.crm.config.InternalAuthProperties;
|
||||||
import com.unis.crm.dto.opportunity.CreateOpportunityFollowUpRequest;
|
import com.unis.crm.dto.opportunity.CreateOpportunityFollowUpRequest;
|
||||||
import com.unis.crm.dto.opportunity.CreateOpportunityRequest;
|
import com.unis.crm.dto.opportunity.CreateOpportunityRequest;
|
||||||
import com.unis.crm.dto.opportunity.OmsPreSalesOptionDTO;
|
import com.unis.crm.dto.opportunity.OmsPreSalesOptionDTO;
|
||||||
import com.unis.crm.dto.opportunity.OpportunityDuplicateCheckDTO;
|
import com.unis.crm.dto.opportunity.OpportunityDuplicateCheckDTO;
|
||||||
|
import com.unis.crm.dto.opportunity.OpportunityFullDTO;
|
||||||
import com.unis.crm.dto.opportunity.OpportunityItemDTO;
|
import com.unis.crm.dto.opportunity.OpportunityItemDTO;
|
||||||
import com.unis.crm.dto.opportunity.OpportunityMetaDTO;
|
import com.unis.crm.dto.opportunity.OpportunityMetaDTO;
|
||||||
import com.unis.crm.dto.opportunity.OpportunityOverviewDTO;
|
import com.unis.crm.dto.opportunity.OpportunityOverviewDTO;
|
||||||
import com.unis.crm.dto.opportunity.PushOpportunityToOmsRequest;
|
import com.unis.crm.dto.opportunity.PushOpportunityToOmsRequest;
|
||||||
import com.unis.crm.service.OpportunityService;
|
import com.unis.crm.service.OpportunityService;
|
||||||
import com.unisbase.common.annotation.Log;
|
import com.unisbase.common.annotation.Log;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import jakarta.validation.Valid;
|
import jakarta.validation.Valid;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
import org.springframework.web.bind.annotation.PathVariable;
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
|
@ -28,10 +33,16 @@ import org.springframework.web.bind.annotation.RestController;
|
||||||
@RequestMapping("/api/opportunities")
|
@RequestMapping("/api/opportunities")
|
||||||
public class OpportunityController {
|
public class OpportunityController {
|
||||||
|
|
||||||
private final OpportunityService opportunityService;
|
private static final String INTERNAL_UNAUTHORIZED_MESSAGE = "内部接口鉴权失败";
|
||||||
|
|
||||||
public OpportunityController(OpportunityService opportunityService) {
|
private final OpportunityService opportunityService;
|
||||||
|
private final InternalAuthProperties internalAuthProperties;
|
||||||
|
|
||||||
|
public OpportunityController(
|
||||||
|
OpportunityService opportunityService,
|
||||||
|
InternalAuthProperties internalAuthProperties) {
|
||||||
this.opportunityService = opportunityService;
|
this.opportunityService = opportunityService;
|
||||||
|
this.internalAuthProperties = internalAuthProperties;
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/meta")
|
@GetMapping("/meta")
|
||||||
|
|
@ -39,6 +50,36 @@ public class OpportunityController {
|
||||||
return ApiResponse.success(opportunityService.getMeta());
|
return ApiResponse.success(opportunityService.getMeta());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取全量商机精简字段清单(含已归档/已签单,不做数据权限过滤)
|
||||||
|
* <p>内部接口,使用 X-Internal-Secret 鉴权。</p>
|
||||||
|
*/
|
||||||
|
@GetMapping("/all")
|
||||||
|
@Log(type = "商机集成", value = "查询全量商机")
|
||||||
|
public ApiResponse<List<OpportunityFullDTO>> listAll(HttpServletRequest httpServletRequest) {
|
||||||
|
validateInternalSecret(httpServletRequest);
|
||||||
|
return ApiResponse.success(opportunityService.listAllOpportunities());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validateInternalSecret(HttpServletRequest httpServletRequest) {
|
||||||
|
if (!internalAuthProperties.isEnabled()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String expectedSecret = trimToNull(internalAuthProperties.getSecret());
|
||||||
|
String actualSecret = trimToNull(httpServletRequest.getHeader(internalAuthProperties.resolveHeaderName()));
|
||||||
|
if (expectedSecret == null || !Objects.equals(expectedSecret, actualSecret)) {
|
||||||
|
throw new UnauthorizedException(INTERNAL_UNAUTHORIZED_MESSAGE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String trimToNull(String value) {
|
||||||
|
if (value == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
String trimmed = value.trim();
|
||||||
|
return trimmed.isEmpty() ? null : trimmed;
|
||||||
|
}
|
||||||
|
|
||||||
@GetMapping("/overview")
|
@GetMapping("/overview")
|
||||||
public ApiResponse<OpportunityOverviewDTO> getOverview(
|
public ApiResponse<OpportunityOverviewDTO> getOverview(
|
||||||
@RequestHeader("X-User-Id") Long userId,
|
@RequestHeader("X-User-Id") Long userId,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,108 @@
|
||||||
|
package com.unis.crm.dto.opportunity;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 全量商机精简字段 DTO,用于对外提供全量商机清单。
|
||||||
|
*/
|
||||||
|
public class OpportunityFullDTO {
|
||||||
|
|
||||||
|
/** 商机编号 */
|
||||||
|
private String opportunityCode;
|
||||||
|
/** 商机名称 */
|
||||||
|
private String opportunityName;
|
||||||
|
/** 商机阶段编码 */
|
||||||
|
private String stageCode;
|
||||||
|
/** 商机阶段名称 */
|
||||||
|
private String stage;
|
||||||
|
/** 把握度等级(A/B/C) */
|
||||||
|
private String confidence;
|
||||||
|
/** 是否已推送OMS */
|
||||||
|
private Boolean pushedToOms;
|
||||||
|
/** 推送OMS时间 */
|
||||||
|
private String omsPushTime;
|
||||||
|
/** 创建时间 */
|
||||||
|
private String createdAt;
|
||||||
|
/** 商机负责人ID */
|
||||||
|
private Long ownerUserId;
|
||||||
|
/** 商机负责人姓名 */
|
||||||
|
private String ownerName;
|
||||||
|
|
||||||
|
public String getOpportunityCode() {
|
||||||
|
return opportunityCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOpportunityCode(String opportunityCode) {
|
||||||
|
this.opportunityCode = opportunityCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getOpportunityName() {
|
||||||
|
return opportunityName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOpportunityName(String opportunityName) {
|
||||||
|
this.opportunityName = opportunityName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getStageCode() {
|
||||||
|
return stageCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStageCode(String stageCode) {
|
||||||
|
this.stageCode = stageCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getStage() {
|
||||||
|
return stage;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStage(String stage) {
|
||||||
|
this.stage = stage;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getConfidence() {
|
||||||
|
return confidence;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setConfidence(String confidence) {
|
||||||
|
this.confidence = confidence;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean getPushedToOms() {
|
||||||
|
return pushedToOms;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPushedToOms(Boolean pushedToOms) {
|
||||||
|
this.pushedToOms = pushedToOms;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getOmsPushTime() {
|
||||||
|
return omsPushTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOmsPushTime(String omsPushTime) {
|
||||||
|
this.omsPushTime = omsPushTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCreatedAt() {
|
||||||
|
return createdAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCreatedAt(String createdAt) {
|
||||||
|
this.createdAt = createdAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getOwnerUserId() {
|
||||||
|
return ownerUserId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOwnerUserId(Long ownerUserId) {
|
||||||
|
this.ownerUserId = ownerUserId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getOwnerName() {
|
||||||
|
return ownerName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOwnerName(String ownerName) {
|
||||||
|
this.ownerName = ownerName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -7,6 +7,7 @@ import com.unis.crm.dto.opportunity.OpportunityCustomerSnapshotDTO;
|
||||||
import com.unis.crm.dto.opportunity.OpportunityDictOptionDTO;
|
import com.unis.crm.dto.opportunity.OpportunityDictOptionDTO;
|
||||||
import com.unis.crm.dto.opportunity.OpportunityDuplicateCheckDTO;
|
import com.unis.crm.dto.opportunity.OpportunityDuplicateCheckDTO;
|
||||||
import com.unis.crm.dto.opportunity.OpportunityFollowUpDTO;
|
import com.unis.crm.dto.opportunity.OpportunityFollowUpDTO;
|
||||||
|
import com.unis.crm.dto.opportunity.OpportunityFullDTO;
|
||||||
import com.unis.crm.dto.opportunity.OpportunityIntegrationTargetDTO;
|
import com.unis.crm.dto.opportunity.OpportunityIntegrationTargetDTO;
|
||||||
import com.unis.crm.dto.opportunity.OpportunityItemDTO;
|
import com.unis.crm.dto.opportunity.OpportunityItemDTO;
|
||||||
import com.unis.crm.dto.opportunity.OpportunityOmsPushDataDTO;
|
import com.unis.crm.dto.opportunity.OpportunityOmsPushDataDTO;
|
||||||
|
|
@ -59,6 +60,11 @@ public interface OpportunityMapper {
|
||||||
@Param("excludeStageCodes") List<String> excludeStageCodes,
|
@Param("excludeStageCodes") List<String> excludeStageCodes,
|
||||||
@Param("stageCodes") List<String> stageCodes);
|
@Param("stageCodes") List<String> stageCodes);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询全量商机精简字段(含已归档/已签单,不做数据权限过滤)
|
||||||
|
*/
|
||||||
|
List<OpportunityFullDTO> selectAllOpportunities();
|
||||||
|
|
||||||
OpportunityItemDTO selectOpportunityDetail(
|
OpportunityItemDTO selectOpportunityDetail(
|
||||||
@Param("userId") Long userId,
|
@Param("userId") Long userId,
|
||||||
@Param("opportunityId") Long opportunityId,
|
@Param("opportunityId") Long opportunityId,
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import com.unis.crm.dto.opportunity.CreateOpportunityFollowUpRequest;
|
||||||
import com.unis.crm.dto.opportunity.CreateOpportunityRequest;
|
import com.unis.crm.dto.opportunity.CreateOpportunityRequest;
|
||||||
import com.unis.crm.dto.opportunity.OmsPreSalesOptionDTO;
|
import com.unis.crm.dto.opportunity.OmsPreSalesOptionDTO;
|
||||||
import com.unis.crm.dto.opportunity.OpportunityDuplicateCheckDTO;
|
import com.unis.crm.dto.opportunity.OpportunityDuplicateCheckDTO;
|
||||||
|
import com.unis.crm.dto.opportunity.OpportunityFullDTO;
|
||||||
import com.unis.crm.dto.opportunity.OpportunityItemDTO;
|
import com.unis.crm.dto.opportunity.OpportunityItemDTO;
|
||||||
import com.unis.crm.dto.opportunity.OpportunityMetaDTO;
|
import com.unis.crm.dto.opportunity.OpportunityMetaDTO;
|
||||||
import com.unis.crm.dto.opportunity.OpportunityOverviewDTO;
|
import com.unis.crm.dto.opportunity.OpportunityOverviewDTO;
|
||||||
|
|
@ -15,6 +16,11 @@ public interface OpportunityService {
|
||||||
|
|
||||||
OpportunityMetaDTO getMeta();
|
OpportunityMetaDTO getMeta();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询全量商机精简字段(含已归档/已签单,不做数据权限过滤)
|
||||||
|
*/
|
||||||
|
List<OpportunityFullDTO> listAllOpportunities();
|
||||||
|
|
||||||
OpportunityOverviewDTO getOverview(Long userId, String keyword, String stage);
|
OpportunityOverviewDTO getOverview(Long userId, String keyword, String stage);
|
||||||
|
|
||||||
OpportunityOverviewDTO getOverview(Long userId, String keyword, String stage, boolean includeDetails);
|
OpportunityOverviewDTO getOverview(Long userId, String keyword, String stage, boolean includeDetails);
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ import com.unis.crm.dto.opportunity.OpportunityDictOptionDTO;
|
||||||
import com.unis.crm.dto.opportunity.OpportunityDuplicateCheckDTO;
|
import com.unis.crm.dto.opportunity.OpportunityDuplicateCheckDTO;
|
||||||
import com.unis.crm.dto.opportunity.OpportunityMetaDTO;
|
import com.unis.crm.dto.opportunity.OpportunityMetaDTO;
|
||||||
import com.unis.crm.dto.opportunity.OpportunityFollowUpDTO;
|
import com.unis.crm.dto.opportunity.OpportunityFollowUpDTO;
|
||||||
|
import com.unis.crm.dto.opportunity.OpportunityFullDTO;
|
||||||
import com.unis.crm.dto.opportunity.OpportunityIntegrationTargetDTO;
|
import com.unis.crm.dto.opportunity.OpportunityIntegrationTargetDTO;
|
||||||
import com.unis.crm.dto.opportunity.OpportunityItemDTO;
|
import com.unis.crm.dto.opportunity.OpportunityItemDTO;
|
||||||
import com.unis.crm.dto.opportunity.OpportunityOmsPushDataDTO;
|
import com.unis.crm.dto.opportunity.OpportunityOmsPushDataDTO;
|
||||||
|
|
@ -207,6 +208,11 @@ public class OpportunityServiceImpl implements OpportunityService {
|
||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<OpportunityFullDTO> listAllOpportunities() {
|
||||||
|
return opportunityMapper.selectAllOpportunities();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public OpportunityDuplicateCheckDTO checkDuplicateOpportunity(Long userId, String name, Long excludeId, List<String> excludeStageCodes) {
|
public OpportunityDuplicateCheckDTO checkDuplicateOpportunity(Long userId, String name, Long excludeId, List<String> excludeStageCodes) {
|
||||||
if (isBlank(name)) {
|
if (isBlank(name)) {
|
||||||
|
|
|
||||||
|
|
@ -54,6 +54,7 @@ unisbase:
|
||||||
permit-all-urls:
|
permit-all-urls:
|
||||||
- /actuator/health
|
- /actuator/health
|
||||||
- /api/wecom/sso/**
|
- /api/wecom/sso/**
|
||||||
|
- /api/opportunities/all
|
||||||
- /api/opportunities/integration/**
|
- /api/opportunities/integration/**
|
||||||
- /api/oms/callback/order-return
|
- /api/oms/callback/order-return
|
||||||
internal-auth:
|
internal-auth:
|
||||||
|
|
|
||||||
|
|
@ -56,6 +56,7 @@ unisbase:
|
||||||
permit-all-urls:
|
permit-all-urls:
|
||||||
- /actuator/health
|
- /actuator/health
|
||||||
- /api/wecom/sso/**
|
- /api/wecom/sso/**
|
||||||
|
- /api/opportunities/all
|
||||||
- /api/opportunities/integration/**
|
- /api/opportunities/integration/**
|
||||||
- /api/oms/callback/order-return
|
- /api/oms/callback/order-return
|
||||||
internal-auth:
|
internal-auth:
|
||||||
|
|
|
||||||
|
|
@ -322,6 +322,53 @@
|
||||||
</if>
|
</if>
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
|
<select id="selectAllOpportunities" resultType="com.unis.crm.dto.opportunity.OpportunityFullDTO">
|
||||||
|
select
|
||||||
|
coalesce(o.opportunity_code, '') as opportunityCode,
|
||||||
|
coalesce(o.opportunity_name, '') as opportunityName,
|
||||||
|
coalesce(stage_dict.item_value, o.stage, '') as stageCode,
|
||||||
|
coalesce(
|
||||||
|
stage_dict.item_label,
|
||||||
|
case coalesce(o.stage, 'initial_contact')
|
||||||
|
when 'initial_contact' then '初步沟通'
|
||||||
|
when 'solution_discussion' then '方案交流'
|
||||||
|
when 'bidding' then '招投标'
|
||||||
|
when 'business_negotiation' then '商务谈判'
|
||||||
|
when 'won' then '已成交'
|
||||||
|
when 'lost' then 'L-已丢单'
|
||||||
|
else coalesce(o.stage, '初步沟通')
|
||||||
|
end
|
||||||
|
) as stage,
|
||||||
|
coalesce(o.confidence_pct, '') as confidence,
|
||||||
|
coalesce(o.pushed_to_oms, false) as pushedToOms,
|
||||||
|
coalesce(to_char(o.oms_push_time, 'YYYY-MM-DD HH24:MI'), '') as omsPushTime,
|
||||||
|
coalesce(to_char(o.created_at, 'YYYY-MM-DD HH24:MI'), '') as createdAt,
|
||||||
|
o.owner_user_id as ownerUserId,
|
||||||
|
coalesce(nullif(btrim(u.display_name), ''), nullif(btrim(u.username), ''), '') as ownerName
|
||||||
|
from crm_opportunity o
|
||||||
|
left join sys_user u on u.user_id = o.owner_user_id
|
||||||
|
left join sys_dict_item stage_dict
|
||||||
|
on stage_dict.type_code = 'sj_xmjd'
|
||||||
|
and (
|
||||||
|
stage_dict.item_value = o.stage
|
||||||
|
or stage_dict.item_label = case coalesce(o.stage, 'initial_contact')
|
||||||
|
when 'initial_contact' then '初步沟通'
|
||||||
|
when 'solution_discussion' then '方案交流'
|
||||||
|
when 'bidding' then '招投标'
|
||||||
|
when 'business_negotiation' then '商务谈判'
|
||||||
|
when 'won' then '已成交'
|
||||||
|
when 'lost' then 'L-已丢单'
|
||||||
|
else o.stage
|
||||||
|
end
|
||||||
|
)
|
||||||
|
and (
|
||||||
|
stage_dict.status = 1
|
||||||
|
or coalesce(o.archived, false) = true
|
||||||
|
)
|
||||||
|
and coalesce(stage_dict.is_deleted, 0) = 0
|
||||||
|
order by o.created_at desc, o.id desc
|
||||||
|
</select>
|
||||||
|
|
||||||
<select id="selectOpportunityDetail" resultType="com.unis.crm.dto.opportunity.OpportunityItemDTO">
|
<select id="selectOpportunityDetail" resultType="com.unis.crm.dto.opportunity.OpportunityItemDTO">
|
||||||
select
|
select
|
||||||
o.id,
|
o.id,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,101 @@
|
||||||
|
# 全量商机查询接口文档
|
||||||
|
|
||||||
|
> 提供 CRM 全量商机精简字段清单,包含已归档/已签单商机,不分页。
|
||||||
|
|
||||||
|
## 基本信息
|
||||||
|
|
||||||
|
| 项目 | 内容 |
|
||||||
|
| --- | --- |
|
||||||
|
| 接口路径 | `GET /api/opportunities/all` |
|
||||||
|
| 调用方 | 内部系统 / 系统间集成(不面向前端用户) |
|
||||||
|
| 请求头鉴权 | `X-Internal-Secret`(内部接口密钥) |
|
||||||
|
| 数据范围 | 全量商机(含已归档/已签单),不做数据权限过滤 |
|
||||||
|
| 排序 | 按创建时间倒序,创建时间相同按 id 倒序 |
|
||||||
|
| 分页 | 无(一次性返回全部) |
|
||||||
|
|
||||||
|
## 鉴权方式
|
||||||
|
|
||||||
|
与商机集成更新接口 `PUT /api/opportunities/integration/update`、OMS 退单回调接口保持一致,使用 `unisbase.internal-auth` 配置的密钥校验。
|
||||||
|
|
||||||
|
- 请求头字段:`X-Internal-Secret`(可通过 `unisbase.internal-auth.header-name` 覆盖)
|
||||||
|
- 秘钥值:`unisbase.internal-auth.secret`
|
||||||
|
- 校验逻辑:
|
||||||
|
- 当 `unisbase.internal-auth.enabled=true` 时强制校验,头信息与配置密钥不一致则返回 `401`(`内部接口鉴权失败`);
|
||||||
|
- 当 `enabled=false` 时跳过鉴权(仅用于本地开发)。
|
||||||
|
- 该路径已加入 `unisbase.security.permit-all-urls`,**不需要** `Authorization: Bearer` 登录态。
|
||||||
|
|
||||||
|
## 请求
|
||||||
|
|
||||||
|
```
|
||||||
|
GET /api/opportunities/all
|
||||||
|
X-Internal-Secret: <配置的secret>
|
||||||
|
```
|
||||||
|
|
||||||
|
无查询参数、无请求体。
|
||||||
|
|
||||||
|
## 响应
|
||||||
|
|
||||||
|
统一响应结构 `ApiResponse<List<OpportunityFullDTO>>`:
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `code` | string | 状态码,`"0"` 表示成功,`"-1"` 表示失败 |
|
||||||
|
| `msg` | string | 提示信息,成功为 `success` |
|
||||||
|
| `data` | array | 商机列表 |
|
||||||
|
|
||||||
|
`data` 元素字段:
|
||||||
|
|
||||||
|
| 字段 | 类型 | 对应库表字段 | 说明 |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `opportunityCode` | string | `crm_opportunity.opportunity_code` | 商机编号 |
|
||||||
|
| `opportunityName` | string | `crm_opportunity.opportunity_name` | 商机名称 |
|
||||||
|
| `stageCode` | string | `crm_opportunity.stage` | 商机阶段编码(取自字典 `sj_xmjd`,未匹配时回退原值) |
|
||||||
|
| `stage` | string | 字典 `sj_xmjd.item_label` | 商机阶段名称(未匹配字典时按内置映射回退) |
|
||||||
|
| `confidence` | string | `crm_opportunity.confidence_pct` | 把握度等级:`A` / `B` / `C` |
|
||||||
|
| `pushedToOms` | boolean | `crm_opportunity.pushed_to_oms` | 是否已推送 OMS |
|
||||||
|
| `omsPushTime` | string | `crm_opportunity.oms_push_time` | 推送 OMS 时间,格式 `yyyy-MM-dd HH:mm`,未推送为空串 |
|
||||||
|
| `createdAt` | string | `crm_opportunity.created_at` | 创建时间,格式 `yyyy-MM-dd HH:mm` |
|
||||||
|
| `ownerUserId` | long | `crm_opportunity.owner_user_id` | 商机负责人 ID |
|
||||||
|
| `ownerName` | string | `sys_user.display_name` / `username` | 商机负责人姓名 |
|
||||||
|
|
||||||
|
响应示例:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": "0",
|
||||||
|
"msg": "success",
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"opportunityCode": "OPP-20260916-001",
|
||||||
|
"opportunityName": "某某医院云桌面项目",
|
||||||
|
"stageCode": "bidding",
|
||||||
|
"stage": "招投标",
|
||||||
|
"confidence": "A",
|
||||||
|
"pushedToOms": true,
|
||||||
|
"omsPushTime": "2026-09-16 10:30",
|
||||||
|
"createdAt": "2026-09-10 09:12",
|
||||||
|
"ownerUserId": 1001,
|
||||||
|
"ownerName": "张三"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 实现说明
|
||||||
|
|
||||||
|
| 层 | 文件 |
|
||||||
|
| --- | --- |
|
||||||
|
| Controller | `backend/src/main/java/com/unis/crm/controller/OpportunityController.java`(`GET /all`) |
|
||||||
|
| Service | `OpportunityService#listAllOpportunities` / `OpportunityServiceImpl` |
|
||||||
|
| Mapper | `OpportunityMapper#selectAllOpportunities` |
|
||||||
|
| SQL | `backend/src/main/resources/mapper/opportunity/OpportunityMapper.xml` |
|
||||||
|
| DTO | `backend/src/main/java/com/unis/crm/dto/opportunity/OpportunityFullDTO.java` |
|
||||||
|
|
||||||
|
要点:
|
||||||
|
|
||||||
|
- 查询语句未加 `archived` 过滤,返回包含已归档/已签单在内的全部商机;
|
||||||
|
- 未拼接数据权限条件(`opportunityVisibilityCondition`),即不受当前用户数据可见范围限制,因此**仅允许内部系统调用**;
|
||||||
|
- 鉴权由 `OpportunityController#validateInternalSecret` 完成,并在 `application.yml` / `application-prod.yml` 的 `unisbase.security.permit-all-urls` 中放开 `/api/opportunities/all`;
|
||||||
|
- 已添加 `@Log(type = "商机集成", value = "查询全量商机")` 审计日志,记录调用留痕;
|
||||||
|
- 阶段名称通过 `sys_dict_item`(`type_code = 'sj_xmjd'`)关联获取,归档商机即使字典项被禁用也能取到名称;
|
||||||
|
- 时间字段统一由数据库格式化后返回字符串,未推送 OMS 时 `omsPushTime` 返回空串。
|
||||||
Loading…
Reference in New Issue