Commit 75c8f70997d81958e1c7d952916d15ce89f72806

Authored by “wangming”
1 parent 430949ed

feat: 1. 拓客明细页面是否字段使用tag样式显示;2. 6个接口支持多门店筛选;3. 门店日报统计接口添加在职人数字段

- 拓客明细页面:是否邀约、是否预约、是否消耗、是否开卡字段使用el-tag样式,绿色表示是,灰色表示否
- 多门店筛选接口:
  * 会员品项接口:BillingStoreId改为支持BillingStoreIds数组
  * 开单明细接口:StoreId改为支持StoreIds数组
  * 耗卡明细接口:StoreId改为支持StoreIds数组
  * 会员升单统计接口:新增StoreIds多门店筛选
  * 储扣列表接口:StoreId改为支持StoreIds数组(已存在,优化逻辑)
  * 门店整体统计接口:StoreId改为支持StoreIds数组
- 门店日报统计接口:添加EmployeeCount在职人数字段,从lq_mdxx.zzrs获取
antis-ncc-admin/.env.development
1 # 开发 1 # 开发
2 2
3 VUE_CLI_BABEL_TRANSPILE_MODULES = true 3 VUE_CLI_BABEL_TRANSPILE_MODULES = true
4 -VUE_APP_BASE_API = 'https://erp.lvqianmeiye.com' 4 +# VUE_APP_BASE_API = 'https://erp.lvqianmeiye.com'
5 # VUE_APP_BASE_API = 'http://erp_test.lvqianmeiye.com' 5 # VUE_APP_BASE_API = 'http://erp_test.lvqianmeiye.com'
6 -# VUE_APP_BASE_API = 'http://localhost:2011' 6 +VUE_APP_BASE_API = 'http://localhost:2011'
7 # VUE_APP_BASE_API = 'http://localhost:2011' 7 # VUE_APP_BASE_API = 'http://localhost:2011'
8 VUE_APP_IMG_API = '' 8 VUE_APP_IMG_API = ''
9 VUE_APP_BASE_WSS = 'ws://192.168.110.45:2011/websocket' 9 VUE_APP_BASE_WSS = 'ws://192.168.110.45:2011/websocket'
antis-ncc-admin/src/components/kpi-drill/tk-analysis.vue
@@ -43,7 +43,15 @@ @@ -43,7 +43,15 @@
43 <el-table-column v-for="col in columns" :key="col.prop" :prop="col.prop" :label="col.label" 43 <el-table-column v-for="col in columns" :key="col.prop" :prop="col.prop" :label="col.label"
44 :width="col.width" :min-width="col.minWidth"> 44 :width="col.width" :min-width="col.minWidth">
45 <template slot-scope="scope"> 45 <template slot-scope="scope">
46 - <span>{{ scope.row[col.prop] || '—' }}</span> 46 + <!-- 是否类字段使用tag样式 -->
  47 + <el-tag
  48 + v-if="['hasInvite', 'hasAppointment', 'hasConsume', 'hasBilling'].includes(col.prop)"
  49 + :type="scope.row[col.prop] === '是' ? 'success' : 'info'"
  50 + size="small">
  51 + {{ scope.row[col.prop] || '否' }}
  52 + </el-tag>
  53 + <!-- 其他字段使用普通文本 -->
  54 + <span v-else>{{ scope.row[col.prop] || '—' }}</span>
47 </template> 55 </template>
48 </el-table-column> 56 </el-table-column>
49 </el-table> 57 </el-table>
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqDailyReport/StoreDailyStatisticsOutput.cs
@@ -19,6 +19,11 @@ namespace NCC.Extend.Entitys.Dto.LqDailyReport @@ -19,6 +19,11 @@ namespace NCC.Extend.Entitys.Dto.LqDailyReport
19 public string StoreName { get; set; } 19 public string StoreName { get; set; }
20 20
21 /// <summary> 21 /// <summary>
  22 + /// 在职人数
  23 + /// </summary>
  24 + public int EmployeeCount { get; set; }
  25 +
  26 + /// <summary>
22 /// 人头数(去重后的消费会员数) 27 /// 人头数(去重后的消费会员数)
23 /// </summary> 28 /// </summary>
24 public int HeadCount { get; set; } 29 public int HeadCount { get; set; }
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKdKdjlb/BillingItemDetailListQueryInput.cs
  1 +using System.Collections.Generic;
1 using NCC.Common.Filter; 2 using NCC.Common.Filter;
2 3
3 namespace NCC.Extend.Entitys.Dto.LqKdKdjlb 4 namespace NCC.Extend.Entitys.Dto.LqKdKdjlb
@@ -78,9 +79,14 @@ namespace NCC.Extend.Entitys.Dto.LqKdKdjlb @@ -78,9 +79,14 @@ namespace NCC.Extend.Entitys.Dto.LqKdKdjlb
78 public string SourceType { get; set; } 79 public string SourceType { get; set; }
79 80
80 /// <summary> 81 /// <summary>
81 - /// 门店ID 82 + /// 门店ID(单门店,兼容旧版本)
82 /// </summary> 83 /// </summary>
83 public string StoreId { get; set; } 84 public string StoreId { get; set; }
  85 +
  86 + /// <summary>
  87 + /// 门店ID列表(多门店筛选)
  88 + /// </summary>
  89 + public List<string> StoreIds { get; set; }
