Commit 18e03666661cf934d7aa17088961aa5a5b1345d2

Authored by “wangming”
1 parent 5eea27e1

完善耗卡列表接口:修复多表查询别名问题,完善API注释文档

netcore/src/Modularity/Common/NCC.Common/Filter/PageInputBase.cs
... ... @@ -21,7 +21,7 @@ namespace NCC.Common.Filter
21 21 /// <summary>
22 22 /// 每页行数
23 23 /// </summary>
24   - public virtual int pageSize { get; set; } = 50;
  24 + public virtual int pageSize { get; set; } = 20;
25 25  
26 26 /// <summary>
27 27 /// 排序字段:sortField
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqXhHyhk/LqXhHyhkListByJksQueryInput.cs 0 → 100644
  1 +using NCC.Common.Filter;
  2 +
  3 +namespace NCC.Extend.Entitys.Dto.LqXhHyhk
  4 +{
  5 + /// <summary>
  6 + /// 根据健康师ID查询耗卡列表输入
  7 + /// </summary>
  8 + public class LqXhHyhkListByJksQueryInput : PageInputBase
  9 + {
  10 + /// <summary>
  11 + /// 健康师ID(必填)
  12 + /// </summary>
  13 + public string jksId { get; set; }
  14 +
  15 + /// <summary>
  16 + /// 开始时间(可选,格式:yyyy-MM-dd 或 yyyy-MM-dd HH:mm:ss)
  17 + /// </summary>
  18 + public string startTime { get; set; }
  19 +
  20 + /// <summary>
  21 + /// 结束时间(可选,格式:yyyy-MM-dd 或 yyyy-MM-dd HH:mm:ss)
  22 + /// </summary>
  23 + public string endTime { get; set; }
  24 +
  25 + /// <summary>
  26 + /// 门店ID(可选)
  27 + /// </summary>
  28 + public string md { get; set; }
  29 +
  30 + /// <summary>
  31 + /// 是否有效(可选,0=全部,1=有效,2=无效)
  32 + /// </summary>
  33 + public int isEffective { get; set; } = 1;
  34 + }
  35 +}
  36 +
  37 +
  38 +
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqXhHyhk/LqXhHyhkListByKjbQueryInput.cs 0 → 100644
  1 +using NCC.Common.Filter;
  2 +
  3 +namespace NCC.Extend.Entitys.Dto.LqXhHyhk
  4 +{
  5 + /// <summary>
  6 + /// 根据科技部老师ID查询耗卡列表输入
  7 + /// </summary>
  8 + public class LqXhHyhkListByKjbQueryInput : PageInputBase
  9 + {
  10 + /// <summary>
  11 + /// 科技部老师ID(必填)
  12 + /// </summary>
  13 + public string kjblsId { get; set; }
  14 +
  15 + /// <summary>
  16 + /// 开始时间(可选,格式:yyyy-MM-dd 或 yyyy-MM-dd HH:mm:ss)
  17 + /// </summary>
  18 + public string startTime { get; set; }
  19 +
  20 + /// <summary>
  21 + /// 结束时间(可选,格式:yyyy-MM-dd 或 yyyy-MM-dd HH:mm:ss)
  22 + /// </summary>
  23 + public string endTime { get; set; }
  24 +
  25 + /// <summary>
  26 + /// 门店ID(可选)
  27 + /// </summary>
  28 + public string md { get; set; }
  29 +
  30 + /// <summary>
  31 + /// 是否有效(可选,0=全部,1=有效,2=无效)
  32 + /// </summary>
  33 + public int isEffective { get; set; } = 1;
  34 + }
  35 +}
  36 +
  37 +
  38 +
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqXhHyhk/LqXhHyhkListOutput.cs
1 1 using System;
2 2 using System.Collections.Generic;
  3 +using NCC.Extend.Entitys.Dto.LqXhJksyj;
  4 +using NCC.Extend.Entitys.Dto.LqXhKjbsyj;
3 5 using NCC.Extend.Entitys.Dto.LqXhPxmx;
4 6  
5 7 namespace NCC.Extend.Entitys.Dto.LqXhHyhk
... ... @@ -110,5 +112,15 @@ namespace NCC.Extend.Entitys.Dto.LqXhHyhk
110 112 /// </summary>
111 113 public decimal? overtimeSgfy { get; set; }
112 114  
  115 + /// <summary>
  116 + /// 健康师业绩列表
  117 + /// </summary>
  118 + public List<LqXhJksyjInfoOutput> lqXhJksyjList { get; set; }
  119 +
  120 + /// <summary>
  121 + /// 科技部老师业绩列表
  122 + /// </summary>
  123 + public List<LqXhKjbsyjInfoOutput> lqXhKjbsyjList { get; set; }
  124 +
113 125 }
114 126 }
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqXhHyhk/LqXhHyhkListQueryInput.cs
... ... @@ -79,5 +79,15 @@ namespace NCC.Extend.Entitys.Dto.LqXhHyhk
79 79 /// </summary>
80 80 public int isEffective { get; set; } = 0;
81 81  
  82 + /// <summary>
  83 + /// 健康师ID(可选,用于过滤该健康师参与的耗卡)
  84 + /// </summary>
  85 + public string jksId { get; set; }
  86 +
  87 + /// <summary>
  88 + /// 科技部老师ID(可选,用于过滤该老师参与的耗卡)
  89 + /// </summary>
  90 + public string kjblsId { get; set; }
  91 +
82 92 }
83 93 }
... ...
netcore/src/Modularity/Extend/NCC.Extend/LqStatisticsService.cs
... ... @@ -3644,7 +3644,7 @@ namespace NCC.Extend.LqStatistics
3644 3644 SELECT COALESCE(SUM(pxmx.F_ProjectNumber), 0) as Count
3645 3645 FROM lq_kd_jksyj jksyj
3646 3646 INNER JOIN lq_kd_pxmx pxmx ON jksyj.F_kdpxid = pxmx.F_Id
3647   - WHERE jksyj.jkszh = '{userId}'
  3647 + WHERE jksyj.jks = '{userId}'
