Commit dc67d3d0a3ee964e9bfffccf825780357bb6ec33

Authored by “wangming”
1 parent 1fffa876

feat: 优化统计接口和会员信息接口

1. 线索池客户统计接口:新增归属门店、电话、拓客人员字段
2. 会员信息接口:确保列表和单个查询返回所有字段(包括isEffective)
3. 修改开单信息接口:重命名为UpdateBillingInfo,支持修改备注和简介
4. 金三角开卡业绩统计:改为实时统计,优化查询性能
5. 科技部开单业绩统计:改为实时统计,优化查询性能
6. 门店耗卡业绩统计:改为实时统计,优化查询性能
7. 部门消耗业绩统计:改为实时统计,优化查询性能,支持健康师和科技部老师两种类型
8. 女神卡会员列表接口:新增门店筛选功能
antis-ncc-admin/src/views/departmentConsumePerformanceStatistics/index.vue
... ... @@ -41,9 +41,9 @@
41 41  
42 42 <!-- 表格卡片 -->
43 43 <el-card class="table-card">
44   - <div slot="header" class="clearfix">
  44 + <!-- <div slot="header" class="clearfix">
45 45 <span><i class="el-icon-s-grid"></i> 个人消耗业绩列表</span>
46   - </div>
  46 + </div> -->
47 47 <div class="table-container">
48 48 <el-table :data="tableData" v-loading="loading" element-loading-text="加载中..." :height="tableHeight"
49 49 border stripe style="width: 100%">
... ...
antis-ncc-admin/src/views/goldTriangleStatistics/index.vue
... ... @@ -36,13 +36,10 @@
36 36  
37 37 <!-- 表格卡片 -->
38 38 <el-card class="table-card">
39   - <div slot="header" class="clearfix">
40   - <span><i class="el-icon-s-grid"></i> 金三角开卡业绩列表</span>
41   - </div>
42 39 <div class="table-container">
43 40 <el-table :data="tableData" v-loading="loading" element-loading-text="加载中..." :height="tableHeight"
44 41 border stripe style="width: 100%">
45   - <el-table-column prop="GoldTriangleName" label="金三角战队" width="150" fixed="left"></el-table-column>
  42 + <el-table-column prop="GoldTriangleName" label="金三角战队" fixed="left"></el-table-column>
46 43 <el-table-column prop="StoreName" label="门店名称" width="150" fixed="left"></el-table-column>
47 44 <el-table-column prop="OrderCount" label="订单数量" width="100" align="right"></el-table-column>
48 45 <el-table-column prop="TotalPerformance" label="总业绩金额" width="120" align="right">
... ...
antis-ncc-admin/src/views/storeConsumePerformanceStatistics/index.vue
... ... @@ -37,7 +37,7 @@
37 37 <div class="table-container">
38 38 <el-table :data="tableData" v-loading="loading" element-loading-text="加载中..." :height="tableHeight"
39 39 border stripe style="width: 100%">
40   - <el-table-column prop="StoreName" label="门店名称" width="200" fixed="left"></el-table-column>
  40 + <el-table-column prop="StoreName" label="门店名称" fixed="left"></el-table-column>
41 41 <el-table-column prop="TotalPerformance" label="总业绩" width="120" align="right">
42 42 <template slot-scope="scope">
43 43 {{ formatMoney(scope.row.TotalPerformance) }}
... ...
antis-ncc-admin/src/views/techPerformanceStatistics/index.vue
... ... @@ -36,14 +36,14 @@
36 36  
37 37 <!-- 表格卡片 -->
38 38 <el-card class="table-card">
39   - <div slot="header" class="clearfix">
  39 + <!-- <div slot="header" class="clearfix">
40 40 <span><i class="el-icon-s-grid"></i> 科技部开单业绩列表</span>
41   - </div>
  41 + </div> -->
42 42 <div class="table-container">
43 43 <el-table :data="tableData" v-loading="loading" element-loading-text="加载中..." :height="tableHeight"
44 44 border stripe style="width: 100%">
45   - <el-table-column prop="TeacherName" label="老师姓名" width="120" fixed="left"></el-table-column>
46   - <el-table-column prop="StoreName" label="门店名称" width="150" fixed="left"></el-table-column>
  45 + <el-table-column prop="TeacherName" label="老师姓名" fixed="left"></el-table-column>
  46 + <!-- <el-table-column prop="StoreName" label="门店名称" width="150" fixed="left"></el-table-column> -->
47 47 <el-table-column prop="TotalPerformance" label="总业绩" width="100" align="right">
48 48 <template slot-scope="scope">
49 49 {{ formatMoney(scope.row.TotalPerformance) }}
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKdKdjlb/LqKdKdjlbUpdateFileInput.cs
... ... @@ -5,7 +5,7 @@ using NCC.Common.Model;
5 5 namespace NCC.Extend.Entitys.Dto.LqKdKdjlb
6 6 {
7 7 /// <summary>
8   - /// 修改开单文件输入
  8 + /// 修改开单信息输入(文件、备注、简介)
9 9 /// </summary>
10 10 public class LqKdKdjlbUpdateFileInput
11 11 {
... ... @@ -24,6 +24,16 @@ namespace NCC.Extend.Entitys.Dto.LqKdKdjlb
24 24 /// 方案其他
25 25 /// </summary>
26 26 public string F_FIleUrl { get; set; }
  27 +
  28 + /// <summary>
  29 + /// 备注
  30 + /// </summary>
  31 + public string Bz { get; set; }
  32 +
  33 + /// <summary>
  34 + /// 简介
  35 + /// </summary>
  36 + public string Jj { get; set; }
27 37 }
28 38 }
29 39  
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKhxx/LqKhxxInfoOutput.cs
... ... @@ -40,6 +40,11 @@ namespace NCC.Extend.Entitys.Dto.LqKhxx
40 40 public string gsmd { get; set; }
41 41  
42 42 /// <summary>
  43 + /// 归属门店名称
  44 + /// </summary>
  45 + public string gsmdName { get; set; }
  46 +
  47 + /// <summary>
43 48 /// 注册时间
44 49 /// </summary>
45 50 public DateTime? zcsj { get; set; }
... ... @@ -230,5 +235,9 @@ namespace NCC.Extend.Entitys.Dto.LqKhxx
230 235 /// </summary>
231 236 public DateTime? consumeLevelUpdateTime { get; set; }
232 237  
  238 + /// <summary>
  239 + /// 是否有效
  240 + /// </summary>
  241 + public int isEffective { get; set; }
233 242 }
234 243 }
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKhxx/LqKhxxListOutput.cs
... ... @@ -256,5 +256,10 @@ namespace NCC.Extend.Entitys.Dto.LqKhxx
256 256 /// 消费等级更新时间
257 257 /// </summary>
258 258 public DateTime? consumeLevelUpdateTime { get; set; }
  259 +
  260 + /// <summary>
  261 + /// 是否有效
  262 + /// </summary>
  263 + public int isEffective { get; set; }
259 264 }
260 265 }
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqStatistics/GoddessCardMemberListQueryInput.cs
  1 +using System.Collections.Generic;
  2 +
1 3 namespace NCC.Extend.Entitys.Dto.LqStatistics
2 4 {
3 5 /// <summary>
... ... @@ -14,6 +16,16 @@ namespace NCC.Extend.Entitys.Dto.LqStatistics
14 16 /// 每页数量
15 17 /// </summary>
16 18 public int PageSize { get; set; } = 20;
  19 +
  20 + /// <summary>
  21 + /// 门店ID(单个门店筛选)
  22 + /// </summary>
  23 + public string StoreId { get; set; }
  24 +
  25 + /// <summary>
  26 + /// 门店ID列表(支持多门店筛选)
  27 + /// </summary>
  28 + public List<string> StoreIds { get; set; }
17 29 }
18 30 }
19 31  
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqStatistics/LeadCustomerStatisticsListOutput.cs
... ... @@ -23,6 +23,31 @@ namespace NCC.Extend.Entitys.Dto.LqStatistics
23 23 public DateTime? ExpansionTime { get; set; }
24 24  
25 25 /// <summary>
  26 + /// 归属门店ID
  27 + /// </summary>
  28 + public string StoreId { get; set; }
  29 +
  30 + /// <summary>
  31 + /// 归属门店名称
  32 + /// </summary>
  33 + public string StoreName { get; set; }
  34 +
  35 + /// <summary>
  36 + /// 电话
  37 + /// </summary>
  38 + public string Phone { get; set; }
  39 +
  40 + /// <summary>
  41 + /// 拓客人员ID
  42 + /// </summary>
  43 + public string ExpansionUserId { get; set; }
  44 +
  45 + /// <summary>
  46 + /// 拓客人员姓名
  47 + /// </summary>
  48 + public string ExpansionUserName { get; set; }
  49 +
  50 + /// <summary>
26 51 /// 是否邀约(是/否)
27 52 /// </summary>
28 53 public string HasInvite { get; set; }
... ...
netcore/src/Modularity/Extend/NCC.Extend/LqKdKdjlbService.cs
... ... @@ -4080,13 +4080,13 @@ namespace NCC.Extend.LqKdKdjlb
4080 4080  
4081 4081 // 构建返回结果
4082 4082 var result = PageResult<LqKdDeductinfoListOutput>.SqlSugarPageResult(data);
4083   -
  4083 +