84 } 90 }
85 } 91 }
86 92
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKdKdjlb/HealthCoachStatisticsQueryInput.cs
1 -using NCC.Common.Filter;  
2 using System; 1 using System;
  2 +using System.Collections.Generic;
  3 +using NCC.Common.Filter;
3 4
4 namespace NCC.Extend.Entitys.Dto.LqKdKdjlb 5 namespace NCC.Extend.Entitys.Dto.LqKdKdjlb
5 { 6 {
@@ -24,11 +25,16 @@ namespace NCC.Extend.Entitys.Dto.LqKdKdjlb @@ -24,11 +25,16 @@ namespace NCC.Extend.Entitys.Dto.LqKdKdjlb
24 public string DepartmentId { get; set; } 25 public string DepartmentId { get; set; }
25 26
26 /// <summary> 27 /// <summary>
27 - /// 门店ID 28 + /// 门店ID(单门店,兼容旧版本)
28 /// </summary> 29 /// </summary>
29 public string StoreId { get; set; } 30 public string StoreId { get; set; }
30 31
31 /// <summary> 32 /// <summary>
  33 + /// 门店ID列表(多门店筛选)
  34 + /// </summary>
  35 + public List<string> StoreIds { get; set; }
  36 +
  37 + /// <summary>
32 /// 健康师姓名 38 /// 健康师姓名
33 /// </summary> 39 /// </summary>
34 public string EmployeeName { get; set; } 40 public string EmployeeName { get; set; }
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKhxx/MemberItemInfoQueryInput.cs
1 using System; 1 using System;
  2 +using System.Collections.Generic;
2 using NCC.Common.Filter; 3 using NCC.Common.Filter;
3 4
4 namespace NCC.Extend.Entitys.Dto.LqKhxx 5 namespace NCC.Extend.Entitys.Dto.LqKhxx
@@ -19,11 +20,16 @@ namespace NCC.Extend.Entitys.Dto.LqKhxx @@ -19,11 +20,16 @@ namespace NCC.Extend.Entitys.Dto.LqKhxx
19 public string BelongStoreId { get; set; } 20 public string BelongStoreId { get; set; }
20 21
21 /// <summary> 22 /// <summary>
22 - /// 开单门店ID 23 + /// 开单门店ID(单门店,兼容旧版本)
23 /// </summary> 24 /// </summary>
24 public string BillingStoreId { get; set; } 25 public string BillingStoreId { get; set; }
25 26
26 /// <summary> 27 /// <summary>
  28 + /// 开单门店ID列表(多门店筛选)
  29 + /// </summary>
  30 + public List<string> BillingStoreIds { get; set; }
  31 +
  32 + /// <summary>
27 /// 会员类型 33 /// 会员类型
28 /// </summary> 34 /// </summary>
29 public int MemberType { get; set; } = -1; 35 public int MemberType { get; set; } = -1;
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqStatistics/MemberUpgradeStatisticsListQueryInput.cs
@@ -37,6 +37,11 @@ namespace NCC.Extend.Entitys.Dto.LqStatistics @@ -37,6 +37,11 @@ namespace NCC.Extend.Entitys.Dto.LqStatistics
37 /// 是否升生美(true-是,false-否,null-不筛选) 37 /// 是否升生美(true-是,false-否,null-不筛选)
38 /// </summary> 38 /// </summary>
39 public bool? HasUpgradeLifeBeauty { get; set; } 39 public bool? HasUpgradeLifeBeauty { get; set; }
  40 +
  41 + /// <summary>
  42 + /// 门店ID列表(多门店筛选)
  43 + /// </summary>
  44 + public List<string> StoreIds { get; set; }
40 } 45 }
41 } 46 }
42 47
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqTkjlb/LqTkjlbListOutput.cs
@@ -117,5 +117,10 @@ namespace NCC.Extend.Entitys.Dto.LqTkjlb @@ -117,5 +117,10 @@ namespace NCC.Extend.Entitys.Dto.LqTkjlb
117 /// 是否开卡(是/否) 117 /// 是否开卡(是/否)
118 /// </summary> 118 /// </summary>
119 public string hasBilling { get; set; } 119 public string hasBilling { get; set; }
  120 +
  121 + /// <summary>
  122 + /// 会员ID(内部使用,不返回给前端)
  123 + /// </summary>
  124 + public string memberId { get; set; }
120 } 125 }
121 } 126 }
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqXhPxmx/ConsumeItemDetailListQueryInput.cs
  1 +using System.Collections.Generic;
