Commit 68b8deb5c5197390118dff40979c2a8c8bae463c

Authored by “wangming”
1 parent e298ca84

123213

netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKdKdjlb/HealthCoachStatisticsOutput.cs 0 → 100644
  1 +namespace NCC.Extend.Entitys.Dto.LqKdKdjlb
  2 +{
  3 + /// <summary>
  4 + /// 健康师统计输出
  5 + /// </summary>
  6 + public class HealthCoachStatisticsOutput
  7 + {
  8 + /// <summary>
  9 + /// 健康师ID
  10 + /// </summary>
  11 + public string employeeId { get; set; }
  12 +
  13 + /// <summary>
  14 + /// 健康师姓名
  15 + /// </summary>
  16 + public string employeeName { get; set; }
  17 +
  18 + /// <summary>
  19 + /// 门店ID
  20 + /// </summary>
  21 + public string storeId { get; set; }
  22 +
  23 + /// <summary>
  24 + /// 门店名称
  25 + /// </summary>
  26 + public string storeName { get; set; }
  27 +
  28 + /// <summary>
  29 + /// 事业部ID
  30 + /// </summary>
  31 + public string departmentId { get; set; }
  32 +
  33 + /// <summary>
  34 + /// 事业部名称
  35 + /// </summary>
  36 + public string departmentName { get; set; }
  37 +
  38 + /// <summary>
  39 + /// 邀约人数 - 统计该健康师在指定时间周期内邀约的客户数量(按客户去重)
  40 + /// </summary>
  41 + public int inviteCount { get; set; }
  42 +
  43 + /// <summary>
  44 + /// 预约人数 - 统计该健康师在指定时间周期内预约的客户数量(按客户去重,无论预约状态)
  45 + /// </summary>
  46 + public int appointmentCount { get; set; }
  47 +
  48 + /// <summary>
  49 + /// 到店人数 - 统计该健康师在指定时间周期内已确认到店的客户数量(按客户去重,仅统计状态为'已确认'的预约)
  50 + /// </summary>
  51 + public int visitCount { get; set; }
  52 +
  53 + /// <summary>
  54 + /// 开单人数 - 统计该健康师在指定时间周期内开单的客户数量(按开单记录去重)
  55 + /// </summary>
  56 + public int billingCount { get; set; }
  57 +
  58 + /// <summary>
  59 + /// 开单金额 - 统计该健康师在指定时间周期内的开单业绩总金额
  60 + /// </summary>
  61 + public decimal billingAmount { get; set; }
  62 +
  63 + /// <summary>
  64 + /// 消耗金额 - 统计该健康师在指定时间周期内的消耗业绩总金额
  65 + /// </summary>
  66 + public decimal consumeAmount { get; set; }
  67 +
  68 + /// <summary>
  69 + /// 人头 - 统计该健康师在指定时间周期内服务的唯一客户数量(按客户去重)
  70 + /// </summary>
  71 + public int headCount { get; set; }
  72 +
  73 + /// <summary>
  74 + /// 人次 - 统计该健康师在指定时间周期内服务的客户人次(按客户+日期去重,同一客户不同天算多次)
  75 + /// </summary>
  76 + public int personCount { get; set; }
  77 +
  78 + /// <summary>
  79 + /// 消耗项目数 - 统计该健康师在指定时间周期内消耗的项目总次数
  80 + /// </summary>
  81 + public int projectCount { get; set; }
  82 + }
  83 +}
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKdKdjlb/HealthCoachStatisticsQueryInput.cs 0 → 100644
  1 +using NCC.Common.Filter;
  2 +using System;
  3 +
  4 +namespace NCC.Extend.Entitys.Dto.LqKdKdjlb
  5 +{
  6 + /// <summary>
  7 + /// 健康师统计查询输入
  8 + /// </summary>
  9 + public class HealthCoachStatisticsQueryInput : PageInputBase
  10 + {
  11 + /// <summary>
  12 + /// 开始时间
  13 + /// </summary>
  14 + public DateTime? StartTime { get; set; }
  15 +
  16 + /// <summary>
  17 + /// 结束时间
  18 + /// </summary>
  19 + public DateTime? EndTime { get; set; }
  20 +
  21 + /// <summary>
  22 + /// 事业部ID
  23 + /// </summary>
  24 + public string DepartmentId { get; set; }
  25 +
  26 + /// <summary>
  27 + /// 门店ID
  28 + /// </summary>
  29 + public string StoreId { get; set; }
  30 +
  31 + /// <summary>
  32 + /// 健康师姓名
  33 + /// </summary>
  34 + public string EmployeeName { get; set; }
  35 + }
  36 +}
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqReport/BusinessStatisticsInput.cs 0 → 100644
  1 +using System;
  2 +
  3 +namespace NCC.Extend.Entitys.Dto.LqReport
  4 +{
  5 + /// <summary>
  6 + /// 业务统计查询输入
  7 + /// </summary>
  8 + public class BusinessStatisticsInput
  9 + {
  10 + /// <summary>
  11 + /// 开始时间
  12 + /// </summary>
  13 + public DateTime? StartTime { get; set; }
  14 +
  15 + /// <summary>
  16 + /// 结束时间
  17 + /// </summary>
  18 + public DateTime? EndTime { get; set; }
  19 +
  20 + /// <summary>
  21 + /// 门店ID列表
  22 + /// </summary>
  23 + public string[] StoreIds { get; set; }
  24 + }
  25 +}
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqReport/BusinessStatisticsOutput.cs 0 → 100644
  1 +namespace NCC.Extend.Entitys.Dto.LqReport
  2 +{
  3 + /// <summary>
  4 + /// 业务统计输出
  5 + /// </summary>
  6 + public class BusinessStatisticsOutput
  7 + {
  8 + /// <summary>
  9 + /// 开单总金额
  10 + /// </summary>
  11 + public decimal TotalBillingAmount { get; set; }
  12 +
  13 + /// <summary>
  14 + /// 耗卡总金额
  15 + /// </summary>
  16 + public decimal TotalConsumeAmount { get; set; }
  17 +
  18 + /// <summary>
  19 + /// 退卡总金额
  20 + /// </summary>
  21 + public decimal TotalRefundAmount { get; set; }
  22 +
  23 + /// <summary>
  24 + /// 开单人数
  25 + /// </summary>
  26 + public int BillingCount { get; set; }
  27 +
  28 + /// <summary>
  29 + /// 耗卡人数
  30 + /// </summary>
  31 + public int ConsumeCount { get; set; }
  32 +
  33 + /// <summary>
  34 + /// 退卡人数
  35 + /// </summary>
  36 + public int RefundCount { get; set; }
  37 + }
  38 +}
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqReport/CustomerTypeStatisticsInput.cs 0 → 100644
  1 +using System;
  2 +
  3 +namespace NCC.Extend.Entitys.Dto.LqReport
  4 +{
  5 + /// <summary>
  6 + /// 客户类型统计查询输入
  7 + /// </summary>
  8 + public class CustomerTypeStatisticsInput
  9 + {
  10 + /// <summary>
  11 + /// 开始时间
  12 + /// </summary>
  13 + public DateTime? StartTime { get; set; }
  14 +
  15 + /// <summary>
  16 + /// 结束时间
  17 + /// </summary>
  18 + public DateTime? EndTime { get; set; }
  19 +
  20 + /// <summary>
  21 + /// 门店ID列表
  22 + /// </summary>
  23 + public string[] StoreIds { get; set; }
  24 + }
  25 +}
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqReport/CustomerTypeStatisticsOutput.cs 0 → 100644
  1 +namespace NCC.Extend.Entitys.Dto.LqReport
  2 +{
  3 + /// <summary>
  4 + /// 客户类型统计输出
  5 + /// </summary>
  6 + public class CustomerTypeStatisticsOutput
  7 + {
  8 + /// <summary>
  9 + /// 线索客户数量
  10 + /// </summary>
  11 + public int LeadCount { get; set; }
  12 +
  13 + /// <summary>
  14 + /// 新客数量
  15 + /// </summary>
  16 + public int NewCustomerCount { get; set; }
  17 +
  18 + /// <summary>
  19 + /// 散客数量
  20 + /// </summary>
  21 + public int CasualCustomerCount { get; set; }
  22 +
  23 + /// <summary>
  24 + /// 会员数量
  25 + /// </summary>
  26 + public int MemberCount { get; set; }
  27 +
  28 + /// <summary>
  29 + /// 拓客总人数
  30 + /// </summary>
  31 + public int TotalInviteCount { get; set; }
  32 +
  33 + /// <summary>
  34 + /// 消耗人数(有消耗金额的)
  35 + /// </summary>
  36 + public int ConsumeCount { get; set; }
  37 +
  38 + /// <summary>
  39 + /// 转化率(消耗人数/拓客人数)
  40 + /// </summary>
  41 + public decimal ConversionRate { get; set; }
  42 + }
  43 +}
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqReport/ItemStatisticsInput.cs 0 → 100644
  1 +using System;
  2 +
  3 +namespace NCC.Extend.Entitys.Dto.LqReport
  4 +{
  5 + /// <summary>
  6 + /// 品项统计查询输入
  7 + /// </summary>
  8 + public class ItemStatisticsInput
  9 + {
  10 + /// <summary>
  11 + /// 开始时间
  12 + /// </summary>
  13 + public DateTime? StartTime { get; set; }
  14 +
  15 + /// <summary>
  16 + /// 结束时间
  17 + /// </summary>
  18 + public DateTime? EndTime { get; set; }
  19 +
  20 + /// <summary>
  21 + /// 门店ID列表
  22 + /// </summary>
  23 + public string[] StoreIds { get; set; }
  24 + }
  25 +}
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqReport/ItemStatisticsOutput.cs 0 → 100644
  1 +namespace NCC.Extend.Entitys.Dto.LqReport
  2 +{
  3 + /// <summary>
  4 + /// 品项统计输出
  5 + /// </summary>
  6 + public class ItemStatisticsOutput
  7 + {
  8 + /// <summary>
  9 + /// 品项ID
  10 + /// </summary>
  11 + public string ItemId { get; set; }
  12 +
  13 + /// <summary>
  14 + /// 品项名称
  15 + /// </summary>
  16 + public string ItemName { get; set; }
  17 +
  18 + /// <summary>
  19 + /// 品项编号
  20 + /// </summary>
  21 + public string ItemNumber { get; set; }
  22 +
  23 + /// <summary>
  24 + /// 开单数量
  25 + /// </summary>
  26 + public int BillingCount { get; set; }
  27 +
  28 + /// <summary>
  29 + /// 开单金额
  30 + /// </summary>
  31 + public decimal BillingAmount { get; set; }
  32 +
  33 + /// <summary>
  34 + /// 消耗数量
  35 + /// </summary>
  36 + public int ConsumeCount { get; set; }
  37 +
  38 + /// <summary>
  39 + /// 消耗金额
  40 + /// </summary>
  41 + public decimal ConsumeAmount { get; set; }
  42 +
  43 + /// <summary>
  44 + /// 退卡数量
  45 + /// </summary>
  46 + public int RefundCount { get; set; }
  47 +
  48 + /// <summary>
  49 + /// 退卡金额
  50 + /// </summary>
  51 + public decimal RefundAmount { get; set; }
  52 + }
  53 +}
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqReport/StorePerformanceComparisonInput.cs 0 → 100644
  1 +using System;
  2 +
  3 +namespace NCC.Extend.Entitys.Dto.LqReport
  4 +{
  5 + /// <summary>
  6 + /// 门店业绩对比统计查询输入
  7 + /// </summary>
  8 + public class StorePerformanceComparisonInput
  9 + {
  10 + /// <summary>
  11 + /// 开始时间
  12 + /// </summary>
  13 + public DateTime? StartTime { get; set; }
  14 +
  15 + /// <summary>
  16 + /// 结束时间
  17 + /// </summary>
  18 + public DateTime? EndTime { get; set; }
  19 +
  20 + /// <summary>
  21 + /// 门店ID列表
  22 + /// </summary>
  23 + public string[] StoreIds { get; set; }
  24 + }
  25 +}
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqReport/StorePerformanceComparisonOutput.cs 0 → 100644
  1 +namespace NCC.Extend.Entitys.Dto.LqReport
  2 +{
  3 + /// <summary>
  4 + /// 门店业绩对比统计输出
  5 + /// </summary>
  6 + public class StorePerformanceComparisonOutput
  7 + {
  8 + /// <summary>
  9 + /// 门店ID
  10 + /// </summary>
  11 + public string StoreId { get; set; }
  12 +
  13 + /// <summary>
  14 + /// 门店名称
  15 + /// </summary>
  16 + public string StoreName { get; set; }
  17 +
  18 + /// <summary>
  19 + /// 目标业绩
  20 + /// </summary>
  21 + public decimal TargetPerformance { get; set; }
  22 +
  23 + /// <summary>
  24 + /// 实际开单业绩
  25 + /// </summary>
  26 + public decimal ActualPerformance { get; set; }
  27 +
  28 + /// <summary>
  29 + /// 完成率(实际业绩/目标业绩)
  30 + /// </summary>
  31 + public decimal CompletionRate { get; set; }
  32 +
  33 + /// <summary>
  34 + /// 差额(实际业绩-目标业绩)
  35 + /// </summary>
  36 + public decimal Difference { get; set; }
  37 +
  38 + /// <summary>
  39 + /// 是否达标(实际业绩 >= 目标业绩)
  40 + /// </summary>
  41 + public bool IsTargetAchieved { get; set; }
  42 + }
  43 +}