3648 3648 AND jksyj.F_IsEffective = 1
3649 3649 AND DATE_FORMAT(jksyj.yjsj, '%Y%m') = '{month}'";
3650 3650  
... ...
netcore/src/Modularity/Extend/NCC.Extend/LqXhHyhkService.cs
... ... @@ -172,12 +172,361 @@ namespace NCC.Extend.LqXhHyhk
172 172 /// <summary>
173 173 /// 获取会员耗卡列表
174 174 /// </summary>
175   - /// <param name="input">请求参数</param>
176   - /// <returns></returns>
  175 + /// <remarks>
  176 + /// 根据多种条件查询会员耗卡记录列表,支持分页、排序、筛选等功能
  177 + ///
  178 + /// 示例请求:
  179 + /// GET /api/Extend/LqXhHyhk/GetList?currentPage=1&amp;pageSize=10&amp;jksId=健康师ID&amp;startTime=2025-01-01&amp;endTime=2025-01-31
  180 + ///
  181 + /// 传入参数说明:
  182 + /// - currentPage: 当前页码(必填,默认1)
  183 + /// - pageSize: 每页数量(必填,默认10)
  184 + /// - sidx: 排序字段(可选,默认"id")
  185 + /// - sort: 排序方式(可选,ASC/DESC,默认DESC)
  186 + /// - keyword: 关键字搜索(可选,搜索会员名称、账号、手机号)
  187 + /// - id: 耗卡编号(可选,模糊匹配)
  188 + /// - md: 门店ID(可选,精确匹配)
  189 + /// - mdbh: 门店编号(可选,模糊匹配)
  190 + /// - mdmc: 门店名称(可选,模糊匹配)
  191 + /// - hy: 会员ID(可选,精确匹配)
  192 + /// - hyzh: 会员账号(可选,模糊匹配)
  193 + /// - hymc: 会员名称(可选,模糊匹配)
  194 + /// - gklx: 顾客类型(可选,精确匹配)
  195 + /// - sfykjb: 是否有科技部(可选,精确匹配)
  196 + /// - hksj: 耗卡时间(可选,格式:开始时间,结束时间,如:2025-01-01,2025-01-31)
  197 + /// - czry: 操作人员ID(可选,精确匹配)
  198 + /// - isEffective: 是否有效(可选,0=全部,1=有效,2=无效,默认0)
  199 + /// - jksId: 健康师ID(可选,传入后只返回该健康师参与的耗卡记录)
  200 + /// - kjblsId: 科技部老师ID(可选,传入后只返回该老师参与的耗卡记录)
  201 + ///
  202 + /// 返回结果说明:
  203 + /// - code: 响应状态码(200=成功)
  204 + /// - msg: 响应消息
  205 + /// - data: 分页数据对象
  206 + /// - list: 耗卡记录列表,每个记录包含:
  207 + /// - id: 耗卡编号
  208 + /// - md: 门店ID
  209 + /// - mdbh: 门店编号
  210 + /// - mdmc: 门店名称
  211 + /// - hy: 会员ID
  212 + /// - hyzh: 会员账号
  213 + /// - hymc: 会员名称
  214 + /// - memberPhone: 会员手机号
  215 + /// - gklx: 顾客类型
  216 + /// - xfje: 消费金额
  217 + /// - sgfy: 手工费用
  218 + /// - sfykjb: 是否有科技部
  219 + /// - hksj: 耗卡时间
  220 + /// - czry: 操作人员ID
  221 + /// - isEffective: 是否有效
  222 + /// - signatureFile: 签名文件
  223 + /// - overtimeCoefficient: 加班系数(NULL或0表示非加班单,大于0表示加班单)
  224 + /// - originalSgfy: 原始手工费(用户输入的原始值)
  225 + /// - overtimeSgfy: 加班手工费(加班计算后的增量值)
  226 + /// - ConsumeDetails: 耗卡明细列表(品项明细),每个明细包含:
  227 + /// - id: 明细编号
  228 + /// - consumeInfoId: 耗卡记录ID
  229 + /// - billingItemId: 开单品项明细表ID
  230 + /// - px: 品项编号
  231 + /// - pxmc: 品项名称
  232 + /// - pxjg: 品项价格
  233 + /// - memberId: 会员ID
  234 + /// - createTime: 创建时间
  235 + /// - projectNumber: 项目次数
  236 + /// - originalProjectNumber: 原始项目次数
  237 + /// - overtimeProjectNumber: 加班项目次数
  238 + /// - sourceType: 来源类型(开卡/赠送/其他)
  239 + /// - totalPrice: 合计金额(品项价格 × 项目次数)
  240 + /// - isEffective: 是否有效
  241 + /// - lqXhJksyjList: 健康师业绩列表,每个业绩包含:
  242 + /// - id: 业绩编号
  243 + /// - glkdbh: 关联耗卡编号
  244 + /// - jks: 健康师ID
  245 + /// - jksxm: 健康师姓名
  246 + /// - jkszh: 健康师账号
  247 + /// - jksyj: 健康师业绩
  248 + /// - yjsj: 业绩时间
  249 + /// - jsjId: 金三角ID
  250 + /// - kdpxid: 耗卡品项ID
  251 + /// - laborCost: 手工费
  252 + /// - kdpxNumber: 耗卡品项次数
  253 + /// - originalKdpxNumber: 原始耗卡品项次数
  254 + /// - overtimeKdpxNumber: 加班耗卡品项次数
  255 + /// - originalLaborCost: 原始手工费
  256 + /// - overtimeLaborCost: 加班手工费
  257 + /// - isAccompanied: 是否陪同(0=否,1=是)
  258 + /// - accompaniedProjectNumber: 陪同项目数
  259 + /// - memberId: 会员ID
  260 + /// - memberName: 会员名称
  261 + /// - lqXhKjbsyjList: 科技部老师业绩列表,每个业绩包含:
  262 + /// - id: 业绩编号
  263 + /// - glkdbh: 关联耗卡编号
  264 + /// - kjbls: 科技部老师ID
  265 + /// - kjblsxm: 科技部老师姓名
  266 + /// - kjblszh: 科技部老师账号
  267 + /// - kjblsyj: 科技部老师业绩
  268 + /// - yjsj: 业绩时间
  269 + /// - hkpxid: 耗卡品项ID
  270 + /// - laborCost: 手工费
  271 + /// - hdpxNumber: 耗卡品项次数
  272 + /// - originalHdpxNumber: 原始耗卡品项次数
  273 + /// - overtimeHdpxNumber: 加班耗卡品项次数
  274 + /// - originalLaborCost: 原始手工费
  275 + /// - overtimeLaborCost: 加班手工费
  276 + /// - pagination: 分页信息
  277 + /// - total: 总记录数
  278 + /// - pageSize: 每页数量
  279 + /// - currentPage: 当前页码
  280 + /// - totalPages: 总页数
  281 + /// </remarks>
  282 + /// <param name="input">查询参数</param>
  283 + /// <returns>分页的耗卡记录列表,包含耗卡基本信息、耗卡明细、健康师业绩、科技部老师业绩</returns>
  284 + /// <response code="200">成功返回耗卡列表</response>
  285 + /// <response code="400">参数错误</response>
  286 + /// <response code="500">服务器内部错误</response>
