From 7b15acce33409eba8e92f3fb41f1595387daa710 Mon Sep 17 00:00:00 2001 From: “wangming” <“wangming@antissoft.com”> Date: Thu, 16 Oct 2025 23:06:30 +0800 Subject: [PATCH] feat: 添加拓客活动漏斗统计和门店顾客详情接口 --- netcore/src/Modularity/Extend/NCC.Extend/LqTkjlbService.cs | 241 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 239 insertions(+), 2 deletions(-) diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqTkjlbService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqTkjlbService.cs index a762f99..8c81539 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend/LqTkjlbService.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend/LqTkjlbService.cs @@ -169,6 +169,8 @@ namespace NCC.Extend.LqTkjlb { throw NCCException.Oh("未找到对应的拓客活动用户信息,请确认活动ID和用户ID是否正确"); } + var MemberNumber = "LQ" + DateTime.Now.ToString("yyyyMMddHHmmssfff"); + var MemberId = YitIdHelper.NextId().ToString(); var eventUserInfo = eventUserInfoList.First(); // 创建拓客记录 var entity = input.Adapt(); @@ -177,16 +179,17 @@ namespace NCC.Extend.LqTkjlb entity.StoreId = eventUserInfo.StoreId; entity.DepId = eventUserInfo.DepId; entity.ExpansionTime = DateTime.Now; + entity.MemberId = MemberId; var isOk = await _db.Insertable(entity).IgnoreColumns(ignoreNullColumn: true).ExecuteCommandAsync(); if (!(isOk > 0)) throw NCCException.Oh("创建拓客记录失败"); // 创建客户信息 LqKhxxEntity MemberInfo = new LqKhxxEntity(); - MemberInfo.Id = YitIdHelper.NextId().ToString(); + MemberInfo.Id = MemberId; MemberInfo.Khmc = entity.CustomerName; MemberInfo.Sjh = input.customerPhone; // 设置手机号 MemberInfo.Khlx = MemberTypeEnum.线索.GetHashCode().ToString(); - MemberInfo.Dah = "LQ" + DateTime.Now.ToString("yyyyMMddHHmmssfff"); + MemberInfo.Dah = MemberNumber; MemberInfo.Jdqd = "19.9卡"; //找到input.expansionUserId的用户信息 @@ -526,5 +529,239 @@ namespace NCC.Extend.LqTkjlb } #endregion + #region 漏斗统计 + /// + /// 获取拓客活动漏斗统计数据 + /// + /// 活动ID + /// 漏斗统计数据 + [HttpGet("GetFunnelStatistics/{eventId}")] + public async Task GetFunnelStatistics(string eventId) + { + try + { + var sql = @" + SELECT + md.F_Id as store_id, + md.dm as store_name, + COUNT(DISTINCT tk.F_Id) as tk_count, -- 拓客数量 + COUNT(DISTINCT CASE WHEN yy.F_Id IS NOT NULL THEN tk.F_CustomerPhone END) as yy_count, -- 预约人数(已确认) + COUNT(DISTINCT CASE WHEN xh.F_Id IS NOT NULL THEN tk.F_CustomerPhone END) as hk_count, -- 耗卡人数 + COALESCE(SUM(CASE WHEN xh.F_Id IS NOT NULL THEN xh.xfje END), 0) as hk_amount, -- 耗卡金额 + ROUND( + COUNT(DISTINCT CASE WHEN yy.F_Id IS NOT NULL THEN tk.F_CustomerPhone END) * 100.0 / + COUNT(DISTINCT tk.F_Id), 2 + ) as yy_conversion_rate, -- 预约转化率(预约人数/拓客数量) + ROUND( + COUNT(DISTINCT CASE WHEN xh.F_Id IS NOT NULL THEN tk.F_CustomerPhone END) * 100.0 / + COUNT(DISTINCT CASE WHEN yy.F_Id IS NOT NULL THEN tk.F_CustomerPhone END), 2 + ) as hk_conversion_rate -- 耗卡转化率(耗卡人数/预约人数) + FROM lq_tkjlb tk + JOIN lq_mdxx md ON tk.F_StoreId = md.F_Id + LEFT JOIN lq_yyjl yy ON tk.F_MemberId = yy.gk + AND yy.F_Status = '已确认' + LEFT JOIN lq_xh_hyhk xh ON tk.F_MemberId = xh.hy + AND xh.F_IsEffective = 1 + WHERE tk.F_EventId = @eventId + GROUP BY md.F_Id, md.dm + ORDER BY tk_count DESC"; + + var result = await _db.Ado.SqlQueryAsync(sql, new { eventId }); + + return new + { + success = true, + data = result, + message = "获取漏斗统计数据成功" + }; + } + catch (Exception ex) + { + throw NCCException.Oh("获取漏斗统计数据失败:" + ex.Message); + } + } + + /// + /// 获取拓客活动总体漏斗统计 + /// + /// 活动ID + /// 总体漏斗统计数据 + [HttpGet("GetOverallFunnelStatistics/{eventId}")] + public async Task GetOverallFunnelStatistics(string eventId) + { + try + { + var sql = @" + SELECT + COUNT(DISTINCT tk.F_Id) as total_tk_count, -- 总拓客数量 + COUNT(DISTINCT CASE WHEN yy.F_Id IS NOT NULL THEN tk.F_CustomerPhone END) as total_yy_count, -- 总预约人数 + COUNT(DISTINCT CASE WHEN xh.F_Id IS NOT NULL THEN tk.F_CustomerPhone END) as total_hk_count, -- 总耗卡人数 + COALESCE(SUM(CASE WHEN xh.F_Id IS NOT NULL THEN xh.xfje END), 0) as total_hk_amount, -- 总耗卡金额 + ROUND( + COUNT(DISTINCT CASE WHEN yy.F_Id IS NOT NULL THEN tk.F_CustomerPhone END) * 100.0 / + COUNT(DISTINCT tk.F_Id), 2 + ) as overall_yy_conversion_rate, -- 总体预约转化率 + ROUND( + COUNT(DISTINCT CASE WHEN xh.F_Id IS NOT NULL THEN tk.F_CustomerPhone END) * 100.0 / + COUNT(DISTINCT CASE WHEN yy.F_Id IS NOT NULL THEN tk.F_CustomerPhone END), 2 + ) as overall_hk_conversion_rate -- 总体耗卡转化率 + FROM lq_tkjlb tk + LEFT JOIN lq_yyjl yy ON tk.F_MemberId = yy.gk + AND yy.F_Status = '已确认' + LEFT JOIN lq_xh_hyhk xh ON tk.F_MemberId = xh.hy + AND xh.F_IsEffective = 1 + WHERE tk.F_EventId = @eventId"; + + var result = await _db.Ado.SqlQueryAsync(sql, new { eventId }); + + return new + { + success = true, + data = result?.FirstOrDefault(), + message = "获取总体漏斗统计数据成功" + }; + } + catch (Exception ex) + { + throw NCCException.Oh("获取总体漏斗统计数据失败:" + ex.Message); + } + } + #endregion + + #region 门店顾客详情 + /// + /// 获取门店拓客活动顾客详情 + /// + /// 活动ID + /// 门店ID + /// 门店顾客详情列表 + [HttpGet("GetStoreCustomerDetails/{eventId}/{storeId}")] + public async Task GetStoreCustomerDetails(string eventId, string storeId) + { + try + { + var sql = @" + SELECT + tk.F_Id as tk_id, -- 拓客记录ID + tk.F_CustomerPhone as customer_phone, -- 顾客手机号 + tk.F_MemberId as member_id, -- 会员ID + tk.F_CustomerName as customer_name, -- 顾客姓名 + tk.F_CreateTime as tk_time, -- 拓客时间 + yy.F_Id as yy_id, -- 预约ID + yy.F_Status as yy_status, -- 预约状态 + yy.F_CreateTime as yy_time, -- 预约时间 + yy.yysj as appointment_time, -- 预约到店时间 + xh.F_Id as xh_id, -- 耗卡ID + xh.F_CreateTime as xh_time, -- 耗卡时间 + xh.xfje as consume_amount, -- 耗卡金额 + CASE + WHEN yy.F_Id IS NOT NULL THEN '已预约' + ELSE '未预约' + END as appointment_status, -- 预约状态描述 + CASE + WHEN xh.F_Id IS NOT NULL THEN '已耗卡' + ELSE '未耗卡' + END as consume_status -- 耗卡状态描述 + FROM lq_tkjlb tk + LEFT JOIN lq_yyjl yy ON tk.F_MemberId = yy.gk + AND yy.F_Status = '已确认' + LEFT JOIN lq_xh_hyhk xh ON tk.F_MemberId = xh.hy + AND xh.F_IsEffective = 1 + WHERE tk.F_EventId = @eventId + AND tk.F_StoreId = @storeId + ORDER BY tk.F_CreateTime DESC"; + + var result = await _db.Ado.SqlQueryAsync(sql, new { eventId, storeId }); + + return new + { + success = true, + data = result, + message = "获取门店顾客详情成功" + }; + } + catch (Exception ex) + { + throw NCCException.Oh("获取门店顾客详情失败:" + ex.Message); + } + } + + /// + /// 获取门店拓客活动顾客详情(分页) + /// + /// 活动ID + /// 门店ID + /// 页码 + /// 页大小 + /// 分页的门店顾客详情列表 + [HttpGet("GetStoreCustomerDetailsPaged/{eventId}/{storeId}")] + public async Task GetStoreCustomerDetailsPaged(string eventId, string storeId, int pageIndex = 1, int pageSize = 20) + { + try + { + var sql = @" + SELECT + tk.F_Id as tk_id, -- 拓客记录ID + tk.F_CustomerPhone as customer_phone, -- 顾客手机号 + tk.F_MemberId as member_id, -- 会员ID + tk.F_CustomerName as customer_name, -- 顾客姓名 + tk.F_CreateTime as tk_time, -- 拓客时间 + yy.F_Id as yy_id, -- 预约ID + yy.F_Status as yy_status, -- 预约状态 + yy.F_CreateTime as yy_time, -- 预约时间 + yy.yysj as appointment_time, -- 预约到店时间 + xh.F_Id as xh_id, -- 耗卡ID + xh.F_CreateTime as xh_time, -- 耗卡时间 + xh.xfje as consume_amount, -- 耗卡金额 + CASE + WHEN yy.F_Id IS NOT NULL THEN '已预约' + ELSE '未预约' + END as appointment_status, -- 预约状态描述 + CASE + WHEN xh.F_Id IS NOT NULL THEN '已耗卡' + ELSE '未耗卡' + END as consume_status -- 耗卡状态描述 + FROM lq_tkjlb tk + LEFT JOIN lq_yyjl yy ON tk.F_MemberId = yy.gk + AND yy.F_Status = '已确认' + LEFT JOIN lq_xh_hyhk xh ON tk.F_MemberId = xh.hy + AND xh.F_IsEffective = 1 + WHERE tk.F_EventId = @eventId + AND tk.F_StoreId = @storeId + ORDER BY tk.F_CreateTime DESC + LIMIT @offset, @pageSize"; + + var countSql = @" + SELECT COUNT(*) as total + FROM lq_tkjlb tk + WHERE tk.F_EventId = @eventId + AND tk.F_StoreId = @storeId"; + + var offset = (pageIndex - 1) * pageSize; + var result = await _db.Ado.SqlQueryAsync(sql, new { eventId, storeId, offset, pageSize }); + var totalResult = await _db.Ado.SqlQueryAsync(countSql, new { eventId, storeId }); + var total = totalResult?.FirstOrDefault()?.total ?? 0; + + return new + { + success = true, + data = new + { + list = result, + total = total, + pageIndex = pageIndex, + pageSize = pageSize, + totalPages = (int)Math.Ceiling((double)total / pageSize) + }, + message = "获取门店顾客详情成功" + }; + } + catch (Exception ex) + { + throw NCCException.Oh("获取门店顾客详情失败:" + ex.Message); + } + } + #endregion + } } -- libgit2 0.21.4