Commit 910e21a04974e54a6f54c777b4957f6db31e1bf3

Authored by “wangming”
1 parent 96e0ccc7

修复开单记录汇总筛选逻辑:当传入筛选条件但无匹配数据时返回空结果,而不是所有记录

netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKdDeductinfo/LqKdDeductinfoListOutput.cs
... ... @@ -95,5 +95,20 @@ namespace NCC.Extend.Entitys.Dto.LqKdDeductinfo
95 95 /// 门店名称
96 96 /// </summary>
97 97 public string StoreName { get; set; }
  98 +
  99 + /// <summary>
  100 + /// 时间期间(开单日期,用于时间范围筛选)
  101 + /// </summary>
  102 + public DateTime? TimePeriod { get; set; }
  103 +
  104 + /// <summary>
  105 + /// 开单类型(购买、赠送、体验等)
  106 + /// </summary>
  107 + public string BillingType { get; set; }
  108 +
  109 + /// <summary>
  110 + /// 合作机构
  111 + /// </summary>
  112 + public string CooperationInstitution { get; set; }
98 113 }
99 114 }
... ...
netcore/src/Modularity/Extend/NCC.Extend/LqKdKdjlbService.cs
... ... @@ -47,6 +47,7 @@ using NCC.Extend.Entitys.lq_mdxx;
47 47 using NCC.Extend.Entitys.lq_card_transfer_log;
48 48 using NCC.Extend.Entitys.Dto.LqDailyReport;
49 49 using NCC.Extend.Entitys.lq_yyjl;
  50 +using NCC.Extend.Entitys.lq_hzf;
50 51  
51 52 namespace NCC.Extend.LqKdKdjlb
52 53 {
... ... @@ -2322,9 +2323,9 @@ namespace NCC.Extend.LqKdKdjlb
2322 2323 }
2323 2324 #endregion
2324 2325  
2325   - #region 获取门店某个时间段的开单记录汇总信息
  2326 + #region 获取门店某个时间段的开单记录汇总信息(台账)
2326 2327 /// <summary>
2327   - /// 获取门店某个时间段的开单记录汇总信息
  2328 + /// 获取门店某个时间段的开单记录汇总信息(台账)
2328 2329 /// </summary>
2329 2330 /// <param name="input">查询参数</param>
2330 2331 /// <returns>开单记录汇总信息</returns>
... ... @@ -2368,12 +2369,15 @@ namespace NCC.Extend.LqKdKdjlb
2368 2369 zdyj = it.Zdyj,
2369 2370 sfyj = it.Sfyj,
2370 2371 qk = it.Qk,
  2372 + hgjg = it.Hgjg,
  2373 + hgjgName = SqlFunc.Subqueryable<LqHzfEntity>().Where(x => x.Id == it.Hgjg).Select(x => x.Hzmc),
2371 2374 kdhyc = SqlFunc.Subqueryable<LqKhxxEntity>().Where(x => x.Id == it.Kdhy).Select(x => x.Khmc),
2372 2375 kdhysjh = SqlFunc.Subqueryable<LqKhxxEntity>().Where(x => x.Id == it.Kdhy).Select(x => x.Sjh),
2373 2376 fkfs = it.Fkfs, // 付款方式
2374 2377 khly = it.Khly, // 客户来源
2375 2378 bz = it.Bz, // 备注
2376 2379 createTime = it.CreateTime
  2380 +
2377 2381 })
2378 2382 .ToListAsync();
2379 2383  
... ... @@ -2491,240 +2495,55 @@ namespace NCC.Extend.LqKdKdjlb
2491 2495 })
2492 2496 .ToListAsync();
2493 2497  
2494   - // 构建按开单记录分组的详细数据
2495   - var detailedRecords = billingRecords.Select(billing => new
2496   - {
2497   - // 基本信息
2498   - date = billing.kdrq?.ToString("yyyy-MM-dd"),
2499   - customerName = billing.kdhyc,
2500   - customerPhone = billing.kdhysjh,
2501   - storeId = billing.djmd,
2502   - goldTriangle = billing.jsj,
2503   - customerType = billing.gjlx,
2504   -
2505   - // 品项分类
2506   - purchasedItems = itemDetails.Where(x => x.glkdbh == billing.id && x.sourceType == "购买").Select(x => new
2507   - {
2508   - id = x.id,
2509   - itemName = x.pxmc,
2510   - itemCode = x.px,
2511   - price = x.pxjg,
2512   - totalPrice = x.totalPrice,
2513   - actualPrice = x.actualPrice,
2514   - projectNumber = x.projectNumber,
2515   - remark = x.remark,
2516   - itemCategory = x.itemCategory,
2517   - }).ToList(),
2518   -
2519   - giftedItems = itemDetails.Where(x => x.glkdbh == billing.id && x.sourceType == "赠送").Select(x => new
2520   - {
2521   - id = x.id,
2522   - itemName = x.pxmc,
2523   - itemCode = x.px,
2524   - price = x.pxjg,
2525   - totalPrice = x.totalPrice,
2526   - actualPrice = x.actualPrice,
2527   - projectNumber = x.projectNumber,
2528   - remark = x.remark,
2529   - itemCategory = x.itemCategory,
2530   - }).ToList(),
2531   -
2532   - experienceItems = itemDetails.Where(x => x.glkdbh == billing.id && x.sourceType == "体验").Select(x => new
2533   - {
2534   - id = x.id,
2535   - itemName = x.pxmc,
2536   - itemCode = x.px,
2537   - price = x.pxjg,
2538   - totalPrice = x.totalPrice,
2539   - actualPrice = x.actualPrice,
2540   - projectNumber = x.projectNumber,
2541   - remark = x.remark,
2542   - itemCategory = x.itemCategory,
2543   - }).ToList(),
2544   -
2545   - // 金额信息
2546   - paidAmount = billing.sfyj,
2547   - debtAmount = billing.qk,
2548   - totalAmount = billing.zdyj,
2549   -
2550   - // 健康师信息
2551   - healthTeachers = healthTeacherData.Where(x => x.glkdbh == billing.id).Select(x => new
2552   - {
2553   - teacherId = x.jks,
2554   - teacherName = x.jksxm,
2555   - teacherAccount = x.jkszh,
2556   - performance = x.jksyj,
2557   - performanceTime = x.yjsj,
2558   - itemDetailId = x.kdpxid,
2559   - goldTriangleId = x.jsj_id,
2560   - itemCategory = x.itemCategory,
2561   - itemId = x.itemId,
2562   - storeId = x.storeId,
2563   - itemName = x.itemName
2564   - }).ToList(),
2565   -
2566   - // 其他信息
2567   - source = billing.khly, // 客户来源
2568   - paymentMethod = billing.fkfs, // 支付方式
2569   - remark = billing.bz, // 备注
2570   - createTime = billing.createTime
2571   - }).OrderByDescending(x => x.date).ToList();
  2498 + // 根据筛选条件过滤开单记录
  2499 + // 收集所有需要匹配的开单ID(取交集)
  2500 + var matchedBillingIds = new List<string>();
  2501 + var hasFilterCondition = false; // 标记是否传入了筛选条件