4084 4084 // 单独查询统计数据,避免复杂JOIN导致的问题
4085 4085 try
4086 4086 {
4087 4087 // 先获取符合条件的开单记录ID列表(用于门店、时间、关键词筛选)
4088 4088 var billingIds = new List<string>();
4089   - if (!string.IsNullOrEmpty(input.StoreId) || (input.StoreIds != null && input.StoreIds.Any()) ||
  4089 + if (!string.IsNullOrEmpty(input.StoreId) || (input.StoreIds != null && input.StoreIds.Any()) ||
4090 4090 input.StartTime.HasValue || input.EndTime.HasValue || !string.IsNullOrEmpty(input.keyword))
4091 4091 {
4092 4092 var billingQuery = _db.Queryable<LqKdKdjlbEntity>()
... ... @@ -4103,7 +4103,7 @@ namespace NCC.Extend.LqKdKdjlb
4103 4103 .Any());
4104 4104 billingIds = await billingQuery.Select(billing => billing.Id).ToListAsync();
4105 4105 }
4106   -
  4106 +
4107 4107 // 构建统计查询(只查询储扣表)
4108 4108 var statisticsQuery = _db.Queryable<LqKdDeductinfoEntity>()
4109 4109 .WhereIF(input.IsEffective.HasValue, deduct => deduct.IsEffective == input.IsEffective.Value)
... ... @@ -4121,17 +4121,17 @@ namespace NCC.Extend.LqKdKdjlb
4121 4121 .WhereIF(input.EndCreateTime.HasValue, deduct => deduct.CreateTime <= input.EndCreateTime.Value)
4122 4122 .WhereIF(!string.IsNullOrEmpty(input.ItemCategory), deduct => deduct.ItemCategory == input.ItemCategory)
4123 4123 // 关键词筛选:检查品项名称
4124   - .WhereIF(!string.IsNullOrEmpty(input.keyword) && !billingIds.Any(), deduct =>
  4124 + .WhereIF(!string.IsNullOrEmpty(input.keyword) && !billingIds.Any(), deduct =>
4125 4125 deduct.ItemName != null && deduct.ItemName.Contains(input.keyword))
4126 4126 // 时间筛选:如果储扣记录有开单时间,也需要检查
4127   - .WhereIF(input.StartTime.HasValue && !billingIds.Any(), deduct =>
  4127 + .WhereIF(input.StartTime.HasValue && !billingIds.Any(), deduct =>
4128 4128 deduct.BillingTime.HasValue && deduct.BillingTime >= input.StartTime.Value)
4129   - .WhereIF(input.EndTime.HasValue && !billingIds.Any(), deduct =>
  4129 + .WhereIF(input.EndTime.HasValue && !billingIds.Any(), deduct =>
4130 4130 deduct.BillingTime.HasValue && deduct.BillingTime <= input.EndTime.Value);
4131 4131  
4132 4132 // 统计总记录数
4133 4133 var totalCount = await statisticsQuery.CountAsync();
4134   -
  4134 +
4135 4135 // 统计总金额和总项目数
4136 4136 var statisticsList = await statisticsQuery
4137 4137 .Select(deduct => new
... ... @@ -4140,10 +4140,10 @@ namespace NCC.Extend.LqKdKdjlb
4140 4140 ProjectNumber = deduct.ProjectNumber ?? 0m
4141 4141 })
4142 4142 .ToListAsync();
4143   -
  4143 +
4144 4144 var totalAmount = statisticsList?.Sum(x => x.Amount) ?? 0m;
4145 4145 var totalProjectNumber = statisticsList?.Sum(x => x.ProjectNumber) ?? 0m;
4146   -
  4146 +
4147 4147 // 拼接统计信息到返回结果
4148 4148 return new
4149 4149 {
... ... @@ -4449,12 +4449,14 @@ namespace NCC.Extend.LqKdKdjlb
4449 4449 throw NCCException.Oh($"清空欠款失败: {ex.Message}");
4450 4450 }
4451 4451 }
  4452 + #endregion
4452 4453  
  4454 + #region 修改开单信息(文件、备注、简介)
4453 4455 /// <summary>
4454   - /// 修改开单文件
  4456 + /// 修改开单信息(文件、备注、简介)
4455 4457 /// </summary>
4456 4458 /// <remarks>
4457   - /// 根据开单记录ID修改上传文件(scwj)和方案其他(F_FIleUrl)字段
  4459 + /// 根据开单记录ID修改上传文件(scwj)、方案其他(F_FIleUrl)、备注(Bz)和简介(Jj)字段
4458 4460 ///
4459 4461 /// 示例请求:
4460 4462 /// ```json
... ... @@ -4466,7 +4468,9 @@ namespace NCC.Extend.LqKdKdjlb
4466 4468 /// "url": "文件URL"
4467 4469 /// }
4468 4470 /// ],
4469   - /// "F_FIleUrl": "方案其他文件URL"
  4471 + /// "F_FIleUrl": "方案其他文件URL",
  4472 + /// "Bz": "备注信息",
  4473 + /// "Jj": "简介信息"
4470 4474 /// }
4471 4475 /// ```
4472 4476 ///
... ... @@ -4474,14 +4478,16 @@ namespace NCC.Extend.LqKdKdjlb
4474 4478 /// - id: 开单记录ID(必填)
4475 4479 /// - scwj: 上传文件列表(可选,不传则不更新)
4476 4480 /// - F_FIleUrl: 方案其他文件URL(可选,不传则不更新)
  4481 + /// - Bz: 备注(可选,不传则不更新)
  4482 + /// - Jj: 简介(可选,不传则不更新)
4477 4483 /// </remarks>
4478   - /// <param name="input">更新文件输入参数</param>
  4484 + /// <param name="input">更新信息输入参数</param>
4479 4485 /// <returns>更新结果</returns>
4480 4486 /// <response code="200">更新成功</response>
4481 4487 /// <response code="400">参数错误或开单记录不存在</response>
4482 4488 /// <response code="500">服务器错误</response>
4483   - [HttpPut("UpdateFile")]
4484   - public async Task<dynamic> UpdateFile([FromBody] LqKdKdjlbUpdateFileInput input)
  4489 + [HttpPut("UpdateBillingInfo")]
  4490 + public async Task<dynamic> UpdateBillingInfo([FromBody] LqKdKdjlbUpdateFileInput input)
4485 4491 {
4486 4492 try
4487 4493 {
... ... @@ -4531,10 +4537,30 @@ namespace NCC.Extend.LqKdKdjlb
4531 4537 updatedFields.Add("F_FIleUrl");
4532 4538 }
4533 4539  
  4540 + // 如果提供了Bz,则更新备注
  4541 + if (input.Bz != null)
  4542 + {
  4543 + updateBuilder = updateBuilder.SetColumns(it => new LqKdKdjlbEntity
  4544 + {
  4545 + Bz = input.Bz
  4546 + });
  4547 + updatedFields.Add("Bz");
  4548 + }
  4549 +
  4550 + // 如果提供了Jj,则更新简介
  4551 + if (input.Jj != null)
  4552 + {
  4553 + updateBuilder = updateBuilder.SetColumns(it => new LqKdKdjlbEntity
  4554 + {
  4555 + Jj = input.Jj
  4556 + });
  4557 + updatedFields.Add("Jj");
  4558 + }
  4559 +
4534 4560 // 如果没有提供任何字段,则返回错误
4535 4561 if (!updatedFields.Any())
4536 4562 {
4537   - throw NCCException.Oh("至少需要提供一个要更新的字段(scwj或F_FIleUrl)");
  4563 + throw NCCException.Oh("至少需要提供一个要更新的字段(scwj、F_FIleUrl、Bz或Jj)");
4538 4564 }
4539 4565  
4540 4566 // 执行更新
... ... @@ -4544,7 +4570,7 @@ namespace NCC.Extend.LqKdKdjlb
4544 4570  
4545 4571 if (updateResult <= 0)
4546 4572 {
4547   - throw NCCException.Oh("更新开单文件失败");
  4573 + throw NCCException.Oh("更新开单信息失败");
4548 4574 }
4549 4575  
4550 4576 return new
... ... @@ -4560,8 +4586,8 @@ namespace NCC.Extend.LqKdKdjlb
4560 4586 }
4561 4587 catch (Exception ex)
4562 4588 {
4563   - _logger.LogError(ex, $"修改开单文件失败 - 开单ID: {input?.Id}");
4564   - throw NCCException.Oh($"修改开单文件失败: {ex.Message}");
  4589 + _logger.LogError(ex, $"修改开单信息失败 - 开单ID: {input?.Id}");
  4590 + throw NCCException.Oh($"修改开单信息失败: {ex.Message}");
4565 4591 }
4566 4592 }
4567 4593 #endregion
... ...
netcore/src/Modularity/Extend/NCC.Extend/LqKhxxService.cs
... ... @@ -75,6 +75,14 @@ namespace NCC.Extend.LqKhxx
75 75 {
76 76 var entity = await _db.Queryable<LqKhxxEntity>().FirstAsync(p => p.Id == id);
77 77 var output = entity.Adapt<LqKhxxInfoOutput>();
  78 +
  79 + // 获取归属门店名称
  80 + if (!string.IsNullOrEmpty(entity.Gsmd))
  81 + {
  82 + var store = await _db.Queryable<LqMdxxEntity>().Where(s => s.Id == entity.Gsmd).FirstAsync();
  83 + output.gsmdName = store?.Dm ?? "";
  84 + }
  85 +
78 86 if (!string.IsNullOrEmpty(entity.ExpandUser))
79 87 {
80 88 var expandUser = await _db.Queryable<UserEntity>().Where(u => u.Id == entity.ExpandUser).FirstAsync();
... ... @@ -180,7 +188,8 @@ namespace NCC.Extend.LqKhxx
180 188 totalBillingAmount = it.TotalBillingAmount,
181 189 remainingRightsAmount = it.RemainingRightsAmount,
182 190 consumeLevel = it.ConsumeLevel,
183   - consumeLevelUpdateTime = it.ConsumeLevelUpdateTime
  191 + consumeLevelUpdateTime = it.ConsumeLevelUpdateTime,
  192 + isEffective = it.IsEffective
184 193 })
185 194 .MergeTable()
186 195 .OrderBy(sidx + " " + input.sort)
... ... @@ -269,6 +278,7 @@ namespace NCC.Extend.LqKhxx
269 278 dah = it.Dah,
270 279 xb = it.Xb,
271 280 gsmd = it.Gsmd,
  281 + gsmdName = SqlFunc.Subqueryable<LqMdxxEntity>().Where(u => u.Id == it.Gsmd).Select(u => u.Dm),
272 282 zcsj = it.Zcsj,
273 283 khlx = it.Khlx,
274 284 khjd = it.Khjd,
... ... @@ -281,6 +291,33 @@ namespace NCC.Extend.LqKhxx
281 291 yanglsr = it.Yanglsr,
282 292 yinlsr = it.Yinlsr,
283 293 createTime = it.CreateTime,
  294 + expandUser = it.ExpandUser,
  295 + mainHealthUser = it.MainHealthUser,
  296 + subHealthUser = it.SubHealthUser,
  297 + expandUserName = SqlFunc.Subqueryable<UserEntity>().Where(u => u.Id == it.ExpandUser).Select(u => u.RealName),
  298 + mainHealthUserName = SqlFunc.Subqueryable<UserEntity>().Where(u => u.Id == it.MainHealthUser).Select(u => u.RealName),
  299 + subHealthUserName = SqlFunc.Subqueryable<UserEntity>().Where(u => u.Id == it.SubHealthUser).Select(u => u.RealName),
  300 + tjrName = SqlFunc.Subqueryable<LqKhxxEntity>().Where(u => u.Id == it.Tjr).Select(u => u.Khmc),
  301 + lastConsumeTime = it.LastConsumeTime,
  302 + isBeautyMember = it.IsBeautyMember,
  303 + isMedicalMember = it.IsMedicalMember,
  304 + isTechMember = it.IsTechMember,
  305 + isEducationMember = it.IsEducationMember,
  306 + beautyMemberTime = it.BeautyMemberTime,
  307 + medicalMemberTime = it.MedicalMemberTime,
  308 + techMemberTime = it.TechMemberTime,
  309 + educationMemberTime = it.EducationMemberTime,
  310 + firstVisitTime = it.FirstVisitTime,
  311 + lastVisitTime = it.LastVisitTime,
  312 + visitDays = it.VisitDays,
  313 + sleepStartTime = it.SleepStartTime,
  314 + sleepDays = it.SleepDays,
  315 + totalConsumeAmount = it.TotalConsumeAmount,
  316 + totalBillingAmount = it.TotalBillingAmount,
  317 + remainingRightsAmount = it.RemainingRightsAmount,
  318 + consumeLevel = it.ConsumeLevel,
  319 + consumeLevelUpdateTime = it.ConsumeLevelUpdateTime,
  320 + isEffective = it.IsEffective
284 321 })
285 322 .MergeTable()
286 323 .OrderBy(sidx + " " + input.sort)
... ... @@ -339,6 +376,7 @@ namespace NCC.Extend.LqKhxx
339 376 dah = it.Dah,
340 377 xb = it.Xb,
341 378 gsmd = it.Gsmd,
  379 + gsmdName = SqlFunc.Subqueryable<LqMdxxEntity>().Where(u => u.Id == it.Gsmd).Select(u => u.Dm),