netcore/src/Modularity/Extend/NCC.Extend/LqKdKdjlbService.cs
@@ -2728,5 +2728,230 @@ namespace NCC.Extend.LqKdKdjlb @@ -2728,5 +2728,230 @@ namespace NCC.Extend.LqKdKdjlb
2728 } 2728 }
2729 } 2729 }
2730 #endregion 2730 #endregion
  2731 +
  2732 + #region 门店整体统计表
  2733 + /// <summary>
  2734 + /// 门店整体统计表
  2735 + /// </summary>
  2736 + /// <remarks>
  2737 + /// 统计每个健康师在指定时间周期内的各项数据指标
  2738 + /// 包括:邀约人数、预约人数、到店人数、开单人数、开单金额、消耗金额、人头、人次、消耗项目数
  2739 + ///
  2740 + /// 示例请求:
  2741 + /// ```json
  2742 + /// {
  2743 + /// "startTime": "2025-10-01",
  2744 + /// "endTime": "2025-10-31",
  2745 + /// "departmentId": "部门ID",
  2746 + /// "storeId": "门店ID",
  2747 + /// "employeeName": "健康师姓名"
  2748 + /// }
  2749 + /// ```
  2750 + ///
  2751 + /// 参数说明:
  2752 + /// - startTime: 开始时间(可选,默认为当月1号)
  2753 + /// - endTime: 结束时间(可选,默认为当前时间)
  2754 + /// - departmentId: 事业部ID(可选)
  2755 + /// - storeId: 门店ID(可选)
  2756 + /// - employeeName: 健康师姓名(可选)
  2757 + ///
  2758 + /// 返回字段说明:
  2759 + /// - EmployeeId: 健康师ID
  2760 + /// - EmployeeName: 健康师姓名
  2761 + /// - StoreId: 门店ID
  2762 + /// - StoreName: 门店名称
  2763 + /// - DepartmentId: 事业部ID
  2764 + /// - DepartmentName: 事业部名称
  2765 + /// - InviteCount: 邀约人数(按客户去重)
  2766 + /// - AppointmentCount: 预约人数(按客户去重,无论预约状态)
  2767 + /// - VisitCount: 到店人数(按客户去重,仅统计状态为'已确认'的预约)
  2768 + /// - BillingCount: 开单人数(按开单记录去重)
  2769 + /// - BillingAmount: 开单金额(开单业绩总金额)
  2770 + /// - ConsumeAmount: 消耗金额(消耗业绩总金额)
  2771 + /// - HeadCount: 人头(按客户去重)
  2772 + /// - PersonCount: 人次(按客户+日期去重,同一客户不同天算多次)
  2773 + /// - ProjectCount: 消耗项目数(项目总次数)
  2774 + /// </remarks>
  2775 + /// <param name="input">查询参数</param>
  2776 + /// <returns>健康师统计数据列表</returns>
  2777 + /// <response code="200">成功返回统计数据</response>
  2778 + /// <response code="400">参数错误</response>
  2779 + /// <response code="500">服务器错误</response>
  2780 + [HttpGet("get-health-coach-statistics")]
  2781 + public async Task<dynamic> GetHealthCoachStatistics([FromQuery] HealthCoachStatisticsQueryInput input)
  2782 + {
  2783 + try
  2784 + {
  2785 + // 设置默认时间范围(如果未提供,默认为当月)
  2786 + var startTime = input.StartTime ?? new DateTime(DateTime.Now.Year, DateTime.Now.Month, 1);
  2787 + var endTime = input.EndTime ?? DateTime.Now;
  2788 +
  2789 + // 构建SQL查询
  2790 + var sql = $@"
  2791 + SELECT
  2792 + u.F_Id as EmployeeId,
  2793 + u.F_REALNAME as EmployeeName,
  2794 + u.F_MDID as StoreId,
  2795 + md.dm as StoreName,
  2796 + md.syb as DepartmentId,
  2797 + dept.F_FullName as DepartmentName,
  2798 +
  2799 + -- 邀约人数
  2800 + COALESCE(invite_stats.InviteCount, 0) as InviteCount,
  2801 +
  2802 + -- 预约人数(无论状态)
  2803 + COALESCE(appointment_stats.AppointmentCount, 0) as AppointmentCount,
  2804 +
  2805 + -- 到店人数(已确认状态)
  2806 + COALESCE(visit_stats.VisitCount, 0) as VisitCount,
  2807 +
  2808 + -- 开单人数和金额
  2809 + COALESCE(billing_stats.BillingCount, 0) as BillingCount,
  2810 + COALESCE(billing_stats.BillingAmount, 0) as BillingAmount,
  2811 +
  2812 + -- 消耗相关统计
  2813 + COALESCE(consume_stats.ConsumeAmount, 0) as ConsumeAmount,
  2814 + COALESCE(consume_stats.HeadCount, 0) as HeadCount,
  2815 + COALESCE(consume_stats.PersonCount, 0) as PersonCount,
  2816 + COALESCE(consume_stats.ProjectCount, 0) as ProjectCount
  2817 +
  2818 + FROM BASE_USER u
  2819 + LEFT JOIN lq_mdxx md ON u.F_MDID = md.F_Id
  2820 + LEFT JOIN base_organize dept ON md.syb = dept.F_Id
  2821 +
  2822 + -- 邀约统计子查询
  2823 + LEFT JOIN (
  2824 + SELECT
  2825 + yyr as EmployeeId,
  2826 + COUNT(DISTINCT yykh) as InviteCount
  2827 + FROM lq_yaoyjl
  2828 + WHERE yyr IS NOT NULL
  2829 + AND F_CreateTime >= @startTime
  2830 + AND F_CreateTime <= @endTime
  2831 + GROUP BY yyr
  2832 + ) invite_stats ON u.F_Id = invite_stats.EmployeeId
  2833 +
  2834 + -- 预约统计子查询
  2835 + LEFT JOIN (
  2836 + SELECT
  2837 + yyr as EmployeeId,
  2838 + COUNT(DISTINCT gk) as AppointmentCount
  2839 + FROM lq_yyjl
  2840 + WHERE yyr IS NOT NULL
  2841 + AND F_CreateTime >= @startTime
  2842 + AND F_CreateTime <= @endTime
  2843 + GROUP BY yyr
  2844 + ) appointment_stats ON u.F_Id = appointment_stats.EmployeeId
  2845 +
  2846 + -- 到店统计子查询
  2847 + LEFT JOIN (
  2848 + SELECT
  2849 + yyr as EmployeeId,
  2850 + COUNT(DISTINCT gk) as VisitCount
  2851 + FROM lq_yyjl
  2852 + WHERE yyr IS NOT NULL
  2853 + AND F_Status = '已确认'
  2854 + AND F_CreateTime >= @startTime
  2855 + AND F_CreateTime <= @endTime
  2856 + GROUP BY yyr
  2857 + ) visit_stats ON u.F_Id = visit_stats.EmployeeId
  2858 +
  2859 + -- 开单统计子查询
  2860 + LEFT JOIN (
  2861 + SELECT
  2862 + jkszh as EmployeeId,
  2863 + COUNT(DISTINCT glkdbh) as BillingCount,
  2864 + SUM(CAST(jksyj AS DECIMAL(18,2))) as BillingAmount
  2865 + FROM lq_kd_jksyj
  2866 + WHERE jkszh IS NOT NULL
  2867 + AND F_IsEffective = 1
  2868 + AND yjsj >= @startTime
  2869 + AND yjsj <= @endTime
  2870 + GROUP BY jkszh
  2871 + ) billing_stats ON u.F_Id = billing_stats.EmployeeId
  2872 +
  2873 + -- 消耗统计子查询
  2874 + LEFT JOIN (
  2875 + SELECT
  2876 + jksyj.jkszh as EmployeeId,
  2877 + SUM(jksyj.jksyj) as ConsumeAmount,
  2878 + COUNT(DISTINCT hyhk.hy) as HeadCount,
  2879 + COUNT(DISTINCT CONCAT(jksyj.jkszh, '_', hyhk.hy, '_', DATE(hyhk.hksj))) as PersonCount,
  2880 + SUM(jksyj.F_kdpxNumber) as ProjectCount
  2881 + FROM lq_xh_jksyj jksyj
  2882 + INNER JOIN lq_xh_hyhk hyhk ON jksyj.glkdbh = hyhk.F_Id
  2883 + WHERE jksyj.jkszh IS NOT NULL
  2884 + AND jksyj.F_IsEffective = 1
  2885 + AND hyhk.F_IsEffective = 1
  2886 + AND hyhk.hksj >= @startTime
  2887 + AND hyhk.hksj <= @endTime
  2888 + GROUP BY jksyj.jkszh
  2889 + ) consume_stats ON u.F_Id = consume_stats.EmployeeId
  2890 +
  2891 + WHERE u.F_GW = '健康师'
  2892 + ";
  2893 +
  2894 + // 添加条件过滤
  2895 + var conditions = new List<string>();
  2896 + var parameters = new List<SugarParameter>
  2897 + {
  2898 + new SugarParameter("@startTime", startTime),
  2899 + new SugarParameter("@endTime", endTime)
  2900 + };
  2901 +
  2902 + if (!string.IsNullOrEmpty(input.DepartmentId))
  2903 + {
  2904 + conditions.Add("md.syb = @departmentId");
  2905 + parameters.Add(new SugarParameter("@departmentId", input.DepartmentId));
  2906 + }
  2907 +
  2908 + if (!string.IsNullOrEmpty(input.StoreId))
  2909 + {
  2910 + conditions.Add("u.F_MDID = @storeId");
  2911 + parameters.Add(new SugarParameter("@storeId", input.StoreId));
  2912 + }
  2913 +
  2914 + if (!string.IsNullOrEmpty(input.EmployeeName))
  2915 + {
  2916 + conditions.Add("u.F_REALNAME LIKE @employeeName");
  2917 + parameters.Add(new SugarParameter("@employeeName", $"%{input.EmployeeName}%"));
  2918 + }
  2919 +
  2920 + if (conditions.Any())
  2921 + {
  2922 + sql += " AND " + string.Join(" AND ", conditions);
  2923 + }
  2924 +
  2925 + sql += " ORDER BY u.F_REALNAME";
  2926 +
  2927 + // 执行查询
  2928 + var allData = await _db.Ado.SqlQueryAsync<HealthCoachStatisticsOutput>(sql, parameters);
  2929 +
  2930 + // 手动分页
  2931 + var totalCount = allData.Count;
  2932 + var pagedData = allData
  2933 + .Skip((input.currentPage - 1) * input.pageSize)
  2934 + .Take(input.pageSize)
  2935 + .ToList();
  2936 +
  2937 + // 直接返回分页结果
  2938 + return new
  2939 + {
  2940 + list = pagedData,
  2941 + pagination = new
  2942 + {
  2943 + pageIndex = input.currentPage,
  2944 + pageSize = input.pageSize,
  2945 + totalCount = totalCount
  2946 + }
  2947 + };
  2948 + }
  2949 + catch (Exception ex)
  2950 + {
  2951 + _logger.LogError(ex, "获取健康师统计数据失败");
  2952 + throw NCCException.Oh($"获取健康师统计数据失败:{ex.Message}");
  2953 + }
  2954 + }
  2955 + #endregion
