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 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 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 7 # VUE_APP_BASE_API = 'http://localhost:2011'
8 8 VUE_APP_IMG_API = ''
9 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 43 <el-table-column v-for="col in columns" :key="col.prop" :prop="col.prop" :label="col.label"
44 44 :width="col.width" :min-width="col.minWidth">
45 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 55 </template>
48 56 </el-table-column>
49 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 19 public string StoreName { get; set; }
20 20  
21 21 /// <summary>
  22 + /// 在职人数
  23 + /// </summary>
  24 + public int EmployeeCount { get; set; }
  25 +
  26 + /// <summary>
22 27 /// 人头数(去重后的消费会员数)
23 28 /// </summary>
24 29 public int HeadCount { get; set; }
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKdKdjlb/BillingItemDetailListQueryInput.cs
  1 +using System.Collections.Generic;
1 2 using NCC.Common.Filter;
2 3  
3 4 namespace NCC.Extend.Entitys.Dto.LqKdKdjlb
... ... @@ -78,9 +79,14 @@ namespace NCC.Extend.Entitys.Dto.LqKdKdjlb
78 79 public string SourceType { get; set; }
79 80  
80 81 /// <summary>
81   - /// 门店ID
  82 + /// 门店ID(单门店,兼容旧版本)
82 83 /// </summary>
83 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 1 using System;
  2 +using System.Collections.Generic;
  3 +using NCC.Common.Filter;
