Commit f7dd1d1b90d40801ab485b75588f44a21baf3f0a

Authored by 李宇
2 parents 5b6e99a5 7855bf04

Merge branch 'master' of http://39.98.150.180/antissoft/lvqianmeiye_ERP

Showing 28 changed files with 1703 additions and 65 deletions
antis-ncc-admin/src/views/lqInventory/InventoryInfoDialog.vue
... ... @@ -191,4 +191,3 @@ export default {
191 191 text-align: right;
192 192 }
193 193 </style>
194   -
... ...
antis-ncc-admin/src/views/personalPerformanceStatistics/index.vue
... ... @@ -50,7 +50,7 @@
50 50 <el-table-column prop="EmployeeName" label="员工姓名" width="120" fixed="left"></el-table-column>
51 51 <el-table-column prop="StoreName" label="门店名称" width="150" fixed="left"></el-table-column>
52 52 <el-table-column prop="Position" label="岗位" width="100" fixed="left"></el-table-column>
53   - <el-table-column prop="GoldTriangleTeam" label="金三角战队" width="120" fixed="left"></el-table-column>
  53 + <el-table-column prop="GoldTriangleName" label="金三角战队" width="120" fixed="left"></el-table-column>
54 54 <el-table-column prop="TotalPerformance" label="总业绩" width="100" align="right">
55 55 <template slot-scope="scope">
56 56 {{ formatMoney(scope.row.TotalPerformance) }}
... ... @@ -66,6 +66,22 @@
66 66 {{ formatMoney(scope.row.UpgradeOrderPerformance) }}
67 67 </template>
68 68 </el-table-column>
  69 + <el-table-column prop="RefundPerformance" label="退单业绩" width="100" align="right">
  70 + <template slot-scope="scope">
  71 + <span style="color: #F56C6C;">{{ formatMoney(scope.row.RefundPerformance) }}</span>
  72 + </template>
  73 + </el-table-column>
  74 + <el-table-column prop="RefundCount" label="退单次数" width="100" align="right">
  75 + <template slot-scope="scope">
  76 + <span style="color: #F56C6C;">{{ scope.row.RefundCount || 0 }}</span>
  77 + </template>
  78 + </el-table-column>
  79 + <el-table-column prop="ActualPerformance" label="实际业绩" width="100" align="right">
  80 + <template slot-scope="scope">
  81 + <span style="color: #67C23A; font-weight: bold;">{{ formatMoney(scope.row.ActualPerformance)
  82 + }}</span>
  83 + </template>
  84 + </el-table-column>
69 85 <el-table-column prop="FirstOrderCount" label="首开单数量" width="100" align="right"></el-table-column>
70 86 <el-table-column prop="UpgradeOrderCount" label="升单数量" width="100" align="right"></el-table-column>
71 87 <el-table-column prop="OrderCount" label="订单总数" width="100" align="right"></el-table-column>
... ...
antis-ncc-admin/src/views/storeTotalPerformanceStatistics/index.vue
... ... @@ -81,6 +81,12 @@
81 81 <span style="color: #F56C6C;">{{ scope.row.RefundCount || 0 }}</span>
82 82 </template>
83 83 </el-table-column>
  84 + <el-table-column prop="ActualPerformance" label="实际业绩" width="120" align="right">
  85 + <template slot-scope="scope">
  86 + <span style="color: #67C23A; font-weight: bold;">{{ formatMoney(scope.row.ActualPerformance)
  87 + }}</span>
  88 + </template>
  89 + </el-table-column>
84 90 <el-table-column prop="CreateTime" label="创建时间" width="150" align="center">
85 91 <template slot-scope="scope">
86 92 {{ formatDateTime(scope.row.CreateTime) }}
... ...
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/LqMdxx/LqMdxxInfoOutput.cs
... ... @@ -123,21 +123,21 @@ namespace NCC.Extend.Entitys.Dto.LqMdxx
123 123 /// <summary>
124 124 /// 门店类别
125 125 /// </summary>
126   - public int? StoreCategory { get; set; }
  126 + public int? storeCategory { get; set; }
127 127  
128 128 /// <summary>
129 129 /// 门店类别名称
130 130 /// </summary>
131   - public string StoreCategoryName => StoreCategory.HasValue ? EnumHelper.GetEnumDesc<StoreCategoryEnum>(StoreCategory.Value) : string.Empty;
  131 + public string storeCategoryName => storeCategory.HasValue ? EnumHelper.GetEnumDesc<StoreCategoryEnum>(storeCategory.Value) : string.Empty;
132 132  
133 133 /// <summary>
134 134 /// 门店类型
135 135 /// </summary>
136   - public int? StoreType { get; set; }
  136 + public int? storeType { get; set; }
137 137  
138 138 /// <summary>
139 139 /// 门店类型名称
140 140 /// </summary>
141   - public string StoreTypeName => StoreType.HasValue ? EnumHelper.GetEnumDesc<StoreTypeEnum>(StoreType.Value) : string.Empty;
  141 + public string storeTypeName => storeType.HasValue ? EnumHelper.GetEnumDesc<StoreTypeEnum>(storeType.Value) : string.Empty;
142 142 }
143 143 }
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqMdxx/LqMdxxListOutput.cs
... ... @@ -152,21 +152,21 @@ namespace NCC.Extend.Entitys.Dto.LqMdxx
152 152 /// <summary>
153 153 /// 门店类别
154 154 /// </summary>
155   - public int? StoreCategory { get; set; }
  155 + public int? storeCategory { get; set; }
156 156  
157 157 /// <summary>
158 158 /// 门店类别名称
159 159 /// </summary>
160   - public string StoreCategoryName => StoreCategory.HasValue ? EnumHelper.GetEnumDesc<StoreCategoryEnum>(StoreCategory.Value) : string.Empty;
  160 + public string storeCategoryName => storeCategory.HasValue ? EnumHelper.GetEnumDesc<StoreCategoryEnum>(storeCategory.Value) : string.Empty;
161 161  
162 162 /// <summary>
163 163 /// 门店类型
164 164 /// </summary>
165   - public int? StoreType { get; set; }
  165 + public int? storeType { get; set; }
166 166  
167 167 /// <summary>
168 168 /// 门店类型名称
169 169 /// </summary>
170   - public string StoreTypeName => StoreType.HasValue ? EnumHelper.GetEnumDesc<StoreTypeEnum>(StoreType.Value) : string.Empty;
  170 + public string storeTypeName => storeType.HasValue ? EnumHelper.GetEnumDesc<StoreTypeEnum>(storeType.Value) : string.Empty;
171 171 }
172 172 }
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqPackageInfo/ActivityStatisticsInput.cs 0 → 100644
  1 +using System;
  2 +using System.ComponentModel.DataAnnotations;
  3 +
  4 +namespace NCC.Extend.Entitys.Dto.LqPackageInfo
  5 +{
  6 + /// <summary>
  7 + /// 营销活动统计查询输入参数
  8 + /// </summary>
  9 + public class ActivityStatisticsInput
  10 + {
  11 + /// <summary>
  12 + /// 营销活动ID
  13 + /// </summary>
  14 + [Required(ErrorMessage = "营销活动ID不能为空")]
  15 + public string ActivityId { get; set; }
  16 +
  17 + /// <summary>
  18 + /// 开始时间(可选,默认为活动开始时间)
  19 + /// </summary>
  20 + public DateTime? StartTime { get; set; }
  21 +
  22 + /// <summary>
  23 + /// 结束时间(可选,默认为活动结束时间)
  24 + /// </summary>
  25 + public DateTime? EndTime { get; set; }
  26 +
  27 + /// <summary>
  28 + /// 门店ID列表(可选)
  29 + /// </summary>
  30 + public string[] StoreIds { get; set; }
  31 + }
  32 +}
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqPackageInfo/ActivityStatisticsOutput.cs 0 → 100644
  1 +using System;
  2 +
  3 +namespace NCC.Extend.Entitys.Dto.LqPackageInfo
  4 +{
  5 + /// <summary>
  6 + /// 营销活动统计输出结果
  7 + /// </summary>
  8 + public class ActivityStatisticsOutput
  9 + {
  10 + /// <summary>
  11 + /// 营销活动ID
  12 + /// </summary>
  13 + public string ActivityId { get; set; }
  14 +
  15 + /// <summary>
  16 + /// 营销活动名称
  17 + /// </summary>
  18 + public string ActivityName { get; set; }
  19 +
  20 + /// <summary>
  21 + /// 开单数量
  22 + /// </summary>
  23 + public int BillingCount { get; set; }
  24 +
  25 + /// <summary>
  26 + /// 开单金额
  27 + /// </summary>
  28 + public decimal BillingAmount { get; set; }
  29 +
  30 + /// <summary>
  31 + /// 退卡数量
  32 + /// </summary>
  33 + public int RefundCount { get; set; }
  34 +
  35 + /// <summary>
  36 + /// 退卡金额
  37 + /// </summary>
  38 + public decimal RefundAmount { get; set; }
  39 +
  40 + /// <summary>
  41 + /// 净开单数量(开单数量 - 退卡数量)
  42 + /// </summary>
  43 + public int NetBillingCount { get; set; }
  44 +
  45 + /// <summary>
  46 + /// 净开单金额(开单金额 - 退卡金额)
  47 + /// </summary>
  48 + public decimal NetBillingAmount { get; set; }
  49 +
  50 + /// <summary>
  51 + /// 退卡率(退卡数量 / 开单数量)
  52 + /// </summary>
  53 + public decimal RefundRate { get; set; }
  54 + }
  55 +}