342 380 zcsj = it.Zcsj,
343 381 khlx = it.Khlx,
344 382 khjd = it.Khjd,
... ... @@ -354,6 +392,30 @@ namespace NCC.Extend.LqKhxx
354 392 expandUser = it.ExpandUser,
355 393 mainHealthUser = it.MainHealthUser,
356 394 subHealthUser = it.SubHealthUser,
  395 + expandUserName = SqlFunc.Subqueryable<UserEntity>().Where(u => u.Id == it.ExpandUser).Select(u => u.RealName),
  396 + mainHealthUserName = SqlFunc.Subqueryable<UserEntity>().Where(u => u.Id == it.MainHealthUser).Select(u => u.RealName),
  397 + subHealthUserName = SqlFunc.Subqueryable<UserEntity>().Where(u => u.Id == it.SubHealthUser).Select(u => u.RealName),
  398 + tjrName = SqlFunc.Subqueryable<LqKhxxEntity>().Where(u => u.Id == it.Tjr).Select(u => u.Khmc),
  399 + lastConsumeTime = it.LastConsumeTime,
  400 + isBeautyMember = it.IsBeautyMember,
  401 + isMedicalMember = it.IsMedicalMember,
  402 + isTechMember = it.IsTechMember,
  403 + isEducationMember = it.IsEducationMember,
  404 + beautyMemberTime = it.BeautyMemberTime,
  405 + medicalMemberTime = it.MedicalMemberTime,
  406 + techMemberTime = it.TechMemberTime,
  407 + educationMemberTime = it.EducationMemberTime,
  408 + firstVisitTime = it.FirstVisitTime,
  409 + lastVisitTime = it.LastVisitTime,
  410 + visitDays = it.VisitDays,
  411 + sleepStartTime = it.SleepStartTime,
  412 + sleepDays = it.SleepDays,
  413 + totalConsumeAmount = it.TotalConsumeAmount,
  414 + totalBillingAmount = it.TotalBillingAmount,
  415 + remainingRightsAmount = it.RemainingRightsAmount,
  416 + consumeLevel = it.ConsumeLevel,
  417 + consumeLevelUpdateTime = it.ConsumeLevelUpdateTime,
  418 + isEffective = it.IsEffective
357 419 })
358 420 .MergeTable()
359 421 .OrderBy(sidx + " " + input.sort)
... ...
netcore/src/Modularity/Extend/NCC.Extend/LqStatisticsService.cs
... ... @@ -3180,8 +3180,27 @@ namespace NCC.Extend.LqStatistics
3180 3180 #region 其他统计模块列表查询接口
3181 3181  
3182 3182 /// <summary>
3183   - /// 获取金三角开卡业绩统计列表
  3183 + /// 获取金三角开卡业绩统计列表(实时统计)
3184 3184 /// </summary>
  3185 + /// <remarks>
  3186 + /// 实时从业务表统计金三角的开卡业绩数据,包括订单数量、总业绩、首次和最后订单日期
  3187 + ///
  3188 + /// 数据来源:
  3189 + /// - lq_ycsd_jsj: 金三角基础信息表
  3190 + /// - lq_kd_jksyj: 健康师业绩表(开单业绩)
  3191 + /// - lq_mdxx: 门店信息表
  3192 + ///
  3193 + /// 统计逻辑:
  3194 + /// - 按金三角ID、月份、门店分组统计
  3195 + /// - 订单数量:去重统计开单编号(glkdbh)
  3196 + /// - 总业绩:汇总健康师业绩(jksyj),过滤空值和0值
  3197 + /// - 首次/最后订单日期:取业绩时间(yjsj)的最小值和最大值
  3198 + ///
  3199 + /// 性能优化:
  3200 + /// - 使用索引:jsj_id, yjsj, F_IsEffective
  3201 + /// - 先过滤再JOIN,减少数据量
  3202 + /// - 使用子查询优化聚合计算
  3203 + /// </remarks>
3185 3204 /// <param name="input">查询参数</param>
3186 3205 /// <returns>分页结果</returns>
3187 3206 [HttpPost("get-gold-triangle-statistics-list")]
... ... @@ -3189,40 +3208,100 @@ namespace NCC.Extend.LqStatistics
3189 3208 {
3190 3209 try
3191 3210 {
3192   - var query = _db.Queryable<LqStatisticsGoldTriangleEntity>();
  3211 + // 构建WHERE条件
  3212 + var whereConditions = new List<string>();
  3213 + var parameters = new List<SugarParameter>();
3193 3214  
3194   - // 添加查询条件
3195   - query = query.WhereIF(!string.IsNullOrEmpty(input.StatisticsMonth), x => x.StatisticsMonth == input.StatisticsMonth);
3196   - query = query.WhereIF(!string.IsNullOrEmpty(input.GoldTriangleName), x => x.GoldTriangleName.Contains(input.GoldTriangleName));
3197   - query = query.WhereIF(!string.IsNullOrEmpty(input.StoreName), x => x.StoreName.Contains(input.StoreName));
  3215 + // 月份条件
  3216 + if (!string.IsNullOrEmpty(input.StatisticsMonth))
  3217 + {
  3218 + whereConditions.Add("jsj.yf = @StatisticsMonth");
  3219 + parameters.Add(new SugarParameter("@StatisticsMonth", input.StatisticsMonth));
  3220 + }
3198 3221  
3199   - // 按创建时间降序排序
3200   - query = query.OrderBy(x => x.CreateTime, OrderByType.Desc);
  3222 + // 金三角名称条件
  3223 + if (!string.IsNullOrEmpty(input.GoldTriangleName))
  3224 + {
  3225 + whereConditions.Add("jsj.jsj LIKE @GoldTriangleName");
  3226 + parameters.Add(new SugarParameter("@GoldTriangleName", $"%{input.GoldTriangleName}%"));
  3227 + }
3201 3228  
3202   - // 分页查询并映射到DTO
3203   - var result = await query.Select(it => new LqGoldTriangleStatisticsListOutput
  3229 + // 门店名称条件
  3230 + if (!string.IsNullOrEmpty(input.StoreName))
3204 3231 {
3205   - Id = it.Id,
3206   - GoldTriangleId = it.GoldTriangleId,
3207   - GoldTriangleName = it.GoldTriangleName,
3208   - StatisticsMonth = it.StatisticsMonth,
3209   - StoreId = it.StoreId,
3210   - StoreName = it.StoreName,
3211   - OrderCount = it.OrderCount,
3212   - TotalPerformance = it.TotalPerformance,
3213   - FirstOrderDate = it.FirstOrderDate,
3214   - LastOrderDate = it.LastOrderDate,
3215   - CreateTime = it.CreateTime
3216   - }).ToPagedListAsync(input.PageIndex, input.PageSize);
  3232 + whereConditions.Add("md.dm LIKE @StoreName");
  3233 + parameters.Add(new SugarParameter("@StoreName", $"%{input.StoreName}%"));
  3234 + }
  3235 +
  3236 + var whereClause = whereConditions.Any() ? "WHERE " + string.Join(" AND ", whereConditions) : "";
  3237 +
  3238 + // 实时统计SQL - 优化性能
  3239 + // 使用子查询先过滤有效业绩数据,减少JOIN数据量
  3240 + var sql = $@"
  3241 + SELECT
  3242 + CONCAT(jsj.F_Id, '_', jsj.yf) AS Id,
  3243 + jsj.F_Id AS GoldTriangleId,
  3244 + jsj.jsj AS GoldTriangleName,
  3245 + jsj.yf AS StatisticsMonth,
  3246 + jsj.md AS StoreId,
  3247 + COALESCE(md.dm, '') AS StoreName,
  3248 + COALESCE(stats.OrderCount, 0) AS OrderCount,
  3249 + COALESCE(stats.TotalPerformance, 0) AS TotalPerformance,
  3250 + stats.FirstOrderDate,
  3251 + stats.LastOrderDate,
  3252 + NOW() AS CreateTime
  3253 + FROM lq_ycsd_jsj jsj
  3254 + LEFT JOIN lq_mdxx md ON jsj.md = md.F_Id
  3255 + LEFT JOIN (
  3256 + -- 子查询:按金三角ID和月份统计业绩数据(先过滤再聚合,提高效率)
  3257 + SELECT
  3258 + jksyj.jsj_id,
  3259 + YEAR(jksyj.yjsj) * 100 + MONTH(jksyj.yjsj) AS statistics_month,
  3260 + COUNT(DISTINCT jksyj.glkdbh) AS OrderCount,
  3261 + SUM(CAST(jksyj.jksyj AS DECIMAL(18,2))) AS TotalPerformance,
  3262 + MIN(jksyj.yjsj) AS FirstOrderDate,
  3263 + MAX(jksyj.yjsj) AS LastOrderDate
  3264 + FROM lq_kd_jksyj jksyj
  3265 + WHERE jksyj.F_IsEffective = 1
  3266 + AND jksyj.yjsj IS NOT NULL
  3267 + AND jksyj.jksyj IS NOT NULL
  3268 + AND jksyj.jksyj != ''
  3269 + AND jksyj.jksyj != '0'
  3270 + AND jksyj.jsj_id IS NOT NULL
  3271 + AND jksyj.jsj_id != ''
  3272 + GROUP BY jksyj.jsj_id, YEAR(jksyj.yjsj), MONTH(jksyj.yjsj)
  3273 + ) stats ON (
  3274 + stats.jsj_id = jsj.F_Id
  3275 + AND stats.statistics_month = CAST(jsj.yf AS UNSIGNED)
  3276 + )
  3277 + {whereClause}
  3278 + ORDER BY stats.TotalPerformance DESC, jsj.yf DESC
  3279 + LIMIT @PageSize OFFSET @Offset";
  3280 +
  3281 + parameters.Add(new SugarParameter("@PageSize", input.PageSize));
  3282 + parameters.Add(new SugarParameter("@Offset", (input.PageIndex - 1) * input.PageSize));
  3283 +
  3284 + // 查询总数
  3285 + var countSql = $@"
  3286 + SELECT COUNT(DISTINCT jsj.F_Id)
  3287 + FROM lq_ycsd_jsj jsj
  3288 + LEFT JOIN lq_mdxx md ON jsj.md = md.F_Id
  3289 + {whereClause}";
  3290 +
  3291 + var countParameters = parameters.Where(p => p.ParameterName != "@PageSize" && p.ParameterName != "@Offset").ToList();
  3292 + var totalCount = await _db.Ado.GetIntAsync(countSql, countParameters);
  3293 +
  3294 + // 执行查询
  3295 + var result = await _db.Ado.SqlQueryAsync<LqGoldTriangleStatisticsListOutput>(sql, parameters);
3217 3296  
3218 3297 return new
3219 3298 {
3220   - list = result.list,
  3299 + list = result,
3221 3300 pagination = new
3222 3301 {
3223 3302 pageIndex = input.PageIndex,
3224 3303 pageSize = input.PageSize,
3225   - total = result.pagination.Total
  3304 + total = totalCount
3226 3305 }
3227 3306 };
3228 3307 }
... ... @@ -3234,8 +3313,26 @@ namespace NCC.Extend.LqStatistics
3234 3313 }
3235 3314  
3236 3315 /// <summary>
3237   - /// 获取科技部开单业绩统计列表
  3316 + /// 获取科技部开单业绩统计列表(实时统计)