3 4  
4 5 namespace NCC.Extend.Entitys.Dto.LqKdKdjlb
5 6 {
... ... @@ -24,11 +25,16 @@ namespace NCC.Extend.Entitys.Dto.LqKdKdjlb
24 25 public string DepartmentId { get; set; }
25 26  
26 27 /// <summary>
27   - /// 门店ID
  28 + /// 门店ID(单门店,兼容旧版本)
28 29 /// </summary>
29 30 public string StoreId { get; set; }
30 31  
31 32 /// <summary>
  33 + /// 门店ID列表(多门店筛选)
  34 + /// </summary>
  35 + public List<string> StoreIds { get; set; }
  36 +
  37 + /// <summary>
32 38 /// 健康师姓名
33 39 /// </summary>
34 40 public string EmployeeName { get; set; }
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKhxx/MemberItemInfoQueryInput.cs
1 1 using System;
  2 +using System.Collections.Generic;
2 3 using NCC.Common.Filter;
3 4  
4 5 namespace NCC.Extend.Entitys.Dto.LqKhxx
... ... @@ -19,11 +20,16 @@ namespace NCC.Extend.Entitys.Dto.LqKhxx
19 20 public string BelongStoreId { get; set; }
20 21  
21 22 /// <summary>
22   - /// 开单门店ID
  23 + /// 开单门店ID(单门店,兼容旧版本)
23 24 /// </summary>
24 25 public string BillingStoreId { get; set; }
25 26  
26 27 /// <summary>
  28 + /// 开单门店ID列表(多门店筛选)
  29 + /// </summary>
  30 + public List<string> BillingStoreIds { get; set; }
  31 +
  32 + /// <summary>
27 33 /// 会员类型
28 34 /// </summary>
29 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 37 /// 是否升生美(true-是,false-否,null-不筛选)
38 38 /// </summary>
39 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 117 /// 是否开卡(是/否)
118 118 /// </summary>
119 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 2 using NCC.Common.Filter;
2 3  
3 4 namespace NCC.Extend.Entitys.Dto.LqXhPxmx
... ... @@ -73,9 +74,14 @@ namespace NCC.Extend.Entitys.Dto.LqXhPxmx
73 74 public string SourceType { get; set; }
74 75  
75 76 /// <summary>
76   - /// 门店ID
  77 + /// 门店ID(单门店,兼容旧版本)
77 78 /// </summary>
78 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 99 /// 返回说明:
100 100 /// - StoreId: 门店ID
101 101 /// - StoreName: 门店名称
  102 + /// - EmployeeCount: 在职人数
102 103 /// - HeadCount: 人头数(去重后的消费会员数)
103 104 /// - PersonCount: 人次(日度去重客户数,同一天同一客户只算一次)
104 105 /// - ProjectCount: 项目数(消耗的项目总数,从品项明细表统计)
... ... @@ -134,6 +135,8 @@ namespace NCC.Extend
134 135 SELECT
135 136 consume.Md as StoreId,
136 137 MAX(store.dm) as StoreName,
  138 + -- 在职人数(从门店表获取)
  139 + MAX(COALESCE(store.zzrs, 0)) as EmployeeCount,
137 140 -- 人头数(去重后的消费会员数)
138 141 COALESCE(COUNT(DISTINCT consume.Hy), 0) as HeadCount,
139 142 -- 人次(日度去重客户数)
... ... @@ -185,6 +188,7 @@ namespace NCC.Extend
185 188 {
186 189 StoreId = item.StoreId,
187 190 StoreName = item.StoreName,
  191 + EmployeeCount = Convert.ToInt32(item.EmployeeCount ?? 0),
188 192 HeadCount = Convert.ToInt32(item.HeadCount),
189 193 PersonCount = Convert.ToInt32(item.PersonCount),
190 194 ProjectCount = Convert.ToInt32(item.ProjectCount),
... ...
netcore/src/Modularity/Extend/NCC.Extend/LqKdKdjlbService.cs
... ... @@ -3635,10 +3635,25 @@ namespace NCC.Extend.LqKdKdjlb
3635 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 3659 if (!string.IsNullOrEmpty(input.EmployeeName))
... ... @@ -3839,9 +3854,20 @@ namespace NCC.Extend.LqKdKdjlb
3839 3854 .WhereIF(!string.IsNullOrEmpty(input.ItemType), pxmx => pxmx.ItemCategory == input.ItemType);
3840 3855  
3841 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 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 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 3872 // 3. 先分页查询主表数据(查询实体类,提高性能)
3847 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 1145 }
1146 1146  
1147 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 1160 !string.IsNullOrEmpty(input.BillingUserId) ||
1150 1161 !string.IsNullOrEmpty(input.PurchaseItemId) ||
1151 1162 !string.IsNullOrEmpty(input.GiftItemId) ||
... ... @@ -1155,9 +1166,9 @@ namespace NCC.Extend.LqKhxx
1155 1166 var billingQuery = _db.Queryable<LqKdKdjlbEntity>()
1156 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 1174 if (!string.IsNullOrEmpty(input.BillingUserId))
... ...
netcore/src/Modularity/Extend/NCC.Extend/LqReportService.cs
... ... @@ -503,7 +503,7 @@ namespace NCC.Extend
503 503 /// - TopAmountMemberName: 开单金额最高会员姓名
504 504 /// - TopAmountValue: 该会员整月实付金额
505 505 /// - TopTimesMemberName: 开单次数最多会员姓名
506   - /// - TopTimesCount: 该会员整月开单次数
  506 + /// - TopTimesCount: 该会员整月开单次数(按天去重,同一天多次开单只算一次)
507 507 /// - DebtTotal: 整月欠款总额(sfyj+ck金额以外的qk累加)
508 508 /// - TopDebtMemberName: 欠款最多会员姓名
509 509 /// - TopDebtValue: 欠款最多会员的欠款金额
... ... @@ -598,15 +598,16 @@ namespace NCC.Extend
598 598 .OrderBy(x => x.Date)
599 599 .ToList();
600 600  
601   - // 3. 会员极值:按会员聚合金额和次数
  601 + // 3. 会员极值:按会员聚合金额和次数(开单次数按天去重)
602 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 606 .Select(g => new
606 607 {
607 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 612 .ToList();
612 613  
... ...
netcore/src/Modularity/Extend/NCC.Extend/LqStatisticsService.cs
... ... @@ -4898,6 +4898,7 @@ namespace NCC.Extend.LqStatistics
4898 4898 ) xh_stats ON xh_stats.member_id = tk.F_MemberId
4899 4899 -- 开单统计子查询(只统计通过预约产生的开单,包含金额和品项)
4900 4900 -- 通过会员ID关联:线索池 -> 邀约 -> 预约 -> 开单(通过预约ID,且会员ID匹配)
  4901 + -- 注意:剔除品项ID是61且金额是0的记录
4901 4902 LEFT JOIN (
4902 4903 SELECT
4903 4904 tk_inner.F_MemberId as member_id,
... ... @@ -4908,7 +4909,14 @@ namespace NCC.Extend.LqStatistics
4908 4909 INNER JOIN lq_yaoyjl yaoy ON yaoy.yykh = tk_inner.F_MemberId
4909 4910 INNER JOIN lq_yyjl yy ON yy.F_InviteId = yaoy.F_Id AND yy.gk = tk_inner.F_MemberId
4910 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 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 4920 GROUP BY tk_inner.F_MemberId
4913 4921 ) kd_stats ON kd_stats.member_id = tk.F_MemberId
4914 4922 -- 实际预约记录数统计(不管是否通过邀约产生)
... ... @@ -5229,6 +5237,7 @@ namespace NCC.Extend.LqStatistics
5229 5237 ) xh_stats ON xh_stats.member_id = tk.F_MemberId
5230 5238 -- 开单统计子查询(只统计通过预约产生的开单,包含金额和品项)
5231 5239 -- 通过会员ID关联:线索池 -> 邀约 -> 预约 -> 开单(通过预约ID,且会员ID匹配)
  5240 + -- 注意:剔除品项ID是61且金额是0的记录
5232 5241 LEFT JOIN (
5233 5242 SELECT
5234 5243 tk_inner.F_MemberId as member_id,
... ... @@ -5239,7 +5248,14 @@ namespace NCC.Extend.LqStatistics
5239 5248 INNER JOIN lq_yaoyjl yaoy ON yaoy.yykh = tk_inner.F_MemberId
5240 5249 INNER JOIN lq_yyjl yy ON yy.F_InviteId = yaoy.F_Id AND yy.gk = tk_inner.F_MemberId
5241 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 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 5259 GROUP BY tk_inner.F_MemberId
5244 5260 ) kd_stats ON kd_stats.member_id = tk.F_MemberId
5245 5261 -- 实际预约记录数统计(不管是否通过邀约产生)
... ... @@ -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 5733 var whereClause = whereConditions.Any() ? "AND " + string.Join(" AND ", whereConditions) : "";
5707 5734  
5708 5735 // 构建HAVING条件(用于筛选升单条件)
... ...
netcore/src/Modularity/Extend/NCC.Extend/LqTkjlbService.cs
... ... @@ -147,12 +147,70 @@ namespace NCC.Extend.LqTkjlb
147 147 hasAppointment = SqlFunc.Subqueryable<LqYyjlEntity>().Where(y => y.Gk == it.MemberId).Any() ? "是" : "否",
148 148 // 是否消耗:通过会员ID关联耗卡表(hy字段存储的是会员ID)
149 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 154 .MergeTable()
154 155 .OrderBy(sidx + " " + input.sort)
155 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 214 return PageResult<LqTkjlbListOutput>.SqlSugarPageResult(data);
157 215 }
158 216 #endregion
... ...
netcore/src/Modularity/Extend/NCC.Extend/LqXhHyhkService.cs
... ... @@ -2036,9 +2036,20 @@ namespace NCC.Extend.LqXhHyhk
2036 2036 .WhereIF(!string.IsNullOrEmpty(input.ItemType), pxmx => pxmx.ItemCategory == input.ItemType);
2037 2037  
2038 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 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 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 2054 // 3. 先分页查询主表数据(查询实体类,提高性能)
2044 2055 var pagedData = await baseQuery.OrderBy(sidx + " " + sort).ToPagedListAsync(input.currentPage, input.pageSize);
... ...