Commit baff42576ab7393c73a8cd1be3d0dacb8c9df126

Authored by 李宇
2 parents 50f29afc 0fae5e6f

Merge branch 'master' of http://39.98.150.180/antissoft/lvqianmeiye_ERP

netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqDailyReport/TianwangGroupPerformanceCompletionOutput.cs
... ... @@ -52,6 +52,11 @@ namespace NCC.Extend.Entitys.Dto.LqDailyReport
52 52 /// 门店数量
53 53 /// </summary>
54 54 public int StoreCount { get; set; }
  55 +
  56 + /// <summary>
  57 + /// 储扣金额(储值扣减金额总和)
  58 + /// </summary>
  59 + public decimal DeductAmount { get; set; }
55 60 }
56 61 }
57 62  
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKdKdjlb/BillingItemDetailListOutput.cs 0 → 100644
  1 +using System;
  2 +
  3 +namespace NCC.Extend.Entitys.Dto.LqKdKdjlb
  4 +{
  5 + /// <summary>
  6 + /// 开单品项明细记录输出
  7 + /// </summary>
  8 + public class BillingItemDetailListOutput
  9 + {
  10 + /// <summary>
  11 + /// 品项明细ID
  12 + /// </summary>
  13 + public string id { get; set; }
  14 +
  15 + /// <summary>
  16 + /// 开单ID
  17 + /// </summary>
  18 + public string billingId { get; set; }
  19 +
  20 + /// <summary>
  21 + /// 开单时间
  22 + /// </summary>
  23 + public DateTime? billingTime { get; set; }
  24 +
  25 + /// <summary>
  26 + /// 营销活动名称
  27 + /// </summary>
  28 + public string activityName { get; set; }
  29 +
  30 + /// <summary>
  31 + /// 会员名称
  32 + /// </summary>
  33 + public string memberName { get; set; }
  34 +
  35 + /// <summary>
  36 + /// 会员手机
  37 + /// </summary>
  38 + public string memberPhone { get; set; }
  39 +
  40 + /// <summary>
  41 + /// 品项名称
  42 + /// </summary>
  43 + public string itemName { get; set; }
  44 +
  45 + /// <summary>
  46 + /// 品项类型(分类④-统计品项用)
  47 + /// </summary>
  48 + public string itemType { get; set; }
  49 +
  50 + /// <summary>
  51 + /// 实付金额
  52 + /// </summary>
  53 + public decimal actualPrice { get; set; }
  54 +
  55 + /// <summary>
  56 + /// 项目次数
  57 + /// </summary>
  58 + public decimal projectNumber { get; set; }
  59 +
  60 + /// <summary>
  61 + /// 来源类型(购买/赠送/体验)
  62 + /// </summary>
  63 + public string sourceType { get; set; }
  64 +
  65 + /// <summary>
  66 + /// 备注
  67 + /// </summary>
  68 + public string remark { get; set; }
  69 + }
  70 +}
  71 +
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKdKdjlb/BillingItemDetailListQueryInput.cs 0 → 100644
  1 +using NCC.Common.Filter;
  2 +
  3 +namespace NCC.Extend.Entitys.Dto.LqKdKdjlb
  4 +{
  5 + /// <summary>
  6 + /// 开单品项明细记录查询输入参数
  7 + /// </summary>
  8 + public class BillingItemDetailListQueryInput : PageInputBase
  9 + {
  10 + /// <summary>
  11 + /// 品项明细ID
  12 + /// </summary>
  13 + public string Id { get; set; }
  14 +
  15 + /// <summary>
  16 + /// 开单ID
  17 + /// </summary>
  18 + public string BillingId { get; set; }
  19 +
  20 + /// <summary>
  21 + /// 开单时间(开始)
  22 + /// </summary>
  23 + public string StartBillingTime { get; set; }
  24 +
  25 + /// <summary>
  26 + /// 开单时间(结束)
  27 + /// </summary>
  28 + public string EndBillingTime { get; set; }
  29 +
  30 + /// <summary>
  31 + /// 营销活动ID
  32 + /// </summary>
  33 + public string ActivityId { get; set; }
  34 +
  35 + /// <summary>
  36 + /// 会员ID
  37 + /// </summary>
  38 + public string MemberId { get; set; }
  39 +
  40 + /// <summary>
  41 + /// 会员名称(模糊查询)
  42 + /// </summary>
  43 + public string MemberName { get; set; }
  44 +
  45 + /// <summary>
  46 + /// 会员手机号
  47 + /// </summary>
  48 + public string MemberPhone { get; set; }
  49 +
  50 + /// <summary>
  51 + /// 品项ID
  52 + /// </summary>
  53 + public string ItemId { get; set; }
  54 +
  55 + /// <summary>
  56 + /// 品项名称(模糊查询)
  57 + /// </summary>
  58 + public string ItemName { get; set; }
  59 +
  60 + /// <summary>
  61 + /// 品项类型(分类④-统计品项用)
  62 + /// </summary>
  63 + public string ItemType { get; set; }
  64 +
  65 + /// <summary>
  66 + /// 来源类型(购买/赠送/体验)
  67 + /// </summary>
  68 + public string SourceType { get; set; }
  69 + }
  70 +}
  71 +
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKdKdjlb/HealthCoachStatisticsOutput.cs
... ... @@ -78,6 +78,6 @@ namespace NCC.Extend.Entitys.Dto.LqKdKdjlb
78 78 /// <summary>
79 79 /// 消耗项目数 - 统计该健康师在指定时间周期内消耗的项目总次数
80 80 /// </summary>
81   - public int projectCount { get; set; }
  81 + public decimal projectCount { get; set; }
82 82 }
83 83 }
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKhxx/MemberItemInfoOutput.cs 0 → 100644
  1 +using System;
  2 +using System.Collections.Generic;
  3 +
  4 +namespace NCC.Extend.Entitys.Dto.LqKhxx
  5 +{
  6 + /// <summary>
  7 + /// 会员品项信息输出
  8 + /// </summary>
  9 + public class MemberItemInfoOutput
  10 + {
  11 + /// <summary>
  12 + /// 会员ID
  13 + /// </summary>
  14 + public string MemberId { get; set; }
  15 +
  16 + /// <summary>
  17 + /// 会员编号(档案号)
  18 + /// </summary>
  19 + public string MemberCode { get; set; }
  20 +
  21 + /// <summary>
  22 + /// 会员名称
  23 + /// </summary>
  24 + public string MemberName { get; set; }
  25 +
  26 + /// <summary>
  27 + /// 会员电话
  28 + /// </summary>
  29 + public string MemberPhone { get; set; }
  30 +
  31 + /// <summary>
  32 + /// 所属门店ID
  33 + /// </summary>
  34 + public string BelongStoreId { get; set; }
  35 +
  36 + /// <summary>
  37 + /// 所属门店名称
  38 + /// </summary>
  39 + public string BelongStoreName { get; set; }
  40 +
  41 + /// <summary>
  42 + /// 开单信息列表
  43 + /// </summary>
  44 + public List<BillingItemInfo> BillingItems { get; set; } = new List<BillingItemInfo>();
  45 + }
  46 +
  47 + /// <summary>
  48 + /// 开单信息
  49 + /// </summary>
  50 + public class BillingItemInfo
  51 + {
  52 + /// <summary>
  53 + /// 开单ID
  54 + /// </summary>
  55 + public string BillingId { get; set; }
  56 +
  57 + /// <summary>
  58 + /// 开单时间
  59 + /// </summary>
  60 + public DateTime? BillingTime { get; set; }
  61 +
  62 + /// <summary>
  63 + /// 开单门店ID
  64 + /// </summary>
  65 + public string BillingStoreId { get; set; }
  66 +
  67 + /// <summary>
  68 + /// 开单门店名称
  69 + /// </summary>
  70 + public string BillingStoreName { get; set; }
  71 +
  72 + /// <summary>
  73 + /// 开单人ID
  74 + /// </summary>
  75 + public string BillingUserId { get; set; }
  76 +
  77 + /// <summary>
  78 + /// 开单人名称
  79 + /// </summary>
  80 + public string BillingUserName { get; set; }
  81 +
  82 + /// <summary>
  83 + /// 购买品项列表
  84 + /// </summary>
  85 + public List<ItemDetail> PurchaseItems { get; set; } = new List<ItemDetail>();
  86 +
  87 + /// <summary>
  88 + /// 购买品项总金额
  89 + /// </summary>
  90 + public decimal PurchaseItemsTotalAmount { get; set; }
  91 +
  92 + /// <summary>
  93 + /// 赠送品项列表
  94 + /// </summary>
  95 + public List<ItemDetail> GiftItems { get; set; } = new List<ItemDetail>();
  96 +
  97 + /// <summary>
  98 + /// 体验品项列表
  99 + /// </summary>
  100 + public List<ItemDetail> ExperienceItems { get; set; } = new List<ItemDetail>();
  101 +
  102 + /// <summary>
  103 + /// 已消耗品项列表
  104 + /// </summary>
  105 + public List<ItemDetail> ConsumedItems { get; set; } = new List<ItemDetail>();
  106 +
  107 + /// <summary>
  108 + /// 已消耗品项总金额
  109 + /// </summary>
  110 + public decimal ConsumedItemsTotalAmount { get; set; }
  111 +
  112 + /// <summary>
  113 + /// 已退卡品项列表
  114 + /// </summary>
  115 + public List<ItemDetail> RefundedItems { get; set; } = new List<ItemDetail>();
  116 +
  117 + /// <summary>
  118 + /// 已退卡品项总金额
  119 + /// </summary>
  120 + public decimal RefundedItemsTotalAmount { get; set; }
  121 +
  122 + /// <summary>
  123 + /// 已储扣品项列表
  124 + /// </summary>
  125 + public List<ItemDetail> DeductedItems { get; set; } = new List<ItemDetail>();
  126 +
  127 + /// <summary>
  128 + /// 已储扣品项总金额
  129 + /// </summary>
  130 + public decimal DeductedItemsTotalAmount { get; set; }
  131 + }
  132 +
  133 + /// <summary>
  134 + /// 品项明细
  135 + /// </summary>
  136 + public class ItemDetail
  137 + {
  138 + /// <summary>
  139 + /// 品项ID
  140 + /// </summary>
  141 + public string ItemId { get; set; }
  142 +
  143 + /// <summary>
  144 + /// 品项名称
  145 + /// </summary>
  146 + public string ItemName { get; set; }
  147 +
  148 + /// <summary>
  149 + /// 品项价格
  150 + /// </summary>
  151 + public decimal ItemPrice { get; set; }
  152 +
  153 + /// <summary>
  154 + /// 项目次数
  155 + /// </summary>
  156 + public decimal ProjectNumber { get; set; }
  157 +
  158 + /// <summary>
  159 + /// 总金额
  160 + /// </summary>
  161 + public decimal TotalAmount { get; set; }
  162 + }
  163 +}
  164 +
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKhxx/MemberItemInfoQueryInput.cs 0 → 100644
  1 +using System;
  2 +using NCC.Common.Filter;
  3 +
  4 +namespace NCC.Extend.Entitys.Dto.LqKhxx
  5 +{
  6 + /// <summary>
  7 + /// 会员品项信息查询输入参数
  8 + /// </summary>
  9 + public class MemberItemInfoQueryInput : PageInputBase
  10 + {
  11 + /// <summary>
  12 + /// 会员ID
  13 + /// </summary>
  14 + public string MemberId { get; set; }
  15 +
  16 + /// <summary>
  17 + /// 所属门店ID
  18 + /// </summary>
  19 + public string BelongStoreId { get; set; }
  20 +
  21 + /// <summary>
  22 + /// 开单门店ID
  23 + /// </summary>
  24 + public string BillingStoreId { get; set; }
  25 +
  26 + /// <summary>
  27 + /// 开单人ID
  28 + /// </summary>
  29 + public string BillingUserId { get; set; }
  30 +
  31 + /// <summary>
  32 + /// 购买品项ID
  33 + /// </summary>
  34 + public string PurchaseItemId { get; set; }
  35 +
  36 + /// <summary>
  37 + /// 赠送品项ID
  38 + /// </summary>
  39 + public string GiftItemId { get; set; }
  40 +
  41 + /// <summary>
  42 + /// 体验品项ID
  43 + /// </summary>
  44 + public string ExperienceItemId { get; set; }
  45 + }
  46 +}
  47 +
... ...
netcore/src/Modularity/Extend/NCC.Extend/LqDailyReportService.cs
... ... @@ -456,7 +456,7 @@ namespace NCC.Extend
456 456 var actualCompletedPerformance = unit.BillingPerformance - unit.RefundPerformance; // 实际完成业绩
457 457 unit.CompletedPerformance = actualCompletedPerformance; // 实际完成业绩
458 458 var completionRate = unit.TargetPerformance > 0 ? (actualCompletedPerformance / unit.TargetPerformance * 100m) : 0m;
459   - unit.CompletionRate = decimal.Round(completionRate, 2);
  459 + unit.CompletionRate = decimal.Round(completionRate, 2);
