Commit 63df7ccb830b7faef56be2b3308e5b7a4bb670dd

Authored by “wangming”
1 parent 88610eda

1

netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqHytkHytk/LqHytkHytkCrInput.cs
... ... @@ -57,6 +57,11 @@ namespace NCC.Extend.Entitys.Dto.LqHytkHytk
57 57 public decimal? tkje { get; set; }
58 58  
59 59 /// <summary>
  60 + /// 实退金额
  61 + /// </summary>
  62 + public decimal? actualRefundAmount { get; set; }
  63 +
  64 + /// <summary>
60 65 /// 手工费用
61 66 /// </summary>
62 67 public decimal? sgfy { get; set; }
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqHytkHytk/LqHytkHytkInfoOutput.cs
... ... @@ -57,6 +57,11 @@ namespace NCC.Extend.Entitys.Dto.LqHytkHytk
57 57 public decimal? tkje { get; set; }
58 58  
59 59 /// <summary>
  60 + /// 实退金额
  61 + /// </summary>
  62 + public decimal? actualRefundAmount { get; set; }
  63 +
  64 + /// <summary>
60 65 /// 手工费用
61 66 /// </summary>
62 67 public decimal? sgfy { get; set; }
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqHytkHytk/LqHytkHytkListOutput.cs
... ... @@ -53,6 +53,11 @@ namespace NCC.Extend.Entitys.Dto.LqHytkHytk
53 53 public decimal? tkje { get; set; }
54 54  
55 55 /// <summary>
  56 + /// 实退金额
  57 + /// </summary>
  58 + public decimal? actualRefundAmount { get; set; }
  59 +
  60 + /// <summary>
56 61 /// 手工费用
57 62 /// </summary>
58 63 public decimal? sgfy { get; set; }
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqHytkHytk/LqHytkHytkUpInput.cs
... ... @@ -57,6 +57,11 @@ namespace NCC.Extend.Entitys.Dto.LqHytkHytk
57 57 public decimal? tkje { get; set; }
58 58  
59 59 /// <summary>
  60 + /// 实退金额
  61 + /// </summary>
  62 + public decimal? actualRefundAmount { get; set; }
  63 +
  64 + /// <summary>
60 65 /// 手工费用
61 66 /// </summary>
62 67 public decimal? sgfy { get; set; }
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKdKdjlb/TransferCardInput.cs
... ... @@ -46,8 +46,6 @@ namespace NCC.Extend.Entitys.Dto.LqKdKdjlb
46 46 [Display(Name = "转卡备注")]
47 47 public string Remarks { get; set; }
48 48  
49   -
50   -
51 49 /// <summary>
52 50 /// 门店ID
53 51 /// </summary>
... ... @@ -109,120 +107,5 @@ namespace NCC.Extend.Entitys.Dto.LqKdKdjlb
109 107 [Display(Name = "来源类型")]
110 108 public string SourceType { get; set; } = "转卡";
111 109  
112   - /// <summary>
113   - /// 健康师业绩列表
114   - /// </summary>
115   - [Display(Name = "健康师业绩列表")]
116   - public List<TransferHealthTeacherPerformanceInput> HealthTeacherPerformances { get; set; }
117   -
118   - /// <summary>
119   - /// 科技部老师业绩列表
120   - /// </summary>
121   - [Display(Name = "科技部老师业绩列表")]
122   - public List<TransferTechTeacherPerformanceInput> TechTeacherPerformances { get; set; }
123   - }
124   -
125   - /// <summary>
126   - /// 转卡健康师业绩输入参数
127   - /// </summary>
128   - public class TransferHealthTeacherPerformanceInput
129   - {
130   - /// <summary>
131   - /// 健康师ID
132   - /// </summary>
133   - [Required(ErrorMessage = "健康师ID不能为空")]
134   - [StringLength(50, ErrorMessage = "健康师ID长度不能超过50个字符")]
135   - [Display(Name = "健康师ID")]
136   - public string HealthTeacherId { get; set; }
137   -
138   - /// <summary>
139   - /// 健康师姓名
140   - /// </summary>
141   - [Required(ErrorMessage = "健康师姓名不能为空")]
142   - [StringLength(50, ErrorMessage = "健康师姓名长度不能超过50个字符")]
143   - [Display(Name = "健康师姓名")]
144   - public string HealthTeacherName { get; set; }
145   -
146   - /// <summary>
147   - /// 健康师账号
148   - /// </summary>
149   - [StringLength(50, ErrorMessage = "健康师账号长度不能超过50个字符")]
150   - [Display(Name = "健康师账号")]
151   - public string HealthTeacherAccount { get; set; }
152   -
153   - /// <summary>
154   - /// 业绩金额
155   - /// </summary>
156   - [Required(ErrorMessage = "业绩金额不能为空")]
157   - [Range(0, double.MaxValue, ErrorMessage = "业绩金额不能为负数")]
158   - [Display(Name = "业绩金额")]
159   - public decimal PerformanceAmount { get; set; }
160   -
161   - /// <summary>
162   - /// 人工成本
163   - /// </summary>
164   - [Range(0, double.MaxValue, ErrorMessage = "人工成本不能为负数")]
165   - [Display(Name = "人工成本")]
166   - public decimal LaborCost { get; set; }
167   -
168   - /// <summary>
169   - /// 品项数量
170   - /// </summary>
171   - [Required(ErrorMessage = "品项数量不能为空")]
172   - [Range(1, int.MaxValue, ErrorMessage = "品项数量必须大于0")]
173   - [Display(Name = "品项数量")]
174   - public int ItemQuantity { get; set; }
175   - }
176   -
177   - /// <summary>
178   - /// 转卡科技部老师业绩输入参数
179   - /// </summary>
180   - public class TransferTechTeacherPerformanceInput
181   - {
182   - /// <summary>
183   - /// 科技部老师ID
184   - /// </summary>
185   - [Required(ErrorMessage = "科技部老师ID不能为空")]
186   - [StringLength(50, ErrorMessage = "科技部老师ID长度不能超过50个字符")]
187   - [Display(Name = "科技部老师ID")]
188   - public string TechTeacherId { get; set; }
189   -
190   - /// <summary>
191   - /// 科技部老师姓名
192   - /// </summary>
193   - [Required(ErrorMessage = "科技部老师姓名不能为空")]
194   - [StringLength(50, ErrorMessage = "科技部老师姓名长度不能超过50个字符")]
195   - [Display(Name = "科技部老师姓名")]
196   - public string TechTeacherName { get; set; }
197   -
198   - /// <summary>
199   - /// 科技部老师账号
200   - /// </summary>
201   - [StringLength(50, ErrorMessage = "科技部老师账号长度不能超过50个字符")]
202   - [Display(Name = "科技部老师账号")]
203   - public string TechTeacherAccount { get; set; }
204   -
205   - /// <summary>
206   - /// 业绩金额
207   - /// </summary>
208   - [Required(ErrorMessage = "业绩金额不能为空")]
209   - [Range(0, double.MaxValue, ErrorMessage = "业绩金额不能为负数")]
210   - [Display(Name = "业绩金额")]
211   - public decimal PerformanceAmount { get; set; }
212   -
213   - /// <summary>
214   - /// 人工成本
215   - /// </summary>
216   - [Range(0, double.MaxValue, ErrorMessage = "人工成本不能为负数")]
217   - [Display(Name = "人工成本")]
218   - public decimal LaborCost { get; set; }
219   -
220   - /// <summary>
221   - /// 品项数量
222   - /// </summary>
223   - [Required(ErrorMessage = "品项数量不能为空")]
224   - [Range(1, int.MaxValue, ErrorMessage = "品项数量必须大于0")]
225   - [Display(Name = "品项数量")]
226   - public int ItemQuantity { get; set; }
227 110 }
228 111 }
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqXhHyhk/LqXhHyhkListOutput.cs
... ... @@ -59,6 +59,7 @@ namespace NCC.Extend.Entitys.Dto.LqXhHyhk
59 59 /// </summary>
60 60 public string xfje { get; set; }
61 61  
  62 +
