Commit e745bcc24062caa5748de0d97678e3a00e605f7b

Authored by “wangming”
1 parent 89aeb0ca

修复转卡方法中的空引用异常和First()方法错误

- 修复金三角用户查询中的First()方法错误,使用ToListAsync() + FirstOrDefault()避免异常
- 添加空值检查,当健康师在金三角团队中不存在时提供友好错误信息
- 完善转卡记录字段,添加门店名称、门店编号、会员账号、顾客类型、退卡时间等字段
- 完善开单记录字段,添加开单日期、顾客类型、总业绩等字段
- 修复金三角用户查询条件,使用正确的用户账号字段(jkszh)而不是用户ID(jks)
- 提高转卡操作的健壮性和错误处理能力
netcore/src/Modularity/Extend/NCC.Extend/LqHytkHytkService.cs
... ... @@ -96,6 +96,7 @@ namespace NCC.Extend.LqHytkHytk
96 96 .WhereIF(queryTksj != null, p => p.Tksj >= new DateTime(startTksj.ToDate().Year, startTksj.ToDate().Month, startTksj.ToDate().Day, 0, 0, 0))
97 97 .WhereIF(queryTksj != null, p => p.Tksj <= new DateTime(endTksj.ToDate().Year, endTksj.ToDate().Month, endTksj.ToDate().Day, 23, 59, 59))
98 98 .WhereIF(!string.IsNullOrEmpty(input.czry), p => p.Czry.Equals(input.czry))
  99 + .WhereIF(input.isEffective != 0, p => p.IsEffective == input.isEffective)