460 460 }
461 461  
462 462 var outputList = businessUnitDict.Values.OrderByDescending(x => x.CompletedPerformance).ToList();
... ... @@ -470,17 +470,23 @@ namespace NCC.Extend
470 470 /// 获取天王团业绩完成情况
471 471 /// </summary>
472 472 /// <remarks>
473   - /// 根据指定日期统计各天王团的业绩完成情况,包括目标业绩、开单业绩、退卡金额、实际完成业绩、完成率
  473 + /// 根据指定日期统计各天王团的业绩完成情况,包括目标业绩、开单业绩、储扣金额、退卡金额、实际完成业绩、完成率
474 474 ///
475 475 /// 业绩统计规则:
476   - /// - 教育部(教育一部、教育二部):统计生美品项(lq_xmzl.qt2='生美')相关的开单和退卡业绩
477   - /// - 科技部(科技一部、科技二部):统计科美品项(lq_xmzl.qt2='科美')相关的开单和退卡业绩
478   - /// - 大项目部(大项目一部、大项目二部):统计医美品项(lq_xmzl.qt2='医美')相关的开单和退卡业绩
  476 + /// - 教育部(教育一部、教育二部):统计生美品项(lq_xmzl.qt2='生美')相关的开单、储扣和退卡业绩
  477 + /// - 科技部(科技一部、科技二部):统计科美品项(lq_xmzl.qt2='科美')相关的开单、储扣和退卡业绩
  478 + /// - 大项目部(大项目一部、大项目二部):统计医美品项(lq_xmzl.qt2='医美')相关的开单、储扣和退卡业绩
479 479 ///
480 480 /// 业绩分配规则:
481   - /// - 生美品项的开单和退卡业绩全部归教育部(不按目标比例分配)
482   - /// - 科美品项的开单和退卡业绩全部归科技部(不按目标比例分配)
483   - /// - 医美品项的开单和退卡业绩全部归大项目部(不按目标比例分配)
  481 + /// - 生美品项的开单、储扣和退卡业绩全部归教育部(不按目标比例分配)
  482 + /// - 科美品项的开单、储扣和退卡业绩全部归科技部(不按目标比例分配)
  483 + /// - 医美品项的开单、储扣和退卡业绩全部归大项目部(不按目标比例分配)
  484 + ///
  485 + /// 业绩计算规则:
  486 + /// - 开单业绩:原始开单业绩(不减去储扣金额)
  487 + /// - 储扣金额:储值扣减金额总和
  488 + /// - 退卡业绩:退卡金额总和
  489 + /// - 实际完成业绩 = 开单业绩 - 储扣金额 - 退卡金额
484 490 ///
485 491 /// 示例请求:
486 492 /// ```json
... ... @@ -500,10 +506,11 @@ namespace NCC.Extend
500 506 /// - DepartmentId: 部门ID
501 507 /// - DepartmentName: 部门名称(教育一部、教育二部、科技一部、科技二部、大项目一部、大项目二部)
502 508 /// - TargetPerformance: 目标业绩(来自门店目标表lq_md_target,根据部门类型使用对应字段:教育部使用F_EducationDepartmentTarget,科技部使用F_TechDepartmentTarget,大项目部使用F_MajorProjectDepartmentTarget,根据开始时间所在月份获取,通过对应归属字段关联,如果未查询到则为0)
503   - /// - BillingPerformance: 开单业绩(指定时间范围内,按品项分类过滤后的开单业绩总和,按门店目标比例分配)
504   - /// - RefundPerformance: 退款业绩(指定时间范围内,按品项分类过滤后的退卡业绩总和,按门店目标比例分配)
  509 + /// - BillingPerformance: 开单业绩(原始开单业绩,不减去储扣金额)
  510 + /// - DeductAmount: 储扣金额(储值扣减金额总和)
  511 + /// - RefundPerformance: 退卡业绩(退卡金额总和)
505 512 /// - ActualPerformance: 开单业绩(与BillingPerformance相同,用于兼容)
506   - /// - CompletedPerformance: 实际完成业绩(开单业绩 - 退款业绩
  513 + /// - CompletedPerformance: 实际完成业绩(开单业绩 - 储扣金额 - 退卡金额
507 514 /// - CompletionRate: 完成率(百分比,CompletedPerformance / TargetPerformance * 100)
508 515 /// - StoreCount: 门店数量(根据门店目标表中归属该部门的门店数统计)
509 516 /// </remarks>
... ... @@ -582,7 +589,8 @@ namespace NCC.Extend
582 589 ActualPerformance = 0,
583 590 CompletedPerformance = 0,
584 591 CompletionRate = 0,
585   - StoreCount = Convert.ToInt32(dept.StoreCount)
  592 + StoreCount = Convert.ToInt32(dept.StoreCount),
  593 + DeductAmount = 0
586 594 };
587 595 // 记录部门对应的字段类型
588 596 departmentFieldMap[deptId] = deptType.Value.fieldName;
... ... @@ -620,6 +628,7 @@ namespace NCC.Extend
620 628 }
621 629 }
622 630  
  631 + // 如果目标表中没有对应月份的数据,直接返回空业绩列表
623 632 if (!storeIds.Any())
624 633 {
625 634 return departmentDict.Values.OrderByDescending(x => x.CompletedPerformance).ToList();
... ... @@ -630,7 +639,7 @@ namespace NCC.Extend
630 639 // 2.2 查询目标表数据(一次性查询,数据量小)
631 640 // 直接查询,不使用 MAX(),因为每个门店在目标表中应该只有一条记录
632 641 var targetSql = $@"
633   - SELECT
  642 + SELECT
634 643 F_StoreId,
635 644 F_EducationDepartment,
636 645 F_TechDepartment,
... ... @@ -650,220 +659,163 @@ namespace NCC.Extend
650 659 }
651 660 }
652 661  
653   - // 2.3 从品项明细表统计业绩,按门店+品项分类分组
  662 + // 如果targetDict为空,说明目标表中没有数据,直接返回
  663 + if (!targetDict.Any())
  664 + {
  665 + return departmentDict.Values.OrderByDescending(x => x.CompletedPerformance).ToList();
  666 + }
  667 +
  668 + // 2.3 分步骤查询:按生美、科美、医美分别查询开单业绩、退卡业绩、储扣金额
