Commit 6f98913a43d5200b84fdd47dab62f347a54a8f08

Authored by 李宇
2 parents 0102df95 15ec2754

Merge branch 'master' of http://39.98.150.180/antissoft/lvqianmeiye_ERP

netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqDailyReport/BusinessUnitPerformanceCompletionOutput.cs
@@ -24,12 +24,27 @@ namespace NCC.Extend.Entitys.Dto.LqDailyReport @@ -24,12 +24,27 @@ namespace NCC.Extend.Entitys.Dto.LqDailyReport
24 public decimal TargetPerformance { get; set; } 24 public decimal TargetPerformance { get; set; }
25 25
26 /// <summary> 26 /// <summary>
27 - /// 完成业绩(管理门店的开单业绩总和) 27 + /// 开单业绩(管理门店的开单业绩总和)
  28 + /// </summary>
  29 + public decimal BillingPerformance { get; set; }
  30 +
  31 + /// <summary>
  32 + /// 退款业绩(退卡业绩总和)
  33 + /// </summary>
  34 + public decimal RefundPerformance { get; set; }
  35 +
  36 + /// <summary>
  37 + /// 开单业绩(管理门店的开单业绩总和)
  38 + /// </summary>
  39 + public decimal ActualPerformance { get; set; }
  40 +
  41 + /// <summary>
  42 + /// 实际完成业绩(开单业绩 - 退款业绩)
28 /// </summary> 43 /// </summary>
29 public decimal CompletedPerformance { get; set; } 44 public decimal CompletedPerformance { get; set; }
30 45
31 /// <summary> 46 /// <summary>
32 - /// 完成率(百分比 47 + /// 完成率(百分比,基于实际完成业绩计算
33 /// </summary> 48 /// </summary>
34 public decimal CompletionRate { get; set; } 49 public decimal CompletionRate { get; set; }
35 50
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqDailyReport/TianwangGroupPerformanceCompletionOutput.cs
@@ -24,12 +24,27 @@ namespace NCC.Extend.Entitys.Dto.LqDailyReport @@ -24,12 +24,27 @@ namespace NCC.Extend.Entitys.Dto.LqDailyReport
24 public decimal TargetPerformance { get; set; } 24 public decimal TargetPerformance { get; set; }
25 25
26 /// <summary> 26 /// <summary>
27 - /// 完成业绩(管理门店的开单业绩总和) 27 + /// 开单业绩(管理门店的开单业绩总和)
  28 + /// </summary>
  29 + public decimal BillingPerformance { get; set; }
  30 +
  31 + /// <summary>
  32 + /// 退款业绩(退卡业绩总和)
  33 + /// </summary>
  34 + public decimal RefundPerformance { get; set; }
  35 +
  36 + /// <summary>
  37 + /// 开单业绩(与BillingPerformance相同,用于兼容)
  38 + /// </summary>
  39 + public decimal ActualPerformance { get; set; }
  40 +
  41 + /// <summary>
  42 + /// 实际完成业绩(开单业绩 - 退款业绩)
28 /// </summary> 43 /// </summary>
29 public decimal CompletedPerformance { get; set; } 44 public decimal CompletedPerformance { get; set; }
30 45
31 /// <summary> 46 /// <summary>
32 - /// 完成率(百分比 47 + /// 完成率(百分比,基于实际完成业绩计算
33 /// </summary> 48 /// </summary>
34 public decimal CompletionRate { get; set; } 49 public decimal CompletionRate { get; set; }
35 50
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqPackageInfo/ActivityStatisticsByStoreItemOutput.cs
@@ -18,15 +18,15 @@ namespace NCC.Extend.Entitys.Dto.LqPackageInfo @@ -18,15 +18,15 @@ namespace NCC.Extend.Entitys.Dto.LqPackageInfo
18 public string ActivityName { get; set; } 18 public string ActivityName { get; set; }
19 19
20 /// <summary> 20 /// <summary>
21 - /// 门店品项统计列表 21 + /// 门店统计列表(层级结构:每个门店下包含品项列表)
22 /// </summary> 22 /// </summary>
23 - public List<StoreItemStatisticsItem> StoreItemList { get; set; } 23 + public List<StoreStatisticsWithItems> StoreList { get; set; }
24 } 24 }
25 25
26 /// <summary> 26 /// <summary>
27 - /// 门店品项统计项 27 + /// 门店统计(包含品项列表)
28 /// </summary> 28 /// </summary>
29 - public class StoreItemStatisticsItem 29 + public class StoreStatisticsWithItems
30 { 30 {
31 /// <summary> 31 /// <summary>
32 /// 门店ID 32 /// 门店ID
@@ -39,6 +39,17 @@ namespace NCC.Extend.Entitys.Dto.LqPackageInfo @@ -39,6 +39,17 @@ namespace NCC.Extend.Entitys.Dto.LqPackageInfo
39 public string StoreName { get; set; } 39 public string StoreName { get; set; }
40 40
41 /// <summary> 41 /// <summary>
  42 + /// 该门店下的品项统计列表
  43 + /// </summary>
  44 + public List<StoreItemStatisticsItem> ItemList { get; set; }
  45 + }
  46 +
  47 + /// <summary>
  48 + /// 门店品项统计项
  49 + /// </summary>
  50 + public class StoreItemStatisticsItem
  51 + {
  52 + /// <summary>
42 /// 品项ID 53 /// 品项ID
43 /// </summary> 54 /// </summary>
44 public string ItemId { get; set; } 55 public string ItemId { get; set; }
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqStatistics/EmployeePerformanceStatisticsOutput.cs
@@ -41,7 +41,7 @@ namespace NCC.Extend.Entitys.Dto.LqStatistics @@ -41,7 +41,7 @@ namespace NCC.Extend.Entitys.Dto.LqStatistics
41 public decimal BillingAmount { get; set; } 41 public decimal BillingAmount { get; set; }
42 42
43 /// <summary> 43 /// <summary>
44 - /// 消耗数量 44 + /// 消耗单数(消耗主表的去重记录数)
45 /// </summary> 45 /// </summary>
46 public int ConsumeCount { get; set; } 46 public int ConsumeCount { get; set; }
47 47
@@ -51,7 +51,7 @@ namespace NCC.Extend.Entitys.Dto.LqStatistics @@ -51,7 +51,7 @@ namespace NCC.Extend.Entitys.Dto.LqStatistics
51 public decimal ConsumeAmount { get; set; } 51 public decimal ConsumeAmount { get; set; }
52 52
53 /// <summary> 53 /// <summary>
54 - /// 退卡数量 54 + /// 退卡单数量(退卡主表的去重记录数)
55 /// </summary> 55 /// </summary>
56 public int RefundCount { get; set; } 56 public int RefundCount { get; set; }
57 57
@@ -76,8 +76,28 @@ namespace NCC.Extend.Entitys.Dto.LqStatistics @@ -76,8 +76,28 @@ namespace NCC.Extend.Entitys.Dto.LqStatistics
76 public int BillingProjectCount { get; set; } 76 public int BillingProjectCount { get; set; }
77 77
78 /// <summary> 78 /// <summary>
79 - /// 消耗项目数(项目次数总和 79 + /// 消耗项目数(项目次数总和,包含原始+加班+陪同
80 /// </summary> 80 /// </summary>
81 public int ConsumeProjectCount { get; set; } 81 public int ConsumeProjectCount { get; set; }
  82 +
  83 + /// <summary>
  84 + /// 消耗原始项目数(原始项目次数总和)
  85 + /// </summary>
  86 + public decimal ConsumeOriginalProjectCount { get; set; }
  87 +
  88 + /// <summary>
  89 + /// 消耗加班项目数(加班项目次数总和)
  90 + /// </summary>
  91 + public decimal ConsumeOvertimeProjectCount { get; set; }
  92 +
  93 + /// <summary>
  94 + /// 消耗陪同项目数(陪同项目次数总和)
  95 + /// </summary>
  96 + public decimal ConsumeAccompaniedProjectCount { get; set; }
  97 +
  98 + /// <summary>
  99 + /// 手工费(消耗手工费总和)
  100 + /// </summary>
  101 + public decimal LaborCost { get; set; }
82 } 102 }
83 } 103 }
netcore/src/Modularity/Extend/NCC.Extend/LqDailyReportService.cs
@@ -13,6 +13,7 @@ using System; @@ -13,6 +13,7 @@ using System;
13 using System.Collections.Generic; 13 using System.Collections.Generic;
14 using System.Linq; 14 using System.Linq;
15 using System.Text; 15 using System.Text;
  16 +using System.Text.RegularExpressions;
