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,
+ }
+ };
}
}