From a30ca3d5eaa888da35dd992a47269cfabaa1361c Mon Sep 17 00:00:00 2001 From: “wangming” <“wangming@antissoft.com”> Date: Mon, 10 Nov 2025 22:15:13 +0800 Subject: [PATCH] feat: 添加事业部开单统计功能;修复科技部老师统计方法,不使用视图直接从表查询;优化错误处理返回全0结果 --- netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqDailyReport/BusinessUnitBillingStatisticsInput.cs | 16 ++++++++++++++++ netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqDailyReport/BusinessUnitBillingStatisticsOutput.cs | 68 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ netcore/src/Modularity/Extend/NCC.Extend/LqDailyReportService.cs | 196 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ netcore/src/Modularity/Extend/NCC.Extend/LqStatisticsService.cs | 190 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------------------------------------------------------------------------ 4 files changed, 386 insertions(+), 84 deletions(-) create mode 100644 netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqDailyReport/BusinessUnitBillingStatisticsInput.cs create mode 100644 netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqDailyReport/BusinessUnitBillingStatisticsOutput.cs diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqDailyReport/BusinessUnitBillingStatisticsInput.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqDailyReport/BusinessUnitBillingStatisticsInput.cs new file mode 100644 index 0000000..20a763a --- /dev/null +++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqDailyReport/BusinessUnitBillingStatisticsInput.cs @@ -0,0 +1,16 @@ +using System; + +namespace NCC.Extend.Entitys.Dto.LqDailyReport +{ + /// + /// 事业部开单统计查询输入 + /// + public class BusinessUnitBillingStatisticsInput + { + /// + /// 统计日期(格式:yyyy-MM-dd) + /// + public string Date { get; set; } + } +} + diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqDailyReport/BusinessUnitBillingStatisticsOutput.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqDailyReport/BusinessUnitBillingStatisticsOutput.cs new file mode 100644 index 0000000..8f0342e --- /dev/null +++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqDailyReport/BusinessUnitBillingStatisticsOutput.cs @@ -0,0 +1,68 @@ +using System; +using System.Collections.Generic; + +namespace NCC.Extend.Entitys.Dto.LqDailyReport +{ + /// + /// 事业部开单统计输出 + /// + public class BusinessUnitBillingStatisticsOutput + { + /// + /// 事业部ID + /// + public string BusinessUnitId { get; set; } + + /// + /// 事业部名称 + /// + public string BusinessUnitName { get; set; } + + /// + /// 总业绩 + /// + public decimal TotalPerformance { get; set; } + + /// + /// 总单量 + /// + public int TotalOrderCount { get; set; } + + /// + /// 开单列表 + /// + public List Orders { get; set; } + } + + /// + /// 开单信息 + /// + public class BillingOrderInfo + { + /// + /// 开单ID + /// + public string OrderId { get; set; } + + /// + /// 门店名称 + /// + public string StoreName { get; set; } + + /// + /// 健康师姓名(多个用逗号分隔) + /// + public string HealthTeacherNames { get; set; } + + /// + /// 金额 + /// + public decimal Amount { get; set; } + + /// + /// 开单时间 + /// + public DateTime? OrderTime { get; set; } + } +} + diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqDailyReportService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqDailyReportService.cs index 5f417d0..af0c333 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend/LqDailyReportService.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend/LqDailyReportService.cs @@ -12,6 +12,7 @@ using SqlSugar; using System; using System.Collections.Generic; using System.Linq; +using System.Text; using System.Threading.Tasks; using Yitter.IdGenerator; using NCC.Common.Helper; @@ -22,6 +23,10 @@ using NCC.DataEncryption; using NCC.ClayObject; using NCC.Extend.Entitys.Dto.LqDailyReport; using NCC.Extend.Entitys.lq_kd_kdjlb; +using NCC.Extend.Entitys.lq_kd_jksyj; +using NCC.Extend.Entitys.lq_mdxx; +using NCC.System.Entitys.Permission; +using NCC.Extend.Entitys.Enum; using Microsoft.AspNetCore.Authorization; namespace NCC.Extend @@ -957,7 +962,198 @@ namespace NCC.Extend return outputList; } + + /// + /// 获取指定日期的事业部开单统计数据 + /// + /// + /// 统计指定日期的事业部开单数据,包括: + /// - 只统计有效的开单(F_IsEffective = 1) + /// - 只统计有金额的开单(sfyj > 0) + /// - 只统计有效的健康师业绩(F_IsEffective = 1) + /// - 如果有多个健康师就合并显示 + /// - 按照事业部名称排序 + /// - 每个事业部内的开单按照时间先后顺序进行排序 + /// + /// 示例请求: + /// ```json + /// { + /// "date": "2025-11-10" + /// } + /// ``` + /// + /// 查询参数 + /// 事业部开单统计数据列表 + /// 成功返回统计数据 + /// 日期格式错误或参数无效 + /// 服务器内部错误 + [HttpPost("get-business-unit-billing-statistics")] + [AllowAnonymous] + public async Task> GetBusinessUnitBillingStatistics(BusinessUnitBillingStatisticsInput input) + { + try + { + // 1. 验证日期参数 + if (string.IsNullOrEmpty(input.Date)) + { + throw NCCException.Oh("日期不能为空"); + } + + if (!DateTime.TryParse(input.Date, out var targetDate)) + { + throw NCCException.Oh("日期格式错误,请使用 yyyy-MM-dd 格式"); + } + + // 2. 查询指定日期的有效开单记录(有金额的) + var billingQuery = _db.Queryable( + (billing, store, org) => billing.Djmd == store.Id && store.Syb == org.Id) + .Where((billing, store, org) => billing.IsEffective == StatusEnum.有效.GetHashCode()) + .Where((billing, store, org) => billing.Sfyj > 0) // 只统计有金额的开单 + .Where((billing, store, org) => billing.Kdrq.HasValue && billing.Kdrq.Value.Date == targetDate.Date) + .Where((billing, store, org) => org.Category == "department") + .Where((billing, store, org) => org.FullName.Contains("事业")) + .Select((billing, store, org) => new + { + OrderId = billing.Id, + StoreName = store.Dm, + BusinessUnitId = org.Id, + BusinessUnitName = org.FullName, + Amount = billing.Sfyj, + OrderTime = billing.Kdrq, + }) + .ToListAsync(); + + var billingRecords = await billingQuery; + + if (!billingRecords.Any()) + { + return new List(); + } + + // 3. 批量查询健康师信息 + var orderIds = billingRecords.Select(x => x.OrderId).Distinct().ToList(); + var healthTeachers = await _db.Queryable() + .Where(x => orderIds.Contains(x.Glkdbh) && x.IsEffective == StatusEnum.有效.GetHashCode()) + .Select(x => new + { + OrderId = x.Glkdbh, + TeacherName = x.Jksxm, + }) + .ToListAsync(); + + // 4. 按开单ID分组健康师 + var healthTeacherDict = healthTeachers + .GroupBy(x => x.OrderId) + .ToDictionary( + g => g.Key, + g => string.Join("、", g.Select(x => x.TeacherName).Distinct()) + ); + + // 5. 按事业部分组统计 + var result = billingRecords + .GroupBy(x => new { x.BusinessUnitId, x.BusinessUnitName }) + .Select(g => new BusinessUnitBillingStatisticsOutput + { + BusinessUnitId = g.Key.BusinessUnitId, + BusinessUnitName = g.Key.BusinessUnitName, + TotalPerformance = g.Sum(x => x.Amount), + TotalOrderCount = g.Count(), + Orders = g.OrderBy(x => x.OrderTime ?? DateTime.MinValue) + .Select(x => new BillingOrderInfo + { + OrderId = x.OrderId, + StoreName = x.StoreName, + HealthTeacherNames = healthTeacherDict.GetValueOrDefault(x.OrderId, ""), + Amount = x.Amount, + OrderTime = x.OrderTime, + }) + .ToList(), + }) + .OrderBy(x => x.BusinessUnitName) // 按事业部名称排序 + .ToList(); + + return result; + } + catch (Exception ex) + { + throw NCCException.Oh($"获取事业部开单统计数据失败: {ex.Message}", ex); + } + } + + /// + /// 将事业部开单统计数据格式化为中文文本 + /// + /// + /// 将结构化的统计数据格式化为中文文本格式,用于显示或发送消息。 + /// + /// 示例请求: + /// ```json + /// { + /// "date": "2025-11-10" + /// } + /// ``` + /// + /// 返回格式: + /// ``` + /// 事业一部业绩:xxxx + /// 事业一部总单量:xxx + /// 第一单:xxx店、xxx健康师,金额xxx + /// 第二单:xxx店、xxx健康师,金额xxx + /// + /// 事业二部业绩:xxxx + /// 事业二部总单量:xxx + /// 第一单:xxx店、xxx健康师,金额xxx + /// 第二单:xxx店、xxx健康师,金额xxx + /// ``` + /// + /// 查询参数 + /// 格式化的中文文本 + /// 成功返回格式化文本 + /// 日期格式错误或参数无效 + /// 服务器内部错误 + [HttpPost("get-business-unit-billing-statistics-text")] + [AllowAnonymous] + public async Task GetBusinessUnitBillingStatisticsText(BusinessUnitBillingStatisticsInput input) + { + try + { + // 1. 获取统计数据 + var statistics = await GetBusinessUnitBillingStatistics(input); + + if (!statistics.Any()) + { + return $"日期 {input.Date} 暂无开单数据"; + } + + // 2. 格式化为中文文本 + var textBuilder = new StringBuilder(); + + foreach (var unit in statistics) + { + textBuilder.AppendLine($"{unit.BusinessUnitName}业绩:{unit.TotalPerformance:F2}"); + textBuilder.AppendLine($"{unit.BusinessUnitName}总单量:{unit.TotalOrderCount}"); + + int orderIndex = 1; + foreach (var order in unit.Orders) + { + var teacherNames = string.IsNullOrEmpty(order.HealthTeacherNames) ? "无健康师" : order.HealthTeacherNames; + textBuilder.AppendLine($"第{orderIndex}单:{order.StoreName}、{teacherNames},金额{order.Amount:F2}"); + orderIndex++; + } + + textBuilder.AppendLine(); // 空行分隔 + } + + return textBuilder.ToString().TrimEnd(); + } + catch (Exception ex) + { + throw NCCException.Oh($"获取事业部开单统计文本失败: {ex.Message}", ex); + } + } #endregion + + } } diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqStatisticsService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqStatisticsService.cs index 5c813cf..9101b8c 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend/LqStatisticsService.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend/LqStatisticsService.cs @@ -28,8 +28,11 @@ using NCC.Extend.Entitys.lq_kd_kjbsyj; using NCC.Extend.Entitys.lq_mdxx; using NCC.Extend.Entitys.lq_md_xdbhsj; using NCC.Extend.Entitys.lq_xh_kjbsyj; +using NCC.Extend.Entitys.lq_xh_hyhk; +using NCC.Extend.Entitys.lq_xh_pxmx; using NCC.Extend.Entitys.lq_ycsd_jsj; using NCC.Extend.Entitys.lq_yjmxb; +using NCC.Extend.Entitys.Enum; using NCC.Extend.Entitys.lq_statistics_gold_triangle; using NCC.Extend.Entitys.lq_statistics_personal_performance; using NCC.Extend.Entitys.lq_statistics_store_consume_performance; @@ -870,125 +873,144 @@ namespace NCC.Extend.LqStatistics public async Task> GetTechTeacherStatistics(TechTeacherStatisticsInput input) { try - { // 1. 从用户表获取所有科技部老师 - var allTeachers = await _db.Queryable() - .Where(x => x.Gw == "科技老师") + { + // 1. 验证必须传入科技老师ID + if (string.IsNullOrEmpty(input.TeacherId)) + { + return new List + { + new TechTeacherSimpleStatisticsOutput + { + DepartmentName = "科技部", + TeacherName = "", + OrderAchievement = 0m, + OrderItemCount = 0, + ConsumeAchievement = 0m, + ConsumeItemCount = 0, + ConsumeProjectCount = 0, + ConsumeLaborCost = 0m, + } + }; + } + + // 2. 获取科技老师信息 + var teacher = await _db.Queryable() + .Where(x => x.Gw == "科技老师" && x.Id == input.TeacherId) .Select(x => new { TeacherId = x.Id, TeacherName = x.RealName, TeacherAccount = x.Account, }) - .ToListAsync(); - - // 2. 获取业绩流水数据 - var flowQuery = _db.Queryable(); + .FirstAsync(); - // 老师过滤(支持通过ID或账号匹配) - if (!string.IsNullOrEmpty(input.TeacherId)) + if (teacher == null) { - // 先通过用户ID查询账号,然后同时匹配ID和账号 - var teacherAccount = allTeachers.FirstOrDefault(t => t.TeacherId == input.TeacherId)?.TeacherAccount; - if (!string.IsNullOrEmpty(teacherAccount)) + return new List { - // 同时匹配ID和账号(因为视图中的teacher_id可能是ID或账号) - flowQuery = flowQuery.Where(x => x.TeacherId == input.TeacherId || x.TeacherAccount == teacherAccount); - } - else - { - // 如果找不到账号,只匹配ID - flowQuery = flowQuery.Where(x => x.TeacherId == input.TeacherId); - } + new TechTeacherSimpleStatisticsOutput + { + DepartmentName = "科技部", + TeacherName = "", + OrderAchievement = 0m, + OrderItemCount = 0, + ConsumeAchievement = 0m, + ConsumeItemCount = 0, + ConsumeProjectCount = 0, + ConsumeLaborCost = 0m, + } + }; } - if (!string.IsNullOrEmpty(input.TeacherName)) - { - flowQuery = flowQuery.Where(x => x.TeacherName.Contains(input.TeacherName)); - } + // 3. 查询开单业绩(从 lq_kd_kjbsyj 关联 lq_kd_kdjlb 和 lq_kd_pxmx) + var orderQuery = _db.Queryable( + (kjbsyj, kdjlb, pxmx) => kjbsyj.Glkdbh == kdjlb.Id && kjbsyj.Kdpxid == pxmx.Id) + .Where((kjbsyj, kdjlb, pxmx) => kjbsyj.IsEffective == StatusEnum.有效.GetHashCode()) + .Where((kjbsyj, kdjlb, pxmx) => kdjlb.IsEffective == StatusEnum.有效.GetHashCode()) + .Where((kjbsyj, kdjlb, pxmx) => kjbsyj.Kjblszh == teacher.TeacherAccount); // 日期过滤 if (input.StartDate.HasValue) { - flowQuery = flowQuery.Where(x => x.BusinessDate >= input.StartDate.Value); + orderQuery = orderQuery.Where((kjbsyj, kdjlb, pxmx) => kjbsyj.Yjsj >= input.StartDate.Value); } if (input.EndDate.HasValue) { - flowQuery = flowQuery.Where(x => x.BusinessDate <= input.EndDate.Value); + orderQuery = orderQuery.Where((kjbsyj, kdjlb, pxmx) => kjbsyj.Yjsj <= input.EndDate.Value); } - var flowRecords = await flowQuery.ToListAsync(); - - // 3. 按老师账号分组统计业绩数据(包括耗卡手工费) - // 注意:视图中的teacher_id是kjbls,teacher_account是kjblszh,使用账号来匹配更准确 - var teacherStatsDict = flowRecords - .GroupBy(x => new + var orderStats = await orderQuery + .Select((kjbsyj, kdjlb, pxmx) => new { - TeacherAccount = x.TeacherAccount ?? string.Empty, - TeacherId = x.TeacherId ?? string.Empty, - TeacherName = x.TeacherName ?? string.Empty, + OrderAchievement = SqlFunc.ToDecimal(kjbsyj.Kjblsyj), + OrderItemCount = SqlFunc.ToInt32(pxmx.ProjectNumber), }) - .ToDictionary( - g => g.Key.TeacherAccount ?? string.Empty, - g => new - { - ConsumeProjectCount = (int)(g.Where(x => x.BusinessType == "耗卡").Sum(x => x.ProjectCount)), - ConsumeAchievement = g.Where(x => x.BusinessType == "耗卡").Sum(x => x.Achievement), - OrderAchievement = g.Where(x => x.BusinessType == "开卡").Sum(x => x.Achievement), - OrderItemCount = g.Where(x => x.BusinessType == "开卡").Sum(x => x.ItemCount), - ConsumeItemCount = g.Where(x => x.BusinessType == "耗卡").Sum(x => x.ItemCount), - ConsumeLaborCost = g.Where(x => x.BusinessType == "耗卡").Sum(x => x.LaborCost), - } - ); + .ToListAsync(); - // 4. 构建结果,包含所有老师 - var result = new List(); + // 4. 查询耗卡业绩(从 lq_xh_kjbsyj 关联 lq_xh_hyhk 和 lq_xh_pxmx) + // 注意:lq_xh_kjbsyj.kjbls 字段存储的是健康师id,不是科技部老师id + // 科技部老师信息在 kjblszh(账号)和 kjblsxm(姓名)字段 + var consumeQuery = _db.Queryable( + (kjbsyj, hyhk, pxmx) => kjbsyj.Glkdbh == hyhk.Id && kjbsyj.Hkpxid == pxmx.Id) + .Where((kjbsyj, hyhk, pxmx) => kjbsyj.IsEffective == StatusEnum.有效.GetHashCode()) + .Where((kjbsyj, hyhk, pxmx) => hyhk.IsEffective == StatusEnum.有效.GetHashCode()) + .Where((kjbsyj, hyhk, pxmx) => kjbsyj.Kjblszh == teacher.TeacherAccount); - foreach (var teacher in allTeachers) + // 日期过滤 + if (input.StartDate.HasValue) { - // 应用过滤条件 - if (!string.IsNullOrEmpty(input.TeacherId) && teacher.TeacherId != input.TeacherId) - continue; - - if (!string.IsNullOrEmpty(input.TeacherName) && !teacher.TeacherName.Contains(input.TeacherName)) - continue; + consumeQuery = consumeQuery.Where((kjbsyj, hyhk, pxmx) => kjbsyj.Yjsj >= input.StartDate.Value); + } - // 使用账号来匹配业绩数据(因为视图中的teacher_account是kjblszh) - var teacherAccount = teacher.TeacherAccount ?? string.Empty; - var stats = teacherStatsDict.GetValueOrDefault( - teacherAccount, - new - { - ConsumeProjectCount = 0, - ConsumeAchievement = 0m, - OrderAchievement = 0m, - OrderItemCount = 0, - ConsumeItemCount = 0, - ConsumeLaborCost = 0m, - } - ); + if (input.EndDate.HasValue) + { + consumeQuery = consumeQuery.Where((kjbsyj, hyhk, pxmx) => kjbsyj.Yjsj <= input.EndDate.Value); + } - var teacherStats = new TechTeacherSimpleStatisticsOutput + var consumeStats = await consumeQuery + .Select((kjbsyj, hyhk, pxmx) => new { - DepartmentName = "科技部", // 固定为科技部 - TeacherName = teacher.TeacherName, - ConsumeProjectCount = stats.ConsumeProjectCount, - ConsumeAchievement = stats.ConsumeAchievement, - OrderAchievement = stats.OrderAchievement, - OrderItemCount = stats.OrderItemCount, - ConsumeItemCount = stats.ConsumeItemCount, - ConsumeLaborCost = stats.ConsumeLaborCost, - }; + ConsumeAchievement = kjbsyj.Kjblsyj, + ConsumeItemCount = SqlFunc.ToInt32(pxmx.ProjectNumber), + ConsumeProjectCount = SqlFunc.ToInt32(kjbsyj.HdpxNumber), + ConsumeLaborCost = kjbsyj.LaborCost, + }) + .ToListAsync(); - result.Add(teacherStats); - } + // 5. 统计并返回结果 + var result = new TechTeacherSimpleStatisticsOutput + { + DepartmentName = "科技部", + TeacherName = teacher.TeacherName, + OrderAchievement = orderStats.Sum(x => x.OrderAchievement != null && decimal.TryParse(x.OrderAchievement.ToString(), out var val) ? val : 0m), + OrderItemCount = orderStats.Sum(x => x.OrderItemCount), + ConsumeAchievement = consumeStats.Sum(x => x.ConsumeAchievement ?? 0m), + ConsumeItemCount = consumeStats.Sum(x => x.ConsumeItemCount), + ConsumeProjectCount = consumeStats.Sum(x => x.ConsumeProjectCount), + ConsumeLaborCost = consumeStats.Sum(x => x.ConsumeLaborCost ?? 0m), + }; - return result; + return new List { result }; } catch (Exception ex) { _logger.LogError(ex, "获取科技部老师业绩统计时发生错误"); - throw NCCException.Oh("获取科技部老师业绩统计失败", ex); + // 发生错误时返回全0的结果,而不是抛出异常 + return new List + { + new TechTeacherSimpleStatisticsOutput + { + DepartmentName = "科技部", + TeacherName = "", + OrderAchievement = 0m, + OrderItemCount = 0, + ConsumeAchievement = 0m, + ConsumeItemCount = 0, + ConsumeProjectCount = 0, + ConsumeLaborCost = 0m, + } + }; } } -- libgit2 0.21.4