2572 2502  
2573   - // 构建返回结果
2574   - var result = new
  2503 + // 如果传入了品项分类或品项筛选条件,收集匹配的开单ID
  2504 + if (!string.IsNullOrEmpty(input.ItemCategory) || !string.IsNullOrEmpty(input.ItemId))
2575 2505 {
2576   - success = true,
2577   - data = new
  2506 + hasFilterCondition = true;
  2507 + var itemMatchedIds = itemDetails.Select(x => x.glkdbh).Distinct().ToList();
  2508 + if (matchedBillingIds.Any())
2578 2509 {
2579   - // 汇总统计
2580   - summary = new
2581   - {
2582   - totalCount = billingRecords.Count,
2583   - totalAmount = billingRecords.Sum(x => x.zdyj),
2584   - totalPaidAmount = billingRecords.Sum(x => x.sfyj),
2585   - totalDebt = billingRecords.Sum(x => x.qk),
2586   - totalPurchasedItems = purchasedItems.Count,
2587   - totalGiftedItems = giftedItems.Count,
2588   - totalExperienceItems = experienceItems.Count,
2589   - totalHealthTeachers = healthTeacherData.GroupBy(x => x.jks).Count()
2590   - },
2591   - // 详细记录列表
2592   - records = detailedRecords
2593   - },
2594   - message = "获取开单记录汇总信息成功"
2595   - };
2596   - return result;
2597   - }
2598   - catch (Exception ex)
2599   - {
2600   - throw NCCException.Oh($"获取开单记录汇总信息失败:{ex.Message}");
2601   - }
2602   - }
2603   - #endregion
2604   -
2605   - #region 获取门店某个时间段的开单记录汇总信息(备份)
2606   - /// <summary>
2607   - /// 获取门店某个时间段的开单记录汇总信息(备份)
2608   - /// </summary>
2609   - /// <param name="input">查询参数</param>
2610   - /// <returns>开单记录汇总信息</returns>
2611   - [HttpGet("GetBillingRecordSummaryByStoreId_bak")]
2612   - public async Task<dynamic> GetBillingRecordSummaryByStoreId_bak([FromQuery] BillingRecordSummaryQueryInput input)
2613   - {
2614   - try
2615   - {
2616   - // 验证参数
2617   - if (string.IsNullOrEmpty(input.StoreId))
2618   - {
2619   - throw NCCException.Oh("门店ID不能为空");
  2510 + matchedBillingIds = matchedBillingIds.Intersect(itemMatchedIds).ToList();
  2511 + }
  2512 + else
  2513 + {
  2514 + matchedBillingIds = itemMatchedIds;
  2515 + }
2620 2516 }
2621 2517  
2622   - // 如果开始时间和结束时间为空,就默认是当月
2623   - if (input.StartTime == null || input.EndTime == null)
  2518 + // 如果传入了健康师筛选条件,收集匹配的开单ID
  2519 + if (!string.IsNullOrEmpty(input.HealthCoachId))
2624 2520 {
2625   - input.StartTime = new DateTime(DateTime.Now.Year, DateTime.Now.Month, 1);
2626   - input.EndTime = DateTime.Now.AddDays(DateTime.DaysInMonth(DateTime.Now.Year, DateTime.Now.Month));
2627   - }
2628   -
2629   - // 查询开单记录
2630   - var billingRecords = await _db.Queryable<LqKdKdjlbEntity>()
2631   - .Where(w => w.Djmd == input.StoreId && w.Kdrq >= input.StartTime && w.Kdrq <= input.EndTime && w.IsEffective == StatusEnum.有效.GetHashCode())
2632   - .Select(it => new
  2521 + hasFilterCondition = true;
  2522 + var healthTeacherMatchedIds = healthTeacherData.Select(x => x.glkdbh).Distinct().ToList();
  2523 + if (matchedBillingIds.Any())
2633 2524 {
2634   - id = it.Id,
2635   - djmd = it.Djmd,
2636   - jsj = it.Jsj,
2637   - kdrq = it.Kdrq,
2638   - gjlx = it.Gjlx,
2639   - zdyj = it.Zdyj,
2640   - sfyj = it.Sfyj,
2641   - qk = it.Qk,
2642   - kdhyc = SqlFunc.Subqueryable<LqKhxxEntity>().Where(x => x.Id == it.Kdhy).Select(x => x.Khmc),
2643   - kdhysjh = SqlFunc.Subqueryable<LqKhxxEntity>().Where(x => x.Id == it.Kdhy).Select(x => x.Sjh),
2644   - fkfs = it.Fkfs, // 付款方式
2645   - khly = it.Khly, // 客户来源
2646   - bz = it.Bz, // 备注
2647   - createTime = it.CreateTime
2648   - })
2649   - .ToListAsync();
2650   -
2651   - if (!billingRecords.Any())
2652   - {
2653   - return new
  2525 + matchedBillingIds = matchedBillingIds.Intersect(healthTeacherMatchedIds).ToList();
  2526 + }
  2527 + else
2654 2528 {
2655   - success = true,
2656   - data = new
2657   - {
2658   - billingSummary = new
2659   - {
2660   - totalCount = 0,
2661   - totalAmount = 0,
2662   - totalPaidAmount = 0,
2663   - totalDebt = 0,
2664   - billingRecords = new List<object>()
2665   - },
2666   - itemSummary = new
2667   - {
2668   - purchased = new { count = 0, totalAmount = 0, items = new List<object>() },
2669   - gifted = new { count = 0, totalAmount = 0, items = new List<object>() },
2670   - experience = new { count = 0, totalAmount = 0, items = new List<object>() }
2671   - },
2672   - healthTeacherSummary = new
2673   - {
2674   - totalCount = 0,
2675   - teachers = new List<object>()
2676   - }
2677   - },
2678   - message = "该时间段内无开单记录"
2679   - };
  2529 + matchedBillingIds = healthTeacherMatchedIds;
  2530 + }
