diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Config/MemberInfoUpdateConfig.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Config/MemberInfoUpdateConfig.cs
new file mode 100644
index 0000000..7b981c0
--- /dev/null
+++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Config/MemberInfoUpdateConfig.cs
@@ -0,0 +1,88 @@
+using System.Collections.Generic;
+
+namespace NCC.Extend.Entitys.Config
+{
+ ///
+ /// 会员信息更新配置
+ ///
+ public static class MemberInfoUpdateConfig
+ {
+ ///
+ /// 生美会员判断金额阈值(元)
+ ///
+ public static decimal BeautyMemberAmountThreshold { get; set; } = 500m;
+
+ ///
+ /// 医美会员判断金额阈值(元)
+ ///
+ public static decimal MedicalMemberAmountThreshold { get; set; } = 3000m;
+
+ ///
+ /// 科技部会员判断金额阈值(元)
+ ///
+ public static decimal TechMemberAmountThreshold { get; set; } = 3800m;
+
+ ///
+ /// 沉睡天数阈值列表(天)
+ ///
+ public static List SleepDaysThresholds { get; set; } = new List { 60, 90, 180, 360 };
+
+ ///
+ /// 消费等级配置(金额阈值,单位:元)
+ /// Key: 等级编号(0-普通, 1-银卡, 2-金卡, 3-钻石等)
+ /// Value: 金额阈值
+ ///
+ public static Dictionary ConsumeLevelThresholds { get; set; } = new Dictionary
+ {
+ { 0, 0m }, // D: 小于 1 万
+ { 1, 10000m }, // C: 1 万
+ { 2, 30000m }, // B: 3 万
+ { 3, 50000m }, // A: 5 万
+ { 4, 100000m }, // A+: 10 万
+ { 5, 200000m } // A++: 20 万
+ };
+
+ ///
+ /// 消费等级名称映射
+ ///
+ public static Dictionary ConsumeLevelNames { get; set; } = new Dictionary
+ {
+ { 0, "D" },
+ { 1, "C" },
+ { 2, "B" },
+ { 3, "A" },
+ { 4, "A+" },
+ { 5, "A++" }
+ };
+
+ ///
+ /// 根据消费金额获取消费等级
+ ///
+ /// 消费金额
+ /// 消费等级编号
+ public static int GetConsumeLevel(decimal amount)
+ {
+ // 从高到低遍历,找到第一个满足条件的等级
+ for (int i = ConsumeLevelThresholds.Count - 1; i >= 0; i--)
+ {
+ if (amount >= ConsumeLevelThresholds[i])
+ {
+ return i;
+ }
+ }
+ return 0; // 默认返回最低等级
+ }
+
+ ///
+ /// 根据消费金额获取消费等级名称
+ ///
+ /// 消费金额
+ /// 消费等级名称
+ public static string GetConsumeLevelName(decimal amount)
+ {
+ var level = GetConsumeLevel(amount);
+ return ConsumeLevelNames.ContainsKey(level) ? ConsumeLevelNames[level] : "D";
+ }
+ }
+}
+
diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKhxx/LqKhxxInfoOutput.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKhxx/LqKhxxInfoOutput.cs
index 821309e..e8762af 100644
--- a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKhxx/LqKhxxInfoOutput.cs
+++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKhxx/LqKhxxInfoOutput.cs
@@ -135,6 +135,85 @@ namespace NCC.Extend.Entitys.Dto.LqKhxx
///
public string subHealthUserName { get; set; }
+ ///
+ /// 是否生美会员(0-否,1-是)
+ ///
+ public int isBeautyMember { get; set; }
+
+ ///
+ /// 是否医美会员(0-否,1-是)
+ ///
+ public int isMedicalMember { get; set; }
+
+ ///
+ /// 是否科技部会员(0-否,1-是)
+ ///
+ public int isTechMember { get; set; }
+
+ ///
+ /// 成为生美会员时间
+ ///
+ public DateTime? beautyMemberTime { get; set; }
+
+ ///
+ /// 成为医美会员时间
+ ///
+ public DateTime? medicalMemberTime { get; set; }
+
+ ///
+ /// 成为科技部会员时间
+ ///
+ public DateTime? techMemberTime { get; set; }
+
+ ///
+ /// 首次到店时间
+ ///
+ public DateTime? firstVisitTime { get; set; }
+
+ ///
+ /// 最后到店时间
+ ///
+ public DateTime? lastVisitTime { get; set; }
+
+ ///
+ /// 到店天数
+ ///
+ public int visitDays { get; set; }
+
+ ///
+ /// 沉睡开始时间
+ ///
+ public DateTime? sleepStartTime { get; set; }
+
+ ///
+ /// 沉睡天数
+ ///
+ public int sleepDays { get; set; }
+
+ ///
+ /// 耗卡总金额(累计所有耗卡的金额总和)
+ ///
+ public decimal totalConsumeAmount { get; set; }
+
+ ///
+ /// 开卡总金额(累计所有开单的实付业绩总和)
+ ///
+ public decimal totalBillingAmount { get; set; }
+
+ ///
+ /// 剩余权益总金额(开卡总金额-耗卡总金额-退款金额)
+ ///
+ public decimal remainingRightsAmount { get; set; }
+
+ ///
+ /// 消费等级(0-普通,1-银卡,2-金卡,3-钻石等)
+ ///
+ public int consumeLevel { get; set; }
+
+ ///
+ /// 消费等级更新时间
+ ///
+ public DateTime? consumeLevelUpdateTime { get; set; }
}
}
diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKhxx/LqKhxxListOutput.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKhxx/LqKhxxListOutput.cs
index 1991964..924fde8 100644
--- a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKhxx/LqKhxxListOutput.cs
+++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKhxx/LqKhxxListOutput.cs
@@ -161,5 +161,85 @@ namespace NCC.Extend.Entitys.Dto.LqKhxx
/// 副健康师名称
///
public string subHealthUserName { get; set; }
+
+ ///
+ /// 是否生美会员(0-否,1-是)
+ ///
+ public int isBeautyMember { get; set; }
+
+ ///
+ /// 是否医美会员(0-否,1-是)
+ ///
+ public int isMedicalMember { get; set; }
+
+ ///
+ /// 是否科技部会员(0-否,1-是)
+ ///
+ public int isTechMember { get; set; }
+
+ ///
+ /// 成为生美会员时间
+ ///
+ public DateTime? beautyMemberTime { get; set; }
+
+ ///
+ /// 成为医美会员时间
+ ///
+ public DateTime? medicalMemberTime { get; set; }
+
+ ///
+ /// 成为科技部会员时间
+ ///
+ public DateTime? techMemberTime { get; set; }
+
+ ///
+ /// 首次到店时间
+ ///
+ public DateTime? firstVisitTime { get; set; }
+
+ ///
+ /// 最后到店时间
+ ///
+ public DateTime? lastVisitTime { get; set; }
+
+ ///
+ /// 到店天数
+ ///
+ public int visitDays { get; set; }
+
+ ///
+ /// 沉睡开始时间
+ ///
+ public DateTime? sleepStartTime { get; set; }
+
+ ///
+ /// 沉睡天数
+ ///
+ public int sleepDays { get; set; }
+
+ ///
+ /// 耗卡总金额(累计所有耗卡的金额总和)
+ ///
+ public decimal totalConsumeAmount { get; set; }
+
+ ///
+ /// 开卡总金额(累计所有开单的实付业绩总和)
+ ///
+ public decimal totalBillingAmount { get; set; }
+
+ ///
+ /// 剩余权益总金额(开卡总金额-耗卡总金额-退款金额)
+ ///
+ public decimal remainingRightsAmount { get; set; }
+
+ ///
+ /// 消费等级(0-普通,1-银卡,2-金卡,3-钻石等)
+ ///
+ public int consumeLevel { get; set; }
+
+ ///
+ /// 消费等级更新时间
+ ///
+ public DateTime? consumeLevelUpdateTime { get; set; }
}
}
diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKhxx/LqKhxxListQueryInput.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKhxx/LqKhxxListQueryInput.cs
index a79029e..c60cd12 100644
--- a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKhxx/LqKhxxListQueryInput.cs
+++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKhxx/LqKhxxListQueryInput.cs
@@ -48,8 +48,6 @@ namespace NCC.Extend.Entitys.Dto.LqKhxx
///
public string xb { get; set; }
-
-
///
/// 归属门店
///
diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKhxx/MemberItemInfoOutput.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKhxx/MemberItemInfoOutput.cs
index 645b424..a5b8638 100644
--- a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKhxx/MemberItemInfoOutput.cs
+++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKhxx/MemberItemInfoOutput.cs
@@ -1,5 +1,8 @@
using System;
using System.Collections.Generic;
+using NCC.Code;
+using NCC.Common.Extension;
+using NCC.Extend.Entitys.Enum;
namespace NCC.Extend.Entitys.Dto.LqKhxx
{
@@ -29,6 +32,21 @@ namespace NCC.Extend.Entitys.Dto.LqKhxx
public string MemberPhone { get; set; }
///
+ /// 会员类型
+ ///
+ public int MemberType { get; set; }
+
+ ///
+ /// 会员类型名称
+ ///
+ public string MemberTypeName
+ {
+ get
+ {
+ return EnumHelper.GetEnumDesc(MemberType);
+ }
+ }
+ ///
/// 所属门店ID
///
public string BelongStoreId { get; set; }
diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKhxx/MemberItemInfoQueryInput.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKhxx/MemberItemInfoQueryInput.cs
index 6f3b19d..2fa2ac7 100644
--- a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKhxx/MemberItemInfoQueryInput.cs
+++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKhxx/MemberItemInfoQueryInput.cs
@@ -24,6 +24,11 @@ namespace NCC.Extend.Entitys.Dto.LqKhxx
public string BillingStoreId { get; set; }
///
+ /// 会员类型
+ ///
+ public int MemberType { get; set; } = -1;
+
+ ///
/// 开单人ID
///
public string BillingUserId { get; set; }
diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqPersonTimesRecord/LqPersonTimesRecordCrInput.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqPersonTimesRecord/LqPersonTimesRecordCrInput.cs
index 6f6b509..b67dddc 100644
--- a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqPersonTimesRecord/LqPersonTimesRecordCrInput.cs
+++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqPersonTimesRecord/LqPersonTimesRecordCrInput.cs
@@ -23,6 +23,11 @@ namespace NCC.Extend.Entitys.Dto.LqPersonTimesRecord
public string businessType { get; set; }
///
+ /// 是否有开单(0-否,1-是)
+ ///
+ public int hasBilling { get; set; } = 0;
+
+ ///
/// 人员类型(健康师/科技老师)
///
public string personType { get; set; }
diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqPersonTimesRecord/LqPersonTimesRecordInfoOutput.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqPersonTimesRecord/LqPersonTimesRecordInfoOutput.cs
index 20563cd..cee2dc9 100644
--- a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqPersonTimesRecord/LqPersonTimesRecordInfoOutput.cs
+++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqPersonTimesRecord/LqPersonTimesRecordInfoOutput.cs
@@ -23,6 +23,11 @@ namespace NCC.Extend.Entitys.Dto.LqPersonTimesRecord
public string businessType { get; set; }
///
+ /// 是否有开单(0-否,1-是)
+ ///
+ public int hasBilling { get; set; }
+
+ ///
/// 人员类型(健康师/科技老师)
///
public string personType { get; set; }
diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqPersonTimesRecord/LqPersonTimesRecordListOutput.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqPersonTimesRecord/LqPersonTimesRecordListOutput.cs
index ef3d419..567fd96 100644
--- a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqPersonTimesRecord/LqPersonTimesRecordListOutput.cs
+++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqPersonTimesRecord/LqPersonTimesRecordListOutput.cs
@@ -23,6 +23,11 @@ namespace NCC.Extend.Entitys.Dto.LqPersonTimesRecord
public string businessType { get; set; }
///
+ /// 是否有开单(0-否,1-是)
+ ///
+ public int hasBilling { get; set; }
+
+ ///
/// 人员类型(健康师/科技老师)
///
public string personType { get; set; }
diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqXhPxmx/ConsumeItemDetailListOutput.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqXhPxmx/ConsumeItemDetailListOutput.cs
new file mode 100644
index 0000000..0a15b43
--- /dev/null
+++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqXhPxmx/ConsumeItemDetailListOutput.cs
@@ -0,0 +1,81 @@
+using System;
+
+namespace NCC.Extend.Entitys.Dto.LqXhPxmx
+{
+ ///
+ /// 耗卡品项明细记录输出
+ ///
+ public class ConsumeItemDetailListOutput
+ {
+ ///
+ /// 品项明细ID
+ ///
+ public string id { get; set; }
+
+ ///
+ /// 耗卡记录ID
+ ///
+ public string consumeId { get; set; }
+
+ ///
+ /// 耗卡时间
+ ///
+ public DateTime? consumeTime { get; set; }
+
+ ///
+ /// 会员名称
+ ///
+ public string memberName { get; set; }
+
+ ///
+ /// 会员手机
+ ///
+ public string memberPhone { get; set; }
+
+ ///
+ /// 品项名称
+ ///
+ public string itemName { get; set; }
+
+ ///
+ /// 品项类型(分类④-统计品项用)
+ ///
+ public string itemType { get; set; }
+
+ ///
+ /// 品项价格
+ ///
+ public decimal itemPrice { get; set; }
+
+ ///
+ /// 项目次数
+ ///
+ public decimal projectNumber { get; set; }
+
+ ///
+ /// 原始项目次数
+ ///
+ public decimal? originalProjectNumber { get; set; }
+
+ ///
+ /// 合计金额
+ ///
+ public decimal totalPrice { get; set; }
+
+ ///
+ /// 来源类型(购买/赠送/体验)
+ ///
+ public string sourceType { get; set; }
+
+ ///
+ /// 门店ID
+ ///
+ public string storeId { get; set; }
+
+ ///
+ /// 门店名称
+ ///
+ public string storeName { get; set; }
+ }
+}
+
diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqXhPxmx/ConsumeItemDetailListQueryInput.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqXhPxmx/ConsumeItemDetailListQueryInput.cs
new file mode 100644
index 0000000..43db838
--- /dev/null
+++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqXhPxmx/ConsumeItemDetailListQueryInput.cs
@@ -0,0 +1,81 @@
+using NCC.Common.Filter;
+
+namespace NCC.Extend.Entitys.Dto.LqXhPxmx
+{
+ ///
+ /// 耗卡品项明细记录查询输入参数
+ ///
+ public class ConsumeItemDetailListQueryInput : PageInputBase
+ {
+ ///
+ /// 品项明细ID
+ ///
+ public string Id { get; set; }
+
+ ///
+ /// 耗卡记录ID
+ ///
+ public string ConsumeId { get; set; }
+
+ ///
+ /// 耗卡时间(开始)
+ ///
+ public string StartConsumeTime { get; set; }
+
+ ///
+ /// 耗卡时间(结束)
+ ///
+ public string EndConsumeTime { get; set; }
+
+ ///
+ /// 开始时间(兼容参数名)
+ ///
+ public string startTime { get; set; }
+
+ ///
+ /// 结束时间(兼容参数名)
+ ///
+ public string endTime { get; set; }
+
+ ///
+ /// 会员ID
+ ///
+ public string MemberId { get; set; }
+
+ ///
+ /// 会员名称(模糊查询)
+ ///
+ public string MemberName { get; set; }
+
+ ///
+ /// 会员手机号
+ ///
+ public string MemberPhone { get; set; }
+
+ ///
+ /// 品项ID
+ ///
+ public string ItemId { get; set; }
+
+ ///
+ /// 品项名称(模糊查询)
+ ///
+ public string ItemName { get; set; }
+
+ ///
+ /// 品项类型(分类④-统计品项用)
+ ///
+ public string ItemType { get; set; }
+
+ ///
+ /// 来源类型(购买/赠送/体验)
+ ///
+ public string SourceType { get; set; }
+
+ ///
+ /// 门店ID
+ ///
+ public string StoreId { get; set; }
+ }
+}
+
diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_khxx/LqKhxxEntity.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_khxx/LqKhxxEntity.cs
index dd4f453..64c4b33 100644
--- a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_khxx/LqKhxxEntity.cs
+++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_khxx/LqKhxxEntity.cs
@@ -142,6 +142,108 @@ namespace NCC.Extend.Entitys.lq_khxx
public string SubHealthUser { get; set; }
///
+ /// 最后消费时间
+ ///
+ [SugarColumn(ColumnName = "F_LastConsumeTime")]
+ public DateTime? LastConsumeTime { get; set; }
+
+ ///
+ /// 是否生美会员(0-否,1-是)
+ ///
+ [SugarColumn(ColumnName = "F_IsBeautyMember")]
+ public int IsBeautyMember { get; set; } = 0;
+
+ ///
+ /// 是否医美会员(0-否,1-是)
+ ///
+ [SugarColumn(ColumnName = "F_IsMedicalMember")]
+ public int IsMedicalMember { get; set; } = 0;
+
+ ///
+ /// 是否科技部会员(0-否,1-是)
+ ///
+ [SugarColumn(ColumnName = "F_IsTechMember")]
+ public int IsTechMember { get; set; } = 0;
+
+ ///
+ /// 成为生美会员时间
+ ///
+ [SugarColumn(ColumnName = "F_BeautyMemberTime")]
+ public DateTime? BeautyMemberTime { get; set; }
+
+ ///
+ /// 成为医美会员时间
+ ///
+ [SugarColumn(ColumnName = "F_MedicalMemberTime")]
+ public DateTime? MedicalMemberTime { get; set; }
+
+ ///
+ /// 成为科技部会员时间
+ ///
+ [SugarColumn(ColumnName = "F_TechMemberTime")]
+ public DateTime? TechMemberTime { get; set; }
+
+ ///
+ /// 首次到店时间
+ ///
+ [SugarColumn(ColumnName = "F_FirstVisitTime")]
+ public DateTime? FirstVisitTime { get; set; }
+
+ ///
+ /// 最后到店时间
+ ///
+ [SugarColumn(ColumnName = "F_LastVisitTime")]
+ public DateTime? LastVisitTime { get; set; }
+
+ ///
+ /// 到店天数
+ ///
+ [SugarColumn(ColumnName = "F_VisitDays")]
+ public int VisitDays { get; set; } = 0;
+
+ ///
+ /// 沉睡开始时间
+ ///
+ [SugarColumn(ColumnName = "F_SleepStartTime")]
+ public DateTime? SleepStartTime { get; set; }
+
+ ///
+ /// 沉睡天数
+ ///
+ [SugarColumn(ColumnName = "F_SleepDays")]
+ public int SleepDays { get; set; } = 0;
+
+ ///
+ /// 耗卡总金额(累计所有耗卡的金额总和)
+ ///
+ [SugarColumn(ColumnName = "F_TotalConsumeAmount")]
+ public decimal TotalConsumeAmount { get; set; } = 0m;
+
+ ///
+ /// 开卡总金额(累计所有开单的实付业绩总和)
+ ///
+ [SugarColumn(ColumnName = "F_TotalBillingAmount")]
+ public decimal TotalBillingAmount { get; set; } = 0m;
+
+ ///
+ /// 剩余权益总金额(开卡总金额-耗卡总金额-退款金额)
+ ///
+ [SugarColumn(ColumnName = "F_RemainingRightsAmount")]
+ public decimal RemainingRightsAmount { get; set; } = 0m;
+
+ ///
+ /// 消费等级(0-普通,1-银卡,2-金卡,3-钻石等)
+ ///
+ [SugarColumn(ColumnName = "F_ConsumeLevel")]
+ public int ConsumeLevel { get; set; } = 0;
+
+ ///
+ /// 消费等级更新时间
+ ///
+ [SugarColumn(ColumnName = "F_ConsumeLevelUpdateTime")]
+ public DateTime? ConsumeLevelUpdateTime { get; set; }
+
+ ///
/// 是否有效
///
[SugarColumn(ColumnName = "F_IsEffective")]
diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_person_times_record/LqPersonTimesRecordEntity.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_person_times_record/LqPersonTimesRecordEntity.cs
index c75e06a..a12b00f 100644
--- a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_person_times_record/LqPersonTimesRecordEntity.cs
+++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_person_times_record/LqPersonTimesRecordEntity.cs
@@ -31,6 +31,12 @@ namespace NCC.Extend.Entitys.lq_person_times_record
public string BusinessType { get; set; }
///
+ /// 是否有开单(0-否,1-是)
+ ///
+ [SugarColumn(ColumnName = "F_HasBilling")]
+ public int HasBilling { get; set; } = 0;
+
+ ///
/// 人员类型(健康师/科技老师)
///
[SugarColumn(ColumnName = "F_PersonType")]
diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Enum/MemberTypeEnum.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Enum/MemberTypeEnum.cs
index fcd3a66..b760212 100644
--- a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Enum/MemberTypeEnum.cs
+++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Enum/MemberTypeEnum.cs
@@ -31,5 +31,10 @@ namespace NCC.Extend.Entitys.Enum
[Description("会员")]
会员 = 3,
+ ///
+ /// 归档
+ ///
+ [Description("归档")]
+ 归档 = 4,
}
}
\ No newline at end of file
diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Mapper/LqKhxxMapper.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Mapper/LqKhxxMapper.cs
index e068112..ac43793 100644
--- a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Mapper/LqKhxxMapper.cs
+++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Mapper/LqKhxxMapper.cs
@@ -12,6 +12,25 @@ namespace NCC.Extend.Entitys.Mapper.LqKhxx
{
// 由于khxf字段现在是string类型,不需要特殊的映射处理
// 使用默认的映射即可
+
+ // 配置实体到详情输出的映射(确保新字段正确映射)
+ config.ForType()
+ .Map(dest => dest.isBeautyMember, src => src.IsBeautyMember)
+ .Map(dest => dest.isMedicalMember, src => src.IsMedicalMember)
+ .Map(dest => dest.isTechMember, src => src.IsTechMember)
+ .Map(dest => dest.beautyMemberTime, src => src.BeautyMemberTime)
+ .Map(dest => dest.medicalMemberTime, src => src.MedicalMemberTime)
+ .Map(dest => dest.techMemberTime, src => src.TechMemberTime)
+ .Map(dest => dest.firstVisitTime, src => src.FirstVisitTime)
+ .Map(dest => dest.lastVisitTime, src => src.LastVisitTime)
+ .Map(dest => dest.visitDays, src => src.VisitDays)
+ .Map(dest => dest.sleepStartTime, src => src.SleepStartTime)
+ .Map(dest => dest.sleepDays, src => src.SleepDays)
+ .Map(dest => dest.totalConsumeAmount, src => src.TotalConsumeAmount)
+ .Map(dest => dest.totalBillingAmount, src => src.TotalBillingAmount)
+ .Map(dest => dest.remainingRightsAmount, src => src.RemainingRightsAmount)
+ .Map(dest => dest.consumeLevel, src => src.ConsumeLevel)
+ .Map(dest => dest.consumeLevelUpdateTime, src => src.ConsumeLevelUpdateTime);
}
}
}
diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqKhxxService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqKhxxService.cs
index 238389a..44e29ff 100644
--- a/netcore/src/Modularity/Extend/NCC.Extend/LqKhxxService.cs
+++ b/netcore/src/Modularity/Extend/NCC.Extend/LqKhxxService.cs
@@ -30,12 +30,14 @@ using NCC.Extend.Entitys.lq_xh_hyhk;
using NCC.Extend.Entitys.lq_xh_pxmx;
using NCC.Extend.Entitys.lq_xmzl;
using NCC.Extend.Interfaces.LqKhxx;
+using NCC.Extend.Entitys.Config;
using NCC.System.Entitys.Permission;
using NCC.FriendlyException;
using NCC.JsonSerialization;
using SqlSugar;
using Yitter.IdGenerator;
using NCC.Extend.Entitys.lq_kd_deductinfo;
+using Microsoft.AspNetCore.Authorization;
namespace NCC.Extend.LqKhxx
{
@@ -109,6 +111,9 @@ namespace NCC.Extend.LqKhxx
public async Task GetList([FromQuery] LqKhxxListQueryInput input)
{
var sidx = input.sidx == null ? "id" : input.sidx;
+ List queryZcsj = input.zcsj != null ? input.zcsj.Split(',').ToObeject>() : null;
+ DateTime? startZcsj = queryZcsj != null ? Ext.GetDateTime(queryZcsj.First()) : null;
+ DateTime? endZcsj = queryZcsj != null ? Ext.GetDateTime(queryZcsj.Last()) : null;
var data = await _db.Queryable()
.WhereIF(!string.IsNullOrEmpty(input.keyWord), p => p.Khmc.Contains(input.keyWord) || p.Sjh.Contains(input.keyWord) || p.Dah.Contains(input.keyWord))
.WhereIF(!string.IsNullOrEmpty(input.id), p => p.Id.Contains(input.id))
@@ -125,6 +130,8 @@ namespace NCC.Extend.LqKhxx
.WhereIF(!string.IsNullOrEmpty(input.jdqd), p => p.Jdqd.Contains(input.jdqd))
.WhereIF(!string.IsNullOrEmpty(input.lxdz), p => p.Lxdz.Contains(input.lxdz))
.WhereIF(!string.IsNullOrEmpty(input.bz), p => p.Bz.Contains(input.bz))
+ .WhereIF(queryZcsj != null, p => p.CreateTime >= new DateTime(startZcsj.ToDate().Year, startZcsj.ToDate().Month, startZcsj.ToDate().Day, 0, 0, 0))
+ .WhereIF(queryZcsj != null, p => p.CreateTime <= new DateTime(endZcsj.ToDate().Year, endZcsj.ToDate().Month, endZcsj.ToDate().Day, 23, 59, 59))
.Select(it => new LqKhxxListOutput
{
id = it.Id,
@@ -153,6 +160,22 @@ namespace NCC.Extend.LqKhxx
mainHealthUserName = SqlFunc.Subqueryable().Where(u => u.Id == it.MainHealthUser).Select(u => u.RealName),
subHealthUserName = SqlFunc.Subqueryable().Where(u => u.Id == it.SubHealthUser).Select(u => u.RealName),
tjrName = SqlFunc.Subqueryable().Where(u => u.Id == it.Tjr).Select(u => u.Khmc),
+ isBeautyMember = it.IsBeautyMember,
+ isMedicalMember = it.IsMedicalMember,
+ isTechMember = it.IsTechMember,
+ beautyMemberTime = it.BeautyMemberTime,
+ medicalMemberTime = it.MedicalMemberTime,
+ techMemberTime = it.TechMemberTime,
+ firstVisitTime = it.FirstVisitTime,
+ lastVisitTime = it.LastVisitTime,
+ visitDays = it.VisitDays,
+ sleepStartTime = it.SleepStartTime,
+ sleepDays = it.SleepDays,
+ totalConsumeAmount = it.TotalConsumeAmount,
+ totalBillingAmount = it.TotalBillingAmount,
+ remainingRightsAmount = it.RemainingRightsAmount,
+ consumeLevel = it.ConsumeLevel,
+ consumeLevelUpdateTime = it.ConsumeLevelUpdateTime
})
.MergeTable()
.OrderBy(sidx + " " + input.sort)
@@ -180,6 +203,7 @@ namespace NCC.Extend.LqKhxx
var entity = input.Adapt();
entity.Id = YitIdHelper.NextId().ToString();
entity.Dah = "LQ" + DateTime.Now.ToString("yyyyMMddHHmmssfff");
+ entity.Zcsj = DateTime.Now;
// 处理客户消费字段:将数组转换为逗号分隔的字符串
if (input.khxf != null && input.khxf.Count > 0)
{
@@ -1048,6 +1072,11 @@ namespace NCC.Extend.LqKhxx
memberQuery = memberQuery.Where(x => x.Gsmd == input.BelongStoreId);
}
+ if (input.MemberType >= 0)
+ {
+ memberQuery = memberQuery.Where(x => x.Khlx == input.MemberType.GetHashCode().ToString());
+ }
+
// 如果指定了开单门店、开单人、品项ID等条件,需要通过开单表进行筛选
if (!string.IsNullOrEmpty(input.BillingStoreId) ||
!string.IsNullOrEmpty(input.BillingUserId) ||
@@ -1070,13 +1099,9 @@ namespace NCC.Extend.LqKhxx
}
// 如果指定了品项ID,需要通过品项明细表进行筛选
- if (!string.IsNullOrEmpty(input.PurchaseItemId) ||
- !string.IsNullOrEmpty(input.GiftItemId) ||
- !string.IsNullOrEmpty(input.ExperienceItemId))
+ if (!string.IsNullOrEmpty(input.PurchaseItemId) || !string.IsNullOrEmpty(input.GiftItemId) || !string.IsNullOrEmpty(input.ExperienceItemId))
{
- var itemQuery = _db.Queryable()
- .Where(x => x.IsEffective == StatusEnum.有效.GetHashCode());
-
+ var itemQuery = _db.Queryable().Where(x => x.IsEffective == StatusEnum.有效.GetHashCode());
if (!string.IsNullOrEmpty(input.PurchaseItemId))
{
itemQuery = itemQuery.Where(x => x.Px == input.PurchaseItemId && x.SourceType == "购买");
@@ -1147,7 +1172,8 @@ namespace NCC.Extend.LqKhxx
x.Dah,
x.Khmc,
x.Sjh,
- x.Gsmd
+ x.Gsmd,
+ x.Khlx
})
.ToListAsync();
@@ -1328,6 +1354,7 @@ namespace NCC.Extend.LqKhxx
MemberCode = member.Dah ?? "",
MemberName = member.Khmc ?? "",
MemberPhone = member.Sjh ?? "",
+ MemberType = member.Khlx.ToInt(),
BelongStoreId = member.Gsmd ?? "",
BelongStoreName = stores.ContainsKey(member.Gsmd ?? "") ? stores[member.Gsmd] : ""
};
@@ -1531,5 +1558,507 @@ namespace NCC.Extend.LqKhxx
}
#endregion
+ #region 会员信息更新
+ ///
+ /// 更新会员信息(单个会员)
+ ///
+ /// 会员ID
+ ///
+ [HttpPost("UpdateMemberInfo")]
+ public async Task UpdateMemberInfo([FromBody] string memberId)
+ {
+ try
+ {
+ if (string.IsNullOrEmpty(memberId))
+ {
+ throw NCCException.Oh("会员ID不能为空");
+ }
+
+ var member = await _db.Queryable()
+ .Where(x => x.Id == memberId && x.IsEffective == StatusEnum.有效.GetHashCode())
+ .FirstAsync();
+
+ if (member == null)
+ {
+ throw NCCException.Oh("会员不存在");
+ }
+
+ await UpdateMemberInfoInternal(member);
+
+ return new { success = true, message = "更新成功" };
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "更新会员信息失败,会员ID:{MemberId}", memberId);
+ throw NCCException.Oh($"更新会员信息失败:{ex.Message}");
+ }
+ }
+
+ ///
+ /// 批量更新所有会员信息(高性能版:使用SQL批量更新)
+ ///
+ ///
+ [HttpPost("BatchUpdateMemberInfo")]
+ [AllowAnonymous]
+ public async Task BatchUpdateMemberInfo()
+ {
+ try
+ {
+ var startTime = DateTime.Now;
+ _logger.LogInformation("开始批量更新会员信息(高性能SQL版)");
+
+ // 1. 获取所有有效会员ID
+ var memberIds = await _db.Queryable()
+ .Where(x => x.IsEffective == StatusEnum.有效.GetHashCode())
+ .Select(x => x.Id)
+ .ToListAsync();
+
+ var totalCount = memberIds.Count;
+
+ if (totalCount == 0)
+ {
+ return new
+ {
+ success = true,
+ totalCount = 0,
+ successCount = 0,
+ failCount = 0,
+ duration = "0秒"
+ };
+ }
+
+ _logger.LogInformation("开始使用SQL批量更新,会员总数:{Total}", totalCount);
+ var sqlStartTime = DateTime.Now;
+
+ // 使用原生SQL一次性更新所有会员信息
+ var beautyThreshold = MemberInfoUpdateConfig.BeautyMemberAmountThreshold;
+ var medicalThreshold = MemberInfoUpdateConfig.MedicalMemberAmountThreshold;
+ var techThreshold = MemberInfoUpdateConfig.TechMemberAmountThreshold;
+ var minSleepThreshold = MemberInfoUpdateConfig.SleepDaysThresholds.Min();
+ var now = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
+
+ // 构建SQL更新语句
+ var updateSql = $@"
+UPDATE lq_khxx kh
+LEFT JOIN (
+ -- 生美会员判断
+ SELECT
+ pxmx.F_MemberId as MemberId,
+ CASE WHEN SUM(CASE WHEN pxmx.F_ItemCategory = '生美' THEN pxmx.F_ActualPrice ELSE 0 END) > {beautyThreshold} THEN 1 ELSE 0 END as IsBeautyMember,
+ MIN(CASE WHEN pxmx.F_ItemCategory = '生美' AND kd.kdrq IS NOT NULL THEN kd.kdrq ELSE NULL END) as BeautyMemberTime
+ FROM lq_kd_pxmx pxmx
+ INNER JOIN lq_kd_kdjlb kd ON pxmx.glkdbh = kd.F_Id
+ WHERE pxmx.F_IsEffective = 1
+ AND kd.F_IsEffective = 1
+ AND pxmx.F_ActualPrice > 0
+ AND pxmx.px != '61'
+ GROUP BY pxmx.F_MemberId
+) beauty ON kh.F_Id = beauty.MemberId
+LEFT JOIN (
+ -- 医美会员判断
+ SELECT
+ pxmx.F_MemberId as MemberId,
+ CASE WHEN SUM(CASE WHEN pxmx.F_ItemCategory = '医美' THEN pxmx.F_ActualPrice ELSE 0 END) > {medicalThreshold} THEN 1 ELSE 0 END as IsMedicalMember,
+ MIN(CASE WHEN pxmx.F_ItemCategory = '医美' AND kd.kdrq IS NOT NULL THEN kd.kdrq ELSE NULL END) as MedicalMemberTime
+ FROM lq_kd_pxmx pxmx
+ INNER JOIN lq_kd_kdjlb kd ON pxmx.glkdbh = kd.F_Id
+ WHERE pxmx.F_IsEffective = 1
+ AND kd.F_IsEffective = 1
+ AND pxmx.F_ActualPrice > 0
+ AND pxmx.px != '61'
+ GROUP BY pxmx.F_MemberId
+) medical ON kh.F_Id = medical.MemberId
+LEFT JOIN (
+ -- 科技部会员判断
+ SELECT
+ pxmx.F_MemberId as MemberId,
+ CASE WHEN MAX(CASE WHEN pxmx.F_ItemCategory = '科美' AND pxmx.F_ActualPrice >= {techThreshold} THEN 1 ELSE 0 END) = 1 THEN 1 ELSE 0 END as IsTechMember,
+ MIN(CASE WHEN pxmx.F_ItemCategory = '科美' AND pxmx.F_ActualPrice >= {techThreshold} AND kd.kdrq IS NOT NULL THEN kd.kdrq ELSE NULL END) as TechMemberTime
+ FROM lq_kd_pxmx pxmx
+ INNER JOIN lq_kd_kdjlb kd ON pxmx.glkdbh = kd.F_Id
+ WHERE pxmx.F_IsEffective = 1
+ AND kd.F_IsEffective = 1
+ AND pxmx.F_ActualPrice > 0
+ AND pxmx.px != '61'
+ GROUP BY pxmx.F_MemberId
+) tech ON kh.F_Id = tech.MemberId
+LEFT JOIN (
+ -- 到店天数、首次到店时间、最后到店时间
+ SELECT
+ xh.hy as MemberId,
+ COUNT(DISTINCT DATE(xh.hksj)) as VisitDays,
+ MIN(xh.hksj) as FirstVisitTime,
+ MAX(xh.hksj) as LastVisitTime
+ FROM lq_xh_hyhk xh
+ WHERE xh.F_IsEffective = 1
+ GROUP BY xh.hy
+) visit ON kh.F_Id = visit.MemberId
+LEFT JOIN (
+ -- 开卡总金额
+ SELECT
+ kd.kdhy as MemberId,
+ COALESCE(SUM(kd.sfyj), 0) as TotalBillingAmount
+ FROM lq_kd_kdjlb kd
+ WHERE kd.F_IsEffective = 1
+ GROUP BY kd.kdhy
+) billing ON kh.F_Id = billing.MemberId
+LEFT JOIN (
+ -- 耗卡总金额
+ SELECT
+ xh.hy as MemberId,
+ COALESCE(SUM(xh.xfje), 0) as TotalConsumeAmount
+ FROM lq_xh_hyhk xh
+ WHERE xh.F_IsEffective = 1
+ GROUP BY xh.hy
+) consume ON kh.F_Id = consume.MemberId
+LEFT JOIN (
+ -- 退款总金额
+ SELECT
+ tk.hy as MemberId,
+ COALESCE(SUM(tk.F_ActualRefundAmount), 0) as TotalRefundAmount
+ FROM lq_hytk_hytk tk
+ WHERE tk.F_IsEffective = 1
+ GROUP BY tk.hy
+) refund ON kh.F_Id = refund.MemberId
+SET
+ kh.F_IsBeautyMember = COALESCE(beauty.IsBeautyMember, 0),
+ kh.F_IsMedicalMember = COALESCE(medical.IsMedicalMember, 0),
+ kh.F_IsTechMember = COALESCE(tech.IsTechMember, 0),
+ kh.F_BeautyMemberTime = CASE WHEN kh.F_BeautyMemberTime IS NULL THEN beauty.BeautyMemberTime ELSE kh.F_BeautyMemberTime END,
+ kh.F_MedicalMemberTime = CASE WHEN kh.F_MedicalMemberTime IS NULL THEN medical.MedicalMemberTime ELSE kh.F_MedicalMemberTime END,
+ kh.F_TechMemberTime = CASE WHEN kh.F_TechMemberTime IS NULL THEN tech.TechMemberTime ELSE kh.F_TechMemberTime END,
+ kh.F_FirstVisitTime = visit.FirstVisitTime,
+ kh.F_LastVisitTime = visit.LastVisitTime,
+ kh.F_VisitDays = COALESCE(visit.VisitDays, 0),
+ kh.F_TotalBillingAmount = COALESCE(billing.TotalBillingAmount, 0),
+ kh.F_TotalConsumeAmount = COALESCE(consume.TotalConsumeAmount, 0),
+ kh.F_RemainingRightsAmount = GREATEST(0, COALESCE(billing.TotalBillingAmount, 0) - COALESCE(consume.TotalConsumeAmount, 0) - COALESCE(refund.TotalRefundAmount, 0)),
+ kh.F_SleepDays = GREATEST(0, DATEDIFF(CURDATE(), COALESCE(kh.F_LastConsumeTime, visit.LastVisitTime, kh.F_CreateTime))),
+ kh.F_SleepStartTime = CASE
+ WHEN DATEDIFF(CURDATE(), COALESCE(kh.F_LastConsumeTime, visit.LastVisitTime, kh.F_CreateTime)) >= {minSleepThreshold}
+ AND kh.F_SleepStartTime IS NULL
+ THEN DATE(COALESCE(kh.F_LastConsumeTime, visit.LastVisitTime, kh.F_CreateTime))
+ WHEN DATEDIFF(CURDATE(), COALESCE(kh.F_LastConsumeTime, visit.LastVisitTime, kh.F_CreateTime)) < {minSleepThreshold}
+ THEN NULL
+ ELSE kh.F_SleepStartTime
+ END,
+ kh.F_ConsumeLevel = CASE
+ WHEN COALESCE(consume.TotalConsumeAmount, 0) >= 200000 THEN 5
+ WHEN COALESCE(consume.TotalConsumeAmount, 0) >= 100000 THEN 4
+ WHEN COALESCE(consume.TotalConsumeAmount, 0) >= 50000 THEN 3
+ WHEN COALESCE(consume.TotalConsumeAmount, 0) >= 30000 THEN 2
+ WHEN COALESCE(consume.TotalConsumeAmount, 0) >= 10000 THEN 1
+ ELSE 0
+ END,
+ kh.F_ConsumeLevelUpdateTime = CASE
+ WHEN kh.F_ConsumeLevel != CASE
+ WHEN COALESCE(consume.TotalConsumeAmount, 0) >= 200000 THEN 5
+ WHEN COALESCE(consume.TotalConsumeAmount, 0) >= 100000 THEN 4
+ WHEN COALESCE(consume.TotalConsumeAmount, 0) >= 50000 THEN 3
+ WHEN COALESCE(consume.TotalConsumeAmount, 0) >= 30000 THEN 2
+ WHEN COALESCE(consume.TotalConsumeAmount, 0) >= 10000 THEN 1
+ ELSE 0
+ END THEN NOW()
+ WHEN kh.F_ConsumeLevelUpdateTime IS NULL AND CASE
+ WHEN COALESCE(consume.TotalConsumeAmount, 0) >= 200000 THEN 5
+ WHEN COALESCE(consume.TotalConsumeAmount, 0) >= 100000 THEN 4
+ WHEN COALESCE(consume.TotalConsumeAmount, 0) >= 50000 THEN 3
+ WHEN COALESCE(consume.TotalConsumeAmount, 0) >= 30000 THEN 2
+ WHEN COALESCE(consume.TotalConsumeAmount, 0) >= 10000 THEN 1
+ ELSE 0
+ END > 0 THEN NOW()
+ ELSE kh.F_ConsumeLevelUpdateTime
+ END
+WHERE kh.F_IsEffective = 1";
+
+ var affectedRows = await _db.Ado.ExecuteCommandAsync(updateSql);
+ var sqlEndTime = DateTime.Now;
+ var duration = (sqlEndTime - sqlStartTime).TotalSeconds;
+
+ _logger.LogInformation("SQL批量更新完成,影响行数:{Rows},耗时:{Duration}秒", affectedRows, duration);
+
+ var endTime = DateTime.Now;
+ var totalDuration = (endTime - startTime).TotalSeconds;
+
+ _logger.LogInformation("批量更新会员信息完成,总数量:{Total},影响行数:{Rows},耗时:{Duration}秒",
+ totalCount, affectedRows, totalDuration);
+
+ return new
+ {
+ success = true,
+ totalCount = totalCount,
+ successCount = affectedRows,
+ failCount = totalCount - affectedRows,
+ duration = $"{totalDuration:F2}秒",
+ sqlDuration = $"{duration:F2}秒"
+ };
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "批量更新会员信息失败");
+ throw NCCException.Oh($"批量更新会员信息失败:{ex.Message}");
+ }
+ }
+
+ ///
+ /// 更新会员信息内部方法
+ ///
+ /// 会员实体
+ private async Task UpdateMemberInfoInternal(LqKhxxEntity member)
+ {
+ // 1. 判断生美/医美/科技部会员
+ await UpdateMemberTypeFlags(member);
+
+ // 2. 计算到店天数
+ await UpdateVisitDays(member);
+
+ // 3. 计算沉睡天数
+ await UpdateSleepDays(member);
+
+ // 4. 计算消费金额和等级
+ await UpdateConsumeInfo(member);
+
+ // 5. 更新到数据库
+ await _db.Updateable(member)
+ .UpdateColumns(x => new
+ {
+ x.IsBeautyMember,
+ x.IsMedicalMember,
+ x.IsTechMember,
+ x.BeautyMemberTime,
+ x.MedicalMemberTime,
+ x.TechMemberTime,
+ x.FirstVisitTime,
+ x.LastVisitTime,
+ x.VisitDays,
+ x.SleepStartTime,
+ x.SleepDays,
+ x.TotalConsumeAmount,
+ x.TotalBillingAmount,
+ x.RemainingRightsAmount,
+ x.ConsumeLevel,
+ x.ConsumeLevelUpdateTime
+ })
+ .ExecuteCommandAsync();
+ }
+
+ ///
+ /// 更新会员类型标识(生美/医美/科技部会员)
+ ///
+ private async Task UpdateMemberTypeFlags(LqKhxxEntity member)
+ {
+ // 查询该会员的所有有效开单品项明细
+ var billingItems = await _db.Queryable()
+ .InnerJoin((pxmx, kd) => pxmx.Glkdbh == kd.Id)
+ .Where((pxmx, kd) =>
+ pxmx.MemberId == member.Id
+ && pxmx.IsEffective == StatusEnum.有效.GetHashCode()
+ && kd.IsEffective == StatusEnum.有效.GetHashCode()
+ && pxmx.ActualPrice > 0
+ && pxmx.Px != "61") // 排除女神卡
+ .Select((pxmx, kd) => new
+ {
+ pxmx.ItemCategory,
+ pxmx.ActualPrice,
+ pxmx.Pxjg,
+ kd.Kdrq
+ })
+ .ToListAsync();
+
+ // 判断生美会员:开卡品项为生美并且金额大于阈值
+ var beautyAmount = billingItems
+ .Where(x => x.ItemCategory == "生美")
+ .Sum(x => x.ActualPrice);
+
+ if (beautyAmount > MemberInfoUpdateConfig.BeautyMemberAmountThreshold)
+ {
+ member.IsBeautyMember = 1;
+ if (!member.BeautyMemberTime.HasValue)
+ {
+ var firstBeautyBilling = billingItems
+ .Where(x => x.ItemCategory == "生美")
+ .OrderBy(x => x.Kdrq)
+ .FirstOrDefault();
+ member.BeautyMemberTime = firstBeautyBilling?.Kdrq;
+ }
+ }
+ else
+ {
+ member.IsBeautyMember = 0;
+ member.BeautyMemberTime = null;
+ }
+
+ // 判断医美会员:开卡品项为医美并且金额大于阈值
+ var medicalAmount = billingItems
+ .Where(x => x.ItemCategory == "医美")
+ .Sum(x => x.ActualPrice);
+
+ if (medicalAmount > MemberInfoUpdateConfig.MedicalMemberAmountThreshold)
+ {
+ member.IsMedicalMember = 1;
+ if (!member.MedicalMemberTime.HasValue)
+ {
+ var firstMedicalBilling = billingItems
+ .Where(x => x.ItemCategory == "医美")
+ .OrderBy(x => x.Kdrq)
+ .FirstOrDefault();
+ member.MedicalMemberTime = firstMedicalBilling?.Kdrq;
+ }
+ }
+ else
+ {
+ member.IsMedicalMember = 0;
+ member.MedicalMemberTime = null;
+ }
+
+ // 判断科技部会员:科美单品项金额大于等于阈值
+ var hasTechMember = billingItems
+ .Where(x => x.ItemCategory == "科美")
+ .Any(x => x.ActualPrice >= MemberInfoUpdateConfig.TechMemberAmountThreshold);
+
+ if (hasTechMember)
+ {
+ member.IsTechMember = 1;
+ if (!member.TechMemberTime.HasValue)
+ {
+ var firstTechBilling = billingItems
+ .Where(x => x.ItemCategory == "科美" && x.ActualPrice >= MemberInfoUpdateConfig.TechMemberAmountThreshold)
+ .OrderBy(x => x.Kdrq)
+ .FirstOrDefault();
+ member.TechMemberTime = firstTechBilling?.Kdrq;
+ }
+ }
+ else
+ {
+ member.IsTechMember = 0;
+ member.TechMemberTime = null;
+ }
+ }
+
+ ///
+ /// 更新到店天数
+ ///
+ private async Task UpdateVisitDays(LqKhxxEntity member)
+ {
+ // 查询该会员的所有有效耗卡记录,按天去重
+ var visitDates = await _db.Queryable()
+ .Where(x =>
+ x.Hy == member.Id
+ && x.IsEffective == StatusEnum.有效.GetHashCode())
+ .Select(x => new
+ {
+ VisitDate = SqlFunc.ToDate(x.Hksj)
+ })
+ .Distinct()
+ .ToListAsync();
+
+ member.VisitDays = visitDates.Count;
+
+ // 更新首次到店时间和最后到店时间
+ if (visitDates.Any())
+ {
+ var firstVisit = await _db.Queryable()
+ .Where(x => x.Hy == member.Id && x.IsEffective == StatusEnum.有效.GetHashCode())
+ .OrderBy(x => x.Hksj)
+ .Select(x => x.Hksj)
+ .FirstAsync();
+ member.FirstVisitTime = firstVisit;
+
+ var lastVisit = await _db.Queryable()
+ .Where(x => x.Hy == member.Id && x.IsEffective == StatusEnum.有效.GetHashCode())
+ .OrderBy(x => x.Hksj, OrderByType.Desc)
+ .Select(x => x.Hksj)
+ .FirstAsync();
+ member.LastVisitTime = lastVisit;
+ }
+ else
+ {
+ member.FirstVisitTime = null;
+ member.LastVisitTime = null;
+ }
+ }
+
+ ///
+ /// 更新沉睡天数
+ ///
+ private Task UpdateSleepDays(LqKhxxEntity member)
+ {
+ var now = DateTime.Now;
+ var lastConsumeTime = member.LastConsumeTime;
+
+ if (!lastConsumeTime.HasValue)
+ {
+ // 如果没有最后消费时间,使用最后到店时间
+ lastConsumeTime = member.LastVisitTime;
+ }
+
+ if (lastConsumeTime.HasValue)
+ {
+ var daysSinceLastConsume = (now.Date - lastConsumeTime.Value.Date).Days;
+ member.SleepDays = Math.Max(0, daysSinceLastConsume);
+
+ // 如果超过最小沉睡阈值,设置沉睡开始时间
+ var minSleepThreshold = MemberInfoUpdateConfig.SleepDaysThresholds.Min();
+ if (member.SleepDays >= minSleepThreshold)
+ {
+ if (!member.SleepStartTime.HasValue)
+ {
+ member.SleepStartTime = lastConsumeTime.Value.Date;
+ }
+ }
+ else
+ {
+ member.SleepStartTime = null;
+ }
+ }
+ else
+ {
+ member.SleepDays = 0;
+ member.SleepStartTime = null;
+ }
+
+ return Task.CompletedTask;
+ }
+
+ ///
+ /// 更新消费信息(开卡总金额、耗卡总金额、剩余权益总金额、消费等级)
+ ///
+ private async Task UpdateConsumeInfo(LqKhxxEntity member)
+ {
+ // 1. 计算开卡总金额(累计所有开单的实付业绩总和)
+ var totalBillingAmount = await _db.Queryable().Where(x => x.Kdhy == member.Id && x.IsEffective == StatusEnum.有效.GetHashCode()).SumAsync(x => (decimal?)x.Sfyj) ?? 0m;
+ member.TotalBillingAmount = totalBillingAmount;
+
+ // 2. 计算耗卡总金额(累计所有耗卡的金额总和)
+ var totalConsumeAmount = await _db.Queryable().Where(x => x.Hy == member.Id && x.IsEffective == StatusEnum.有效.GetHashCode()).SumAsync(x => (decimal?)x.Xfje) ?? 0m;
+ member.TotalConsumeAmount = totalConsumeAmount;
+
+ // 3. 计算退款总金额
+ var totalRefundAmount = await _db.Queryable().Where(x => x.Hy == member.Id && x.IsEffective == StatusEnum.有效.GetHashCode()).SumAsync(x => (decimal?)x.ActualRefundAmount) ?? 0m;
+
+ // 4. 计算剩余权益总金额
+ member.RemainingRightsAmount = totalBillingAmount - totalConsumeAmount - totalRefundAmount;
+ if (member.RemainingRightsAmount < 0)
+ {
+ member.RemainingRightsAmount = 0m;
+ }
+
+ // 5. 计算消费等级(根据耗卡总金额)
+ var oldLevel = member.ConsumeLevel;
+ member.ConsumeLevel = MemberInfoUpdateConfig.GetConsumeLevel(totalConsumeAmount);
+
+ // 如果等级发生变化,更新等级更新时间
+ if (oldLevel != member.ConsumeLevel)
+ {
+ member.ConsumeLevelUpdateTime = DateTime.Now;
+ }
+ else if (!member.ConsumeLevelUpdateTime.HasValue && member.ConsumeLevel > 0)
+ {
+ // 如果之前没有等级更新时间,但当前有等级,设置当前时间
+ member.ConsumeLevelUpdateTime = DateTime.Now;
+ }
+ }
+ #endregion
+
}
}
diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqTkjlbService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqTkjlbService.cs
index d43be6e..5c106cc 100644
--- a/netcore/src/Modularity/Extend/NCC.Extend/LqTkjlbService.cs
+++ b/netcore/src/Modularity/Extend/NCC.Extend/LqTkjlbService.cs
@@ -192,6 +192,7 @@ namespace NCC.Extend.LqTkjlb
MemberInfo.Khlx = MemberTypeEnum.线索.GetHashCode().ToString();
MemberInfo.Dah = MemberNumber;
MemberInfo.Jdqd = "19.9卡";
+ MemberInfo.Zcsj = DateTime.Now;
//找到input.expansionUserId的用户信息
var userInfo = await _db.Queryable().Where(u => u.Id == input.expansionUserId).FirstAsync();
diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqXhHyhkService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqXhHyhkService.cs
index df2e9a2..781dce3 100644
--- a/netcore/src/Modularity/Extend/NCC.Extend/LqXhHyhkService.cs
+++ b/netcore/src/Modularity/Extend/NCC.Extend/LqXhHyhkService.cs
@@ -32,6 +32,7 @@ using NCC.Extend.Entitys.lq_xh_jksyj;
using NCC.Extend.Entitys.lq_xh_kjbsyj;
using NCC.Extend.Entitys.lq_xh_pxmx;
using NCC.Extend.Entitys.lq_xmzl;
+using NCC.Extend.Entitys.lq_mdxx;
using NCC.Extend.Interfaces.LqXhHyhk;
using NCC.FriendlyException;
using NCC.JsonSerialization;
@@ -899,6 +900,11 @@ namespace NCC.Extend.LqXhHyhk
{
memberInfo.Khlx = MemberTypeEnum.新客.GetHashCode().ToString();
}
+ //判断消费金额是否大于0
+ if (entity.Sgfy > 0)
+ {
+ memberInfo.LastConsumeTime = DateTime.Now;
+ }
//保存会员信息
await _db.Updateable(memberInfo).ExecuteCommandAsync();
// 新增会员耗卡记录
@@ -1003,6 +1009,8 @@ namespace NCC.Extend.LqXhHyhk
}
}
}
+ //查询开单记录
+ var billingRecord = await _db.Queryable().Where(x => x.Kdhy == entity.Hy && x.IsEffective == StatusEnum.有效.GetHashCode() && x.Sfyj > 0).AnyAsync();
// 剔除所有T区健康师(姓名中包含"T区"的健康师)
var NoTallJksyjEntities = allJksyjEntities.Where(x => x.Jks != null && x.Jks != "" && (x.Jksxm == null || !x.Jksxm.Contains("T区"))).ToList();
//获取这次耗卡有多少个健康师
@@ -1025,6 +1033,7 @@ namespace NCC.Extend.LqXhHyhk
Quantity = 1 / (decimal)jksCount,
CreateTime = DateTime.Now,
IsEffective = StatusEnum.有效.GetHashCode(),
+ HasBilling = billingRecord ? 1 : 0,
};
allPersonTimesRecordEntities.Add(personTimesRecordEntity);
}
@@ -1050,6 +1059,7 @@ namespace NCC.Extend.LqXhHyhk
Quantity = 1 / (decimal)kjbCount,
CreateTime = DateTime.Now,
IsEffective = StatusEnum.有效.GetHashCode(),
+ HasBilling = billingRecord ? 1 : 0,
};
allPersonTimesRecordEntities.Add(personTimesRecordEntity);
}
@@ -1894,5 +1904,180 @@ namespace NCC.Extend.LqXhHyhk
}
#endregion
+ #region 耗卡品项明细列表
+ ///
+ /// 获取耗卡品项明细记录列表
+ ///
+ ///
+ /// 根据多种条件查询耗卡品项明细记录列表,支持分页、排序、筛选等功能
+ ///
+ /// 示例请求:
+ /// GET /api/Extend/LqXhHyhk/consume-item-detail-list?currentPage=1&pageSize=10&startTime=2025-01-01&endTime=2025-01-31
+ ///
+ /// 传入参数说明:
+ /// - currentPage: 当前页码(必填,默认1)
+ /// - pageSize: 每页数量(必填,默认10)
+ /// - sidx: 排序字段(可选,默认"yjsj")
+ /// - sort: 排序方式(可选,ASC/DESC,默认DESC)
+ /// - Id: 品项明细ID(可选,精确匹配)
+ /// - ConsumeId: 耗卡记录ID(可选,精确匹配)
+ /// - StartConsumeTime/EndConsumeTime: 耗卡时间范围(可选,格式:yyyy-MM-dd)
+ /// - startTime/endTime: 耗卡时间范围(兼容参数名,格式:yyyy-MM-dd)
+ /// - MemberId: 会员ID(可选,精确匹配)
+ /// - MemberName: 会员名称(可选,模糊查询)
+ /// - MemberPhone: 会员手机号(可选,精确匹配)
+ /// - ItemId: 品项ID(可选,精确匹配)
+ /// - ItemName: 品项名称(可选,模糊查询)
+ /// - ItemType: 品项类型(可选,精确匹配)
+ /// - SourceType: 来源类型(可选,精确匹配)
+ /// - StoreId: 门店ID(可选,精确匹配)
+ ///
+ /// 返回字段说明:
+ /// - id: 品项明细ID
+ /// - consumeId: 耗卡记录ID
+ /// - consumeTime: 耗卡时间(DateTime格式)
+ /// - memberName: 会员名称(关联会员表)
+ /// - memberPhone: 会员手机号(关联会员表)
+ /// - itemName: 品项名称(品项明细表字段)
+ /// - itemType: 品项类型(关联项目资料表的qt2字段)
+ /// - itemPrice: 品项价格(decimal类型)
+ /// - projectNumber: 项目次数(decimal类型,支持小数)
+ /// - originalProjectNumber: 原始项目次数(decimal类型)
+ /// - totalPrice: 合计金额(decimal类型)
+ /// - sourceType: 来源类型(字符串,如:购买、赠送、体验)
+ /// - storeId: 门店ID(关联耗卡记录表的门店ID)
+ /// - storeName: 门店名称(关联门店表的店名字段)
+ ///
+ /// 查询参数
+ /// 耗卡品项明细记录列表(分页)
+ /// 查询成功,返回耗卡品项明细记录列表
+ /// 参数错误,如页码或页大小无效
+ /// 服务器错误,查询过程中发生异常
+ [HttpGet("consume-item-detail-list")]
+ public async Task GetConsumeItemDetailList([FromQuery] ConsumeItemDetailListQueryInput input)
+ {
+ try
+ {
+ var sidx = input.sidx == null ? "yjsj" : input.sidx;
+ var sort = string.IsNullOrEmpty(input.sort) ? "DESC" : input.sort;
+
+ // 处理耗卡时间范围(兼容 StartConsumeTime/EndConsumeTime 和 startTime/endTime 两种参数名)
+ DateTime? startConsumeTime = null;
+ DateTime? endConsumeTime = null;
+
+ // 优先使用 StartConsumeTime/EndConsumeTime,如果没有则使用 startTime/endTime
+ string startTimeStr = !string.IsNullOrEmpty(input.StartConsumeTime) ? input.StartConsumeTime : input.startTime;
+ string endTimeStr = !string.IsNullOrEmpty(input.EndConsumeTime) ? input.EndConsumeTime : input.endTime;
+
+ if (!string.IsNullOrEmpty(startTimeStr) && !string.IsNullOrEmpty(endTimeStr))
+ {
+ // 尝试解析日期字符串(支持多种格式)
+ if (DateTime.TryParse(startTimeStr, out DateTime startDate))
+ {
+ startConsumeTime = startDate;
+ }
+ else
+ {
+ throw NCCException.Oh($"开始时间格式错误:{startTimeStr}");
+ }
+
+ if (DateTime.TryParse(endTimeStr, out DateTime endDate))
+ {
+ endConsumeTime = endDate;
+ }
+ else
+ {
+ throw NCCException.Oh($"结束时间格式错误:{endTimeStr}");
+ }
+ }
+
+ // 优化查询:先分页查询主表,再批量查询关联数据,避免子查询性能问题
+ // 1. 先构建基础查询条件
+ var baseQuery = _db.Queryable()
+ .Where(pxmx => pxmx.IsEffective == StatusEnum.有效.GetHashCode())
+ .WhereIF(!string.IsNullOrEmpty(input.Id), pxmx => pxmx.Id == input.Id)
+ .WhereIF(!string.IsNullOrEmpty(input.ConsumeId), pxmx => pxmx.ConsumeInfoId == input.ConsumeId)
+ .WhereIF(startConsumeTime.HasValue, pxmx => pxmx.Yjsj >= new DateTime(startConsumeTime.Value.Year, startConsumeTime.Value.Month, startConsumeTime.Value.Day, 0, 0, 0))
+ .WhereIF(endConsumeTime.HasValue, pxmx => pxmx.Yjsj <= new DateTime(endConsumeTime.Value.Year, endConsumeTime.Value.Month, endConsumeTime.Value.Day, 23, 59, 59))
+ .WhereIF(!string.IsNullOrEmpty(input.MemberId), pxmx => pxmx.MemberId == input.MemberId)
+ .WhereIF(!string.IsNullOrEmpty(input.ItemId), pxmx => pxmx.Px == input.ItemId)
+ .WhereIF(!string.IsNullOrEmpty(input.ItemName), pxmx => pxmx.Pxmc != null && pxmx.Pxmc.Contains(input.ItemName))
+ .WhereIF(!string.IsNullOrEmpty(input.SourceType), pxmx => pxmx.SourceType == input.SourceType)
+ .WhereIF(!string.IsNullOrEmpty(input.ItemType), pxmx => pxmx.ItemCategory == input.ItemType);
+
+ // 2. 通过 EXISTS 子查询筛选关联字段(在分页前筛选,确保分页准确)
+ baseQuery = baseQuery.WhereIF(!string.IsNullOrEmpty(input.MemberName), pxmx => SqlFunc.Subqueryable().Where(x => x.Id == pxmx.MemberId && x.Khmc != null && x.Khmc.Contains(input.MemberName)).Any())
+ .WhereIF(!string.IsNullOrEmpty(input.MemberPhone), pxmx => SqlFunc.Subqueryable().Where(x => x.Id == pxmx.MemberId && x.Sjh == input.MemberPhone).Any())
+ .WhereIF(!string.IsNullOrEmpty(input.StoreId), pxmx => SqlFunc.Subqueryable().Where(x => x.Id == pxmx.ConsumeInfoId && x.Md == input.StoreId).Any());
+
+ // 3. 先分页查询主表数据(查询实体类,提高性能)
+ var pagedData = await baseQuery.OrderBy(sidx + " " + sort).ToPagedListAsync(input.currentPage, input.pageSize);
+
+ // 4. 批量查询关联数据
+ var itemIds = pagedData.list.Select(x => x.Id).ToList();
+ var memberIds = pagedData.list.Where(x => !string.IsNullOrEmpty(x.MemberId)).Select(x => x.MemberId).Distinct().ToList();
+ var consumeIds = pagedData.list.Where(x => !string.IsNullOrEmpty(x.ConsumeInfoId)).Select(x => x.ConsumeInfoId).Distinct().ToList();
+
+ // 批量查询会员信息
+ var memberDict = new Dictionary();
+ if (memberIds.Any())
+ {
+ var members = await _db.Queryable().Where(x => memberIds.Contains(x.Id)).Select(x => new { x.Id, x.Khmc, x.Sjh }).ToListAsync();
+ memberDict = members.ToDictionary(x => x.Id, x => (x.Khmc ?? "", x.Sjh ?? ""));
+ }
+
+ // 批量查询耗卡记录,获取门店ID和耗卡时间
+ var consumeDict = new Dictionary();
+ if (consumeIds.Any())
+ {
+ var consumes = await _db.Queryable().Where(x => consumeIds.Contains(x.Id)).Select(x => new { x.Id, x.Md, x.Hksj }).ToListAsync();
+ consumeDict = consumes.ToDictionary(x => x.Id, x => (x.Md ?? "", x.Hksj));
+ }
+
+ // 批量查询门店信息
+ var storeDict = new Dictionary();
+ var storeIds = consumeDict.Values.Where(x => !string.IsNullOrEmpty(x.StoreId)).Select(x => x.StoreId).Distinct().ToList();
+ if (storeIds.Any())
+ {
+ var stores = await _db.Queryable().Where(x => storeIds.Contains(x.Id)).Select(x => new { x.Id, x.Dm }).ToListAsync();
+ storeDict = stores.ToDictionary(x => x.Id, x => x.Dm ?? "");
+ }
+
+ // 5. 组装返回数据
+ var resultList = pagedData.list.Select(pxmx => new ConsumeItemDetailListOutput
+ {
+ id = pxmx.Id,
+ consumeId = pxmx.ConsumeInfoId,
+ consumeTime = pxmx.ConsumeInfoId != null && consumeDict.ContainsKey(pxmx.ConsumeInfoId) ? consumeDict[pxmx.ConsumeInfoId].ConsumeTime : pxmx.Yjsj,
+ memberName = pxmx.MemberId != null && memberDict.ContainsKey(pxmx.MemberId) ? memberDict[pxmx.MemberId].Name : "",
+ memberPhone = pxmx.MemberId != null && memberDict.ContainsKey(pxmx.MemberId) ? memberDict[pxmx.MemberId].Phone : "",
+ itemName = pxmx.Pxmc,
+ itemType = pxmx.ItemCategory,
+ itemPrice = pxmx.Pxjg,
+ projectNumber = pxmx.ProjectNumber,
+ originalProjectNumber = pxmx.OriginalProjectNumber,
+ totalPrice = pxmx.TotalPrice,
+ sourceType = pxmx.SourceType,
+ storeId = pxmx.ConsumeInfoId != null && consumeDict.ContainsKey(pxmx.ConsumeInfoId) ? consumeDict[pxmx.ConsumeInfoId].StoreId : "",
+ storeName = pxmx.ConsumeInfoId != null && consumeDict.ContainsKey(pxmx.ConsumeInfoId) && !string.IsNullOrEmpty(consumeDict[pxmx.ConsumeInfoId].StoreId) && storeDict.ContainsKey(consumeDict[pxmx.ConsumeInfoId].StoreId) ? storeDict[consumeDict[pxmx.ConsumeInfoId].StoreId] : ""
+ }).ToList();
+
+ // 6. 返回分页结果
+ var result = new SqlSugarPagedList
+ {
+ list = resultList,
+ pagination = pagedData.pagination
+ };
+
+ return PageResult.SqlSugarPageResult(result);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, $"获取耗卡品项明细记录列表失败: {ex.ToString()}");
+ throw NCCException.Oh(ErrorCode.COM1005, $"获取耗卡品项明细记录列表失败: {ex.Message}");
+ }
+ }
+ #endregion
+
}
}
diff --git a/sql/添加人次记录表字段.sql b/sql/添加人次记录表字段.sql
new file mode 100644
index 0000000..fdbe586
--- /dev/null
+++ b/sql/添加人次记录表字段.sql
@@ -0,0 +1,18 @@
+-- ============================================
+-- 为人次记录表(lq_person_times_record)添加字段
+-- ============================================
+-- 说明:添加"是否有开单"字段,用于标识该人次记录是否关联开单业务
+-- ============================================
+
+-- 添加"是否有开单"字段 (0-否, 1-是)
+ALTER TABLE `lq_person_times_record`
+ADD COLUMN `F_HasBilling` INT(11) DEFAULT 0 COMMENT '是否有开单(0-否,1-是)' AFTER `F_BusinessType`;
+
+-- ============================================
+-- 验证字段添加
+-- ============================================
+-- SELECT COLUMN_NAME, DATA_TYPE, IS_NULLABLE, COLUMN_KEY, COLUMN_DEFAULT, COLUMN_COMMENT
+-- FROM INFORMATION_SCHEMA.COLUMNS
+-- WHERE TABLE_NAME = 'lq_person_times_record'
+-- AND COLUMN_NAME = 'F_HasBilling';
+
diff --git a/sql/添加会员信息表字段.sql b/sql/添加会员信息表字段.sql
new file mode 100644
index 0000000..ef6721c
--- /dev/null
+++ b/sql/添加会员信息表字段.sql
@@ -0,0 +1,87 @@
+-- 为会员信息表(lq_khxx)添加新字段
+-- 执行时间:2025-01-XX
+
+-- ============================================
+-- 一、会员类型标识字段
+-- ============================================
+
+-- 1. 是否生美会员 (0-否, 1-是)
+ALTER TABLE `lq_khxx`
+ADD COLUMN `F_IsBeautyMember` INT(11) DEFAULT 0 COMMENT '是否生美会员(0-否,1-是)' AFTER `F_LastConsumeTime`;
+
+-- 2. 是否医美会员 (0-否, 1-是)
+ALTER TABLE `lq_khxx`
+ADD COLUMN `F_IsMedicalMember` INT(11) DEFAULT 0 COMMENT '是否医美会员(0-否,1-是)' AFTER `F_IsBeautyMember`;
+
+-- 3. 是否科技部会员 (0-否, 1-是)
+ALTER TABLE `lq_khxx`
+ADD COLUMN `F_IsTechMember` INT(11) DEFAULT 0 COMMENT '是否科技部会员(0-否,1-是)' AFTER `F_IsMedicalMember`;
+
+-- ============================================
+-- 二、会员类型成为时间字段(记录成为各类会员的时间)
+-- ============================================
+
+-- 4. 成为生美会员时间
+ALTER TABLE `lq_khxx`
+ADD COLUMN `F_BeautyMemberTime` DATETIME DEFAULT NULL COMMENT '成为生美会员时间' AFTER `F_IsTechMember`;
+
+-- 5. 成为医美会员时间
+ALTER TABLE `lq_khxx`
+ADD COLUMN `F_MedicalMemberTime` DATETIME DEFAULT NULL COMMENT '成为医美会员时间' AFTER `F_BeautyMemberTime`;
+
+-- 6. 成为科技部会员时间
+ALTER TABLE `lq_khxx`
+ADD COLUMN `F_TechMemberTime` DATETIME DEFAULT NULL COMMENT '成为科技部会员时间' AFTER `F_MedicalMemberTime`;
+
+-- ============================================
+-- 三、到店相关字段(用于计算到店天数)
+-- ============================================
+
+-- 7. 首次到店时间
+ALTER TABLE `lq_khxx`
+ADD COLUMN `F_FirstVisitTime` DATETIME DEFAULT NULL COMMENT '首次到店时间' AFTER `F_TechMemberTime`;
+
+-- 8. 最后到店时间
+ALTER TABLE `lq_khxx`
+ADD COLUMN `F_LastVisitTime` DATETIME DEFAULT NULL COMMENT '最后到店时间' AFTER `F_FirstVisitTime`;
+
+-- 9. 到店天数 (整数,记录会员到店的天数)
+ALTER TABLE `lq_khxx`
+ADD COLUMN `F_VisitDays` INT(11) DEFAULT 0 COMMENT '到店天数' AFTER `F_LastVisitTime`;
+
+-- ============================================
+-- 四、沉睡相关字段(用于计算沉睡天数)
+-- ============================================
+
+-- 10. 沉睡开始时间
+ALTER TABLE `lq_khxx`
+ADD COLUMN `F_SleepStartTime` DATETIME DEFAULT NULL COMMENT '沉睡开始时间' AFTER `F_VisitDays`;
+
+-- 11. 沉睡天数 (整数,记录会员沉睡的天数)
+ALTER TABLE `lq_khxx`
+ADD COLUMN `F_SleepDays` INT(11) DEFAULT 0 COMMENT '沉睡天数' AFTER `F_SleepStartTime`;
+
+-- ============================================
+-- 五、消费相关字段(用于计算消费等级)
+-- ============================================
+
+-- 12. 耗卡总金额 (累计所有耗卡的金额总和,从lq_xh_hyhk表的xfje字段统计)
+ALTER TABLE `lq_khxx`
+ADD COLUMN `F_TotalConsumeAmount` DECIMAL(18,2) DEFAULT 0.00 COMMENT '耗卡总金额(累计所有耗卡的金额总和)' AFTER `F_SleepDays`;
+
+-- 13. 开卡总金额 (累计所有开单的实付业绩总和,从lq_kd_kdjlb表的sfyj字段统计)
+ALTER TABLE `lq_khxx`
+ADD COLUMN `F_TotalBillingAmount` DECIMAL(18,2) DEFAULT 0.00 COMMENT '开卡总金额(累计所有开单的实付业绩总和)' AFTER `F_TotalConsumeAmount`;
+
+-- 14. 剩余权益总金额 (开卡总金额 - 耗卡总金额 - 退款金额)
+ALTER TABLE `lq_khxx`
+ADD COLUMN `F_RemainingRightsAmount` DECIMAL(18,2) DEFAULT 0.00 COMMENT '剩余权益总金额(开卡总金额-耗卡总金额-退款金额)' AFTER `F_TotalBillingAmount`;
+
+-- 15. 消费等级 (整数,记录会员的消费等级,如:0-普通, 1-银卡, 2-金卡, 3-钻石等)
+ALTER TABLE `lq_khxx`
+ADD COLUMN `F_ConsumeLevel` INT(11) DEFAULT 0 COMMENT '消费等级(0-普通,1-银卡,2-金卡,3-钻石等)' AFTER `F_RemainingRightsAmount`;
+
+-- 16. 消费等级更新时间
+ALTER TABLE `lq_khxx`
+ADD COLUMN `F_ConsumeLevelUpdateTime` DATETIME DEFAULT NULL COMMENT '消费等级更新时间' AFTER `F_ConsumeLevel`;
+