Commit e97f24a6b626ceb8ad2c7d90633aa202d7bb9cab

Authored by “wangming”
1 parent 9825c841

feat: 新增开单品项明细记录列表查询接口

- 新增开单品项明细记录列表查询接口,支持多条件筛选和分页
- 优化查询性能,采用先分页后关联的查询方式
- 支持筛选条件:品项明细ID、开单ID、开单时间、营销活动、会员信息、品项信息等
- 返回字段:id、开单id、开单时间、营销活动名称、会员名、会员手机、品项名称、品项类型、实付金额、项目次数、来源类型、备注
- 更新接口文档,完善参数说明和返回字段说明
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/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
... ... @@ -1745,6 +1745,8 @@ namespace NCC.Extend
1745 1745 }
1746 1746 #endregion
1747 1747  
  1748 +
  1749 +
1748 1750 #region 获取储值扣减金额统计
1749 1751 /// <summary>
1750 1752 /// 获取储值扣减金额统计
... ...
netcore/src/Modularity/Extend/NCC.Extend/LqKdKdjlbService.cs
... ... @@ -3211,5 +3211,218 @@ 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
  3343 + .WhereIF(!string.IsNullOrEmpty(input.MemberName), pxmx =>
  3344 + SqlFunc.Subqueryable<LqKhxxEntity>().Where(x => x.Id == pxmx.MemberId && x.Khmc != null && x.Khmc.Contains(input.MemberName)).Any())
  3345 + .WhereIF(!string.IsNullOrEmpty(input.MemberPhone), pxmx =>
  3346 + SqlFunc.Subqueryable<LqKhxxEntity>().Where(x => x.Id == pxmx.MemberId && x.Sjh == input.MemberPhone).Any())
  3347 + .WhereIF(!string.IsNullOrEmpty(input.ItemType), pxmx =>
  3348 + SqlFunc.Subqueryable<LqXmzlEntity>().Where(x => x.Id == pxmx.Px && x.Fl4 == input.ItemType).Any());
  3349 +
  3350 + // 3. 先分页查询主表数据(查询实体类,提高性能)
  3351 + var pagedData = await baseQuery
  3352 + .OrderBy(sidx + " " + sort)
  3353 + .ToPagedListAsync(input.currentPage, input.pageSize);
  3354 +
  3355 + // 4. 批量查询关联数据
  3356 + var itemIds = pagedData.list.Select(x => x.Id).ToList();
  3357 + var memberIds = pagedData.list.Where(x => !string.IsNullOrEmpty(x.MemberId)).Select(x => x.MemberId).Distinct().ToList();
  3358 + var activityIds = pagedData.list.Where(x => !string.IsNullOrEmpty(x.ActivityId)).Select(x => x.ActivityId).Distinct().ToList();
  3359 + var projectIds = pagedData.list.Where(x => !string.IsNullOrEmpty(x.Px)).Select(x => x.Px).Distinct().ToList();
  3360 +
  3361 + // 批量查询会员信息
  3362 + var memberDict = new Dictionary<string, (string Name, string Phone)>();
  3363 + if (memberIds.Any())
  3364 + {
  3365 + var members = await _db.Queryable<LqKhxxEntity>()
  3366 + .Where(x => memberIds.Contains(x.Id))
  3367 + .Select(x => new { x.Id, x.Khmc, x.Sjh })
  3368 + .ToListAsync();
  3369 + memberDict = members.ToDictionary(x => x.Id, x => (x.Khmc ?? "", x.Sjh ?? ""));
  3370 + }
  3371 +
  3372 + // 批量查询活动信息
  3373 + var activityDict = new Dictionary<string, string>();
  3374 + if (activityIds.Any())
  3375 + {
  3376 + var activities = await _db.Queryable<LqPackageInfoEntity>()
  3377 + .Where(x => activityIds.Contains(x.Id))
  3378 + .Select(x => new { x.Id, x.ActivityName })
  3379 + .ToListAsync();
  3380 + activityDict = activities.ToDictionary(x => x.Id, x => x.ActivityName ?? "");
  3381 + }
  3382 +
  3383 + // 批量查询项目资料
  3384 + var projectDict = new Dictionary<string, string>();
  3385 + if (projectIds.Any())
  3386 + {
  3387 + var projects = await _db.Queryable<LqXmzlEntity>()
  3388 + .Where(x => projectIds.Contains(x.Id))
  3389 + .Select(x => new { x.Id, x.Qt2 })
  3390 + .ToListAsync();
  3391 + projectDict = projects.ToDictionary(x => x.Id, x => x.Qt2 ?? "");
  3392 + }
  3393 +
  3394 + // 5. 组装返回数据
  3395 + var resultList = pagedData.list.Select(pxmx => new BillingItemDetailListOutput
  3396 + {
  3397 + id = pxmx.Id,
  3398 + billingId = pxmx.Glkdbh,
  3399 + billingTime = pxmx.Yjsj,
  3400 + activityName = pxmx.ActivityId != null && activityDict.ContainsKey(pxmx.ActivityId) ? activityDict[pxmx.ActivityId] : "",
  3401 + memberName = pxmx.MemberId != null && memberDict.ContainsKey(pxmx.MemberId) ? memberDict[pxmx.MemberId].Name : "",
  3402 + memberPhone = pxmx.MemberId != null && memberDict.ContainsKey(pxmx.MemberId) ? memberDict[pxmx.MemberId].Phone : "",
  3403 + itemName = pxmx.Pxmc,
  3404 + itemType = pxmx.Px != null && projectDict.ContainsKey(pxmx.Px) ? projectDict[pxmx.Px] : "",
  3405 + actualPrice = pxmx.ActualPrice,
  3406 + projectNumber = pxmx.ProjectNumber,
  3407 + sourceType = pxmx.SourceType,
  3408 + remark = pxmx.Remark
  3409 + }).ToList();
  3410 +
  3411 + // 6. 返回分页结果
  3412 + var result = new SqlSugarPagedList<BillingItemDetailListOutput>
  3413 + {
  3414 + list = resultList,
  3415 + pagination = pagedData.pagination
  3416 + };
  3417 +
  3418 + return PageResult<BillingItemDetailListOutput>.SqlSugarPageResult(result);
  3419 + }
  3420 + catch (Exception ex)
  3421 + {
  3422 + _logger.LogError(ex, $"获取开单品项明细记录列表失败: {ex.ToString()}");
  3423 + throw NCCException.Oh(ErrorCode.COM1005, $"获取开单品项明细记录列表失败: {ex.Message}");
  3424 + }
  3425 + }
  3426 + #endregion
3214 3427 }
3215 3428 }
... ...
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();
... ...
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 +
... ...