feat(llm): add channel analytics report feature
新增渠道统计分析报表功能,包含认证级别分布、合作意向、覆盖省市、年度营收分档统计,补充对应Mapper接口、数据权限配置与工具支持main
parent
28c336fd13
commit
52a35e5b20
|
|
@ -4,7 +4,21 @@
|
||||||
<option name="autoReloadType" value="SELECTIVE" />
|
<option name="autoReloadType" value="SELECTIVE" />
|
||||||
</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/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/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/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/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/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/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/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" />
|
||||||
|
</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" />
|
||||||
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
|
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
|
||||||
|
|
@ -102,6 +116,7 @@
|
||||||
<workItem from="1788916111243" duration="625000" />
|
<workItem from="1788916111243" duration="625000" />
|
||||||
<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" />
|
||||||
</task>
|
</task>
|
||||||
<task id="LOCAL-00001" summary="修改定位信息 0323">
|
<task id="LOCAL-00001" summary="修改定位信息 0323">
|
||||||
<option name="closed" value="true" />
|
<option name="closed" value="true" />
|
||||||
|
|
|
||||||
|
|
@ -224,6 +224,30 @@ public interface LlmMcpMapper {
|
||||||
@Param("limit") int limit,
|
@Param("limit") int limit,
|
||||||
@Param("offset") int offset);
|
@Param("offset") int offset);
|
||||||
|
|
||||||
|
List<Map<String, Object>> channelTierDistribution(
|
||||||
|
@Param("startDate") LocalDate startDate,
|
||||||
|
@Param("endDate") LocalDate endDate,
|
||||||
|
@Param("ownerUserId") Long ownerUserId,
|
||||||
|
@Param("tenantId") Long tenantId);
|
||||||
|
|
||||||
|
List<Map<String, Object>> channelIntentDistribution(
|
||||||
|
@Param("startDate") LocalDate startDate,
|
||||||
|
@Param("endDate") LocalDate endDate,
|
||||||
|
@Param("ownerUserId") Long ownerUserId,
|
||||||
|
@Param("tenantId") Long tenantId);
|
||||||
|
|
||||||
|
List<Map<String, Object>> channelCoverageSummary(
|
||||||
|
@Param("startDate") LocalDate startDate,
|
||||||
|
@Param("endDate") LocalDate endDate,
|
||||||
|
@Param("ownerUserId") Long ownerUserId,
|
||||||
|
@Param("tenantId") Long tenantId);
|
||||||
|
|
||||||
|
List<Map<String, Object>> channelRevenueTier(
|
||||||
|
@Param("startDate") LocalDate startDate,
|
||||||
|
@Param("endDate") LocalDate endDate,
|
||||||
|
@Param("ownerUserId") Long ownerUserId,
|
||||||
|
@Param("tenantId") Long tenantId);
|
||||||
|
|
||||||
Map<String, Object> selectWorkTodayStatus(
|
Map<String, Object> selectWorkTodayStatus(
|
||||||
@Param("targetUserId") Long targetUserId,
|
@Param("targetUserId") Long targetUserId,
|
||||||
@Param("tenantId") Long tenantId,
|
@Param("tenantId") Long tenantId,
|
||||||
|
|
|
||||||
|
|
@ -415,7 +415,8 @@ public class McpDataPermissionInterceptor implements Interceptor {
|
||||||
"selectSalesExpansionDetail", "selectSalesExpansionFollowups");
|
"selectSalesExpansionDetail", "selectSalesExpansionFollowups");
|
||||||
add(policies, CrmDataVisibilityService.RESOURCE_EXPANSION, "c.owner_user_id", null, false,
|
add(policies, CrmDataVisibilityService.RESOURCE_EXPANSION, "c.owner_user_id", null, false,
|
||||||
"universalSearchChannelExpansions", "countChannelExpansions", "channelExpansionSummary", "searchChannelExpansions",
|
"universalSearchChannelExpansions", "countChannelExpansions", "channelExpansionSummary", "searchChannelExpansions",
|
||||||
"selectChannelExpansionDetail", "selectChannelExpansionContacts", "selectChannelExpansionFollowups");
|
"selectChannelExpansionDetail", "selectChannelExpansionContacts", "selectChannelExpansionFollowups",
|
||||||
|
"channelTierDistribution", "channelIntentDistribution", "channelCoverageSummary", "channelRevenueTier");
|
||||||
add(policies, CrmDataVisibilityService.RESOURCE_WORK, "t.user_id", null, false,
|
add(policies, CrmDataVisibilityService.RESOURCE_WORK, "t.user_id", null, false,
|
||||||
"universalSearchTodos", "countTodos", "searchTodos", "todoSummary", "selectTodoDetail");
|
"universalSearchTodos", "countTodos", "searchTodos", "todoSummary", "selectTodoDetail");
|
||||||
add(policies, CrmDataVisibilityService.RESOURCE_WORK, "l.operator_user_id", null, false,
|
add(policies, CrmDataVisibilityService.RESOURCE_WORK, "l.operator_user_id", null, false,
|
||||||
|
|
|
||||||
|
|
@ -55,6 +55,7 @@ public class CrmReportCatalogToolProvider extends PermissionedMcpToolProvider {
|
||||||
Map.of("reportType", "daily_report_completion", "description", "日报应交、已交、未交、完成率"),
|
Map.of("reportType", "daily_report_completion", "description", "日报应交、已交、未交、完成率"),
|
||||||
Map.of("reportType", "checkin_summary", "description", "外勤打卡统计"),
|
Map.of("reportType", "checkin_summary", "description", "外勤打卡统计"),
|
||||||
Map.of("reportType", "expansion_summary", "description", "销售拓客与渠道拓客统计"),
|
Map.of("reportType", "expansion_summary", "description", "销售拓客与渠道拓客统计"),
|
||||||
|
Map.of("reportType", "channel_analytics", "description", "渠道统计分析:认证级别分布、合作意向、覆盖省市、年度营收分档(SQL 全量聚合)"),
|
||||||
Map.of("reportType", "customer_summary", "description", "客户状态、来源、行业统计"),
|
Map.of("reportType", "customer_summary", "description", "客户状态、来源、行业统计"),
|
||||||
Map.of("reportType", "todo_summary", "description", "待办状态与优先级统计")));
|
Map.of("reportType", "todo_summary", "description", "待办状态与优先级统计")));
|
||||||
result.put("security", "查询结果遵循系统 @DataScope 数据权限。");
|
result.put("security", "查询结果遵循系统 @DataScope 数据权限。");
|
||||||
|
|
|
||||||
|
|
@ -59,7 +59,8 @@ public class CrmReportQueryToolProvider extends PermissionedMcpToolProvider {
|
||||||
"checkin_summary",
|
"checkin_summary",
|
||||||
"expansion_summary",
|
"expansion_summary",
|
||||||
"customer_summary",
|
"customer_summary",
|
||||||
"todo_summary")));
|
"todo_summary",
|
||||||
|
"channel_analytics")));
|
||||||
queryProperties.put("startDate", stringProperty("开始日期 YYYY-MM-DD;不传默认本月。"));
|
queryProperties.put("startDate", stringProperty("开始日期 YYYY-MM-DD;不传默认本月。"));
|
||||||
queryProperties.put("endDate", stringProperty("结束日期 YYYY-MM-DD;不传默认今天,最大跨度 366 天。"));
|
queryProperties.put("endDate", stringProperty("结束日期 YYYY-MM-DD;不传默认今天,最大跨度 366 天。"));
|
||||||
queryProperties.put("ownerUserId", integerProperty("目标用户 ID,实际可见范围仍按系统数据权限裁剪。"));
|
queryProperties.put("ownerUserId", integerProperty("目标用户 ID,实际可见范围仍按系统数据权限裁剪。"));
|
||||||
|
|
@ -92,6 +93,7 @@ public class CrmReportQueryToolProvider extends PermissionedMcpToolProvider {
|
||||||
case "expansion_summary" -> expansionSummary(context);
|
case "expansion_summary" -> expansionSummary(context);
|
||||||
case "customer_summary" -> customerSummary(context);
|
case "customer_summary" -> customerSummary(context);
|
||||||
case "todo_summary" -> todoSummary(context);
|
case "todo_summary" -> todoSummary(context);
|
||||||
|
case "channel_analytics" -> channelAnalytics(context);
|
||||||
default -> throw new BusinessException("不支持的 reportType:" + reportType);
|
default -> throw new BusinessException("不支持的 reportType:" + reportType);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -208,6 +210,22 @@ public class CrmReportQueryToolProvider extends PermissionedMcpToolProvider {
|
||||||
context.offset()));
|
context.offset()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private List<Map<String, Object>> channelAnalytics(QueryContext context) {
|
||||||
|
List<Map<String, Object>> rows = new ArrayList<>();
|
||||||
|
Map<String, Object> row = new LinkedHashMap<>();
|
||||||
|
row.put("dimension", "tier");
|
||||||
|
row.put("tierDistribution", llmMcpMapper.channelTierDistribution(
|
||||||
|
context.startDate(), context.endDate(), context.ownerUserId(), context.tenantId()));
|
||||||
|
row.put("intentDistribution", llmMcpMapper.channelIntentDistribution(
|
||||||
|
context.startDate(), context.endDate(), context.ownerUserId(), context.tenantId()));
|
||||||
|
row.put("coverages", llmMcpMapper.channelCoverageSummary(
|
||||||
|
context.startDate(), context.endDate(), context.ownerUserId(), context.tenantId()));
|
||||||
|
row.put("revenueTier", llmMcpMapper.channelRevenueTier(
|
||||||
|
context.startDate(), context.endDate(), context.ownerUserId(), context.tenantId()));
|
||||||
|
rows.add(row);
|
||||||
|
return sanitizeRows(rows);
|
||||||
|
}
|
||||||
|
|
||||||
private QueryContext buildContext(Map<String, Object> arguments) {
|
private QueryContext buildContext(Map<String, Object> arguments) {
|
||||||
LocalDate endDate = getDate(arguments, "endDate");
|
LocalDate endDate = getDate(arguments, "endDate");
|
||||||
LocalDate startDate = getDate(arguments, "startDate");
|
LocalDate startDate = getDate(arguments, "startDate");
|
||||||
|
|
|
||||||
|
|
@ -2198,9 +2198,9 @@
|
||||||
select
|
select
|
||||||
case
|
case
|
||||||
when annual_revenue is null then '未填写'
|
when annual_revenue is null then '未填写'
|
||||||
when annual_revenue < 100 then '<100万'
|
when annual_revenue < 100 then '<100万'
|
||||||
when annual_revenue < 500 then '100-500万'
|
when annual_revenue < 500 then '100-500万'
|
||||||
when annual_revenue < 1000 then '500-1000万'
|
when annual_revenue < 1000 then '500-1000万'
|
||||||
else '1000万+'
|
else '1000万+'
|
||||||
end as "revenueTier",
|
end as "revenueTier",
|
||||||
count(1)::bigint as "channelCount",
|
count(1)::bigint as "channelCount",
|
||||||
|
|
@ -2211,9 +2211,9 @@
|
||||||
<if test="tenantId != null">and exists (select 1 from sys_tenant_user tu where tu.user_id = c.owner_user_id and tu.tenant_id = #{tenantId} and coalesce(tu.is_deleted, 0) = 0)</if>
|
<if test="tenantId != null">and exists (select 1 from sys_tenant_user tu where tu.user_id = c.owner_user_id and tu.tenant_id = #{tenantId} and coalesce(tu.is_deleted, 0) = 0)</if>
|
||||||
group by case
|
group by case
|
||||||
when annual_revenue is null then '未填写'
|
when annual_revenue is null then '未填写'
|
||||||
when annual_revenue < 100 then '<100万'
|
when annual_revenue < 100 then '<100万'
|
||||||
when annual_revenue < 500 then '100-500万'
|
when annual_revenue < 500 then '100-500万'
|
||||||
when annual_revenue < 1000 then '500-1000万'
|
when annual_revenue < 1000 then '500-1000万'
|
||||||
else '1000万+'
|
else '1000万+'
|
||||||
end
|
end
|
||||||
order by "channelCount" desc
|
order by "channelCount" desc
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue