diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqDailyReport/TianwangGroupPerformanceCompletionOutput.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqDailyReport/TianwangGroupPerformanceCompletionOutput.cs index e35dd64..26df5a9 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqDailyReport/TianwangGroupPerformanceCompletionOutput.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqDailyReport/TianwangGroupPerformanceCompletionOutput.cs @@ -52,6 +52,11 @@ namespace NCC.Extend.Entitys.Dto.LqDailyReport /// 门店数量 /// public int StoreCount { get; set; } + + /// + /// 储扣金额(储值扣减金额总和) + /// + public decimal DeductAmount { get; set; } } } diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKdKdjlb/BillingItemDetailListOutput.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKdKdjlb/BillingItemDetailListOutput.cs new file mode 100644 index 0000000..132e466 --- /dev/null +++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKdKdjlb/BillingItemDetailListOutput.cs @@ -0,0 +1,71 @@ +using System; + +namespace NCC.Extend.Entitys.Dto.LqKdKdjlb +{ + /// + /// 开单品项明细记录输出 + /// + public class BillingItemDetailListOutput + { + /// + /// 品项明细ID + /// + public string id { get; set; } + + /// + /// 开单ID + /// + public string billingId { get; set; } + + /// + /// 开单时间 + /// + public DateTime? billingTime { get; set; } + + /// + /// 营销活动名称 + /// + public string activityName { get; set; } + + /// + /// 会员名称 + /// + public string memberName { get; set; } + + /// + /// 会员手机 + /// + public string memberPhone { get; set; } + + /// + /// 品项名称 + /// + public string itemName { get; set; } + + /// + /// 品项类型(分类④-统计品项用) + /// + public string itemType { get; set; } + + /// + /// 实付金额 + /// + public decimal actualPrice { get; set; } + + /// + /// 项目次数 + /// + public decimal projectNumber { get; set; } + + /// + /// 来源类型(购买/赠送/体验) + /// + public string sourceType { get; set; } + + /// + /// 备注 + /// + public string remark { get; set; } + } +} + diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKdKdjlb/BillingItemDetailListQueryInput.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKdKdjlb/BillingItemDetailListQueryInput.cs new file mode 100644 index 0000000..1d5f3a4 --- /dev/null +++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKdKdjlb/BillingItemDetailListQueryInput.cs @@ -0,0 +1,71 @@ +using NCC.Common.Filter; + +namespace NCC.Extend.Entitys.Dto.LqKdKdjlb +{ + /// + /// 开单品项明细记录查询输入参数 + /// + public class BillingItemDetailListQueryInput : PageInputBase + { + /// + /// 品项明细ID + /// + public string Id { get; set; } + + /// + /// 开单ID + /// + public string BillingId { get; set; } + + /// + /// 开单时间(开始) + /// + public string StartBillingTime { get; set; } + + /// + /// 开单时间(结束) + /// + public string EndBillingTime { get; set; } + + /// + /// 营销活动ID + /// + public string ActivityId { get; set; } + + /// + /// 会员ID + /// + public string MemberId { get; set; } + + /// + /// 会员名称(模糊查询) + /// + public string MemberName { get; set; } + + /// + /// 会员手机号 + /// + public string MemberPhone { get; set; } + + /// + /// 品项ID + /// + public string ItemId { get; set; } + + /// + /// 品项名称(模糊查询) + /// + public string ItemName { get; set; } + + /// + /// 品项类型(分类④-统计品项用) + /// + public string ItemType { get; set; } + + /// + /// 来源类型(购买/赠送/体验) + /// + public string SourceType { get; set; } + } +} + 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 43973dd..76e94d3 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 @@ -78,6 +78,6 @@ namespace NCC.Extend.Entitys.Dto.LqKdKdjlb /// /// 消耗项目数 - 统计该健康师在指定时间周期内消耗的项目总次数 /// - public int projectCount { get; set; } + public decimal projectCount { get; set; } } } diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKhxx/MemberItemInfoOutput.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKhxx/MemberItemInfoOutput.cs new file mode 100644 index 0000000..645b424 --- /dev/null +++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKhxx/MemberItemInfoOutput.cs @@ -0,0 +1,164 @@ +using System; +using System.Collections.Generic; + +namespace NCC.Extend.Entitys.Dto.LqKhxx +{ + /// + /// 会员品项信息输出 + /// + public class MemberItemInfoOutput + { + /// + /// 会员ID + /// + public string MemberId { get; set; } + + /// + /// 会员编号(档案号) + /// + public string MemberCode { get; set; } + + /// + /// 会员名称 + /// + public string MemberName { get; set; } + + /// + /// 会员电话 + /// + public string MemberPhone { get; set; } + + /// + /// 所属门店ID + /// + public string BelongStoreId { get; set; } + + /// + /// 所属门店名称 + /// + public string BelongStoreName { get; set; } + + /// + /// 开单信息列表 + /// + public List BillingItems { get; set; } = new List(); + } + + /// + /// 开单信息 + /// + public class BillingItemInfo + { + /// + /// 开单ID + /// + public string BillingId { get; set; } + + /// + /// 开单时间 + /// + public DateTime? BillingTime { get; set; } + + /// + /// 开单门店ID + /// + public string BillingStoreId { get; set; } + + /// + /// 开单门店名称 + /// + public string BillingStoreName { get; set; } + + /// + /// 开单人ID + /// + public string BillingUserId { get; set; } + + /// + /// 开单人名称 + /// + public string BillingUserName { get; set; } + + /// + /// 购买品项列表 + /// + public List PurchaseItems { get; set; } = new List(); + + /// + /// 购买品项总金额 + /// + public decimal PurchaseItemsTotalAmount { get; set; } + + /// + /// 赠送品项列表 + /// + public List GiftItems { get; set; } = new List(); + + /// + /// 体验品项列表 + /// + public List ExperienceItems { get; set; } = new List(); + + /// + /// 已消耗品项列表 + /// + public List ConsumedItems { get; set; } = new List(); + + /// + /// 已消耗品项总金额 + /// + public decimal ConsumedItemsTotalAmount { get; set; } + + /// + /// 已退卡品项列表 + /// + public List RefundedItems { get; set; } = new List(); + + /// + /// 已退卡品项总金额 + /// + public decimal RefundedItemsTotalAmount { get; set; } + + /// + /// 已储扣品项列表 + /// + public List DeductedItems { get; set; } = new List(); + + /// + /// 已储扣品项总金额 + /// + public decimal DeductedItemsTotalAmount { get; set; } + } + + /// + /// 品项明细 + /// + public class ItemDetail + { + /// + /// 品项ID + /// + public string ItemId { get; set; } + + /// + /// 品项名称 + /// + public string ItemName { get; set; } + + /// + /// 品项价格 + /// + public decimal ItemPrice { get; set; } + + /// + /// 项目次数 + /// + public decimal ProjectNumber { get; set; } + + /// + /// 总金额 + /// + public decimal TotalAmount { get; set; } + } +} + diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKhxx/MemberItemInfoQueryInput.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKhxx/MemberItemInfoQueryInput.cs new file mode 100644 index 0000000..6f3b19d --- /dev/null +++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKhxx/MemberItemInfoQueryInput.cs @@ -0,0 +1,47 @@ +using System; +using NCC.Common.Filter; + +namespace NCC.Extend.Entitys.Dto.LqKhxx +{ + /// + /// 会员品项信息查询输入参数 + /// + public class MemberItemInfoQueryInput : PageInputBase + { + /// + /// 会员ID + /// + public string MemberId { get; set; } + + /// + /// 所属门店ID + /// + public string BelongStoreId { get; set; } + + /// + /// 开单门店ID + /// + public string BillingStoreId { get; set; } + + /// + /// 开单人ID + /// + public string BillingUserId { get; set; } + + /// + /// 购买品项ID + /// + public string PurchaseItemId { get; set; } + + /// + /// 赠送品项ID + /// + public string GiftItemId { get; set; } + + /// + /// 体验品项ID + /// + public string ExperienceItemId { get; set; } + } +} + diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqDailyReportService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqDailyReportService.cs index 2e086ef..19701a8 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend/LqDailyReportService.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend/LqDailyReportService.cs @@ -456,7 +456,7 @@ namespace NCC.Extend var actualCompletedPerformance = unit.BillingPerformance - unit.RefundPerformance; // 实际完成业绩 unit.CompletedPerformance = actualCompletedPerformance; // 实际完成业绩 var completionRate = unit.TargetPerformance > 0 ? (actualCompletedPerformance / unit.TargetPerformance * 100m) : 0m; - unit.CompletionRate = decimal.Round(completionRate, 2); + unit.CompletionRate = decimal.Round(completionRate, 2); } var outputList = businessUnitDict.Values.OrderByDescending(x => x.CompletedPerformance).ToList(); @@ -470,17 +470,23 @@ namespace NCC.Extend /// 获取天王团业绩完成情况 /// /// - /// 根据指定日期统计各天王团的业绩完成情况,包括目标业绩、开单业绩、退卡金额、实际完成业绩、完成率 + /// 根据指定日期统计各天王团的业绩完成情况,包括目标业绩、开单业绩、储扣金额、退卡金额、实际完成业绩、完成率 /// /// 业绩统计规则: - /// - 教育部(教育一部、教育二部):统计生美品项(lq_xmzl.qt2='生美')相关的开单和退卡业绩 - /// - 科技部(科技一部、科技二部):统计科美品项(lq_xmzl.qt2='科美')相关的开单和退卡业绩 - /// - 大项目部(大项目一部、大项目二部):统计医美品项(lq_xmzl.qt2='医美')相关的开单和退卡业绩 + /// - 教育部(教育一部、教育二部):统计生美品项(lq_xmzl.qt2='生美')相关的开单、储扣和退卡业绩 + /// - 科技部(科技一部、科技二部):统计科美品项(lq_xmzl.qt2='科美')相关的开单、储扣和退卡业绩 + /// - 大项目部(大项目一部、大项目二部):统计医美品项(lq_xmzl.qt2='医美')相关的开单、储扣和退卡业绩 /// /// 业绩分配规则: - /// - 生美品项的开单和退卡业绩全部归教育部(不按目标比例分配) - /// - 科美品项的开单和退卡业绩全部归科技部(不按目标比例分配) - /// - 医美品项的开单和退卡业绩全部归大项目部(不按目标比例分配) + /// - 生美品项的开单、储扣和退卡业绩全部归教育部(不按目标比例分配) + /// - 科美品项的开单、储扣和退卡业绩全部归科技部(不按目标比例分配) + /// - 医美品项的开单、储扣和退卡业绩全部归大项目部(不按目标比例分配) + /// + /// 业绩计算规则: + /// - 开单业绩:原始开单业绩(不减去储扣金额) + /// - 储扣金额:储值扣减金额总和 + /// - 退卡业绩:退卡金额总和 + /// - 实际完成业绩 = 开单业绩 - 储扣金额 - 退卡金额 /// /// 示例请求: /// ```json @@ -500,10 +506,11 @@ namespace NCC.Extend /// - DepartmentId: 部门ID /// - DepartmentName: 部门名称(教育一部、教育二部、科技一部、科技二部、大项目一部、大项目二部) /// - TargetPerformance: 目标业绩(来自门店目标表lq_md_target,根据部门类型使用对应字段:教育部使用F_EducationDepartmentTarget,科技部使用F_TechDepartmentTarget,大项目部使用F_MajorProjectDepartmentTarget,根据开始时间所在月份获取,通过对应归属字段关联,如果未查询到则为0) - /// - BillingPerformance: 开单业绩(指定时间范围内,按品项分类过滤后的开单业绩总和,按门店目标比例分配) - /// - RefundPerformance: 退款业绩(指定时间范围内,按品项分类过滤后的退卡业绩总和,按门店目标比例分配) + /// - BillingPerformance: 开单业绩(原始开单业绩,不减去储扣金额) + /// - DeductAmount: 储扣金额(储值扣减金额总和) + /// - RefundPerformance: 退卡业绩(退卡金额总和) /// - ActualPerformance: 开单业绩(与BillingPerformance相同,用于兼容) - /// - CompletedPerformance: 实际完成业绩(开单业绩 - 退款业绩) + /// - CompletedPerformance: 实际完成业绩(开单业绩 - 储扣金额 - 退卡金额) /// - CompletionRate: 完成率(百分比,CompletedPerformance / TargetPerformance * 100) /// - StoreCount: 门店数量(根据门店目标表中归属该部门的门店数统计) /// @@ -582,7 +589,8 @@ namespace NCC.Extend ActualPerformance = 0, CompletedPerformance = 0, CompletionRate = 0, - StoreCount = Convert.ToInt32(dept.StoreCount) + StoreCount = Convert.ToInt32(dept.StoreCount), + DeductAmount = 0 }; // 记录部门对应的字段类型 departmentFieldMap[deptId] = deptType.Value.fieldName; @@ -620,6 +628,7 @@ namespace NCC.Extend } } + // 如果目标表中没有对应月份的数据,直接返回空业绩列表 if (!storeIds.Any()) { return departmentDict.Values.OrderByDescending(x => x.CompletedPerformance).ToList(); @@ -630,7 +639,7 @@ namespace NCC.Extend // 2.2 查询目标表数据(一次性查询,数据量小) // 直接查询,不使用 MAX(),因为每个门店在目标表中应该只有一条记录 var targetSql = $@" - SELECT + SELECT F_StoreId, F_EducationDepartment, F_TechDepartment, @@ -650,220 +659,163 @@ namespace NCC.Extend } } - // 2.3 从品项明细表统计业绩,按门店+品项分类分组 + // 如果targetDict为空,说明目标表中没有数据,直接返回 + if (!targetDict.Any()) + { + return departmentDict.Values.OrderByDescending(x => x.CompletedPerformance).ToList(); + } + + // 2.3 分步骤查询:按生美、科美、医美分别查询开单业绩、退卡业绩、储扣金额 // 重要:业绩从品项明细表(lq_kd_pxmx)的 F_ActualPrice 获取,不是从开单记录表的 sfyj 获取 // 因为一个开单可能包含多个品项,每个品项属于不同的分类(生美、科美、医美) // 例如:开单A实付500元,包含科美100元、医美250元、生美150元 // 那么科技部统计100元,大项目部统计250元,教育部统计150元 - var billingSql = $@" - SELECT - temp.djmd as StoreId, - item.qt2 as ItemType, - COALESCE(SUM(temp.F_ActualPrice), 0) as StoreAmount - FROM ( - SELECT pxmx.px, pxmx.F_ActualPrice, billing.djmd - FROM lq_kd_pxmx pxmx - INNER JOIN lq_kd_kdjlb billing ON pxmx.glkdbh = billing.F_Id - WHERE pxmx.F_IsEffective = 1 - AND billing.F_IsEffective = 1 - AND billing.djmd IN ('{storeIdsStr}') - AND billing.kdrq >= '{startDate:yyyy-MM-dd} 00:00:00' - AND billing.kdrq < '{endDate.AddDays(1):yyyy-MM-dd} 00:00:00' - ) temp - INNER JOIN lq_xmzl item ON temp.px = item.F_Id - WHERE item.F_IsEffective = 1 - AND item.qt2 IN ('生美', '科美', '医美') - GROUP BY temp.djmd, item.qt2"; - - var billingData = await _db.Ado.SqlQueryAsync(billingSql); - - // 2.4 从退卡明细表统计退卡业绩,按门店+品项分类分组 - // 重要:退卡业绩从退卡明细表(lq_hytk_mx)的 tkje 获取,按品项分类统计 - var refundSql = $@" - SELECT - temp.md as StoreId, - item.qt2 as ItemType, - COALESCE(SUM(temp.tkje), 0) as StoreRefundAmount - FROM ( - SELECT refund_mx.px, refund_mx.tkje, refund.md - FROM lq_hytk_mx refund_mx - INNER JOIN lq_hytk_hytk refund ON refund_mx.F_RefundInfoId = refund.F_Id - WHERE refund_mx.F_IsEffective = 1 - AND refund.F_IsEffective = 1 - AND refund.md IN ('{storeIdsStr}') - AND refund.tksj >= '{startDate:yyyy-MM-dd} 00:00:00' - AND refund.tksj < '{endDate.AddDays(1):yyyy-MM-dd} 00:00:00' - ) temp - INNER JOIN lq_xmzl item ON temp.px = item.F_Id - WHERE item.F_IsEffective = 1 - AND item.qt2 IN ('生美', '科美', '医美') - GROUP BY temp.md, item.qt2"; - - var refundData = await _db.Ado.SqlQueryAsync(refundSql); - - // 2.5 从储扣明细表统计储扣金额,按门店+品项分类分组 - // 重要:储扣金额从储扣明细表(lq_kd_deductinfo)的 F_Amount 获取,按品项分类统计 - var deductSql = $@" - SELECT - billing.djmd as StoreId, - item.qt2 as ItemType, - COALESCE(SUM(deduct.F_Amount), 0) as StoreDeductAmount - FROM lq_kd_deductinfo deduct - INNER JOIN lq_kd_kdjlb billing ON deduct.F_BillingId = billing.F_Id - INNER JOIN lq_xmzl item ON deduct.F_ItemId = item.F_Id - WHERE deduct.F_IsEffective = 1 - AND billing.F_IsEffective = 1 - AND item.F_IsEffective = 1 - AND billing.djmd IN ('{storeIdsStr}') - AND billing.kdrq >= '{startDate:yyyy-MM-dd} 00:00:00' - AND billing.kdrq < '{endDate.AddDays(1):yyyy-MM-dd} 00:00:00' - AND item.qt2 IN ('生美', '科美', '医美') - GROUP BY billing.djmd, item.qt2"; - - var deductData = await _db.Ado.SqlQueryAsync(deductSql); - - // 2.6 根据品项分类和目标表分配业绩到对应部门 - // 分配规则: - // - 生美品项 → 分配给教育部门(F_EducationDepartment) - // - 科美品项 → 分配给科技部门(F_TechDepartment) - // - 医美品项 → 分配给大项目部门(F_MajorProjectDepartment) - // 注意:一个门店可能同时属于多个部门,但每个品项的业绩只分配给对应的一个部门 - foreach (var billing in billingData ?? Enumerable.Empty()) - { - var storeId = billing?.StoreId?.ToString(); - var itemType = billing?.ItemType?.ToString(); - var storeAmount = billing?.StoreAmount != null ? Convert.ToDecimal(billing.StoreAmount) : 0m; - if (string.IsNullOrEmpty(storeId) || string.IsNullOrEmpty(itemType) || storeAmount <= 0 || !targetDict.ContainsKey(storeId)) - continue; + // 品项类型列表 + var itemTypes = new[] { "生美", "科美", "医美" }; - var target = targetDict[storeId]; - if (target == null) - continue; - - string targetDeptId = null; - - // 根据品项分类,分配到对应的部门 + // 循环处理每个品项类型 + foreach (var itemType in itemTypes) + { + // 确定该品项类型对应的部门字段 + string deptField = ""; if (itemType == "生美") { - // 生美品项 → 教育部门 - targetDeptId = target.F_EducationDepartment?.ToString(); + deptField = "F_EducationDepartment"; } else if (itemType == "科美") { - // 科美品项 → 科技部门 - targetDeptId = target.F_TechDepartment?.ToString(); + deptField = "F_TechDepartment"; } else if (itemType == "医美") { - // 医美品项 → 大项目部门 - targetDeptId = target.F_MajorProjectDepartment?.ToString(); + deptField = "F_MajorProjectDepartment"; } - // 只分配给在查询列表中的部门,避免重复统计 - if (!string.IsNullOrEmpty(targetDeptId) && departmentDict.ContainsKey(targetDeptId)) - { - departmentDict[targetDeptId].BillingPerformance += storeAmount; - } - } + // 2.3.1 查询开单业绩(按品项类型) + var billingSql = $@" + SELECT + target.{deptField} as TargetDeptId, + COALESCE(SUM(pxmx.F_ActualPrice), 0) as StoreAmount + FROM lq_kd_pxmx pxmx + INNER JOIN lq_kd_kdjlb billing ON pxmx.glkdbh = billing.F_Id + INNER JOIN lq_xmzl item ON pxmx.px = item.F_Id + INNER JOIN lq_md_target target ON billing.djmd = target.F_StoreId AND target.F_Month = '{month}' + WHERE pxmx.F_IsEffective = 1 + AND billing.F_IsEffective = 1 + AND item.F_IsEffective = 1 + AND item.qt2 = '{itemType}' + AND billing.djmd IN ('{storeIdsStr}') + AND billing.kdrq >= '{startDate.ToString("yyyy-MM-dd")} 00:00:00' + AND billing.kdrq < '{endDate.AddDays(1).ToString("yyyy-MM-dd")} 00:00:00' + GROUP BY target.{deptField}"; - // 2.6.1 减去储扣金额 - foreach (var deduct in deductData ?? Enumerable.Empty()) - { - var storeId = deduct?.StoreId?.ToString(); - var itemType = deduct?.ItemType?.ToString(); - var storeDeductAmount = deduct?.StoreDeductAmount != null ? Convert.ToDecimal(deduct.StoreDeductAmount) : 0m; + var billingData = await _db.Ado.SqlQueryAsync(billingSql); - if (string.IsNullOrEmpty(storeId) || string.IsNullOrEmpty(itemType) || storeDeductAmount <= 0 || !targetDict.ContainsKey(storeId)) - continue; - - var target = targetDict[storeId]; - if (target == null) - continue; + // 分配开单业绩到对应部门 + foreach (var billing in billingData ?? Enumerable.Empty()) + { + var storeAmount = billing?.StoreAmount != null ? Convert.ToDecimal(billing.StoreAmount) : 0m; + var targetDeptId = billing?.TargetDeptId?.ToString(); - string targetDeptId = null; + if (storeAmount <= 0 || string.IsNullOrEmpty(targetDeptId)) + continue; - // 根据品项分类,从对应的部门减去储扣金额 - if (itemType == "生美") - { - // 生美品项储扣 → 从教育部门减去 - targetDeptId = target.F_EducationDepartment?.ToString(); - } - else if (itemType == "科美") - { - // 科美品项储扣 → 从科技部门减去 - targetDeptId = target.F_TechDepartment?.ToString(); - } - else if (itemType == "医美") - { - // 医美品项储扣 → 从大项目部门减去 - targetDeptId = target.F_MajorProjectDepartment?.ToString(); + // 只分配给在查询列表中的部门,避免重复统计 + if (departmentDict.ContainsKey(targetDeptId)) + { + departmentDict[targetDeptId].BillingPerformance += storeAmount; + } } - // 只从在查询列表中的部门减去,避免重复统计 - if (!string.IsNullOrEmpty(targetDeptId) && departmentDict.ContainsKey(targetDeptId)) + // 2.3.2 查询退卡业绩(按品项类型) + var refundSql = $@" + SELECT + target.{deptField} as TargetDeptId, + COALESCE(SUM(refund_mx.tkje), 0) as StoreRefundAmount + FROM lq_hytk_mx refund_mx + INNER JOIN lq_hytk_hytk refund ON refund_mx.F_RefundInfoId = refund.F_Id + INNER JOIN lq_xmzl item ON refund_mx.px = item.F_Id + INNER JOIN lq_md_target target ON refund.md = target.F_StoreId AND target.F_Month = '{month}' + WHERE refund_mx.F_IsEffective = 1 + AND refund.F_IsEffective = 1 + AND item.F_IsEffective = 1 + AND item.qt2 = '{itemType}' + AND refund.md IN ('{storeIdsStr}') + AND refund.tksj >= '{startDate.ToString("yyyy-MM-dd")} 00:00:00' + AND refund.tksj < '{endDate.AddDays(1).ToString("yyyy-MM-dd")} 00:00:00' + GROUP BY target.{deptField}"; + + var refundData = await _db.Ado.SqlQueryAsync(refundSql); + + // 分配退卡业绩到对应部门 + foreach (var refund in refundData ?? Enumerable.Empty()) { - departmentDict[targetDeptId].BillingPerformance -= storeDeductAmount; - } - } + var storeRefundAmount = refund?.StoreRefundAmount != null ? Convert.ToDecimal(refund.StoreRefundAmount) : 0m; + var targetDeptId = refund?.TargetDeptId?.ToString(); - // 2.7 根据品项分类和目标表分配退卡业绩到对应部门 - // 分配规则与开单业绩相同: - // - 生美品项退卡 → 分配给教育部门 - // - 科美品项退卡 → 分配给科技部门 - // - 医美品项退卡 → 分配给大项目部门 - foreach (var refund in refundData ?? Enumerable.Empty()) - { - var storeId = refund?.StoreId?.ToString(); - var itemType = refund?.ItemType?.ToString(); - var storeRefundAmount = refund?.StoreRefundAmount != null ? Convert.ToDecimal(refund.StoreRefundAmount) : 0m; + if (storeRefundAmount <= 0 || string.IsNullOrEmpty(targetDeptId)) + continue; - if (string.IsNullOrEmpty(storeId) || string.IsNullOrEmpty(itemType) || storeRefundAmount <= 0 || !targetDict.ContainsKey(storeId)) - continue; + // 只分配给在查询列表中的部门,避免重复统计 + if (departmentDict.ContainsKey(targetDeptId)) + { + departmentDict[targetDeptId].RefundPerformance += storeRefundAmount; + } + } - var target = targetDict[storeId]; - if (target == null) - continue; + // 2.3.3 查询储扣金额(按品项类型) + var deductSql = $@" + SELECT + target.{deptField} as TargetDeptId, + COALESCE(SUM(deduct.F_Amount), 0) as StoreDeductAmount + FROM lq_kd_deductinfo deduct + INNER JOIN lq_kd_kdjlb billing ON deduct.F_BillingId = billing.F_Id + INNER JOIN lq_xmzl item ON deduct.F_ItemId = item.F_Id + INNER JOIN lq_md_target target ON billing.djmd = target.F_StoreId AND target.F_Month = '{month}' + WHERE deduct.F_IsEffective = 1 + AND billing.F_IsEffective = 1 + AND item.F_IsEffective = 1 + AND item.qt2 = '{itemType}' + AND billing.djmd IN ('{storeIdsStr}') + AND billing.kdrq >= '{startDate.ToString("yyyy-MM-dd")} 00:00:00' + AND billing.kdrq < '{endDate.AddDays(1).ToString("yyyy-MM-dd")} 00:00:00' + GROUP BY target.{deptField}"; - string targetDeptId = null; + var deductData = await _db.Ado.SqlQueryAsync(deductSql); - // 根据品项分类,分配到对应的部门 - if (itemType == "生美") - { - // 生美品项退卡 → 教育部门 - targetDeptId = target.F_EducationDepartment?.ToString(); - } - else if (itemType == "科美") - { - // 科美品项退卡 → 科技部门 - targetDeptId = target.F_TechDepartment?.ToString(); - } - else if (itemType == "医美") + // 分配储扣金额到对应部门 + foreach (var deduct in deductData ?? Enumerable.Empty()) { - // 医美品项退卡 → 大项目部门 - targetDeptId = target.F_MajorProjectDepartment?.ToString(); - } + var storeDeductAmount = deduct?.StoreDeductAmount != null ? Convert.ToDecimal(deduct.StoreDeductAmount) : 0m; + var targetDeptId = deduct?.TargetDeptId?.ToString(); - // 只分配给在查询列表中的部门,避免重复统计 - if (!string.IsNullOrEmpty(targetDeptId) && departmentDict.ContainsKey(targetDeptId)) - { - departmentDict[targetDeptId].RefundPerformance += storeRefundAmount; + if (storeDeductAmount <= 0 || string.IsNullOrEmpty(targetDeptId)) + continue; + + // 只累加到在查询列表中的部门,避免重复统计 + if (departmentDict.ContainsKey(targetDeptId)) + { + departmentDict[targetDeptId].DeductAmount += storeDeductAmount; + } } } // 第三步:计算实际完成业绩和完成率,并保留两位小数 foreach (var dept in departmentDict.Values) { - dept.BillingPerformance = decimal.Round(dept.BillingPerformance, 2); - dept.RefundPerformance = decimal.Round(dept.RefundPerformance, 2); - dept.ActualPerformance = dept.BillingPerformance; // 开单业绩 - var actualCompletedPerformance = dept.BillingPerformance - dept.RefundPerformance; // 实际完成业绩 + dept.BillingPerformance = decimal.Round(dept.BillingPerformance, 2); // 开单业绩(原始,不减去储扣) + dept.RefundPerformance = decimal.Round(dept.RefundPerformance, 2); // 退卡业绩 + dept.DeductAmount = decimal.Round(dept.DeductAmount, 2); // 储扣金额,保留两位小数 + dept.ActualPerformance = dept.BillingPerformance; // 开单业绩(与BillingPerformance相同,用于兼容) + // 实际完成业绩 = 开单业绩 - 储扣金额 - 退卡金额 + var actualCompletedPerformance = dept.BillingPerformance - dept.DeductAmount - dept.RefundPerformance; dept.CompletedPerformance = decimal.Round(actualCompletedPerformance, 2); // 实际完成业绩,保留两位小数 - var completionRate = dept.TargetPerformance > 0 - ? (actualCompletedPerformance / dept.TargetPerformance * 100m) - : 0m; - dept.CompletionRate = decimal.Round(completionRate, 2); + var completionRate = dept.TargetPerformance > 0 + ? (actualCompletedPerformance / dept.TargetPerformance * 100m) + : 0m; + dept.CompletionRate = decimal.Round(completionRate, 2); } var outputList = departmentDict.Values @@ -1174,12 +1126,13 @@ namespace NCC.Extend } // SQL查询:统计科技部老师的消耗业绩、见客数、项目数 + // 注意:GROUP BY 中移除了 user.F_RealName,避免同一老师ID因姓名不同产生重复记录 var consumeSql = $@" SELECT techDept.F_Id as TechDepartmentId, techDept.F_FullName as TechDepartmentName, consume.kjbls as TeacherId, - user.F_RealName as TeacherName, + MAX(user.F_RealName) as TeacherName, COUNT(DISTINCT hyhk.hy) as CustomerCount, SUM(consume.F_hdpxNumber) as ConsumeProjectCount, SUM(consume.kjblsyj) as ConsumeAchievement @@ -1194,7 +1147,7 @@ namespace NCC.Extend AND DATE(hyhk.hksj) <= '{endDate:yyyy-MM-dd}' {techFilter} {teacherFilter} - GROUP BY techDept.F_Id, techDept.F_FullName, consume.kjbls, user.F_RealName"; + GROUP BY techDept.F_Id, techDept.F_FullName, consume.kjbls"; var consumeResult = await _db.Ado.SqlQueryAsync(consumeSql); @@ -1202,48 +1155,257 @@ namespace NCC.Extend var orderSql = $@" SELECT techDept.F_Id as TechDepartmentId, + techDept.F_FullName as TechDepartmentName, ord.kjbls as TeacherId, + MAX(user.F_RealName) as TeacherName, SUM(ord.kjblsyj) as OrderAchievement FROM lq_kd_kjbsyj ord INNER JOIN lq_kd_kdjlb kdjlb ON ord.glkdbh = kdjlb.F_Id INNER JOIN lq_mdxx store ON kdjlb.djmd = store.F_Id LEFT JOIN base_organize techDept ON store.kjb = techDept.F_Id + LEFT JOIN BASE_USER user ON ord.kjbls = user.F_Id WHERE ord.F_IsEffective = 1 AND kdjlb.F_IsEffective = 1 AND DATE(kdjlb.kdrq) >= '{startDate:yyyy-MM-dd}' AND DATE(kdjlb.kdrq) <= '{endDate:yyyy-MM-dd}' {techFilter} {teacherFilterForOrder} - GROUP BY techDept.F_Id, ord.kjbls"; + GROUP BY techDept.F_Id, techDept.F_FullName, ord.kjbls"; var orderResult = await _db.Ado.SqlQueryAsync(orderSql); - // 合并数据 + // 合并数据:按员工ID汇总,避免同一员工在多个科技部重复出现 + // 使用 TeacherId 作为唯一键,汇总所有科技部的数据 var teacherDict = new Dictionary(); + // 记录每个员工在各个科技部的消耗业绩,用于确定主要科技部 + var teacherDeptConsumePerformance = new Dictionary>(); + // 记录每个员工在各个科技部的开单业绩,用于确定主要科技部(当消耗业绩为0时) + var teacherDeptOrderPerformance = new Dictionary>(); + // 第一步:处理消耗数据,按员工ID汇总 foreach (var item in consumeResult ?? Enumerable.Empty()) { - var key = $"{item.TechDepartmentId}_{item.TeacherId}"; - teacherDict[key] = new TechTeacherDailyStatisticsOutput + var teacherId = item.TeacherId?.ToString(); + var techDeptId = item.TechDepartmentId?.ToString(); + var techDeptName = item.TechDepartmentName?.ToString(); + var consumeAchievement = Convert.ToDecimal(item.ConsumeAchievement); + + if (string.IsNullOrEmpty(teacherId)) + continue; + + // 记录员工在各科技部的消耗业绩(累加,因为同一员工在同一科技部可能有多个门店的数据) + if (!teacherDeptConsumePerformance.ContainsKey(teacherId)) { - TechDepartmentId = item.TechDepartmentId?.ToString(), - TechDepartmentName = item.TechDepartmentName?.ToString(), - TeacherId = item.TeacherId?.ToString(), - TeacherName = item.TeacherName?.ToString(), - CustomerCount = Convert.ToInt32(item.CustomerCount), - ConsumeProjectCount = Convert.ToInt32(item.ConsumeProjectCount), - ConsumeAchievement = Convert.ToDecimal(item.ConsumeAchievement), - OrderAchievement = 0 - }; + teacherDeptConsumePerformance[teacherId] = new Dictionary(); + } + if (!string.IsNullOrEmpty(techDeptId)) + { + if (!teacherDeptConsumePerformance[teacherId].ContainsKey(techDeptId)) + { + teacherDeptConsumePerformance[teacherId][techDeptId] = 0; + } + teacherDeptConsumePerformance[teacherId][techDeptId] += consumeAchievement; + } + + // 按员工ID汇总数据 + if (!teacherDict.ContainsKey(teacherId)) + { + teacherDict[teacherId] = new TechTeacherDailyStatisticsOutput + { + TechDepartmentId = techDeptId, + TechDepartmentName = techDeptName, + TeacherId = teacherId, + TeacherName = item.TeacherName?.ToString(), + CustomerCount = 0, + ConsumeProjectCount = 0, + ConsumeAchievement = 0, + OrderAchievement = 0 + }; + } + + // 汇总数据(累加项目数和业绩,见客数后面统一重新计算去重) + var teacher = teacherDict[teacherId]; + teacher.ConsumeProjectCount += Convert.ToInt32(item.ConsumeProjectCount); + teacher.ConsumeAchievement += consumeAchievement; } - // 合并开单业绩 + // 第二步:处理开单业绩,按员工ID汇总 foreach (var item in orderResult ?? Enumerable.Empty()) { - var key = $"{item.TechDepartmentId}_{item.TeacherId}"; - if (teacherDict.ContainsKey(key)) + var teacherId = item.TeacherId?.ToString(); + var techDeptId = item.TechDepartmentId?.ToString(); + var techDeptName = item.TechDepartmentName?.ToString(); + var orderAchievement = Convert.ToDecimal(item.OrderAchievement); + + if (string.IsNullOrEmpty(teacherId)) + continue; + + // 记录员工在各科技部的开单业绩(累加,因为同一员工在同一科技部可能有多个门店的数据) + if (!teacherDeptOrderPerformance.ContainsKey(teacherId)) { - teacherDict[key].OrderAchievement = Convert.ToDecimal(item.OrderAchievement); + teacherDeptOrderPerformance[teacherId] = new Dictionary(); + } + if (!string.IsNullOrEmpty(techDeptId)) + { + if (!teacherDeptOrderPerformance[teacherId].ContainsKey(techDeptId)) + { + teacherDeptOrderPerformance[teacherId][techDeptId] = 0; + } + teacherDeptOrderPerformance[teacherId][techDeptId] += orderAchievement; + } + + if (!teacherDict.ContainsKey(teacherId)) + { + // 如果消耗数据中没有,但开单数据中有,需要创建记录 + teacherDict[teacherId] = new TechTeacherDailyStatisticsOutput + { + TechDepartmentId = techDeptId, + TechDepartmentName = techDeptName, + TeacherId = teacherId, + TeacherName = item.TeacherName?.ToString(), + CustomerCount = 0, + ConsumeProjectCount = 0, + ConsumeAchievement = 0, + OrderAchievement = 0 + }; + } + else + { + // 如果消耗数据中有,但名称为空,尝试从开单数据中获取 + if (string.IsNullOrEmpty(teacherDict[teacherId].TeacherName)) + { + teacherDict[teacherId].TeacherName = item.TeacherName?.ToString(); + } + } + + teacherDict[teacherId].OrderAchievement += orderAchievement; + } + + // 第三步:确定每个员工的主要科技部 + // 优先按消耗业绩最多的科技部,如果消耗业绩为0,则按开单业绩最多的科技部 + foreach (var teacherId in teacherDict.Keys.ToList()) + { + var teacher = teacherDict[teacherId]; + string mainDeptId = null; + string mainDeptName = null; + + // 优先按消耗业绩确定主要科技部 + if (teacherDeptConsumePerformance.ContainsKey(teacherId) && teacherDeptConsumePerformance[teacherId].Any()) + { + var mainDept = teacherDeptConsumePerformance[teacherId] + .OrderByDescending(x => x.Value) + .FirstOrDefault(); + + if (!string.IsNullOrEmpty(mainDept.Key) && mainDept.Value > 0) + { + mainDeptId = mainDept.Key; + // 从消耗数据中获取该科技部的名称 + var mainDeptData = consumeResult?.FirstOrDefault(x => + x.TeacherId?.ToString() == teacherId && + x.TechDepartmentId?.ToString() == mainDeptId); + + if (mainDeptData != null) + { + mainDeptName = mainDeptData.TechDepartmentName?.ToString(); + } + } + } + + // 如果消耗业绩为0或没有,则按开单业绩确定主要科技部 + if (string.IsNullOrEmpty(mainDeptId) && teacherDeptOrderPerformance.ContainsKey(teacherId) && teacherDeptOrderPerformance[teacherId].Any()) + { + var mainDept = teacherDeptOrderPerformance[teacherId] + .OrderByDescending(x => x.Value) + .FirstOrDefault(); + + if (!string.IsNullOrEmpty(mainDept.Key) && mainDept.Value > 0) + { + mainDeptId = mainDept.Key; + // 从开单数据中获取该科技部的名称 + var mainDeptData = orderResult?.FirstOrDefault(x => + x.TeacherId?.ToString() == teacherId && + x.TechDepartmentId?.ToString() == mainDeptId); + + if (mainDeptData != null) + { + mainDeptName = mainDeptData.TechDepartmentName?.ToString(); + } + } + } + + // 如果仍然没有确定主要科技部,但已有科技部ID和名称,保持原样 + if (!string.IsNullOrEmpty(mainDeptId)) + { + teacher.TechDepartmentId = mainDeptId; + if (!string.IsNullOrEmpty(mainDeptName)) + { + teacher.TechDepartmentName = mainDeptName; + } + } + else if (string.IsNullOrEmpty(teacher.TechDepartmentName) && !string.IsNullOrEmpty(teacher.TechDepartmentId)) + { + // 如果仍然没有科技部名称,尝试从开单数据中获取 + var orderDeptData = orderResult?.FirstOrDefault(x => + x.TeacherId?.ToString() == teacherId && + x.TechDepartmentId?.ToString() == teacher.TechDepartmentId); + + if (orderDeptData != null && !string.IsNullOrEmpty(orderDeptData.TechDepartmentName?.ToString())) + { + teacher.TechDepartmentName = orderDeptData.TechDepartmentName?.ToString(); + } + } + } + + // 第四步:重新计算见客数(去重,因为同一个客户可能在多个科技部被统计) + // 由于已经汇总了数据,这里需要重新查询去重后的见客数 + var teacherIds = teacherDict.Keys.ToList(); + if (teacherIds.Any()) + { + var teacherIdsStr = string.Join("','", teacherIds); + var customerCountSql = $@" + SELECT + consume.kjbls as TeacherId, + COUNT(DISTINCT hyhk.hy) as CustomerCount + FROM lq_xh_kjbsyj consume + INNER JOIN lq_xh_hyhk hyhk ON consume.glkdbh = hyhk.F_Id + WHERE consume.F_IsEffective = 1 + AND hyhk.F_IsEffective = 1 + AND DATE(hyhk.hksj) >= '{startDate:yyyy-MM-dd}' + AND DATE(hyhk.hksj) <= '{endDate:yyyy-MM-dd}' + AND consume.kjbls IN ('{teacherIdsStr}') + GROUP BY consume.kjbls"; + + var customerCountResult = await _db.Ado.SqlQueryAsync(customerCountSql); + foreach (var item in customerCountResult ?? Enumerable.Empty()) + { + var teacherId = item.TeacherId?.ToString(); + if (!string.IsNullOrEmpty(teacherId) && teacherDict.ContainsKey(teacherId)) + { + teacherDict[teacherId].CustomerCount = Convert.ToInt32(item.CustomerCount); + } + } + + // 第五步:统一查询所有用户名称,确保所有用户都能获取到名称 + var teacherNamesSql = $@" + SELECT + F_Id as TeacherId, + F_RealName as TeacherName + FROM BASE_USER + WHERE F_Id IN ('{teacherIdsStr}')"; + + var teacherNamesResult = await _db.Ado.SqlQueryAsync(teacherNamesSql); + foreach (var item in teacherNamesResult ?? Enumerable.Empty()) + { + var teacherId = item.TeacherId?.ToString(); + var teacherName = item.TeacherName?.ToString(); + if (!string.IsNullOrEmpty(teacherId) && teacherDict.ContainsKey(teacherId)) + { + // 如果名称为空,则填充;如果已有名称,保持不变 + if (string.IsNullOrEmpty(teacherDict[teacherId].TeacherName) && !string.IsNullOrEmpty(teacherName)) + { + teacherDict[teacherId].TeacherName = teacherName; + } + } } } @@ -1583,6 +1745,8 @@ namespace NCC.Extend } #endregion + + #region 获取储值扣减金额统计 /// /// 获取储值扣减金额统计 diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqKdKdjlbService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqKdKdjlbService.cs index 11e4fb7..a81735b 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend/LqKdKdjlbService.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend/LqKdKdjlbService.cs @@ -3071,7 +3071,7 @@ namespace NCC.Extend.LqKdKdjlb COALESCE(consume_stats.ConsumeAmount, 0) as ConsumeAmount, COALESCE(consume_stats.HeadCount, 0) as HeadCount, COALESCE(consume_stats.PersonCount, 0) as PersonCount, - COALESCE(consume_stats.ProjectCount, 0) as ProjectCount + CAST(COALESCE(consume_stats.ProjectCount, 0) AS DECIMAL(18,2)) as ProjectCount FROM BASE_USER u LEFT JOIN lq_mdxx md ON u.F_MDID = md.F_Id @@ -3135,7 +3135,7 @@ namespace NCC.Extend.LqKdKdjlb SUM(jksyj.jksyj) as ConsumeAmount, COUNT(DISTINCT hyhk.hy) as HeadCount, COUNT(DISTINCT CONCAT(jksyj.jkszh, '_', hyhk.hy, '_', DATE(hyhk.hksj))) as PersonCount, - SUM(jksyj.F_kdpxNumber) as ProjectCount + CAST(SUM(jksyj.F_kdpxNumber) AS DECIMAL(18,2)) as ProjectCount FROM lq_xh_jksyj jksyj INNER JOIN lq_xh_hyhk hyhk ON jksyj.glkdbh = hyhk.F_Id WHERE jksyj.jkszh IS NOT NULL @@ -3211,5 +3211,203 @@ namespace NCC.Extend.LqKdKdjlb } } #endregion + + #region 获取开单品项明细记录列表 + /// + /// 获取开单品项明细记录列表(分页) + /// + /// + /// 查询开单品项明细记录,支持多条件筛选和分页查询。采用先分页后关联的查询方式,确保分页准确性和查询性能。 + /// + /// 示例请求: + /// ```json + /// { + /// "currentPage": 1, + /// "pageSize": 10, + /// "sidx": "yjsj", + /// "sort": "DESC", + /// "Id": "品项明细ID", + /// "BillingId": "开单ID", + /// "StartBillingTime": "2025-01-01", + /// "EndBillingTime": "2025-12-31", + /// "ActivityId": "营销活动ID", + /// "MemberId": "会员ID", + /// "MemberName": "会员名称", + /// "MemberPhone": "会员手机号", + /// "ItemId": "品项ID", + /// "ItemName": "品项名称", + /// "ItemType": "品项类型", + /// "SourceType": "来源类型" + /// } + /// ``` + /// + /// 查询参数说明: + /// - currentPage: 当前页码(必填,从1开始) + /// - pageSize: 每页数量(必填) + /// - sidx: 排序字段(可选,默认:yjsj,支持:id、billingId、billingTime、itemName、actualPrice、projectNumber等) + /// - sort: 排序方式(可选,默认:DESC,支持:ASC、DESC) + /// - Id: 品项明细ID(可选,精确匹配) + /// - BillingId: 开单ID(可选,精确匹配) + /// - StartBillingTime: 开单时间开始(可选,格式:yyyy-MM-dd,与EndBillingTime同时使用) + /// - EndBillingTime: 开单时间结束(可选,格式:yyyy-MM-dd,与StartBillingTime同时使用) + /// - ActivityId: 营销活动ID(可选,精确匹配) + /// - MemberId: 会员ID(可选,精确匹配) + /// - MemberName: 会员名称(可选,模糊查询) + /// - MemberPhone: 会员手机号(可选,精确匹配) + /// - ItemId: 品项ID(可选,精确匹配) + /// - ItemName: 品项名称(可选,模糊查询) + /// - ItemType: 品项类型(可选,精确匹配,对应项目资料表的qt2字段) + /// - SourceType: 来源类型(可选,精确匹配,如:购买、赠送、体验) + /// + /// 返回数据结构: + /// ```json + /// { + /// "pagination": { + /// "pageIndex": 1, + /// "pageSize": 10, + /// "total": 100 + /// }, + /// "list": [ + /// { + /// "id": "品项明细ID", + /// "billingId": "开单ID", + /// "billingTime": "2025-11-15T10:00:00", + /// "activityName": "营销活动名称", + /// "memberName": "会员名称", + /// "memberPhone": "会员手机号", + /// "itemName": "品项名称", + /// "itemType": "品项类型", + /// "actualPrice": 500.00, + /// "projectNumber": 5.0, + /// "sourceType": "购买", + /// "remark": "备注" + /// } + /// ] + /// } + /// ``` + /// + /// 返回字段说明: + /// - id: 品项明细ID(主键) + /// - billingId: 开单ID(关联开单记录表) + /// - billingTime: 开单时间(业绩时间yjsj,DateTime格式) + /// - activityName: 营销活动名称(关联营销活动表) + /// - memberName: 会员名称(关联会员表) + /// - memberPhone: 会员手机号(关联会员表) + /// - itemName: 品项名称(品项明细表字段) + /// - itemType: 品项类型(关联项目资料表的qt2字段) + /// - actualPrice: 实付金额(decimal类型) + /// - projectNumber: 项目次数(decimal类型,支持小数) + /// - sourceType: 来源类型(字符串,如:购买、赠送、体验) + /// - remark: 备注(字符串) + /// + /// 查询参数 + /// 开单品项明细记录列表(分页) + /// 查询成功,返回开单品项明细记录列表 + /// 参数错误,如页码或页大小无效 + /// 服务器错误,查询过程中发生异常 + [HttpGet("billing-item-detail-list")] + public async Task GetBillingItemDetailList([FromQuery] BillingItemDetailListQueryInput input) + { + try + { + var sidx = input.sidx == null ? "yjsj" : input.sidx; + var sort = string.IsNullOrEmpty(input.sort) ? "DESC" : input.sort; + + // 处理开单时间范围 + List queryBillingTime = null; + DateTime? startBillingTime = null; + DateTime? endBillingTime = null; + if (!string.IsNullOrEmpty(input.StartBillingTime) && !string.IsNullOrEmpty(input.EndBillingTime)) + { + queryBillingTime = new List { input.StartBillingTime, input.EndBillingTime }; + startBillingTime = Ext.GetDateTime(queryBillingTime.First()); + endBillingTime = Ext.GetDateTime(queryBillingTime.Last()); + } + + // 优化查询:先分页查询主表,再批量查询关联数据,避免子查询性能问题 + // 1. 先构建基础查询条件 + var baseQuery = _db.Queryable() + .Where(pxmx => pxmx.IsEffective == StatusEnum.有效.GetHashCode()) + .WhereIF(!string.IsNullOrEmpty(input.Id), pxmx => pxmx.Id == input.Id) + .WhereIF(!string.IsNullOrEmpty(input.BillingId), pxmx => pxmx.Glkdbh == input.BillingId) + .WhereIF(queryBillingTime != null && startBillingTime.HasValue, pxmx => pxmx.Yjsj >= new DateTime(startBillingTime.Value.Year, startBillingTime.Value.Month, startBillingTime.Value.Day, 0, 0, 0)) + .WhereIF(queryBillingTime != null && endBillingTime.HasValue, pxmx => pxmx.Yjsj <= new DateTime(endBillingTime.Value.Year, endBillingTime.Value.Month, endBillingTime.Value.Day, 23, 59, 59)) + .WhereIF(!string.IsNullOrEmpty(input.ActivityId), pxmx => pxmx.ActivityId == input.ActivityId) + .WhereIF(!string.IsNullOrEmpty(input.MemberId), pxmx => pxmx.MemberId == input.MemberId) + .WhereIF(!string.IsNullOrEmpty(input.ItemId), pxmx => pxmx.Px == input.ItemId) + .WhereIF(!string.IsNullOrEmpty(input.ItemName), pxmx => pxmx.Pxmc != null && pxmx.Pxmc.Contains(input.ItemName)) + .WhereIF(!string.IsNullOrEmpty(input.SourceType), pxmx => pxmx.SourceType == input.SourceType); + + // 2. 通过 EXISTS 子查询筛选关联字段(在分页前筛选,确保分页准确) + baseQuery = baseQuery.WhereIF(!string.IsNullOrEmpty(input.MemberName), pxmx => SqlFunc.Subqueryable().Where(x => x.Id == pxmx.MemberId && x.Khmc != null && x.Khmc.Contains(input.MemberName)).Any()) + .WhereIF(!string.IsNullOrEmpty(input.MemberPhone), pxmx => SqlFunc.Subqueryable().Where(x => x.Id == pxmx.MemberId && x.Sjh == input.MemberPhone).Any()) + .WhereIF(!string.IsNullOrEmpty(input.ItemType), pxmx => SqlFunc.Subqueryable().Where(x => x.Id == pxmx.Px && x.Fl4 == input.ItemType).Any()); + + // 3. 先分页查询主表数据(查询实体类,提高性能) + var pagedData = await baseQuery.OrderBy(sidx + " " + sort).ToPagedListAsync(input.currentPage, input.pageSize); + + // 4. 批量查询关联数据 + var itemIds = pagedData.list.Select(x => x.Id).ToList(); + var memberIds = pagedData.list.Where(x => !string.IsNullOrEmpty(x.MemberId)).Select(x => x.MemberId).Distinct().ToList(); + var activityIds = pagedData.list.Where(x => !string.IsNullOrEmpty(x.ActivityId)).Select(x => x.ActivityId).Distinct().ToList(); + var projectIds = pagedData.list.Where(x => !string.IsNullOrEmpty(x.Px)).Select(x => x.Px).Distinct().ToList(); + + // 批量查询会员信息 + var memberDict = new Dictionary(); + if (memberIds.Any()) + { + var members = await _db.Queryable().Where(x => memberIds.Contains(x.Id)).Select(x => new { x.Id, x.Khmc, x.Sjh }).ToListAsync(); + memberDict = members.ToDictionary(x => x.Id, x => (x.Khmc ?? "", x.Sjh ?? "")); + } + + // 批量查询活动信息 + var activityDict = new Dictionary(); + if (activityIds.Any()) + { + var activities = await _db.Queryable().Where(x => activityIds.Contains(x.Id)).Select(x => new { x.Id, x.ActivityName }).ToListAsync(); + activityDict = activities.ToDictionary(x => x.Id, x => x.ActivityName ?? ""); + } + + // 批量查询项目资料 + var projectDict = new Dictionary(); + if (projectIds.Any()) + { + var projects = await _db.Queryable().Where(x => projectIds.Contains(x.Id)).Select(x => new { x.Id, x.Qt2 }).ToListAsync(); + projectDict = projects.ToDictionary(x => x.Id, x => x.Qt2 ?? ""); + } + + // 5. 组装返回数据 + var resultList = pagedData.list.Select(pxmx => new BillingItemDetailListOutput + { + id = pxmx.Id, + billingId = pxmx.Glkdbh, + billingTime = pxmx.Yjsj, + activityName = pxmx.ActivityId != null && activityDict.ContainsKey(pxmx.ActivityId) ? activityDict[pxmx.ActivityId] : "", + memberName = pxmx.MemberId != null && memberDict.ContainsKey(pxmx.MemberId) ? memberDict[pxmx.MemberId].Name : "", + memberPhone = pxmx.MemberId != null && memberDict.ContainsKey(pxmx.MemberId) ? memberDict[pxmx.MemberId].Phone : "", + itemName = pxmx.Pxmc, + itemType = pxmx.Px != null && projectDict.ContainsKey(pxmx.Px) ? projectDict[pxmx.Px] : "", + actualPrice = pxmx.ActualPrice, + projectNumber = pxmx.ProjectNumber, + sourceType = pxmx.SourceType, + remark = pxmx.Remark + }).ToList(); + + // 6. 返回分页结果 + var result = new SqlSugarPagedList + { + list = resultList, + pagination = pagedData.pagination + }; + + return PageResult.SqlSugarPageResult(result); + } + catch (Exception ex) + { + _logger.LogError(ex, $"获取开单品项明细记录列表失败: {ex.ToString()}"); + throw NCCException.Oh(ErrorCode.COM1005, $"获取开单品项明细记录列表失败: {ex.Message}"); + } + } + #endregion } } diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqKhxxService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqKhxxService.cs index e7b3951..8f431f7 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend/LqKhxxService.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend/LqKhxxService.cs @@ -776,5 +776,676 @@ namespace NCC.Extend.LqKhxx } #endregion + #region 获取会员品项信息 + /// + /// 获取所有会员的品项信息(分页) + /// + /// + /// 查询所有会员的品项信息,包括购买品项、赠送品项、体验品项、已消耗品项、已退卡品项、已储扣品项 + /// + /// 示例请求: + /// ```json + /// { + /// "currentPage": 1, + /// "pageSize": 10, + /// "MemberId": "会员ID", + /// "BelongStoreId": "所属门店ID", + /// "BillingStoreId": "开单门店ID", + /// "BillingUserId": "开单人ID", + /// "PurchaseItemId": "购买品项ID", + /// "GiftItemId": "赠送品项ID", + /// "ExperienceItemId": "体验品项ID" + /// } + /// ``` + /// + /// 参数说明: + /// - currentPage: 当前页码(必填) + /// - pageSize: 每页数量(必填) + /// - MemberId: 会员ID(可选) + /// - BelongStoreId: 所属门店ID(可选) + /// - BillingStoreId: 开单门店ID(可选) + /// - BillingUserId: 开单人ID(可选) + /// - PurchaseItemId: 购买品项ID(可选) + /// - GiftItemId: 赠送品项ID(可选) + /// - ExperienceItemId: 体验品项ID(可选) + /// + /// 返回数据结构说明: + /// ```json + /// { + /// "pagination": { + /// "pageIndex": 1, + /// "pageSize": 10, + /// "total": 100 + /// }, + /// "list": [ + /// { + /// "memberId": "会员ID", + /// "memberCode": "会员编号(档案号)", + /// "memberName": "会员名称", + /// "memberPhone": "会员电话", + /// "belongStoreId": "所属门店ID", + /// "belongStoreName": "所属门店名称", + /// "billingItems": [ + /// { + /// "billingId": "开单ID", + /// "billingTime": "2025-11-15T10:00:00", + /// "billingStoreId": "开单门店ID", + /// "billingStoreName": "开单门店名称", + /// "billingUserId": "开单人ID", + /// "billingUserName": "开单人名称", + /// "purchaseItems": [ + /// { + /// "itemId": "品项ID", + /// "itemName": "品项名称", + /// "itemPrice": 100.00, + /// "projectNumber": 5.0, + /// "totalAmount": 500.00 + /// } + /// ], + /// "purchaseItemsTotalAmount": 500.00, + /// "giftItems": [ + /// { + /// "itemId": "品项ID", + /// "itemName": "品项名称", + /// "itemPrice": 50.00, + /// "projectNumber": 2.0, + /// "totalAmount": 100.00 + /// } + /// ], + /// "experienceItems": [ + /// { + /// "itemId": "品项ID", + /// "itemName": "品项名称", + /// "itemPrice": 30.00, + /// "projectNumber": 1.0, + /// "totalAmount": 30.00 + /// } + /// ], + /// "consumedItems": [ + /// { + /// "itemId": "品项ID", + /// "itemName": "品项名称", + /// "itemPrice": 100.00, + /// "projectNumber": 2.0, + /// "totalAmount": 200.00 + /// } + /// ], + /// "consumedItemsTotalAmount": 200.00, + /// "refundedItems": [ + /// { + /// "itemId": "品项ID", + /// "itemName": "品项名称", + /// "itemPrice": 100.00, + /// "projectNumber": 1.0, + /// "totalAmount": 100.00 + /// } + /// ], + /// "refundedItemsTotalAmount": 100.00, + /// "deductedItems": [ + /// { + /// "itemId": "品项ID", + /// "itemName": "品项名称", + /// "itemPrice": 100.00, + /// "projectNumber": 1.0, + /// "totalAmount": 100.00 + /// } + /// ], + /// "deductedItemsTotalAmount": 100.00 + /// } + /// ] + /// } + /// ] + /// } + /// ``` + /// + /// 返回字段说明: + /// + /// **分页信息 (pagination)**: + /// - pageIndex: 当前页码 + /// - pageSize: 每页数量 + /// - total: 总记录数 + /// + /// **会员信息 (list[].memberInfo)**: + /// - memberId: 会员ID + /// - memberCode: 会员编号(档案号) + /// - memberName: 会员名称 + /// - memberPhone: 会员电话 + /// - belongStoreId: 所属门店ID + /// - belongStoreName: 所属门店名称 + /// + /// **开单信息 (list[].billingItems[])**: + /// - billingId: 开单ID + /// - billingTime: 开单时间(DateTime格式) + /// - billingStoreId: 开单门店ID + /// - billingStoreName: 开单门店名称 + /// - billingUserId: 开单人ID + /// - billingUserName: 开单人名称 + /// + /// **品项信息 (list[].billingItems[].*Items[])**: + /// - purchaseItems: 购买品项列表 + /// - purchaseItemsTotalAmount: 购买品项总金额 + /// - giftItems: 赠送品项列表 + /// - experienceItems: 体验品项列表 + /// - consumedItems: 已消耗品项列表 + /// - consumedItemsTotalAmount: 已消耗品项总金额 + /// - refundedItems: 已退卡品项列表 + /// - refundedItemsTotalAmount: 已退卡品项总金额 + /// - deductedItems: 已储扣品项列表 + /// - deductedItemsTotalAmount: 已储扣品项总金额 + /// + /// **品项明细 (ItemDetail)**: + /// - itemId: 品项ID + /// - itemName: 品项名称 + /// - itemPrice: 品项单价 + /// - projectNumber: 项目次数(支持小数) + /// - totalAmount: 总金额(单价 × 次数) + /// + /// 查询参数 + /// 会员品项信息列表(分页) + /// 查询成功,返回会员品项信息列表 + /// 参数错误,如页码或页大小无效 + /// 服务器错误,查询过程中发生异常 + [HttpPost("get-member-item-info")] + public async Task GetMemberItemInfo([FromBody] MemberItemInfoQueryInput input) + { + try + { + // 1. 构建会员查询条件 + var memberQuery = _db.Queryable(); + + // 筛选条件 + if (!string.IsNullOrEmpty(input.MemberId)) + { + memberQuery = memberQuery.Where(x => x.Id == input.MemberId); + } + + if (!string.IsNullOrEmpty(input.BelongStoreId)) + { + memberQuery = memberQuery.Where(x => x.Gsmd == input.BelongStoreId); + } + + // 如果指定了开单门店、开单人、品项ID等条件,需要通过开单表进行筛选 + if (!string.IsNullOrEmpty(input.BillingStoreId) || + !string.IsNullOrEmpty(input.BillingUserId) || + !string.IsNullOrEmpty(input.PurchaseItemId) || + !string.IsNullOrEmpty(input.GiftItemId) || + !string.IsNullOrEmpty(input.ExperienceItemId)) + { + // 先查询符合条件的开单记录,获取会员ID列表 + var billingQuery = _db.Queryable() + .Where(x => x.IsEffective == StatusEnum.有效.GetHashCode()); + + if (!string.IsNullOrEmpty(input.BillingStoreId)) + { + billingQuery = billingQuery.Where(x => x.Djmd == input.BillingStoreId); + } + + if (!string.IsNullOrEmpty(input.BillingUserId)) + { + billingQuery = billingQuery.Where(x => x.CreateUser == input.BillingUserId); + } + + // 如果指定了品项ID,需要通过品项明细表进行筛选 + if (!string.IsNullOrEmpty(input.PurchaseItemId) || + !string.IsNullOrEmpty(input.GiftItemId) || + !string.IsNullOrEmpty(input.ExperienceItemId)) + { + var itemQuery = _db.Queryable() + .Where(x => x.IsEffective == StatusEnum.有效.GetHashCode()); + + if (!string.IsNullOrEmpty(input.PurchaseItemId)) + { + itemQuery = itemQuery.Where(x => x.Px == input.PurchaseItemId && x.SourceType == "购买"); + } + + if (!string.IsNullOrEmpty(input.GiftItemId)) + { + itemQuery = itemQuery.Where(x => x.Px == input.GiftItemId && x.SourceType == "赠送"); + } + + if (!string.IsNullOrEmpty(input.ExperienceItemId)) + { + itemQuery = itemQuery.Where(x => x.Px == input.ExperienceItemId && x.SourceType == "体验"); + } + + var itemBillingIds = await itemQuery.Select(x => x.Glkdbh).Distinct().ToListAsync(); + if (itemBillingIds != null && itemBillingIds.Any()) + { + billingQuery = billingQuery.Where(x => itemBillingIds.Contains(x.Id)); + } + else + { + // 如果没有匹配的品项记录,返回空结果 + return PageResult.SqlSugarPageResult(new SqlSugarPagedList + { + list = new List(), + pagination = new PagedModel + { + PageIndex = input.currentPage, + PageSize = input.pageSize, + Total = 0 + } + }); + } + } + + var memberIds = await billingQuery.Select(x => x.Kdhy).Distinct().ToListAsync(); + if (memberIds != null && memberIds.Any()) + { + memberQuery = memberQuery.Where(x => memberIds.Contains(x.Id)); + } + else + { + // 如果没有匹配的开单记录,返回空结果 + return PageResult.SqlSugarPageResult(new SqlSugarPagedList + { + list = new List(), + pagination = new PagedModel + { + PageIndex = input.currentPage, + PageSize = input.pageSize, + Total = 0 + } + }); + } + } + + // 2. 分页查询会员列表 + var totalCount = await memberQuery.CountAsync(); + var skipCount = (input.currentPage - 1) * input.pageSize; + var memberList = await memberQuery + .OrderBy(x => x.Id) + .Skip(skipCount) + .Take(input.pageSize) + .Select(x => new + { + x.Id, + x.Dah, + x.Khmc, + x.Sjh, + x.Gsmd + }) + .ToListAsync(); + + var memberData = new SqlSugarPagedList + { + list = memberList.Cast().ToList(), + pagination = new PagedModel + { + PageIndex = input.currentPage, + PageSize = input.pageSize, + Total = totalCount + } + }; + + if (memberList == null || !memberList.Any()) + { + return PageResult.SqlSugarPageResult(new SqlSugarPagedList + { + list = new List(), + pagination = new PagedModel + { + PageIndex = input.currentPage, + PageSize = input.pageSize, + Total = totalCount + } + }); + } + + var memberIdsList = memberList.Select(x => x.Id).ToList(); + + // 3. 批量查询会员的开单记录 + var billingRecords = await _db.Queryable() + .Where(x => memberIdsList.Contains(x.Kdhy) && x.IsEffective == StatusEnum.有效.GetHashCode()) + .Select(x => new + { + x.Id, + x.Kdhy, + x.Kdrq, + x.Djmd, + x.CreateUser + }) + .ToListAsync(); + + var billingIds = billingRecords.Select(x => x.Id).ToList(); + + // 4. 批量查询开单的品项明细 + var itemDetails = new List(); + if (billingIds.Any()) + { + var itemDetailsData = await _db.Queryable() + .Where(x => billingIds.Contains(x.Glkdbh) && x.IsEffective == StatusEnum.有效.GetHashCode()) + .Select(x => new + { + x.Id, + x.Glkdbh, + x.Px, + x.Pxmc, + x.Pxjg, + x.SourceType, + x.ProjectNumber, + x.TotalPrice + }) + .ToListAsync(); + itemDetails = itemDetailsData.Cast().ToList(); + } + + var itemDetailIds = itemDetails.Select(x => (string)x.Id).ToList(); + + // 5. 批量查询消耗品项 + var consumedItems = new List(); + if (itemDetailIds.Any()) + { + var consumedItemsData = await _db.Queryable() + .Where(x => itemDetailIds.Contains(x.BillingItemId) && x.IsEffective == StatusEnum.有效.GetHashCode()) + .Select(x => new + { + x.BillingItemId, + x.Px, + x.Pxmc, + x.Pxjg, + x.ProjectNumber, + x.TotalPrice + }) + .ToListAsync(); + consumedItems = consumedItemsData.Cast().ToList(); + } + + // 6. 批量查询退卡品项 + var refundedItems = new List(); + if (itemDetailIds.Any()) + { + var refundedItemsData = await _db.Queryable() + .Where(x => itemDetailIds.Contains(x.BillingItemId) && x.IsEffective == StatusEnum.有效.GetHashCode()) + .Select(x => new + { + x.BillingItemId, + x.Px, + x.Pxmc, + x.Pxjg, + x.ProjectNumber, + x.Tkje + }) + .ToListAsync(); + refundedItems = refundedItemsData.Cast().ToList(); + } + + // 7. 批量查询储扣品项 + var deductedItems = new List(); + if (billingIds.Any()) + { + var deductedItemsData = await _db.Queryable() + .Where(x => billingIds.Contains(x.BillingId) && x.IsEffective == StatusEnum.有效.GetHashCode()) + .Select(x => new + { + x.BillingId, + x.ItemId, + x.ItemName, + x.UnitPrice, + x.ProjectNumber, + x.Amount + }) + .ToListAsync(); + deductedItems = deductedItemsData.Cast().ToList(); + } + + // 8. 批量查询门店信息 + var storeIds = new List(); + if (memberList != null && memberList.Any()) + { + storeIds.AddRange(memberList.Select(x => x.Gsmd).Where(x => !string.IsNullOrEmpty(x))); + } + if (billingRecords != null && billingRecords.Any()) + { + storeIds.AddRange(billingRecords.Select(x => x.Djmd).Where(x => !string.IsNullOrEmpty(x))); + } + storeIds = storeIds.Distinct().ToList(); + + var stores = new Dictionary(); + if (storeIds.Any()) + { + var storeList = await _db.Queryable() + .Where(x => storeIds.Contains(x.Id)) + .Select(x => new { x.Id, x.Dm }) + .ToListAsync(); + + stores = storeList.ToDictionary(x => x.Id, x => x.Dm ?? ""); + } + + // 9. 批量查询用户信息 + var userIds = new List(); + if (billingRecords != null && billingRecords.Any()) + { + userIds = billingRecords.Select(x => x.CreateUser) + .Where(x => !string.IsNullOrEmpty(x)) + .Distinct() + .ToList(); + } + + var users = new Dictionary(); + if (userIds.Any()) + { + var userList = await _db.Queryable() + .Where(x => userIds.Contains(x.Id)) + .Select(x => new { x.Id, x.RealName }) + .ToListAsync(); + + users = userList.ToDictionary(x => x.Id, x => x.RealName ?? ""); + } + + // 10. 组装数据 + var result = new List(); + + foreach (var member in memberList) + { + var memberOutput = new MemberItemInfoOutput + { + MemberId = member.Id, + MemberCode = member.Dah ?? "", + MemberName = member.Khmc ?? "", + MemberPhone = member.Sjh ?? "", + BelongStoreId = member.Gsmd ?? "", + BelongStoreName = stores.ContainsKey(member.Gsmd ?? "") ? stores[member.Gsmd] : "" + }; + + // 获取该会员的所有开单 + var memberBillings = billingRecords.Where(x => x.Kdhy == member.Id).ToList(); + + foreach (var billing in memberBillings) + { + var billingItem = new BillingItemInfo + { + BillingId = billing.Id, + BillingTime = billing.Kdrq, + BillingStoreId = billing.Djmd ?? "", + BillingStoreName = stores.ContainsKey(billing.Djmd ?? "") ? stores[billing.Djmd] : "", + BillingUserId = billing.CreateUser ?? "", + BillingUserName = users.ContainsKey(billing.CreateUser ?? "") ? users[billing.CreateUser] : "" + }; + + // 获取该开单的所有品项明细 + var billingItemDetails = itemDetails.Where(x => + { + try + { + return x.Glkdbh?.ToString() == billing.Id; + } + catch + { + return false; + } + }).ToList(); + + // 分类品项 + foreach (var item in billingItemDetails) + { + try + { + var itemDetail = new ItemDetail + { + ItemId = item.Px?.ToString() ?? "", + ItemName = item.Pxmc?.ToString() ?? "", + ItemPrice = Convert.ToDecimal(item.Pxjg ?? 0), + ProjectNumber = Convert.ToDecimal(item.ProjectNumber ?? 0), + TotalAmount = Convert.ToDecimal(item.TotalPrice ?? 0) + }; + + var sourceType = item.SourceType?.ToString() ?? ""; + if (sourceType == "购买") + { + billingItem.PurchaseItems.Add(itemDetail); + billingItem.PurchaseItemsTotalAmount += itemDetail.TotalAmount; + } + else if (sourceType == "赠送") + { + billingItem.GiftItems.Add(itemDetail); + } + else if (sourceType == "体验") + { + billingItem.ExperienceItems.Add(itemDetail); + } + } + catch (Exception ex) + { + _logger.LogWarning(ex, "处理品项明细时出错,开单ID:{BillingId}", billing.Id); + } + } + + // 获取消耗品项 + var billingItemIds = billingItemDetails.Select(x => + { + try + { + return x.Id?.ToString() ?? ""; + } + catch + { + return ""; + } + }).Where(x => !string.IsNullOrEmpty(x)).ToList(); + + var consumed = consumedItems.Where(x => + { + try + { + return billingItemIds.Contains(x.BillingItemId?.ToString() ?? ""); + } + catch + { + return false; + } + }).ToList(); + + foreach (var consumedItem in consumed) + { + try + { + billingItem.ConsumedItems.Add(new ItemDetail + { + ItemId = consumedItem.Px?.ToString() ?? "", + ItemName = consumedItem.Pxmc?.ToString() ?? "", + ItemPrice = Convert.ToDecimal(consumedItem.Pxjg ?? 0), + ProjectNumber = Convert.ToDecimal(consumedItem.ProjectNumber ?? 0), + TotalAmount = Convert.ToDecimal(consumedItem.TotalPrice ?? 0) + }); + billingItem.ConsumedItemsTotalAmount += Convert.ToDecimal(consumedItem.TotalPrice ?? 0); + } + catch (Exception ex) + { + _logger.LogWarning(ex, "处理消耗品项时出错"); + } + } + + // 获取退卡品项 + var refunded = refundedItems.Where(x => + { + try + { + return billingItemIds.Contains(x.BillingItemId?.ToString() ?? ""); + } + catch + { + return false; + } + }).ToList(); + + foreach (var refundedItem in refunded) + { + try + { + billingItem.RefundedItems.Add(new ItemDetail + { + ItemId = refundedItem.Px?.ToString() ?? "", + ItemName = refundedItem.Pxmc?.ToString() ?? "", + ItemPrice = Convert.ToDecimal(refundedItem.Pxjg ?? 0), + ProjectNumber = Convert.ToDecimal(refundedItem.ProjectNumber ?? 0), + TotalAmount = Convert.ToDecimal(refundedItem.Tkje ?? 0) + }); + billingItem.RefundedItemsTotalAmount += Convert.ToDecimal(refundedItem.Tkje ?? 0); + } + catch (Exception ex) + { + _logger.LogWarning(ex, "处理退卡品项时出错"); + } + } + + // 获取储扣品项 + var deducted = deductedItems.Where(x => + { + try + { + return x.BillingId?.ToString() == billing.Id; + } + catch + { + return false; + } + }).ToList(); + + foreach (var deductedItem in deducted) + { + try + { + billingItem.DeductedItems.Add(new ItemDetail + { + ItemId = deductedItem.ItemId?.ToString() ?? "", + ItemName = deductedItem.ItemName?.ToString() ?? "", + ItemPrice = Convert.ToDecimal(deductedItem.UnitPrice ?? 0), + ProjectNumber = Convert.ToDecimal(deductedItem.ProjectNumber ?? 0), + TotalAmount = Convert.ToDecimal(deductedItem.Amount ?? 0) + }); + billingItem.DeductedItemsTotalAmount += Convert.ToDecimal(deductedItem.Amount ?? 0); + } + catch (Exception ex) + { + _logger.LogWarning(ex, "处理储扣品项时出错"); + } + } + + memberOutput.BillingItems.Add(billingItem); + } + + result.Add(memberOutput); + } + + return PageResult.SqlSugarPageResult(new SqlSugarPagedList + { + list = result, + pagination = new PagedModel + { + PageIndex = input.currentPage, + PageSize = input.pageSize, + Total = totalCount + } + }); + } + catch (Exception ex) + { + _logger.LogError(ex, "查询会员品项信息失败,异常详情:{ExceptionDetail}", ex.ToString()); + throw NCCException.Oh(ErrorCode.COM1005, $"查询会员品项信息失败: {ex.Message}"); + } + } + #endregion + } } diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqXhHyhkService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqXhHyhkService.cs index 25a252b..3870d34 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend/LqXhHyhkService.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend/LqXhHyhkService.cs @@ -971,6 +971,8 @@ namespace NCC.Extend.LqXhHyhk { memberInfo.Khlx = MemberTypeEnum.新客.GetHashCode().ToString(); } + //保存会员信息 + await _db.Updateable(memberInfo).ExecuteCommandAsync(); // 新增会员耗卡记录 var newEntity = await _db.Insertable(entity).IgnoreColumns(ignoreNullColumn: true).ExecuteReturnEntityAsync(); // 收集所有需要插入的实体,然后批量插入 @@ -1265,6 +1267,15 @@ namespace NCC.Extend.LqXhHyhk //更新会员耗卡记录 await _db.Updateable(entity).IgnoreColumns(ignoreAllNullColumns: true).ExecuteCommandAsync(); + var memberInfo = await _db.Queryable().Where(w => w.Id == entity.Hy).FirstAsync(); + //如果会员类型是线索,那么就更新为新客 + if (memberInfo.Khlx == MemberTypeEnum.线索.GetHashCode().ToString()) + { + memberInfo.Khlx = MemberTypeEnum.新客.GetHashCode().ToString(); + } + //保存会员信息 + await _db.Updateable(memberInfo).ExecuteCommandAsync(); + //清空原有数据 await _db.Deleteable().Where(u => u.Glkdbh == id).ExecuteCommandAsync(); await _db.Deleteable().Where(u => u.Glkdbh == id).ExecuteCommandAsync(); diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqXmzlService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqXmzlService.cs index fe7cb99..10427a9 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend/LqXmzlService.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend/LqXmzlService.cs @@ -817,6 +817,7 @@ namespace NCC.Extend.LqXmzl } #endregion + } /// /// 品项统计数据(内部类) diff --git a/sql/更新潜客为新客.sql b/sql/更新潜客为新客.sql new file mode 100644 index 0000000..372112d --- /dev/null +++ b/sql/更新潜客为新客.sql @@ -0,0 +1,54 @@ +-- ============================================ +-- 更新会员类型:将存在耗卡记录的潜客(0)更新为新客(1) +-- ============================================ +-- +-- 功能说明: +-- 1. 查找会员类型为 0(潜客/线索)的会员 +-- 2. 如果该会员存在有效的耗卡记录(lq_xh_hyhk 表中有记录且 F_IsEffective = 1) +-- 3. 将该会员的类型更新为 1(新客) +-- +-- 执行前建议: +-- 1. 先执行查询语句查看会更新多少条记录 +-- 2. 确认无误后再执行更新语句 +-- ============================================ + +-- 1. 查询语句:查看将要更新的会员数量和信息 +SELECT + kh.F_Id AS 会员ID, + kh.khmc AS 会员名称, + kh.sjh AS 手机号, + kh.khlx AS 当前类型, + COUNT(hyhk.F_Id) AS 耗卡记录数 +FROM lq_khxx kh +INNER JOIN lq_xh_hyhk hyhk ON kh.F_Id = hyhk.hy +WHERE kh.khlx = '0' -- 会员类型为 0(潜客/线索) + AND hyhk.F_IsEffective = 1 -- 耗卡记录有效 +GROUP BY kh.F_Id, kh.khmc, kh.sjh, kh.khlx; + +-- 2. 更新语句:将存在耗卡记录的潜客更新为新客 +UPDATE lq_khxx kh +SET kh.khlx = '1' -- 更新为新客(1) +WHERE kh.khlx = '0' -- 会员类型为 0(潜客/线索) + AND EXISTS ( + -- 确保至少有一条有效的耗卡记录 + SELECT 1 + FROM lq_xh_hyhk hyhk + WHERE hyhk.hy = kh.F_Id + AND hyhk.F_IsEffective = 1 + LIMIT 1 + ); + +-- 3. 验证语句:查看更新后的结果 +SELECT + kh.F_Id AS 会员ID, + kh.khmc AS 会员名称, + kh.sjh AS 手机号, + kh.khlx AS 更新后类型, + COUNT(hyhk.F_Id) AS 耗卡记录数 +FROM lq_khxx kh +INNER JOIN lq_xh_hyhk hyhk ON kh.F_Id = hyhk.hy +WHERE kh.khlx = '1' -- 会员类型为 1(新客) + AND hyhk.F_IsEffective = 1 -- 耗卡记录有效 +GROUP BY kh.F_Id, kh.khmc, kh.sjh, kh.khlx +HAVING COUNT(hyhk.F_Id) > 0; +