... ...
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.Entitys/Dto/LqStatistics/LqStoreTotalPerformanceStatisticsListOutput.cs
... ... @@ -73,6 +73,11 @@ namespace NCC.Extend.Entitys.Dto.LqStatistics
73 73 public int RefundCount { get; set; }
74 74  
75 75 /// <summary>
  76 + /// 实际业绩
  77 + /// </summary>
  78 + public decimal ActualPerformance { get; set; }
  79 +
  80 + /// <summary>
76 81 /// 创建时间
77 82 /// </summary>
78 83 public DateTime CreateTime { get; set; }
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqStatisticsPersonalPerformance/LqStatisticsPersonalPerformanceListOutput.cs
... ... @@ -93,6 +93,21 @@ namespace NCC.Extend.Entitys.Dto.LqStatisticsPersonalPerformance
93 93 public decimal UpgradeOrderPerformance { get; set; }
94 94  
95 95 /// <summary>
  96 + /// 退单业绩
  97 + /// </summary>
  98 + public decimal RefundPerformance { get; set; }
  99 +
  100 + /// <summary>
  101 + /// 退单次数
  102 + /// </summary>
  103 + public int RefundCount { get; set; }
  104 +
  105 + /// <summary>
  106 + /// 实际业绩
  107 + /// </summary>
  108 + public decimal ActualPerformance { get; set; }
  109 +
  110 + /// <summary>
96 111 /// 最后订单日期
97 112 /// </summary>
98 113 public DateTime? LastOrderDate { get; set; }
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqStatisticsStoreTotalPerformance/LqStatisticsStoreTotalPerformanceListOutput.cs
... ... @@ -86,5 +86,10 @@ namespace NCC.Extend.Entitys.Dto.LqStatisticsStoreTotalPerformance
86 86 /// 创建时间
87 87 /// </summary>
88 88 public DateTime? createTime { get; set; }
  89 +
  90 + /// <summary>
  91 + /// 实际业绩
  92 + /// </summary>
  93 + public decimal actualPerformance { get; set; }
89 94 }
90 95 }
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_statistics_personal_performance/LqStatisticsPersonalPerformanceEntity.cs
... ... @@ -114,6 +114,24 @@ namespace NCC.Extend.Entitys.lq_statistics_personal_performance
114 114 public decimal UpgradeOrderPerformance { get; set; }
115 115  
116 116 /// <summary>
  117 + /// 退单业绩
  118 + /// </summary>
  119 + [SugarColumn(ColumnName = "F_RefundPerformance")]
  120 + public decimal RefundPerformance { get; set; }
  121 +
  122 + /// <summary>
  123 + /// 退单次数
  124 + /// </summary>
  125 + [SugarColumn(ColumnName = "F_RefundCount")]
  126 + public int RefundCount { get; set; }
  127 +
  128 + /// <summary>
  129 + /// 实际业绩
  130 + /// </summary>
  131 + [SugarColumn(ColumnName = "F_ActualPerformance")]
  132 + public decimal ActualPerformance { get; set; }
  133 +
  134 + /// <summary>
117 135 /// 最后订单日期
118 136 /// </summary>
119 137 [SugarColumn(ColumnName = "F_LastOrderDate")]
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_statistics_store_total_performance/LqStatisticsStoreTotalPerformanceEntity.cs
... ... @@ -102,6 +102,12 @@ namespace NCC.Extend.Entitys.lq_statistics_store_total_performance
102 102 public int RefundCount { get; set; }
103 103  
104 104 /// <summary>
  105 + /// 实际业绩
  106 + /// </summary>
  107 + [SugarColumn(ColumnName = "F_ActualPerformance")]
  108 + public decimal ActualPerformance { get; set; }
  109 +
  110 + /// <summary>
105 111 /// 创建时间
106 112 /// </summary>
107 113 [SugarColumn(ColumnName = "F_CreateTime")]
... ...
netcore/src/Modularity/Extend/NCC.Extend/LqKdKdjlbService.cs
... ... @@ -1179,6 +1179,25 @@ namespace NCC.Extend.LqKdKdjlb
1179 1179 }
1180 1180 #endregion
1181 1181  
  1182 + #region 获取状态枚举内容(所有的状态通用)
  1183 + /// <summary>
  1184 + /// 获取状态枚举内容
  1185 + /// </summary>
  1186 + /// <returns>状态枚举列表</returns>
  1187 + [HttpGet("status-enum")]
  1188 + public List<EnumOutput> GetStatusEnum()
  1189 + {
  1190 + return Enum.GetValues<StatusEnum>()
  1191 + .Select(e => new EnumOutput
  1192 + {
  1193 + Value = (int)e,
  1194 + Name = e.ToString(),
  1195 + Description = e.GetDescription(),
  1196 + })
  1197 + .ToList();
  1198 + }
  1199 + #endregion
  1200 +
1182 1201 #region 修改开单记录
1183 1202 /// <summary>
1184 1203 /// 修改开单记录
... ... @@ -2486,7 +2505,7 @@ namespace NCC.Extend.LqKdKdjlb
2486 2505 {
2487 2506 Id = YitIdHelper.NextId().ToString(),
2488 2507 Gltkbh = refundId,
2489   - Jks = jks.Jks,
  2508 + Jks = jks.Jkszh,
2490 2509 Jksxm = jks.Jksxm,
2491 2510 Jkszh = jks.Jkszh,
2492 2511 Jksyj = (totalItemDeduction / refundKdyjEntities.Count()),
... ... @@ -2588,7 +2607,7 @@ namespace NCC.Extend.LqKdKdjlb
2588 2607 {
2589 2608 Id = YitIdHelper.NextId().ToString(),
2590 2609 Glkdbh = billingId,
2591   - Jks = jks.Jks,
  2610 + Jks = jks.Jkszh,
2592 2611 Jksxm = jks.Jksxm,
2593 2612 Jkszh = jks.Jkszh,
2594 2613 Jksyj = jks.Jksyj.ToString(),
... ... @@ -2710,6 +2729,229 @@ namespace NCC.Extend.LqKdKdjlb
2710 2729 }
2711 2730 #endregion
2712 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 + }
2713 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
2714 2956 }
2715 2957 }
... ...
netcore/src/Modularity/Extend/NCC.Extend/LqMdxxService.cs
... ... @@ -24,6 +24,7 @@ using NCC.DataEncryption;
24 24 using NCC.ClayObject;
25 25 using NCC.Extend.Entitys.Enum;
26 26 using NCC.Code;
  27 +using NCC.Extend.Entitys.Dto.Common;