2680 2531 }
2681 2532  
2682   - var billingIds = billingRecords.Select(x => x.id).ToList();
2683   -
2684   - // 查询品项明细,按F_SourceType分类
2685   - var itemDetails = await _db.Queryable<LqKdPxmxEntity>()
2686   - .Where(w => billingIds.Contains(w.Glkdbh) && w.IsEffective == StatusEnum.有效.GetHashCode())
2687   - .Select(it => new
  2533 + // 如果传入了筛选条件,必须过滤开单记录(即使没有匹配数据也要过滤,返回空结果)
  2534 + if (hasFilterCondition)
  2535 + {
  2536 + if (matchedBillingIds.Any())
2688 2537 {
2689   - id = it.Id,
2690   - glkdbh = it.Glkdbh,
2691   - px = it.Px,
2692   - pxmc = it.Pxmc,
2693   - pxjg = it.Pxjg,
2694   - sourceType = it.SourceType,
2695   - totalPrice = it.TotalPrice,
2696   - actualPrice = it.ActualPrice,
2697   - projectNumber = it.ProjectNumber,
2698   - remark = it.Remark,
2699   - itemCategory = it.ItemCategory,
2700   - })
2701   - .ToListAsync();
2702   -
2703   - // 按来源类型分类品项
2704   - var purchasedItems = itemDetails.Where(x => x.sourceType == "购买").ToList();
2705   - var giftedItems = itemDetails.Where(x => x.sourceType == "赠送").ToList();
2706   - var experienceItems = itemDetails.Where(x => x.sourceType == "体验").ToList();
2707   -
2708   - // 查询健康师业绩数据
2709   - var healthTeacherData = await _db.Queryable<LqKdJksyjEntity>()
2710   - .Where(w => billingIds.Contains(w.Glkdbh) && w.IsEffective == StatusEnum.有效.GetHashCode())
2711   - .Select(it => new
  2538 + billingRecords = billingRecords.Where(x => matchedBillingIds.Contains(x.id)).ToList();
  2539 + }
  2540 + else
2712 2541 {
2713   - id = it.Id,
2714   - glkdbh = it.Glkdbh,
2715   - jks = it.Jks,
2716   - jksxm = it.Jksxm,
2717   - jkszh = it.Jkszh,
2718   - jksyj = it.Jksyj,
2719   - kdpxid = it.Kdpxid,
2720   - yjsj = it.Yjsj,
2721   - jsj_id = it.Jsj_id,
2722   - itemCategory = it.ItemCategory,
2723   - storeId = it.StoreId,
2724   - itemId = it.ItemId,
2725   - itemName = it.ItemName,
2726   - })
2727   - .ToListAsync();
  2542 + // 传入了筛选条件但没有匹配数据,返回空结果
  2543 + billingRecords = billingRecords.Where(x => false).ToList();
  2544 + }
  2545 + billingIds = billingRecords.Select(x => x.id).ToList();
  2546 + }