2731 } 2956 }
2732 } 2957 }
netcore/src/Modularity/Extend/NCC.Extend/LqReportService.cs
@@ -25,6 +25,8 @@ using NCC.Extend.Entitys.lq_jinsanjiao_user; @@ -25,6 +25,8 @@ using NCC.Extend.Entitys.lq_jinsanjiao_user;
25 using NCC.Extend.Entitys.lq_kd_kdjlb; 25 using NCC.Extend.Entitys.lq_kd_kdjlb;
26 using NCC.Extend.Entitys.lq_xh_hyhk; 26 using NCC.Extend.Entitys.lq_xh_hyhk;
27 using NCC.Extend.Entitys.lq_khxx; 27 using NCC.Extend.Entitys.lq_khxx;
  28 +using NCC.Extend.Entitys.Dto.LqReport;
  29 +using NCC.Extend.Entitys.Enum;
28 using SqlSugar; 30 using SqlSugar;
29 31
30 namespace NCC.Extend 32 namespace NCC.Extend
@@ -40,6 +42,12 @@ namespace NCC.Extend @@ -40,6 +42,12 @@ namespace NCC.Extend
40 private readonly IUserManager _userManager; 42 private readonly IUserManager _userManager;
41 private readonly ILogger<LqReportService> _logger; 43 private readonly ILogger<LqReportService> _logger;
42 44
  45 + /// <summary>
  46 + /// 构造函数
  47 + /// </summary>
  48 + /// <param name="db">数据库客户端</param>
  49 + /// <param name="userManager">用户管理器</param>
  50 + /// <param name="logger">日志记录器</param>