177 287 [HttpGet("")]
178 288 public async Task<dynamic> GetList([FromQuery] LqXhHyhkListQueryInput input)
179 289 {
180 290 var sidx = input.sidx == null ? "id" : input.sidx;
  291 + var sort = string.IsNullOrEmpty(input.sort) ? "DESC" : input.sort;
  292 + List<string> queryHksj = input.hksj != null ? input.hksj.Split(',').ToObeject<List<string>>() : null;
  293 + DateTime? startHksj = queryHksj != null ? Ext.GetDateTime(queryHksj.First()) : null;
  294 + DateTime? endHksj = queryHksj != null ? Ext.GetDateTime(queryHksj.Last()) : null;
  295 +
  296 + // 根据是否传入健康师ID或科技部老师ID来决定查询方式
  297 + ISugarQueryable<LqXhHyhkEntity> query = null;
  298 +
  299 + // 如果两个都传入了,需要同时JOIN两个表
  300 + if (!string.IsNullOrEmpty(input.jksId) && !string.IsNullOrEmpty(input.kjblsId))
  301 + {
  302 + query = _db.Queryable<LqXhJksyjEntity, LqXhKjbsyjEntity, LqXhHyhkEntity>(
  303 + (jksyj, kjbsyj, hyhk) => jksyj.Glkdbh == hyhk.Id && kjbsyj.Glkdbh == hyhk.Id)
  304 + .Where((jksyj, kjbsyj, hyhk) => jksyj.Jkszh == input.jksId && jksyj.IsEffective == StatusEnum.有效.GetHashCode())
  305 + .Where((jksyj, kjbsyj, hyhk) => kjbsyj.Kjblszh == input.kjblsId && kjbsyj.IsEffective == StatusEnum.有效.GetHashCode())
  306 + .Select((jksyj, kjbsyj, hyhk) => hyhk)
  307 + .Distinct()
  308 + .MergeTable(); // 将多表结果集变成单表,后续可以使用it别名
  309 + }
  310 + // 如果只传入了健康师ID,需要通过JOIN健康师业绩表来过滤
  311 + else if (!string.IsNullOrEmpty(input.jksId))
  312 + {
  313 + query = _db.Queryable<LqXhJksyjEntity, LqXhHyhkEntity>(
  314 + (jksyj, hyhk) => jksyj.Glkdbh == hyhk.Id)
  315 + .Where((jksyj, hyhk) => jksyj.Jkszh == input.jksId && jksyj.IsEffective == StatusEnum.有效.GetHashCode())
  316 + .Select((jksyj, hyhk) => hyhk)
  317 + .Distinct()
  318 + .MergeTable(); // 将多表结果集变成单表,后续可以使用it别名
  319 + }
  320 + // 如果只传入了科技部老师ID,需要通过JOIN科技部老师业绩表来过滤
  321 + else if (!string.IsNullOrEmpty(input.kjblsId))
  322 + {
  323 + query = _db.Queryable<LqXhKjbsyjEntity, LqXhHyhkEntity>(
  324 + (kjbsyj, hyhk) => kjbsyj.Glkdbh == hyhk.Id)
  325 + .Where((kjbsyj, hyhk) => kjbsyj.Kjblszh == input.kjblsId && kjbsyj.IsEffective == StatusEnum.有效.GetHashCode())
  326 + .Select((kjbsyj, hyhk) => hyhk)
  327 + .Distinct()
  328 + .MergeTable(); // 将多表结果集变成单表,后续可以使用it别名
  329 + }
  330 + // 如果都没有传入,直接查询耗卡表
  331 + else
  332 + {
  333 + query = _db.Queryable<LqXhHyhkEntity>();
  334 + }
  335 +
  336 + var data = await query
  337 + .WhereIF(!string.IsNullOrEmpty(input.keyword), p => p.Hymc.Contains(input.keyword) || p.Hyzh.Contains(input.keyword) || p.MemberPhone.Contains(input.keyword))
  338 + .WhereIF(!string.IsNullOrEmpty(input.id), p => p.Id.Contains(input.id))
  339 + .WhereIF(!string.IsNullOrEmpty(input.md), p => p.Md.Equals(input.md))
  340 + .WhereIF(!string.IsNullOrEmpty(input.mdbh), p => p.Mdbh.Contains(input.mdbh))
  341 + .WhereIF(!string.IsNullOrEmpty(input.mdmc), p => p.Mdmc.Contains(input.mdmc))
  342 + .WhereIF(!string.IsNullOrEmpty(input.hy), p => p.Hy.Equals(input.hy))
  343 + .WhereIF(!string.IsNullOrEmpty(input.hyzh), p => p.Hyzh.Contains(input.hyzh))
  344 + .WhereIF(!string.IsNullOrEmpty(input.hymc), p => p.Hymc.Contains(input.hymc))
  345 + .WhereIF(!string.IsNullOrEmpty(input.gklx), p => p.Gklx.Equals(input.gklx))
  346 + .WhereIF(!string.IsNullOrEmpty(input.sfykjb), p => p.Sfykjb.Equals(input.sfykjb))
  347 + .WhereIF(queryHksj != null, p => p.Hksj >= new DateTime(startHksj.ToDate().Year, startHksj.ToDate().Month, startHksj.ToDate().Day, 0, 0, 0))
  348 + .WhereIF(queryHksj != null, p => p.Hksj <= new DateTime(endHksj.ToDate().Year, endHksj.ToDate().Month, endHksj.ToDate().Day, 23, 59, 59))
  349 + .WhereIF(!string.IsNullOrEmpty(input.czry), p => p.Czry.Equals(input.czry))
  350 + .WhereIF(input.isEffective != 0, p => p.IsEffective == input.isEffective)
  351 + .Select(it => new LqXhHyhkListOutput
  352 + {
  353 + id = it.Id,
  354 + md = it.Md,
  355 + mdbh = it.Mdbh,
  356 + mdmc = it.Mdmc,
  357 + hy = it.Hy,
  358 + hyzh = it.Hyzh,
  359 + hymc = SqlFunc.Subqueryable<LqKhxxEntity>().Where(w => w.Id == it.Hy).Select(w => w.Khmc),
  360 + gklx = it.Gklx,
  361 + xfje = SqlFunc.ToString(it.Xfje),
  362 + sgfy = SqlFunc.ToString(it.Sgfy),
  363 + sfykjb = it.Sfykjb,
  364 + hksj = it.Hksj,
  365 + czry = it.Czry,
  366 + memberPhone = SqlFunc.Subqueryable<LqKhxxEntity>().Where(w => w.Id == it.Hy).Select(w => w.Sjh),
  367 + isEffective = it.IsEffective,
  368 + signatureFile = it.SignatureFile,
  369 + overtimeCoefficient = it.OvertimeCoefficient,
  370 + originalSgfy = it.OriginalSgfy,
  371 + overtimeSgfy = it.OvertimeSgfy,
  372 + })
  373 + .MergeTable()
  374 + .OrderBy($"{sidx} {sort}")
  375 + .ToPagedListAsync(input.currentPage, input.pageSize);
  376 +
  377 + // 获取当前页的耗卡记录ID列表
  378 + var consumeIds = data.list.Select(x => x.id).ToList();
  379 +
  380 + // 批量查询耗卡明细
  381 + var consumeDetails = new List<LqXhPxmxInfoOutput>();
  382 + if (consumeIds.Any())
  383 + {
  384 + consumeDetails = await _db.Queryable<LqXhPxmxEntity>()
  385 + .Where(x => consumeIds.Contains(x.ConsumeInfoId) && x.IsEffective == StatusEnum.有效.GetHashCode())
  386 + .Select(x => new LqXhPxmxInfoOutput
  387 + {
  388 + id = x.Id,
  389 + consumeInfoId = x.ConsumeInfoId,
  390 + billingItemId = x.BillingItemId,
  391 + px = x.Px,
  392 + pxmc = x.Pxmc,
  393 + pxjg = x.Pxjg,
  394 + memberId = x.MemberId,
  395 + createTime = x.CreateTIme,
  396 + projectNumber = x.ProjectNumber,
  397 + originalProjectNumber = x.OriginalProjectNumber,
  398 + overtimeProjectNumber = x.OvertimeProjectNumber,
  399 + sourceType = x.SourceType,
  400 + totalPrice = x.TotalPrice,
  401 + isEffective = x.IsEffective,
  402 + })
  403 + .ToListAsync();
  404 + }
  405 +
  406 + // 按耗卡记录ID分组耗卡明细
  407 + var consumeDetailsGrouped = consumeDetails.GroupBy(x => x.consumeInfoId)
  408 + .ToDictionary(g => g.Key, g => g.ToList());
  409 +
  410 + // 批量查询健康师业绩(性能优化:一次性查询所有耗卡的健康师业绩)
  411 + var jksyjList = new List<LqXhJksyjInfoOutput>();
  412 + if (consumeIds.Any())
  413 + {
  414 + // 先查询业绩数据
  415 + var jksyjEntities = await _db.Queryable<LqXhJksyjEntity>()
  416 + .Where(x => consumeIds.Contains(x.Glkdbh) && x.IsEffective == StatusEnum.有效.GetHashCode())
  417 + .ToListAsync();
  418 +
  419 + // 批量查询耗卡记录获取会员ID
  420 + var hyhkList = await _db.Queryable<LqXhHyhkEntity>()
  421 + .Where(x => consumeIds.Contains(x.Id))
  422 + .Select(x => new { x.Id, x.Hy })
  423 + .ToListAsync();
  424 + var hyhkDict = hyhkList.ToDictionary(x => x.Id, x => x.Hy);
  425 +
  426 + // 批量查询会员信息
  427 + var memberIds = hyhkDict.Values.Where(x => !string.IsNullOrEmpty(x)).Distinct().ToList();
  428 + var memberList = new List<(string Id, string Khmc)>();
  429 + if (memberIds.Any())
  430 + {
  431 + var memberData = await _db.Queryable<LqKhxxEntity>()
  432 + .Where(x => memberIds.Contains(x.Id))
  433 + .Select(x => new { x.Id, x.Khmc })
  434 + .ToListAsync();
  435 + memberList = memberData.Select(x => (x.Id, x.Khmc)).ToList();
  436 + }
  437 + var memberDict = memberList.ToDictionary(x => x.Id, x => x.Khmc);
  438 +
  439 + // 转换为输出DTO
  440 + jksyjList = jksyjEntities.Select(x => new LqXhJksyjInfoOutput
  441 + {
  442 + id = x.Id,
  443 + glkdbh = x.Glkdbh,
  444 + jks = x.Jks,
  445 + jksxm = x.Jksxm,
  446 + jkszh = x.Jkszh,
  447 + jksyj = x.Jksyj?.ToString() ?? "0",
  448 + yjsj = x.Yjsj,
  449 + jsjId = x.JsjId,
  450 + kdpxid = x.Kdpxid,
  451 + laborCost = x.LaborCost,
  452 + kdpxNumber = x.KdpxNumber,
  453 + originalKdpxNumber = x.OriginalKdpxNumber,
  454 + overtimeKdpxNumber = x.OvertimeKdpxNumber,
  455 + originalLaborCost = x.OriginalLaborCost,
  456 + overtimeLaborCost = x.OvertimeLaborCost,
  457 + isAccompanied = x.IsAccompanied,
  458 + accompaniedProjectNumber = x.AccompaniedProjectNumber,
  459 + memberId = hyhkDict.ContainsKey(x.Glkdbh) ? hyhkDict[x.Glkdbh] : null,
  460 + memberName = hyhkDict.ContainsKey(x.Glkdbh) && memberDict.ContainsKey(hyhkDict[x.Glkdbh])
  461 + ? memberDict[hyhkDict[x.Glkdbh]]
  462 + : null,
  463 + }).ToList();
  464 + }
  465 +
  466 + // 批量查询科技部老师业绩(性能优化:一次性查询所有耗卡的科技部老师业绩)
  467 + var kjbsyjList = new List<LqXhKjbsyjInfoOutput>();
  468 + if (consumeIds.Any())
  469 + {
  470 + kjbsyjList = await _db.Queryable<LqXhKjbsyjEntity>()
  471 + .Where(x => consumeIds.Contains(x.Glkdbh) && x.IsEffective == StatusEnum.有效.GetHashCode())
  472 + .Select(x => new LqXhKjbsyjInfoOutput
  473 + {
  474 + id = x.Id,
  475 + glkdbh = x.Glkdbh,
  476 + kjbls = x.Kjbls,
  477 + kjblsxm = x.Kjblsxm,
  478 + kjblszh = x.Kjblszh,
  479 + kjblsyj = SqlFunc.ToString(x.Kjblsyj),
  480 + yjsj = x.Yjsj,
  481 + hkpxid = x.Hkpxid,
  482 + laborCost = x.LaborCost,
  483 + hdpxNumber = x.HdpxNumber,
  484 + originalHdpxNumber = x.OriginalHdpxNumber,
  485 + overtimeHdpxNumber = x.OvertimeHdpxNumber,
  486 + originalLaborCost = x.OriginalLaborCost,
  487 + overtimeLaborCost = x.OvertimeLaborCost,
  488 + })
  489 + .ToListAsync();
  490 + }
  491 +
  492 + // 按耗卡记录ID分组健康师业绩
  493 + var jksyjGrouped = jksyjList.GroupBy(x => x.glkdbh)
  494 + .ToDictionary(g => g.Key, g => g.ToList());
  495 +
  496 + // 按耗卡记录ID分组科技部老师业绩
  497 + var kjbsyjGrouped = kjbsyjList.GroupBy(x => x.glkdbh)
  498 + .ToDictionary(g => g.Key, g => g.ToList());
  499 +
  500 + // 为每个耗卡记录分配耗卡明细、健康师业绩和科技部老师业绩
  501 + foreach (var item in data.list)
  502 + {
  503 + item.ConsumeDetails = consumeDetailsGrouped.ContainsKey(item.id)
  504 + ? consumeDetailsGrouped[item.id]
  505 + : new List<LqXhPxmxInfoOutput>();
  506 +
  507 + item.lqXhJksyjList = jksyjGrouped.ContainsKey(item.id)
  508 + ? jksyjGrouped[item.id]
  509 + : new List<LqXhJksyjInfoOutput>();
  510 +
  511 + item.lqXhKjbsyjList = kjbsyjGrouped.ContainsKey(item.id)
  512 + ? kjbsyjGrouped[item.id]
  513 + : new List<LqXhKjbsyjInfoOutput>();
  514 + }
  515 +
  516 + return PageResult<LqXhHyhkListOutput>.SqlSugarPageResult(data);
  517 + }
  518 + #endregion
  519 +
  520 + #region 获取会员耗卡列表(备份)
  521 + /// <summary>
  522 + /// 获取会员耗卡列表
  523 + /// </summary>
  524 + /// <param name="input">请求参数</param>
  525 + /// <returns></returns>
  526 + [HttpGet("GetListBak")]
  527 + public async Task<dynamic> GetListBak([FromQuery] LqXhHyhkListQueryInput input)
  528 + {
  529 + var sidx = input.sidx == null ? "id" : input.sidx;
181 530 List<string> queryHksj = input.hksj != null ? input.hksj.Split(',').ToObeject<List<string>>() : null;
182 531 DateTime? startHksj = queryHksj != null ? Ext.GetDateTime(queryHksj.First()) : null;
183 532 DateTime? endHksj = queryHksj != null ? Ext.GetDateTime(queryHksj.Last()) : null;
... ... @@ -263,6 +612,294 @@ namespace NCC.Extend.LqXhHyhk
263 612 }
264 613 #endregion
265 614  
  615 + #region 根据健康师ID获取耗卡列表
  616 + /// <summary>
  617 + /// 根据健康师ID获取耗卡列表
  618 + /// </summary>
  619 + /// <remarks>
  620 + /// 根据健康师ID查询该健康师参与的所有耗卡记录,支持时间周期查询和分页
  621 + ///
  622 + /// 示例请求:
  623 + /// GET /api/Extend/LqXhHyhk/GetListByJksId?jksId=健康师ID&amp;startTime=2025-01-01&amp;endTime=2025-01-31&amp;currentPage=1&amp;pageSize=10
  624 + ///
  625 + /// 参数说明:
  626 + /// - jksId: 健康师ID(必填)
  627 + /// - startTime: 开始时间(可选,格式:yyyy-MM-dd 或 yyyy-MM-dd HH:mm:ss)
  628 + /// - endTime: 结束时间(可选,格式:yyyy-MM-dd 或 yyyy-MM-dd HH:mm:ss)
  629 + /// - md: 门店ID(可选)
  630 + /// - isEffective: 是否有效(可选,0=全部,1=有效,2=无效,默认1)
  631 + /// - currentPage: 当前页码(必填)
  632 + /// - pageSize: 每页数量(必填)
  633 + ///
  634 + /// 返回说明:
  635 + /// - 返回格式与GetList接口相同,包含耗卡基本信息及耗卡明细列表
  636 + /// </remarks>
  637 + /// <param name="input">查询参数</param>
  638 + /// <returns>耗卡列表(分页)</returns>
  639 + /// <response code="200">查询成功</response>
  640 + /// <response code="400">参数错误</response>
  641 + /// <response code="500">服务器内部错误</response>
  642 + [HttpGet("GetListByJksId")]
  643 + public async Task<dynamic> GetListByJksId([FromQuery] LqXhHyhkListByJksQueryInput input)
  644 + {
  645 + try
  646 + {
  647 + if (string.IsNullOrEmpty(input.jksId))
  648 + {
  649 + throw NCCException.Oh("健康师ID不能为空");
  650 + }
  651 +
  652 + var sidx = input.sidx == null ? "hksj" : input.sidx;
  653 + var sort = string.IsNullOrEmpty(input.sort) ? "DESC" : input.sort;
  654 +
  655 + // 解析时间范围
  656 + DateTime? startDate = null;
  657 + DateTime? endDate = null;
  658 + if (!string.IsNullOrEmpty(input.startTime))
  659 + {
  660 + startDate = Ext.GetDateTime(input.startTime);
  661 + }
  662 + if (!string.IsNullOrEmpty(input.endTime))
  663 + {
  664 + endDate = Ext.GetDateTime(input.endTime);
  665 + // 如果只传了日期,则设置为当天的23:59:59
  666 + if (endDate.HasValue && !input.endTime.Contains(":"))
  667 + {
  668 + endDate = new DateTime(endDate.Value.Year, endDate.Value.Month, endDate.Value.Day, 23, 59, 59);
  669 + }
  670 + }
  671 +
  672 + // 通过健康师业绩表关联查询耗卡记录
  673 + var data = await _db.Queryable<LqXhJksyjEntity, LqXhHyhkEntity>(
  674 + (jksyj, hyhk) => jksyj.Glkdbh == hyhk.Id)
  675 + .Where((jksyj, hyhk) => jksyj.Jkszh == input.jksId)
  676 + .WhereIF(input.isEffective != 0, (jksyj, hyhk) => jksyj.IsEffective == input.isEffective && hyhk.IsEffective == input.isEffective)
  677 + .WhereIF(input.isEffective == 0, (jksyj, hyhk) => jksyj.IsEffective == StatusEnum.有效.GetHashCode() && hyhk.IsEffective == StatusEnum.有效.GetHashCode())
  678 + .WhereIF(startDate.HasValue, (jksyj, hyhk) => hyhk.Hksj >= startDate.Value)
  679 + .WhereIF(endDate.HasValue, (jksyj, hyhk) => hyhk.Hksj <= endDate.Value)
  680 + .WhereIF(!string.IsNullOrEmpty(input.md), (jksyj, hyhk) => hyhk.Md == input.md)
  681 + .Select((jksyj, hyhk) => new LqXhHyhkListOutput
  682 + {
  683 + id = hyhk.Id,
  684 + md = hyhk.Md,
  685 + mdbh = hyhk.Mdbh,
  686 + mdmc = hyhk.Mdmc,
  687 + hy = hyhk.Hy,
  688 + hyzh = hyhk.Hyzh,
  689 + hymc = SqlFunc.Subqueryable<LqKhxxEntity>().Where(w => w.Id == hyhk.Hy).Select(w => w.Khmc),
  690 + gklx = hyhk.Gklx,
  691 + xfje = SqlFunc.ToString(hyhk.Xfje),
  692 + sgfy = SqlFunc.ToString(hyhk.Sgfy),
  693 + sfykjb = hyhk.Sfykjb,
  694 + hksj = hyhk.Hksj,
  695 + czry = hyhk.Czry,
  696 + memberPhone = SqlFunc.Subqueryable<LqKhxxEntity>().Where(w => w.Id == hyhk.Hy).Select(w => w.Sjh),
  697 + isEffective = hyhk.IsEffective,
  698 + signatureFile = hyhk.SignatureFile,
  699 + overtimeCoefficient = hyhk.OvertimeCoefficient,
  700 + originalSgfy = hyhk.OriginalSgfy,
  701 + overtimeSgfy = hyhk.OvertimeSgfy,
  702 + })
  703 + .MergeTable()
  704 + .Distinct() // 去重,因为一个耗卡可能对应多个健康师业绩记录
  705 + .OrderBy($"{sidx} {sort}")
  706 + .ToPagedListAsync(input.currentPage, input.pageSize);
  707 +
  708 + // 获取当前页的耗卡记录ID列表
  709 + var consumeIds = data.list.Select(x => x.id).ToList();
  710 +
  711 + // 批量查询耗卡明细
  712 + var consumeDetails = new List<LqXhPxmxInfoOutput>();
  713 + if (consumeIds.Any())
  714 + {
  715 + consumeDetails = await _db.Queryable<LqXhPxmxEntity>()
  716 + .Where(x => consumeIds.Contains(x.ConsumeInfoId) && x.IsEffective == StatusEnum.有效.GetHashCode())
  717 + .Select(x => new LqXhPxmxInfoOutput
  718 + {
  719 + id = x.Id,
  720 + consumeInfoId = x.ConsumeInfoId,
  721 + billingItemId = x.BillingItemId,
  722 + px = x.Px,
  723 + pxmc = x.Pxmc,
  724 + pxjg = x.Pxjg,
  725 + memberId = x.MemberId,
  726 + createTime = x.CreateTIme,
  727 + projectNumber = x.ProjectNumber,
  728 + originalProjectNumber = x.OriginalProjectNumber,
  729 + overtimeProjectNumber = x.OvertimeProjectNumber,
  730 + sourceType = x.SourceType,
  731 + totalPrice = x.TotalPrice,
  732 + isEffective = x.IsEffective,
  733 + })
  734 + .ToListAsync();
  735 + }
  736 +
  737 + // 按耗卡记录ID分组耗卡明细
  738 + var consumeDetailsGrouped = consumeDetails.GroupBy(x => x.consumeInfoId)
  739 + .ToDictionary(g => g.Key, g => g.ToList());
  740 +
  741 + // 为每个耗卡记录分配耗卡明细
  742 + foreach (var item in data.list)
  743 + {
  744 + item.ConsumeDetails = consumeDetailsGrouped.ContainsKey(item.id)
  745 + ? consumeDetailsGrouped[item.id]
  746 + : new List<LqXhPxmxInfoOutput>();
  747 + }
  748 +
  749 + return PageResult<LqXhHyhkListOutput>.SqlSugarPageResult(data);
  750 + }
  751 + catch (Exception ex)
  752 + {
  753 + _logger.LogError(ex, $"根据健康师ID获取耗卡列表失败 - 健康师ID: {input?.jksId}, 开始时间: {input?.startTime}, 结束时间: {input?.endTime}");
  754 + throw NCCException.Oh($"根据健康师ID获取耗卡列表失败: {ex.Message}");
  755 + }
  756 + }
  757 + #endregion
  758 +
  759 + #region 根据科技部老师ID获取耗卡列表
  760 + /// <summary>
  761 + /// 根据科技部老师ID获取耗卡列表
  762 + /// </summary>
  763 + /// <remarks>
  764 + /// 根据科技部老师ID查询该老师参与的所有耗卡记录,支持时间周期查询和分页
  765 + ///
  766 + /// 示例请求:
  767 + /// GET /api/Extend/LqXhHyhk/GetListByKjbId?kjblsId=科技部老师ID&amp;startTime=2025-01-01&amp;endTime=2025-01-31&amp;currentPage=1&amp;pageSize=10
  768 + ///
  769 + /// 参数说明:
  770 + /// - kjblsId: 科技部老师ID(必填)
  771 + /// - startTime: 开始时间(可选,格式:yyyy-MM-dd 或 yyyy-MM-dd HH:mm:ss)
  772 + /// - endTime: 结束时间(可选,格式:yyyy-MM-dd 或 yyyy-MM-dd HH:mm:ss)
  773 + /// - md: 门店ID(可选)
  774 + /// - isEffective: 是否有效(可选,0=全部,1=有效,2=无效,默认1)
  775 + /// - currentPage: 当前页码(必填)
  776 + /// - pageSize: 每页数量(必填)
  777 + ///
  778 + /// 返回说明:
  779 + /// - 返回格式与GetList接口相同,包含耗卡基本信息及耗卡明细列表
  780 + /// </remarks>
  781 + /// <param name="input">查询参数</param>
  782 + /// <returns>耗卡列表(分页)</returns>
  783 + /// <response code="200">查询成功</response>
  784 + /// <response code="400">参数错误</response>
  785 + /// <response code="500">服务器内部错误</response>
  786 + [HttpGet("GetListByKjbId")]
  787 + public async Task<dynamic> GetListByKjbId([FromQuery] LqXhHyhkListByKjbQueryInput input)
  788 + {
  789 + try
  790 + {
  791 + if (string.IsNullOrEmpty(input.kjblsId))
  792 + {
  793 + throw NCCException.Oh("科技部老师ID不能为空");
  794 + }
  795 +
  796 + var sidx = input.sidx == null ? "hksj" : input.sidx;
  797 + var sort = string.IsNullOrEmpty(input.sort) ? "DESC" : input.sort;
  798 +
  799 + // 解析时间范围
  800 + DateTime? startDate = null;
  801 + DateTime? endDate = null;
  802 + if (!string.IsNullOrEmpty(input.startTime))
  803 + {
  804 + startDate = Ext.GetDateTime(input.startTime);
  805 + }
  806 + if (!string.IsNullOrEmpty(input.endTime))
  807 + {
  808 + endDate = Ext.GetDateTime(input.endTime);
  809 + // 如果只传了日期,则设置为当天的23:59:59
  810 + if (endDate.HasValue && !input.endTime.Contains(":"))
  811 + {
  812 + endDate = new DateTime(endDate.Value.Year, endDate.Value.Month, endDate.Value.Day, 23, 59, 59);
  813 + }
  814 + }
  815 +
  816 + // 通过科技部老师业绩表关联查询耗卡记录
  817 + var data = await _db.Queryable<LqXhKjbsyjEntity, LqXhHyhkEntity>(
  818 + (kjbsyj, hyhk) => kjbsyj.Glkdbh == hyhk.Id)
  819 + .Where((kjbsyj, hyhk) => kjbsyj.Kjblszh == input.kjblsId)
  820 + .WhereIF(input.isEffective != 0, (kjbsyj, hyhk) => kjbsyj.IsEffective == input.isEffective && hyhk.IsEffective == input.isEffective)
  821 + .WhereIF(input.isEffective == 0, (kjbsyj, hyhk) => kjbsyj.IsEffective == StatusEnum.有效.GetHashCode() && hyhk.IsEffective == StatusEnum.有效.GetHashCode())
  822 + .WhereIF(startDate.HasValue, (kjbsyj, hyhk) => hyhk.Hksj >= startDate.Value)
  823 + .WhereIF(endDate.HasValue, (kjbsyj, hyhk) => hyhk.Hksj <= endDate.Value)
  824 + .WhereIF(!string.IsNullOrEmpty(input.md), (kjbsyj, hyhk) => hyhk.Md == input.md)
  825 + .Select((kjbsyj, hyhk) => new LqXhHyhkListOutput
  826 + {
  827 + id = hyhk.Id,
  828 + md = hyhk.Md,
  829 + mdbh = hyhk.Mdbh,
  830 + mdmc = hyhk.Mdmc,
  831 + hy = hyhk.Hy,
  832 + hyzh = hyhk.Hyzh,
  833 + hymc = SqlFunc.Subqueryable<LqKhxxEntity>().Where(w => w.Id == hyhk.Hy).Select(w => w.Khmc),
  834 + gklx = hyhk.Gklx,
  835 + xfje = SqlFunc.ToString(hyhk.Xfje),
  836 + sgfy = SqlFunc.ToString(hyhk.Sgfy),
  837 + sfykjb = hyhk.Sfykjb,
  838 + hksj = hyhk.Hksj,
  839 + czry = hyhk.Czry,
  840 + memberPhone = SqlFunc.Subqueryable<LqKhxxEntity>().Where(w => w.Id == hyhk.Hy).Select(w => w.Sjh),
  841 + isEffective = hyhk.IsEffective,
  842 + signatureFile = hyhk.SignatureFile,
  843 + overtimeCoefficient = hyhk.OvertimeCoefficient,
  844 + originalSgfy = hyhk.OriginalSgfy,
  845 + overtimeSgfy = hyhk.OvertimeSgfy,
  846 + })
  847 + .MergeTable()
  848 + .Distinct() // 去重,因为一个耗卡可能对应多个科技部老师业绩记录
  849 + .OrderBy($"{sidx} {sort}")
  850 + .ToPagedListAsync(input.currentPage, input.pageSize);
  851 +
  852 + // 获取当前页的耗卡记录ID列表
  853 + var consumeIds = data.list.Select(x => x.id).ToList();
  854 +
  855 + // 批量查询耗卡明细
  856 + var consumeDetails = new List<LqXhPxmxInfoOutput>();
  857 + if (consumeIds.Any())
  858 + {
  859 + consumeDetails = await _db.Queryable<LqXhPxmxEntity>()
  860 + .Where(x => consumeIds.Contains(x.ConsumeInfoId) && x.IsEffective == StatusEnum.有效.GetHashCode())
  861 + .Select(x => new LqXhPxmxInfoOutput
  862 + {
  863 + id = x.Id,
  864 + consumeInfoId = x.ConsumeInfoId,
  865 + billingItemId = x.BillingItemId,
  866 + px = x.Px,
  867 + pxmc = x.Pxmc,
  868 + pxjg = x.Pxjg,
  869 + memberId = x.MemberId,
  870 + createTime = x.CreateTIme,
  871 + projectNumber = x.ProjectNumber,
  872 + originalProjectNumber = x.OriginalProjectNumber,
  873 + overtimeProjectNumber = x.OvertimeProjectNumber,
  874 + sourceType = x.SourceType,
  875 + totalPrice = x.TotalPrice,
  876 + isEffective = x.IsEffective,
  877 + })
  878 + .ToListAsync();
  879 + }
  880 +
  881 + // 按耗卡记录ID分组耗卡明细
  882 + var consumeDetailsGrouped = consumeDetails.GroupBy(x => x.consumeInfoId)
  883 + .ToDictionary(g => g.Key, g => g.ToList());
  884 +
  885 + // 为每个耗卡记录分配耗卡明细
  886 + foreach (var item in data.list)
  887 + {
  888 + item.ConsumeDetails = consumeDetailsGrouped.ContainsKey(item.id)
  889 + ? consumeDetailsGrouped[item.id]
  890 + : new List<LqXhPxmxInfoOutput>();
  891 + }
  892 +
  893 + return PageResult<LqXhHyhkListOutput>.SqlSugarPageResult(data);
  894 + }
  895 + catch (Exception ex)
  896 + {
  897 + _logger.LogError(ex, $"根据科技部老师ID获取耗卡列表失败 - 科技部老师ID: {input?.kjblsId}, 开始时间: {input?.startTime}, 结束时间: {input?.endTime}");
  898 + throw NCCException.Oh($"根据科技部老师ID获取耗卡列表失败: {ex.Message}");
  899 + }
  900 + }
  901 + #endregion
  902 +