1 using NCC.Common.Filter; 2 using NCC.Common.Filter;
2 3
3 namespace NCC.Extend.Entitys.Dto.LqXhPxmx 4 namespace NCC.Extend.Entitys.Dto.LqXhPxmx
@@ -73,9 +74,14 @@ namespace NCC.Extend.Entitys.Dto.LqXhPxmx @@ -73,9 +74,14 @@ namespace NCC.Extend.Entitys.Dto.LqXhPxmx
73 public string SourceType { get; set; } 74 public string SourceType { get; set; }
74 75
75 /// <summary> 76 /// <summary>
76 - /// 门店ID 77 + /// 门店ID(单门店,兼容旧版本)
77 /// </summary> 78 /// </summary>
78 public string StoreId { get; set; } 79 public string StoreId { get; set; }
  80 +
  81 + /// <summary>
  82 + /// 门店ID列表(多门店筛选)
  83 + /// </summary>
  84 + public List<string> StoreIds { get; set; }
79 } 85 }
80 } 86 }
81 87
netcore/src/Modularity/Extend/NCC.Extend/LqDailyReportService.cs
@@ -99,6 +99,7 @@ namespace NCC.Extend @@ -99,6 +99,7 @@ namespace NCC.Extend
99 /// 返回说明: 99 /// 返回说明:
100 /// - StoreId: 门店ID 100 /// - StoreId: 门店ID
101 /// - StoreName: 门店名称 101 /// - StoreName: 门店名称
  102 + /// - EmployeeCount: 在职人数
102 /// - HeadCount: 人头数(去重后的消费会员数) 103 /// - HeadCount: 人头数(去重后的消费会员数)
103 /// - PersonCount: 人次(日度去重客户数,同一天同一客户只算一次) 104 /// - PersonCount: 人次(日度去重客户数,同一天同一客户只算一次)
104 /// - ProjectCount: 项目数(消耗的项目总数,从品项明细表统计) 105 /// - ProjectCount: 项目数(消耗的项目总数,从品项明细表统计)
@@ -134,6 +135,8 @@ namespace NCC.Extend @@ -134,6 +135,8 @@ namespace NCC.Extend
134 SELECT 135 SELECT
135 consume.Md as StoreId, 136 consume.Md as StoreId,
136 MAX(store.dm) as StoreName, 137 MAX(store.dm) as StoreName,
  138 + -- 在职人数(从门店表获取)
  139 + MAX(COALESCE(store.zzrs, 0)) as EmployeeCount,
137 -- 人头数(去重后的消费会员数) 140 -- 人头数(去重后的消费会员数)
138 COALESCE(COUNT(DISTINCT consume.Hy), 0) as HeadCount, 141 COALESCE(COUNT(DISTINCT consume.Hy), 0) as HeadCount,
139 -- 人次(日度去重客户数) 142 -- 人次(日度去重客户数)
@@ -185,6 +188,7 @@ namespace NCC.Extend @@ -185,6 +188,7 @@ namespace NCC.Extend
185 { 188 {
186 StoreId = item.StoreId, 189 StoreId = item.StoreId,
187 StoreName = item.StoreName, 190 StoreName = item.StoreName,
  191 + EmployeeCount = Convert.ToInt32(item.EmployeeCount ?? 0),
188 HeadCount = Convert.ToInt32(item.HeadCount), 192 HeadCount = Convert.ToInt32(item.HeadCount),
189 PersonCount = Convert.ToInt32(item.PersonCount), 193 PersonCount = Convert.ToInt32(item.PersonCount),
190 ProjectCount = Convert.ToInt32(item.ProjectCount), 194 ProjectCount = Convert.ToInt32(item.ProjectCount),
netcore/src/Modularity/Extend/NCC.Extend/LqKdKdjlbService.cs
@@ -3635,10 +3635,25 @@ namespace NCC.Extend.LqKdKdjlb @@ -3635,10 +3635,25 @@ namespace NCC.Extend.LqKdKdjlb
3635 parameters.Add(new SugarParameter("@departmentId", input.DepartmentId)); 3635 parameters.Add(new SugarParameter("@departmentId", input.DepartmentId));
3636 } 3636 }
3637 3637
3638 - if (!string.IsNullOrEmpty(input.StoreId)) 3638 + // 处理多门店筛选:优先使用StoreIds,如果没有则使用StoreId
  3639 + var storeIds = new List<string>();
  3640 + if (input.StoreIds != null && input.StoreIds.Any())