16 using System.Threading.Tasks; 17 using System.Threading.Tasks;
17 using Yitter.IdGenerator; 18 using Yitter.IdGenerator;
18 using NCC.Common.Helper; 19 using NCC.Common.Helper;
@@ -124,7 +125,7 @@ namespace NCC.Extend @@ -124,7 +125,7 @@ namespace NCC.Extend
124 storeFilter = $"AND consume.Md IN ('{storeIdsStr}')"; 125 storeFilter = $"AND consume.Md IN ('{storeIdsStr}')";
125 } 126 }
126 127
127 - // SQL查询:获取门店每日运营数据 128 + // SQL查询:获取门店每日运营数据(只统计zxzt为"开店"的门店)
128 var sql = $@" 129 var sql = $@"
129 SELECT 130 SELECT
130 consume.Md as StoreId, 131 consume.Md as StoreId,
@@ -147,7 +148,7 @@ namespace NCC.Extend @@ -147,7 +148,7 @@ namespace NCC.Extend
147 -- 消耗业绩(总金额) 148 -- 消耗业绩(总金额)
148 COALESCE(SUM(project.F_TotalPrice), 0) as ConsumePerformance 149 COALESCE(SUM(project.F_TotalPrice), 0) as ConsumePerformance
149 FROM lq_xh_hyhk consume 150 FROM lq_xh_hyhk consume
150 - LEFT JOIN lq_mdxx store ON consume.Md = store.F_Id 151 + INNER JOIN lq_mdxx store ON consume.Md = store.F_Id AND store.zxzt = '开店'
151 LEFT JOIN lq_xh_pxmx project ON consume.F_Id = project.F_ConsumeInfoId AND project.F_IsEffective = 1 152 LEFT JOIN lq_xh_pxmx project ON consume.F_Id = project.F_ConsumeInfoId AND project.F_IsEffective = 1
152 WHERE consume.F_IsEffective = 1 153 WHERE consume.F_IsEffective = 1
153 AND DATE(consume.Hksj) >= '{startDate:yyyy-MM-dd}' 154 AND DATE(consume.Hksj) >= '{startDate:yyyy-MM-dd}'
@@ -226,7 +227,7 @@ namespace NCC.Extend @@ -226,7 +227,7 @@ namespace NCC.Extend
226 storeFilter = $"AND store.F_Id IN ('{storeIdsStr}')"; 227 storeFilter = $"AND store.F_Id IN ('{storeIdsStr}')";
227 } 228 }
228 229
229 - // SQL查询:获取门店业绩完成情况 230 + // SQL查询:获取门店业绩完成情况(只统计zxzt为"开店"的门店)
230 var sql = $@" 231 var sql = $@"
231 SELECT 232 SELECT
232 store.F_Id as StoreId, 233 store.F_Id as StoreId,
@@ -253,7 +254,7 @@ namespace NCC.Extend @@ -253,7 +254,7 @@ namespace NCC.Extend
253 ), 0) as RefundPerformance 254 ), 0) as RefundPerformance
254 FROM lq_mdxx store 255 FROM lq_mdxx store
255 LEFT JOIN lq_md_target target ON target.F_StoreId = store.F_Id AND target.F_Month = '{month}' 256 LEFT JOIN lq_md_target target ON target.F_StoreId = store.F_Id AND target.F_Month = '{month}'
256 - WHERE 1=1 {storeFilter} 257 + WHERE store.zxzt = '开店' {storeFilter}
257 ORDER BY (BillingPerformance - RefundPerformance) DESC"; 258 ORDER BY (BillingPerformance - RefundPerformance) DESC";
258 259
259 var result = await _db.Ado.SqlQueryAsync<dynamic>(sql); 260 var result = await _db.Ado.SqlQueryAsync<dynamic>(sql);
@@ -288,7 +289,7 @@ namespace NCC.Extend @@ -288,7 +289,7 @@ namespace NCC.Extend
288 /// 获取事业部业绩完成情况 289 /// 获取事业部业绩完成情况
289 /// </summary> 290 /// </summary>
290 /// <remarks> 291 /// <remarks>
291 - /// 根据指定日期统计各事业部的业绩完成情况,包括目标业绩、完成业绩、完成率 292 + /// 根据指定日期统计各事业部的业绩完成情况,包括目标业绩、开单业绩、退卡金额、实际完成业绩、完成率
292 /// 293 ///
293 /// 示例请求: 294 /// 示例请求:
294 /// ```json 295 /// ```json
@@ -308,7 +309,10 @@ namespace NCC.Extend @@ -308,7 +309,10 @@ namespace NCC.Extend
308 /// - BusinessUnitId: 事业部ID 309 /// - BusinessUnitId: 事业部ID
309 /// - BusinessUnitName: 事业部名称 310 /// - BusinessUnitName: 事业部名称
310 /// - TargetPerformance: 目标业绩(来自门店目标表lq_md_target的F_BusinessUnitTarget字段,根据开始时间所在月份获取,通过F_BusinessUnit字段关联,如果未查询到则为0) 311 /// - TargetPerformance: 目标业绩(来自门店目标表lq_md_target的F_BusinessUnitTarget字段,根据开始时间所在月份获取,通过F_BusinessUnit字段关联,如果未查询到则为0)
311 - /// - CompletedPerformance: 完成业绩(指定时间范围内的开单业绩总和) 312 + /// - BillingPerformance: 开单业绩(指定时间范围内的开单业绩总和)
  313 + /// - RefundPerformance: 退款业绩(指定时间范围内的退卡业绩总和)
  314 + /// - ActualPerformance: 开单业绩(与BillingPerformance相同,用于兼容)
  315 + /// - CompletedPerformance: 实际完成业绩(开单业绩 - 退款业绩)
312 /// - CompletionRate: 完成率(百分比,CompletedPerformance / TargetPerformance * 100) 316 /// - CompletionRate: 完成率(百分比,CompletedPerformance / TargetPerformance * 100)
313 /// - StoreCount: 门店数量(根据门店目标表中归属该事业部的门店数统计) 317 /// - StoreCount: 门店数量(根据门店目标表中归属该事业部的门店数统计)
314 /// </remarks> 318 /// </remarks>
@@ -367,20 +371,23 @@ namespace NCC.Extend @@ -367,20 +371,23 @@ namespace NCC.Extend
367 BusinessUnitId = unit.BusinessUnitId.ToString(), 371 BusinessUnitId = unit.BusinessUnitId.ToString(),
368 BusinessUnitName = unit.BusinessUnitName.ToString(), 372 BusinessUnitName = unit.BusinessUnitName.ToString(),
369 TargetPerformance = Convert.ToDecimal(unit.TargetPerformance), 373 TargetPerformance = Convert.ToDecimal(unit.TargetPerformance),
  374 + BillingPerformance = 0,
  375 + RefundPerformance = 0,
  376 + ActualPerformance = 0,
370 CompletedPerformance = 0, 377 CompletedPerformance = 0,
371 CompletionRate = 0, 378 CompletionRate = 0,
372 StoreCount = Convert.ToInt32(unit.StoreCount) 379 StoreCount = Convert.ToInt32(unit.StoreCount)
373 }; 380 };
374 } 381 }
375 382
376 - // 第二步:统计各事业部的完成业绩 383 + // 第二步:统计各事业部的开单业绩
377 var businessUnitIds = businessUnitDict.Keys.ToList(); 384 var businessUnitIds = businessUnitDict.Keys.ToList();
378 var unitIdsStr = string.Join("','", businessUnitIds); 385 var unitIdsStr = string.Join("','", businessUnitIds);
379 386
380 - var completedPerformanceSql = $@" 387 + var billingPerformanceSql = $@"
381 SELECT 388 SELECT
382 store.syb as BusinessUnitId, 389 store.syb as BusinessUnitId,
383 - COALESCE(SUM(billing.sfyj), 0) as CompletedPerformance 390 + COALESCE(SUM(billing.sfyj), 0) as BillingPerformance
384 FROM lq_kd_kdjlb billing 391 FROM lq_kd_kdjlb billing
385 INNER JOIN lq_mdxx store ON billing.djmd = store.F_Id 392 INNER JOIN lq_mdxx store ON billing.djmd = store.F_Id
386 INNER JOIN base_organize o ON store.syb = o.F_Id 393 INNER JOIN base_organize o ON store.syb = o.F_Id
@@ -393,28 +400,58 @@ namespace NCC.Extend @@ -393,28 +400,58 @@ namespace NCC.Extend
393 AND store.syb IN ('{unitIdsStr}') 400 AND store.syb IN ('{unitIdsStr}')
394 GROUP BY store.syb"; 401 GROUP BY store.syb";
395 402
396 - var completedPerformanceData = await _db.Ado.SqlQueryAsync<dynamic>(completedPerformanceSql); 403 + var billingPerformanceData = await _db.Ado.SqlQueryAsync<dynamic>(billingPerformanceSql);
  404 +
  405 + // 第三步:统计各事业部的退卡金额
  406 + var refundPerformanceSql = $@"
  407 + SELECT
  408 + store.syb as BusinessUnitId,
  409 + COALESCE(SUM(refund.F_ActualRefundAmount), 0) as RefundPerformance
  410 + FROM lq_hytk_hytk refund
  411 + INNER JOIN lq_mdxx store ON refund.md = store.F_Id
  412 + INNER JOIN base_organize o ON store.syb = o.F_Id
  413 + WHERE refund.F_IsEffective = 1
  414 + AND o.F_Category = 'department'
  415 + AND (o.F_DeleteMark IS NULL OR o.F_DeleteMark != 1)
  416 + AND o.F_FullName IN ('事业一部', '事业二部', '事业三部', '事业四部', '事业五部', '事业六部')
  417 + AND DATE(refund.tksj) >= '{startDate:yyyy-MM-dd}'
  418 + AND DATE(refund.tksj) <= '{endDate:yyyy-MM-dd}'
  419 + AND store.syb IN ('{unitIdsStr}')
  420 + GROUP BY store.syb";
  421 +
  422 + var refundPerformanceData = await _db.Ado.SqlQueryAsync<dynamic>(refundPerformanceSql);
