From e745bcc24062caa5748de0d97678e3a00e605f7b Mon Sep 17 00:00:00 2001 From: “wangming” <“wangming@antissoft.com”> Date: Tue, 21 Oct 2025 18:13:39 +0800 Subject: [PATCH] 修复转卡方法中的空引用异常和First()方法错误 --- netcore/src/Modularity/Extend/NCC.Extend/LqHytkHytkService.cs | 1 + netcore/src/Modularity/Extend/NCC.Extend/LqKdKdjlbService.cs | 507 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- netcore/src/Modularity/Extend/NCC.Extend/LqTkjlbService.cs | 175 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------------------------------------------------------------- netcore/src/Modularity/Extend/NCC.Extend/LqXhHyhkService.cs | 1 + netcore/src/Modularity/Extend/NCC.Extend/LqXmzlService.cs | 341 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------- 5 files changed, 500 insertions(+), 525 deletions(-) diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqHytkHytkService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqHytkHytkService.cs index f94c400..fad3d6d 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend/LqHytkHytkService.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend/LqHytkHytkService.cs @@ -96,6 +96,7 @@ namespace NCC.Extend.LqHytkHytk .WhereIF(queryTksj != null, p => p.Tksj >= new DateTime(startTksj.ToDate().Year, startTksj.ToDate().Month, startTksj.ToDate().Day, 0, 0, 0)) .WhereIF(queryTksj != null, p => p.Tksj <= new DateTime(endTksj.ToDate().Year, endTksj.ToDate().Month, endTksj.ToDate().Day, 23, 59, 59)) .WhereIF(!string.IsNullOrEmpty(input.czry), p => p.Czry.Equals(input.czry)) + .WhereIF(input.isEffective != 0, p => p.IsEffective == input.isEffective) .Select(it => new LqHytkHytkListOutput { id = it.Id, diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqKdKdjlbService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqKdKdjlbService.cs index 6b63c97..7f99c04 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend/LqKdKdjlbService.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend/LqKdKdjlbService.cs @@ -43,6 +43,7 @@ using NCC.System.Entitys.Permission; using SqlSugar; using Yitter.IdGenerator; using NCC.Extend.Entitys.lq_package_info; +using NCC.Extend.Entitys.lq_mdxx; namespace NCC.Extend.LqKdKdjlb { @@ -216,9 +217,9 @@ namespace NCC.Extend.LqKdKdjlb .WhereIF(queryKdrq != null, p => p.Kdrq <= new DateTime(endKdrq.ToDate().Year, endKdrq.ToDate().Month, endKdrq.ToDate().Day, 23, 59, 59)) .WhereIF(!string.IsNullOrEmpty(input.gjlx), p => p.Gjlx.Equals(input.gjlx)) .WhereIF(!string.IsNullOrEmpty(input.hgjg), p => p.Hgjg.Equals(input.hgjg)) - .WhereIF(!string.IsNullOrEmpty(input.zdyj), p => p.Zdyj.Equals(input.zdyj)) - .WhereIF(!string.IsNullOrEmpty(input.sfyj), p => p.Sfyj.Equals(input.sfyj)) - .WhereIF(!string.IsNullOrEmpty(input.qk), p => p.Qk.Equals(input.qk)) + .WhereIF(input.zdyj != null, p => p.Zdyj > input.zdyj) + .WhereIF(input.sfyj != null, p => p.Sfyj > input.sfyj) + .WhereIF(input.qk != null, p => p.Qk > input.qk) .WhereIF(!string.IsNullOrEmpty(input.ckfs), p => p.Ckfs.Equals(input.ckfs)) .WhereIF(!string.IsNullOrEmpty(input.fkfs), p => p.Fkfs.Equals(input.fkfs)) .WhereIF(!string.IsNullOrEmpty(input.fkyy), p => p.Fkyy.Equals(input.fkyy)) @@ -233,6 +234,7 @@ namespace NCC.Extend.LqKdKdjlb .WhereIF(!string.IsNullOrEmpty(input.kdhysjh), p => p.Kdhysjh.Contains(input.kdhysjh)) .WhereIF(!string.IsNullOrEmpty(input.F_FIleUrl), p => p.F_FIleUrl.Contains(input.F_FIleUrl)) .WhereIF(!string.IsNullOrEmpty(input.CreateUser), p => p.CreateUser.Equals(input.CreateUser)) + .WhereIF(input.isEffective != 0, p => p.IsEffective == input.isEffective) .Select(it => new LqKdKdjlbListOutput { id = it.Id, @@ -781,9 +783,9 @@ namespace NCC.Extend.LqKdKdjlb .WhereIF(queryKdrq != null, p => p.Kdrq <= new DateTime(endKdrq.ToDate().Year, endKdrq.ToDate().Month, endKdrq.ToDate().Day, 23, 59, 59)) .WhereIF(!string.IsNullOrEmpty(input.gjlx), p => p.Gjlx.Equals(input.gjlx)) .WhereIF(!string.IsNullOrEmpty(input.hgjg), p => p.Hgjg.Equals(input.hgjg)) - .WhereIF(!string.IsNullOrEmpty(input.zdyj), p => p.Zdyj.Equals(input.zdyj)) - .WhereIF(!string.IsNullOrEmpty(input.sfyj), p => p.Sfyj.Equals(input.sfyj)) - .WhereIF(!string.IsNullOrEmpty(input.qk), p => p.Qk.Equals(input.qk)) + .WhereIF(input.zdyj != null, p => p.Zdyj > input.zdyj) + .WhereIF(input.sfyj != null, p => p.Sfyj > input.sfyj) + .WhereIF(input.qk != null, p => p.Qk > input.qk) .WhereIF(!string.IsNullOrEmpty(input.ckfs), p => p.Ckfs.Equals(input.ckfs)) .WhereIF(!string.IsNullOrEmpty(input.fkfs), p => p.Fkfs.Equals(input.fkfs)) .WhereIF(!string.IsNullOrEmpty(input.fkyy), p => p.Fkyy.Equals(input.fkyy)) @@ -797,6 +799,7 @@ namespace NCC.Extend.LqKdKdjlb .WhereIF(!string.IsNullOrEmpty(input.kdhyc), p => p.Kdhyc.Contains(input.kdhyc)) .WhereIF(!string.IsNullOrEmpty(input.kdhysjh), p => p.Kdhysjh.Contains(input.kdhysjh)) .WhereIF(!string.IsNullOrEmpty(input.F_FIleUrl), p => p.F_FIleUrl.Contains(input.F_FIleUrl)) + .WhereIF(input.isEffective != 0, p => p.IsEffective == input.isEffective) .Select(it => new LqKdKdjlbListOutput { id = it.Id, @@ -2324,6 +2327,7 @@ namespace NCC.Extend.LqKdKdjlb /// 转卡是指一个会员把自己剩余的某些品项,转给另外一个会员 /// 转卡会员转出的品项,就做退卡操作 /// 被转卡会员收到的品项,做开卡操作 + /// 系统会自动从原开单记录中获取健康师和科技部老师的业绩数据 /// /// 示例请求: /// ```json @@ -2331,68 +2335,37 @@ namespace NCC.Extend.LqKdKdjlb /// "fromMemberId": "member001", /// "toMemberId": "member002", /// "storeId": "store001", - /// "signatureFile": "签名文件路径", - /// "remarks": "转卡备注", + /// "signatureFile": "base64签名数据", + /// "remarks": "转卡备注信息", /// "transferItems": [ /// { - /// "billingItemId": "item001", + /// "billingItemId": "pxmx001", /// "transferQuantity": 5, - /// "itemId": "px001", - /// "itemName": "美容项目", - /// "itemPrice": 100.00, - /// "sourceType": "转卡", - /// "healthTeacherPerformances": [ - /// { - /// "healthTeacherId": "jks001", - /// "healthTeacherName": "张医生", - /// "healthTeacherAccount": "zhang001", - /// "performanceAmount": 50.00, - /// "laborCost": 20.00, - /// "itemQuantity": 5 - /// } - /// ], - /// "techTeacherPerformances": [ - /// { - /// "techTeacherId": "kjbs001", - /// "techTeacherName": "李老师", - /// "techTeacherAccount": "li001", - /// "performanceAmount": 30.00, - /// "laborCost": 10.00, - /// "itemQuantity": 5 - /// } - /// ] + /// "itemName": "美容项目A", + /// "itemPrice": 100.00 /// } /// ] /// } /// ``` /// /// 参数说明: - /// - fromMemberId: 转出会员ID - /// - toMemberId: 转入会员ID - /// - storeId: 门店ID - /// - signatureFile: 转出会员签名 - /// - remarks: 转卡备注 - /// - transferItems: 转出品项列表 - /// - billingItemId: 开单品项明细ID - /// - transferQuantity: 转出数量 - /// - itemId: 品项ID - /// - itemName: 品项名称 - /// - itemPrice: 品项价格 - /// - sourceType: 来源类型 - /// - healthTeacherPerformances: 健康师业绩列表 - /// - healthTeacherId: 健康师ID - /// - healthTeacherName: 健康师姓名 - /// - healthTeacherAccount: 健康师账号 - /// - performanceAmount: 业绩金额 - /// - laborCost: 人工成本 - /// - itemQuantity: 品项数量 - /// - techTeacherPerformances: 科技部老师业绩列表 - /// - techTeacherId: 科技部老师ID - /// - techTeacherName: 科技部老师姓名 - /// - techTeacherAccount: 科技部老师账号 - /// - performanceAmount: 业绩金额 - /// - laborCost: 人工成本 - /// - itemQuantity: 品项数量 + /// - fromMemberId: 转出会员ID(必填) + /// - toMemberId: 转入会员ID(必填) + /// - storeId: 门店ID(必填) + /// - signatureFile: 转出会员签名文件(必填) + /// - remarks: 转卡备注信息(可选) + /// - transferItems: 转出品项列表(必填) + /// - billingItemId: 开单品项明细ID(必填) + /// - transferQuantity: 转出数量(必填,大于0) + /// - itemName: 品项名称(必填) + /// - itemPrice: 品项价格(必填,大于等于0) + /// + /// 业务规则: + /// - 转出和转入会员必须存在且有效 + /// - 转出品项的剩余数量必须足够 + /// - 系统会自动从原开单记录中获取健康师和科技部老师业绩数据 + /// - 转卡操作会同时创建退卡记录和开卡记录 + /// - 所有操作在事务中执行,确保数据一致性 /// /// 转卡输入参数 /// 转卡结果 @@ -2406,10 +2379,8 @@ namespace NCC.Extend.LqKdKdjlb { throw NCCException.Oh("转卡参数不能为空"); } - var userInfo = await _userManager.GetUserInfo(); var transferTime = DateTime.Now; - try { // 开启事务 @@ -2425,7 +2396,6 @@ namespace NCC.Extend.LqKdKdjlb { throw NCCException.Oh("转入会员不存在或已失效"); } - // 2. 验证转出品项的余额是否足够 foreach (var item in input.TransferItems) { @@ -2434,7 +2404,6 @@ namespace NCC.Extend.LqKdKdjlb { throw NCCException.Oh($"品项明细不存在:{item.ItemName}"); } - // 查询剩余数量 var remainingCount = await GetItemRemainingCount(item.BillingItemId); if (remainingCount < item.TransferQuantity) @@ -2446,6 +2415,7 @@ namespace NCC.Extend.LqKdKdjlb // 3. 创建退卡记录(转出方) var refundId = YitIdHelper.NextId().ToString(); var totalRefundAmount = input.TransferItems.Sum(x => x.ItemPrice * x.TransferQuantity); + var StoreEntity = await _db.Queryable().Where(p => p.Id == input.StoreId).FirstAsync(); var refundEntity = new LqHytkHytkEntity { Id = refundId, @@ -2456,7 +2426,12 @@ namespace NCC.Extend.LqKdKdjlb Hy = input.FromMemberId, Hymc = fromMember.Khmc, Md = input.StoreId, + Mdmc = StoreEntity.Dm, + Mdbh = StoreEntity.Mdbm, SignatureFile = input.SignatureFile, + Hyzh = input.FromMemberId, + Gklx = fromMember.Khlx, + Tksj = DateTime.Now, Tkje = totalRefundAmount, ActualRefundAmount = totalRefundAmount, // 转卡时实退金额等于退卡总金额 Tkyy = "转卡", @@ -2472,9 +2447,9 @@ namespace NCC.Extend.LqKdKdjlb foreach (var item in input.TransferItems) { - var refundPxmxEntity = _db.Queryable().First(p => p.Id == item.BillingItemId); + var refundPxmxEntity = await _db.Queryable().Where(p => p.Id == item.BillingItemId).FirstAsync(); //计算品项扣除总金额 - var 品项扣除总金额 = refundPxmxEntity.Pxjg * item.TransferQuantity; + var totalItemDeduction = item.ItemPrice * item.TransferQuantity; //保存退卡明细表 var refundMxEntity = new LqHytkMxEntity { @@ -2483,13 +2458,13 @@ namespace NCC.Extend.LqKdKdjlb BillingItemId = item.BillingItemId, CreateTime = transferTime, CreateUser = userInfo.userId, - Px = item.ItemId, + Px = refundPxmxEntity.Px, Pxmc = item.ItemName, Pxjg = item.ItemPrice, - Tkje = 品项扣除总金额, + Tkje = totalItemDeduction, ProjectNumber = item.TransferQuantity, - SourceType = item.SourceType, - TotalPrice = 品项扣除总金额, + SourceType = refundPxmxEntity.SourceType, + TotalPrice = totalItemDeduction, IsEffective = StatusEnum.有效.GetHashCode() }; refundMxEntities.Add(refundMxEntity); @@ -2499,7 +2474,12 @@ namespace NCC.Extend.LqKdKdjlb { //获取健康师当月金三角id var monthString = DateTime.Now.ToString("yyyyMM"); - var GetMonTeam = _db.Queryable().Where(p => p.UserId == jks.Jks && p.Month == monthString).First(); + var GetMonTeamResult = await _db.Queryable().Where(p => p.UserId == jks.Jkszh && p.Month == monthString).ToListAsync(); + var GetMonTeam = GetMonTeamResult.FirstOrDefault(); + if (GetMonTeam == null) + { + throw NCCException.Oh($"健康师 {jks.Jksxm} 在 {monthString} 月份的金三角团队中不存在,请先配置金三角团队信息"); + } //获取本月 refundJksyjEntities.Add(new LqHytkJksyjEntity { @@ -2508,7 +2488,7 @@ namespace NCC.Extend.LqKdKdjlb Jks = jks.Jks, Jksxm = jks.Jksxm, Jkszh = jks.Jkszh, - Jksyj = (品项扣除总金额 / refundKdyjEntities.Count()), + Jksyj = (totalItemDeduction / refundKdyjEntities.Count()), Tksj = DateTime.Now, F_jsjid = GetMonTeam.JsjId, F_tkpxid = refundMxEntity.Id, @@ -2531,7 +2511,7 @@ namespace NCC.Extend.LqKdKdjlb Kjbls = kjbs.Kjbls, Kjblsxm = kjbs.Kjblsxm, Kjblszh = kjbs.Kjblszh, - Kjblsyj = (品项扣除总金额 / TechTeacherEntities.Count()), + Kjblsyj = (totalItemDeduction / TechTeacherEntities.Count()), Tksj = transferTime, F_tkpxid = refundMxEntity.Id, F_LaborCost = kjbs.LaborCost, @@ -2565,8 +2545,11 @@ namespace NCC.Extend.LqKdKdjlb CreateUser = userInfo.userId, Kdhy = input.ToMemberId, Djmd = input.StoreId, + Kdrq = DateTime.Now, + Gjlx = toMember.Khlx, Fkfs = "转卡", Sfskdd = "否", + Zdyj = input.TransferItems.Sum(x => x.ItemPrice * x.TransferQuantity), Sfyj = input.TransferItems.Sum(x => x.ItemPrice * x.TransferQuantity), Bz = $"从会员 {fromMember.Khmc} 转入,{input.Remarks}" }; @@ -2578,18 +2561,19 @@ namespace NCC.Extend.LqKdKdjlb foreach (var item in input.TransferItems) { + var refundPxmxEntity = await _db.Queryable().Where(p => p.Id == item.BillingItemId).FirstAsync(); var billingPxmxEntity = new LqKdPxmxEntity { Id = YitIdHelper.NextId().ToString(), Glkdbh = billingId, - Px = item.ItemId, + Px = refundPxmxEntity.Px, Pxmc = item.ItemName, Pxjg = item.ItemPrice, MemberId = input.ToMemberId, CreateTIme = transferTime, ProjectNumber = item.TransferQuantity, IsEnabled = StatusEnum.有效.GetHashCode(), - SourceType = item.SourceType, + SourceType = refundPxmxEntity.SourceType, TotalPrice = item.ItemPrice * item.TransferQuantity, ActualPrice = item.ItemPrice * item.TransferQuantity, IsEffective = StatusEnum.有效.GetHashCode(), @@ -2666,361 +2650,6 @@ namespace NCC.Extend.LqKdKdjlb } #endregion - // #region 会员转卡操作 - // /// - // /// 会员转卡操作 - // /// - // /// - // /// 转卡是指一个会员把自己剩余的某些品项,转给另外一个会员 - // /// 转卡会员转出的品项,就做退卡操作 - // /// 被转卡会员收到的品项,做开卡操作 - // /// - // /// 示例请求: - // /// ```json - // /// { - // /// "fromMemberId": "member001", - // /// "toMemberId": "member002", - // /// "storeId": "store001", - // /// "signatureFile": "签名文件路径", - // /// "remarks": "转卡备注", - // /// "transferItems": [ - // /// { - // /// "billingItemId": "item001", - // /// "transferQuantity": 5, - // /// "itemId": "px001", - // /// "itemName": "美容项目", - // /// "itemPrice": 100.00, - // /// "sourceType": "转卡", - // /// "healthTeacherPerformances": [ - // /// { - // /// "healthTeacherId": "jks001", - // /// "healthTeacherName": "张医生", - // /// "healthTeacherAccount": "zhang001", - // /// "performanceAmount": 50.00, - // /// "laborCost": 20.00, - // /// "itemQuantity": 5 - // /// } - // /// ], - // /// "techTeacherPerformances": [ - // /// { - // /// "techTeacherId": "kjbs001", - // /// "techTeacherName": "李老师", - // /// "techTeacherAccount": "li001", - // /// "performanceAmount": 30.00, - // /// "laborCost": 10.00, - // /// "itemQuantity": 5 - // /// } - // /// ] - // /// } - // /// ] - // /// } - // /// ``` - // /// - // /// 参数说明: - // /// - fromMemberId: 转出会员ID - // /// - toMemberId: 转入会员ID - // /// - storeId: 门店ID - // /// - signatureFile: 转出会员签名 - // /// - remarks: 转卡备注 - // /// - transferItems: 转出品项列表 - // /// - billingItemId: 开单品项明细ID - // /// - transferQuantity: 转出数量 - // /// - itemId: 品项ID - // /// - itemName: 品项名称 - // /// - itemPrice: 品项价格 - // /// - sourceType: 来源类型 - // /// - healthTeacherPerformances: 健康师业绩列表 - // /// - healthTeacherId: 健康师ID - // /// - healthTeacherName: 健康师姓名 - // /// - healthTeacherAccount: 健康师账号 - // /// - performanceAmount: 业绩金额 - // /// - laborCost: 人工成本 - // /// - itemQuantity: 品项数量 - // /// - techTeacherPerformances: 科技部老师业绩列表 - // /// - techTeacherId: 科技部老师ID - // /// - techTeacherName: 科技部老师姓名 - // /// - techTeacherAccount: 科技部老师账号 - // /// - performanceAmount: 业绩金额 - // /// - laborCost: 人工成本 - // /// - itemQuantity: 品项数量 - // /// - // /// 转卡输入参数 - // /// 转卡结果 - // /// 转卡成功 - // /// 参数错误或业务规则验证失败 - // /// 服务器错误 - // [HttpPost("TransferCard")] - // public async Task TransferCard([FromBody] TransferCardInput input) - // { - // if (input == null) - // { - // throw NCCException.Oh("转卡参数不能为空"); - // } - - // var userInfo = await _userManager.GetUserInfo(); - // var transferTime = DateTime.Now; - - // try - // { - // // 开启事务 - // _db.BeginTran(); - - // // 1. 验证转出和转入会员是否存在 - // var fromMember = await _db.Queryable().Where(x => x.Id == input.FromMemberId && x.IsEffective == StatusEnum.有效.GetHashCode()).FirstAsync(); - // if (fromMember == null) - // { - // throw NCCException.Oh("转出会员不存在或已失效"); - // } - // var toMember = await _db.Queryable().Where(x => x.Id == input.ToMemberId && x.IsEffective == StatusEnum.有效.GetHashCode()).FirstAsync(); - // if (toMember == null) - // { - // throw NCCException.Oh("转入会员不存在或已失效"); - // } - - // // 2. 验证转出品项的余额是否足够 - // foreach (var item in input.TransferItems) - // { - // var billingItem = await _db.Queryable().Where(x => x.Id == item.BillingItemId && x.IsEffective == StatusEnum.有效.GetHashCode()).FirstAsync(); - // if (billingItem == null) - // { - // throw NCCException.Oh($"品项明细不存在:{item.ItemName}"); - // } - - // // 查询剩余数量 - // var remainingCount = await GetItemRemainingCount(item.BillingItemId); - // if (remainingCount < item.TransferQuantity) - // { - // throw NCCException.Oh($"品项 {item.ItemName} 剩余数量不足,当前剩余:{remainingCount},需要转出:{item.TransferQuantity}"); - // } - // } - - // // 3. 创建退卡记录(转出方) - // var refundId = YitIdHelper.NextId().ToString(); - // var totalRefundAmount = input.TransferItems.Sum(x => x.ItemPrice * x.TransferQuantity); - // var refundEntity = new LqHytkHytkEntity - // { - // Id = refundId, - // F_CreateTime = transferTime, - // F_CreateUser = userInfo.userId, - // IsEffective = StatusEnum.有效.GetHashCode(), - // Czry = userInfo.userId, - // Hy = input.FromMemberId, - // Hymc = fromMember.Khmc, - // Md = input.StoreId, - // SignatureFile = input.SignatureFile, - // Tkje = totalRefundAmount, - // ActualRefundAmount = totalRefundAmount, // 转卡时实退金额等于退卡总金额 - // Tkyy = "转卡", - // Bz = $"转卡给会员:{toMember.Khmc},{input.Remarks}" - // }; - // await _db.Insertable(refundEntity).ExecuteCommandAsync(); - - // // 4. 创建退卡品项明细和业绩记录 - // var refundMxEntities = new List(); - // var refundJksyjEntities = new List(); - // var refundKjbsyjEntities = new List(); - - // foreach (var item in input.TransferItems) - // { - // var refundMxEntity = new LqHytkMxEntity - // { - // Id = YitIdHelper.NextId().ToString(), - // RefundInfoId = refundId, - // BillingItemId = item.BillingItemId, - // CreateTime = transferTime, - // CreateUser = userInfo.userId, - // Px = item.ItemId, - // Pxmc = item.ItemName, - // Pxjg = item.ItemPrice, - // Tkje = item.ItemPrice * item.TransferQuantity, - // ProjectNumber = item.TransferQuantity, - // SourceType = item.SourceType, - // TotalPrice = item.ItemPrice * item.TransferQuantity, - // IsEffective = StatusEnum.有效.GetHashCode() - // }; - // refundMxEntities.Add(refundMxEntity); - - // // 创建退卡健康师业绩记录 - // if (item.HealthTeacherPerformances != null && item.HealthTeacherPerformances.Any()) - // { - // foreach (var jks in item.HealthTeacherPerformances) - // { - // refundJksyjEntities.Add(new LqHytkJksyjEntity - // { - // Id = YitIdHelper.NextId().ToString(), - // Gltkbh = refundId, - // Jks = jks.HealthTeacherId, - // Jksxm = jks.HealthTeacherName, - // Jkszh = jks.HealthTeacherAccount, - // Jksyj = jks.PerformanceAmount, - // Tksj = transferTime, - // F_jsjid = jks.HealthTeacherId, - // F_tkpxid = refundMxEntity.Id, - // F_LaborCost = jks.LaborCost, - // F_tkpxNumber = jks.ItemQuantity, - // F_CreateTime = transferTime, - // F_CreateUser = userInfo.userId, - // CardReturn = refundMxEntity.Id, - // IsEffective = StatusEnum.有效.GetHashCode() - // }); - // } - // } - - // // 创建退卡科技部老师业绩记录 - // if (item.TechTeacherPerformances != null && item.TechTeacherPerformances.Any()) - // { - // foreach (var kjbs in item.TechTeacherPerformances) - // { - // refundKjbsyjEntities.Add(new LqHytkKjbsyjEntity - // { - // Id = YitIdHelper.NextId().ToString(), - // Gltkbh = refundId, - // Kjbls = kjbs.TechTeacherId, - // Kjblsxm = kjbs.TechTeacherName, - // Kjblszh = kjbs.TechTeacherAccount, - // Kjblsyj = kjbs.PerformanceAmount, - // Tksj = transferTime, - // F_tkpxid = refundMxEntity.Id, - // F_LaborCost = kjbs.LaborCost, - // F_tkpxNumber = kjbs.ItemQuantity, - // F_CreateTime = transferTime, - // F_CreateUser = userInfo.userId, - // CardReturn = refundMxEntity.Id, - // IsEffective = StatusEnum.有效.GetHashCode() - // }); - // } - // } - // } - - // await _db.Insertable(refundMxEntities).ExecuteCommandAsync(); - // if (refundJksyjEntities.Any()) - // { - // await _db.Insertable(refundJksyjEntities).ExecuteCommandAsync(); - // } - // if (refundKjbsyjEntities.Any()) - // { - // await _db.Insertable(refundKjbsyjEntities).ExecuteCommandAsync(); - // } - - // // 5. 创建开卡记录(转入方) - // var billingId = YitIdHelper.NextId().ToString(); - // var billingEntity = new LqKdKdjlbEntity - // { - // Id = billingId, - // CreateTime = transferTime, - // UpdateTime = transferTime, - // IsEffective = StatusEnum.有效.GetHashCode(), - // CreateUser = userInfo.userId, - // Kdhy = input.ToMemberId, - // Djmd = input.StoreId, - // Sfyj = input.TransferItems.Sum(x => x.ItemPrice * x.TransferQuantity), - // Bz = $"从会员 {fromMember.Khmc} 转入,{input.Remarks}" - // }; - // await _db.Insertable(billingEntity).ExecuteCommandAsync(); - // // 6. 创建开单品项明细和业绩记录 - // var billingPxmxEntities = new List(); - // var billingJksyjEntities = new List(); - // var billingKjbsyjEntities = new List(); - - // foreach (var item in input.TransferItems) - // { - // var billingPxmxEntity = new LqKdPxmxEntity - // { - // Id = YitIdHelper.NextId().ToString(), - // Glkdbh = billingId, - // Px = item.ItemId, - // Pxmc = item.ItemName, - // Pxjg = item.ItemPrice, - // MemberId = input.ToMemberId, - // CreateTIme = transferTime, - // ProjectNumber = item.TransferQuantity, - // IsEnabled = StatusEnum.有效.GetHashCode(), - // SourceType = item.SourceType, - // TotalPrice = item.ItemPrice * item.TransferQuantity, - // ActualPrice = item.ItemPrice * item.TransferQuantity, - // IsEffective = StatusEnum.有效.GetHashCode(), - // Remark = $"从会员 {fromMember.Khmc} 转入" - // }; - // billingPxmxEntities.Add(billingPxmxEntity); - - // // 创建开卡健康师业绩记录 - // if (item.HealthTeacherPerformances != null && item.HealthTeacherPerformances.Any()) - // { - // foreach (var jks in item.HealthTeacherPerformances) - // { - // billingJksyjEntities.Add(new LqKdJksyjEntity - // { - // Id = YitIdHelper.NextId().ToString(), - // Glkdbh = billingId, - // Jks = jks.HealthTeacherId, - // Jksxm = jks.HealthTeacherName, - // Jkszh = jks.HealthTeacherAccount, - // Jksyj = jks.PerformanceAmount.ToString(), - // Yjsj = transferTime, - // Jsj_id = jks.HealthTeacherId, - // Kdpxid = billingPxmxEntity.Id, - // IsEffective = StatusEnum.有效.GetHashCode() - // }); - // } - // } - - // // 创建开卡科技部老师业绩记录 - // if (item.TechTeacherPerformances != null && item.TechTeacherPerformances.Any()) - // { - // foreach (var kjbs in item.TechTeacherPerformances) - // { - // billingKjbsyjEntities.Add(new LqKdKjbsyjEntity - // { - // Id = YitIdHelper.NextId().ToString(), - // Glkdbh = billingId, - // Kjbls = kjbs.TechTeacherId, - // Kjblsxm = kjbs.TechTeacherName, - // Kjblszh = kjbs.TechTeacherAccount, - // Kjblsyj = kjbs.PerformanceAmount.ToString(), - // Yjsj = transferTime, - // Kdpxid = billingPxmxEntity.Id, - // LaborCost = kjbs.LaborCost, - // IsEffective = StatusEnum.有效.GetHashCode() - // }); - // } - // } - // } - - // await _db.Insertable(billingPxmxEntities).ExecuteCommandAsync(); - // if (billingJksyjEntities.Any()) - // { - // await _db.Insertable(billingJksyjEntities).ExecuteCommandAsync(); - // } - // if (billingKjbsyjEntities.Any()) - // { - // await _db.Insertable(billingKjbsyjEntities).ExecuteCommandAsync(); - // } - // // 提交事务 - // _db.CommitTran(); - - // return new TransferCardOutput - // { - // Success = true, - // TransferId = refundId, - // RefundId = refundId, - // BillingId = billingId, - // TotalAmount = input.TransferItems.Sum(x => x.ItemPrice * x.TransferQuantity), - // TotalQuantity = input.TransferItems.Sum(x => x.TransferQuantity), - // FromMemberName = fromMember.Khmc, - // ToMemberName = toMember.Khmc, - // TransferTime = transferTime, - // Message = "转卡操作成功" - // }; - // } - // catch (Exception ex) - // { - // _db.RollbackTran(); - // _logger.LogError(ex, "转卡操作失败:{Message}", ex.Message); - // throw NCCException.Oh($"转卡操作失败:{ex.Message}"); - // } - // } - // #endregion - #region 获取品项剩余数量 /// /// 获取品项剩余数量 @@ -3032,13 +2661,25 @@ namespace NCC.Extend.LqKdKdjlb try { // 查询购买数量 - var purchasedCount = await _db.Queryable().Where(x => x.Id == billingItemId && x.IsEffective == StatusEnum.有效.GetHashCode()).Select(x => x.ProjectNumber).FirstAsync(); + var purchasedCount = await _db.Queryable() + .Where(x => x.Id == billingItemId && x.IsEffective == StatusEnum.有效.GetHashCode()) + .SumAsync(x => x.ProjectNumber); + // 查询消费数量 - var consumedCount = await _db.Queryable().Where(x => x.BillingItemId == billingItemId && x.IsEffective == StatusEnum.有效.GetHashCode()).SumAsync(x => x.ProjectNumber); + var consumedCount = await _db.Queryable() + .Where(x => x.BillingItemId == billingItemId && x.IsEffective == StatusEnum.有效.GetHashCode()) + .SumAsync(x => x.ProjectNumber); + // 查询退卡数量 - var refundedCount = await _db.Queryable().Where(x => x.BillingItemId == billingItemId && x.IsEffective == StatusEnum.有效.GetHashCode()).SumAsync(x => x.ProjectNumber); + var refundedCount = await _db.Queryable() + .Where(x => x.BillingItemId == billingItemId && x.IsEffective == StatusEnum.有效.GetHashCode()) + .SumAsync(x => x.ProjectNumber); + // 查询储扣数量 - var deductCount = await _db.Queryable().Where(x => x.DeductId == billingItemId && x.IsEffective == StatusEnum.有效.GetHashCode()).SumAsync(x => x.ProjectNumber); + var deductCount = await _db.Queryable() + .Where(x => x.DeductId == billingItemId && x.IsEffective == StatusEnum.有效.GetHashCode()) + .SumAsync(x => x.ProjectNumber) ?? 0; + // 计算剩余数量 var remainingCount = (int)(purchasedCount - consumedCount - refundedCount - deductCount); return Math.Max(0, remainingCount); // 确保不为负数 diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqTkjlbService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqTkjlbService.cs index 1a4b767..7fbe557 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend/LqTkjlbService.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend/LqTkjlbService.cs @@ -684,55 +684,55 @@ namespace NCC.Extend.LqTkjlb var tkData = await _db.Ado.SqlQueryAsync(tkSql, new { eventId }); var tkDict = tkData.ToDictionary(x => (string)x.F_StoreId, x => x); - // 第二步:获取邀约数据 + // 第二步:获取邀约数据(按会员ID去重) var yaoySql = @" SELECT tk.F_StoreId, - COUNT(DISTINCT yy.F_Id) as yaoy_count + COUNT(DISTINCT tk.F_MemberId) as yaoy_count FROM lq_tkjlb tk LEFT JOIN lq_yaoyjl yy ON tk.F_MemberId = yy.yykh AND yy.F_StoreId = tk.F_StoreId - WHERE tk.F_EventId = @eventId + WHERE tk.F_EventId = @eventId AND yy.F_Id IS NOT NULL GROUP BY tk.F_StoreId"; var yaoyData = await _db.Ado.SqlQueryAsync(yaoySql, new { eventId }); var yaoyDict = yaoyData.ToDictionary(x => (string)x.F_StoreId, x => (int)x.yaoy_count); - // 第三步:获取预约数据 + // 第三步:获取预约数据(按会员ID去重) var yySql = @" SELECT tk.F_StoreId, - COUNT(DISTINCT yyjl.F_Id) as yy_count + COUNT(DISTINCT tk.F_MemberId) as yy_count FROM lq_tkjlb tk LEFT JOIN lq_yyjl yyjl ON tk.F_MemberId = yyjl.gk AND yyjl.F_Status = '已确认' - WHERE tk.F_EventId = @eventId + WHERE tk.F_EventId = @eventId AND yyjl.F_Id IS NOT NULL GROUP BY tk.F_StoreId"; var yyData = await _db.Ado.SqlQueryAsync(yySql, new { eventId }); var yyDict = yyData.ToDictionary(x => (string)x.F_StoreId, x => (int)x.yy_count); - // 第四步:获取耗卡数据 + // 第四步:获取耗卡数据(按会员ID去重,但金额不去重) var hkSql = @" SELECT tk.F_StoreId, - COUNT(DISTINCT xh.F_Id) as hk_count, + COUNT(DISTINCT tk.F_MemberId) as hk_count, COALESCE(SUM(xh.xfje), 0) as hk_amount FROM lq_tkjlb tk LEFT JOIN lq_xh_hyhk xh ON tk.F_MemberId = xh.hy AND xh.F_IsEffective = 1 - WHERE tk.F_EventId = @eventId + WHERE tk.F_EventId = @eventId AND xh.F_Id IS NOT NULL GROUP BY tk.F_StoreId"; var hkData = await _db.Ado.SqlQueryAsync(hkSql, new { eventId }); var hkDict = hkData.ToDictionary(x => (string)x.F_StoreId, x => new { count = (int)x.hk_count, amount = (decimal)x.hk_amount }); - // 第五步:获取开单数据 + // 第五步:获取开单数据(按会员ID去重,但金额不去重) var kdSql = @" SELECT tk.F_StoreId, - COUNT(DISTINCT kd.F_Id) as kd_count, + COUNT(DISTINCT tk.F_MemberId) as kd_count, COALESCE(SUM(kd.sfyj), 0) as kd_amount FROM lq_tkjlb tk LEFT JOIN lq_kd_kdjlb kd ON tk.F_MemberId = kd.kdhy AND kd.F_IsEffective = 1 - WHERE tk.F_EventId = @eventId + WHERE tk.F_EventId = @eventId AND kd.F_Id IS NOT NULL GROUP BY tk.F_StoreId"; var kdData = await _db.Ado.SqlQueryAsync(kdSql, new { eventId }); @@ -770,6 +770,91 @@ namespace NCC.Extend.LqTkjlb #region 门店顾客详情 /// + /// 生成门店顾客详情查询SQL + /// + /// 是否分页 + /// SQL语句 + private string GetStoreCustomerDetailsSql(bool isPaged = false) + { + var baseSql = @" + SELECT + tk.F_Id as tk_id, -- 拓客记录ID + tk.F_CustomerPhone as customer_phone, -- 顾客手机号 + tk.F_MemberId as member_id, -- 会员ID + tk.F_CustomerName as customer_name, -- 顾客姓名 + tk.F_CreateTime as tk_time, -- 拓客时间 + -- 邀约信息 + yaoy.F_Id as yaoy_id, -- 邀约ID + yaoy.F_CreateTime as yaoy_time, -- 邀约时间 + yaoy.F_Status as yaoy_status, -- 邀约状态 + -- 预约信息 + yy.F_Id as yy_id, -- 预约ID + yy.F_Status as yy_status, -- 预约状态 + yy.F_CreateTime as yy_time, -- 预约时间 + yy.yysj as appointment_time, -- 预约到店时间 + -- 耗卡信息(聚合) + xh_summary.total_consume_amount, -- 耗卡总金额 + xh_summary.consume_count, -- 耗卡次数 + xh_summary.first_consume_time, -- 首次耗卡时间 + xh_summary.last_consume_time, -- 最后耗卡时间 + -- 开卡信息(聚合) + kd_summary.total_billing_amount, -- 开卡总金额 + kd_summary.total_debt_amount, -- 总欠款金额 + kd_summary.billing_count, -- 开卡次数 + kd_summary.first_billing_time, -- 首次开卡时间 + kd_summary.last_billing_time, -- 最后开卡时间 + -- 状态描述 + CASE + WHEN yaoy.F_Id IS NOT NULL THEN '已邀约' + ELSE '未邀约' + END as invitation_status, -- 邀约状态描述 + CASE + WHEN yy.F_Id IS NOT NULL THEN '已预约' + ELSE '未预约' + END as appointment_status, -- 预约状态描述 + CASE + WHEN xh_summary.total_consume_amount > 0 THEN '已耗卡' + ELSE '未耗卡' + END as consume_status, -- 耗卡状态描述 + CASE + WHEN kd_summary.total_billing_amount > 0 THEN '已开卡' + ELSE '未开卡' + END as billing_status -- 开卡状态描述 + FROM lq_tkjlb tk + LEFT JOIN lq_yaoyjl yaoy ON tk.F_MemberId = yaoy.yykh + AND yaoy.F_StoreId = tk.F_StoreId + LEFT JOIN lq_yyjl yy ON tk.F_MemberId = yy.gk + AND yy.F_Status = '已确认' + LEFT JOIN ( + SELECT + hy as member_id, + SUM(xfje) as total_consume_amount, + COUNT(*) as consume_count, + MIN(F_CreateTime) as first_consume_time, + MAX(F_CreateTime) as last_consume_time + FROM lq_xh_hyhk + WHERE F_IsEffective = 1 + GROUP BY hy + ) xh_summary ON tk.F_MemberId = xh_summary.member_id + LEFT JOIN ( + SELECT + kdhy as member_id, + SUM(sfyj) as total_billing_amount, + SUM(qk) as total_debt_amount, + COUNT(*) as billing_count, + MIN(F_CreateTime) as first_billing_time, + MAX(F_CreateTime) as last_billing_time + FROM lq_kd_kdjlb + WHERE F_IsEffective = 1 + GROUP BY kdhy + ) kd_summary ON tk.F_MemberId = kd_summary.member_id + WHERE tk.F_EventId = @eventId + AND tk.F_StoreId = @storeId + ORDER BY tk.F_CreateTime DESC"; + return isPaged ? baseSql + " LIMIT @offset, @pageSize" : baseSql; + } + + /// /// 获取门店拓客活动顾客详情 /// /// 活动ID @@ -780,37 +865,7 @@ namespace NCC.Extend.LqTkjlb { try { - var sql = @" - SELECT - tk.F_Id as tk_id, -- 拓客记录ID - tk.F_CustomerPhone as customer_phone, -- 顾客手机号 - tk.F_MemberId as member_id, -- 会员ID - tk.F_CustomerName as customer_name, -- 顾客姓名 - tk.F_CreateTime as tk_time, -- 拓客时间 - yy.F_Id as yy_id, -- 预约ID - yy.F_Status as yy_status, -- 预约状态 - yy.F_CreateTime as yy_time, -- 预约时间 - yy.yysj as appointment_time, -- 预约到店时间 - xh.F_Id as xh_id, -- 耗卡ID - xh.F_CreateTime as xh_time, -- 耗卡时间 - xh.xfje as consume_amount, -- 耗卡金额 - CASE - WHEN yy.F_Id IS NOT NULL THEN '已预约' - ELSE '未预约' - END as appointment_status, -- 预约状态描述 - CASE - WHEN xh.F_Id IS NOT NULL THEN '已耗卡' - ELSE '未耗卡' - END as consume_status -- 耗卡状态描述 - FROM lq_tkjlb tk - LEFT JOIN lq_yyjl yy ON tk.F_MemberId = yy.gk - AND yy.F_Status = '已确认' - LEFT JOIN lq_xh_hyhk xh ON tk.F_MemberId = xh.hy - AND xh.F_IsEffective = 1 - WHERE tk.F_EventId = @eventId - AND tk.F_StoreId = @storeId - ORDER BY tk.F_CreateTime DESC"; - + var sql = GetStoreCustomerDetailsSql(false); var result = await _db.Ado.SqlQueryAsync(sql, new { eventId, storeId }); return new @@ -839,44 +894,12 @@ namespace NCC.Extend.LqTkjlb { try { - var sql = @" - SELECT - tk.F_Id as tk_id, -- 拓客记录ID - tk.F_CustomerPhone as customer_phone, -- 顾客手机号 - tk.F_MemberId as member_id, -- 会员ID - tk.F_CustomerName as customer_name, -- 顾客姓名 - tk.F_CreateTime as tk_time, -- 拓客时间 - yy.F_Id as yy_id, -- 预约ID - yy.F_Status as yy_status, -- 预约状态 - yy.F_CreateTime as yy_time, -- 预约时间 - yy.yysj as appointment_time, -- 预约到店时间 - xh.F_Id as xh_id, -- 耗卡ID - xh.F_CreateTime as xh_time, -- 耗卡时间 - xh.xfje as consume_amount, -- 耗卡金额 - CASE - WHEN yy.F_Id IS NOT NULL THEN '已预约' - ELSE '未预约' - END as appointment_status, -- 预约状态描述 - CASE - WHEN xh.F_Id IS NOT NULL THEN '已耗卡' - ELSE '未耗卡' - END as consume_status -- 耗卡状态描述 - FROM lq_tkjlb tk - LEFT JOIN lq_yyjl yy ON tk.F_MemberId = yy.gk - AND yy.F_Status = '已确认' - LEFT JOIN lq_xh_hyhk xh ON tk.F_MemberId = xh.hy - AND xh.F_IsEffective = 1 - WHERE tk.F_EventId = @eventId - AND tk.F_StoreId = @storeId - ORDER BY tk.F_CreateTime DESC - LIMIT @offset, @pageSize"; - + var sql = GetStoreCustomerDetailsSql(true); var countSql = @" SELECT COUNT(*) as total FROM lq_tkjlb tk WHERE tk.F_EventId = @eventId AND tk.F_StoreId = @storeId"; - var offset = (pageIndex - 1) * pageSize; var result = await _db.Ado.SqlQueryAsync(sql, new { eventId, storeId, offset, pageSize }); var totalResult = await _db.Ado.SqlQueryAsync(countSql, new { eventId, storeId }); diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqXhHyhkService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqXhHyhkService.cs index 8e6f6cd..56bbc87 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend/LqXhHyhkService.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend/LqXhHyhkService.cs @@ -193,6 +193,7 @@ namespace NCC.Extend.LqXhHyhk .WhereIF(queryHksj != null, p => p.Hksj >= new DateTime(startHksj.ToDate().Year, startHksj.ToDate().Month, startHksj.ToDate().Day, 0, 0, 0)) .WhereIF(queryHksj != null, p => p.Hksj <= new DateTime(endHksj.ToDate().Year, endHksj.ToDate().Month, endHksj.ToDate().Day, 23, 59, 59)) .WhereIF(!string.IsNullOrEmpty(input.czry), p => p.Czry.Equals(input.czry)) + .WhereIF(input.isEffective != 0, p => p.IsEffective == input.isEffective) .Select(it => new LqXhHyhkListOutput { id = it.Id, diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqXmzlService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqXmzlService.cs index 6c50d62..3024f22 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend/LqXmzlService.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend/LqXmzlService.cs @@ -15,6 +15,12 @@ using System.Linq; using System.Threading.Tasks; using NCC.Extend.Entitys.lq_xmzl; using NCC.Extend.Entitys.Dto.LqXmzl; +using NCC.Extend.Entitys.lq_kd_pxmx; +using NCC.Extend.Entitys.lq_xh_pxmx; +using NCC.Extend.Entitys.lq_hytk_mx; +using NCC.Extend.Entitys.lq_kd_kdjlb; +using NCC.Extend.Entitys.lq_xh_hyhk; +using NCC.Extend.Entitys.lq_hytk_hytk; using Yitter.IdGenerator; using NCC.Common.Helper; using NCC.JsonSerialization; @@ -28,7 +34,7 @@ namespace NCC.Extend.LqXmzl /// /// 项目资料服务 /// - [ApiDescriptionSettings(Tag = "绿纤项目资料服务", Name = "LqXmzl", Order = 200)] + [ApiDescriptionSettings(Tag = "绿纤品项资料服务", Name = "LqXmzl", Order = 200)] [Route("api/Extend/[controller]")] public class LqXmzlService : ILqXmzlService, IDynamicApiController, ITransient { @@ -46,7 +52,7 @@ namespace NCC.Extend.LqXmzl _userManager = userManager; } - #region 获取项目资料 + #region 获取品项资料 /// /// 获取项目资料 @@ -62,9 +68,9 @@ namespace NCC.Extend.LqXmzl } #endregion - #region 获取项目资料列表 + #region 获取品项资料列表 /// - /// 获取项目资料列表 + /// 获取品项资料列表 /// /// 请求参数 /// @@ -114,9 +120,9 @@ namespace NCC.Extend.LqXmzl } #endregion - #region 新建项目资料 + #region 新建品项资料 /// - /// 新建项目资料 + /// 新建品项资料 /// /// 参数 /// @@ -131,9 +137,9 @@ namespace NCC.Extend.LqXmzl } #endregion - #region 获取项目资料无分页列表 + #region 获取品项资料无分页列表 /// - /// 获取项目资料无分页列表 + /// 获取品项资料无分页列表 /// /// 请求参数 /// @@ -177,9 +183,9 @@ namespace NCC.Extend.LqXmzl } #endregion - #region 导出项目资料 + #region 导出品项资料 /// - /// 导出项目资料 + /// 导出品项资料 /// /// 请求参数 /// @@ -225,9 +231,9 @@ namespace NCC.Extend.LqXmzl } #endregion - #region 批量删除项目资料 + #region 批量删除品项资料 /// - /// 批量删除项目资料 + /// 批量删除品项资料 /// /// 主键数组 /// @@ -257,9 +263,9 @@ namespace NCC.Extend.LqXmzl } #endregion - #region 更新项目资料 + #region 更新品项资料 /// - /// 更新项目资料 + /// 更新品项资料 /// /// 主键 /// 参数 @@ -273,9 +279,9 @@ namespace NCC.Extend.LqXmzl } #endregion - #region 删除项目资料 + #region 删除品项资料 /// - /// 删除项目资料 + /// 删除品项资料 /// /// 主键 /// @@ -386,6 +392,309 @@ namespace NCC.Extend.LqXmzl } #endregion + #region 品项维度统计 + /// + /// 品项维度统计 + /// + /// + /// 按品项维度统计开卡、消耗、退卡等数据 + /// 包括业绩、人数、占比、复购率等指标 + /// + /// 示例请求: + /// ```json + /// { + /// "startTime": "2024-01-01", + /// "endTime": "2024-12-31", + /// "storeId": "store001", + /// "category": "美容", + /// "itemId": "item001" + /// } + /// ``` + /// + /// 参数说明: + /// - startTime: 开始时间(可选) + /// - endTime: 结束时间(可选) + /// - storeId: 门店ID(可选) + /// - category: 品项分类(可选) + /// - itemId: 品项ID(可选,单个品项统计) + /// + /// 统计输入参数 + /// 品项维度统计数据 + /// 统计成功 + /// 参数错误 + /// 服务器错误 + [HttpPost("GetItemStatistics")] + public async Task GetItemStatistics([FromBody] LqXmzlStatisticsInput input) + { + try + { + // 第一步:获取品项基础信息 + var itemsQuery = _db.Queryable() + .Where(x => x.IsEffective == 1); + + if (!string.IsNullOrEmpty(input.ItemId)) + { + itemsQuery = itemsQuery.Where(x => x.Id == input.ItemId); + } + + if (!string.IsNullOrEmpty(input.Category)) + { + itemsQuery = itemsQuery.Where(x => x.Fl1 == input.Category || x.Fl2 == input.Category || x.Fl == input.Category); + } + + var items = await itemsQuery.ToListAsync(); + + if (!items.Any()) + { + return new + { + success = true, + data = new List(), + message = "未找到符合条件的品项" + }; + } + + var itemIds = items.Select(x => x.Id).ToList(); + + // 第二步:开卡数据统计 + var billingStats = await GetBillingStatistics(itemIds, input); + + // 第三步:消耗数据统计 + var consumeStats = await GetConsumeStatistics(itemIds, input); + + // 第四步:退卡数据统计 + var refundStats = await GetRefundStatistics(itemIds, input); + + // 第五步:计算总数据用于占比计算 + var totalBillingAmount = billingStats.Sum(x => x.BillingAmount); + var totalConsumeAmount = consumeStats.Sum(x => x.ConsumeAmount); + var totalBuyers = billingStats.Sum(x => x.TotalBuyers); + + // 第六步:合并数据并计算占比 + var result = new List(); + + foreach (var item in items) + { + var billingData = billingStats.FirstOrDefault(x => x.ItemId == item.Id); + var consumeData = consumeStats.FirstOrDefault(x => x.ItemId == item.Id); + var refundData = refundStats.FirstOrDefault(x => x.ItemId == item.Id); + + var output = new LqXmzlStatisticsOutput + { + ItemId = item.Id, + ItemName = item.Xmmc, + ItemNumber = item.Xmbh, + BillingAmount = billingData?.BillingAmount ?? 0, + BillingAmountRatio = totalBillingAmount > 0 ? (billingData?.BillingAmount ?? 0) / totalBillingAmount : 0, + TotalBuyers = billingData?.TotalBuyers ?? 0, + ItemRatio = totalBuyers > 0 ? (billingData?.TotalBuyers ?? 0) / (decimal)totalBuyers : 0, + RepeatBuyers = billingData?.RepeatBuyers ?? 0, + RepeatBuyRate = (billingData?.TotalBuyers ?? 0) > 0 ? (billingData?.RepeatBuyers ?? 0) / (decimal)(billingData?.TotalBuyers ?? 1) : 0, + ConsumeAmount = consumeData?.ConsumeAmount ?? 0, + ConsumeAmountRatio = totalConsumeAmount > 0 ? (consumeData?.ConsumeAmount ?? 0) / totalConsumeAmount : 0, + ConsumePurchaseCount = consumeData?.ConsumePurchaseCount ?? 0, + ConsumeGiftCount = consumeData?.ConsumeGiftCount ?? 0, + ConsumeExperienceCount = consumeData?.ConsumeExperienceCount ?? 0, + RefundAmount = refundData?.RefundAmount ?? 0, + RefundCount = refundData?.RefundCount ?? 0 + }; + + result.Add(output); + } + + return new + { + success = true, + data = result, + message = "品项维度统计成功" + }; + } + catch (Exception ex) + { + throw NCCException.Oh($"品项维度统计失败:{ex.Message}"); + } + } + + /// + /// 获取开卡统计数据 + /// + private async Task> GetBillingStatistics(List itemIds, LqXmzlStatisticsInput input) + { + var query = _db.Queryable() + .Where(x => itemIds.Contains(x.Px) && x.IsEffective == 1); + + // 时间过滤 + if (input.StartTime.HasValue) + { + query = query.Where(x => x.CreateTIme >= input.StartTime.Value); + } + if (input.EndTime.HasValue) + { + query = query.Where(x => x.CreateTIme <= input.EndTime.Value); + } + + // 门店过滤(通过开单记录关联) + if (!string.IsNullOrEmpty(input.StoreId)) + { + query = query.InnerJoin((px, kd) => px.Glkdbh == kd.Id) + .Where((px, kd) => kd.Djmd == input.StoreId && kd.IsEffective == 1); + } + + var result = await query + .GroupBy(x => x.Px) + .Select(x => new ItemStatisticsData + { + ItemId = x.Px, + BillingAmount = SqlFunc.AggregateSum(x.Pxjg * x.ProjectNumber), + TotalBuyers = SqlFunc.AggregateCount(x.MemberId), + RepeatBuyers = 0 // 复购人数需要单独计算 + }) + .ToListAsync(); + + // 单独计算复购人数 + foreach (var item in result) + { + var memberCountQuery = _db.Queryable() + .Where(x => x.Px == item.ItemId && x.IsEffective == 1); + + if (input.StartTime.HasValue) + { + memberCountQuery = memberCountQuery.Where(x => x.CreateTIme >= input.StartTime.Value); + } + if (input.EndTime.HasValue) + { + memberCountQuery = memberCountQuery.Where(x => x.CreateTIme <= input.EndTime.Value); + } + if (!string.IsNullOrEmpty(input.StoreId)) + { + memberCountQuery = memberCountQuery.InnerJoin((px, kd) => px.Glkdbh == kd.Id) + .Where((px, kd) => kd.Djmd == input.StoreId && kd.IsEffective == 1); + } + + var memberStats = await memberCountQuery + .GroupBy(x => x.MemberId) + .Having(x => SqlFunc.AggregateCount(x.MemberId) > 1) + .Select(x => SqlFunc.AggregateCount(x.MemberId)) + .ToListAsync(); + + item.RepeatBuyers = memberStats.Count; + } + + return result; + } + + /// + /// 获取消耗统计数据 + /// + private async Task> GetConsumeStatistics(List itemIds, LqXmzlStatisticsInput input) + { + var query = _db.Queryable() + .Where(x => itemIds.Contains(x.Px)); + + // 时间过滤 + if (input.StartTime.HasValue) + { + query = query.Where(x => x.CreateTIme >= input.StartTime.Value); + } + if (input.EndTime.HasValue) + { + query = query.Where(x => x.CreateTIme <= input.EndTime.Value); + } + + // 门店过滤(通过耗卡记录关联) + if (!string.IsNullOrEmpty(input.StoreId)) + { + query = query.InnerJoin((px, xh) => px.ConsumeInfoId == xh.Id) + .Where((px, xh) => xh.Md == input.StoreId && xh.IsEffective == 1); + } + + var result = await query + .GroupBy(x => x.Px) + .Select(x => new ItemConsumeStatisticsData + { + ItemId = x.Px, + ConsumeAmount = SqlFunc.AggregateSum(x.Pxjg * x.ProjectNumber), + ConsumePurchaseCount = SqlFunc.AggregateSum(SqlFunc.IIF(x.SourceType == "购买", x.ProjectNumber, 0)), + ConsumeGiftCount = SqlFunc.AggregateSum(SqlFunc.IIF(x.SourceType == "赠送", x.ProjectNumber, 0)), + ConsumeExperienceCount = SqlFunc.AggregateSum(SqlFunc.IIF(x.SourceType == "体验", x.ProjectNumber, 0)) + }) + .ToListAsync(); + + return result; + } + + /// + /// 获取退卡统计数据 + /// + private async Task> GetRefundStatistics(List itemIds, LqXmzlStatisticsInput input) + { + var query = _db.Queryable() + .Where(x => itemIds.Contains(x.Px)); + + // 时间过滤 + if (input.StartTime.HasValue) + { + query = query.Where(x => x.Tksj >= input.StartTime.Value); + } + if (input.EndTime.HasValue) + { + query = query.Where(x => x.Tksj <= input.EndTime.Value); + } + + // 门店过滤(通过退卡记录关联) + if (!string.IsNullOrEmpty(input.StoreId)) + { + query = query.InnerJoin((mx, hytk) => mx.RefundInfoId == hytk.Id) + .Where((mx, hytk) => hytk.Md == input.StoreId && hytk.IsEffective == 1); + } + + var result = await query + .GroupBy(x => x.Px) + .Select(x => new ItemRefundStatisticsData + { + ItemId = x.Px, + RefundAmount = SqlFunc.AggregateSum(x.Tkje ?? 0), + RefundCount = SqlFunc.AggregateCount(x.Id) + }) + .ToListAsync(); + + return result; + } + #endregion + + } + + /// + /// 品项统计数据(内部类) + /// + public class ItemStatisticsData + { + public string ItemId { get; set; } + public decimal BillingAmount { get; set; } + public int TotalBuyers { get; set; } + public int RepeatBuyers { get; set; } + } + + /// + /// 品项消耗统计数据(内部类) + /// + public class ItemConsumeStatisticsData + { + public string ItemId { get; set; } + public decimal ConsumeAmount { get; set; } + public int ConsumePurchaseCount { get; set; } + public int ConsumeGiftCount { get; set; } + public int ConsumeExperienceCount { get; set; } + } + + /// + /// 品项退卡统计数据(内部类) + /// + public class ItemRefundStatisticsData + { + public string ItemId { get; set; } + public decimal RefundAmount { get; set; } + public int RefundCount { get; set; } } } -- libgit2 0.21.4