3639 { 3641 {
3640 - conditions.Add("u.F_MDID = @storeId");  
3641 - parameters.Add(new SugarParameter("@storeId", input.StoreId)); 3642 + storeIds = input.StoreIds.Where(x => !string.IsNullOrEmpty(x)).ToList();
  3643 + }
  3644 + else if (!string.IsNullOrEmpty(input.StoreId))
  3645 + {
  3646 + storeIds = new List<string> { input.StoreId };
  3647 + }
  3648 +
  3649 + if (storeIds.Any())
  3650 + {
  3651 + var storeIdParams = string.Join(",", storeIds.Select((_, i) => $"@storeId{i}"));
  3652 + conditions.Add($"u.F_MDID IN ({storeIdParams})");
  3653 + for (int i = 0; i < storeIds.Count; i++)
  3654 + {
  3655 + parameters.Add(new SugarParameter($"@storeId{i}", storeIds[i]));
  3656 + }
3642 } 3657 }
3643 3658
3644 if (!string.IsNullOrEmpty(input.EmployeeName)) 3659 if (!string.IsNullOrEmpty(input.EmployeeName))
@@ -3839,9 +3854,20 @@ namespace NCC.Extend.LqKdKdjlb @@ -3839,9 +3854,20 @@ namespace NCC.Extend.LqKdKdjlb
3839 .WhereIF(!string.IsNullOrEmpty(input.ItemType), pxmx => pxmx.ItemCategory == input.ItemType); 3854 .WhereIF(!string.IsNullOrEmpty(input.ItemType), pxmx => pxmx.ItemCategory == input.ItemType);
3840 3855
3841 // 2. 通过 EXISTS 子查询筛选关联字段(在分页前筛选,确保分页准确) 3856 // 2. 通过 EXISTS 子查询筛选关联字段(在分页前筛选,确保分页准确)
  3857 + // 处理多门店筛选:优先使用StoreIds,如果没有则使用StoreId
  3858 + var filterStoreIds = new List<string>();
  3859 + if (input.StoreIds != null && input.StoreIds.Any())
  3860 + {
  3861 + filterStoreIds = input.StoreIds.Where(x => !string.IsNullOrEmpty(x)).ToList();
  3862 + }
  3863 + else if (!string.IsNullOrEmpty(input.StoreId))
  3864 + {
  3865 + filterStoreIds = new List<string> { input.StoreId };
  3866 + }
  3867 +