654 669 // 重要:业绩从品项明细表(lq_kd_pxmx)的 F_ActualPrice 获取,不是从开单记录表的 sfyj 获取
655 670 // 因为一个开单可能包含多个品项,每个品项属于不同的分类(生美、科美、医美)
656 671 // 例如:开单A实付500元,包含科美100元、医美250元、生美150元
657 672 // 那么科技部统计100元,大项目部统计250元,教育部统计150元
658   - var billingSql = $@"
659   - SELECT
660   - temp.djmd as StoreId,
661   - item.qt2 as ItemType,
662   - COALESCE(SUM(temp.F_ActualPrice), 0) as StoreAmount
663   - FROM (
664   - SELECT pxmx.px, pxmx.F_ActualPrice, billing.djmd
665   - FROM lq_kd_pxmx pxmx
666   - INNER JOIN lq_kd_kdjlb billing ON pxmx.glkdbh = billing.F_Id
667   - WHERE pxmx.F_IsEffective = 1
668   - AND billing.F_IsEffective = 1
669   - AND billing.djmd IN ('{storeIdsStr}')
670   - AND billing.kdrq >= '{startDate:yyyy-MM-dd} 00:00:00'
671   - AND billing.kdrq < '{endDate.AddDays(1):yyyy-MM-dd} 00:00:00'
672   - ) temp
673   - INNER JOIN lq_xmzl item ON temp.px = item.F_Id
674   - WHERE item.F_IsEffective = 1
675   - AND item.qt2 IN ('生美', '科美', '医美')
676   - GROUP BY temp.djmd, item.qt2";
677   -
678   - var billingData = await _db.Ado.SqlQueryAsync<dynamic>(billingSql);
679   -
680   - // 2.4 从退卡明细表统计退卡业绩,按门店+品项分类分组
681   - // 重要:退卡业绩从退卡明细表(lq_hytk_mx)的 tkje 获取,按品项分类统计
682   - var refundSql = $@"
683   - SELECT
684   - temp.md as StoreId,
685   - item.qt2 as ItemType,
686   - COALESCE(SUM(temp.tkje), 0) as StoreRefundAmount
687   - FROM (
688   - SELECT refund_mx.px, refund_mx.tkje, refund.md
689   - FROM lq_hytk_mx refund_mx
690   - INNER JOIN lq_hytk_hytk refund ON refund_mx.F_RefundInfoId = refund.F_Id
691   - WHERE refund_mx.F_IsEffective = 1
692   - AND refund.F_IsEffective = 1
693   - AND refund.md IN ('{storeIdsStr}')
694   - AND refund.tksj >= '{startDate:yyyy-MM-dd} 00:00:00'
695   - AND refund.tksj < '{endDate.AddDays(1):yyyy-MM-dd} 00:00:00'
696   - ) temp
697   - INNER JOIN lq_xmzl item ON temp.px = item.F_Id
698   - WHERE item.F_IsEffective = 1
699   - AND item.qt2 IN ('生美', '科美', '医美')
700   - GROUP BY temp.md, item.qt2";
701   -
702   - var refundData = await _db.Ado.SqlQueryAsync<dynamic>(refundSql);
703   -
704   - // 2.5 从储扣明细表统计储扣金额,按门店+品项分类分组
705   - // 重要:储扣金额从储扣明细表(lq_kd_deductinfo)的 F_Amount 获取,按品项分类统计
706   - var deductSql = $@"
707   - SELECT
708   - billing.djmd as StoreId,
709   - item.qt2 as ItemType,
710   - COALESCE(SUM(deduct.F_Amount), 0) as StoreDeductAmount
711   - FROM lq_kd_deductinfo deduct
712   - INNER JOIN lq_kd_kdjlb billing ON deduct.F_BillingId = billing.F_Id
713   - INNER JOIN lq_xmzl item ON deduct.F_ItemId = item.F_Id
714   - WHERE deduct.F_IsEffective = 1
715   - AND billing.F_IsEffective = 1
716   - AND item.F_IsEffective = 1
717   - AND billing.djmd IN ('{storeIdsStr}')
718   - AND billing.kdrq >= '{startDate:yyyy-MM-dd} 00:00:00'
719   - AND billing.kdrq < '{endDate.AddDays(1):yyyy-MM-dd} 00:00:00'
720   - AND item.qt2 IN ('生美', '科美', '医美')
721   - GROUP BY billing.djmd, item.qt2";
722   -
723   - var deductData = await _db.Ado.SqlQueryAsync<dynamic>(deductSql);
724   -
725   - // 2.6 根据品项分类和目标表分配业绩到对应部门
726   - // 分配规则:
727   - // - 生美品项 → 分配给教育部门(F_EducationDepartment)
728   - // - 科美品项 → 分配给科技部门(F_TechDepartment)
729   - // - 医美品项 → 分配给大项目部门(F_MajorProjectDepartment)
730   - // 注意:一个门店可能同时属于多个部门,但每个品项的业绩只分配给对应的一个部门
731   - foreach (var billing in billingData ?? Enumerable.Empty<dynamic>())
732   - {
733   - var storeId = billing?.StoreId?.ToString();
734   - var itemType = billing?.ItemType?.ToString();
735   - var storeAmount = billing?.StoreAmount != null ? Convert.ToDecimal(billing.StoreAmount) : 0m;
736 673  
737   - if (string.IsNullOrEmpty(storeId) || string.IsNullOrEmpty(itemType) || storeAmount <= 0 || !targetDict.ContainsKey(storeId))
738   - continue;
  674 + // 品项类型列表
  675 + var itemTypes = new[] { "生美", "科美", "医美" };
739 676  
740   - var target = targetDict[storeId];
741   - if (target == null)
742   - continue;
743   -
744   - string targetDeptId = null;
745   -
746   - // 根据品项分类,分配到对应的部门
  677 + // 循环处理每个品项类型
  678 + foreach (var itemType in itemTypes)
  679 + {
  680 + // 确定该品项类型对应的部门字段
  681 + string deptField = "";
747 682 if (itemType == "生美")
748 683 {
749   - // 生美品项 → 教育部门
750   - targetDeptId = target.F_EducationDepartment?.ToString();
  684 + deptField = "F_EducationDepartment";
751 685 }
752 686 else if (itemType == "科美")
753 687 {
754   - // 科美品项 → 科技部门
755   - targetDeptId = target.F_TechDepartment?.ToString();
  688 + deptField = "F_TechDepartment";
756 689 }
757 690 else if (itemType == "医美")
758 691 {
759   - // 医美品项 → 大项目部门
760   - targetDeptId = target.F_MajorProjectDepartment?.ToString();
  692 + deptField = "F_MajorProjectDepartment";
761 693 }
762 694  
763   - // 只分配给在查询列表中的部门,避免重复统计
764   - if (!string.IsNullOrEmpty(targetDeptId) && departmentDict.ContainsKey(targetDeptId))
765   - {
766   - departmentDict[targetDeptId].BillingPerformance += storeAmount;
767   - }
768   - }
  695 + // 2.3.1 查询开单业绩(按品项类型)
  696 + var billingSql = $@"
  697 + SELECT
  698 + target.{deptField} as TargetDeptId,
  699 + COALESCE(SUM(pxmx.F_ActualPrice), 0) as StoreAmount
  700 + FROM lq_kd_pxmx pxmx
  701 + INNER JOIN lq_kd_kdjlb billing ON pxmx.glkdbh = billing.F_Id
  702 + INNER JOIN lq_xmzl item ON pxmx.px = item.F_Id
  703 + INNER JOIN lq_md_target target ON billing.djmd = target.F_StoreId AND target.F_Month = '{month}'
  704 + WHERE pxmx.F_IsEffective = 1
  705 + AND billing.F_IsEffective = 1
  706 + AND item.F_IsEffective = 1
  707 + AND item.qt2 = '{itemType}'
  708 + AND billing.djmd IN ('{storeIdsStr}')
  709 + AND billing.kdrq >= '{startDate.ToString("yyyy-MM-dd")} 00:00:00'
  710 + AND billing.kdrq < '{endDate.AddDays(1).ToString("yyyy-MM-dd")} 00:00:00'
  711 + GROUP BY target.{deptField}";
769 712  
770   - // 2.6.1 减去储扣金额
771   - foreach (var deduct in deductData ?? Enumerable.Empty<dynamic>())
772   - {
773   - var storeId = deduct?.StoreId?.ToString();
774   - var itemType = deduct?.ItemType?.ToString();
775   - var storeDeductAmount = deduct?.StoreDeductAmount != null ? Convert.ToDecimal(deduct.StoreDeductAmount) : 0m;
  713 + var billingData = await _db.Ado.SqlQueryAsync<dynamic>(billingSql);
776 714  
777   - if (string.IsNullOrEmpty(storeId) || string.IsNullOrEmpty(itemType) || storeDeductAmount <= 0 || !targetDict.ContainsKey(storeId))
778   - continue;
779   -
780   - var target = targetDict[storeId];
781   - if (target == null)
782   - continue;
  715 + // 分配开单业绩到对应部门
  716 + foreach (var billing in billingData ?? Enumerable.Empty<dynamic>())
  717 + {
  718 + var storeAmount = billing?.StoreAmount != null ? Convert.ToDecimal(billing.StoreAmount) : 0m;
  719 + var targetDeptId = billing?.TargetDeptId?.ToString();
783 720  
784   - string targetDeptId = null;
  721 + if (storeAmount <= 0 || string.IsNullOrEmpty(targetDeptId))
  722 + continue;
785 723  
786   - // 根据品项分类,从对应的部门减去储扣金额
787   - if (itemType == "生美")
788   - {
789   - // 生美品项储扣 → 从教育部门减去
790   - targetDeptId = target.F_EducationDepartment?.ToString();
791   - }
792   - else if (itemType == "科美")
793   - {
794   - // 科美品项储扣 → 从科技部门减去
795   - targetDeptId = target.F_TechDepartment?.ToString();
796   - }
797   - else if (itemType == "医美")
798   - {
799   - // 医美品项储扣 → 从大项目部门减去
800   - targetDeptId = target.F_MajorProjectDepartment?.ToString();
  724 + // 只分配给在查询列表中的部门,避免重复统计
  725 + if (departmentDict.ContainsKey(targetDeptId))
  726 + {
  727 + departmentDict[targetDeptId].BillingPerformance += storeAmount;
  728 + }
801 729 }
802 730  
803   - // 只从在查询列表中的部门减去,避免重复统计
804   - if (!string.IsNullOrEmpty(targetDeptId) && departmentDict.ContainsKey(targetDeptId))
  731 + // 2.3.2 查询退卡业绩(按品项类型)
  732 + var refundSql = $@"
  733 + SELECT
  734 + target.{deptField} as TargetDeptId,
  735 + COALESCE(SUM(refund_mx.tkje), 0) as StoreRefundAmount
  736 + FROM lq_hytk_mx refund_mx
  737 + INNER JOIN lq_hytk_hytk refund ON refund_mx.F_RefundInfoId = refund.F_Id
  738 + INNER JOIN lq_xmzl item ON refund_mx.px = item.F_Id
  739 + INNER JOIN lq_md_target target ON refund.md = target.F_StoreId AND target.F_Month = '{month}'
  740 + WHERE refund_mx.F_IsEffective = 1
  741 + AND refund.F_IsEffective = 1
  742 + AND item.F_IsEffective = 1
  743 + AND item.qt2 = '{itemType}'
  744 + AND refund.md IN ('{storeIdsStr}')
  745 + AND refund.tksj >= '{startDate.ToString("yyyy-MM-dd")} 00:00:00'
  746 + AND refund.tksj < '{endDate.AddDays(1).ToString("yyyy-MM-dd")} 00:00:00'
  747 + GROUP BY target.{deptField}";
  748 +
  749 + var refundData = await _db.Ado.SqlQueryAsync<dynamic>(refundSql);
  750 +
  751 + // 分配退卡业绩到对应部门
  752 + foreach (var refund in refundData ?? Enumerable.Empty<dynamic>())
805 753 {
806   - departmentDict[targetDeptId].BillingPerformance -= storeDeductAmount;
807   - }
808   - }
  754 + var storeRefundAmount = refund?.StoreRefundAmount != null ? Convert.ToDecimal(refund.StoreRefundAmount) : 0m;
  755 + var targetDeptId = refund?.TargetDeptId?.ToString();
809 756  
810   - // 2.7 根据品项分类和目标表分配退卡业绩到对应部门
811   - // 分配规则与开单业绩相同:
812   - // - 生美品项退卡 → 分配给教育部门
813   - // - 科美品项退卡 → 分配给科技部门
814   - // - 医美品项退卡 → 分配给大项目部门
815   - foreach (var refund in refundData ?? Enumerable.Empty<dynamic>())
816   - {
817   - var storeId = refund?.StoreId?.ToString();
818   - var itemType = refund?.ItemType?.ToString();
819   - var storeRefundAmount = refund?.StoreRefundAmount != null ? Convert.ToDecimal(refund.StoreRefundAmount) : 0m;
  757 + if (storeRefundAmount <= 0 || string.IsNullOrEmpty(targetDeptId))
  758 + continue;
820 759  
821   - if (string.IsNullOrEmpty(storeId) || string.IsNullOrEmpty(itemType) || storeRefundAmount <= 0 || !targetDict.ContainsKey(storeId))
822   - continue;
  760 + // 只分配给在查询列表中的部门,避免重复统计
  761 + if (departmentDict.ContainsKey(targetDeptId))
  762 + {
  763 + departmentDict[targetDeptId].RefundPerformance += storeRefundAmount;
  764 + }
  765 + }
823 766  
824   - var target = targetDict[storeId];
825   - if (target == null)
826   - continue;
  767 + // 2.3.3 查询储扣金额(按品项类型)
  768 + var deductSql = $@"
  769 + SELECT
  770 + target.{deptField} as TargetDeptId,
  771 + COALESCE(SUM(deduct.F_Amount), 0) as StoreDeductAmount
  772 + FROM lq_kd_deductinfo deduct
  773 + INNER JOIN lq_kd_kdjlb billing ON deduct.F_BillingId = billing.F_Id
  774 + INNER JOIN lq_xmzl item ON deduct.F_ItemId = item.F_Id
  775 + INNER JOIN lq_md_target target ON billing.djmd = target.F_StoreId AND target.F_Month = '{month}'
  776 + WHERE deduct.F_IsEffective = 1
  777 + AND billing.F_IsEffective = 1
  778 + AND item.F_IsEffective = 1
  779 + AND item.qt2 = '{itemType}'
  780 + AND billing.djmd IN ('{storeIdsStr}')
  781 + AND billing.kdrq >= '{startDate.ToString("yyyy-MM-dd")} 00:00:00'
  782 + AND billing.kdrq < '{endDate.AddDays(1).ToString("yyyy-MM-dd")} 00:00:00'
  783 + GROUP BY target.{deptField}";
827 784  
828   - string targetDeptId = null;
  785 + var deductData = await _db.Ado.SqlQueryAsync<dynamic>(deductSql);
829 786  
830   - // 根据品项分类,分配到对应的部门
831   - if (itemType == "生美")
832   - {
833   - // 生美品项退卡 → 教育部门
834   - targetDeptId = target.F_EducationDepartment?.ToString();
835   - }
836   - else if (itemType == "科美")
837   - {
838   - // 科美品项退卡 → 科技部门
839   - targetDeptId = target.F_TechDepartment?.ToString();
840   - }
841   - else if (itemType == "医美")
  787 + // 分配储扣金额到对应部门
  788 + foreach (var deduct in deductData ?? Enumerable.Empty<dynamic>())
842 789 {
843   - // 医美品项退卡 → 大项目部门
844   - targetDeptId = target.F_MajorProjectDepartment?.ToString();
845   - }
  790 + var storeDeductAmount = deduct?.StoreDeductAmount != null ? Convert.ToDecimal(deduct.StoreDeductAmount) : 0m;
  791 + var targetDeptId = deduct?.TargetDeptId?.ToString();
846 792  
847   - // 只分配给在查询列表中的部门,避免重复统计
848   - if (!string.IsNullOrEmpty(targetDeptId) && departmentDict.ContainsKey(targetDeptId))
849   - {
850   - departmentDict[targetDeptId].RefundPerformance += storeRefundAmount;
  793 + if (storeDeductAmount <= 0 || string.IsNullOrEmpty(targetDeptId))
  794 + continue;
  795 +
  796 + // 只累加到在查询列表中的部门,避免重复统计
  797 + if (departmentDict.ContainsKey(targetDeptId))
  798 + {
  799 + departmentDict[targetDeptId].DeductAmount += storeDeductAmount;
  800 + }
851 801 }
852 802 }
853 803  
854 804 // 第三步:计算实际完成业绩和完成率,并保留两位小数
855 805 foreach (var dept in departmentDict.Values)
856 806 {
857   - dept.BillingPerformance = decimal.Round(dept.BillingPerformance, 2);
858   - dept.RefundPerformance = decimal.Round(dept.RefundPerformance, 2);
859   - dept.ActualPerformance = dept.BillingPerformance; // 开单业绩
860   - var actualCompletedPerformance = dept.BillingPerformance - dept.RefundPerformance; // 实际完成业绩
  807 + dept.BillingPerformance = decimal.Round(dept.BillingPerformance, 2); // 开单业绩(原始,不减去储扣)
  808 + dept.RefundPerformance = decimal.Round(dept.RefundPerformance, 2); // 退卡业绩
  809 + dept.DeductAmount = decimal.Round(dept.DeductAmount, 2); // 储扣金额,保留两位小数
  810 + dept.ActualPerformance = dept.BillingPerformance; // 开单业绩(与BillingPerformance相同,用于兼容)
  811 + // 实际完成业绩 = 开单业绩 - 储扣金额 - 退卡金额
  812 + var actualCompletedPerformance = dept.BillingPerformance - dept.DeductAmount - dept.RefundPerformance;
861 813 dept.CompletedPerformance = decimal.Round(actualCompletedPerformance, 2); // 实际完成业绩,保留两位小数
862 814  
863   - var completionRate = dept.TargetPerformance > 0
864   - ? (actualCompletedPerformance / dept.TargetPerformance * 100m)
865   - : 0m;
866   - dept.CompletionRate = decimal.Round(completionRate, 2);
  815 + var completionRate = dept.TargetPerformance > 0
  816 + ? (actualCompletedPerformance / dept.TargetPerformance * 100m)
  817 + : 0m;
  818 + dept.CompletionRate = decimal.Round(completionRate, 2);
867 819 }
868 820  
869 821 var outputList = departmentDict.Values
... ... @@ -1174,12 +1126,13 @@ namespace NCC.Extend
1174 1126 }
1175 1127  
1176 1128 // SQL查询:统计科技部老师的消耗业绩、见客数、项目数
  1129 + // 注意:GROUP BY 中移除了 user.F_RealName,避免同一老师ID因姓名不同产生重复记录
1177 1130 var consumeSql = $@"
1178 1131 SELECT
1179 1132 techDept.F_Id as TechDepartmentId,
1180 1133 techDept.F_FullName as TechDepartmentName,
1181 1134 consume.kjbls as TeacherId,
1182   - user.F_RealName as TeacherName,
  1135 + MAX(user.F_RealName) as TeacherName,
1183 1136 COUNT(DISTINCT hyhk.hy) as CustomerCount,
1184 1137 SUM(consume.F_hdpxNumber) as ConsumeProjectCount,
1185 1138 SUM(consume.kjblsyj) as ConsumeAchievement
... ... @@ -1194,7 +1147,7 @@ namespace NCC.Extend
1194 1147 AND DATE(hyhk.hksj) <= '{endDate:yyyy-MM-dd}'
1195 1148 {techFilter}
1196 1149 {teacherFilter}
1197   - GROUP BY techDept.F_Id, techDept.F_FullName, consume.kjbls, user.F_RealName";
  1150 + GROUP BY techDept.F_Id, techDept.F_FullName, consume.kjbls";