2728 2547  
2729 2548 // 构建按开单记录分组的详细数据
2730 2549 var detailedRecords = billingRecords.Select(billing => new
... ... @@ -2736,7 +2555,8 @@ namespace NCC.Extend.LqKdKdjlb
2736 2555 storeId = billing.djmd,
2737 2556 goldTriangle = billing.jsj,
2738 2557 customerType = billing.gjlx,
2739   -
  2558 + cooperationInstitution = billing.hgjg,
  2559 + cooperationInstitutionName = billing.hgjgName,
2740 2560 // 品项分类
2741 2561 purchasedItems = itemDetails.Where(x => x.glkdbh == billing.id && x.sourceType == "购买").Select(x => new
2742 2562 {
... ... @@ -3647,351 +3467,6 @@ namespace NCC.Extend.LqKdKdjlb
3647 3467 }
3648 3468 #endregion
3649 3469  
3650   - #region 门店整体统计表(备份)
3651   - /// <summary>
3652   - /// 门店整体统计表
3653   - /// </summary>
3654   - /// <remarks>
3655   - /// 统计每个健康师在指定时间周期内的各项数据指标
3656   - /// 包括:邀约人数、预约人数、到店人数、开单人数、开单金额、消耗金额、人头、人次、消耗项目数
3657   - ///
3658   - /// 示例请求:
3659   - /// ```json
3660   - /// {
3661   - /// "startTime": "2025-10-01",
3662   - /// "endTime": "2025-10-31",
3663   - /// "departmentId": "部门ID",
3664   - /// "storeId": "门店ID",
3665   - /// "employeeName": "健康师姓名"
3666   - /// }
3667   - /// ```
3668   - ///
3669   - /// 参数说明:
3670   - /// - startTime: 开始时间(可选,默认为当月1号)
3671   - /// - endTime: 结束时间(可选,默认为当前时间)
3672   - /// - departmentId: 事业部ID(可选)
3673   - /// - storeId: 门店ID(可选)
3674   - /// - employeeName: 健康师姓名(可选)
3675   - ///
3676   - /// 返回字段说明:
3677   - /// - EmployeeId: 健康师ID
3678   - /// - EmployeeName: 健康师姓名
3679   - /// - StoreId: 门店ID
3680   - /// - StoreName: 门店名称
3681   - /// - DepartmentId: 事业部ID
3682   - /// - DepartmentName: 事业部名称
3683   - /// - InviteCount: 邀约人数(按客户去重)
3684   - /// - AppointmentCount: 预约人数(按客户去重,无论预约状态)
3685   - /// - VisitCount: 到店人数(按客户去重,仅统计状态为'已确认'的预约)
3686   - /// - BillingCount: 开单人数(按开单记录去重)
3687   - /// - BillingAmount: 开单金额(开单业绩总金额)
3688   - /// - ConsumeAmount: 消耗金额(消耗业绩总金额)
3689   - /// - HeadCount: 人头(按客户去重)
3690   - /// - PersonCount: 人次(按客户+日期去重,同一客户不同天算多次)
3691   - /// - ProjectCount: 消耗项目数(项目总次数)
3692   - /// - RefundAmount: 退卡金额(健康师退卡业绩总金额)
3693   - /// - LaborCost: 手工费(消耗时的手工费总金额)
3694   - /// - OriginalLaborCost: 原始手工费(消耗时的原始手工费总金额)
3695   - /// - OvertimeLaborCost: 加班手工费(消耗时的加班手工费总金额)
3696   - /// </remarks>
3697   - /// <param name="input">查询参数</param>
3698   - /// <returns>健康师统计数据列表</returns>
3699   - /// <response code="200">成功返回统计数据</response>
3700   - /// <response code="400">参数错误</response>
3701   - /// <response code="500">服务器错误</response>
3702   - [HttpGet("get-health-coach-statistics_bak")]
3703   - public async Task<dynamic> GetHealthCoachStatistics_bak([FromQuery] HealthCoachStatisticsQueryInput input)
3704   - {
3705   - try
3706   - {
3707   - // 设置默认时间范围(如果未提供,默认为当月)
3708   - var startTime = input.StartTime ?? new DateTime(DateTime.Now.Year, DateTime.Now.Month, 1);
3709   - var endTime = input.EndTime ?? DateTime.Now;
3710   -
3711   - // 构建SQL查询
3712   - var sql = $@"
3713   - SELECT
3714   - u.F_Id as EmployeeId,
3715   - u.F_REALNAME as EmployeeName,
3716   - u.F_MDID as StoreId,
3717   - md.dm as StoreName,
3718   - md.syb as DepartmentId,
3719   - dept.F_FullName as DepartmentName,
3720   -
3721   - -- 邀约人数
3722   - COALESCE(invite_stats.InviteCount, 0) as InviteCount,
3723   -
3724   - -- 预约人数(无论状态)
3725   - COALESCE(appointment_stats.AppointmentCount, 0) as AppointmentCount,
3726   -
3727   - -- 到店人数(已确认状态)
3728   - COALESCE(visit_stats.VisitCount, 0) as VisitCount,
3729   -
3730   - -- 开单人数和金额
3731   - COALESCE(billing_stats.BillingCount, 0) as BillingCount,
3732   - COALESCE(billing_stats.BillingAmount, 0) as BillingAmount,
3733   -
3734   - -- 消耗相关统计
3735   - COALESCE(consume_stats.ConsumeAmount, 0) as ConsumeAmount,
3736   - CAST(COALESCE(headcount_stats.HeadCount, 0) AS DECIMAL(18,2)) as HeadCount,
3737   - CAST(COALESCE(personcount_stats.PersonCount, 0) AS DECIMAL(18,2)) as PersonCount,
3738   - CAST(COALESCE(invalid_headcount_stats.HeadCount, 0) AS DECIMAL(18,2)) as InvalidHeadCount,
3739   - CAST(COALESCE(invalid_personcount_stats.PersonCount, 0) AS DECIMAL(18,2)) as InvalidPersonCount,
3740   - CAST(COALESCE(consume_stats.ProjectCount, 0) AS DECIMAL(18,2)) as ProjectCount,
3741   -
3742   - -- 退卡金额
3743   - COALESCE(refund_stats.RefundAmount, 0) as RefundAmount,
3744   -
3745   - -- 手工费相关统计
3746   - COALESCE(consume_stats.LaborCost, 0) as LaborCost,
3747   - COALESCE(consume_stats.OriginalLaborCost, 0) as OriginalLaborCost,
3748   - COALESCE(consume_stats.OvertimeLaborCost, 0) as OvertimeLaborCost
3749   -
3750   - FROM BASE_USER u
3751   - LEFT JOIN lq_mdxx md ON u.F_MDID = md.F_Id
3752   - LEFT JOIN base_organize dept ON md.syb = dept.F_Id
3753   -
3754   - -- 邀约统计子查询
3755   - LEFT JOIN (
3756   - SELECT
3757   - yyr as EmployeeId,
3758   - COUNT(DISTINCT yykh) as InviteCount
3759   - FROM lq_yaoyjl
3760   - WHERE yyr IS NOT NULL
3761   - AND F_CreateTime >= @startTime
3762   - AND F_CreateTime <= @endTime
3763   - GROUP BY yyr
3764   - ) invite_stats ON u.F_Id = invite_stats.EmployeeId
3765   -
3766   - -- 预约统计子查询
3767   - LEFT JOIN (
3768   - SELECT
3769   - yyr as EmployeeId,
3770   - COUNT(DISTINCT gk) as AppointmentCount
3771   - FROM lq_yyjl
3772   - WHERE yyr IS NOT NULL
3773   - AND F_CreateTime >= @startTime
3774   - AND F_CreateTime <= @endTime
3775   - GROUP BY yyr
3776   - ) appointment_stats ON u.F_Id = appointment_stats.EmployeeId
3777   -
3778   - -- 到店统计子查询
3779   - LEFT JOIN (
3780   - SELECT
3781   - yyr as EmployeeId,
3782   - COUNT(DISTINCT gk) as VisitCount
3783   - FROM lq_yyjl
3784   - WHERE yyr IS NOT NULL
3785   - AND F_Status = '已确认'
3786   - AND F_CreateTime >= @startTime
3787   - AND F_CreateTime <= @endTime
3788   - GROUP BY yyr
3789   - ) visit_stats ON u.F_Id = visit_stats.EmployeeId
3790   -
3791   - -- 开单统计子查询
3792   - LEFT JOIN (
3793   - SELECT
3794   - jkszh as EmployeeId,
3795   - COUNT(DISTINCT glkdbh) as BillingCount,
3796   - SUM(CAST(jksyj AS DECIMAL(18,2))) as BillingAmount
3797   - FROM lq_kd_jksyj
3798   - WHERE jkszh IS NOT NULL
3799   - AND F_IsEffective = 1
3800   - AND yjsj >= @startTime
3801   - AND yjsj <= @endTime
3802   - GROUP BY jkszh
3803   - ) billing_stats ON u.F_Id = billing_stats.EmployeeId
3804   -
3805   - -- 消耗统计子查询
3806   - LEFT JOIN (
3807   - SELECT
3808   - jksyj.jks as EmployeeId,
3809   - SUM(jksyj.jksyj) as ConsumeAmount,
3810   - CAST(SUM(jksyj.F_kdpxNumber) AS DECIMAL(18,2)) as ProjectCount,
3811   - COALESCE(SUM(jksyj.F_LaborCost), 0) as LaborCost,
3812   - COALESCE(SUM(jksyj.F_OriginalLaborCost), 0) as OriginalLaborCost,
3813   - COALESCE(SUM(jksyj.F_OvertimeLaborCost), 0) as OvertimeLaborCost
3814   - FROM lq_xh_jksyj jksyj
3815   - INNER JOIN lq_xh_hyhk hyhk ON jksyj.glkdbh = hyhk.F_Id
3816   - WHERE jksyj.jks IS NOT NULL
3817   - AND jksyj.F_IsEffective = 1
3818   - AND hyhk.F_IsEffective = 1
3819   - AND hyhk.hksj >= @startTime
3820   - AND hyhk.hksj <= @endTime
3821   - GROUP BY jksyj.jks
3822   - ) consume_stats ON u.F_Id = consume_stats.EmployeeId
3823   -
3824   - -- 有效人头统计子查询(从人次记录表获取,按月份+客户+数量去重后累加数量,F_HasBilling=1)
3825   - LEFT JOIN (
3826   - SELECT
3827   - F_PersonId as EmployeeId,
3828   - CAST(COALESCE(SUM(F_Quantity), 0) AS DECIMAL(18,2)) as HeadCount
3829   - FROM (
3830   - SELECT
3831   - F_PersonId,
3832   - F_WorkMonth,
3833   - F_MemberId,
3834   - F_Quantity
3835   - FROM lq_person_times_record
3836   - WHERE F_PersonId IS NOT NULL
3837   - AND F_IsEffective = 1
3838   - AND F_PersonType = '健康师'
3839   - AND F_HasBilling = 1
3840   - AND F_WorkDate >= DATE(@startTime)
3841   - AND F_WorkDate <= DATE(@endTime)
3842   - GROUP BY F_PersonId, F_WorkMonth, F_MemberId, F_Quantity
3843   - ) as distinct_headcount
3844   - GROUP BY F_PersonId
3845   - ) headcount_stats ON u.F_Id = headcount_stats.EmployeeId
3846   -
3847   - -- 有效人次统计子查询(从人次记录表获取,按日期+客户+数量去重后累加数量,F_HasBilling=1)
3848   - LEFT JOIN (
3849   - SELECT
3850   - F_PersonId as EmployeeId,
3851   - CAST(COALESCE(SUM(F_Quantity), 0) AS DECIMAL(18,2)) as PersonCount
3852   - FROM (
3853   - SELECT
3854   - F_PersonId,
3855   - F_WorkDate,
3856   - F_MemberId,
3857   - F_Quantity
3858   - FROM lq_person_times_record
3859   - WHERE F_PersonId IS NOT NULL
3860   - AND F_IsEffective = 1
3861   - AND F_PersonType = '健康师'
3862   - AND F_HasBilling = 1
3863   - AND F_WorkDate >= DATE(@startTime)
3864   - AND F_WorkDate <= DATE(@endTime)
3865   - GROUP BY F_PersonId, F_WorkDate, F_MemberId, F_Quantity
3866   - ) as distinct_personcount
3867   - GROUP BY F_PersonId
3868   - ) personcount_stats ON u.F_Id = personcount_stats.EmployeeId
3869   -
3870   - -- 无效人头统计子查询(从人次记录表获取,按月份+客户+数量去重后累加数量,F_HasBilling=0)
3871   - LEFT JOIN (
3872   - SELECT
3873   - F_PersonId as EmployeeId,
3874   - CAST(COALESCE(SUM(F_Quantity), 0) AS DECIMAL(18,2)) as HeadCount
3875   - FROM (
3876   - SELECT
3877   - F_PersonId,
3878   - F_WorkMonth,
3879   - F_MemberId,
3880   - F_Quantity
3881   - FROM lq_person_times_record
3882   - WHERE F_PersonId IS NOT NULL
3883   - AND F_IsEffective = 1
3884   - AND F_PersonType = '健康师'
3885   - AND F_HasBilling = 0
3886   - AND F_WorkDate >= DATE(@startTime)
3887   - AND F_WorkDate <= DATE(@endTime)
3888   - GROUP BY F_PersonId, F_WorkMonth, F_MemberId, F_Quantity
3889   - ) as distinct_headcount
3890   - GROUP BY F_PersonId
3891   - ) invalid_headcount_stats ON u.F_Id = invalid_headcount_stats.EmployeeId
3892   -
3893   - -- 无效人次统计子查询(从人次记录表获取,按日期+客户+数量去重后累加数量,F_HasBilling=0)
3894   - LEFT JOIN (
3895   - SELECT
3896   - F_PersonId as EmployeeId,
3897   - CAST(COALESCE(SUM(F_Quantity), 0) AS DECIMAL(18,2)) as PersonCount
3898   - FROM (
3899   - SELECT
3900   - F_PersonId,
3901   - F_WorkDate,
3902   - F_MemberId,
3903   - F_Quantity
3904   - FROM lq_person_times_record
3905   - WHERE F_PersonId IS NOT NULL
3906   - AND F_IsEffective = 1
3907   - AND F_PersonType = '健康师'
3908   - AND F_HasBilling = 0
3909   - AND F_WorkDate >= DATE(@startTime)
3910   - AND F_WorkDate <= DATE(@endTime)
3911   - GROUP BY F_PersonId, F_WorkDate, F_MemberId, F_Quantity
3912   - ) as distinct_personcount
3913   - GROUP BY F_PersonId
3914   - ) invalid_personcount_stats ON u.F_Id = invalid_personcount_stats.EmployeeId
3915   -
3916   - -- 退卡统计子查询
3917   - LEFT JOIN (
3918   - SELECT
3919   - jkszh as EmployeeId,
3920   - SUM(CAST(jksyj AS DECIMAL(18,2))) as RefundAmount
3921   - FROM lq_hytk_jksyj
3922   - WHERE jkszh IS NOT NULL
3923   - AND F_IsEffective = 1
3924   - AND tksj >= @startTime
3925   - AND tksj <= @endTime
3926   - GROUP BY jkszh
3927   - ) refund_stats ON u.F_Id = refund_stats.EmployeeId
3928   -
3929   - WHERE u.F_GW = '健康师'
3930   - ";
3931   -
3932   - // 添加条件过滤
3933   - var conditions = new List<string>();
3934   - var parameters = new List<SugarParameter>
3935   - {
3936   - new SugarParameter("@startTime", startTime),
3937   - new SugarParameter("@endTime", endTime)
3938   - };
3939   -
3940   - if (!string.IsNullOrEmpty(input.DepartmentId))
3941   - {
3942   - conditions.Add("md.syb = @departmentId");
3943   - parameters.Add(new SugarParameter("@departmentId", input.DepartmentId));
3944   - }
3945   -
3946   - if (!string.IsNullOrEmpty(input.StoreId))
3947   - {
3948   - conditions.Add("u.F_MDID = @storeId");
3949   - parameters.Add(new SugarParameter("@storeId", input.StoreId));
3950   - }
3951   -
3952   - if (!string.IsNullOrEmpty(input.EmployeeName))
3953   - {
3954   - conditions.Add("u.F_REALNAME LIKE @employeeName");
3955   - parameters.Add(new SugarParameter("@employeeName", $"%{input.EmployeeName}%"));
3956   - }
3957   -
3958   - if (conditions.Any())
3959   - {
3960   - sql += " AND " + string.Join(" AND ", conditions);
3961   - }
3962   -
3963   - sql += " ORDER BY u.F_REALNAME";
3964   -
3965   - // 执行查询
3966   - var allData = await _db.Ado.SqlQueryAsync<HealthCoachStatisticsOutput>(sql, parameters);
3967   -
3968   - // 手动分页
3969   - var totalCount = allData.Count;
3970   - var pagedData = allData
3971   - .Skip((input.currentPage - 1) * input.pageSize)
3972   - .Take(input.pageSize)
3973   - .ToList();
3974   -
3975   - // 直接返回分页结果
3976   - return new
3977   - {
3978   - list = pagedData,
3979   - pagination = new
3980   - {
3981   - pageIndex = input.currentPage,
3982   - pageSize = input.pageSize,
3983   - totalCount = totalCount
3984   - }
3985   - };
3986   - }
3987   - catch (Exception ex)
3988   - {
3989   - _logger.LogError(ex, "获取健康师统计数据失败");
3990   - throw NCCException.Oh($"获取健康师统计数据失败:{ex.Message}");
3991   - }
3992   - }
3993   - #endregion
3994   -
3995 3470 #region 获取开单品项明细记录列表
3996 3471 /// <summary>
3997 3472 /// 获取开单品项明细记录列表(分页)
... ... @@ -4274,7 +3749,11 @@ namespace NCC.Extend.LqKdKdjlb
4274 3749 var sidx = string.IsNullOrEmpty(input.sidx) ? "CreateTime" : input.sidx;
4275 3750  
4276 3751 // 构建基础查询:储扣信息 JOIN 开单记录 JOIN 客户信息 JOIN 门店信息
4277   - var baseQuery = _db.Queryable<LqKdDeductinfoEntity, LqKdKdjlbEntity, LqKhxxEntity, LqMdxxEntity>((deduct, billing, member, store) => deduct.BillingId == billing.Id && billing.Kdhy == member.Id && billing.Djmd == store.Id)
  3752 + var baseQuery = _db.Queryable<LqKdDeductinfoEntity, LqKdKdjlbEntity, LqKhxxEntity, LqMdxxEntity>(
  3753 + (deduct, billing, member, store) =>
  3754 + deduct.BillingId == billing.Id &&
  3755 + billing.Kdhy == member.Id &&
  3756 + billing.Djmd == store.Id)
4278 3757 .WhereIF(!string.IsNullOrEmpty(input.DeductType), (deduct, billing, member, store) => deduct.DeductType == input.DeductType)
4279 3758 .WhereIF(!string.IsNullOrEmpty(input.DeductId), (deduct, billing, member, store) => deduct.DeductId == input.DeductId)
4280 3759 .WhereIF(!string.IsNullOrEmpty(input.BillingId), (deduct, billing, member, store) => deduct.BillingId == input.BillingId)
... ... @@ -4294,7 +3773,7 @@ namespace NCC.Extend.LqKdKdjlb
4294 3773 (billing.Id != null && billing.Id.Contains(input.keyword)));
4295 3774  
4296 3775  
4297   - // 查询并分页
  3776 + // 查询并分页,使用子查询获取开单类型
4298 3777 var data = await baseQuery.Select((deduct, billing, member, store) => new LqKdDeductinfoListOutput
4299 3778 {
4300 3779 Id = deduct.Id ?? "",
... ... @@ -4314,7 +3793,12 @@ namespace NCC.Extend.LqKdKdjlb
4314 3793 MemberName = member.Khmc ?? "",
4315 3794 MemberPhone = member.Sjh ?? "",
4316 3795 StoreId = billing.Djmd ?? "",
4317   - StoreName = store.Dm ?? ""
  3796 + StoreName = store.Dm ?? "",
  3797 + TimePeriod = billing.Kdrq,
  3798 + BillingType = SqlFunc.Subqueryable<LqKdPxmxEntity>()
  3799 + .Where(pxmx => pxmx.Id == deduct.DeductId && pxmx.Px == deduct.ItemId)
  3800 + .Select(pxmx => pxmx.SourceType),
  3801 + CooperationInstitution = billing.Hgjg ?? ""
4318 3802 }).MergeTable().OrderBy(sidx + " " + input.sort).ToPagedListAsync(input.currentPage, input.pageSize);
4319 3803  
4320 3804 return PageResult<LqKdDeductinfoListOutput>.SqlSugarPageResult(data);
... ...
netcore/src/Modularity/Extend/NCC.Extend/LqStatisticsService.cs
... ... @@ -2133,7 +2133,7 @@ namespace NCC.Extend.LqStatistics
2133 2133 ) personcount_stats ON jksyj.jkszh = personcount_stats.jkszh AND hyhk.md = personcount_stats.md
2134 2134 WHERE jksyj.F_IsEffective = 1
2135 2135 AND DATE_FORMAT(hyhk.hksj, '%Y%m') = @statisticsMonth
2136   - GROUP BY jksyj.jkszh, jksyj.jksxm, hyhk.md, md.mdbm, md.dm, headcount_stats.F_HeadCount, personcount_stats.F_PersonCount";
  2136 + GROUP BY jksyj.jks, jksyj.jksxm, hyhk.md, md.mdbm, md.dm, headcount_stats.F_HeadCount, personcount_stats.F_PersonCount";