3842 baseQuery = baseQuery.WhereIF(!string.IsNullOrEmpty(input.MemberName), pxmx => SqlFunc.Subqueryable<LqKhxxEntity>().Where(x => x.Id == pxmx.MemberId && x.Khmc != null && x.Khmc.Contains(input.MemberName)).Any()) 3868 baseQuery = baseQuery.WhereIF(!string.IsNullOrEmpty(input.MemberName), pxmx => SqlFunc.Subqueryable<LqKhxxEntity>().Where(x => x.Id == pxmx.MemberId && x.Khmc != null && x.Khmc.Contains(input.MemberName)).Any())
3843 .WhereIF(!string.IsNullOrEmpty(input.MemberPhone), pxmx => SqlFunc.Subqueryable<LqKhxxEntity>().Where(x => x.Id == pxmx.MemberId && x.Sjh == input.MemberPhone).Any()) 3869 .WhereIF(!string.IsNullOrEmpty(input.MemberPhone), pxmx => SqlFunc.Subqueryable<LqKhxxEntity>().Where(x => x.Id == pxmx.MemberId && x.Sjh == input.MemberPhone).Any())
3844 - .WhereIF(!string.IsNullOrEmpty(input.StoreId), pxmx => SqlFunc.Subqueryable<LqKdKdjlbEntity>().Where(x => x.Id == pxmx.Glkdbh && x.Djmd == input.StoreId).Any()); 3870 + .WhereIF(filterStoreIds.Any(), pxmx => SqlFunc.Subqueryable<LqKdKdjlbEntity>().Where(x => x.Id == pxmx.Glkdbh && filterStoreIds.Contains(x.Djmd)).Any());
3845 3871
3846 // 3. 先分页查询主表数据(查询实体类,提高性能) 3872 // 3. 先分页查询主表数据(查询实体类,提高性能)
3847 var pagedData = await baseQuery.OrderBy(sidx + " " + sort).ToPagedListAsync(input.currentPage, input.pageSize); 3873 var pagedData = await baseQuery.OrderBy(sidx + " " + sort).ToPagedListAsync(input.currentPage, input.pageSize);
netcore/src/Modularity/Extend/NCC.Extend/LqKhxxService.cs
@@ -1145,7 +1145,18 @@ namespace NCC.Extend.LqKhxx @@ -1145,7 +1145,18 @@ namespace NCC.Extend.LqKhxx
1145 } 1145 }
1146 1146
1147 // 如果指定了开单门店、开单人、品项ID等条件,需要通过开单表进行筛选 1147 // 如果指定了开单门店、开单人、品项ID等条件,需要通过开单表进行筛选
1148 - if (!string.IsNullOrEmpty(input.BillingStoreId) || 1148 + // 处理多门店筛选:优先使用BillingStoreIds,如果没有则使用BillingStoreId
  1149 + var billingStoreIds = new List<string>();
  1150 + if (input.BillingStoreIds != null && input.BillingStoreIds.Any())
  1151 + {
  1152 + billingStoreIds = input.BillingStoreIds.Where(x => !string.IsNullOrEmpty(x)).ToList();
  1153 + }
  1154 + else if (!string.IsNullOrEmpty(input.BillingStoreId))
  1155 + {
  1156 + billingStoreIds = new List<string> { input.BillingStoreId };
  1157 + }
  1158 +
  1159 + if (billingStoreIds.Any() ||
1149 !string.IsNullOrEmpty(input.BillingUserId) || 1160 !string.IsNullOrEmpty(input.BillingUserId) ||
1150 !string.IsNullOrEmpty(input.PurchaseItemId) || 1161 !string.IsNullOrEmpty(input.PurchaseItemId) ||
1151 !string.IsNullOrEmpty(input.GiftItemId) || 1162 !string.IsNullOrEmpty(input.GiftItemId) ||
@@ -1155,9 +1166,9 @@ namespace NCC.Extend.LqKhxx @@ -1155,9 +1166,9 @@ namespace NCC.Extend.LqKhxx
1155 var billingQuery = _db.Queryable<LqKdKdjlbEntity>() 1166 var billingQuery = _db.Queryable<LqKdKdjlbEntity>()
1156 .Where(x => x.IsEffective == StatusEnum.有效.GetHashCode()); 1167 .Where(x => x.IsEffective == StatusEnum.有效.GetHashCode());
1157 1168
1158 - if (!string.IsNullOrEmpty(input.BillingStoreId)) 1169 + if (billingStoreIds.Any())
1159 { 1170 {
1160 - billingQuery = billingQuery.Where(x => x.Djmd == input.BillingStoreId); 1171 + billingQuery = billingQuery.Where(x => billingStoreIds.Contains(x.Djmd));
1161 } 1172 }
1162 1173
1163 if (!string.IsNullOrEmpty(input.BillingUserId)) 1174 if (!string.IsNullOrEmpty(input.BillingUserId))
netcore/src/Modularity/Extend/NCC.Extend/LqReportService.cs
@@ -503,7 +503,7 @@ namespace NCC.Extend @@ -503,7 +503,7 @@ namespace NCC.Extend
503 /// - TopAmountMemberName: 开单金额最高会员姓名 503 /// - TopAmountMemberName: 开单金额最高会员姓名
504 /// - TopAmountValue: 该会员整月实付金额 504 /// - TopAmountValue: 该会员整月实付金额
505 /// - TopTimesMemberName: 开单次数最多会员姓名 505 /// - TopTimesMemberName: 开单次数最多会员姓名
506 - /// - TopTimesCount: 该会员整月开单次数 506 + /// - TopTimesCount: 该会员整月开单次数(按天去重,同一天多次开单只算一次)
507 /// - DebtTotal: 整月欠款总额(sfyj+ck金额以外的qk累加) 507 /// - DebtTotal: 整月欠款总额(sfyj+ck金额以外的qk累加)
508 /// - TopDebtMemberName: 欠款最多会员姓名 508 /// - TopDebtMemberName: 欠款最多会员姓名
509 /// - TopDebtValue: 欠款最多会员的欠款金额 509 /// - TopDebtValue: 欠款最多会员的欠款金额
@@ -598,15 +598,16 @@ namespace NCC.Extend @@ -598,15 +598,16 @@ namespace NCC.Extend
598 .OrderBy(x => x.Date) 598 .OrderBy(x => x.Date)
599 .ToList(); 599 .ToList();
600 600
601 - // 3. 会员极值:按会员聚合金额和次数 601 + // 3. 会员极值:按会员聚合金额和次数(开单次数按天去重)
602 var memberAgg = rawList 602 var memberAgg = rawList
603 - .Where(x => !string.IsNullOrEmpty(x.MemberId))  
604 - .GroupBy(x => x.MemberId) 603 + .Where(x => !string.IsNullOrEmpty(x.MemberId) && x.BillingDate.HasValue)
  604 + .GroupBy(x => new { x.MemberId, Date = x.BillingDate.Value.Date })
  605 + .GroupBy(g => g.Key.MemberId)
605 .Select(g => new 606 .Select(g => new
606 { 607 {
607 MemberId = g.Key, 608 MemberId = g.Key,
608 - Amount = g.Sum(x => x.Amount),  
609 - Times = g.Count() 609 + Amount = g.SelectMany(dayGroup => dayGroup).Sum(x => x.Amount),
  610 + Times = g.Count() // 按天去重后的开单天数
610 }) 611 })
611 .ToList(); 612 .ToList();
612 613
netcore/src/Modularity/Extend/NCC.Extend/LqStatisticsService.cs
@@ -4898,6 +4898,7 @@ namespace NCC.Extend.LqStatistics @@ -4898,6 +4898,7 @@ namespace NCC.Extend.LqStatistics
4898 ) xh_stats ON xh_stats.member_id = tk.F_MemberId 4898 ) xh_stats ON xh_stats.member_id = tk.F_MemberId
4899 -- 开单统计子查询(只统计通过预约产生的开单,包含金额和品项) 4899 -- 开单统计子查询(只统计通过预约产生的开单,包含金额和品项)
4900 -- 通过会员ID关联:线索池 -> 邀约 -> 预约 -> 开单(通过预约ID,且会员ID匹配) 4900 -- 通过会员ID关联:线索池 -> 邀约 -> 预约 -> 开单(通过预约ID,且会员ID匹配)
  4901 + -- 注意:剔除品项ID是61且金额是0的记录