62 63 /// <summary>
63 64 /// 手工费用
64 65 /// </summary>
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_hytk_hytk/LqHytkHytkEntity.cs
... ... @@ -67,6 +67,12 @@ namespace NCC.Extend.Entitys.lq_hytk_hytk
67 67 public decimal? Tkje { get; set; }
68 68  
69 69 /// <summary>
  70 + /// 实退金额
  71 + /// </summary>
  72 + [SugarColumn(ColumnName = "F_ActualRefundAmount")]
  73 + public decimal? ActualRefundAmount { get; set; }
  74 +
  75 + /// <summary>
70 76 /// 手工费用
71 77 /// </summary>
72 78 [SugarColumn(ColumnName = "sgfy")]
... ...
netcore/src/Modularity/Extend/NCC.Extend/LqHytkHytkService.cs
... ... @@ -116,6 +116,7 @@ namespace NCC.Extend.LqHytkHytk
116 116 fileUrl = it.FileUrl,
117 117 isEffective = it.IsEffective,
118 118 cancelRemark = it.CancelRemark,
  119 + actualRefundAmount = it.ActualRefundAmount
119 120 })
120 121 .MergeTable()
121 122 .OrderBy(sidx + " " + input.sort)
... ...
netcore/src/Modularity/Extend/NCC.Extend/LqKdKdjlbService.cs
... ... @@ -2414,7 +2414,6 @@ namespace NCC.Extend.LqKdKdjlb
2414 2414 {
2415 2415 // 开启事务
2416 2416 _db.BeginTran();
2417   -
2418 2417 // 1. 验证转出和转入会员是否存在
2419 2418 var fromMember = await _db.Queryable<LqKhxxEntity>().Where(x => x.Id == input.FromMemberId && x.IsEffective == StatusEnum.有效.GetHashCode()).FirstAsync();
2420 2419 if (fromMember == null)
... ... @@ -2446,6 +2445,7 @@ namespace NCC.Extend.LqKdKdjlb
2446 2445  
2447 2446 // 3. 创建退卡记录(转出方)
2448 2447 var refundId = YitIdHelper.NextId().ToString();
  2448 + var totalRefundAmount = input.TransferItems.Sum(x => x.ItemPrice * x.TransferQuantity);
2449 2449 var refundEntity = new LqHytkHytkEntity
2450 2450 {
2451 2451 Id = refundId,
... ... @@ -2457,7 +2457,8 @@ namespace NCC.Extend.LqKdKdjlb
2457 2457 Hymc = fromMember.Khmc,
2458 2458 Md = input.StoreId,
2459 2459 SignatureFile = input.SignatureFile,
2460   - Tkje = input.TransferItems.Sum(x => x.ItemPrice * x.TransferQuantity),
  2460 + Tkje = totalRefundAmount,
  2461 + ActualRefundAmount = totalRefundAmount, // 转卡时实退金额等于退卡总金额
2461 2462 Tkyy = "转卡",
2462 2463 Bz = $"转卡给会员:{toMember.Khmc},{input.Remarks}"
2463 2464 };
... ... @@ -2466,10 +2467,15 @@ namespace NCC.Extend.LqKdKdjlb
2466 2467 // 4. 创建退卡品项明细和业绩记录
2467 2468 var refundMxEntities = new List<LqHytkMxEntity>();
2468 2469 var refundJksyjEntities = new List<LqHytkJksyjEntity>();
  2470 +
2469 2471 var refundKjbsyjEntities = new List<LqHytkKjbsyjEntity>();
2470 2472  
2471 2473 foreach (var item in input.TransferItems)
2472 2474 {
  2475 + var refundPxmxEntity = _db.Queryable<LqKdPxmxEntity>().First(p => p.Id == item.BillingItemId);
  2476 + //计算品项扣除总金额
  2477 + var 品项扣除总金额 = refundPxmxEntity.Pxjg * item.TransferQuantity;
  2478 + //保存退卡明细表
2473 2479 var refundMxEntity = new LqHytkMxEntity
2474 2480 {
2475 2481 Id = YitIdHelper.NextId().ToString(),
... ... @@ -2480,63 +2486,61 @@ namespace NCC.Extend.LqKdKdjlb
2480 2486 Px = item.ItemId,
2481 2487 Pxmc = item.ItemName,
2482 2488 Pxjg = item.ItemPrice,
2483   - Tkje = item.ItemPrice * item.TransferQuantity,
  2489 + Tkje = 品项扣除总金额,
2484 2490 ProjectNumber = item.TransferQuantity,
2485 2491 SourceType = item.SourceType,
2486   - TotalPrice = item.ItemPrice * item.TransferQuantity,
  2492 + TotalPrice = 品项扣除总金额,
2487 2493 IsEffective = StatusEnum.有效.GetHashCode()
2488 2494 };
2489 2495 refundMxEntities.Add(refundMxEntity);
2490   -
2491   - // 创建退卡健康师业绩记录
2492   - if (item.HealthTeacherPerformances != null && item.HealthTeacherPerformances.Any())
  2496 + var refundKdyjEntities = _db.Queryable<LqKdJksyjEntity>().Where(p => p.Kdpxid == item.BillingItemId).ToList();
  2497 + //保存退卡健康师业绩表
  2498 + foreach (var jks in refundKdyjEntities)
2493 2499 {
2494   - foreach (var jks in item.HealthTeacherPerformances)
  2500 + //获取健康师当月金三角id
  2501 + var monthString = DateTime.Now.ToString("yyyyMM");
  2502 + var GetMonTeam = _db.Queryable<LqJinsanjiaoUserEntity>().Where(p => p.UserId == jks.Jks && p.Month == monthString).First();
  2503 + //获取本月
  2504 + refundJksyjEntities.Add(new LqHytkJksyjEntity
2495 2505 {
2496   - refundJksyjEntities.Add(new LqHytkJksyjEntity
2497   - {
2498   - Id = YitIdHelper.NextId().ToString(),
2499   - Gltkbh = refundId,
2500   - Jks = jks.HealthTeacherId,
2501   - Jksxm = jks.HealthTeacherName,
2502   - Jkszh = jks.HealthTeacherAccount,
2503   - Jksyj = jks.PerformanceAmount,
2504   - Tksj = transferTime,
2505   - F_jsjid = jks.HealthTeacherId,
2506   - F_tkpxid = refundMxEntity.Id,
2507   - F_LaborCost = jks.LaborCost,
2508   - F_tkpxNumber = jks.ItemQuantity,
2509   - F_CreateTime = transferTime,
2510   - F_CreateUser = userInfo.userId,
2511   - CardReturn = refundMxEntity.Id,
2512   - IsEffective = StatusEnum.有效.GetHashCode()
2513   - });
2514   - }
  2506 + Id = YitIdHelper.NextId().ToString(),
  2507 + Gltkbh = refundId,
  2508 + Jks = jks.Jks,
  2509 + Jksxm = jks.Jksxm,
  2510 + Jkszh = jks.Jkszh,
  2511 + Jksyj = (品项扣除总金额 / refundKdyjEntities.Count()),
  2512 + Tksj = DateTime.Now,
  2513 + F_jsjid = GetMonTeam.JsjId,
  2514 + F_tkpxid = refundMxEntity.Id,
  2515 + F_tkpxNumber = item.TransferQuantity,
  2516 + F_CreateTime = transferTime,
  2517 + F_CreateUser = userInfo.userId,
  2518 + CardReturn = refundMxEntity.Id,
  2519 + IsEffective = StatusEnum.有效.GetHashCode()
  2520 + });
2515 2521 }
2516   -
  2522 + //查询科技部老师的业绩
  2523 + var TechTeacherEntities = _db.Queryable<LqKdKjbsyjEntity>().Where(p => p.Kdpxid == item.BillingItemId).ToList();
2517 2524 // 创建退卡科技部老师业绩记录
2518   - if (item.TechTeacherPerformances != null && item.TechTeacherPerformances.Any())
  2525 + foreach (var kjbs in TechTeacherEntities)
2519 2526 {
2520   - foreach (var kjbs in item.TechTeacherPerformances)
  2527 + refundKjbsyjEntities.Add(new LqHytkKjbsyjEntity
2521 2528 {
2522   - refundKjbsyjEntities.Add(new LqHytkKjbsyjEntity
2523   - {
2524   - Id = YitIdHelper.NextId().ToString(),
2525   - Gltkbh = refundId,
2526   - Kjbls = kjbs.TechTeacherId,
2527   - Kjblsxm = kjbs.TechTeacherName,
2528   - Kjblszh = kjbs.TechTeacherAccount,
2529   - Kjblsyj = kjbs.PerformanceAmount,
2530   - Tksj = transferTime,
2531   - F_tkpxid = refundMxEntity.Id,
2532   - F_LaborCost = kjbs.LaborCost,
2533   - F_tkpxNumber = kjbs.ItemQuantity,
2534   - F_CreateTime = transferTime,
2535   - F_CreateUser = userInfo.userId,
2536   - CardReturn = refundMxEntity.Id,
2537   - IsEffective = StatusEnum.有效.GetHashCode()
2538   - });
2539   - }
  2529 + Id = YitIdHelper.NextId().ToString(),
  2530 + Gltkbh = refundId,
  2531 + Kjbls = kjbs.Kjbls,
  2532 + Kjblsxm = kjbs.Kjblsxm,
  2533 + Kjblszh = kjbs.Kjblszh,
  2534 + Kjblsyj = (品项扣除总金额 / TechTeacherEntities.Count()),
  2535 + Tksj = transferTime,
  2536 + F_tkpxid = refundMxEntity.Id,
  2537 + F_LaborCost = kjbs.LaborCost,
  2538 + F_tkpxNumber = item.TransferQuantity,
  2539 + F_CreateTime = transferTime,
  2540 + F_CreateUser = userInfo.userId,
  2541 + CardReturn = refundMxEntity.Id,
  2542 + IsEffective = StatusEnum.有效.GetHashCode()
  2543 + });
2540 2544 }
2541 2545 }
2542 2546  
... ... @@ -2561,6 +2565,8 @@ namespace NCC.Extend.LqKdKdjlb
2561 2565 CreateUser = userInfo.userId,
2562 2566 Kdhy = input.ToMemberId,
2563 2567 Djmd = input.StoreId,
  2568 + Fkfs = "转卡",
  2569 + Sfskdd = "否",