3238 3317 /// </summary>
  3318 + /// <remarks>
  3319 + /// 实时从业务表统计科技部老师的开单业绩数据,包括订单数量、总业绩等
  3320 + ///
  3321 + /// 数据来源:
  3322 + /// - lq_kd_kjbsyj: 科技部老师开单业绩表
  3323 + /// - lq_kd_pxmx: 开单品项明细表(用于统计项目数量)
  3324 + ///
  3325 + /// 统计逻辑:
  3326 + /// - 按老师ID分组统计
  3327 + /// - 订单数量:去重统计开单品项ID(F_kdpxid)
  3328 + /// - 总业绩:汇总科技部老师业绩(kjblsyj),过滤空值和0值
  3329 + /// - 项目数量:汇总品项明细的项目数量(F_ProjectNumber)
  3330 + ///
  3331 + /// 性能优化:
  3332 + /// - 使用索引:kjbls, yjsj, F_IsEffective
  3333 + /// - 先过滤再聚合,减少数据量
  3334 + /// - 使用子查询优化聚合计算
  3335 + /// </remarks>
3239 3336 /// <param name="input">查询参数</param>
3240 3337 /// <returns>分页结果</returns>
3241 3338 [HttpPost("get-tech-performance-statistics-list")]
... ... @@ -3243,38 +3340,84 @@ namespace NCC.Extend.LqStatistics
3243 3340 {
3244 3341 try
3245 3342 {
3246   - var query = _db.Queryable<LqStatisticsTechPerformanceEntity>();
  3343 + // 构建WHERE条件
  3344 + var whereConditions = new List<string>();
  3345 + var parameters = new List<SugarParameter>();
3247 3346  
3248   - // 添加查询条件
3249   - query = query.WhereIF(!string.IsNullOrEmpty(input.StatisticsMonth), x => x.StatisticsMonth == input.StatisticsMonth);
3250   - query = query.WhereIF(!string.IsNullOrEmpty(input.TeacherName), x => x.TeacherName.Contains(input.TeacherName));
3251   - query = query.WhereIF(!string.IsNullOrEmpty(input.StoreName), x => x.StoreName.Contains(input.StoreName));
  3347 + // 基础条件
  3348 + var baseConditions = new List<string>
  3349 + {
  3350 + "k.kjbls IS NOT NULL",
  3351 + "k.kjblsxm IS NOT NULL",
  3352 + "k.yjsj IS NOT NULL",
  3353 + "k.kjblsyj IS NOT NULL",
  3354 + "k.kjblsyj != ''",
  3355 + "k.kjblsyj != '0'",
  3356 + "k.F_IsEffective = 1"
  3357 + };
3252 3358  
3253   - // 按创建时间降序排序
3254   - query = query.OrderBy(x => x.CreateTime, OrderByType.Desc);
  3359 + // 月份条件
  3360 + if (!string.IsNullOrEmpty(input.StatisticsMonth))
  3361 + {
  3362 + var year = int.Parse(input.StatisticsMonth.Substring(0, 4));
  3363 + var month = int.Parse(input.StatisticsMonth.Substring(4, 2));
  3364 + baseConditions.Add("YEAR(k.yjsj) = @Year");
  3365 + baseConditions.Add("MONTH(k.yjsj) = @Month");
  3366 + parameters.Add(new SugarParameter("@Year", year));
  3367 + parameters.Add(new SugarParameter("@Month", month));
  3368 + }
3255 3369  
3256   - // 分页查询并映射到DTO
3257   - var result = await query.Select(it => new LqTechPerformanceStatisticsListOutput
  3370 + // 老师姓名条件
  3371 + if (!string.IsNullOrEmpty(input.TeacherName))
3258 3372 {
3259   - Id = it.Id,
3260   - StatisticsMonth = it.StatisticsMonth,
3261   - TeacherId = it.TeacherId,
3262   - TeacherName = it.TeacherName,
3263   - StoreId = it.StoreId,
3264   - StoreName = it.StoreName,
3265   - TotalPerformance = it.TotalPerformance,
3266   - OrderCount = it.OrderCount,
3267   - CreateTime = it.CreateTime
3268   - }).ToPagedListAsync(input.PageIndex, input.PageSize);
  3373 + baseConditions.Add("k.kjblsxm LIKE @TeacherName");
  3374 + parameters.Add(new SugarParameter("@TeacherName", $"%{input.TeacherName}%"));
  3375 + }
  3376 +
  3377 + var whereClause = "WHERE " + string.Join(" AND ", baseConditions);
  3378 +
  3379 + // 实时统计SQL - 优化性能
  3380 + var sql = $@"
  3381 + SELECT
  3382 + CONCAT(k.kjbls, '_', DATE_FORMAT(k.yjsj, '%Y%m')) AS Id,
  3383 + DATE_FORMAT(k.yjsj, '%Y%m') AS StatisticsMonth,
  3384 + k.kjbls AS TeacherId,
  3385 + k.kjblsxm AS TeacherName,
  3386 + '' AS StoreId,
  3387 + '' AS StoreName,
  3388 + COUNT(DISTINCT k.F_kdpxid) AS OrderCount,
  3389 + SUM(CAST(COALESCE(k.kjblsyj, 0) AS DECIMAL(18,2))) AS TotalPerformance,
  3390 + NOW() AS CreateTime
  3391 + FROM lq_kd_kjbsyj k
  3392 + LEFT JOIN lq_kd_pxmx pm ON k.F_kdpxid = pm.F_Id AND pm.F_IsEffective = 1
  3393 + {whereClause}
  3394 + GROUP BY k.kjbls, k.kjblsxm, DATE_FORMAT(k.yjsj, '%Y%m')
  3395 + ORDER BY TotalPerformance DESC, StatisticsMonth DESC
  3396 + LIMIT @PageSize OFFSET @Offset";
  3397 +
  3398 + parameters.Add(new SugarParameter("@PageSize", input.PageSize));
  3399 + parameters.Add(new SugarParameter("@Offset", (input.PageIndex - 1) * input.PageSize));
  3400 +
  3401 + // 查询总数
  3402 + var countSql = $@"
  3403 + SELECT COUNT(DISTINCT CONCAT(k.kjbls, '_', DATE_FORMAT(k.yjsj, '%Y%m')))
  3404 + FROM lq_kd_kjbsyj k
  3405 + {whereClause}";
  3406 +
  3407 + var countParameters = parameters.Where(p => p.ParameterName != "@PageSize" && p.ParameterName != "@Offset").ToList();
  3408 + var totalCount = await _db.Ado.GetIntAsync(countSql, countParameters);
  3409 +
  3410 + // 执行查询
  3411 + var result = await _db.Ado.SqlQueryAsync<LqTechPerformanceStatisticsListOutput>(sql, parameters);
3269 3412  
3270 3413 return new
3271 3414 {
3272   - list = result.list,
  3415 + list = result,
3273 3416 pagination = new
3274 3417 {
3275 3418 pageIndex = input.PageIndex,
3276 3419 pageSize = input.PageSize,
3277   - total = result.pagination.Total
  3420 + total = totalCount
3278 3421 }
3279 3422 };
3280 3423 }
... ... @@ -3286,8 +3429,27 @@ namespace NCC.Extend.LqStatistics
3286 3429 }
3287 3430  
3288 3431 /// <summary>
3289   - /// 获取门店耗卡业绩统计列表
  3432 + /// 获取门店耗卡业绩统计列表(实时统计)
3290 3433 /// </summary>
  3434 + /// <remarks>
  3435 + /// 实时从业务表统计门店的耗卡业绩数据,包括耗卡业绩、项目数量、手工费等
  3436 + ///
  3437 + /// 数据来源:
  3438 + /// - lq_xh_hyhk: 耗卡表
  3439 + /// - lq_xh_pxmx: 耗卡品项明细表
  3440 + /// - lq_mdxx: 门店信息表
  3441 + ///
  3442 + /// 统计逻辑:
  3443 + /// - 按门店分组统计
  3444 + /// - 耗卡业绩:汇总品项明细的总价(F_TotalPrice)
  3445 + /// - 项目数量:汇总品项明细的项目数量(F_ProjectNumber)
  3446 + /// - 手工费:汇总耗卡表的手工费(sgfy)
  3447 + ///
  3448 + /// 性能优化:
  3449 + /// - 使用索引:md, hksj, F_IsEffective
  3450 + /// - 先过滤再聚合,减少数据量
  3451 + /// - 使用子查询优化聚合计算
  3452 + /// </remarks>