27 28  
28 29 namespace NCC.Extend.LqMdxx
29 30 {
... ... @@ -120,8 +121,8 @@ namespace NCC.Extend.LqMdxx
120 121 rt1 = it.Rt1,
121 122 rt2 = it.Rt2,
122 123 rc = it.Rc,
123   - StoreCategory = it.StoreCategory,
124   - StoreType = it.StoreType,
  124 + storeCategory = it.StoreCategory,
  125 + storeType = it.StoreType,
125 126 }).MergeTable().OrderBy(sidx + " " + input.sort).ToPagedListAsync(input.currentPage, input.pageSize);
126 127 return PageResult<LqMdxxListOutput>.SqlSugarPageResult(data);
127 128 }
... ... @@ -191,8 +192,21 @@ namespace NCC.Extend.LqMdxx
191 192 gsmc = it.Gsmc,
192 193 fr = it.Fr,
193 194 ywsb = it.Ywsb,
194   - StoreCategory = it.StoreCategory,
195   - StoreType = it.StoreType,
  195 + jyb = it.Jyb,
  196 + kjb = it.Kjb,
  197 + dxmb = it.Dxmb,
  198 + syb = it.Syb,
  199 + gsqssj = it.Gsqssj,
  200 + gszzsj = it.Gszzsj,
  201 + status = it.Status,
  202 + xsyj = it.Xsyj,
  203 + xhyj = it.Xhyj,
  204 + xms = it.Xms,
  205 + rt1 = it.Rt1,
  206 + rt2 = it.Rt2,
  207 + rc = it.Rc,
  208 + storeCategory = it.StoreCategory,
  209 + storeType = it.StoreType,
196 210 }).MergeTable().OrderBy(sidx + " " + input.sort).ToListAsync();
197 211 return data;
198 212 }
... ... @@ -359,11 +373,14 @@ namespace NCC.Extend.LqMdxx
359 373 /// </summary>
360 374 /// <returns></returns>
361 375 [HttpGet("Selector/StoreCategory")]
362   - public async Task<dynamic> GetStoreCategorySelector()
  376 + public List<EnumOutput> GetStoreCategorySelector()
363 377 {
364   - //从Enum中获取门店类别
365   - var storeCategoryEnum = EnumExtensions.GetEnumDescDictionary(typeof(StoreCategoryEnum));
366   - return new { list = storeCategoryEnum };
  378 + return Enum.GetValues<StoreCategoryEnum>().Select(e => new EnumOutput
  379 + {
  380 + Value = (int)e,
  381 + Name = e.ToString(),
  382 + Description = e.GetDescription(),
  383 + }).ToList();
367 384 }
368 385 #endregion
369 386  
... ... @@ -373,11 +390,18 @@ namespace NCC.Extend.LqMdxx
373 390 /// </summary>
374 391 /// <returns></returns>
375 392 [HttpGet("Selector/StoreType")]
376   - public async Task<dynamic> GetStoreTypeSelector()
  393 + public List<EnumOutput> GetStoreTypeSelector()
377 394 {
378 395 //从Enum中获取门店类型
379   - var storeTypeEnum = EnumExtensions.GetEnumDescDictionary(typeof(StoreTypeEnum));
380   - return new { list = storeTypeEnum };
  396 + var storeTypeEnum = Enum.GetValues<StoreTypeEnum>()
  397 + .Select(e => new EnumOutput
  398 + {
  399 + Value = (int)e,
  400 + Name = e.ToString(),
  401 + Description = e.GetDescription(),
  402 + })
  403 + .ToList();
  404 + return storeTypeEnum;
381 405 }
382 406 #endregion
383 407 }
... ...
netcore/src/Modularity/Extend/NCC.Extend/LqPackageInfoService.cs
... ... @@ -551,5 +551,163 @@ namespace NCC.Extend.LqPackageInfo
551 551 return output;
552 552 }
553 553 #endregion
  554 +
  555 + #region 营销活动统计
  556 + /// <summary>
  557 + /// 获取营销活动统计数据
  558 + /// </summary>
  559 + /// <remarks>
  560 + /// 统计指定营销活动的开单、退卡相关数据
  561 + /// 包括:开单数量、开单金额、退卡数量、退卡金额、净开单数量、净开单金额、退卡率
  562 + ///
  563 + /// 示例请求:
  564 + /// ```json
  565 + /// {
  566 + /// "activityId": "营销活动ID",
  567 + /// "startTime": "2025-10-01",
  568 + /// "endTime": "2025-10-31",
  569 + /// "storeIds": ["门店ID1", "门店ID2"]
  570 + /// }
  571 + /// ```
  572 + ///
  573 + /// 参数说明:
  574 + /// - activityId: 营销活动ID(必填)
  575 + /// - startTime: 开始时间(可选,默认为活动开始时间)
  576 + /// - endTime: 结束时间(可选,默认为活动结束时间)
  577 + /// - storeIds: 门店ID列表(可选)
  578 + ///
  579 + /// 返回字段说明:
  580 + /// - ActivityId: 营销活动ID
  581 + /// - ActivityName: 营销活动名称
  582 + /// - BillingCount: 开单数量
  583 + /// - BillingAmount: 开单金额
  584 + /// - RefundCount: 退卡数量
  585 + /// - RefundAmount: 退卡金额
  586 + /// - NetBillingCount: 净开单数量(开单数量 - 退卡数量)
  587 + /// - NetBillingAmount: 净开单金额(开单金额 - 退卡金额)
  588 + /// - RefundRate: 退卡率(退卡数量 / 开单数量)
  589 + /// </remarks>
  590 + /// <param name="input">查询参数</param>
  591 + /// <returns>营销活动统计数据</returns>
  592 + /// <response code="200">成功返回统计数据</response>
  593 + /// <response code="400">参数错误</response>
  594 + /// <response code="500">服务器错误</response>
  595 + [HttpPost("get-activity-statistics")]
  596 + public async Task<object> GetActivityStatistics(ActivityStatisticsInput input)
  597 + {
  598 + try
  599 + {
  600 + // 1. 获取营销活动信息
  601 + var activity = await _db.Queryable<LqPackageInfoEntity>()
  602 + .Where(x => x.Id == input.ActivityId && x.IsEffective == StatusEnum.有效.GetHashCode())
  603 + .FirstAsync();
  604 +
  605 + if (activity == null)
  606 + {
  607 + throw NCCException.Oh("营销活动不存在或已失效");
  608 + }
  609 +
  610 + // 2. 设置时间范围(如果未提供,使用活动时间范围)
  611 + var startTime = input.StartTime ?? activity.StartTime;
  612 + var endTime = input.EndTime ?? activity.EndTime;
  613 +
  614 + // 3. 获取营销活动关联的品项ID列表
  615 + var itemIds = await _db.Queryable<LqPackageItemDetailEntity>()
  616 + .Where(x => x.ActivityId == input.ActivityId && x.IsEffective == StatusEnum.有效.GetHashCode())
  617 + .Select(x => x.ItemId)
  618 + .ToListAsync();
  619 +
  620 + if (!itemIds.Any())
  621 + {
  622 + return new ActivityStatisticsOutput
  623 + {
  624 + ActivityId = input.ActivityId,
  625 + ActivityName = activity.ActivityName,
  626 + BillingCount = 0,
  627 + BillingAmount = 0,
  628 + RefundCount = 0,
  629 + RefundAmount = 0,
  630 + NetBillingCount = 0,
  631 + NetBillingAmount = 0,
  632 + RefundRate = 0
  633 + };
  634 + }
  635 +
  636 + // 4. 构建品项ID过滤条件
  637 + var itemIdsStr = string.Join("','", itemIds);
  638 + var itemIdsFilter = $"AND px.px IN ('{itemIdsStr}')";
  639 + var itemIdsFilterRefund = $"AND hytkmx.px IN ('{itemIdsStr}')";
  640 +
  641 + // 5. 构建门店过滤条件
  642 + string storeFilter = "";
  643 + string storeFilterRefund = "";
  644 +
  645 + if (input.StoreIds != null && input.StoreIds.Any())
  646 + {
  647 + var storeIdsStr = string.Join("','", input.StoreIds);
  648 + storeFilter = $"AND kd.djmd IN ('{storeIdsStr}')";
  649 + storeFilterRefund = $"AND hytk.md IN ('{storeIdsStr}')";
  650 + }
  651 +
  652 + // 6. 开单统计
  653 + var billingSql = $@"
  654 + SELECT
  655 + COUNT(DISTINCT kd.F_Id) as billing_count,
  656 + SUM(CAST(px.F_ActualPrice AS DECIMAL(18,2))) as billing_amount
  657 + FROM lq_kd_pxmx px
  658 + LEFT JOIN lq_kd_kdjlb kd ON px.glkdbh = kd.F_Id
  659 + WHERE px.F_IsEffective = 1
  660 + {itemIdsFilter}
  661 + AND px.yjsj >= '{startTime:yyyy-MM-dd HH:mm:ss}'
  662 + AND px.yjsj <= '{endTime:yyyy-MM-dd HH:mm:ss}'
  663 + {storeFilter}";
  664 +
  665 + var billingData = await _db.Ado.SqlQueryAsync<dynamic>(billingSql);
  666 + var billingCount = Convert.ToInt32(billingData.FirstOrDefault()?.billing_count ?? 0);
  667 + var billingAmount = Convert.ToDecimal(billingData.FirstOrDefault()?.billing_amount ?? 0);
  668 +
  669 + // 7. 退卡统计
  670 + var refundSql = $@"
  671 + SELECT
  672 + COUNT(DISTINCT hytk.F_Id) as refund_count,
  673 + SUM(CAST(hytkmx.tkje AS DECIMAL(18,2))) as refund_amount
  674 + FROM lq_hytk_mx hytkmx
  675 + LEFT JOIN lq_hytk_hytk hytk ON hytkmx.F_RefundInfoId = hytk.F_Id
  676 + WHERE hytkmx.F_IsEffective = 1
  677 + {itemIdsFilterRefund}
  678 + AND hytkmx.tksj >= '{startTime:yyyy-MM-dd HH:mm:ss}'
  679 + AND hytkmx.tksj <= '{endTime:yyyy-MM-dd HH:mm:ss}'
  680 + {storeFilterRefund}";
  681 +
  682 + var refundData = await _db.Ado.SqlQueryAsync<dynamic>(refundSql);
  683 + var refundCount = Convert.ToInt32(refundData.FirstOrDefault()?.refund_count ?? 0);
  684 + var refundAmount = Convert.ToDecimal(refundData.FirstOrDefault()?.refund_amount ?? 0);
  685 +
  686 + // 8. 计算净值和退卡率
  687 + var netBillingCount = billingCount - refundCount;
  688 + var netBillingAmount = billingAmount - refundAmount;
  689 + var refundRate = billingCount > 0 ? Math.Round((decimal)refundCount / billingCount * 100, 2) : 0;
  690 +
  691 + // 9. 返回统计结果
  692 + return new ActivityStatisticsOutput
  693 + {
  694 + ActivityId = input.ActivityId,
  695 + ActivityName = activity.ActivityName,
  696 + BillingCount = billingCount,
  697 + BillingAmount = billingAmount,
  698 + RefundCount = refundCount,
  699 + RefundAmount = refundAmount,
  700 + NetBillingCount = netBillingCount,
  701 + NetBillingAmount = netBillingAmount,
  702 + RefundRate = refundRate
  703 + };
  704 + }
  705 + catch (Exception ex)
  706 + {
  707 + throw NCCException.Oh($"获取营销活动统计数据失败: {ex.Message}");
  708 + }
  709 + }
  710 + #endregion
  711 +