1198 1151  
1199 1152 var consumeResult = await _db.Ado.SqlQueryAsync<dynamic>(consumeSql);
1200 1153  
... ... @@ -1202,48 +1155,257 @@ namespace NCC.Extend
1202 1155 var orderSql = $@"
1203 1156 SELECT
1204 1157 techDept.F_Id as TechDepartmentId,
  1158 + techDept.F_FullName as TechDepartmentName,
1205 1159 ord.kjbls as TeacherId,
  1160 + MAX(user.F_RealName) as TeacherName,
1206 1161 SUM(ord.kjblsyj) as OrderAchievement
1207 1162 FROM lq_kd_kjbsyj ord
1208 1163 INNER JOIN lq_kd_kdjlb kdjlb ON ord.glkdbh = kdjlb.F_Id
1209 1164 INNER JOIN lq_mdxx store ON kdjlb.djmd = store.F_Id
1210 1165 LEFT JOIN base_organize techDept ON store.kjb = techDept.F_Id
  1166 + LEFT JOIN BASE_USER user ON ord.kjbls = user.F_Id
1211 1167 WHERE ord.F_IsEffective = 1
1212 1168 AND kdjlb.F_IsEffective = 1
1213 1169 AND DATE(kdjlb.kdrq) >= '{startDate:yyyy-MM-dd}'
1214 1170 AND DATE(kdjlb.kdrq) <= '{endDate:yyyy-MM-dd}'
1215 1171 {techFilter}
1216 1172 {teacherFilterForOrder}
1217   - GROUP BY techDept.F_Id, ord.kjbls";
  1173 + GROUP BY techDept.F_Id, techDept.F_FullName, ord.kjbls";
1218 1174  
1219 1175 var orderResult = await _db.Ado.SqlQueryAsync<dynamic>(orderSql);
1220 1176  
1221   - // 合并数据
  1177 + // 合并数据:按员工ID汇总,避免同一员工在多个科技部重复出现
  1178 + // 使用 TeacherId 作为唯一键,汇总所有科技部的数据
1222 1179 var teacherDict = new Dictionary<string, TechTeacherDailyStatisticsOutput>();
  1180 + // 记录每个员工在各个科技部的消耗业绩,用于确定主要科技部
  1181 + var teacherDeptConsumePerformance = new Dictionary<string, Dictionary<string, decimal>>();
  1182 + // 记录每个员工在各个科技部的开单业绩,用于确定主要科技部(当消耗业绩为0时)
  1183 + var teacherDeptOrderPerformance = new Dictionary<string, Dictionary<string, decimal>>();
1223 1184  
  1185 + // 第一步:处理消耗数据,按员工ID汇总
1224 1186 foreach (var item in consumeResult ?? Enumerable.Empty<dynamic>())
1225 1187 {
1226   - var key = $"{item.TechDepartmentId}_{item.TeacherId}";
1227   - teacherDict[key] = new TechTeacherDailyStatisticsOutput
  1188 + var teacherId = item.TeacherId?.ToString();
  1189 + var techDeptId = item.TechDepartmentId?.ToString();
  1190 + var techDeptName = item.TechDepartmentName?.ToString();
  1191 + var consumeAchievement = Convert.ToDecimal(item.ConsumeAchievement);
  1192 +
  1193 + if (string.IsNullOrEmpty(teacherId))
  1194 + continue;
  1195 +
  1196 + // 记录员工在各科技部的消耗业绩(累加,因为同一员工在同一科技部可能有多个门店的数据)
  1197 + if (!teacherDeptConsumePerformance.ContainsKey(teacherId))
1228 1198 {
1229   - TechDepartmentId = item.TechDepartmentId?.ToString(),
1230   - TechDepartmentName = item.TechDepartmentName?.ToString(),
1231   - TeacherId = item.TeacherId?.ToString(),
1232   - TeacherName = item.TeacherName?.ToString(),
1233   - CustomerCount = Convert.ToInt32(item.CustomerCount),
1234   - ConsumeProjectCount = Convert.ToInt32(item.ConsumeProjectCount),
1235   - ConsumeAchievement = Convert.ToDecimal(item.ConsumeAchievement),
1236   - OrderAchievement = 0
1237   - };
  1199 + teacherDeptConsumePerformance[teacherId] = new Dictionary<string, decimal>();
  1200 + }
  1201 + if (!string.IsNullOrEmpty(techDeptId))
  1202 + {
  1203 + if (!teacherDeptConsumePerformance[teacherId].ContainsKey(techDeptId))
  1204 + {
  1205 + teacherDeptConsumePerformance[teacherId][techDeptId] = 0;
  1206 + }
  1207 + teacherDeptConsumePerformance[teacherId][techDeptId] += consumeAchievement;
  1208 + }
  1209 +
  1210 + // 按员工ID汇总数据
  1211 + if (!teacherDict.ContainsKey(teacherId))
  1212 + {
  1213 + teacherDict[teacherId] = new TechTeacherDailyStatisticsOutput
  1214 + {
  1215 + TechDepartmentId = techDeptId,
  1216 + TechDepartmentName = techDeptName,
  1217 + TeacherId = teacherId,
  1218 + TeacherName = item.TeacherName?.ToString(),
  1219 + CustomerCount = 0,
  1220 + ConsumeProjectCount = 0,
  1221 + ConsumeAchievement = 0,
  1222 + OrderAchievement = 0
  1223 + };
  1224 + }
  1225 +
  1226 + // 汇总数据(累加项目数和业绩,见客数后面统一重新计算去重)
  1227 + var teacher = teacherDict[teacherId];
  1228 + teacher.ConsumeProjectCount += Convert.ToInt32(item.ConsumeProjectCount);
  1229 + teacher.ConsumeAchievement += consumeAchievement;
1238 1230 }
1239 1231  
1240   - // 合并开单业绩
  1232 + // 第二步:处理开单业绩,按员工ID汇总
1241 1233 foreach (var item in orderResult ?? Enumerable.Empty<dynamic>())
1242 1234 {
1243   - var key = $"{item.TechDepartmentId}_{item.TeacherId}";
1244   - if (teacherDict.ContainsKey(key))
  1235 + var teacherId = item.TeacherId?.ToString();
  1236 + var techDeptId = item.TechDepartmentId?.ToString();
  1237 + var techDeptName = item.TechDepartmentName?.ToString();
  1238 + var orderAchievement = Convert.ToDecimal(item.OrderAchievement);
  1239 +
  1240 + if (string.IsNullOrEmpty(teacherId))
  1241 + continue;
  1242 +
  1243 + // 记录员工在各科技部的开单业绩(累加,因为同一员工在同一科技部可能有多个门店的数据)
  1244 + if (!teacherDeptOrderPerformance.ContainsKey(teacherId))
1245 1245 {
1246   - teacherDict[key].OrderAchievement = Convert.ToDecimal(item.OrderAchievement);
  1246 + teacherDeptOrderPerformance[teacherId] = new Dictionary<string, decimal>();
  1247 + }
  1248 + if (!string.IsNullOrEmpty(techDeptId))
  1249 + {
  1250 + if (!teacherDeptOrderPerformance[teacherId].ContainsKey(techDeptId))
  1251 + {
  1252 + teacherDeptOrderPerformance[teacherId][techDeptId] = 0;
  1253 + }
  1254 + teacherDeptOrderPerformance[teacherId][techDeptId] += orderAchievement;
  1255 + }
  1256 +
  1257 + if (!teacherDict.ContainsKey(teacherId))
  1258 + {
  1259 + // 如果消耗数据中没有,但开单数据中有,需要创建记录
  1260 + teacherDict[teacherId] = new TechTeacherDailyStatisticsOutput
  1261 + {
  1262 + TechDepartmentId = techDeptId,
  1263 + TechDepartmentName = techDeptName,
  1264 + TeacherId = teacherId,
  1265 + TeacherName = item.TeacherName?.ToString(),
  1266 + CustomerCount = 0,
  1267 + ConsumeProjectCount = 0,
  1268 + ConsumeAchievement = 0,
  1269 + OrderAchievement = 0
  1270 + };
  1271 + }
  1272 + else
  1273 + {
  1274 + // 如果消耗数据中有,但名称为空,尝试从开单数据中获取
  1275 + if (string.IsNullOrEmpty(teacherDict[teacherId].TeacherName))
  1276 + {
  1277 + teacherDict[teacherId].TeacherName = item.TeacherName?.ToString();
  1278 + }
  1279 + }
  1280 +
  1281 + teacherDict[teacherId].OrderAchievement += orderAchievement;
  1282 + }
  1283 +
  1284 + // 第三步:确定每个员工的主要科技部
  1285 + // 优先按消耗业绩最多的科技部,如果消耗业绩为0,则按开单业绩最多的科技部
  1286 + foreach (var teacherId in teacherDict.Keys.ToList())
  1287 + {
  1288 + var teacher = teacherDict[teacherId];
  1289 + string mainDeptId = null;
  1290 + string mainDeptName = null;
  1291 +
  1292 + // 优先按消耗业绩确定主要科技部
  1293 + if (teacherDeptConsumePerformance.ContainsKey(teacherId) && teacherDeptConsumePerformance[teacherId].Any())
  1294 + {
  1295 + var mainDept = teacherDeptConsumePerformance[teacherId]
  1296 + .OrderByDescending(x => x.Value)
  1297 + .FirstOrDefault();
  1298 +
  1299 + if (!string.IsNullOrEmpty(mainDept.Key) && mainDept.Value > 0)
  1300 + {
  1301 + mainDeptId = mainDept.Key;
  1302 + // 从消耗数据中获取该科技部的名称
  1303 + var mainDeptData = consumeResult?.FirstOrDefault(x =>
  1304 + x.TeacherId?.ToString() == teacherId &&
  1305 + x.TechDepartmentId?.ToString() == mainDeptId);
  1306 +
  1307 + if (mainDeptData != null)
  1308 + {
  1309 + mainDeptName = mainDeptData.TechDepartmentName?.ToString();
  1310 + }
  1311 + }
  1312 + }
  1313 +
  1314 + // 如果消耗业绩为0或没有,则按开单业绩确定主要科技部
  1315 + if (string.IsNullOrEmpty(mainDeptId) && teacherDeptOrderPerformance.ContainsKey(teacherId) && teacherDeptOrderPerformance[teacherId].Any())
  1316 + {
  1317 + var mainDept = teacherDeptOrderPerformance[teacherId]
  1318 + .OrderByDescending(x => x.Value)
  1319 + .FirstOrDefault();
  1320 +
  1321 + if (!string.IsNullOrEmpty(mainDept.Key) && mainDept.Value > 0)
  1322 + {
  1323 + mainDeptId = mainDept.Key;
  1324 + // 从开单数据中获取该科技部的名称
  1325 + var mainDeptData = orderResult?.FirstOrDefault(x =>
  1326 + x.TeacherId?.ToString() == teacherId &&
  1327 + x.TechDepartmentId?.ToString() == mainDeptId);
  1328 +
  1329 + if (mainDeptData != null)
  1330 + {
  1331 + mainDeptName = mainDeptData.TechDepartmentName?.ToString();
  1332 + }
  1333 + }
  1334 + }
  1335 +
  1336 + // 如果仍然没有确定主要科技部,但已有科技部ID和名称,保持原样
  1337 + if (!string.IsNullOrEmpty(mainDeptId))
  1338 + {
  1339 + teacher.TechDepartmentId = mainDeptId;
  1340 + if (!string.IsNullOrEmpty(mainDeptName))
  1341 + {
  1342 + teacher.TechDepartmentName = mainDeptName;
  1343 + }
  1344 + }
  1345 + else if (string.IsNullOrEmpty(teacher.TechDepartmentName) && !string.IsNullOrEmpty(teacher.TechDepartmentId))
  1346 + {
  1347 + // 如果仍然没有科技部名称,尝试从开单数据中获取
  1348 + var orderDeptData = orderResult?.FirstOrDefault(x =>
  1349 + x.TeacherId?.ToString() == teacherId &&
  1350 + x.TechDepartmentId?.ToString() == teacher.TechDepartmentId);
  1351 +
  1352 + if (orderDeptData != null && !string.IsNullOrEmpty(orderDeptData.TechDepartmentName?.ToString()))
  1353 + {
  1354 + teacher.TechDepartmentName = orderDeptData.TechDepartmentName?.ToString();
  1355 + }
  1356 + }
  1357 + }
  1358 +
  1359 + // 第四步:重新计算见客数(去重,因为同一个客户可能在多个科技部被统计)
  1360 + // 由于已经汇总了数据,这里需要重新查询去重后的见客数
  1361 + var teacherIds = teacherDict.Keys.ToList();
  1362 + if (teacherIds.Any())
  1363 + {
  1364 + var teacherIdsStr = string.Join("','", teacherIds);
  1365 + var customerCountSql = $@"
  1366 + SELECT
  1367 + consume.kjbls as TeacherId,
  1368 + COUNT(DISTINCT hyhk.hy) as CustomerCount
  1369 + FROM lq_xh_kjbsyj consume
  1370 + INNER JOIN lq_xh_hyhk hyhk ON consume.glkdbh = hyhk.F_Id
  1371 + WHERE consume.F_IsEffective = 1
  1372 + AND hyhk.F_IsEffective = 1
  1373 + AND DATE(hyhk.hksj) >= '{startDate:yyyy-MM-dd}'
  1374 + AND DATE(hyhk.hksj) <= '{endDate:yyyy-MM-dd}'
  1375 + AND consume.kjbls IN ('{teacherIdsStr}')
  1376 + GROUP BY consume.kjbls";
  1377 +
  1378 + var customerCountResult = await _db.Ado.SqlQueryAsync<dynamic>(customerCountSql);
  1379 + foreach (var item in customerCountResult ?? Enumerable.Empty<dynamic>())
  1380 + {
  1381 + var teacherId = item.TeacherId?.ToString();
  1382 + if (!string.IsNullOrEmpty(teacherId) && teacherDict.ContainsKey(teacherId))
  1383 + {
  1384 + teacherDict[teacherId].CustomerCount = Convert.ToInt32(item.CustomerCount);
  1385 + }
  1386 + }
  1387 +
  1388 + // 第五步:统一查询所有用户名称,确保所有用户都能获取到名称
  1389 + var teacherNamesSql = $@"
  1390 + SELECT
  1391 + F_Id as TeacherId,
  1392 + F_RealName as TeacherName
  1393 + FROM BASE_USER
  1394 + WHERE F_Id IN ('{teacherIdsStr}')";
  1395 +
  1396 + var teacherNamesResult = await _db.Ado.SqlQueryAsync<dynamic>(teacherNamesSql);
  1397 + foreach (var item in teacherNamesResult ?? Enumerable.Empty<dynamic>())
  1398 + {
  1399 + var teacherId = item.TeacherId?.ToString();
  1400 + var teacherName = item.TeacherName?.ToString();
  1401 + if (!string.IsNullOrEmpty(teacherId) && teacherDict.ContainsKey(teacherId))
  1402 + {
  1403 + // 如果名称为空,则填充;如果已有名称,保持不变
  1404 + if (string.IsNullOrEmpty(teacherDict[teacherId].TeacherName) && !string.IsNullOrEmpty(teacherName))
  1405 + {
  1406 + teacherDict[teacherId].TeacherName = teacherName;
  1407 + }
  1408 + }
1247 1409 }
1248 1410 }
1249 1411  
... ... @@ -1583,6 +1745,8 @@ namespace NCC.Extend
1583 1745 }
1584 1746 #endregion
1585 1747  
  1748 +
  1749 +
