diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqDailyReport/BusinessUnitPerformanceCompletionOutput.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqDailyReport/BusinessUnitPerformanceCompletionOutput.cs index 1209cba..9143895 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqDailyReport/BusinessUnitPerformanceCompletionOutput.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqDailyReport/BusinessUnitPerformanceCompletionOutput.cs @@ -24,12 +24,27 @@ namespace NCC.Extend.Entitys.Dto.LqDailyReport public decimal TargetPerformance { get; set; } /// - /// 完成业绩(管理门店的开单业绩总和) + /// 开单业绩(管理门店的开单业绩总和) + /// + public decimal BillingPerformance { get; set; } + + /// + /// 退款业绩(退卡业绩总和) + /// + public decimal RefundPerformance { get; set; } + + /// + /// 开单业绩(管理门店的开单业绩总和) + /// + public decimal ActualPerformance { get; set; } + + /// + /// 实际完成业绩(开单业绩 - 退款业绩) /// public decimal CompletedPerformance { get; set; } /// - /// 完成率(百分比) + /// 完成率(百分比,基于实际完成业绩计算) /// public decimal CompletionRate { get; set; } diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqDailyReport/TianwangGroupPerformanceCompletionOutput.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqDailyReport/TianwangGroupPerformanceCompletionOutput.cs index b033c17..e35dd64 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqDailyReport/TianwangGroupPerformanceCompletionOutput.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqDailyReport/TianwangGroupPerformanceCompletionOutput.cs @@ -24,12 +24,27 @@ namespace NCC.Extend.Entitys.Dto.LqDailyReport public decimal TargetPerformance { get; set; } /// - /// 完成业绩(管理门店的开单业绩总和) + /// 开单业绩(管理门店的开单业绩总和) + /// + public decimal BillingPerformance { get; set; } + + /// + /// 退款业绩(退卡业绩总和) + /// + public decimal RefundPerformance { get; set; } + + /// + /// 开单业绩(与BillingPerformance相同,用于兼容) + /// + public decimal ActualPerformance { get; set; } + + /// + /// 实际完成业绩(开单业绩 - 退款业绩) /// public decimal CompletedPerformance { get; set; } /// - /// 完成率(百分比) + /// 完成率(百分比,基于实际完成业绩计算) /// public decimal CompletionRate { get; set; } diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqPackageInfo/ActivityStatisticsByStoreItemOutput.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqPackageInfo/ActivityStatisticsByStoreItemOutput.cs index 3f75828..abb9115 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqPackageInfo/ActivityStatisticsByStoreItemOutput.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqPackageInfo/ActivityStatisticsByStoreItemOutput.cs @@ -18,15 +18,15 @@ namespace NCC.Extend.Entitys.Dto.LqPackageInfo public string ActivityName { get; set; } /// - /// 门店品项统计列表 + /// 门店统计列表(层级结构:每个门店下包含品项列表) /// - public List StoreItemList { get; set; } + public List StoreList { get; set; } } /// - /// 门店品项统计项 + /// 门店统计(包含品项列表) /// - public class StoreItemStatisticsItem + public class StoreStatisticsWithItems { /// /// 门店ID @@ -39,6 +39,17 @@ namespace NCC.Extend.Entitys.Dto.LqPackageInfo public string StoreName { get; set; } /// + /// 该门店下的品项统计列表 + /// + public List ItemList { get; set; } + } + + /// + /// 门店品项统计项 + /// + public class StoreItemStatisticsItem + { + /// /// 品项ID /// public string ItemId { get; set; } diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqStatistics/EmployeePerformanceStatisticsOutput.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqStatistics/EmployeePerformanceStatisticsOutput.cs index 3ded29a..92a6d54 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqStatistics/EmployeePerformanceStatisticsOutput.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqStatistics/EmployeePerformanceStatisticsOutput.cs @@ -41,7 +41,7 @@ namespace NCC.Extend.Entitys.Dto.LqStatistics public decimal BillingAmount { get; set; } /// - /// 消耗数量 + /// 消耗单数(消耗主表的去重记录数) /// public int ConsumeCount { get; set; } @@ -51,7 +51,7 @@ namespace NCC.Extend.Entitys.Dto.LqStatistics public decimal ConsumeAmount { get; set; } /// - /// 退卡数量 + /// 退卡单数量(退卡主表的去重记录数) /// public int RefundCount { get; set; } @@ -76,8 +76,28 @@ namespace NCC.Extend.Entitys.Dto.LqStatistics public int BillingProjectCount { get; set; } /// - /// 消耗项目数(项目次数总和) + /// 消耗项目数(项目次数总和,包含原始+加班+陪同) /// public int ConsumeProjectCount { get; set; } + + /// + /// 消耗原始项目数(原始项目次数总和) + /// + public decimal ConsumeOriginalProjectCount { get; set; } + + /// + /// 消耗加班项目数(加班项目次数总和) + /// + public decimal ConsumeOvertimeProjectCount { get; set; } + + /// + /// 消耗陪同项目数(陪同项目次数总和) + /// + public decimal ConsumeAccompaniedProjectCount { get; set; } + + /// + /// 手工费(消耗手工费总和) + /// + public decimal LaborCost { get; set; } } } diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqDailyReportService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqDailyReportService.cs index fa274d2..286d5ce 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend/LqDailyReportService.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend/LqDailyReportService.cs @@ -13,6 +13,7 @@ using System; using System.Collections.Generic; using System.Linq; using System.Text; +using System.Text.RegularExpressions; using System.Threading.Tasks; using Yitter.IdGenerator; using NCC.Common.Helper; @@ -124,7 +125,7 @@ namespace NCC.Extend storeFilter = $"AND consume.Md IN ('{storeIdsStr}')"; } - // SQL查询:获取门店每日运营数据 + // SQL查询:获取门店每日运营数据(只统计zxzt为"开店"的门店) var sql = $@" SELECT consume.Md as StoreId, @@ -147,7 +148,7 @@ namespace NCC.Extend -- 消耗业绩(总金额) COALESCE(SUM(project.F_TotalPrice), 0) as ConsumePerformance FROM lq_xh_hyhk consume - LEFT JOIN lq_mdxx store ON consume.Md = store.F_Id + INNER JOIN lq_mdxx store ON consume.Md = store.F_Id AND store.zxzt = '开店' LEFT JOIN lq_xh_pxmx project ON consume.F_Id = project.F_ConsumeInfoId AND project.F_IsEffective = 1 WHERE consume.F_IsEffective = 1 AND DATE(consume.Hksj) >= '{startDate:yyyy-MM-dd}' @@ -226,7 +227,7 @@ namespace NCC.Extend storeFilter = $"AND store.F_Id IN ('{storeIdsStr}')"; } - // SQL查询:获取门店业绩完成情况 + // SQL查询:获取门店业绩完成情况(只统计zxzt为"开店"的门店) var sql = $@" SELECT store.F_Id as StoreId, @@ -253,7 +254,7 @@ namespace NCC.Extend ), 0) as RefundPerformance FROM lq_mdxx store LEFT JOIN lq_md_target target ON target.F_StoreId = store.F_Id AND target.F_Month = '{month}' - WHERE 1=1 {storeFilter} + WHERE store.zxzt = '开店' {storeFilter} ORDER BY (BillingPerformance - RefundPerformance) DESC"; var result = await _db.Ado.SqlQueryAsync(sql); @@ -288,7 +289,7 @@ namespace NCC.Extend /// 获取事业部业绩完成情况 /// /// - /// 根据指定日期统计各事业部的业绩完成情况,包括目标业绩、完成业绩、完成率 + /// 根据指定日期统计各事业部的业绩完成情况,包括目标业绩、开单业绩、退卡金额、实际完成业绩、完成率 /// /// 示例请求: /// ```json @@ -308,7 +309,10 @@ namespace NCC.Extend /// - BusinessUnitId: 事业部ID /// - BusinessUnitName: 事业部名称 /// - TargetPerformance: 目标业绩(来自门店目标表lq_md_target的F_BusinessUnitTarget字段,根据开始时间所在月份获取,通过F_BusinessUnit字段关联,如果未查询到则为0) - /// - CompletedPerformance: 完成业绩(指定时间范围内的开单业绩总和) + /// - BillingPerformance: 开单业绩(指定时间范围内的开单业绩总和) + /// - RefundPerformance: 退款业绩(指定时间范围内的退卡业绩总和) + /// - ActualPerformance: 开单业绩(与BillingPerformance相同,用于兼容) + /// - CompletedPerformance: 实际完成业绩(开单业绩 - 退款业绩) /// - CompletionRate: 完成率(百分比,CompletedPerformance / TargetPerformance * 100) /// - StoreCount: 门店数量(根据门店目标表中归属该事业部的门店数统计) /// @@ -367,20 +371,23 @@ namespace NCC.Extend BusinessUnitId = unit.BusinessUnitId.ToString(), BusinessUnitName = unit.BusinessUnitName.ToString(), TargetPerformance = Convert.ToDecimal(unit.TargetPerformance), + BillingPerformance = 0, + RefundPerformance = 0, + ActualPerformance = 0, CompletedPerformance = 0, CompletionRate = 0, StoreCount = Convert.ToInt32(unit.StoreCount) }; } - // 第二步:统计各事业部的完成业绩 + // 第二步:统计各事业部的开单业绩 var businessUnitIds = businessUnitDict.Keys.ToList(); var unitIdsStr = string.Join("','", businessUnitIds); - var completedPerformanceSql = $@" + var billingPerformanceSql = $@" SELECT store.syb as BusinessUnitId, - COALESCE(SUM(billing.sfyj), 0) as CompletedPerformance + COALESCE(SUM(billing.sfyj), 0) as BillingPerformance FROM lq_kd_kdjlb billing INNER JOIN lq_mdxx store ON billing.djmd = store.F_Id INNER JOIN base_organize o ON store.syb = o.F_Id @@ -393,28 +400,58 @@ namespace NCC.Extend AND store.syb IN ('{unitIdsStr}') GROUP BY store.syb"; - var completedPerformanceData = await _db.Ado.SqlQueryAsync(completedPerformanceSql); + var billingPerformanceData = await _db.Ado.SqlQueryAsync(billingPerformanceSql); + + // 第三步:统计各事业部的退卡金额 + var refundPerformanceSql = $@" + SELECT + store.syb as BusinessUnitId, + COALESCE(SUM(refund.F_ActualRefundAmount), 0) as RefundPerformance + FROM lq_hytk_hytk refund + INNER JOIN lq_mdxx store ON refund.md = store.F_Id + INNER JOIN base_organize o ON store.syb = o.F_Id + WHERE refund.F_IsEffective = 1 + AND o.F_Category = 'department' + AND (o.F_DeleteMark IS NULL OR o.F_DeleteMark != 1) + AND o.F_FullName IN ('事业一部', '事业二部', '事业三部', '事业四部', '事业五部', '事业六部') + AND DATE(refund.tksj) >= '{startDate:yyyy-MM-dd}' + AND DATE(refund.tksj) <= '{endDate:yyyy-MM-dd}' + AND store.syb IN ('{unitIdsStr}') + GROUP BY store.syb"; + + var refundPerformanceData = await _db.Ado.SqlQueryAsync(refundPerformanceSql); - // 第三步:合并数据并计算完成率 - foreach (var item in completedPerformanceData) + // 第四步:合并开单业绩数据 + foreach (var item in billingPerformanceData) { var businessUnitId = item.BusinessUnitId.ToString(); if (businessUnitDict.ContainsKey(businessUnitId)) { - var completedPerformance = Convert.ToDecimal(item.CompletedPerformance); - var unit = businessUnitDict[businessUnitId]; - unit.CompletedPerformance = completedPerformance; - - var completionRate = unit.TargetPerformance > 0 - ? (completedPerformance / unit.TargetPerformance * 100m) - : 0m; - unit.CompletionRate = decimal.Round(completionRate, 2); + businessUnitDict[businessUnitId].BillingPerformance = Convert.ToDecimal(item.BillingPerformance); } } - var outputList = businessUnitDict.Values - .OrderByDescending(x => x.CompletedPerformance) - .ToList(); + // 第五步:合并退卡金额数据 + foreach (var item in refundPerformanceData) + { + var businessUnitId = item.BusinessUnitId.ToString(); + if (businessUnitDict.ContainsKey(businessUnitId)) + { + businessUnitDict[businessUnitId].RefundPerformance = Convert.ToDecimal(item.RefundPerformance); + } + } + + // 第六步:计算实际完成业绩和完成率 + foreach (var unit in businessUnitDict.Values) + { + unit.ActualPerformance = unit.BillingPerformance; // 开单业绩 + var actualCompletedPerformance = unit.BillingPerformance - unit.RefundPerformance; // 实际完成业绩 + unit.CompletedPerformance = actualCompletedPerformance; // 实际完成业绩 + var completionRate = unit.TargetPerformance > 0 ? (actualCompletedPerformance / unit.TargetPerformance * 100m) : 0m; + unit.CompletionRate = decimal.Round(completionRate, 2); + } + + var outputList = businessUnitDict.Values.OrderByDescending(x => x.CompletedPerformance).ToList(); return outputList; } @@ -425,7 +462,7 @@ namespace NCC.Extend /// 获取天王团业绩完成情况 /// /// - /// 根据指定日期统计各天王团的业绩完成情况,包括目标业绩、完成业绩、完成率 + /// 根据指定日期统计各天王团的业绩完成情况,包括目标业绩、开单业绩、退卡金额、实际完成业绩、完成率 /// /// 示例请求: /// ```json @@ -445,7 +482,10 @@ namespace NCC.Extend /// - DepartmentId: 部门ID /// - DepartmentName: 部门名称(教育一部、教育二部、科技一部、科技二部、大项目一部、大项目二部) /// - TargetPerformance: 目标业绩(来自门店目标表lq_md_target,根据部门类型使用对应字段:教育部使用F_EducationDepartmentTarget,科技部使用F_TechDepartmentTarget,大项目部使用F_MajorProjectDepartmentTarget,根据开始时间所在月份获取,通过对应归属字段关联,如果未查询到则为0) - /// - CompletedPerformance: 完成业绩(指定时间范围内的开单业绩总和) + /// - BillingPerformance: 开单业绩(指定时间范围内的开单业绩总和) + /// - RefundPerformance: 退款业绩(指定时间范围内的退卡业绩总和) + /// - ActualPerformance: 开单业绩(与BillingPerformance相同,用于兼容) + /// - CompletedPerformance: 实际完成业绩(开单业绩 - 退款业绩) /// - CompletionRate: 完成率(百分比,CompletedPerformance / TargetPerformance * 100) /// - StoreCount: 门店数量(根据门店目标表中归属该部门的门店数统计) /// @@ -516,6 +556,9 @@ namespace NCC.Extend DepartmentId = dept.DepartmentId.ToString(), DepartmentName = dept.DepartmentName.ToString(), TargetPerformance = Convert.ToDecimal(dept.TargetPerformance), + BillingPerformance = 0, + RefundPerformance = 0, + ActualPerformance = 0, CompletedPerformance = 0, CompletionRate = 0, StoreCount = Convert.ToInt32(dept.StoreCount) @@ -529,65 +572,84 @@ namespace NCC.Extend return new List(); } - // 第二步:统计各天王团的完成业绩(分类型统计) - var completedPerformanceDataList = new List(); + // 第二步:统计各天王团的开单业绩(直接按部门ID统计,避免重复计算) + var deptIdsStrForQuery = string.Join("','", departmentDict.Keys); - foreach (var deptType in tianwangDepartments) - { - var deptIdsStrForQuery = string.Join("','", departmentDict.Keys); + // 使用 OR 条件连接三个部门类型字段,确保每个开单记录只被统计一次到对应的部门 + var billingPerformanceSql = $@" + SELECT + o.F_Id as DepartmentId, + COALESCE(SUM(billing.sfyj), 0) as BillingPerformance + FROM lq_kd_kdjlb billing + INNER JOIN lq_mdxx store ON billing.djmd = store.F_Id + INNER JOIN base_organize o ON ( + (store.jyb = o.F_Id AND o.F_FullName IN ('教育一部', '教育二部')) + OR (store.kjb = o.F_Id AND o.F_FullName IN ('科技一部', '科技二部')) + OR (store.dxmb = o.F_Id AND o.F_FullName IN ('大项目一部', '大项目二部')) + ) + WHERE billing.F_IsEffective = 1 + AND o.F_Category = 'department' + AND (o.F_DeleteMark IS NULL OR o.F_DeleteMark != 1) + AND DATE(billing.kdrq) >= '{startDate:yyyy-MM-dd}' + AND DATE(billing.kdrq) <= '{endDate:yyyy-MM-dd}' + AND o.F_Id IN ('{deptIdsStrForQuery}') + GROUP BY o.F_Id"; - var completedPerformanceSql = $@" - SELECT - o.F_Id as DepartmentId, - COALESCE(SUM(billing.sfyj), 0) as CompletedPerformance - FROM lq_kd_kdjlb billing - INNER JOIN lq_mdxx store ON billing.djmd = store.F_Id - INNER JOIN base_organize o ON store.{deptType.Key} = o.F_Id - WHERE billing.F_IsEffective = 1 - AND o.F_Category = 'department' - AND (o.F_DeleteMark IS NULL OR o.F_DeleteMark != 1) - AND DATE(billing.kdrq) >= '{startDate:yyyy-MM-dd}' - AND DATE(billing.kdrq) <= '{endDate:yyyy-MM-dd}' - AND o.F_Id IN ('{deptIdsStrForQuery}') - GROUP BY o.F_Id"; + var billingPerformanceData = await _db.Ado.SqlQueryAsync(billingPerformanceSql); - var completedPerformanceData = await _db.Ado.SqlQueryAsync(completedPerformanceSql); - if (completedPerformanceData != null) + // 第三步:统计各天王团的退卡金额 + var refundPerformanceSql = $@" + SELECT + o.F_Id as DepartmentId, + COALESCE(SUM(refund.F_ActualRefundAmount), 0) as RefundPerformance + FROM lq_hytk_hytk refund + INNER JOIN lq_mdxx store ON refund.md = store.F_Id + INNER JOIN base_organize o ON ( + (store.jyb = o.F_Id AND o.F_FullName IN ('教育一部', '教育二部')) + OR (store.kjb = o.F_Id AND o.F_FullName IN ('科技一部', '科技二部')) + OR (store.dxmb = o.F_Id AND o.F_FullName IN ('大项目一部', '大项目二部')) + ) + WHERE refund.F_IsEffective = 1 + AND o.F_Category = 'department' + AND (o.F_DeleteMark IS NULL OR o.F_DeleteMark != 1) + AND DATE(refund.tksj) >= '{startDate:yyyy-MM-dd}' + AND DATE(refund.tksj) <= '{endDate:yyyy-MM-dd}' + AND o.F_Id IN ('{deptIdsStrForQuery}') + GROUP BY o.F_Id"; + + var refundPerformanceData = await _db.Ado.SqlQueryAsync(refundPerformanceSql); + + // 第四步:合并开单业绩数据 + foreach (var item in billingPerformanceData ?? Enumerable.Empty()) + { + var departmentId = item.DepartmentId.ToString(); + if (departmentDict.ContainsKey(departmentId)) { - completedPerformanceDataList.AddRange(completedPerformanceData); + departmentDict[departmentId].BillingPerformance = Convert.ToDecimal(item.BillingPerformance); } } - // 第三步:合并数据并计算完成率(按部门ID去重累加) - var departmentPerformanceDict = new Dictionary(); - foreach (var item in completedPerformanceDataList) + // 第五步:合并退卡金额数据 + foreach (var item in refundPerformanceData ?? Enumerable.Empty()) { var departmentId = item.DepartmentId.ToString(); - var completedPerformance = Convert.ToDecimal(item.CompletedPerformance); - - if (departmentPerformanceDict.ContainsKey(departmentId)) + if (departmentDict.ContainsKey(departmentId)) { - departmentPerformanceDict[departmentId] += completedPerformance; - } - else - { - departmentPerformanceDict[departmentId] = completedPerformance; + departmentDict[departmentId].RefundPerformance = Convert.ToDecimal(item.RefundPerformance); } } - // 计算完成率 - foreach (var kvp in departmentPerformanceDict) + // 第六步:计算实际完成业绩和完成率 + foreach (var dept in departmentDict.Values) { - if (departmentDict.ContainsKey(kvp.Key)) - { - var dept = departmentDict[kvp.Key]; - dept.CompletedPerformance = kvp.Value; - - var completionRate = dept.TargetPerformance > 0 - ? (kvp.Value / dept.TargetPerformance * 100m) - : 0m; - dept.CompletionRate = decimal.Round(completionRate, 2); - } + dept.ActualPerformance = dept.BillingPerformance; // 开单业绩 + var actualCompletedPerformance = dept.BillingPerformance - dept.RefundPerformance; // 实际完成业绩 + dept.CompletedPerformance = actualCompletedPerformance; // 实际完成业绩 + + var completionRate = dept.TargetPerformance > 0 + ? (actualCompletedPerformance / dept.TargetPerformance * 100m) + : 0m; + dept.CompletionRate = decimal.Round(completionRate, 2); } var outputList = departmentDict.Values @@ -1069,7 +1131,9 @@ namespace NCC.Extend }) .ToList(), }) - .OrderBy(x => x.BusinessUnitName) // 按事业部名称排序 + .ToList() + .OrderBy(x => ExtractBusinessUnitNumber(x.BusinessUnitName)) // 按事业部数字排序(事业一部、事业二部...) + .ThenBy(x => x.BusinessUnitName) // 如果数字相同,按名称排序 .ToList(); return result; @@ -1152,19 +1216,12 @@ namespace NCC.Extend // 日期(格式:11月8日) textBuilder.AppendLine($"{targetDate.Month}月{targetDate.Day}日"); - textBuilder.AppendLine(); - // 今日总业绩和总单量 textBuilder.AppendLine($"今日总业绩:{totalPerformance:F0}"); - textBuilder.AppendLine(); textBuilder.AppendLine($"今日总单量:{totalOrderCount}"); - textBuilder.AppendLine(); - // 标语 textBuilder.AppendLine("绿纤人用结果捍卫尊严 "); - textBuilder.AppendLine(); textBuilder.AppendLine("业绩捷报 "); - textBuilder.AppendLine(); // 每个事业部的数据 foreach (var unit in statistics) @@ -1175,10 +1232,7 @@ namespace NCC.Extend // 事业部业绩和单量 textBuilder.AppendLine($"{unit.BusinessUnitName}总业绩:{unit.TotalPerformance:F0}"); - textBuilder.AppendLine(); textBuilder.AppendLine($"{unit.BusinessUnitName}总单量:{unit.TotalOrderCount}"); - textBuilder.AppendLine(); - // 按门店分组订单 var ordersByStore = unit.Orders .GroupBy(x => x.StoreName) @@ -1207,7 +1261,6 @@ namespace NCC.Extend { textBuilder.AppendLine($"第{orderIndex}单:{order.StoreName}{teacherNames}{order.Amount:F0}"); } - textBuilder.AppendLine(); orderIndex++; } } @@ -1220,6 +1273,84 @@ namespace NCC.Extend throw NCCException.Oh($"获取事业部开单统计文本失败: {ex.Message}", ex); } } + + /// + /// 从事业部名称中提取数字(用于排序) + /// 例如:"事业一部" -> 1, "事业二部" -> 2, "事业三部" -> 3 + /// + /// 事业部名称 + /// 提取的数字,如果提取失败返回999(排在最后) + private int ExtractBusinessUnitNumber(string businessUnitName) + { + if (string.IsNullOrEmpty(businessUnitName)) + { + return 999; + } + + // 使用正则表达式提取数字 + var match = Regex.Match(businessUnitName, @"[一二三四五六七八九十]+|[\d]+"); + if (match.Success) + { + var numberStr = match.Value; + + // 如果是中文数字,转换为阿拉伯数字 + if (Regex.IsMatch(numberStr, @"[一二三四五六七八九十]+")) + { + var chineseNumbers = new Dictionary + { + { "一", 1 }, { "二", 2 }, { "三", 3 }, { "四", 4 }, { "五", 5 }, + { "六", 6 }, { "七", 7 }, { "八", 8 }, { "九", 9 }, { "十", 10 } + }; + + // 处理"十"的情况(如"十一"、"十二"等) + if (numberStr == "十") + { + return 10; + } + else if (numberStr.StartsWith("十")) + { + // 十一、十二...十九 + var remainder = numberStr.Substring(1); + if (chineseNumbers.ContainsKey(remainder)) + { + return 10 + chineseNumbers[remainder]; + } + } + else if (numberStr.EndsWith("十")) + { + // 二十、三十...九十 + var prefix = numberStr.Substring(0, numberStr.Length - 1); + if (chineseNumbers.ContainsKey(prefix)) + { + return chineseNumbers[prefix] * 10; + } + } + else if (numberStr.Length == 2 && numberStr.Contains("十")) + { + // 二十一、二十二...九十九 + var parts = numberStr.Split('十'); + if (parts.Length == 2) + { + var tens = parts[0] == "" ? 1 : (chineseNumbers.ContainsKey(parts[0]) ? chineseNumbers[parts[0]] : 0); + var ones = parts[1] == "" ? 0 : (chineseNumbers.ContainsKey(parts[1]) ? chineseNumbers[parts[1]] : 0); + return tens * 10 + ones; + } + } + else if (chineseNumbers.ContainsKey(numberStr)) + { + return chineseNumbers[numberStr]; + } + } + else if (int.TryParse(numberStr, out var number)) + { + // 如果是阿拉伯数字,直接返回 + return number; + } + } + + // 如果提取失败,返回999(排在最后) + return 999; + } #endregion diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqMdxxService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqMdxxService.cs index ab17b91..1680509 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend/LqMdxxService.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend/LqMdxxService.cs @@ -361,6 +361,7 @@ namespace NCC.Extend.LqMdxx public async Task GetSelector() { var list = await _db.Queryable() + .Where(it => it.Zxzt == "开店") .Select(it => new { id = it.Id, fullName = it.Dm, enCode = it.Mdbm }) .ToListAsync(); return new { list = list }; diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqPackageInfoService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqPackageInfoService.cs index eb6bc42..59d0fa8 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend/LqPackageInfoService.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend/LqPackageInfoService.cs @@ -901,6 +901,7 @@ namespace NCC.Extend.LqPackageInfo } // 5. 按品项分组统计(先获取基础统计数据) + // 注意:SqlSugar 的 AggregateSum 可能不支持三元运算符,需要先查询数据再计算 var itemStatistics = await baseQuery .GroupBy((px, kd) => new { ItemId = px.Px, ItemName = px.Pxmc }) .Select((px, kd) => new @@ -908,14 +909,25 @@ namespace NCC.Extend.LqPackageInfo ItemId = px.Px ?? "", ItemName = px.Pxmc ?? "", SalesQuantity = SqlFunc.AggregateSum(px.ProjectNumber), - SalesAmount = SqlFunc.AggregateSum(px.ActualPrice > 0 ? px.ActualPrice : px.TotalPrice), + SalesAmountActual = SqlFunc.AggregateSum(px.ActualPrice), + SalesAmountTotal = SqlFunc.AggregateSum(px.TotalPrice), SalesCount = SqlFunc.AggregateCount(px.Id) }) .ToListAsync(); + // 计算实际销售金额(ActualPrice > 0 时使用 ActualPrice,否则使用 TotalPrice) + var itemStatisticsWithAmount = itemStatistics.Select(stat => new + { + stat.ItemId, + stat.ItemName, + stat.SalesQuantity, + SalesAmount = stat.SalesAmountActual > 0 ? stat.SalesAmountActual : stat.SalesAmountTotal, + stat.SalesCount + }).ToList(); + // 6. 单独统计每个品项的开单数量(去重开单ID) var itemList = new List(); - foreach (var stat in itemStatistics) + foreach (var stat in itemStatisticsWithAmount) { var billingCountQuery = _db.Queryable( (px, kd) => px.Glkdbh == kd.Id) @@ -981,6 +993,20 @@ namespace NCC.Extend.LqPackageInfo /// "storeIds": ["门店ID1", "门店ID2"] /// } /// ``` + /// + /// 返回格式(层级结构): + /// - ActivityId: 营销活动ID + /// - ActivityName: 营销活动名称 + /// - StoreList: 门店统计列表 + /// - StoreId: 门店ID + /// - StoreName: 门店名称 + /// - ItemList: 该门店下的品项统计列表 + /// - ItemId: 品项ID + /// - ItemName: 品项名称 + /// - SalesQuantity: 销售数量(项目次数总和) + /// - SalesAmount: 销售金额 + /// - BillingCount: 开单数量(包含该品项的开单数) + /// - SalesCount: 销售次数(品项明细记录数) /// /// 查询参数 /// 按门店品项统计数据 @@ -1021,6 +1047,7 @@ namespace NCC.Extend.LqPackageInfo } // 5. 按门店和品项分组统计(先获取基础统计数据) + // 注意:SqlSugar 的 AggregateSum 可能不支持三元运算符,需要先查询数据再计算 var storeItemStatistics = await baseQuery .GroupBy((px, kd, md) => new { @@ -1036,14 +1063,28 @@ namespace NCC.Extend.LqPackageInfo ItemId = px.Px ?? "", ItemName = px.Pxmc ?? "", SalesQuantity = SqlFunc.AggregateSum(px.ProjectNumber), - SalesAmount = SqlFunc.AggregateSum(px.ActualPrice > 0 ? px.ActualPrice : px.TotalPrice), + SalesAmountActual = SqlFunc.AggregateSum(px.ActualPrice), + SalesAmountTotal = SqlFunc.AggregateSum(px.TotalPrice), SalesCount = SqlFunc.AggregateCount(px.Id) }) .ToListAsync(); - // 6. 单独统计每个门店品项的开单数量(去重开单ID) - var storeItemList = new List(); - foreach (var stat in storeItemStatistics) + // 计算实际销售金额(ActualPrice > 0 时使用 ActualPrice,否则使用 TotalPrice) + var storeItemStatisticsWithAmount = storeItemStatistics.Select(stat => new + { + stat.StoreId, + stat.StoreName, + stat.ItemId, + stat.ItemName, + stat.SalesQuantity, + SalesAmount = stat.SalesAmountActual > 0 ? stat.SalesAmountActual : stat.SalesAmountTotal, + stat.SalesCount + }).ToList(); + + // 6. 单独统计每个门店品项的开单数量(去重开单ID),并组装成层级结构 + var storeItemDict = new Dictionary>(); + + foreach (var stat in storeItemStatisticsWithAmount) { var billingCountQuery = _db.Queryable( (px, kd) => px.Glkdbh == kd.Id) @@ -1064,28 +1105,53 @@ namespace NCC.Extend.LqPackageInfo .Select((px, kd) => px.Glkdbh) .CountAsync(); - storeItemList.Add(new StoreItemStatisticsItem + var itemStat = new StoreItemStatisticsItem { - StoreId = stat.StoreId ?? "", - StoreName = stat.StoreName ?? "", ItemId = stat.ItemId ?? "", ItemName = stat.ItemName ?? "", SalesQuantity = stat.SalesQuantity, SalesAmount = stat.SalesAmount, BillingCount = billingCount, SalesCount = stat.SalesCount - }); + }; + + // 按门店分组 + var storeKey = stat.StoreId ?? ""; + if (!storeItemDict.ContainsKey(storeKey)) + { + storeItemDict[storeKey] = new List(); + } + storeItemDict[storeKey].Add(itemStat); } - // 7. 排序 - storeItemList = storeItemList.OrderBy(x => x.StoreName).ThenBy(x => x.ItemName).ToList(); + // 7. 组装层级结构:门店 -> 品项列表 + var storeList = new List(); + foreach (var kvp in storeItemDict) + { + var firstItem = storeItemStatistics.FirstOrDefault(s => s.StoreId == kvp.Key); + if (firstItem != null) + { + // 对品项列表排序 + var sortedItemList = kvp.Value.OrderBy(x => x.ItemName).ToList(); - // 7. 返回统计结果 + storeList.Add(new StoreStatisticsWithItems + { + StoreId = firstItem.StoreId ?? "", + StoreName = firstItem.StoreName ?? "", + ItemList = sortedItemList + }); + } + } + + // 8. 按门店名称排序 + storeList = storeList.OrderBy(x => x.StoreName).ToList(); + + // 9. 返回统计结果 return new ActivityStatisticsByStoreItemOutput { ActivityId = input.ActivityId, ActivityName = activity.ActivityName, - StoreItemList = storeItemList + StoreList = storeList }; } catch (Exception ex) diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqStatisticsService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqStatisticsService.cs index 9101b8c..e79ab7c 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend/LqStatisticsService.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend/LqStatisticsService.cs @@ -1073,45 +1073,62 @@ namespace NCC.Extend.LqStatistics var startDate = new DateTime(now.Year, now.Month, 1); var endDate = startDate.AddMonths(1).AddDays(-1); - // 构建查询参数 - var parameters = new Dictionary { { "@startDate", startDate.ToString("yyyy-MM-dd 00:00:00") }, { "@endDate", endDate.ToString("yyyy-MM-dd 23:59:59") } }; + // 获取当前月份(YYYYMM格式) + var currentMonth = now.ToString("yyyyMM"); - // 查询本月全门店统计数据 - var sql = - @" + // 1. 获取本月的门店目标业绩总和(从门店目标表获取 F_StoreTarget 字段,按月份过滤) + var targetPerformanceSql = @" SELECT - SUM(target_performance) AS TotalTargetPerformance, - SUM(actual_performance) AS TotalActualPerformance, - CASE - WHEN SUM(target_performance) > 0 - THEN (SUM(actual_performance) / SUM(target_performance)) * 100 - ELSE 0 - END AS TotalCompletionRate - FROM v_store_performance_simple"; + COALESCE(SUM(F_StoreTarget), 0) AS TotalTargetPerformance + FROM lq_md_target + WHERE F_Month = @month"; - var result = await _db.Ado.SqlQueryAsync(sql, parameters); - var stats = result.FirstOrDefault(); + var targetParameters = new Dictionary + { + { "@month", currentMonth } + }; - if (stats != null) + var targetResult = await _db.Ado.SqlQueryAsync(targetPerformanceSql, targetParameters); + var targetPerformance = 0m; + if (targetResult != null && targetResult.Any()) { - var targetPerformance = Convert.ToDecimal(stats.TotalTargetPerformance ?? 0); - var actualPerformance = Convert.ToDecimal(stats.TotalActualPerformance ?? 0); - var completionRate = Convert.ToDecimal(stats.TotalCompletionRate ?? 0); + targetPerformance = Convert.ToDecimal(targetResult.FirstOrDefault()?.TotalTargetPerformance ?? 0); + } - return new - { - TargetPerformance = targetPerformance, - ActualPerformance = actualPerformance, - CompletionRate = completionRate, - Month = now.ToString("yyyy年MM月"), - }; + // 2. 从开单记录表获取本月实付金额的总和(sfyj 字段) + var actualPerformanceSql = @" + SELECT + COALESCE(SUM(sfyj), 0) AS TotalActualPerformance + FROM lq_kd_kdjlb + WHERE F_IsEffective = 1 + AND kdrq >= @startDate + AND kdrq <= @endDate"; + + var parameters = new Dictionary + { + { "@startDate", startDate.ToString("yyyy-MM-dd 00:00:00") }, + { "@endDate", endDate.ToString("yyyy-MM-dd 23:59:59") } + }; + + var actualResult = await _db.Ado.SqlQueryAsync(actualPerformanceSql, parameters); + var actualPerformance = 0m; + if (actualResult != null && actualResult.Any()) + { + actualPerformance = Convert.ToDecimal(actualResult.FirstOrDefault()?.TotalActualPerformance ?? 0); + } + + // 3. 计算完成率 + var completionRate = 0m; + if (targetPerformance > 0) + { + completionRate = (actualPerformance / targetPerformance) * 100m; } return new { - TargetPerformance = 0m, - ActualPerformance = 0m, - CompletionRate = 0m, + TargetPerformance = targetPerformance, + ActualPerformance = actualPerformance, + CompletionRate = decimal.Round(completionRate, 2), Month = now.ToString("yyyy年MM月"), }; } @@ -3478,13 +3495,17 @@ namespace NCC.Extend.LqStatistics /// - BillingCount: 开单数量 /// - BillingAmount: 开单金额 /// - BillingProjectCount: 开单项目数(项目次数总和) - /// - ConsumeCount: 消耗数量 + /// - ConsumeCount: 消耗单数(消耗主表的去重记录数) /// - ConsumeAmount: 消耗金额 - /// - ConsumeProjectCount: 消耗项目数(项目次数总和) - /// - RefundCount: 退卡数量 + /// - ConsumeProjectCount: 消耗项目数(项目次数总和,包含原始+加班+陪同) + /// - ConsumeOriginalProjectCount: 消耗原始项目数(原始项目次数总和) + /// - ConsumeOvertimeProjectCount: 消耗加班项目数(加班项目次数总和) + /// - ConsumeAccompaniedProjectCount: 消耗陪同项目数(陪同项目次数总和) + /// - RefundCount: 退卡单数量 /// - RefundAmount: 退卡金额 /// - HeadCount: 人头(月度去重客户数) /// - PersonCount: 人次(日度去重客户数) + /// - LaborCost: 手工费(消耗手工费总和) /// /// 查询参数 /// 员工业绩统计数据 @@ -3532,8 +3553,11 @@ namespace NCC.Extend.LqStatistics // 9. 开单项目数统计 var billingProjectCount = await GetBillingProjectCount(input.UserId, statisticsMonth); - // 10. 消耗项目数统计 - var consumeProjectCount = await GetConsumeProjectCount(input.UserId, statisticsMonth); + // 10. 消耗项目数统计(包含总项目数、原始项目数、加班项目数、陪同项目数) + var consumeProjectStats = await GetConsumeProjectCountDetails(input.UserId, statisticsMonth); + + // 11. 手工费统计 + var laborCost = await GetLaborCost(input.UserId, statisticsMonth); return new EmployeePerformanceStatisticsOutput { @@ -3551,7 +3575,11 @@ namespace NCC.Extend.LqStatistics HeadCount = headCount, PersonCount = personCount, BillingProjectCount = billingProjectCount, - ConsumeProjectCount = consumeProjectCount + ConsumeProjectCount = consumeProjectStats.TotalCount, + ConsumeOriginalProjectCount = consumeProjectStats.OriginalCount, + ConsumeOvertimeProjectCount = consumeProjectStats.OvertimeCount, + ConsumeAccompaniedProjectCount = consumeProjectStats.AccompaniedCount, + LaborCost = laborCost }; } catch (Exception ex) @@ -3630,9 +3658,10 @@ namespace NCC.Extend.LqStatistics /// private async Task<(int Count, decimal Amount)> GetConsumeStats(string userId, string month) { + // 统计消耗单数(消耗主表的去重记录数)和消耗金额(健康师业绩总和) var sql = $@" SELECT - COUNT(*) as Count, + COUNT(DISTINCT hyhk.F_Id) as Count, COALESCE(SUM(jksyj.jksyj), 0) as Amount FROM lq_xh_jksyj jksyj INNER JOIN lq_xh_hyhk hyhk ON jksyj.glkdbh = hyhk.F_Id @@ -3651,14 +3680,17 @@ namespace NCC.Extend.LqStatistics /// private async Task<(int Count, decimal Amount)> GetRefundStats(string userId, string month) { + // 统计退卡单数(退卡主表的去重记录数)和退卡金额(健康师业绩总和) var sql = $@" SELECT - COUNT(*) as Count, - COALESCE(SUM(jksyj), 0) as Amount - FROM lq_hytk_jksyj - WHERE jkszh = '{userId}' - AND F_IsEffective = 1 - AND DATE_FORMAT(tksj, '%Y%m') = '{month}'"; + COUNT(DISTINCT hytk.F_Id) as Count, + COALESCE(SUM(jksyj.jksyj), 0) as Amount + FROM lq_hytk_jksyj jksyj + INNER JOIN lq_hytk_hytk hytk ON jksyj.gltkbh = hytk.F_Id + WHERE jksyj.jkszh = '{userId}' + AND jksyj.F_IsEffective = 1 + AND hytk.F_IsEffective = 1 + AND DATE_FORMAT(hytk.tksj, '%Y%m') = '{month}'"; var result = await _db.Ado.SqlQueryAsync(sql); var data = result.FirstOrDefault(); @@ -3737,6 +3769,53 @@ namespace NCC.Extend.LqStatistics return Convert.ToInt32(result.FirstOrDefault()?.Count ?? 0); } + /// + /// 统计消耗项目数详情(包含总项目数、原始项目数、加班项目数、陪同项目数) + /// + private async Task<(int TotalCount, decimal OriginalCount, decimal OvertimeCount, decimal AccompaniedCount)> GetConsumeProjectCountDetails(string userId, string month) + { + var sql = $@" + SELECT + COALESCE(SUM(pxmx.F_ProjectNumber), 0) as TotalCount, + COALESCE(SUM(COALESCE(pxmx.F_OriginalProjectNumber, pxmx.F_ProjectNumber)), 0) as OriginalCount, + COALESCE(SUM(COALESCE(pxmx.F_OvertimeProjectNumber, 0)), 0) as OvertimeCount, + COALESCE(SUM(COALESCE(jksyj.F_AccompaniedProjectNumber, 0)), 0) as AccompaniedCount + FROM lq_xh_jksyj jksyj + INNER JOIN lq_xh_hyhk hyhk ON jksyj.glkdbh = hyhk.F_Id + INNER JOIN lq_xh_pxmx pxmx ON pxmx.F_ConsumeInfoId = hyhk.F_Id + WHERE jksyj.jkszh = '{userId}' + AND jksyj.F_IsEffective = 1 + AND hyhk.F_IsEffective = 1 + AND DATE_FORMAT(hyhk.hksj, '%Y%m') = '{month}'"; + + var result = await _db.Ado.SqlQueryAsync(sql); + var data = result.FirstOrDefault(); + return ( + Convert.ToInt32(data?.TotalCount ?? 0), + Convert.ToDecimal(data?.OriginalCount ?? 0), + Convert.ToDecimal(data?.OvertimeCount ?? 0), + Convert.ToDecimal(data?.AccompaniedCount ?? 0) + ); + } + + /// + /// 统计手工费(消耗手工费总和) + /// + private async Task GetLaborCost(string userId, string month) + { + var sql = $@" + SELECT COALESCE(SUM(jksyj.F_LaborCost), 0) as LaborCost + FROM lq_xh_jksyj jksyj + INNER JOIN lq_xh_hyhk hyhk ON jksyj.glkdbh = hyhk.F_Id + WHERE jksyj.jkszh = '{userId}' + AND jksyj.F_IsEffective = 1 + AND hyhk.F_IsEffective = 1 + AND DATE_FORMAT(hyhk.hksj, '%Y%m') = '{month}'"; + + var result = await _db.Ado.SqlQueryAsync(sql); + return Convert.ToDecimal(result.FirstOrDefault()?.LaborCost ?? 0); + } + #endregion } } diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqXhHyhkService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqXhHyhkService.cs index 63bccf5..dac043f 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend/LqXhHyhkService.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend/LqXhHyhkService.cs @@ -964,6 +964,13 @@ namespace NCC.Extend.LqXhHyhk { // 开启事务 _db.BeginTran(); + // 查询会员信息 + var memberInfo = await _db.Queryable().Where(w => w.Id == entity.Hy).FirstAsync(); + //如果会员类型是线索,那么就更新为新客 + if (memberInfo.Khlx == MemberTypeEnum.线索.GetHashCode().ToString()) + { + memberInfo.Khlx = MemberTypeEnum.新客.GetHashCode().ToString(); + } // 新增会员耗卡记录 var newEntity = await _db.Insertable(entity).IgnoreColumns(ignoreNullColumn: true).ExecuteReturnEntityAsync(); // 收集所有需要插入的实体,然后批量插入