2564 2570 Sfyj = input.TransferItems.Sum(x => x.ItemPrice * x.TransferQuantity),
2565 2571 Bz = $"从会员 {fromMember.Khmc} 转入,{input.Remarks}"
2566 2572 };
... ... @@ -2591,46 +2597,37 @@ namespace NCC.Extend.LqKdKdjlb
2591 2597 };
2592 2598 billingPxmxEntities.Add(billingPxmxEntity);
2593 2599  
2594   - // 创建开卡健康师业绩记录
2595   - if (item.HealthTeacherPerformances != null && item.HealthTeacherPerformances.Any())
  2600 + foreach (var jks in refundJksyjEntities)
2596 2601 {
2597   - foreach (var jks in item.HealthTeacherPerformances)
  2602 + billingJksyjEntities.Add(new LqKdJksyjEntity
2598 2603 {
2599   - billingJksyjEntities.Add(new LqKdJksyjEntity
2600   - {
2601   - Id = YitIdHelper.NextId().ToString(),
2602   - Glkdbh = billingId,
2603   - Jks = jks.HealthTeacherId,
2604   - Jksxm = jks.HealthTeacherName,
2605   - Jkszh = jks.HealthTeacherAccount,
2606   - Jksyj = jks.PerformanceAmount.ToString(),
2607   - Yjsj = transferTime,
2608   - Jsj_id = jks.HealthTeacherId,
2609   - Kdpxid = billingPxmxEntity.Id,
2610   - IsEffective = StatusEnum.有效.GetHashCode()
2611   - });
2612   - }
  2604 + Id = YitIdHelper.NextId().ToString(),
  2605 + Glkdbh = billingId,
  2606 + Jks = jks.Jks,
  2607 + Jksxm = jks.Jksxm,
  2608 + Jkszh = jks.Jkszh,
  2609 + Jksyj = jks.Jksyj.ToString(),
  2610 + Yjsj = transferTime,
  2611 + Jsj_id = jks.F_jsjid,
  2612 + Kdpxid = billingPxmxEntity.Id,
  2613 + IsEffective = StatusEnum.有效.GetHashCode()
  2614 + });
2613 2615 }
2614 2616  
2615   - // 创建开卡科技部老师业绩记录
2616   - if (item.TechTeacherPerformances != null && item.TechTeacherPerformances.Any())
  2617 + foreach (var kjbs in refundKjbsyjEntities)
2617 2618 {
2618   - foreach (var kjbs in item.TechTeacherPerformances)
  2619 + billingKjbsyjEntities.Add(new LqKdKjbsyjEntity
2619 2620 {
2620   - billingKjbsyjEntities.Add(new LqKdKjbsyjEntity
2621   - {
2622   - Id = YitIdHelper.NextId().ToString(),
2623   - Glkdbh = billingId,
2624   - Kjbls = kjbs.TechTeacherId,
2625   - Kjblsxm = kjbs.TechTeacherName,
2626   - Kjblszh = kjbs.TechTeacherAccount,
2627   - Kjblsyj = kjbs.PerformanceAmount.ToString(),
2628   - Yjsj = transferTime,
2629   - Kdpxid = billingPxmxEntity.Id,
2630   - LaborCost = kjbs.LaborCost,
2631   - IsEffective = StatusEnum.有效.GetHashCode()
2632   - });
2633   - }
  2621 + Id = YitIdHelper.NextId().ToString(),
  2622 + Glkdbh = billingId,
  2623 + Kjbls = kjbs.Kjbls,
  2624 + Kjblsxm = kjbs.Kjblsxm,
  2625 + Kjblszh = kjbs.Kjblszh,
  2626 + Kjblsyj = kjbs.Kjblsyj.ToString(),
  2627 + Yjsj = transferTime,
  2628 + Kdpxid = billingPxmxEntity.Id,
  2629 + IsEffective = StatusEnum.有效.GetHashCode()
  2630 + });