1586 1750 #region 获取储值扣减金额统计
1587 1751 /// <summary>
1588 1752 /// 获取储值扣减金额统计
... ...
netcore/src/Modularity/Extend/NCC.Extend/LqKdKdjlbService.cs
... ... @@ -3071,7 +3071,7 @@ namespace NCC.Extend.LqKdKdjlb
3071 3071 COALESCE(consume_stats.ConsumeAmount, 0) as ConsumeAmount,
3072 3072 COALESCE(consume_stats.HeadCount, 0) as HeadCount,
3073 3073 COALESCE(consume_stats.PersonCount, 0) as PersonCount,
3074   - COALESCE(consume_stats.ProjectCount, 0) as ProjectCount
  3074 + CAST(COALESCE(consume_stats.ProjectCount, 0) AS DECIMAL(18,2)) as ProjectCount
3075 3075  
3076 3076 FROM BASE_USER u
3077 3077 LEFT JOIN lq_mdxx md ON u.F_MDID = md.F_Id
... ... @@ -3135,7 +3135,7 @@ namespace NCC.Extend.LqKdKdjlb
3135 3135 SUM(jksyj.jksyj) as ConsumeAmount,
3136 3136 COUNT(DISTINCT hyhk.hy) as HeadCount,
3137 3137 COUNT(DISTINCT CONCAT(jksyj.jkszh, '_', hyhk.hy, '_', DATE(hyhk.hksj))) as PersonCount,
3138   - SUM(jksyj.F_kdpxNumber) as ProjectCount
  3138 + CAST(SUM(jksyj.F_kdpxNumber) AS DECIMAL(18,2)) as ProjectCount
3139 3139 FROM lq_xh_jksyj jksyj
3140 3140 INNER JOIN lq_xh_hyhk hyhk ON jksyj.glkdbh = hyhk.F_Id
3141 3141 WHERE jksyj.jkszh IS NOT NULL
... ... @@ -3211,5 +3211,203 @@ namespace NCC.Extend.LqKdKdjlb
3211 3211 }
3212 3212 }
3213 3213 #endregion
  3214 +
  3215 + #region 获取开单品项明细记录列表
  3216 + /// <summary>
  3217 + /// 获取开单品项明细记录列表(分页)
  3218 + /// </summary>
  3219 + /// <remarks>
  3220 + /// 查询开单品项明细记录,支持多条件筛选和分页查询。采用先分页后关联的查询方式,确保分页准确性和查询性能。
  3221 + ///
  3222 + /// 示例请求:
  3223 + /// ```json
  3224 + /// {
  3225 + /// "currentPage": 1,
  3226 + /// "pageSize": 10,
  3227 + /// "sidx": "yjsj",
  3228 + /// "sort": "DESC",
  3229 + /// "Id": "品项明细ID",
  3230 + /// "BillingId": "开单ID",
  3231 + /// "StartBillingTime": "2025-01-01",
  3232 + /// "EndBillingTime": "2025-12-31",
  3233 + /// "ActivityId": "营销活动ID",
  3234 + /// "MemberId": "会员ID",
  3235 + /// "MemberName": "会员名称",
  3236 + /// "MemberPhone": "会员手机号",
  3237 + /// "ItemId": "品项ID",
  3238 + /// "ItemName": "品项名称",
  3239 + /// "ItemType": "品项类型",
  3240 + /// "SourceType": "来源类型"
  3241 + /// }
  3242 + /// ```
  3243 + ///
  3244 + /// 查询参数说明:
  3245 + /// - currentPage: 当前页码(必填,从1开始)
  3246 + /// - pageSize: 每页数量(必填)
  3247 + /// - sidx: 排序字段(可选,默认:yjsj,支持:id、billingId、billingTime、itemName、actualPrice、projectNumber等)
  3248 + /// - sort: 排序方式(可选,默认:DESC,支持:ASC、DESC)
  3249 + /// - Id: 品项明细ID(可选,精确匹配)
  3250 + /// - BillingId: 开单ID(可选,精确匹配)
  3251 + /// - StartBillingTime: 开单时间开始(可选,格式:yyyy-MM-dd,与EndBillingTime同时使用)
  3252 + /// - EndBillingTime: 开单时间结束(可选,格式:yyyy-MM-dd,与StartBillingTime同时使用)
  3253 + /// - ActivityId: 营销活动ID(可选,精确匹配)
  3254 + /// - MemberId: 会员ID(可选,精确匹配)
  3255 + /// - MemberName: 会员名称(可选,模糊查询)
  3256 + /// - MemberPhone: 会员手机号(可选,精确匹配)
  3257 + /// - ItemId: 品项ID(可选,精确匹配)
  3258 + /// - ItemName: 品项名称(可选,模糊查询)
  3259 + /// - ItemType: 品项类型(可选,精确匹配,对应项目资料表的qt2字段)
  3260 + /// - SourceType: 来源类型(可选,精确匹配,如:购买、赠送、体验)
  3261 + ///
  3262 + /// 返回数据结构:
  3263 + /// ```json
  3264 + /// {
  3265 + /// "pagination": {
  3266 + /// "pageIndex": 1,
  3267 + /// "pageSize": 10,
  3268 + /// "total": 100
  3269 + /// },
  3270 + /// "list": [
  3271 + /// {
  3272 + /// "id": "品项明细ID",
  3273 + /// "billingId": "开单ID",
  3274 + /// "billingTime": "2025-11-15T10:00:00",
  3275 + /// "activityName": "营销活动名称",
  3276 + /// "memberName": "会员名称",
  3277 + /// "memberPhone": "会员手机号",
  3278 + /// "itemName": "品项名称",
  3279 + /// "itemType": "品项类型",
  3280 + /// "actualPrice": 500.00,
  3281 + /// "projectNumber": 5.0,
  3282 + /// "sourceType": "购买",
  3283 + /// "remark": "备注"
  3284 + /// }
  3285 + /// ]
  3286 + /// }
  3287 + /// ```
  3288 + ///
  3289 + /// 返回字段说明:
  3290 + /// - id: 品项明细ID(主键)
  3291 + /// - billingId: 开单ID(关联开单记录表)
  3292 + /// - billingTime: 开单时间(业绩时间yjsj,DateTime格式)
  3293 + /// - activityName: 营销活动名称(关联营销活动表)
  3294 + /// - memberName: 会员名称(关联会员表)
  3295 + /// - memberPhone: 会员手机号(关联会员表)
  3296 + /// - itemName: 品项名称(品项明细表字段)
  3297 + /// - itemType: 品项类型(关联项目资料表的qt2字段)
  3298 + /// - actualPrice: 实付金额(decimal类型)
  3299 + /// - projectNumber: 项目次数(decimal类型,支持小数)
  3300 + /// - sourceType: 来源类型(字符串,如:购买、赠送、体验)
  3301 + /// - remark: 备注(字符串)
  3302 + /// </remarks>
  3303 + /// <param name="input">查询参数</param>
  3304 + /// <returns>开单品项明细记录列表(分页)</returns>
  3305 + /// <response code="200">查询成功,返回开单品项明细记录列表</response>
  3306 + /// <response code="400">参数错误,如页码或页大小无效</response>
  3307 + /// <response code="500">服务器错误,查询过程中发生异常</response>
  3308 + [HttpGet("billing-item-detail-list")]
  3309 + public async Task<dynamic> GetBillingItemDetailList([FromQuery] BillingItemDetailListQueryInput input)
  3310 + {
  3311 + try
  3312 + {
  3313 + var sidx = input.sidx == null ? "yjsj" : input.sidx;
  3314 + var sort = string.IsNullOrEmpty(input.sort) ? "DESC" : input.sort;
  3315 +
  3316 + // 处理开单时间范围
  3317 + List<string> queryBillingTime = null;
  3318 + DateTime? startBillingTime = null;
  3319 + DateTime? endBillingTime = null;
  3320 + if (!string.IsNullOrEmpty(input.StartBillingTime) && !string.IsNullOrEmpty(input.EndBillingTime))
  3321 + {
  3322 + queryBillingTime = new List<string> { input.StartBillingTime, input.EndBillingTime };
  3323 + startBillingTime = Ext.GetDateTime(queryBillingTime.First());
  3324 + endBillingTime = Ext.GetDateTime(queryBillingTime.Last());
  3325 + }
  3326 +
  3327 + // 优化查询:先分页查询主表,再批量查询关联数据,避免子查询性能问题
  3328 + // 1. 先构建基础查询条件
  3329 + var baseQuery = _db.Queryable<LqKdPxmxEntity>()
  3330 + .Where(pxmx => pxmx.IsEffective == StatusEnum.有效.GetHashCode())
  3331 + .WhereIF(!string.IsNullOrEmpty(input.Id), pxmx => pxmx.Id == input.Id)
  3332 + .WhereIF(!string.IsNullOrEmpty(input.BillingId), pxmx => pxmx.Glkdbh == input.BillingId)
  3333 + .WhereIF(queryBillingTime != null && startBillingTime.HasValue, pxmx => pxmx.Yjsj >= new DateTime(startBillingTime.Value.Year, startBillingTime.Value.Month, startBillingTime.Value.Day, 0, 0, 0))
  3334 + .WhereIF(queryBillingTime != null && endBillingTime.HasValue, pxmx => pxmx.Yjsj <= new DateTime(endBillingTime.Value.Year, endBillingTime.Value.Month, endBillingTime.Value.Day, 23, 59, 59))
  3335 + .WhereIF(!string.IsNullOrEmpty(input.ActivityId), pxmx => pxmx.ActivityId == input.ActivityId)
  3336 + .WhereIF(!string.IsNullOrEmpty(input.MemberId), pxmx => pxmx.MemberId == input.MemberId)
  3337 + .WhereIF(!string.IsNullOrEmpty(input.ItemId), pxmx => pxmx.Px == input.ItemId)
  3338 + .WhereIF(!string.IsNullOrEmpty(input.ItemName), pxmx => pxmx.Pxmc != null && pxmx.Pxmc.Contains(input.ItemName))
  3339 + .WhereIF(!string.IsNullOrEmpty(input.SourceType), pxmx => pxmx.SourceType == input.SourceType);
  3340 +
  3341 + // 2. 通过 EXISTS 子查询筛选关联字段(在分页前筛选,确保分页准确)
  3342 + baseQuery = baseQuery.WhereIF(!string.IsNullOrEmpty(input.MemberName), pxmx => SqlFunc.Subqueryable<LqKhxxEntity>().Where(x => x.Id == pxmx.MemberId && x.Khmc != null && x.Khmc.Contains(input.MemberName)).Any())
  3343 + .WhereIF(!string.IsNullOrEmpty(input.MemberPhone), pxmx => SqlFunc.Subqueryable<LqKhxxEntity>().Where(x => x.Id == pxmx.MemberId && x.Sjh == input.MemberPhone).Any())
  3344 + .WhereIF(!string.IsNullOrEmpty(input.ItemType), pxmx => SqlFunc.Subqueryable<LqXmzlEntity>().Where(x => x.Id == pxmx.Px && x.Fl4 == input.ItemType).Any());
  3345 +
  3346 + // 3. 先分页查询主表数据(查询实体类,提高性能)
  3347 + var pagedData = await baseQuery.OrderBy(sidx + " " + sort).ToPagedListAsync(input.currentPage, input.pageSize);
  3348 +
  3349 + // 4. 批量查询关联数据
  3350 + var itemIds = pagedData.list.Select(x => x.Id).ToList();
  3351 + var memberIds = pagedData.list.Where(x => !string.IsNullOrEmpty(x.MemberId)).Select(x => x.MemberId).Distinct().ToList();
  3352 + var activityIds = pagedData.list.Where(x => !string.IsNullOrEmpty(x.ActivityId)).Select(x => x.ActivityId).Distinct().ToList();
  3353 + var projectIds = pagedData.list.Where(x => !string.IsNullOrEmpty(x.Px)).Select(x => x.Px).Distinct().ToList();
  3354 +
  3355 + // 批量查询会员信息
  3356 + var memberDict = new Dictionary<string, (string Name, string Phone)>();
  3357 + if (memberIds.Any())
  3358 + {
  3359 + var members = await _db.Queryable<LqKhxxEntity>().Where(x => memberIds.Contains(x.Id)).Select(x => new { x.Id, x.Khmc, x.Sjh }).ToListAsync();
  3360 + memberDict = members.ToDictionary(x => x.Id, x => (x.Khmc ?? "", x.Sjh ?? ""));
  3361 + }
  3362 +
  3363 + // 批量查询活动信息
  3364 + var activityDict = new Dictionary<string, string>();
  3365 + if (activityIds.Any())
  3366 + {
  3367 + var activities = await _db.Queryable<LqPackageInfoEntity>().Where(x => activityIds.Contains(x.Id)).Select(x => new { x.Id, x.ActivityName }).ToListAsync();
  3368 + activityDict = activities.ToDictionary(x => x.Id, x => x.ActivityName ?? "");
  3369 + }
  3370 +
  3371 + // 批量查询项目资料
  3372 + var projectDict = new Dictionary<string, string>();
  3373 + if (projectIds.Any())
  3374 + {
  3375 + var projects = await _db.Queryable<LqXmzlEntity>().Where(x => projectIds.Contains(x.Id)).Select(x => new { x.Id, x.Qt2 }).ToListAsync();
  3376 + projectDict = projects.ToDictionary(x => x.Id, x => x.Qt2 ?? "");
  3377 + }
  3378 +
  3379 + // 5. 组装返回数据
  3380 + var resultList = pagedData.list.Select(pxmx => new BillingItemDetailListOutput
  3381 + {
  3382 + id = pxmx.Id,
  3383 + billingId = pxmx.Glkdbh,
  3384 + billingTime = pxmx.Yjsj,
  3385 + activityName = pxmx.ActivityId != null && activityDict.ContainsKey(pxmx.ActivityId) ? activityDict[pxmx.ActivityId] : "",
  3386 + memberName = pxmx.MemberId != null && memberDict.ContainsKey(pxmx.MemberId) ? memberDict[pxmx.MemberId].Name : "",
  3387 + memberPhone = pxmx.MemberId != null && memberDict.ContainsKey(pxmx.MemberId) ? memberDict[pxmx.MemberId].Phone : "",
  3388 + itemName = pxmx.Pxmc,
  3389 + itemType = pxmx.Px != null && projectDict.ContainsKey(pxmx.Px) ? projectDict[pxmx.Px] : "",
  3390 + actualPrice = pxmx.ActualPrice,
  3391 + projectNumber = pxmx.ProjectNumber,
  3392 + sourceType = pxmx.SourceType,
  3393 + remark = pxmx.Remark
  3394 + }).ToList();
  3395 +
  3396 + // 6. 返回分页结果
  3397 + var result = new SqlSugarPagedList<BillingItemDetailListOutput>
  3398 + {
  3399 + list = resultList,
  3400 + pagination = pagedData.pagination
  3401 + };
  3402 +
  3403 + return PageResult<BillingItemDetailListOutput>.SqlSugarPageResult(result);
  3404 + }
  3405 + catch (Exception ex)
  3406 + {
  3407 + _logger.LogError(ex, $"获取开单品项明细记录列表失败: {ex.ToString()}");
  3408 + throw NCCException.Oh(ErrorCode.COM1005, $"获取开单品项明细记录列表失败: {ex.Message}");
  3409 + }
  3410 + }
  3411 + #endregion