554 712 }
555 713 }
... ...
netcore/src/Modularity/Extend/NCC.Extend/LqReportService.cs
... ... @@ -25,6 +25,8 @@ using NCC.Extend.Entitys.lq_jinsanjiao_user;
25 25 using NCC.Extend.Entitys.lq_kd_kdjlb;
26 26 using NCC.Extend.Entitys.lq_xh_hyhk;
27 27 using NCC.Extend.Entitys.lq_khxx;
  28 +using NCC.Extend.Entitys.Dto.LqReport;
  29 +using NCC.Extend.Entitys.Enum;
28 30 using SqlSugar;
29 31  
30 32 namespace NCC.Extend
... ... @@ -40,6 +42,12 @@ namespace NCC.Extend
40 42 private readonly IUserManager _userManager;
41 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 51 public LqReportService(ISqlSugarClient db, IUserManager userManager, ILogger<LqReportService> logger)
44 52 {
45 53 _db = db;
... ... @@ -548,7 +556,7 @@ namespace NCC.Extend
548 556  
549 557 #endregion
550 558  
551   - #region 综合仪表盘
  559 + #region 获取综合仪表盘数据
552 560  
553 561 /// <summary>
554 562 /// 获取综合仪表盘数据
... ... @@ -682,5 +690,594 @@ namespace NCC.Extend
682 690 }
683 691  
684 692 #endregion
  693 +
  694 + #region 获取业务统计数据
  695 +
  696 + /// <summary>
  697 + /// 获取业务统计数据
  698 + /// </summary>
  699 + /// <remarks>
  700 + /// 统计指定时间范围内的开单、耗卡、退卡相关数据
  701 + /// 包括:开单总金额、耗卡总金额、退卡总金额、开单人数、耗卡人数、退卡人数
  702 + ///
  703 + /// 示例请求:
  704 + /// ```json
  705 + /// {
  706 + /// "startTime": "2025-10-01",
  707 + /// "endTime": "2025-10-31",
  708 + /// "storeIds": ["门店ID1", "门店ID2"]
  709 + /// }
  710 + /// ```
  711 + ///
  712 + /// 参数说明:
  713 + /// - startTime: 开始时间(可选,默认为当月1号)
  714 + /// - endTime: 结束时间(可选,默认为当前时间)
  715 + /// - storeIds: 门店ID列表(可选)
  716 + ///
  717 + /// 返回字段说明:
  718 + /// - TotalBillingAmount: 开单总金额
  719 + /// - TotalConsumeAmount: 耗卡总金额
  720 + /// - TotalRefundAmount: 退卡总金额
  721 + /// - BillingCount: 开单人数(按客户去重)
  722 + /// - ConsumeCount: 耗卡人数(按客户去重)
  723 + /// - RefundCount: 退卡人数(按客户去重)
  724 + /// </remarks>
  725 + /// <param name="input">查询参数</param>
  726 + /// <returns>业务统计数据</returns>
  727 + /// <response code="200">成功返回统计数据</response>
  728 + /// <response code="400">参数错误</response>
  729 + /// <response code="500">服务器错误</response>
  730 + [HttpPost("get-business-statistics")]
  731 + public async Task<object> GetBusinessStatistics(BusinessStatisticsInput input)
  732 + {
  733 + try
  734 + {
  735 + // 设置默认时间范围(如果未提供,默认为当月)
  736 + var startTime = input.StartTime ?? new DateTime(DateTime.Now.Year, DateTime.Now.Month, 1);
  737 + var endTime = input.EndTime ?? DateTime.Now;
  738 +
  739 + // 第一步:获取开单统计数据
  740 + var billingSql = @"
  741 + SELECT
  742 + COUNT(DISTINCT kd.kdhy) as billing_count,
  743 + COALESCE(SUM(CAST(kd.sfyj AS DECIMAL(18,2))), 0) as billing_amount
  744 + FROM lq_kd_kdjlb kd
  745 + WHERE kd.F_IsEffective = 1
  746 + AND kd.kdrq >= @startTime
  747 + AND kd.kdrq <= @endTime";
  748 +
  749 + object billingParameters;
  750 + if (input.StoreIds != null && input.StoreIds.Any())
  751 + {
  752 + billingSql += " AND kd.F_StoreId IN @storeIds";
  753 + billingParameters = new { startTime, endTime, storeIds = input.StoreIds };
  754 + }
  755 + else
  756 + {
  757 + billingParameters = new { startTime, endTime };
  758 + }
  759 +
  760 + var billingResult = await _db.Ado.SqlQueryAsync<dynamic>(billingSql, billingParameters);
  761 + var billingCount = Convert.ToInt32(billingResult?.FirstOrDefault()?.billing_count ?? 0);
  762 + var billingAmount = Convert.ToDecimal(billingResult?.FirstOrDefault()?.billing_amount ?? 0m);
  763 +
  764 + // 第二步:获取耗卡统计数据
  765 + var consumeSql = @"
  766 + SELECT
  767 + COUNT(DISTINCT xh.hy) as consume_count,
  768 + COALESCE(SUM(CAST(xh.xfje AS DECIMAL(18,2))), 0) as consume_amount
  769 + FROM lq_xh_hyhk xh
  770 + WHERE xh.F_IsEffective = 1
  771 + AND xh.hksj >= @startTime
  772 + AND xh.hksj <= @endTime";
  773 +
  774 + object consumeParameters;
  775 + if (input.StoreIds != null && input.StoreIds.Any())
  776 + {
  777 + consumeSql += " AND xh.F_StoreId IN @storeIds";
  778 + consumeParameters = new { startTime, endTime, storeIds = input.StoreIds };
  779 + }
  780 + else
  781 + {
  782 + consumeParameters = new { startTime, endTime };
  783 + }
  784 +
  785 + var consumeResult = await _db.Ado.SqlQueryAsync<dynamic>(consumeSql, consumeParameters);
  786 + var consumeCount = Convert.ToInt32(consumeResult?.FirstOrDefault()?.consume_count ?? 0);
  787 + var consumeAmount = Convert.ToDecimal(consumeResult?.FirstOrDefault()?.consume_amount ?? 0m);
  788 +
  789 + // 第三步:获取退卡统计数据
  790 + var refundSql = @"
  791 + SELECT
  792 + COUNT(DISTINCT hytk.hy) as refund_count,
  793 + COALESCE(SUM(CAST(hytk.tkje AS DECIMAL(18,2))), 0) as refund_amount
  794 + FROM lq_hytk_hytk hytk
  795 + WHERE hytk.F_IsEffective = 1
  796 + AND hytk.tksj >= @startTime
  797 + AND hytk.tksj <= @endTime";
  798 +
  799 + object refundParameters;
  800 + if (input.StoreIds != null && input.StoreIds.Any())
  801 + {
  802 + refundSql += " AND hytk.F_StoreId IN @storeIds";
  803 + refundParameters = new { startTime, endTime, storeIds = input.StoreIds };
  804 + }
  805 + else
  806 + {
  807 + refundParameters = new { startTime, endTime };
  808 + }
  809 +
  810 + var refundResult = await _db.Ado.SqlQueryAsync<dynamic>(refundSql, refundParameters);
  811 + var refundCount = Convert.ToInt32(refundResult?.FirstOrDefault()?.refund_count ?? 0);
  812 + var refundAmount = Convert.ToDecimal(refundResult?.FirstOrDefault()?.refund_amount ?? 0m);
  813 +
  814 + var result = new BusinessStatisticsOutput
  815 + {
  816 + TotalBillingAmount = billingAmount,
  817 + TotalConsumeAmount = consumeAmount,
  818 + TotalRefundAmount = refundAmount,
  819 + BillingCount = billingCount,
  820 + ConsumeCount = consumeCount,
  821 + RefundCount = refundCount
  822 + };
  823 +
  824 + return result;
  825 + }
  826 + catch (Exception ex)
  827 + {
  828 + _logger.LogError(ex, $"获取业务统计数据失败 - 开始时间: {input?.StartTime}, 结束时间: {input?.EndTime}");
  829 + throw NCCException.Oh($"获取业务统计数据失败: {ex.Message}");
  830 + }
  831 + }
  832 +
  833 + #endregion
  834 +
  835 + #region 获取客户类型统计数据
  836 + /// <summary>
  837 + /// 获取客户类型统计数据
  838 + /// </summary>
  839 + /// <remarks>
  840 + /// 统计指定时间范围内不同类型的客户数量和转化率
  841 + /// 包括:线索、新客、散客、会员数量,以及拓客人数、消耗人数、转化率
  842 + ///
  843 + /// 示例请求:
  844 + /// ```json
  845 + /// {
  846 + /// "startTime": "2025-10-01",
  847 + /// "endTime": "2025-10-31",
  848 + /// "storeIds": ["门店ID1", "门店ID2"]
  849 + /// }
  850 + /// ```
  851 + ///
  852 + /// 参数说明:
  853 + /// - startTime: 开始时间(可选,默认为当月1号)
  854 + /// - endTime: 结束时间(可选,默认为当前时间)
  855 + /// - storeIds: 门店ID列表(可选)
  856 + ///
  857 + /// 返回字段说明:
  858 + /// - LeadCount: 线索客户数量
  859 + /// - NewCustomerCount: 新客数量
  860 + /// - CasualCustomerCount: 散客数量
  861 + /// - MemberCount: 会员数量
  862 + /// - TotalInviteCount: 拓客总人数
  863 + /// - ConsumeCount: 消耗人数(有消耗金额的)
  864 + /// - ConversionRate: 转化率(消耗人数/拓客人数)
  865 + /// </remarks>
  866 + /// <param name="input">查询参数</param>
  867 + /// <returns>客户类型统计数据</returns>
  868 + /// <response code="200">成功返回统计数据</response>
  869 + /// <response code="400">参数错误</response>
  870 + /// <response code="500">服务器错误</response>
  871 + [HttpPost("get-customer-type-statistics")]
  872 + public async Task<object> GetCustomerTypeStatistics(CustomerTypeStatisticsInput input)
  873 + {
  874 + try
  875 + {
  876 + // 设置默认时间范围(如果未提供,默认为当月)
  877 + var startTime = input.StartTime ?? new DateTime(DateTime.Now.Year, DateTime.Now.Month, 1);
  878 + var endTime = input.EndTime ?? DateTime.Now;
  879 +
  880 + // 第一步:获取客户类型统计
  881 + var customerTypeSql = $@"
  882 + SELECT
  883 + SUM(CASE WHEN kh.khlx = '{MemberTypeEnum.线索.GetHashCode()}' THEN 1 ELSE 0 END) as lead_count,
  884 + SUM(CASE WHEN kh.khlx = '{MemberTypeEnum.新客.GetHashCode()}' THEN 1 ELSE 0 END) as new_customer_count,
  885 + SUM(CASE WHEN kh.khlx = '{MemberTypeEnum.散客.GetHashCode()}' THEN 1 ELSE 0 END) as casual_customer_count,
  886 + SUM(CASE WHEN kh.khlx = '{MemberTypeEnum.会员.GetHashCode()}' THEN 1 ELSE 0 END) as member_count
  887 + FROM lq_khxx kh
  888 + WHERE kh.F_CreateTime >= @startTime
  889 + AND kh.F_CreateTime <= @endTime";
  890 +
  891 + object customerTypeParameters;
  892 + if (input.StoreIds != null && input.StoreIds.Any())
  893 + {
  894 + customerTypeSql += " AND kh.F_StoreId IN @storeIds";
  895 + customerTypeParameters = new { startTime, endTime, storeIds = input.StoreIds };
  896 + }
  897 + else
  898 + {
  899 + customerTypeParameters = new { startTime, endTime };
  900 + }
  901 +
  902 + var customerTypeResult = await _db.Ado.SqlQueryAsync<dynamic>(customerTypeSql, customerTypeParameters);
  903 + var leadCount = Convert.ToInt32(customerTypeResult?.FirstOrDefault()?.lead_count ?? 0);
  904 + var newCustomerCount = Convert.ToInt32(customerTypeResult?.FirstOrDefault()?.new_customer_count ?? 0);
  905 + var casualCustomerCount = Convert.ToInt32(customerTypeResult?.FirstOrDefault()?.casual_customer_count ?? 0);
  906 + var memberCount = Convert.ToInt32(customerTypeResult?.FirstOrDefault()?.member_count ?? 0);
  907 +
  908 + // 第二步:获取拓客总人数(按客户去重)
  909 + var inviteSql = @"
  910 + SELECT COUNT(DISTINCT tk.F_MemberId) as invite_count
  911 + FROM lq_tkjlb tk
  912 + WHERE tk.F_CreateTime >= @startTime
  913 + AND tk.F_CreateTime <= @endTime";
  914 +
  915 + object inviteParameters;
  916 + if (input.StoreIds != null && input.StoreIds.Any())
  917 + {
  918 + inviteSql += " AND tk.F_StoreId IN @storeIds";
  919 + inviteParameters = new { startTime, endTime, storeIds = input.StoreIds };
  920 + }
  921 + else
  922 + {
  923 + inviteParameters = new { startTime, endTime };
  924 + }
  925 +
  926 + var inviteResult = await _db.Ado.SqlQueryAsync<dynamic>(inviteSql, inviteParameters);
  927 + var totalInviteCount = Convert.ToInt32(inviteResult?.FirstOrDefault()?.invite_count ?? 0);
  928 +
  929 + // 第三步:获取消耗人数(有消耗金额的,按客户去重)
  930 + var consumeSql = @"
  931 + SELECT COUNT(DISTINCT xh.hy) as consume_count
  932 + FROM lq_xh_hyhk xh
  933 + WHERE xh.F_IsEffective = 1
  934 + AND xh.hksj >= @startTime
  935 + AND xh.hksj <= @endTime
  936 + AND xh.xfje > 0";
  937 +
  938 + object consumeParameters;
  939 + if (input.StoreIds != null && input.StoreIds.Any())
  940 + {
  941 + consumeSql += " AND xh.F_StoreId IN @storeIds";
  942 + consumeParameters = new { startTime, endTime, storeIds = input.StoreIds };
  943 + }
  944 + else
  945 + {
  946 + consumeParameters = new { startTime, endTime };
  947 + }
  948 +
  949 + var consumeResult = await _db.Ado.SqlQueryAsync<dynamic>(consumeSql, consumeParameters);
  950 + var consumeCount = Convert.ToInt32(consumeResult?.FirstOrDefault()?.consume_count ?? 0);
  951 +
  952 + // 计算转化率
  953 + var conversionRate = totalInviteCount > 0 ? Math.Round(consumeCount * 100.0m / totalInviteCount, 2) : 0;
  954 +
  955 + var result = new CustomerTypeStatisticsOutput
  956 + {
  957 + LeadCount = leadCount,
  958 + NewCustomerCount = newCustomerCount,
  959 + CasualCustomerCount = casualCustomerCount,
  960 + MemberCount = memberCount,
  961 + TotalInviteCount = totalInviteCount,
  962 + ConsumeCount = consumeCount,
  963 + ConversionRate = conversionRate
  964 + };
  965 +
  966 + return result;
  967 + }
  968 + catch (Exception ex)
  969 + {
  970 + _logger.LogError(ex, $"获取客户类型统计数据失败 - 开始时间: {input?.StartTime}, 结束时间: {input?.EndTime}");
  971 + throw NCCException.Oh($"获取客户类型统计数据失败: {ex.Message}");
  972 + }
  973 + }
  974 +
  975 + #endregion
  976 +
  977 + #region 获取门店业绩对比统计数据
  978 + /// <summary>
  979 + /// 获取门店业绩对比统计数据
  980 + /// </summary>
  981 + /// <remarks>
  982 + /// 统计指定时间范围内各门店的开单业绩与目标业绩的对比情况
  983 + /// 包括:目标业绩、实际开单业绩、完成率、差额、是否达标
  984 + ///
  985 + /// 示例请求:
  986 + /// ```json
  987 + /// {
  988 + /// "startTime": "2025-10-01",
  989 + /// "endTime": "2025-10-31",
  990 + /// "storeIds": ["门店ID1", "门店ID2"]
  991 + /// }
  992 + /// ```
  993 + ///
  994 + /// 参数说明:
  995 + /// - startTime: 开始时间(可选,默认为当月1号)
  996 + /// - endTime: 结束时间(可选,默认为当前时间)
  997 + /// - storeIds: 门店ID列表(可选)
  998 + ///
  999 + /// 返回字段说明:
  1000 + /// - StoreId: 门店ID
  1001 + /// - StoreName: 门店名称
  1002 + /// - TargetPerformance: 目标业绩(来自门店资料表xsyj字段)
  1003 + /// - ActualPerformance: 实际开单业绩(统计期间内的开单业绩总和)
  1004 + /// - CompletionRate: 完成率(实际业绩/目标业绩 × 100%)
  1005 + /// - Difference: 差额(实际业绩-目标业绩)
  1006 + /// - IsTargetAchieved: 是否达标(实际业绩 >= 目标业绩)
  1007 + /// </remarks>
  1008 + /// <param name="input">查询参数</param>
  1009 + /// <returns>门店业绩对比统计数据</returns>
  1010 + /// <response code="200">成功返回统计数据</response>
  1011 + /// <response code="400">参数错误</response>
  1012 + /// <response code="500">服务器错误</response>
  1013 + [HttpPost("get-store-performance-comparison")]
  1014 + public async Task<object> GetStorePerformanceComparison(StorePerformanceComparisonInput input)
  1015 + {
  1016 + try
  1017 + {
  1018 + // 设置默认时间范围(如果未提供,默认为当月)
  1019 + var startTime = input.StartTime ?? new DateTime(DateTime.Now.Year, DateTime.Now.Month, 1);
  1020 + var endTime = input.EndTime ?? DateTime.Now;
  1021 +
  1022 + // 构建SQL查询
  1023 + var sql = @"
  1024 + SELECT
  1025 + md.F_Id as store_id,
  1026 + md.dm as store_name,
  1027 + COALESCE(CAST(md.xsyj AS DECIMAL(18,2)), 0) as target_performance,
  1028 + COALESCE(SUM(CAST(kd.sfyj AS DECIMAL(18,2))), 0) as actual_performance
  1029 + FROM lq_mdxx md
  1030 + LEFT JOIN lq_kd_kdjlb kd ON md.F_Id = kd.djmd
  1031 + AND kd.F_IsEffective = 1
  1032 + AND kd.kdrq >= @startTime
  1033 + AND kd.kdrq <= @endTime";
  1034 +
  1035 + object parameters;
  1036 + if (input.StoreIds != null && input.StoreIds.Any())
  1037 + {
  1038 + sql += " AND md.F_Id IN @storeIds";
  1039 + parameters = new { startTime, endTime, storeIds = input.StoreIds };
  1040 + }
  1041 + else
  1042 + {
  1043 + parameters = new { startTime, endTime };
  1044 + }
  1045 +
  1046 + sql += " GROUP BY md.F_Id, md.dm, md.xsyj ORDER BY actual_performance DESC";
  1047 +
  1048 + var results = await _db.Ado.SqlQueryAsync<dynamic>(sql, parameters);
  1049 +
  1050 + var storePerformanceList = new List<StorePerformanceComparisonOutput>();
  1051 +
  1052 + foreach (var item in results)
  1053 + {
  1054 + var storeId = item.store_id?.ToString();
  1055 + var storeName = item.store_name?.ToString();
  1056 + var targetPerformance = Convert.ToDecimal(item.target_performance ?? 0);
  1057 + var actualPerformance = Convert.ToDecimal(item.actual_performance ?? 0);
  1058 +
  1059 + // 计算完成率
  1060 + var completionRate = targetPerformance > 0
  1061 + ? Math.Round(actualPerformance * 100.0m / targetPerformance, 2)
  1062 + : 0;
  1063 +
  1064 + // 计算差额
  1065 + var difference = actualPerformance - targetPerformance;
  1066 +
  1067 + // 判断是否达标
  1068 + var isTargetAchieved = actualPerformance >= targetPerformance;
  1069 +
  1070 + storePerformanceList.Add(new StorePerformanceComparisonOutput
  1071 + {
  1072 + StoreId = storeId,
  1073 + StoreName = storeName,
  1074 + TargetPerformance = targetPerformance,
  1075 + ActualPerformance = actualPerformance,
  1076 + CompletionRate = completionRate,
  1077 + Difference = difference,
  1078 + IsTargetAchieved = isTargetAchieved
  1079 + });
  1080 + }
  1081 +
  1082 + return storePerformanceList;
  1083 + }
  1084 + catch (Exception ex)
  1085 + {
  1086 + _logger.LogError(ex, $"获取门店业绩对比统计数据失败 - 开始时间: {input?.StartTime}, 结束时间: {input?.EndTime}");
  1087 + throw NCCException.Oh($"获取门店业绩对比统计数据失败: {ex.Message}");
  1088 + }
  1089 + }
  1090 + #endregion
  1091 +
  1092 + #region 获取品项统计数据
  1093 +
  1094 + /// <summary>
  1095 + /// 获取品项统计数据
  1096 + /// </summary>
  1097 + /// <remarks>
  1098 + /// 统计指定时间范围内各品项的开单、消耗、退卡相关数据
  1099 + /// 包括:开单数量、开单金额、消耗数量、消耗金额、退卡数量、退卡金额
  1100 + ///
  1101 + /// 示例请求:
  1102 + /// ```json
  1103 + /// {
  1104 + /// "startTime": "2025-10-01",
  1105 + /// "endTime": "2025-10-31",
  1106 + /// "storeIds": ["门店ID1", "门店ID2"]
  1107 + /// }
  1108 + /// ```
  1109 + ///
  1110 + /// 参数说明:
  1111 + /// - startTime: 开始时间(可选,默认为当月1号)
  1112 + /// - endTime: 结束时间(可选,默认为当前时间)
  1113 + /// - storeIds: 门店ID列表(可选)
  1114 + ///
  1115 + /// 返回字段说明:
  1116 + /// - ItemId: 品项ID
  1117 + /// - ItemName: 品项名称
  1118 + /// - ItemNumber: 品项编号
  1119 + /// - BillingCount: 开单数量
  1120 + /// - BillingAmount: 开单金额
  1121 + /// - ConsumeCount: 消耗数量
  1122 + /// - ConsumeAmount: 消耗金额
  1123 + /// - RefundCount: 退卡数量
  1124 + /// - RefundAmount: 退卡金额
  1125 + /// </remarks>
  1126 + /// <param name="input">查询参数</param>
  1127 + /// <returns>品项统计数据列表(按开单金额降序排列)</returns>
  1128 + /// <response code="200">成功返回统计数据</response>
  1129 + /// <response code="400">参数错误</response>
  1130 + /// <response code="500">服务器错误</response>
  1131 + [HttpPost("get-item-statistics")]
  1132 + public async Task<object> GetItemStatistics(ItemStatisticsInput input)
  1133 + {
  1134 + try
  1135 + {
  1136 + // 设置默认时间范围(如果未提供,默认为当月)
  1137 + var startTime = input.StartTime ?? new DateTime(DateTime.Now.Year, DateTime.Now.Month, 1);
  1138 + var endTime = input.EndTime ?? DateTime.Now;
  1139 +
  1140 + // 构建门店过滤条件
  1141 + string storeCondition = "";
  1142 + if (input.StoreIds != null && input.StoreIds.Any())
  1143 + {
  1144 + var storeIdsStr = string.Join("','", input.StoreIds);
  1145 + storeCondition = $@"
  1146 + AND (
  1147 + kd.djmd IN ('{storeIdsStr}') OR
  1148 + xhhyhk.F_StoreId IN ('{storeIdsStr}') OR
  1149 + hytk.md IN ('{storeIdsStr}')
  1150 + )";
  1151 + }
  1152 +
  1153 + // 使用最激进的优化:分步查询,避免复杂JOIN
  1154 + var itemStatisticsDict = new Dictionary<string, ItemStatisticsOutput>();
  1155 +
  1156 + // 1. 先获取品项基础信息
  1157 + var itemSql = @"
  1158 + SELECT F_Id, xmmc, xmbh
  1159 + FROM lq_xmzl
  1160 + WHERE F_IsEffective = 1";
  1161 +
  1162 + var itemData = await _db.Ado.SqlQueryAsync<dynamic>(itemSql);
  1163 +
  1164 + // 初始化品项字典
  1165 + foreach (var item in itemData)
  1166 + {
  1167 + var itemId = item.F_Id?.ToString();
  1168 + if (!string.IsNullOrEmpty(itemId))
  1169 + {
  1170 + itemStatisticsDict[itemId] = new ItemStatisticsOutput
  1171 + {
  1172 + ItemId = itemId,
  1173 + ItemName = item.xmmc?.ToString() ?? "未知品项",
  1174 + ItemNumber = item.xmbh?.ToString() ?? "",
  1175 + BillingCount = 0,
  1176 + BillingAmount = 0,
  1177 + ConsumeCount = 0,
  1178 + ConsumeAmount = 0,
  1179 + RefundCount = 0,
  1180 + RefundAmount = 0
  1181 + };
  1182 + }
  1183 + }
  1184 +
  1185 + // 2. 开单统计 - 直接查询,避免JOIN
  1186 + var billingSql = $@"
  1187 + SELECT
  1188 + px.px as item_id,
  1189 + SUM(px.F_ProjectNumber) as billing_count,
  1190 + SUM(CAST(px.F_ActualPrice AS DECIMAL(18,2))) as billing_amount
  1191 + FROM lq_kd_pxmx px
  1192 + WHERE px.F_IsEffective = 1
  1193 + AND px.yjsj >= '{startTime:yyyy-MM-dd HH:mm:ss}'
  1194 + AND px.yjsj <= '{endTime:yyyy-MM-dd HH:mm:ss}'
  1195 + {(input.StoreIds != null && input.StoreIds.Any() ? $"AND px.glkdbh IN (SELECT F_Id FROM lq_kd_kdjlb WHERE djmd IN ('{string.Join("','", input.StoreIds)}'))" : "")}
  1196 + GROUP BY px.px";
  1197 +
  1198 + var billingData = await _db.Ado.SqlQueryAsync<dynamic>(billingSql);
  1199 +
  1200 + // 合并开单数据
  1201 + foreach (var billing in billingData)
  1202 + {
  1203 + var itemId = billing.item_id?.ToString();
  1204 + if (!string.IsNullOrEmpty(itemId) && itemStatisticsDict.ContainsKey(itemId))
  1205 + {
  1206 + itemStatisticsDict[itemId].BillingCount = Convert.ToInt32(billing.billing_count ?? 0);
  1207 + itemStatisticsDict[itemId].BillingAmount = Convert.ToDecimal(billing.billing_amount ?? 0);
  1208 + }
  1209 + }
  1210 +
  1211 + // 3. 消耗统计 - 直接查询,避免JOIN
  1212 + var consumeSql = $@"
  1213 + SELECT
  1214 + xh.px as item_id,
  1215 + SUM(xh.F_ProjectNumber) as consume_count,
  1216 + SUM(CAST(xh.F_TotalPrice AS DECIMAL(18,2))) as consume_amount
  1217 + FROM lq_xh_pxmx xh
  1218 + WHERE xh.F_IsEffective = 1
  1219 + AND xh.F_ConsumeInfoId IN (
  1220 + SELECT F_Id FROM lq_xh_hyhk
  1221 + WHERE hksj >= '{startTime:yyyy-MM-dd HH:mm:ss}'
  1222 + AND hksj <= '{endTime:yyyy-MM-dd HH:mm:ss}'
  1223 + {(input.StoreIds != null && input.StoreIds.Any() ? $"AND F_StoreId IN ('{string.Join("','", input.StoreIds)}')" : "")}
  1224 + )
  1225 + GROUP BY xh.px";
  1226 +
  1227 + var consumeData = await _db.Ado.SqlQueryAsync<dynamic>(consumeSql);
  1228 +
  1229 + // 合并消耗数据
  1230 + foreach (var consume in consumeData)
  1231 + {
  1232 + var itemId = consume.item_id?.ToString();
  1233 + if (!string.IsNullOrEmpty(itemId) && itemStatisticsDict.ContainsKey(itemId))
  1234 + {
  1235 + itemStatisticsDict[itemId].ConsumeCount = Convert.ToInt32(consume.consume_count ?? 0);
  1236 + itemStatisticsDict[itemId].ConsumeAmount = Convert.ToDecimal(consume.consume_amount ?? 0);
  1237 + }
  1238 + }
  1239 +
  1240 + // 4. 退卡统计 - 直接查询,避免JOIN
  1241 + var refundSql = $@"
  1242 + SELECT
  1243 + hytkmx.px as item_id,
  1244 + SUM(hytkmx.F_ProjectNumber) as refund_count,
  1245 + SUM(CAST(hytkmx.tkje AS DECIMAL(18,2))) as refund_amount
  1246 + FROM lq_hytk_mx hytkmx
  1247 + WHERE hytkmx.F_IsEffective = 1
  1248 + AND hytkmx.tksj >= '{startTime:yyyy-MM-dd HH:mm:ss}'
  1249 + AND hytkmx.tksj <= '{endTime:yyyy-MM-dd HH:mm:ss}'
  1250 + {(input.StoreIds != null && input.StoreIds.Any() ? $"AND hytkmx.F_RefundInfoId IN (SELECT F_Id FROM lq_hytk_hytk WHERE md IN ('{string.Join("','", input.StoreIds)}'))" : "")}
  1251 + GROUP BY hytkmx.px";
  1252 +
  1253 + var refundData = await _db.Ado.SqlQueryAsync<dynamic>(refundSql);
  1254 +
  1255 + // 合并退卡数据
  1256 + foreach (var refund in refundData)
  1257 + {
  1258 + var itemId = refund.item_id?.ToString();
  1259 + if (!string.IsNullOrEmpty(itemId) && itemStatisticsDict.ContainsKey(itemId))
  1260 + {
  1261 + itemStatisticsDict[itemId].RefundCount = Convert.ToInt32(refund.refund_count ?? 0);
  1262 + itemStatisticsDict[itemId].RefundAmount = Convert.ToDecimal(refund.refund_amount ?? 0);
  1263 + }
  1264 + }
  1265 +
  1266 + // 5. 过滤并排序
  1267 + var itemStatisticsList = itemStatisticsDict.Values
  1268 + .Where(x => x.BillingCount > 0 || x.ConsumeCount > 0 || x.RefundCount > 0)
  1269 + .OrderByDescending(x => x.BillingAmount)
  1270 + .ToList();
  1271 +
  1272 + return itemStatisticsList;
  1273 + }
  1274 + catch (Exception ex)
  1275 + {
  1276 + _logger.LogError(ex, $"获取品项统计数据失败 - 开始时间: {input?.StartTime}, 结束时间: {input?.EndTime}");
  1277 + throw NCCException.Oh($"获取品项统计数据失败: {ex.Message}");
  1278 + }
  1279 + }
  1280 +
  1281 + #endregion