2634 2631 }
2635 2632 }
2636 2633  
... ... @@ -2669,6 +2666,361 @@ namespace NCC.Extend.LqKdKdjlb
2669 2666 }
2670 2667 #endregion
2671 2668  
  2669 + // #region 会员转卡操作
  2670 + // /// <summary>
  2671 + // /// 会员转卡操作
  2672 + // /// </summary>
  2673 + // /// <remarks>
  2674 + // /// 转卡是指一个会员把自己剩余的某些品项,转给另外一个会员
  2675 + // /// 转卡会员转出的品项,就做退卡操作
  2676 + // /// 被转卡会员收到的品项,做开卡操作
  2677 + // ///
  2678 + // /// 示例请求:
  2679 + // /// ```json
  2680 + // /// {
  2681 + // /// "fromMemberId": "member001",
  2682 + // /// "toMemberId": "member002",
  2683 + // /// "storeId": "store001",
  2684 + // /// "signatureFile": "签名文件路径",
  2685 + // /// "remarks": "转卡备注",
  2686 + // /// "transferItems": [
  2687 + // /// {
  2688 + // /// "billingItemId": "item001",
  2689 + // /// "transferQuantity": 5,
  2690 + // /// "itemId": "px001",
  2691 + // /// "itemName": "美容项目",
  2692 + // /// "itemPrice": 100.00,
  2693 + // /// "sourceType": "转卡",
  2694 + // /// "healthTeacherPerformances": [
  2695 + // /// {
  2696 + // /// "healthTeacherId": "jks001",
  2697 + // /// "healthTeacherName": "张医生",
  2698 + // /// "healthTeacherAccount": "zhang001",
  2699 + // /// "performanceAmount": 50.00,
  2700 + // /// "laborCost": 20.00,
  2701 + // /// "itemQuantity": 5
  2702 + // /// }
  2703 + // /// ],
  2704 + // /// "techTeacherPerformances": [
  2705 + // /// {
  2706 + // /// "techTeacherId": "kjbs001",
  2707 + // /// "techTeacherName": "李老师",
  2708 + // /// "techTeacherAccount": "li001",
  2709 + // /// "performanceAmount": 30.00,
  2710 + // /// "laborCost": 10.00,
  2711 + // /// "itemQuantity": 5
  2712 + // /// }
  2713 + // /// ]
  2714 + // /// }
  2715 + // /// ]
  2716 + // /// }
  2717 + // /// ```
  2718 + // ///
  2719 + // /// 参数说明:
  2720 + // /// - fromMemberId: 转出会员ID
  2721 + // /// - toMemberId: 转入会员ID
  2722 + // /// - storeId: 门店ID
  2723 + // /// - signatureFile: 转出会员签名
  2724 + // /// - remarks: 转卡备注
  2725 + // /// - transferItems: 转出品项列表
  2726 + // /// - billingItemId: 开单品项明细ID
  2727 + // /// - transferQuantity: 转出数量
  2728 + // /// - itemId: 品项ID
  2729 + // /// - itemName: 品项名称
  2730 + // /// - itemPrice: 品项价格
  2731 + // /// - sourceType: 来源类型
  2732 + // /// - healthTeacherPerformances: 健康师业绩列表
  2733 + // /// - healthTeacherId: 健康师ID
  2734 + // /// - healthTeacherName: 健康师姓名
  2735 + // /// - healthTeacherAccount: 健康师账号
  2736 + // /// - performanceAmount: 业绩金额
  2737 + // /// - laborCost: 人工成本
  2738 + // /// - itemQuantity: 品项数量
  2739 + // /// - techTeacherPerformances: 科技部老师业绩列表
  2740 + // /// - techTeacherId: 科技部老师ID
  2741 + // /// - techTeacherName: 科技部老师姓名
  2742 + // /// - techTeacherAccount: 科技部老师账号
  2743 + // /// - performanceAmount: 业绩金额
  2744 + // /// - laborCost: 人工成本
  2745 + // /// - itemQuantity: 品项数量
  2746 + // /// </remarks>
  2747 + // /// <param name="input">转卡输入参数</param>
  2748 + // /// <returns>转卡结果</returns>
  2749 + // /// <response code="200">转卡成功</response>
  2750 + // /// <response code="400">参数错误或业务规则验证失败</response>
  2751 + // /// <response code="500">服务器错误</response>
  2752 + // [HttpPost("TransferCard")]
  2753 + // public async Task<TransferCardOutput> TransferCard([FromBody] TransferCardInput input)
  2754 + // {
  2755 + // if (input == null)
  2756 + // {
  2757 + // throw NCCException.Oh("转卡参数不能为空");
  2758 + // }
  2759 +
  2760 + // var userInfo = await _userManager.GetUserInfo();
  2761 + // var transferTime = DateTime.Now;
  2762 +
  2763 + // try
  2764 + // {
  2765 + // // 开启事务
  2766 + // _db.BeginTran();
  2767 +
  2768 + // // 1. 验证转出和转入会员是否存在
  2769 + // var fromMember = await _db.Queryable<LqKhxxEntity>().Where(x => x.Id == input.FromMemberId && x.IsEffective == StatusEnum.有效.GetHashCode()).FirstAsync();
  2770 + // if (fromMember == null)
  2771 + // {
  2772 + // throw NCCException.Oh("转出会员不存在或已失效");
  2773 + // }
  2774 + // var toMember = await _db.Queryable<LqKhxxEntity>().Where(x => x.Id == input.ToMemberId && x.IsEffective == StatusEnum.有效.GetHashCode()).FirstAsync();
  2775 + // if (toMember == null)
  2776 + // {
  2777 + // throw NCCException.Oh("转入会员不存在或已失效");
  2778 + // }
  2779 +
  2780 + // // 2. 验证转出品项的余额是否足够
  2781 + // foreach (var item in input.TransferItems)
  2782 + // {
  2783 + // var billingItem = await _db.Queryable<LqKdPxmxEntity>().Where(x => x.Id == item.BillingItemId && x.IsEffective == StatusEnum.有效.GetHashCode()).FirstAsync();
  2784 + // if (billingItem == null)
  2785 + // {
  2786 + // throw NCCException.Oh($"品项明细不存在:{item.ItemName}");
  2787 + // }
  2788 +
  2789 + // // 查询剩余数量
  2790 + // var remainingCount = await GetItemRemainingCount(item.BillingItemId);
  2791 + // if (remainingCount < item.TransferQuantity)
  2792 + // {
  2793 + // throw NCCException.Oh($"品项 {item.ItemName} 剩余数量不足,当前剩余:{remainingCount},需要转出:{item.TransferQuantity}");
  2794 + // }
  2795 + // }
  2796 +
  2797 + // // 3. 创建退卡记录(转出方)
  2798 + // var refundId = YitIdHelper.NextId().ToString();
  2799 + // var totalRefundAmount = input.TransferItems.Sum(x => x.ItemPrice * x.TransferQuantity);
  2800 + // var refundEntity = new LqHytkHytkEntity
  2801 + // {
  2802 + // Id = refundId,
  2803 + // F_CreateTime = transferTime,
  2804 + // F_CreateUser = userInfo.userId,
  2805 + // IsEffective = StatusEnum.有效.GetHashCode(),
  2806 + // Czry = userInfo.userId,
  2807 + // Hy = input.FromMemberId,
  2808 + // Hymc = fromMember.Khmc,
  2809 + // Md = input.StoreId,
  2810 + // SignatureFile = input.SignatureFile,
  2811 + // Tkje = totalRefundAmount,
  2812 + // ActualRefundAmount = totalRefundAmount, // 转卡时实退金额等于退卡总金额
  2813 + // Tkyy = "转卡",
  2814 + // Bz = $"转卡给会员:{toMember.Khmc},{input.Remarks}"
  2815 + // };
  2816 + // await _db.Insertable(refundEntity).ExecuteCommandAsync();
  2817 +
  2818 + // // 4. 创建退卡品项明细和业绩记录
  2819 + // var refundMxEntities = new List<LqHytkMxEntity>();
  2820 + // var refundJksyjEntities = new List<LqHytkJksyjEntity>();
  2821 + // var refundKjbsyjEntities = new List<LqHytkKjbsyjEntity>();
  2822 +
  2823 + // foreach (var item in input.TransferItems)
  2824 + // {
  2825 + // var refundMxEntity = new LqHytkMxEntity
  2826 + // {
  2827 + // Id = YitIdHelper.NextId().ToString(),
  2828 + // RefundInfoId = refundId,
  2829 + // BillingItemId = item.BillingItemId,
  2830 + // CreateTime = transferTime,
  2831 + // CreateUser = userInfo.userId,
  2832 + // Px = item.ItemId,
  2833 + // Pxmc = item.ItemName,
  2834 + // Pxjg = item.ItemPrice,
  2835 + // Tkje = item.ItemPrice * item.TransferQuantity,
  2836 + // ProjectNumber = item.TransferQuantity,
  2837 + // SourceType = item.SourceType,
  2838 + // TotalPrice = item.ItemPrice * item.TransferQuantity,
  2839 + // IsEffective = StatusEnum.有效.GetHashCode()
  2840 + // };
  2841 + // refundMxEntities.Add(refundMxEntity);
  2842 +
  2843 + // // 创建退卡健康师业绩记录
  2844 + // if (item.HealthTeacherPerformances != null && item.HealthTeacherPerformances.Any())
  2845 + // {
  2846 + // foreach (var jks in item.HealthTeacherPerformances)
  2847 + // {
  2848 + // refundJksyjEntities.Add(new LqHytkJksyjEntity
  2849 + // {
  2850 + // Id = YitIdHelper.NextId().ToString(),
  2851 + // Gltkbh = refundId,
  2852 + // Jks = jks.HealthTeacherId,
  2853 + // Jksxm = jks.HealthTeacherName,
  2854 + // Jkszh = jks.HealthTeacherAccount,
  2855 + // Jksyj = jks.PerformanceAmount,
  2856 + // Tksj = transferTime,
  2857 + // F_jsjid = jks.HealthTeacherId,
  2858 + // F_tkpxid = refundMxEntity.Id,
  2859 + // F_LaborCost = jks.LaborCost,
  2860 + // F_tkpxNumber = jks.ItemQuantity,
  2861 + // F_CreateTime = transferTime,
  2862 + // F_CreateUser = userInfo.userId,
  2863 + // CardReturn = refundMxEntity.Id,
  2864 + // IsEffective = StatusEnum.有效.GetHashCode()
  2865 + // });
  2866 + // }
  2867 + // }
  2868 +
  2869 + // // 创建退卡科技部老师业绩记录
  2870 + // if (item.TechTeacherPerformances != null && item.TechTeacherPerformances.Any())
  2871 + // {
  2872 + // foreach (var kjbs in item.TechTeacherPerformances)
  2873 + // {
  2874 + // refundKjbsyjEntities.Add(new LqHytkKjbsyjEntity
  2875 + // {
  2876 + // Id = YitIdHelper.NextId().ToString(),
  2877 + // Gltkbh = refundId,
  2878 + // Kjbls = kjbs.TechTeacherId,
  2879 + // Kjblsxm = kjbs.TechTeacherName,
  2880 + // Kjblszh = kjbs.TechTeacherAccount,
  2881 + // Kjblsyj = kjbs.PerformanceAmount,
  2882 + // Tksj = transferTime,
  2883 + // F_tkpxid = refundMxEntity.Id,
  2884 + // F_LaborCost = kjbs.LaborCost,
  2885 + // F_tkpxNumber = kjbs.ItemQuantity,
  2886 + // F_CreateTime = transferTime,
  2887 + // F_CreateUser = userInfo.userId,
  2888 + // CardReturn = refundMxEntity.Id,
  2889 + // IsEffective = StatusEnum.有效.GetHashCode()
  2890 + // });
  2891 + // }
  2892 + // }
  2893 + // }
  2894 +
  2895 + // await _db.Insertable(refundMxEntities).ExecuteCommandAsync();
  2896 + // if (refundJksyjEntities.Any())
  2897 + // {
  2898 + // await _db.Insertable(refundJksyjEntities).ExecuteCommandAsync();
  2899 + // }
  2900 + // if (refundKjbsyjEntities.Any())
  2901 + // {
  2902 + // await _db.Insertable(refundKjbsyjEntities).ExecuteCommandAsync();
  2903 + // }
  2904 +
  2905 + // // 5. 创建开卡记录(转入方)
  2906 + // var billingId = YitIdHelper.NextId().ToString();
  2907 + // var billingEntity = new LqKdKdjlbEntity
  2908 + // {
  2909 + // Id = billingId,
  2910 + // CreateTime = transferTime,
  2911 + // UpdateTime = transferTime,
  2912 + // IsEffective = StatusEnum.有效.GetHashCode(),
  2913 + // CreateUser = userInfo.userId,
  2914 + // Kdhy = input.ToMemberId,
  2915 + // Djmd = input.StoreId,
  2916 + // Sfyj = input.TransferItems.Sum(x => x.ItemPrice * x.TransferQuantity),
  2917 + // Bz = $"从会员 {fromMember.Khmc} 转入,{input.Remarks}"
  2918 + // };
  2919 + // await _db.Insertable(billingEntity).ExecuteCommandAsync();
  2920 + // // 6. 创建开单品项明细和业绩记录
  2921 + // var billingPxmxEntities = new List<LqKdPxmxEntity>();
  2922 + // var billingJksyjEntities = new List<LqKdJksyjEntity>();
  2923 + // var billingKjbsyjEntities = new List<LqKdKjbsyjEntity>();
  2924 +
  2925 + // foreach (var item in input.TransferItems)
  2926 + // {
  2927 + // var billingPxmxEntity = new LqKdPxmxEntity
  2928 + // {
  2929 + // Id = YitIdHelper.NextId().ToString(),
  2930 + // Glkdbh = billingId,
  2931 + // Px = item.ItemId,
  2932 + // Pxmc = item.ItemName,
  2933 + // Pxjg = item.ItemPrice,
  2934 + // MemberId = input.ToMemberId,
  2935 + // CreateTIme = transferTime,
  2936 + // ProjectNumber = item.TransferQuantity,
  2937 + // IsEnabled = StatusEnum.有效.GetHashCode(),
  2938 + // SourceType = item.SourceType,
  2939 + // TotalPrice = item.ItemPrice * item.TransferQuantity,
  2940 + // ActualPrice = item.ItemPrice * item.TransferQuantity,
  2941 + // IsEffective = StatusEnum.有效.GetHashCode(),
  2942 + // Remark = $"从会员 {fromMember.Khmc} 转入"
  2943 + // };
  2944 + // billingPxmxEntities.Add(billingPxmxEntity);
  2945 +
  2946 + // // 创建开卡健康师业绩记录
  2947 + // if (item.HealthTeacherPerformances != null && item.HealthTeacherPerformances.Any())
  2948 + // {
  2949 + // foreach (var jks in item.HealthTeacherPerformances)
  2950 + // {
  2951 + // billingJksyjEntities.Add(new LqKdJksyjEntity
  2952 + // {
  2953 + // Id = YitIdHelper.NextId().ToString(),
  2954 + // Glkdbh = billingId,
  2955 + // Jks = jks.HealthTeacherId,
  2956 + // Jksxm = jks.HealthTeacherName,
  2957 + // Jkszh = jks.HealthTeacherAccount,
  2958 + // Jksyj = jks.PerformanceAmount.ToString(),
  2959 + // Yjsj = transferTime,
  2960 + // Jsj_id = jks.HealthTeacherId,
  2961 + // Kdpxid = billingPxmxEntity.Id,
  2962 + // IsEffective = StatusEnum.有效.GetHashCode()
  2963 + // });
  2964 + // }
  2965 + // }
  2966 +
  2967 + // // 创建开卡科技部老师业绩记录
  2968 + // if (item.TechTeacherPerformances != null && item.TechTeacherPerformances.Any())
  2969 + // {
  2970 + // foreach (var kjbs in item.TechTeacherPerformances)
  2971 + // {
  2972 + // billingKjbsyjEntities.Add(new LqKdKjbsyjEntity
  2973 + // {
  2974 + // Id = YitIdHelper.NextId().ToString(),
  2975 + // Glkdbh = billingId,
  2976 + // Kjbls = kjbs.TechTeacherId,
  2977 + // Kjblsxm = kjbs.TechTeacherName,
  2978 + // Kjblszh = kjbs.TechTeacherAccount,
  2979 + // Kjblsyj = kjbs.PerformanceAmount.ToString(),
  2980 + // Yjsj = transferTime,
  2981 + // Kdpxid = billingPxmxEntity.Id,
  2982 + // LaborCost = kjbs.LaborCost,
  2983 + // IsEffective = StatusEnum.有效.GetHashCode()
  2984 + // });
  2985 + // }
  2986 + // }
  2987 + // }
  2988 +
  2989 + // await _db.Insertable(billingPxmxEntities).ExecuteCommandAsync();
  2990 + // if (billingJksyjEntities.Any())
  2991 + // {
  2992 + // await _db.Insertable(billingJksyjEntities).ExecuteCommandAsync();
  2993 + // }
  2994 + // if (billingKjbsyjEntities.Any())
  2995 + // {
  2996 + // await _db.Insertable(billingKjbsyjEntities).ExecuteCommandAsync();
  2997 + // }
  2998 + // // 提交事务
  2999 + // _db.CommitTran();
  3000 +
  3001 + // return new TransferCardOutput
  3002 + // {
  3003 + // Success = true,
  3004 + // TransferId = refundId,
  3005 + // RefundId = refundId,
  3006 + // BillingId = billingId,
  3007 + // TotalAmount = input.TransferItems.Sum(x => x.ItemPrice * x.TransferQuantity),
  3008 + // TotalQuantity = input.TransferItems.Sum(x => x.TransferQuantity),
  3009 + // FromMemberName = fromMember.Khmc,
  3010 + // ToMemberName = toMember.Khmc,
  3011 + // TransferTime = transferTime,
  3012 + // Message = "转卡操作成功"
  3013 + // };
  3014 + // }
  3015 + // catch (Exception ex)
  3016 + // {
  3017 + // _db.RollbackTran();
  3018 + // _logger.LogError(ex, "转卡操作失败:{Message}", ex.Message);
  3019 + // throw NCCException.Oh($"转卡操作失败:{ex.Message}");
  3020 + // }
  3021 + // }
  3022 + // #endregion
  3023 +
