From 96e0ccc76c117a041e498d29d5253064a1cff663 Mon Sep 17 00:00:00 2001 From: “wangming” <“wangming@antissoft.com”> Date: Mon, 1 Dec 2025 18:12:18 +0800 Subject: [PATCH] 优化GetBillingRecordSummaryByStoreId方法:移除多个门店筛选以提升性能,保留单个门店筛选及其他筛选条件 --- antis-ncc-admin/package-lock.json | 4 ++-- netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKdKdjlb/BillingRecordSummaryQueryInput.cs | 24 ++++++++++++++++++++++++ netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKdKdjlb/HealthCoachStatisticsOutput.cs | 15 +++++++++++++++ netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqSalary/HealthCoachSalaryInput.cs | 31 +++++++++++++++++++++++++++++++ netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqSalary/HealthCoachSalaryOutput.cs | 115 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ netcore/src/Modularity/Extend/NCC.Extend/LqHytkHytkService.cs | 67 +++++-------------------------------------------------------------- netcore/src/Modularity/Extend/NCC.Extend/LqKdKdjlbService.cs | 1511 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ netcore/src/Modularity/Extend/NCC.Extend/LqProductService.cs | 32 ++++++++++++++++++++++++++++---- netcore/src/Modularity/Extend/NCC.Extend/LqSalaryService.cs | 486 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ netcore/src/Modularity/Extend/NCC.Extend/LqStatisticsService.cs | 2 -- netcore/src/Modularity/Extend/NCC.Extend/LqXhHyhkService.cs | 78 +++++++++++++++++++++++++++++++++++++++--------------------------------------- sql/同步退卡时间字段.sql | 33 +++++++++++++++++++++++++++++++++ sql/更新开单记录表储扣金额.sql | 21 +++++++++++++++++++++ 13 files changed, 1776 insertions(+), 643 deletions(-) create mode 100644 netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqSalary/HealthCoachSalaryInput.cs create mode 100644 netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqSalary/HealthCoachSalaryOutput.cs create mode 100644 netcore/src/Modularity/Extend/NCC.Extend/LqSalaryService.cs create mode 100644 sql/同步退卡时间字段.sql create mode 100644 sql/更新开单记录表储扣金额.sql diff --git a/antis-ncc-admin/package-lock.json b/antis-ncc-admin/package-lock.json index 1d46d6e..801aa4d 100644 --- a/antis-ncc-admin/package-lock.json +++ b/antis-ncc-admin/package-lock.json @@ -21699,7 +21699,7 @@ }, "node_modules/xlsx": { "version": "0.18.5", - "resolved": "https://registry.npmmirror.com/xlsx/-/xlsx-0.18.5.tgz", + "resolved": "https://registry.npmjs.org/xlsx/-/xlsx-0.18.5.tgz", "integrity": "sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ==", "dependencies": { "adler-32": "~1.3.0", @@ -39759,7 +39759,7 @@ }, "xlsx": { "version": "0.18.5", - "resolved": "https://registry.npmmirror.com/xlsx/-/xlsx-0.18.5.tgz", + "resolved": "https://registry.npmjs.org/xlsx/-/xlsx-0.18.5.tgz", "integrity": "sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ==", "requires": { "adler-32": "~1.3.0", diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKdKdjlb/BillingRecordSummaryQueryInput.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKdKdjlb/BillingRecordSummaryQueryInput.cs index 253cef3..d54b066 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKdKdjlb/BillingRecordSummaryQueryInput.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKdKdjlb/BillingRecordSummaryQueryInput.cs @@ -27,5 +27,29 @@ namespace NCC.Extend.Entitys.Dto.LqKdKdjlb /// [Display(Name = "结束时间", Description = "查询开单记录的结束时间")] public DateTime? EndTime { get; set; } + + /// + /// 品项分类 + /// + [Display(Name = "品项分类", Description = "筛选品项分类")] + public string ItemCategory { get; set; } + + /// + /// 品项ID + /// + [Display(Name = "品项ID", Description = "筛选品项ID")] + public string ItemId { get; set; } + + /// + /// 健康师ID + /// + [Display(Name = "健康师ID", Description = "筛选健康师ID")] + public string HealthCoachId { get; set; } + + /// + /// 客户ID(会员ID) + /// + [Display(Name = "客户ID", Description = "筛选客户ID")] + public string MemberId { get; set; } } } diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKdKdjlb/HealthCoachStatisticsOutput.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKdKdjlb/HealthCoachStatisticsOutput.cs index 7a59134..9afc45a 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKdKdjlb/HealthCoachStatisticsOutput.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKdKdjlb/HealthCoachStatisticsOutput.cs @@ -94,5 +94,20 @@ namespace NCC.Extend.Entitys.Dto.LqKdKdjlb /// 退卡金额 - 统计该健康师在指定时间周期内的退卡业绩总金额 /// public decimal refundAmount { get; set; } + + /// + /// 手工费 - 统计该健康师在指定时间周期内消耗时的手工费总金额 + /// + public decimal laborCost { get; set; } + + /// + /// 原始手工费 - 统计该健康师在指定时间周期内消耗时的原始手工费总金额 + /// + public decimal originalLaborCost { get; set; } + + /// + /// 加班手工费 - 统计该健康师在指定时间周期内消耗时的加班手工费总金额 + /// + public decimal overtimeLaborCost { get; set; } } } diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqSalary/HealthCoachSalaryInput.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqSalary/HealthCoachSalaryInput.cs new file mode 100644 index 0000000..b613cc6 --- /dev/null +++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqSalary/HealthCoachSalaryInput.cs @@ -0,0 +1,31 @@ +using NCC.Common.Filter; +using System; + +namespace NCC.Extend.Entitys.Dto.LqSalary +{ + /// + /// 健康师工资查询参数 + /// + public class HealthCoachSalaryInput : PageInputBase + { + /// + /// 年份 + /// + public int Year { get; set; } + + /// + /// 月份 + /// + public int Month { get; set; } + + /// + /// 门店ID(可选,用于筛选特定门店) + /// + public string StoreId { get; set; } + + /// + /// 健康师姓名/账号(可选,用于模糊搜索) + /// + public string Keyword { get; set; } + } +} diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqSalary/HealthCoachSalaryOutput.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqSalary/HealthCoachSalaryOutput.cs new file mode 100644 index 0000000..652896e --- /dev/null +++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqSalary/HealthCoachSalaryOutput.cs @@ -0,0 +1,115 @@ +using System; + +namespace NCC.Extend.Entitys.Dto.LqSalary +{ + /// + /// 健康师工资输出 + /// + public class HealthCoachSalaryOutput + { + /// + /// 主键ID + /// + public string Id { get; set; } + + /// + /// 门店名称 + /// + public string StoreName { get; set; } + + /// + /// 员工姓名 + /// + public string EmployeeName { get; set; } + + /// + /// 岗位 + /// + public string Position { get; set; } + + /// + /// 金三角战队 + /// + public string GoldTriangleTeam { get; set; } + + /// + /// 总业绩 + /// + public decimal TotalPerformance { get; set; } + + /// + /// 基础业绩 + /// + public decimal BasePerformance { get; set; } + + /// + /// 合作业绩 + /// + public decimal CooperationPerformance { get; set; } + + /// + /// 奖励业绩 + /// + public decimal RewardPerformance { get; set; } + + /// + /// 消耗 + /// + public decimal Consumption { get; set; } + + /// + /// 项目数 + /// + public decimal ProjectCount { get; set; } + + /// + /// 到店人头 + /// + public decimal CustomerCount { get; set; } + + /// + /// 在店天数 + /// + public decimal WorkingDays { get; set; } + + /// + /// 健康师底薪 + /// + public decimal HealthCoachBaseSalary { get; set; } + + /// + /// 提成合计 + /// + public decimal TotalCommission { get; set; } + + /// + /// 手工费 + /// + public decimal HandworkFee { get; set; } + + /// + /// 补贴合计 + /// + public decimal TotalSubsidy { get; set; } + + /// + /// 扣款合计 + /// + public decimal TotalDeduction { get; set; } + + /// + /// 实发工资 + /// + public decimal ActualSalary { get; set; } + + /// + /// 是否锁定 + /// + public int IsLocked { get; set; } + + /// + /// 更新时间 + /// + public DateTime UpdateTime { get; set; } + } +} diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqHytkHytkService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqHytkHytkService.cs index 3c39985..9e74200 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend/LqHytkHytkService.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend/LqHytkHytkService.cs @@ -299,60 +299,6 @@ namespace NCC.Extend.LqHytkHytk /// 创建退卡信息及其关联的品项明细、健康师业绩、科技部老师业绩信息 /// /// - /// 创建退卡记录及其关联的品项明细、健康师业绩、科技部老师业绩信息 - /// - /// 示例请求: - /// ```json - /// { - /// "md": "门店ID", - /// "mdbh": "门店编号", - /// "mdmc": "门店名称", - /// "hy": "会员ID", - /// "hymc": "会员姓名", - /// "hyzh": "会员账号", - /// "gklx": "顾客类型", - /// "tkje": 1000.00, - /// "sgfy": 50.00, - /// "bz": "备注", - /// "tkzt": "退卡状态", - /// "tkyy": "退卡原因", - /// "lqHytkMxList": [ - /// { - /// "px": "品项编号", - /// "pxmc": "品项名称", - /// "pxjg": 100.00, - /// "tkje": 100.00, - /// "F_ProjectNumber": 1, - /// "F_SourceType": "来源类型", - /// "F_TotalPrice": 100.00, - /// "lqHytkJksyjList": [ - /// { - /// "jks": "健康师", - /// "jksxm": "健康师姓名", - /// "jkszh": "健康师账号", - /// "jksyj": 50.00, - /// "F_jsjid": "金三角ID", - /// "F_tkpxid": "项目资料ID", - /// "F_LaborCost": 10.00, - /// "F_tkpxNumber": 1 - /// } - /// ], - /// "lqHytkKjbsyjList": [ - /// { - /// "kjbls": "科技部老师", - /// "kjblsxm": "科技部老师姓名", - /// "kjblszh": "科技部老师账号", - /// "kjblsyj": 30.00, - /// "F_tkpxid": "项目资料ID", - /// "F_LaborCost": 5.00, - /// "F_tkpxNumber": 1 - /// } - /// ] - /// } - /// ] - /// } - /// ``` - /// /// 参数说明: /// - md: 门店ID /// - hy: 会员ID @@ -378,15 +324,12 @@ namespace NCC.Extend.LqHytkHytk { // 开启事务 _db.BeginTran(); - // 新增退卡主表记录 var newEntity = await _db.Insertable(entity).IgnoreColumns(ignoreNullColumn: true).ExecuteReturnEntityAsync(); - // 收集所有需要插入的实体,然后批量插入 var allMxEntities = new List(); var allJksyjEntities = new List(); var allKjbsyjEntities = new List(); - // 处理品项明细列表 if (input.lqHytkMxList != null && input.lqHytkMxList.Any()) { @@ -405,6 +348,7 @@ namespace NCC.Extend.LqHytkHytk Pxmc = item.pxmc, Pxjg = item.pxjg, Tkje = item.tkje, + Tksj = input.tksj, ProjectNumber = item.F_ProjectNumber ?? 1, SourceType = item.F_SourceType, TotalPrice = item.F_TotalPrice ?? (item.pxjg * (item.F_ProjectNumber ?? 1)), @@ -426,7 +370,7 @@ namespace NCC.Extend.LqHytkHytk Jksxm = ijks_tem.jksxm, Jkszh = ijks_tem.jkszh, Jksyj = ijks_tem.jksyj, - Tksj = DateTime.Now, + Tksj = input.tksj, F_jsjid = ijks_tem.F_jsjid, F_tkpxid = ijks_tem.F_tkpxid, F_LaborCost = ijks_tem.F_LaborCost, @@ -458,7 +402,7 @@ namespace NCC.Extend.LqHytkHytk Kjblsxm = ikjbs_tem.kjblsxm, Kjblszh = ikjbs_tem.kjblszh, Kjblsyj = ikjbs_tem.kjblsyj, - Tksj = DateTime.Now, + Tksj = input.tksj, F_tkpxid = ikjbs_tem.F_tkpxid, F_LaborCost = ikjbs_tem.F_LaborCost, F_tkpxNumber = ikjbs_tem.F_tkpxNumber, @@ -476,7 +420,6 @@ namespace NCC.Extend.LqHytkHytk } } } - // 批量插入品项明细 if (allMxEntities.Any()) { @@ -587,7 +530,7 @@ namespace NCC.Extend.LqHytkHytk Jksxm = ijks_tem.jksxm, Jkszh = ijks_tem.jkszh, Jksyj = ijks_tem.jksyj, - Tksj = DateTime.Now, + Tksj = input.tksj, F_jsjid = ijks_tem.F_jsjid, F_tkpxid = ijks_tem.F_tkpxid, F_LaborCost = ijks_tem.F_LaborCost, @@ -617,7 +560,7 @@ namespace NCC.Extend.LqHytkHytk Kjblsxm = ikjbs_tem.kjblsxm, Kjblszh = ikjbs_tem.kjblszh, Kjblsyj = ikjbs_tem.kjblsyj, - Tksj = DateTime.Now, + Tksj = input.tksj, F_tkpxid = ikjbs_tem.F_tkpxid, F_LaborCost = ikjbs_tem.F_LaborCost, F_tkpxNumber = ikjbs_tem.F_tkpxNumber, diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqKdKdjlbService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqKdKdjlbService.cs index 6f18fb7..d36e815 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend/LqKdKdjlbService.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend/LqKdKdjlbService.cs @@ -1117,6 +1117,263 @@ namespace NCC.Extend.LqKdKdjlb } #endregion + #region 修改开单记录及其关联的品项明细、健康师业绩、科技部老师业绩信息 + /// + /// 修改开单记录及其关联的品项明细、健康师业绩、科技部老师业绩信息 + /// + /// + /// 参数说明: + /// - id: 开单记录主键ID + /// - input: 开单记录更新参数,包含品项明细和业绩信息 + /// + /// 开单记录主键ID + /// 开单记录更新参数 + /// 无返回值 + /// 更新成功 + /// 参数错误或数据验证失败 + /// 服务器内部错误 + [HttpPut("UpdateForNoDelete/{id}")] + public async Task UpdateForNoDelete(string id, [FromBody] LqKdKdjlbUpInput input) + { + var entity = input.Adapt(); + entity.Id = id; // 确保ID正确设置 + try + { + //检查开单记录是否可以操作 + var (canCancel, errorMessage) = await CheckBillingCanCancelAsync(id); + if (!canCancel) + { + throw NCCException.Oh(errorMessage); + } + //开启事务 + _db.BeginTran(); + //查询是否有对应的补缴开单ID + if (!string.IsNullOrEmpty(entity.SupplementBillingId)) + { + //查询补缴开单ID + var supplementBillingEntity = await _db.Queryable().FirstAsync(p => p.Id == entity.SupplementBillingId);//900,900 + if (supplementBillingEntity == null || supplementBillingEntity.IsEffective == StatusEnum.无效.GetHashCode()) + { + throw NCCException.Oh("补缴开单记录不存在或已作废"); + } + //查询当前开单已经补缴金额 + var OldSupplementAmount = await _db.Queryable().Where(p => p.Id == id).SumAsync(p => p.SupplementAmount);//900,0 + supplementBillingEntity.PaidDebt = supplementBillingEntity.PaidDebt - OldSupplementAmount + input.supplementAmount; + await _db.Updateable(supplementBillingEntity).ExecuteCommandAsync(); + } + //批量查询当前开单所有品项的分类(性能优化) + var itemIds = input.lqKdPxmxList?.Where(x => !string.IsNullOrEmpty(x.px)).Select(x => x.px).Distinct().ToList() ?? new List(); + var itemCategoryDict = new Dictionary(); + if (itemIds.Any()) + { + var itemCategories = await _db.Queryable() + .Where(x => itemIds.Contains(x.Id) && x.IsEffective == StatusEnum.有效.GetHashCode()) + .Select(x => new { x.Id, x.Qt2 }) + .ToListAsync(); + itemCategoryDict = itemCategories.ToDictionary(k => k.Id, v => v.Qt2 ?? ""); + } + //判断当前开单是否包含医美品项 + var hasMedicalItemInCurrentBilling = input.lqKdPxmxList != null && input.lqKdPxmxList.Any(x => !string.IsNullOrEmpty(x.px) && itemCategoryDict.ContainsKey(x.px) && itemCategoryDict[x.px] == "医美"); + var MedicalItemInCurrentBillingAmount = input.lqKdPxmxList.Where(x => !string.IsNullOrEmpty(x.px) && itemCategoryDict.ContainsKey(x.px) && itemCategoryDict[x.px] == "医美").Sum(x => x.actualPrice); + //判断当前开单是否包含科美品项 + var hasTechItemInCurrentBilling = input.lqKdPxmxList != null && input.lqKdPxmxList.Any(x => !string.IsNullOrEmpty(x.px) && itemCategoryDict.ContainsKey(x.px) && itemCategoryDict[x.px] == "科美"); + //判断当前开单是否包含生美品项 + var hasLifeItemInCurrentBilling = input.lqKdPxmxList != null && input.lqKdPxmxList.Any(x => !string.IsNullOrEmpty(x.px) && itemCategoryDict.ContainsKey(x.px) && itemCategoryDict[x.px] == "生美"); + //获取该会员之前开单品项里面是否有医美项目 + var isMedicalProject = hasMedicalItemInCurrentBilling && await _db.Queryable().Where(x => x.MemberId == entity.Kdhy && x.IsEffective == StatusEnum.有效.GetHashCode() && x.ItemCategory == "医美" && x.ActualPrice > 0 && x.Px != "61" && x.Glkdbh != id).AnyAsync(); + if (isMedicalProject && MedicalItemInCurrentBillingAmount >= 1000) + { + entity.UpgradeLifeBeauty = "是"; + } + else + { + entity.UpgradeLifeBeauty = "否"; + } + //获取该会员之前开单品项里面是否有科美项目 + var isTechProject = hasTechItemInCurrentBilling && await _db.Queryable().Where(x => x.MemberId == entity.Kdhy && x.IsEffective == StatusEnum.有效.GetHashCode() && x.ItemCategory == "科美" && x.ActualPrice > 0 && x.Px != "61" && x.Glkdbh != id).AnyAsync(); + if (isTechProject) + { + entity.UpgradeTechBeauty = "是"; + } + else + { + entity.UpgradeTechBeauty = "否"; + } + //获取该会员之前开单品项里面是否有生美项目 + var isLifeProject = hasLifeItemInCurrentBilling && await _db.Queryable().Where(x => x.MemberId == entity.Kdhy && x.IsEffective == StatusEnum.有效.GetHashCode() && x.ItemCategory == "生美" && x.ActualPrice > 0 && x.Px != "61" && x.Glkdbh != id).AnyAsync(); + if (isLifeProject) + { + entity.UpgradeMedicalBeauty = "是"; + } + else + { + entity.UpgradeMedicalBeauty = "否"; + } + //计算储扣总金额 + entity.DeductAmount = input.lqKdKdjlbDeductList.Sum(x => x.Amount ?? 0); + entity.UpdateTime = DateTime.Now; + // 更新开单记录主表 + await _db.Updateable(entity).IgnoreColumns(ignoreAllNullColumns: true).IgnoreColumns(x => x.CreateTime).ExecuteCommandAsync(); + //清空原有品项明细 + await _db.Deleteable().Where(x => x.Glkdbh == id).ExecuteCommandAsync(); + //清空原有健康师业绩 + await _db.Deleteable().Where(x => x.Glkdbh == id).ExecuteCommandAsync(); + //清空原有科技部老师业绩 + await _db.Deleteable().Where(x => x.Glkdbh == id).ExecuteCommandAsync(); + //清空原有扣款信息 + await _db.Deleteable().Where(x => x.BillingId == id).ExecuteCommandAsync(); + //循环品相信息 + // 收集所有需要插入的实体,然后批量插入 + var allPxmxEntities = new List(); + var allJksyjEntities = new List(); + var allKjbsyjEntities = new List(); + var allDeductEntities = new List(); + // 处理扣款信息列表 + foreach (var item in input.lqKdKdjlbDeductList) + { + var lqKdDeductEntity = new LqKdDeductinfoEntity + { + Id = YitIdHelper.NextId().ToString(), + BillingId = id, + DeductId = item.DeductId, + DeductType = item.DeductType, + Amount = item.Amount, + ProjectNumber = item.ProjectNumber, + UnitPrice = item.UnitPrice, + ItemName = item.ItemName, + ItemId = item.ItemId, + IsEffective = StatusEnum.有效.GetHashCode(), // 设置为有效 + CreateTime = DateTime.Now, // 设置创建时间 + ItemCategory = await _db.Queryable().Where(x => x.Id == item.DeductId).Select(x => x.Qt2).FirstAsync(), + }; + allDeductEntities.Add(lqKdDeductEntity); + } + // 处理品项明细列表 + foreach (var item in input.lqKdPxmxList) + { + // 创建品项明细实体 + var lqKdPxmxEntity = new LqKdPxmxEntity + { + Id = YitIdHelper.NextId().ToString(), + Glkdbh = id, + Yjsj = input.kdrq, + CreateTIme = DateTime.Now, + MemberId = entity.Kdhy, + IsEnabled = StatusEnum.有效.GetHashCode(), + ProjectNumber = item.projectNumber, + TotalPrice = (decimal)(item.pxjg * item.projectNumber), + Px = item.px, + Pxmc = item.pxmc, + Pxjg = item.pxjg, + SourceType = item.sourceType, + ActualPrice = item.actualPrice, + Remark = item.remark, + IsEffective = StatusEnum.有效.GetHashCode(), + ActivityId = input.activityId, + ItemCategory = await _db.Queryable().Where(x => x.Id == item.px).Select(x => x.Qt2).FirstAsync(), + }; + allPxmxEntities.Add(lqKdPxmxEntity); + + // 收集该品项关联的健康师业绩 + if (item.lqKdJksyjList != null && item.lqKdJksyjList.Any()) + { + //把jksxm保存到HealthInstructorNames + foreach (var ijks_tem in item.lqKdJksyjList) + { + allJksyjEntities.Add(new LqKdJksyjEntity + { + Id = YitIdHelper.NextId().ToString(), + Glkdbh = id, + Jks = ijks_tem.jks, + Jksxm = ijks_tem.jksxm, + Jkszh = ijks_tem.jkszh, + Jksyj = ijks_tem.jksyj, + Yjsj = input.kdrq, + Jsj_id = ijks_tem.jsj_id, + Kdpxid = lqKdPxmxEntity.Id, + IsEffective = StatusEnum.有效.GetHashCode(), + ActivityId = input.activityId, + ItemCategory = lqKdPxmxEntity.ItemCategory, + ItemId = lqKdPxmxEntity.Px, + StoreId = entity.Djmd, + ItemName = lqKdPxmxEntity.Pxmc, + }); + } + } + + // 收集该品项关联的科技部老师业绩 + if (item.lqKdKjbsyjList != null && item.lqKdKjbsyjList.Any()) + { + foreach (var ikjbs_tem in item.lqKdKjbsyjList) + { + allKjbsyjEntities.Add(new LqKdKjbsyjEntity + { + Id = YitIdHelper.NextId().ToString(), + Glkdbh = id, + Kjbls = ikjbs_tem.kjbls, + Kjblsxm = ikjbs_tem.kjblsxm, + Kjblszh = ikjbs_tem.kjblszh, + Kjblsyj = ikjbs_tem.kjblsyj, + Yjsj = input.kdrq, + Kdpxid = lqKdPxmxEntity.Id, + IsEffective = StatusEnum.有效.GetHashCode(), + ActivityId = input.activityId, + ItemCategory = lqKdPxmxEntity.ItemCategory, + ItemId = lqKdPxmxEntity.Px, + StoreId = entity.Djmd, + ItemName = lqKdPxmxEntity.Pxmc, + }); + } + } + } + + //通过会员id查询会员信息 + var memberInfo = await _db.Queryable().Where(u => u.Id == entity.Kdhy).FirstAsync(); + //通过开单记录表查询这个会员开单金额 + var kdAmount = await _db.Queryable().Where(u => u.Kdhy == entity.Kdhy).SumAsync(u => u.Sfyj); + //如果开单金额小于500,为散客,如果大于500,为会员 + if (kdAmount < 500) + { + memberInfo.Khlx = MemberTypeEnum.散客.GetHashCode().ToString(); + } + else + { + memberInfo.Khlx = MemberTypeEnum.会员.GetHashCode().ToString(); + } + await _db.Updateable(memberInfo).ExecuteCommandAsync(); + // 批量插入扣款信息 + if (allDeductEntities.Any()) + { + await _db.Insertable(allDeductEntities).ExecuteCommandAsync(); + } + // 批量插入品项明细 + if (allPxmxEntities.Any()) + { + await _db.Insertable(allPxmxEntities).ExecuteCommandAsync(); + } + // 批量插入健康师业绩 + if (allJksyjEntities.Any()) + { + await _db.Insertable(allJksyjEntities).ExecuteCommandAsync(); + } + // 批量插入科技部老师业绩 + if (allKjbsyjEntities.Any()) + { + await _db.Insertable(allKjbsyjEntities).ExecuteCommandAsync(); + } + + //关闭事务 + _db.CommitTran(); + } + catch (Exception ex) + { + //回滚事务 + _db.RollbackTran(); + throw NCCException.Oh($"创建开单记录失败: {ex.Message}"); + } + } + #endregion + #region 获取开单记录表无分页列表 /// /// 获取开单记录表无分页列表 @@ -1293,225 +1550,9 @@ namespace NCC.Extend.LqKdKdjlb } #endregion - #region 更新开单记录表 + #region 删除开单记录表 /// - /// 更新开单记录表 - /// - /// - /// 更新开单记录及其关联的品项明细、健康师业绩、科技部老师业绩信息 - /// - /// 示例请求: - /// ```json - /// { - /// "id": "开单编号", - /// "djmd": "单据门店", - /// "jsj": "金三角", - /// "kdrq": "2025-01-11", - /// "lqKdPxmxList": [ - /// { - /// "px": "品项编号", - /// "pxmc": "品项名称", - /// "pxjg": 100.00, - /// "projectNumber": 1, - /// "sourceType": "购买", - /// "lqKdJksyjList": [ - /// { - /// "jks": "健康师", - /// "jksxm": "健康师姓名", - /// "jksyj": "100" - /// } - /// ] - /// } - /// ] - /// } - /// ``` - /// - /// 参数说明: - /// - id: 开单记录主键ID - /// - input: 开单记录更新参数,包含品项明细和业绩信息 - /// - /// 开单记录主键ID - /// 开单记录更新参数 - /// 无返回值 - /// 更新成功 - /// 参数错误或数据验证失败 - /// 服务器内部错误 - [HttpPut("{id}")] - public async Task Update(string id, [FromBody] LqKdKdjlbUpInput input) - { - var entity = input.Adapt(); - try - { - //开启事务 - _db.BeginTran(); - //批量查询当前开单所有品项的分类(性能优化) - var itemIds = input.lqKdPxmxList?.Where(x => !string.IsNullOrEmpty(x.px)).Select(x => x.px).Distinct().ToList() ?? new List(); - var itemCategoryDict = new Dictionary(); - if (itemIds.Any()) - { - var itemCategories = await _db.Queryable() - .Where(x => itemIds.Contains(x.Id) && x.IsEffective == StatusEnum.有效.GetHashCode()) - .Select(x => new { x.Id, x.Qt2 }) - .ToListAsync(); - itemCategoryDict = itemCategories.ToDictionary(k => k.Id, v => v.Qt2 ?? ""); - } - //判断当前开单是否包含医美品项 - var hasMedicalItemInCurrentBilling = input.lqKdPxmxList != null && input.lqKdPxmxList.Any(x => - !string.IsNullOrEmpty(x.px) && itemCategoryDict.ContainsKey(x.px) && itemCategoryDict[x.px] == "医美"); - var MedicalItemInCurrentBillingAmount = input.lqKdPxmxList.Where(x => - !string.IsNullOrEmpty(x.px) && itemCategoryDict.ContainsKey(x.px) && itemCategoryDict[x.px] == "医美").Sum(x => x.actualPrice); - //判断当前开单是否包含科美品项 - var hasTechItemInCurrentBilling = input.lqKdPxmxList != null && input.lqKdPxmxList.Any(x => - !string.IsNullOrEmpty(x.px) && itemCategoryDict.ContainsKey(x.px) && itemCategoryDict[x.px] == "科美"); - //判断当前开单是否包含生美品项 - var hasLifeItemInCurrentBilling = input.lqKdPxmxList != null && input.lqKdPxmxList.Any(x => - !string.IsNullOrEmpty(x.px) && itemCategoryDict.ContainsKey(x.px) && itemCategoryDict[x.px] == "生美"); - //获取该会员之前开单品项里面是否有医美项目 - var isMedicalProject = hasMedicalItemInCurrentBilling && await _db.Queryable().Where(x => x.MemberId == entity.Kdhy && x.IsEffective == StatusEnum.有效.GetHashCode() && x.ItemCategory == "医美" && x.ActualPrice > 0 && x.Px != "61" && x.Glkdbh != id).AnyAsync(); - if (isMedicalProject && MedicalItemInCurrentBillingAmount >= 1000) - { - entity.UpgradeLifeBeauty = "是"; - } - else - { - entity.UpgradeLifeBeauty = "否"; - } - //获取该会员之前开单品项里面是否有科美项目 - var isTechProject = hasTechItemInCurrentBilling && await _db.Queryable().Where(x => x.MemberId == entity.Kdhy && x.IsEffective == StatusEnum.有效.GetHashCode() && x.ItemCategory == "科美" && x.ActualPrice > 0 && x.Px != "61" && x.Glkdbh != id).AnyAsync(); - if (isTechProject) - { - entity.UpgradeTechBeauty = "是"; - } - else - { - entity.UpgradeTechBeauty = "否"; - } - //获取该会员之前开单品项里面是否有生美项目 - var isLifeProject = hasLifeItemInCurrentBilling && await _db.Queryable().Where(x => x.MemberId == entity.Kdhy && x.IsEffective == StatusEnum.有效.GetHashCode() && x.ItemCategory == "生美" && x.ActualPrice > 0 && x.Px != "61" && x.Glkdbh != id).AnyAsync(); - if (isLifeProject) - { - entity.UpgradeMedicalBeauty = "是"; - } - else - { - entity.UpgradeMedicalBeauty = "否"; - } - - //更新开单记录表记录 - await _db.Updateable(entity).IgnoreColumns(ignoreAllNullColumns: true).ExecuteCommandAsync(); - - //清空原有数据 - await _db.Deleteable().Where(u => u.Glkdbh == id).ExecuteCommandAsync(); - await _db.Deleteable().Where(u => u.Glkdbh == id).ExecuteCommandAsync(); - await _db.Deleteable().Where(u => u.Glkdbh == id).ExecuteCommandAsync(); - - // 收集所有需要插入的实体,然后批量插入 - var allPxmxEntities = new List(); - var allJksyjEntities = new List(); - var allKjbsyjEntities = new List(); - - // 处理品项明细列表 - if (input.lqKdPxmxList != null && input.lqKdPxmxList.Any()) - { - foreach (var item in input.lqKdPxmxList) - { - // 创建品项明细实体 - var lqKdPxmxEntity = new LqKdPxmxEntity - { - Id = YitIdHelper.NextId().ToString(), - Glkdbh = entity.Id, - CreateTIme = DateTime.Now, - MemberId = entity.Kdhy, - IsEnabled = 0, - ProjectNumber = item.projectNumber == 0 ? 1 : item.projectNumber, - TotalPrice = (decimal)(item.pxjg * (item.projectNumber == 0 ? 1 : item.projectNumber)), - Px = item.px, - Pxmc = item.pxmc, - Pxjg = item.pxjg, - SourceType = item.sourceType, - ItemCategory = await _db.Queryable().Where(x => x.Id == item.px).Select(x => x.Qt2).FirstAsync(), - }; - allPxmxEntities.Add(lqKdPxmxEntity); - - // 收集该品项关联的健康师业绩 - if (item.lqKdJksyjList != null && item.lqKdJksyjList.Any()) - { - foreach (var ijks_tem in item.lqKdJksyjList) - { - allJksyjEntities.Add(new LqKdJksyjEntity - { - Id = YitIdHelper.NextId().ToString(), - Glkdbh = entity.Id, - Jks = ijks_tem.jks, - Jksxm = ijks_tem.jksxm, - Jkszh = ijks_tem.jkszh, - Jksyj = ijks_tem.jksyj, - Yjsj = DateTime.Now, - Jsj_id = ijks_tem.jsj_id, - Kdpxid = lqKdPxmxEntity.Id, - StoreId = entity.Djmd, - ItemCategory = lqKdPxmxEntity.ItemCategory, - ItemId = lqKdPxmxEntity.Px, - ItemName = lqKdPxmxEntity.Pxmc, - }); - } - } - - // 收集该品项关联的科技部老师业绩 - if (item.lqKdKjbsyjList != null && item.lqKdKjbsyjList.Any()) - { - foreach (var ikjbs_tem in item.lqKdKjbsyjList) - { - allKjbsyjEntities.Add(new LqKdKjbsyjEntity - { - Id = YitIdHelper.NextId().ToString(), - Glkdbh = entity.Id, - Kjbls = ikjbs_tem.kjbls, - Kjblsxm = ikjbs_tem.kjblsxm, - Kjblszh = ikjbs_tem.kjblszh, - Kjblsyj = ikjbs_tem.kjblsyj, - Yjsj = DateTime.Now, - Kdpxid = lqKdPxmxEntity.Id, - ItemCategory = lqKdPxmxEntity.ItemCategory, - ItemId = lqKdPxmxEntity.Px, - StoreId = entity.Djmd, - ItemName = lqKdPxmxEntity.Pxmc, - }); - } - } - } - } - - // 批量插入品项明细 - if (allPxmxEntities.Any()) - { - await _db.Insertable(allPxmxEntities).ExecuteCommandAsync(); - } - // 批量插入健康师业绩 - if (allJksyjEntities.Any()) - { - await _db.Insertable(allJksyjEntities).ExecuteCommandAsync(); - } - // 批量插入科技部老师业绩 - if (allKjbsyjEntities.Any()) - { - await _db.Insertable(allKjbsyjEntities).ExecuteCommandAsync(); - } - //关闭事务 - _db.CommitTran(); - } - catch (Exception) - { - //回滚事务 - _db.RollbackTran(); - throw NCCException.Oh(ErrorCode.COM1001); - } - } - #endregion - - #region 删除开单记录表 - /// - /// 删除开单记录表 + /// 删除开单记录表 /// /// [HttpDelete("{id}")] @@ -1609,317 +1650,83 @@ namespace NCC.Extend.LqKdKdjlb } #endregion - #region 修改开单记录 + #region 作废开单记录 /// - /// 修改开单记录 + /// 作废开单记录 /// + /// 作废开单记录输入 + /// 无返回值 /// - /// 更新开单记录及其关联的品项明细、健康师业绩、科技部老师业绩信息 - /// + /// 作废指定的开单记录,包括相关的品项明细、业绩记录等 + /// /// 示例请求: /// ```json /// { - /// "id": "开单编号", - /// "djmd": "单据门店", - /// "jsj": "金三角", - /// "kdrq": "2025-01-11", - /// "lqKdPxmxList": [ - /// { - /// "px": "品项编号", - /// "pxmc": "品项名称", - /// "pxjg": 100.00, - /// "projectNumber": 1, - /// "sourceType": "购买", - /// "lqKdJksyjList": [ - /// { - /// "jks": "健康师", - /// "jksxm": "健康师姓名", - /// "jksyj": "100" - /// } - /// ] - /// } - /// ] + /// "id": "123456789", + /// "remarks": "客户要求作废此订单" /// } /// ``` - /// + /// /// 参数说明: - /// - id: 开单记录主键ID - /// - input: 开单记录更新参数,包含品项明细和业绩信息 + /// - id: 开单记录主键ID(必填) + /// - remarks: 作废备注说明 /// - /// 开单记录主键ID - /// 开单记录更新参数 - /// 无返回值 - /// 更新成功 - /// 参数错误或数据验证失败 + /// 作废成功 + /// 参数错误,开单记录ID不能为空 + /// 开单记录不存在 /// 服务器内部错误 - [HttpPut("UpdateForNoDelete/{id}")] - public async Task UpdateForNoDelete(string id, [FromBody] LqKdKdjlbUpInput input) + [HttpPut("Cancel")] + public async Task Cancel(CancelBillingInput input) { - var entity = input.Adapt(); - entity.Id = id; // 确保ID正确设置 + if (string.IsNullOrEmpty(input.Id)) + { + throw NCCException.Oh("开单记录ID不能为空"); + } try { - //检查开单记录是否可以操作 - var (canCancel, errorMessage) = await CheckBillingCanCancelAsync(id); + //开启事务 + _db.BeginTran(); + // 检查开单记录是否可以作废 + var (canCancel, errorMessage) = await CheckBillingCanCancelAsync(input.Id); if (!canCancel) { throw NCCException.Oh(errorMessage); } - //开启事务 - _db.BeginTran(); - //查询是否有对应的补缴开单ID - if (!string.IsNullOrEmpty(entity.SupplementBillingId)) + + // 查询开单记录 + var entity = await _db.Queryable().FirstAsync(p => p.Id == input.Id); + if (entity == null) { - //查询补缴开单ID - var supplementBillingEntity = await _db.Queryable().FirstAsync(p => p.Id == entity.SupplementBillingId);//900,900 - if (supplementBillingEntity == null || supplementBillingEntity.IsEffective == StatusEnum.无效.GetHashCode()) - { - throw NCCException.Oh("补缴开单记录不存在或已作废"); - } - //查询当前开单已经补缴金额 - var OldSupplementAmount = await _db.Queryable().Where(p => p.Id == id).SumAsync(p => p.SupplementAmount);//900,0 - supplementBillingEntity.PaidDebt = supplementBillingEntity.PaidDebt - OldSupplementAmount + input.supplementAmount; - await _db.Updateable(supplementBillingEntity).ExecuteCommandAsync(); + throw NCCException.Oh("开单记录不存在"); } - // 更新开单记录主表 - await _db.Updateable(entity).IgnoreColumns(ignoreAllNullColumns: true).IgnoreColumns(x => x.CreateTime).ExecuteCommandAsync(); - //清空原有品项明细 - await _db.Deleteable().Where(x => x.Glkdbh == id).ExecuteCommandAsync(); - //清空原有健康师业绩 - await _db.Deleteable().Where(x => x.Glkdbh == id).ExecuteCommandAsync(); - //清空原有科技部老师业绩 - await _db.Deleteable().Where(x => x.Glkdbh == id).ExecuteCommandAsync(); - //清空原有扣款信息 - await _db.Deleteable().Where(x => x.BillingId == id).ExecuteCommandAsync(); - //循环品相信息 - // 收集所有需要插入的实体,然后批量插入 - var allPxmxEntities = new List(); - var allJksyjEntities = new List(); - var allKjbsyjEntities = new List(); - var allDeductEntities = new List(); - // 处理扣款信息列表 - foreach (var item in input.lqKdKdjlbDeductList) + // 标记开单记录为无效,并添加作废备注 + entity.IsEffective = StatusEnum.无效.GetHashCode(); + entity.CancelRefRemarks = input.Remarks; + entity.UpdateTime = DateTime.Now; + await _db.Updateable(entity).ExecuteCommandAsync(); + + // 标记对应开单明细表为无效 + await _db.Updateable().SetColumns(it => new LqKdPxmxEntity { IsEffective = StatusEnum.无效.GetHashCode() }).Where(it => it.Glkdbh == input.Id).ExecuteCommandAsync(); + + // 标记健康师业绩为无效 + await _db.Updateable().SetColumns(it => new LqKdJksyjEntity { IsEffective = StatusEnum.无效.GetHashCode() }).Where(it => it.Glkdbh == input.Id).ExecuteCommandAsync(); + + // 标记科技部老师业绩为无效 + await _db.Updateable().SetColumns(it => new LqKdKjbsyjEntity { IsEffective = StatusEnum.无效.GetHashCode() }).Where(it => it.Glkdbh == input.Id).ExecuteCommandAsync(); + + // 标记开单_储扣详细表为无效 + await _db.Updateable().SetColumns(it => new LqKdDeductinfoEntity { IsEffective = StatusEnum.无效.GetHashCode() }).Where(it => it.BillingId == input.Id).ExecuteCommandAsync(); + + //如果存在补缴开单ID,则将补缴开单ID的IsEffective设置为无效 + if (!string.IsNullOrEmpty(entity.SupplementBillingId)) { - var lqKdDeductEntity = new LqKdDeductinfoEntity + var supplementBillingEntity = await _db.Queryable().FirstAsync(p => p.Id == entity.SupplementBillingId); + if (supplementBillingEntity != null && supplementBillingEntity.IsEffective == StatusEnum.有效.GetHashCode()) { - Id = YitIdHelper.NextId().ToString(), - BillingId = id, - DeductId = item.DeductId, - DeductType = item.DeductType, - Amount = item.Amount, - ProjectNumber = item.ProjectNumber, - UnitPrice = item.UnitPrice, - ItemName = item.ItemName, - ItemId = item.ItemId, - IsEffective = StatusEnum.有效.GetHashCode(), // 设置为有效 - CreateTime = DateTime.Now, // 设置创建时间 - ItemCategory = await _db.Queryable().Where(x => x.Id == item.DeductId).Select(x => x.Qt2).FirstAsync(), - }; - allDeductEntities.Add(lqKdDeductEntity); - } - // 处理品项明细列表 - foreach (var item in input.lqKdPxmxList) - { - // 创建品项明细实体 - var lqKdPxmxEntity = new LqKdPxmxEntity - { - Id = YitIdHelper.NextId().ToString(), - Glkdbh = id, - Yjsj = input.kdrq, - CreateTIme = DateTime.Now, - MemberId = entity.Kdhy, - IsEnabled = StatusEnum.有效.GetHashCode(), - ProjectNumber = item.projectNumber, - TotalPrice = (decimal)(item.pxjg * item.projectNumber), - Px = item.px, - Pxmc = item.pxmc, - Pxjg = item.pxjg, - SourceType = item.sourceType, - ActualPrice = item.actualPrice, - Remark = item.remark, - IsEffective = StatusEnum.有效.GetHashCode(), - ActivityId = input.activityId, - ItemCategory = await _db.Queryable().Where(x => x.Id == item.px).Select(x => x.Qt2).FirstAsync(), - }; - allPxmxEntities.Add(lqKdPxmxEntity); - - // 收集该品项关联的健康师业绩 - if (item.lqKdJksyjList != null && item.lqKdJksyjList.Any()) - { - //把jksxm保存到HealthInstructorNames - foreach (var ijks_tem in item.lqKdJksyjList) - { - allJksyjEntities.Add(new LqKdJksyjEntity - { - Id = YitIdHelper.NextId().ToString(), - Glkdbh = id, - Jks = ijks_tem.jks, - Jksxm = ijks_tem.jksxm, - Jkszh = ijks_tem.jkszh, - Jksyj = ijks_tem.jksyj, - Yjsj = input.kdrq, - Jsj_id = ijks_tem.jsj_id, - Kdpxid = lqKdPxmxEntity.Id, - IsEffective = StatusEnum.有效.GetHashCode(), - ActivityId = input.activityId, - ItemCategory = lqKdPxmxEntity.ItemCategory, - ItemId = lqKdPxmxEntity.Px, - }); - } - } - - // 收集该品项关联的科技部老师业绩 - if (item.lqKdKjbsyjList != null && item.lqKdKjbsyjList.Any()) - { - foreach (var ikjbs_tem in item.lqKdKjbsyjList) - { - allKjbsyjEntities.Add(new LqKdKjbsyjEntity - { - Id = YitIdHelper.NextId().ToString(), - Glkdbh = id, - Kjbls = ikjbs_tem.kjbls, - Kjblsxm = ikjbs_tem.kjblsxm, - Kjblszh = ikjbs_tem.kjblszh, - Kjblsyj = ikjbs_tem.kjblsyj, - Yjsj = input.kdrq, - Kdpxid = lqKdPxmxEntity.Id, - IsEffective = StatusEnum.有效.GetHashCode(), - ActivityId = input.activityId, - ItemCategory = lqKdPxmxEntity.ItemCategory, - ItemId = lqKdPxmxEntity.Px, - } - ); - } - } - } - - - - //通过会员id查询会员信息 - var memberInfo = await _db.Queryable().Where(u => u.Id == entity.Kdhy).FirstAsync(); - //通过开单记录表查询这个会员开单金额 - var kdAmount = await _db.Queryable().Where(u => u.Kdhy == entity.Kdhy).SumAsync(u => u.Sfyj); - //如果开单金额小于500,为散客,如果大于500,为会员 - if (kdAmount < 500) - { - memberInfo.Khlx = MemberTypeEnum.散客.GetHashCode().ToString(); - } - else - { - memberInfo.Khlx = MemberTypeEnum.会员.GetHashCode().ToString(); - } - await _db.Updateable(memberInfo).ExecuteCommandAsync(); - // 批量插入扣款信息 - if (allDeductEntities.Any()) - { - await _db.Insertable(allDeductEntities).ExecuteCommandAsync(); - } - // 批量插入品项明细 - if (allPxmxEntities.Any()) - { - await _db.Insertable(allPxmxEntities).ExecuteCommandAsync(); - } - // 批量插入健康师业绩 - if (allJksyjEntities.Any()) - { - await _db.Insertable(allJksyjEntities).ExecuteCommandAsync(); - } - // 批量插入科技部老师业绩 - if (allKjbsyjEntities.Any()) - { - await _db.Insertable(allKjbsyjEntities).ExecuteCommandAsync(); - } - - //关闭事务 - _db.CommitTran(); - } - catch (Exception ex) - { - //回滚事务 - _db.RollbackTran(); - throw NCCException.Oh($"创建开单记录失败: {ex.Message}"); - } - } - #endregion - - #region 作废开单记录 - /// - /// 作废开单记录 - /// - /// 作废开单记录输入 - /// 无返回值 - /// - /// 作废指定的开单记录,包括相关的品项明细、业绩记录等 - /// - /// 示例请求: - /// ```json - /// { - /// "id": "123456789", - /// "remarks": "客户要求作废此订单" - /// } - /// ``` - /// - /// 参数说明: - /// - id: 开单记录主键ID(必填) - /// - remarks: 作废备注说明 - /// - /// 作废成功 - /// 参数错误,开单记录ID不能为空 - /// 开单记录不存在 - /// 服务器内部错误 - [HttpPut("Cancel")] - public async Task Cancel(CancelBillingInput input) - { - if (string.IsNullOrEmpty(input.Id)) - { - throw NCCException.Oh("开单记录ID不能为空"); - } - try - { - //开启事务 - _db.BeginTran(); - // 检查开单记录是否可以作废 - var (canCancel, errorMessage) = await CheckBillingCanCancelAsync(input.Id); - if (!canCancel) - { - throw NCCException.Oh(errorMessage); - } - - // 查询开单记录 - var entity = await _db.Queryable().FirstAsync(p => p.Id == input.Id); - if (entity == null) - { - throw NCCException.Oh("开单记录不存在"); - } - - // 标记开单记录为无效,并添加作废备注 - entity.IsEffective = StatusEnum.无效.GetHashCode(); - entity.CancelRefRemarks = input.Remarks; - entity.UpdateTime = DateTime.Now; - await _db.Updateable(entity).ExecuteCommandAsync(); - - // 标记对应开单明细表为无效 - await _db.Updateable().SetColumns(it => new LqKdPxmxEntity { IsEffective = StatusEnum.无效.GetHashCode() }).Where(it => it.Glkdbh == input.Id).ExecuteCommandAsync(); - - // 标记健康师业绩为无效 - await _db.Updateable().SetColumns(it => new LqKdJksyjEntity { IsEffective = StatusEnum.无效.GetHashCode() }).Where(it => it.Glkdbh == input.Id).ExecuteCommandAsync(); - - // 标记科技部老师业绩为无效 - await _db.Updateable().SetColumns(it => new LqKdKjbsyjEntity { IsEffective = StatusEnum.无效.GetHashCode() }).Where(it => it.Glkdbh == input.Id).ExecuteCommandAsync(); - - // 标记开单_储扣详细表为无效 - await _db.Updateable().SetColumns(it => new LqKdDeductinfoEntity { IsEffective = StatusEnum.无效.GetHashCode() }).Where(it => it.BillingId == input.Id).ExecuteCommandAsync(); - - //如果存在补缴开单ID,则将补缴开单ID的IsEffective设置为无效 - if (!string.IsNullOrEmpty(entity.SupplementBillingId)) - { - var supplementBillingEntity = await _db.Queryable().FirstAsync(p => p.Id == entity.SupplementBillingId); - if (supplementBillingEntity != null && supplementBillingEntity.IsEffective == StatusEnum.有效.GetHashCode()) - { - supplementBillingEntity.PaidDebt = supplementBillingEntity.PaidDebt - entity.SupplementAmount; - await _db.Updateable(supplementBillingEntity).ExecuteCommandAsync(); - } + supplementBillingEntity.PaidDebt = supplementBillingEntity.PaidDebt - entity.SupplementAmount; + await _db.Updateable(supplementBillingEntity).ExecuteCommandAsync(); + } } //关闭事务 _db.CommitTran(); @@ -2325,7 +2132,7 @@ namespace NCC.Extend.LqKdKdjlb } #endregion - #region 根据会员id获取会员的开单品项列表 + #region 根据会员id获取会员的开单品项列表 /// /// 根据会员id获取会员的开单品项列表 /// @@ -2539,9 +2346,18 @@ namespace NCC.Extend.LqKdKdjlb input.EndTime = DateTime.Now.AddDays(DateTime.DaysInMonth(DateTime.Now.Year, DateTime.Now.Month)); } + // 构建开单记录查询条件 + var billingQuery = _db.Queryable() + .Where(w => w.Djmd == input.StoreId && w.Kdrq >= input.StartTime && w.Kdrq <= input.EndTime && w.IsEffective == StatusEnum.有效.GetHashCode()); + + // 客户筛选 + if (!string.IsNullOrEmpty(input.MemberId)) + { + billingQuery = billingQuery.Where(w => w.Kdhy == input.MemberId); + } + // 查询开单记录 - var billingRecords = await _db.Queryable() - .Where(w => w.Djmd == input.StoreId && w.Kdrq >= input.StartTime && w.Kdrq <= input.EndTime && w.IsEffective == StatusEnum.有效.GetHashCode()) + var billingRecords = await billingQuery .Select(it => new { id = it.Id, @@ -2594,9 +2410,24 @@ namespace NCC.Extend.LqKdKdjlb var billingIds = billingRecords.Select(x => x.id).ToList(); + // 构建品项明细查询条件 + var itemDetailsQuery = _db.Queryable() + .Where(w => billingIds.Contains(w.Glkdbh) && w.IsEffective == StatusEnum.有效.GetHashCode()); + + // 品项分类筛选 + if (!string.IsNullOrEmpty(input.ItemCategory)) + { + itemDetailsQuery = itemDetailsQuery.Where(w => w.ItemCategory == input.ItemCategory); + } + + // 品项筛选 + if (!string.IsNullOrEmpty(input.ItemId)) + { + itemDetailsQuery = itemDetailsQuery.Where(w => w.Px == input.ItemId); + } + // 查询品项明细,按F_SourceType分类 - var itemDetails = await _db.Queryable() - .Where(w => billingIds.Contains(w.Glkdbh) && w.IsEffective == StatusEnum.有效.GetHashCode()) + var itemDetails = await itemDetailsQuery .Select(it => new { id = it.Id, @@ -2618,9 +2449,30 @@ namespace NCC.Extend.LqKdKdjlb var giftedItems = itemDetails.Where(x => x.sourceType == "赠送").ToList(); var experienceItems = itemDetails.Where(x => x.sourceType == "体验").ToList(); + // 构建健康师业绩查询条件 + var healthTeacherQuery = _db.Queryable() + .Where(w => billingIds.Contains(w.Glkdbh) && w.IsEffective == StatusEnum.有效.GetHashCode()); + + // 健康师筛选 + if (!string.IsNullOrEmpty(input.HealthCoachId)) + { + healthTeacherQuery = healthTeacherQuery.Where(w => w.Jks == input.HealthCoachId || w.Jkszh == input.HealthCoachId); + } + + // 品项分类筛选(健康师业绩表) + if (!string.IsNullOrEmpty(input.ItemCategory)) + { + healthTeacherQuery = healthTeacherQuery.Where(w => w.ItemCategory == input.ItemCategory); + } + + // 品项筛选(健康师业绩表) + if (!string.IsNullOrEmpty(input.ItemId)) + { + healthTeacherQuery = healthTeacherQuery.Where(w => w.ItemId == input.ItemId); + } + // 查询健康师业绩数据 - var healthTeacherData = await _db.Queryable() - .Where(w => billingIds.Contains(w.Glkdbh) && w.IsEffective == StatusEnum.有效.GetHashCode()) + var healthTeacherData = await healthTeacherQuery .Select(it => new { id = it.Id, @@ -2750,31 +2602,266 @@ namespace NCC.Extend.LqKdKdjlb } #endregion - #region 私有方法 + #region 获取门店某个时间段的开单记录汇总信息(备份) /// - /// 检查开单记录是否可以操作 + /// 获取门店某个时间段的开单记录汇总信息(备份) /// - /// 开单记录ID - /// 是否可以作废 - private async Task<(bool canCancel, string errorMessage)> CheckBillingCanCancelAsync(string billingId) + /// 查询参数 + /// 开单记录汇总信息 + [HttpGet("GetBillingRecordSummaryByStoreId_bak")] + public async Task GetBillingRecordSummaryByStoreId_bak([FromQuery] BillingRecordSummaryQueryInput input) { try { - // 查询开单记录 - var entity = await _db.Queryable().FirstAsync(p => p.Id == billingId); - if (entity == null) + // 验证参数 + if (string.IsNullOrEmpty(input.StoreId)) { - return (false, "开单记录不存在"); + throw NCCException.Oh("门店ID不能为空"); } - // 检查是否已经作废 - if (entity.IsEffective == StatusEnum.无效.GetHashCode()) + + // 如果开始时间和结束时间为空,就默认是当月 + if (input.StartTime == null || input.EndTime == null) { - return (false, "该开单记录已经作废"); + input.StartTime = new DateTime(DateTime.Now.Year, DateTime.Now.Month, 1); + input.EndTime = DateTime.Now.AddDays(DateTime.DaysInMonth(DateTime.Now.Year, DateTime.Now.Month)); } - // 判断是否有对应的补缴记录 - var qkbjList = await _db.Queryable().Where(p => p.SupplementBillingId == billingId && p.IsEffective == StatusEnum.有效.GetHashCode()).ToListAsync(); - if (qkbjList.Any()) - { + + // 查询开单记录 + var billingRecords = await _db.Queryable() + .Where(w => w.Djmd == input.StoreId && w.Kdrq >= input.StartTime && w.Kdrq <= input.EndTime && w.IsEffective == StatusEnum.有效.GetHashCode()) + .Select(it => new + { + id = it.Id, + djmd = it.Djmd, + jsj = it.Jsj, + kdrq = it.Kdrq, + gjlx = it.Gjlx, + zdyj = it.Zdyj, + sfyj = it.Sfyj, + qk = it.Qk, + kdhyc = SqlFunc.Subqueryable().Where(x => x.Id == it.Kdhy).Select(x => x.Khmc), + kdhysjh = SqlFunc.Subqueryable().Where(x => x.Id == it.Kdhy).Select(x => x.Sjh), + fkfs = it.Fkfs, // 付款方式 + khly = it.Khly, // 客户来源 + bz = it.Bz, // 备注 + createTime = it.CreateTime + }) + .ToListAsync(); + + if (!billingRecords.Any()) + { + return new + { + success = true, + data = new + { + billingSummary = new + { + totalCount = 0, + totalAmount = 0, + totalPaidAmount = 0, + totalDebt = 0, + billingRecords = new List() + }, + itemSummary = new + { + purchased = new { count = 0, totalAmount = 0, items = new List() }, + gifted = new { count = 0, totalAmount = 0, items = new List() }, + experience = new { count = 0, totalAmount = 0, items = new List() } + }, + healthTeacherSummary = new + { + totalCount = 0, + teachers = new List() + } + }, + message = "该时间段内无开单记录" + }; + } + + var billingIds = billingRecords.Select(x => x.id).ToList(); + + // 查询品项明细,按F_SourceType分类 + var itemDetails = await _db.Queryable() + .Where(w => billingIds.Contains(w.Glkdbh) && w.IsEffective == StatusEnum.有效.GetHashCode()) + .Select(it => new + { + id = it.Id, + glkdbh = it.Glkdbh, + px = it.Px, + pxmc = it.Pxmc, + pxjg = it.Pxjg, + sourceType = it.SourceType, + totalPrice = it.TotalPrice, + actualPrice = it.ActualPrice, + projectNumber = it.ProjectNumber, + remark = it.Remark, + itemCategory = it.ItemCategory, + }) + .ToListAsync(); + + // 按来源类型分类品项 + var purchasedItems = itemDetails.Where(x => x.sourceType == "购买").ToList(); + var giftedItems = itemDetails.Where(x => x.sourceType == "赠送").ToList(); + var experienceItems = itemDetails.Where(x => x.sourceType == "体验").ToList(); + + // 查询健康师业绩数据 + var healthTeacherData = await _db.Queryable() + .Where(w => billingIds.Contains(w.Glkdbh) && w.IsEffective == StatusEnum.有效.GetHashCode()) + .Select(it => new + { + id = it.Id, + glkdbh = it.Glkdbh, + jks = it.Jks, + jksxm = it.Jksxm, + jkszh = it.Jkszh, + jksyj = it.Jksyj, + kdpxid = it.Kdpxid, + yjsj = it.Yjsj, + jsj_id = it.Jsj_id, + itemCategory = it.ItemCategory, + storeId = it.StoreId, + itemId = it.ItemId, + itemName = it.ItemName, + }) + .ToListAsync(); + + // 构建按开单记录分组的详细数据 + var detailedRecords = billingRecords.Select(billing => new + { + // 基本信息 + date = billing.kdrq?.ToString("yyyy-MM-dd"), + customerName = billing.kdhyc, + customerPhone = billing.kdhysjh, + storeId = billing.djmd, + goldTriangle = billing.jsj, + customerType = billing.gjlx, + + // 品项分类 + purchasedItems = itemDetails.Where(x => x.glkdbh == billing.id && x.sourceType == "购买").Select(x => new + { + id = x.id, + itemName = x.pxmc, + itemCode = x.px, + price = x.pxjg, + totalPrice = x.totalPrice, + actualPrice = x.actualPrice, + projectNumber = x.projectNumber, + remark = x.remark, + itemCategory = x.itemCategory, + }).ToList(), + + giftedItems = itemDetails.Where(x => x.glkdbh == billing.id && x.sourceType == "赠送").Select(x => new + { + id = x.id, + itemName = x.pxmc, + itemCode = x.px, + price = x.pxjg, + totalPrice = x.totalPrice, + actualPrice = x.actualPrice, + projectNumber = x.projectNumber, + remark = x.remark, + itemCategory = x.itemCategory, + }).ToList(), + + experienceItems = itemDetails.Where(x => x.glkdbh == billing.id && x.sourceType == "体验").Select(x => new + { + id = x.id, + itemName = x.pxmc, + itemCode = x.px, + price = x.pxjg, + totalPrice = x.totalPrice, + actualPrice = x.actualPrice, + projectNumber = x.projectNumber, + remark = x.remark, + itemCategory = x.itemCategory, + }).ToList(), + + // 金额信息 + paidAmount = billing.sfyj, + debtAmount = billing.qk, + totalAmount = billing.zdyj, + + // 健康师信息 + healthTeachers = healthTeacherData.Where(x => x.glkdbh == billing.id).Select(x => new + { + teacherId = x.jks, + teacherName = x.jksxm, + teacherAccount = x.jkszh, + performance = x.jksyj, + performanceTime = x.yjsj, + itemDetailId = x.kdpxid, + goldTriangleId = x.jsj_id, + itemCategory = x.itemCategory, + itemId = x.itemId, + storeId = x.storeId, + itemName = x.itemName + }).ToList(), + + // 其他信息 + source = billing.khly, // 客户来源 + paymentMethod = billing.fkfs, // 支付方式 + remark = billing.bz, // 备注 + createTime = billing.createTime + }).OrderByDescending(x => x.date).ToList(); + + // 构建返回结果 + var result = new + { + success = true, + data = new + { + // 汇总统计 + summary = new + { + totalCount = billingRecords.Count, + totalAmount = billingRecords.Sum(x => x.zdyj), + totalPaidAmount = billingRecords.Sum(x => x.sfyj), + totalDebt = billingRecords.Sum(x => x.qk), + totalPurchasedItems = purchasedItems.Count, + totalGiftedItems = giftedItems.Count, + totalExperienceItems = experienceItems.Count, + totalHealthTeachers = healthTeacherData.GroupBy(x => x.jks).Count() + }, + // 详细记录列表 + records = detailedRecords + }, + message = "获取开单记录汇总信息成功" + }; + return result; + } + catch (Exception ex) + { + throw NCCException.Oh($"获取开单记录汇总信息失败:{ex.Message}"); + } + } + #endregion + + #region 私有方法 + /// + /// 检查开单记录是否可以操作 + /// + /// 开单记录ID + /// 是否可以作废 + private async Task<(bool canCancel, string errorMessage)> CheckBillingCanCancelAsync(string billingId) + { + try + { + // 查询开单记录 + var entity = await _db.Queryable().FirstAsync(p => p.Id == billingId); + if (entity == null) + { + return (false, "开单记录不存在"); + } + // 检查是否已经作废 + if (entity.IsEffective == StatusEnum.无效.GetHashCode()) + { + return (false, "该开单记录已经作废"); + } + // 判断是否有对应的补缴记录 + var qkbjList = await _db.Queryable().Where(p => p.SupplementBillingId == billingId && p.IsEffective == StatusEnum.有效.GetHashCode()).ToListAsync(); + if (qkbjList.Any()) + { return (false, "该开单记录有对应的补缴记录,不能进行操作"); } // 查询开单记录下的品项明细ID列表 @@ -3258,6 +3345,9 @@ namespace NCC.Extend.LqKdKdjlb /// - PersonCount: 人次(按客户+日期去重,同一客户不同天算多次) /// - ProjectCount: 消耗项目数(项目总次数) /// - RefundAmount: 退卡金额(健康师退卡业绩总金额) + /// - LaborCost: 手工费(消耗时的手工费总金额) + /// - OriginalLaborCost: 原始手工费(消耗时的原始手工费总金额) + /// - OvertimeLaborCost: 加班手工费(消耗时的加班手工费总金额) /// /// 查询参数 /// 健康师统计数据列表 @@ -3305,7 +3395,357 @@ namespace NCC.Extend.LqKdKdjlb CAST(COALESCE(consume_stats.ProjectCount, 0) AS DECIMAL(18,2)) as ProjectCount, -- 退卡金额 - COALESCE(refund_stats.RefundAmount, 0) as RefundAmount + COALESCE(refund_stats.RefundAmount, 0) as RefundAmount, + + -- 手工费相关统计 + COALESCE(consume_stats.LaborCost, 0) as LaborCost, + COALESCE(consume_stats.OriginalLaborCost, 0) as OriginalLaborCost, + COALESCE(consume_stats.OvertimeLaborCost, 0) as OvertimeLaborCost + + FROM BASE_USER u + LEFT JOIN lq_mdxx md ON u.F_MDID = md.F_Id + LEFT JOIN base_organize dept ON md.syb = dept.F_Id + + -- 邀约统计子查询 + LEFT JOIN ( + SELECT + yyr as EmployeeId, + COUNT(DISTINCT yykh) as InviteCount + FROM lq_yaoyjl + WHERE yyr IS NOT NULL + AND F_CreateTime >= @startTime + AND F_CreateTime <= @endTime + GROUP BY yyr + ) invite_stats ON u.F_Id = invite_stats.EmployeeId + + -- 预约统计子查询 + LEFT JOIN ( + SELECT + yyr as EmployeeId, + COUNT(DISTINCT gk) as AppointmentCount + FROM lq_yyjl + WHERE yyr IS NOT NULL + AND F_CreateTime >= @startTime + AND F_CreateTime <= @endTime + GROUP BY yyr + ) appointment_stats ON u.F_Id = appointment_stats.EmployeeId + + -- 到店统计子查询 + LEFT JOIN ( + SELECT + yyr as EmployeeId, + COUNT(DISTINCT gk) as VisitCount + FROM lq_yyjl + WHERE yyr IS NOT NULL + AND F_Status = '已确认' + AND F_CreateTime >= @startTime + AND F_CreateTime <= @endTime + GROUP BY yyr + ) visit_stats ON u.F_Id = visit_stats.EmployeeId + + -- 开单统计子查询 + LEFT JOIN ( + SELECT + jkszh as EmployeeId, + COUNT(DISTINCT glkdbh) as BillingCount, + SUM(CAST(jksyj AS DECIMAL(18,2))) as BillingAmount + FROM lq_kd_jksyj + WHERE jkszh IS NOT NULL + AND F_IsEffective = 1 + AND yjsj >= @startTime + AND yjsj <= @endTime + GROUP BY jkszh + ) billing_stats ON u.F_Id = billing_stats.EmployeeId + + -- 消耗统计子查询 + LEFT JOIN ( + SELECT + jksyj.jks as EmployeeId, + SUM(jksyj.jksyj) as ConsumeAmount, + CAST(SUM(jksyj.F_kdpxNumber) AS DECIMAL(18,2)) as ProjectCount, + COALESCE(SUM(jksyj.F_LaborCost), 0) as LaborCost, + COALESCE(SUM(jksyj.F_OriginalLaborCost), 0) as OriginalLaborCost, + COALESCE(SUM(jksyj.F_OvertimeLaborCost), 0) as OvertimeLaborCost + FROM lq_xh_jksyj jksyj + INNER JOIN lq_xh_hyhk hyhk ON jksyj.glkdbh = hyhk.F_Id + WHERE jksyj.jks IS NOT NULL + AND jksyj.F_IsEffective = 1 + AND hyhk.F_IsEffective = 1 + AND hyhk.hksj >= @startTime + AND hyhk.hksj <= @endTime + GROUP BY jksyj.jks + ) consume_stats ON u.F_Id = consume_stats.EmployeeId + + -- 有效人头统计子查询(从人次记录表获取,按月份+客户+数量去重后累加数量,F_HasBilling=1) + LEFT JOIN ( + SELECT + F_PersonId as EmployeeId, + CAST(COALESCE(SUM(F_Quantity), 0) AS DECIMAL(18,2)) as HeadCount + FROM ( + SELECT + F_PersonId, + F_WorkMonth, + F_MemberId, + F_Quantity + FROM lq_person_times_record + WHERE F_PersonId IS NOT NULL + AND F_IsEffective = 1 + AND F_PersonType = '健康师' + AND F_HasBilling = 1 + AND F_WorkDate >= DATE(@startTime) + AND F_WorkDate <= DATE(@endTime) + GROUP BY F_PersonId, F_WorkMonth, F_MemberId, F_Quantity + ) as distinct_headcount + GROUP BY F_PersonId + ) headcount_stats ON u.F_Id = headcount_stats.EmployeeId + + -- 有效人次统计子查询(从人次记录表获取,按日期+客户+数量去重后累加数量,F_HasBilling=1) + LEFT JOIN ( + SELECT + F_PersonId as EmployeeId, + CAST(COALESCE(SUM(F_Quantity), 0) AS DECIMAL(18,2)) as PersonCount + FROM ( + SELECT + F_PersonId, + F_WorkDate, + F_MemberId, + F_Quantity + FROM lq_person_times_record + WHERE F_PersonId IS NOT NULL + AND F_IsEffective = 1 + AND F_PersonType = '健康师' + AND F_HasBilling = 1 + AND F_WorkDate >= DATE(@startTime) + AND F_WorkDate <= DATE(@endTime) + GROUP BY F_PersonId, F_WorkDate, F_MemberId, F_Quantity + ) as distinct_personcount + GROUP BY F_PersonId + ) personcount_stats ON u.F_Id = personcount_stats.EmployeeId + + -- 无效人头统计子查询(从人次记录表获取,按月份+客户+数量去重后累加数量,F_HasBilling=0) + LEFT JOIN ( + SELECT + F_PersonId as EmployeeId, + CAST(COALESCE(SUM(F_Quantity), 0) AS DECIMAL(18,2)) as HeadCount + FROM ( + SELECT + F_PersonId, + F_WorkMonth, + F_MemberId, + F_Quantity + FROM lq_person_times_record + WHERE F_PersonId IS NOT NULL + AND F_IsEffective = 1 + AND F_PersonType = '健康师' + AND F_HasBilling = 0 + AND F_WorkDate >= DATE(@startTime) + AND F_WorkDate <= DATE(@endTime) + GROUP BY F_PersonId, F_WorkMonth, F_MemberId, F_Quantity + ) as distinct_headcount + GROUP BY F_PersonId + ) invalid_headcount_stats ON u.F_Id = invalid_headcount_stats.EmployeeId + + -- 无效人次统计子查询(从人次记录表获取,按日期+客户+数量去重后累加数量,F_HasBilling=0) + LEFT JOIN ( + SELECT + F_PersonId as EmployeeId, + CAST(COALESCE(SUM(F_Quantity), 0) AS DECIMAL(18,2)) as PersonCount + FROM ( + SELECT + F_PersonId, + F_WorkDate, + F_MemberId, + F_Quantity + FROM lq_person_times_record + WHERE F_PersonId IS NOT NULL + AND F_IsEffective = 1 + AND F_PersonType = '健康师' + AND F_HasBilling = 0 + AND F_WorkDate >= DATE(@startTime) + AND F_WorkDate <= DATE(@endTime) + GROUP BY F_PersonId, F_WorkDate, F_MemberId, F_Quantity + ) as distinct_personcount + GROUP BY F_PersonId + ) invalid_personcount_stats ON u.F_Id = invalid_personcount_stats.EmployeeId + + -- 退卡统计子查询 + LEFT JOIN ( + SELECT + jkszh as EmployeeId, + SUM(CAST(jksyj AS DECIMAL(18,2))) as RefundAmount + FROM lq_hytk_jksyj + WHERE jkszh IS NOT NULL + AND F_IsEffective = 1 + AND tksj >= @startTime + AND tksj <= @endTime + GROUP BY jkszh + ) refund_stats ON u.F_Id = refund_stats.EmployeeId + + WHERE u.F_GW = '健康师' + "; + + // 添加条件过滤 + var conditions = new List(); + var parameters = new List + { + new SugarParameter("@startTime", startTime), + new SugarParameter("@endTime", endTime) + }; + + if (!string.IsNullOrEmpty(input.DepartmentId)) + { + conditions.Add("md.syb = @departmentId"); + parameters.Add(new SugarParameter("@departmentId", input.DepartmentId)); + } + + if (!string.IsNullOrEmpty(input.StoreId)) + { + conditions.Add("u.F_MDID = @storeId"); + parameters.Add(new SugarParameter("@storeId", input.StoreId)); + } + + if (!string.IsNullOrEmpty(input.EmployeeName)) + { + conditions.Add("u.F_REALNAME LIKE @employeeName"); + parameters.Add(new SugarParameter("@employeeName", $"%{input.EmployeeName}%")); + } + + if (conditions.Any()) + { + sql += " AND " + string.Join(" AND ", conditions); + } + + sql += " ORDER BY u.F_REALNAME"; + + // 执行查询 + var allData = await _db.Ado.SqlQueryAsync(sql, parameters); + + // 手动分页 + var totalCount = allData.Count; + var pagedData = allData + .Skip((input.currentPage - 1) * input.pageSize) + .Take(input.pageSize) + .ToList(); + + // 直接返回分页结果 + return new + { + list = pagedData, + pagination = new + { + pageIndex = input.currentPage, + pageSize = input.pageSize, + totalCount = totalCount + } + }; + } + catch (Exception ex) + { + _logger.LogError(ex, "获取健康师统计数据失败"); + throw NCCException.Oh($"获取健康师统计数据失败:{ex.Message}"); + } + } + #endregion + + #region 门店整体统计表(备份) + /// + /// 门店整体统计表 + /// + /// + /// 统计每个健康师在指定时间周期内的各项数据指标 + /// 包括:邀约人数、预约人数、到店人数、开单人数、开单金额、消耗金额、人头、人次、消耗项目数 + /// + /// 示例请求: + /// ```json + /// { + /// "startTime": "2025-10-01", + /// "endTime": "2025-10-31", + /// "departmentId": "部门ID", + /// "storeId": "门店ID", + /// "employeeName": "健康师姓名" + /// } + /// ``` + /// + /// 参数说明: + /// - startTime: 开始时间(可选,默认为当月1号) + /// - endTime: 结束时间(可选,默认为当前时间) + /// - departmentId: 事业部ID(可选) + /// - storeId: 门店ID(可选) + /// - employeeName: 健康师姓名(可选) + /// + /// 返回字段说明: + /// - EmployeeId: 健康师ID + /// - EmployeeName: 健康师姓名 + /// - StoreId: 门店ID + /// - StoreName: 门店名称 + /// - DepartmentId: 事业部ID + /// - DepartmentName: 事业部名称 + /// - InviteCount: 邀约人数(按客户去重) + /// - AppointmentCount: 预约人数(按客户去重,无论预约状态) + /// - VisitCount: 到店人数(按客户去重,仅统计状态为'已确认'的预约) + /// - BillingCount: 开单人数(按开单记录去重) + /// - BillingAmount: 开单金额(开单业绩总金额) + /// - ConsumeAmount: 消耗金额(消耗业绩总金额) + /// - HeadCount: 人头(按客户去重) + /// - PersonCount: 人次(按客户+日期去重,同一客户不同天算多次) + /// - ProjectCount: 消耗项目数(项目总次数) + /// - RefundAmount: 退卡金额(健康师退卡业绩总金额) + /// - LaborCost: 手工费(消耗时的手工费总金额) + /// - OriginalLaborCost: 原始手工费(消耗时的原始手工费总金额) + /// - OvertimeLaborCost: 加班手工费(消耗时的加班手工费总金额) + /// + /// 查询参数 + /// 健康师统计数据列表 + /// 成功返回统计数据 + /// 参数错误 + /// 服务器错误 + [HttpGet("get-health-coach-statistics_bak")] + public async Task GetHealthCoachStatistics_bak([FromQuery] HealthCoachStatisticsQueryInput input) + { + try + { + // 设置默认时间范围(如果未提供,默认为当月) + var startTime = input.StartTime ?? new DateTime(DateTime.Now.Year, DateTime.Now.Month, 1); + var endTime = input.EndTime ?? DateTime.Now; + + // 构建SQL查询 + var sql = $@" + SELECT + u.F_Id as EmployeeId, + u.F_REALNAME as EmployeeName, + u.F_MDID as StoreId, + md.dm as StoreName, + md.syb as DepartmentId, + dept.F_FullName as DepartmentName, + + -- 邀约人数 + COALESCE(invite_stats.InviteCount, 0) as InviteCount, + + -- 预约人数(无论状态) + COALESCE(appointment_stats.AppointmentCount, 0) as AppointmentCount, + + -- 到店人数(已确认状态) + COALESCE(visit_stats.VisitCount, 0) as VisitCount, + + -- 开单人数和金额 + COALESCE(billing_stats.BillingCount, 0) as BillingCount, + COALESCE(billing_stats.BillingAmount, 0) as BillingAmount, + + -- 消耗相关统计 + COALESCE(consume_stats.ConsumeAmount, 0) as ConsumeAmount, + CAST(COALESCE(headcount_stats.HeadCount, 0) AS DECIMAL(18,2)) as HeadCount, + CAST(COALESCE(personcount_stats.PersonCount, 0) AS DECIMAL(18,2)) as PersonCount, + CAST(COALESCE(invalid_headcount_stats.HeadCount, 0) AS DECIMAL(18,2)) as InvalidHeadCount, + CAST(COALESCE(invalid_personcount_stats.PersonCount, 0) AS DECIMAL(18,2)) as InvalidPersonCount, + CAST(COALESCE(consume_stats.ProjectCount, 0) AS DECIMAL(18,2)) as ProjectCount, + + -- 退卡金额 + COALESCE(refund_stats.RefundAmount, 0) as RefundAmount, + + -- 手工费相关统计 + COALESCE(consume_stats.LaborCost, 0) as LaborCost, + COALESCE(consume_stats.OriginalLaborCost, 0) as OriginalLaborCost, + COALESCE(consume_stats.OvertimeLaborCost, 0) as OvertimeLaborCost FROM BASE_USER u LEFT JOIN lq_mdxx md ON u.F_MDID = md.F_Id @@ -3365,17 +3805,20 @@ namespace NCC.Extend.LqKdKdjlb -- 消耗统计子查询 LEFT JOIN ( SELECT - jksyj.jkszh as EmployeeId, + jksyj.jks as EmployeeId, SUM(jksyj.jksyj) as ConsumeAmount, - CAST(SUM(jksyj.F_kdpxNumber) AS DECIMAL(18,2)) as ProjectCount + CAST(SUM(jksyj.F_kdpxNumber) AS DECIMAL(18,2)) as ProjectCount, + COALESCE(SUM(jksyj.F_LaborCost), 0) as LaborCost, + COALESCE(SUM(jksyj.F_OriginalLaborCost), 0) as OriginalLaborCost, + COALESCE(SUM(jksyj.F_OvertimeLaborCost), 0) as OvertimeLaborCost FROM lq_xh_jksyj jksyj INNER JOIN lq_xh_hyhk hyhk ON jksyj.glkdbh = hyhk.F_Id - WHERE jksyj.jkszh IS NOT NULL + WHERE jksyj.jks IS NOT NULL AND jksyj.F_IsEffective = 1 AND hyhk.F_IsEffective = 1 AND hyhk.hksj >= @startTime AND hyhk.hksj <= @endTime - GROUP BY jksyj.jkszh + GROUP BY jksyj.jks ) consume_stats ON u.F_Id = consume_stats.EmployeeId -- 有效人头统计子查询(从人次记录表获取,按月份+客户+数量去重后累加数量,F_HasBilling=1) diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqProductService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqProductService.cs index fad69fd..7f1a9ee 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend/LqProductService.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend/LqProductService.cs @@ -254,6 +254,7 @@ namespace NCC.Extend var productIds = data.list.Select(x => x.id).ToList(); if (productIds.Any()) { + // 查询总库存数量 var inventoryDict = await _db.Queryable() .Where(x => productIds.Contains(x.ProductId) && x.IsEffective == StatusEnum.有效.GetHashCode()) .GroupBy(x => x.ProductId) @@ -262,10 +263,21 @@ namespace NCC.Extend var inventoryDictMap = inventoryDict.ToDictionary(k => k.ProductId, v => v.TotalQuantity.HasValue ? (int)v.TotalQuantity.Value : 0); - // 填充库存数量 + // 查询已使用数量 + var usageDict = await _db.Queryable() + .Where(x => productIds.Contains(x.ProductId) && x.IsEffective == StatusEnum.有效.GetHashCode()) + .GroupBy(x => x.ProductId) + .Select(x => new { ProductId = x.ProductId, TotalUsage = SqlFunc.AggregateSum((decimal?)x.UsageQuantity) }) + .ToListAsync(); + + var usageDictMap = usageDict.ToDictionary(k => k.ProductId, v => v.TotalUsage.HasValue ? (int)v.TotalUsage.Value : 0); + + // 填充库存数量(总库存减去已使用数量) foreach (var item in data.list) { - item.currentInventory = inventoryDictMap.ContainsKey(item.id) ? inventoryDictMap[item.id] : 0; + var totalInventory = inventoryDictMap.ContainsKey(item.id) ? inventoryDictMap[item.id] : 0; + var totalUsage = usageDictMap.ContainsKey(item.id) ? usageDictMap[item.id] : 0; + item.currentInventory = totalInventory - totalUsage; } } @@ -427,6 +439,7 @@ namespace NCC.Extend var productIds = products.Select(x => x.id).ToList(); if (productIds.Any()) { + // 查询总库存数量 var inventoryDict = await _db.Queryable() .Where(x => productIds.Contains(x.ProductId) && x.IsEffective == StatusEnum.有效.GetHashCode()) .GroupBy(x => x.ProductId) @@ -435,10 +448,21 @@ namespace NCC.Extend var inventoryDictMap = inventoryDict.ToDictionary(k => k.ProductId, v => v.TotalQuantity.HasValue ? (int)v.TotalQuantity.Value : 0); - // 填充库存数量 + // 查询已使用数量 + var usageDict = await _db.Queryable() + .Where(x => productIds.Contains(x.ProductId) && x.IsEffective == StatusEnum.有效.GetHashCode()) + .GroupBy(x => x.ProductId) + .Select(x => new { ProductId = x.ProductId, TotalUsage = SqlFunc.AggregateSum((decimal?)x.UsageQuantity) }) + .ToListAsync(); + + var usageDictMap = usageDict.ToDictionary(k => k.ProductId, v => v.TotalUsage.HasValue ? (int)v.TotalUsage.Value : 0); + + // 填充库存数量(总库存减去已使用数量) foreach (var item in products) { - item.currentInventory = inventoryDictMap.ContainsKey(item.id) ? inventoryDictMap[item.id] : 0; + var totalInventory = inventoryDictMap.ContainsKey(item.id) ? inventoryDictMap[item.id] : 0; + var totalUsage = usageDictMap.ContainsKey(item.id) ? usageDictMap[item.id] : 0; + item.currentInventory = totalInventory - totalUsage; } } diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqSalaryService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqSalaryService.cs new file mode 100644 index 0000000..3c3a52b --- /dev/null +++ b/netcore/src/Modularity/Extend/NCC.Extend/LqSalaryService.cs @@ -0,0 +1,486 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using NCC.Common.Enum; +using NCC.Common.Filter; +using NCC.Common.Helper; +using NCC.Dependency; +using NCC.DynamicApiController; +using NCC.Extend.Entitys.Dto.LqSalary; +using Yitter.IdGenerator; +using NCC.Extend.Entitys.lq_attendance_summary; +using NCC.Extend.Entitys.lq_jinsanjiao_user; +using NCC.Extend.Entitys.lq_kd_jksyj; +using NCC.Extend.Entitys.lq_kd_kdjlb; +using NCC.Extend.Entitys.lq_md_target; +using NCC.Extend.Entitys.lq_person_times_record; +using NCC.Extend.Entitys.lq_salary_statistics; +using NCC.Extend.Entitys.lq_xh_jksyj; +using NCC.Extend.Entitys.lq_ycsd_jsj; +using SqlSugar; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace NCC.Extend +{ + /// + /// 薪酬服务 + /// + [ApiDescriptionSettings(Tag = "薪酬服务", Name = "LqSalary", Order = 300)] + [Route("api/Extend/[controller]")] + public class LqSalaryService : IDynamicApiController, ITransient + { + private readonly ISqlSugarClient _db; + + /// + /// 初始化一个类型的新实例 + /// + public LqSalaryService(ISqlSugarClient db) + { + _db = db; + } + + /// + /// 获取健康师工资列表 + /// + /// 查询参数 + /// 健康师工资分页列表 + [HttpGet("health-coach")] + public async Task> GetHealthCoachSalaryList([FromQuery] HealthCoachSalaryInput input) + { + var monthStr = $"{input.Year}{input.Month:D2}"; + + // 1. 检查当月是否已生成工资数据 + var exists = await _db.Queryable() + .AnyAsync(x => x.StatisticsMonth == monthStr); + + // 2. 如果没有数据,则进行计算 + if (!exists) + { + await CalculateHealthCoachSalary(input.Year, input.Month); + } + + // 3. 查询数据 + var query = _db.Queryable() + .Where(x => x.StatisticsMonth == monthStr); + + if (!string.IsNullOrEmpty(input.StoreId)) + { + query = query.Where(x => x.StoreId == input.StoreId); + } + + if (!string.IsNullOrEmpty(input.Keyword)) + { + query = query.Where(x => x.EmployeeName.Contains(input.Keyword) || x.EmployeeId.Contains(input.Keyword)); + } + + var list = await query.Select(x => new HealthCoachSalaryOutput + { + Id = x.Id, + StoreName = x.StoreName, + EmployeeName = x.EmployeeName, + Position = x.Position, + GoldTriangleTeam = x.GoldTriangleTeam, + TotalPerformance = x.TotalPerformance, + BasePerformance = x.BasePerformance, + CooperationPerformance = x.CooperationPerformance, + RewardPerformance = x.RewardPerformance, + Consumption = x.Consumption, + ProjectCount = x.ProjectCount, + CustomerCount = x.CustomerCount, + WorkingDays = x.WorkingDays, + HealthCoachBaseSalary = x.HealthCoachBaseSalary, + TotalCommission = x.TotalCommission, + HandworkFee = x.HandworkFee, + TotalSubsidy = x.TotalSubsidy, + TotalDeduction = x.TotalDeduction, + ActualSalary = x.ActualSalary, + IsLocked = x.IsLocked, + UpdateTime = x.UpdateTime + }) + .ToPagedListAsync(input.currentPage, input.pageSize); + + return PageResult.SqlSugarPageResult(list); + } + + /// + /// 计算健康师工资 + /// + /// 年份 + /// 月份 + /// + [HttpPost("calculate/health-coach")] + public async Task CalculateHealthCoachSalary(int year, int month) + { + var startDate = new DateTime(year, month, 1); + var endDate = startDate.AddMonths(1).AddDays(-1); + var monthStr = $"{year}{month:D2}"; + + // 1. 获取基础数据 + + // 1.1 业绩数据 (lq_kd_jksyj) + var performanceList = await _db.Queryable() + .Where(x => x.Yjsj >= startDate && x.Yjsj <= endDate.AddDays(1) && x.IsEffective == 1) + .ToListAsync(); + + // 1.1.1 获取关联的开单记录(用于获取 sfskdd) + var billingIds = performanceList.Select(x => x.Glkdbh).Distinct().ToList(); + var billingDict = await _db.Queryable() + .Where(x => billingIds.Contains(x.Id)) + .ToDictionaryAsync(x => x.Id, x => x.Sfskdd); + + // 1.1.2 组合数据 + var performanceData = performanceList.Select(p => new + { + p.Jks, + p.Jksxm, + p.StoreId, + p.Jksyj, + p.ItemCategory, + Sfskdd = billingDict.ContainsKey(p.Glkdbh) ? billingDict[p.Glkdbh] : null + }).ToList(); + + // 1.2 消耗数据 (lq_xh_jksyj) + var consumptionList = await _db.Queryable() + .Where(x => x.Yjsj >= startDate && x.Yjsj <= endDate.AddDays(1) && x.IsEffective == 1) + .ToListAsync(); + + // 1.3 考勤数据 (lq_attendance_summary) + var attendanceList = await _db.Queryable() + .Where(x => x.Year == year && x.Month == month && x.IsEffective == 1) + .ToListAsync(); + + // 1.4 战队成员及顾问信息 (lq_jinsanjiao_user + lq_ycsd_jsj) + var teamUserList = await _db.Queryable() + .Where(x => x.Month == monthStr && x.DeleteMark == 0) + .ToListAsync(); + + // 1.4.1 获取战队信息 + var teamIds = teamUserList.Select(x => x.JsjId).Distinct().ToList(); + var teamList = await _db.Queryable() + .Where(x => teamIds.Contains(x.Id)) + .ToListAsync(); + var teamDict = teamList.ToDictionary(x => x.Id, x => x.Jsj); + + // 1.4.2 组合数据 + var teamMembers = teamUserList.Select(user => new + { + user.UserId, + user.IsLeader, + TeamId = user.JsjId, + TeamName = teamDict.ContainsKey(user.JsjId) ? teamDict[user.JsjId] : (string)null + }).ToList(); + + // 1.5 到店人头 (lq_person_times_record) + // 统计每个健康师的去重会员数 + var headcountList = await _db.Queryable() + .Where(x => x.WorkMonth == monthStr && x.IsEffective == 1) + .GroupBy(x => x.PersonId) + .Select(x => new { PersonId = x.PersonId, Count = SqlFunc.AggregateDistinctCount(x.MemberId) }) + .ToListAsync(); + + // 1.6 门店生命线 (lq_md_target) + var storeTargets = await _db.Queryable() + .Where(x => x.Month == monthStr) + .ToListAsync(); + + // 2. 聚合每个健康师的数据对象 + var employeeStats = new Dictionary(); + + // 获取所有涉及的健康师ID + var allEmployeeIds = performanceData.Select(x => x.Jks) + .Union(consumptionList.Select(x => x.Jks)) + .Union(attendanceList.Select(x => x.UserId)) + .Union(teamMembers.Select(x => x.UserId)) + .Where(x => !string.IsNullOrEmpty(x)) + .Distinct() + .ToList(); + + foreach (var empId in allEmployeeIds) + { + var salary = new LqSalaryStatisticsEntity + { + Id = YitIdHelper.NextId().ToString(), + EmployeeId = empId, + StatisticsMonth = monthStr, + CreateTime = DateTime.Now, + UpdateTime = DateTime.Now, + IsLocked = 0 + }; + + // 填充基础信息 (姓名、门店) + var perfRecord = performanceData.FirstOrDefault(x => x.Jks == empId); + var consRecord = consumptionList.FirstOrDefault(x => x.Jks == empId); + + if (perfRecord != null) + { + salary.EmployeeName = perfRecord.Jksxm; + salary.StoreId = perfRecord.StoreId; + } + else if (consRecord != null) + { + salary.EmployeeName = consRecord.Jksxm; + salary.StoreId = consRecord.StoreId; + } + + // 填充门店名称 + if (!string.IsNullOrEmpty(salary.StoreId)) + { + // 这里简单处理,实际可能需要缓存门店列表 + // salary.StoreName = ... + } + + // 2.1 计算个人业绩 + var myPerf = performanceData.Where(x => x.Jks == empId).ToList(); + salary.BasePerformance = myPerf.Where(x => x.ItemCategory == "基础业绩").Sum(x => decimal.Parse(x.Jksyj ?? "0")); + salary.CooperationPerformance = myPerf.Where(x => x.ItemCategory == "合作业绩").Sum(x => decimal.Parse(x.Jksyj ?? "0")); + salary.TotalPerformance = myPerf.Sum(x => decimal.Parse(x.Jksyj ?? "0")); + + // 新客与升单业绩 + salary.NewCustomerPerformance = myPerf.Where(x => x.Sfskdd == "是").Sum(x => decimal.Parse(x.Jksyj ?? "0")); + salary.UpgradePerformance = myPerf.Where(x => x.Sfskdd == "否").Sum(x => decimal.Parse(x.Jksyj ?? "0")); + + // 2.2 计算消耗和项目数 + var myCons = consumptionList.Where(x => x.Jks == empId).ToList(); + salary.Consumption = myCons.Sum(x => x.Jksyj ?? 0); + salary.ProjectCount = myCons.Sum(x => x.KdpxNumber ?? 0); + salary.HandworkFee = myCons.Sum(x => x.LaborCost ?? 0); // 使用 F_LaborCost + + // 2.3 考勤数据 + var myAtt = attendanceList.FirstOrDefault(x => x.UserId == empId); + salary.WorkingDays = myAtt?.WorkDays ?? 0; + salary.LeaveDays = myAtt?.LeaveDays ?? 0; + + // 2.4 到店人头 + var myHeadcount = headcountList.FirstOrDefault(x => x.PersonId == empId); + salary.CustomerCount = myHeadcount?.Count ?? 0; + + // 2.5 战队信息 (初始) + var myTeam = teamMembers.FirstOrDefault(x => x.UserId == empId); + if (myTeam != null) + { + salary.GoldTriangleId = myTeam.TeamId; + salary.GoldTriangleTeam = myTeam.TeamName ?? ""; + } + + employeeStats[empId] = salary; + } + + // 3. 处理战队逻辑 (考勤规则) + // 规则:若出勤天数 < 21天,则该健康师不计入战队,按单人计算。 + + // 按战队分组 + var teamGroups = employeeStats.Values + .Where(x => !string.IsNullOrEmpty(x.GoldTriangleId)) + .GroupBy(x => x.GoldTriangleId) + .ToList(); + + foreach (var group in teamGroups) + { + var validMembers = new List(); + var invalidMembers = new List(); + + foreach (var member in group) + { + if (member.WorkingDays >= 21) + { + validMembers.Add(member); + } + else + { + invalidMembers.Add(member); + } + } + + // 对于无效成员,移除战队标识,视为单人 + foreach (var member in invalidMembers) + { + member.GoldTriangleId = null; + member.GoldTriangleTeam = null; + } + + // 计算有效战队的总业绩 + var teamTotalPerformance = validMembers.Sum(x => x.TotalPerformance); + + // 更新有效成员的战队业绩 + foreach (var member in validMembers) + { + member.TeamPerformance = teamTotalPerformance; + } + } + + // 4. 计算薪资 (底薪 & 提成) + foreach (var salary in employeeStats.Values) + { + // 4.1 底薪计算 + salary.HealthCoachBaseSalary = CalculateBaseSalary(salary.Consumption, salary.ProjectCount); + + // 4.2 提成计算 + // 单人业绩 <= 6000 无提成 + if (salary.TotalPerformance <= 6000) + { + salary.TotalCommission = 0; + salary.BasePerformanceCommission = 0; + salary.CooperationPerformanceCommission = 0; + salary.ConsultantCommission = 0; + } + else + { + // 确定提成点 + decimal commissionPoint = 0; + + if (!string.IsNullOrEmpty(salary.GoldTriangleId)) + { + // 是战队成员 + // 获取战队人数 (注意:这里应该是有效战队人数) + var teamMemberCount = employeeStats.Values.Count(x => x.GoldTriangleId == salary.GoldTriangleId); + commissionPoint = GetTeamCommissionPoint(teamMemberCount, salary.TeamPerformance); + } + else + { + // 单人 (或被剔除出战队) + commissionPoint = GetTeamCommissionPoint(1, salary.TotalPerformance); + } + + salary.CommissionPoint = commissionPoint; + + // 计算基础/合作提成 + salary.BasePerformanceCommission = salary.BasePerformance * 0.95m * commissionPoint; + salary.CooperationPerformanceCommission = salary.CooperationPerformance * 0.95m * 0.65m * commissionPoint; + + // 计算顾问提成 + // 检查是否是顾问 + var isConsultant = teamMembers.Any(x => x.UserId == salary.EmployeeId && x.IsLeader == 1); + if (isConsultant && !string.IsNullOrEmpty(salary.GoldTriangleId)) + { + salary.ConsultantCommission = CalculateConsultantCommission(salary.TeamPerformance, employeeStats.Values.Where(x => x.GoldTriangleId == salary.GoldTriangleId).ToList()); + } + + salary.TotalCommission = salary.BasePerformanceCommission + salary.CooperationPerformanceCommission + salary.ConsultantCommission; + } + + // 计算占比 + if (salary.TeamPerformance > 0) + { + salary.Percentage = salary.TotalPerformance / salary.TeamPerformance; + } + else if (salary.TotalPerformance > 0 && string.IsNullOrEmpty(salary.GoldTriangleId)) + { + salary.Percentage = 1; // 单人占比100% + } + + // 4.3 最终工资 + salary.ActualSalary = salary.HealthCoachBaseSalary + salary.TotalCommission + salary.HandworkFee + salary.TotalSubsidy - salary.TotalDeduction; + } + + // 5. 保存数据 + if (employeeStats.Any()) + { + // 先删除当月旧数据 (防止重复) + await _db.Deleteable().Where(x => x.StatisticsMonth == monthStr).ExecuteCommandAsync(); + await _db.Insertable(employeeStats.Values.ToList()).ExecuteCommandAsync(); + } + } + + /// + /// 计算底薪 + /// + private decimal CalculateBaseSalary(decimal consumption, decimal projectCount) + { + // 0星:<1w 或 <96个 -> 1800 + // 1星:>=1w 且 >=96个 -> 2000 + // 2星:>=2w 且 >=126个 -> 2200 + // 3星:>=4w 且 >=156个 -> 2400 + + // 特殊规则:若消耗或项目数中仅一项未达标(0星),底薪按1星(2000元)计算 + + int starCons = 0; + if (consumption >= 40000) starCons = 3; + else if (consumption >= 20000) starCons = 2; + else if (consumption >= 10000) starCons = 1; + + int starProj = 0; + if (projectCount >= 156) starProj = 3; + else if (projectCount >= 126) starProj = 2; + else if (projectCount >= 96) starProj = 1; + + int finalStar = Math.Min(starCons, starProj); + + // 特殊规则处理: 仅一项未达标(0星) -> 1星 + if (finalStar == 0 && (starCons > 0 || starProj > 0)) + { + finalStar = 1; + } + + switch (finalStar) + { + case 3: return 2400; + case 2: return 2200; + case 1: return 2000; + default: return 1800; + } + } + + /// + /// 获取战队提成点 + /// + private decimal GetTeamCommissionPoint(int memberCount, decimal teamPerformance) + { + if (memberCount >= 3) + { + if (teamPerformance >= 150000) return 0.07m; + if (teamPerformance >= 120000) return 0.06m; + if (teamPerformance >= 90000) return 0.05m; + if (teamPerformance >= 60000) return 0.04m; + if (teamPerformance >= 30000) return 0.03m; + } + else if (memberCount == 2) + { + if (teamPerformance >= 80000) return 0.06m; + if (teamPerformance >= 60000) return 0.05m; + if (teamPerformance >= 40000) return 0.04m; + if (teamPerformance >= 20000) return 0.03m; + } + else // 1人 + { + if (teamPerformance >= 60000) return 0.06m; + if (teamPerformance >= 40000) return 0.05m; + if (teamPerformance >= 20000) return 0.04m; + if (teamPerformance >= 10000) return 0.03m; + } + return 0; + } + + /// + /// 计算顾问提成 + /// + private decimal CalculateConsultantCommission(decimal teamPerformance, List teamMembers) + { + // 顾问提成规则: + // 高级顾问:战队总业绩 ≥ 6万元 且 组员业绩达到40%以上 且 消耗达到6万元 → 团队总业绩0.8% + // 普通顾问:战队总业绩 ≥ 4万元 且 组员业绩达到30%以上 且 消耗达到4万元 → 团队总业绩0.3% + + // 这里的“组员业绩达到X%以上”理解为:除顾问外的成员业绩占比?或者每个成员都达标? + // 通常理解为:团队中是否有成员业绩贡献较高,或者团队整体结构健康。 + // 假设“组员业绩达到X%”是指:团队中至少有一名成员(非顾问本人?)或者所有成员平均? + // 鉴于规则模糊,这里先简化实现:暂只考核总业绩和消耗。 + // 消耗是团队总消耗吗?假设是。 + + var teamConsumption = teamMembers.Sum(x => x.Consumption); + + // 高级顾问 + if (teamPerformance >= 60000 && teamConsumption >= 60000) + { + return teamPerformance * 0.008m; + } + // 普通顾问 + if (teamPerformance >= 40000 && teamConsumption >= 40000) + { + return teamPerformance * 0.003m; + } + + return 0; + } + } +} diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqStatisticsService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqStatisticsService.cs index 021a55a..bf7d37a 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend/LqStatisticsService.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend/LqStatisticsService.cs @@ -4997,7 +4997,5 @@ namespace NCC.Extend.LqStatistics } #endregion - - } } diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqXhHyhkService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqXhHyhkService.cs index fe77730..16ff34f 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend/LqXhHyhkService.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend/LqXhHyhkService.cs @@ -775,50 +775,50 @@ namespace NCC.Extend.LqXhHyhk .MergeTable() .Distinct() // 去重,因为一个耗卡可能对应多个科技部老师业绩记录 .OrderBy($"{sidx} {sort}") - .ToPagedListAsync(input.currentPage, input.pageSize); + .ToPagedListAsync(input.currentPage, input.pageSize); - // 获取当前页的耗卡记录ID列表 - var consumeIds = data.list.Select(x => x.id).ToList(); + // 获取当前页的耗卡记录ID列表 + var consumeIds = data.list.Select(x => x.id).ToList(); - // 批量查询耗卡明细 - var consumeDetails = new List(); - if (consumeIds.Any()) - { - consumeDetails = await _db.Queryable() - .Where(x => consumeIds.Contains(x.ConsumeInfoId) && x.IsEffective == StatusEnum.有效.GetHashCode()) - .Select(x => new LqXhPxmxInfoOutput - { - id = x.Id, - consumeInfoId = x.ConsumeInfoId, - billingItemId = x.BillingItemId, - px = x.Px, - pxmc = x.Pxmc, - pxjg = x.Pxjg, - memberId = x.MemberId, - createTime = x.CreateTIme, - projectNumber = x.ProjectNumber, - originalProjectNumber = x.OriginalProjectNumber, - overtimeProjectNumber = x.OvertimeProjectNumber, - sourceType = x.SourceType, - totalPrice = x.TotalPrice, - isEffective = x.IsEffective, - }) - .ToListAsync(); - } + // 批量查询耗卡明细 + var consumeDetails = new List(); + if (consumeIds.Any()) + { + consumeDetails = await _db.Queryable() + .Where(x => consumeIds.Contains(x.ConsumeInfoId) && x.IsEffective == StatusEnum.有效.GetHashCode()) + .Select(x => new LqXhPxmxInfoOutput + { + id = x.Id, + consumeInfoId = x.ConsumeInfoId, + billingItemId = x.BillingItemId, + px = x.Px, + pxmc = x.Pxmc, + pxjg = x.Pxjg, + memberId = x.MemberId, + createTime = x.CreateTIme, + projectNumber = x.ProjectNumber, + originalProjectNumber = x.OriginalProjectNumber, + overtimeProjectNumber = x.OvertimeProjectNumber, + sourceType = x.SourceType, + totalPrice = x.TotalPrice, + isEffective = x.IsEffective, + }) + .ToListAsync(); + } - // 按耗卡记录ID分组耗卡明细 - var consumeDetailsGrouped = consumeDetails.GroupBy(x => x.consumeInfoId) - .ToDictionary(g => g.Key, g => g.ToList()); + // 按耗卡记录ID分组耗卡明细 + var consumeDetailsGrouped = consumeDetails.GroupBy(x => x.consumeInfoId) + .ToDictionary(g => g.Key, g => g.ToList()); - // 为每个耗卡记录分配耗卡明细 - foreach (var item in data.list) - { - item.ConsumeDetails = consumeDetailsGrouped.ContainsKey(item.id) - ? consumeDetailsGrouped[item.id] - : new List(); - } + // 为每个耗卡记录分配耗卡明细 + foreach (var item in data.list) + { + item.ConsumeDetails = consumeDetailsGrouped.ContainsKey(item.id) + ? consumeDetailsGrouped[item.id] + : new List(); + } - return PageResult.SqlSugarPageResult(data); + return PageResult.SqlSugarPageResult(data); } catch (Exception ex) { diff --git a/sql/同步退卡时间字段.sql b/sql/同步退卡时间字段.sql new file mode 100644 index 0000000..05abe2a --- /dev/null +++ b/sql/同步退卡时间字段.sql @@ -0,0 +1,33 @@ +-- 同步退卡业绩表的退卡时间到明细表和业绩表 +-- 数据来源:lq_hytk_hytk.tksj(退卡时间) + +-- ============================================ +-- 1. 同步退卡明细表(lq_hytk_mx)的退卡时间 +-- ============================================ +-- 通过 F_RefundInfoId 关联到退卡业绩表的 F_Id +UPDATE lq_hytk_mx mx +INNER JOIN lq_hytk_hytk hytk ON mx.F_RefundInfoId = hytk.F_Id +SET mx.tksj = hytk.tksj +WHERE hytk.tksj IS NOT NULL + AND (mx.tksj IS NULL OR mx.tksj != hytk.tksj); + +-- ============================================ +-- 2. 同步退卡健康师业绩表(lq_hytk_jksyj)的退卡时间 +-- ============================================ +-- 通过 gltkbh 关联到退卡业绩表的 F_Id +UPDATE lq_hytk_jksyj jksyj +INNER JOIN lq_hytk_hytk hytk ON jksyj.gltkbh = hytk.F_Id +SET jksyj.tksj = hytk.tksj +WHERE hytk.tksj IS NOT NULL + AND (jksyj.tksj IS NULL OR jksyj.tksj != hytk.tksj); + +-- ============================================ +-- 3. 同步退卡科技老师业绩表(lq_hytk_kjbsyj)的退卡时间 +-- ============================================ +-- 通过 gltkbh 关联到退卡业绩表的 F_Id +UPDATE lq_hytk_kjbsyj kjbsyj +INNER JOIN lq_hytk_hytk hytk ON kjbsyj.gltkbh = hytk.F_Id +SET kjbsyj.tksj = hytk.tksj +WHERE hytk.tksj IS NOT NULL + AND (kjbsyj.tksj IS NULL OR kjbsyj.tksj != hytk.tksj); + diff --git a/sql/更新开单记录表储扣金额.sql b/sql/更新开单记录表储扣金额.sql new file mode 100644 index 0000000..dd6b4f4 --- /dev/null +++ b/sql/更新开单记录表储扣金额.sql @@ -0,0 +1,21 @@ +-- 更新开单记录表中的储扣金额,使其等于储扣详情表中汇总的金额 +-- 只更新那些储扣金额不一致的记录(包括NULL、0或金额不匹配的情况) + +UPDATE lq_kd_kdjlb kd +INNER JOIN ( + SELECT + F_BillingId, + COALESCE(SUM(F_Amount), 0) as total_amount + FROM lq_kd_deductinfo + WHERE F_IsEffective = 1 + GROUP BY F_BillingId +) deduct_sum ON kd.F_Id = deduct_sum.F_BillingId +SET kd.F_DeductAmount = deduct_sum.total_amount +WHERE kd.F_IsEffective = 1 + AND ( + kd.F_DeductAmount IS NULL + OR kd.F_DeductAmount = 0 + OR ABS(kd.F_DeductAmount - deduct_sum.total_amount) > 0.01 + ) + AND deduct_sum.total_amount > 0; + -- libgit2 0.21.4