397 423
398 - // 第三步:合并数据并计算完成率  
399 - foreach (var item in completedPerformanceData) 424 + // 第四步:合并开单业绩数据
  425 + foreach (var item in billingPerformanceData)
400 { 426 {
401 var businessUnitId = item.BusinessUnitId.ToString(); 427 var businessUnitId = item.BusinessUnitId.ToString();
402 if (businessUnitDict.ContainsKey(businessUnitId)) 428 if (businessUnitDict.ContainsKey(businessUnitId))
403 { 429 {
404 - var completedPerformance = Convert.ToDecimal(item.CompletedPerformance);  
405 - var unit = businessUnitDict[businessUnitId];  
406 - unit.CompletedPerformance = completedPerformance;  
407 -  
408 - var completionRate = unit.TargetPerformance > 0  
409 - ? (completedPerformance / unit.TargetPerformance * 100m)  
410 - : 0m;  
411 - unit.CompletionRate = decimal.Round(completionRate, 2); 430 + businessUnitDict[businessUnitId].BillingPerformance = Convert.ToDecimal(item.BillingPerformance);
412 } 431 }
413 } 432 }
414 433
415 - var outputList = businessUnitDict.Values  
416 - .OrderByDescending(x => x.CompletedPerformance)  
417 - .ToList(); 434 + // 第五步:合并退卡金额数据
  435 + foreach (var item in refundPerformanceData)
  436 + {
  437 + var businessUnitId = item.BusinessUnitId.ToString();
  438 + if (businessUnitDict.ContainsKey(businessUnitId))
  439 + {
  440 + businessUnitDict[businessUnitId].RefundPerformance = Convert.ToDecimal(item.RefundPerformance);
  441 + }
  442 + }
  443 +
  444 + // 第六步:计算实际完成业绩和完成率
  445 + foreach (var unit in businessUnitDict.Values)
  446 + {
  447 + unit.ActualPerformance = unit.BillingPerformance; // 开单业绩
  448 + var actualCompletedPerformance = unit.BillingPerformance - unit.RefundPerformance; // 实际完成业绩
  449 + unit.CompletedPerformance = actualCompletedPerformance; // 实际完成业绩
  450 + var completionRate = unit.TargetPerformance > 0 ? (actualCompletedPerformance / unit.TargetPerformance * 100m) : 0m;
  451 + unit.CompletionRate = decimal.Round(completionRate, 2);
  452 + }
  453 +
  454 + var outputList = businessUnitDict.Values.OrderByDescending(x => x.CompletedPerformance).ToList();
418 455
419 return outputList; 456 return outputList;
420 } 457 }
@@ -425,7 +462,7 @@ namespace NCC.Extend @@ -425,7 +462,7 @@ namespace NCC.Extend
425 /// 获取天王团业绩完成情况 462 /// 获取天王团业绩完成情况
426 /// </summary> 463 /// </summary>
427 /// <remarks> 464 /// <remarks>
428 - /// 根据指定日期统计各天王团的业绩完成情况,包括目标业绩、完成业绩、完成率 465 + /// 根据指定日期统计各天王团的业绩完成情况,包括目标业绩、开单业绩、退卡金额、实际完成业绩、完成率
429 /// 466 ///
430 /// 示例请求: 467 /// 示例请求:
431 /// ```json 468 /// ```json
@@ -445,7 +482,10 @@ namespace NCC.Extend @@ -445,7 +482,10 @@ namespace NCC.Extend
445 /// - DepartmentId: 部门ID 482 /// - DepartmentId: 部门ID
446 /// - DepartmentName: 部门名称(教育一部、教育二部、科技一部、科技二部、大项目一部、大项目二部) 483 /// - DepartmentName: 部门名称(教育一部、教育二部、科技一部、科技二部、大项目一部、大项目二部)
447 /// - TargetPerformance: 目标业绩(来自门店目标表lq_md_target,根据部门类型使用对应字段:教育部使用F_EducationDepartmentTarget,科技部使用F_TechDepartmentTarget,大项目部使用F_MajorProjectDepartmentTarget,根据开始时间所在月份获取,通过对应归属字段关联,如果未查询到则为0) 484 /// - TargetPerformance: 目标业绩(来自门店目标表lq_md_target,根据部门类型使用对应字段:教育部使用F_EducationDepartmentTarget,科技部使用F_TechDepartmentTarget,大项目部使用F_MajorProjectDepartmentTarget,根据开始时间所在月份获取,通过对应归属字段关联,如果未查询到则为0)
448 - /// - CompletedPerformance: 完成业绩(指定时间范围内的开单业绩总和) 485 + /// - BillingPerformance: 开单业绩(指定时间范围内的开单业绩总和)
  486 + /// - RefundPerformance: 退款业绩(指定时间范围内的退卡业绩总和)
  487 + /// - ActualPerformance: 开单业绩(与BillingPerformance相同,用于兼容)
  488 + /// - CompletedPerformance: 实际完成业绩(开单业绩 - 退款业绩)
449 /// - CompletionRate: 完成率(百分比,CompletedPerformance / TargetPerformance * 100) 489 /// - CompletionRate: 完成率(百分比,CompletedPerformance / TargetPerformance * 100)
450 /// - StoreCount: 门店数量(根据门店目标表中归属该部门的门店数统计) 490 /// - StoreCount: 门店数量(根据门店目标表中归属该部门的门店数统计)
451 /// </remarks> 491 /// </remarks>
@@ -516,6 +556,9 @@ namespace NCC.Extend @@ -516,6 +556,9 @@ namespace NCC.Extend
516 DepartmentId = dept.DepartmentId.ToString(), 556 DepartmentId = dept.DepartmentId.ToString(),
517 DepartmentName = dept.DepartmentName.ToString(), 557 DepartmentName = dept.DepartmentName.ToString(),
518 TargetPerformance = Convert.ToDecimal(dept.TargetPerformance), 558 TargetPerformance = Convert.ToDecimal(dept.TargetPerformance),
  559 + BillingPerformance = 0,
  560 + RefundPerformance = 0,
  561 + ActualPerformance = 0,
519 CompletedPerformance = 0, 562 CompletedPerformance = 0,
520 CompletionRate = 0, 563 CompletionRate = 0,
521 StoreCount = Convert.ToInt32(dept.StoreCount) 564 StoreCount = Convert.ToInt32(dept.StoreCount)
@@ -529,65 +572,84 @@ namespace NCC.Extend @@ -529,65 +572,84 @@ namespace NCC.Extend
529 return new List<TianwangGroupPerformanceCompletionOutput>(); 572 return new List<TianwangGroupPerformanceCompletionOutput>();
530 } 573 }
531 574
532 - // 第二步:统计各天王团的完成业绩(分类型统计)  
533 - var completedPerformanceDataList = new List<dynamic>(); 575 + // 第二步:统计各天王团的开单业绩(直接按部门ID统计,避免重复计算)
  576 + var deptIdsStrForQuery = string.Join("','", departmentDict.Keys);