685 1282 }
686 1283 }
... ...
netcore/src/Modularity/Extend/NCC.Extend/LqStatisticsService.cs
... ... @@ -1432,6 +1432,8 @@ namespace NCC.Extend.LqStatistics
1432 1432 order_stats.FirstOrderDate,
1433 1433 COALESCE(coop_stats.CooperationPerformance, 0) AS CooperationPerformance,
1434 1434 COALESCE(base_stats.BasePerformance, 0) AS BasePerformance,
  1435 + COALESCE(refund_stats.RefundPerformance, 0) AS RefundPerformance,
  1436 + COALESCE(refund_stats.RefundCount, 0) AS RefundCount,
1435 1437 order_stats.TotalPerformance
1436 1438 FROM (
1437 1439 -- 按开单记录统计基础数据,避免重复计算
... ... @@ -1541,6 +1543,23 @@ namespace NCC.Extend.LqStatistics
1541 1543 AND (xmzl.fl3 IS NULL OR xmzl.fl3 != '合作业绩')
1542 1544 GROUP BY jksyj.jkszh
1543 1545 ) base_stats ON order_stats.EmployeeId = base_stats.EmployeeId
  1546 + LEFT JOIN (
  1547 + -- 退单业绩统计
  1548 + SELECT
  1549 + hytk_jksyj.jkszh AS EmployeeId,
  1550 + SUM(CAST(hytk_jksyj.jksyj AS DECIMAL(18,2))) AS RefundPerformance,
  1551 + COUNT(*) AS RefundCount
  1552 + FROM lq_hytk_jksyj hytk_jksyj
  1553 + INNER JOIN lq_hytk_hytk hytk ON hytk_jksyj.gltkbh = hytk.F_Id
  1554 + WHERE hytk_jksyj.jksyj IS NOT NULL
  1555 + AND hytk_jksyj.jksyj != ''
  1556 + AND hytk_jksyj.jksyj != '0'
  1557 + AND hytk_jksyj.F_IsEffective = 1
  1558 + AND hytk.F_IsEffective = 1
  1559 + AND YEAR(hytk_jksyj.tksj) = @year
  1560 + AND MONTH(hytk_jksyj.tksj) = @month
  1561 + GROUP BY hytk_jksyj.jks
  1562 + ) refund_stats ON order_stats.EmployeeId = refund_stats.EmployeeId