3214 3412 }
3215 3413 }
... ...
netcore/src/Modularity/Extend/NCC.Extend/LqKhxxService.cs
... ... @@ -776,5 +776,676 @@ namespace NCC.Extend.LqKhxx
776 776 }
777 777 #endregion
778 778  
  779 + #region 获取会员品项信息
  780 + /// <summary>
  781 + /// 获取所有会员的品项信息(分页)
  782 + /// </summary>
  783 + /// <remarks>
  784 + /// 查询所有会员的品项信息,包括购买品项、赠送品项、体验品项、已消耗品项、已退卡品项、已储扣品项
  785 + ///
  786 + /// 示例请求:
  787 + /// ```json
  788 + /// {
  789 + /// "currentPage": 1,
  790 + /// "pageSize": 10,
  791 + /// "MemberId": "会员ID",
  792 + /// "BelongStoreId": "所属门店ID",
  793 + /// "BillingStoreId": "开单门店ID",
  794 + /// "BillingUserId": "开单人ID",
  795 + /// "PurchaseItemId": "购买品项ID",
  796 + /// "GiftItemId": "赠送品项ID",
  797 + /// "ExperienceItemId": "体验品项ID"
  798 + /// }
  799 + /// ```
  800 + ///
  801 + /// 参数说明:
  802 + /// - currentPage: 当前页码(必填)
  803 + /// - pageSize: 每页数量(必填)
  804 + /// - MemberId: 会员ID(可选)
  805 + /// - BelongStoreId: 所属门店ID(可选)
  806 + /// - BillingStoreId: 开单门店ID(可选)
  807 + /// - BillingUserId: 开单人ID(可选)
  808 + /// - PurchaseItemId: 购买品项ID(可选)
  809 + /// - GiftItemId: 赠送品项ID(可选)
  810 + /// - ExperienceItemId: 体验品项ID(可选)
  811 + ///
  812 + /// 返回数据结构说明:
  813 + /// ```json
  814 + /// {
  815 + /// "pagination": {
  816 + /// "pageIndex": 1,
  817 + /// "pageSize": 10,
  818 + /// "total": 100
  819 + /// },
  820 + /// "list": [
  821 + /// {
  822 + /// "memberId": "会员ID",
  823 + /// "memberCode": "会员编号(档案号)",
  824 + /// "memberName": "会员名称",
  825 + /// "memberPhone": "会员电话",
  826 + /// "belongStoreId": "所属门店ID",
  827 + /// "belongStoreName": "所属门店名称",
  828 + /// "billingItems": [
  829 + /// {
  830 + /// "billingId": "开单ID",
  831 + /// "billingTime": "2025-11-15T10:00:00",
  832 + /// "billingStoreId": "开单门店ID",
  833 + /// "billingStoreName": "开单门店名称",
  834 + /// "billingUserId": "开单人ID",
  835 + /// "billingUserName": "开单人名称",
  836 + /// "purchaseItems": [
  837 + /// {
  838 + /// "itemId": "品项ID",
  839 + /// "itemName": "品项名称",
  840 + /// "itemPrice": 100.00,
  841 + /// "projectNumber": 5.0,
  842 + /// "totalAmount": 500.00
  843 + /// }
  844 + /// ],
  845 + /// "purchaseItemsTotalAmount": 500.00,
  846 + /// "giftItems": [
  847 + /// {
  848 + /// "itemId": "品项ID",
  849 + /// "itemName": "品项名称",
  850 + /// "itemPrice": 50.00,
  851 + /// "projectNumber": 2.0,
  852 + /// "totalAmount": 100.00
  853 + /// }
  854 + /// ],
  855 + /// "experienceItems": [
  856 + /// {
  857 + /// "itemId": "品项ID",
  858 + /// "itemName": "品项名称",
  859 + /// "itemPrice": 30.00,
  860 + /// "projectNumber": 1.0,
  861 + /// "totalAmount": 30.00
  862 + /// }
  863 + /// ],
  864 + /// "consumedItems": [
  865 + /// {
  866 + /// "itemId": "品项ID",
  867 + /// "itemName": "品项名称",
  868 + /// "itemPrice": 100.00,
  869 + /// "projectNumber": 2.0,
  870 + /// "totalAmount": 200.00
  871 + /// }
  872 + /// ],
  873 + /// "consumedItemsTotalAmount": 200.00,
  874 + /// "refundedItems": [
  875 + /// {
  876 + /// "itemId": "品项ID",
  877 + /// "itemName": "品项名称",
  878 + /// "itemPrice": 100.00,
  879 + /// "projectNumber": 1.0,
  880 + /// "totalAmount": 100.00
  881 + /// }
  882 + /// ],
  883 + /// "refundedItemsTotalAmount": 100.00,
  884 + /// "deductedItems": [
  885 + /// {
  886 + /// "itemId": "品项ID",
  887 + /// "itemName": "品项名称",
  888 + /// "itemPrice": 100.00,
  889 + /// "projectNumber": 1.0,
  890 + /// "totalAmount": 100.00
  891 + /// }
  892 + /// ],
  893 + /// "deductedItemsTotalAmount": 100.00
  894 + /// }
  895 + /// ]
  896 + /// }
  897 + /// ]
  898 + /// }
  899 + /// ```
  900 + ///
  901 + /// 返回字段说明:
  902 + ///
  903 + /// **分页信息 (pagination)**:
  904 + /// - pageIndex: 当前页码
  905 + /// - pageSize: 每页数量
  906 + /// - total: 总记录数
  907 + ///
  908 + /// **会员信息 (list[].memberInfo)**:
  909 + /// - memberId: 会员ID
  910 + /// - memberCode: 会员编号(档案号)
  911 + /// - memberName: 会员名称
  912 + /// - memberPhone: 会员电话
  913 + /// - belongStoreId: 所属门店ID
  914 + /// - belongStoreName: 所属门店名称
  915 + ///
  916 + /// **开单信息 (list[].billingItems[])**:
  917 + /// - billingId: 开单ID
  918 + /// - billingTime: 开单时间(DateTime格式)
  919 + /// - billingStoreId: 开单门店ID
  920 + /// - billingStoreName: 开单门店名称
  921 + /// - billingUserId: 开单人ID
  922 + /// - billingUserName: 开单人名称
  923 + ///
  924 + /// **品项信息 (list[].billingItems[].*Items[])**:
  925 + /// - purchaseItems: 购买品项列表
  926 + /// - purchaseItemsTotalAmount: 购买品项总金额
  927 + /// - giftItems: 赠送品项列表
  928 + /// - experienceItems: 体验品项列表
  929 + /// - consumedItems: 已消耗品项列表
  930 + /// - consumedItemsTotalAmount: 已消耗品项总金额
  931 + /// - refundedItems: 已退卡品项列表
  932 + /// - refundedItemsTotalAmount: 已退卡品项总金额
  933 + /// - deductedItems: 已储扣品项列表
  934 + /// - deductedItemsTotalAmount: 已储扣品项总金额
  935 + ///
  936 + /// **品项明细 (ItemDetail)**:
  937 + /// - itemId: 品项ID
  938 + /// - itemName: 品项名称
  939 + /// - itemPrice: 品项单价
  940 + /// - projectNumber: 项目次数(支持小数)
  941 + /// - totalAmount: 总金额(单价 × 次数)
  942 + /// </remarks>
  943 + /// <param name="input">查询参数</param>
  944 + /// <returns>会员品项信息列表(分页)</returns>
  945 + /// <response code="200">查询成功,返回会员品项信息列表</response>
  946 + /// <response code="400">参数错误,如页码或页大小无效</response>
  947 + /// <response code="500">服务器错误,查询过程中发生异常</response>
  948 + [HttpPost("get-member-item-info")]
  949 + public async Task<dynamic> GetMemberItemInfo([FromBody] MemberItemInfoQueryInput input)
  950 + {
  951 + try
  952 + {
  953 + // 1. 构建会员查询条件
  954 + var memberQuery = _db.Queryable<LqKhxxEntity>();
  955 +
  956 + // 筛选条件
  957 + if (!string.IsNullOrEmpty(input.MemberId))
  958 + {
  959 + memberQuery = memberQuery.Where(x => x.Id == input.MemberId);
  960 + }
  961 +
  962 + if (!string.IsNullOrEmpty(input.BelongStoreId))
  963 + {
  964 + memberQuery = memberQuery.Where(x => x.Gsmd == input.BelongStoreId);
  965 + }
  966 +
  967 + // 如果指定了开单门店、开单人、品项ID等条件,需要通过开单表进行筛选
  968 + if (!string.IsNullOrEmpty(input.BillingStoreId) ||
  969 + !string.IsNullOrEmpty(input.BillingUserId) ||
  970 + !string.IsNullOrEmpty(input.PurchaseItemId) ||
  971 + !string.IsNullOrEmpty(input.GiftItemId) ||
  972 + !string.IsNullOrEmpty(input.ExperienceItemId))
  973 + {
  974 + // 先查询符合条件的开单记录,获取会员ID列表
  975 + var billingQuery = _db.Queryable<LqKdKdjlbEntity>()
  976 + .Where(x => x.IsEffective == StatusEnum.有效.GetHashCode());
  977 +
  978 + if (!string.IsNullOrEmpty(input.BillingStoreId))
  979 + {
  980 + billingQuery = billingQuery.Where(x => x.Djmd == input.BillingStoreId);
  981 + }
  982 +
  983 + if (!string.IsNullOrEmpty(input.BillingUserId))
  984 + {
  985 + billingQuery = billingQuery.Where(x => x.CreateUser == input.BillingUserId);
  986 + }
  987 +
  988 + // 如果指定了品项ID,需要通过品项明细表进行筛选
  989 + if (!string.IsNullOrEmpty(input.PurchaseItemId) ||
  990 + !string.IsNullOrEmpty(input.GiftItemId) ||
  991 + !string.IsNullOrEmpty(input.ExperienceItemId))
  992 + {
  993 + var itemQuery = _db.Queryable<LqKdPxmxEntity>()
  994 + .Where(x => x.IsEffective == StatusEnum.有效.GetHashCode());
  995 +
  996 + if (!string.IsNullOrEmpty(input.PurchaseItemId))
  997 + {
  998 + itemQuery = itemQuery.Where(x => x.Px == input.PurchaseItemId && x.SourceType == "购买");
  999 + }
  1000 +
  1001 + if (!string.IsNullOrEmpty(input.GiftItemId))
  1002 + {
  1003 + itemQuery = itemQuery.Where(x => x.Px == input.GiftItemId && x.SourceType == "赠送");
  1004 + }
  1005 +
  1006 + if (!string.IsNullOrEmpty(input.ExperienceItemId))
  1007 + {
  1008 + itemQuery = itemQuery.Where(x => x.Px == input.ExperienceItemId && x.SourceType == "体验");
  1009 + }
  1010 +
  1011 + var itemBillingIds = await itemQuery.Select(x => x.Glkdbh).Distinct().ToListAsync();
  1012 + if (itemBillingIds != null && itemBillingIds.Any())
  1013 + {
  1014 + billingQuery = billingQuery.Where(x => itemBillingIds.Contains(x.Id));
  1015 + }
  1016 + else
  1017 + {
  1018 + // 如果没有匹配的品项记录,返回空结果
  1019 + return PageResult<MemberItemInfoOutput>.SqlSugarPageResult(new SqlSugarPagedList<MemberItemInfoOutput>
  1020 + {
  1021 + list = new List<MemberItemInfoOutput>(),
  1022 + pagination = new PagedModel
  1023 + {
  1024 + PageIndex = input.currentPage,
  1025 + PageSize = input.pageSize,
  1026 + Total = 0
  1027 + }
  1028 + });
  1029 + }
  1030 + }
  1031 +
  1032 + var memberIds = await billingQuery.Select(x => x.Kdhy).Distinct().ToListAsync();
  1033 + if (memberIds != null && memberIds.Any())
  1034 + {
  1035 + memberQuery = memberQuery.Where(x => memberIds.Contains(x.Id));
  1036 + }
  1037 + else
  1038 + {
  1039 + // 如果没有匹配的开单记录,返回空结果
  1040 + return PageResult<MemberItemInfoOutput>.SqlSugarPageResult(new SqlSugarPagedList<MemberItemInfoOutput>
  1041 + {
  1042 + list = new List<MemberItemInfoOutput>(),
  1043 + pagination = new PagedModel
  1044 + {
  1045 + PageIndex = input.currentPage,
  1046 + PageSize = input.pageSize,
  1047 + Total = 0
  1048 + }
  1049 + });
  1050 + }
  1051 + }
  1052 +
  1053 + // 2. 分页查询会员列表
  1054 + var totalCount = await memberQuery.CountAsync();
  1055 + var skipCount = (input.currentPage - 1) * input.pageSize;
  1056 + var memberList = await memberQuery
  1057 + .OrderBy(x => x.Id)
  1058 + .Skip(skipCount)
  1059 + .Take(input.pageSize)
  1060 + .Select(x => new
  1061 + {
  1062 + x.Id,
  1063 + x.Dah,
  1064 + x.Khmc,
  1065 + x.Sjh,
  1066 + x.Gsmd
  1067 + })
  1068 + .ToListAsync();
  1069 +
  1070 + var memberData = new SqlSugarPagedList<dynamic>
  1071 + {
  1072 + list = memberList.Cast<dynamic>().ToList(),
  1073 + pagination = new PagedModel
  1074 + {
  1075 + PageIndex = input.currentPage,
  1076 + PageSize = input.pageSize,
  1077 + Total = totalCount
  1078 + }
  1079 + };
  1080 +
  1081 + if (memberList == null || !memberList.Any())
  1082 + {
  1083 + return PageResult<MemberItemInfoOutput>.SqlSugarPageResult(new SqlSugarPagedList<MemberItemInfoOutput>
  1084 + {
  1085 + list = new List<MemberItemInfoOutput>(),
  1086 + pagination = new PagedModel
  1087 + {
  1088 + PageIndex = input.currentPage,
  1089 + PageSize = input.pageSize,
  1090 + Total = totalCount
  1091 + }
  1092 + });
  1093 + }
  1094 +
  1095 + var memberIdsList = memberList.Select(x => x.Id).ToList();
  1096 +
  1097 + // 3. 批量查询会员的开单记录
  1098 + var billingRecords = await _db.Queryable<LqKdKdjlbEntity>()
  1099 + .Where(x => memberIdsList.Contains(x.Kdhy) && x.IsEffective == StatusEnum.有效.GetHashCode())
  1100 + .Select(x => new
  1101 + {
  1102 + x.Id,
  1103 + x.Kdhy,
  1104 + x.Kdrq,
  1105 + x.Djmd,
  1106 + x.CreateUser
  1107 + })
  1108 + .ToListAsync();
  1109 +
  1110 + var billingIds = billingRecords.Select(x => x.Id).ToList();
  1111 +
  1112 + // 4. 批量查询开单的品项明细
  1113 + var itemDetails = new List<dynamic>();
  1114 + if (billingIds.Any())
  1115 + {
  1116 + var itemDetailsData = await _db.Queryable<LqKdPxmxEntity>()
  1117 + .Where(x => billingIds.Contains(x.Glkdbh) && x.IsEffective == StatusEnum.有效.GetHashCode())
  1118 + .Select(x => new
  1119 + {
  1120 + x.Id,
  1121 + x.Glkdbh,
  1122 + x.Px,
  1123 + x.Pxmc,
  1124 + x.Pxjg,
  1125 + x.SourceType,
  1126 + x.ProjectNumber,
  1127 + x.TotalPrice
  1128 + })
  1129 + .ToListAsync();
  1130 + itemDetails = itemDetailsData.Cast<dynamic>().ToList();
  1131 + }
  1132 +
  1133 + var itemDetailIds = itemDetails.Select(x => (string)x.Id).ToList();
  1134 +
  1135 + // 5. 批量查询消耗品项
  1136 + var consumedItems = new List<dynamic>();
  1137 + if (itemDetailIds.Any())
  1138 + {
  1139 + var consumedItemsData = await _db.Queryable<LqXhPxmxEntity>()
  1140 + .Where(x => itemDetailIds.Contains(x.BillingItemId) && x.IsEffective == StatusEnum.有效.GetHashCode())
  1141 + .Select(x => new
  1142 + {
  1143 + x.BillingItemId,
  1144 + x.Px,
  1145 + x.Pxmc,
  1146 + x.Pxjg,
  1147 + x.ProjectNumber,
  1148 + x.TotalPrice
  1149 + })
  1150 + .ToListAsync();
  1151 + consumedItems = consumedItemsData.Cast<dynamic>().ToList();
  1152 + }
  1153 +
  1154 + // 6. 批量查询退卡品项
  1155 + var refundedItems = new List<dynamic>();
  1156 + if (itemDetailIds.Any())
  1157 + {
  1158 + var refundedItemsData = await _db.Queryable<LqHytkMxEntity>()
  1159 + .Where(x => itemDetailIds.Contains(x.BillingItemId) && x.IsEffective == StatusEnum.有效.GetHashCode())
  1160 + .Select(x => new
  1161 + {
  1162 + x.BillingItemId,
  1163 + x.Px,
  1164 + x.Pxmc,
  1165 + x.Pxjg,
  1166 + x.ProjectNumber,
  1167 + x.Tkje
  1168 + })
  1169 + .ToListAsync();
  1170 + refundedItems = refundedItemsData.Cast<dynamic>().ToList();
  1171 + }
  1172 +
  1173 + // 7. 批量查询储扣品项
  1174 + var deductedItems = new List<dynamic>();
  1175 + if (billingIds.Any())
  1176 + {
  1177 + var deductedItemsData = await _db.Queryable<LqKdDeductinfoEntity>()
  1178 + .Where(x => billingIds.Contains(x.BillingId) && x.IsEffective == StatusEnum.有效.GetHashCode())
  1179 + .Select(x => new
  1180 + {
  1181 + x.BillingId,
  1182 + x.ItemId,
  1183 + x.ItemName,
  1184 + x.UnitPrice,
  1185 + x.ProjectNumber,
  1186 + x.Amount
  1187 + })
  1188 + .ToListAsync();
  1189 + deductedItems = deductedItemsData.Cast<dynamic>().ToList();
  1190 + }
  1191 +
  1192 + // 8. 批量查询门店信息
  1193 + var storeIds = new List<string>();
  1194 + if (memberList != null && memberList.Any())
  1195 + {
  1196 + storeIds.AddRange(memberList.Select(x => x.Gsmd).Where(x => !string.IsNullOrEmpty(x)));
  1197 + }
  1198 + if (billingRecords != null && billingRecords.Any())
  1199 + {
  1200 + storeIds.AddRange(billingRecords.Select(x => x.Djmd).Where(x => !string.IsNullOrEmpty(x)));
  1201 + }
  1202 + storeIds = storeIds.Distinct().ToList();
  1203 +
  1204 + var stores = new Dictionary<string, string>();
  1205 + if (storeIds.Any())
  1206 + {
  1207 + var storeList = await _db.Queryable<LqMdxxEntity>()
  1208 + .Where(x => storeIds.Contains(x.Id))
  1209 + .Select(x => new { x.Id, x.Dm })
  1210 + .ToListAsync();
  1211 +
  1212 + stores = storeList.ToDictionary(x => x.Id, x => x.Dm ?? "");
  1213 + }
  1214 +
  1215 + // 9. 批量查询用户信息
  1216 + var userIds = new List<string>();
  1217 + if (billingRecords != null && billingRecords.Any())
  1218 + {
  1219 + userIds = billingRecords.Select(x => x.CreateUser)
  1220 + .Where(x => !string.IsNullOrEmpty(x))
  1221 + .Distinct()
  1222 + .ToList();
  1223 + }
  1224 +
  1225 + var users = new Dictionary<string, string>();
  1226 + if (userIds.Any())
  1227 + {
  1228 + var userList = await _db.Queryable<UserEntity>()
  1229 + .Where(x => userIds.Contains(x.Id))
  1230 + .Select(x => new { x.Id, x.RealName })
  1231 + .ToListAsync();
  1232 +
  1233 + users = userList.ToDictionary(x => x.Id, x => x.RealName ?? "");
  1234 + }
  1235 +
  1236 + // 10. 组装数据
  1237 + var result = new List<MemberItemInfoOutput>();
  1238 +
  1239 + foreach (var member in memberList)
  1240 + {
  1241 + var memberOutput = new MemberItemInfoOutput
  1242 + {
  1243 + MemberId = member.Id,
  1244 + MemberCode = member.Dah ?? "",
  1245 + MemberName = member.Khmc ?? "",
  1246 + MemberPhone = member.Sjh ?? "",
  1247 + BelongStoreId = member.Gsmd ?? "",
  1248 + BelongStoreName = stores.ContainsKey(member.Gsmd ?? "") ? stores[member.Gsmd] : ""
  1249 + };
  1250 +
  1251 + // 获取该会员的所有开单
  1252 + var memberBillings = billingRecords.Where(x => x.Kdhy == member.Id).ToList();
  1253 +
  1254 + foreach (var billing in memberBillings)
  1255 + {
  1256 + var billingItem = new BillingItemInfo
  1257 + {
  1258 + BillingId = billing.Id,
  1259 + BillingTime = billing.Kdrq,
  1260 + BillingStoreId = billing.Djmd ?? "",
  1261 + BillingStoreName = stores.ContainsKey(billing.Djmd ?? "") ? stores[billing.Djmd] : "",
  1262 + BillingUserId = billing.CreateUser ?? "",
  1263 + BillingUserName = users.ContainsKey(billing.CreateUser ?? "") ? users[billing.CreateUser] : ""
  1264 + };
  1265 +
  1266 + // 获取该开单的所有品项明细
  1267 + var billingItemDetails = itemDetails.Where(x =>
  1268 + {
  1269 + try
  1270 + {
  1271 + return x.Glkdbh?.ToString() == billing.Id;
  1272 + }
  1273 + catch
  1274 + {
  1275 + return false;
  1276 + }
  1277 + }).ToList();
  1278 +
  1279 + // 分类品项
  1280 + foreach (var item in billingItemDetails)
  1281 + {
  1282 + try
  1283 + {
  1284 + var itemDetail = new ItemDetail
  1285 + {
  1286 + ItemId = item.Px?.ToString() ?? "",
  1287 + ItemName = item.Pxmc?.ToString() ?? "",
  1288 + ItemPrice = Convert.ToDecimal(item.Pxjg ?? 0),
  1289 + ProjectNumber = Convert.ToDecimal(item.ProjectNumber ?? 0),
  1290 + TotalAmount = Convert.ToDecimal(item.TotalPrice ?? 0)
  1291 + };
  1292 +
  1293 + var sourceType = item.SourceType?.ToString() ?? "";
  1294 + if (sourceType == "购买")
  1295 + {
  1296 + billingItem.PurchaseItems.Add(itemDetail);
  1297 + billingItem.PurchaseItemsTotalAmount += itemDetail.TotalAmount;
  1298 + }
  1299 + else if (sourceType == "赠送")
  1300 + {
  1301 + billingItem.GiftItems.Add(itemDetail);
  1302 + }
  1303 + else if (sourceType == "体验")
  1304 + {
  1305 + billingItem.ExperienceItems.Add(itemDetail);
  1306 + }
  1307 + }
  1308 + catch (Exception ex)
  1309 + {
  1310 + _logger.LogWarning(ex, "处理品项明细时出错,开单ID:{BillingId}", billing.Id);
  1311 + }
  1312 + }
  1313 +
  1314 + // 获取消耗品项
  1315 + var billingItemIds = billingItemDetails.Select(x =>
  1316 + {
  1317 + try
  1318 + {
  1319 + return x.Id?.ToString() ?? "";
  1320 + }
  1321 + catch
  1322 + {
  1323 + return "";
  1324 + }
  1325 + }).Where(x => !string.IsNullOrEmpty(x)).ToList();
  1326 +
  1327 + var consumed = consumedItems.Where(x =>
  1328 + {
  1329 + try
  1330 + {
  1331 + return billingItemIds.Contains(x.BillingItemId?.ToString() ?? "");
  1332 + }
  1333 + catch
  1334 + {
  1335 + return false;
  1336 + }
  1337 + }).ToList();
  1338 +
  1339 + foreach (var consumedItem in consumed)
  1340 + {
  1341 + try
  1342 + {
  1343 + billingItem.ConsumedItems.Add(new ItemDetail
  1344 + {
  1345 + ItemId = consumedItem.Px?.ToString() ?? "",
  1346 + ItemName = consumedItem.Pxmc?.ToString() ?? "",
  1347 + ItemPrice = Convert.ToDecimal(consumedItem.Pxjg ?? 0),
  1348 + ProjectNumber = Convert.ToDecimal(consumedItem.ProjectNumber ?? 0),
  1349 + TotalAmount = Convert.ToDecimal(consumedItem.TotalPrice ?? 0)
  1350 + });
  1351 + billingItem.ConsumedItemsTotalAmount += Convert.ToDecimal(consumedItem.TotalPrice ?? 0);
  1352 + }
  1353 + catch (Exception ex)
  1354 + {
  1355 + _logger.LogWarning(ex, "处理消耗品项时出错");
  1356 + }
  1357 + }
  1358 +
  1359 + // 获取退卡品项
  1360 + var refunded = refundedItems.Where(x =>
  1361 + {
  1362 + try
  1363 + {
  1364 + return billingItemIds.Contains(x.BillingItemId?.ToString() ?? "");
  1365 + }
  1366 + catch
  1367 + {
  1368 + return false;
  1369 + }
  1370 + }).ToList();
  1371 +
  1372 + foreach (var refundedItem in refunded)
  1373 + {
  1374 + try
  1375 + {
  1376 + billingItem.RefundedItems.Add(new ItemDetail
  1377 + {
  1378 + ItemId = refundedItem.Px?.ToString() ?? "",
  1379 + ItemName = refundedItem.Pxmc?.ToString() ?? "",
  1380 + ItemPrice = Convert.ToDecimal(refundedItem.Pxjg ?? 0),
  1381 + ProjectNumber = Convert.ToDecimal(refundedItem.ProjectNumber ?? 0),
  1382 + TotalAmount = Convert.ToDecimal(refundedItem.Tkje ?? 0)
  1383 + });
  1384 + billingItem.RefundedItemsTotalAmount += Convert.ToDecimal(refundedItem.Tkje ?? 0);
  1385 + }
  1386 + catch (Exception ex)
  1387 + {
  1388 + _logger.LogWarning(ex, "处理退卡品项时出错");
  1389 + }
  1390 + }
  1391 +
  1392 + // 获取储扣品项
  1393 + var deducted = deductedItems.Where(x =>
  1394 + {
  1395 + try
  1396 + {
  1397 + return x.BillingId?.ToString() == billing.Id;
  1398 + }
  1399 + catch
  1400 + {
  1401 + return false;
  1402 + }
  1403 + }).ToList();
  1404 +
  1405 + foreach (var deductedItem in deducted)
  1406 + {
  1407 + try
  1408 + {
  1409 + billingItem.DeductedItems.Add(new ItemDetail
  1410 + {
  1411 + ItemId = deductedItem.ItemId?.ToString() ?? "",
  1412 + ItemName = deductedItem.ItemName?.ToString() ?? "",
  1413 + ItemPrice = Convert.ToDecimal(deductedItem.UnitPrice ?? 0),
  1414 + ProjectNumber = Convert.ToDecimal(deductedItem.ProjectNumber ?? 0),
  1415 + TotalAmount = Convert.ToDecimal(deductedItem.Amount ?? 0)
  1416 + });
  1417 + billingItem.DeductedItemsTotalAmount += Convert.ToDecimal(deductedItem.Amount ?? 0);
  1418 + }
  1419 + catch (Exception ex)
  1420 + {
  1421 + _logger.LogWarning(ex, "处理储扣品项时出错");
  1422 + }
  1423 + }
  1424 +
  1425 + memberOutput.BillingItems.Add(billingItem);
  1426 + }
  1427 +
  1428 + result.Add(memberOutput);
  1429 + }
  1430 +
  1431 + return PageResult<MemberItemInfoOutput>.SqlSugarPageResult(new SqlSugarPagedList<MemberItemInfoOutput>
  1432 + {
  1433 + list = result,
  1434 + pagination = new PagedModel
  1435 + {
  1436 + PageIndex = input.currentPage,
  1437 + PageSize = input.pageSize,
  1438 + Total = totalCount
  1439 + }
  1440 + });
  1441 + }
  1442 + catch (Exception ex)
  1443 + {
  1444 + _logger.LogError(ex, "查询会员品项信息失败,异常详情:{ExceptionDetail}", ex.ToString());
  1445 + throw NCCException.Oh(ErrorCode.COM1005, $"查询会员品项信息失败: {ex.Message}");
  1446 + }
  1447 + }
  1448 + #endregion
  1449 +