534 577
535 - foreach (var deptType in tianwangDepartments)  
536 - {  
537 - var deptIdsStrForQuery = string.Join("','", departmentDict.Keys); 578 + // 使用 OR 条件连接三个部门类型字段,确保每个开单记录只被统计一次到对应的部门
  579 + var billingPerformanceSql = $@"
  580 + SELECT
  581 + o.F_Id as DepartmentId,
  582 + COALESCE(SUM(billing.sfyj), 0) as BillingPerformance
  583 + FROM lq_kd_kdjlb billing
  584 + INNER JOIN lq_mdxx store ON billing.djmd = store.F_Id
  585 + INNER JOIN base_organize o ON (
  586 + (store.jyb = o.F_Id AND o.F_FullName IN ('教育一部', '教育二部'))
  587 + OR (store.kjb = o.F_Id AND o.F_FullName IN ('科技一部', '科技二部'))
  588 + OR (store.dxmb = o.F_Id AND o.F_FullName IN ('大项目一部', '大项目二部'))
  589 + )
  590 + WHERE billing.F_IsEffective = 1
  591 + AND o.F_Category = 'department'
  592 + AND (o.F_DeleteMark IS NULL OR o.F_DeleteMark != 1)
  593 + AND DATE(billing.kdrq) >= '{startDate:yyyy-MM-dd}'
  594 + AND DATE(billing.kdrq) <= '{endDate:yyyy-MM-dd}'
  595 + AND o.F_Id IN ('{deptIdsStrForQuery}')
  596 + GROUP BY o.F_Id";
538 597
539 - var completedPerformanceSql = $@"  
540 - SELECT  
541 - o.F_Id as DepartmentId,  
542 - COALESCE(SUM(billing.sfyj), 0) as CompletedPerformance  
543 - FROM lq_kd_kdjlb billing  
544 - INNER JOIN lq_mdxx store ON billing.djmd = store.F_Id  
545 - INNER JOIN base_organize o ON store.{deptType.Key} = o.F_Id  
546 - WHERE billing.F_IsEffective = 1  
547 - AND o.F_Category = 'department'  
548 - AND (o.F_DeleteMark IS NULL OR o.F_DeleteMark != 1)  
549 - AND DATE(billing.kdrq) >= '{startDate:yyyy-MM-dd}'  
550 - AND DATE(billing.kdrq) <= '{endDate:yyyy-MM-dd}'  
551 - AND o.F_Id IN ('{deptIdsStrForQuery}')  
552 - GROUP BY o.F_Id"; 598 + var billingPerformanceData = await _db.Ado.SqlQueryAsync<dynamic>(billingPerformanceSql);
553 599
554 - var completedPerformanceData = await _db.Ado.SqlQueryAsync<dynamic>(completedPerformanceSql);  
555 - if (completedPerformanceData != null) 600 + // 第三步:统计各天王团的退卡金额
  601 + var refundPerformanceSql = $@"
  602 + SELECT
  603 + o.F_Id as DepartmentId,
  604 + COALESCE(SUM(refund.F_ActualRefundAmount), 0) as RefundPerformance
  605 + FROM lq_hytk_hytk refund
  606 + INNER JOIN lq_mdxx store ON refund.md = store.F_Id
  607 + INNER JOIN base_organize o ON (
  608 + (store.jyb = o.F_Id AND o.F_FullName IN ('教育一部', '教育二部'))
  609 + OR (store.kjb = o.F_Id AND o.F_FullName IN ('科技一部', '科技二部'))
  610 + OR (store.dxmb = o.F_Id AND o.F_FullName IN ('大项目一部', '大项目二部'))
  611 + )
  612 + WHERE refund.F_IsEffective = 1
  613 + AND o.F_Category = 'department'
  614 + AND (o.F_DeleteMark IS NULL OR o.F_DeleteMark != 1)
  615 + AND DATE(refund.tksj) >= '{startDate:yyyy-MM-dd}'
  616 + AND DATE(refund.tksj) <= '{endDate:yyyy-MM-dd}'
  617 + AND o.F_Id IN ('{deptIdsStrForQuery}')
  618 + GROUP BY o.F_Id";
  619 +
  620 + var refundPerformanceData = await _db.Ado.SqlQueryAsync<dynamic>(refundPerformanceSql);
  621 +
  622 + // 第四步:合并开单业绩数据
  623 + foreach (var item in billingPerformanceData ?? Enumerable.Empty<dynamic>())
  624 + {
  625 + var departmentId = item.DepartmentId.ToString();
  626 + if (departmentDict.ContainsKey(departmentId))
556 { 627 {
557 - completedPerformanceDataList.AddRange(completedPerformanceData); 628 + departmentDict[departmentId].BillingPerformance = Convert.ToDecimal(item.BillingPerformance);
558 } 629 }
559 } 630 }
560 631
561 - // 第三步:合并数据并计算完成率(按部门ID去重累加)  
562 - var departmentPerformanceDict = new Dictionary<string, decimal>();  
563 - foreach (var item in completedPerformanceDataList) 632 + // 第五步:合并退卡金额数据
  633 + foreach (var item in refundPerformanceData ?? Enumerable.Empty<dynamic>())
564 { 634 {
565 var departmentId = item.DepartmentId.ToString(); 635 var departmentId = item.DepartmentId.ToString();
566 - var completedPerformance = Convert.ToDecimal(item.CompletedPerformance);  
567 -  
568 - if (departmentPerformanceDict.ContainsKey(departmentId)) 636 + if (departmentDict.ContainsKey(departmentId))
569 { 637 {
570 - departmentPerformanceDict[departmentId] += completedPerformance;  
571 - }  
572 - else  
573 - {  
574 - departmentPerformanceDict[departmentId] = completedPerformance; 638 + departmentDict[departmentId].RefundPerformance = Convert.ToDecimal(item.RefundPerformance);
575 } 639 }
576 } 640 }
577 641
578 - // 计算完成率  
579 - foreach (var kvp in departmentPerformanceDict) 642 + // 第六步:计算实际完成业绩和完成率
  643 + foreach (var dept in departmentDict.Values)
580 { 644 {
581 - if (departmentDict.ContainsKey(kvp.Key))  
582 - {  
583 - var dept = departmentDict[kvp.Key];  
584 - dept.CompletedPerformance = kvp.Value;  
585 -  
586 - var completionRate = dept.TargetPerformance > 0  
587 - ? (kvp.Value / dept.TargetPerformance * 100m)  
588 - : 0m;  
589 - dept.CompletionRate = decimal.Round(completionRate, 2);  
590 - } 645 + dept.ActualPerformance = dept.BillingPerformance; // 开单业绩
  646 + var actualCompletedPerformance = dept.BillingPerformance - dept.RefundPerformance; // 实际完成业绩
  647 + dept.CompletedPerformance = actualCompletedPerformance; // 实际完成业绩
  648 +
  649 + var completionRate = dept.TargetPerformance > 0
  650 + ? (actualCompletedPerformance / dept.TargetPerformance * 100m)
  651 + : 0m;
  652 + dept.CompletionRate = decimal.Round(completionRate, 2);
591 } 653 }
592 654
593 var outputList = departmentDict.Values 655 var outputList = departmentDict.Values
@@ -1069,7 +1131,9 @@ namespace NCC.Extend @@ -1069,7 +1131,9 @@ namespace NCC.Extend
1069 }) 1131 })
1070 .ToList(), 1132 .ToList(),
1071 }) 1133 })
1072 - .OrderBy(x => x.BusinessUnitName) // 按事业部名称排序 1134 + .ToList()
  1135 + .OrderBy(x => ExtractBusinessUnitNumber(x.BusinessUnitName)) // 按事业部数字排序(事业一部、事业二部...)
  1136 + .ThenBy(x => x.BusinessUnitName) // 如果数字相同,按名称排序
