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;
+