2137 2137  
2138 2138 var healthCoachData = await _db.Ado.SqlQueryAsync<dynamic>(healthCoachSql, new { statisticsMonth });
2139 2139 foreach (var item in healthCoachData)
... ...
netcore/src/Modularity/Extend/NCC.Extend/LqXhHyhkService.cs
... ... @@ -777,48 +777,48 @@ namespace NCC.Extend.LqXhHyhk
777 777 .OrderBy($"{sidx} {sort}")
778 778 .ToPagedListAsync(input.currentPage, input.pageSize);
779 779  
780   - // 获取当前页的耗卡记录ID列表
781   - var consumeIds = data.list.Select(x => x.id).ToList();
  780 + // 获取当前页的耗卡记录ID列表
  781 + var consumeIds = data.list.Select(x => x.id).ToList();
782 782  
783   - // 批量查询耗卡明细
784   - var consumeDetails = new List<LqXhPxmxInfoOutput>();
785   - if (consumeIds.Any())
786   - {
787   - consumeDetails = await _db.Queryable<LqXhPxmxEntity>()
788   - .Where(x => consumeIds.Contains(x.ConsumeInfoId) && x.IsEffective == StatusEnum.有效.GetHashCode())
789   - .Select(x => new LqXhPxmxInfoOutput
790   - {
791   - id = x.Id,
792   - consumeInfoId = x.ConsumeInfoId,
793   - billingItemId = x.BillingItemId,
794   - px = x.Px,
795   - pxmc = x.Pxmc,
796   - pxjg = x.Pxjg,
797   - memberId = x.MemberId,
798   - createTime = x.CreateTIme,
799   - projectNumber = x.ProjectNumber,
800   - originalProjectNumber = x.OriginalProjectNumber,
801   - overtimeProjectNumber = x.OvertimeProjectNumber,
802   - sourceType = x.SourceType,
803   - totalPrice = x.TotalPrice,
804   - isEffective = x.IsEffective,
805   - })
806   - .ToListAsync();
807   - }
  783 + // 批量查询耗卡明细
  784 + var consumeDetails = new List<LqXhPxmxInfoOutput>();
  785 + if (consumeIds.Any())
  786 + {
  787 + consumeDetails = await _db.Queryable<LqXhPxmxEntity>()
  788 + .Where(x => consumeIds.Contains(x.ConsumeInfoId) && x.IsEffective == StatusEnum.有效.GetHashCode())
  789 + .Select(x => new LqXhPxmxInfoOutput
  790 + {
  791 + id = x.Id,
  792 + consumeInfoId = x.ConsumeInfoId,
  793 + billingItemId = x.BillingItemId,
  794 + px = x.Px,
  795 + pxmc = x.Pxmc,
  796 + pxjg = x.Pxjg,
  797 + memberId = x.MemberId,
  798 + createTime = x.CreateTIme,
  799 + projectNumber = x.ProjectNumber,
  800 + originalProjectNumber = x.OriginalProjectNumber,
  801 + overtimeProjectNumber = x.OvertimeProjectNumber,
  802 + sourceType = x.SourceType,
  803 + totalPrice = x.TotalPrice,
  804 + isEffective = x.IsEffective,
  805 + })
  806 + .ToListAsync();
  807 + }
