diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqPackageInfo/ActivityStatisticsByItemOutput.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqPackageInfo/ActivityStatisticsByItemOutput.cs new file mode 100644 index 0000000..5d5d29c --- /dev/null +++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqPackageInfo/ActivityStatisticsByItemOutput.cs @@ -0,0 +1,62 @@ +using System.Collections.Generic; + +namespace NCC.Extend.Entitys.Dto.LqPackageInfo +{ + /// + /// 营销活动按品项统计输出 + /// + public class ActivityStatisticsByItemOutput + { + /// + /// 营销活动ID + /// + public string ActivityId { get; set; } + + /// + /// 营销活动名称 + /// + public string ActivityName { get; set; } + + /// + /// 品项统计列表 + /// + public List ItemList { get; set; } + } + + /// + /// 品项统计项 + /// + public class ItemStatisticsItem + { + /// + /// 品项ID + /// + public string ItemId { get; set; } + + /// + /// 品项名称 + /// + public string ItemName { get; set; } + + /// + /// 销售数量(项目次数总和) + /// + public decimal SalesQuantity { get; set; } + + /// + /// 销售金额 + /// + public decimal SalesAmount { get; set; } + + /// + /// 开单数量(包含该品项的开单数) + /// + public int BillingCount { get; set; } + + /// + /// 销售次数(品项明细记录数) + /// + public int SalesCount { get; set; } + } +} + diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqPackageInfo/ActivityStatisticsByStoreItemOutput.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqPackageInfo/ActivityStatisticsByStoreItemOutput.cs new file mode 100644 index 0000000..3f75828 --- /dev/null +++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqPackageInfo/ActivityStatisticsByStoreItemOutput.cs @@ -0,0 +1,72 @@ +using System.Collections.Generic; + +namespace NCC.Extend.Entitys.Dto.LqPackageInfo +{ + /// + /// 营销活动按门店品项统计输出 + /// + public class ActivityStatisticsByStoreItemOutput + { + /// + /// 营销活动ID + /// + public string ActivityId { get; set; } + + /// + /// 营销活动名称 + /// + public string ActivityName { get; set; } + + /// + /// 门店品项统计列表 + /// + public List StoreItemList { get; set; } + } + + /// + /// 门店品项统计项 + /// + public class StoreItemStatisticsItem + { + /// + /// 门店ID + /// + public string StoreId { get; set; } + + /// + /// 门店名称 + /// + public string StoreName { get; set; } + + /// + /// 品项ID + /// + public string ItemId { get; set; } + + /// + /// 品项名称 + /// + public string ItemName { get; set; } + + /// + /// 销售数量(项目次数总和) + /// + public decimal SalesQuantity { get; set; } + + /// + /// 销售金额 + /// + public decimal SalesAmount { get; set; } + + /// + /// 开单数量(包含该品项的开单数) + /// + public int BillingCount { get; set; } + + /// + /// 销售次数(品项明细记录数) + /// + public int SalesCount { get; set; } + } +} + diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqPackageInfo/ActivityStatisticsByStoreOutput.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqPackageInfo/ActivityStatisticsByStoreOutput.cs new file mode 100644 index 0000000..6909027 --- /dev/null +++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqPackageInfo/ActivityStatisticsByStoreOutput.cs @@ -0,0 +1,67 @@ +using System.Collections.Generic; + +namespace NCC.Extend.Entitys.Dto.LqPackageInfo +{ + /// + /// 营销活动按门店统计输出 + /// + public class ActivityStatisticsByStoreOutput + { + /// + /// 营销活动ID + /// + public string ActivityId { get; set; } + + /// + /// 营销活动名称 + /// + public string ActivityName { get; set; } + + /// + /// 门店统计列表 + /// + public List StoreList { get; set; } + } + + /// + /// 门店统计项 + /// + public class StoreStatisticsItem + { + /// + /// 门店ID + /// + public string StoreId { get; set; } + + /// + /// 门店名称 + /// + public string StoreName { get; set; } + + /// + /// 开单数量 + /// + public int BillingCount { get; set; } + + /// + /// 开单金额 + /// + public decimal BillingAmount { get; set; } + + /// + /// 欠款金额 + /// + public decimal DebtAmount { get; set; } + + /// + /// 人头数(去重客户数) + /// + public int CustomerCount { get; set; } + + /// + /// 项目数(品项项目次数总和) + /// + public decimal ItemCount { get; set; } + } +} + diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqYaoyjl/LqYaoyjlListQueryInput.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqYaoyjl/LqYaoyjlListQueryInput.cs index 36188b4..475c1f2 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqYaoyjl/LqYaoyjlListQueryInput.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqYaoyjl/LqYaoyjlListQueryInput.cs @@ -17,47 +17,50 @@ namespace NCC.Extend.Entitys.Dto.LqYaoyjl /// /// public int dataType { get; set; } - - /// /// 邀约编号 /// public string id { get; set; } - + /// /// 邀约人 /// public string yyr { get; set; } - + /// /// 邀约时间 /// public string yysj { get; set; } - + /// /// 邀约客户 /// public string yykh { get; set; } - + /// /// 邀约客户姓名 /// public string yykhxm { get; set; } - + /// /// 电话是否有效 /// public string dhsfyx { get; set; } - + /// /// 联系时间 /// public string lxsj { get; set; } - + /// /// 联系记录 /// public string lxjl { get; set; } - + + /// + /// 门店ID + /// + public string storeId { get; set; } + } } diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqDailyReportService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqDailyReportService.cs index af0c333..fa274d2 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend/LqDailyReportService.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend/LqDailyReportService.cs @@ -1095,15 +1095,27 @@ namespace NCC.Extend /// /// 返回格式: /// ``` - /// 事业一部业绩:xxxx - /// 事业一部总单量:xxx - /// 第一单:xxx店、xxx健康师,金额xxx - /// 第二单:xxx店、xxx健康师,金额xxx + /// 11月8日 /// - /// 事业二部业绩:xxxx - /// 事业二部总单量:xxx - /// 第一单:xxx店、xxx健康师,金额xxx - /// 第二单:xxx店、xxx健康师,金额xxx + /// 今日总业绩:32317 + /// + /// 今日总单量:18 + /// + /// 绿纤人用结果捍卫尊严 + /// + /// 业绩捷报 + /// + /// ▃▃▃▃▃▃▃▃▃▃ + /// + /// 事业1部总业绩:4722 + /// + /// 事业1部总单量:4 + /// + /// 第1单:红光店马丽亚1000 + /// + /// 第2单:468店陈小琴、苟小春500 + /// + /// ... /// ``` /// /// 查询参数 @@ -1125,23 +1137,80 @@ namespace NCC.Extend return $"日期 {input.Date} 暂无开单数据"; } - // 2. 格式化为中文文本 + // 2. 解析日期 + if (!DateTime.TryParse(input.Date, out var targetDate)) + { + throw NCCException.Oh("日期格式错误,请使用 yyyy-MM-dd 格式"); + } + + // 3. 计算总业绩和总单量 + var totalPerformance = statistics.Sum(x => x.TotalPerformance); + var totalOrderCount = statistics.Sum(x => x.TotalOrderCount); + + // 4. 格式化为中文文本 var textBuilder = new StringBuilder(); + // 日期(格式:11月8日) + textBuilder.AppendLine($"{targetDate.Month}月{targetDate.Day}日"); + textBuilder.AppendLine(); + + // 今日总业绩和总单量 + textBuilder.AppendLine($"今日总业绩:{totalPerformance:F0}"); + textBuilder.AppendLine(); + textBuilder.AppendLine($"今日总单量:{totalOrderCount}"); + textBuilder.AppendLine(); + + // 标语 + textBuilder.AppendLine("绿纤人用结果捍卫尊严 "); + textBuilder.AppendLine(); + textBuilder.AppendLine("业绩捷报 "); + textBuilder.AppendLine(); + + // 每个事业部的数据 foreach (var unit in statistics) { - textBuilder.AppendLine($"{unit.BusinessUnitName}业绩:{unit.TotalPerformance:F2}"); + // 分隔线 + textBuilder.AppendLine("▃▃▃▃▃▃▃▃▃▃"); + textBuilder.AppendLine(); + + // 事业部业绩和单量 + textBuilder.AppendLine($"{unit.BusinessUnitName}总业绩:{unit.TotalPerformance:F0}"); + textBuilder.AppendLine(); textBuilder.AppendLine($"{unit.BusinessUnitName}总单量:{unit.TotalOrderCount}"); + textBuilder.AppendLine(); + + // 按门店分组订单 + var ordersByStore = unit.Orders + .GroupBy(x => x.StoreName) + .OrderBy(g => g.Key) // 按门店名称排序 + .ToList(); - int orderIndex = 1; - foreach (var order in unit.Orders) + // 遍历每个门店的订单 + foreach (var storeGroup in ordersByStore) { - var teacherNames = string.IsNullOrEmpty(order.HealthTeacherNames) ? "无健康师" : order.HealthTeacherNames; - textBuilder.AppendLine($"第{orderIndex}单:{order.StoreName}、{teacherNames},金额{order.Amount:F2}"); - orderIndex++; + // 每个门店的订单按时间排序 + var storeOrders = storeGroup + .OrderBy(x => x.OrderTime ?? DateTime.MinValue) + .ToList(); + + // 每个门店的订单单独编号(从第1单开始) + int orderIndex = 1; + foreach (var order in storeOrders) + { + var teacherNames = string.IsNullOrEmpty(order.HealthTeacherNames) ? "" : order.HealthTeacherNames; + // 格式:第1单:门店名健康师名金额(没有"、",没有"金额"字样,直接数字) + if (string.IsNullOrEmpty(teacherNames)) + { + textBuilder.AppendLine($"第{orderIndex}单:{order.StoreName}{order.Amount:F0}"); + } + else + { + textBuilder.AppendLine($"第{orderIndex}单:{order.StoreName}{teacherNames}{order.Amount:F0}"); + } + textBuilder.AppendLine(); + orderIndex++; + } } - - textBuilder.AppendLine(); // 空行分隔 } return textBuilder.ToString().TrimEnd(); diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqKdKdjlbService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqKdKdjlbService.cs index cae73ad..11e4fb7 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend/LqKdKdjlbService.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend/LqKdKdjlbService.cs @@ -45,6 +45,7 @@ using Yitter.IdGenerator; using NCC.Extend.Entitys.lq_package_info; using NCC.Extend.Entitys.lq_mdxx; using NCC.Extend.Entitys.lq_card_transfer_log; +using NCC.Extend.Entitys.Dto.LqDailyReport; namespace NCC.Extend.LqKdKdjlb { @@ -64,6 +65,7 @@ namespace NCC.Extend.LqKdKdjlb private readonly WeChatBotService _weChatBotService; private readonly LqKdKdjlbStringGenerator _stringGenerator; private readonly ILogger _logger; + private readonly LqDailyReportService _dailyReportService; /// /// 初始化一个类型的新实例 @@ -76,7 +78,8 @@ namespace NCC.Extend.LqKdKdjlb IUserManager userManager, WeChatBotService weChatBotService, LqKdKdjlbStringGenerator stringGenerator, - ILogger logger + ILogger logger, + LqDailyReportService dailyReportService ) { _lqKdKdjlbRepository = lqKdKdjlbRepository; @@ -88,6 +91,7 @@ namespace NCC.Extend.LqKdKdjlb _weChatBotService = weChatBotService; _stringGenerator = stringGenerator; _logger = logger; + _dailyReportService = dailyReportService; } #region 获取开单记录表 @@ -966,6 +970,47 @@ namespace NCC.Extend.LqKdKdjlb // 字符串生成失败不影响主流程,只记录日志 Console.WriteLine($"生成开单记录字符串失败: {ex.Message}"); } + + // 播报事业部开单统计数据 + try + { + //判断如果开单金额大于0,则播报事业部开单统计数据 + if (newEntity.Sfyj > 0 && newEntity.Kdrq.HasValue) + { + var billingDate = newEntity.Kdrq.Value.ToString("yyyy-MM-dd"); + var statisticsInput = new BusinessUnitBillingStatisticsInput + { + Date = billingDate + }; + var statisticsText = await _dailyReportService.GetBusinessUnitBillingStatisticsText(statisticsInput); + + if (!string.IsNullOrEmpty(statisticsText) && !statisticsText.Contains("暂无开单数据")) + { + // 发送到企业微信群 + try + { + var sendResult = await _weChatBotService.SendTextMessage(statisticsText); + if (sendResult) + { + Console.WriteLine("事业部开单统计数据已成功发送到企业微信群"); + } + else + { + Console.WriteLine("事业部开单统计数据发送到企业微信群失败"); + } + } + catch (Exception wechatEx) + { + Console.WriteLine($"发送事业部开单统计数据异常: {wechatEx.Message}"); + } + } + } + } + catch (Exception ex) + { + // 播报失败不影响主流程,只记录日志 + Console.WriteLine($"播报事业部开单统计数据失败: {ex.Message}"); + } } catch (Exception ex) { diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqPackageInfoService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqPackageInfoService.cs index 4ad743a..eb6bc42 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend/LqPackageInfoService.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend/LqPackageInfoService.cs @@ -14,6 +14,9 @@ using NCC.Extend.Entitys.Enum; using NCC.Extend.Entitys.lq_package_info; using NCC.Extend.Entitys.lq_package_item_detail; using NCC.Extend.Entitys.lq_xmzl; +using NCC.Extend.Entitys.lq_kd_kdjlb; +using NCC.Extend.Entitys.lq_kd_pxmx; +using NCC.Extend.Entitys.lq_mdxx; using NCC.Extend.Interfaces.LqPackageInfo; using NCC.FriendlyException; using SqlSugar; @@ -693,5 +696,404 @@ namespace NCC.Extend.LqPackageInfo } #endregion + #region 营销活动按门店统计 + /// + /// 获取营销活动按门店统计数据 + /// + /// + /// 统计指定营销活动在各个门店的开单数据 + /// 包括:开单数量、开单金额、欠款金额、人头数、项目数 + /// + /// 示例请求: + /// ```json + /// { + /// "activityId": "营销活动ID", + /// "startTime": "2025-10-01", + /// "endTime": "2025-10-31", + /// "storeIds": ["门店ID1", "门店ID2"] + /// } + /// ``` + /// + /// 返回字段说明: + /// - ActivityId: 营销活动ID + /// - ActivityName: 营销活动名称 + /// - StoreList: 门店统计列表 + /// - StoreId: 门店ID + /// - StoreName: 门店名称 + /// - BillingCount: 开单数量 + /// - BillingAmount: 开单金额(实付业绩总和) + /// - DebtAmount: 欠款金额 + /// - CustomerCount: 人头数(去重客户数) + /// - ItemCount: 项目数(品项项目次数总和,使用F_ProjectNumber字段) + /// + /// 查询参数 + /// 按门店统计数据 + /// 成功返回统计数据 + /// 参数错误 + /// 服务器错误 + [HttpPost("get-activity-statistics-by-store")] + public async Task GetActivityStatisticsByStore(ActivityStatisticsInput input) + { + try + { + // 1. 获取营销活动信息 + var activity = await _db.Queryable() + .Where(x => x.Id == input.ActivityId && x.IsEffective == StatusEnum.有效.GetHashCode()) + .FirstAsync(); + + if (activity == null) + { + throw NCCException.Oh("营销活动不存在或已失效"); + } + + // 2. 设置时间范围(如果未提供,使用活动时间范围) + var startTime = input.StartTime ?? activity.StartTime; + var endTime = input.EndTime ?? activity.EndTime; + + // 3. 构建基础查询:开单记录 JOIN 门店表 + var baseQuery = _db.Queryable( + (kd, md) => kd.Djmd == md.Id) + .Where((kd, md) => kd.IsEffective == StatusEnum.有效.GetHashCode()) + .Where((kd, md) => kd.ActivityId == input.ActivityId) + .Where((kd, md) => kd.Kdrq.HasValue && kd.Kdrq.Value >= startTime && kd.Kdrq.Value <= endTime); + + // 4. 门店筛选 + if (input.StoreIds != null && input.StoreIds.Any()) + { + baseQuery = baseQuery.Where((kd, md) => input.StoreIds.Contains(kd.Djmd)); + } + + // 5. 按门店分组统计(先获取基础统计数据) + var storeStatistics = await baseQuery + .GroupBy((kd, md) => new { StoreId = kd.Djmd, StoreName = md.Dm }) + .Select((kd, md) => new + { + StoreId = kd.Djmd, + StoreName = md.Dm ?? "", + BillingCount = SqlFunc.AggregateCount(kd.Id), + BillingAmount = SqlFunc.AggregateSum(kd.Sfyj), + DebtAmount = SqlFunc.AggregateSum(kd.Qk) + }) + .ToListAsync(); + + // 6. 单独统计每个门店的人头数和项目数 + var storeList = new List(); + foreach (var stat in storeStatistics) + { + // 统计人头数(去重客户数) + var customerCountQuery = _db.Queryable() + .Where(kd => kd.Djmd == stat.StoreId + && kd.ActivityId == input.ActivityId + && kd.IsEffective == StatusEnum.有效.GetHashCode() + && kd.Kdrq.HasValue && kd.Kdrq.Value >= startTime && kd.Kdrq.Value <= endTime); + + if (input.StoreIds != null && input.StoreIds.Any()) + { + customerCountQuery = customerCountQuery.Where(kd => input.StoreIds.Contains(kd.Djmd)); + } + + var customerCount = await customerCountQuery + .GroupBy(kd => kd.Kdhy) + .Select(kd => kd.Kdhy) + .CountAsync(); + + // 统计项目数(品项项目次数总和,使用F_ProjectNumber字段) + var itemCountQuery = _db.Queryable( + (px, kd) => px.Glkdbh == kd.Id) + .Where((px, kd) => kd.Djmd == stat.StoreId + && px.IsEffective == StatusEnum.有效.GetHashCode() + && kd.IsEffective == StatusEnum.有效.GetHashCode() + && kd.ActivityId == input.ActivityId + && kd.Kdrq.HasValue && kd.Kdrq.Value >= startTime && kd.Kdrq.Value <= endTime); + + if (input.StoreIds != null && input.StoreIds.Any()) + { + itemCountQuery = itemCountQuery.Where((px, kd) => input.StoreIds.Contains(kd.Djmd)); + } + + var itemCount = await itemCountQuery + .Select((px, kd) => SqlFunc.AggregateSum(px.ProjectNumber)) + .FirstAsync(); + + storeList.Add(new StoreStatisticsItem + { + StoreId = stat.StoreId ?? "", + StoreName = stat.StoreName ?? "", + BillingCount = stat.BillingCount, + BillingAmount = stat.BillingAmount, + DebtAmount = stat.DebtAmount, + CustomerCount = customerCount, + ItemCount = itemCount + }); + } + + // 7. 排序 + storeList = storeList.OrderBy(x => x.StoreName).ToList(); + + // 7. 返回统计结果 + return new ActivityStatisticsByStoreOutput + { + ActivityId = input.ActivityId, + ActivityName = activity.ActivityName, + StoreList = storeList + }; + } + catch (Exception ex) + { + throw NCCException.Oh($"获取营销活动按门店统计数据失败: {ex.Message}"); + } + } + #endregion + + #region 营销活动按品项统计 + /// + /// 获取营销活动按品项统计数据 + /// + /// + /// 统计指定营销活动各个品项的销售情况 + /// 包括:销售数量、销售金额、开单数量、销售次数 + /// + /// 示例请求: + /// ```json + /// { + /// "activityId": "营销活动ID", + /// "startTime": "2025-10-01", + /// "endTime": "2025-10-31" + /// } + /// ``` + /// + /// 查询参数 + /// 按品项统计数据 + /// 成功返回统计数据 + /// 参数错误 + /// 服务器错误 + [HttpPost("get-activity-statistics-by-item")] + public async Task GetActivityStatisticsByItem(ActivityStatisticsInput input) + { + try + { + // 1. 获取营销活动信息 + var activity = await _db.Queryable() + .Where(x => x.Id == input.ActivityId && x.IsEffective == StatusEnum.有效.GetHashCode()) + .FirstAsync(); + + if (activity == null) + { + throw NCCException.Oh("营销活动不存在或已失效"); + } + + // 2. 设置时间范围(如果未提供,使用活动时间范围) + var startTime = input.StartTime ?? activity.StartTime; + var endTime = input.EndTime ?? activity.EndTime; + + // 3. 构建基础查询:品项明细 JOIN 开单记录(用于过滤活动ID和时间) + var baseQuery = _db.Queryable( + (px, kd) => px.Glkdbh == kd.Id) + .Where((px, kd) => px.IsEffective == StatusEnum.有效.GetHashCode()) + .Where((px, kd) => kd.IsEffective == StatusEnum.有效.GetHashCode()) + .Where((px, kd) => kd.ActivityId == input.ActivityId) + .Where((px, kd) => kd.Kdrq.HasValue && kd.Kdrq.Value >= startTime && kd.Kdrq.Value <= endTime); + + // 4. 门店筛选(如果提供) + if (input.StoreIds != null && input.StoreIds.Any()) + { + baseQuery = baseQuery.Where((px, kd) => input.StoreIds.Contains(kd.Djmd)); + } + + // 5. 按品项分组统计(先获取基础统计数据) + var itemStatistics = await baseQuery + .GroupBy((px, kd) => new { ItemId = px.Px, ItemName = px.Pxmc }) + .Select((px, kd) => new + { + ItemId = px.Px ?? "", + ItemName = px.Pxmc ?? "", + SalesQuantity = SqlFunc.AggregateSum(px.ProjectNumber), + SalesAmount = SqlFunc.AggregateSum(px.ActualPrice > 0 ? px.ActualPrice : px.TotalPrice), + SalesCount = SqlFunc.AggregateCount(px.Id) + }) + .ToListAsync(); + + // 6. 单独统计每个品项的开单数量(去重开单ID) + var itemList = new List(); + foreach (var stat in itemStatistics) + { + var billingCountQuery = _db.Queryable( + (px, kd) => px.Glkdbh == kd.Id) + .Where((px, kd) => px.Px == stat.ItemId + && px.IsEffective == StatusEnum.有效.GetHashCode() + && kd.IsEffective == StatusEnum.有效.GetHashCode() + && kd.ActivityId == input.ActivityId + && kd.Kdrq.HasValue && kd.Kdrq.Value >= startTime && kd.Kdrq.Value <= endTime); + + if (input.StoreIds != null && input.StoreIds.Any()) + { + billingCountQuery = billingCountQuery.Where((px, kd) => input.StoreIds.Contains(kd.Djmd)); + } + + var billingCount = await billingCountQuery + .GroupBy((px, kd) => px.Glkdbh) + .Select((px, kd) => px.Glkdbh) + .CountAsync(); + + itemList.Add(new ItemStatisticsItem + { + ItemId = stat.ItemId ?? "", + ItemName = stat.ItemName ?? "", + SalesQuantity = stat.SalesQuantity, + SalesAmount = stat.SalesAmount, + BillingCount = billingCount, + SalesCount = stat.SalesCount + }); + } + + // 7. 排序 + itemList = itemList.OrderBy(x => x.ItemName).ToList(); + + // 7. 返回统计结果 + return new ActivityStatisticsByItemOutput + { + ActivityId = input.ActivityId, + ActivityName = activity.ActivityName, + ItemList = itemList + }; + } + catch (Exception ex) + { + throw NCCException.Oh($"获取营销活动按品项统计数据失败: {ex.Message}"); + } + } + #endregion + + #region 营销活动按门店品项统计 + /// + /// 获取营销活动按门店品项统计数据 + /// + /// + /// 统计指定营销活动在各个门店的各个品项销售情况 + /// 包括:销售数量、销售金额、开单数量、销售次数 + /// + /// 示例请求: + /// ```json + /// { + /// "activityId": "营销活动ID", + /// "startTime": "2025-10-01", + /// "endTime": "2025-10-31", + /// "storeIds": ["门店ID1", "门店ID2"] + /// } + /// ``` + /// + /// 查询参数 + /// 按门店品项统计数据 + /// 成功返回统计数据 + /// 参数错误 + /// 服务器错误 + [HttpPost("get-activity-statistics-by-store-item")] + public async Task GetActivityStatisticsByStoreItem(ActivityStatisticsInput input) + { + try + { + // 1. 获取营销活动信息 + var activity = await _db.Queryable() + .Where(x => x.Id == input.ActivityId && x.IsEffective == StatusEnum.有效.GetHashCode()) + .FirstAsync(); + + if (activity == null) + { + throw NCCException.Oh("营销活动不存在或已失效"); + } + + // 2. 设置时间范围(如果未提供,使用活动时间范围) + var startTime = input.StartTime ?? activity.StartTime; + var endTime = input.EndTime ?? activity.EndTime; + + // 3. 构建基础查询:品项明细 JOIN 开单记录 JOIN 门店表 + var baseQuery = _db.Queryable( + (px, kd, md) => px.Glkdbh == kd.Id && kd.Djmd == md.Id) + .Where((px, kd, md) => px.IsEffective == StatusEnum.有效.GetHashCode()) + .Where((px, kd, md) => kd.IsEffective == StatusEnum.有效.GetHashCode()) + .Where((px, kd, md) => kd.ActivityId == input.ActivityId) + .Where((px, kd, md) => kd.Kdrq.HasValue && kd.Kdrq.Value >= startTime && kd.Kdrq.Value <= endTime); + + // 4. 门店筛选(如果提供) + if (input.StoreIds != null && input.StoreIds.Any()) + { + baseQuery = baseQuery.Where((px, kd, md) => input.StoreIds.Contains(kd.Djmd)); + } + + // 5. 按门店和品项分组统计(先获取基础统计数据) + var storeItemStatistics = await baseQuery + .GroupBy((px, kd, md) => new + { + StoreId = kd.Djmd, + StoreName = md.Dm, + ItemId = px.Px, + ItemName = px.Pxmc + }) + .Select((px, kd, md) => new + { + StoreId = kd.Djmd ?? "", + StoreName = md.Dm ?? "", + ItemId = px.Px ?? "", + ItemName = px.Pxmc ?? "", + SalesQuantity = SqlFunc.AggregateSum(px.ProjectNumber), + SalesAmount = SqlFunc.AggregateSum(px.ActualPrice > 0 ? px.ActualPrice : px.TotalPrice), + SalesCount = SqlFunc.AggregateCount(px.Id) + }) + .ToListAsync(); + + // 6. 单独统计每个门店品项的开单数量(去重开单ID) + var storeItemList = new List(); + foreach (var stat in storeItemStatistics) + { + var billingCountQuery = _db.Queryable( + (px, kd) => px.Glkdbh == kd.Id) + .Where((px, kd) => px.Px == stat.ItemId + && kd.Djmd == stat.StoreId + && px.IsEffective == StatusEnum.有效.GetHashCode() + && kd.IsEffective == StatusEnum.有效.GetHashCode() + && kd.ActivityId == input.ActivityId + && kd.Kdrq.HasValue && kd.Kdrq.Value >= startTime && kd.Kdrq.Value <= endTime); + + if (input.StoreIds != null && input.StoreIds.Any()) + { + billingCountQuery = billingCountQuery.Where((px, kd) => input.StoreIds.Contains(kd.Djmd)); + } + + var billingCount = await billingCountQuery + .GroupBy((px, kd) => px.Glkdbh) + .Select((px, kd) => px.Glkdbh) + .CountAsync(); + + storeItemList.Add(new StoreItemStatisticsItem + { + StoreId = stat.StoreId ?? "", + StoreName = stat.StoreName ?? "", + ItemId = stat.ItemId ?? "", + ItemName = stat.ItemName ?? "", + SalesQuantity = stat.SalesQuantity, + SalesAmount = stat.SalesAmount, + BillingCount = billingCount, + SalesCount = stat.SalesCount + }); + } + + // 7. 排序 + storeItemList = storeItemList.OrderBy(x => x.StoreName).ThenBy(x => x.ItemName).ToList(); + + // 7. 返回统计结果 + return new ActivityStatisticsByStoreItemOutput + { + ActivityId = input.ActivityId, + ActivityName = activity.ActivityName, + StoreItemList = storeItemList + }; + } + catch (Exception ex) + { + throw NCCException.Oh($"获取营销活动按门店品项统计数据失败: {ex.Message}"); + } + } + #endregion + } } diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqYaoyjlService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqYaoyjlService.cs index 3b45e75..dd1d95d 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend/LqYaoyjlService.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend/LqYaoyjlService.cs @@ -93,6 +93,7 @@ namespace NCC.Extend.LqYaoyjl .WhereIF(queryLxsj != null, p => p.Lxsj >= new DateTime(startLxsj.ToDate().Year, startLxsj.ToDate().Month, startLxsj.ToDate().Day, 0, 0, 0)) .WhereIF(queryLxsj != null, p => p.Lxsj <= new DateTime(endLxsj.ToDate().Year, endLxsj.ToDate().Month, endLxsj.ToDate().Day, 23, 59, 59)) .WhereIF(!string.IsNullOrEmpty(input.lxjl), p => p.Lxjl.Contains(input.lxjl)) + .WhereIF(!string.IsNullOrEmpty(input.storeId), p => p.StoreId.Equals(input.storeId)) .Select(it => new LqYaoyjlListOutput { id = it.Id, @@ -158,6 +159,7 @@ namespace NCC.Extend.LqYaoyjl .WhereIF(queryLxsj != null, p => p.Lxsj >= new DateTime(startLxsj.ToDate().Year, startLxsj.ToDate().Month, startLxsj.ToDate().Day, 0, 0, 0)) .WhereIF(queryLxsj != null, p => p.Lxsj <= new DateTime(endLxsj.ToDate().Year, endLxsj.ToDate().Month, endLxsj.ToDate().Day, 23, 59, 59)) .WhereIF(!string.IsNullOrEmpty(input.lxjl), p => p.Lxjl.Contains(input.lxjl)) + .WhereIF(!string.IsNullOrEmpty(input.storeId), p => p.StoreId.Equals(input.storeId)) .Select(it => new LqYaoyjlListOutput { id = it.Id,