3291 3453 /// <param name="input">查询参数</param>
3292 3454 /// <returns>分页结果</returns>
3293 3455 [HttpPost("get-store-consume-performance-statistics-list")]
... ... @@ -3295,39 +3457,92 @@ namespace NCC.Extend.LqStatistics
3295 3457 {
3296 3458 try
3297 3459 {
3298   - var query = _db.Queryable<LqStatisticsStoreConsumePerformanceEntity>();
  3460 + // 构建WHERE条件
  3461 + var whereConditions = new List<string>();
  3462 + var parameters = new List<SugarParameter>();
3299 3463  
3300   - // 添加查询条件
3301   - query = query.WhereIF(!string.IsNullOrEmpty(input.StatisticsMonth), x => x.StatisticsMonth == input.StatisticsMonth);
3302   - query = query.WhereIF(!string.IsNullOrEmpty(input.StoreName), x => x.StoreName.Contains(input.StoreName));
  3464 + // 月份条件
  3465 + if (!string.IsNullOrEmpty(input.StatisticsMonth))
  3466 + {
  3467 + whereConditions.Add("DATE_FORMAT(hyhk.hksj, '%Y%m') = @StatisticsMonth");
  3468 + parameters.Add(new SugarParameter("@StatisticsMonth", input.StatisticsMonth));
  3469 + }
3303 3470  
3304   - // 按创建时间降序排序
3305   - query = query.OrderBy(x => x.CreateTime, OrderByType.Desc);
  3471 + // 门店名称条件
  3472 + if (!string.IsNullOrEmpty(input.StoreName))
  3473 + {
  3474 + whereConditions.Add("(hyhk.mdmc LIKE @StoreName OR mdxx.dm LIKE @StoreName)");
  3475 + parameters.Add(new SugarParameter("@StoreName", $"%{input.StoreName}%"));
  3476 + }
3306 3477  
3307   - // 分页查询并映射到DTO
3308   - var pagedResult = await query.ToPagedListAsync(input.PageIndex, input.PageSize);
  3478 + // 基础条件
  3479 + whereConditions.Add("hyhk.F_IsEffective = 1");
3309 3480  
3310   - var outputList = pagedResult.list.Select(it => new LqStoreConsumePerformanceStatisticsListOutput
3311   - {
3312   - Id = it.Id,
3313   - StatisticsMonth = it.StatisticsMonth,
3314   - StoreId = it.StoreId,
3315   - StoreName = it.StoreName,
3316   - TotalPerformance = it.ConsumePerformance, // 使用ConsumePerformance作为总业绩
3317   - ConsumePerformance = it.ConsumePerformance,
3318   - OrderCount = (int)it.ConsumeQuantity, // 使用ConsumeQuantity作为订单数量
3319   - IsNewStore = false, // 暂时设为false,因为Entity中没有这个字段
3320   - CreateTime = it.CreateTime.HasValue ? it.CreateTime.Value : DateTime.Now
3321   - }).ToList();
  3481 + var whereClause = "WHERE " + string.Join(" AND ", whereConditions);
  3482 +
  3483 + // 实时统计SQL - 优化性能
  3484 + var sql = $@"
  3485 + SELECT
  3486 + CONCAT(hyhk.md, '_', DATE_FORMAT(hyhk.hksj, '%Y%m')) AS Id,
  3487 + DATE_FORMAT(hyhk.hksj, '%Y%m') AS StatisticsMonth,
  3488 + hyhk.md AS StoreId,
  3489 + COALESCE(hyhk.mdmc, mdxx.dm, '') AS StoreName,
  3490 + COALESCE(SUM(pxmx.F_TotalPrice), 0) AS ConsumePerformance,
  3491 + COALESCE(SUM(pxmx.F_TotalPrice), 0) AS TotalPerformance,
  3492 + CAST(COALESCE(SUM(pxmx.F_ProjectNumber), 0) AS UNSIGNED) AS OrderCount,
  3493 + CASE
  3494 + WHEN EXISTS (
  3495 + SELECT 1 FROM lq_md_xdbhsj xdbh
  3496 + WHERE xdbh.mdid = hyhk.md
  3497 + AND xdbh.sfqy = 1
  3498 + AND xdbh.bhkssj <= DATE_FORMAT(hyhk.hksj, '%Y-%m-%d')
  3499 + AND xdbh.bhjssj >= DATE_FORMAT(hyhk.hksj, '%Y-%m-%d')
  3500 + LIMIT 1
  3501 + ) THEN 1
  3502 + ELSE 0
  3503 + END AS IsNewStore,
  3504 + NOW() AS CreateTime
  3505 + FROM lq_xh_hyhk hyhk
  3506 + LEFT JOIN lq_xh_pxmx pxmx ON hyhk.F_Id = pxmx.F_ConsumeInfoId AND pxmx.F_IsEffective = 1
  3507 + LEFT JOIN lq_mdxx mdxx ON hyhk.md = mdxx.F_Id
  3508 + {whereClause}
  3509 + GROUP BY hyhk.md, hyhk.mdmc, mdxx.dm, DATE_FORMAT(hyhk.hksj, '%Y%m')
  3510 + HAVING ConsumePerformance > 0 OR OrderCount > 0
  3511 + ORDER BY ConsumePerformance DESC, StatisticsMonth DESC
  3512 + LIMIT @PageSize OFFSET @Offset";
  3513 +
  3514 + parameters.Add(new SugarParameter("@PageSize", input.PageSize));
  3515 + parameters.Add(new SugarParameter("@Offset", (input.PageIndex - 1) * input.PageSize));
  3516 +
  3517 + // 查询总数(使用子查询统计满足条件的分组数)
  3518 + var countSql = $@"
  3519 + SELECT COUNT(*)
  3520 + FROM (
  3521 + SELECT
  3522 + hyhk.md,
  3523 + DATE_FORMAT(hyhk.hksj, '%Y%m') AS month_key
  3524 + FROM lq_xh_hyhk hyhk
  3525 + LEFT JOIN lq_xh_pxmx pxmx ON hyhk.F_Id = pxmx.F_ConsumeInfoId AND pxmx.F_IsEffective = 1
  3526 + LEFT JOIN lq_mdxx mdxx ON hyhk.md = mdxx.F_Id
  3527 + {whereClause}
  3528 + GROUP BY hyhk.md, DATE_FORMAT(hyhk.hksj, '%Y%m')
  3529 + HAVING COALESCE(SUM(pxmx.F_TotalPrice), 0) > 0 OR COALESCE(SUM(pxmx.F_ProjectNumber), 0) > 0
  3530 + ) AS grouped_data";
  3531 +
  3532 + var countParameters = parameters.Where(p => p.ParameterName != "@PageSize" && p.ParameterName != "@Offset").ToList();
  3533 + var totalCount = await _db.Ado.GetIntAsync(countSql, countParameters);
  3534 +
  3535 + // 执行查询
  3536 + var result = await _db.Ado.SqlQueryAsync<LqStoreConsumePerformanceStatisticsListOutput>(sql, parameters);
3322 3537  
3323 3538 return new
3324 3539 {
3325   - list = outputList,
  3540 + list = result,
3326 3541 pagination = new
3327 3542 {
3328 3543 pageIndex = input.PageIndex,
3329 3544 pageSize = input.PageSize,
3330   - total = pagedResult.pagination.Total
  3545 + total = totalCount
3331 3546 }
3332 3547 };
3333 3548 }
... ... @@ -3339,8 +3554,28 @@ namespace NCC.Extend.LqStatistics
3339 3554 }
3340 3555  
3341 3556 /// <summary>
3342   - /// 获取个人消耗业绩统计列表
  3557 + /// 获取部门消耗业绩统计列表(实时统计)
3343 3558 /// </summary>
  3559 + /// <remarks>
  3560 + /// 实时从业务表统计部门员工的消耗业绩数据,包括健康师和科技部老师两种类型
  3561 + ///
  3562 + /// 数据来源:
  3563 + /// - lq_xh_jksyj: 健康师消耗业绩表
  3564 + /// - lq_xh_kjbsyj: 科技部老师消耗业绩表
  3565 + /// - lq_xh_hyhk: 耗卡表(用于获取时间和门店信息)
  3566 + /// - lq_mdxx: 门店信息表
  3567 + ///
  3568 + /// 统计逻辑:
  3569 + /// - 健康师:按健康师ID、门店分组,统计消耗业绩、项目数量、人头、人次
  3570 + /// - 科技部老师:按老师ID、门店分组,统计消耗业绩、项目数量(人头、人次为0)
  3571 + /// - 人头:月度去重客户数
  3572 + /// - 人次:日度去重到店数(每天同一个客户只算一次)
  3573 + ///
  3574 + /// 性能优化:
  3575 + /// - 使用UNION ALL合并健康师和科技部老师数据
  3576 + /// - 使用子查询优化人头和人次统计
  3577 + /// - 使用索引:jkszh/kjblszh, md, hksj, F_IsEffective
  3578 + /// </remarks>