2672 3024 #region 获取品项剩余数量
2673 3025 /// <summary>
2674 3026 /// 获取品项剩余数量
... ...
netcore/src/Modularity/Extend/NCC.Extend/LqTkjlbService.cs
... ... @@ -531,7 +531,7 @@ namespace NCC.Extend.LqTkjlb
531 531  
532 532 #region 漏斗统计
533 533 /// <summary>
534   - /// 获取拓客活动漏斗统计数据
  534 + /// 获取拓客活动漏斗统计数据(优化版本)
535 535 /// </summary>
536 536 /// <param name="eventId">活动ID</param>
537 537 /// <returns>漏斗统计数据</returns>
... ... @@ -540,31 +540,49 @@ namespace NCC.Extend.LqTkjlb
540 540 {
541 541 try
542 542 {
  543 + // 优化版本:使用子查询减少JOIN复杂度
543 544 var sql = @"
544 545 SELECT
545 546 md.F_Id as store_id,
546 547 md.dm as store_name,
547   - COUNT(DISTINCT tk.F_Id) as tk_count, -- 拓客数量
548   - COUNT(DISTINCT CASE WHEN yy.F_Id IS NOT NULL THEN tk.F_CustomerPhone END) as yy_count, -- 预约人数(已确认)
549   - COUNT(DISTINCT CASE WHEN xh.F_Id IS NOT NULL THEN tk.F_CustomerPhone END) as hk_count, -- 耗卡人数
550   - COALESCE(SUM(CASE WHEN xh.F_Id IS NOT NULL THEN xh.xfje END), 0) as hk_amount, -- 耗卡金额
551   - ROUND(
552   - COUNT(DISTINCT CASE WHEN yy.F_Id IS NOT NULL THEN tk.F_CustomerPhone END) * 100.0 /
553   - COUNT(DISTINCT tk.F_Id), 2
554   - ) as yy_conversion_rate, -- 预约转化率(预约人数/拓客数量)
555   - ROUND(
556   - COUNT(DISTINCT CASE WHEN xh.F_Id IS NOT NULL THEN tk.F_CustomerPhone END) * 100.0 /
557   - COUNT(DISTINCT CASE WHEN yy.F_Id IS NOT NULL THEN tk.F_CustomerPhone END), 2
558   - ) as hk_conversion_rate -- 耗卡转化率(耗卡人数/预约人数)
559   - FROM lq_tkjlb tk
560   - JOIN lq_mdxx md ON tk.F_StoreId = md.F_Id
561   - LEFT JOIN lq_yyjl yy ON tk.F_MemberId = yy.gk
562   - AND yy.F_Status = '已确认'
563   - LEFT JOIN lq_xh_hyhk xh ON tk.F_MemberId = xh.hy
564   - AND xh.F_IsEffective = 1
565   - WHERE tk.F_EventId = @eventId
566   - GROUP BY md.F_Id, md.dm
567   - ORDER BY tk_count DESC";
  548 + tk_stats.tk_count,
  549 + tk_stats.yaoy_count,
  550 + tk_stats.yy_count,
  551 + tk_stats.hk_count,
  552 + tk_stats.kd_count,
  553 + tk_stats.hk_amount,
  554 + tk_stats.kd_amount,
  555 + CASE
  556 + WHEN tk_stats.tk_count > 0
  557 + THEN ROUND(tk_stats.yy_count * 100.0 / tk_stats.tk_count, 2)
  558 + ELSE 0
  559 + END as yy_conversion_rate,
  560 + CASE
  561 + WHEN tk_stats.yy_count > 0
  562 + THEN ROUND(tk_stats.hk_count * 100.0 / tk_stats.yy_count, 2)
  563 + ELSE 0
  564 + END as hk_conversion_rate
  565 + FROM lq_mdxx md
  566 + LEFT JOIN (
  567 + SELECT
  568 + tk.F_StoreId,
  569 + COUNT(DISTINCT tk.F_Id) as tk_count,
  570 + COUNT(DISTINCT yy.F_Id) as yaoy_count,
  571 + COUNT(DISTINCT yyjl.F_Id) as yy_count,
  572 + COUNT(DISTINCT xh.F_Id) as hk_count,
  573 + COUNT(DISTINCT kd.F_Id) as kd_count,
  574 + COALESCE(SUM(xh.xfje), 0) as hk_amount,
  575 + COALESCE(SUM(kd.sfyj), 0) as kd_amount
  576 + FROM lq_tkjlb tk
  577 + LEFT JOIN lq_yaoyjl yy ON tk.F_MemberId = yy.yykh AND yy.F_StoreId = tk.F_StoreId
  578 + LEFT JOIN lq_yyjl yyjl ON tk.F_MemberId = yyjl.gk AND yyjl.F_Status = '已确认'
  579 + LEFT JOIN lq_xh_hyhk xh ON tk.F_MemberId = xh.hy AND xh.F_IsEffective = 1
  580 + LEFT JOIN lq_kd_kdjlb kd ON tk.F_MemberId = kd.kdhy AND kd.F_IsEffective = 1
  581 + WHERE tk.F_EventId = @eventId
  582 + GROUP BY tk.F_StoreId
  583 + ) tk_stats ON md.F_Id = tk_stats.F_StoreId
  584 + WHERE tk_stats.F_StoreId IS NOT NULL
  585 + ORDER BY tk_stats.tk_count DESC";
568 586  
569 587 var result = await _db.Ado.SqlQueryAsync<dynamic>(sql, new { eventId });
570 588  
... ... @@ -582,7 +600,7 @@ namespace NCC.Extend.LqTkjlb
582 600 }
583 601  
584 602 /// <summary>
585   - /// 获取拓客活动总体漏斗统计
  603 + /// 获取拓客活动总体漏斗统计(优化版本)
586 604 /// </summary>
587 605 /// <param name="eventId">活动ID</param>
588 606 /// <returns>总体漏斗统计数据</returns>
... ... @@ -593,24 +611,39 @@ namespace NCC.Extend.LqTkjlb
593 611 {
594 612 var sql = @"
595 613 SELECT
596   - COUNT(DISTINCT tk.F_Id) as total_tk_count, -- 总拓客数量
597   - COUNT(DISTINCT CASE WHEN yy.F_Id IS NOT NULL THEN tk.F_CustomerPhone END) as total_yy_count, -- 总预约人数
598   - COUNT(DISTINCT CASE WHEN xh.F_Id IS NOT NULL THEN tk.F_CustomerPhone END) as total_hk_count, -- 总耗卡人数
599   - COALESCE(SUM(CASE WHEN xh.F_Id IS NOT NULL THEN xh.xfje END), 0) as total_hk_amount, -- 总耗卡金额
600   - ROUND(
601   - COUNT(DISTINCT CASE WHEN yy.F_Id IS NOT NULL THEN tk.F_CustomerPhone END) * 100.0 /
602   - COUNT(DISTINCT tk.F_Id), 2
603   - ) as overall_yy_conversion_rate, -- 总体预约转化率
604   - ROUND(
605   - COUNT(DISTINCT CASE WHEN xh.F_Id IS NOT NULL THEN tk.F_CustomerPhone END) * 100.0 /
606   - COUNT(DISTINCT CASE WHEN yy.F_Id IS NOT NULL THEN tk.F_CustomerPhone END), 2
607   - ) as overall_hk_conversion_rate -- 总体耗卡转化率
608   - FROM lq_tkjlb tk
609   - LEFT JOIN lq_yyjl yy ON tk.F_MemberId = yy.gk
610   - AND yy.F_Status = '已确认'
611   - LEFT JOIN lq_xh_hyhk xh ON tk.F_MemberId = xh.hy
612   - AND xh.F_IsEffective = 1
613   - WHERE tk.F_EventId = @eventId";
  614 + SUM(tk_count) as total_tk_count,
  615 + SUM(yaoy_count) as total_yaoy_count,
  616 + SUM(yy_count) as total_yy_count,
  617 + SUM(hk_count) as total_hk_count,
  618 + SUM(kd_count) as total_kd_count,
  619 + SUM(hk_amount) as total_hk_amount,
  620 + SUM(kd_amount) as total_kd_amount,
  621 + CASE
  622 + WHEN SUM(tk_count) > 0
  623 + THEN ROUND(SUM(yy_count) * 100.0 / SUM(tk_count), 2)
  624 + ELSE 0
  625 + END as overall_yy_conversion_rate,
  626 + CASE
  627 + WHEN SUM(yy_count) > 0
  628 + THEN ROUND(SUM(hk_count) * 100.0 / SUM(yy_count), 2)
  629 + ELSE 0
  630 + END as overall_hk_conversion_rate
  631 + FROM (
  632 + SELECT
  633 + COUNT(DISTINCT tk.F_Id) as tk_count,
  634 + COUNT(DISTINCT yy.F_Id) as yaoy_count,
  635 + COUNT(DISTINCT yyjl.F_Id) as yy_count,
  636 + COUNT(DISTINCT xh.F_Id) as hk_count,
  637 + COUNT(DISTINCT kd.F_Id) as kd_count,
  638 + COALESCE(SUM(xh.xfje), 0) as hk_amount,
  639 + COALESCE(SUM(kd.sfyj), 0) as kd_amount
  640 + FROM lq_tkjlb tk
  641 + LEFT JOIN lq_yaoyjl yy ON tk.F_MemberId = yy.yykh AND yy.F_StoreId = tk.F_StoreId
  642 + LEFT JOIN lq_yyjl yyjl ON tk.F_MemberId = yyjl.gk AND yyjl.F_Status = '已确认'
  643 + LEFT JOIN lq_xh_hyhk xh ON tk.F_MemberId = xh.hy AND xh.F_IsEffective = 1
  644 + LEFT JOIN lq_kd_kdjlb kd ON tk.F_MemberId = kd.kdhy AND kd.F_IsEffective = 1
  645 + WHERE tk.F_EventId = @eventId
  646 + ) stats";
614 647  
615 648 var result = await _db.Ado.SqlQueryAsync<dynamic>(sql, new { eventId });
616 649  
... ... @@ -626,6 +659,113 @@ namespace NCC.Extend.LqTkjlb
626 659 throw NCCException.Oh("获取总体漏斗统计数据失败:" + ex.Message);
627 660 }
628 661 }
  662 +
  663 + /// <summary>
  664 + /// 获取拓客活动漏斗统计数据(高性能版本 - 分步查询)
  665 + /// </summary>
  666 + /// <param name="eventId">活动ID</param>
  667 + /// <returns>漏斗统计数据</returns>
  668 + [HttpGet("GetFunnelStatisticsFast/{eventId}")]
  669 + public async Task<dynamic> GetFunnelStatisticsFast(string eventId)
  670 + {
  671 + try
  672 + {
  673 + // 第一步:获取拓客基础数据
  674 + var tkSql = @"
  675 + SELECT
  676 + tk.F_StoreId,
  677 + md.dm as store_name,
  678 + COUNT(DISTINCT tk.F_Id) as tk_count
  679 + FROM lq_tkjlb tk
  680 + JOIN lq_mdxx md ON tk.F_StoreId = md.F_Id
  681 + WHERE tk.F_EventId = @eventId
  682 + GROUP BY tk.F_StoreId, md.dm";
  683 +
  684 + var tkData = await _db.Ado.SqlQueryAsync<dynamic>(tkSql, new { eventId });
  685 + var tkDict = tkData.ToDictionary(x => (string)x.F_StoreId, x => x);
  686 +
  687 + // 第二步:获取邀约数据
  688 + var yaoySql = @"
  689 + SELECT
  690 + tk.F_StoreId,
  691 + COUNT(DISTINCT yy.F_Id) as yaoy_count
  692 + FROM lq_tkjlb tk
  693 + LEFT JOIN lq_yaoyjl yy ON tk.F_MemberId = yy.yykh AND yy.F_StoreId = tk.F_StoreId
  694 + WHERE tk.F_EventId = @eventId
  695 + GROUP BY tk.F_StoreId";
  696 +
  697 + var yaoyData = await _db.Ado.SqlQueryAsync<dynamic>(yaoySql, new { eventId });
  698 + var yaoyDict = yaoyData.ToDictionary(x => (string)x.F_StoreId, x => (int)x.yaoy_count);
  699 +
  700 + // 第三步:获取预约数据
  701 + var yySql = @"
  702 + SELECT
  703 + tk.F_StoreId,
  704 + COUNT(DISTINCT yyjl.F_Id) as yy_count
  705 + FROM lq_tkjlb tk
  706 + LEFT JOIN lq_yyjl yyjl ON tk.F_MemberId = yyjl.gk AND yyjl.F_Status = '已确认'
  707 + WHERE tk.F_EventId = @eventId
  708 + GROUP BY tk.F_StoreId";
  709 +
  710 + var yyData = await _db.Ado.SqlQueryAsync<dynamic>(yySql, new { eventId });
  711 + var yyDict = yyData.ToDictionary(x => (string)x.F_StoreId, x => (int)x.yy_count);
  712 +
  713 + // 第四步:获取耗卡数据
  714 + var hkSql = @"
  715 + SELECT
  716 + tk.F_StoreId,
  717 + COUNT(DISTINCT xh.F_Id) as hk_count,
  718 + COALESCE(SUM(xh.xfje), 0) as hk_amount
  719 + FROM lq_tkjlb tk
  720 + LEFT JOIN lq_xh_hyhk xh ON tk.F_MemberId = xh.hy AND xh.F_IsEffective = 1
  721 + WHERE tk.F_EventId = @eventId
  722 + GROUP BY tk.F_StoreId";
  723 +
  724 + var hkData = await _db.Ado.SqlQueryAsync<dynamic>(hkSql, new { eventId });
  725 + var hkDict = hkData.ToDictionary(x => (string)x.F_StoreId, x => new { count = (int)x.hk_count, amount = (decimal)x.hk_amount });
  726 +
  727 + // 第五步:获取开单数据
  728 + var kdSql = @"
  729 + SELECT
  730 + tk.F_StoreId,
  731 + COUNT(DISTINCT kd.F_Id) as kd_count,
  732 + COALESCE(SUM(kd.sfyj), 0) as kd_amount
  733 + FROM lq_tkjlb tk
  734 + LEFT JOIN lq_kd_kdjlb kd ON tk.F_MemberId = kd.kdhy AND kd.F_IsEffective = 1
  735 + WHERE tk.F_EventId = @eventId
  736 + GROUP BY tk.F_StoreId";
  737 +
  738 + var kdData = await _db.Ado.SqlQueryAsync<dynamic>(kdSql, new { eventId });
  739 + var kdDict = kdData.ToDictionary(x => (string)x.F_StoreId, x => new { count = (int)x.kd_count, amount = (decimal)x.kd_amount });
  740 +
  741 + // 合并结果
  742 + var result = tkDict.Values.Select(tk => new
  743 + {
  744 + store_id = tk.F_StoreId,
  745 + store_name = tk.store_name,
  746 + tk_count = (int)tk.tk_count,
  747 + yaoy_count = yaoyDict.ContainsKey(tk.F_StoreId) ? yaoyDict[tk.F_StoreId] : 0,
  748 + yy_count = yyDict.ContainsKey(tk.F_StoreId) ? yyDict[tk.F_StoreId] : 0,
  749 + hk_count = hkDict.ContainsKey(tk.F_StoreId) ? hkDict[tk.F_StoreId].count : 0,
  750 + kd_count = kdDict.ContainsKey(tk.F_StoreId) ? kdDict[tk.F_StoreId].count : 0,
  751 + hk_amount = hkDict.ContainsKey(tk.F_StoreId) ? hkDict[tk.F_StoreId].amount : 0m,
  752 + kd_amount = kdDict.ContainsKey(tk.F_StoreId) ? kdDict[tk.F_StoreId].amount : 0m,
  753 + yy_conversion_rate = (int)tk.tk_count > 0 ? Math.Round((yyDict.ContainsKey(tk.F_StoreId) ? yyDict[tk.F_StoreId] : 0) * 100.0 / (int)tk.tk_count, 2) : 0,
  754 + hk_conversion_rate = (yyDict.ContainsKey(tk.F_StoreId) ? yyDict[tk.F_StoreId] : 0) > 0 ? Math.Round((hkDict.ContainsKey(tk.F_StoreId) ? hkDict[tk.F_StoreId].count : 0) * 100.0 / (yyDict.ContainsKey(tk.F_StoreId) ? yyDict[tk.F_StoreId] : 0), 2) : 0
  755 + }).OrderByDescending(x => x.tk_count).ToList();
  756 +
  757 + return new
  758 + {
  759 + success = true,
  760 + data = result,
  761 + message = "获取漏斗统计数据成功(高性能版本)"
  762 + };
  763 + }
  764 + catch (Exception ex)
  765 + {
  766 + throw NCCException.Oh("获取漏斗统计数据失败:" + ex.Message);
  767 + }
  768 + }