808 808  
809   - // 按耗卡记录ID分组耗卡明细
810   - var consumeDetailsGrouped = consumeDetails.GroupBy(x => x.consumeInfoId)
811   - .ToDictionary(g => g.Key, g => g.ToList());
  809 + // 按耗卡记录ID分组耗卡明细
  810 + var consumeDetailsGrouped = consumeDetails.GroupBy(x => x.consumeInfoId)
  811 + .ToDictionary(g => g.Key, g => g.ToList());
812 812  
813   - // 为每个耗卡记录分配耗卡明细
814   - foreach (var item in data.list)
815   - {
816   - item.ConsumeDetails = consumeDetailsGrouped.ContainsKey(item.id)
817   - ? consumeDetailsGrouped[item.id]
818   - : new List<LqXhPxmxInfoOutput>();
819   - }
  813 + // 为每个耗卡记录分配耗卡明细
  814 + foreach (var item in data.list)
  815 + {
  816 + item.ConsumeDetails = consumeDetailsGrouped.ContainsKey(item.id)
  817 + ? consumeDetailsGrouped[item.id]
  818 + : new List<LqXhPxmxInfoOutput>();
  819 + }
820 820  
821   - return PageResult<LqXhHyhkListOutput>.SqlSugarPageResult(data);
  821 + return PageResult<LqXhHyhkListOutput>.SqlSugarPageResult(data);