1073 .ToList(); 1137 .ToList();
1074 1138
1075 return result; 1139 return result;
@@ -1152,19 +1216,12 @@ namespace NCC.Extend @@ -1152,19 +1216,12 @@ namespace NCC.Extend
1152 1216
1153 // 日期(格式:11月8日) 1217 // 日期(格式:11月8日)
1154 textBuilder.AppendLine($"{targetDate.Month}月{targetDate.Day}日"); 1218 textBuilder.AppendLine($"{targetDate.Month}月{targetDate.Day}日");
1155 - textBuilder.AppendLine();  
1156 -  
1157 // 今日总业绩和总单量 1219 // 今日总业绩和总单量
1158 textBuilder.AppendLine($"今日总业绩:{totalPerformance:F0}"); 1220 textBuilder.AppendLine($"今日总业绩:{totalPerformance:F0}");
1159 - textBuilder.AppendLine();  
1160 textBuilder.AppendLine($"今日总单量:{totalOrderCount}"); 1221 textBuilder.AppendLine($"今日总单量:{totalOrderCount}");
1161 - textBuilder.AppendLine();  
1162 -  
1163 // 标语 1222 // 标语
1164 textBuilder.AppendLine("绿纤人用结果捍卫尊严 "); 1223 textBuilder.AppendLine("绿纤人用结果捍卫尊严 ");
1165 - textBuilder.AppendLine();  
1166 textBuilder.AppendLine("业绩捷报 "); 1224 textBuilder.AppendLine("业绩捷报 ");
1167 - textBuilder.AppendLine();  
1168 1225
1169 // 每个事业部的数据 1226 // 每个事业部的数据
1170 foreach (var unit in statistics) 1227 foreach (var unit in statistics)
@@ -1175,10 +1232,7 @@ namespace NCC.Extend @@ -1175,10 +1232,7 @@ namespace NCC.Extend
1175 1232
1176 // 事业部业绩和单量 1233 // 事业部业绩和单量
1177 textBuilder.AppendLine($"{unit.BusinessUnitName}总业绩:{unit.TotalPerformance:F0}"); 1234 textBuilder.AppendLine($"{unit.BusinessUnitName}总业绩:{unit.TotalPerformance:F0}");
1178 - textBuilder.AppendLine();  
1179 textBuilder.AppendLine($"{unit.BusinessUnitName}总单量:{unit.TotalOrderCount}"); 1235 textBuilder.AppendLine($"{unit.BusinessUnitName}总单量:{unit.TotalOrderCount}");
1180 - textBuilder.AppendLine();  
1181 -  
1182 // 按门店分组订单 1236 // 按门店分组订单
1183 var ordersByStore = unit.Orders 1237 var ordersByStore = unit.Orders
1184 .GroupBy(x => x.StoreName) 1238 .GroupBy(x => x.StoreName)
@@ -1207,7 +1261,6 @@ namespace NCC.Extend @@ -1207,7 +1261,6 @@ namespace NCC.Extend
1207 { 1261 {
1208 textBuilder.AppendLine($"第{orderIndex}单:{order.StoreName}{teacherNames}{order.Amount:F0}"); 1262 textBuilder.AppendLine($"第{orderIndex}单:{order.StoreName}{teacherNames}{order.Amount:F0}");
1209 } 1263 }
1210 - textBuilder.AppendLine();  
1211 orderIndex++; 1264 orderIndex++;
1212 } 1265 }
1213 } 1266 }
@@ -1220,6 +1273,84 @@ namespace NCC.Extend @@ -1220,6 +1273,84 @@ namespace NCC.Extend
1220 throw NCCException.Oh($"获取事业部开单统计文本失败: {ex.Message}", ex); 1273 throw NCCException.Oh($"获取事业部开单统计文本失败: {ex.Message}", ex);
1221 } 1274 }
1222 } 1275 }
  1276 +
  1277 + /// <summary>
  1278 + /// 从事业部名称中提取数字(用于排序)
  1279 + /// 例如:"事业一部" -> 1, "事业二部" -> 2, "事业三部" -> 3
  1280 + /// </summary>
  1281 + /// <param name="businessUnitName">事业部名称</param>
  1282 + /// <returns>提取的数字,如果提取失败返回999(排在最后)</returns>
  1283 + private int ExtractBusinessUnitNumber(string businessUnitName)
  1284 + {
  1285 + if (string.IsNullOrEmpty(businessUnitName))
  1286 + {
  1287 + return 999;
  1288 + }
  1289 +
  1290 + // 使用正则表达式提取数字
  1291 + var match = Regex.Match(businessUnitName, @"[一二三四五六七八九十]+|[\d]+");
  1292 + if (match.Success)
  1293 + {
  1294 + var numberStr = match.Value;
  1295 +
  1296 + // 如果是中文数字,转换为阿拉伯数字
  1297 + if (Regex.IsMatch(numberStr, @"[一二三四五六七八九十]+"))
  1298 + {
  1299 + var chineseNumbers = new Dictionary<string, int>
  1300 + {
  1301 + { "一", 1 }, { "二", 2 }, { "三", 3 }, { "四", 4 }, { "五", 5 },
  1302 + { "六", 6 }, { "七", 7 }, { "八", 8 }, { "九", 9 }, { "十", 10 }
  1303 + };
  1304 +
  1305 + // 处理"十"的情况(如"十一"、"十二"等)
  1306 + if (numberStr == "十")
  1307 + {
  1308 + return 10;
  1309 + }
  1310 + else if (numberStr.StartsWith("十"))
  1311 + {
  1312 + // 十一、十二...十九
  1313 + var remainder = numberStr.Substring(1);
  1314 + if (chineseNumbers.ContainsKey(remainder))
  1315 + {
  1316 + return 10 + chineseNumbers[remainder];
  1317 + }
  1318 + }
  1319 + else if (numberStr.EndsWith("十"))
  1320 + {
  1321 + // 二十、三十...九十
  1322 + var prefix = numberStr.Substring(0, numberStr.Length - 1);
  1323 + if (chineseNumbers.ContainsKey(prefix))
  1324 + {
  1325 + return chineseNumbers[prefix] * 10;
  1326 + }
  1327 + }
  1328 + else if (numberStr.Length == 2 && numberStr.Contains("十"))
  1329 + {
  1330 + // 二十一、二十二...九十九
  1331 + var parts = numberStr.Split('十');
  1332 + if (parts.Length == 2)
  1333 + {
  1334 + var tens = parts[0] == "" ? 1 : (chineseNumbers.ContainsKey(parts[0]) ? chineseNumbers[parts[0]] : 0);
  1335 + var ones = parts[1] == "" ? 0 : (chineseNumbers.ContainsKey(parts[1]) ? chineseNumbers[parts[1]] : 0);
  1336 + return tens * 10 + ones;
  1337 + }
  1338 + }
  1339 + else if (chineseNumbers.ContainsKey(numberStr))
  1340 + {
  1341 + return chineseNumbers[numberStr];
  1342 + }
  1343 + }
  1344 + else if (int.TryParse(numberStr, out var number))
  1345 + {
  1346 + // 如果是阿拉伯数字,直接返回
  1347 + return number;
  1348 + }
  1349 + }
  1350 +
  1351 + // 如果提取失败,返回999(排在最后)
  1352 + return 999;
  1353 + }
1223 #endregion 1354 #endregion
1224 1355
1225 1356
netcore/src/Modularity/Extend/NCC.Extend/LqMdxxService.cs
@@ -361,6 +361,7 @@ namespace NCC.Extend.LqMdxx @@ -361,6 +361,7 @@ namespace NCC.Extend.LqMdxx
361 public async Task<dynamic> GetSelector() 361 public async Task<dynamic> GetSelector()
362 { 362 {
363 var list = await _db.Queryable<LqMdxxEntity>() 363 var list = await _db.Queryable<LqMdxxEntity>()
  364 + .Where(it => it.Zxzt == "开店")
364 .Select(it => new { id = it.Id, fullName = it.Dm, enCode = it.Mdbm }) 365 .Select(it => new { id = it.Id, fullName = it.Dm, enCode = it.Mdbm })
365 .ToListAsync(); 366 .ToListAsync();
366 return new { list = list }; 367 return new { list = list };
netcore/src/Modularity/Extend/NCC.Extend/LqPackageInfoService.cs
@@ -901,6 +901,7 @@ namespace NCC.Extend.LqPackageInfo @@ -901,6 +901,7 @@ namespace NCC.Extend.LqPackageInfo
901 } 901 }
902 902
903 // 5. 按品项分组统计(先获取基础统计数据) 903 // 5. 按品项分组统计(先获取基础统计数据)
  904 + // 注意:SqlSugar 的 AggregateSum 可能不支持三元运算符,需要先查询数据再计算
904 var itemStatistics = await baseQuery 905 var itemStatistics = await baseQuery
905 .GroupBy((px, kd) => new { ItemId = px.Px, ItemName = px.Pxmc }) 906 .GroupBy((px, kd) => new { ItemId = px.Px, ItemName = px.Pxmc })
906 .Select((px, kd) => new 907 .Select((px, kd) => new
@@ -908,14 +909,25 @@ namespace NCC.Extend.LqPackageInfo @@ -908,14 +909,25 @@ namespace NCC.Extend.LqPackageInfo
908 ItemId = px.Px ?? "", 909 ItemId = px.Px ?? "",
909 ItemName = px.Pxmc ?? "", 910 ItemName = px.Pxmc ?? "",
910 SalesQuantity = SqlFunc.AggregateSum(px.ProjectNumber), 911 SalesQuantity = SqlFunc.AggregateSum(px.ProjectNumber),
911 - SalesAmount = SqlFunc.AggregateSum(px.ActualPrice > 0 ? px.ActualPrice : px.TotalPrice), 912 + SalesAmountActual = SqlFunc.AggregateSum(px.ActualPrice),
  913 + SalesAmountTotal = SqlFunc.AggregateSum(px.TotalPrice),
912 SalesCount = SqlFunc.AggregateCount(px.Id) 914 SalesCount = SqlFunc.AggregateCount(px.Id)
913 }) 915 })
914 .ToListAsync(); 916 .ToListAsync();
915 917
  918 + // 计算实际销售金额(ActualPrice > 0 时使用 ActualPrice,否则使用 TotalPrice)
  919 + var itemStatisticsWithAmount = itemStatistics.Select(stat => new
  920 + {
  921 + stat.ItemId,
  922 + stat.ItemName,
  923 + stat.SalesQuantity,
  924 + SalesAmount = stat.SalesAmountActual > 0 ? stat.SalesAmountActual : stat.SalesAmountTotal,
  925 + stat.SalesCount
  926 + }).ToList();
  927 +