779 1450 }
780 1451 }
... ...
netcore/src/Modularity/Extend/NCC.Extend/LqXhHyhkService.cs
... ... @@ -971,6 +971,8 @@ namespace NCC.Extend.LqXhHyhk
971 971 {
972 972 memberInfo.Khlx = MemberTypeEnum.新客.GetHashCode().ToString();
973 973 }
  974 + //保存会员信息
  975 + await _db.Updateable(memberInfo).ExecuteCommandAsync();
974 976 // 新增会员耗卡记录
975 977 var newEntity = await _db.Insertable(entity).IgnoreColumns(ignoreNullColumn: true).ExecuteReturnEntityAsync();
976 978 // 收集所有需要插入的实体,然后批量插入
... ... @@ -1265,6 +1267,15 @@ namespace NCC.Extend.LqXhHyhk
1265 1267 //更新会员耗卡记录
1266 1268 await _db.Updateable(entity).IgnoreColumns(ignoreAllNullColumns: true).ExecuteCommandAsync();
1267 1269  
  1270 + var memberInfo = await _db.Queryable<LqKhxxEntity>().Where(w => w.Id == entity.Hy).FirstAsync();
  1271 + //如果会员类型是线索,那么就更新为新客
  1272 + if (memberInfo.Khlx == MemberTypeEnum.线索.GetHashCode().ToString())
  1273 + {
  1274 + memberInfo.Khlx = MemberTypeEnum.新客.GetHashCode().ToString();
  1275 + }
  1276 + //保存会员信息
  1277 + await _db.Updateable(memberInfo).ExecuteCommandAsync();
  1278 +
