using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using NCC.Dependency;
using NCC.DynamicApiController;
using NCC.FriendlyException;
using NCC.Extend.Entitys.Dto.LqBusinessUnitDashboard;
using NCC.Extend.Entitys.lq_kd_kdjlb;
using NCC.Extend.Entitys.lq_xh_hyhk;
using NCC.Extend.Entitys.lq_hytk_hytk;
using NCC.Extend.Entitys.lq_kd_pxmx;
using NCC.Extend.Entitys.lq_hytk_mx;
using NCC.Extend.Entitys.lq_md_target;
using NCC.Extend.Entitys.lq_xh_pxmx;
using NCC.Extend.Entitys.lq_mdxx;
using NCC.Extend.Entitys.lq_md_general_manager_lifeline;
using NCC.Extend.Entitys.lq_business_unit_manager_salary_statistics;
using NCC.Extend.Entitys.lq_kd_jksyj;
using NCC.Extend.Entitys.lq_xh_jksyj;
using NCC.Extend.Entitys.lq_hytk_jksyj;
using NCC.System.Entitys.Permission;
using NCC.Extend.Entitys;
using SqlSugar;
namespace NCC.Extend
{
///
/// 事业部驾驶舱服务
///
[ApiDescriptionSettings(Tag = "事业部驾驶舱服务", Name = "LqBusinessUnitDashboard", Order = 203)]
[Route("api/Extend/[controller]")]
public class LqBusinessUnitDashboardService : IDynamicApiController, ITransient
{
private readonly ISqlSugarClient _db;
private readonly ILogger _logger;
///
/// 初始化事业部驾驶舱服务
///
public LqBusinessUnitDashboardService(ISqlSugarClient db, ILogger logger)
{
_db = db;
_logger = logger;
}
// 辅助方法:获取门店列表
private async Task> GetStoreIdsAsync(string businessUnitId, List storeIds, string statisticsMonth)
{
if (!string.IsNullOrWhiteSpace(businessUnitId))
{
return await _db.Queryable()
.Where(x => x.BusinessUnit == businessUnitId && x.Month == statisticsMonth)
.Select(x => x.StoreId)
.Distinct()
.ToListAsync();
}
return storeIds ?? new List();
}
// 辅助方法:获取门店列表(重载)
private async Task> GetStoreIdsAsync(BusinessUnitDashboardStatisticsInput input)
{
return await GetStoreIdsAsync(input.BusinessUnitId, input.StoreIds, input.StatisticsMonth);
}
// 辅助方法:获取指定月份的门店列表
private async Task> GetStoreIdsForMonthAsync(string businessUnitId, List storeIds, string month)
{
if (!string.IsNullOrWhiteSpace(businessUnitId))
{
return await _db.Queryable()
.Where(x => x.BusinessUnit == businessUnitId && x.Month == month)
.Select(x => x.StoreId)
.Distinct()
.ToListAsync();
}
return storeIds ?? new List();
}
// 辅助方法:计算单月业绩指标
private async Task<(decimal billingPerformance, decimal consumePerformance, decimal refundAmount, decimal netPerformance, decimal targetPerformance, decimal completionRate,
decimal lifeBeauty, decimal techBeauty, decimal medicalBeauty, decimal product)> CalculateMonthlyPerformance(
List storeIds, string month)
{
if (!storeIds.Any()) return (0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
var year = int.Parse(month.Substring(0, 4));
var monthNum = int.Parse(month.Substring(4, 2));
var startDate = new DateTime(year, monthNum, 1);
var endDate = startDate.AddMonths(1).AddDays(-1);
var endDateTime = month == DateTime.Now.ToString("yyyyMM")
? DateTime.Now
: endDate.Date.AddHours(23).AddMinutes(59).AddSeconds(59);
// 开单业绩
var billingAmount = await _db.Queryable()
.Where(x => storeIds.Contains(x.Djmd) && x.IsEffective == 1)
.Where(x => x.Kdrq.HasValue && x.Kdrq.Value >= startDate && x.Kdrq.Value <= endDateTime)
.SumAsync(x => (decimal?)x.Sfyj) ?? 0m;
// 退卡金额
var refundAmount = await _db.Queryable()
.Where(x => storeIds.Contains(x.Md) && x.IsEffective == 1)
.Where(x => x.Tksj.HasValue && x.Tksj.Value.Date >= startDate.Date && x.Tksj.Value.Date <= endDate.Date)
.SumAsync(x => (decimal?)(x.ActualRefundAmount ?? x.Tkje ?? 0)) ?? 0m;
// 净业绩
var netPerformance = billingAmount - refundAmount;
// 消耗业绩
var consumeAmount = await _db.Queryable()
.Where(x => storeIds.Contains(x.Md) && x.IsEffective == 1)
.Where(x => x.Hksj.HasValue && x.Hksj.Value >= startDate && x.Hksj.Value <= endDateTime)
.SumAsync(x => (decimal?)x.Xfje) ?? 0m;
// 目标业绩
var targetPerformance = await _db.Queryable()
.Where(x => storeIds.Contains(x.StoreId) && x.Month == month)
.SumAsync(x => (decimal?)x.BusinessUnitTarget) ?? 0m;
// 完成率
var completionRate = targetPerformance > 0 ? (netPerformance / targetPerformance * 100m) : 0m;
// 品项分类业绩
var (billingLifeBeauty, billingTechBeauty, billingMedicalBeauty, billingProduct) =
await CalculatePerformanceByCategory(storeIds, month, false);
var (refundLifeBeauty, refundTechBeauty, refundMedicalBeauty, refundProduct) =
await CalculatePerformanceByCategory(storeIds, month, true);
var lifeBeauty = billingLifeBeauty - refundLifeBeauty;
var techBeauty = billingTechBeauty - refundTechBeauty;
var medicalBeauty = billingMedicalBeauty - refundMedicalBeauty;
var product = billingProduct - refundProduct;
return (billingAmount, consumeAmount, refundAmount, netPerformance, targetPerformance, completionRate,
lifeBeauty, techBeauty, medicalBeauty, product);
}
// 辅助方法:计算品项分类业绩
private async Task<(decimal lifeBeauty, decimal techBeauty, decimal medicalBeauty, decimal product)> CalculatePerformanceByCategory(
List storeIds, string month, bool isRefund = false)
{
if (!storeIds.Any()) return (0, 0, 0, 0);
var year = int.Parse(month.Substring(0, 4));
var monthNum = int.Parse(month.Substring(4, 2));
var startDate = new DateTime(year, monthNum, 1);
var endDate = startDate.AddMonths(1).AddDays(-1);
var endDateTime = month == DateTime.Now.ToString("yyyyMM")
? DateTime.Now
: endDate.Date.AddHours(23).AddMinutes(59).AddSeconds(59);
var storeIdsStr = string.Join("','", storeIds);
if (isRefund)
{
// 退卡品项分类业绩
var refundSql = $@"
SELECT
COALESCE(refund_mx.F_ItemCategory, '') as ItemCategory,
COALESCE(SUM(COALESCE(refund_mx.tkje, 0)), 0) as Amount
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'
GROUP BY refund_mx.F_ItemCategory";
var refundData = await _db.Ado.SqlQueryAsync(refundSql);
decimal lifeBeauty = 0, techBeauty = 0, medicalBeauty = 0, product = 0;
foreach (var row in refundData ?? Enumerable.Empty())
{
var category = row?.ItemCategory?.ToString() ?? "";
var amount = row?.Amount != null ? Convert.ToDecimal(row.Amount) : 0m;
if (category == "生美") lifeBeauty = amount;
else if (category == "科美") techBeauty = amount;
else if (category == "医美") medicalBeauty = amount;
else if (category == "产品") product = amount;
}
return (lifeBeauty, techBeauty, medicalBeauty, product);
}
else
{
// 开单品项分类业绩(从lq_kd_pxmx表的F_ActualPrice字段统计)
var billingSql = $@"
SELECT
COALESCE(pxmx.F_ItemCategory, '') as ItemCategory,
COALESCE(SUM(COALESCE(pxmx.F_ActualPrice, 0)), 0) as Amount
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'
GROUP BY pxmx.F_ItemCategory";
var billingData = await _db.Ado.SqlQueryAsync(billingSql);
decimal lifeBeauty = 0, techBeauty = 0, medicalBeauty = 0, product = 0;
foreach (var row in billingData ?? Enumerable.Empty())
{
var category = row?.ItemCategory?.ToString() ?? "";
var amount = row?.Amount != null ? Convert.ToDecimal(row.Amount) : 0m;
if (category == "生美") lifeBeauty = amount;
else if (category == "科美") techBeauty = amount;
else if (category == "医美") medicalBeauty = amount;
else if (category == "产品") product = amount;
}
return (lifeBeauty, techBeauty, medicalBeauty, product);
}
}
///
/// 获取事业部驾驶舱统计数据
///
///
/// 获取指定事业部在指定月份的核心指标:开单业绩、消耗业绩、净业绩、目标业绩、完成率、管理的门店数、活跃门店数、品项分类业绩等
///
/// 示例请求:
/// ```json
/// {
/// "businessUnitId": "1649328471923847169",
/// "statisticsMonth": "202512"
/// }
/// ```
///
/// 或者:
/// ```json
/// {
/// "storeIds": ["1649328471923847169", "1649328471923847170"],
/// "statisticsMonth": "202512"
/// }
/// ```
///
/// 参数说明:
/// - businessUnitId: 事业部ID(BASE_ORGANIZE表的组织ID),与storeIds两者必填其一
/// - storeIds: 门店ID列表,与businessUnitId两者必填其一
/// - statisticsMonth: 统计月份,格式为YYYYMM(必填)
///
/// 返回数据说明:
/// - BillingPerformance: 开单业绩(开单实付业绩总和)
/// - ConsumePerformance: 消耗业绩(消耗金额总和)
/// - RefundAmount: 退卡金额(退卡实退金额总和)
/// - NetPerformance: 净业绩(开单业绩 - 退卡金额)
/// - TargetPerformance: 目标业绩(管理的所有门店的事业部业绩目标总和)
/// - CompletionRate: 完成率(净业绩 / 目标业绩 × 100%)
/// - ManagedStoreCount: 管理的门店数
/// - ActiveStoreCount: 活跃门店数(有开单或消耗的门店数量)
/// - 其他运营指标:开单次数、消耗次数、退卡次数、平均金额、人头数、人次、项目数、消耗率、退卡率
/// - 品项分类业绩:生美业绩、科美业绩、医美业绩、产品业绩
///
/// 查询参数
/// 事业部驾驶舱统计数据
/// 成功返回统计数据
/// 参数错误
/// 服务器错误
[HttpPost("GetStatistics")]
public async Task GetStatistics([FromBody] BusinessUnitDashboardStatisticsInput input)
{
try
{
if (input == null)
{
throw NCCException.Oh("请求参数不能为空");
}
if (string.IsNullOrWhiteSpace(input.StatisticsMonth) || input.StatisticsMonth.Length != 6)
{
throw NCCException.Oh("统计月份格式错误,必须为YYYYMM格式");
}
// 参数验证:事业部ID和门店ID列表两者必填其一
if (string.IsNullOrWhiteSpace(input.BusinessUnitId) &&
(input.StoreIds == null || !input.StoreIds.Any()))
{
throw NCCException.Oh("事业部ID和门店ID列表不能同时为空,必须传入其中一个");
}
if (!string.IsNullOrWhiteSpace(input.BusinessUnitId) &&
input.StoreIds != null && input.StoreIds.Any())
{
throw NCCException.Oh("事业部ID和门店ID列表不能同时传入,请只传入其中一个");
}
_logger.LogInformation("开始查询事业部驾驶舱统计数据,事业部ID:{BusinessUnitId},门店ID列表:{StoreIds},统计月份:{StatisticsMonth}",
input.BusinessUnitId, input.StoreIds != null ? string.Join(",", input.StoreIds) : "", input.StatisticsMonth);
// 解析月份获取时间范围
var year = int.Parse(input.StatisticsMonth.Substring(0, 4));
var month = int.Parse(input.StatisticsMonth.Substring(4, 2));
var startDate = new DateTime(year, month, 1);
var endDate = startDate.AddMonths(1).AddDays(-1);
var endDateTime = input.StatisticsMonth == DateTime.Now.ToString("yyyyMM")
? DateTime.Now
: endDate.Date.AddHours(23).AddMinutes(59).AddSeconds(59);
// 获取门店列表
List storeIds;
if (!string.IsNullOrWhiteSpace(input.BusinessUnitId))
{
// 方式1:从lq_md_target表查询该月份、该事业部归属的门店列表
storeIds = await _db.Queryable()
.Where(x => x.BusinessUnit == input.BusinessUnitId && x.Month == input.StatisticsMonth)
.Select(x => x.StoreId)
.Distinct()
.ToListAsync();
if (!storeIds.Any())
{
_logger.LogWarning("事业部ID:{BusinessUnitId} 在 {StatisticsMonth} 月份没有管理的门店", input.BusinessUnitId, input.StatisticsMonth);
// 返回空数据
return new BusinessUnitDashboardStatisticsOutput();
}
}
else
{
// 方式2:直接使用传入的门店ID列表
storeIds = input.StoreIds;
}
// 1. 统计开单业绩(从lq_kd_kdjlb表的sfyj字段)
var billingAmount = await _db.Queryable()
.Where(x => storeIds.Contains(x.Djmd) && x.IsEffective == 1)
.Where(x => x.Kdrq.HasValue && x.Kdrq.Value >= startDate && x.Kdrq.Value <= endDateTime)
.SumAsync(x => (decimal?)x.Sfyj) ?? 0m;
// 2. 统计退卡金额(从lq_hytk_hytk表的F_ActualRefundAmount或tkje字段)
var refundAmount = await _db.Queryable()
.Where(x => storeIds.Contains(x.Md) && x.IsEffective == 1)
.Where(x => x.Tksj.HasValue && x.Tksj.Value.Date >= startDate.Date && x.Tksj.Value.Date <= endDate.Date)
.SumAsync(x => (decimal?)(x.ActualRefundAmount ?? x.Tkje ?? 0)) ?? 0m;
// 3. 计算净业绩
var netPerformance = billingAmount - refundAmount;
// 4. 统计消耗业绩(从lq_xh_hyhk表的xfje字段)
var consumeAmount = await _db.Queryable()
.Where(x => storeIds.Contains(x.Md) && x.IsEffective == 1)
.Where(x => x.Hksj.HasValue && x.Hksj.Value >= startDate && x.Hksj.Value <= endDateTime)
.SumAsync(x => (decimal?)x.Xfje) ?? 0m;
// 5. 统计目标业绩(从lq_md_target表的F_BusinessUnitTarget字段)
var targetPerformance = await _db.Queryable()
.Where(x => storeIds.Contains(x.StoreId) && x.Month == input.StatisticsMonth)
.SumAsync(x => (decimal?)x.BusinessUnitTarget) ?? 0m;
// 6. 计算完成率
var completionRate = targetPerformance > 0 ? (netPerformance / targetPerformance * 100m) : 0m;
// 7. 管理的门店数
var managedStoreCount = storeIds.Count;
// 8. 统计活跃门店数(有开单或消耗的门店数量)
var storesWithBilling = await _db.Queryable()
.Where(x => storeIds.Contains(x.Djmd) && x.IsEffective == 1)
.Where(x => x.Kdrq.HasValue && x.Kdrq.Value >= startDate && x.Kdrq.Value <= endDateTime)
.Select(x => x.Djmd)
.Distinct()
.ToListAsync();
var storesWithConsume = await _db.Queryable()
.Where(x => storeIds.Contains(x.Md) && x.IsEffective == 1)
.Where(x => x.Hksj.HasValue && x.Hksj.Value >= startDate && x.Hksj.Value <= endDateTime)
.Select(x => x.Md)
.Distinct()
.ToListAsync();
var allActiveStoreIds = storesWithBilling.Union(storesWithConsume).Distinct().ToList();
var activeStoreCount = allActiveStoreIds.Count;
// 9. 统计开单次数
var billingCount = await _db.Queryable()
.Where(x => storeIds.Contains(x.Djmd) && x.IsEffective == 1)
.Where(x => x.Kdrq.HasValue && x.Kdrq.Value >= startDate && x.Kdrq.Value <= endDateTime)
.CountAsync();
// 10. 统计消耗次数
var consumeCount = await _db.Queryable()
.Where(x => storeIds.Contains(x.Md) && x.IsEffective == 1)
.Where(x => x.Hksj.HasValue && x.Hksj.Value >= startDate && x.Hksj.Value <= endDateTime)
.CountAsync();
// 11. 统计退卡次数
var refundCount = await _db.Queryable()
.Where(x => storeIds.Contains(x.Md) && x.IsEffective == 1)
.Where(x => x.Tksj.HasValue && x.Tksj.Value.Date >= startDate.Date && x.Tksj.Value.Date <= endDate.Date)
.CountAsync();
// 12. 计算平均开单金额
var avgBillingAmount = billingCount > 0 ? billingAmount / (decimal)billingCount : 0m;
// 13. 计算平均消耗金额
var avgConsumeAmount = consumeCount > 0 ? consumeAmount / (decimal)consumeCount : 0m;
// 14. 统计人头数(去重后的消费会员数)
var headCount = await _db.Queryable()
.Where(x => storeIds.Contains(x.Md) && x.IsEffective == 1)
.Where(x => x.Hksj.HasValue && x.Hksj.Value >= startDate && x.Hksj.Value <= endDateTime)
.Select(x => x.Hy)
.Distinct()
.CountAsync();
// 15. 统计人次(日度去重客户数)- 使用SQL查询
var storeIdsStr = string.Join("','", storeIds);
var personCountSql = $@"
SELECT COUNT(DISTINCT CONCAT(xh.Hy, '-', DATE_FORMAT(xh.Hksj, '%Y-%m-%d'))) as PersonCount
FROM lq_xh_hyhk xh
WHERE xh.Md IN ('{storeIdsStr}')
AND xh.F_IsEffective = 1
AND xh.Hksj >= '{startDate:yyyy-MM-dd HH:mm:ss}'
AND xh.Hksj <= '{endDateTime:yyyy-MM-dd HH:mm:ss}'";
var personCountResult = await _db.Ado.SqlQueryAsync(personCountSql);
var personCount = personCountResult?.FirstOrDefault() != null
? Convert.ToInt32(personCountResult.FirstOrDefault().PersonCount ?? 0)
: 0;
// 16. 统计项目数(消耗的项目总数,从品项明细表统计原始项目数)
var projectCountSql = $@"
SELECT COALESCE(SUM(COALESCE(px.F_OriginalProjectNumber, px.F_ProjectNumber, 0)), 0) as ProjectCount
FROM lq_xh_pxmx px
INNER JOIN lq_xh_hyhk xh ON px.F_ConsumeInfoId = xh.F_Id
WHERE xh.Md IN ('{storeIdsStr}')
AND xh.F_IsEffective = 1
AND px.F_IsEffective = 1
AND xh.Hksj >= '{startDate:yyyy-MM-dd HH:mm:ss}'
AND xh.Hksj <= '{endDateTime:yyyy-MM-dd HH:mm:ss}'";
var projectCountResult = await _db.Ado.SqlQueryAsync(projectCountSql);
var projectCount = projectCountResult?.FirstOrDefault() != null
? Convert.ToDecimal(projectCountResult.FirstOrDefault().ProjectCount ?? 0)
: 0m;
// 17. 计算消耗率
var consumeRate = billingAmount > 0 ? (consumeAmount / billingAmount * 100m) : 0m;
// 18. 计算退卡率
var refundRate = billingAmount > 0 ? (refundAmount / billingAmount * 100m) : 0m;
// 19. 计算品项分类业绩(开单业绩 - 退卡业绩)
var (billingLifeBeauty, billingTechBeauty, billingMedicalBeauty, billingProduct) =
await CalculatePerformanceByCategory(storeIds, input.StatisticsMonth, false);
var (refundLifeBeauty, refundTechBeauty, refundMedicalBeauty, refundProduct) =
await CalculatePerformanceByCategory(storeIds, input.StatisticsMonth, true);
var lifeBeautyPerformance = billingLifeBeauty - refundLifeBeauty;
var techBeautyPerformance = billingTechBeauty - refundTechBeauty;
var medicalBeautyPerformance = billingMedicalBeauty - refundMedicalBeauty;
var productPerformance = billingProduct - refundProduct;
var result = new BusinessUnitDashboardStatisticsOutput
{
BillingPerformance = billingAmount,
ConsumePerformance = consumeAmount,
RefundAmount = refundAmount,
NetPerformance = netPerformance,
TargetPerformance = targetPerformance,
CompletionRate = completionRate,
ManagedStoreCount = managedStoreCount,
ActiveStoreCount = activeStoreCount,
BillingCount = billingCount,
ConsumeCount = consumeCount,
RefundCount = refundCount,
AvgBillingAmount = avgBillingAmount,
AvgConsumeAmount = avgConsumeAmount,
HeadCount = headCount,
PersonCount = personCount,
ProjectCount = projectCount,
ConsumeRate = consumeRate,
RefundRate = refundRate,
LifeBeautyPerformance = lifeBeautyPerformance,
TechBeautyPerformance = techBeautyPerformance,
MedicalBeautyPerformance = medicalBeautyPerformance,
ProductPerformance = productPerformance
};
_logger.LogInformation("事业部驾驶舱统计数据查询完成,开单业绩:{BillingPerformance},消耗业绩:{ConsumePerformance},净业绩:{NetPerformance}",
result.BillingPerformance, result.ConsumePerformance, result.NetPerformance);
return result;
}
catch (Exception ex)
{
_logger.LogError(ex, "查询事业部驾驶舱统计数据失败");
throw;
}
}
///
/// 获取事业部驾驶舱业绩趋势数据
///
///
/// 获取指定事业部在指定时间范围内的业绩趋势:开单业绩趋势、消耗业绩趋势、净业绩趋势、品项分类业绩趋势
///
/// 示例请求:
/// ```json
/// {
/// "businessUnitId": "734725299018663173",
/// "statisticsMonth": "202512",
/// "monthCount": 12
/// }
/// ```
///
/// 参数说明:
/// - businessUnitId: 事业部ID(BASE_ORGANIZE表的组织ID),与storeIds两者必填其一
/// - storeIds: 门店ID列表,与businessUnitId两者必填其一
/// - statisticsMonth: 统计月份,格式为YYYYMM(必填,作为结束月份)
/// - monthCount: 月份数量(可选,默认12,支持:3、6、12)
///
/// 返回数据说明:
/// - TrendData: 趋势数据列表,每个数据点包含月份、开单业绩、消耗业绩、退卡金额、净业绩、目标业绩、完成率、品项分类业绩等
///
/// 查询参数
/// 事业部驾驶舱业绩趋势数据
/// 成功返回趋势数据
/// 参数错误
/// 服务器错误
[HttpPost("GetPerformanceTrend")]
public async Task GetPerformanceTrend([FromBody] BusinessUnitDashboardPerformanceTrendInput input)
{
try
{
if (input == null) throw NCCException.Oh("请求参数不能为空");
if (string.IsNullOrWhiteSpace(input.StatisticsMonth) || input.StatisticsMonth.Length != 6)
throw NCCException.Oh("统计月份格式错误,必须为YYYYMM格式");
if (string.IsNullOrWhiteSpace(input.BusinessUnitId) && (input.StoreIds == null || !input.StoreIds.Any()))
throw NCCException.Oh("事业部ID和门店ID列表不能同时为空,必须传入其中一个");
var monthCount = input.MonthCount;
if (monthCount != 3 && monthCount != 6 && monthCount != 12)
monthCount = 12;
var baseMonth = DateTime.ParseExact(input.StatisticsMonth, "yyyyMM", null);
var result = new BusinessUnitDashboardPerformanceTrendOutput();
for (int i = monthCount - 1; i >= 0; i--)
{
var trendMonth = baseMonth.AddMonths(-i);
var trendMonthStr = trendMonth.ToString("yyyyMM");
List storeIds;
if (!string.IsNullOrWhiteSpace(input.BusinessUnitId))
{
storeIds = await GetStoreIdsForMonthAsync(input.BusinessUnitId, null, trendMonthStr);
}
else
{
storeIds = input.StoreIds;
}
if (!storeIds.Any())
{
result.TrendData.Add(new BusinessUnitPerformanceTrendPoint { Month = trendMonthStr });
continue;
}
var (billingPerformance, consumePerformance, refundAmount, netPerformance, targetPerformance, completionRate,
lifeBeauty, techBeauty, medicalBeauty, product) = await CalculateMonthlyPerformance(storeIds, trendMonthStr);
result.TrendData.Add(new BusinessUnitPerformanceTrendPoint
{
Month = trendMonthStr,
BillingPerformance = billingPerformance,
ConsumePerformance = consumePerformance,
RefundAmount = refundAmount,
NetPerformance = netPerformance,
TargetPerformance = targetPerformance,
CompletionRate = completionRate,
LifeBeautyPerformance = lifeBeauty,
TechBeautyPerformance = techBeauty,
MedicalBeautyPerformance = medicalBeauty,
ProductPerformance = product
});
}
return result;
}
catch (Exception ex)
{
_logger.LogError(ex, "查询事业部驾驶舱业绩趋势数据失败");
throw NCCException.Oh($"查询失败:{ex.Message}");
}
}
///
/// 获取事业部驾驶舱门店排行数据
///
///
/// 获取指定事业部在指定月份的门店业绩排行数据,支持按开单业绩、消耗业绩、完成率排序
///
/// 示例请求:
/// ```json
/// {
/// "businessUnitId": "734725299018663173",
/// "statisticsMonth": "202512",
/// "rankingType": "Billing",
/// "topCount": 10
/// }
/// ```
///
/// 参数说明:
/// - businessUnitId: 事业部ID(BASE_ORGANIZE表的组织ID),与storeIds两者必填其一
/// - storeIds: 门店ID列表,与businessUnitId两者必填其一
/// - statisticsMonth: 统计月份,格式为YYYYMM(必填)
/// - rankingType: 排行类型(可选,默认:Billing)
/// - Billing - 开单业绩排行
/// - Consume - 消耗业绩排行
/// - CompletionRate - 完成率排行
/// - topCount: 排行数量(可选,默认10)
///
/// 返回数据说明:
/// - RankingData: 门店排行数据列表,包含排名、门店信息、业绩数据、完成率、运营指标等
///
/// 查询参数
/// 事业部驾驶舱门店排行数据
/// 成功返回排行数据
/// 参数错误
/// 服务器错误
[HttpPost("GetStoreRanking")]
public async Task GetStoreRanking([FromBody] BusinessUnitDashboardStoreRankingInput input)
{
try
{
if (input == null) throw NCCException.Oh("请求参数不能为空");
if (string.IsNullOrWhiteSpace(input.StatisticsMonth) || input.StatisticsMonth.Length != 6)
throw NCCException.Oh("统计月份格式错误,必须为YYYYMM格式");
if (string.IsNullOrWhiteSpace(input.BusinessUnitId) && (input.StoreIds == null || !input.StoreIds.Any()))
throw NCCException.Oh("事业部ID和门店ID列表不能同时为空,必须传入其中一个");
_logger.LogInformation("开始查询事业部驾驶舱门店排行数据,事业部ID:{BusinessUnitId},统计月份:{StatisticsMonth},排行类型:{RankingType}",
input.BusinessUnitId, input.StatisticsMonth, input.RankingType);
// 解析月份获取时间范围
var year = int.Parse(input.StatisticsMonth.Substring(0, 4));
var month = int.Parse(input.StatisticsMonth.Substring(4, 2));
var startDate = new DateTime(year, month, 1);
var endDate = startDate.AddMonths(1).AddDays(-1);
var endDateTime = input.StatisticsMonth == DateTime.Now.ToString("yyyyMM")
? DateTime.Now
: endDate.Date.AddHours(23).AddMinutes(59).AddSeconds(59);
// 获取门店列表
var storeIds = await GetStoreIdsAsync(input.BusinessUnitId, input.StoreIds, input.StatisticsMonth);
if (!storeIds.Any())
{
_logger.LogWarning("事业部ID:{BusinessUnitId} 在 {StatisticsMonth} 月份没有管理的门店", input.BusinessUnitId, input.StatisticsMonth);
return new BusinessUnitDashboardStoreRankingOutput();
}
// 获取门店信息(名称、编码)
var stores = await _db.Queryable()
.Where(x => storeIds.Contains(x.Id))
.Select(x => new { x.Id, x.Dm, x.Mdbm })
.ToListAsync();
var storeDict = stores.ToDictionary(x => x.Id, x => new { Name = x.Dm ?? "", Code = x.Mdbm ?? "" });
// 计算每个门店的业绩数据
var storeIdsStr = string.Join("','", storeIds);
var rankingData = new List();
foreach (var storeId in storeIds)
{
// 开单业绩
var billingAmount = await _db.Queryable()
.Where(x => x.Djmd == storeId && x.IsEffective == 1)
.Where(x => x.Kdrq.HasValue && x.Kdrq.Value >= startDate && x.Kdrq.Value <= endDateTime)
.SumAsync(x => (decimal?)x.Sfyj) ?? 0m;
// 退卡金额
var refundAmount = await _db.Queryable()
.Where(x => x.Md == storeId && x.IsEffective == 1)
.Where(x => x.Tksj.HasValue && x.Tksj.Value.Date >= startDate.Date && x.Tksj.Value.Date <= endDate.Date)
.SumAsync(x => (decimal?)(x.ActualRefundAmount ?? x.Tkje ?? 0)) ?? 0m;
// 净业绩
var netPerformance = billingAmount - refundAmount;
// 消耗业绩
var consumeAmount = await _db.Queryable()
.Where(x => x.Md == storeId && x.IsEffective == 1)
.Where(x => x.Hksj.HasValue && x.Hksj.Value >= startDate && x.Hksj.Value <= endDateTime)
.SumAsync(x => (decimal?)x.Xfje) ?? 0m;
// 目标业绩
var targetPerformance = await _db.Queryable()
.Where(x => x.StoreId == storeId && x.Month == input.StatisticsMonth)
.SumAsync(x => (decimal?)x.BusinessUnitTarget) ?? 0m;
// 完成率
var completionRate = targetPerformance > 0 ? (netPerformance / targetPerformance * 100m) : 0m;
// 开单次数
var billingCount = await _db.Queryable()
.Where(x => x.Djmd == storeId && x.IsEffective == 1)
.Where(x => x.Kdrq.HasValue && x.Kdrq.Value >= startDate && x.Kdrq.Value <= endDateTime)
.CountAsync();
// 消耗次数
var consumeCount = await _db.Queryable()
.Where(x => x.Md == storeId && x.IsEffective == 1)
.Where(x => x.Hksj.HasValue && x.Hksj.Value >= startDate && x.Hksj.Value <= endDateTime)
.CountAsync();
// 人头数
var headCount = await _db.Queryable()
.Where(x => x.Md == storeId && x.IsEffective == 1)
.Where(x => x.Hksj.HasValue && x.Hksj.Value >= startDate && x.Hksj.Value <= endDateTime)
.Select(x => x.Hy)
.Distinct()
.CountAsync();
// 人次
var personCountSql = $@"
SELECT COUNT(DISTINCT CONCAT(xh.Hy, '-', DATE_FORMAT(xh.Hksj, '%Y-%m-%d'))) as PersonCount
FROM lq_xh_hyhk xh
WHERE xh.Md = '{storeId}'
AND xh.F_IsEffective = 1
AND xh.Hksj >= '{startDate:yyyy-MM-dd HH:mm:ss}'
AND xh.Hksj <= '{endDateTime:yyyy-MM-dd HH:mm:ss}'";
var personCountResult = await _db.Ado.SqlQueryAsync(personCountSql);
var personCount = personCountResult?.FirstOrDefault() != null
? Convert.ToInt32(personCountResult.FirstOrDefault().PersonCount ?? 0)
: 0;
// 项目数
var projectCountSql = $@"
SELECT COALESCE(SUM(COALESCE(px.F_OriginalProjectNumber, px.F_ProjectNumber, 0)), 0) as ProjectCount
FROM lq_xh_pxmx px
INNER JOIN lq_xh_hyhk xh ON px.F_ConsumeInfoId = xh.F_Id
WHERE xh.Md = '{storeId}'
AND xh.F_IsEffective = 1
AND px.F_IsEffective = 1
AND xh.Hksj >= '{startDate:yyyy-MM-dd HH:mm:ss}'
AND xh.Hksj <= '{endDateTime:yyyy-MM-dd HH:mm:ss}'";
var projectCountResult = await _db.Ado.SqlQueryAsync(projectCountSql);
var projectCount = projectCountResult?.FirstOrDefault() != null
? Convert.ToDecimal(projectCountResult.FirstOrDefault().ProjectCount ?? 0)
: 0m;
var storeInfo = storeDict.ContainsKey(storeId) ? storeDict[storeId] : new { Name = "", Code = "" };
rankingData.Add(new BusinessUnitStoreRankingItem
{
StoreId = storeId,
StoreCode = storeInfo.Code,
StoreName = storeInfo.Name,
BillingPerformance = billingAmount,
ConsumePerformance = consumeAmount,
RefundAmount = refundAmount,
NetPerformance = netPerformance,
TargetPerformance = targetPerformance,
CompletionRate = completionRate,
BillingCount = billingCount,
ConsumeCount = consumeCount,
HeadCount = headCount,
PersonCount = personCount,
ProjectCount = projectCount,
Percentage = 0m // 占比稍后计算
});
}
// 根据排行类型排序
var rankingType = (input.RankingType ?? "Billing").ToLower();
switch (rankingType)
{
case "consume":
rankingData = rankingData.OrderByDescending(x => x.ConsumePerformance).ToList();
break;
case "completionrate":
rankingData = rankingData.OrderByDescending(x => x.CompletionRate).ToList();
break;
default: // Billing
rankingData = rankingData.OrderByDescending(x => x.BillingPerformance).ToList();
break;
}
// 计算总业绩(用于计算占比)
decimal totalPerformance = 0m;
switch (rankingType)
{
case "consume":
totalPerformance = rankingData.Sum(x => x.ConsumePerformance);
break;
case "completionrate":
totalPerformance = rankingData.Sum(x => x.NetPerformance);
break;
default: // Billing
totalPerformance = rankingData.Sum(x => x.BillingPerformance);
break;
}
// 计算占比并设置排名
var topCount = input.TopCount > 0 ? input.TopCount : 10;
var result = new BusinessUnitDashboardStoreRankingOutput();
for (int i = 0; i < Math.Min(topCount, rankingData.Count); i++)
{
var item = rankingData[i];
item.Ranking = i + 1;
item.Percentage = totalPerformance > 0
? (rankingType == "billing" ? item.BillingPerformance :
rankingType == "consume" ? item.ConsumePerformance : item.NetPerformance) / totalPerformance * 100m
: 0m;
result.RankingData.Add(item);
}
_logger.LogInformation("事业部驾驶舱门店排行数据查询完成,返回{Count}条数据", result.RankingData.Count);
return result;
}
catch (Exception ex)
{
_logger.LogError(ex, "查询事业部驾驶舱门店排行数据失败");
throw NCCException.Oh($"查询失败:{ex.Message}");
}
}
///
/// 获取事业部驾驶舱运营统计数据
///
///
/// 获取指定事业部在指定月份的运营分析数据:开单分析、消耗分析、退卡分析
///
/// 示例请求:
/// ```json
/// {
/// "businessUnitId": "734725299018663173",
/// "statisticsMonth": "202512"
/// }
/// ```
///
/// 参数说明:
/// - businessUnitId: 事业部ID(BASE_ORGANIZE表的组织ID),与storeIds两者必填其一
/// - storeIds: 门店ID列表,与businessUnitId两者必填其一
/// - statisticsMonth: 统计月份,格式为YYYYMM(必填)
///
/// 返回数据说明:
/// - BillingAnalysis: 开单分析(开单次数、平均开单金额、开单门店数)
/// - ConsumeAnalysis: 消耗分析(消耗次数、消耗金额、消耗率)
/// - RefundAnalysis: 退卡分析(退卡次数、退卡金额、退卡率)
///
/// 查询参数
/// 事业部驾驶舱运营统计数据
/// 成功返回运营统计数据
/// 参数错误
/// 服务器错误
[HttpPost("GetOperationStatistics")]
public async Task GetOperationStatistics([FromBody] BusinessUnitDashboardStatisticsInput input)
{
try
{
if (input == null) throw NCCException.Oh("请求参数不能为空");
if (string.IsNullOrWhiteSpace(input.StatisticsMonth) || input.StatisticsMonth.Length != 6)
throw NCCException.Oh("统计月份格式错误,必须为YYYYMM格式");
if (string.IsNullOrWhiteSpace(input.BusinessUnitId) && (input.StoreIds == null || !input.StoreIds.Any()))
throw NCCException.Oh("事业部ID和门店ID列表不能同时为空,必须传入其中一个");
_logger.LogInformation("开始查询事业部驾驶舱运营统计数据,事业部ID:{BusinessUnitId},统计月份:{StatisticsMonth}",
input.BusinessUnitId, input.StatisticsMonth);
// 解析月份获取时间范围
var year = int.Parse(input.StatisticsMonth.Substring(0, 4));
var month = int.Parse(input.StatisticsMonth.Substring(4, 2));
var startDate = new DateTime(year, month, 1);
var endDate = startDate.AddMonths(1).AddDays(-1);
var endDateTime = input.StatisticsMonth == DateTime.Now.ToString("yyyyMM")
? DateTime.Now
: endDate.Date.AddHours(23).AddMinutes(59).AddSeconds(59);
// 获取门店列表
var storeIds = await GetStoreIdsAsync(input.BusinessUnitId, input.StoreIds, input.StatisticsMonth);
if (!storeIds.Any())
{
_logger.LogWarning("事业部ID:{BusinessUnitId} 在 {StatisticsMonth} 月份没有管理的门店", input.BusinessUnitId, input.StatisticsMonth);
return new BusinessUnitDashboardOperationStatisticsOutput();
}
// 1. 开单分析
var billingQuery = _db.Queryable()
.Where(x => storeIds.Contains(x.Djmd) && x.IsEffective == 1)
.Where(x => x.Kdrq.HasValue && x.Kdrq.Value >= startDate && x.Kdrq.Value <= endDateTime);
var billingCount = await billingQuery.CountAsync();
var billingAmount = await billingQuery.SumAsync(x => (decimal?)x.Sfyj) ?? 0m;
var avgBillingAmount = billingCount > 0 ? billingAmount / (decimal)billingCount : 0m;
// 开单门店数(有开单的门店数量)
var billingStoreIds = await billingQuery
.Select(x => x.Djmd)
.Distinct()
.ToListAsync();
var billingStoreCount = billingStoreIds.Count;
// 2. 消耗分析
var consumeQuery = _db.Queryable()
.Where(x => storeIds.Contains(x.Md) && x.IsEffective == 1)
.Where(x => x.Hksj.HasValue && x.Hksj.Value >= startDate && x.Hksj.Value <= endDateTime);
var consumeCount = await consumeQuery.CountAsync();
var consumeAmount = await consumeQuery.SumAsync(x => (decimal?)x.Xfje) ?? 0m;
var consumeRate = billingAmount > 0 ? (consumeAmount / billingAmount * 100m) : 0m;
// 3. 退卡分析
var refundQuery = _db.Queryable()
.Where(x => storeIds.Contains(x.Md) && x.IsEffective == 1)
.Where(x => x.Tksj.HasValue && x.Tksj.Value.Date >= startDate.Date && x.Tksj.Value.Date <= endDate.Date);
var refundCount = await refundQuery.CountAsync();
var refundAmount = await refundQuery.SumAsync(x => (decimal?)(x.ActualRefundAmount ?? x.Tkje ?? 0)) ?? 0m;
var refundRate = billingAmount > 0 ? (refundAmount / billingAmount * 100m) : 0m;
var result = new BusinessUnitDashboardOperationStatisticsOutput
{
BillingAnalysis = new BusinessUnitBillingAnalysis
{
BillingCount = billingCount,
AverageBillingAmount = avgBillingAmount,
BillingStoreCount = billingStoreCount
},
ConsumeAnalysis = new BusinessUnitConsumeAnalysis
{
ConsumeCount = consumeCount,
ConsumeAmount = consumeAmount,
ConsumeRate = consumeRate
},
RefundAnalysis = new BusinessUnitRefundAnalysis
{
RefundCount = refundCount,
RefundAmount = refundAmount,
RefundRate = refundRate
}
};
_logger.LogInformation("事业部驾驶舱运营统计数据查询完成,开单次数:{BillingCount},消耗次数:{ConsumeCount},退卡次数:{RefundCount}",
result.BillingAnalysis.BillingCount, result.ConsumeAnalysis.ConsumeCount, result.RefundAnalysis.RefundCount);
return result;
}
catch (Exception ex)
{
_logger.LogError(ex, "查询事业部驾驶舱运营统计数据失败");
throw NCCException.Oh($"查询失败:{ex.Message}");
}
}
///
/// 获取事业部驾驶舱门店明细列表
///
///
/// 获取指定事业部在指定月份的门店业绩明细列表,支持分页、排序、筛选
///
/// 示例请求:
/// ```json
/// {
/// "businessUnitId": "734725299018663173",
/// "statisticsMonth": "202512",
/// "currentPage": 1,
/// "pageSize": 10,
/// "storeName": "门店名称(可选,模糊查询)"
/// }
/// ```
///
/// 参数说明:
/// - businessUnitId: 事业部ID(BASE_ORGANIZE表的组织ID),与storeIds两者必填其一
/// - storeIds: 门店ID列表,与businessUnitId两者必填其一
/// - statisticsMonth: 统计月份,格式为YYYYMM(必填)
/// - currentPage: 当前页码(可选,默认1)
/// - pageSize: 每页数量(可选,默认10)
/// - storeName: 门店名称(可选,模糊查询)
///
/// 返回数据说明:
/// - 返回分页结果,包含门店列表和分页信息
/// - 每个门店明细包含:门店信息、业绩数据、运营指标等
///
/// 查询参数
/// 事业部驾驶舱门店明细列表
/// 成功返回明细列表
/// 参数错误
/// 服务器错误
[HttpPost("GetStoreDetailList")]
public async Task GetStoreDetailList([FromBody] BusinessUnitDashboardStoreDetailListInput input)
{
try
{
if (input == null) throw NCCException.Oh("请求参数不能为空");
if (string.IsNullOrWhiteSpace(input.StatisticsMonth) || input.StatisticsMonth.Length != 6)
throw NCCException.Oh("统计月份格式错误,必须为YYYYMM格式");
if (string.IsNullOrWhiteSpace(input.BusinessUnitId) && (input.StoreIds == null || !input.StoreIds.Any()))
throw NCCException.Oh("事业部ID和门店ID列表不能同时为空,必须传入其中一个");
_logger.LogInformation("开始查询事业部驾驶舱门店明细列表,事业部ID:{BusinessUnitId},统计月份:{StatisticsMonth},页码:{CurrentPage},每页数量:{PageSize}",
input.BusinessUnitId, input.StatisticsMonth, input.currentPage, input.pageSize);
// 解析月份获取时间范围
var year = int.Parse(input.StatisticsMonth.Substring(0, 4));
var month = int.Parse(input.StatisticsMonth.Substring(4, 2));
var startDate = new DateTime(year, month, 1);
var endDate = startDate.AddMonths(1).AddDays(-1);
var endDateTime = input.StatisticsMonth == DateTime.Now.ToString("yyyyMM")
? DateTime.Now
: endDate.Date.AddHours(23).AddMinutes(59).AddSeconds(59);
// 获取门店列表
var storeIds = await GetStoreIdsAsync(input.BusinessUnitId, input.StoreIds, input.StatisticsMonth);
if (!storeIds.Any())
{
_logger.LogWarning("事业部ID:{BusinessUnitId} 在 {StatisticsMonth} 月份没有管理的门店", input.BusinessUnitId, input.StatisticsMonth);
return new { total = 0, list = new List() };
}
// 获取门店信息(名称、编码),支持门店名称筛选
var storeQuery = _db.Queryable()
.Where(x => storeIds.Contains(x.Id));
if (!string.IsNullOrWhiteSpace(input.StoreName))
{
storeQuery = storeQuery.Where(x => x.Dm.Contains(input.StoreName));
}
var stores = await storeQuery.Select(x => new { x.Id, x.Dm, x.Mdbm }).ToListAsync();
var filteredStoreIds = stores.Select(x => x.Id).ToList();
if (!filteredStoreIds.Any())
{
return new { total = 0, list = new List() };
}
// 计算每个门店的业绩数据
var detailList = new List();
var storeDict = stores.ToDictionary(x => x.Id, x => new { Name = x.Dm ?? "", Code = x.Mdbm ?? "" });
foreach (var storeId in filteredStoreIds)
{
// 开单业绩
var billingAmount = await _db.Queryable()
.Where(x => x.Djmd == storeId && x.IsEffective == 1)
.Where(x => x.Kdrq.HasValue && x.Kdrq.Value >= startDate && x.Kdrq.Value <= endDateTime)
.SumAsync(x => (decimal?)x.Sfyj) ?? 0m;
// 退卡金额
var refundAmount = await _db.Queryable()
.Where(x => x.Md == storeId && x.IsEffective == 1)
.Where(x => x.Tksj.HasValue && x.Tksj.Value.Date >= startDate.Date && x.Tksj.Value.Date <= endDate.Date)
.SumAsync(x => (decimal?)(x.ActualRefundAmount ?? x.Tkje ?? 0)) ?? 0m;
// 净业绩
var netPerformance = billingAmount - refundAmount;
// 消耗业绩
var consumeAmount = await _db.Queryable()
.Where(x => x.Md == storeId && x.IsEffective == 1)
.Where(x => x.Hksj.HasValue && x.Hksj.Value >= startDate && x.Hksj.Value <= endDateTime)
.SumAsync(x => (decimal?)x.Xfje) ?? 0m;
// 目标业绩
var targetPerformance = await _db.Queryable()
.Where(x => x.StoreId == storeId && x.Month == input.StatisticsMonth)
.SumAsync(x => (decimal?)x.BusinessUnitTarget) ?? 0m;
// 完成率
var completionRate = targetPerformance > 0 ? (netPerformance / targetPerformance * 100m) : 0m;
// 开单次数
var billingCount = await _db.Queryable()
.Where(x => x.Djmd == storeId && x.IsEffective == 1)
.Where(x => x.Kdrq.HasValue && x.Kdrq.Value >= startDate && x.Kdrq.Value <= endDateTime)
.CountAsync();
// 消耗次数
var consumeCount = await _db.Queryable()
.Where(x => x.Md == storeId && x.IsEffective == 1)
.Where(x => x.Hksj.HasValue && x.Hksj.Value >= startDate && x.Hksj.Value <= endDateTime)
.CountAsync();
// 退卡次数
var refundCount = await _db.Queryable()
.Where(x => x.Md == storeId && x.IsEffective == 1)
.Where(x => x.Tksj.HasValue && x.Tksj.Value.Date >= startDate.Date && x.Tksj.Value.Date <= endDate.Date)
.CountAsync();
// 平均开单金额
var avgBillingAmount = billingCount > 0 ? billingAmount / (decimal)billingCount : 0m;
// 平均消耗金额
var avgConsumeAmount = consumeCount > 0 ? consumeAmount / (decimal)consumeCount : 0m;
// 人头数
var headCount = await _db.Queryable()
.Where(x => x.Md == storeId && x.IsEffective == 1)
.Where(x => x.Hksj.HasValue && x.Hksj.Value >= startDate && x.Hksj.Value <= endDateTime)
.Select(x => x.Hy)
.Distinct()
.CountAsync();
// 人次
var personCountSql = $@"
SELECT COUNT(DISTINCT CONCAT(xh.Hy, '-', DATE_FORMAT(xh.Hksj, '%Y-%m-%d'))) as PersonCount
FROM lq_xh_hyhk xh
WHERE xh.Md = '{storeId}'
AND xh.F_IsEffective = 1
AND xh.Hksj >= '{startDate:yyyy-MM-dd HH:mm:ss}'
AND xh.Hksj <= '{endDateTime:yyyy-MM-dd HH:mm:ss}'";
var personCountResult = await _db.Ado.SqlQueryAsync(personCountSql);
var personCount = personCountResult?.FirstOrDefault() != null
? Convert.ToInt32(personCountResult.FirstOrDefault().PersonCount ?? 0)
: 0;
// 项目数
var projectCountSql = $@"
SELECT COALESCE(SUM(COALESCE(px.F_OriginalProjectNumber, px.F_ProjectNumber, 0)), 0) as ProjectCount
FROM lq_xh_pxmx px
INNER JOIN lq_xh_hyhk xh ON px.F_ConsumeInfoId = xh.F_Id
WHERE xh.Md = '{storeId}'
AND xh.F_IsEffective = 1
AND px.F_IsEffective = 1
AND xh.Hksj >= '{startDate:yyyy-MM-dd HH:mm:ss}'
AND xh.Hksj <= '{endDateTime:yyyy-MM-dd HH:mm:ss}'";
var projectCountResult = await _db.Ado.SqlQueryAsync(projectCountSql);
var projectCount = projectCountResult?.FirstOrDefault() != null
? Convert.ToDecimal(projectCountResult.FirstOrDefault().ProjectCount ?? 0)
: 0m;
var storeInfo = storeDict.ContainsKey(storeId) ? storeDict[storeId] : new { Name = "", Code = "" };
detailList.Add(new BusinessUnitDashboardStoreDetailListOutput
{
StoreId = storeId,
StoreCode = storeInfo.Code,
StoreName = storeInfo.Name,
BillingPerformance = billingAmount,
ConsumePerformance = consumeAmount,
RefundAmount = refundAmount,
NetPerformance = netPerformance,
TargetPerformance = targetPerformance,
CompletionRate = completionRate,
BillingCount = billingCount,
ConsumeCount = consumeCount,
RefundCount = refundCount,
AvgBillingAmount = avgBillingAmount,
AvgConsumeAmount = avgConsumeAmount,
HeadCount = headCount,
PersonCount = personCount,
ProjectCount = projectCount
});
}
// 分页处理
var currentPage = input.currentPage > 0 ? input.currentPage : 1;
var pageSize = input.pageSize > 0 ? input.pageSize : 10;
var total = detailList.Count;
var pagedList = detailList.Skip((currentPage - 1) * pageSize).Take(pageSize).ToList();
_logger.LogInformation("事业部驾驶舱门店明细列表查询完成,总数:{Total},返回{Count}条数据", total, pagedList.Count);
return new { total = total, list = pagedList };
}
catch (Exception ex)
{
_logger.LogError(ex, "查询事业部驾驶舱门店明细列表失败");
throw NCCException.Oh($"查询失败:{ex.Message}");
}
}
///
/// 获取事业部驾驶舱总经理/经理业绩排行
///
///
/// 获取指定事业部在指定月份的总经理/经理业绩排行数据
///
/// 示例请求:
/// ```json
/// {
/// "businessUnitId": "734725299018663173",
/// "statisticsMonth": "202512",
/// "topCount": 10
/// }
/// ```
///
/// 参数说明:
/// - businessUnitId: 事业部ID(BASE_ORGANIZE表的组织ID),与storeIds两者必填其一
/// - storeIds: 门店ID列表,与businessUnitId两者必填其一
/// - statisticsMonth: 统计月份,格式为YYYYMM(必填)
/// - topCount: 排行数量(可选,默认10)
///
/// 返回数据说明:
/// - RankingData: 总经理/经理排行数据列表,包含排名、姓名、管理的门店、业绩、工资等信息
///
/// 查询参数
/// 事业部驾驶舱总经理/经理排行数据
/// 成功返回排行数据
/// 参数错误
/// 服务器错误
[HttpPost("GetManagerRanking")]
public async Task GetManagerRanking([FromBody] BusinessUnitDashboardManagerRankingInput input)
{
try
{
if (input == null) throw NCCException.Oh("请求参数不能为空");
if (string.IsNullOrWhiteSpace(input.StatisticsMonth) || input.StatisticsMonth.Length != 6)
throw NCCException.Oh("统计月份格式错误,必须为YYYYMM格式");
if (string.IsNullOrWhiteSpace(input.BusinessUnitId) && (input.StoreIds == null || !input.StoreIds.Any()))
throw NCCException.Oh("事业部ID和门店ID列表不能同时为空,必须传入其中一个");
_logger.LogInformation("开始查询事业部驾驶舱总经理/经理排行数据,事业部ID:{BusinessUnitId},统计月份:{StatisticsMonth}",
input.BusinessUnitId, input.StatisticsMonth);
// 解析月份获取时间范围
var year = int.Parse(input.StatisticsMonth.Substring(0, 4));
var month = int.Parse(input.StatisticsMonth.Substring(4, 2));
var startDate = new DateTime(year, month, 1);
var endDate = startDate.AddMonths(1).AddDays(-1);
var endDateTime = input.StatisticsMonth == DateTime.Now.ToString("yyyyMM")
? DateTime.Now
: endDate.Date.AddHours(23).AddMinutes(59).AddSeconds(59);
// 获取门店列表
var storeIds = await GetStoreIdsAsync(input.BusinessUnitId, input.StoreIds, input.StatisticsMonth);
if (!storeIds.Any())
{
_logger.LogWarning("事业部ID:{BusinessUnitId} 在 {StatisticsMonth} 月份没有管理的门店", input.BusinessUnitId, input.StatisticsMonth);
return new BusinessUnitDashboardManagerRankingOutput();
}
// 1. 获取总经理/经理归属信息(从lq_md_general_manager_lifeline表,只查询管理这些门店的总经理/经理)
var lifelineList = await _db.Queryable()
.Where(x => x.Month == input.StatisticsMonth && storeIds.Contains(x.StoreId))
.ToListAsync();
if (!lifelineList.Any())
{
return new BusinessUnitDashboardManagerRankingOutput();
}
// 2. 获取所有不重复的总经理/经理ID
var allManagerIds = lifelineList
.Where(x => !string.IsNullOrEmpty(x.GeneralManagerId))
.Select(x => x.GeneralManagerId)
.Distinct()
.ToList();
// 3. 获取用户信息
var users = await _db.Queryable()
.Where(x => allManagerIds.Contains(x.Id) && x.DeleteMark == null)
.Select(x => new { x.Id, x.RealName })
.ToListAsync();
var userDict = users.ToDictionary(x => x.Id, x => x.RealName ?? "");
// 4. 获取门店信息
var stores = await _db.Queryable()
.Where(x => storeIds.Contains(x.Id))
.Select(x => new { x.Id, x.Dm })
.ToListAsync();
var storeDict = stores.ToDictionary(x => x.Id, x => x.Dm ?? "");
// 5. 获取工资统计数据
var salaryStats = await _db.Queryable()
.Where(x => x.StatisticsMonth == input.StatisticsMonth && allManagerIds.Contains(x.EmployeeId))
.ToListAsync();
var salaryDict = salaryStats.ToDictionary(x => x.EmployeeId, x => x);
// 6. 按总经理/经理ID分组,获取每个总经理/经理管理的门店
var managerStoreDict = lifelineList
.Where(x => !string.IsNullOrEmpty(x.GeneralManagerId) && !string.IsNullOrEmpty(x.StoreId))
.GroupBy(x => x.GeneralManagerId)
.ToDictionary(g => g.Key, g => g.Select(x => x.StoreId).Distinct().ToList());
// 7. 计算每个总经理/经理的业绩数据
var rankingData = new List();
foreach (var managerId in allManagerIds)
{
var managerStores = managerStoreDict.ContainsKey(managerId) ? managerStoreDict[managerId] : new List();
if (!managerStores.Any()) continue;
// 计算管理的门店总业绩
decimal totalBilling = 0m;
decimal totalRefund = 0m;
decimal totalConsume = 0m;
foreach (var storeId in managerStores)
{
// 开单业绩
var billing = await _db.Queryable()
.Where(x => x.Djmd == storeId && x.IsEffective == 1)
.Where(x => x.Kdrq.HasValue && x.Kdrq.Value >= startDate && x.Kdrq.Value <= endDateTime)
.SumAsync(x => (decimal?)x.Sfyj) ?? 0m;
totalBilling += billing;
// 退卡金额
var refund = await _db.Queryable()
.Where(x => x.Md == storeId && x.IsEffective == 1)
.Where(x => x.Tksj.HasValue && x.Tksj.Value.Date >= startDate.Date && x.Tksj.Value.Date <= endDate.Date)
.SumAsync(x => (decimal?)(x.ActualRefundAmount ?? x.Tkje ?? 0)) ?? 0m;
totalRefund += refund;
// 消耗业绩
var consume = await _db.Queryable()
.Where(x => x.Md == storeId && x.IsEffective == 1)
.Where(x => x.Hksj.HasValue && x.Hksj.Value >= startDate && x.Hksj.Value <= endDateTime)
.SumAsync(x => (decimal?)x.Xfje) ?? 0m;
totalConsume += consume;
}
var totalPerformance = totalBilling - totalRefund;
// 获取工资数据
var salary = salaryDict.ContainsKey(managerId) ? salaryDict[managerId] : null;
var managerLifeline = lifelineList.FirstOrDefault(x => x.GeneralManagerId == managerId);
var storeNames = managerStores
.Where(s => storeDict.ContainsKey(s))
.Select(s => storeDict[s])
.ToList();
rankingData.Add(new BusinessUnitManagerRankingItem
{
ManagerId = managerId,
ManagerName = userDict.ContainsKey(managerId) ? userDict[managerId] : "",
ManagerType = managerLifeline?.ManagerType ?? 1,
Position = managerLifeline?.ManagerType == 1 ? "总经理" : "经理",
ManagedStoreCount = managerStores.Count,
ManagedStoreNames = storeNames,
TotalBillingPerformance = totalBilling,
TotalConsumePerformance = totalConsume,
TotalGrossProfit = salary?.GrossProfit ?? 0m,
BaseSalary = salary?.BaseSalary ?? 0m,
Commission = salary?.TotalCommission ?? 0m,
TotalSalary = salary?.FinalGrossSalary ?? 0m,
Percentage = 0m // 稍后计算
});
}
// 8. 按总业绩排序
rankingData = rankingData.OrderByDescending(x => x.TotalBillingPerformance).ToList();
// 9. 计算总业绩和占比
var totalPerformanceSum = rankingData.Sum(x => x.TotalBillingPerformance);
var topCount = input.TopCount > 0 ? input.TopCount : 10;
var result = new BusinessUnitDashboardManagerRankingOutput();
for (int i = 0; i < Math.Min(topCount, rankingData.Count); i++)
{
var item = rankingData[i];
item.Ranking = i + 1;
item.Percentage = totalPerformanceSum > 0 ? (item.TotalBillingPerformance / totalPerformanceSum * 100m) : 0m;
result.RankingData.Add(item);
}
_logger.LogInformation("事业部驾驶舱总经理/经理排行数据查询完成,返回{Count}条数据", result.RankingData.Count);
return result;
}
catch (Exception ex)
{
_logger.LogError(ex, "查询事业部驾驶舱总经理/经理排行数据失败");
throw NCCException.Oh($"查询失败:{ex.Message}");
}
}
///
/// 获取事业部驾驶舱对比分析数据
///
///
/// 获取指定事业部的对比分析数据:时间对比(环比、同比)、部门对比
///
/// 示例请求:
/// ```json
/// {
/// "businessUnitId": "734725299018663173",
/// "statisticsMonth": "202512",
/// "comparisonType": "Time"
/// }
/// ```
///
/// 参数说明:
/// - businessUnitId: 事业部ID(BASE_ORGANIZE表的组织ID),与storeIds两者必填其一
/// - storeIds: 门店ID列表,与businessUnitId两者必填其一
/// - statisticsMonth: 统计月份,格式为YYYYMM(必填)
/// - comparisonType: 对比类型(可选,默认:Time)
/// - Time - 时间对比(环比、同比)
/// - Department - 部门对比(各事业部对比)
///
/// 返回数据说明:
/// - TimeComparison: 时间对比数据(环比、同比)
/// - DepartmentComparison: 部门对比数据(各事业部对比)
///
/// 查询参数
/// 事业部驾驶舱对比分析数据
/// 成功返回对比分析数据
/// 参数错误
/// 服务器错误
[HttpPost("GetComparisonAnalysis")]
public async Task GetComparisonAnalysis([FromBody] BusinessUnitDashboardComparisonAnalysisInput input)
{
try
{
if (input == null) throw NCCException.Oh("请求参数不能为空");
if (string.IsNullOrWhiteSpace(input.StatisticsMonth) || input.StatisticsMonth.Length != 6)
throw NCCException.Oh("统计月份格式错误,必须为YYYYMM格式");
if (string.IsNullOrWhiteSpace(input.BusinessUnitId) && (input.StoreIds == null || !input.StoreIds.Any()))
throw NCCException.Oh("事业部ID和门店ID列表不能同时为空,必须传入其中一个");
_logger.LogInformation("开始查询事业部驾驶舱对比分析数据,事业部ID:{BusinessUnitId},统计月份:{StatisticsMonth}",
input.BusinessUnitId, input.StatisticsMonth);
var result = new BusinessUnitDashboardComparisonAnalysisOutput();
// 1. 时间对比(环比、同比)
var currentMonth = DateTime.ParseExact(input.StatisticsMonth, "yyyyMM", null);
var lastMonth = currentMonth.AddMonths(-1);
var lastYearMonth = currentMonth.AddYears(-1);
// 当前月数据
var currentData = await CalculateMonthlyPerformance(
await GetStoreIdsAsync(input.BusinessUnitId, input.StoreIds, input.StatisticsMonth),
input.StatisticsMonth);
// 上月数据(环比)
var lastMonthStr = lastMonth.ToString("yyyyMM");
var lastMonthStoreIds = await GetStoreIdsForMonthAsync(input.BusinessUnitId, input.StoreIds, lastMonthStr);
var lastMonthData = await CalculateMonthlyPerformance(lastMonthStoreIds, lastMonthStr);
// 去年同月数据(同比)
var lastYearMonthStr = lastYearMonth.ToString("yyyyMM");
var lastYearMonthStoreIds = await GetStoreIdsForMonthAsync(input.BusinessUnitId, input.StoreIds, lastYearMonthStr);
var lastYearMonthData = await CalculateMonthlyPerformance(lastYearMonthStoreIds, lastYearMonthStr);
// 环比对比
result.TimeComparison.MonthOverMonth = new BusinessUnitComparisonItem
{
BillingPerformance = new BusinessUnitComparisonValue
{
CurrentValue = currentData.billingPerformance,
CompareValue = lastMonthData.billingPerformance,
GrowthRate = lastMonthData.billingPerformance > 0
? ((currentData.billingPerformance - lastMonthData.billingPerformance) / lastMonthData.billingPerformance * 100m)
: 0m
},
ConsumePerformance = new BusinessUnitComparisonValue
{
CurrentValue = currentData.consumePerformance,
CompareValue = lastMonthData.consumePerformance,
GrowthRate = lastMonthData.consumePerformance > 0
? ((currentData.consumePerformance - lastMonthData.consumePerformance) / lastMonthData.consumePerformance * 100m)
: 0m
},
NetPerformance = new BusinessUnitComparisonValue
{
CurrentValue = currentData.netPerformance,
CompareValue = lastMonthData.netPerformance,
GrowthRate = lastMonthData.netPerformance > 0
? ((currentData.netPerformance - lastMonthData.netPerformance) / lastMonthData.netPerformance * 100m)
: 0m
},
CompletionRate = new BusinessUnitComparisonValue
{
CurrentValue = currentData.completionRate,
CompareValue = lastMonthData.completionRate,
GrowthRate = lastMonthData.completionRate > 0
? ((currentData.completionRate - lastMonthData.completionRate) / lastMonthData.completionRate * 100m)
: 0m
}
};
// 同比对比
result.TimeComparison.YearOverYear = new BusinessUnitComparisonItem
{
BillingPerformance = new BusinessUnitComparisonValue
{
CurrentValue = currentData.billingPerformance,
CompareValue = lastYearMonthData.billingPerformance,
GrowthRate = lastYearMonthData.billingPerformance > 0
? ((currentData.billingPerformance - lastYearMonthData.billingPerformance) / lastYearMonthData.billingPerformance * 100m)
: 0m
},
ConsumePerformance = new BusinessUnitComparisonValue
{
CurrentValue = currentData.consumePerformance,
CompareValue = lastYearMonthData.consumePerformance,
GrowthRate = lastYearMonthData.consumePerformance > 0
? ((currentData.consumePerformance - lastYearMonthData.consumePerformance) / lastYearMonthData.consumePerformance * 100m)
: 0m
},
NetPerformance = new BusinessUnitComparisonValue
{
CurrentValue = currentData.netPerformance,
CompareValue = lastYearMonthData.netPerformance,
GrowthRate = lastYearMonthData.netPerformance > 0
? ((currentData.netPerformance - lastYearMonthData.netPerformance) / lastYearMonthData.netPerformance * 100m)
: 0m
},
CompletionRate = new BusinessUnitComparisonValue
{
CurrentValue = currentData.completionRate,
CompareValue = lastYearMonthData.completionRate,
GrowthRate = lastYearMonthData.completionRate > 0
? ((currentData.completionRate - lastYearMonthData.completionRate) / lastYearMonthData.completionRate * 100m)
: 0m
}
};
// 2. 部门对比(各事业部对比)- 如果comparisonType包含Department
if (input.ComparisonType != null && input.ComparisonType.ToLower().Contains("department"))
{
// 获取所有事业部ID(从BASE_ORGANIZE表)
var allBusinessUnits = await _db.Queryable()
.Where(x => x.DeleteMark == null && x.EnabledMark == 1)
.Where(x => x.Category == "事业部" || x.FullName.Contains("事业部"))
.Select(x => new { x.Id, x.FullName })
.ToListAsync();
foreach (var bu in allBusinessUnits)
{
var buStoreIds = await GetStoreIdsForMonthAsync(bu.Id, null, input.StatisticsMonth);
if (!buStoreIds.Any()) continue;
var buData = await CalculateMonthlyPerformance(buStoreIds, input.StatisticsMonth);
result.DepartmentComparison.DepartmentData.Add(new BusinessUnitDepartmentComparisonItem
{
BusinessUnitId = bu.Id,
BusinessUnitName = bu.FullName ?? "",
BillingPerformance = buData.billingPerformance,
ConsumePerformance = buData.consumePerformance,
NetPerformance = buData.netPerformance,
CompletionRate = buData.completionRate
});
}
}
_logger.LogInformation("事业部驾驶舱对比分析数据查询完成");
return result;
}
catch (Exception ex)
{
_logger.LogError(ex, "查询事业部驾驶舱对比分析数据失败");
throw NCCException.Oh($"查询失败:{ex.Message}");
}
}
///
/// 获取事业部驾驶舱门店业绩分布数据
///
///
/// 获取指定事业部在指定月份的门店业绩分布数据(饼图和柱状图数据)
///
/// 示例请求:
/// ```json
/// {
/// "businessUnitId": "734725299018663173",
/// "statisticsMonth": "202512"
/// }
/// ```
///
/// 参数说明:
/// - businessUnitId: 事业部ID(BASE_ORGANIZE表的组织ID),与storeIds两者必填其一
/// - storeIds: 门店ID列表,与businessUnitId两者必填其一
/// - statisticsMonth: 统计月份,格式为YYYYMM(必填)
///
/// 返回数据说明:
/// - DistributionData: 门店业绩分布数据列表(饼图数据,包含占比)
/// - ComparisonData: 门店业绩对比数据列表(柱状图数据)
///
/// 查询参数
/// 事业部驾驶舱门店业绩分布数据
/// 成功返回分布数据
/// 参数错误
/// 服务器错误
[HttpPost("GetStoreDistribution")]
public async Task GetStoreDistribution([FromBody] BusinessUnitDashboardStatisticsInput input)
{
try
{
if (input == null) throw NCCException.Oh("请求参数不能为空");
if (string.IsNullOrWhiteSpace(input.StatisticsMonth) || input.StatisticsMonth.Length != 6)
throw NCCException.Oh("统计月份格式错误,必须为YYYYMM格式");
if (string.IsNullOrWhiteSpace(input.BusinessUnitId) && (input.StoreIds == null || !input.StoreIds.Any()))
throw NCCException.Oh("事业部ID和门店ID列表不能同时为空,必须传入其中一个");
_logger.LogInformation("开始查询事业部驾驶舱门店业绩分布数据,事业部ID:{BusinessUnitId},统计月份:{StatisticsMonth}",
input.BusinessUnitId, input.StatisticsMonth);
// 解析月份获取时间范围
var year = int.Parse(input.StatisticsMonth.Substring(0, 4));
var month = int.Parse(input.StatisticsMonth.Substring(4, 2));
var startDate = new DateTime(year, month, 1);
var endDate = startDate.AddMonths(1).AddDays(-1);
var endDateTime = input.StatisticsMonth == DateTime.Now.ToString("yyyyMM")
? DateTime.Now
: endDate.Date.AddHours(23).AddMinutes(59).AddSeconds(59);
// 获取门店列表
var storeIds = await GetStoreIdsAsync(input.BusinessUnitId, input.StoreIds, input.StatisticsMonth);
if (!storeIds.Any())
{
_logger.LogWarning("事业部ID:{BusinessUnitId} 在 {StatisticsMonth} 月份没有管理的门店", input.BusinessUnitId, input.StatisticsMonth);
return new BusinessUnitDashboardStoreDistributionOutput();
}
// 获取门店信息
var stores = await _db.Queryable()
.Where(x => storeIds.Contains(x.Id))
.Select(x => new { x.Id, x.Dm })
.ToListAsync();
var storeDict = stores.ToDictionary(x => x.Id, x => x.Dm ?? "");
// 计算每个门店的业绩
var distributionList = new List();
decimal totalPerformance = 0m;
foreach (var storeId in storeIds)
{
// 开单业绩
var billingAmount = await _db.Queryable()
.Where(x => x.Djmd == storeId && x.IsEffective == 1)
.Where(x => x.Kdrq.HasValue && x.Kdrq.Value >= startDate && x.Kdrq.Value <= endDateTime)
.SumAsync(x => (decimal?)x.Sfyj) ?? 0m;
// 退卡金额
var refundAmount = await _db.Queryable()
.Where(x => x.Md == storeId && x.IsEffective == 1)
.Where(x => x.Tksj.HasValue && x.Tksj.Value.Date >= startDate.Date && x.Tksj.Value.Date <= endDate.Date)
.SumAsync(x => (decimal?)(x.ActualRefundAmount ?? x.Tkje ?? 0)) ?? 0m;
// 净业绩
var netPerformance = billingAmount - refundAmount;
totalPerformance += netPerformance;
distributionList.Add(new BusinessUnitStoreDistributionItem
{
StoreId = storeId,
StoreName = storeDict.ContainsKey(storeId) ? storeDict[storeId] : "",
Performance = netPerformance,
Percentage = 0m // 稍后计算
});
}
// 计算占比
foreach (var item in distributionList)
{
item.Percentage = totalPerformance > 0 ? (item.Performance / totalPerformance * 100m) : 0m;
}
var result = new BusinessUnitDashboardStoreDistributionOutput
{
DistributionData = distributionList,
ComparisonData = distributionList.OrderByDescending(x => x.Performance).ToList()
};
_logger.LogInformation("事业部驾驶舱门店业绩分布数据查询完成,门店数量:{Count}", distributionList.Count);
return result;
}
catch (Exception ex)
{
_logger.LogError(ex, "查询事业部驾驶舱门店业绩分布数据失败");
throw NCCException.Oh($"查询失败:{ex.Message}");
}
}
///
/// 获取事业部驾驶舱总经理/经理业绩分布数据
///
///
/// 获取指定事业部在指定月份的总经理/经理业绩分布数据(饼图数据)
///
/// 示例请求:
/// ```json
/// {
/// "businessUnitId": "734725299018663173",
/// "statisticsMonth": "202512"
/// }
/// ```
///
/// 查询参数
/// 事业部驾驶舱总经理/经理业绩分布数据
[HttpPost("GetManagerDistribution")]
public async Task GetManagerDistribution([FromBody] BusinessUnitDashboardStatisticsInput input)
{
try
{
if (input == null) throw NCCException.Oh("请求参数不能为空");
if (string.IsNullOrWhiteSpace(input.StatisticsMonth) || input.StatisticsMonth.Length != 6)
throw NCCException.Oh("统计月份格式错误,必须为YYYYMM格式");
if (string.IsNullOrWhiteSpace(input.BusinessUnitId) && (input.StoreIds == null || !input.StoreIds.Any()))
throw NCCException.Oh("事业部ID和门店ID列表不能同时为空,必须传入其中一个");
// 解析月份获取时间范围
var year = int.Parse(input.StatisticsMonth.Substring(0, 4));
var month = int.Parse(input.StatisticsMonth.Substring(4, 2));
var startDate = new DateTime(year, month, 1);
var endDate = startDate.AddMonths(1).AddDays(-1);
var endDateTime = input.StatisticsMonth == DateTime.Now.ToString("yyyyMM")
? DateTime.Now
: endDate.Date.AddHours(23).AddMinutes(59).AddSeconds(59);
// 获取门店列表
var storeIds = await GetStoreIdsAsync(input.BusinessUnitId, input.StoreIds, input.StatisticsMonth);
if (!storeIds.Any())
{
return new BusinessUnitDashboardManagerDistributionOutput();
}
// 获取总经理/经理归属信息
var lifelineList = await _db.Queryable()
.Where(x => x.Month == input.StatisticsMonth && storeIds.Contains(x.StoreId))
.ToListAsync();
if (!lifelineList.Any())
{
return new BusinessUnitDashboardManagerDistributionOutput();
}
var allManagerIds = lifelineList
.Where(x => !string.IsNullOrEmpty(x.GeneralManagerId))
.Select(x => x.GeneralManagerId)
.Distinct()
.ToList();
// 获取用户信息
var users = await _db.Queryable()
.Where(x => allManagerIds.Contains(x.Id) && x.DeleteMark == null)
.Select(x => new { x.Id, x.RealName })
.ToListAsync();
var userDict = users.ToDictionary(x => x.Id, x => x.RealName ?? "");
// 按总经理/经理ID分组,获取每个总经理/经理管理的门店
var managerStoreDict = lifelineList
.Where(x => !string.IsNullOrEmpty(x.GeneralManagerId) && !string.IsNullOrEmpty(x.StoreId))
.GroupBy(x => x.GeneralManagerId)
.ToDictionary(g => g.Key, g => g.Select(x => x.StoreId).Distinct().ToList());
// 计算每个总经理/经理的业绩
var distributionList = new List();
decimal totalPerformance = 0m;
foreach (var managerId in allManagerIds)
{
var managerStores = managerStoreDict.ContainsKey(managerId) ? managerStoreDict[managerId] : new List();
if (!managerStores.Any()) continue;
decimal totalBilling = 0m;
decimal totalRefund = 0m;
foreach (var storeId in managerStores)
{
var billing = await _db.Queryable()
.Where(x => x.Djmd == storeId && x.IsEffective == 1)
.Where(x => x.Kdrq.HasValue && x.Kdrq.Value >= startDate && x.Kdrq.Value <= endDateTime)
.SumAsync(x => (decimal?)x.Sfyj) ?? 0m;
totalBilling += billing;
var refund = await _db.Queryable()
.Where(x => x.Md == storeId && x.IsEffective == 1)
.Where(x => x.Tksj.HasValue && x.Tksj.Value.Date >= startDate.Date && x.Tksj.Value.Date <= endDate.Date)
.SumAsync(x => (decimal?)(x.ActualRefundAmount ?? x.Tkje ?? 0)) ?? 0m;
totalRefund += refund;
}
var performance = totalBilling - totalRefund;
totalPerformance += performance;
distributionList.Add(new BusinessUnitManagerDistributionItem
{
ManagerId = managerId,
ManagerName = userDict.ContainsKey(managerId) ? userDict[managerId] : "",
Performance = performance,
Percentage = 0m // 稍后计算
});
}
// 计算占比
foreach (var item in distributionList)
{
item.Percentage = totalPerformance > 0 ? (item.Performance / totalPerformance * 100m) : 0m;
}
return new BusinessUnitDashboardManagerDistributionOutput
{
DistributionData = distributionList.OrderByDescending(x => x.Performance).ToList()
};
}
catch (Exception ex)
{
_logger.LogError(ex, "查询事业部驾驶舱总经理/经理业绩分布数据失败");
throw NCCException.Oh($"查询失败:{ex.Message}");
}
}
///
/// 获取事业部驾驶舱总经理/经理业绩趋势数据
///
///
/// 获取指定总经理/经理的近N个月业绩趋势数据
///
/// 示例请求:
/// ```json
/// {
/// "businessUnitId": "734725299018663173",
/// "statisticsMonth": "202512",
/// "managerIds": ["1649328471923847169"],
/// "monthCount": 12
/// }
/// ```
///
/// 查询参数
/// 事业部驾驶舱总经理/经理业绩趋势数据
[HttpPost("GetManagerTrend")]
public async Task GetManagerTrend([FromBody] BusinessUnitDashboardManagerTrendInput input)
{
try
{
if (input == null) throw NCCException.Oh("请求参数不能为空");
if (string.IsNullOrWhiteSpace(input.StatisticsMonth) || input.StatisticsMonth.Length != 6)
throw NCCException.Oh("统计月份格式错误,必须为YYYYMM格式");
if (input.ManagerIds == null || !input.ManagerIds.Any())
throw NCCException.Oh("总经理/经理ID列表不能为空");
var monthCount = input.MonthCount;
if (monthCount != 3 && monthCount != 6 && monthCount != 12)
monthCount = 12;
var baseMonth = DateTime.ParseExact(input.StatisticsMonth, "yyyyMM", null);
var result = new BusinessUnitDashboardManagerTrendOutput();
// 获取用户信息
var users = await _db.Queryable()
.Where(x => input.ManagerIds.Contains(x.Id) && x.DeleteMark == null)
.Select(x => new { x.Id, x.RealName })
.ToListAsync();
var userDict = users.ToDictionary(x => x.Id, x => x.RealName ?? "");
foreach (var managerId in input.ManagerIds)
{
var trendData = new BusinessUnitManagerTrendData
{
ManagerId = managerId,
ManagerName = userDict.ContainsKey(managerId) ? userDict[managerId] : ""
};
for (int i = monthCount - 1; i >= 0; i--)
{
var trendMonth = baseMonth.AddMonths(-i);
var trendMonthStr = trendMonth.ToString("yyyyMM");
// 获取该月份该总经理/经理管理的门店
var managerStores = await _db.Queryable()
.Where(x => x.GeneralManagerId == managerId && x.Month == trendMonthStr)
.Select(x => x.StoreId)
.Distinct()
.ToListAsync();
if (!managerStores.Any())
{
trendData.TrendPoints.Add(new BusinessUnitManagerTrendPoint { Month = trendMonthStr });
continue;
}
// 计算该月份的总业绩
var year = trendMonth.Year;
var month = trendMonth.Month;
var startDate = new DateTime(year, month, 1);
var endDate = startDate.AddMonths(1).AddDays(-1);
var endDateTime = trendMonthStr == DateTime.Now.ToString("yyyyMM")
? DateTime.Now
: endDate.Date.AddHours(23).AddMinutes(59).AddSeconds(59);
decimal totalBilling = 0m;
decimal totalRefund = 0m;
foreach (var storeId in managerStores)
{
var billing = await _db.Queryable()
.Where(x => x.Djmd == storeId && x.IsEffective == 1)
.Where(x => x.Kdrq.HasValue && x.Kdrq.Value >= startDate && x.Kdrq.Value <= endDateTime)
.SumAsync(x => (decimal?)x.Sfyj) ?? 0m;
totalBilling += billing;
var refund = await _db.Queryable()
.Where(x => x.Md == storeId && x.IsEffective == 1)
.Where(x => x.Tksj.HasValue && x.Tksj.Value.Date >= startDate.Date && x.Tksj.Value.Date <= endDate.Date)
.SumAsync(x => (decimal?)(x.ActualRefundAmount ?? x.Tkje ?? 0)) ?? 0m;
totalRefund += refund;
}
trendData.TrendPoints.Add(new BusinessUnitManagerTrendPoint
{
Month = trendMonthStr,
TotalPerformance = totalBilling - totalRefund
});
}
result.ManagerTrendData.Add(trendData);
}
return result;
}
catch (Exception ex)
{
_logger.LogError(ex, "查询事业部驾驶舱总经理/经理业绩趋势数据失败");
throw NCCException.Oh($"查询失败:{ex.Message}");
}
}
///
/// 获取事业部驾驶舱店长业绩排行
///
///
/// 获取指定事业部在指定月份的店长业绩排行数据
///
/// 示例请求:
/// ```json
/// {
/// "businessUnitId": "734725299018663173",
/// "statisticsMonth": "202512",
/// "topCount": 10
/// }
/// ```
///
/// 查询参数
/// 事业部驾驶舱店长业绩排行数据
[HttpPost("GetStoreManagerRanking")]
public async Task GetStoreManagerRanking([FromBody] BusinessUnitDashboardStoreManagerRankingInput input)
{
try
{
if (input == null) throw NCCException.Oh("请求参数不能为空");
if (string.IsNullOrWhiteSpace(input.StatisticsMonth) || input.StatisticsMonth.Length != 6)
throw NCCException.Oh("统计月份格式错误,必须为YYYYMM格式");
if (string.IsNullOrWhiteSpace(input.BusinessUnitId) && (input.StoreIds == null || !input.StoreIds.Any()))
throw NCCException.Oh("事业部ID和门店ID列表不能同时为空,必须传入其中一个");
// 解析月份获取时间范围
var year = int.Parse(input.StatisticsMonth.Substring(0, 4));
var month = int.Parse(input.StatisticsMonth.Substring(4, 2));
var startDate = new DateTime(year, month, 1);
var endDate = startDate.AddMonths(1).AddDays(-1);
var endDateTime = input.StatisticsMonth == DateTime.Now.ToString("yyyyMM")
? DateTime.Now
: endDate.Date.AddHours(23).AddMinutes(59).AddSeconds(59);
// 获取门店列表
var storeIds = await GetStoreIdsAsync(input.BusinessUnitId, input.StoreIds, input.StatisticsMonth);
if (!storeIds.Any())
{
return new BusinessUnitDashboardStoreManagerRankingOutput();
}
// 获取店长信息(BASE_USER表,F_GW = '店长')
var storeManagers = await _db.Queryable()
.Where(x => x.Gw == "店长" && storeIds.Contains(x.Mdid) && x.DeleteMark == null && x.EnabledMark == 1)
.Select(x => new { x.Id, x.RealName, x.Mdid })
.ToListAsync();
if (!storeManagers.Any())
{
return new BusinessUnitDashboardStoreManagerRankingOutput();
}
// 获取门店信息
var stores = await _db.Queryable()
.Where(x => storeIds.Contains(x.Id))
.Select(x => new { x.Id, x.Dm })
.ToListAsync();
var storeDict = stores.ToDictionary(x => x.Id, x => x.Dm ?? "");
// 计算每个店长的业绩
var rankingData = new List();
foreach (var manager in storeManagers)
{
var storeId = manager.Mdid;
if (string.IsNullOrEmpty(storeId)) continue;
// 开单业绩
var billingAmount = await _db.Queryable()
.Where(x => x.Djmd == storeId && x.IsEffective == 1)
.Where(x => x.Kdrq.HasValue && x.Kdrq.Value >= startDate && x.Kdrq.Value <= endDateTime)
.SumAsync(x => (decimal?)x.Sfyj) ?? 0m;
// 退卡金额
var refundAmount = await _db.Queryable()
.Where(x => x.Md == storeId && x.IsEffective == 1)
.Where(x => x.Tksj.HasValue && x.Tksj.Value.Date >= startDate.Date && x.Tksj.Value.Date <= endDate.Date)
.SumAsync(x => (decimal?)(x.ActualRefundAmount ?? x.Tkje ?? 0)) ?? 0m;
// 净业绩
var netPerformance = billingAmount - refundAmount;
// 消耗业绩
var consumeAmount = await _db.Queryable()
.Where(x => x.Md == storeId && x.IsEffective == 1)
.Where(x => x.Hksj.HasValue && x.Hksj.Value >= startDate && x.Hksj.Value <= endDateTime)
.SumAsync(x => (decimal?)x.Xfje) ?? 0m;
// 目标业绩
var targetPerformance = await _db.Queryable()
.Where(x => x.StoreId == storeId && x.Month == input.StatisticsMonth)
.SumAsync(x => (decimal?)x.BusinessUnitTarget) ?? 0m;
// 完成率
var completionRate = targetPerformance > 0 ? (netPerformance / targetPerformance * 100m) : 0m;
rankingData.Add(new BusinessUnitStoreManagerRankingItem
{
StoreManagerId = manager.Id,
StoreManagerName = manager.RealName ?? "",
StoreId = storeId,
StoreName = storeDict.ContainsKey(storeId) ? storeDict[storeId] : "",
BillingPerformance = billingAmount,
ConsumePerformance = consumeAmount,
RefundAmount = refundAmount,
NetPerformance = netPerformance,
CompletionRate = completionRate,
Ranking = 0 // 稍后设置
});
}
// 按净业绩排序
rankingData = rankingData.OrderByDescending(x => x.NetPerformance).ToList();
// 设置排名
var topCount = input.TopCount > 0 ? input.TopCount : 10;
var result = new BusinessUnitDashboardStoreManagerRankingOutput();
for (int i = 0; i < Math.Min(topCount, rankingData.Count); i++)
{
rankingData[i].Ranking = i + 1;
result.RankingData.Add(rankingData[i]);
}
return result;
}
catch (Exception ex)
{
_logger.LogError(ex, "查询事业部驾驶舱店长业绩排行数据失败");
throw NCCException.Oh($"查询失败:{ex.Message}");
}
}
///
/// 获取事业部驾驶舱健康师业绩排行
///
///
/// 获取指定事业部在指定月份的健康师业绩排行数据
///
/// 示例请求:
/// ```json
/// {
/// "businessUnitId": "734725299018663173",
/// "statisticsMonth": "202512",
/// "topCount": 10
/// }
/// ```
///
/// 查询参数
/// 事业部驾驶舱健康师业绩排行数据
[HttpPost("GetHealthCoachRanking")]
public async Task GetHealthCoachRanking([FromBody] BusinessUnitDashboardHealthCoachRankingInput input)
{
try
{
if (input == null) throw NCCException.Oh("请求参数不能为空");
if (string.IsNullOrWhiteSpace(input.StatisticsMonth) || input.StatisticsMonth.Length != 6)
throw NCCException.Oh("统计月份格式错误,必须为YYYYMM格式");
if (string.IsNullOrWhiteSpace(input.BusinessUnitId) && (input.StoreIds == null || !input.StoreIds.Any()))
throw NCCException.Oh("事业部ID和门店ID列表不能同时为空,必须传入其中一个");
// 解析月份获取时间范围
var year = int.Parse(input.StatisticsMonth.Substring(0, 4));
var month = int.Parse(input.StatisticsMonth.Substring(4, 2));
var startDate = new DateTime(year, month, 1);
var endDate = startDate.AddMonths(1).AddDays(-1);
var endDateTime = input.StatisticsMonth == DateTime.Now.ToString("yyyyMM")
? DateTime.Now
: endDate.Date.AddHours(23).AddMinutes(59).AddSeconds(59);
// 获取门店列表
var storeIds = await GetStoreIdsAsync(input.BusinessUnitId, input.StoreIds, input.StatisticsMonth);
if (!storeIds.Any())
{
return new BusinessUnitDashboardHealthCoachRankingOutput();
}
// 获取健康师信息(BASE_USER表,F_GW = '健康师')
var healthCoaches = await _db.Queryable()
.Where(x => x.Gw == "健康师" && storeIds.Contains(x.Mdid) && x.DeleteMark == null && x.EnabledMark == 1)
.Select(x => new { x.Id, x.RealName, x.Mdid })
.ToListAsync();
if (!healthCoaches.Any())
{
return new BusinessUnitDashboardHealthCoachRankingOutput();
}
// 获取门店信息
var stores = await _db.Queryable()
.Where(x => storeIds.Contains(x.Id))
.Select(x => new { x.Id, x.Dm })
.ToListAsync();
var storeDict = stores.ToDictionary(x => x.Id, x => x.Dm ?? "");
// 计算每个健康师的业绩
var rankingData = new List();
var storeIdsStr = string.Join("','", storeIds);
foreach (var coach in healthCoaches)
{
var storeId = coach.Mdid;
if (string.IsNullOrEmpty(storeId)) continue;
// 开单业绩(从lq_kd_jksyj表,jksyj字段是字符串类型,需要转换,使用jkszh字段匹配健康师ID)
var billingSql = $@"
SELECT COALESCE(SUM(CAST(jksyj AS DECIMAL(18,2))), 0) as Amount
FROM lq_kd_jksyj
WHERE F_IsEffective = 1
AND F_StoreId = '{storeId}'
AND jkszh = '{coach.Id}'
AND yjsj >= '{startDate:yyyy-MM-dd HH:mm:ss}'
AND yjsj <= '{endDateTime:yyyy-MM-dd HH:mm:ss}'";
var billingResult = await _db.Ado.SqlQueryAsync(billingSql);
var billingPerformance = billingResult?.FirstOrDefault() != null
? Convert.ToDecimal(billingResult.FirstOrDefault().Amount ?? 0)
: 0m;
// 消耗业绩(从lq_xh_jksyj表,使用jkszh字段匹配健康师ID)
var consumePerformance = await _db.Queryable()
.Where(x => x.StoreId == storeId && x.Jkszh == coach.Id && x.IsEffective == 1)
.Where(x => x.Yjsj.HasValue && x.Yjsj.Value >= startDate && x.Yjsj.Value <= endDateTime)
.SumAsync(x => (decimal?)(x.Jksyj ?? 0)) ?? 0m;
// 退卡业绩(从lq_hytk_jksyj表,使用jkszh字段匹配健康师ID)
var refundPerformance = await _db.Queryable()
.Where(x => x.StoreId == storeId && x.Jkszh == coach.Id && x.IsEffective == 1)
.Where(x => x.Tksj.HasValue && x.Tksj.Value.Date >= startDate.Date && x.Tksj.Value.Date <= endDate.Date)
.SumAsync(x => (decimal?)(x.Jksyj ?? 0)) ?? 0m;
// 总业绩
var totalPerformance = billingPerformance - refundPerformance;
// 项目数(从lq_xh_pxmx表,关联lq_xh_hyhk,再关联lq_xh_jksyj)
var projectCountSql = $@"
SELECT COALESCE(SUM(COALESCE(px.F_OriginalProjectNumber, px.F_ProjectNumber, 0)), 0) as ProjectCount
FROM lq_xh_pxmx px
INNER JOIN lq_xh_hyhk xh ON px.F_ConsumeInfoId = xh.F_Id
INNER JOIN lq_xh_jksyj jksyj ON jksyj.glkdbh = xh.F_Id
WHERE xh.Md = '{storeId}'
AND jksyj.jkszh = '{coach.Id}'
AND xh.F_IsEffective = 1
AND jksyj.F_IsEffective = 1
AND px.F_IsEffective = 1
AND xh.Hksj >= '{startDate:yyyy-MM-dd HH:mm:ss}'
AND xh.Hksj <= '{endDateTime:yyyy-MM-dd HH:mm:ss}'";
var projectCountResult = await _db.Ado.SqlQueryAsync(projectCountSql);
var projectCount = projectCountResult?.FirstOrDefault() != null
? Convert.ToDecimal(projectCountResult.FirstOrDefault().ProjectCount ?? 0)
: 0m;
rankingData.Add(new BusinessUnitHealthCoachRankingItem
{
HealthCoachId = coach.Id,
HealthCoachName = coach.RealName ?? "",
StoreId = storeId,
StoreName = storeDict.ContainsKey(storeId) ? storeDict[storeId] : "",
BillingPerformance = billingPerformance,
ConsumePerformance = consumePerformance,
RefundPerformance = refundPerformance,
TotalPerformance = totalPerformance,
ProjectCount = projectCount,
Ranking = 0 // 稍后设置
});
}
// 按总业绩排序
rankingData = rankingData.OrderByDescending(x => x.TotalPerformance).ToList();
// 设置排名
var topCount = input.TopCount > 0 ? input.TopCount : 10;
var result = new BusinessUnitDashboardHealthCoachRankingOutput();
for (int i = 0; i < Math.Min(topCount, rankingData.Count); i++)
{
rankingData[i].Ranking = i + 1;
result.RankingData.Add(rankingData[i]);
}
return result;
}
catch (Exception ex)
{
_logger.LogError(ex, "查询事业部驾驶舱健康师业绩排行数据失败");
throw NCCException.Oh($"查询失败:{ex.Message}");
}
}
///
/// 获取事业部驾驶舱总经理/经理明细列表
///
///
/// 获取指定事业部在指定月份的总经理/经理明细列表,支持分页、排序、筛选
///
/// 查询参数
/// 事业部驾驶舱总经理/经理明细列表
[HttpPost("GetManagerDetailList")]
public async Task GetManagerDetailList([FromBody] BusinessUnitDashboardManagerDetailListInput input)
{
try
{
if (input == null) throw NCCException.Oh("请求参数不能为空");
if (string.IsNullOrWhiteSpace(input.StatisticsMonth) || input.StatisticsMonth.Length != 6)
throw NCCException.Oh("统计月份格式错误,必须为YYYYMM格式");
if (string.IsNullOrWhiteSpace(input.BusinessUnitId) && (input.StoreIds == null || !input.StoreIds.Any()))
throw NCCException.Oh("事业部ID和门店ID列表不能同时为空,必须传入其中一个");
// 解析月份获取时间范围
var year = int.Parse(input.StatisticsMonth.Substring(0, 4));
var month = int.Parse(input.StatisticsMonth.Substring(4, 2));
var startDate = new DateTime(year, month, 1);
var endDate = startDate.AddMonths(1).AddDays(-1);
var endDateTime = input.StatisticsMonth == DateTime.Now.ToString("yyyyMM")
? DateTime.Now
: endDate.Date.AddHours(23).AddMinutes(59).AddSeconds(59);
// 获取门店列表
var storeIds = await GetStoreIdsAsync(input.BusinessUnitId, input.StoreIds, input.StatisticsMonth);
if (!storeIds.Any())
{
return new { total = 0, list = new List() };
}
// 获取总经理/经理归属信息
var lifelineList = await _db.Queryable()
.Where(x => x.Month == input.StatisticsMonth && storeIds.Contains(x.StoreId))
.ToListAsync();
if (!lifelineList.Any())
{
return new { total = 0, list = new List() };
}
var allManagerIds = lifelineList
.Where(x => !string.IsNullOrEmpty(x.GeneralManagerId))
.Select(x => x.GeneralManagerId)
.Distinct()
.ToList();
// 获取用户信息,支持姓名筛选
var userQuery = _db.Queryable()
.Where(x => allManagerIds.Contains(x.Id) && x.DeleteMark == null);
if (!string.IsNullOrWhiteSpace(input.ManagerName))
{
userQuery = userQuery.Where(x => x.RealName.Contains(input.ManagerName));
}
var users = await userQuery.Select(x => new { x.Id, x.RealName }).ToListAsync();
var filteredManagerIds = users.Select(x => x.Id).ToList();
if (!filteredManagerIds.Any())
{
return new { total = 0, list = new List() };
}
var userDict = users.ToDictionary(x => x.Id, x => x.RealName ?? "");
// 获取门店信息
var stores = await _db.Queryable()
.Where(x => storeIds.Contains(x.Id))
.Select(x => new { x.Id, x.Dm })
.ToListAsync();
var storeDict = stores.ToDictionary(x => x.Id, x => x.Dm ?? "");
// 获取工资统计数据
var salaryStats = await _db.Queryable()
.Where(x => x.StatisticsMonth == input.StatisticsMonth && filteredManagerIds.Contains(x.EmployeeId))
.ToListAsync();
var salaryDict = salaryStats.ToDictionary(x => x.EmployeeId, x => x);
// 按总经理/经理ID分组
var managerStoreDict = lifelineList
.Where(x => filteredManagerIds.Contains(x.GeneralManagerId) && !string.IsNullOrEmpty(x.StoreId))
.GroupBy(x => x.GeneralManagerId)
.ToDictionary(g => g.Key, g => g.Select(x => x.StoreId).Distinct().ToList());
// 计算每个总经理/经理的业绩数据
var detailList = new List();
foreach (var managerId in filteredManagerIds)
{
var managerStores = managerStoreDict.ContainsKey(managerId) ? managerStoreDict[managerId] : new List();
if (!managerStores.Any()) continue;
decimal totalBilling = 0m;
decimal totalRefund = 0m;
decimal totalConsume = 0m;
foreach (var storeId in managerStores)
{
var billing = await _db.Queryable()
.Where(x => x.Djmd == storeId && x.IsEffective == 1)
.Where(x => x.Kdrq.HasValue && x.Kdrq.Value >= startDate && x.Kdrq.Value <= endDateTime)
.SumAsync(x => (decimal?)x.Sfyj) ?? 0m;
totalBilling += billing;
var refund = await _db.Queryable()
.Where(x => x.Md == storeId && x.IsEffective == 1)
.Where(x => x.Tksj.HasValue && x.Tksj.Value.Date >= startDate.Date && x.Tksj.Value.Date <= endDate.Date)
.SumAsync(x => (decimal?)(x.ActualRefundAmount ?? x.Tkje ?? 0)) ?? 0m;
totalRefund += refund;
var consume = await _db.Queryable()
.Where(x => x.Md == storeId && x.IsEffective == 1)
.Where(x => x.Hksj.HasValue && x.Hksj.Value >= startDate && x.Hksj.Value <= endDateTime)
.SumAsync(x => (decimal?)x.Xfje) ?? 0m;
totalConsume += consume;
}
var salary = salaryDict.ContainsKey(managerId) ? salaryDict[managerId] : null;
var managerLifeline = lifelineList.FirstOrDefault(x => x.GeneralManagerId == managerId);
var storeNames = managerStores
.Where(s => storeDict.ContainsKey(s))
.Select(s => storeDict[s])
.ToList();
detailList.Add(new BusinessUnitDashboardManagerDetailListOutput
{
ManagerId = managerId,
ManagerName = userDict.ContainsKey(managerId) ? userDict[managerId] : "",
ManagerType = managerLifeline?.ManagerType ?? 1,
Position = managerLifeline?.ManagerType == 1 ? "总经理" : "经理",
ManagedStoreCount = managerStores.Count,
ManagedStoreNames = storeNames,
TotalBillingPerformance = totalBilling,
TotalConsumePerformance = totalConsume,
TotalGrossProfit = salary?.GrossProfit ?? 0m,
BaseSalary = salary?.BaseSalary ?? 0m,
Commission = salary?.TotalCommission ?? 0m,
TotalSalary = salary?.FinalGrossSalary ?? 0m
});
}
// 分页处理
var currentPage = input.currentPage > 0 ? input.currentPage : 1;
var pageSize = input.pageSize > 0 ? input.pageSize : 10;
var total = detailList.Count;
var pagedList = detailList.Skip((currentPage - 1) * pageSize).Take(pageSize).ToList();
return new { total = total, list = pagedList };
}
catch (Exception ex)
{
_logger.LogError(ex, "查询事业部驾驶舱总经理/经理明细列表失败");
throw NCCException.Oh($"查询失败:{ex.Message}");
}
}
///
/// 获取事业部驾驶舱店长明细列表
///
///
/// 获取指定事业部在指定月份的店长明细列表,支持分页、排序、筛选
///
/// 查询参数
/// 事业部驾驶舱店长明细列表
[HttpPost("GetStoreManagerDetailList")]
public async Task GetStoreManagerDetailList([FromBody] BusinessUnitDashboardStoreManagerDetailListInput input)
{
try
{
if (input == null) throw NCCException.Oh("请求参数不能为空");
if (string.IsNullOrWhiteSpace(input.StatisticsMonth) || input.StatisticsMonth.Length != 6)
throw NCCException.Oh("统计月份格式错误,必须为YYYYMM格式");
if (string.IsNullOrWhiteSpace(input.BusinessUnitId) && (input.StoreIds == null || !input.StoreIds.Any()))
throw NCCException.Oh("事业部ID和门店ID列表不能同时为空,必须传入其中一个");
// 解析月份获取时间范围
var year = int.Parse(input.StatisticsMonth.Substring(0, 4));
var month = int.Parse(input.StatisticsMonth.Substring(4, 2));
var startDate = new DateTime(year, month, 1);
var endDate = startDate.AddMonths(1).AddDays(-1);
var endDateTime = input.StatisticsMonth == DateTime.Now.ToString("yyyyMM")
? DateTime.Now
: endDate.Date.AddHours(23).AddMinutes(59).AddSeconds(59);
// 获取门店列表
var storeIds = await GetStoreIdsAsync(input.BusinessUnitId, input.StoreIds, input.StatisticsMonth);
if (!storeIds.Any())
{
return new { total = 0, list = new List() };
}
// 获取门店信息,支持门店名称筛选
var storeQuery = _db.Queryable()
.Where(x => storeIds.Contains(x.Id));
if (!string.IsNullOrWhiteSpace(input.StoreName))
{
storeQuery = storeQuery.Where(x => x.Dm.Contains(input.StoreName));
}
var stores = await storeQuery.Select(x => new { x.Id, x.Dm }).ToListAsync();
var filteredStoreIds = stores.Select(x => x.Id).ToList();
if (!filteredStoreIds.Any())
{
return new { total = 0, list = new List() };
}
var storeDict = stores.ToDictionary(x => x.Id, x => x.Dm ?? "");
// 获取店长信息,支持店长姓名筛选
var managerQuery = _db.Queryable()
.Where(x => x.Gw == "店长" && filteredStoreIds.Contains(x.Mdid) && x.DeleteMark == null && x.EnabledMark == 1);
if (!string.IsNullOrWhiteSpace(input.StoreManagerName))
{
managerQuery = managerQuery.Where(x => x.RealName.Contains(input.StoreManagerName));
}
var storeManagers = await managerQuery.Select(x => new { x.Id, x.RealName, x.Mdid }).ToListAsync();
if (!storeManagers.Any())
{
return new { total = 0, list = new List() };
}
// 计算每个店长的业绩数据
var detailList = new List();
foreach (var manager in storeManagers)
{
var storeId = manager.Mdid;
if (string.IsNullOrEmpty(storeId)) continue;
// 开单业绩
var billingAmount = await _db.Queryable()
.Where(x => x.Djmd == storeId && x.IsEffective == 1)
.Where(x => x.Kdrq.HasValue && x.Kdrq.Value >= startDate && x.Kdrq.Value <= endDateTime)
.SumAsync(x => (decimal?)x.Sfyj) ?? 0m;
// 退卡金额
var refundAmount = await _db.Queryable()
.Where(x => x.Md == storeId && x.IsEffective == 1)
.Where(x => x.Tksj.HasValue && x.Tksj.Value.Date >= startDate.Date && x.Tksj.Value.Date <= endDate.Date)
.SumAsync(x => (decimal?)(x.ActualRefundAmount ?? x.Tkje ?? 0)) ?? 0m;
// 净业绩
var netPerformance = billingAmount - refundAmount;
// 消耗业绩
var consumeAmount = await _db.Queryable()
.Where(x => x.Md == storeId && x.IsEffective == 1)
.Where(x => x.Hksj.HasValue && x.Hksj.Value >= startDate && x.Hksj.Value <= endDateTime)
.SumAsync(x => (decimal?)x.Xfje) ?? 0m;
// 目标业绩
var targetPerformance = await _db.Queryable()
.Where(x => x.StoreId == storeId && x.Month == input.StatisticsMonth)
.SumAsync(x => (decimal?)x.BusinessUnitTarget) ?? 0m;
// 完成率
var completionRate = targetPerformance > 0 ? (netPerformance / targetPerformance * 100m) : 0m;
detailList.Add(new BusinessUnitDashboardStoreManagerDetailListOutput
{
StoreManagerId = manager.Id,
StoreManagerName = manager.RealName ?? "",
StoreId = storeId,
StoreName = storeDict.ContainsKey(storeId) ? storeDict[storeId] : "",
BillingPerformance = billingAmount,
ConsumePerformance = consumeAmount,
RefundAmount = refundAmount,
NetPerformance = netPerformance,
TargetPerformance = targetPerformance,
CompletionRate = completionRate
});
}
// 分页处理
var currentPage = input.currentPage > 0 ? input.currentPage : 1;
var pageSize = input.pageSize > 0 ? input.pageSize : 10;
var total = detailList.Count;
var pagedList = detailList.Skip((currentPage - 1) * pageSize).Take(pageSize).ToList();
return new { total = total, list = pagedList };
}
catch (Exception ex)
{
_logger.LogError(ex, "查询事业部驾驶舱店长明细列表失败");
throw NCCException.Oh($"查询失败:{ex.Message}");
}
}
///
/// 获取事业部驾驶舱健康师明细列表
///
///
/// 获取指定事业部在指定月份的健康师明细列表,支持分页、排序、筛选
///
/// 查询参数
/// 事业部驾驶舱健康师明细列表
[HttpPost("GetHealthCoachDetailList")]
public async Task GetHealthCoachDetailList([FromBody] BusinessUnitDashboardHealthCoachDetailListInput input)
{
try
{
if (input == null) throw NCCException.Oh("请求参数不能为空");
if (string.IsNullOrWhiteSpace(input.StatisticsMonth) || input.StatisticsMonth.Length != 6)
throw NCCException.Oh("统计月份格式错误,必须为YYYYMM格式");
if (string.IsNullOrWhiteSpace(input.BusinessUnitId) && (input.StoreIds == null || !input.StoreIds.Any()))
throw NCCException.Oh("事业部ID和门店ID列表不能同时为空,必须传入其中一个");
// 解析月份获取时间范围
var year = int.Parse(input.StatisticsMonth.Substring(0, 4));
var month = int.Parse(input.StatisticsMonth.Substring(4, 2));
var startDate = new DateTime(year, month, 1);
var endDate = startDate.AddMonths(1).AddDays(-1);
var endDateTime = input.StatisticsMonth == DateTime.Now.ToString("yyyyMM")
? DateTime.Now
: endDate.Date.AddHours(23).AddMinutes(59).AddSeconds(59);
// 获取门店列表
var storeIds = await GetStoreIdsAsync(input.BusinessUnitId, input.StoreIds, input.StatisticsMonth);
if (!storeIds.Any())
{
return new { total = 0, list = new List() };
}
// 获取门店信息,支持门店名称筛选
var storeQuery = _db.Queryable()
.Where(x => storeIds.Contains(x.Id));
if (!string.IsNullOrWhiteSpace(input.StoreName))
{
storeQuery = storeQuery.Where(x => x.Dm.Contains(input.StoreName));
}
var stores = await storeQuery.Select(x => new { x.Id, x.Dm }).ToListAsync();
var filteredStoreIds = stores.Select(x => x.Id).ToList();
if (!filteredStoreIds.Any())
{
return new { total = 0, list = new List() };
}
var storeDict = stores.ToDictionary(x => x.Id, x => x.Dm ?? "");
// 获取健康师信息,支持健康师姓名筛选
var coachQuery = _db.Queryable()
.Where(x => x.Gw == "健康师" && filteredStoreIds.Contains(x.Mdid) && x.DeleteMark == null && x.EnabledMark == 1);
if (!string.IsNullOrWhiteSpace(input.HealthCoachName))
{
coachQuery = coachQuery.Where(x => x.RealName.Contains(input.HealthCoachName));
}
var healthCoaches = await coachQuery.Select(x => new { x.Id, x.RealName, x.Mdid }).ToListAsync();
if (!healthCoaches.Any())
{
return new { total = 0, list = new List() };
}
// 计算每个健康师的业绩数据
var detailList = new List();
var storeIdsStr = string.Join("','", filteredStoreIds);
foreach (var coach in healthCoaches)
{
var storeId = coach.Mdid;
if (string.IsNullOrEmpty(storeId)) continue;
// 开单业绩(从lq_kd_jksyj表,jksyj字段是字符串类型,需要转换,使用jkszh字段匹配健康师ID)
var billingSql = $@"
SELECT COALESCE(SUM(CAST(jksyj AS DECIMAL(18,2))), 0) as Amount
FROM lq_kd_jksyj
WHERE F_IsEffective = 1
AND F_StoreId = '{storeId}'
AND jkszh = '{coach.Id}'
AND yjsj >= '{startDate:yyyy-MM-dd HH:mm:ss}'
AND yjsj <= '{endDateTime:yyyy-MM-dd HH:mm:ss}'";
var billingResult = await _db.Ado.SqlQueryAsync(billingSql);
var billingPerformance = billingResult?.FirstOrDefault() != null
? Convert.ToDecimal(billingResult.FirstOrDefault().Amount ?? 0)
: 0m;
// 消耗业绩(从lq_xh_jksyj表,使用jkszh字段匹配健康师ID)
var consumePerformance = await _db.Queryable()
.Where(x => x.StoreId == storeId && x.Jkszh == coach.Id && x.IsEffective == 1)
.Where(x => x.Yjsj.HasValue && x.Yjsj.Value >= startDate && x.Yjsj.Value <= endDateTime)
.SumAsync(x => (decimal?)(x.Jksyj ?? 0)) ?? 0m;
// 退卡业绩(从lq_hytk_jksyj表,使用jkszh字段匹配健康师ID)
var refundPerformance = await _db.Queryable()
.Where(x => x.StoreId == storeId && x.Jkszh == coach.Id && x.IsEffective == 1)
.Where(x => x.Tksj.HasValue && x.Tksj.Value.Date >= startDate.Date && x.Tksj.Value.Date <= endDate.Date)
.SumAsync(x => (decimal?)(x.Jksyj ?? 0)) ?? 0m;
// 总业绩
var totalPerformance = billingPerformance - refundPerformance;
// 项目数(从lq_xh_pxmx表,关联lq_xh_hyhk,再关联lq_xh_jksyj)
var projectCountSql = $@"
SELECT COALESCE(SUM(COALESCE(px.F_OriginalProjectNumber, px.F_ProjectNumber, 0)), 0) as ProjectCount
FROM lq_xh_pxmx px
INNER JOIN lq_xh_hyhk xh ON px.F_ConsumeInfoId = xh.F_Id
INNER JOIN lq_xh_jksyj jksyj ON jksyj.glkdbh = xh.F_Id
WHERE xh.Md = '{storeId}'
AND jksyj.jkszh = '{coach.Id}'
AND xh.F_IsEffective = 1
AND jksyj.F_IsEffective = 1
AND px.F_IsEffective = 1
AND xh.Hksj >= '{startDate:yyyy-MM-dd HH:mm:ss}'
AND xh.Hksj <= '{endDateTime:yyyy-MM-dd HH:mm:ss}'";
var projectCountResult = await _db.Ado.SqlQueryAsync(projectCountSql);
var projectCount = projectCountResult?.FirstOrDefault() != null
? Convert.ToDecimal(projectCountResult.FirstOrDefault().ProjectCount ?? 0)
: 0m;
detailList.Add(new BusinessUnitDashboardHealthCoachDetailListOutput
{
HealthCoachId = coach.Id,
HealthCoachName = coach.RealName ?? "",
StoreId = storeId,
StoreName = storeDict.ContainsKey(storeId) ? storeDict[storeId] : "",
BillingPerformance = billingPerformance,
ConsumePerformance = consumePerformance,
RefundPerformance = refundPerformance,
TotalPerformance = totalPerformance,
ProjectCount = projectCount
});
}
// 分页处理
var currentPage = input.currentPage > 0 ? input.currentPage : 1;
var pageSize = input.pageSize > 0 ? input.pageSize : 10;
var total = detailList.Count;
var pagedList = detailList.Skip((currentPage - 1) * pageSize).Take(pageSize).ToList();
return new { total = total, list = pagedList };
}
catch (Exception ex)
{
_logger.LogError(ex, "查询事业部驾驶舱健康师明细列表失败");
throw NCCException.Oh($"查询失败:{ex.Message}");
}
}
}
}