1544 1563 ORDER BY order_stats.TotalPerformance DESC";
1545 1564  
1546 1565 // 解析统计月份
... ... @@ -1582,9 +1601,12 @@ namespace NCC.Extend.LqStatistics
1582 1601 Position = stats.Position?.ToString() ?? "",
1583 1602 EmployeeId = stats.EmployeeId?.ToString() ?? "",
1584 1603 EmployeeName = stats.EmployeeName?.ToString() ?? "",
1585   - TotalPerformance = Convert.ToDecimal(stats.TotalPerformance ?? 0),
  1604 + TotalPerformance = Convert.ToDecimal(stats.TotalPerformance ?? 0) - Convert.ToDecimal(stats.RefundPerformance ?? 0),
1586 1605 BasePerformance = Convert.ToDecimal(stats.BasePerformance ?? 0),
1587 1606 CooperationPerformance = Convert.ToDecimal(stats.CooperationPerformance ?? 0),
  1607 + RefundPerformance = Convert.ToDecimal(stats.RefundPerformance ?? 0),
  1608 + RefundCount = Convert.ToInt32(stats.RefundCount ?? 0),
  1609 + ActualPerformance = Convert.ToDecimal(stats.TotalPerformance ?? 0) - Convert.ToDecimal(stats.RefundPerformance ?? 0),
