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`;
+