916 // 6. 单独统计每个品项的开单数量(去重开单ID) 928 // 6. 单独统计每个品项的开单数量(去重开单ID)
917 var itemList = new List<ItemStatisticsItem>(); 929 var itemList = new List<ItemStatisticsItem>();
918 - foreach (var stat in itemStatistics) 930 + foreach (var stat in itemStatisticsWithAmount)
919 { 931 {
920 var billingCountQuery = _db.Queryable<LqKdPxmxEntity, LqKdKdjlbEntity>( 932 var billingCountQuery = _db.Queryable<LqKdPxmxEntity, LqKdKdjlbEntity>(
921 (px, kd) => px.Glkdbh == kd.Id) 933 (px, kd) => px.Glkdbh == kd.Id)
@@ -981,6 +993,20 @@ namespace NCC.Extend.LqPackageInfo @@ -981,6 +993,20 @@ namespace NCC.Extend.LqPackageInfo
981 /// "storeIds": ["门店ID1", "门店ID2"] 993 /// "storeIds": ["门店ID1", "门店ID2"]
982 /// } 994 /// }
983 /// ``` 995 /// ```
  996 + ///
  997 + /// 返回格式(层级结构):
  998 + /// - ActivityId: 营销活动ID
  999 + /// - ActivityName: 营销活动名称
  1000 + /// - StoreList: 门店统计列表
  1001 + /// - StoreId: 门店ID
  1002 + /// - StoreName: 门店名称
  1003 + /// - ItemList: 该门店下的品项统计列表
  1004 + /// - ItemId: 品项ID
  1005 + /// - ItemName: 品项名称
  1006 + /// - SalesQuantity: 销售数量(项目次数总和)
  1007 + /// - SalesAmount: 销售金额
  1008 + /// - BillingCount: 开单数量(包含该品项的开单数)
  1009 + /// - SalesCount: 销售次数(品项明细记录数)
984 /// </remarks> 1010 /// </remarks>
985 /// <param name="input">查询参数</param> 1011 /// <param name="input">查询参数</param>
986 /// <returns>按门店品项统计数据</returns> 1012 /// <returns>按门店品项统计数据</returns>
@@ -1021,6 +1047,7 @@ namespace NCC.Extend.LqPackageInfo @@ -1021,6 +1047,7 @@ namespace NCC.Extend.LqPackageInfo
1021 } 1047 }
1022 1048
1023 // 5. 按门店和品项分组统计(先获取基础统计数据) 1049 // 5. 按门店和品项分组统计(先获取基础统计数据)
  1050 + // 注意:SqlSugar 的 AggregateSum 可能不支持三元运算符,需要先查询数据再计算
1024 var storeItemStatistics = await baseQuery 1051 var storeItemStatistics = await baseQuery
1025 .GroupBy((px, kd, md) => new 1052 .GroupBy((px, kd, md) => new
1026 { 1053 {
@@ -1036,14 +1063,28 @@ namespace NCC.Extend.LqPackageInfo @@ -1036,14 +1063,28 @@ namespace NCC.Extend.LqPackageInfo
1036 ItemId = px.Px ?? "", 1063 ItemId = px.Px ?? "",
1037 ItemName = px.Pxmc ?? "", 1064 ItemName = px.Pxmc ?? "",
1038 SalesQuantity = SqlFunc.AggregateSum(px.ProjectNumber), 1065 SalesQuantity = SqlFunc.AggregateSum(px.ProjectNumber),
1039 - SalesAmount = SqlFunc.AggregateSum(px.ActualPrice > 0 ? px.ActualPrice : px.TotalPrice), 1066 + SalesAmountActual = SqlFunc.AggregateSum(px.ActualPrice),
  1067 + SalesAmountTotal = SqlFunc.AggregateSum(px.TotalPrice),
1040 SalesCount = SqlFunc.AggregateCount(px.Id) 1068 SalesCount = SqlFunc.AggregateCount(px.Id)
1041 }) 1069 })
1042 .ToListAsync(); 1070 .ToListAsync();
1043 1071
1044 - // 6. 单独统计每个门店品项的开单数量(去重开单ID)  
1045 - var storeItemList = new List<StoreItemStatisticsItem>();  
1046 - foreach (var stat in storeItemStatistics) 1072 + // 计算实际销售金额(ActualPrice > 0 时使用 ActualPrice,否则使用 TotalPrice)
  1073 + var storeItemStatisticsWithAmount = storeItemStatistics.Select(stat => new
  1074 + {
  1075 + stat.StoreId,
  1076 + stat.StoreName,
  1077 + stat.ItemId,
  1078 + stat.ItemName,
  1079 + stat.SalesQuantity,
  1080 + SalesAmount = stat.SalesAmountActual > 0 ? stat.SalesAmountActual : stat.SalesAmountTotal,
  1081 + stat.SalesCount
  1082 + }).ToList();
  1083 +
  1084 + // 6. 单独统计每个门店品项的开单数量(去重开单ID),并组装成层级结构
  1085 + var storeItemDict = new Dictionary<string, List<StoreItemStatisticsItem>>();
  1086 +
  1087 + foreach (var stat in storeItemStatisticsWithAmount)
1047 { 1088 {
1048 var billingCountQuery = _db.Queryable<LqKdPxmxEntity, LqKdKdjlbEntity>( 1089 var billingCountQuery = _db.Queryable<LqKdPxmxEntity, LqKdKdjlbEntity>(
1049 (px, kd) => px.Glkdbh == kd.Id) 1090 (px, kd) => px.Glkdbh == kd.Id)
@@ -1064,28 +1105,53 @@ namespace NCC.Extend.LqPackageInfo @@ -1064,28 +1105,53 @@ namespace NCC.Extend.LqPackageInfo
1064 .Select((px, kd) => px.Glkdbh) 1105 .Select((px, kd) => px.Glkdbh)
1065 .CountAsync(); 1106 .CountAsync();
1066 1107
1067 - storeItemList.Add(new StoreItemStatisticsItem 1108 + var itemStat = new StoreItemStatisticsItem
1068 { 1109 {
1069 - StoreId = stat.StoreId ?? "",  
1070 - StoreName = stat.StoreName ?? "",  
1071 ItemId = stat.ItemId ?? "", 1110 ItemId = stat.ItemId ?? "",
1072 ItemName = stat.ItemName ?? "", 1111 ItemName = stat.ItemName ?? "",
1073 SalesQuantity = stat.SalesQuantity, 1112 SalesQuantity = stat.SalesQuantity,
1074 SalesAmount = stat.SalesAmount, 1113 SalesAmount = stat.SalesAmount,
1075 BillingCount = billingCount, 1114 BillingCount = billingCount,
1076 SalesCount = stat.SalesCount 1115 SalesCount = stat.SalesCount
1077 - }); 1116 + };
  1117 +
  1118 + // 按门店分组
  1119 + var storeKey = stat.StoreId ?? "";
  1120 + if (!storeItemDict.ContainsKey(storeKey))
  1121 + {
  1122 + storeItemDict[storeKey] = new List<StoreItemStatisticsItem>();
  1123 + }
  1124 + storeItemDict[storeKey].Add(itemStat);
1078 } 1125 }
1079 1126
1080 - // 7. 排序  
1081 - storeItemList = storeItemList.OrderBy(x => x.StoreName).ThenBy(x => x.ItemName).ToList(); 1127 + // 7. 组装层级结构:门店 -> 品项列表
  1128 + var storeList = new List<StoreStatisticsWithItems>();
  1129 + foreach (var kvp in storeItemDict)
  1130 + {
  1131 + var firstItem = storeItemStatistics.FirstOrDefault(s => s.StoreId == kvp.Key);
  1132 + if (firstItem != null)
  1133 + {
  1134 + // 对品项列表排序
  1135 + var sortedItemList = kvp.Value.OrderBy(x => x.ItemName).ToList();
1082 1136
1083 - // 7. 返回统计结果 1137 + storeList.Add(new StoreStatisticsWithItems
  1138 + {
  1139 + StoreId = firstItem.StoreId ?? "",
  1140 + StoreName = firstItem.StoreName ?? "",
  1141 + ItemList = sortedItemList
  1142 + });
  1143 + }
  1144 + }
  1145 +
  1146 + // 8. 按门店名称排序
  1147 + storeList = storeList.OrderBy(x => x.StoreName).ToList();
  1148 +
  1149 + // 9. 返回统计结果