1588 1610 OrderCount = Convert.ToInt32(stats.OrderCount ?? 0),
1589 1611 FirstOrderCount = Convert.ToInt32(stats.FirstOrderCount ?? 0),
1590 1612 UpgradeOrderCount = Convert.ToInt32(stats.UpgradeOrderCount ?? 0),
... ... @@ -1665,7 +1687,7 @@ namespace NCC.Extend.LqStatistics
1665 1687 }
1666 1688  
1667 1689 /// <summary>
1668   - /// 分页查询个人开单业绩统计数据
  1690 + /// 分页查询个人开单业绩统计数据(在用)
1669 1691 /// </summary>
1670 1692 /// <remarks>
1671 1693 /// 分页查询个人业绩统计数据,支持多条件筛选
... ... @@ -1727,7 +1749,10 @@ namespace NCC.Extend.LqStatistics
1727 1749 UpgradeOrderPerformance = it.UpgradeOrderPerformance,
1728 1750 LastOrderDate = it.LastOrderDate,
1729 1751 FirstOrderDate = it.FirstOrderDate,
1730   - CreateTime = it.CreateTime
  1752 + CreateTime = it.CreateTime,
  1753 + RefundPerformance = it.RefundPerformance,
  1754 + RefundCount = it.RefundCount,
  1755 + ActualPerformance = it.ActualPerformance