266 903 #region 新建会员耗卡
267 904 /// <summary>
268 905 /// 新建会员耗卡
... ...
netcore/src/Modularity/Extend/NCC.Extend/Utils/WeChatBotService.cs
... ... @@ -17,7 +17,7 @@ namespace NCC.Extend.Utils
17 17 //https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=581c22a6-cb67-42e5-8c76-b8e90052e188
18 18 //测试地址
19 19 //https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=6f8686ec-5011-4c1d-bae9-d82a2a2f4d83
20   - private const string WEBHOOK_URL = "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=581c22a6-cb67-42e5-8c76-b8e90052e188";
  20 + private const string WEBHOOK_URL = "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=496f1add-122b-43fc-9e38-0ca79c48b33f";
21 21 private const string WEBHOOK_URL_TEST = "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=6f8686ec-5011-4c1d-bae9-d82a2a2f4d83";
22 22  
23 23 public WeChatBotService(HttpClient httpClient)
... ... @@ -36,8 +36,8 @@ namespace NCC.Extend.Utils
36 36 {
37 37 var requestData = new
38 38 {
39   - // webhookUrl = WEBHOOK_URL,
40   - webhookUrl = WEBHOOK_URL_TEST,
  39 + webhookUrl = WEBHOOK_URL,
  40 + // webhookUrl = WEBHOOK_URL_TEST,
41 41 content = content,
42 42 mentionedList = (string)null,
43 43 mentionedMobileList = (string)null,
... ...