diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqStatistics/LeadCustomerStatisticsListOutput.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqStatistics/LeadCustomerStatisticsListOutput.cs new file mode 100644 index 0000000..b3fc690 --- /dev/null +++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqStatistics/LeadCustomerStatisticsListOutput.cs @@ -0,0 +1,81 @@ +using System; + +namespace NCC.Extend.Entitys.Dto.LqStatistics +{ + /// + /// 线索池客户统计报表输出 + /// + public class LeadCustomerStatisticsListOutput + { + /// + /// 线索池客户(拓客编号) + /// + public string LeadCustomerId { get; set; } + + /// + /// 客户姓名 + /// + public string CustomerName { get; set; } + + /// + /// 拓客时间 + /// + public DateTime? ExpansionTime { get; set; } + + /// + /// 是否邀约(是/否) + /// + public string HasInvite { get; set; } + + /// + /// 是否预约(是/否) + /// + public string HasAppointment { get; set; } + + /// + /// 是否有消耗(是/否) + /// + public string HasConsume { get; set; } + + /// + /// 是否开单(是/否) + /// + public string HasBilling { get; set; } + + /// + /// 未开单原因 + /// + public string NoBillingReason { get; set; } + + /// + /// 开卡金额 + /// + public decimal BillingAmount { get; set; } + + /// + /// 开卡卡项(多个卡项用顿号分隔) + /// + public string BillingItems { get; set; } + + /// + /// 实际预约记录数(不管是否通过邀约产生) + /// + public int ActualAppointmentCount { get; set; } + + /// + /// 实际消耗记录数(不管是否通过预约产生) + /// + public int ActualConsumeCount { get; set; } + + /// + /// 实际开单记录数(不管是否通过预约产生) + /// + public int ActualBillingCount { get; set; } + + /// + /// 问题分析说明 + /// + public string Analysis { get; set; } + } +} + diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqStatistics/LeadCustomerStatisticsListQueryInput.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqStatistics/LeadCustomerStatisticsListQueryInput.cs new file mode 100644 index 0000000..5c691bd --- /dev/null +++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqStatistics/LeadCustomerStatisticsListQueryInput.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; + +namespace NCC.Extend.Entitys.Dto.LqStatistics +{ + /// + /// 线索池客户统计报表查询输入 + /// + public class LeadCustomerStatisticsListQueryInput + { + /// + /// 页码 + /// + [Required] + public int PageIndex { get; set; } = 1; + + /// + /// 页大小 + /// + [Required] + public int PageSize { get; set; } = 20; + + /// + /// 开始时间(拓客时间范围) + /// + public DateTime? StartTime { get; set; } + + /// + /// 结束时间(拓客时间范围) + /// + public DateTime? EndTime { get; set; } + + /// + /// 门店ID列表(可以多个门店) + /// + public List StoreIds { get; set; } + + /// + /// 拓客活动ID + /// + public string EventId { get; set; } + } +} + diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqStatistics/MemberUpgradeStatisticsListOutput.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqStatistics/MemberUpgradeStatisticsListOutput.cs new file mode 100644 index 0000000..6d1d4b2 --- /dev/null +++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqStatistics/MemberUpgradeStatisticsListOutput.cs @@ -0,0 +1,39 @@ +namespace NCC.Extend.Entitys.Dto.LqStatistics +{ + /// + /// 会员升单统计输出 + /// + public class MemberUpgradeStatisticsListOutput + { + /// + /// 会员ID + /// + public string MemberId { get; set; } + + /// + /// 会员姓名 + /// + public string MemberName { get; set; } + + /// + /// 会员手机号 + /// + public string MemberPhone { get; set; } + + /// + /// 前4单中是否有升医美(是/否) + /// + public string HasUpgradeMedicalBeauty { get; set; } + + /// + /// 前4单中是否有升科美(是/否) + /// + public string HasUpgradeTechBeauty { get; set; } + + /// + /// 前4单中是否有升生美(是/否) + /// + public string HasUpgradeLifeBeauty { get; set; } + } +} + diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqStatistics/MemberUpgradeStatisticsListQueryInput.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqStatistics/MemberUpgradeStatisticsListQueryInput.cs new file mode 100644 index 0000000..5e4f55b --- /dev/null +++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqStatistics/MemberUpgradeStatisticsListQueryInput.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; + +namespace NCC.Extend.Entitys.Dto.LqStatistics +{ + /// + /// 会员升单统计查询输入 + /// + public class MemberUpgradeStatisticsListQueryInput + { + /// + /// 页码 + /// + public int PageIndex { get; set; } = 1; + + /// + /// 每页数量 + /// + public int PageSize { get; set; } = 20; + + /// + /// 会员ID列表(可选,不传则查询所有会员) + /// + public List MemberIds { get; set; } + + /// + /// 是否升医美(true-是,false-否,null-不筛选) + /// + public bool? HasUpgradeMedicalBeauty { get; set; } + + /// + /// 是否升科美(true-是,false-否,null-不筛选) + /// + public bool? HasUpgradeTechBeauty { get; set; } + + /// + /// 是否升生美(true-是,false-否,null-不筛选) + /// + public bool? HasUpgradeLifeBeauty { get; set; } + } +} + diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqStatistics/StoreStatisticsListOutput.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqStatistics/StoreStatisticsListOutput.cs new file mode 100644 index 0000000..59465e5 --- /dev/null +++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqStatistics/StoreStatisticsListOutput.cs @@ -0,0 +1,54 @@ +namespace NCC.Extend.Entitys.Dto.LqStatistics +{ + /// + /// 门店统计报表输出 + /// + public class StoreStatisticsListOutput + { + /// + /// 门店ID + /// + public string StoreId { get; set; } + + /// + /// 门店名称 + /// + public string StoreName { get; set; } + + /// + /// 总人数(拓客记录数) + /// + public int TotalCount { get; set; } + + /// + /// 拓客人数(去重的会员数) + /// + public int TkMemberCount { get; set; } + + /// + /// 邀约数(通过拓客编号关联的邀约记录数) + /// + public int InviteCount { get; set; } + + /// + /// 预约数(通过邀约ID关联的预约记录数) + /// + public int AppointmentCount { get; set; } + + /// + /// 耗卡数(通过预约ID关联的耗卡记录数) + /// + public int ConsumeCount { get; set; } + + /// + /// 开单数(通过预约ID关联的开单记录数) + /// + public int BillingCount { get; set; } + + /// + /// 开单金额(通过预约ID关联的开单记录金额汇总) + /// + public decimal BillingAmount { get; set; } + } +} + diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqStatistics/StoreStatisticsListQueryInput.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqStatistics/StoreStatisticsListQueryInput.cs new file mode 100644 index 0000000..9b46308 --- /dev/null +++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqStatistics/StoreStatisticsListQueryInput.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; + +namespace NCC.Extend.Entitys.Dto.LqStatistics +{ + /// + /// 门店统计报表查询输入 + /// + public class StoreStatisticsListQueryInput + { + /// + /// 开始时间(拓客时间范围) + /// + public DateTime? StartTime { get; set; } + + /// + /// 结束时间(拓客时间范围) + /// + public DateTime? EndTime { get; set; } + + /// + /// 门店ID列表(可以多个门店) + /// + public List StoreIds { get; set; } + + /// + /// 拓客活动ID(可选,不传则查询所有活动) + /// + public string EventId { get; set; } + } +} + diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_hytk_jksyj/LqHytkJksyjEntity.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_hytk_jksyj/LqHytkJksyjEntity.cs index 8f8bfc7..6fd7d97 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_hytk_jksyj/LqHytkJksyjEntity.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_hytk_jksyj/LqHytkJksyjEntity.cs @@ -113,5 +113,17 @@ namespace NCC.Extend.Entitys.lq_hytk_jksyj /// [SugarColumn(ColumnName = "F_IsEffective")] public int IsEffective { get; set; } = StatusEnum.有效.GetHashCode(); + + /// + /// 品项分类 + /// + [SugarColumn(ColumnName = "F_ItemCategory")] + public string ItemCategory { get; set; } + + /// + /// 品项ID + /// + [SugarColumn(ColumnName = "F_ItemId")] + public string ItemId { get; set; } } } diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_hytk_kjbsyj/LqHytkKjbsyjEntity.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_hytk_kjbsyj/LqHytkKjbsyjEntity.cs index d5b6a6a..5cd3173 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_hytk_kjbsyj/LqHytkKjbsyjEntity.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_hytk_kjbsyj/LqHytkKjbsyjEntity.cs @@ -108,5 +108,17 @@ namespace NCC.Extend.Entitys.lq_hytk_kjbsyj /// [SugarColumn(ColumnName = "F_IsEffective")] public int IsEffective { get; set; } = StatusEnum.有效.GetHashCode(); + + /// + /// 品项分类 + /// + [SugarColumn(ColumnName = "F_ItemCategory")] + public string ItemCategory { get; set; } + + /// + /// 品项ID + /// + [SugarColumn(ColumnName = "F_ItemId")] + public string ItemId { get; set; } } } diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_kd_jksyj/LqKdJksyjEntity.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_kd_jksyj/LqKdJksyjEntity.cs index 28cf514..43f808d 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_kd_jksyj/LqKdJksyjEntity.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_kd_jksyj/LqKdJksyjEntity.cs @@ -76,5 +76,17 @@ namespace NCC.Extend.Entitys.lq_kd_jksyj /// [SugarColumn(ColumnName = "F_ActivityId")] public string ActivityId { get; set; } + + /// + /// 品项分类 + /// + [SugarColumn(ColumnName = "F_ItemCategory")] + public string ItemCategory { get; set; } + + /// + /// 品项ID + /// + [SugarColumn(ColumnName = "F_ItemId")] + public string ItemId { get; set; } } } diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_kd_kjbsyj/LqKdKjbsyjEntity.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_kd_kjbsyj/LqKdKjbsyjEntity.cs index 14fe19e..55c506d 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_kd_kjbsyj/LqKdKjbsyjEntity.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_kd_kjbsyj/LqKdKjbsyjEntity.cs @@ -76,5 +76,17 @@ namespace NCC.Extend.Entitys.lq_kd_kjbsyj /// [SugarColumn(ColumnName = "F_ActivityId")] public string ActivityId { get; set; } + + /// + /// 品项分类 + /// + [SugarColumn(ColumnName = "F_ItemCategory")] + public string ItemCategory { get; set; } + + /// + /// 品项ID + /// + [SugarColumn(ColumnName = "F_ItemId")] + public string ItemId { get; set; } } } diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_xh_jksyj/LqXhJksyjEntity.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_xh_jksyj/LqXhJksyjEntity.cs index de57f7d..29d273a 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_xh_jksyj/LqXhJksyjEntity.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_xh_jksyj/LqXhJksyjEntity.cs @@ -124,5 +124,17 @@ namespace NCC.Extend.Entitys.lq_xh_jksyj /// [SugarColumn(ColumnName = "F_AccompaniedProjectNumber")] public decimal? AccompaniedProjectNumber { get; set; } + + /// + /// 品项分类 + /// + [SugarColumn(ColumnName = "F_ItemCategory")] + public string ItemCategory { get; set; } + + /// + /// 品项ID + /// + [SugarColumn(ColumnName = "F_ItemId")] + public string ItemId { get; set; } } } \ No newline at end of file diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_xh_kjbsyj/LqXhKjbsyjEntity.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_xh_kjbsyj/LqXhKjbsyjEntity.cs index 1daec75..d63b339 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_xh_kjbsyj/LqXhKjbsyjEntity.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_xh_kjbsyj/LqXhKjbsyjEntity.cs @@ -106,5 +106,17 @@ namespace NCC.Extend.Entitys.lq_xh_kjbsyj /// [SugarColumn(ColumnName = "F_IsEffective")] public int? IsEffective { get; set; } = 1; + + /// + /// 品项分类 + /// + [SugarColumn(ColumnName = "F_ItemCategory")] + public string ItemCategory { get; set; } + + /// + /// 品项ID + /// + [SugarColumn(ColumnName = "F_ItemId")] + public string ItemId { get; set; } } } \ No newline at end of file diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqInventoryUsageService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqInventoryUsageService.cs index 17b220f..f9fca08 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend/LqInventoryUsageService.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend/LqInventoryUsageService.cs @@ -446,11 +446,9 @@ namespace NCC.Extend } // 查询该批次的所有使用记录 - var usageRecords = await _db.Queryable( - (u, product) => u.ProductId == product.Id) - .LeftJoin((u, product, store) => u.StoreId == store.Id) - .Where((u, product, store) => u.UsageBatchId == batchId) - .Select((u, product, store) => new LqInventoryUsageListOutput + var usageRecords = await _db.Queryable((u, product) => u.ProductId == product.Id) + .Where((u, product) => u.UsageBatchId == batchId) + .Select((u, product) => new LqInventoryUsageListOutput { id = u.Id, productId = u.ProductId, @@ -458,7 +456,7 @@ namespace NCC.Extend productCategory = product.ProductCategory, productPrice = product.Price, storeId = u.StoreId, - storeName = store.Dm, + storeName = SqlFunc.Subqueryable().Where(store => store.Id == u.StoreId).Select(store => store.Dm), usageTime = u.UsageTime, usageQuantity = u.UsageQuantity, relatedConsumeId = u.RelatedConsumeId, diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqKhxxService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqKhxxService.cs index 44e29ff..f6a4761 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend/LqKhxxService.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend/LqKhxxService.cs @@ -1595,7 +1595,7 @@ namespace NCC.Extend.LqKhxx } /// - /// 批量更新所有会员信息(高性能版:使用SQL批量更新) + /// 批量更新所有会员信息(高性能版:使用SQL批量更新)【通过定时任务去执行,每天晚上执行一次】 /// /// [HttpPost("BatchUpdateMemberInfo")] diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqStatisticsService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqStatisticsService.cs index 4f08fac..a9cd25a 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend/LqStatisticsService.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend/LqStatisticsService.cs @@ -53,6 +53,9 @@ using SqlSugar; using Yitter.IdGenerator; using NCC.Extend.Entitys.lq_kd_pxmx; using NCC.Extend.Entitys.lq_khxx; +using NCC.Extend.Entitys.lq_tkjlb; +using NCC.Extend.Entitys.lq_yaoyjl; +using NCC.Extend.Entitys.lq_yyjl; namespace NCC.Extend.LqStatistics { @@ -3949,6 +3952,754 @@ namespace NCC.Extend.LqStatistics } #endregion + #region 线索池客户统计报表 + /// + /// 获取线索池客户统计报表 + /// + /// + /// 根据拓客记录统计线索池客户的邀约、预约、消耗、开单等信息 + /// + /// 业务链路:拓客 -> 邀约 -> 预约 -> 开单/消耗 + /// + /// 示例请求: + /// ```json + /// { + /// "pageIndex": 1, + /// "pageSize": 20, + /// "startTime": "2025-10-01T00:00:00", + /// "endTime": "2025-10-31T23:59:59", + /// "storeIds": ["store1", "store2"], + /// "eventId": "event123" + /// } + /// ``` + /// + /// 参数说明: + /// - pageIndex: 页码,从1开始 + /// - pageSize: 每页数量 + /// - startTime: 拓客时间范围开始时间 + /// - endTime: 拓客时间范围结束时间 + /// - storeIds: 门店ID列表,可传多个 + /// - eventId: 拓客活动ID + /// + /// 返回数据说明: + /// - LeadCustomerId: 线索池客户(拓客编号) + /// - CustomerName: 客户姓名 + /// - ExpansionTime: 拓客时间 + /// - HasInvite: 是否邀约(是/否),通过拓客编号关联邀约表 + /// - HasAppointment: 是否预约(是/否),只统计通过邀约产生的预约(预约表的F_InviteId关联邀约表) + /// - HasConsume: 是否有消耗(是/否),只统计通过预约产生的耗卡(耗卡表的F_AppointmentId关联预约表) + /// - HasBilling: 是否开单(是/否),只统计通过预约产生的开单(开单表的F_AppointmentId关联预约表) + /// - NoBillingReason: 未开单原因,从预约记录的F_NoDealRemark字段获取 + /// - BillingAmount: 开卡金额,汇总通过预约产生的开单记录的整单业绩(zdyj) + /// - BillingItems: 开卡卡项,汇总通过预约产生的开单品项名称,多个用顿号分隔 + /// - ActualAppointmentCount: 实际预约记录数(不管是否通过邀约产生),用于问题分析 + /// - ActualConsumeCount: 实际消耗记录数(不管是否通过预约产生),用于问题分析 + /// - ActualBillingCount: 实际开单记录数(不管是否通过预约产生),用于问题分析 + /// - Analysis: 问题分析说明,自动分析数据异常情况,如:有预约记录但未通过邀约产生、有消耗记录但未通过预约产生等 + /// + /// 返回示例: + /// ```json + /// { + /// "list": [ + /// { + /// "LeadCustomerId": "751248448816153862", + /// "CustomerName": "王女士", + /// "ExpansionTime": "2025-10-24T03:33:10.000Z", + /// "HasInvite": "否", + /// "HasAppointment": "否", + /// "HasConsume": "否", + /// "HasBilling": "否", + /// "NoBillingReason": null, + /// "BillingAmount": 0, + /// "BillingItems": null, + /// "ActualAppointmentCount": 3, + /// "ActualConsumeCount": 4, + /// "ActualBillingCount": 5, + /// "Analysis": "有3条预约记录,但未通过邀约产生(F_InviteId为null);有4条消耗记录,但未通过预约产生(F_AppointmentId为null或预约未通过邀约产生);有5条开单记录,但未通过预约产生(F_AppointmentId为null或预约未通过邀约产生)" + /// } + /// ], + /// "pagination": { + /// "pageIndex": 1, + /// "pageSize": 20, + /// "total": 1511 + /// } + /// } + /// ``` + /// + /// 查询条件 + /// 线索池客户统计报表列表,包含统计数据和问题分析 + /// 查询成功,返回统计报表列表和分页信息 + /// 参数错误 + /// 服务器内部错误 + [HttpPost("get-lead-customer-statistics-list")] + public async Task GetLeadCustomerStatisticsList([FromBody] LeadCustomerStatisticsListQueryInput input) + { + try + { + // 构建WHERE条件 + var whereConditions = new List(); + var parameters = new List(); + + if (input.StartTime.HasValue) + { + whereConditions.Add("tk.F_ExpansionTime >= @StartTime"); + parameters.Add(new SugarParameter("@StartTime", input.StartTime.Value)); + } + + if (input.EndTime.HasValue) + { + whereConditions.Add("tk.F_ExpansionTime <= @EndTime"); + parameters.Add(new SugarParameter("@EndTime", input.EndTime.Value)); + } + + if (input.StoreIds != null && input.StoreIds.Any()) + { + var storeIdParams = string.Join(",", input.StoreIds.Select((_, i) => $"@StoreId{i}")); + whereConditions.Add($"tk.F_StoreId IN ({storeIdParams})"); + for (int i = 0; i < input.StoreIds.Count; i++) + { + parameters.Add(new SugarParameter($"@StoreId{i}", input.StoreIds[i])); + } + } + + if (!string.IsNullOrEmpty(input.EventId)) + { + whereConditions.Add("tk.F_EventId = @EventId"); + parameters.Add(new SugarParameter("@EventId", input.EventId)); + } + + var whereClause = whereConditions.Any() ? "WHERE " + string.Join(" AND ", whereConditions) : ""; + + // 使用子查询优化性能,避免复杂的JOIN和GROUP BY + var sql = $@" + SELECT + tk.F_Id as LeadCustomerId, + tk.F_CustomerName as CustomerName, + tk.F_ExpansionTime as ExpansionTime, + -- 是否邀约:通过拓客编号关联 + CASE WHEN yaoy_stats.has_invite = 1 THEN '是' ELSE '否' END as HasInvite, + -- 是否预约:通过邀约ID关联(只统计通过邀约产生的预约) + CASE WHEN yy_stats.has_appointment = 1 THEN '是' ELSE '否' END as HasAppointment, + -- 是否有消耗:通过预约ID关联(只统计通过预约产生的耗卡) + CASE WHEN xh_stats.has_consume = 1 THEN '是' ELSE '否' END as HasConsume, + -- 是否开单:通过预约ID关联(只统计通过预约产生的开单) + CASE WHEN kd_stats.has_billing = 1 THEN '是' ELSE '否' END as HasBilling, + -- 未开单原因:从预约记录中获取(只取通过邀约产生的预约) + yy_stats.no_billing_reason as NoBillingReason, + -- 开卡金额:汇总通过预约产生的开单记录 + COALESCE(kd_stats.billing_amount, 0) as BillingAmount, + -- 开卡卡项:汇总通过预约产生的开单品项 + kd_stats.billing_items as BillingItems, + -- 实际预约记录数(不管是否通过邀约产生) + COALESCE(yy_actual.count, 0) as ActualAppointmentCount, + -- 实际消耗记录数(不管是否通过预约产生) + COALESCE(xh_actual.count, 0) as ActualConsumeCount, + -- 实际开单记录数(不管是否通过预约产生) + COALESCE(kd_actual.count, 0) as ActualBillingCount + FROM lq_tkjlb tk + -- 邀约统计子查询 + LEFT JOIN ( + SELECT + yaoy.tkbh as tk_id, + 1 as has_invite + FROM lq_yaoyjl yaoy + GROUP BY yaoy.tkbh + ) yaoy_stats ON yaoy_stats.tk_id = tk.F_Id + -- 预约统计子查询(只统计通过邀约产生的预约) + LEFT JOIN ( + SELECT + tk_inner.F_MemberId as member_id, + 1 as has_appointment, + MAX(yy.F_NoDealRemark) as no_billing_reason + FROM lq_tkjlb tk_inner + INNER JOIN lq_yaoyjl yaoy ON yaoy.tkbh = tk_inner.F_Id + INNER JOIN lq_yyjl yy ON yy.F_InviteId = yaoy.F_Id + GROUP BY tk_inner.F_MemberId + ) yy_stats ON yy_stats.member_id = tk.F_MemberId + -- 消耗统计子查询(只统计通过预约产生的耗卡) + LEFT JOIN ( + SELECT + tk_inner.F_MemberId as member_id, + 1 as has_consume + FROM lq_tkjlb tk_inner + INNER JOIN lq_yaoyjl yaoy ON yaoy.tkbh = tk_inner.F_Id + INNER JOIN lq_yyjl yy ON yy.F_InviteId = yaoy.F_Id + INNER JOIN lq_xh_hyhk xh ON xh.F_AppointmentId = yy.F_Id AND xh.F_IsEffective = 1 + GROUP BY tk_inner.F_MemberId + ) xh_stats ON xh_stats.member_id = tk.F_MemberId + -- 开单统计子查询(只统计通过预约产生的开单,包含金额和品项) + LEFT JOIN ( + SELECT + tk_inner.F_MemberId as member_id, + 1 as has_billing, + SUM(kd.zdyj) as billing_amount, + GROUP_CONCAT(DISTINCT kdpx.pxmc SEPARATOR '、') as billing_items + FROM lq_tkjlb tk_inner + INNER JOIN lq_yaoyjl yaoy ON yaoy.tkbh = tk_inner.F_Id + INNER JOIN lq_yyjl yy ON yy.F_InviteId = yaoy.F_Id + INNER JOIN lq_kd_kdjlb kd ON kd.F_AppointmentId = yy.F_Id AND kd.F_IsEffective = 1 + LEFT JOIN lq_kd_pxmx kdpx ON kdpx.glkdbh = kd.F_Id AND kdpx.F_IsEffective = 1 + GROUP BY tk_inner.F_MemberId + ) kd_stats ON kd_stats.member_id = tk.F_MemberId + -- 实际预约记录数统计(不管是否通过邀约产生) + LEFT JOIN ( + SELECT + yy.gk as member_id, + COUNT(*) as count + FROM lq_yyjl yy + GROUP BY yy.gk + ) yy_actual ON yy_actual.member_id = tk.F_MemberId + -- 实际消耗记录数统计(不管是否通过预约产生) + LEFT JOIN ( + SELECT + xh.hy as member_id, + COUNT(*) as count + FROM lq_xh_hyhk xh + WHERE xh.F_IsEffective = 1 + GROUP BY xh.hy + ) xh_actual ON xh_actual.member_id = tk.F_MemberId + -- 实际开单记录数统计(不管是否通过预约产生) + LEFT JOIN ( + SELECT + kd.kdhy as member_id, + COUNT(*) as count + FROM lq_kd_kdjlb kd + WHERE kd.F_IsEffective = 1 + GROUP BY kd.kdhy + ) kd_actual ON kd_actual.member_id = tk.F_MemberId + {whereClause} + ORDER BY tk.F_ExpansionTime DESC + LIMIT @PageSize OFFSET @Offset"; + + parameters.Add(new SugarParameter("@PageSize", input.PageSize)); + parameters.Add(new SugarParameter("@Offset", (input.PageIndex - 1) * input.PageSize)); + + // 查询总数 + var countSql = $@" + SELECT COUNT(*) + FROM lq_tkjlb tk + {whereClause}"; + + var countParameters = parameters.Where(p => p.ParameterName != "@PageSize" && p.ParameterName != "@Offset").ToList(); + var totalCount = await _db.Ado.GetIntAsync(countSql, countParameters); + + // 执行查询 + var result = await _db.Ado.SqlQueryAsync(sql, parameters); + + // 生成问题分析说明 + foreach (var item in result) + { + var analysisList = new List(); + + if (item.HasInvite == "否" && item.HasAppointment == "否" && item.ActualAppointmentCount > 0) + { + analysisList.Add($"有{item.ActualAppointmentCount}条预约记录,但未通过邀约产生(F_InviteId为null)"); + } + + if (item.HasAppointment == "否" && item.HasConsume == "否" && item.ActualConsumeCount > 0) + { + analysisList.Add($"有{item.ActualConsumeCount}条消耗记录,但未通过预约产生(F_AppointmentId为null或预约未通过邀约产生)"); + } + + if (item.HasAppointment == "否" && item.HasBilling == "否" && item.ActualBillingCount > 0) + { + analysisList.Add($"有{item.ActualBillingCount}条开单记录,但未通过预约产生(F_AppointmentId为null或预约未通过邀约产生)"); + } + + if (item.HasInvite == "是" && item.HasAppointment == "否" && item.ActualAppointmentCount > 0) + { + analysisList.Add($"有邀约记录,有{item.ActualAppointmentCount}条预约记录,但预约记录的F_InviteId未关联到邀约记录"); + } + + if (item.HasAppointment == "是" && item.HasConsume == "否" && item.ActualConsumeCount > 0) + { + analysisList.Add($"有预约记录,有{item.ActualConsumeCount}条消耗记录,但消耗记录的F_AppointmentId未关联到预约记录"); + } + + if (item.HasAppointment == "是" && item.HasBilling == "否" && item.ActualBillingCount > 0) + { + analysisList.Add($"有预约记录,有{item.ActualBillingCount}条开单记录,但开单记录的F_AppointmentId未关联到预约记录"); + } + + if (analysisList.Count == 0) + { + item.Analysis = "数据正常,符合业务链路:拓客 -> 邀约 -> 预约 -> 开单/消耗"; + } + else + { + item.Analysis = string.Join(";", analysisList); + } + } + + return new + { + list = result, + pagination = new + { + pageIndex = input.PageIndex, + pageSize = input.PageSize, + total = totalCount + } + }; + } + catch (Exception ex) + { + _logger.LogError(ex, "获取线索池客户统计报表失败"); + throw NCCException.Oh($"获取线索池客户统计报表失败:{ex.Message}"); + } + } + #endregion + + #region 门店统计报表 + /// + /// 获取门店统计报表 + /// + /// + /// 按门店统计拓客、邀约、预约、消耗、开单等数据 + /// + /// 业务链路:拓客 -> 邀约 -> 预约 -> 开单/消耗 + /// + /// 示例请求: + /// ```json + /// { + /// "startTime": "2025-10-01T00:00:00", + /// "endTime": "2025-10-31T23:59:59", + /// "storeIds": ["store1", "store2"], + /// "eventId": "event123" + /// } + /// ``` + /// + /// 参数说明: + /// - startTime: 拓客时间范围开始时间 + /// - endTime: 拓客时间范围结束时间 + /// - storeIds: 门店ID列表,可传多个 + /// - eventId: 拓客活动ID + /// + /// 返回数据说明: + /// - StoreId: 门店ID + /// - StoreName: 门店名称 + /// - TotalCount: 总人数(从客户信息表按归属门店统计) + /// - TkMemberCount: 拓客人数(拓客记录数,不去重) + /// - InviteCount: 邀约数(通过拓客编号关联的邀约记录数) + /// - AppointmentCount: 预约数(通过邀约ID关联的预约记录数,只统计通过邀约产生的预约) + /// - ConsumeCount: 耗卡数(通过预约ID关联的耗卡记录数,只统计通过预约产生的耗卡) + /// - BillingCount: 开单数(通过预约ID关联的开单记录数,只统计通过预约产生的开单) + /// - BillingAmount: 开单金额(通过预约ID关联的开单记录金额汇总) + /// + /// 返回示例: + /// ```json + /// { + /// "list": [ + /// { + /// "StoreId": "1649328471923847169", + /// "StoreName": "绿纤紫荆店", + /// "TotalCount": 119, + /// "TkMemberCount": 117, + /// "InviteCount": 4, + /// "AppointmentCount": 2, + /// "ConsumeCount": 1, + /// "BillingCount": 1, + /// "BillingAmount": 199.00 + /// } + /// ] + /// } + /// ``` + /// + /// 查询条件 + /// 门店统计报表列表 + /// 查询成功,返回门店统计报表列表 + /// 参数错误 + /// 服务器内部错误 + [HttpPost("get-store-statistics-list")] + public async Task GetStoreStatisticsList([FromBody] StoreStatisticsListQueryInput input) + { + try + { + // 构建WHERE条件(带表别名,用于子查询) + var whereConditions = new List(); + // 构建WHERE条件(不带表别名,用于UNION的SELECT) + var whereConditionsNoAlias = new List(); + var parameters = new List(); + + if (input.StartTime.HasValue) + { + whereConditions.Add("tk.F_ExpansionTime >= @StartTime"); + whereConditionsNoAlias.Add("F_ExpansionTime >= @StartTime"); + parameters.Add(new SugarParameter("@StartTime", input.StartTime.Value)); + } + + if (input.EndTime.HasValue) + { + whereConditions.Add("tk.F_ExpansionTime <= @EndTime"); + whereConditionsNoAlias.Add("F_ExpansionTime <= @EndTime"); + parameters.Add(new SugarParameter("@EndTime", input.EndTime.Value)); + } + + if (input.StoreIds != null && input.StoreIds.Any()) + { + var storeIdParams = string.Join(",", input.StoreIds.Select((_, i) => $"@StoreId{i}")); + whereConditions.Add($"tk.F_StoreId IN ({storeIdParams})"); + whereConditionsNoAlias.Add($"F_StoreId IN ({storeIdParams})"); + for (int i = 0; i < input.StoreIds.Count; i++) + { + parameters.Add(new SugarParameter($"@StoreId{i}", input.StoreIds[i])); + } + } + + if (!string.IsNullOrEmpty(input.EventId)) + { + whereConditions.Add("tk.F_EventId = @EventId"); + whereConditionsNoAlias.Add("F_EventId = @EventId"); + parameters.Add(new SugarParameter("@EventId", input.EventId)); + } + + var whereClause = whereConditions.Any() ? "WHERE " + string.Join(" AND ", whereConditions) : ""; + var whereClauseNoAlias = whereConditionsNoAlias.Any() ? "WHERE " + string.Join(" AND ", whereConditionsNoAlias) : ""; + + // 构建门店筛选条件(用于客户信息表查询) + var khWhereConditions = new List(); + var khWhereConditionsNoAlias = new List(); + if (input.StoreIds != null && input.StoreIds.Any()) + { + var storeIdParams = string.Join(",", input.StoreIds.Select((_, i) => $"@StoreId{i}")); + khWhereConditions.Add($"kh.gsmd IN ({storeIdParams})"); + khWhereConditionsNoAlias.Add($"gsmd IN ({storeIdParams})"); + } + + var khWhereClause = khWhereConditions.Any() ? "WHERE " + string.Join(" AND ", khWhereConditions) : "WHERE kh.gsmd IS NOT NULL"; + var khWhereClauseNoAlias = khWhereConditionsNoAlias.Any() ? "WHERE " + string.Join(" AND ", khWhereConditionsNoAlias) : "WHERE gsmd IS NOT NULL"; + + // 使用子查询优化性能,避免复杂的JOIN + var sql = $@" + SELECT + COALESCE(total_stats.StoreId, tk_stats.StoreId, yaoy_stats.StoreId, yy_stats.StoreId, xh_stats.StoreId, kd_stats.StoreId) as StoreId, + COALESCE(md.dm, '') as StoreName, + COALESCE(total_stats.TotalCount, 0) as TotalCount, + COALESCE(tk_stats.TkMemberCount, 0) as TkMemberCount, + COALESCE(yaoy_stats.InviteCount, 0) as InviteCount, + COALESCE(yy_stats.AppointmentCount, 0) as AppointmentCount, + COALESCE(xh_stats.ConsumeCount, 0) as ConsumeCount, + COALESCE(kd_stats.BillingCount, 0) as BillingCount, + COALESCE(kd_stats.BillingAmount, 0) as BillingAmount + FROM ( + SELECT DISTINCT StoreId FROM ( + SELECT gsmd as StoreId FROM lq_khxx {khWhereClauseNoAlias} + UNION + SELECT F_StoreId as StoreId FROM lq_tkjlb {whereClauseNoAlias} + ) as all_stores + ) as stores + LEFT JOIN lq_mdxx md ON md.F_Id = stores.StoreId + -- 总人数统计(从客户信息表按归属门店统计) + LEFT JOIN ( + SELECT + kh.gsmd as StoreId, + COUNT(*) as TotalCount + FROM lq_khxx kh + {khWhereClause} + GROUP BY kh.gsmd + ) total_stats ON total_stats.StoreId = stores.StoreId + -- 拓客人数统计(不用去重) + LEFT JOIN ( + SELECT + tk.F_StoreId as StoreId, + COUNT(tk.F_MemberId) as TkMemberCount + FROM lq_tkjlb tk + {(string.IsNullOrEmpty(whereClause) ? "" : whereClause)} + GROUP BY tk.F_StoreId + ) tk_stats ON tk_stats.StoreId = stores.StoreId + -- 邀约数统计 + LEFT JOIN ( + SELECT + tk.F_StoreId as StoreId, + COUNT(DISTINCT yaoy.F_Id) as InviteCount + FROM lq_tkjlb tk + INNER JOIN lq_yaoyjl yaoy ON yaoy.tkbh = tk.F_Id + {(string.IsNullOrEmpty(whereClause) ? "" : whereClause)} + GROUP BY tk.F_StoreId + ) yaoy_stats ON yaoy_stats.StoreId = stores.StoreId + -- 预约数统计(通过邀约ID关联) + LEFT JOIN ( + SELECT + tk.F_StoreId as StoreId, + COUNT(DISTINCT yy.F_Id) as AppointmentCount + FROM lq_tkjlb tk + INNER JOIN lq_yaoyjl yaoy ON yaoy.tkbh = tk.F_Id + INNER JOIN lq_yyjl yy ON yy.F_InviteId = yaoy.F_Id + {(string.IsNullOrEmpty(whereClause) ? "" : whereClause)} + GROUP BY tk.F_StoreId + ) yy_stats ON yy_stats.StoreId = stores.StoreId + -- 耗卡数统计(通过预约ID关联) + LEFT JOIN ( + SELECT + tk.F_StoreId as StoreId, + COUNT(DISTINCT xh.F_Id) as ConsumeCount + FROM lq_tkjlb tk + INNER JOIN lq_yaoyjl yaoy ON yaoy.tkbh = tk.F_Id + INNER JOIN lq_yyjl yy ON yy.F_InviteId = yaoy.F_Id + INNER JOIN lq_xh_hyhk xh ON xh.F_AppointmentId = yy.F_Id AND xh.F_IsEffective = 1 + {(string.IsNullOrEmpty(whereClause) ? "" : whereClause)} + GROUP BY tk.F_StoreId + ) xh_stats ON xh_stats.StoreId = stores.StoreId + -- 开单数和开单金额统计(通过预约ID关联) + LEFT JOIN ( + SELECT + tk.F_StoreId as StoreId, + COUNT(DISTINCT kd.F_Id) as BillingCount, + SUM(kd.zdyj) as BillingAmount + FROM lq_tkjlb tk + INNER JOIN lq_yaoyjl yaoy ON yaoy.tkbh = tk.F_Id + INNER JOIN lq_yyjl yy ON yy.F_InviteId = yaoy.F_Id + INNER JOIN lq_kd_kdjlb kd ON kd.F_AppointmentId = yy.F_Id AND kd.F_IsEffective = 1 + {(string.IsNullOrEmpty(whereClause) ? "" : whereClause)} + GROUP BY tk.F_StoreId + ) kd_stats ON kd_stats.StoreId = stores.StoreId + WHERE stores.StoreId IS NOT NULL + ORDER BY stores.StoreId"; + + // 执行查询 + var result = await _db.Ado.SqlQueryAsync(sql, parameters); + + return new + { + list = result + }; + } + catch (Exception ex) + { + _logger.LogError(ex, "获取门店统计报表失败"); + throw NCCException.Oh($"获取门店统计报表失败:{ex.Message}"); + } + } + #endregion + + #region 会员升单统计 + /// + /// 获取会员升单统计(前4单中是否有升医美、升科美、升生美) + /// + /// + /// 统计每个会员的前4单开单记录中是否有升医美、升科美、升生美 + /// + /// 示例请求: + /// ```json + /// { + /// "pageIndex": 1, + /// "pageSize": 20, + /// "memberIds": ["member1", "member2"], + /// "hasUpgradeMedicalBeauty": true, + /// "hasUpgradeTechBeauty": false, + /// "hasUpgradeLifeBeauty": null + /// } + /// ``` + /// + /// 参数说明: + /// - pageIndex: 页码,从1开始 + /// - pageSize: 每页数量 + /// - memberIds: 会员ID列表(可选,不传则查询所有会员) + /// - hasUpgradeMedicalBeauty: 是否升医美(true-是,false-否,null-不筛选) + /// - hasUpgradeTechBeauty: 是否升科美(true-是,false-否,null-不筛选) + /// - hasUpgradeLifeBeauty: 是否升生美(true-是,false-否,null-不筛选) + /// + /// 返回数据说明: + /// - MemberId: 会员ID + /// - MemberName: 会员姓名 + /// - MemberPhone: 会员手机号 + /// - HasUpgradeMedicalBeauty: 前4单中是否有升医美(是/否) + /// - HasUpgradeTechBeauty: 前4单中是否有升科美(是/否) + /// - HasUpgradeLifeBeauty: 前4单中是否有升生美(是/否) + /// + /// 返回示例: + /// ```json + /// { + /// "list": [ + /// { + /// "MemberId": "744326092097062149", + /// "MemberName": "张女士", + /// "MemberPhone": "13800138000", + /// "HasUpgradeMedicalBeauty": "否", + /// "HasUpgradeTechBeauty": "否", + /// "HasUpgradeLifeBeauty": "否" + /// } + /// ], + /// "pagination": { + /// "pageIndex": 1, + /// "pageSize": 20, + /// "total": 100 + /// } + /// } + /// ``` + /// + /// 查询条件 + /// 会员升单统计列表 + /// 查询成功,返回会员升单统计列表 + /// 参数错误 + /// 服务器内部错误 + [HttpPost("get-member-upgrade-statistics-list")] + public async Task GetMemberUpgradeStatisticsList([FromBody] MemberUpgradeStatisticsListQueryInput input) + { + try + { + // 构建WHERE条件 + var whereConditions = new List(); + var parameters = new List(); + + if (input.MemberIds != null && input.MemberIds.Any()) + { + var memberIdParams = string.Join(",", input.MemberIds.Select((_, i) => $"@MemberId{i}")); + whereConditions.Add($"kd.kdhy IN ({memberIdParams})"); + for (int i = 0; i < input.MemberIds.Count; i++) + { + parameters.Add(new SugarParameter($"@MemberId{i}", input.MemberIds[i])); + } + } + + var whereClause = whereConditions.Any() ? "AND " + string.Join(" AND ", whereConditions) : ""; + + // 构建HAVING条件(用于筛选升单条件) + var havingConditions = new List(); + if (input.HasUpgradeMedicalBeauty.HasValue) + { + if (input.HasUpgradeMedicalBeauty.Value) + { + havingConditions.Add("MAX(CASE WHEN kd.F_UpgradeMedicalBeauty = '是' THEN 1 ELSE 0 END) = 1"); + } + else + { + havingConditions.Add("MAX(CASE WHEN kd.F_UpgradeMedicalBeauty = '是' THEN 1 ELSE 0 END) = 0"); + } + } + if (input.HasUpgradeTechBeauty.HasValue) + { + if (input.HasUpgradeTechBeauty.Value) + { + havingConditions.Add("MAX(CASE WHEN kd.F_UpgradeTechBeauty = '是' THEN 1 ELSE 0 END) = 1"); + } + else + { + havingConditions.Add("MAX(CASE WHEN kd.F_UpgradeTechBeauty = '是' THEN 1 ELSE 0 END) = 0"); + } + } + if (input.HasUpgradeLifeBeauty.HasValue) + { + if (input.HasUpgradeLifeBeauty.Value) + { + havingConditions.Add("MAX(CASE WHEN kd.F_UpgradeLifeBeauty = '是' THEN 1 ELSE 0 END) = 1"); + } + else + { + havingConditions.Add("MAX(CASE WHEN kd.F_UpgradeLifeBeauty = '是' THEN 1 ELSE 0 END) = 0"); + } + } + + var havingClause = havingConditions.Any() ? "HAVING " + string.Join(" AND ", havingConditions) : ""; + + // 分页参数 + var offset = (input.PageIndex - 1) * input.PageSize; + parameters.Add(new SugarParameter("@PageSize", input.PageSize)); + parameters.Add(new SugarParameter("@Offset", offset)); + + // 查询每个会员的前4单中是否有升医美、升科美、升生美 + var sql = $@" + SELECT + kd.kdhy as MemberId, + kh.khmc as MemberName, + kh.sjh as MemberPhone, + CASE WHEN MAX(CASE WHEN kd.F_UpgradeMedicalBeauty = '是' THEN 1 ELSE 0 END) = 1 THEN '是' ELSE '否' END as HasUpgradeMedicalBeauty, + CASE WHEN MAX(CASE WHEN kd.F_UpgradeTechBeauty = '是' THEN 1 ELSE 0 END) = 1 THEN '是' ELSE '否' END as HasUpgradeTechBeauty, + CASE WHEN MAX(CASE WHEN kd.F_UpgradeLifeBeauty = '是' THEN 1 ELSE 0 END) = 1 THEN '是' ELSE '否' END as HasUpgradeLifeBeauty + FROM ( + SELECT + kd.kdhy, + kd.F_Id, + kd.kdrq, + kd.F_CreateTime, + kd.F_UpgradeMedicalBeauty, + kd.F_UpgradeTechBeauty, + kd.F_UpgradeLifeBeauty + FROM lq_kd_kdjlb kd + WHERE kd.F_IsEffective = 1 + AND kd.kdhy IS NOT NULL + {whereClause} + AND ( + SELECT COUNT(*) + FROM lq_kd_kdjlb kd2 + WHERE kd2.kdhy = kd.kdhy + AND kd2.F_IsEffective = 1 + AND ( + kd2.kdrq > kd.kdrq + OR (kd2.kdrq = kd.kdrq AND kd2.F_CreateTime > kd.F_CreateTime) + OR (kd2.kdrq = kd.kdrq AND kd2.F_CreateTime = kd.F_CreateTime AND kd2.F_Id > kd.F_Id) + ) + ) < 4 + ) kd + LEFT JOIN lq_khxx kh ON kh.F_Id = kd.kdhy + GROUP BY kd.kdhy, kh.khmc, kh.sjh + {havingClause} + ORDER BY kd.kdhy + LIMIT @PageSize OFFSET @Offset"; + + // 查询总数(需要应用相同的HAVING条件) + var countSql = $@" + SELECT COUNT(*) + FROM ( + SELECT + kd.kdhy, + CASE WHEN MAX(CASE WHEN kd.F_UpgradeMedicalBeauty = '是' THEN 1 ELSE 0 END) = 1 THEN '是' ELSE '否' END as HasUpgradeMedicalBeauty, + CASE WHEN MAX(CASE WHEN kd.F_UpgradeTechBeauty = '是' THEN 1 ELSE 0 END) = 1 THEN '是' ELSE '否' END as HasUpgradeTechBeauty, + CASE WHEN MAX(CASE WHEN kd.F_UpgradeLifeBeauty = '是' THEN 1 ELSE 0 END) = 1 THEN '是' ELSE '否' END as HasUpgradeLifeBeauty + FROM ( + SELECT + kd.kdhy, + kd.F_Id, + kd.kdrq, + kd.F_CreateTime, + kd.F_UpgradeMedicalBeauty, + kd.F_UpgradeTechBeauty, + kd.F_UpgradeLifeBeauty + FROM lq_kd_kdjlb kd + WHERE kd.F_IsEffective = 1 + AND kd.kdhy IS NOT NULL + {whereClause} + AND ( + SELECT COUNT(*) + FROM lq_kd_kdjlb kd2 + WHERE kd2.kdhy = kd.kdhy + AND kd2.F_IsEffective = 1 + AND ( + kd2.kdrq > kd.kdrq + OR (kd2.kdrq = kd.kdrq AND kd2.F_CreateTime > kd.F_CreateTime) + OR (kd2.kdrq = kd.kdrq AND kd2.F_CreateTime = kd.F_CreateTime AND kd2.F_Id > kd.F_Id) + ) + ) < 4 + ) kd + GROUP BY kd.kdhy + {havingClause} + ) as filtered_results"; + + var countParameters = parameters.Where(p => p.ParameterName != "@PageSize" && p.ParameterName != "@Offset").ToList(); + var totalCount = await _db.Ado.GetIntAsync(countSql, countParameters); + + // 执行查询 + var result = await _db.Ado.SqlQueryAsync(sql, parameters); + + return new + { + list = result, + pagination = new + { + pageIndex = input.PageIndex, + pageSize = input.PageSize, + total = totalCount + } + }; + } + catch (Exception ex) + { + _logger.LogError(ex, "获取会员升单统计失败"); + throw NCCException.Oh($"获取会员升单统计失败:{ex.Message}"); + } + } + #endregion + } diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqStoreConsumableInventoryService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqStoreConsumableInventoryService.cs index 367f998..1b799fd 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend/LqStoreConsumableInventoryService.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend/LqStoreConsumableInventoryService.cs @@ -1,13 +1,16 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; using NCC.Common.Core.Manager; using NCC.Common.Enum; +using NCC.Common.Extension; using NCC.Common.Filter; using NCC.Dependency; using NCC.DynamicApiController; +using NCC.Extend.Entitys.Dto.Common; using NCC.Extend.Entitys.Dto.LqStoreConsumableInventory; using NCC.Extend.Entitys.Enum; using NCC.Extend.Entitys.lq_mdxx; @@ -367,6 +370,26 @@ namespace NCC.Extend } } #endregion + + + #region 获取消耗品产品类型枚举内容 + /// + /// 获取消耗品产品类型枚举内容 + /// + /// 消耗品产品类型枚举列表 + [HttpGet("consumable-product-type")] + public List GetConsumableProductTypeSelector() + { + return Enum.GetValues() + .Select(e => new EnumOutput + { + Value = (int)e, + Name = e.ToString(), + Description = e.GetDescription(), + }) + .ToList(); + } + #endregion } } diff --git a/sql/同步健康师业绩表品项分类和品项ID.sql b/sql/同步健康师业绩表品项分类和品项ID.sql new file mode 100644 index 0000000..1e55a12 --- /dev/null +++ b/sql/同步健康师业绩表品项分类和品项ID.sql @@ -0,0 +1,61 @@ +-- 同步健康师业绩表和科技老师业绩表中的品项分类和品项ID字段 +-- 数据来源:通过关联的品项明细表获取 + +-- ============================================ +-- 健康师业绩表同步 +-- ============================================ + +-- 1. 开单健康师业绩表:从开单品项明细表(lq_kd_pxmx)同步 +UPDATE lq_kd_jksyj kd +INNER JOIN lq_kd_pxmx px ON px.F_Id = kd.F_kdpxid +SET + kd.F_ItemCategory = px.F_ItemCategory, + kd.F_ItemId = px.px +WHERE kd.F_kdpxid IS NOT NULL; + +-- 2. 耗卡健康师业绩表:从耗卡品项明细表(lq_xh_pxmx)同步 +UPDATE lq_xh_jksyj xh +INNER JOIN lq_xh_pxmx px ON px.F_Id = xh.F_kdpxid +SET + xh.F_ItemCategory = px.F_ItemCategory, + xh.F_ItemId = px.px +WHERE xh.F_kdpxid IS NOT NULL; + +-- 3. 退卡健康师业绩表:从退卡品项明细表(lq_hytk_mx)同步 +-- 注意:F_CardReturn 关联到 lq_hytk_mx.F_Id,F_tkpxid 是项目资料表ID(品项ID) +UPDATE lq_hytk_jksyj tk +INNER JOIN lq_hytk_mx mx ON mx.F_Id = tk.F_CardReturn +SET + tk.F_ItemCategory = mx.F_ItemCategory, + tk.F_ItemId = mx.px +WHERE tk.F_CardReturn IS NOT NULL; + +-- ============================================ +-- 科技老师业绩表同步 +-- ============================================ + +-- 4. 开单科技老师业绩表:从开单品项明细表(lq_kd_pxmx)同步 +UPDATE lq_kd_kjbsyj kd +INNER JOIN lq_kd_pxmx px ON px.F_Id = kd.F_kdpxid +SET + kd.F_ItemCategory = px.F_ItemCategory, + kd.F_ItemId = px.px +WHERE kd.F_kdpxid IS NOT NULL; + +-- 5. 耗卡科技老师业绩表:从耗卡品项明细表(lq_xh_pxmx)同步 +UPDATE lq_xh_kjbsyj xh +INNER JOIN lq_xh_pxmx px ON px.F_Id = xh.F_hkpxid +SET + xh.F_ItemCategory = px.F_ItemCategory, + xh.F_ItemId = px.px +WHERE xh.F_hkpxid IS NOT NULL; + +-- 6. 退卡科技老师业绩表:从退卡品项明细表(lq_hytk_mx)同步 +-- 注意:F_CardReturn 关联到 lq_hytk_mx.F_Id,F_tkpxid 是项目资料表ID(品项ID) +UPDATE lq_hytk_kjbsyj tk +INNER JOIN lq_hytk_mx mx ON mx.F_Id = tk.F_CardReturn +SET + tk.F_ItemCategory = mx.F_ItemCategory, + tk.F_ItemId = mx.px +WHERE tk.F_CardReturn IS NOT NULL; + diff --git a/sql/添加业绩表品项分类字段.sql b/sql/添加业绩表品项分类字段.sql new file mode 100644 index 0000000..4c32578 --- /dev/null +++ b/sql/添加业绩表品项分类字段.sql @@ -0,0 +1,32 @@ +-- 为6个业绩表添加品项分类字段和品项ID字段 + +-- 1. 开单健康师业绩表 +ALTER TABLE `lq_kd_jksyj` +ADD COLUMN `F_ItemCategory` VARCHAR(50) NULL DEFAULT NULL COMMENT '品项分类' AFTER `F_ActivityId`, +ADD COLUMN `F_ItemId` VARCHAR(50) NULL DEFAULT NULL COMMENT '品项ID' AFTER `F_ItemCategory`; + +-- 2. 开单科技老师业绩表 +ALTER TABLE `lq_kd_kjbsyj` +ADD COLUMN `F_ItemCategory` VARCHAR(50) NULL DEFAULT NULL COMMENT '品项分类' AFTER `F_ActivityId`, +ADD COLUMN `F_ItemId` VARCHAR(50) NULL DEFAULT NULL COMMENT '品项ID' AFTER `F_ItemCategory`; + +-- 3. 耗卡健康师业绩表 +ALTER TABLE `lq_xh_jksyj` +ADD COLUMN `F_ItemCategory` VARCHAR(50) NULL DEFAULT NULL COMMENT '品项分类' AFTER `F_AccompaniedProjectNumber`, +ADD COLUMN `F_ItemId` VARCHAR(50) NULL DEFAULT NULL COMMENT '品项ID' AFTER `F_ItemCategory`; + +-- 4. 耗卡科技老师业绩表 +ALTER TABLE `lq_xh_kjbsyj` +ADD COLUMN `F_ItemCategory` VARCHAR(50) NULL DEFAULT NULL COMMENT '品项分类' AFTER `F_IsEffective`, +ADD COLUMN `F_ItemId` VARCHAR(50) NULL DEFAULT NULL COMMENT '品项ID' AFTER `F_ItemCategory`; + +-- 5. 退卡健康师业绩表 +ALTER TABLE `lq_hytk_jksyj` +ADD COLUMN `F_ItemCategory` VARCHAR(50) NULL DEFAULT NULL COMMENT '品项分类' AFTER `F_IsEffective`, +ADD COLUMN `F_ItemId` VARCHAR(50) NULL DEFAULT NULL COMMENT '品项ID' AFTER `F_ItemCategory`; + +-- 6. 退卡科技老师业绩表 +ALTER TABLE `lq_hytk_kjbsyj` +ADD COLUMN `F_ItemCategory` VARCHAR(50) NULL DEFAULT NULL COMMENT '品项分类' AFTER `F_IsEffective`, +ADD COLUMN `F_ItemId` VARCHAR(50) NULL DEFAULT NULL COMMENT '品项ID' AFTER `F_ItemCategory`; +