43 public LqReportService(ISqlSugarClient db, IUserManager userManager, ILogger<LqReportService> logger) 51 public LqReportService(ISqlSugarClient db, IUserManager userManager, ILogger<LqReportService> logger)
44 { 52 {
45 _db = db; 53 _db = db;
@@ -47,6 +55,8 @@ namespace NCC.Extend @@ -47,6 +55,8 @@ namespace NCC.Extend
47 _logger = logger; 55 _logger = logger;
48 } 56 }
49 57
  58 +
  59 +
50 #region 门店业绩报表 60 #region 门店业绩报表
51 61
52 /// <summary> 62 /// <summary>
@@ -681,6 +691,522 @@ namespace NCC.Extend @@ -681,6 +691,522 @@ namespace NCC.Extend
681 } 691 }
682 } 692 }
683 693
  694 + /// <summary>
  695 + /// 获取业务统计数据
  696 + /// </summary>
  697 + /// <remarks>
  698 + /// 统计指定时间范围内的开单、耗卡、退卡相关数据
  699 + /// 包括:开单总金额、耗卡总金额、退卡总金额、开单人数、耗卡人数、退卡人数
  700 + ///
  701 + /// 示例请求:
  702 + /// ```json
  703 + /// {
  704 + /// "startTime": "2025-10-01",
  705 + /// "endTime": "2025-10-31",
  706 + /// "storeIds": ["门店ID1", "门店ID2"]
  707 + /// }
  708 + /// ```
  709 + ///
  710 + /// 参数说明:
  711 + /// - startTime: 开始时间(可选,默认为当月1号)
  712 + /// - endTime: 结束时间(可选,默认为当前时间)
  713 + /// - storeIds: 门店ID列表(可选)
  714 + ///
  715 + /// 返回字段说明:
  716 + /// - TotalBillingAmount: 开单总金额
  717 + /// - TotalConsumeAmount: 耗卡总金额
  718 + /// - TotalRefundAmount: 退卡总金额
  719 + /// - BillingCount: 开单人数(按客户去重)
  720 + /// - ConsumeCount: 耗卡人数(按客户去重)
  721 + /// - RefundCount: 退卡人数(按客户去重)
  722 + /// </remarks>
  723 + /// <param name="input">查询参数</param>
  724 + /// <returns>业务统计数据</returns>
  725 + /// <response code="200">成功返回统计数据</response>
  726 + /// <response code="400">参数错误</response>
  727 + /// <response code="500">服务器错误</response>
  728 + [HttpPost("get-business-statistics")]
  729 + public async Task<object> GetBusinessStatistics(BusinessStatisticsInput input)
  730 + {
  731 + try
  732 + {
  733 + // 设置默认时间范围(如果未提供,默认为当月)
  734 + var startTime = input.StartTime ?? new DateTime(DateTime.Now.Year, DateTime.Now.Month, 1);
  735 + var endTime = input.EndTime ?? DateTime.Now;
  736 +
  737 + // 第一步:获取开单统计数据
  738 + var billingSql = @"
  739 + SELECT
  740 + COUNT(DISTINCT kd.kdhy) as billing_count,
  741 + COALESCE(SUM(CAST(kd.sfyj AS DECIMAL(18,2))), 0) as billing_amount
  742 + FROM lq_kd_kdjlb kd
  743 + WHERE kd.F_IsEffective = 1
  744 + AND kd.kdrq >= @startTime
  745 + AND kd.kdrq <= @endTime";
  746 +
  747 + object billingParameters;
  748 + if (input.StoreIds != null && input.StoreIds.Any())
  749 + {
  750 + billingSql += " AND kd.F_StoreId IN @storeIds";
  751 + billingParameters = new { startTime, endTime, storeIds = input.StoreIds };
  752 + }
  753 + else
  754 + {
  755 + billingParameters = new { startTime, endTime };
  756 + }
  757 +
  758 + var billingResult = await _db.Ado.SqlQueryAsync<dynamic>(billingSql, billingParameters);
  759 + var billingCount = Convert.ToInt32(billingResult?.FirstOrDefault()?.billing_count ?? 0);
  760 + var billingAmount = Convert.ToDecimal(billingResult?.FirstOrDefault()?.billing_amount ?? 0m);
  761 +
  762 + // 第二步:获取耗卡统计数据
  763 + var consumeSql = @"
  764 + SELECT
  765 + COUNT(DISTINCT xh.hy) as consume_count,
  766 + COALESCE(SUM(CAST(xh.xfje AS DECIMAL(18,2))), 0) as consume_amount
  767 + FROM lq_xh_hyhk xh
  768 + WHERE xh.F_IsEffective = 1
  769 + AND xh.hksj >= @startTime
  770 + AND xh.hksj <= @endTime";
  771 +
  772 + object consumeParameters;
  773 + if (input.StoreIds != null && input.StoreIds.Any())
  774 + {
  775 + consumeSql += " AND xh.F_StoreId IN @storeIds";
  776 + consumeParameters = new { startTime, endTime, storeIds = input.StoreIds };
  777 + }
  778 + else
  779 + {
  780 + consumeParameters = new { startTime, endTime };
  781 + }
  782 +
  783 + var consumeResult = await _db.Ado.SqlQueryAsync<dynamic>(consumeSql, consumeParameters);
  784 + var consumeCount = Convert.ToInt32(consumeResult?.FirstOrDefault()?.consume_count ?? 0);
  785 + var consumeAmount = Convert.ToDecimal(consumeResult?.FirstOrDefault()?.consume_amount ?? 0m);
  786 +
  787 + // 第三步:获取退卡统计数据
  788 + var refundSql = @"
  789 + SELECT
  790 + COUNT(DISTINCT hytk.hy) as refund_count,
  791 + COALESCE(SUM(CAST(hytk.tkje AS DECIMAL(18,2))), 0) as refund_amount
  792 + FROM lq_hytk_hytk hytk
  793 + WHERE hytk.F_IsEffective = 1
  794 + AND hytk.tksj >= @startTime
  795 + AND hytk.tksj <= @endTime";
  796 +
  797 + object refundParameters;
  798 + if (input.StoreIds != null && input.StoreIds.Any())
  799 + {
  800 + refundSql += " AND hytk.F_StoreId IN @storeIds";
  801 + refundParameters = new { startTime, endTime, storeIds = input.StoreIds };
  802 + }
  803 + else
  804 + {
  805 + refundParameters = new { startTime, endTime };
  806 + }
  807 +
  808 + var refundResult = await _db.Ado.SqlQueryAsync<dynamic>(refundSql, refundParameters);
  809 + var refundCount = Convert.ToInt32(refundResult?.FirstOrDefault()?.refund_count ?? 0);
  810 + var refundAmount = Convert.ToDecimal(refundResult?.FirstOrDefault()?.refund_amount ?? 0m);
  811 +
  812 + var result = new BusinessStatisticsOutput
  813 + {
  814 + TotalBillingAmount = billingAmount,
  815 + TotalConsumeAmount = consumeAmount,
  816 + TotalRefundAmount = refundAmount,
  817 + BillingCount = billingCount,
  818 + ConsumeCount = consumeCount,
  819 + RefundCount = refundCount
  820 + };
  821 +
  822 + return result;
  823 + }
  824 + catch (Exception ex)
  825 + {
  826 + _logger.LogError(ex, $"获取业务统计数据失败 - 开始时间: {input?.StartTime}, 结束时间: {input?.EndTime}");
  827 + throw NCCException.Oh($"获取业务统计数据失败: {ex.Message}");
  828 + }
  829 + }
  830 +
  831 + /// <summary>
  832 + /// 获取客户类型统计数据
  833 + /// </summary>
  834 + /// <remarks>
  835 + /// 统计指定时间范围内不同类型的客户数量和转化率
  836 + /// 包括:线索、新客、散客、会员数量,以及拓客人数、消耗人数、转化率
  837 + ///
  838 + /// 示例请求:
  839 + /// ```json
  840 + /// {
  841 + /// "startTime": "2025-10-01",
  842 + /// "endTime": "2025-10-31",
  843 + /// "storeIds": ["门店ID1", "门店ID2"]
  844 + /// }
  845 + /// ```
  846 + ///
  847 + /// 参数说明:
  848 + /// - startTime: 开始时间(可选,默认为当月1号)
  849 + /// - endTime: 结束时间(可选,默认为当前时间)
  850 + /// - storeIds: 门店ID列表(可选)
  851 + ///
  852 + /// 返回字段说明:
  853 + /// - LeadCount: 线索客户数量
  854 + /// - NewCustomerCount: 新客数量
  855 + /// - CasualCustomerCount: 散客数量
  856 + /// - MemberCount: 会员数量
  857 + /// - TotalInviteCount: 拓客总人数
  858 + /// - ConsumeCount: 消耗人数(有消耗金额的)
  859 + /// - ConversionRate: 转化率(消耗人数/拓客人数)
  860 + /// </remarks>
  861 + /// <param name="input">查询参数</param>
  862 + /// <returns>客户类型统计数据</returns>
  863 + /// <response code="200">成功返回统计数据</response>
  864 + /// <response code="400">参数错误</response>
  865 + /// <response code="500">服务器错误</response>
  866 + [HttpPost("get-customer-type-statistics")]
  867 + public async Task<object> GetCustomerTypeStatistics(CustomerTypeStatisticsInput input)
  868 + {
  869 + try
  870 + {
  871 + // 设置默认时间范围(如果未提供,默认为当月)
  872 + var startTime = input.StartTime ?? new DateTime(DateTime.Now.Year, DateTime.Now.Month, 1);
  873 + var endTime = input.EndTime ?? DateTime.Now;
  874 +
  875 + // 第一步:获取客户类型统计
  876 + var customerTypeSql = $@"
  877 + SELECT
  878 + SUM(CASE WHEN kh.khlx = '{MemberTypeEnum.线索.GetHashCode()}' THEN 1 ELSE 0 END) as lead_count,
  879 + SUM(CASE WHEN kh.khlx = '{MemberTypeEnum.新客.GetHashCode()}' THEN 1 ELSE 0 END) as new_customer_count,
  880 + SUM(CASE WHEN kh.khlx = '{MemberTypeEnum.散客.GetHashCode()}' THEN 1 ELSE 0 END) as casual_customer_count,
  881 + SUM(CASE WHEN kh.khlx = '{MemberTypeEnum.会员.GetHashCode()}' THEN 1 ELSE 0 END) as member_count
  882 + FROM lq_khxx kh
  883 + WHERE kh.F_CreateTime >= @startTime
  884 + AND kh.F_CreateTime <= @endTime";
  885 +
  886 + object customerTypeParameters;
  887 + if (input.StoreIds != null && input.StoreIds.Any())
  888 + {
  889 + customerTypeSql += " AND kh.F_StoreId IN @storeIds";
  890 + customerTypeParameters = new { startTime, endTime, storeIds = input.StoreIds };
  891 + }
  892 + else
  893 + {
  894 + customerTypeParameters = new { startTime, endTime };
  895 + }
  896 +
  897 + var customerTypeResult = await _db.Ado.SqlQueryAsync<dynamic>(customerTypeSql, customerTypeParameters);
  898 + var leadCount = Convert.ToInt32(customerTypeResult?.FirstOrDefault()?.lead_count ?? 0);
  899 + var newCustomerCount = Convert.ToInt32(customerTypeResult?.FirstOrDefault()?.new_customer_count ?? 0);
  900 + var casualCustomerCount = Convert.ToInt32(customerTypeResult?.FirstOrDefault()?.casual_customer_count ?? 0);
  901 + var memberCount = Convert.ToInt32(customerTypeResult?.FirstOrDefault()?.member_count ?? 0);
  902 +
  903 + // 第二步:获取拓客总人数(按客户去重)
  904 + var inviteSql = @"
  905 + SELECT COUNT(DISTINCT tk.F_MemberId) as invite_count
  906 + FROM lq_tkjlb tk
  907 + WHERE tk.F_CreateTime >= @startTime
  908 + AND tk.F_CreateTime <= @endTime";
  909 +
  910 + object inviteParameters;
  911 + if (input.StoreIds != null && input.StoreIds.Any())
  912 + {
  913 + inviteSql += " AND tk.F_StoreId IN @storeIds";
  914 + inviteParameters = new { startTime, endTime, storeIds = input.StoreIds };
  915 + }
  916 + else
  917 + {
  918 + inviteParameters = new { startTime, endTime };
  919 + }
  920 +
  921 + var inviteResult = await _db.Ado.SqlQueryAsync<dynamic>(inviteSql, inviteParameters);
  922 + var totalInviteCount = Convert.ToInt32(inviteResult?.FirstOrDefault()?.invite_count ?? 0);
  923 +
  924 + // 第三步:获取消耗人数(有消耗金额的,按客户去重)
  925 + var consumeSql = @"
  926 + SELECT COUNT(DISTINCT xh.hy) as consume_count
  927 + FROM lq_xh_hyhk xh
  928 + WHERE xh.F_IsEffective = 1
  929 + AND xh.hksj >= @startTime
  930 + AND xh.hksj <= @endTime
  931 + AND xh.xfje > 0";
  932 +
  933 + object consumeParameters;
  934 + if (input.StoreIds != null && input.StoreIds.Any())
  935 + {
  936 + consumeSql += " AND xh.F_StoreId IN @storeIds";
  937 + consumeParameters = new { startTime, endTime, storeIds = input.StoreIds };
  938 + }
  939 + else
  940 + {
  941 + consumeParameters = new { startTime, endTime };
  942 + }
  943 +
  944 + var consumeResult = await _db.Ado.SqlQueryAsync<dynamic>(consumeSql, consumeParameters);
  945 + var consumeCount = Convert.ToInt32(consumeResult?.FirstOrDefault()?.consume_count ?? 0);
  946 +
  947 + // 计算转化率
  948 + var conversionRate = totalInviteCount > 0 ? Math.Round(consumeCount * 100.0m / totalInviteCount, 2) : 0;
  949 +
  950 + var result = new CustomerTypeStatisticsOutput
  951 + {
  952 + LeadCount = leadCount,
  953 + NewCustomerCount = newCustomerCount,
  954 + CasualCustomerCount = casualCustomerCount,
  955 + MemberCount = memberCount,
  956 + TotalInviteCount = totalInviteCount,
  957 + ConsumeCount = consumeCount,
  958 + ConversionRate = conversionRate
  959 + };
  960 +
  961 + return result;
  962 + }
  963 + catch (Exception ex)
  964 + {
  965 + _logger.LogError(ex, $"获取客户类型统计数据失败 - 开始时间: {input?.StartTime}, 结束时间: {input?.EndTime}");
  966 + throw NCCException.Oh($"获取客户类型统计数据失败: {ex.Message}");
  967 + }
  968 + }
  969 +
  970 + /// <summary>
  971 + /// 获取门店业绩对比统计数据
  972 + /// </summary>
  973 + /// <remarks>
  974 + /// 统计指定时间范围内各门店的开单业绩与目标业绩的对比情况
  975 + /// 包括:目标业绩、实际开单业绩、完成率、差额、是否达标
  976 + ///
  977 + /// 示例请求:
  978 + /// ```json
  979 + /// {
  980 + /// "startTime": "2025-10-01",
  981 + /// "endTime": "2025-10-31",
  982 + /// "storeIds": ["门店ID1", "门店ID2"]
  983 + /// }
  984 + /// ```
  985 + ///
  986 + /// 参数说明:
  987 + /// - startTime: 开始时间(可选,默认为当月1号)
  988 + /// - endTime: 结束时间(可选,默认为当前时间)
  989 + /// - storeIds: 门店ID列表(可选)
  990 + ///
  991 + /// 返回字段说明:
  992 + /// - StoreId: 门店ID
  993 + /// - StoreName: 门店名称
  994 + /// - TargetPerformance: 目标业绩(来自门店资料表xsyj字段)
  995 + /// - ActualPerformance: 实际开单业绩(统计期间内的开单业绩总和)
  996 + /// - CompletionRate: 完成率(实际业绩/目标业绩 × 100%)
  997 + /// - Difference: 差额(实际业绩-目标业绩)
  998 + /// - IsTargetAchieved: 是否达标(实际业绩 >= 目标业绩)
  999 + /// </remarks>
  1000 + /// <param name="input">查询参数</param>
  1001 + /// <returns>门店业绩对比统计数据</returns>
  1002 + /// <response code="200">成功返回统计数据</response>
  1003 + /// <response code="400">参数错误</response>
  1004 + /// <response code="500">服务器错误</response>
  1005 + [HttpPost("get-store-performance-comparison")]
  1006 + public async Task<object> GetStorePerformanceComparison(StorePerformanceComparisonInput input)
  1007 + {
  1008 + try
  1009 + {
  1010 + // 设置默认时间范围(如果未提供,默认为当月)
  1011 + var startTime = input.StartTime ?? new DateTime(DateTime.Now.Year, DateTime.Now.Month, 1);
  1012 + var endTime = input.EndTime ?? DateTime.Now;
  1013 +
  1014 + // 构建SQL查询
  1015 + var sql = @"
  1016 + SELECT
  1017 + md.F_Id as store_id,
  1018 + md.dm as store_name,
  1019 + COALESCE(CAST(md.xsyj AS DECIMAL(18,2)), 0) as target_performance,
  1020 + COALESCE(SUM(CAST(kd.sfyj AS DECIMAL(18,2))), 0) as actual_performance
  1021 + FROM lq_mdxx md
  1022 + LEFT JOIN lq_kd_kdjlb kd ON md.F_Id = kd.djmd
  1023 + AND kd.F_IsEffective = 1
  1024 + AND kd.kdrq >= @startTime
  1025 + AND kd.kdrq <= @endTime";
  1026 +
  1027 + object parameters;
  1028 + if (input.StoreIds != null && input.StoreIds.Any())
  1029 + {
  1030 + sql += " AND md.F_Id IN @storeIds";
  1031 + parameters = new { startTime, endTime, storeIds = input.StoreIds };
  1032 + }
  1033 + else
  1034 + {
  1035 + parameters = new { startTime, endTime };
  1036 + }
  1037 +
  1038 + sql += " GROUP BY md.F_Id, md.dm, md.xsyj ORDER BY actual_performance DESC";
  1039 +
  1040 + var results = await _db.Ado.SqlQueryAsync<dynamic>(sql, parameters);
  1041 +
  1042 + var storePerformanceList = new List<StorePerformanceComparisonOutput>();
  1043 +
  1044 + foreach (var item in results)
  1045 + {
  1046 + var storeId = item.store_id?.ToString();
  1047 + var storeName = item.store_name?.ToString();
  1048 + var targetPerformance = Convert.ToDecimal(item.target_performance ?? 0);
  1049 + var actualPerformance = Convert.ToDecimal(item.actual_performance ?? 0);
  1050 +
  1051 + // 计算完成率
  1052 + var completionRate = targetPerformance > 0
  1053 + ? Math.Round(actualPerformance * 100.0m / targetPerformance, 2)
  1054 + : 0;
  1055 +
  1056 + // 计算差额
  1057 + var difference = actualPerformance - targetPerformance;
  1058 +
  1059 + // 判断是否达标
  1060 + var isTargetAchieved = actualPerformance >= targetPerformance;
  1061 +
  1062 + storePerformanceList.Add(new StorePerformanceComparisonOutput
  1063 + {
  1064 + StoreId = storeId,
  1065 + StoreName = storeName,
  1066 + TargetPerformance = targetPerformance,
  1067 + ActualPerformance = actualPerformance,
  1068 + CompletionRate = completionRate,
  1069 + Difference = difference,
  1070 + IsTargetAchieved = isTargetAchieved
  1071 + });
  1072 + }
  1073 +
  1074 + return storePerformanceList;
  1075 + }
  1076 + catch (Exception ex)
  1077 + {
  1078 + _logger.LogError(ex, $"获取门店业绩对比统计数据失败 - 开始时间: {input?.StartTime}, 结束时间: {input?.EndTime}");
  1079 + throw NCCException.Oh($"获取门店业绩对比统计数据失败: {ex.Message}");
  1080 + }
  1081 + }
  1082 +
  1083 + /// <summary>
  1084 + /// 获取品项统计数据
  1085 + /// </summary>
  1086 + /// <remarks>
  1087 + /// 统计指定时间范围内各品项的开单、消耗、退卡相关数据
  1088 + /// 包括:开单数量、开单金额、消耗数量、消耗金额、退卡数量、退卡金额
  1089 + ///
  1090 + /// 示例请求:
  1091 + /// ```json
  1092 + /// {
  1093 + /// "startTime": "2025-10-01",
  1094 + /// "endTime": "2025-10-31",
  1095 + /// "storeIds": ["门店ID1", "门店ID2"]
  1096 + /// }
  1097 + /// ```
  1098 + ///
  1099 + /// 参数说明:
  1100 + /// - startTime: 开始时间(可选,默认为当月1号)
  1101 + /// - endTime: 结束时间(可选,默认为当前时间)
  1102 + /// - storeIds: 门店ID列表(可选)
  1103 + ///
  1104 + /// 返回字段说明:
  1105 + /// - ItemId: 品项ID
  1106 + /// - ItemName: 品项名称
  1107 + /// - ItemNumber: 品项编号
  1108 + /// - BillingCount: 开单数量
  1109 + /// - BillingAmount: 开单金额
  1110 + /// - ConsumeCount: 消耗数量
  1111 + /// - ConsumeAmount: 消耗金额
  1112 + /// - RefundCount: 退卡数量
  1113 + /// - RefundAmount: 退卡金额
  1114 + /// </remarks>
  1115 + /// <param name="input">查询参数</param>
  1116 + /// <returns>品项统计数据列表(按开单金额降序排列)</returns>
  1117 + /// <response code="200">成功返回统计数据</response>
  1118 + /// <response code="400">参数错误</response>
  1119 + /// <response code="500">服务器错误</response>
  1120 + [HttpPost("get-item-statistics")]
  1121 + public async Task<object> GetItemStatistics(ItemStatisticsInput input)
  1122 + {
  1123 + try
  1124 + {
  1125 + // 设置默认时间范围(如果未提供,默认为当月)
  1126 + var startTime = input.StartTime ?? new DateTime(DateTime.Now.Year, DateTime.Now.Month, 1);
  1127 + var endTime = input.EndTime ?? DateTime.Now;
  1128 +
  1129 + // 构建SQL查询
  1130 + var sql = @"
  1131 + SELECT
  1132 + xm.F_Id as item_id,
  1133 + xm.xmmc as item_name,
  1134 + xm.xmbh as item_number,
  1135 +
  1136 + -- 开单统计
  1137 + COALESCE(SUM(CASE WHEN px.F_IsEffective = 1 AND px.yjsj >= @startTime AND px.yjsj <= @endTime THEN px.F_ProjectNumber ELSE 0 END), 0) as billing_count,
  1138 + COALESCE(SUM(CASE WHEN px.F_IsEffective = 1 AND px.yjsj >= @startTime AND px.yjsj <= @endTime THEN CAST(px.F_ActualPrice AS DECIMAL(18,2)) ELSE 0 END), 0) as billing_amount,
  1139 +
  1140 + -- 消耗统计
  1141 + COALESCE(SUM(CASE WHEN xh.F_IsEffective = 1 AND xhhyhk.hksj >= @startTime AND xhhyhk.hksj <= @endTime THEN xh.F_ProjectNumber ELSE 0 END), 0) as consume_count,
  1142 + COALESCE(SUM(CASE WHEN xh.F_IsEffective = 1 AND xhhyhk.hksj >= @startTime AND xhhyhk.hksj <= @endTime THEN CAST(xh.F_TotalPrice AS DECIMAL(18,2)) ELSE 0 END), 0) as consume_amount,
  1143 +
  1144 + -- 退卡统计
  1145 + COALESCE(SUM(CASE WHEN hytkmx.F_IsEffective = 1 AND hytkmx.tksj >= @startTime AND hytkmx.tksj <= @endTime THEN hytkmx.F_ProjectNumber ELSE 0 END), 0) as refund_count,
  1146 + COALESCE(SUM(CASE WHEN hytkmx.F_IsEffective = 1 AND hytkmx.tksj >= @startTime AND hytkmx.tksj <= @endTime THEN CAST(hytkmx.tkje AS DECIMAL(18,2)) ELSE 0 END), 0) as refund_amount
  1147 +
  1148 + FROM lq_xmzl xm
  1149 +
  1150 + -- 开单品项明细
  1151 + LEFT JOIN lq_kd_pxmx px ON xm.F_Id = px.px
  1152 +
  1153 + -- 消耗品项明细
  1154 + LEFT JOIN lq_xh_pxmx xh ON xm.F_Id = xh.px
  1155 + LEFT JOIN lq_xh_hyhk xhhyhk ON xh.F_ConsumeInfoId = xhhyhk.F_Id
  1156 +
  1157 + -- 退卡品项明细
  1158 + LEFT JOIN lq_hytk_mx hytkmx ON xm.F_Id = hytkmx.px
  1159 +
  1160 + WHERE xm.F_IsEffective = 1";
  1161 +
  1162 + object parameters;
  1163 + if (input.StoreIds != null && input.StoreIds.Any())
  1164 + {
  1165 + sql += @" AND (
  1166 + (px.glkdbh IN (SELECT F_Id FROM lq_kd_kdjlb WHERE djmd IN @storeIds)) OR
  1167 + (xhhyhk.F_StoreId IN @storeIds) OR
  1168 + (hytkmx.F_RefundInfoId IN (SELECT F_Id FROM lq_hytk_hytk WHERE F_StoreId IN @storeIds))
  1169 + )";
  1170 + parameters = new { startTime, endTime, storeIds = input.StoreIds };
  1171 + }
  1172 + else
  1173 + {
  1174 + parameters = new { startTime, endTime };
  1175 + }
  1176 +
  1177 + sql += @" GROUP BY xm.F_Id, xm.xmmc, xm.xmbh
  1178 + HAVING (billing_count > 0 OR consume_count > 0 OR refund_count > 0)
  1179 + ORDER BY billing_amount DESC";
  1180 +
  1181 + var results = await _db.Ado.SqlQueryAsync<dynamic>(sql, parameters);
  1182 +
  1183 + var itemStatisticsList = new List<ItemStatisticsOutput>();
  1184 +
  1185 + foreach (var item in results)
  1186 + {
  1187 + itemStatisticsList.Add(new ItemStatisticsOutput
  1188 + {
  1189 + ItemId = item.item_id?.ToString(),
  1190 + ItemName = item.item_name?.ToString(),
  1191 + ItemNumber = item.item_number?.ToString(),
  1192 + BillingCount = Convert.ToInt32(item.billing_count ?? 0),
  1193 + BillingAmount = Convert.ToDecimal(item.billing_amount ?? 0),
  1194 + ConsumeCount = Convert.ToInt32(item.consume_count ?? 0),
  1195 + ConsumeAmount = Convert.ToDecimal(item.consume_amount ?? 0),
  1196 + RefundCount = Convert.ToInt32(item.refund_count ?? 0),
  1197 + RefundAmount = Convert.ToDecimal(item.refund_amount ?? 0)
  1198 + });
  1199 + }
  1200 +
  1201 + return itemStatisticsList;
  1202 + }
  1203 + catch (Exception ex)
  1204 + {
  1205 + _logger.LogError(ex, $"获取品项统计数据失败 - 开始时间: {input?.StartTime}, 结束时间: {input?.EndTime}");
  1206 + throw NCCException.Oh($"获取品项统计数据失败: {ex.Message}");
  1207 + }
  1208 + }
  1209 +