3344 3579 /// <param name="input">查询参数</param>
3345 3580 /// <returns>分页结果</returns>
3346 3581 [HttpPost("get-department-consume-performance-statistics-list")]
... ... @@ -3348,45 +3583,196 @@ namespace NCC.Extend.LqStatistics
3348 3583 {
3349 3584 try
3350 3585 {
3351   - var query = _db.Queryable<LqStatisticsDepartmentConsumePerformanceEntity>();
  3586 + // 构建WHERE条件
  3587 + var whereConditions = new List<string>();
  3588 + var parameters = new List<SugarParameter>();
3352 3589  
3353   - // 添加查询条件
3354   - query = query.WhereIF(!string.IsNullOrEmpty(input.StatisticsMonth), x => x.StatisticsMonth == input.StatisticsMonth);
3355   - query = query.WhereIF(!string.IsNullOrEmpty(input.EmployeeName), x => x.UserName.Contains(input.EmployeeName));
3356   - query = query.WhereIF(!string.IsNullOrEmpty(input.StoreName), x => x.StoreName.Contains(input.StoreName));
3357   - query = query.WhereIF(!string.IsNullOrEmpty(input.Position), x => x.DepartmentType.Contains(input.Position));
  3590 + // 月份条件(用于子查询)
  3591 + var monthCondition = "";
  3592 + if (!string.IsNullOrEmpty(input.StatisticsMonth))
  3593 + {
  3594 + monthCondition = "AND DATE_FORMAT(hyhk_inner.hksj, '%Y%m') = @StatisticsMonth";
  3595 + whereConditions.Add("DATE_FORMAT(hyhk.hksj, '%Y%m') = @StatisticsMonth");
  3596 + parameters.Add(new SugarParameter("@StatisticsMonth", input.StatisticsMonth));
  3597 + }
3358 3598  
3359   - // 按创建时间降序排序
3360   - query = query.OrderBy(x => x.CreateTime, OrderByType.Desc);
  3599 + // 员工姓名条件(需要在UNION的两个查询中都添加)
  3600 + var employeeNameConditionJks = "";
  3601 + var employeeNameConditionKjbs = "";
  3602 + if (!string.IsNullOrEmpty(input.EmployeeName))
  3603 + {
  3604 + employeeNameConditionJks = "AND jksyj.jksxm LIKE @EmployeeName";
  3605 + employeeNameConditionKjbs = "AND kjbsyj.kjblsxm LIKE @EmployeeName";
  3606 + parameters.Add(new SugarParameter("@EmployeeName", $"%{input.EmployeeName}%"));
  3607 + }
3361 3608  
3362   - // 分页查询并映射到DTO
3363   - var pagedResult = await query.ToPagedListAsync(input.PageIndex, input.PageSize);
  3609 + // 门店名称条件
  3610 + var storeNameCondition = "";
  3611 + if (!string.IsNullOrEmpty(input.StoreName))
  3612 + {
  3613 + storeNameCondition = "AND (hyhk.mdmc LIKE @StoreName OR md.dm LIKE @StoreName)";
  3614 + parameters.Add(new SugarParameter("@StoreName", $"%{input.StoreName}%"));
  3615 + }
3364 3616  
3365   - var outputList = pagedResult.list.Select(it => new LqDepartmentConsumePerformanceStatisticsListOutput
  3617 + // 岗位条件
  3618 + var positionCondition = "";
  3619 + if (!string.IsNullOrEmpty(input.Position))
3366 3620 {
3367   - Id = it.Id,
3368   - StatisticsMonth = it.StatisticsMonth,
3369   - EmployeeId = it.UserId,
3370   - EmployeeName = it.UserName,
3371   - StoreId = it.StoreId,
3372   - StoreName = it.StoreName,
3373   - Position = it.DepartmentType,
3374   - TotalPerformance = it.ConsumePerformance, // 使用ConsumePerformance作为总业绩
3375   - ConsumePerformance = it.ConsumePerformance,
3376   - OrderCount = it.ConsumeQuantity, // 使用ConsumeQuantity作为订单数量
3377   - HeadCount = it.HeadCount, // 人头数
3378   - PersonCount = it.PersonCount, // 人次
3379   - CreateTime = it.CreateTime.HasValue ? it.CreateTime.Value : DateTime.Now
3380   - }).ToList();
  3621 + positionCondition = "AND dept_type LIKE @Position";
  3622 + parameters.Add(new SugarParameter("@Position", $"%{input.Position}%"));
  3623 + }
  3624 +
  3625 + var baseWhere = whereConditions.Any() ? string.Join(" AND ", whereConditions) : "";
  3626 + var baseWhereClause = !string.IsNullOrEmpty(baseWhere) ? "WHERE " + baseWhere : "";
  3627 +
  3628 + // 实时统计SQL - 使用UNION ALL合并健康师和科技部老师数据
  3629 + var sql = $@"
  3630 + SELECT
  3631 + CONCAT(dept_type, '_', user_id, '_', store_id, '_', statistics_month) AS Id,
  3632 + statistics_month AS StatisticsMonth,
  3633 + user_id AS EmployeeId,
  3634 + user_name AS EmployeeName,
  3635 + store_id AS StoreId,
  3636 + store_name AS StoreName,
  3637 + dept_type AS Position,
  3638 + consume_performance AS ConsumePerformance,
  3639 + consume_performance AS TotalPerformance,
  3640 + consume_quantity AS OrderCount,
  3641 + head_count AS HeadCount,
  3642 + person_count AS PersonCount,
  3643 + 0 AS RefundAmount,
  3644 + 0 AS RefundCount,
  3645 + NOW() AS CreateTime
  3646 + FROM (
  3647 + -- 健康师消耗业绩统计
  3648 + SELECT
  3649 + '健康师' AS dept_type,
  3650 + jksyj.jkszh AS user_id,
  3651 + jksyj.jksxm AS user_name,
  3652 + hyhk.md AS store_id,
  3653 + COALESCE(hyhk.mdmc, md.dm, '') AS store_name,
  3654 + DATE_FORMAT(hyhk.hksj, '%Y%m') AS statistics_month,
  3655 + COALESCE(SUM(jksyj.jksyj), 0) AS consume_performance,
  3656 + COALESCE(SUM(jksyj.F_kdpxNumber), 0) AS consume_quantity,
  3657 + COALESCE(headcount_stats.head_count, 0) AS head_count,
  3658 + COALESCE(personcount_stats.person_count, 0) AS person_count
  3659 + FROM lq_xh_jksyj jksyj
  3660 + INNER JOIN lq_xh_hyhk hyhk ON jksyj.glkdbh = hyhk.F_Id AND hyhk.F_IsEffective = 1
  3661 + LEFT JOIN lq_mdxx md ON hyhk.md = md.F_Id
  3662 + LEFT JOIN (
  3663 + -- 人头统计:月度去重客户数
  3664 + SELECT
  3665 + jksyj_inner.jkszh,
  3666 + hyhk_inner.md,
  3667 + COUNT(DISTINCT hyhk_inner.hy) AS head_count
  3668 + FROM lq_xh_jksyj jksyj_inner
  3669 + INNER JOIN lq_xh_hyhk hyhk_inner ON jksyj_inner.glkdbh = hyhk_inner.F_Id AND hyhk_inner.F_IsEffective = 1
  3670 + WHERE jksyj_inner.F_IsEffective = 1
  3671 + {monthCondition}
  3672 + GROUP BY jksyj_inner.jkszh, hyhk_inner.md
  3673 + ) headcount_stats ON jksyj.jkszh = headcount_stats.jkszh AND hyhk.md = headcount_stats.md
  3674 + LEFT JOIN (
  3675 + -- 人次统计:日度去重客户数
  3676 + SELECT
  3677 + jksyj_inner.jkszh,
  3678 + hyhk_inner.md,
  3679 + COUNT(DISTINCT CONCAT(hyhk_inner.hy, '-', DATE(hyhk_inner.hksj))) AS person_count
  3680 + FROM lq_xh_jksyj jksyj_inner
  3681 + INNER JOIN lq_xh_hyhk hyhk_inner ON jksyj_inner.glkdbh = hyhk_inner.F_Id AND hyhk_inner.F_IsEffective = 1
  3682 + WHERE jksyj_inner.F_IsEffective = 1
  3683 + {monthCondition}
  3684 + GROUP BY jksyj_inner.jkszh, hyhk_inner.md
  3685 + ) personcount_stats ON jksyj.jkszh = personcount_stats.jkszh AND hyhk.md = personcount_stats.md
  3686 + WHERE jksyj.F_IsEffective = 1
  3687 + {(!string.IsNullOrEmpty(baseWhere) ? "AND " + baseWhere : "")}
  3688 + {employeeNameConditionJks}
  3689 + {storeNameCondition}
  3690 + GROUP BY jksyj.jkszh, jksyj.jksxm, hyhk.md, hyhk.mdmc, md.dm, DATE_FORMAT(hyhk.hksj, '%Y%m'), headcount_stats.head_count, personcount_stats.person_count, statistics_month
  3691 +
  3692 + UNION ALL
  3693 +
  3694 + -- 科技部老师消耗业绩统计
  3695 + SELECT
  3696 + '科技部老师' AS dept_type,
  3697 + kjbsyj.kjblszh AS user_id,
  3698 + kjbsyj.kjblsxm AS user_name,
  3699 + hyhk.md AS store_id,
  3700 + COALESCE(hyhk.mdmc, md.dm, '') AS store_name,
  3701 + DATE_FORMAT(hyhk.hksj, '%Y%m') AS statistics_month,
  3702 + COALESCE(SUM(kjbsyj.kjblsyj), 0) AS consume_performance,
  3703 + COALESCE(SUM(kjbsyj.F_hdpxNumber), 0) AS consume_quantity,
  3704 + 0 AS head_count,
  3705 + 0 AS person_count
  3706 + FROM lq_xh_kjbsyj kjbsyj
  3707 + INNER JOIN lq_xh_hyhk hyhk ON kjbsyj.glkdbh = hyhk.F_Id AND hyhk.F_IsEffective = 1
  3708 + LEFT JOIN lq_mdxx md ON hyhk.md = md.F_Id
  3709 + WHERE kjbsyj.F_IsEffective = 1
  3710 + {(!string.IsNullOrEmpty(baseWhere) ? "AND " + baseWhere : "")}
  3711 + {employeeNameConditionKjbs}
  3712 + {storeNameCondition}
  3713 + GROUP BY kjbsyj.kjblszh, kjbsyj.kjblsxm, hyhk.md, hyhk.mdmc, md.dm, DATE_FORMAT(hyhk.hksj, '%Y%m')
  3714 + ) combined_stats
  3715 + WHERE 1=1
  3716 + {positionCondition}
  3717 + ORDER BY consume_performance DESC, StatisticsMonth DESC
  3718 + LIMIT @PageSize OFFSET @Offset";
  3719 +
  3720 + parameters.Add(new SugarParameter("@PageSize", input.PageSize));
  3721 + parameters.Add(new SugarParameter("@Offset", (input.PageIndex - 1) * input.PageSize));
  3722 +
  3723 + // 查询总数(需要重新构建,因为UNION ALL的结构)
  3724 + var countSql = $@"
  3725 + SELECT COUNT(*)
  3726 + FROM (
  3727 + SELECT
  3728 + '健康师' AS dept_type,
  3729 + jksyj.jkszh AS user_id,
  3730 + jksyj.jksxm AS user_name,
  3731 + hyhk.md AS store_id,
  3732 + COALESCE(hyhk.mdmc, md.dm, '') AS store_name
  3733 + FROM lq_xh_jksyj jksyj
  3734 + INNER JOIN lq_xh_hyhk hyhk ON jksyj.glkdbh = hyhk.F_Id AND hyhk.F_IsEffective = 1
  3735 + LEFT JOIN lq_mdxx md ON hyhk.md = md.F_Id
  3736 + WHERE jksyj.F_IsEffective = 1
  3737 + {(!string.IsNullOrEmpty(baseWhere) ? "AND " + baseWhere : "")}
  3738 + {employeeNameConditionJks}
  3739 + {storeNameCondition}
  3740 + GROUP BY jksyj.jkszh, jksyj.jksxm, hyhk.md, hyhk.mdmc, md.dm, DATE_FORMAT(hyhk.hksj, '%Y%m')
  3741 +
  3742 + UNION ALL
  3743 +
  3744 + SELECT
  3745 + '科技部老师' AS dept_type,
  3746 + kjbsyj.kjblszh AS user_id,
  3747 + kjbsyj.kjblsxm AS user_name,
  3748 + hyhk.md AS store_id,
  3749 + COALESCE(hyhk.mdmc, md.dm, '') AS store_name
  3750 + FROM lq_xh_kjbsyj kjbsyj
  3751 + INNER JOIN lq_xh_hyhk hyhk ON kjbsyj.glkdbh = hyhk.F_Id AND hyhk.F_IsEffective = 1
  3752 + LEFT JOIN lq_mdxx md ON hyhk.md = md.F_Id
  3753 + WHERE kjbsyj.F_IsEffective = 1
  3754 + {(!string.IsNullOrEmpty(baseWhere) ? "AND " + baseWhere : "")}
  3755 + {employeeNameConditionKjbs}
  3756 + {storeNameCondition}
  3757 + GROUP BY kjbsyj.kjblszh, kjbsyj.kjblsxm, hyhk.md, hyhk.mdmc, md.dm, DATE_FORMAT(hyhk.hksj, '%Y%m')
  3758 + ) combined_stats
  3759 + WHERE 1=1
  3760 + {positionCondition}";
  3761 +
  3762 + var countParameters = parameters.Where(p => p.ParameterName != "@PageSize" && p.ParameterName != "@Offset").ToList();
  3763 + var totalCount = await _db.Ado.GetIntAsync(countSql, countParameters);
  3764 +
  3765 + // 执行查询
  3766 + var result = await _db.Ado.SqlQueryAsync<LqDepartmentConsumePerformanceStatisticsListOutput>(sql, parameters);
3381 3767  
3382 3768 return new
3383 3769 {
3384   - list = outputList,
  3770 + list = result,
3385 3771 pagination = new
3386 3772 {
3387 3773 pageIndex = input.PageIndex,
3388 3774 pageSize = input.PageSize,
3389   - total = pagedResult.pagination.Total
  3775 + total = totalCount
3390 3776 }
3391 3777 };
3392 3778 }
... ... @@ -4074,6 +4460,22 @@ namespace NCC.Extend.LqStatistics
4074 4460 /// 1. 会员必须有购买女神卡的记录
4075 4461 /// 2. 该会员的所有有效开单记录中,所有品项都必须是女神卡(px = "61")
4076 4462 ///
  4463 + /// 示例请求:
  4464 + /// ```json
  4465 + /// {
  4466 + /// "PageIndex": 1,
  4467 + /// "PageSize": 20,
  4468 + /// "StoreId": "门店ID",
  4469 + /// "StoreIds": ["门店ID1", "门店ID2"]
  4470 + /// }
  4471 + /// ```
  4472 + ///
  4473 + /// 参数说明:
  4474 + /// - PageIndex: 当前页码(从1开始)
  4475 + /// - PageSize: 每页数量
  4476 + /// - StoreId: 门店ID(单个门店筛选,与StoreIds二选一)
  4477 + /// - StoreIds: 门店ID列表(支持多门店筛选,与StoreId二选一)
  4478 + ///