1268 1279 //清空原有数据
1269 1280 await _db.Deleteable<LqXhJksyjEntity>().Where(u => u.Glkdbh == id).ExecuteCommandAsync();
1270 1281 await _db.Deleteable<LqXhKjbsyjEntity>().Where(u => u.Glkdbh == id).ExecuteCommandAsync();
... ...
netcore/src/Modularity/Extend/NCC.Extend/LqXmzlService.cs
... ... @@ -817,6 +817,7 @@ namespace NCC.Extend.LqXmzl
817 817 }
818 818 #endregion
819 819  
  820 +
820 821 }
821 822 /// <summary>
822 823 /// 品项统计数据(内部类)
... ...
sql/更新潜客为新客.sql 0 → 100644
  1 +-- ============================================
  2 +-- 更新会员类型:将存在耗卡记录的潜客(0)更新为新客(1)
  3 +-- ============================================
  4 +--
  5 +-- 功能说明:
  6 +-- 1. 查找会员类型为 0(潜客/线索)的会员
  7 +-- 2. 如果该会员存在有效的耗卡记录(lq_xh_hyhk 表中有记录且 F_IsEffective = 1)
  8 +-- 3. 将该会员的类型更新为 1(新客)
  9 +--
  10 +-- 执行前建议:
  11 +-- 1. 先执行查询语句查看会更新多少条记录
  12 +-- 2. 确认无误后再执行更新语句
  13 +-- ============================================
  14 +
  15 +-- 1. 查询语句:查看将要更新的会员数量和信息
  16 +SELECT
  17 + kh.F_Id AS 会员ID,
  18 + kh.khmc AS 会员名称,
  19 + kh.sjh AS 手机号,
  20 + kh.khlx AS 当前类型,
  21 + COUNT(hyhk.F_Id) AS 耗卡记录数
  22 +FROM lq_khxx kh
  23 +INNER JOIN lq_xh_hyhk hyhk ON kh.F_Id = hyhk.hy
  24 +WHERE kh.khlx = '0' -- 会员类型为 0(潜客/线索)
  25 + AND hyhk.F_IsEffective = 1 -- 耗卡记录有效
  26 +GROUP BY kh.F_Id, kh.khmc, kh.sjh, kh.khlx;
  27 +
  28 +-- 2. 更新语句:将存在耗卡记录的潜客更新为新客
  29 +UPDATE lq_khxx kh
  30 +SET kh.khlx = '1' -- 更新为新客(1)
  31 +WHERE kh.khlx = '0' -- 会员类型为 0(潜客/线索)
  32 + AND EXISTS (
  33 + -- 确保至少有一条有效的耗卡记录
  34 + SELECT 1
  35 + FROM lq_xh_hyhk hyhk
  36 + WHERE hyhk.hy = kh.F_Id
  37 + AND hyhk.F_IsEffective = 1
  38 + LIMIT 1
  39 + );
  40 +
  41 +-- 3. 验证语句:查看更新后的结果
  42 +SELECT
  43 + kh.F_Id AS 会员ID,
  44 + kh.khmc AS 会员名称,
  45 + kh.sjh AS 手机号,
  46 + kh.khlx AS 更新后类型,
  47 + COUNT(hyhk.F_Id) AS 耗卡记录数
  48 +FROM lq_khxx kh
  49 +INNER JOIN lq_xh_hyhk hyhk ON kh.F_Id = hyhk.hy
  50 +WHERE kh.khlx = '1' -- 会员类型为 1(新客)
  51 + AND hyhk.F_IsEffective = 1 -- 耗卡记录有效
  52 +GROUP BY kh.F_Id, kh.khmc, kh.sjh, kh.khlx
  53 +HAVING COUNT(hyhk.F_Id) > 0;
  54 +
... ...