1084 return new ActivityStatisticsByStoreItemOutput 1150 return new ActivityStatisticsByStoreItemOutput
1085 { 1151 {
1086 ActivityId = input.ActivityId, 1152 ActivityId = input.ActivityId,
1087 ActivityName = activity.ActivityName, 1153 ActivityName = activity.ActivityName,
1088 - StoreItemList = storeItemList 1154 + StoreList = storeList
1089 }; 1155 };
1090 } 1156 }
1091 catch (Exception ex) 1157 catch (Exception ex)
netcore/src/Modularity/Extend/NCC.Extend/LqStatisticsService.cs
@@ -1073,45 +1073,62 @@ namespace NCC.Extend.LqStatistics @@ -1073,45 +1073,62 @@ namespace NCC.Extend.LqStatistics
1073 var startDate = new DateTime(now.Year, now.Month, 1); 1073 var startDate = new DateTime(now.Year, now.Month, 1);
1074 var endDate = startDate.AddMonths(1).AddDays(-1); 1074 var endDate = startDate.AddMonths(1).AddDays(-1);
1075 1075
1076 - // 构建查询参数  
1077 - var parameters = new Dictionary<string, object> { { "@startDate", startDate.ToString("yyyy-MM-dd 00:00:00") }, { "@endDate", endDate.ToString("yyyy-MM-dd 23:59:59") } }; 1076 + // 获取当前月份(YYYYMM格式)
  1077 + var currentMonth = now.ToString("yyyyMM");
1078 1078
1079 - // 查询本月全门店统计数据  
1080 - var sql =  
1081 - @" 1079 + // 1. 获取本月的门店目标业绩总和(从门店目标表获取 F_StoreTarget 字段,按月份过滤)
  1080 + var targetPerformanceSql = @"
1082 SELECT 1081 SELECT
1083 - SUM(target_performance) AS TotalTargetPerformance,  
1084 - SUM(actual_performance) AS TotalActualPerformance,  
1085 - CASE  
1086 - WHEN SUM(target_performance) > 0  
1087 - THEN (SUM(actual_performance) / SUM(target_performance)) * 100  
1088 - ELSE 0  
1089 - END AS TotalCompletionRate  
1090 - FROM v_store_performance_simple"; 1082 + COALESCE(SUM(F_StoreTarget), 0) AS TotalTargetPerformance
  1083 + FROM lq_md_target
  1084 + WHERE F_Month = @month";
1091 1085
1092 - var result = await _db.Ado.SqlQueryAsync<dynamic>(sql, parameters);  
1093 - var stats = result.FirstOrDefault(); 1086 + var targetParameters = new Dictionary<string, object>
  1087 + {
  1088 + { "@month", currentMonth }
  1089 + };
1094 1090
1095 - if (stats != null) 1091 + var targetResult = await _db.Ado.SqlQueryAsync<dynamic>(targetPerformanceSql, targetParameters);
  1092 + var targetPerformance = 0m;
  1093 + if (targetResult != null && targetResult.Any())
1096 { 1094 {
1097 - var targetPerformance = Convert.ToDecimal(stats.TotalTargetPerformance ?? 0);  
1098 - var actualPerformance = Convert.ToDecimal(stats.TotalActualPerformance ?? 0);  
1099 - var completionRate = Convert.ToDecimal(stats.TotalCompletionRate ?? 0); 1095 + targetPerformance = Convert.ToDecimal(targetResult.FirstOrDefault()?.TotalTargetPerformance ?? 0);
  1096 + }
1100 1097
1101 - return new  
1102 - {  
1103 - TargetPerformance = targetPerformance,  
1104 - ActualPerformance = actualPerformance,  
1105 - CompletionRate = completionRate,  
1106 - Month = now.ToString("yyyy年MM月"),  
1107 - }; 1098 + // 2. 从开单记录表获取本月实付金额的总和(sfyj 字段)
  1099 + var actualPerformanceSql = @"
  1100 + SELECT
  1101 + COALESCE(SUM(sfyj), 0) AS TotalActualPerformance
  1102 + FROM lq_kd_kdjlb
  1103 + WHERE F_IsEffective = 1
  1104 + AND kdrq >= @startDate
  1105 + AND kdrq <= @endDate";
  1106 +
  1107 + var parameters = new Dictionary<string, object>
  1108 + {
  1109 + { "@startDate", startDate.ToString("yyyy-MM-dd 00:00:00") },
  1110 + { "@endDate", endDate.ToString("yyyy-MM-dd 23:59:59") }
  1111 + };
  1112 +
  1113 + var actualResult = await _db.Ado.SqlQueryAsync<dynamic>(actualPerformanceSql, parameters);
  1114 + var actualPerformance = 0m;
  1115 + if (actualResult != null && actualResult.Any())
  1116 + {
  1117 + actualPerformance = Convert.ToDecimal(actualResult.FirstOrDefault()?.TotalActualPerformance ?? 0);
  1118 + }
  1119 +
  1120 + // 3. 计算完成率
  1121 + var completionRate = 0m;
  1122 + if (targetPerformance > 0)
  1123 + {
  1124 + completionRate = (actualPerformance / targetPerformance) * 100m;
1108 } 1125 }
1109 1126
1110 return new 1127 return new
1111 { 1128 {
1112 - TargetPerformance = 0m,  
1113 - ActualPerformance = 0m,  
1114 - CompletionRate = 0m, 1129 + TargetPerformance = targetPerformance,
  1130 + ActualPerformance = actualPerformance,
  1131 + CompletionRate = decimal.Round(completionRate, 2),
1115 Month = now.ToString("yyyy年MM月"), 1132 Month = now.ToString("yyyy年MM月"),
1116 }; 1133 };
1117 } 1134 }
@@ -3478,13 +3495,17 @@ namespace NCC.Extend.LqStatistics @@ -3478,13 +3495,17 @@ namespace NCC.Extend.LqStatistics
3478 /// - BillingCount: 开单数量 3495 /// - BillingCount: 开单数量
3479 /// - BillingAmount: 开单金额 3496 /// - BillingAmount: 开单金额
3480 /// - BillingProjectCount: 开单项目数(项目次数总和) 3497 /// - BillingProjectCount: 开单项目数(项目次数总和)
3481 - /// - ConsumeCount: 消耗数量 3498 + /// - ConsumeCount: 消耗单数(消耗主表的去重记录数)
3482 /// - ConsumeAmount: 消耗金额 3499 /// - ConsumeAmount: 消耗金额
3483 - /// - ConsumeProjectCount: 消耗项目数(项目次数总和)  
3484 - /// - RefundCount: 退卡数量 3500 + /// - ConsumeProjectCount: 消耗项目数(项目次数总和,包含原始+加班+陪同)
  3501 + /// - ConsumeOriginalProjectCount: 消耗原始项目数(原始项目次数总和)
  3502 + /// - ConsumeOvertimeProjectCount: 消耗加班项目数(加班项目次数总和)
  3503 + /// - ConsumeAccompaniedProjectCount: 消耗陪同项目数(陪同项目次数总和)
  3504 + /// - RefundCount: 退卡单数量
3485 /// - RefundAmount: 退卡金额 3505 /// - RefundAmount: 退卡金额
3486 /// - HeadCount: 人头(月度去重客户数) 3506 /// - HeadCount: 人头(月度去重客户数)
3487 /// - PersonCount: 人次(日度去重客户数) 3507 /// - PersonCount: 人次(日度去重客户数)
  3508 + /// - LaborCost: 手工费(消耗手工费总和)
3488 /// </remarks> 3509 /// </remarks>
3489 /// <param name="input">查询参数</param> 3510 /// <param name="input">查询参数</param>
3490 /// <returns>员工业绩统计数据</returns> 3511 /// <returns>员工业绩统计数据</returns>
@@ -3532,8 +3553,11 @@ namespace NCC.Extend.LqStatistics @@ -3532,8 +3553,11 @@ namespace NCC.Extend.LqStatistics
3532 // 9. 开单项目数统计 3553 // 9. 开单项目数统计
3533 var billingProjectCount = await GetBillingProjectCount(input.UserId, statisticsMonth); 3554 var billingProjectCount = await GetBillingProjectCount(input.UserId, statisticsMonth);
3534 3555
3535 - // 10. 消耗项目数统计  
3536 - var consumeProjectCount = await GetConsumeProjectCount(input.UserId, statisticsMonth); 3556 + // 10. 消耗项目数统计(包含总项目数、原始项目数、加班项目数、陪同项目数)
  3557 + var consumeProjectStats = await GetConsumeProjectCountDetails(input.UserId, statisticsMonth);
  3558 +
  3559 + // 11. 手工费统计
  3560 + var laborCost = await GetLaborCost(input.UserId, statisticsMonth);
