diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqDailyReport/DeductAmountStatisticsInput.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqDailyReport/DeductAmountStatisticsInput.cs
new file mode 100644
index 0000000..054049b
--- /dev/null
+++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqDailyReport/DeductAmountStatisticsInput.cs
@@ -0,0 +1,21 @@
+using System;
+
+namespace NCC.Extend.Entitys.Dto.LqDailyReport
+{
+ ///
+ /// 储值扣减金额统计输入参数
+ ///
+ public class DeductAmountStatisticsInput
+ {
+ ///
+ /// 开始时间(可选,不传则默认为当月1号)
+ ///
+ public DateTime? StartTime { get; set; }
+
+ ///
+ /// 结束时间(可选,不传则默认为今天)
+ ///
+ public DateTime? EndTime { get; set; }
+ }
+}
+
diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqDailyReport/DeductAmountStatisticsOutput.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqDailyReport/DeductAmountStatisticsOutput.cs
new file mode 100644
index 0000000..25263e9
--- /dev/null
+++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqDailyReport/DeductAmountStatisticsOutput.cs
@@ -0,0 +1,36 @@
+using System;
+
+namespace NCC.Extend.Entitys.Dto.LqDailyReport
+{
+ ///
+ /// 储值扣减金额统计输出结果
+ ///
+ public class DeductAmountStatisticsOutput
+ {
+ ///
+ /// 医美储扣金额
+ ///
+ public decimal YiMeiAmount { get; set; }
+
+ ///
+ /// 生美储扣金额
+ ///
+ public decimal ShengMeiAmount { get; set; }
+
+ ///
+ /// 科美储扣金额
+ ///
+ public decimal KeMeiAmount { get; set; }
+
+ ///
+ /// 其他品项储扣金额
+ ///
+ public decimal OtherAmount { get; set; }
+
+ ///
+ /// 总储扣金额
+ ///
+ public decimal TotalAmount { get; set; }
+ }
+}
+
diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Interfaces/LqDailyReport/ILqDailyReportService.cs b/netcore/src/Modularity/Extend/NCC.Extend.Interfaces/LqDailyReport/ILqDailyReportService.cs
index 2ef49a6..dac50e3 100644
--- a/netcore/src/Modularity/Extend/NCC.Extend.Interfaces/LqDailyReport/ILqDailyReportService.cs
+++ b/netcore/src/Modularity/Extend/NCC.Extend.Interfaces/LqDailyReport/ILqDailyReportService.cs
@@ -57,6 +57,13 @@ namespace NCC.Extend.Interfaces.LqDailyReport
/// 查询参数
/// 科技部老师统计列表
Task> GetTechTeacherDailyStatistics(TechTeacherDailyStatisticsInput input);
+
+ ///
+ /// 获取储值扣减金额统计
+ ///
+ /// 查询参数
+ /// 储值扣减金额统计结果
+ Task GetDeductAmountStatistics(DeductAmountStatisticsInput input);
}
}
diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqDailyReportService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqDailyReportService.cs
index abac9e4..2e086ef 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();
@@ -472,6 +472,16 @@ namespace NCC.Extend
///
/// 根据指定日期统计各天王团的业绩完成情况,包括目标业绩、开单业绩、退卡金额、实际完成业绩、完成率
///
+ /// 业绩统计规则:
+ /// - 教育部(教育一部、教育二部):统计生美品项(lq_xmzl.qt2='生美')相关的开单和退卡业绩
+ /// - 科技部(科技一部、科技二部):统计科美品项(lq_xmzl.qt2='科美')相关的开单和退卡业绩
+ /// - 大项目部(大项目一部、大项目二部):统计医美品项(lq_xmzl.qt2='医美')相关的开单和退卡业绩
+ ///
+ /// 业绩分配规则:
+ /// - 生美品项的开单和退卡业绩全部归教育部(不按目标比例分配)
+ /// - 科美品项的开单和退卡业绩全部归科技部(不按目标比例分配)
+ /// - 医美品项的开单和退卡业绩全部归大项目部(不按目标比例分配)
+ ///
/// 示例请求:
/// ```json
/// {
@@ -490,8 +500,8 @@ namespace NCC.Extend
/// - DepartmentId: 部门ID
/// - DepartmentName: 部门名称(教育一部、教育二部、科技一部、科技二部、大项目一部、大项目二部)
/// - TargetPerformance: 目标业绩(来自门店目标表lq_md_target,根据部门类型使用对应字段:教育部使用F_EducationDepartmentTarget,科技部使用F_TechDepartmentTarget,大项目部使用F_MajorProjectDepartmentTarget,根据开始时间所在月份获取,通过对应归属字段关联,如果未查询到则为0)
- /// - BillingPerformance: 开单业绩(指定时间范围内的开单业绩总和)
- /// - RefundPerformance: 退款业绩(指定时间范围内的退卡业绩总和)
+ /// - BillingPerformance: 开单业绩(指定时间范围内,按品项分类过滤后的开单业绩总和,按门店目标比例分配)
+ /// - RefundPerformance: 退款业绩(指定时间范围内,按品项分类过滤后的退卡业绩总和,按门店目标比例分配)
/// - ActualPerformance: 开单业绩(与BillingPerformance相同,用于兼容)
/// - CompletedPerformance: 实际完成业绩(开单业绩 - 退款业绩)
/// - CompletionRate: 完成率(百分比,CompletedPerformance / TargetPerformance * 100)
@@ -520,12 +530,12 @@ namespace NCC.Extend
departmentFilter = $"AND o.F_Id IN ('{filterDeptIdsStr}')";
}
- // 天王团部门信息(字段名、目标字段名和部门名称的映射)
- var tianwangDepartments = new Dictionary
+ // 天王团部门信息(字段名、目标字段名、部门名称和品项分类的映射)
+ var tianwangDepartments = new Dictionary
{
- { "jyb", ("F_EducationDepartment", "F_EducationDepartmentTarget", new[] { "教育一部", "教育二部" }) },
- { "kjb", ("F_TechDepartment", "F_TechDepartmentTarget", new[] { "科技一部", "科技二部" }) },
- { "dxmb", ("F_MajorProjectDepartment", "F_MajorProjectDepartmentTarget", new[] { "大项目一部", "大项目二部" }) }
+ { "jyb", ("F_EducationDepartment", "F_EducationDepartmentTarget", new[] { "教育一部", "教育二部" }, "生美") },
+ { "kjb", ("F_TechDepartment", "F_TechDepartmentTarget", new[] { "科技一部", "科技二部" }, "科美") },
+ { "dxmb", ("F_MajorProjectDepartment", "F_MajorProjectDepartmentTarget", new[] { "大项目一部", "大项目二部" }, "医美") }
};
// 分步查询,提高效率
@@ -585,122 +595,275 @@ namespace NCC.Extend
return new List();
}
- // 第二步:统计各天王团的开单业绩(按门店目标比例分配,避免重复计算)
- // 使用目标表中的部门ID进行关联,而不是门店表中的部门字段
- var deptIdsStrForQuery = string.Join("','", departmentDict.Keys);
- var billingPerformanceSql = $@"
+ // 第二步:优化策略 - 先获取门店列表,只查询这些门店的数据,大幅提升性能
+ var deptIdsStr = string.Join("','", departmentDict.Keys);
+
+ // 2.1 先快速获取有目标记录的门店ID列表(使用索引,非常快,通常<100ms)
+ var storeIdsSql = $@"
+ SELECT DISTINCT F_StoreId
+ FROM lq_md_target
+ WHERE F_Month = '{month}'
+ AND (
+ F_EducationDepartment IN ('{deptIdsStr}')
+ OR F_TechDepartment IN ('{deptIdsStr}')
+ OR F_MajorProjectDepartment IN ('{deptIdsStr}')
+ )";
+
+ var storeIdsData = await _db.Ado.SqlQueryAsync(storeIdsSql);
+ var storeIds = new List();
+ foreach (var row in storeIdsData ?? Enumerable.Empty())
+ {
+ var storeId = row?.F_StoreId?.ToString();
+ if (!string.IsNullOrEmpty(storeId))
+ {
+ storeIds.Add(storeId);
+ }
+ }
+
+ if (!storeIds.Any())
+ {
+ return departmentDict.Values.OrderByDescending(x => x.CompletedPerformance).ToList();
+ }
+
+ var storeIdsStr = string.Join("','", storeIds);
+
+ // 2.2 查询目标表数据(一次性查询,数据量小)
+ // 直接查询,不使用 MAX(),因为每个门店在目标表中应该只有一条记录
+ var targetSql = $@"
+ SELECT
+ F_StoreId,
+ F_EducationDepartment,
+ F_TechDepartment,
+ F_MajorProjectDepartment
+ FROM lq_md_target
+ WHERE F_Month = '{month}'
+ AND F_StoreId IN ('{storeIdsStr}')";
+
+ var targetData = await _db.Ado.SqlQueryAsync(targetSql);
+ var targetDict = new Dictionary();
+ foreach (var target in targetData ?? Enumerable.Empty())
+ {
+ var storeId = target?.F_StoreId?.ToString();
+ if (!string.IsNullOrEmpty(storeId))
+ {
+ targetDict[storeId] = target;
+ }
+ }
+
+ // 2.3 从品项明细表统计业绩,按门店+品项分类分组
+ // 重要:业绩从品项明细表(lq_kd_pxmx)的 F_ActualPrice 获取,不是从开单记录表的 sfyj 获取
+ // 因为一个开单可能包含多个品项,每个品项属于不同的分类(生美、科美、医美)
+ // 例如:开单A实付500元,包含科美100元、医美250元、生美150元
+ // 那么科技部统计100元,大项目部统计250元,教育部统计150元
+ var billingSql = $@"
SELECT
- o.F_Id as DepartmentId,
- COALESCE(SUM(
- CASE
- -- 如果总目标 > 0,按目标比例分配
- WHEN COALESCE(target.F_EducationDepartmentTarget, 0) + COALESCE(target.F_TechDepartmentTarget, 0) + COALESCE(target.F_MajorProjectDepartmentTarget, 0) > 0 THEN
- CASE
- WHEN target.F_EducationDepartment = o.F_Id THEN
- billing.sfyj * COALESCE(target.F_EducationDepartmentTarget, 0) /
- (COALESCE(target.F_EducationDepartmentTarget, 0) + COALESCE(target.F_TechDepartmentTarget, 0) + COALESCE(target.F_MajorProjectDepartmentTarget, 0))
- WHEN target.F_TechDepartment = o.F_Id THEN
- billing.sfyj * COALESCE(target.F_TechDepartmentTarget, 0) /
- (COALESCE(target.F_EducationDepartmentTarget, 0) + COALESCE(target.F_TechDepartmentTarget, 0) + COALESCE(target.F_MajorProjectDepartmentTarget, 0))
- WHEN target.F_MajorProjectDepartment = o.F_Id THEN
- billing.sfyj * COALESCE(target.F_MajorProjectDepartmentTarget, 0) /
- (COALESCE(target.F_EducationDepartmentTarget, 0) + COALESCE(target.F_TechDepartmentTarget, 0) + COALESCE(target.F_MajorProjectDepartmentTarget, 0))
- ELSE 0
- END
- ELSE 0
- END
- ), 0) as BillingPerformance
- FROM lq_kd_kdjlb billing
- INNER JOIN lq_mdxx store ON billing.djmd = store.F_Id
- INNER JOIN lq_md_target target ON target.F_StoreId = store.F_Id AND target.F_Month = '{month}'
- INNER JOIN base_organize o ON (
- (target.F_EducationDepartment = o.F_Id AND o.F_FullName IN ('教育一部', '教育二部'))
- OR (target.F_TechDepartment = o.F_Id AND o.F_FullName IN ('科技一部', '科技二部'))
- OR (target.F_MajorProjectDepartment = 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)
+ 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 o.F_Id IN ('{deptIdsStrForQuery}')
- GROUP BY o.F_Id";
+ 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;
- var billingPerformanceData = await _db.Ado.SqlQueryAsync(billingPerformanceSql);
+ if (string.IsNullOrEmpty(storeId) || string.IsNullOrEmpty(itemType) || storeAmount <= 0 || !targetDict.ContainsKey(storeId))
+ continue;
- // 第三步:统计各天王团的退卡金额(按门店目标比例分配,避免重复计算)
- // 使用目标表中的部门ID进行关联,而不是门店表中的部门字段
- var refundPerformanceSql = $@"
- SELECT
- o.F_Id as DepartmentId,
- COALESCE(SUM(
- CASE
- -- 如果总目标 > 0,按目标比例分配
- WHEN COALESCE(target.F_EducationDepartmentTarget, 0) + COALESCE(target.F_TechDepartmentTarget, 0) + COALESCE(target.F_MajorProjectDepartmentTarget, 0) > 0 THEN
- CASE
- WHEN target.F_EducationDepartment = o.F_Id THEN
- refund.F_ActualRefundAmount * COALESCE(target.F_EducationDepartmentTarget, 0) /
- (COALESCE(target.F_EducationDepartmentTarget, 0) + COALESCE(target.F_TechDepartmentTarget, 0) + COALESCE(target.F_MajorProjectDepartmentTarget, 0))
- WHEN target.F_TechDepartment = o.F_Id THEN
- refund.F_ActualRefundAmount * COALESCE(target.F_TechDepartmentTarget, 0) /
- (COALESCE(target.F_EducationDepartmentTarget, 0) + COALESCE(target.F_TechDepartmentTarget, 0) + COALESCE(target.F_MajorProjectDepartmentTarget, 0))
- WHEN target.F_MajorProjectDepartment = o.F_Id THEN
- refund.F_ActualRefundAmount * COALESCE(target.F_MajorProjectDepartmentTarget, 0) /
- (COALESCE(target.F_EducationDepartmentTarget, 0) + COALESCE(target.F_TechDepartmentTarget, 0) + COALESCE(target.F_MajorProjectDepartmentTarget, 0))
- ELSE 0
- END
- ELSE 0
- END
- ), 0) as RefundPerformance
- FROM lq_hytk_hytk refund
- INNER JOIN lq_mdxx store ON refund.md = store.F_Id
- INNER JOIN lq_md_target target ON target.F_StoreId = store.F_Id AND target.F_Month = '{month}'
- INNER JOIN base_organize o ON (
- (target.F_EducationDepartment = o.F_Id AND o.F_FullName IN ('教育一部', '教育二部'))
- OR (target.F_TechDepartment = o.F_Id AND o.F_FullName IN ('科技一部', '科技二部'))
- OR (target.F_MajorProjectDepartment = 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 refund.tksj >= '{startDate:yyyy-MM-dd} 00:00:00'
- AND refund.tksj < '{endDate.AddDays(1):yyyy-MM-dd} 00:00:00'
- AND o.F_Id IN ('{deptIdsStrForQuery}')
- GROUP BY o.F_Id";
+ var target = targetDict[storeId];
+ if (target == null)
+ continue;
- var refundPerformanceData = await _db.Ado.SqlQueryAsync(refundPerformanceSql);
+ string targetDeptId = null;
- // 第四步:合并开单业绩数据
- foreach (var item in billingPerformanceData ?? Enumerable.Empty())
+ // 根据品项分类,分配到对应的部门
+ if (itemType == "生美")
+ {
+ // 生美品项 → 教育部门
+ targetDeptId = target.F_EducationDepartment?.ToString();
+ }
+ else if (itemType == "科美")
+ {
+ // 科美品项 → 科技部门
+ targetDeptId = target.F_TechDepartment?.ToString();
+ }
+ else if (itemType == "医美")
+ {
+ // 医美品项 → 大项目部门
+ targetDeptId = target.F_MajorProjectDepartment?.ToString();
+ }
+
+ // 只分配给在查询列表中的部门,避免重复统计
+ 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;
+
+ if (string.IsNullOrEmpty(storeId) || string.IsNullOrEmpty(itemType) || storeDeductAmount <= 0 || !targetDict.ContainsKey(storeId))
+ continue;
+
+ var target = targetDict[storeId];
+ if (target == null)
+ continue;
+
+ string targetDeptId = null;
+
+ // 根据品项分类,从对应的部门减去储扣金额
+ if (itemType == "生美")
+ {
+ // 生美品项储扣 → 从教育部门减去
+ targetDeptId = target.F_EducationDepartment?.ToString();
+ }
+ else if (itemType == "科美")
{
- var departmentId = item.DepartmentId.ToString();
- if (departmentDict.ContainsKey(departmentId))
+ // 科美品项储扣 → 从科技部门减去
+ targetDeptId = target.F_TechDepartment?.ToString();
+ }
+ else if (itemType == "医美")
+ {
+ // 医美品项储扣 → 从大项目部门减去
+ targetDeptId = target.F_MajorProjectDepartment?.ToString();
+ }
+
+ // 只从在查询列表中的部门减去,避免重复统计
+ if (!string.IsNullOrEmpty(targetDeptId) && departmentDict.ContainsKey(targetDeptId))
{
- departmentDict[departmentId].BillingPerformance = decimal.Round(Convert.ToDecimal(item.BillingPerformance), 2);
+ departmentDict[targetDeptId].BillingPerformance -= storeDeductAmount;
}
}
- // 第五步:合并退卡金额数据
- foreach (var item in refundPerformanceData ?? Enumerable.Empty())
+ // 2.7 根据品项分类和目标表分配退卡业绩到对应部门
+ // 分配规则与开单业绩相同:
+ // - 生美品项退卡 → 分配给教育部门
+ // - 科美品项退卡 → 分配给科技部门
+ // - 医美品项退卡 → 分配给大项目部门
+ foreach (var refund in refundData ?? Enumerable.Empty())
{
- var departmentId = item.DepartmentId.ToString();
- if (departmentDict.ContainsKey(departmentId))
+ var storeId = refund?.StoreId?.ToString();
+ var itemType = refund?.ItemType?.ToString();
+ var storeRefundAmount = refund?.StoreRefundAmount != null ? Convert.ToDecimal(refund.StoreRefundAmount) : 0m;
+
+ if (string.IsNullOrEmpty(storeId) || string.IsNullOrEmpty(itemType) || storeRefundAmount <= 0 || !targetDict.ContainsKey(storeId))
+ continue;
+
+ var target = targetDict[storeId];
+ if (target == null)
+ continue;
+
+ string targetDeptId = null;
+
+ // 根据品项分类,分配到对应的部门
+ if (itemType == "生美")
+ {
+ // 生美品项退卡 → 教育部门
+ targetDeptId = target.F_EducationDepartment?.ToString();
+ }
+ else if (itemType == "科美")
{
- departmentDict[departmentId].RefundPerformance = decimal.Round(Convert.ToDecimal(item.RefundPerformance), 2);
+ // 科美品项退卡 → 科技部门
+ targetDeptId = target.F_TechDepartment?.ToString();
+ }
+ else if (itemType == "医美")
+ {
+ // 医美品项退卡 → 大项目部门
+ targetDeptId = target.F_MajorProjectDepartment?.ToString();
+ }
+
+ // 只分配给在查询列表中的部门,避免重复统计
+ if (!string.IsNullOrEmpty(targetDeptId) && departmentDict.ContainsKey(targetDeptId))
+ {
+ departmentDict[targetDeptId].RefundPerformance += storeRefundAmount;
}
}
- // 第六步:计算实际完成业绩和完成率
+ // 第三步:计算实际完成业绩和完成率,并保留两位小数
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.CompletedPerformance = decimal.Round(actualCompletedPerformance, 2); // 实际完成业绩,保留两位小数
- var completionRate = dept.TargetPerformance > 0
+ var completionRate = dept.TargetPerformance > 0
? (actualCompletedPerformance / dept.TargetPerformance * 100m)
- : 0m;
- dept.CompletionRate = decimal.Round(completionRate, 2);
+ : 0m;
+ dept.CompletionRate = decimal.Round(completionRate, 2);
}
var outputList = departmentDict.Values
@@ -1420,6 +1583,86 @@ namespace NCC.Extend
}
#endregion
+ #region 获取储值扣减金额统计
+ ///
+ /// 获取储值扣减金额统计
+ ///
+ ///
+ /// 根据指定时间范围统计储值扣减金额,按品项分类(医美、生美、科美、其他)分组统计
+ ///
+ /// 示例请求:
+ /// ```json
+ /// {
+ /// "startTime": "2025-11-01",
+ /// "endTime": "2025-11-30"
+ /// }
+ /// ```
+ ///
+ /// 参数说明:
+ /// - startTime: 开始时间,可选,不传则默认为当月1号
+ /// - endTime: 结束时间,可选,不传则默认为今天
+ ///
+ /// 返回说明:
+ /// - YiMeiAmount: 医美储扣金额
+ /// - ShengMeiAmount: 生美储扣金额
+ /// - KeMeiAmount: 科美储扣金额
+ /// - OtherAmount: 其他品项储扣金额
+ /// - TotalAmount: 总储扣金额
+ ///
+ /// 查询参数
+ /// 储值扣减金额统计结果
+ /// 成功返回储值扣减金额统计结果
+ /// 日期格式错误或参数无效
+ /// 服务器内部错误
+ [AllowAnonymous]
+ [HttpPost("get-deduct-amount-statistics")]
+ public async Task GetDeductAmountStatistics(DeductAmountStatisticsInput input)
+ {
+ // 获取时间范围
+ var (startDate, endDate) = GetTimeRange(input.StartTime, input.EndTime);
+
+ // 统计储扣金额,按品项分类分组
+ var sql = $@"
+ SELECT
+ COALESCE(SUM(CASE WHEN item.qt2 = '医美' THEN deduct.F_Amount ELSE 0 END), 0) as YiMeiAmount,
+ COALESCE(SUM(CASE WHEN item.qt2 = '生美' THEN deduct.F_Amount ELSE 0 END), 0) as ShengMeiAmount,
+ COALESCE(SUM(CASE WHEN item.qt2 = '科美' THEN deduct.F_Amount ELSE 0 END), 0) as KeMeiAmount,
+ COALESCE(SUM(CASE WHEN item.qt2 NOT IN ('医美', '生美', '科美') OR item.qt2 IS NULL THEN deduct.F_Amount ELSE 0 END), 0) as OtherAmount,
+ COALESCE(SUM(deduct.F_Amount), 0) as TotalAmount
+ FROM lq_kd_deductinfo deduct
+ INNER JOIN lq_kd_kdjlb billing ON deduct.F_BillingId = billing.F_Id
+ LEFT JOIN lq_xmzl item ON deduct.F_ItemId = item.F_Id AND item.F_IsEffective = 1
+ WHERE deduct.F_IsEffective = 1
+ AND billing.F_IsEffective = 1
+ AND billing.kdrq >= '{startDate:yyyy-MM-dd} 00:00:00'
+ AND billing.kdrq < '{endDate.AddDays(1):yyyy-MM-dd} 00:00:00'";
+
+ var result = await _db.Ado.SqlQueryAsync(sql);
+ var data = result.FirstOrDefault();
+
+ if (data == null)
+ {
+ return new DeductAmountStatisticsOutput
+ {
+ YiMeiAmount = 0,
+ ShengMeiAmount = 0,
+ KeMeiAmount = 0,
+ OtherAmount = 0,
+ TotalAmount = 0
+ };
+ }
+
+ return new DeductAmountStatisticsOutput
+ {
+ YiMeiAmount = decimal.Round(data.YiMeiAmount != null ? Convert.ToDecimal(data.YiMeiAmount) : 0m, 2),
+ ShengMeiAmount = decimal.Round(data.ShengMeiAmount != null ? Convert.ToDecimal(data.ShengMeiAmount) : 0m, 2),
+ KeMeiAmount = decimal.Round(data.KeMeiAmount != null ? Convert.ToDecimal(data.KeMeiAmount) : 0m, 2),
+ OtherAmount = decimal.Round(data.OtherAmount != null ? Convert.ToDecimal(data.OtherAmount) : 0m, 2),
+ TotalAmount = decimal.Round(data.TotalAmount != null ? Convert.ToDecimal(data.TotalAmount) : 0m, 2)
+ };
+ }
+ #endregion
+
}
}
diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqStatisticsService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqStatisticsService.cs
index 6878cb7..0e09053 100644
--- a/netcore/src/Modularity/Extend/NCC.Extend/LqStatisticsService.cs
+++ b/netcore/src/Modularity/Extend/NCC.Extend/LqStatisticsService.cs
@@ -3588,19 +3588,17 @@ namespace NCC.Extend.LqStatistics
///
private async Task<(int TotalCount, decimal OriginalCount, decimal OvertimeCount, decimal AccompaniedCount)> GetConsumeProjectCountDetails(string userId, string month)
{
+ // 直接从健康师业绩表统计项目数,使用业绩时间字段过滤,不需要JOIN其他表
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(jksyj.F_kdpxNumber), 0) as TotalCount,
+ COALESCE(SUM(COALESCE(jksyj.F_OriginalKdpxNumber, jksyj.F_kdpxNumber)), 0) as OriginalCount,
+ COALESCE(SUM(COALESCE(jksyj.F_OvertimeKdpxNumber, 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}'";
+ AND DATE_FORMAT(jksyj.yjsj, '%Y%m') = '{month}'";
var result = await _db.Ado.SqlQueryAsync(sql);
var data = result.FirstOrDefault();