684 #endregion 1210 #endregion
685 } 1211 }
686 } 1212 }
netcore/src/Modularity/Extend/NCC.Extend/LqTkjlbService.cs
@@ -609,49 +609,83 @@ namespace NCC.Extend.LqTkjlb @@ -609,49 +609,83 @@ namespace NCC.Extend.LqTkjlb
609 { 609 {
610 try 610 try
611 { 611 {
612 - var sql = @" 612 + // 第一步:获取拓客总数
  613 + var tkSql = @"
  614 + SELECT COUNT(DISTINCT F_Id) as tk_count
  615 + FROM lq_tkjlb
  616 + WHERE F_EventId = @eventId";
  617 +
  618 + var tkResult = await _db.Ado.SqlQueryAsync<dynamic>(tkSql, new { eventId });
  619 + var tkCount = tkResult?.FirstOrDefault()?.tk_count ?? 0;
  620 +
  621 + // 第二步:获取邀约总数(按会员ID去重)
  622 + var yaoySql = @"
  623 + SELECT COUNT(DISTINCT tk.F_MemberId) as yaoy_count
  624 + FROM lq_tkjlb tk
  625 + LEFT JOIN lq_yaoyjl yy ON tk.F_MemberId = yy.yykh AND yy.F_StoreId = tk.F_StoreId
  626 + WHERE tk.F_EventId = @eventId AND yy.F_Id IS NOT NULL";
  627 +
  628 + var yaoyResult = await _db.Ado.SqlQueryAsync<dynamic>(yaoySql, new { eventId });
  629 + var yaoyCount = yaoyResult?.FirstOrDefault()?.yaoy_count ?? 0;
  630 +
  631 + // 第三步:获取预约总数(按会员ID去重)
  632 + var yySql = @"
  633 + SELECT COUNT(DISTINCT tk.F_MemberId) as yy_count
  634 + FROM lq_tkjlb tk
  635 + LEFT JOIN lq_yyjl yyjl ON tk.F_MemberId = yyjl.gk AND yyjl.F_Status = '已确认'
  636 + WHERE tk.F_EventId = @eventId AND yyjl.F_Id IS NOT NULL";
  637 +
  638 + var yyResult = await _db.Ado.SqlQueryAsync<dynamic>(yySql, new { eventId });
  639 + var yyCount = yyResult?.FirstOrDefault()?.yy_count ?? 0;
  640 +
  641 + // 第四步:获取耗卡总数和金额(按会员ID去重,但金额不去重)
  642 + var hkSql = @"
613 SELECT 643 SELECT
614 - SUM(tk_count) as total_tk_count,  
615 - SUM(yaoy_count) as total_yaoy_count,  
616 - SUM(yy_count) as total_yy_count,  
617 - SUM(hk_count) as total_hk_count,  
618 - SUM(kd_count) as total_kd_count,  
619 - SUM(hk_amount) as total_hk_amount,  
620 - SUM(kd_amount) as total_kd_amount,  
621 - CASE  
622 - WHEN SUM(tk_count) > 0  
623 - THEN ROUND(SUM(yy_count) * 100.0 / SUM(tk_count), 2)  
624 - ELSE 0  
625 - END as overall_yy_conversion_rate,  
626 - CASE  
627 - WHEN SUM(yy_count) > 0  
628 - THEN ROUND(SUM(hk_count) * 100.0 / SUM(yy_count), 2)  
629 - ELSE 0  
630 - END as overall_hk_conversion_rate  
631 - FROM (  
632 - SELECT  
633 - COUNT(DISTINCT tk.F_Id) as tk_count,  
634 - COUNT(DISTINCT yy.F_Id) as yaoy_count,  
635 - COUNT(DISTINCT yyjl.F_Id) as yy_count,  
636 - COUNT(DISTINCT xh.F_Id) as hk_count,  
637 - COUNT(DISTINCT kd.F_Id) as kd_count,  
638 - COALESCE(SUM(xh.xfje), 0) as hk_amount,  
639 - COALESCE(SUM(kd.sfyj), 0) as kd_amount  
640 - FROM lq_tkjlb tk  
641 - LEFT JOIN lq_yaoyjl yy ON tk.F_MemberId = yy.yykh AND yy.F_StoreId = tk.F_StoreId  
642 - LEFT JOIN lq_yyjl yyjl ON tk.F_MemberId = yyjl.gk AND yyjl.F_Status = '已确认'  
643 - LEFT JOIN lq_xh_hyhk xh ON tk.F_MemberId = xh.hy AND xh.F_IsEffective = 1  
644 - LEFT JOIN lq_kd_kdjlb kd ON tk.F_MemberId = kd.kdhy AND kd.F_IsEffective = 1  
645 - WHERE tk.F_EventId = @eventId  
646 - ) stats"; 644 + COUNT(DISTINCT tk.F_MemberId) as hk_count,
  645 + COALESCE(SUM(xh.xfje), 0) as hk_amount
  646 + FROM lq_tkjlb tk
  647 + LEFT JOIN lq_xh_hyhk xh ON tk.F_MemberId = xh.hy AND xh.F_IsEffective = 1
  648 + WHERE tk.F_EventId = @eventId AND xh.F_Id IS NOT NULL";
647 649
648 - var result = await _db.Ado.SqlQueryAsync<dynamic>(sql, new { eventId }); 650 + var hkResult = await _db.Ado.SqlQueryAsync<dynamic>(hkSql, new { eventId });
  651 + var hkCount = hkResult?.FirstOrDefault()?.hk_count ?? 0;
  652 + var hkAmount = hkResult?.FirstOrDefault()?.hk_amount ?? 0m;
  653 +
  654 + // 第五步:获取开单总数和金额(按会员ID去重,但金额不去重)
  655 + var kdSql = @"
  656 + SELECT
  657 + COUNT(DISTINCT tk.F_MemberId) as kd_count,
  658 + COALESCE(SUM(kd.sfyj), 0) as kd_amount
  659 + FROM lq_tkjlb tk
  660 + LEFT JOIN lq_kd_kdjlb kd ON tk.F_MemberId = kd.kdhy AND kd.F_IsEffective = 1
  661 + WHERE tk.F_EventId = @eventId AND kd.F_Id IS NOT NULL";
  662 +
  663 + var kdResult = await _db.Ado.SqlQueryAsync<dynamic>(kdSql, new { eventId });
  664 + var kdCount = kdResult?.FirstOrDefault()?.kd_count ?? 0;
  665 + var kdAmount = kdResult?.FirstOrDefault()?.kd_amount ?? 0m;
  666 +
  667 + // 计算转化率
  668 + var yyConversionRate = tkCount > 0 ? Math.Round(yyCount * 100.0 / tkCount, 2) : 0;
  669 + var hkConversionRate = yyCount > 0 ? Math.Round(hkCount * 100.0 / yyCount, 2) : 0;
  670 +
  671 + var result = new
  672 + {
  673 + total_tk_count = tkCount,
  674 + total_yaoy_count = yaoyCount,
  675 + total_yy_count = yyCount,
  676 + total_hk_count = hkCount,
  677 + total_kd_count = kdCount,
  678 + total_hk_amount = hkAmount,
  679 + total_kd_amount = kdAmount,
  680 + overall_yy_conversion_rate = yyConversionRate,
  681 + overall_hk_conversion_rate = hkConversionRate
  682 + };
649 683
650 return new 684 return new
651 { 685 {
652 success = true, 686 success = true,
653 - data = result?.FirstOrDefault(),  
654 - message = "获取总体漏斗统计数据成功" 687 + data = result,
  688 + message = "获取总体漏斗统计数据成功(高性能版本)"
655 }; 689 };
656 } 690 }
657 catch (Exception ex) 691 catch (Exception ex)