99 100 .Select(it => new LqHytkHytkListOutput
100 101 {
101 102 id = it.Id,
... ...
netcore/src/Modularity/Extend/NCC.Extend/LqKdKdjlbService.cs
... ... @@ -43,6 +43,7 @@ using NCC.System.Entitys.Permission;
43 43 using SqlSugar;
44 44 using Yitter.IdGenerator;
45 45 using NCC.Extend.Entitys.lq_package_info;
  46 +using NCC.Extend.Entitys.lq_mdxx;
46 47  
47 48 namespace NCC.Extend.LqKdKdjlb
48 49 {
... ... @@ -216,9 +217,9 @@ namespace NCC.Extend.LqKdKdjlb
216 217 .WhereIF(queryKdrq != null, p => p.Kdrq <= new DateTime(endKdrq.ToDate().Year, endKdrq.ToDate().Month, endKdrq.ToDate().Day, 23, 59, 59))
217 218 .WhereIF(!string.IsNullOrEmpty(input.gjlx), p => p.Gjlx.Equals(input.gjlx))
218 219 .WhereIF(!string.IsNullOrEmpty(input.hgjg), p => p.Hgjg.Equals(input.hgjg))
219   - .WhereIF(!string.IsNullOrEmpty(input.zdyj), p => p.Zdyj.Equals(input.zdyj))
220   - .WhereIF(!string.IsNullOrEmpty(input.sfyj), p => p.Sfyj.Equals(input.sfyj))
221   - .WhereIF(!string.IsNullOrEmpty(input.qk), p => p.Qk.Equals(input.qk))
  220 + .WhereIF(input.zdyj != null, p => p.Zdyj > input.zdyj)
  221 + .WhereIF(input.sfyj != null, p => p.Sfyj > input.sfyj)
  222 + .WhereIF(input.qk != null, p => p.Qk > input.qk)
222 223 .WhereIF(!string.IsNullOrEmpty(input.ckfs), p => p.Ckfs.Equals(input.ckfs))
223 224 .WhereIF(!string.IsNullOrEmpty(input.fkfs), p => p.Fkfs.Equals(input.fkfs))
224 225 .WhereIF(!string.IsNullOrEmpty(input.fkyy), p => p.Fkyy.Equals(input.fkyy))
... ... @@ -233,6 +234,7 @@ namespace NCC.Extend.LqKdKdjlb
233 234 .WhereIF(!string.IsNullOrEmpty(input.kdhysjh), p => p.Kdhysjh.Contains(input.kdhysjh))
234 235 .WhereIF(!string.IsNullOrEmpty(input.F_FIleUrl), p => p.F_FIleUrl.Contains(input.F_FIleUrl))
235 236 .WhereIF(!string.IsNullOrEmpty(input.CreateUser), p => p.CreateUser.Equals(input.CreateUser))
  237 + .WhereIF(input.isEffective != 0, p => p.IsEffective == input.isEffective)
236 238 .Select(it => new LqKdKdjlbListOutput
237 239 {
238 240 id = it.Id,
... ... @@ -781,9 +783,9 @@ namespace NCC.Extend.LqKdKdjlb
781 783 .WhereIF(queryKdrq != null, p => p.Kdrq <= new DateTime(endKdrq.ToDate().Year, endKdrq.ToDate().Month, endKdrq.ToDate().Day, 23, 59, 59))
782 784 .WhereIF(!string.IsNullOrEmpty(input.gjlx), p => p.Gjlx.Equals(input.gjlx))
783 785 .WhereIF(!string.IsNullOrEmpty(input.hgjg), p => p.Hgjg.Equals(input.hgjg))
784   - .WhereIF(!string.IsNullOrEmpty(input.zdyj), p => p.Zdyj.Equals(input.zdyj))
785   - .WhereIF(!string.IsNullOrEmpty(input.sfyj), p => p.Sfyj.Equals(input.sfyj))
786   - .WhereIF(!string.IsNullOrEmpty(input.qk), p => p.Qk.Equals(input.qk))
  786 + .WhereIF(input.zdyj != null, p => p.Zdyj > input.zdyj)
  787 + .WhereIF(input.sfyj != null, p => p.Sfyj > input.sfyj)
  788 + .WhereIF(input.qk != null, p => p.Qk > input.qk)
787 789 .WhereIF(!string.IsNullOrEmpty(input.ckfs), p => p.Ckfs.Equals(input.ckfs))
788 790 .WhereIF(!string.IsNullOrEmpty(input.fkfs), p => p.Fkfs.Equals(input.fkfs))
789 791 .WhereIF(!string.IsNullOrEmpty(input.fkyy), p => p.Fkyy.Equals(input.fkyy))
... ... @@ -797,6 +799,7 @@ namespace NCC.Extend.LqKdKdjlb
797 799 .WhereIF(!string.IsNullOrEmpty(input.kdhyc), p => p.Kdhyc.Contains(input.kdhyc))
798 800 .WhereIF(!string.IsNullOrEmpty(input.kdhysjh), p => p.Kdhysjh.Contains(input.kdhysjh))
799 801 .WhereIF(!string.IsNullOrEmpty(input.F_FIleUrl), p => p.F_FIleUrl.Contains(input.F_FIleUrl))
  802 + .WhereIF(input.isEffective != 0, p => p.IsEffective == input.isEffective)
800 803 .Select(it => new LqKdKdjlbListOutput
801 804 {
802 805 id = it.Id,
... ... @@ -2324,6 +2327,7 @@ namespace NCC.Extend.LqKdKdjlb
2324 2327 /// 转卡是指一个会员把自己剩余的某些品项,转给另外一个会员
2325 2328 /// 转卡会员转出的品项,就做退卡操作
2326 2329 /// 被转卡会员收到的品项,做开卡操作
  2330 + /// 系统会自动从原开单记录中获取健康师和科技部老师的业绩数据
2327 2331 ///
2328 2332 /// 示例请求:
2329 2333 /// ```json
... ... @@ -2331,68 +2335,37 @@ namespace NCC.Extend.LqKdKdjlb
2331 2335 /// "fromMemberId": "member001",
2332 2336 /// "toMemberId": "member002",
2333 2337 /// "storeId": "store001",
2334   - /// "signatureFile": "签名文件路径",
2335   - /// "remarks": "转卡备注",
  2338 + /// "signatureFile": "base64签名数据",
  2339 + /// "remarks": "转卡备注信息",
2336 2340 /// "transferItems": [
2337 2341 /// {
2338   - /// "billingItemId": "item001",
  2342 + /// "billingItemId": "pxmx001",
2339 2343 /// "transferQuantity": 5,
2340   - /// "itemId": "px001",
2341   - /// "itemName": "美容项目",
2342   - /// "itemPrice": 100.00,
2343   - /// "sourceType": "转卡",
2344   - /// "healthTeacherPerformances": [
2345   - /// {
2346   - /// "healthTeacherId": "jks001",
2347   - /// "healthTeacherName": "张医生",
2348   - /// "healthTeacherAccount": "zhang001",
2349   - /// "performanceAmount": 50.00,
2350   - /// "laborCost": 20.00,
2351   - /// "itemQuantity": 5
2352   - /// }
2353   - /// ],
2354   - /// "techTeacherPerformances": [
2355   - /// {
2356   - /// "techTeacherId": "kjbs001",
2357   - /// "techTeacherName": "李老师",
2358   - /// "techTeacherAccount": "li001",
2359   - /// "performanceAmount": 30.00,
2360   - /// "laborCost": 10.00,
2361   - /// "itemQuantity": 5
2362   - /// }
2363   - /// ]
  2344 + /// "itemName": "美容项目A",
  2345 + /// "itemPrice": 100.00
2364 2346 /// }
2365 2347 /// ]
2366 2348 /// }
2367 2349 /// ```
2368 2350 ///
2369 2351 /// 参数说明:
2370   - /// - fromMemberId: 转出会员ID
2371   - /// - toMemberId: 转入会员ID
2372   - /// - storeId: 门店ID
2373   - /// - signatureFile: 转出会员签名
2374   - /// - remarks: 转卡备注
2375   - /// - transferItems: 转出品项列表
2376   - /// - billingItemId: 开单品项明细ID
2377   - /// - transferQuantity: 转出数量
2378   - /// - itemId: 品项ID
2379   - /// - itemName: 品项名称
2380   - /// - itemPrice: 品项价格
2381   - /// - sourceType: 来源类型
2382   - /// - healthTeacherPerformances: 健康师业绩列表
2383   - /// - healthTeacherId: 健康师ID
2384   - /// - healthTeacherName: 健康师姓名
2385   - /// - healthTeacherAccount: 健康师账号
2386   - /// - performanceAmount: 业绩金额
2387   - /// - laborCost: 人工成本
2388   - /// - itemQuantity: 品项数量
2389   - /// - techTeacherPerformances: 科技部老师业绩列表
2390   - /// - techTeacherId: 科技部老师ID
2391   - /// - techTeacherName: 科技部老师姓名
2392   - /// - techTeacherAccount: 科技部老师账号
2393   - /// - performanceAmount: 业绩金额
2394   - /// - laborCost: 人工成本
2395   - /// - itemQuantity: 品项数量
  2352 + /// - fromMemberId: 转出会员ID(必填)
  2353 + /// - toMemberId: 转入会员ID(必填)
  2354 + /// - storeId: 门店ID(必填)
  2355 + /// - signatureFile: 转出会员签名文件(必填)
  2356 + /// - remarks: 转卡备注信息(可选)
  2357 + /// - transferItems: 转出品项列表(必填)
  2358 + /// - billingItemId: 开单品项明细ID(必填)
  2359 + /// - transferQuantity: 转出数量(必填,大于0)
  2360 + /// - itemName: 品项名称(必填)
  2361 + /// - itemPrice: 品项价格(必填,大于等于0)
  2362 + ///
  2363 + /// 业务规则:
  2364 + /// - 转出和转入会员必须存在且有效
  2365 + /// - 转出品项的剩余数量必须足够
  2366 + /// - 系统会自动从原开单记录中获取健康师和科技部老师业绩数据
  2367 + /// - 转卡操作会同时创建退卡记录和开卡记录
  2368 + /// - 所有操作在事务中执行,确保数据一致性
2396 2369 /// </remarks>
2397 2370 /// <param name="input">转卡输入参数</param>
2398 2371 /// <returns>转卡结果</returns>
... ... @@ -2406,10 +2379,8 @@ namespace NCC.Extend.LqKdKdjlb
2406 2379 {
2407 2380 throw NCCException.Oh("转卡参数不能为空");
2408 2381 }
2409   -
2410 2382 var userInfo = await _userManager.GetUserInfo();
2411 2383 var transferTime = DateTime.Now;
2412   -
2413 2384 try
2414 2385 {
2415 2386 // 开启事务
... ... @@ -2425,7 +2396,6 @@ namespace NCC.Extend.LqKdKdjlb
2425 2396 {
2426 2397 throw NCCException.Oh("转入会员不存在或已失效");
2427 2398 }
2428   -
2429 2399 // 2. 验证转出品项的余额是否足够
2430 2400 foreach (var item in input.TransferItems)
2431 2401 {
... ... @@ -2434,7 +2404,6 @@ namespace NCC.Extend.LqKdKdjlb
2434 2404 {
2435 2405 throw NCCException.Oh($"品项明细不存在:{item.ItemName}");
2436 2406 }
2437   -
2438 2407 // 查询剩余数量
2439 2408 var remainingCount = await GetItemRemainingCount(item.BillingItemId);
2440 2409 if (remainingCount < item.TransferQuantity)
... ... @@ -2446,6 +2415,7 @@ namespace NCC.Extend.LqKdKdjlb
2446 2415 // 3. 创建退卡记录(转出方)
2447 2416 var refundId = YitIdHelper.NextId().ToString();
2448 2417 var totalRefundAmount = input.TransferItems.Sum(x => x.ItemPrice * x.TransferQuantity);
  2418 + var StoreEntity = await _db.Queryable<LqMdxxEntity>().Where(p => p.Id == input.StoreId).FirstAsync();
2449 2419 var refundEntity = new LqHytkHytkEntity
2450 2420 {
2451 2421 Id = refundId,
... ... @@ -2456,7 +2426,12 @@ namespace NCC.Extend.LqKdKdjlb
2456 2426 Hy = input.FromMemberId,
2457 2427 Hymc = fromMember.Khmc,
2458 2428 Md = input.StoreId,
  2429 + Mdmc = StoreEntity.Dm,
  2430 + Mdbh = StoreEntity.Mdbm,
2459 2431 SignatureFile = input.SignatureFile,
  2432 + Hyzh = input.FromMemberId,
  2433 + Gklx = fromMember.Khlx,
  2434 + Tksj = DateTime.Now,
2460 2435 Tkje = totalRefundAmount,
2461 2436 ActualRefundAmount = totalRefundAmount, // 转卡时实退金额等于退卡总金额
2462 2437 Tkyy = "转卡",
... ... @@ -2472,9 +2447,9 @@ namespace NCC.Extend.LqKdKdjlb
2472 2447  
2473 2448 foreach (var item in input.TransferItems)
2474 2449 {
2475   - var refundPxmxEntity = _db.Queryable<LqKdPxmxEntity>().First(p => p.Id == item.BillingItemId);
  2450 + var refundPxmxEntity = await _db.Queryable<LqKdPxmxEntity>().Where(p => p.Id == item.BillingItemId).FirstAsync();
2476 2451 //计算品项扣除总金额
2477   - var 品项扣除总金额 = refundPxmxEntity.Pxjg * item.TransferQuantity;
  2452 + var totalItemDeduction = item.ItemPrice * item.TransferQuantity;
2478 2453 //保存退卡明细表
2479 2454 var refundMxEntity = new LqHytkMxEntity
2480 2455 {
... ... @@ -2483,13 +2458,13 @@ namespace NCC.Extend.LqKdKdjlb
2483 2458 BillingItemId = item.BillingItemId,
2484 2459 CreateTime = transferTime,
2485 2460 CreateUser = userInfo.userId,
2486   - Px = item.ItemId,
  2461 + Px = refundPxmxEntity.Px,
2487 2462 Pxmc = item.ItemName,
2488 2463 Pxjg = item.ItemPrice,
2489   - Tkje = 品项扣除总金额,
  2464 + Tkje = totalItemDeduction,
2490 2465 ProjectNumber = item.TransferQuantity,
2491   - SourceType = item.SourceType,
2492   - TotalPrice = 品项扣除总金额,
  2466 + SourceType = refundPxmxEntity.SourceType,
  2467 + TotalPrice = totalItemDeduction,
2493 2468 IsEffective = StatusEnum.有效.GetHashCode()
2494 2469 };
2495 2470 refundMxEntities.Add(refundMxEntity);
... ... @@ -2499,7 +2474,12 @@ namespace NCC.Extend.LqKdKdjlb
2499 2474 {
2500 2475 //获取健康师当月金三角id
2501 2476 var monthString = DateTime.Now.ToString("yyyyMM");
2502   - var GetMonTeam = _db.Queryable<LqJinsanjiaoUserEntity>().Where(p => p.UserId == jks.Jks && p.Month == monthString).First();
  2477 + var GetMonTeamResult = await _db.Queryable<LqJinsanjiaoUserEntity>().Where(p => p.UserId == jks.Jkszh && p.Month == monthString).ToListAsync();
  2478 + var GetMonTeam = GetMonTeamResult.FirstOrDefault();
  2479 + if (GetMonTeam == null)
  2480 + {
  2481 + throw NCCException.Oh($"健康师 {jks.Jksxm} 在 {monthString} 月份的金三角团队中不存在,请先配置金三角团队信息");
  2482 + }
2503 2483 //获取本月
2504 2484 refundJksyjEntities.Add(new LqHytkJksyjEntity
2505 2485 {
... ... @@ -2508,7 +2488,7 @@ namespace NCC.Extend.LqKdKdjlb
2508 2488 Jks = jks.Jks,
2509 2489 Jksxm = jks.Jksxm,
2510 2490 Jkszh = jks.Jkszh,
2511   - Jksyj = (品项扣除总金额 / refundKdyjEntities.Count()),
  2491 + Jksyj = (totalItemDeduction / refundKdyjEntities.Count()),
2512 2492 Tksj = DateTime.Now,
2513 2493 F_jsjid = GetMonTeam.JsjId,
2514 2494 F_tkpxid = refundMxEntity.Id,
... ... @@ -2531,7 +2511,7 @@ namespace NCC.Extend.LqKdKdjlb
2531 2511 Kjbls = kjbs.Kjbls,
2532 2512 Kjblsxm = kjbs.Kjblsxm,
2533 2513 Kjblszh = kjbs.Kjblszh,
2534   - Kjblsyj = (品项扣除总金额 / TechTeacherEntities.Count()),
  2514 + Kjblsyj = (totalItemDeduction / TechTeacherEntities.Count()),
2535 2515 Tksj = transferTime,
2536 2516 F_tkpxid = refundMxEntity.Id,
2537 2517 F_LaborCost = kjbs.LaborCost,
... ... @@ -2565,8 +2545,11 @@ namespace NCC.Extend.LqKdKdjlb
2565 2545 CreateUser = userInfo.userId,
2566 2546 Kdhy = input.ToMemberId,
2567 2547 Djmd = input.StoreId,
  2548 + Kdrq = DateTime.Now,
  2549 + Gjlx = toMember.Khlx,
2568 2550 Fkfs = "转卡",
2569 2551 Sfskdd = "否",
  2552 + Zdyj = input.TransferItems.Sum(x => x.ItemPrice * x.TransferQuantity),
2570 2553 Sfyj = input.TransferItems.Sum(x => x.ItemPrice * x.TransferQuantity),
2571 2554 Bz = $"从会员 {fromMember.Khmc} 转入,{input.Remarks}"
2572 2555 };
... ... @@ -2578,18 +2561,19 @@ namespace NCC.Extend.LqKdKdjlb
2578 2561  
2579 2562 foreach (var item in input.TransferItems)
2580 2563 {
  2564 + var refundPxmxEntity = await _db.Queryable<LqKdPxmxEntity>().Where(p => p.Id == item.BillingItemId).FirstAsync();
2581 2565 var billingPxmxEntity = new LqKdPxmxEntity
2582 2566 {
2583 2567 Id = YitIdHelper.NextId().ToString(),
2584 2568 Glkdbh = billingId,
2585   - Px = item.ItemId,
  2569 + Px = refundPxmxEntity.Px,
2586 2570 Pxmc = item.ItemName,
2587 2571 Pxjg = item.ItemPrice,
2588 2572 MemberId = input.ToMemberId,
2589 2573 CreateTIme = transferTime,
2590 2574 ProjectNumber = item.TransferQuantity,
2591 2575 IsEnabled = StatusEnum.有效.GetHashCode(),
2592   - SourceType = item.SourceType,
  2576 + SourceType = refundPxmxEntity.SourceType,
2593 2577 TotalPrice = item.ItemPrice * item.TransferQuantity,
2594 2578 ActualPrice = item.ItemPrice * item.TransferQuantity,
2595 2579 IsEffective = StatusEnum.有效.GetHashCode(),
... ... @@ -2666,361 +2650,6 @@ namespace NCC.Extend.LqKdKdjlb
2666 2650 }
2667 2651 #endregion
2668 2652  
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   -
3024 2653 #region 获取品项剩余数量
3025 2654 /// <summary>
3026 2655 /// 获取品项剩余数量
... ... @@ -3032,13 +2661,25 @@ namespace NCC.Extend.LqKdKdjlb
3032 2661 try
3033 2662 {
3034 2663 // 查询购买数量
3035   - var purchasedCount = await _db.Queryable<LqKdPxmxEntity>().Where(x => x.Id == billingItemId && x.IsEffective == StatusEnum.有效.GetHashCode()).Select(x => x.ProjectNumber).FirstAsync();
  2664 + var purchasedCount = await _db.Queryable<LqKdPxmxEntity>()
  2665 + .Where(x => x.Id == billingItemId && x.IsEffective == StatusEnum.有效.GetHashCode())
  2666 + .SumAsync(x => x.ProjectNumber);
  2667 +
3036 2668 // 查询消费数量
3037   - var consumedCount = await _db.Queryable<LqXhPxmxEntity>().Where(x => x.BillingItemId == billingItemId && x.IsEffective == StatusEnum.有效.GetHashCode()).SumAsync(x => x.ProjectNumber);
  2669 + var consumedCount = await _db.Queryable<LqXhPxmxEntity>()
  2670 + .Where(x => x.BillingItemId == billingItemId && x.IsEffective == StatusEnum.有效.GetHashCode())
  2671 + .SumAsync(x => x.ProjectNumber);
  2672 +
3038 2673 // 查询退卡数量
3039   - var refundedCount = await _db.Queryable<LqHytkMxEntity>().Where(x => x.BillingItemId == billingItemId && x.IsEffective == StatusEnum.有效.GetHashCode()).SumAsync(x => x.ProjectNumber);
  2674 + var refundedCount = await _db.Queryable<LqHytkMxEntity>()
  2675 + .Where(x => x.BillingItemId == billingItemId && x.IsEffective == StatusEnum.有效.GetHashCode())
  2676 + .SumAsync(x => x.ProjectNumber);
  2677 +
3040 2678 // 查询储扣数量
3041   - var deductCount = await _db.Queryable<LqKdDeductinfoEntity>().Where(x => x.DeductId == billingItemId && x.IsEffective == StatusEnum.有效.GetHashCode()).SumAsync(x => x.ProjectNumber);
  2679 + var deductCount = await _db.Queryable<LqKdDeductinfoEntity>()
  2680 + .Where(x => x.DeductId == billingItemId && x.IsEffective == StatusEnum.有效.GetHashCode())
  2681 + .SumAsync(x => x.ProjectNumber) ?? 0;
  2682 +
3042 2683 // 计算剩余数量
3043 2684 var remainingCount = (int)(purchasedCount - consumedCount - refundedCount - deductCount);
3044 2685 return Math.Max(0, remainingCount); // 确保不为负数
... ...
netcore/src/Modularity/Extend/NCC.Extend/LqTkjlbService.cs
... ... @@ -684,55 +684,55 @@ namespace NCC.Extend.LqTkjlb
684 684 var tkData = await _db.Ado.SqlQueryAsync<dynamic>(tkSql, new { eventId });
685 685 var tkDict = tkData.ToDictionary(x => (string)x.F_StoreId, x => x);
686 686  
687   - // 第二步:获取邀约数据
  687 + // 第二步:获取邀约数据(按会员ID去重)
688 688 var yaoySql = @"
689 689 SELECT
690 690 tk.F_StoreId,
691   - COUNT(DISTINCT yy.F_Id) as yaoy_count
  691 + COUNT(DISTINCT tk.F_MemberId) as yaoy_count
692 692 FROM lq_tkjlb tk
693 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
  694 + WHERE tk.F_EventId = @eventId AND yy.F_Id IS NOT NULL
695 695 GROUP BY tk.F_StoreId";
696 696  
697 697 var yaoyData = await _db.Ado.SqlQueryAsync<dynamic>(yaoySql, new { eventId });
698 698 var yaoyDict = yaoyData.ToDictionary(x => (string)x.F_StoreId, x => (int)x.yaoy_count);
699 699  
700   - // 第三步:获取预约数据
  700 + // 第三步:获取预约数据(按会员ID去重)
701 701 var yySql = @"
702 702 SELECT
703 703 tk.F_StoreId,
704   - COUNT(DISTINCT yyjl.F_Id) as yy_count
  704 + COUNT(DISTINCT tk.F_MemberId) as yy_count
705 705 FROM lq_tkjlb tk
706 706 LEFT JOIN lq_yyjl yyjl ON tk.F_MemberId = yyjl.gk AND yyjl.F_Status = '已确认'
707   - WHERE tk.F_EventId = @eventId
  707 + WHERE tk.F_EventId = @eventId AND yyjl.F_Id IS NOT NULL
708 708 GROUP BY tk.F_StoreId";
709 709  
710 710 var yyData = await _db.Ado.SqlQueryAsync<dynamic>(yySql, new { eventId });
711 711 var yyDict = yyData.ToDictionary(x => (string)x.F_StoreId, x => (int)x.yy_count);
712 712  
713   - // 第四步:获取耗卡数据
  713 + // 第四步:获取耗卡数据(按会员ID去重,但金额不去重)
714 714 var hkSql = @"
715 715 SELECT
716 716 tk.F_StoreId,
717   - COUNT(DISTINCT xh.F_Id) as hk_count,
  717 + COUNT(DISTINCT tk.F_MemberId) as hk_count,
718 718 COALESCE(SUM(xh.xfje), 0) as hk_amount
719 719 FROM lq_tkjlb tk
720 720 LEFT JOIN lq_xh_hyhk xh ON tk.F_MemberId = xh.hy AND xh.F_IsEffective = 1
721   - WHERE tk.F_EventId = @eventId
  721 + WHERE tk.F_EventId = @eventId AND xh.F_Id IS NOT NULL
722 722 GROUP BY tk.F_StoreId";
723 723  
724 724 var hkData = await _db.Ado.SqlQueryAsync<dynamic>(hkSql, new { eventId });
725 725 var hkDict = hkData.ToDictionary(x => (string)x.F_StoreId, x => new { count = (int)x.hk_count, amount = (decimal)x.hk_amount });
726 726  
727   - // 第五步:获取开单数据
  727 + // 第五步:获取开单数据(按会员ID去重,但金额不去重)
728 728 var kdSql = @"
729 729 SELECT
730 730 tk.F_StoreId,
731   - COUNT(DISTINCT kd.F_Id) as kd_count,
  731 + COUNT(DISTINCT tk.F_MemberId) as kd_count,
732 732 COALESCE(SUM(kd.sfyj), 0) as kd_amount
733 733 FROM lq_tkjlb tk
734 734 LEFT JOIN lq_kd_kdjlb kd ON tk.F_MemberId = kd.kdhy AND kd.F_IsEffective = 1
735   - WHERE tk.F_EventId = @eventId
  735 + WHERE tk.F_EventId = @eventId AND kd.F_Id IS NOT NULL
736 736 GROUP BY tk.F_StoreId";
737 737  
738 738 var kdData = await _db.Ado.SqlQueryAsync<dynamic>(kdSql, new { eventId });
... ... @@ -770,6 +770,91 @@ namespace NCC.Extend.LqTkjlb
770 770  
771 771 #region 门店顾客详情
772 772 /// <summary>
  773 + /// 生成门店顾客详情查询SQL
  774 + /// </summary>
  775 + /// <param name="isPaged">是否分页</param>
  776 + /// <returns>SQL语句</returns>
  777 + private string GetStoreCustomerDetailsSql(bool isPaged = false)
  778 + {
  779 + var baseSql = @"
  780 + SELECT
  781 + tk.F_Id as tk_id, -- 拓客记录ID
  782 + tk.F_CustomerPhone as customer_phone, -- 顾客手机号
  783 + tk.F_MemberId as member_id, -- 会员ID
  784 + tk.F_CustomerName as customer_name, -- 顾客姓名
  785 + tk.F_CreateTime as tk_time, -- 拓客时间
  786 + -- 邀约信息
  787 + yaoy.F_Id as yaoy_id, -- 邀约ID
  788 + yaoy.F_CreateTime as yaoy_time, -- 邀约时间
  789 + yaoy.F_Status as yaoy_status, -- 邀约状态
  790 + -- 预约信息
  791 + yy.F_Id as yy_id, -- 预约ID
  792 + yy.F_Status as yy_status, -- 预约状态
  793 + yy.F_CreateTime as yy_time, -- 预约时间
  794 + yy.yysj as appointment_time, -- 预约到店时间
  795 + -- 耗卡信息(聚合)
  796 + xh_summary.total_consume_amount, -- 耗卡总金额
  797 + xh_summary.consume_count, -- 耗卡次数
  798 + xh_summary.first_consume_time, -- 首次耗卡时间
  799 + xh_summary.last_consume_time, -- 最后耗卡时间
  800 + -- 开卡信息(聚合)
  801 + kd_summary.total_billing_amount, -- 开卡总金额
  802 + kd_summary.total_debt_amount, -- 总欠款金额
  803 + kd_summary.billing_count, -- 开卡次数
  804 + kd_summary.first_billing_time, -- 首次开卡时间
  805 + kd_summary.last_billing_time, -- 最后开卡时间
  806 + -- 状态描述
  807 + CASE
  808 + WHEN yaoy.F_Id IS NOT NULL THEN '已邀约'
  809 + ELSE '未邀约'
  810 + END as invitation_status, -- 邀约状态描述
  811 + CASE
  812 + WHEN yy.F_Id IS NOT NULL THEN '已预约'
  813 + ELSE '未预约'
  814 + END as appointment_status, -- 预约状态描述
  815 + CASE
  816 + WHEN xh_summary.total_consume_amount > 0 THEN '已耗卡'
  817 + ELSE '未耗卡'
  818 + END as consume_status, -- 耗卡状态描述
  819 + CASE
  820 + WHEN kd_summary.total_billing_amount > 0 THEN '已开卡'
  821 + ELSE '未开卡'
  822 + END as billing_status -- 开卡状态描述
  823 + FROM lq_tkjlb tk
  824 + LEFT JOIN lq_yaoyjl yaoy ON tk.F_MemberId = yaoy.yykh
  825 + AND yaoy.F_StoreId = tk.F_StoreId
  826 + LEFT JOIN lq_yyjl yy ON tk.F_MemberId = yy.gk
  827 + AND yy.F_Status = '已确认'
  828 + LEFT JOIN (
  829 + SELECT
  830 + hy as member_id,
  831 + SUM(xfje) as total_consume_amount,
  832 + COUNT(*) as consume_count,
  833 + MIN(F_CreateTime) as first_consume_time,
  834 + MAX(F_CreateTime) as last_consume_time
  835 + FROM lq_xh_hyhk
  836 + WHERE F_IsEffective = 1
  837 + GROUP BY hy
  838 + ) xh_summary ON tk.F_MemberId = xh_summary.member_id
  839 + LEFT JOIN (
  840 + SELECT
  841 + kdhy as member_id,
  842 + SUM(sfyj) as total_billing_amount,
  843 + SUM(qk) as total_debt_amount,
  844 + COUNT(*) as billing_count,
  845 + MIN(F_CreateTime) as first_billing_time,
  846 + MAX(F_CreateTime) as last_billing_time
  847 + FROM lq_kd_kdjlb
  848 + WHERE F_IsEffective = 1
  849 + GROUP BY kdhy
  850 + ) kd_summary ON tk.F_MemberId = kd_summary.member_id
  851 + WHERE tk.F_EventId = @eventId
  852 + AND tk.F_StoreId = @storeId
  853 + ORDER BY tk.F_CreateTime DESC";
  854 + return isPaged ? baseSql + " LIMIT @offset, @pageSize" : baseSql;
  855 + }
  856 +
  857 + /// <summary>
773 858 /// 获取门店拓客活动顾客详情
774 859 /// </summary>
775 860 /// <param name="eventId">活动ID</param>
... ... @@ -780,37 +865,7 @@ namespace NCC.Extend.LqTkjlb
780 865 {
781 866 try
782 867 {
783   - var sql = @"
784   - SELECT
785   - tk.F_Id as tk_id, -- 拓客记录ID
786   - tk.F_CustomerPhone as customer_phone, -- 顾客手机号
787   - tk.F_MemberId as member_id, -- 会员ID
788   - tk.F_CustomerName as customer_name, -- 顾客姓名
789   - tk.F_CreateTime as tk_time, -- 拓客时间
790   - yy.F_Id as yy_id, -- 预约ID
791   - yy.F_Status as yy_status, -- 预约状态
792   - yy.F_CreateTime as yy_time, -- 预约时间
793   - yy.yysj as appointment_time, -- 预约到店时间
794   - xh.F_Id as xh_id, -- 耗卡ID
795   - xh.F_CreateTime as xh_time, -- 耗卡时间
796   - xh.xfje as consume_amount, -- 耗卡金额
797   - CASE
798   - WHEN yy.F_Id IS NOT NULL THEN '已预约'
799   - ELSE '未预约'
800   - END as appointment_status, -- 预约状态描述
801   - CASE
802   - WHEN xh.F_Id IS NOT NULL THEN '已耗卡'
803   - ELSE '未耗卡'
804   - END as consume_status -- 耗卡状态描述
805   - FROM lq_tkjlb tk
806   - LEFT JOIN lq_yyjl yy ON tk.F_MemberId = yy.gk
807   - AND yy.F_Status = '已确认'
808   - LEFT JOIN lq_xh_hyhk xh ON tk.F_MemberId = xh.hy
809   - AND xh.F_IsEffective = 1
810   - WHERE tk.F_EventId = @eventId
811   - AND tk.F_StoreId = @storeId
812   - ORDER BY tk.F_CreateTime DESC";
813   -
  868 + var sql = GetStoreCustomerDetailsSql(false);
814 869 var result = await _db.Ado.SqlQueryAsync<dynamic>(sql, new { eventId, storeId });
815 870  
816 871 return new
... ... @@ -839,44 +894,12 @@ namespace NCC.Extend.LqTkjlb
839 894 {
840 895 try
841 896 {
842   - var sql = @"
843   - SELECT
844   - tk.F_Id as tk_id, -- 拓客记录ID
845   - tk.F_CustomerPhone as customer_phone, -- 顾客手机号
846   - tk.F_MemberId as member_id, -- 会员ID
847   - tk.F_CustomerName as customer_name, -- 顾客姓名
848   - tk.F_CreateTime as tk_time, -- 拓客时间
849   - yy.F_Id as yy_id, -- 预约ID
850   - yy.F_Status as yy_status, -- 预约状态
851   - yy.F_CreateTime as yy_time, -- 预约时间
852   - yy.yysj as appointment_time, -- 预约到店时间
853   - xh.F_Id as xh_id, -- 耗卡ID
854   - xh.F_CreateTime as xh_time, -- 耗卡时间
855   - xh.xfje as consume_amount, -- 耗卡金额
856   - CASE
857   - WHEN yy.F_Id IS NOT NULL THEN '已预约'
858   - ELSE '未预约'
859   - END as appointment_status, -- 预约状态描述
860   - CASE
861   - WHEN xh.F_Id IS NOT NULL THEN '已耗卡'
862   - ELSE '未耗卡'
863   - END as consume_status -- 耗卡状态描述
864   - FROM lq_tkjlb tk
865   - LEFT JOIN lq_yyjl yy ON tk.F_MemberId = yy.gk
866   - AND yy.F_Status = '已确认'
867   - LEFT JOIN lq_xh_hyhk xh ON tk.F_MemberId = xh.hy
868   - AND xh.F_IsEffective = 1
869   - WHERE tk.F_EventId = @eventId
870   - AND tk.F_StoreId = @storeId
871   - ORDER BY tk.F_CreateTime DESC
872   - LIMIT @offset, @pageSize";
873   -
  897 + var sql = GetStoreCustomerDetailsSql(true);
874 898 var countSql = @"
875 899 SELECT COUNT(*) as total
876 900 FROM lq_tkjlb tk
877 901 WHERE tk.F_EventId = @eventId
878 902 AND tk.F_StoreId = @storeId";
879   -
880 903 var offset = (pageIndex - 1) * pageSize;
881 904 var result = await _db.Ado.SqlQueryAsync<dynamic>(sql, new { eventId, storeId, offset, pageSize });
882 905 var totalResult = await _db.Ado.SqlQueryAsync<dynamic>(countSql, new { eventId, storeId });
... ...
netcore/src/Modularity/Extend/NCC.Extend/LqXhHyhkService.cs
... ... @@ -193,6 +193,7 @@ namespace NCC.Extend.LqXhHyhk
193 193 .WhereIF(queryHksj != null, p => p.Hksj >= new DateTime(startHksj.ToDate().Year, startHksj.ToDate().Month, startHksj.ToDate().Day, 0, 0, 0))
194 194 .WhereIF(queryHksj != null, p => p.Hksj <= new DateTime(endHksj.ToDate().Year, endHksj.ToDate().Month, endHksj.ToDate().Day, 23, 59, 59))
195 195 .WhereIF(!string.IsNullOrEmpty(input.czry), p => p.Czry.Equals(input.czry))
  196 + .WhereIF(input.isEffective != 0, p => p.IsEffective == input.isEffective)
196 197 .Select(it => new LqXhHyhkListOutput
197 198 {
198 199 id = it.Id,
... ...
netcore/src/Modularity/Extend/NCC.Extend/LqXmzlService.cs
... ... @@ -15,6 +15,12 @@ using System.Linq;
15 15 using System.Threading.Tasks;
16 16 using NCC.Extend.Entitys.lq_xmzl;
17 17 using NCC.Extend.Entitys.Dto.LqXmzl;
  18 +using NCC.Extend.Entitys.lq_kd_pxmx;
  19 +using NCC.Extend.Entitys.lq_xh_pxmx;
  20 +using NCC.Extend.Entitys.lq_hytk_mx;
  21 +using NCC.Extend.Entitys.lq_kd_kdjlb;
  22 +using NCC.Extend.Entitys.lq_xh_hyhk;
  23 +using NCC.Extend.Entitys.lq_hytk_hytk;
18 24 using Yitter.IdGenerator;
19 25 using NCC.Common.Helper;
20 26 using NCC.JsonSerialization;
... ... @@ -28,7 +34,7 @@ namespace NCC.Extend.LqXmzl
28 34 /// <summary>
29 35 /// 项目资料服务
30 36 /// </summary>
31   - [ApiDescriptionSettings(Tag = "绿纤项目资料服务", Name = "LqXmzl", Order = 200)]
  37 + [ApiDescriptionSettings(Tag = "绿纤品项资料服务", Name = "LqXmzl", Order = 200)]
32 38 [Route("api/Extend/[controller]")]
33 39 public class LqXmzlService : ILqXmzlService, IDynamicApiController, ITransient
34 40 {
... ... @@ -46,7 +52,7 @@ namespace NCC.Extend.LqXmzl
46 52 _userManager = userManager;
47 53 }
48 54  
49   - #region 获取项目资料
  55 + #region 获取品项资料
50 56  
51 57 /// <summary>
52 58 /// 获取项目资料
... ... @@ -62,9 +68,9 @@ namespace NCC.Extend.LqXmzl
62 68 }
63 69 #endregion
64 70  
65   - #region 获取项目资料列表
  71 + #region 获取品项资料列表
66 72 /// <summary>
67   - /// 获取项目资料列表
  73 + /// 获取品项资料列表
68 74 /// </summary>
69 75 /// <param name="input">请求参数</param>
70 76 /// <returns></returns>
... ... @@ -114,9 +120,9 @@ namespace NCC.Extend.LqXmzl
114 120 }
115 121 #endregion
116 122  
117   - #region 新建项目资料
  123 + #region 新建品项资料
118 124 /// <summary>
119   - /// 新建项目资料
  125 + /// 新建品项资料
120 126 /// </summary>
121 127 /// <param name="input">参数</param>
122 128 /// <returns></returns>
... ... @@ -131,9 +137,9 @@ namespace NCC.Extend.LqXmzl
131 137 }
132 138 #endregion
133 139  
134   - #region 获取项目资料无分页列表
  140 + #region 获取品项资料无分页列表
135 141 /// <summary>
136   - /// 获取项目资料无分页列表
  142 + /// 获取品项资料无分页列表
137 143 /// </summary>
138 144 /// <param name="input">请求参数</param>
139 145 /// <returns></returns>
... ... @@ -177,9 +183,9 @@ namespace NCC.Extend.LqXmzl
177 183 }
178 184 #endregion
179 185  
180   - #region 导出项目资料
  186 + #region 导出品项资料
181 187 /// <summary>
182   - /// 导出项目资料
  188 + /// 导出品项资料
183 189 /// </summary>
184 190 /// <param name="input">请求参数</param>
185 191 /// <returns></returns>
... ... @@ -225,9 +231,9 @@ namespace NCC.Extend.LqXmzl
225 231 }
226 232 #endregion
227 233  
228   - #region 批量删除项目资料
  234 + #region 批量删除品项资料
229 235 /// <summary>
230   - /// 批量删除项目资料
  236 + /// 批量删除品项资料
231 237 /// </summary>
232 238 /// <param name="ids">主键数组</param>
233 239 /// <returns></returns>
... ... @@ -257,9 +263,9 @@ namespace NCC.Extend.LqXmzl
257 263 }
258 264 #endregion
259 265  
260   - #region 更新项目资料
  266 + #region 更新品项资料
261 267 /// <summary>
262   - /// 更新项目资料
  268 + /// 更新品项资料
263 269 /// </summary>
264 270 /// <param name="id">主键</param>
265 271 /// <param name="input">参数</param>
... ... @@ -273,9 +279,9 @@ namespace NCC.Extend.LqXmzl
273 279 }
274 280 #endregion
275 281  
276   - #region 删除项目资料
  282 + #region 删除品项资料
277 283 /// <summary>
278   - /// 删除项目资料
  284 + /// 删除品项资料
279 285 /// </summary>
280 286 /// <param name="id">主键</param>
281 287 /// <returns></returns>
... ... @@ -386,6 +392,309 @@ namespace NCC.Extend.LqXmzl
386 392 }
387 393 #endregion
388 394  
  395 + #region 品项维度统计
  396 + /// <summary>
  397 + /// 品项维度统计
  398 + /// </summary>
  399 + /// <remarks>
  400 + /// 按品项维度统计开卡、消耗、退卡等数据
  401 + /// 包括业绩、人数、占比、复购率等指标
  402 + ///
  403 + /// 示例请求:
  404 + /// ```json
  405 + /// {
  406 + /// "startTime": "2024-01-01",
  407 + /// "endTime": "2024-12-31",
  408 + /// "storeId": "store001",
  409 + /// "category": "美容",
  410 + /// "itemId": "item001"
  411 + /// }
  412 + /// ```
  413 + ///
  414 + /// 参数说明:
  415 + /// - startTime: 开始时间(可选)
  416 + /// - endTime: 结束时间(可选)
  417 + /// - storeId: 门店ID(可选)
  418 + /// - category: 品项分类(可选)
  419 + /// - itemId: 品项ID(可选,单个品项统计)
  420 + /// </remarks>
  421 + /// <param name="input">统计输入参数</param>
  422 + /// <returns>品项维度统计数据</returns>
  423 + /// <response code="200">统计成功</response>
  424 + /// <response code="400">参数错误</response>
  425 + /// <response code="500">服务器错误</response>
  426 + [HttpPost("GetItemStatistics")]
  427 + public async Task<dynamic> GetItemStatistics([FromBody] LqXmzlStatisticsInput input)
  428 + {
  429 + try
  430 + {
  431 + // 第一步:获取品项基础信息
  432 + var itemsQuery = _db.Queryable<LqXmzlEntity>()
  433 + .Where(x => x.IsEffective == 1);
  434 +
  435 + if (!string.IsNullOrEmpty(input.ItemId))
  436 + {
  437 + itemsQuery = itemsQuery.Where(x => x.Id == input.ItemId);
  438 + }
  439 +
  440 + if (!string.IsNullOrEmpty(input.Category))
  441 + {
  442 + itemsQuery = itemsQuery.Where(x => x.Fl1 == input.Category || x.Fl2 == input.Category || x.Fl == input.Category);
  443 + }
  444 +
  445 + var items = await itemsQuery.ToListAsync();
  446 +
  447 + if (!items.Any())
  448 + {
  449 + return new
  450 + {
  451 + success = true,
  452 + data = new List<LqXmzlStatisticsOutput>(),
  453 + message = "未找到符合条件的品项"
  454 + };
  455 + }
  456 +
  457 + var itemIds = items.Select(x => x.Id).ToList();
  458 +
  459 + // 第二步:开卡数据统计
  460 + var billingStats = await GetBillingStatistics(itemIds, input);
  461 +
  462 + // 第三步:消耗数据统计
  463 + var consumeStats = await GetConsumeStatistics(itemIds, input);
  464 +
  465 + // 第四步:退卡数据统计
  466 + var refundStats = await GetRefundStatistics(itemIds, input);
  467 +
  468 + // 第五步:计算总数据用于占比计算
  469 + var totalBillingAmount = billingStats.Sum(x => x.BillingAmount);
  470 + var totalConsumeAmount = consumeStats.Sum(x => x.ConsumeAmount);
  471 + var totalBuyers = billingStats.Sum(x => x.TotalBuyers);
  472 +
  473 + // 第六步:合并数据并计算占比
  474 + var result = new List<LqXmzlStatisticsOutput>();
  475 +
  476 + foreach (var item in items)
  477 + {
  478 + var billingData = billingStats.FirstOrDefault(x => x.ItemId == item.Id);
  479 + var consumeData = consumeStats.FirstOrDefault(x => x.ItemId == item.Id);
  480 + var refundData = refundStats.FirstOrDefault(x => x.ItemId == item.Id);
  481 +
  482 + var output = new LqXmzlStatisticsOutput
  483 + {
  484 + ItemId = item.Id,
  485 + ItemName = item.Xmmc,
  486 + ItemNumber = item.Xmbh,
  487 + BillingAmount = billingData?.BillingAmount ?? 0,
  488 + BillingAmountRatio = totalBillingAmount > 0 ? (billingData?.BillingAmount ?? 0) / totalBillingAmount : 0,
  489 + TotalBuyers = billingData?.TotalBuyers ?? 0,
  490 + ItemRatio = totalBuyers > 0 ? (billingData?.TotalBuyers ?? 0) / (decimal)totalBuyers : 0,
  491 + RepeatBuyers = billingData?.RepeatBuyers ?? 0,
  492 + RepeatBuyRate = (billingData?.TotalBuyers ?? 0) > 0 ? (billingData?.RepeatBuyers ?? 0) / (decimal)(billingData?.TotalBuyers ?? 1) : 0,
  493 + ConsumeAmount = consumeData?.ConsumeAmount ?? 0,
  494 + ConsumeAmountRatio = totalConsumeAmount > 0 ? (consumeData?.ConsumeAmount ?? 0) / totalConsumeAmount : 0,
  495 + ConsumePurchaseCount = consumeData?.ConsumePurchaseCount ?? 0,
  496 + ConsumeGiftCount = consumeData?.ConsumeGiftCount ?? 0,
  497 + ConsumeExperienceCount = consumeData?.ConsumeExperienceCount ?? 0,
  498 + RefundAmount = refundData?.RefundAmount ?? 0,
  499 + RefundCount = refundData?.RefundCount ?? 0
  500 + };
  501 +
  502 + result.Add(output);
  503 + }
  504 +
  505 + return new
  506 + {
  507 + success = true,
  508 + data = result,
  509 + message = "品项维度统计成功"
  510 + };
  511 + }
  512 + catch (Exception ex)
  513 + {
  514 + throw NCCException.Oh($"品项维度统计失败:{ex.Message}");
  515 + }
  516 + }
  517 +
  518 + /// <summary>
  519 + /// 获取开卡统计数据
  520 + /// </summary>
  521 + private async Task<List<ItemStatisticsData>> GetBillingStatistics(List<string> itemIds, LqXmzlStatisticsInput input)
  522 + {
  523 + var query = _db.Queryable<LqKdPxmxEntity>()
  524 + .Where(x => itemIds.Contains(x.Px) && x.IsEffective == 1);
  525 +
  526 + // 时间过滤
  527 + if (input.StartTime.HasValue)
  528 + {
  529 + query = query.Where(x => x.CreateTIme >= input.StartTime.Value);
  530 + }
  531 + if (input.EndTime.HasValue)
  532 + {
  533 + query = query.Where(x => x.CreateTIme <= input.EndTime.Value);
  534 + }
  535 +
  536 + // 门店过滤(通过开单记录关联)
  537 + if (!string.IsNullOrEmpty(input.StoreId))
  538 + {
  539 + query = query.InnerJoin<LqKdKdjlbEntity>((px, kd) => px.Glkdbh == kd.Id)
  540 + .Where((px, kd) => kd.Djmd == input.StoreId && kd.IsEffective == 1);
  541 + }
  542 +
  543 + var result = await query
  544 + .GroupBy(x => x.Px)
  545 + .Select(x => new ItemStatisticsData
  546 + {
  547 + ItemId = x.Px,
  548 + BillingAmount = SqlFunc.AggregateSum(x.Pxjg * x.ProjectNumber),
  549 + TotalBuyers = SqlFunc.AggregateCount(x.MemberId),
  550 + RepeatBuyers = 0 // 复购人数需要单独计算
  551 + })
  552 + .ToListAsync();
  553 +
  554 + // 单独计算复购人数
  555 + foreach (var item in result)
  556 + {
  557 + var memberCountQuery = _db.Queryable<LqKdPxmxEntity>()
  558 + .Where(x => x.Px == item.ItemId && x.IsEffective == 1);
  559 +
  560 + if (input.StartTime.HasValue)
  561 + {
  562 + memberCountQuery = memberCountQuery.Where(x => x.CreateTIme >= input.StartTime.Value);
  563 + }
  564 + if (input.EndTime.HasValue)
  565 + {
  566 + memberCountQuery = memberCountQuery.Where(x => x.CreateTIme <= input.EndTime.Value);
  567 + }
389 568  
  569 + if (!string.IsNullOrEmpty(input.StoreId))
  570 + {
  571 + memberCountQuery = memberCountQuery.InnerJoin<LqKdKdjlbEntity>((px, kd) => px.Glkdbh == kd.Id)
  572 + .Where((px, kd) => kd.Djmd == input.StoreId && kd.IsEffective == 1);
  573 + }
  574 +
  575 + var memberStats = await memberCountQuery
  576 + .GroupBy(x => x.MemberId)
  577 + .Having(x => SqlFunc.AggregateCount(x.MemberId) > 1)
  578 + .Select(x => SqlFunc.AggregateCount(x.MemberId))
  579 + .ToListAsync();
  580 +
  581 + item.RepeatBuyers = memberStats.Count;
  582 + }
  583 +
  584 + return result;
  585 + }
  586 +
  587 + /// <summary>
  588 + /// 获取消耗统计数据
  589 + /// </summary>
  590 + private async Task<List<ItemConsumeStatisticsData>> GetConsumeStatistics(List<string> itemIds, LqXmzlStatisticsInput input)
  591 + {
  592 + var query = _db.Queryable<LqXhPxmxEntity>()
  593 + .Where(x => itemIds.Contains(x.Px));
  594 +
  595 + // 时间过滤
  596 + if (input.StartTime.HasValue)
  597 + {
  598 + query = query.Where(x => x.CreateTIme >= input.StartTime.Value);
  599 + }
  600 + if (input.EndTime.HasValue)
  601 + {
  602 + query = query.Where(x => x.CreateTIme <= input.EndTime.Value);
  603 + }
  604 +
  605 + // 门店过滤(通过耗卡记录关联)
  606 + if (!string.IsNullOrEmpty(input.StoreId))
  607 + {
  608 + query = query.InnerJoin<LqXhHyhkEntity>((px, xh) => px.ConsumeInfoId == xh.Id)
  609 + .Where((px, xh) => xh.Md == input.StoreId && xh.IsEffective == 1);
  610 + }
  611 +
  612 + var result = await query
  613 + .GroupBy(x => x.Px)
  614 + .Select(x => new ItemConsumeStatisticsData
  615 + {
  616 + ItemId = x.Px,
  617 + ConsumeAmount = SqlFunc.AggregateSum(x.Pxjg * x.ProjectNumber),
  618 + ConsumePurchaseCount = SqlFunc.AggregateSum(SqlFunc.IIF(x.SourceType == "购买", x.ProjectNumber, 0)),
  619 + ConsumeGiftCount = SqlFunc.AggregateSum(SqlFunc.IIF(x.SourceType == "赠送", x.ProjectNumber, 0)),
  620 + ConsumeExperienceCount = SqlFunc.AggregateSum(SqlFunc.IIF(x.SourceType == "体验", x.ProjectNumber, 0))
  621 + })
  622 + .ToListAsync();
  623 +
  624 + return result;
  625 + }
  626 +
  627 + /// <summary>
  628 + /// 获取退卡统计数据
  629 + /// </summary>
  630 + private async Task<List<ItemRefundStatisticsData>> GetRefundStatistics(List<string> itemIds, LqXmzlStatisticsInput input)
  631 + {
  632 + var query = _db.Queryable<LqHytkMxEntity>()
  633 + .Where(x => itemIds.Contains(x.Px));
  634 +
  635 + // 时间过滤
  636 + if (input.StartTime.HasValue)
  637 + {
  638 + query = query.Where(x => x.Tksj >= input.StartTime.Value);
  639 + }
  640 + if (input.EndTime.HasValue)
  641 + {
  642 + query = query.Where(x => x.Tksj <= input.EndTime.Value);
  643 + }
  644 +
  645 + // 门店过滤(通过退卡记录关联)
  646 + if (!string.IsNullOrEmpty(input.StoreId))
  647 + {
  648 + query = query.InnerJoin<LqHytkHytkEntity>((mx, hytk) => mx.RefundInfoId == hytk.Id)
  649 + .Where((mx, hytk) => hytk.Md == input.StoreId && hytk.IsEffective == 1);
  650 + }
  651 +
  652 + var result = await query
  653 + .GroupBy(x => x.Px)
  654 + .Select(x => new ItemRefundStatisticsData
  655 + {
  656 + ItemId = x.Px,
  657 + RefundAmount = SqlFunc.AggregateSum(x.Tkje ?? 0),
  658 + RefundCount = SqlFunc.AggregateCount(x.Id)
  659 + })
  660 + .ToListAsync();
  661 +
  662 + return result;
  663 + }
  664 + #endregion
  665 +
  666 + }
  667 +
  668 + /// <summary>
  669 + /// 品项统计数据(内部类)
  670 + /// </summary>
  671 + public class ItemStatisticsData
  672 + {
  673 + public string ItemId { get; set; }
  674 + public decimal BillingAmount { get; set; }
  675 + public int TotalBuyers { get; set; }
  676 + public int RepeatBuyers { get; set; }
  677 + }
  678 +
  679 + /// <summary>
  680 + /// 品项消耗统计数据(内部类)
  681 + /// </summary>
  682 + public class ItemConsumeStatisticsData
  683 + {
  684 + public string ItemId { get; set; }
  685 + public decimal ConsumeAmount { get; set; }
  686 + public int ConsumePurchaseCount { get; set; }
  687 + public int ConsumeGiftCount { get; set; }
  688 + public int ConsumeExperienceCount { get; set; }
  689 + }
  690 +
  691 + /// <summary>
  692 + /// 品项退卡统计数据(内部类)
  693 + /// </summary>
  694 + public class ItemRefundStatisticsData
  695 + {
  696 + public string ItemId { get; set; }
  697 + public decimal RefundAmount { get; set; }
  698 + public int RefundCount { get; set; }
390 699 }
391 700 }
... ...