Commit b26a469f7479eb0cc7064902101f98c975b3131e
1 parent
b5ed92da
feat: 添加耗卡品项明细查询接口和人次记录表字段
- 新增耗卡品项明细查询接口(GetConsumeItemDetailList),支持分页和多条件筛选 - 为人次记录表(lq_person_times_record)添加'是否有开单'字段(F_HasBilling) - 更新相关实体类和DTO,支持新字段 - 添加SQL脚本用于数据库字段添加
Showing
20 changed files
with
1406 additions
and
9 deletions
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Config/MemberInfoUpdateConfig.cs
0 → 100644
| 1 | +using System.Collections.Generic; | |
| 2 | + | |
| 3 | +namespace NCC.Extend.Entitys.Config | |
| 4 | +{ | |
| 5 | + /// <summary> | |
| 6 | + /// 会员信息更新配置 | |
| 7 | + /// </summary> | |
| 8 | + public static class MemberInfoUpdateConfig | |
| 9 | + { | |
| 10 | + /// <summary> | |
| 11 | + /// 生美会员判断金额阈值(元) | |
| 12 | + /// </summary> | |
| 13 | + public static decimal BeautyMemberAmountThreshold { get; set; } = 500m; | |
| 14 | + | |
| 15 | + /// <summary> | |
| 16 | + /// 医美会员判断金额阈值(元) | |
| 17 | + /// </summary> | |
| 18 | + public static decimal MedicalMemberAmountThreshold { get; set; } = 3000m; | |
| 19 | + | |
| 20 | + /// <summary> | |
| 21 | + /// 科技部会员判断金额阈值(元) | |
| 22 | + /// </summary> | |
| 23 | + public static decimal TechMemberAmountThreshold { get; set; } = 3800m; | |
| 24 | + | |
| 25 | + /// <summary> | |
| 26 | + /// 沉睡天数阈值列表(天) | |
| 27 | + /// </summary> | |
| 28 | + public static List<int> SleepDaysThresholds { get; set; } = new List<int> { 60, 90, 180, 360 }; | |
| 29 | + | |
| 30 | + /// <summary> | |
| 31 | + /// 消费等级配置(金额阈值,单位:元) | |
| 32 | + /// Key: 等级编号(0-普通, 1-银卡, 2-金卡, 3-钻石等) | |
| 33 | + /// Value: 金额阈值 | |
| 34 | + /// </summary> | |
| 35 | + public static Dictionary<int, decimal> ConsumeLevelThresholds { get; set; } = new Dictionary<int, decimal> | |
| 36 | + { | |
| 37 | + { 0, 0m }, // D: 小于 1 万 | |
| 38 | + { 1, 10000m }, // C: 1 万 | |
| 39 | + { 2, 30000m }, // B: 3 万 | |
| 40 | + { 3, 50000m }, // A: 5 万 | |
| 41 | + { 4, 100000m }, // A+: 10 万 | |
| 42 | + { 5, 200000m } // A++: 20 万 | |
| 43 | + }; | |
| 44 | + | |
| 45 | + /// <summary> | |
| 46 | + /// 消费等级名称映射 | |
| 47 | + /// </summary> | |
| 48 | + public static Dictionary<int, string> ConsumeLevelNames { get; set; } = new Dictionary<int, string> | |
| 49 | + { | |
| 50 | + { 0, "D" }, | |
| 51 | + { 1, "C" }, | |
| 52 | + { 2, "B" }, | |
| 53 | + { 3, "A" }, | |
| 54 | + { 4, "A+" }, | |
| 55 | + { 5, "A++" } | |
| 56 | + }; | |
| 57 | + | |
| 58 | + /// <summary> | |
| 59 | + /// 根据消费金额获取消费等级 | |
| 60 | + /// </summary> | |
| 61 | + /// <param name="amount">消费金额</param> | |
| 62 | + /// <returns>消费等级编号</returns> | |
| 63 | + public static int GetConsumeLevel(decimal amount) | |
| 64 | + { | |
| 65 | + // 从高到低遍历,找到第一个满足条件的等级 | |
| 66 | + for (int i = ConsumeLevelThresholds.Count - 1; i >= 0; i--) | |
| 67 | + { | |
| 68 | + if (amount >= ConsumeLevelThresholds[i]) | |
| 69 | + { | |
| 70 | + return i; | |
| 71 | + } | |
| 72 | + } | |
| 73 | + return 0; // 默认返回最低等级 | |
| 74 | + } | |
| 75 | + | |
| 76 | + /// <summary> | |
| 77 | + /// 根据消费金额获取消费等级名称 | |
| 78 | + /// </summary> | |
| 79 | + /// <param name="amount">消费金额</param> | |
| 80 | + /// <returns>消费等级名称</returns> | |
| 81 | + public static string GetConsumeLevelName(decimal amount) | |
| 82 | + { | |
| 83 | + var level = GetConsumeLevel(amount); | |
| 84 | + return ConsumeLevelNames.ContainsKey(level) ? ConsumeLevelNames[level] : "D"; | |
| 85 | + } | |
| 86 | + } | |
| 87 | +} | |
| 88 | + | ... | ... |
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKhxx/LqKhxxInfoOutput.cs
| ... | ... | @@ -135,6 +135,85 @@ namespace NCC.Extend.Entitys.Dto.LqKhxx |
| 135 | 135 | /// </summary> |
| 136 | 136 | public string subHealthUserName { get; set; } |
| 137 | 137 | |
| 138 | + /// <summary> | |
| 139 | + /// 是否生美会员(0-否,1-是) | |
| 140 | + /// </summary> | |
| 141 | + public int isBeautyMember { get; set; } | |
| 142 | + | |
| 143 | + /// <summary> | |
| 144 | + /// 是否医美会员(0-否,1-是) | |
| 145 | + /// </summary> | |
| 146 | + public int isMedicalMember { get; set; } | |
| 147 | + | |
| 148 | + /// <summary> | |
| 149 | + /// 是否科技部会员(0-否,1-是) | |
| 150 | + /// </summary> | |
| 151 | + public int isTechMember { get; set; } | |
| 152 | + | |
| 153 | + /// <summary> | |
| 154 | + /// 成为生美会员时间 | |
| 155 | + /// </summary> | |
| 156 | + public DateTime? beautyMemberTime { get; set; } | |
| 157 | + | |
| 158 | + /// <summary> | |
| 159 | + /// 成为医美会员时间 | |
| 160 | + /// </summary> | |
| 161 | + public DateTime? medicalMemberTime { get; set; } | |
| 162 | + | |
| 163 | + /// <summary> | |
| 164 | + /// 成为科技部会员时间 | |
| 165 | + /// </summary> | |
| 166 | + public DateTime? techMemberTime { get; set; } | |
| 167 | + | |
| 168 | + /// <summary> | |
| 169 | + /// 首次到店时间 | |
| 170 | + /// </summary> | |
| 171 | + public DateTime? firstVisitTime { get; set; } | |
| 172 | + | |
| 173 | + /// <summary> | |
| 174 | + /// 最后到店时间 | |
| 175 | + /// </summary> | |
| 176 | + public DateTime? lastVisitTime { get; set; } | |
| 177 | + | |
| 178 | + /// <summary> | |
| 179 | + /// 到店天数 | |
| 180 | + /// </summary> | |
| 181 | + public int visitDays { get; set; } | |
| 182 | + | |
| 183 | + /// <summary> | |
| 184 | + /// 沉睡开始时间 | |
| 185 | + /// </summary> | |
| 186 | + public DateTime? sleepStartTime { get; set; } | |
| 187 | + | |
| 188 | + /// <summary> | |
| 189 | + /// 沉睡天数 | |
| 190 | + /// </summary> | |
| 191 | + public int sleepDays { get; set; } | |
| 192 | + | |
| 193 | + /// <summary> | |
| 194 | + /// 耗卡总金额(累计所有耗卡的金额总和) | |
| 195 | + /// </summary> | |
| 196 | + public decimal totalConsumeAmount { get; set; } | |
| 197 | + | |
| 198 | + /// <summary> | |
| 199 | + /// 开卡总金额(累计所有开单的实付业绩总和) | |
| 200 | + /// </summary> | |
| 201 | + public decimal totalBillingAmount { get; set; } | |
| 202 | + | |
| 203 | + /// <summary> | |
| 204 | + /// 剩余权益总金额(开卡总金额-耗卡总金额-退款金额) | |
| 205 | + /// </summary> | |
| 206 | + public decimal remainingRightsAmount { get; set; } | |
| 207 | + | |
| 208 | + /// <summary> | |
| 209 | + /// 消费等级(0-普通,1-银卡,2-金卡,3-钻石等) | |
| 210 | + /// </summary> | |
| 211 | + public int consumeLevel { get; set; } | |
| 212 | + | |
| 213 | + /// <summary> | |
| 214 | + /// 消费等级更新时间 | |
| 215 | + /// </summary> | |
| 216 | + public DateTime? consumeLevelUpdateTime { get; set; } | |
| 138 | 217 | |
| 139 | 218 | } |
| 140 | 219 | } | ... | ... |
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKhxx/LqKhxxListOutput.cs
| ... | ... | @@ -161,5 +161,85 @@ namespace NCC.Extend.Entitys.Dto.LqKhxx |
| 161 | 161 | /// 副健康师名称 |
| 162 | 162 | /// </summary> |
| 163 | 163 | public string subHealthUserName { get; set; } |
| 164 | + | |
| 165 | + /// <summary> | |
| 166 | + /// 是否生美会员(0-否,1-是) | |
| 167 | + /// </summary> | |
| 168 | + public int isBeautyMember { get; set; } | |
| 169 | + | |
| 170 | + /// <summary> | |
| 171 | + /// 是否医美会员(0-否,1-是) | |
| 172 | + /// </summary> | |
| 173 | + public int isMedicalMember { get; set; } | |
| 174 | + | |
| 175 | + /// <summary> | |
| 176 | + /// 是否科技部会员(0-否,1-是) | |
| 177 | + /// </summary> | |
| 178 | + public int isTechMember { get; set; } | |
| 179 | + | |
| 180 | + /// <summary> | |
| 181 | + /// 成为生美会员时间 | |
| 182 | + /// </summary> | |
| 183 | + public DateTime? beautyMemberTime { get; set; } | |
| 184 | + | |
| 185 | + /// <summary> | |
| 186 | + /// 成为医美会员时间 | |
| 187 | + /// </summary> | |
| 188 | + public DateTime? medicalMemberTime { get; set; } | |
| 189 | + | |
| 190 | + /// <summary> | |
| 191 | + /// 成为科技部会员时间 | |
| 192 | + /// </summary> | |
| 193 | + public DateTime? techMemberTime { get; set; } | |
| 194 | + | |
| 195 | + /// <summary> | |
| 196 | + /// 首次到店时间 | |
| 197 | + /// </summary> | |
| 198 | + public DateTime? firstVisitTime { get; set; } | |
| 199 | + | |
| 200 | + /// <summary> | |
| 201 | + /// 最后到店时间 | |
| 202 | + /// </summary> | |
| 203 | + public DateTime? lastVisitTime { get; set; } | |
| 204 | + | |
| 205 | + /// <summary> | |
| 206 | + /// 到店天数 | |
| 207 | + /// </summary> | |
| 208 | + public int visitDays { get; set; } | |
| 209 | + | |
| 210 | + /// <summary> | |
| 211 | + /// 沉睡开始时间 | |
| 212 | + /// </summary> | |
| 213 | + public DateTime? sleepStartTime { get; set; } | |
| 214 | + | |
| 215 | + /// <summary> | |
| 216 | + /// 沉睡天数 | |
| 217 | + /// </summary> | |
| 218 | + public int sleepDays { get; set; } | |
| 219 | + | |
| 220 | + /// <summary> | |
| 221 | + /// 耗卡总金额(累计所有耗卡的金额总和) | |
| 222 | + /// </summary> | |
| 223 | + public decimal totalConsumeAmount { get; set; } | |
| 224 | + | |
| 225 | + /// <summary> | |
| 226 | + /// 开卡总金额(累计所有开单的实付业绩总和) | |
| 227 | + /// </summary> | |
| 228 | + public decimal totalBillingAmount { get; set; } | |
| 229 | + | |
| 230 | + /// <summary> | |
| 231 | + /// 剩余权益总金额(开卡总金额-耗卡总金额-退款金额) | |
| 232 | + /// </summary> | |
| 233 | + public decimal remainingRightsAmount { get; set; } | |
| 234 | + | |
| 235 | + /// <summary> | |
| 236 | + /// 消费等级(0-普通,1-银卡,2-金卡,3-钻石等) | |
| 237 | + /// </summary> | |
| 238 | + public int consumeLevel { get; set; } | |
| 239 | + | |
| 240 | + /// <summary> | |
| 241 | + /// 消费等级更新时间 | |
| 242 | + /// </summary> | |
| 243 | + public DateTime? consumeLevelUpdateTime { get; set; } | |
| 164 | 244 | } |
| 165 | 245 | } | ... | ... |
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKhxx/LqKhxxListQueryInput.cs
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKhxx/MemberItemInfoOutput.cs
| 1 | 1 | using System; |
| 2 | 2 | using System.Collections.Generic; |
| 3 | +using NCC.Code; | |
| 4 | +using NCC.Common.Extension; | |
| 5 | +using NCC.Extend.Entitys.Enum; | |
| 3 | 6 | |
| 4 | 7 | namespace NCC.Extend.Entitys.Dto.LqKhxx |
| 5 | 8 | { |
| ... | ... | @@ -29,6 +32,21 @@ namespace NCC.Extend.Entitys.Dto.LqKhxx |
| 29 | 32 | public string MemberPhone { get; set; } |
| 30 | 33 | |
| 31 | 34 | /// <summary> |
| 35 | + /// 会员类型 | |
| 36 | + /// </summary> | |
| 37 | + public int MemberType { get; set; } | |
| 38 | + | |
| 39 | + /// <summary> | |
| 40 | + /// 会员类型名称 | |
| 41 | + /// </summary> | |
| 42 | + public string MemberTypeName | |
| 43 | + { | |
| 44 | + get | |
| 45 | + { | |
| 46 | + return EnumHelper.GetEnumDesc<MemberTypeEnum>(MemberType); | |
| 47 | + } | |
| 48 | + } | |
| 49 | + /// <summary> | |
| 32 | 50 | /// 所属门店ID |
| 33 | 51 | /// </summary> |
| 34 | 52 | public string BelongStoreId { get; set; } | ... | ... |
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKhxx/MemberItemInfoQueryInput.cs
| ... | ... | @@ -24,6 +24,11 @@ namespace NCC.Extend.Entitys.Dto.LqKhxx |
| 24 | 24 | public string BillingStoreId { get; set; } |
| 25 | 25 | |
| 26 | 26 | /// <summary> |
| 27 | + /// 会员类型 | |
| 28 | + /// </summary> | |
| 29 | + public int MemberType { get; set; } = -1; | |
| 30 | + | |
| 31 | + /// <summary> | |
| 27 | 32 | /// 开单人ID |
| 28 | 33 | /// </summary> |
| 29 | 34 | public string BillingUserId { get; set; } | ... | ... |
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqPersonTimesRecord/LqPersonTimesRecordCrInput.cs
| ... | ... | @@ -23,6 +23,11 @@ namespace NCC.Extend.Entitys.Dto.LqPersonTimesRecord |
| 23 | 23 | public string businessType { get; set; } |
| 24 | 24 | |
| 25 | 25 | /// <summary> |
| 26 | + /// 是否有开单(0-否,1-是) | |
| 27 | + /// </summary> | |
| 28 | + public int hasBilling { get; set; } = 0; | |
| 29 | + | |
| 30 | + /// <summary> | |
| 26 | 31 | /// 人员类型(健康师/科技老师) |
| 27 | 32 | /// </summary> |
| 28 | 33 | public string personType { get; set; } | ... | ... |
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqPersonTimesRecord/LqPersonTimesRecordInfoOutput.cs
| ... | ... | @@ -23,6 +23,11 @@ namespace NCC.Extend.Entitys.Dto.LqPersonTimesRecord |
| 23 | 23 | public string businessType { get; set; } |
| 24 | 24 | |
| 25 | 25 | /// <summary> |
| 26 | + /// 是否有开单(0-否,1-是) | |
| 27 | + /// </summary> | |
| 28 | + public int hasBilling { get; set; } | |
| 29 | + | |
| 30 | + /// <summary> | |
| 26 | 31 | /// 人员类型(健康师/科技老师) |
| 27 | 32 | /// </summary> |
| 28 | 33 | public string personType { get; set; } | ... | ... |
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqPersonTimesRecord/LqPersonTimesRecordListOutput.cs
| ... | ... | @@ -23,6 +23,11 @@ namespace NCC.Extend.Entitys.Dto.LqPersonTimesRecord |
| 23 | 23 | public string businessType { get; set; } |
| 24 | 24 | |
| 25 | 25 | /// <summary> |
| 26 | + /// 是否有开单(0-否,1-是) | |
| 27 | + /// </summary> | |
| 28 | + public int hasBilling { get; set; } | |
| 29 | + | |
| 30 | + /// <summary> | |
| 26 | 31 | /// 人员类型(健康师/科技老师) |
| 27 | 32 | /// </summary> |
| 28 | 33 | public string personType { get; set; } | ... | ... |
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqXhPxmx/ConsumeItemDetailListOutput.cs
0 → 100644
| 1 | +using System; | |
| 2 | + | |
| 3 | +namespace NCC.Extend.Entitys.Dto.LqXhPxmx | |
| 4 | +{ | |
| 5 | + /// <summary> | |
| 6 | + /// 耗卡品项明细记录输出 | |
| 7 | + /// </summary> | |
| 8 | + public class ConsumeItemDetailListOutput | |
| 9 | + { | |
| 10 | + /// <summary> | |
| 11 | + /// 品项明细ID | |
| 12 | + /// </summary> | |
| 13 | + public string id { get; set; } | |
| 14 | + | |
| 15 | + /// <summary> | |
| 16 | + /// 耗卡记录ID | |
| 17 | + /// </summary> | |
| 18 | + public string consumeId { get; set; } | |
| 19 | + | |
| 20 | + /// <summary> | |
| 21 | + /// 耗卡时间 | |
| 22 | + /// </summary> | |
| 23 | + public DateTime? consumeTime { get; set; } | |
| 24 | + | |
| 25 | + /// <summary> | |
| 26 | + /// 会员名称 | |
| 27 | + /// </summary> | |
| 28 | + public string memberName { get; set; } | |
| 29 | + | |
| 30 | + /// <summary> | |
| 31 | + /// 会员手机 | |
| 32 | + /// </summary> | |
| 33 | + public string memberPhone { get; set; } | |
| 34 | + | |
| 35 | + /// <summary> | |
| 36 | + /// 品项名称 | |
| 37 | + /// </summary> | |
| 38 | + public string itemName { get; set; } | |
| 39 | + | |
| 40 | + /// <summary> | |
| 41 | + /// 品项类型(分类④-统计品项用) | |
| 42 | + /// </summary> | |
| 43 | + public string itemType { get; set; } | |
| 44 | + | |
| 45 | + /// <summary> | |
| 46 | + /// 品项价格 | |
| 47 | + /// </summary> | |
| 48 | + public decimal itemPrice { get; set; } | |
| 49 | + | |
| 50 | + /// <summary> | |
| 51 | + /// 项目次数 | |
| 52 | + /// </summary> | |
| 53 | + public decimal projectNumber { get; set; } | |
| 54 | + | |
| 55 | + /// <summary> | |
| 56 | + /// 原始项目次数 | |
| 57 | + /// </summary> | |
| 58 | + public decimal? originalProjectNumber { get; set; } | |
| 59 | + | |
| 60 | + /// <summary> | |
| 61 | + /// 合计金额 | |
| 62 | + /// </summary> | |
| 63 | + public decimal totalPrice { get; set; } | |
| 64 | + | |
| 65 | + /// <summary> | |
| 66 | + /// 来源类型(购买/赠送/体验) | |
| 67 | + /// </summary> | |
| 68 | + public string sourceType { get; set; } | |
| 69 | + | |
| 70 | + /// <summary> | |
| 71 | + /// 门店ID | |
| 72 | + /// </summary> | |
| 73 | + public string storeId { get; set; } | |
| 74 | + | |
| 75 | + /// <summary> | |
| 76 | + /// 门店名称 | |
| 77 | + /// </summary> | |
| 78 | + public string storeName { get; set; } | |
| 79 | + } | |
| 80 | +} | |
| 81 | + | ... | ... |
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqXhPxmx/ConsumeItemDetailListQueryInput.cs
0 → 100644
| 1 | +using NCC.Common.Filter; | |
| 2 | + | |
| 3 | +namespace NCC.Extend.Entitys.Dto.LqXhPxmx | |
| 4 | +{ | |
| 5 | + /// <summary> | |
| 6 | + /// 耗卡品项明细记录查询输入参数 | |
| 7 | + /// </summary> | |
| 8 | + public class ConsumeItemDetailListQueryInput : PageInputBase | |
| 9 | + { | |
| 10 | + /// <summary> | |
| 11 | + /// 品项明细ID | |
| 12 | + /// </summary> | |
| 13 | + public string Id { get; set; } | |
| 14 | + | |
| 15 | + /// <summary> | |
| 16 | + /// 耗卡记录ID | |
| 17 | + /// </summary> | |
| 18 | + public string ConsumeId { get; set; } | |
| 19 | + | |
| 20 | + /// <summary> | |
| 21 | + /// 耗卡时间(开始) | |
| 22 | + /// </summary> | |
| 23 | + public string StartConsumeTime { get; set; } | |
| 24 | + | |
| 25 | + /// <summary> | |
| 26 | + /// 耗卡时间(结束) | |
| 27 | + /// </summary> | |
| 28 | + public string EndConsumeTime { get; set; } | |
| 29 | + | |
| 30 | + /// <summary> | |
| 31 | + /// 开始时间(兼容参数名) | |
| 32 | + /// </summary> | |
| 33 | + public string startTime { get; set; } | |
| 34 | + | |
| 35 | + /// <summary> | |
| 36 | + /// 结束时间(兼容参数名) | |
| 37 | + /// </summary> | |
| 38 | + public string endTime { get; set; } | |
| 39 | + | |
| 40 | + /// <summary> | |
| 41 | + /// 会员ID | |
| 42 | + /// </summary> | |
| 43 | + public string MemberId { get; set; } | |
| 44 | + | |
| 45 | + /// <summary> | |
| 46 | + /// 会员名称(模糊查询) | |
| 47 | + /// </summary> | |
| 48 | + public string MemberName { get; set; } | |
| 49 | + | |
| 50 | + /// <summary> | |
| 51 | + /// 会员手机号 | |
| 52 | + /// </summary> | |
| 53 | + public string MemberPhone { get; set; } | |
| 54 | + | |
| 55 | + /// <summary> | |
| 56 | + /// 品项ID | |
| 57 | + /// </summary> | |
| 58 | + public string ItemId { get; set; } | |
| 59 | + | |
| 60 | + /// <summary> | |
| 61 | + /// 品项名称(模糊查询) | |
| 62 | + /// </summary> | |
| 63 | + public string ItemName { get; set; } | |
| 64 | + | |
| 65 | + /// <summary> | |
| 66 | + /// 品项类型(分类④-统计品项用) | |
| 67 | + /// </summary> | |
| 68 | + public string ItemType { get; set; } | |
| 69 | + | |
| 70 | + /// <summary> | |
| 71 | + /// 来源类型(购买/赠送/体验) | |
| 72 | + /// </summary> | |
| 73 | + public string SourceType { get; set; } | |
| 74 | + | |
| 75 | + /// <summary> | |
| 76 | + /// 门店ID | |
| 77 | + /// </summary> | |
| 78 | + public string StoreId { get; set; } | |
| 79 | + } | |
| 80 | +} | |
| 81 | + | ... | ... |
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_khxx/LqKhxxEntity.cs
| ... | ... | @@ -142,6 +142,108 @@ namespace NCC.Extend.Entitys.lq_khxx |
| 142 | 142 | public string SubHealthUser { get; set; } |
| 143 | 143 | |
| 144 | 144 | /// <summary> |
| 145 | + /// 最后消费时间 | |
| 146 | + /// </summary> | |
| 147 | + [SugarColumn(ColumnName = "F_LastConsumeTime")] | |
| 148 | + public DateTime? LastConsumeTime { get; set; } | |
| 149 | + | |
| 150 | + /// <summary> | |
| 151 | + /// 是否生美会员(0-否,1-是) | |
| 152 | + /// </summary> | |
| 153 | + [SugarColumn(ColumnName = "F_IsBeautyMember")] | |
| 154 | + public int IsBeautyMember { get; set; } = 0; | |
| 155 | + | |
| 156 | + /// <summary> | |
| 157 | + /// 是否医美会员(0-否,1-是) | |
| 158 | + /// </summary> | |
| 159 | + [SugarColumn(ColumnName = "F_IsMedicalMember")] | |
| 160 | + public int IsMedicalMember { get; set; } = 0; | |
| 161 | + | |
| 162 | + /// <summary> | |
| 163 | + /// 是否科技部会员(0-否,1-是) | |
| 164 | + /// </summary> | |
| 165 | + [SugarColumn(ColumnName = "F_IsTechMember")] | |
| 166 | + public int IsTechMember { get; set; } = 0; | |
| 167 | + | |
| 168 | + /// <summary> | |
| 169 | + /// 成为生美会员时间 | |
| 170 | + /// </summary> | |
| 171 | + [SugarColumn(ColumnName = "F_BeautyMemberTime")] | |
| 172 | + public DateTime? BeautyMemberTime { get; set; } | |
| 173 | + | |
| 174 | + /// <summary> | |
| 175 | + /// 成为医美会员时间 | |
| 176 | + /// </summary> | |
| 177 | + [SugarColumn(ColumnName = "F_MedicalMemberTime")] | |
| 178 | + public DateTime? MedicalMemberTime { get; set; } | |
| 179 | + | |
| 180 | + /// <summary> | |
| 181 | + /// 成为科技部会员时间 | |
| 182 | + /// </summary> | |
| 183 | + [SugarColumn(ColumnName = "F_TechMemberTime")] | |
| 184 | + public DateTime? TechMemberTime { get; set; } | |
| 185 | + | |
| 186 | + /// <summary> | |
| 187 | + /// 首次到店时间 | |
| 188 | + /// </summary> | |
| 189 | + [SugarColumn(ColumnName = "F_FirstVisitTime")] | |
| 190 | + public DateTime? FirstVisitTime { get; set; } | |
| 191 | + | |
| 192 | + /// <summary> | |
| 193 | + /// 最后到店时间 | |
| 194 | + /// </summary> | |
| 195 | + [SugarColumn(ColumnName = "F_LastVisitTime")] | |
| 196 | + public DateTime? LastVisitTime { get; set; } | |
| 197 | + | |
| 198 | + /// <summary> | |
| 199 | + /// 到店天数 | |
| 200 | + /// </summary> | |
| 201 | + [SugarColumn(ColumnName = "F_VisitDays")] | |
| 202 | + public int VisitDays { get; set; } = 0; | |
| 203 | + | |
| 204 | + /// <summary> | |
| 205 | + /// 沉睡开始时间 | |
| 206 | + /// </summary> | |
| 207 | + [SugarColumn(ColumnName = "F_SleepStartTime")] | |
| 208 | + public DateTime? SleepStartTime { get; set; } | |
| 209 | + | |
| 210 | + /// <summary> | |
| 211 | + /// 沉睡天数 | |
| 212 | + /// </summary> | |
| 213 | + [SugarColumn(ColumnName = "F_SleepDays")] | |
| 214 | + public int SleepDays { get; set; } = 0; | |
| 215 | + | |
| 216 | + /// <summary> | |
| 217 | + /// 耗卡总金额(累计所有耗卡的金额总和) | |
| 218 | + /// </summary> | |
| 219 | + [SugarColumn(ColumnName = "F_TotalConsumeAmount")] | |
| 220 | + public decimal TotalConsumeAmount { get; set; } = 0m; | |
| 221 | + | |
| 222 | + /// <summary> | |
| 223 | + /// 开卡总金额(累计所有开单的实付业绩总和) | |
| 224 | + /// </summary> | |
| 225 | + [SugarColumn(ColumnName = "F_TotalBillingAmount")] | |
| 226 | + public decimal TotalBillingAmount { get; set; } = 0m; | |
| 227 | + | |
| 228 | + /// <summary> | |
| 229 | + /// 剩余权益总金额(开卡总金额-耗卡总金额-退款金额) | |
| 230 | + /// </summary> | |
| 231 | + [SugarColumn(ColumnName = "F_RemainingRightsAmount")] | |
| 232 | + public decimal RemainingRightsAmount { get; set; } = 0m; | |
| 233 | + | |
| 234 | + /// <summary> | |
| 235 | + /// 消费等级(0-普通,1-银卡,2-金卡,3-钻石等) | |
| 236 | + /// </summary> | |
| 237 | + [SugarColumn(ColumnName = "F_ConsumeLevel")] | |
| 238 | + public int ConsumeLevel { get; set; } = 0; | |
| 239 | + | |
| 240 | + /// <summary> | |
| 241 | + /// 消费等级更新时间 | |
| 242 | + /// </summary> | |
| 243 | + [SugarColumn(ColumnName = "F_ConsumeLevelUpdateTime")] | |
| 244 | + public DateTime? ConsumeLevelUpdateTime { get; set; } | |
| 245 | + | |
| 246 | + /// <summary> | |
| 145 | 247 | /// 是否有效 |
| 146 | 248 | /// </summary> |
| 147 | 249 | [SugarColumn(ColumnName = "F_IsEffective")] | ... | ... |
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 |
| 31 | 31 | public string BusinessType { get; set; } |
| 32 | 32 | |
| 33 | 33 | /// <summary> |
| 34 | + /// 是否有开单(0-否,1-是) | |
| 35 | + /// </summary> | |
| 36 | + [SugarColumn(ColumnName = "F_HasBilling")] | |
| 37 | + public int HasBilling { get; set; } = 0; | |
| 38 | + | |
| 39 | + /// <summary> | |
| 34 | 40 | /// 人员类型(健康师/科技老师) |
| 35 | 41 | /// </summary> |
| 36 | 42 | [SugarColumn(ColumnName = "F_PersonType")] | ... | ... |
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Enum/MemberTypeEnum.cs
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Mapper/LqKhxxMapper.cs
| ... | ... | @@ -12,6 +12,25 @@ namespace NCC.Extend.Entitys.Mapper.LqKhxx |
| 12 | 12 | { |
| 13 | 13 | // 由于khxf字段现在是string类型,不需要特殊的映射处理 |
| 14 | 14 | // 使用默认的映射即可 |
| 15 | + | |
| 16 | + // 配置实体到详情输出的映射(确保新字段正确映射) | |
| 17 | + config.ForType<LqKhxxEntity, LqKhxxInfoOutput>() | |
| 18 | + .Map(dest => dest.isBeautyMember, src => src.IsBeautyMember) | |
| 19 | + .Map(dest => dest.isMedicalMember, src => src.IsMedicalMember) | |
| 20 | + .Map(dest => dest.isTechMember, src => src.IsTechMember) | |
| 21 | + .Map(dest => dest.beautyMemberTime, src => src.BeautyMemberTime) | |
| 22 | + .Map(dest => dest.medicalMemberTime, src => src.MedicalMemberTime) | |
| 23 | + .Map(dest => dest.techMemberTime, src => src.TechMemberTime) | |
| 24 | + .Map(dest => dest.firstVisitTime, src => src.FirstVisitTime) | |
| 25 | + .Map(dest => dest.lastVisitTime, src => src.LastVisitTime) | |
| 26 | + .Map(dest => dest.visitDays, src => src.VisitDays) | |
| 27 | + .Map(dest => dest.sleepStartTime, src => src.SleepStartTime) | |
| 28 | + .Map(dest => dest.sleepDays, src => src.SleepDays) | |
| 29 | + .Map(dest => dest.totalConsumeAmount, src => src.TotalConsumeAmount) | |
| 30 | + .Map(dest => dest.totalBillingAmount, src => src.TotalBillingAmount) | |
| 31 | + .Map(dest => dest.remainingRightsAmount, src => src.RemainingRightsAmount) | |
| 32 | + .Map(dest => dest.consumeLevel, src => src.ConsumeLevel) | |
| 33 | + .Map(dest => dest.consumeLevelUpdateTime, src => src.ConsumeLevelUpdateTime); | |
| 15 | 34 | } |
| 16 | 35 | } |
| 17 | 36 | } | ... | ... |
netcore/src/Modularity/Extend/NCC.Extend/LqKhxxService.cs
| ... | ... | @@ -30,12 +30,14 @@ using NCC.Extend.Entitys.lq_xh_hyhk; |
| 30 | 30 | using NCC.Extend.Entitys.lq_xh_pxmx; |
| 31 | 31 | using NCC.Extend.Entitys.lq_xmzl; |
| 32 | 32 | using NCC.Extend.Interfaces.LqKhxx; |
| 33 | +using NCC.Extend.Entitys.Config; | |
| 33 | 34 | using NCC.System.Entitys.Permission; |
| 34 | 35 | using NCC.FriendlyException; |
| 35 | 36 | using NCC.JsonSerialization; |
| 36 | 37 | using SqlSugar; |
| 37 | 38 | using Yitter.IdGenerator; |
| 38 | 39 | using NCC.Extend.Entitys.lq_kd_deductinfo; |
| 40 | +using Microsoft.AspNetCore.Authorization; | |
| 39 | 41 | |
| 40 | 42 | namespace NCC.Extend.LqKhxx |
| 41 | 43 | { |
| ... | ... | @@ -109,6 +111,9 @@ namespace NCC.Extend.LqKhxx |
| 109 | 111 | public async Task<dynamic> GetList([FromQuery] LqKhxxListQueryInput input) |
| 110 | 112 | { |
| 111 | 113 | var sidx = input.sidx == null ? "id" : input.sidx; |
| 114 | + List<string> queryZcsj = input.zcsj != null ? input.zcsj.Split(',').ToObeject<List<string>>() : null; | |
| 115 | + DateTime? startZcsj = queryZcsj != null ? Ext.GetDateTime(queryZcsj.First()) : null; | |
| 116 | + DateTime? endZcsj = queryZcsj != null ? Ext.GetDateTime(queryZcsj.Last()) : null; | |
| 112 | 117 | var data = await _db.Queryable<LqKhxxEntity>() |
| 113 | 118 | .WhereIF(!string.IsNullOrEmpty(input.keyWord), p => p.Khmc.Contains(input.keyWord) || p.Sjh.Contains(input.keyWord) || p.Dah.Contains(input.keyWord)) |
| 114 | 119 | .WhereIF(!string.IsNullOrEmpty(input.id), p => p.Id.Contains(input.id)) |
| ... | ... | @@ -125,6 +130,8 @@ namespace NCC.Extend.LqKhxx |
| 125 | 130 | .WhereIF(!string.IsNullOrEmpty(input.jdqd), p => p.Jdqd.Contains(input.jdqd)) |
| 126 | 131 | .WhereIF(!string.IsNullOrEmpty(input.lxdz), p => p.Lxdz.Contains(input.lxdz)) |
| 127 | 132 | .WhereIF(!string.IsNullOrEmpty(input.bz), p => p.Bz.Contains(input.bz)) |
| 133 | + .WhereIF(queryZcsj != null, p => p.CreateTime >= new DateTime(startZcsj.ToDate().Year, startZcsj.ToDate().Month, startZcsj.ToDate().Day, 0, 0, 0)) | |
| 134 | + .WhereIF(queryZcsj != null, p => p.CreateTime <= new DateTime(endZcsj.ToDate().Year, endZcsj.ToDate().Month, endZcsj.ToDate().Day, 23, 59, 59)) | |
| 128 | 135 | .Select(it => new LqKhxxListOutput |
| 129 | 136 | { |
| 130 | 137 | id = it.Id, |
| ... | ... | @@ -153,6 +160,22 @@ namespace NCC.Extend.LqKhxx |
| 153 | 160 | mainHealthUserName = SqlFunc.Subqueryable<UserEntity>().Where(u => u.Id == it.MainHealthUser).Select(u => u.RealName), |
| 154 | 161 | subHealthUserName = SqlFunc.Subqueryable<UserEntity>().Where(u => u.Id == it.SubHealthUser).Select(u => u.RealName), |
| 155 | 162 | tjrName = SqlFunc.Subqueryable<LqKhxxEntity>().Where(u => u.Id == it.Tjr).Select(u => u.Khmc), |
| 163 | + isBeautyMember = it.IsBeautyMember, | |
| 164 | + isMedicalMember = it.IsMedicalMember, | |
| 165 | + isTechMember = it.IsTechMember, | |
| 166 | + beautyMemberTime = it.BeautyMemberTime, | |
| 167 | + medicalMemberTime = it.MedicalMemberTime, | |
| 168 | + techMemberTime = it.TechMemberTime, | |
| 169 | + firstVisitTime = it.FirstVisitTime, | |
| 170 | + lastVisitTime = it.LastVisitTime, | |
| 171 | + visitDays = it.VisitDays, | |
| 172 | + sleepStartTime = it.SleepStartTime, | |
| 173 | + sleepDays = it.SleepDays, | |
| 174 | + totalConsumeAmount = it.TotalConsumeAmount, | |
| 175 | + totalBillingAmount = it.TotalBillingAmount, | |
| 176 | + remainingRightsAmount = it.RemainingRightsAmount, | |
| 177 | + consumeLevel = it.ConsumeLevel, | |
| 178 | + consumeLevelUpdateTime = it.ConsumeLevelUpdateTime | |
| 156 | 179 | }) |
| 157 | 180 | .MergeTable() |
| 158 | 181 | .OrderBy(sidx + " " + input.sort) |
| ... | ... | @@ -180,6 +203,7 @@ namespace NCC.Extend.LqKhxx |
| 180 | 203 | var entity = input.Adapt<LqKhxxEntity>(); |
| 181 | 204 | entity.Id = YitIdHelper.NextId().ToString(); |
| 182 | 205 | entity.Dah = "LQ" + DateTime.Now.ToString("yyyyMMddHHmmssfff"); |
| 206 | + entity.Zcsj = DateTime.Now; | |
| 183 | 207 | // 处理客户消费字段:将数组转换为逗号分隔的字符串 |
| 184 | 208 | if (input.khxf != null && input.khxf.Count > 0) |
| 185 | 209 | { |
| ... | ... | @@ -1048,6 +1072,11 @@ namespace NCC.Extend.LqKhxx |
| 1048 | 1072 | memberQuery = memberQuery.Where(x => x.Gsmd == input.BelongStoreId); |
| 1049 | 1073 | } |
| 1050 | 1074 | |
| 1075 | + if (input.MemberType >= 0) | |
| 1076 | + { | |
| 1077 | + memberQuery = memberQuery.Where(x => x.Khlx == input.MemberType.GetHashCode().ToString()); | |
| 1078 | + } | |
| 1079 | + | |
| 1051 | 1080 | // 如果指定了开单门店、开单人、品项ID等条件,需要通过开单表进行筛选 |
| 1052 | 1081 | if (!string.IsNullOrEmpty(input.BillingStoreId) || |
| 1053 | 1082 | !string.IsNullOrEmpty(input.BillingUserId) || |
| ... | ... | @@ -1070,13 +1099,9 @@ namespace NCC.Extend.LqKhxx |
| 1070 | 1099 | } |
| 1071 | 1100 | |
| 1072 | 1101 | // 如果指定了品项ID,需要通过品项明细表进行筛选 |
| 1073 | - if (!string.IsNullOrEmpty(input.PurchaseItemId) || | |
| 1074 | - !string.IsNullOrEmpty(input.GiftItemId) || | |
| 1075 | - !string.IsNullOrEmpty(input.ExperienceItemId)) | |
| 1102 | + if (!string.IsNullOrEmpty(input.PurchaseItemId) || !string.IsNullOrEmpty(input.GiftItemId) || !string.IsNullOrEmpty(input.ExperienceItemId)) | |
| 1076 | 1103 | { |
| 1077 | - var itemQuery = _db.Queryable<LqKdPxmxEntity>() | |
| 1078 | - .Where(x => x.IsEffective == StatusEnum.有效.GetHashCode()); | |
| 1079 | - | |
| 1104 | + var itemQuery = _db.Queryable<LqKdPxmxEntity>().Where(x => x.IsEffective == StatusEnum.有效.GetHashCode()); | |
| 1080 | 1105 | if (!string.IsNullOrEmpty(input.PurchaseItemId)) |
| 1081 | 1106 | { |
| 1082 | 1107 | itemQuery = itemQuery.Where(x => x.Px == input.PurchaseItemId && x.SourceType == "购买"); |
| ... | ... | @@ -1147,7 +1172,8 @@ namespace NCC.Extend.LqKhxx |
| 1147 | 1172 | x.Dah, |
| 1148 | 1173 | x.Khmc, |
| 1149 | 1174 | x.Sjh, |
| 1150 | - x.Gsmd | |
| 1175 | + x.Gsmd, | |
| 1176 | + x.Khlx | |
| 1151 | 1177 | }) |
| 1152 | 1178 | .ToListAsync(); |
| 1153 | 1179 | |
| ... | ... | @@ -1328,6 +1354,7 @@ namespace NCC.Extend.LqKhxx |
| 1328 | 1354 | MemberCode = member.Dah ?? "", |
| 1329 | 1355 | MemberName = member.Khmc ?? "", |
| 1330 | 1356 | MemberPhone = member.Sjh ?? "", |
| 1357 | + MemberType = member.Khlx.ToInt(), | |
| 1331 | 1358 | BelongStoreId = member.Gsmd ?? "", |
| 1332 | 1359 | BelongStoreName = stores.ContainsKey(member.Gsmd ?? "") ? stores[member.Gsmd] : "" |
| 1333 | 1360 | }; |
| ... | ... | @@ -1531,5 +1558,507 @@ namespace NCC.Extend.LqKhxx |
| 1531 | 1558 | } |
| 1532 | 1559 | #endregion |
| 1533 | 1560 | |
| 1561 | + #region 会员信息更新 | |
| 1562 | + /// <summary> | |
| 1563 | + /// 更新会员信息(单个会员) | |
| 1564 | + /// </summary> | |
| 1565 | + /// <param name="memberId">会员ID</param> | |
| 1566 | + /// <returns></returns> | |
| 1567 | + [HttpPost("UpdateMemberInfo")] | |
| 1568 | + public async Task<dynamic> UpdateMemberInfo([FromBody] string memberId) | |
| 1569 | + { | |
| 1570 | + try | |
| 1571 | + { | |
| 1572 | + if (string.IsNullOrEmpty(memberId)) | |
| 1573 | + { | |
| 1574 | + throw NCCException.Oh("会员ID不能为空"); | |
| 1575 | + } | |
| 1576 | + | |
| 1577 | + var member = await _db.Queryable<LqKhxxEntity>() | |
| 1578 | + .Where(x => x.Id == memberId && x.IsEffective == StatusEnum.有效.GetHashCode()) | |
| 1579 | + .FirstAsync(); | |
| 1580 | + | |
| 1581 | + if (member == null) | |
| 1582 | + { | |
| 1583 | + throw NCCException.Oh("会员不存在"); | |
| 1584 | + } | |
| 1585 | + | |
| 1586 | + await UpdateMemberInfoInternal(member); | |
| 1587 | + | |
| 1588 | + return new { success = true, message = "更新成功" }; | |
| 1589 | + } | |
| 1590 | + catch (Exception ex) | |
| 1591 | + { | |
| 1592 | + _logger.LogError(ex, "更新会员信息失败,会员ID:{MemberId}", memberId); | |
| 1593 | + throw NCCException.Oh($"更新会员信息失败:{ex.Message}"); | |
| 1594 | + } | |
| 1595 | + } | |
| 1596 | + | |
| 1597 | + /// <summary> | |
| 1598 | + /// 批量更新所有会员信息(高性能版:使用SQL批量更新) | |
| 1599 | + /// </summary> | |
| 1600 | + /// <returns></returns> | |
| 1601 | + [HttpPost("BatchUpdateMemberInfo")] | |
| 1602 | + [AllowAnonymous] | |
| 1603 | + public async Task<dynamic> BatchUpdateMemberInfo() | |
| 1604 | + { | |
| 1605 | + try | |
| 1606 | + { | |
| 1607 | + var startTime = DateTime.Now; | |
| 1608 | + _logger.LogInformation("开始批量更新会员信息(高性能SQL版)"); | |
| 1609 | + | |
| 1610 | + // 1. 获取所有有效会员ID | |
| 1611 | + var memberIds = await _db.Queryable<LqKhxxEntity>() | |
| 1612 | + .Where(x => x.IsEffective == StatusEnum.有效.GetHashCode()) | |
| 1613 | + .Select(x => x.Id) | |
| 1614 | + .ToListAsync(); | |
| 1615 | + | |
| 1616 | + var totalCount = memberIds.Count; | |
| 1617 | + | |
| 1618 | + if (totalCount == 0) | |
| 1619 | + { | |
| 1620 | + return new | |
| 1621 | + { | |
| 1622 | + success = true, | |
| 1623 | + totalCount = 0, | |
| 1624 | + successCount = 0, | |
| 1625 | + failCount = 0, | |
| 1626 | + duration = "0秒" | |
| 1627 | + }; | |
| 1628 | + } | |
| 1629 | + | |
| 1630 | + _logger.LogInformation("开始使用SQL批量更新,会员总数:{Total}", totalCount); | |
| 1631 | + var sqlStartTime = DateTime.Now; | |
| 1632 | + | |
| 1633 | + // 使用原生SQL一次性更新所有会员信息 | |
| 1634 | + var beautyThreshold = MemberInfoUpdateConfig.BeautyMemberAmountThreshold; | |
| 1635 | + var medicalThreshold = MemberInfoUpdateConfig.MedicalMemberAmountThreshold; | |
| 1636 | + var techThreshold = MemberInfoUpdateConfig.TechMemberAmountThreshold; | |
| 1637 | + var minSleepThreshold = MemberInfoUpdateConfig.SleepDaysThresholds.Min(); | |
| 1638 | + var now = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"); | |
| 1639 | + | |
| 1640 | + // 构建SQL更新语句 | |
| 1641 | + var updateSql = $@" | |
| 1642 | +UPDATE lq_khxx kh | |
| 1643 | +LEFT JOIN ( | |
| 1644 | + -- 生美会员判断 | |
| 1645 | + SELECT | |
| 1646 | + pxmx.F_MemberId as MemberId, | |
| 1647 | + CASE WHEN SUM(CASE WHEN pxmx.F_ItemCategory = '生美' THEN pxmx.F_ActualPrice ELSE 0 END) > {beautyThreshold} THEN 1 ELSE 0 END as IsBeautyMember, | |
| 1648 | + MIN(CASE WHEN pxmx.F_ItemCategory = '生美' AND kd.kdrq IS NOT NULL THEN kd.kdrq ELSE NULL END) as BeautyMemberTime | |
| 1649 | + FROM lq_kd_pxmx pxmx | |
| 1650 | + INNER JOIN lq_kd_kdjlb kd ON pxmx.glkdbh = kd.F_Id | |
| 1651 | + WHERE pxmx.F_IsEffective = 1 | |
| 1652 | + AND kd.F_IsEffective = 1 | |
| 1653 | + AND pxmx.F_ActualPrice > 0 | |
| 1654 | + AND pxmx.px != '61' | |
| 1655 | + GROUP BY pxmx.F_MemberId | |
| 1656 | +) beauty ON kh.F_Id = beauty.MemberId | |
| 1657 | +LEFT JOIN ( | |
| 1658 | + -- 医美会员判断 | |
| 1659 | + SELECT | |
| 1660 | + pxmx.F_MemberId as MemberId, | |
| 1661 | + CASE WHEN SUM(CASE WHEN pxmx.F_ItemCategory = '医美' THEN pxmx.F_ActualPrice ELSE 0 END) > {medicalThreshold} THEN 1 ELSE 0 END as IsMedicalMember, | |
| 1662 | + MIN(CASE WHEN pxmx.F_ItemCategory = '医美' AND kd.kdrq IS NOT NULL THEN kd.kdrq ELSE NULL END) as MedicalMemberTime | |
| 1663 | + FROM lq_kd_pxmx pxmx | |
| 1664 | + INNER JOIN lq_kd_kdjlb kd ON pxmx.glkdbh = kd.F_Id | |
| 1665 | + WHERE pxmx.F_IsEffective = 1 | |
| 1666 | + AND kd.F_IsEffective = 1 | |
| 1667 | + AND pxmx.F_ActualPrice > 0 | |
| 1668 | + AND pxmx.px != '61' | |
| 1669 | + GROUP BY pxmx.F_MemberId | |
| 1670 | +) medical ON kh.F_Id = medical.MemberId | |
| 1671 | +LEFT JOIN ( | |
| 1672 | + -- 科技部会员判断 | |
| 1673 | + SELECT | |
| 1674 | + pxmx.F_MemberId as MemberId, | |
| 1675 | + 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, | |
| 1676 | + 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 | |
| 1677 | + FROM lq_kd_pxmx pxmx | |
| 1678 | + INNER JOIN lq_kd_kdjlb kd ON pxmx.glkdbh = kd.F_Id | |
| 1679 | + WHERE pxmx.F_IsEffective = 1 | |
| 1680 | + AND kd.F_IsEffective = 1 | |
| 1681 | + AND pxmx.F_ActualPrice > 0 | |
| 1682 | + AND pxmx.px != '61' | |
| 1683 | + GROUP BY pxmx.F_MemberId | |
| 1684 | +) tech ON kh.F_Id = tech.MemberId | |
| 1685 | +LEFT JOIN ( | |
| 1686 | + -- 到店天数、首次到店时间、最后到店时间 | |
| 1687 | + SELECT | |
| 1688 | + xh.hy as MemberId, | |
| 1689 | + COUNT(DISTINCT DATE(xh.hksj)) as VisitDays, | |
| 1690 | + MIN(xh.hksj) as FirstVisitTime, | |
| 1691 | + MAX(xh.hksj) as LastVisitTime | |
| 1692 | + FROM lq_xh_hyhk xh | |
| 1693 | + WHERE xh.F_IsEffective = 1 | |
| 1694 | + GROUP BY xh.hy | |
| 1695 | +) visit ON kh.F_Id = visit.MemberId | |
| 1696 | +LEFT JOIN ( | |
| 1697 | + -- 开卡总金额 | |
| 1698 | + SELECT | |
| 1699 | + kd.kdhy as MemberId, | |
| 1700 | + COALESCE(SUM(kd.sfyj), 0) as TotalBillingAmount | |
| 1701 | + FROM lq_kd_kdjlb kd | |
| 1702 | + WHERE kd.F_IsEffective = 1 | |
| 1703 | + GROUP BY kd.kdhy | |
| 1704 | +) billing ON kh.F_Id = billing.MemberId | |
| 1705 | +LEFT JOIN ( | |
| 1706 | + -- 耗卡总金额 | |
| 1707 | + SELECT | |
| 1708 | + xh.hy as MemberId, | |
| 1709 | + COALESCE(SUM(xh.xfje), 0) as TotalConsumeAmount | |
| 1710 | + FROM lq_xh_hyhk xh | |
| 1711 | + WHERE xh.F_IsEffective = 1 | |
| 1712 | + GROUP BY xh.hy | |
| 1713 | +) consume ON kh.F_Id = consume.MemberId | |
| 1714 | +LEFT JOIN ( | |
| 1715 | + -- 退款总金额 | |
| 1716 | + SELECT | |
| 1717 | + tk.hy as MemberId, | |
| 1718 | + COALESCE(SUM(tk.F_ActualRefundAmount), 0) as TotalRefundAmount | |
| 1719 | + FROM lq_hytk_hytk tk | |
| 1720 | + WHERE tk.F_IsEffective = 1 | |
| 1721 | + GROUP BY tk.hy | |
| 1722 | +) refund ON kh.F_Id = refund.MemberId | |
| 1723 | +SET | |
| 1724 | + kh.F_IsBeautyMember = COALESCE(beauty.IsBeautyMember, 0), | |
| 1725 | + kh.F_IsMedicalMember = COALESCE(medical.IsMedicalMember, 0), | |
| 1726 | + kh.F_IsTechMember = COALESCE(tech.IsTechMember, 0), | |
| 1727 | + kh.F_BeautyMemberTime = CASE WHEN kh.F_BeautyMemberTime IS NULL THEN beauty.BeautyMemberTime ELSE kh.F_BeautyMemberTime END, | |
| 1728 | + kh.F_MedicalMemberTime = CASE WHEN kh.F_MedicalMemberTime IS NULL THEN medical.MedicalMemberTime ELSE kh.F_MedicalMemberTime END, | |
| 1729 | + kh.F_TechMemberTime = CASE WHEN kh.F_TechMemberTime IS NULL THEN tech.TechMemberTime ELSE kh.F_TechMemberTime END, | |
| 1730 | + kh.F_FirstVisitTime = visit.FirstVisitTime, | |
| 1731 | + kh.F_LastVisitTime = visit.LastVisitTime, | |
| 1732 | + kh.F_VisitDays = COALESCE(visit.VisitDays, 0), | |
| 1733 | + kh.F_TotalBillingAmount = COALESCE(billing.TotalBillingAmount, 0), | |
| 1734 | + kh.F_TotalConsumeAmount = COALESCE(consume.TotalConsumeAmount, 0), | |
| 1735 | + kh.F_RemainingRightsAmount = GREATEST(0, COALESCE(billing.TotalBillingAmount, 0) - COALESCE(consume.TotalConsumeAmount, 0) - COALESCE(refund.TotalRefundAmount, 0)), | |
| 1736 | + kh.F_SleepDays = GREATEST(0, DATEDIFF(CURDATE(), COALESCE(kh.F_LastConsumeTime, visit.LastVisitTime, kh.F_CreateTime))), | |
| 1737 | + kh.F_SleepStartTime = CASE | |
| 1738 | + WHEN DATEDIFF(CURDATE(), COALESCE(kh.F_LastConsumeTime, visit.LastVisitTime, kh.F_CreateTime)) >= {minSleepThreshold} | |
| 1739 | + AND kh.F_SleepStartTime IS NULL | |
| 1740 | + THEN DATE(COALESCE(kh.F_LastConsumeTime, visit.LastVisitTime, kh.F_CreateTime)) | |
| 1741 | + WHEN DATEDIFF(CURDATE(), COALESCE(kh.F_LastConsumeTime, visit.LastVisitTime, kh.F_CreateTime)) < {minSleepThreshold} | |
| 1742 | + THEN NULL | |
| 1743 | + ELSE kh.F_SleepStartTime | |
| 1744 | + END, | |
| 1745 | + kh.F_ConsumeLevel = CASE | |
| 1746 | + WHEN COALESCE(consume.TotalConsumeAmount, 0) >= 200000 THEN 5 | |
| 1747 | + WHEN COALESCE(consume.TotalConsumeAmount, 0) >= 100000 THEN 4 | |
| 1748 | + WHEN COALESCE(consume.TotalConsumeAmount, 0) >= 50000 THEN 3 | |
| 1749 | + WHEN COALESCE(consume.TotalConsumeAmount, 0) >= 30000 THEN 2 | |
| 1750 | + WHEN COALESCE(consume.TotalConsumeAmount, 0) >= 10000 THEN 1 | |
| 1751 | + ELSE 0 | |
| 1752 | + END, | |
| 1753 | + kh.F_ConsumeLevelUpdateTime = CASE | |
| 1754 | + WHEN kh.F_ConsumeLevel != CASE | |
| 1755 | + WHEN COALESCE(consume.TotalConsumeAmount, 0) >= 200000 THEN 5 | |
| 1756 | + WHEN COALESCE(consume.TotalConsumeAmount, 0) >= 100000 THEN 4 | |
| 1757 | + WHEN COALESCE(consume.TotalConsumeAmount, 0) >= 50000 THEN 3 | |
| 1758 | + WHEN COALESCE(consume.TotalConsumeAmount, 0) >= 30000 THEN 2 | |
| 1759 | + WHEN COALESCE(consume.TotalConsumeAmount, 0) >= 10000 THEN 1 | |
| 1760 | + ELSE 0 | |
| 1761 | + END THEN NOW() | |
| 1762 | + WHEN kh.F_ConsumeLevelUpdateTime IS NULL AND CASE | |
| 1763 | + WHEN COALESCE(consume.TotalConsumeAmount, 0) >= 200000 THEN 5 | |
| 1764 | + WHEN COALESCE(consume.TotalConsumeAmount, 0) >= 100000 THEN 4 | |
| 1765 | + WHEN COALESCE(consume.TotalConsumeAmount, 0) >= 50000 THEN 3 | |
| 1766 | + WHEN COALESCE(consume.TotalConsumeAmount, 0) >= 30000 THEN 2 | |
| 1767 | + WHEN COALESCE(consume.TotalConsumeAmount, 0) >= 10000 THEN 1 | |
| 1768 | + ELSE 0 | |
| 1769 | + END > 0 THEN NOW() | |
| 1770 | + ELSE kh.F_ConsumeLevelUpdateTime | |
| 1771 | + END | |
| 1772 | +WHERE kh.F_IsEffective = 1"; | |
| 1773 | + | |
| 1774 | + var affectedRows = await _db.Ado.ExecuteCommandAsync(updateSql); | |
| 1775 | + var sqlEndTime = DateTime.Now; | |
| 1776 | + var duration = (sqlEndTime - sqlStartTime).TotalSeconds; | |
| 1777 | + | |
| 1778 | + _logger.LogInformation("SQL批量更新完成,影响行数:{Rows},耗时:{Duration}秒", affectedRows, duration); | |
| 1779 | + | |
| 1780 | + var endTime = DateTime.Now; | |
| 1781 | + var totalDuration = (endTime - startTime).TotalSeconds; | |
| 1782 | + | |
| 1783 | + _logger.LogInformation("批量更新会员信息完成,总数量:{Total},影响行数:{Rows},耗时:{Duration}秒", | |
| 1784 | + totalCount, affectedRows, totalDuration); | |
| 1785 | + | |
| 1786 | + return new | |
| 1787 | + { | |
| 1788 | + success = true, | |
| 1789 | + totalCount = totalCount, | |
| 1790 | + successCount = affectedRows, | |
| 1791 | + failCount = totalCount - affectedRows, | |
| 1792 | + duration = $"{totalDuration:F2}秒", | |
| 1793 | + sqlDuration = $"{duration:F2}秒" | |
| 1794 | + }; | |
| 1795 | + } | |
| 1796 | + catch (Exception ex) | |
| 1797 | + { | |
| 1798 | + _logger.LogError(ex, "批量更新会员信息失败"); | |
| 1799 | + throw NCCException.Oh($"批量更新会员信息失败:{ex.Message}"); | |
| 1800 | + } | |
| 1801 | + } | |
| 1802 | + | |
| 1803 | + /// <summary> | |
| 1804 | + /// 更新会员信息内部方法 | |
| 1805 | + /// </summary> | |
| 1806 | + /// <param name="member">会员实体</param> | |
| 1807 | + private async Task UpdateMemberInfoInternal(LqKhxxEntity member) | |
| 1808 | + { | |
| 1809 | + // 1. 判断生美/医美/科技部会员 | |
| 1810 | + await UpdateMemberTypeFlags(member); | |
| 1811 | + | |
| 1812 | + // 2. 计算到店天数 | |
| 1813 | + await UpdateVisitDays(member); | |
| 1814 | + | |
| 1815 | + // 3. 计算沉睡天数 | |
| 1816 | + await UpdateSleepDays(member); | |
| 1817 | + | |
| 1818 | + // 4. 计算消费金额和等级 | |
| 1819 | + await UpdateConsumeInfo(member); | |
| 1820 | + | |
| 1821 | + // 5. 更新到数据库 | |
| 1822 | + await _db.Updateable(member) | |
| 1823 | + .UpdateColumns(x => new | |
| 1824 | + { | |
| 1825 | + x.IsBeautyMember, | |
| 1826 | + x.IsMedicalMember, | |
| 1827 | + x.IsTechMember, | |
| 1828 | + x.BeautyMemberTime, | |
| 1829 | + x.MedicalMemberTime, | |
| 1830 | + x.TechMemberTime, | |
| 1831 | + x.FirstVisitTime, | |
| 1832 | + x.LastVisitTime, | |
| 1833 | + x.VisitDays, | |
| 1834 | + x.SleepStartTime, | |
| 1835 | + x.SleepDays, | |
| 1836 | + x.TotalConsumeAmount, | |
| 1837 | + x.TotalBillingAmount, | |
| 1838 | + x.RemainingRightsAmount, | |
| 1839 | + x.ConsumeLevel, | |
| 1840 | + x.ConsumeLevelUpdateTime | |
| 1841 | + }) | |
| 1842 | + .ExecuteCommandAsync(); | |
| 1843 | + } | |
| 1844 | + | |
| 1845 | + /// <summary> | |
| 1846 | + /// 更新会员类型标识(生美/医美/科技部会员) | |
| 1847 | + /// </summary> | |
| 1848 | + private async Task UpdateMemberTypeFlags(LqKhxxEntity member) | |
| 1849 | + { | |
| 1850 | + // 查询该会员的所有有效开单品项明细 | |
| 1851 | + var billingItems = await _db.Queryable<LqKdPxmxEntity>() | |
| 1852 | + .InnerJoin<LqKdKdjlbEntity>((pxmx, kd) => pxmx.Glkdbh == kd.Id) | |
| 1853 | + .Where((pxmx, kd) => | |
| 1854 | + pxmx.MemberId == member.Id | |
| 1855 | + && pxmx.IsEffective == StatusEnum.有效.GetHashCode() | |
| 1856 | + && kd.IsEffective == StatusEnum.有效.GetHashCode() | |
| 1857 | + && pxmx.ActualPrice > 0 | |
| 1858 | + && pxmx.Px != "61") // 排除女神卡 | |
| 1859 | + .Select((pxmx, kd) => new | |
| 1860 | + { | |
| 1861 | + pxmx.ItemCategory, | |
| 1862 | + pxmx.ActualPrice, | |
| 1863 | + pxmx.Pxjg, | |
| 1864 | + kd.Kdrq | |
| 1865 | + }) | |
| 1866 | + .ToListAsync(); | |
| 1867 | + | |
| 1868 | + // 判断生美会员:开卡品项为生美并且金额大于阈值 | |
| 1869 | + var beautyAmount = billingItems | |
| 1870 | + .Where(x => x.ItemCategory == "生美") | |
| 1871 | + .Sum(x => x.ActualPrice); | |
| 1872 | + | |
| 1873 | + if (beautyAmount > MemberInfoUpdateConfig.BeautyMemberAmountThreshold) | |
| 1874 | + { | |
| 1875 | + member.IsBeautyMember = 1; | |
| 1876 | + if (!member.BeautyMemberTime.HasValue) | |
| 1877 | + { | |
| 1878 | + var firstBeautyBilling = billingItems | |
| 1879 | + .Where(x => x.ItemCategory == "生美") | |
| 1880 | + .OrderBy(x => x.Kdrq) | |
| 1881 | + .FirstOrDefault(); | |
| 1882 | + member.BeautyMemberTime = firstBeautyBilling?.Kdrq; | |
| 1883 | + } | |
| 1884 | + } | |
| 1885 | + else | |
| 1886 | + { | |
| 1887 | + member.IsBeautyMember = 0; | |
| 1888 | + member.BeautyMemberTime = null; | |
| 1889 | + } | |
| 1890 | + | |
| 1891 | + // 判断医美会员:开卡品项为医美并且金额大于阈值 | |
| 1892 | + var medicalAmount = billingItems | |
| 1893 | + .Where(x => x.ItemCategory == "医美") | |
| 1894 | + .Sum(x => x.ActualPrice); | |
| 1895 | + | |
| 1896 | + if (medicalAmount > MemberInfoUpdateConfig.MedicalMemberAmountThreshold) | |
| 1897 | + { | |
| 1898 | + member.IsMedicalMember = 1; | |
| 1899 | + if (!member.MedicalMemberTime.HasValue) | |
| 1900 | + { | |
| 1901 | + var firstMedicalBilling = billingItems | |
| 1902 | + .Where(x => x.ItemCategory == "医美") | |
| 1903 | + .OrderBy(x => x.Kdrq) | |
| 1904 | + .FirstOrDefault(); | |
| 1905 | + member.MedicalMemberTime = firstMedicalBilling?.Kdrq; | |
| 1906 | + } | |
| 1907 | + } | |
| 1908 | + else | |
| 1909 | + { | |
| 1910 | + member.IsMedicalMember = 0; | |
| 1911 | + member.MedicalMemberTime = null; | |
| 1912 | + } | |
| 1913 | + | |
| 1914 | + // 判断科技部会员:科美单品项金额大于等于阈值 | |
| 1915 | + var hasTechMember = billingItems | |
| 1916 | + .Where(x => x.ItemCategory == "科美") | |
| 1917 | + .Any(x => x.ActualPrice >= MemberInfoUpdateConfig.TechMemberAmountThreshold); | |
| 1918 | + | |
| 1919 | + if (hasTechMember) | |
| 1920 | + { | |
| 1921 | + member.IsTechMember = 1; | |
| 1922 | + if (!member.TechMemberTime.HasValue) | |
| 1923 | + { | |
| 1924 | + var firstTechBilling = billingItems | |
| 1925 | + .Where(x => x.ItemCategory == "科美" && x.ActualPrice >= MemberInfoUpdateConfig.TechMemberAmountThreshold) | |
| 1926 | + .OrderBy(x => x.Kdrq) | |
| 1927 | + .FirstOrDefault(); | |
| 1928 | + member.TechMemberTime = firstTechBilling?.Kdrq; | |
| 1929 | + } | |
| 1930 | + } | |
| 1931 | + else | |
| 1932 | + { | |
| 1933 | + member.IsTechMember = 0; | |
| 1934 | + member.TechMemberTime = null; | |
| 1935 | + } | |
| 1936 | + } | |
| 1937 | + | |
| 1938 | + /// <summary> | |
| 1939 | + /// 更新到店天数 | |
| 1940 | + /// </summary> | |
| 1941 | + private async Task UpdateVisitDays(LqKhxxEntity member) | |
| 1942 | + { | |
| 1943 | + // 查询该会员的所有有效耗卡记录,按天去重 | |
| 1944 | + var visitDates = await _db.Queryable<LqXhHyhkEntity>() | |
| 1945 | + .Where(x => | |
| 1946 | + x.Hy == member.Id | |
| 1947 | + && x.IsEffective == StatusEnum.有效.GetHashCode()) | |
| 1948 | + .Select(x => new | |
| 1949 | + { | |
| 1950 | + VisitDate = SqlFunc.ToDate(x.Hksj) | |
| 1951 | + }) | |
| 1952 | + .Distinct() | |
| 1953 | + .ToListAsync(); | |
| 1954 | + | |
| 1955 | + member.VisitDays = visitDates.Count; | |
| 1956 | + | |
| 1957 | + // 更新首次到店时间和最后到店时间 | |
| 1958 | + if (visitDates.Any()) | |
| 1959 | + { | |
| 1960 | + var firstVisit = await _db.Queryable<LqXhHyhkEntity>() | |
| 1961 | + .Where(x => x.Hy == member.Id && x.IsEffective == StatusEnum.有效.GetHashCode()) | |
| 1962 | + .OrderBy(x => x.Hksj) | |
| 1963 | + .Select(x => x.Hksj) | |
| 1964 | + .FirstAsync(); | |
| 1965 | + member.FirstVisitTime = firstVisit; | |
| 1966 | + | |
| 1967 | + var lastVisit = await _db.Queryable<LqXhHyhkEntity>() | |
| 1968 | + .Where(x => x.Hy == member.Id && x.IsEffective == StatusEnum.有效.GetHashCode()) | |
| 1969 | + .OrderBy(x => x.Hksj, OrderByType.Desc) | |
| 1970 | + .Select(x => x.Hksj) | |
| 1971 | + .FirstAsync(); | |
| 1972 | + member.LastVisitTime = lastVisit; | |
| 1973 | + } | |
| 1974 | + else | |
| 1975 | + { | |
| 1976 | + member.FirstVisitTime = null; | |
| 1977 | + member.LastVisitTime = null; | |
| 1978 | + } | |
| 1979 | + } | |
| 1980 | + | |
| 1981 | + /// <summary> | |
| 1982 | + /// 更新沉睡天数 | |
| 1983 | + /// </summary> | |
| 1984 | + private Task UpdateSleepDays(LqKhxxEntity member) | |
| 1985 | + { | |
| 1986 | + var now = DateTime.Now; | |
| 1987 | + var lastConsumeTime = member.LastConsumeTime; | |
| 1988 | + | |
| 1989 | + if (!lastConsumeTime.HasValue) | |
| 1990 | + { | |
| 1991 | + // 如果没有最后消费时间,使用最后到店时间 | |
| 1992 | + lastConsumeTime = member.LastVisitTime; | |
| 1993 | + } | |
| 1994 | + | |
| 1995 | + if (lastConsumeTime.HasValue) | |
| 1996 | + { | |
| 1997 | + var daysSinceLastConsume = (now.Date - lastConsumeTime.Value.Date).Days; | |
| 1998 | + member.SleepDays = Math.Max(0, daysSinceLastConsume); | |
| 1999 | + | |
| 2000 | + // 如果超过最小沉睡阈值,设置沉睡开始时间 | |
| 2001 | + var minSleepThreshold = MemberInfoUpdateConfig.SleepDaysThresholds.Min(); | |
| 2002 | + if (member.SleepDays >= minSleepThreshold) | |
| 2003 | + { | |
| 2004 | + if (!member.SleepStartTime.HasValue) | |
| 2005 | + { | |
| 2006 | + member.SleepStartTime = lastConsumeTime.Value.Date; | |
| 2007 | + } | |
| 2008 | + } | |
| 2009 | + else | |
| 2010 | + { | |
| 2011 | + member.SleepStartTime = null; | |
| 2012 | + } | |
| 2013 | + } | |
| 2014 | + else | |
| 2015 | + { | |
| 2016 | + member.SleepDays = 0; | |
| 2017 | + member.SleepStartTime = null; | |
| 2018 | + } | |
| 2019 | + | |
| 2020 | + return Task.CompletedTask; | |
| 2021 | + } | |
| 2022 | + | |
| 2023 | + /// <summary> | |
| 2024 | + /// 更新消费信息(开卡总金额、耗卡总金额、剩余权益总金额、消费等级) | |
| 2025 | + /// </summary> | |
| 2026 | + private async Task UpdateConsumeInfo(LqKhxxEntity member) | |
| 2027 | + { | |
| 2028 | + // 1. 计算开卡总金额(累计所有开单的实付业绩总和) | |
| 2029 | + var totalBillingAmount = await _db.Queryable<LqKdKdjlbEntity>().Where(x => x.Kdhy == member.Id && x.IsEffective == StatusEnum.有效.GetHashCode()).SumAsync(x => (decimal?)x.Sfyj) ?? 0m; | |
| 2030 | + member.TotalBillingAmount = totalBillingAmount; | |
| 2031 | + | |
| 2032 | + // 2. 计算耗卡总金额(累计所有耗卡的金额总和) | |
| 2033 | + var totalConsumeAmount = await _db.Queryable<LqXhHyhkEntity>().Where(x => x.Hy == member.Id && x.IsEffective == StatusEnum.有效.GetHashCode()).SumAsync(x => (decimal?)x.Xfje) ?? 0m; | |
| 2034 | + member.TotalConsumeAmount = totalConsumeAmount; | |
| 2035 | + | |
| 2036 | + // 3. 计算退款总金额 | |
| 2037 | + var totalRefundAmount = await _db.Queryable<LqHytkHytkEntity>().Where(x => x.Hy == member.Id && x.IsEffective == StatusEnum.有效.GetHashCode()).SumAsync(x => (decimal?)x.ActualRefundAmount) ?? 0m; | |
| 2038 | + | |
| 2039 | + // 4. 计算剩余权益总金额 | |
| 2040 | + member.RemainingRightsAmount = totalBillingAmount - totalConsumeAmount - totalRefundAmount; | |
| 2041 | + if (member.RemainingRightsAmount < 0) | |
| 2042 | + { | |
| 2043 | + member.RemainingRightsAmount = 0m; | |
| 2044 | + } | |
| 2045 | + | |
| 2046 | + // 5. 计算消费等级(根据耗卡总金额) | |
| 2047 | + var oldLevel = member.ConsumeLevel; | |
| 2048 | + member.ConsumeLevel = MemberInfoUpdateConfig.GetConsumeLevel(totalConsumeAmount); | |
| 2049 | + | |
| 2050 | + // 如果等级发生变化,更新等级更新时间 | |
| 2051 | + if (oldLevel != member.ConsumeLevel) | |
| 2052 | + { | |
| 2053 | + member.ConsumeLevelUpdateTime = DateTime.Now; | |
| 2054 | + } | |
| 2055 | + else if (!member.ConsumeLevelUpdateTime.HasValue && member.ConsumeLevel > 0) | |
| 2056 | + { | |
| 2057 | + // 如果之前没有等级更新时间,但当前有等级,设置当前时间 | |
| 2058 | + member.ConsumeLevelUpdateTime = DateTime.Now; | |
| 2059 | + } | |
| 2060 | + } | |
| 2061 | + #endregion | |
| 2062 | + | |
| 1534 | 2063 | } |
| 1535 | 2064 | } | ... | ... |
netcore/src/Modularity/Extend/NCC.Extend/LqTkjlbService.cs
| ... | ... | @@ -192,6 +192,7 @@ namespace NCC.Extend.LqTkjlb |
| 192 | 192 | MemberInfo.Khlx = MemberTypeEnum.线索.GetHashCode().ToString(); |
| 193 | 193 | MemberInfo.Dah = MemberNumber; |
| 194 | 194 | MemberInfo.Jdqd = "19.9卡"; |
| 195 | + MemberInfo.Zcsj = DateTime.Now; | |
| 195 | 196 | |
| 196 | 197 | //找到input.expansionUserId的用户信息 |
| 197 | 198 | var userInfo = await _db.Queryable<UserEntity>().Where(u => u.Id == input.expansionUserId).FirstAsync(); | ... | ... |
netcore/src/Modularity/Extend/NCC.Extend/LqXhHyhkService.cs
| ... | ... | @@ -32,6 +32,7 @@ using NCC.Extend.Entitys.lq_xh_jksyj; |
| 32 | 32 | using NCC.Extend.Entitys.lq_xh_kjbsyj; |
| 33 | 33 | using NCC.Extend.Entitys.lq_xh_pxmx; |
| 34 | 34 | using NCC.Extend.Entitys.lq_xmzl; |
| 35 | +using NCC.Extend.Entitys.lq_mdxx; | |
| 35 | 36 | using NCC.Extend.Interfaces.LqXhHyhk; |
| 36 | 37 | using NCC.FriendlyException; |
| 37 | 38 | using NCC.JsonSerialization; |
| ... | ... | @@ -899,6 +900,11 @@ namespace NCC.Extend.LqXhHyhk |
| 899 | 900 | { |
| 900 | 901 | memberInfo.Khlx = MemberTypeEnum.新客.GetHashCode().ToString(); |
| 901 | 902 | } |
| 903 | + //判断消费金额是否大于0 | |
| 904 | + if (entity.Sgfy > 0) | |
| 905 | + { | |
| 906 | + memberInfo.LastConsumeTime = DateTime.Now; | |
| 907 | + } | |
| 902 | 908 | //保存会员信息 |
| 903 | 909 | await _db.Updateable(memberInfo).ExecuteCommandAsync(); |
| 904 | 910 | // 新增会员耗卡记录 |
| ... | ... | @@ -1003,6 +1009,8 @@ namespace NCC.Extend.LqXhHyhk |
| 1003 | 1009 | } |
| 1004 | 1010 | } |
| 1005 | 1011 | } |
| 1012 | + //查询开单记录 | |
| 1013 | + var billingRecord = await _db.Queryable<LqKdKdjlbEntity>().Where(x => x.Kdhy == entity.Hy && x.IsEffective == StatusEnum.有效.GetHashCode() && x.Sfyj > 0).AnyAsync(); | |
| 1006 | 1014 | // 剔除所有T区健康师(姓名中包含"T区"的健康师) |
| 1007 | 1015 | var NoTallJksyjEntities = allJksyjEntities.Where(x => x.Jks != null && x.Jks != "" && (x.Jksxm == null || !x.Jksxm.Contains("T区"))).ToList(); |
| 1008 | 1016 | //获取这次耗卡有多少个健康师 |
| ... | ... | @@ -1025,6 +1033,7 @@ namespace NCC.Extend.LqXhHyhk |
| 1025 | 1033 | Quantity = 1 / (decimal)jksCount, |
| 1026 | 1034 | CreateTime = DateTime.Now, |
| 1027 | 1035 | IsEffective = StatusEnum.有效.GetHashCode(), |
| 1036 | + HasBilling = billingRecord ? 1 : 0, | |
| 1028 | 1037 | }; |
| 1029 | 1038 | allPersonTimesRecordEntities.Add(personTimesRecordEntity); |
| 1030 | 1039 | } |
| ... | ... | @@ -1050,6 +1059,7 @@ namespace NCC.Extend.LqXhHyhk |
| 1050 | 1059 | Quantity = 1 / (decimal)kjbCount, |
| 1051 | 1060 | CreateTime = DateTime.Now, |
| 1052 | 1061 | IsEffective = StatusEnum.有效.GetHashCode(), |
| 1062 | + HasBilling = billingRecord ? 1 : 0, | |
| 1053 | 1063 | }; |
| 1054 | 1064 | allPersonTimesRecordEntities.Add(personTimesRecordEntity); |
| 1055 | 1065 | } |
| ... | ... | @@ -1894,5 +1904,180 @@ namespace NCC.Extend.LqXhHyhk |
| 1894 | 1904 | } |
| 1895 | 1905 | #endregion |
| 1896 | 1906 | |
| 1907 | + #region 耗卡品项明细列表 | |
| 1908 | + /// <summary> | |
| 1909 | + /// 获取耗卡品项明细记录列表 | |
| 1910 | + /// </summary> | |
| 1911 | + /// <remarks> | |
| 1912 | + /// 根据多种条件查询耗卡品项明细记录列表,支持分页、排序、筛选等功能 | |
| 1913 | + /// | |
| 1914 | + /// 示例请求: | |
| 1915 | + /// GET /api/Extend/LqXhHyhk/consume-item-detail-list?currentPage=1&pageSize=10&startTime=2025-01-01&endTime=2025-01-31 | |
| 1916 | + /// | |
| 1917 | + /// 传入参数说明: | |
| 1918 | + /// - currentPage: 当前页码(必填,默认1) | |
| 1919 | + /// - pageSize: 每页数量(必填,默认10) | |
| 1920 | + /// - sidx: 排序字段(可选,默认"yjsj") | |
| 1921 | + /// - sort: 排序方式(可选,ASC/DESC,默认DESC) | |
| 1922 | + /// - Id: 品项明细ID(可选,精确匹配) | |
| 1923 | + /// - ConsumeId: 耗卡记录ID(可选,精确匹配) | |
| 1924 | + /// - StartConsumeTime/EndConsumeTime: 耗卡时间范围(可选,格式:yyyy-MM-dd) | |
| 1925 | + /// - startTime/endTime: 耗卡时间范围(兼容参数名,格式:yyyy-MM-dd) | |
| 1926 | + /// - MemberId: 会员ID(可选,精确匹配) | |
| 1927 | + /// - MemberName: 会员名称(可选,模糊查询) | |
| 1928 | + /// - MemberPhone: 会员手机号(可选,精确匹配) | |
| 1929 | + /// - ItemId: 品项ID(可选,精确匹配) | |
| 1930 | + /// - ItemName: 品项名称(可选,模糊查询) | |
| 1931 | + /// - ItemType: 品项类型(可选,精确匹配) | |
| 1932 | + /// - SourceType: 来源类型(可选,精确匹配) | |
| 1933 | + /// - StoreId: 门店ID(可选,精确匹配) | |
| 1934 | + /// | |
| 1935 | + /// 返回字段说明: | |
| 1936 | + /// - id: 品项明细ID | |
| 1937 | + /// - consumeId: 耗卡记录ID | |
| 1938 | + /// - consumeTime: 耗卡时间(DateTime格式) | |
| 1939 | + /// - memberName: 会员名称(关联会员表) | |
| 1940 | + /// - memberPhone: 会员手机号(关联会员表) | |
| 1941 | + /// - itemName: 品项名称(品项明细表字段) | |
| 1942 | + /// - itemType: 品项类型(关联项目资料表的qt2字段) | |
| 1943 | + /// - itemPrice: 品项价格(decimal类型) | |
| 1944 | + /// - projectNumber: 项目次数(decimal类型,支持小数) | |
| 1945 | + /// - originalProjectNumber: 原始项目次数(decimal类型) | |
| 1946 | + /// - totalPrice: 合计金额(decimal类型) | |
| 1947 | + /// - sourceType: 来源类型(字符串,如:购买、赠送、体验) | |
| 1948 | + /// - storeId: 门店ID(关联耗卡记录表的门店ID) | |
| 1949 | + /// - storeName: 门店名称(关联门店表的店名字段) | |
| 1950 | + /// </remarks> | |
| 1951 | + /// <param name="input">查询参数</param> | |
| 1952 | + /// <returns>耗卡品项明细记录列表(分页)</returns> | |
| 1953 | + /// <response code="200">查询成功,返回耗卡品项明细记录列表</response> | |
| 1954 | + /// <response code="400">参数错误,如页码或页大小无效</response> | |
| 1955 | + /// <response code="500">服务器错误,查询过程中发生异常</response> | |
| 1956 | + [HttpGet("consume-item-detail-list")] | |
| 1957 | + public async Task<dynamic> GetConsumeItemDetailList([FromQuery] ConsumeItemDetailListQueryInput input) | |
| 1958 | + { | |
| 1959 | + try | |
| 1960 | + { | |
| 1961 | + var sidx = input.sidx == null ? "yjsj" : input.sidx; | |
| 1962 | + var sort = string.IsNullOrEmpty(input.sort) ? "DESC" : input.sort; | |
| 1963 | + | |
| 1964 | + // 处理耗卡时间范围(兼容 StartConsumeTime/EndConsumeTime 和 startTime/endTime 两种参数名) | |
| 1965 | + DateTime? startConsumeTime = null; | |
| 1966 | + DateTime? endConsumeTime = null; | |
| 1967 | + | |
| 1968 | + // 优先使用 StartConsumeTime/EndConsumeTime,如果没有则使用 startTime/endTime | |
| 1969 | + string startTimeStr = !string.IsNullOrEmpty(input.StartConsumeTime) ? input.StartConsumeTime : input.startTime; | |
| 1970 | + string endTimeStr = !string.IsNullOrEmpty(input.EndConsumeTime) ? input.EndConsumeTime : input.endTime; | |
| 1971 | + | |
| 1972 | + if (!string.IsNullOrEmpty(startTimeStr) && !string.IsNullOrEmpty(endTimeStr)) | |
| 1973 | + { | |
| 1974 | + // 尝试解析日期字符串(支持多种格式) | |
| 1975 | + if (DateTime.TryParse(startTimeStr, out DateTime startDate)) | |
| 1976 | + { | |
| 1977 | + startConsumeTime = startDate; | |
| 1978 | + } | |
| 1979 | + else | |
| 1980 | + { | |
| 1981 | + throw NCCException.Oh($"开始时间格式错误:{startTimeStr}"); | |
| 1982 | + } | |
| 1983 | + | |
| 1984 | + if (DateTime.TryParse(endTimeStr, out DateTime endDate)) | |
| 1985 | + { | |
| 1986 | + endConsumeTime = endDate; | |
| 1987 | + } | |
| 1988 | + else | |
| 1989 | + { | |
| 1990 | + throw NCCException.Oh($"结束时间格式错误:{endTimeStr}"); | |
| 1991 | + } | |
| 1992 | + } | |
| 1993 | + | |
| 1994 | + // 优化查询:先分页查询主表,再批量查询关联数据,避免子查询性能问题 | |
| 1995 | + // 1. 先构建基础查询条件 | |
| 1996 | + var baseQuery = _db.Queryable<LqXhPxmxEntity>() | |
| 1997 | + .Where(pxmx => pxmx.IsEffective == StatusEnum.有效.GetHashCode()) | |
| 1998 | + .WhereIF(!string.IsNullOrEmpty(input.Id), pxmx => pxmx.Id == input.Id) | |
| 1999 | + .WhereIF(!string.IsNullOrEmpty(input.ConsumeId), pxmx => pxmx.ConsumeInfoId == input.ConsumeId) | |
| 2000 | + .WhereIF(startConsumeTime.HasValue, pxmx => pxmx.Yjsj >= new DateTime(startConsumeTime.Value.Year, startConsumeTime.Value.Month, startConsumeTime.Value.Day, 0, 0, 0)) | |
| 2001 | + .WhereIF(endConsumeTime.HasValue, pxmx => pxmx.Yjsj <= new DateTime(endConsumeTime.Value.Year, endConsumeTime.Value.Month, endConsumeTime.Value.Day, 23, 59, 59)) | |
| 2002 | + .WhereIF(!string.IsNullOrEmpty(input.MemberId), pxmx => pxmx.MemberId == input.MemberId) | |
| 2003 | + .WhereIF(!string.IsNullOrEmpty(input.ItemId), pxmx => pxmx.Px == input.ItemId) | |
| 2004 | + .WhereIF(!string.IsNullOrEmpty(input.ItemName), pxmx => pxmx.Pxmc != null && pxmx.Pxmc.Contains(input.ItemName)) | |
| 2005 | + .WhereIF(!string.IsNullOrEmpty(input.SourceType), pxmx => pxmx.SourceType == input.SourceType) | |
| 2006 | + .WhereIF(!string.IsNullOrEmpty(input.ItemType), pxmx => pxmx.ItemCategory == input.ItemType); | |
| 2007 | + | |
| 2008 | + // 2. 通过 EXISTS 子查询筛选关联字段(在分页前筛选,确保分页准确) | |
| 2009 | + baseQuery = baseQuery.WhereIF(!string.IsNullOrEmpty(input.MemberName), pxmx => SqlFunc.Subqueryable<LqKhxxEntity>().Where(x => x.Id == pxmx.MemberId && x.Khmc != null && x.Khmc.Contains(input.MemberName)).Any()) | |
| 2010 | + .WhereIF(!string.IsNullOrEmpty(input.MemberPhone), pxmx => SqlFunc.Subqueryable<LqKhxxEntity>().Where(x => x.Id == pxmx.MemberId && x.Sjh == input.MemberPhone).Any()) | |
| 2011 | + .WhereIF(!string.IsNullOrEmpty(input.StoreId), pxmx => SqlFunc.Subqueryable<LqXhHyhkEntity>().Where(x => x.Id == pxmx.ConsumeInfoId && x.Md == input.StoreId).Any()); | |
| 2012 | + | |
| 2013 | + // 3. 先分页查询主表数据(查询实体类,提高性能) | |
| 2014 | + var pagedData = await baseQuery.OrderBy(sidx + " " + sort).ToPagedListAsync(input.currentPage, input.pageSize); | |
| 2015 | + | |
| 2016 | + // 4. 批量查询关联数据 | |
| 2017 | + var itemIds = pagedData.list.Select(x => x.Id).ToList(); | |
| 2018 | + var memberIds = pagedData.list.Where(x => !string.IsNullOrEmpty(x.MemberId)).Select(x => x.MemberId).Distinct().ToList(); | |
| 2019 | + var consumeIds = pagedData.list.Where(x => !string.IsNullOrEmpty(x.ConsumeInfoId)).Select(x => x.ConsumeInfoId).Distinct().ToList(); | |
| 2020 | + | |
| 2021 | + // 批量查询会员信息 | |
| 2022 | + var memberDict = new Dictionary<string, (string Name, string Phone)>(); | |
| 2023 | + if (memberIds.Any()) | |
| 2024 | + { | |
| 2025 | + var members = await _db.Queryable<LqKhxxEntity>().Where(x => memberIds.Contains(x.Id)).Select(x => new { x.Id, x.Khmc, x.Sjh }).ToListAsync(); | |
| 2026 | + memberDict = members.ToDictionary(x => x.Id, x => (x.Khmc ?? "", x.Sjh ?? "")); | |
| 2027 | + } | |
| 2028 | + | |
| 2029 | + // 批量查询耗卡记录,获取门店ID和耗卡时间 | |
| 2030 | + var consumeDict = new Dictionary<string, (string StoreId, DateTime? ConsumeTime)>(); | |
| 2031 | + if (consumeIds.Any()) | |
| 2032 | + { | |
| 2033 | + var consumes = await _db.Queryable<LqXhHyhkEntity>().Where(x => consumeIds.Contains(x.Id)).Select(x => new { x.Id, x.Md, x.Hksj }).ToListAsync(); | |
| 2034 | + consumeDict = consumes.ToDictionary(x => x.Id, x => (x.Md ?? "", x.Hksj)); | |
| 2035 | + } | |
| 2036 | + | |
| 2037 | + // 批量查询门店信息 | |
| 2038 | + var storeDict = new Dictionary<string, string>(); | |
| 2039 | + var storeIds = consumeDict.Values.Where(x => !string.IsNullOrEmpty(x.StoreId)).Select(x => x.StoreId).Distinct().ToList(); | |
| 2040 | + if (storeIds.Any()) | |
| 2041 | + { | |
| 2042 | + var stores = await _db.Queryable<LqMdxxEntity>().Where(x => storeIds.Contains(x.Id)).Select(x => new { x.Id, x.Dm }).ToListAsync(); | |
| 2043 | + storeDict = stores.ToDictionary(x => x.Id, x => x.Dm ?? ""); | |
| 2044 | + } | |
| 2045 | + | |
| 2046 | + // 5. 组装返回数据 | |
| 2047 | + var resultList = pagedData.list.Select(pxmx => new ConsumeItemDetailListOutput | |
| 2048 | + { | |
| 2049 | + id = pxmx.Id, | |
| 2050 | + consumeId = pxmx.ConsumeInfoId, | |
| 2051 | + consumeTime = pxmx.ConsumeInfoId != null && consumeDict.ContainsKey(pxmx.ConsumeInfoId) ? consumeDict[pxmx.ConsumeInfoId].ConsumeTime : pxmx.Yjsj, | |
| 2052 | + memberName = pxmx.MemberId != null && memberDict.ContainsKey(pxmx.MemberId) ? memberDict[pxmx.MemberId].Name : "", | |
| 2053 | + memberPhone = pxmx.MemberId != null && memberDict.ContainsKey(pxmx.MemberId) ? memberDict[pxmx.MemberId].Phone : "", | |
| 2054 | + itemName = pxmx.Pxmc, | |
| 2055 | + itemType = pxmx.ItemCategory, | |
| 2056 | + itemPrice = pxmx.Pxjg, | |
| 2057 | + projectNumber = pxmx.ProjectNumber, | |
| 2058 | + originalProjectNumber = pxmx.OriginalProjectNumber, | |
| 2059 | + totalPrice = pxmx.TotalPrice, | |
| 2060 | + sourceType = pxmx.SourceType, | |
| 2061 | + storeId = pxmx.ConsumeInfoId != null && consumeDict.ContainsKey(pxmx.ConsumeInfoId) ? consumeDict[pxmx.ConsumeInfoId].StoreId : "", | |
| 2062 | + 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] : "" | |
| 2063 | + }).ToList(); | |
| 2064 | + | |
| 2065 | + // 6. 返回分页结果 | |
| 2066 | + var result = new SqlSugarPagedList<ConsumeItemDetailListOutput> | |
| 2067 | + { | |
| 2068 | + list = resultList, | |
| 2069 | + pagination = pagedData.pagination | |
| 2070 | + }; | |
| 2071 | + | |
| 2072 | + return PageResult<ConsumeItemDetailListOutput>.SqlSugarPageResult(result); | |
| 2073 | + } | |
| 2074 | + catch (Exception ex) | |
| 2075 | + { | |
| 2076 | + _logger.LogError(ex, $"获取耗卡品项明细记录列表失败: {ex.ToString()}"); | |
| 2077 | + throw NCCException.Oh(ErrorCode.COM1005, $"获取耗卡品项明细记录列表失败: {ex.Message}"); | |
| 2078 | + } | |
| 2079 | + } | |
| 2080 | + #endregion | |
| 2081 | + | |
| 1897 | 2082 | } |
| 1898 | 2083 | } | ... | ... |
sql/添加人次记录表字段.sql
0 → 100644
| 1 | +-- ============================================ | |
| 2 | +-- 为人次记录表(lq_person_times_record)添加字段 | |
| 3 | +-- ============================================ | |
| 4 | +-- 说明:添加"是否有开单"字段,用于标识该人次记录是否关联开单业务 | |
| 5 | +-- ============================================ | |
| 6 | + | |
| 7 | +-- 添加"是否有开单"字段 (0-否, 1-是) | |
| 8 | +ALTER TABLE `lq_person_times_record` | |
| 9 | +ADD COLUMN `F_HasBilling` INT(11) DEFAULT 0 COMMENT '是否有开单(0-否,1-是)' AFTER `F_BusinessType`; | |
| 10 | + | |
| 11 | +-- ============================================ | |
| 12 | +-- 验证字段添加 | |
| 13 | +-- ============================================ | |
| 14 | +-- SELECT COLUMN_NAME, DATA_TYPE, IS_NULLABLE, COLUMN_KEY, COLUMN_DEFAULT, COLUMN_COMMENT | |
| 15 | +-- FROM INFORMATION_SCHEMA.COLUMNS | |
| 16 | +-- WHERE TABLE_NAME = 'lq_person_times_record' | |
| 17 | +-- AND COLUMN_NAME = 'F_HasBilling'; | |
| 18 | + | ... | ... |
sql/添加会员信息表字段.sql
0 → 100644
| 1 | +-- 为会员信息表(lq_khxx)添加新字段 | |
| 2 | +-- 执行时间:2025-01-XX | |
| 3 | + | |
| 4 | +-- ============================================ | |
| 5 | +-- 一、会员类型标识字段 | |
| 6 | +-- ============================================ | |
| 7 | + | |
| 8 | +-- 1. 是否生美会员 (0-否, 1-是) | |
| 9 | +ALTER TABLE `lq_khxx` | |
| 10 | +ADD COLUMN `F_IsBeautyMember` INT(11) DEFAULT 0 COMMENT '是否生美会员(0-否,1-是)' AFTER `F_LastConsumeTime`; | |
| 11 | + | |
| 12 | +-- 2. 是否医美会员 (0-否, 1-是) | |
| 13 | +ALTER TABLE `lq_khxx` | |
| 14 | +ADD COLUMN `F_IsMedicalMember` INT(11) DEFAULT 0 COMMENT '是否医美会员(0-否,1-是)' AFTER `F_IsBeautyMember`; | |
| 15 | + | |
| 16 | +-- 3. 是否科技部会员 (0-否, 1-是) | |
| 17 | +ALTER TABLE `lq_khxx` | |
| 18 | +ADD COLUMN `F_IsTechMember` INT(11) DEFAULT 0 COMMENT '是否科技部会员(0-否,1-是)' AFTER `F_IsMedicalMember`; | |
| 19 | + | |
| 20 | +-- ============================================ | |
| 21 | +-- 二、会员类型成为时间字段(记录成为各类会员的时间) | |
| 22 | +-- ============================================ | |
| 23 | + | |
| 24 | +-- 4. 成为生美会员时间 | |
| 25 | +ALTER TABLE `lq_khxx` | |
| 26 | +ADD COLUMN `F_BeautyMemberTime` DATETIME DEFAULT NULL COMMENT '成为生美会员时间' AFTER `F_IsTechMember`; | |
| 27 | + | |
| 28 | +-- 5. 成为医美会员时间 | |
| 29 | +ALTER TABLE `lq_khxx` | |
| 30 | +ADD COLUMN `F_MedicalMemberTime` DATETIME DEFAULT NULL COMMENT '成为医美会员时间' AFTER `F_BeautyMemberTime`; | |
| 31 | + | |
| 32 | +-- 6. 成为科技部会员时间 | |
| 33 | +ALTER TABLE `lq_khxx` | |
| 34 | +ADD COLUMN `F_TechMemberTime` DATETIME DEFAULT NULL COMMENT '成为科技部会员时间' AFTER `F_MedicalMemberTime`; | |
| 35 | + | |
| 36 | +-- ============================================ | |
| 37 | +-- 三、到店相关字段(用于计算到店天数) | |
| 38 | +-- ============================================ | |
| 39 | + | |
| 40 | +-- 7. 首次到店时间 | |
| 41 | +ALTER TABLE `lq_khxx` | |
| 42 | +ADD COLUMN `F_FirstVisitTime` DATETIME DEFAULT NULL COMMENT '首次到店时间' AFTER `F_TechMemberTime`; | |
| 43 | + | |
| 44 | +-- 8. 最后到店时间 | |
| 45 | +ALTER TABLE `lq_khxx` | |
| 46 | +ADD COLUMN `F_LastVisitTime` DATETIME DEFAULT NULL COMMENT '最后到店时间' AFTER `F_FirstVisitTime`; | |
| 47 | + | |
| 48 | +-- 9. 到店天数 (整数,记录会员到店的天数) | |
| 49 | +ALTER TABLE `lq_khxx` | |
| 50 | +ADD COLUMN `F_VisitDays` INT(11) DEFAULT 0 COMMENT '到店天数' AFTER `F_LastVisitTime`; | |
| 51 | + | |
| 52 | +-- ============================================ | |
| 53 | +-- 四、沉睡相关字段(用于计算沉睡天数) | |
| 54 | +-- ============================================ | |
| 55 | + | |
| 56 | +-- 10. 沉睡开始时间 | |
| 57 | +ALTER TABLE `lq_khxx` | |
| 58 | +ADD COLUMN `F_SleepStartTime` DATETIME DEFAULT NULL COMMENT '沉睡开始时间' AFTER `F_VisitDays`; | |
| 59 | + | |
| 60 | +-- 11. 沉睡天数 (整数,记录会员沉睡的天数) | |
| 61 | +ALTER TABLE `lq_khxx` | |
| 62 | +ADD COLUMN `F_SleepDays` INT(11) DEFAULT 0 COMMENT '沉睡天数' AFTER `F_SleepStartTime`; | |
| 63 | + | |
| 64 | +-- ============================================ | |
| 65 | +-- 五、消费相关字段(用于计算消费等级) | |
| 66 | +-- ============================================ | |
| 67 | + | |
| 68 | +-- 12. 耗卡总金额 (累计所有耗卡的金额总和,从lq_xh_hyhk表的xfje字段统计) | |
| 69 | +ALTER TABLE `lq_khxx` | |
| 70 | +ADD COLUMN `F_TotalConsumeAmount` DECIMAL(18,2) DEFAULT 0.00 COMMENT '耗卡总金额(累计所有耗卡的金额总和)' AFTER `F_SleepDays`; | |
| 71 | + | |
| 72 | +-- 13. 开卡总金额 (累计所有开单的实付业绩总和,从lq_kd_kdjlb表的sfyj字段统计) | |
| 73 | +ALTER TABLE `lq_khxx` | |
| 74 | +ADD COLUMN `F_TotalBillingAmount` DECIMAL(18,2) DEFAULT 0.00 COMMENT '开卡总金额(累计所有开单的实付业绩总和)' AFTER `F_TotalConsumeAmount`; | |
| 75 | + | |
| 76 | +-- 14. 剩余权益总金额 (开卡总金额 - 耗卡总金额 - 退款金额) | |
| 77 | +ALTER TABLE `lq_khxx` | |
| 78 | +ADD COLUMN `F_RemainingRightsAmount` DECIMAL(18,2) DEFAULT 0.00 COMMENT '剩余权益总金额(开卡总金额-耗卡总金额-退款金额)' AFTER `F_TotalBillingAmount`; | |
| 79 | + | |
| 80 | +-- 15. 消费等级 (整数,记录会员的消费等级,如:0-普通, 1-银卡, 2-金卡, 3-钻石等) | |
| 81 | +ALTER TABLE `lq_khxx` | |
| 82 | +ADD COLUMN `F_ConsumeLevel` INT(11) DEFAULT 0 COMMENT '消费等级(0-普通,1-银卡,2-金卡,3-钻石等)' AFTER `F_RemainingRightsAmount`; | |
| 83 | + | |
| 84 | +-- 16. 消费等级更新时间 | |
| 85 | +ALTER TABLE `lq_khxx` | |
| 86 | +ADD COLUMN `F_ConsumeLevelUpdateTime` DATETIME DEFAULT NULL COMMENT '消费等级更新时间' AFTER `F_ConsumeLevel`; | |
| 87 | + | ... | ... |