4901 LEFT JOIN ( 4902 LEFT JOIN (
4902 SELECT 4903 SELECT
4903 tk_inner.F_MemberId as member_id, 4904 tk_inner.F_MemberId as member_id,
@@ -4908,7 +4909,14 @@ namespace NCC.Extend.LqStatistics @@ -4908,7 +4909,14 @@ namespace NCC.Extend.LqStatistics
4908 INNER JOIN lq_yaoyjl yaoy ON yaoy.yykh = tk_inner.F_MemberId 4909 INNER JOIN lq_yaoyjl yaoy ON yaoy.yykh = tk_inner.F_MemberId
4909 INNER JOIN lq_yyjl yy ON yy.F_InviteId = yaoy.F_Id AND yy.gk = tk_inner.F_MemberId 4910 INNER JOIN lq_yyjl yy ON yy.F_InviteId = yaoy.F_Id AND yy.gk = tk_inner.F_MemberId
4910 INNER JOIN lq_kd_kdjlb kd ON kd.F_AppointmentId = yy.F_Id AND kd.kdhy = tk_inner.F_MemberId AND kd.F_IsEffective = 1 4911 INNER JOIN lq_kd_kdjlb kd ON kd.F_AppointmentId = yy.F_Id AND kd.kdhy = tk_inner.F_MemberId AND kd.F_IsEffective = 1
  4912 + INNER JOIN (
  4913 + SELECT DISTINCT glkdbh
  4914 + FROM lq_kd_pxmx
  4915 + WHERE F_IsEffective = 1
  4916 + AND NOT (px = '61' AND (COALESCE(F_ActualPrice, 0) = 0 AND COALESCE(F_TotalPrice, 0) = 0))
  4917 + ) valid_px ON valid_px.glkdbh = kd.F_Id
4911 LEFT JOIN lq_kd_pxmx kdpx ON kdpx.glkdbh = kd.F_Id AND kdpx.F_IsEffective = 1 4918 LEFT JOIN lq_kd_pxmx kdpx ON kdpx.glkdbh = kd.F_Id AND kdpx.F_IsEffective = 1
  4919 + AND NOT (kdpx.px = '61' AND (COALESCE(kdpx.F_ActualPrice, 0) = 0 AND COALESCE(kdpx.F_TotalPrice, 0) = 0))
4912 GROUP BY tk_inner.F_MemberId 4920 GROUP BY tk_inner.F_MemberId
4913 ) kd_stats ON kd_stats.member_id = tk.F_MemberId 4921 ) kd_stats ON kd_stats.member_id = tk.F_MemberId
4914 -- 实际预约记录数统计(不管是否通过邀约产生) 4922 -- 实际预约记录数统计(不管是否通过邀约产生)
@@ -5229,6 +5237,7 @@ namespace NCC.Extend.LqStatistics @@ -5229,6 +5237,7 @@ namespace NCC.Extend.LqStatistics
5229 ) xh_stats ON xh_stats.member_id = tk.F_MemberId 5237 ) xh_stats ON xh_stats.member_id = tk.F_MemberId
5230 -- 开单统计子查询(只统计通过预约产生的开单,包含金额和品项) 5238 -- 开单统计子查询(只统计通过预约产生的开单,包含金额和品项)
5231 -- 通过会员ID关联:线索池 -> 邀约 -> 预约 -> 开单(通过预约ID,且会员ID匹配) 5239 -- 通过会员ID关联:线索池 -> 邀约 -> 预约 -> 开单(通过预约ID,且会员ID匹配)
  5240 + -- 注意:剔除品项ID是61且金额是0的记录
5232 LEFT JOIN ( 5241 LEFT JOIN (
5233 SELECT 5242 SELECT
5234 tk_inner.F_MemberId as member_id, 5243 tk_inner.F_MemberId as member_id,
@@ -5239,7 +5248,14 @@ namespace NCC.Extend.LqStatistics @@ -5239,7 +5248,14 @@ namespace NCC.Extend.LqStatistics
5239 INNER JOIN lq_yaoyjl yaoy ON yaoy.yykh = tk_inner.F_MemberId 5248 INNER JOIN lq_yaoyjl yaoy ON yaoy.yykh = tk_inner.F_MemberId
5240 INNER JOIN lq_yyjl yy ON yy.F_InviteId = yaoy.F_Id AND yy.gk = tk_inner.F_MemberId 5249 INNER JOIN lq_yyjl yy ON yy.F_InviteId = yaoy.F_Id AND yy.gk = tk_inner.F_MemberId
5241 INNER JOIN lq_kd_kdjlb kd ON kd.F_AppointmentId = yy.F_Id AND kd.kdhy = tk_inner.F_MemberId AND kd.F_IsEffective = 1 5250 INNER JOIN lq_kd_kdjlb kd ON kd.F_AppointmentId = yy.F_Id AND kd.kdhy = tk_inner.F_MemberId AND kd.F_IsEffective = 1
  5251 + INNER JOIN (
  5252 + SELECT DISTINCT glkdbh
  5253 + FROM lq_kd_pxmx
  5254 + WHERE F_IsEffective = 1
  5255 + AND NOT (px = '61' AND (COALESCE(F_ActualPrice, 0) = 0 AND COALESCE(F_TotalPrice, 0) = 0))
  5256 + ) valid_px ON valid_px.glkdbh = kd.F_Id
5242 LEFT JOIN lq_kd_pxmx kdpx ON kdpx.glkdbh = kd.F_Id AND kdpx.F_IsEffective = 1 5257 LEFT JOIN lq_kd_pxmx kdpx ON kdpx.glkdbh = kd.F_Id AND kdpx.F_IsEffective = 1
  5258 + AND NOT (kdpx.px = '61' AND (COALESCE(kdpx.F_ActualPrice, 0) = 0 AND COALESCE(kdpx.F_TotalPrice, 0) = 0))
5243 GROUP BY tk_inner.F_MemberId 5259 GROUP BY tk_inner.F_MemberId
5244 ) kd_stats ON kd_stats.member_id = tk.F_MemberId 5260 ) kd_stats ON kd_stats.member_id = tk.F_MemberId
5245 -- 实际预约记录数统计(不管是否通过邀约产生) 5261 -- 实际预约记录数统计(不管是否通过邀约产生)
@@ -5703,6 +5719,17 @@ namespace NCC.Extend.LqStatistics @@ -5703,6 +5719,17 @@ namespace NCC.Extend.LqStatistics
5703 } 5719 }
5704 } 5720 }
5705 5721
  5722 + // 添加多门店筛选
  5723 + if (input.StoreIds != null && input.StoreIds.Any())
  5724 + {
  5725 + var storeIdParams = string.Join(",", input.StoreIds.Select((_, i) => $"@StoreId{i}"));
  5726 + whereConditions.Add($"kd.Djmd IN ({storeIdParams})");
  5727 + for (int i = 0; i < input.StoreIds.Count; i++)
  5728 + {
  5729 + parameters.Add(new SugarParameter($"@StoreId{i}", input.StoreIds[i]));
  5730 + }
  5731 + }
  5732 +