822 822 }
823 823 catch (Exception ex)
824 824 {
... ... @@ -1390,6 +1390,8 @@ namespace NCC.Extend.LqXhHyhk
1390 1390 }
1391 1391 }
1392 1392 }
  1393 + //查询开单记录
  1394 + var billingRecord = await _db.Queryable<LqKdKdjlbEntity>().Where(x => x.Kdhy == entity.Hy && x.IsEffective == StatusEnum.有效.GetHashCode() && x.Sfyj > 0).AnyAsync();
1393 1395 //剔除所有T区健康师(姓名中包含"T区"的健康师)
1394 1396 var NoTallJksyjEntities = allJksyjEntities.Where(x => x.Jks != null && x.Jks != "" && (x.Jksxm == null || !x.Jksxm.Contains("T区"))).ToList();
1395 1397 //获取这次耗卡有多少个健康师
... ... @@ -1412,6 +1414,7 @@ namespace NCC.Extend.LqXhHyhk
1412 1414 Quantity = 1 / (decimal)jksCount,
1413 1415 CreateTime = DateTime.Now,
1414 1416 IsEffective = StatusEnum.有效.GetHashCode(),
  1417 + HasBilling = billingRecord ? 1 : 0,
1415 1418 };
1416 1419 allPersonTimesRecordEntities.Add(personTimesRecordEntity);
1417 1420 }
... ... @@ -1437,6 +1440,7 @@ namespace NCC.Extend.LqXhHyhk
1437 1440 Quantity = 1 / (decimal)kjbCount,
1438 1441 CreateTime = DateTime.Now,
1439 1442 IsEffective = StatusEnum.有效.GetHashCode(),
  1443 + HasBilling = billingRecord ? 1 : 0,