1731 1756 }).ToPagedListAsync(input.currentPage, input.pageSize);
1732 1757  
1733 1758 return new
... ... @@ -2588,6 +2613,7 @@ namespace NCC.Extend.LqStatistics
2588 2613 UpgradeOrderPerformance = Convert.ToDecimal(data.F_UpgradeOrderPerformance ?? 0),
2589 2614 RefundAmount = Convert.ToDecimal(data.F_RefundAmount ?? 0),
2590 2615 RefundCount = Convert.ToInt32(data.F_RefundCount ?? 0),
  2616 + ActualPerformance = Convert.ToDecimal(data.F_TotalOrderPerformance ?? 0) - Convert.ToDecimal(data.F_RefundAmount ?? 0),
2591 2617 CreateTime = DateTime.Now
2592 2618 }).ToList();
2593 2619  
... ... @@ -2671,6 +2697,9 @@ namespace NCC.Extend.LqStatistics
2671 2697 upgradeOrderCount = it.UpgradeOrderCount,
2672 2698 firstOrderPerformance = it.FirstOrderPerformance,
2673 2699 upgradeOrderPerformance = it.UpgradeOrderPerformance,
  2700 + refundAmount = it.RefundAmount,
  2701 + refundCount = it.RefundCount,
  2702 + actualPerformance = it.ActualPerformance,
2674 2703 createTime = it.CreateTime
2675 2704 })
2676 2705 .OrderBy(sidx + " " + input.sort)
... ... @@ -3329,7 +3358,8 @@ namespace NCC.Extend.LqStatistics
3329 3358 UpgradeOrderCount = it.UpgradeOrderCount,
3330 3359 RefundAmount = it.RefundAmount,
3331 3360 RefundCount = it.RefundCount,
3332   - CreateTime = it.CreateTime.HasValue ? it.CreateTime.Value : DateTime.Now
  3361 + CreateTime = it.CreateTime.HasValue ? it.CreateTime.Value : DateTime.Now,
  3362 + ActualPerformance = it.ActualPerformance
3333 3363 }).ToList();
3334 3364  
3335 3365 return new
... ...
netcore/src/Modularity/Extend/NCC.Extend/LqTkjlbService.cs
... ... @@ -609,49 +609,83 @@ namespace NCC.Extend.LqTkjlb
609 609 {
610 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 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 684 return new
651 685 {
652 686 success = true,
653   - data = result?.FirstOrDefault(),
654   - message = "获取总体漏斗统计数据成功"
  687 + data = result,
  688 + message = "获取总体漏斗统计数据成功(高性能版本)"
655 689 };
656 690 }
657 691 catch (Exception ex)
... ...