3537 3561
3538 return new EmployeePerformanceStatisticsOutput 3562 return new EmployeePerformanceStatisticsOutput
3539 { 3563 {
@@ -3551,7 +3575,11 @@ namespace NCC.Extend.LqStatistics @@ -3551,7 +3575,11 @@ namespace NCC.Extend.LqStatistics
3551 HeadCount = headCount, 3575 HeadCount = headCount,
3552 PersonCount = personCount, 3576 PersonCount = personCount,
3553 BillingProjectCount = billingProjectCount, 3577 BillingProjectCount = billingProjectCount,
3554 - ConsumeProjectCount = consumeProjectCount 3578 + ConsumeProjectCount = consumeProjectStats.TotalCount,
  3579 + ConsumeOriginalProjectCount = consumeProjectStats.OriginalCount,
  3580 + ConsumeOvertimeProjectCount = consumeProjectStats.OvertimeCount,
  3581 + ConsumeAccompaniedProjectCount = consumeProjectStats.AccompaniedCount,
  3582 + LaborCost = laborCost
3555 }; 3583 };
3556 } 3584 }
3557 catch (Exception ex) 3585 catch (Exception ex)
@@ -3630,9 +3658,10 @@ namespace NCC.Extend.LqStatistics @@ -3630,9 +3658,10 @@ namespace NCC.Extend.LqStatistics
3630 /// </summary> 3658 /// </summary>
3631 private async Task<(int Count, decimal Amount)> GetConsumeStats(string userId, string month) 3659 private async Task<(int Count, decimal Amount)> GetConsumeStats(string userId, string month)
3632 { 3660 {
  3661 + // 统计消耗单数(消耗主表的去重记录数)和消耗金额(健康师业绩总和)
3633 var sql = $@" 3662 var sql = $@"
3634 SELECT 3663 SELECT
3635 - COUNT(*) as Count, 3664 + COUNT(DISTINCT hyhk.F_Id) as Count,
3636 COALESCE(SUM(jksyj.jksyj), 0) as Amount 3665 COALESCE(SUM(jksyj.jksyj), 0) as Amount
3637 FROM lq_xh_jksyj jksyj 3666 FROM lq_xh_jksyj jksyj
3638 INNER JOIN lq_xh_hyhk hyhk ON jksyj.glkdbh = hyhk.F_Id 3667 INNER JOIN lq_xh_hyhk hyhk ON jksyj.glkdbh = hyhk.F_Id
@@ -3651,14 +3680,17 @@ namespace NCC.Extend.LqStatistics @@ -3651,14 +3680,17 @@ namespace NCC.Extend.LqStatistics
3651 /// </summary> 3680 /// </summary>
3652 private async Task<(int Count, decimal Amount)> GetRefundStats(string userId, string month) 3681 private async Task<(int Count, decimal Amount)> GetRefundStats(string userId, string month)
3653 { 3682 {
  3683 + // 统计退卡单数(退卡主表的去重记录数)和退卡金额(健康师业绩总和)
3654 var sql = $@" 3684 var sql = $@"
3655 SELECT 3685 SELECT
3656 - COUNT(*) as Count,  
3657 - COALESCE(SUM(jksyj), 0) as Amount  
3658 - FROM lq_hytk_jksyj  
3659 - WHERE jkszh = '{userId}'  
3660 - AND F_IsEffective = 1  
3661 - AND DATE_FORMAT(tksj, '%Y%m') = '{month}'"; 3686 + COUNT(DISTINCT hytk.F_Id) as Count,
  3687 + COALESCE(SUM(jksyj.jksyj), 0) as Amount
  3688 + FROM lq_hytk_jksyj jksyj
  3689 + INNER JOIN lq_hytk_hytk hytk ON jksyj.gltkbh = hytk.F_Id
  3690 + WHERE jksyj.jkszh = '{userId}'
  3691 + AND jksyj.F_IsEffective = 1
  3692 + AND hytk.F_IsEffective = 1
  3693 + AND DATE_FORMAT(hytk.tksj, '%Y%m') = '{month}'";
3662 3694
3663 var result = await _db.Ado.SqlQueryAsync<dynamic>(sql); 3695 var result = await _db.Ado.SqlQueryAsync<dynamic>(sql);
3664 var data = result.FirstOrDefault(); 3696 var data = result.FirstOrDefault();
@@ -3737,6 +3769,53 @@ namespace NCC.Extend.LqStatistics @@ -3737,6 +3769,53 @@ namespace NCC.Extend.LqStatistics
3737 return Convert.ToInt32(result.FirstOrDefault()?.Count ?? 0); 3769 return Convert.ToInt32(result.FirstOrDefault()?.Count ?? 0);
3738 } 3770 }
3739 3771
  3772 + /// <summary>
  3773 + /// 统计消耗项目数详情(包含总项目数、原始项目数、加班项目数、陪同项目数)
  3774 + /// </summary>
  3775 + private async Task<(int TotalCount, decimal OriginalCount, decimal OvertimeCount, decimal AccompaniedCount)> GetConsumeProjectCountDetails(string userId, string month)
  3776 + {
  3777 + var sql = $@"
  3778 + SELECT
  3779 + COALESCE(SUM(pxmx.F_ProjectNumber), 0) as TotalCount,
  3780 + COALESCE(SUM(COALESCE(pxmx.F_OriginalProjectNumber, pxmx.F_ProjectNumber)), 0) as OriginalCount,
  3781 + COALESCE(SUM(COALESCE(pxmx.F_OvertimeProjectNumber, 0)), 0) as OvertimeCount,
  3782 + COALESCE(SUM(COALESCE(jksyj.F_AccompaniedProjectNumber, 0)), 0) as AccompaniedCount
  3783 + FROM lq_xh_jksyj jksyj
  3784 + INNER JOIN lq_xh_hyhk hyhk ON jksyj.glkdbh = hyhk.F_Id
  3785 + INNER JOIN lq_xh_pxmx pxmx ON pxmx.F_ConsumeInfoId = hyhk.F_Id
  3786 + WHERE jksyj.jkszh = '{userId}'
  3787 + AND jksyj.F_IsEffective = 1
  3788 + AND hyhk.F_IsEffective = 1
  3789 + AND DATE_FORMAT(hyhk.hksj, '%Y%m') = '{month}'";
  3790 +
  3791 + var result = await _db.Ado.SqlQueryAsync<dynamic>(sql);
  3792 + var data = result.FirstOrDefault();
  3793 + return (
  3794 + Convert.ToInt32(data?.TotalCount ?? 0),
  3795 + Convert.ToDecimal(data?.OriginalCount ?? 0),
  3796 + Convert.ToDecimal(data?.OvertimeCount ?? 0),
  3797 + Convert.ToDecimal(data?.AccompaniedCount ?? 0)
  3798 + );
  3799 + }
  3800 +
  3801 + /// <summary>
  3802 + /// 统计手工费(消耗手工费总和)
  3803 + /// </summary>
  3804 + private async Task<decimal> GetLaborCost(string userId, string month)
  3805 + {
  3806 + var sql = $@"
  3807 + SELECT COALESCE(SUM(jksyj.F_LaborCost), 0) as LaborCost
  3808 + FROM lq_xh_jksyj jksyj
  3809 + INNER JOIN lq_xh_hyhk hyhk ON jksyj.glkdbh = hyhk.F_Id
  3810 + WHERE jksyj.jkszh = '{userId}'
  3811 + AND jksyj.F_IsEffective = 1
  3812 + AND hyhk.F_IsEffective = 1
  3813 + AND DATE_FORMAT(hyhk.hksj, '%Y%m') = '{month}'";
  3814 +
  3815 + var result = await _db.Ado.SqlQueryAsync<dynamic>(sql);
  3816 + return Convert.ToDecimal(result.FirstOrDefault()?.LaborCost ?? 0);
  3817 + }
  3818 +
3740 #endregion 3819 #endregion
3741 } 3820 }
3742 } 3821 }
netcore/src/Modularity/Extend/NCC.Extend/LqXhHyhkService.cs
@@ -964,6 +964,13 @@ namespace NCC.Extend.LqXhHyhk @@ -964,6 +964,13 @@ namespace NCC.Extend.LqXhHyhk
964 { 964 {
965 // 开启事务 965 // 开启事务
966 _db.BeginTran(); 966 _db.BeginTran();
  967 + // 查询会员信息
  968 + var memberInfo = await _db.Queryable<LqKhxxEntity>().Where(w => w.Id == entity.Hy).FirstAsync();
  969 + //如果会员类型是线索,那么就更新为新客
  970 + if (memberInfo.Khlx == MemberTypeEnum.线索.GetHashCode().ToString())
  971 + {
  972 + memberInfo.Khlx = MemberTypeEnum.新客.GetHashCode().ToString();
  973 + }
967 // 新增会员耗卡记录 974 // 新增会员耗卡记录
968 var newEntity = await _db.Insertable(entity).IgnoreColumns(ignoreNullColumn: true).ExecuteReturnEntityAsync(); 975 var newEntity = await _db.Insertable(entity).IgnoreColumns(ignoreNullColumn: true).ExecuteReturnEntityAsync();
969 // 收集所有需要插入的实体,然后批量插入 976 // 收集所有需要插入的实体,然后批量插入