5706 var whereClause = whereConditions.Any() ? "AND " + string.Join(" AND ", whereConditions) : ""; 5733 var whereClause = whereConditions.Any() ? "AND " + string.Join(" AND ", whereConditions) : "";
5707 5734
5708 // 构建HAVING条件(用于筛选升单条件) 5735 // 构建HAVING条件(用于筛选升单条件)
netcore/src/Modularity/Extend/NCC.Extend/LqTkjlbService.cs
@@ -147,12 +147,70 @@ namespace NCC.Extend.LqTkjlb @@ -147,12 +147,70 @@ namespace NCC.Extend.LqTkjlb
147 hasAppointment = SqlFunc.Subqueryable<LqYyjlEntity>().Where(y => y.Gk == it.MemberId).Any() ? "是" : "否", 147 hasAppointment = SqlFunc.Subqueryable<LqYyjlEntity>().Where(y => y.Gk == it.MemberId).Any() ? "是" : "否",
148 // 是否消耗:通过会员ID关联耗卡表(hy字段存储的是会员ID) 148 // 是否消耗:通过会员ID关联耗卡表(hy字段存储的是会员ID)
149 hasConsume = SqlFunc.Subqueryable<LqXhHyhkEntity>().Where(x => x.Hy == it.MemberId).Any() ? "是" : "否", 149 hasConsume = SqlFunc.Subqueryable<LqXhHyhkEntity>().Where(x => x.Hy == it.MemberId).Any() ? "是" : "否",
150 - // 是否开卡:通过会员ID关联开单表(kdhy字段存储的是会员ID)  
151 - hasBilling = SqlFunc.Subqueryable<LqKdKdjlbEntity>().Where(k => k.Kdhy == it.MemberId).Any() ? "是" : "否" 150 + // 是否开卡:通过会员ID关联开单表(kdhy字段存储的是会员ID),剔除品项ID是61(女神卡)且金额是0的记录
  151 + // 先设置为否,后面在内存中处理
  152 + hasBilling = "否"
152 }) 153 })
153 .MergeTable() 154 .MergeTable()
154 .OrderBy(sidx + " " + input.sort) 155 .OrderBy(sidx + " " + input.sort)
155 .ToPagedListAsync(input.currentPage, input.pageSize); 156 .ToPagedListAsync(input.currentPage, input.pageSize);
  157 +
  158 + // 批量查询有有效开单的会员ID(剔除品项ID是61且金额是0的记录)
  159 + // 先查询所有拓客记录的会员ID
  160 + var tkIds = data.list?.Select(x => x.id).Where(x => !string.IsNullOrEmpty(x)).Distinct().ToList() ?? new List<string>();
  161 + var memberIdDict = new Dictionary<string, string>(); // tkId -> MemberId
  162 +
  163 + if (tkIds.Any())
  164 + {
  165 + // 查询拓客记录的会员ID
  166 + var tkMembers = await _db.Queryable<LqTkjlbEntity>()
  167 + .Where(x => tkIds.Contains(x.Id))
  168 + .Select(x => new { x.Id, x.MemberId })
  169 + .ToListAsync();
  170 + memberIdDict = tkMembers?.Where(x => !string.IsNullOrEmpty(x.MemberId))
  171 + .ToDictionary(x => x.Id, x => x.MemberId) ?? new Dictionary<string, string>();
  172 + }
  173 +
  174 + var memberIds = memberIdDict.Values.Distinct().ToList();
  175 + var hasBillingMemberIds = new HashSet<string>();
  176 +
  177 + if (memberIds.Any())
  178 + {
  179 + var sql = $@"
  180 + SELECT DISTINCT kd.kdhy as MemberId
  181 + FROM lq_kd_kdjlb kd
  182 + INNER JOIN (
  183 + SELECT DISTINCT glkdbh
  184 + FROM lq_kd_pxmx
  185 + WHERE F_IsEffective = 1
  186 + AND NOT (px = '61' AND (COALESCE(F_ActualPrice, 0) = 0 AND COALESCE(F_TotalPrice, 0) = 0))
  187 + ) valid_px ON valid_px.glkdbh = kd.F_Id
  188 + WHERE kd.F_IsEffective = 1
  189 + AND kd.kdhy IN ('{string.Join("','", memberIds)}')";
  190 + var billingMembers = await _db.Ado.SqlQueryAsync<dynamic>(sql);
  191 + if (billingMembers != null)
  192 + {
  193 + var memberIdList = billingMembers.Select(x => x.MemberId?.ToString()).Where(x => !string.IsNullOrEmpty(x)).Cast<string>().ToList();
  194 + hasBillingMemberIds = new HashSet<string>(memberIdList);
  195 + }
  196 + }
  197 +
  198 + // 更新hasBilling字段
  199 + if (data.list != null)
  200 + {
  201 + foreach (var item in data.list)
  202 + {
  203 + if (memberIdDict.TryGetValue(item.id, out var memberId) && !string.IsNullOrEmpty(memberId))
  204 + {
  205 + item.hasBilling = hasBillingMemberIds.Contains(memberId) ? "是" : "否";
  206 + }
  207 + else
  208 + {
  209 + item.hasBilling = "否";
  210 + }
  211 + }
  212 + }
  213 +