629 769 #endregion
630 770  
631 771 #region 门店顾客详情
... ...
netcore/src/Modularity/Extend/NCC.Extend/LqXmzlService.cs
... ... @@ -292,13 +292,13 @@ namespace NCC.Extend.LqXmzl
292 292 /// </summary>
293 293 /// <param name="fieldName">字段名称,支持:fl1、fl2、fl3、fl4、fl、qt1、beautyType、sourceType</param>
294 294 /// <returns>去重后的字段数据</returns>
295   - [HttpPost("GetDistinctFieldData")]
296   - public async Task<dynamic> GetDistinctFieldData([FromBody] string fieldName)
  295 + [HttpGet("GetDistinctFieldData")]
  296 + public async Task<dynamic> GetDistinctFieldData([FromQuery] string fieldName)
297 297 {
298 298 try
299 299 {
300 300 // 验证字段名称
301   - var validFields = new[] { "fl1", "fl2", "fl3", "fl4", "fl", "qt1", "beautyType", "sourceType" };
  301 + var validFields = new[] { "fl1", "fl2", "fl3", "fl4", "fl", "qt1", "qt2", "beautyType", "sourceType" };
302 302 if (!validFields.Contains(fieldName))
303 303 {
304 304 throw NCCException.Oh($"无效的字段名称: {fieldName}。支持的字段: {string.Join(", ", validFields)}");
... ... @@ -350,6 +350,13 @@ namespace NCC.Extend.LqXmzl
350 350 .Distinct()
351 351 .ToListAsync();
352 352 break;
  353 + case "qt2":
  354 + distinctValues = await _db.Queryable<LqXmzlEntity>()
  355 + .Where(p => !string.IsNullOrEmpty(p.Qt2))
  356 + .Select(p => p.Qt2)
  357 + .Distinct()
  358 + .ToListAsync();
  359 + break;
353 360 case "beautyType":
354 361 distinctValues = await _db.Queryable<LqXmzlEntity>()
355 362 .Where(p => !string.IsNullOrEmpty(p.BeautyType))
... ...