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 e35dd64..26df5a9 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
@@ -52,6 +52,11 @@ namespace NCC.Extend.Entitys.Dto.LqDailyReport
/// 门店数量
///
public int StoreCount { get; set; }
+
+ ///
+ /// 储扣金额(储值扣减金额总和)
+ ///
+ public decimal DeductAmount { get; set; }
}
}
diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqDailyReportService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqDailyReportService.cs
index 2e086ef..fc78e8d 100644
--- a/netcore/src/Modularity/Extend/NCC.Extend/LqDailyReportService.cs
+++ b/netcore/src/Modularity/Extend/NCC.Extend/LqDailyReportService.cs
@@ -456,7 +456,7 @@ namespace NCC.Extend
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);
+ unit.CompletionRate = decimal.Round(completionRate, 2);
}
var outputList = businessUnitDict.Values.OrderByDescending(x => x.CompletedPerformance).ToList();
@@ -470,17 +470,23 @@ namespace NCC.Extend
/// 获取天王团业绩完成情况
///
///
- /// 根据指定日期统计各天王团的业绩完成情况,包括目标业绩、开单业绩、退卡金额、实际完成业绩、完成率
+ /// 根据指定日期统计各天王团的业绩完成情况,包括目标业绩、开单业绩、储扣金额、退卡金额、实际完成业绩、完成率
///
/// 业绩统计规则:
- /// - 教育部(教育一部、教育二部):统计生美品项(lq_xmzl.qt2='生美')相关的开单和退卡业绩
- /// - 科技部(科技一部、科技二部):统计科美品项(lq_xmzl.qt2='科美')相关的开单和退卡业绩
- /// - 大项目部(大项目一部、大项目二部):统计医美品项(lq_xmzl.qt2='医美')相关的开单和退卡业绩
+ /// - 教育部(教育一部、教育二部):统计生美品项(lq_xmzl.qt2='生美')相关的开单、储扣和退卡业绩
+ /// - 科技部(科技一部、科技二部):统计科美品项(lq_xmzl.qt2='科美')相关的开单、储扣和退卡业绩
+ /// - 大项目部(大项目一部、大项目二部):统计医美品项(lq_xmzl.qt2='医美')相关的开单、储扣和退卡业绩
///
/// 业绩分配规则:
- /// - 生美品项的开单和退卡业绩全部归教育部(不按目标比例分配)
- /// - 科美品项的开单和退卡业绩全部归科技部(不按目标比例分配)
- /// - 医美品项的开单和退卡业绩全部归大项目部(不按目标比例分配)
+ /// - 生美品项的开单、储扣和退卡业绩全部归教育部(不按目标比例分配)
+ /// - 科美品项的开单、储扣和退卡业绩全部归科技部(不按目标比例分配)
+ /// - 医美品项的开单、储扣和退卡业绩全部归大项目部(不按目标比例分配)
+ ///
+ /// 业绩计算规则:
+ /// - 开单业绩:原始开单业绩(不减去储扣金额)
+ /// - 储扣金额:储值扣减金额总和
+ /// - 退卡业绩:退卡金额总和
+ /// - 实际完成业绩 = 开单业绩 - 储扣金额 - 退卡金额
///
/// 示例请求:
/// ```json
@@ -500,10 +506,11 @@ namespace NCC.Extend
/// - DepartmentId: 部门ID
/// - DepartmentName: 部门名称(教育一部、教育二部、科技一部、科技二部、大项目一部、大项目二部)
/// - TargetPerformance: 目标业绩(来自门店目标表lq_md_target,根据部门类型使用对应字段:教育部使用F_EducationDepartmentTarget,科技部使用F_TechDepartmentTarget,大项目部使用F_MajorProjectDepartmentTarget,根据开始时间所在月份获取,通过对应归属字段关联,如果未查询到则为0)
- /// - BillingPerformance: 开单业绩(指定时间范围内,按品项分类过滤后的开单业绩总和,按门店目标比例分配)
- /// - RefundPerformance: 退款业绩(指定时间范围内,按品项分类过滤后的退卡业绩总和,按门店目标比例分配)
+ /// - BillingPerformance: 开单业绩(原始开单业绩,不减去储扣金额)
+ /// - DeductAmount: 储扣金额(储值扣减金额总和)
+ /// - RefundPerformance: 退卡业绩(退卡金额总和)
/// - ActualPerformance: 开单业绩(与BillingPerformance相同,用于兼容)
- /// - CompletedPerformance: 实际完成业绩(开单业绩 - 退款业绩)
+ /// - CompletedPerformance: 实际完成业绩(开单业绩 - 储扣金额 - 退卡金额)
/// - CompletionRate: 完成率(百分比,CompletedPerformance / TargetPerformance * 100)
/// - StoreCount: 门店数量(根据门店目标表中归属该部门的门店数统计)
///
@@ -582,7 +589,8 @@ namespace NCC.Extend
ActualPerformance = 0,
CompletedPerformance = 0,
CompletionRate = 0,
- StoreCount = Convert.ToInt32(dept.StoreCount)
+ StoreCount = Convert.ToInt32(dept.StoreCount),
+ DeductAmount = 0
};
// 记录部门对应的字段类型
departmentFieldMap[deptId] = deptType.Value.fieldName;
@@ -620,6 +628,7 @@ namespace NCC.Extend
}
}
+ // 如果目标表中没有对应月份的数据,直接返回空业绩列表
if (!storeIds.Any())
{
return departmentDict.Values.OrderByDescending(x => x.CompletedPerformance).ToList();
@@ -630,7 +639,7 @@ namespace NCC.Extend
// 2.2 查询目标表数据(一次性查询,数据量小)
// 直接查询,不使用 MAX(),因为每个门店在目标表中应该只有一条记录
var targetSql = $@"
- SELECT
+ SELECT
F_StoreId,
F_EducationDepartment,
F_TechDepartment,
@@ -650,220 +659,163 @@ namespace NCC.Extend
}
}
- // 2.3 从品项明细表统计业绩,按门店+品项分类分组
+ // 如果targetDict为空,说明目标表中没有数据,直接返回
+ if (!targetDict.Any())
+ {
+ return departmentDict.Values.OrderByDescending(x => x.CompletedPerformance).ToList();
+ }
+
+ // 2.3 分步骤查询:按生美、科美、医美分别查询开单业绩、退卡业绩、储扣金额
// 重要:业绩从品项明细表(lq_kd_pxmx)的 F_ActualPrice 获取,不是从开单记录表的 sfyj 获取
// 因为一个开单可能包含多个品项,每个品项属于不同的分类(生美、科美、医美)
// 例如:开单A实付500元,包含科美100元、医美250元、生美150元
// 那么科技部统计100元,大项目部统计250元,教育部统计150元
- var billingSql = $@"
- SELECT
- temp.djmd as StoreId,
- item.qt2 as ItemType,
- COALESCE(SUM(temp.F_ActualPrice), 0) as StoreAmount
- FROM (
- SELECT pxmx.px, pxmx.F_ActualPrice, billing.djmd
- FROM lq_kd_pxmx pxmx
- INNER JOIN lq_kd_kdjlb billing ON pxmx.glkdbh = billing.F_Id
- WHERE pxmx.F_IsEffective = 1
- AND billing.F_IsEffective = 1
- AND billing.djmd IN ('{storeIdsStr}')
- AND billing.kdrq >= '{startDate:yyyy-MM-dd} 00:00:00'
- AND billing.kdrq < '{endDate.AddDays(1):yyyy-MM-dd} 00:00:00'
- ) temp
- INNER JOIN lq_xmzl item ON temp.px = item.F_Id
- WHERE item.F_IsEffective = 1
- AND item.qt2 IN ('生美', '科美', '医美')
- GROUP BY temp.djmd, item.qt2";
-
- var billingData = await _db.Ado.SqlQueryAsync(billingSql);
-
- // 2.4 从退卡明细表统计退卡业绩,按门店+品项分类分组
- // 重要:退卡业绩从退卡明细表(lq_hytk_mx)的 tkje 获取,按品项分类统计
- var refundSql = $@"
- SELECT
- temp.md as StoreId,
- item.qt2 as ItemType,
- COALESCE(SUM(temp.tkje), 0) as StoreRefundAmount
- FROM (
- SELECT refund_mx.px, refund_mx.tkje, refund.md
- FROM lq_hytk_mx refund_mx
- INNER JOIN lq_hytk_hytk refund ON refund_mx.F_RefundInfoId = refund.F_Id
- WHERE refund_mx.F_IsEffective = 1
- AND refund.F_IsEffective = 1
- AND refund.md IN ('{storeIdsStr}')
- AND refund.tksj >= '{startDate:yyyy-MM-dd} 00:00:00'
- AND refund.tksj < '{endDate.AddDays(1):yyyy-MM-dd} 00:00:00'
- ) temp
- INNER JOIN lq_xmzl item ON temp.px = item.F_Id
- WHERE item.F_IsEffective = 1
- AND item.qt2 IN ('生美', '科美', '医美')
- GROUP BY temp.md, item.qt2";
-
- var refundData = await _db.Ado.SqlQueryAsync(refundSql);
-
- // 2.5 从储扣明细表统计储扣金额,按门店+品项分类分组
- // 重要:储扣金额从储扣明细表(lq_kd_deductinfo)的 F_Amount 获取,按品项分类统计
- var deductSql = $@"
- SELECT
- billing.djmd as StoreId,
- item.qt2 as ItemType,
- COALESCE(SUM(deduct.F_Amount), 0) as StoreDeductAmount
- FROM lq_kd_deductinfo deduct
- INNER JOIN lq_kd_kdjlb billing ON deduct.F_BillingId = billing.F_Id
- INNER JOIN lq_xmzl item ON deduct.F_ItemId = item.F_Id
- WHERE deduct.F_IsEffective = 1
- AND billing.F_IsEffective = 1
- AND item.F_IsEffective = 1
- AND billing.djmd IN ('{storeIdsStr}')
- AND billing.kdrq >= '{startDate:yyyy-MM-dd} 00:00:00'
- AND billing.kdrq < '{endDate.AddDays(1):yyyy-MM-dd} 00:00:00'
- AND item.qt2 IN ('生美', '科美', '医美')
- GROUP BY billing.djmd, item.qt2";
-
- var deductData = await _db.Ado.SqlQueryAsync(deductSql);
-
- // 2.6 根据品项分类和目标表分配业绩到对应部门
- // 分配规则:
- // - 生美品项 → 分配给教育部门(F_EducationDepartment)
- // - 科美品项 → 分配给科技部门(F_TechDepartment)
- // - 医美品项 → 分配给大项目部门(F_MajorProjectDepartment)
- // 注意:一个门店可能同时属于多个部门,但每个品项的业绩只分配给对应的一个部门
- foreach (var billing in billingData ?? Enumerable.Empty())
- {
- var storeId = billing?.StoreId?.ToString();
- var itemType = billing?.ItemType?.ToString();
- var storeAmount = billing?.StoreAmount != null ? Convert.ToDecimal(billing.StoreAmount) : 0m;
- if (string.IsNullOrEmpty(storeId) || string.IsNullOrEmpty(itemType) || storeAmount <= 0 || !targetDict.ContainsKey(storeId))
- continue;
-
- var target = targetDict[storeId];
- if (target == null)
- continue;
+ // 品项类型列表
+ var itemTypes = new[] { "生美", "科美", "医美" };
- string targetDeptId = null;
-
- // 根据品项分类,分配到对应的部门
+ // 循环处理每个品项类型
+ foreach (var itemType in itemTypes)
+ {
+ // 确定该品项类型对应的部门字段
+ string deptField = "";
if (itemType == "生美")
{
- // 生美品项 → 教育部门
- targetDeptId = target.F_EducationDepartment?.ToString();
+ deptField = "F_EducationDepartment";
}
else if (itemType == "科美")
{
- // 科美品项 → 科技部门
- targetDeptId = target.F_TechDepartment?.ToString();
+ deptField = "F_TechDepartment";
}
else if (itemType == "医美")
{
- // 医美品项 → 大项目部门
- targetDeptId = target.F_MajorProjectDepartment?.ToString();
+ deptField = "F_MajorProjectDepartment";
}
- // 只分配给在查询列表中的部门,避免重复统计
- if (!string.IsNullOrEmpty(targetDeptId) && departmentDict.ContainsKey(targetDeptId))
- {
- departmentDict[targetDeptId].BillingPerformance += storeAmount;
- }
- }
-
- // 2.6.1 减去储扣金额
- foreach (var deduct in deductData ?? Enumerable.Empty())
- {
- var storeId = deduct?.StoreId?.ToString();
- var itemType = deduct?.ItemType?.ToString();
- var storeDeductAmount = deduct?.StoreDeductAmount != null ? Convert.ToDecimal(deduct.StoreDeductAmount) : 0m;
+ // 2.3.1 查询开单业绩(按品项类型)
+ var billingSql = $@"
+ SELECT
+ target.{deptField} as TargetDeptId,
+ COALESCE(SUM(pxmx.F_ActualPrice), 0) as StoreAmount
+ FROM lq_kd_pxmx pxmx
+ INNER JOIN lq_kd_kdjlb billing ON pxmx.glkdbh = billing.F_Id
+ INNER JOIN lq_xmzl item ON pxmx.px = item.F_Id
+ INNER JOIN lq_md_target target ON billing.djmd = target.F_StoreId AND target.F_Month = '{month}'
+ WHERE pxmx.F_IsEffective = 1
+ AND billing.F_IsEffective = 1
+ AND item.F_IsEffective = 1
+ AND item.qt2 = '{itemType}'
+ AND billing.djmd IN ('{storeIdsStr}')
+ AND billing.kdrq >= '{startDate.ToString("yyyy-MM-dd")} 00:00:00'
+ AND billing.kdrq < '{endDate.AddDays(1).ToString("yyyy-MM-dd")} 00:00:00'
+ GROUP BY target.{deptField}";
- if (string.IsNullOrEmpty(storeId) || string.IsNullOrEmpty(itemType) || storeDeductAmount <= 0 || !targetDict.ContainsKey(storeId))
- continue;
+ var billingData = await _db.Ado.SqlQueryAsync(billingSql);
- var target = targetDict[storeId];
- if (target == null)
- continue;
+ // 分配开单业绩到对应部门
+ foreach (var billing in billingData ?? Enumerable.Empty())
+ {
+ var storeAmount = billing?.StoreAmount != null ? Convert.ToDecimal(billing.StoreAmount) : 0m;
+ var targetDeptId = billing?.TargetDeptId?.ToString();
- string targetDeptId = null;
+ if (storeAmount <= 0 || string.IsNullOrEmpty(targetDeptId))
+ continue;
- // 根据品项分类,从对应的部门减去储扣金额
- if (itemType == "生美")
- {
- // 生美品项储扣 → 从教育部门减去
- targetDeptId = target.F_EducationDepartment?.ToString();
- }
- else if (itemType == "科美")
- {
- // 科美品项储扣 → 从科技部门减去
- targetDeptId = target.F_TechDepartment?.ToString();
- }
- else if (itemType == "医美")
- {
- // 医美品项储扣 → 从大项目部门减去
- targetDeptId = target.F_MajorProjectDepartment?.ToString();
+ // 只分配给在查询列表中的部门,避免重复统计
+ if (departmentDict.ContainsKey(targetDeptId))
+ {
+ departmentDict[targetDeptId].BillingPerformance += storeAmount;
+ }
}
- // 只从在查询列表中的部门减去,避免重复统计
- if (!string.IsNullOrEmpty(targetDeptId) && departmentDict.ContainsKey(targetDeptId))
+ // 2.3.2 查询退卡业绩(按品项类型)
+ var refundSql = $@"
+ SELECT
+ target.{deptField} as TargetDeptId,
+ COALESCE(SUM(refund_mx.tkje), 0) as StoreRefundAmount
+ FROM lq_hytk_mx refund_mx
+ INNER JOIN lq_hytk_hytk refund ON refund_mx.F_RefundInfoId = refund.F_Id
+ INNER JOIN lq_xmzl item ON refund_mx.px = item.F_Id
+ INNER JOIN lq_md_target target ON refund.md = target.F_StoreId AND target.F_Month = '{month}'
+ WHERE refund_mx.F_IsEffective = 1
+ AND refund.F_IsEffective = 1
+ AND item.F_IsEffective = 1
+ AND item.qt2 = '{itemType}'
+ AND refund.md IN ('{storeIdsStr}')
+ AND refund.tksj >= '{startDate.ToString("yyyy-MM-dd")} 00:00:00'
+ AND refund.tksj < '{endDate.AddDays(1).ToString("yyyy-MM-dd")} 00:00:00'
+ GROUP BY target.{deptField}";
+
+ var refundData = await _db.Ado.SqlQueryAsync(refundSql);
+
+ // 分配退卡业绩到对应部门
+ foreach (var refund in refundData ?? Enumerable.Empty())
{
- departmentDict[targetDeptId].BillingPerformance -= storeDeductAmount;
- }
- }
+ var storeRefundAmount = refund?.StoreRefundAmount != null ? Convert.ToDecimal(refund.StoreRefundAmount) : 0m;
+ var targetDeptId = refund?.TargetDeptId?.ToString();
- // 2.7 根据品项分类和目标表分配退卡业绩到对应部门
- // 分配规则与开单业绩相同:
- // - 生美品项退卡 → 分配给教育部门
- // - 科美品项退卡 → 分配给科技部门
- // - 医美品项退卡 → 分配给大项目部门
- foreach (var refund in refundData ?? Enumerable.Empty())
- {
- var storeId = refund?.StoreId?.ToString();
- var itemType = refund?.ItemType?.ToString();
- var storeRefundAmount = refund?.StoreRefundAmount != null ? Convert.ToDecimal(refund.StoreRefundAmount) : 0m;
+ if (storeRefundAmount <= 0 || string.IsNullOrEmpty(targetDeptId))
+ continue;
- if (string.IsNullOrEmpty(storeId) || string.IsNullOrEmpty(itemType) || storeRefundAmount <= 0 || !targetDict.ContainsKey(storeId))
- continue;
+ // 只分配给在查询列表中的部门,避免重复统计
+ if (departmentDict.ContainsKey(targetDeptId))
+ {
+ departmentDict[targetDeptId].RefundPerformance += storeRefundAmount;
+ }
+ }
- var target = targetDict[storeId];
- if (target == null)
- continue;
+ // 2.3.3 查询储扣金额(按品项类型)
+ var deductSql = $@"
+ SELECT
+ target.{deptField} as TargetDeptId,
+ COALESCE(SUM(deduct.F_Amount), 0) as StoreDeductAmount
+ FROM lq_kd_deductinfo deduct
+ INNER JOIN lq_kd_kdjlb billing ON deduct.F_BillingId = billing.F_Id
+ INNER JOIN lq_xmzl item ON deduct.F_ItemId = item.F_Id
+ INNER JOIN lq_md_target target ON billing.djmd = target.F_StoreId AND target.F_Month = '{month}'
+ WHERE deduct.F_IsEffective = 1
+ AND billing.F_IsEffective = 1
+ AND item.F_IsEffective = 1
+ AND item.qt2 = '{itemType}'
+ AND billing.djmd IN ('{storeIdsStr}')
+ AND billing.kdrq >= '{startDate.ToString("yyyy-MM-dd")} 00:00:00'
+ AND billing.kdrq < '{endDate.AddDays(1).ToString("yyyy-MM-dd")} 00:00:00'
+ GROUP BY target.{deptField}";
- string targetDeptId = null;
+ var deductData = await _db.Ado.SqlQueryAsync(deductSql);
- // 根据品项分类,分配到对应的部门
- if (itemType == "生美")
+ // 分配储扣金额到对应部门
+ foreach (var deduct in deductData ?? Enumerable.Empty())
{
- // 生美品项退卡 → 教育部门
- targetDeptId = target.F_EducationDepartment?.ToString();
- }
- else if (itemType == "科美")
- {
- // 科美品项退卡 → 科技部门
- targetDeptId = target.F_TechDepartment?.ToString();
- }
- else if (itemType == "医美")
- {
- // 医美品项退卡 → 大项目部门
- targetDeptId = target.F_MajorProjectDepartment?.ToString();
- }
+ var storeDeductAmount = deduct?.StoreDeductAmount != null ? Convert.ToDecimal(deduct.StoreDeductAmount) : 0m;
+ var targetDeptId = deduct?.TargetDeptId?.ToString();
- // 只分配给在查询列表中的部门,避免重复统计
- if (!string.IsNullOrEmpty(targetDeptId) && departmentDict.ContainsKey(targetDeptId))
- {
- departmentDict[targetDeptId].RefundPerformance += storeRefundAmount;
+ if (storeDeductAmount <= 0 || string.IsNullOrEmpty(targetDeptId))
+ continue;
+
+ // 只累加到在查询列表中的部门,避免重复统计
+ if (departmentDict.ContainsKey(targetDeptId))
+ {
+ departmentDict[targetDeptId].DeductAmount += storeDeductAmount;
+ }
}
}
// 第三步:计算实际完成业绩和完成率,并保留两位小数
foreach (var dept in departmentDict.Values)
{
- dept.BillingPerformance = decimal.Round(dept.BillingPerformance, 2);
- dept.RefundPerformance = decimal.Round(dept.RefundPerformance, 2);
- dept.ActualPerformance = dept.BillingPerformance; // 开单业绩
- var actualCompletedPerformance = dept.BillingPerformance - dept.RefundPerformance; // 实际完成业绩
+ dept.BillingPerformance = decimal.Round(dept.BillingPerformance, 2); // 开单业绩(原始,不减去储扣)
+ dept.RefundPerformance = decimal.Round(dept.RefundPerformance, 2); // 退卡业绩
+ dept.DeductAmount = decimal.Round(dept.DeductAmount, 2); // 储扣金额,保留两位小数
+ dept.ActualPerformance = dept.BillingPerformance; // 开单业绩(与BillingPerformance相同,用于兼容)
+ // 实际完成业绩 = 开单业绩 - 储扣金额 - 退卡金额
+ var actualCompletedPerformance = dept.BillingPerformance - dept.DeductAmount - dept.RefundPerformance;
dept.CompletedPerformance = decimal.Round(actualCompletedPerformance, 2); // 实际完成业绩,保留两位小数
- var completionRate = dept.TargetPerformance > 0
- ? (actualCompletedPerformance / dept.TargetPerformance * 100m)
- : 0m;
- dept.CompletionRate = decimal.Round(completionRate, 2);
+ var completionRate = dept.TargetPerformance > 0
+ ? (actualCompletedPerformance / dept.TargetPerformance * 100m)
+ : 0m;
+ dept.CompletionRate = decimal.Round(completionRate, 2);
}
var outputList = departmentDict.Values
@@ -1174,12 +1126,13 @@ namespace NCC.Extend
}
// SQL查询:统计科技部老师的消耗业绩、见客数、项目数
+ // 注意:GROUP BY 中移除了 user.F_RealName,避免同一老师ID因姓名不同产生重复记录
var consumeSql = $@"
SELECT
techDept.F_Id as TechDepartmentId,
techDept.F_FullName as TechDepartmentName,
consume.kjbls as TeacherId,
- user.F_RealName as TeacherName,
+ MAX(user.F_RealName) as TeacherName,
COUNT(DISTINCT hyhk.hy) as CustomerCount,
SUM(consume.F_hdpxNumber) as ConsumeProjectCount,
SUM(consume.kjblsyj) as ConsumeAchievement
@@ -1194,7 +1147,7 @@ namespace NCC.Extend
AND DATE(hyhk.hksj) <= '{endDate:yyyy-MM-dd}'
{techFilter}
{teacherFilter}
- GROUP BY techDept.F_Id, techDept.F_FullName, consume.kjbls, user.F_RealName";
+ GROUP BY techDept.F_Id, techDept.F_FullName, consume.kjbls";
var consumeResult = await _db.Ado.SqlQueryAsync(consumeSql);
@@ -1202,6 +1155,7 @@ namespace NCC.Extend
var orderSql = $@"
SELECT
techDept.F_Id as TechDepartmentId,
+ techDept.F_FullName as TechDepartmentName,
ord.kjbls as TeacherId,
SUM(ord.kjblsyj) as OrderAchievement
FROM lq_kd_kjbsyj ord
@@ -1214,36 +1168,211 @@ namespace NCC.Extend
AND DATE(kdjlb.kdrq) <= '{endDate:yyyy-MM-dd}'
{techFilter}
{teacherFilterForOrder}
- GROUP BY techDept.F_Id, ord.kjbls";
+ GROUP BY techDept.F_Id, techDept.F_FullName, ord.kjbls";
var orderResult = await _db.Ado.SqlQueryAsync(orderSql);
- // 合并数据
+ // 合并数据:按员工ID汇总,避免同一员工在多个科技部重复出现
+ // 使用 TeacherId 作为唯一键,汇总所有科技部的数据
var teacherDict = new Dictionary();
+ // 记录每个员工在各个科技部的消耗业绩,用于确定主要科技部
+ var teacherDeptConsumePerformance = new Dictionary>();
+ // 记录每个员工在各个科技部的开单业绩,用于确定主要科技部(当消耗业绩为0时)
+ var teacherDeptOrderPerformance = new Dictionary>();
+ // 第一步:处理消耗数据,按员工ID汇总
foreach (var item in consumeResult ?? Enumerable.Empty())
{
- var key = $"{item.TechDepartmentId}_{item.TeacherId}";
- teacherDict[key] = new TechTeacherDailyStatisticsOutput
+ var teacherId = item.TeacherId?.ToString();
+ var techDeptId = item.TechDepartmentId?.ToString();
+ var techDeptName = item.TechDepartmentName?.ToString();
+ var consumeAchievement = Convert.ToDecimal(item.ConsumeAchievement);
+
+ if (string.IsNullOrEmpty(teacherId))
+ continue;
+
+ // 记录员工在各科技部的消耗业绩(累加,因为同一员工在同一科技部可能有多个门店的数据)
+ if (!teacherDeptConsumePerformance.ContainsKey(teacherId))
{
- TechDepartmentId = item.TechDepartmentId?.ToString(),
- TechDepartmentName = item.TechDepartmentName?.ToString(),
- TeacherId = item.TeacherId?.ToString(),
- TeacherName = item.TeacherName?.ToString(),
- CustomerCount = Convert.ToInt32(item.CustomerCount),
- ConsumeProjectCount = Convert.ToInt32(item.ConsumeProjectCount),
- ConsumeAchievement = Convert.ToDecimal(item.ConsumeAchievement),
- OrderAchievement = 0
- };
+ teacherDeptConsumePerformance[teacherId] = new Dictionary();
+ }
+ if (!string.IsNullOrEmpty(techDeptId))
+ {
+ if (!teacherDeptConsumePerformance[teacherId].ContainsKey(techDeptId))
+ {
+ teacherDeptConsumePerformance[teacherId][techDeptId] = 0;
+ }
+ teacherDeptConsumePerformance[teacherId][techDeptId] += consumeAchievement;
+ }
+
+ // 按员工ID汇总数据
+ if (!teacherDict.ContainsKey(teacherId))
+ {
+ teacherDict[teacherId] = new TechTeacherDailyStatisticsOutput
+ {
+ TechDepartmentId = techDeptId,
+ TechDepartmentName = techDeptName,
+ TeacherId = teacherId,
+ TeacherName = item.TeacherName?.ToString(),
+ CustomerCount = 0,
+ ConsumeProjectCount = 0,
+ ConsumeAchievement = 0,
+ OrderAchievement = 0
+ };
+ }
+
+ // 汇总数据(累加项目数和业绩,见客数后面统一重新计算去重)
+ var teacher = teacherDict[teacherId];
+ teacher.ConsumeProjectCount += Convert.ToInt32(item.ConsumeProjectCount);
+ teacher.ConsumeAchievement += consumeAchievement;
}
- // 合并开单业绩
+ // 第二步:处理开单业绩,按员工ID汇总
foreach (var item in orderResult ?? Enumerable.Empty())
{
- var key = $"{item.TechDepartmentId}_{item.TeacherId}";
- if (teacherDict.ContainsKey(key))
+ var teacherId = item.TeacherId?.ToString();
+ var techDeptId = item.TechDepartmentId?.ToString();
+ var techDeptName = item.TechDepartmentName?.ToString();
+ var orderAchievement = Convert.ToDecimal(item.OrderAchievement);
+
+ if (string.IsNullOrEmpty(teacherId))
+ continue;
+
+ // 记录员工在各科技部的开单业绩(累加,因为同一员工在同一科技部可能有多个门店的数据)
+ if (!teacherDeptOrderPerformance.ContainsKey(teacherId))
+ {
+ teacherDeptOrderPerformance[teacherId] = new Dictionary();
+ }
+ if (!string.IsNullOrEmpty(techDeptId))
{
- teacherDict[key].OrderAchievement = Convert.ToDecimal(item.OrderAchievement);
+ if (!teacherDeptOrderPerformance[teacherId].ContainsKey(techDeptId))
+ {
+ teacherDeptOrderPerformance[teacherId][techDeptId] = 0;
+ }
+ teacherDeptOrderPerformance[teacherId][techDeptId] += orderAchievement;
+ }
+
+ if (!teacherDict.ContainsKey(teacherId))
+ {
+ // 如果消耗数据中没有,但开单数据中有,需要创建记录
+ teacherDict[teacherId] = new TechTeacherDailyStatisticsOutput
+ {
+ TechDepartmentId = techDeptId,
+ TechDepartmentName = techDeptName,
+ TeacherId = teacherId,
+ TeacherName = null,
+ CustomerCount = 0,
+ ConsumeProjectCount = 0,
+ ConsumeAchievement = 0,
+ OrderAchievement = 0
+ };
+ }
+
+ teacherDict[teacherId].OrderAchievement += orderAchievement;
+ }
+
+ // 第三步:确定每个员工的主要科技部
+ // 优先按消耗业绩最多的科技部,如果消耗业绩为0,则按开单业绩最多的科技部
+ foreach (var teacherId in teacherDict.Keys.ToList())
+ {
+ var teacher = teacherDict[teacherId];
+ string mainDeptId = null;
+ string mainDeptName = null;
+
+ // 优先按消耗业绩确定主要科技部
+ if (teacherDeptConsumePerformance.ContainsKey(teacherId) && teacherDeptConsumePerformance[teacherId].Any())
+ {
+ var mainDept = teacherDeptConsumePerformance[teacherId]
+ .OrderByDescending(x => x.Value)
+ .FirstOrDefault();
+
+ if (!string.IsNullOrEmpty(mainDept.Key) && mainDept.Value > 0)
+ {
+ mainDeptId = mainDept.Key;
+ // 从消耗数据中获取该科技部的名称
+ var mainDeptData = consumeResult?.FirstOrDefault(x =>
+ x.TeacherId?.ToString() == teacherId &&
+ x.TechDepartmentId?.ToString() == mainDeptId);
+
+ if (mainDeptData != null)
+ {
+ mainDeptName = mainDeptData.TechDepartmentName?.ToString();
+ }
+ }
+ }
+
+ // 如果消耗业绩为0或没有,则按开单业绩确定主要科技部
+ if (string.IsNullOrEmpty(mainDeptId) && teacherDeptOrderPerformance.ContainsKey(teacherId) && teacherDeptOrderPerformance[teacherId].Any())
+ {
+ var mainDept = teacherDeptOrderPerformance[teacherId]
+ .OrderByDescending(x => x.Value)
+ .FirstOrDefault();
+
+ if (!string.IsNullOrEmpty(mainDept.Key) && mainDept.Value > 0)
+ {
+ mainDeptId = mainDept.Key;
+ // 从开单数据中获取该科技部的名称
+ var mainDeptData = orderResult?.FirstOrDefault(x =>
+ x.TeacherId?.ToString() == teacherId &&
+ x.TechDepartmentId?.ToString() == mainDeptId);
+
+ if (mainDeptData != null)
+ {
+ mainDeptName = mainDeptData.TechDepartmentName?.ToString();
+ }
+ }
+ }
+
+ // 如果仍然没有确定主要科技部,但已有科技部ID和名称,保持原样
+ if (!string.IsNullOrEmpty(mainDeptId))
+ {
+ teacher.TechDepartmentId = mainDeptId;
+ if (!string.IsNullOrEmpty(mainDeptName))
+ {
+ teacher.TechDepartmentName = mainDeptName;
+ }
+ }
+ else if (string.IsNullOrEmpty(teacher.TechDepartmentName) && !string.IsNullOrEmpty(teacher.TechDepartmentId))
+ {
+ // 如果仍然没有科技部名称,尝试从开单数据中获取
+ var orderDeptData = orderResult?.FirstOrDefault(x =>
+ x.TeacherId?.ToString() == teacherId &&
+ x.TechDepartmentId?.ToString() == teacher.TechDepartmentId);
+
+ if (orderDeptData != null && !string.IsNullOrEmpty(orderDeptData.TechDepartmentName?.ToString()))
+ {
+ teacher.TechDepartmentName = orderDeptData.TechDepartmentName?.ToString();
+ }
+ }
+ }
+
+ // 第四步:重新计算见客数(去重,因为同一个客户可能在多个科技部被统计)
+ // 由于已经汇总了数据,这里需要重新查询去重后的见客数
+ var teacherIds = teacherDict.Keys.ToList();
+ if (teacherIds.Any())
+ {
+ var teacherIdsStr = string.Join("','", teacherIds);
+ var customerCountSql = $@"
+ SELECT
+ consume.kjbls as TeacherId,
+ COUNT(DISTINCT hyhk.hy) as CustomerCount
+ FROM lq_xh_kjbsyj consume
+ INNER JOIN lq_xh_hyhk hyhk ON consume.glkdbh = hyhk.F_Id
+ WHERE consume.F_IsEffective = 1
+ AND hyhk.F_IsEffective = 1
+ AND DATE(hyhk.hksj) >= '{startDate:yyyy-MM-dd}'
+ AND DATE(hyhk.hksj) <= '{endDate:yyyy-MM-dd}'
+ AND consume.kjbls IN ('{teacherIdsStr}')
+ GROUP BY consume.kjbls";
+
+ var customerCountResult = await _db.Ado.SqlQueryAsync(customerCountSql);
+ foreach (var item in customerCountResult ?? Enumerable.Empty())
+ {
+ var teacherId = item.TeacherId?.ToString();
+ if (!string.IsNullOrEmpty(teacherId) && teacherDict.ContainsKey(teacherId))
+ {
+ teacherDict[teacherId].CustomerCount = Convert.ToInt32(item.CustomerCount);
+ }
}
}