156 return PageResult<LqTkjlbListOutput>.SqlSugarPageResult(data); 214 return PageResult<LqTkjlbListOutput>.SqlSugarPageResult(data);
157 } 215 }
158 #endregion 216 #endregion
netcore/src/Modularity/Extend/NCC.Extend/LqXhHyhkService.cs
@@ -2036,9 +2036,20 @@ namespace NCC.Extend.LqXhHyhk @@ -2036,9 +2036,20 @@ namespace NCC.Extend.LqXhHyhk
2036 .WhereIF(!string.IsNullOrEmpty(input.ItemType), pxmx => pxmx.ItemCategory == input.ItemType); 2036 .WhereIF(!string.IsNullOrEmpty(input.ItemType), pxmx => pxmx.ItemCategory == input.ItemType);
2037 2037
2038 // 2. 通过 EXISTS 子查询筛选关联字段(在分页前筛选,确保分页准确) 2038 // 2. 通过 EXISTS 子查询筛选关联字段(在分页前筛选,确保分页准确)
  2039 + // 处理多门店筛选:优先使用StoreIds,如果没有则使用StoreId
  2040 + var filterStoreIds = new List<string>();
  2041 + if (input.StoreIds != null && input.StoreIds.Any())
  2042 + {
  2043 + filterStoreIds = input.StoreIds.Where(x => !string.IsNullOrEmpty(x)).ToList();
  2044 + }
  2045 + else if (!string.IsNullOrEmpty(input.StoreId))
  2046 + {
  2047 + filterStoreIds = new List<string> { input.StoreId };
  2048 + }
  2049 +
2039 baseQuery = baseQuery.WhereIF(!string.IsNullOrEmpty(input.MemberName), pxmx => SqlFunc.Subqueryable<LqKhxxEntity>().Where(x => x.Id == pxmx.MemberId && x.Khmc != null && x.Khmc.Contains(input.MemberName)).Any()) 2050 baseQuery = baseQuery.WhereIF(!string.IsNullOrEmpty(input.MemberName), pxmx => SqlFunc.Subqueryable<LqKhxxEntity>().Where(x => x.Id == pxmx.MemberId && x.Khmc != null && x.Khmc.Contains(input.MemberName)).Any())
2040 .WhereIF(!string.IsNullOrEmpty(input.MemberPhone), pxmx => SqlFunc.Subqueryable<LqKhxxEntity>().Where(x => x.Id == pxmx.MemberId && x.Sjh == input.MemberPhone).Any()) 2051 .WhereIF(!string.IsNullOrEmpty(input.MemberPhone), pxmx => SqlFunc.Subqueryable<LqKhxxEntity>().Where(x => x.Id == pxmx.MemberId && x.Sjh == input.MemberPhone).Any())
2041 - .WhereIF(!string.IsNullOrEmpty(input.StoreId), pxmx => SqlFunc.Subqueryable<LqXhHyhkEntity>().Where(x => x.Id == pxmx.ConsumeInfoId && x.Md == input.StoreId).Any()); 2052 + .WhereIF(filterStoreIds.Any(), pxmx => SqlFunc.Subqueryable<LqXhHyhkEntity>().Where(x => x.Id == pxmx.ConsumeInfoId && filterStoreIds.Contains(x.Md)).Any());
2042 2053
2043 // 3. 先分页查询主表数据(查询实体类,提高性能) 2054 // 3. 先分页查询主表数据(查询实体类,提高性能)
2044 var pagedData = await baseQuery.OrderBy(sidx + " " + sort).ToPagedListAsync(input.currentPage, input.pageSize); 2055 var pagedData = await baseQuery.OrderBy(sidx + " " + sort).ToPagedListAsync(input.currentPage, input.pageSize);