1440 1444 };
1441 1445 allPersonTimesRecordEntities.Add(personTimesRecordEntity);
1442 1446 }
... ...
sql/修复人次记录HasBilling字段.sql 0 → 100644
  1 +-- 修复人次记录表中F_HasBilling字段
  2 +-- 对于耗卡类型的人次记录,如果会员有开单记录(F_IsEffective=1且Sfyj>0),则设置F_HasBilling=1
  3 +
  4 +-- 1. 先查看需要修复的记录数量
  5 +SELECT COUNT(*) as need_fix_count
  6 +FROM lq_person_times_record ptr
  7 +WHERE ptr.F_BusinessType = '耗卡'
  8 + AND ptr.F_HasBilling = 0
  9 + AND ptr.F_MemberId IS NOT NULL
  10 + AND ptr.F_MemberId != ''
  11 + AND EXISTS (
  12 + SELECT 1
  13 + FROM lq_kd_kdjlb kd
  14 + WHERE kd.Kdhy = ptr.F_MemberId
  15 + AND kd.F_IsEffective = 1
  16 + AND kd.Sfyj > 0
  17 + );
  18 +
  19 +-- 2. 修复耗卡类型的人次记录
  20 +UPDATE lq_person_times_record ptr
  21 +INNER JOIN (
  22 + SELECT DISTINCT Kdhy
  23 + FROM lq_kd_kdjlb
  24 + WHERE F_IsEffective = 1
  25 + AND Sfyj > 0
  26 +) kd ON ptr.F_MemberId = kd.Kdhy
  27 +SET ptr.F_HasBilling = 1
  28 +WHERE ptr.F_BusinessType = '耗卡'
  29 + AND ptr.F_HasBilling = 0
  30 + AND ptr.F_MemberId IS NOT NULL
  31 + AND ptr.F_MemberId != '';
  32 +
  33 +-- 3. 验证修复结果(可选)
  34 +SELECT COUNT(*) as fixed_count
  35 +FROM lq_person_times_record ptr
  36 +WHERE ptr.F_BusinessType = '耗卡'
  37 + AND ptr.F_HasBilling = 1
  38 + AND ptr.F_MemberId IS NOT NULL
  39 + AND ptr.F_MemberId != ''
  40 + AND EXISTS (
  41 + SELECT 1
  42 + FROM lq_kd_kdjlb kd
  43 + WHERE kd.Kdhy = ptr.F_MemberId
  44 + AND kd.F_IsEffective = 1
  45 + AND kd.Sfyj > 0
  46 + );
  47 +
... ...
项目信息-薪酬规则与名词解释.md
... ... @@ -124,8 +124,8 @@
124 124  
125 125 ### 顾问提成规则
126 126  
127   -- **高级顾问**:战队总业绩 ≥ 6万元 且 组员业绩达到40%以上 且 消耗达到6万元 → 团队总业绩0.8%
128   -- **普通顾问**:战队总业绩 ≥ 4万元 且 组员业绩达到30%以上 且 消耗达到4万元 → 团队总业绩0.3%
  127 +- 战队总业绩 ≥ 6万元 且 组员业绩达到40%以上 且 消耗达到6万元 → 团队总业绩0.8%
  128 +- 战队总业绩 ≥ 4万元 且 组员业绩达到30%以上 且 消耗达到4万元 → 团队总业绩0.3%
129 129  
130 130 ### 店助考核规则
131 131  
... ...