4077 4479 /// 返回说明:
4078 4480 /// - list: 会员列表
4079 4481 /// - memberId: 会员ID
... ... @@ -4111,9 +4513,30 @@ namespace NCC.Extend.LqStatistics
4111 4513 {
4112 4514 try
4113 4515 {
  4516 + // 构建门店筛选条件
  4517 + var storeFilterConditions = new List<string>();
  4518 + var parameters = new List<SugarParameter>();
  4519 +
  4520 + if (!string.IsNullOrEmpty(input.StoreId))
  4521 + {
  4522 + storeFilterConditions.Add("kd1.djmd = @StoreId");
  4523 + parameters.Add(new SugarParameter("@StoreId", input.StoreId));
  4524 + }
  4525 + else if (input.StoreIds != null && input.StoreIds.Any())
  4526 + {
  4527 + var storeIdParams = string.Join(",", input.StoreIds.Select((_, i) => $"@StoreId{i}"));
  4528 + storeFilterConditions.Add($"kd1.djmd IN ({storeIdParams})");
  4529 + for (int i = 0; i < input.StoreIds.Count; i++)
  4530 + {
  4531 + parameters.Add(new SugarParameter($"@StoreId{i}", input.StoreIds[i]));
  4532 + }
  4533 + }
  4534 +
  4535 + var storeFilterClause = storeFilterConditions.Any() ? "AND " + string.Join(" AND ", storeFilterConditions) : "";
  4536 +
4114 4537 // 使用SQL一次性查询出符合条件的会员ID
4115 4538 // 逻辑:会员有购买女神卡的记录,且该会员的所有开单记录的所有品项都是女神卡
4116   - var sql = @"
  4539 + var sql = $@"
4117 4540 SELECT DISTINCT kd1.Kdhy AS MemberId
4118 4541 FROM lq_kd_pxmx pxmx1
4119 4542 INNER JOIN lq_kd_kdjlb kd1 ON pxmx1.glkdbh = kd1.F_Id
... ... @@ -4122,6 +4545,7 @@ namespace NCC.Extend.LqStatistics
4122 4545 AND kd1.F_IsEffective = 1
4123 4546 AND kd1.Kdhy IS NOT NULL
4124 4547 AND kd1.Kdhy != ''
  4548 + {storeFilterClause}
4125 4549 AND NOT EXISTS (
4126 4550 -- 排除那些有非女神卡品项的会员
4127 4551 SELECT 1
... ... @@ -4133,7 +4557,7 @@ namespace NCC.Extend.LqStatistics
4133 4557 AND pxmx2.px != '61'
4134 4558 )";
4135 4559  
4136   - var validMemberIds = await _db.Ado.SqlQueryAsync<string>(sql);
  4560 + var validMemberIds = await _db.Ado.SqlQueryAsync<string>(sql, parameters);
4137 4561  
4138 4562 if (!validMemberIds.Any())
4139 4563 {
... ... @@ -4299,6 +4723,11 @@ namespace NCC.Extend.LqStatistics
4299 4723 /// - LeadCustomerId: 线索池客户(拓客编号)
4300 4724 /// - CustomerName: 客户姓名
4301 4725 /// - ExpansionTime: 拓客时间
  4726 + /// - StoreId: 归属门店ID(从客户信息表获取)
  4727 + /// - StoreName: 归属门店名称
  4728 + /// - Phone: 电话(优先使用拓客记录的电话,如果没有则使用客户信息表的电话)
  4729 + /// - ExpansionUserId: 拓客人员ID
  4730 + /// - ExpansionUserName: 拓客人员姓名
4302 4731 /// - HasInvite: 是否邀约(是/否),通过拓客编号关联邀约表
4303 4732 /// - HasAppointment: 是否预约(是/否),只统计通过邀约产生的预约(预约表的F_InviteId关联邀约表)
4304 4733 /// - HasConsume: 是否有消耗(是/否),只统计通过预约产生的耗卡(耗卡表的F_AppointmentId关联预约表)
... ... @@ -4319,6 +4748,11 @@ namespace NCC.Extend.LqStatistics
4319 4748 /// "LeadCustomerId": "751248448816153862",
4320 4749 /// "CustomerName": "王女士",
4321 4750 /// "ExpansionTime": "2025-10-24T03:33:10.000Z",
  4751 + /// "StoreId": "1649328471923847169",
  4752 + /// "StoreName": "绿纤紫荆店",
  4753 + /// "Phone": "13800138000",
  4754 + /// "ExpansionUserId": "123456789",
  4755 + /// "ExpansionUserName": "张三",
4322 4756 /// "HasInvite": "否",
4323 4757 /// "HasAppointment": "否",
4324 4758 /// "HasConsume": "否",
... ... @@ -4392,6 +4826,16 @@ namespace NCC.Extend.LqStatistics
4392 4826 tk.F_Id as LeadCustomerId,
4393 4827 tk.F_CustomerName as CustomerName,
4394 4828 tk.F_ExpansionTime as ExpansionTime,
  4829 + -- 归属门店ID(从客户信息表获取)
  4830 + COALESCE(kh.gsmd, '') as StoreId,
  4831 + -- 归属门店名称
  4832 + COALESCE(md.dm, '') as StoreName,
  4833 + -- 电话(优先使用拓客记录的电话,如果没有则使用客户信息表的电话)
  4834 + COALESCE(NULLIF(tk.F_CustomerPhone, ''), kh.sjh, '') as Phone,
  4835 + -- 拓客人员ID
  4836 + COALESCE(tk.F_ExpansionUserId, '') as ExpansionUserId,
  4837 + -- 拓客人员姓名
  4838 + COALESCE(usr.F_REALNAME, '') as ExpansionUserName,
4395 4839 -- 是否邀约:通过会员ID关联(邀约表的yykh字段存储的是会员ID)
4396 4840 CASE WHEN yaoy_stats.has_invite = 1 THEN '是' ELSE '否' END as HasInvite,
4397 4841 -- 是否预约:通过邀约ID关联(只统计通过邀约产生的预约)
... ... @@ -4413,6 +4857,12 @@ namespace NCC.Extend.LqStatistics
4413 4857 -- 实际开单记录数(不管是否通过预约产生)
4414 4858 COALESCE(kd_actual.count, 0) as ActualBillingCount
4415 4859 FROM lq_tkjlb tk
  4860 + -- 关联客户信息表(获取归属门店和电话)
  4861 + LEFT JOIN lq_khxx kh ON kh.F_Id = tk.F_MemberId
  4862 + -- 关联门店表(获取归属门店名称)
  4863 + LEFT JOIN lq_mdxx md ON md.F_Id = kh.gsmd
  4864 + -- 关联用户表(获取拓客人员姓名)
  4865 + LEFT JOIN BASE_USER usr ON usr.F_Id = tk.F_ExpansionUserId AND usr.F_DeleteMark IS NULL
4416 4866 -- 邀约统计子查询(通过会员ID关联:邀约表的yykh字段存储的是会员ID)
4417 4867 LEFT JOIN (
4418 4868 SELECT
... ... @@ -5020,6 +5470,9 @@ namespace NCC.Extend.LqStatistics
5020 5470 var khWhereClause = khWhereConditions.Any() ? "WHERE " + string.Join(" AND ", khWhereConditions) : "WHERE kh.gsmd IS NOT NULL";
5021 5471 var khWhereClauseNoAlias = khWhereConditionsNoAlias.Any() ? "WHERE " + string.Join(" AND ", khWhereConditionsNoAlias) : "WHERE gsmd IS NOT NULL";
5022 5472  
  5473 + // 判断是否有活动筛选
  5474 + var hasEventFilter = !string.IsNullOrEmpty(input.EventId);
  5475 +
5023 5476 // 使用子查询优化性能,避免复杂的JOIN
5024 5477 var sql = $@"
5025 5478 SELECT
... ... @@ -5037,6 +5490,12 @@ namespace NCC.Extend.LqStatistics
5037 5490 SELECT gsmd as StoreId FROM lq_khxx {khWhereClauseNoAlias}
5038 5491 UNION
5039 5492 SELECT F_StoreId as StoreId FROM lq_tkjlb {whereClauseNoAlias}
  5493 + UNION
  5494 + SELECT F_StoreId as StoreId FROM lq_yaoyjl WHERE F_StoreId IS NOT NULL
  5495 + UNION
  5496 + SELECT djmd as StoreId FROM lq_yyjl WHERE djmd IS NOT NULL
  5497 + UNION
  5498 + SELECT djmd as StoreId FROM lq_kd_kdjlb WHERE djmd IS NOT NULL AND F_IsEffective = 1
5040 5499 ) as all_stores
5041 5500 ) as stores
5042 5501 LEFT JOIN lq_mdxx md ON md.F_Id = stores.StoreId
... ... @@ -5058,51 +5517,92 @@ namespace NCC.Extend.LqStatistics
5058 5517 {(string.IsNullOrEmpty(whereClause) ? "" : whereClause)}
5059 5518 GROUP BY tk.F_StoreId
5060 5519 ) tk_stats ON tk_stats.StoreId = stores.StoreId
5061   - -- 邀约数统计
  5520 + -- 邀约数统计(如果有活动筛选,通过拓客记录关联;否则直接统计)
5062 5521 LEFT JOIN (
  5522 + {(hasEventFilter ? @"
5063 5523 SELECT
5064 5524 tk.F_StoreId as StoreId,
5065 5525 COUNT(DISTINCT yaoy.F_Id) as InviteCount
5066   - FROM lq_tkjlb tk
5067   - INNER JOIN lq_yaoyjl yaoy ON yaoy.tkbh = tk.F_Id
5068   - {(string.IsNullOrEmpty(whereClause) ? "" : whereClause)}
5069   - GROUP BY tk.F_StoreId
  5526 + FROM lq_yaoyjl yaoy
  5527 + INNER JOIN lq_tkjlb tk ON tk.F_Id = yaoy.tkbh
  5528 + WHERE tk.F_StoreId IS NOT NULL
  5529 + AND tk.F_EventId = @EventId
  5530 + GROUP BY tk.F_StoreId" : @"
  5531 + SELECT
  5532 + yaoy.F_StoreId as StoreId,
  5533 + COUNT(DISTINCT yaoy.F_Id) as InviteCount
  5534 + FROM lq_yaoyjl yaoy
  5535 + WHERE yaoy.F_StoreId IS NOT NULL
  5536 + GROUP BY yaoy.F_StoreId")}
5070 5537 ) yaoy_stats ON yaoy_stats.StoreId = stores.StoreId
5071   - -- 预约数统计(通过邀约ID关联
  5538 + -- 预约数统计(如果有活动筛选,通过邀约->拓客链路关联;否则直接统计
5072 5539 LEFT JOIN (
  5540 + {(hasEventFilter ? @"
5073 5541 SELECT
5074 5542 tk.F_StoreId as StoreId,
5075 5543 COUNT(DISTINCT yy.F_Id) as AppointmentCount
5076   - FROM lq_tkjlb tk
5077   - INNER JOIN lq_yaoyjl yaoy ON yaoy.tkbh = tk.F_Id
5078   - INNER JOIN lq_yyjl yy ON yy.F_InviteId = yaoy.F_Id
5079   - {(string.IsNullOrEmpty(whereClause) ? "" : whereClause)}
5080   - GROUP BY tk.F_StoreId
  5544 + FROM lq_yyjl yy
  5545 + INNER JOIN lq_yaoyjl yaoy ON yaoy.F_Id = yy.F_InviteId
  5546 + INNER JOIN lq_tkjlb tk ON tk.F_Id = yaoy.tkbh
  5547 + WHERE tk.F_StoreId IS NOT NULL
  5548 + AND tk.F_EventId = @EventId
  5549 + GROUP BY tk.F_StoreId" : @"
  5550 + SELECT
  5551 + yy.djmd as StoreId,
  5552 + COUNT(DISTINCT yy.F_Id) as AppointmentCount
  5553 + FROM lq_yyjl yy
  5554 + WHERE yy.djmd IS NOT NULL
  5555 + GROUP BY yy.djmd")}
5081 5556 ) yy_stats ON yy_stats.StoreId = stores.StoreId
5082   - -- 耗卡数统计(通过预约ID关联
  5557 + -- 耗卡数统计(如果有活动筛选,通过预约->邀约->拓客链路关联;否则直接统计
5083 5558 LEFT JOIN (
  5559 + {(hasEventFilter ? @"
5084 5560 SELECT
5085 5561 tk.F_StoreId as StoreId,
5086 5562 COUNT(DISTINCT xh.F_Id) as ConsumeCount
5087   - FROM lq_tkjlb tk
5088   - INNER JOIN lq_yaoyjl yaoy ON yaoy.tkbh = tk.F_Id
5089   - INNER JOIN lq_yyjl yy ON yy.F_InviteId = yaoy.F_Id
  5563 + FROM lq_yyjl yy
5090 5564 INNER JOIN lq_xh_hyhk xh ON xh.F_AppointmentId = yy.F_Id AND xh.F_IsEffective = 1
5091   - {(string.IsNullOrEmpty(whereClause) ? "" : whereClause)}
5092   - GROUP BY tk.F_StoreId
  5565 + INNER JOIN lq_yaoyjl yaoy ON yaoy.F_Id = yy.F_InviteId
  5566 + INNER JOIN lq_tkjlb tk ON tk.F_Id = yaoy.tkbh
  5567 + WHERE tk.F_StoreId IS NOT NULL
  5568 + AND tk.F_EventId = @EventId
  5569 + GROUP BY tk.F_StoreId" : @"
  5570 + SELECT
  5571 + yy.djmd as StoreId,
  5572 + COUNT(DISTINCT xh.F_Id) as ConsumeCount
  5573 + FROM lq_yyjl yy
  5574 + INNER JOIN lq_xh_hyhk xh ON xh.F_AppointmentId = yy.F_Id AND xh.F_IsEffective = 1
  5575 + WHERE yy.djmd IS NOT NULL
  5576 + GROUP BY yy.djmd")}
5093 5577 ) xh_stats ON xh_stats.StoreId = stores.StoreId
5094   - -- 开单数和开单金额统计(通过预约ID关联
  5578 + -- 开单数和开单金额统计(如果有活动筛选,通过预约->邀约->拓客链路关联;否则直接统计
5095 5579 LEFT JOIN (
  5580 + {(hasEventFilter ? @"
5096 5581 SELECT
5097 5582 tk.F_StoreId as StoreId,
5098 5583 COUNT(DISTINCT kd.F_Id) as BillingCount,
5099 5584 SUM(kd.zdyj) as BillingAmount
5100   - FROM lq_tkjlb tk
5101   - INNER JOIN lq_yaoyjl yaoy ON yaoy.tkbh = tk.F_Id
5102   - INNER JOIN lq_yyjl yy ON yy.F_InviteId = yaoy.F_Id
5103   - INNER JOIN lq_kd_kdjlb kd ON kd.F_AppointmentId = yy.F_Id AND kd.F_IsEffective = 1
5104   - {(string.IsNullOrEmpty(whereClause) ? "" : whereClause)}
5105   - GROUP BY tk.F_StoreId
  5585 + FROM lq_kd_kdjlb kd
  5586 + INNER JOIN lq_yyjl yy ON yy.F_Id = kd.F_AppointmentId
  5587 + INNER JOIN lq_yaoyjl yaoy ON yaoy.F_Id = yy.F_InviteId
  5588 + INNER JOIN lq_tkjlb tk ON tk.F_Id = yaoy.tkbh
  5589 + WHERE kd.F_IsEffective = 1
  5590 + AND kd.F_AppointmentId IS NOT NULL
  5591 + AND kd.F_AppointmentId != ''
  5592 + AND tk.F_StoreId IS NOT NULL
  5593 + AND tk.F_EventId = @EventId
  5594 + GROUP BY tk.F_StoreId" : @"
  5595 + SELECT
  5596 + kd.djmd as StoreId,
  5597 + COUNT(DISTINCT kd.F_Id) as BillingCount,
  5598 + SUM(kd.zdyj) as BillingAmount
  5599 + FROM lq_kd_kdjlb kd
  5600 + INNER JOIN lq_yyjl yy ON yy.F_Id = kd.F_AppointmentId
  5601 + WHERE kd.F_IsEffective = 1
  5602 + AND kd.F_AppointmentId IS NOT NULL
  5603 + AND kd.F_AppointmentId != ''
  5604 + AND kd.djmd IS NOT NULL
  5605 + GROUP BY kd.djmd")}
5106 5606 ) kd_stats ON kd_stats.StoreId = stores.StoreId
5107 5607 WHERE stores.StoreId IS NOT NULL
5108 5608 ORDER BY stores.StoreId";
... ...
接口调用说明-修改开单信息.md 0 → 100644
  1 +# 修改开单信息接口调用说明
  2 +
  3 +## 接口信息
  4 +
  5 +- **接口地址**: `PUT /api/Extend/lqkdkdjlb/UpdateBillingInfo`
  6 +- **请求方式**: `PUT`
  7 +- **Content-Type**: `application/json`
  8 +- **需要认证**: 是(需要在Header中携带Authorization token)
  9 +
  10 +## 请求参数
  11 +
  12 +| 参数名 | 类型 | 必填 | 说明 |
  13 +|--------|------|------|------|
  14 +| id | string | 是 | 开单记录ID |
  15 +| scwj | List<FileControlsModel> | 否 | 上传文件列表(不传则不更新) |
  16 +| F_FIleUrl | string | 否 | 方案其他文件URL(不传则不更新) |
  17 +| Bz | string | 否 | 备注(不传则不更新) |
  18 +| Jj | string | 否 | 简介(不传则不更新) |
  19 +
  20 +**注意**: 至少需要提供一个要更新的字段(scwj、F_FIleUrl、Bz或Jj)
  21 +
  22 +## 响应格式
  23 +
  24 +### 成功响应 (200)
  25 +
  26 +```json
  27 +{
  28 + "code": 200,
  29 + "msg": "更新成功",
  30 + "data": {
  31 + "id": "开单记录ID",
  32 + "updatedFields": ["Bz", "Jj"]
  33 + }
  34 +}
  35 +```
  36 +
  37 +### 错误响应
  38 +
  39 +```json
  40 +{
  41 + "code": 400,
  42 + "msg": "错误信息",
  43 + "data": null
  44 +}
  45 +```
  46 +
  47 +## 调用示例
  48 +
  49 +### JavaScript/Axios 示例
  50 +
  51 +```javascript
  52 +import axios from 'axios';
  53 +
  54 +// 更新备注和简介
  55 +const updateBillingInfo = async (billingId, remark, description) => {
  56 + try {
  57 + const token = localStorage.getItem('token'); // 从本地存储获取token
  58 +
  59 + const response = await axios.put(
  60 + '/api/Extend/lqkdkdjlb/UpdateBillingInfo',
  61 + {
  62 + id: billingId,
  63 + Bz: remark, // 备注
  64 + Jj: description // 简介
  65 + },
  66 + {
  67 + headers: {
  68 + 'Authorization': token,
  69 + 'Content-Type': 'application/json'
  70 + }
  71 + }
  72 + );
  73 +
  74 + if (response.data.code === 200) {
  75 + console.log('更新成功', response.data.data);
  76 + return response.data;
  77 + } else {
  78 + console.error('更新失败', response.data.msg);
  79 + throw new Error(response.data.msg);
  80 + }
  81 + } catch (error) {
  82 + console.error('请求失败', error);
  83 + throw error;
  84 + }
  85 +};
  86 +
  87 +// 使用示例
  88 +updateBillingInfo('759263766553560325', '这是备注信息', '这是简介信息');
  89 +```
  90 +
  91 +### 只更新备注
  92 +
  93 +```javascript
  94 +const updateRemark = async (billingId, remark) => {
  95 + const response = await axios.put(
  96 + '/api/Extend/lqkdkdjlb/UpdateBillingInfo',
  97 + {
  98 + id: billingId,
  99 + Bz: remark
  100 + },
  101 + {
  102 + headers: {
  103 + 'Authorization': localStorage.getItem('token'),
  104 + 'Content-Type': 'application/json'
  105 + }
  106 + }
  107 + );
  108 + return response.data;
  109 +};
  110 +```
  111 +
  112 +### 只更新简介
  113 +
  114 +```javascript
  115 +const updateDescription = async (billingId, description) => {
  116 + const response = await axios.put(
  117 + '/api/Extend/lqkdkdjlb/UpdateBillingInfo',
  118 + {
  119 + id: billingId,
  120 + Jj: description
  121 + },
  122 + {
  123 + headers: {
  124 + 'Authorization': localStorage.getItem('token'),
  125 + 'Content-Type': 'application/json'
  126 + }
  127 + }
  128 + );
  129 + return response.data;
  130 +};
  131 +```
  132 +
  133 +### 同时更新文件、备注和简介
  134 +
  135 +```javascript
  136 +const updateAll = async (billingId, files, remark, description, fileUrl) => {
  137 + const response = await axios.put(
  138 + '/api/Extend/lqkdkdjlb/UpdateBillingInfo',
  139 + {
  140 + id: billingId,
  141 + scwj: files, // 文件列表
  142 + F_FIleUrl: fileUrl, // 方案其他文件URL
  143 + Bz: remark, // 备注
  144 + Jj: description // 简介
  145 + },
  146 + {
  147 + headers: {
  148 + 'Authorization': localStorage.getItem('token'),
  149 + 'Content-Type': 'application/json'
  150 + }
  151 + }
  152 + );
  153 + return response.data;
  154 +};
  155 +```
  156 +
  157 +### cURL 示例
  158 +
  159 +```bash
  160 +# 更新备注和简介
  161 +curl -X PUT "http://localhost:2011/api/Extend/lqkdkdjlb/UpdateBillingInfo" \
  162 + -H "Authorization: Bearer YOUR_TOKEN" \
  163 + -H "Content-Type: application/json" \
  164 + -d '{
  165 + "id": "759263766553560325",
  166 + "Bz": "这是备注信息",
  167 + "Jj": "这是简介信息"
  168 + }'
  169 +
  170 +# 只更新备注
  171 +curl -X PUT "http://localhost:2011/api/Extend/lqkdkdjlb/UpdateBillingInfo" \
  172 + -H "Authorization: Bearer YOUR_TOKEN" \
  173 + -H "Content-Type: application/json" \
  174 + -d '{
  175 + "id": "759263766553560325",
  176 + "Bz": "这是备注信息"
  177 + }'
  178 +
  179 +# 只更新简介
  180 +curl -X PUT "http://localhost:2011/api/Extend/lqkdkdjlb/UpdateBillingInfo" \
  181 + -H "Authorization: Bearer YOUR_TOKEN" \
  182 + -H "Content-Type: application/json" \
  183 + -d '{
  184 + "id": "759263766553560325",
  185 + "Jj": "这是简介信息"
  186 + }'
  187 +```
  188 +
  189 +## 注意事项
  190 +
  191 +1. **服务重启**: 修改代码后需要重启后端服务才能生效
  192 +2. **Token获取**: 需要先调用登录接口获取token
  193 +3. **字段可选**: 所有字段都是可选的,只需要传入要更新的字段即可
  194 +4. **至少一个字段**: 必须至少提供一个要更新的字段,否则会返回错误
  195 +
  196 +## 错误码说明
  197 +
  198 +- `200`: 更新成功
  199 +- `400`: 参数错误或开单记录不存在
  200 +- `500`: 服务器错误
  201 +
... ...