Commit 8514f46544def94ef639ababadd29b7523f896ea

Authored by “wangming”
1 parent 873e7a7d

feat: 优化业务逻辑和功能完善

- 更新项目规则文档
- 优化会员信息相关接口和DTO
- 优化开单记录、耗卡、退卡等业务逻辑
- 新增开单记录按会员ID查询卡项接口
- 优化健康师统计输出
- 优化消耗反馈查询逻辑
.vscode/launch.json 0 → 100644
  1 +{
  2 + "version": "0.2.0",
  3 + "configurations": [
  4 + {
  5 + // Use IntelliSense to find out which attributes exist for C# debugging
  6 + // Use hover for the description of the existing attributes
  7 + // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md
  8 + "name": ".NET Core Launch (web)",
  9 + "type": "coreclr",
  10 + "request": "launch",
  11 + "preLaunchTask": "build",
  12 + // If you have changed target frameworks, make sure to update the program path.
  13 + "program": "${workspaceFolder}/netcore/src/Application/NCC.API/bin/Debug/net6.0/NCC.API.dll",
  14 + "args": [],
  15 + "cwd": "${workspaceFolder}/netcore/src/Application/NCC.API",
  16 + "stopAtEntry": false,
  17 + // Enable launching a web browser when ASP.NET Core starts. For more information: https://aka.ms/VSCode-CS-LaunchJson-WebBrowser
  18 + "serverReadyAction": {
  19 + "action": "openExternally",
  20 + "pattern": "\\bNow listening on:\\s+(https?://\\S+)"
  21 + },
  22 + "env": {
  23 + "ASPNETCORE_ENVIRONMENT": "Development"
  24 + },
  25 + "sourceFileMap": {
  26 + "/Views": "${workspaceFolder}/Views"
  27 + }
  28 + },
  29 + {
  30 + "name": ".NET Core Attach",
  31 + "type": "coreclr",
  32 + "request": "attach"
  33 + }
  34 + ]
  35 +}
0 36 \ No newline at end of file
... ...
.vscode/tasks.json 0 → 100644
  1 +{
  2 + "version": "2.0.0",
  3 + "tasks": [
  4 + {
  5 + "label": "build",
  6 + "command": "dotnet",
  7 + "type": "process",
  8 + "args": [
  9 + "build",
  10 + "${workspaceFolder}/netcore/src/Application/NCC.API/NCC.API.csproj",
  11 + "/property:GenerateFullPaths=true",
  12 + "/consoleloggerparameters:NoSummary"
  13 + ],
  14 + "problemMatcher": "$msCompile"
  15 + },
  16 + {
  17 + "label": "publish",
  18 + "command": "dotnet",
  19 + "type": "process",
  20 + "args": [
  21 + "publish",
  22 + "${workspaceFolder}/netcore/src/Application/NCC.API/NCC.API.csproj",
  23 + "/property:GenerateFullPaths=true",
  24 + "/consoleloggerparameters:NoSummary"
  25 + ],
  26 + "problemMatcher": "$msCompile"
  27 + },
  28 + {
  29 + "label": "watch",
  30 + "command": "dotnet",
  31 + "type": "process",
  32 + "args": [
  33 + "watch",
  34 + "run",
  35 + "${workspaceFolder}/netcore/src/Application/NCC.API/NCC.API.csproj",
  36 + "/property:GenerateFullPaths=true",
  37 + "/consoleloggerparameters:NoSummary"
  38 + ],
  39 + "problemMatcher": "$msCompile"
  40 + }
  41 + ]
  42 +}
0 43 \ No newline at end of file
... ...
PROJECT_RULES.md
... ... @@ -35,6 +35,7 @@
35 35 - **图标显示**: 所有列表数据都要有图标,不同颜色区分类型
36 36 - **空值显示**: 没有信息的字段显示"无"
37 37 - **列表规范**: 列表数据不能换行
  38 +- **弹窗显示**: 弹窗需要使用圆角 12px
38 39  
39 40 ### 性能要求
40 41 - 启用懒加载和代码分割
... ...
antis-ncc-admin/.env.development
... ... @@ -2,7 +2,7 @@
2 2  
3 3 VUE_CLI_BABEL_TRANSPILE_MODULES = true
4 4 # VUE_APP_BASE_API = 'https://erp.lvqianmeiye.com'
5   -VUE_APP_BASE_API = 'http://erp_test.lvqianmeiye.com'
6   -# VUE_APP_BASE_API = 'http://localhost:2011'
  5 +# VUE_APP_BASE_API = 'http://erp_test.lvqianmeiye.com'
  6 +VUE_APP_BASE_API = 'http://localhost:2011'
7 7 # VUE_APP_BASE_API = 'http://localhost:2011'
8 8 VUE_APP_BASE_WSS = 'ws://192.168.110.45:2011/websocket'
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKdKdjlb/BillingItemsByMemberIdQueryInput.cs 0 → 100644
  1 +using System;
  2 +using NCC.Common.Filter;
  3 +
  4 +namespace NCC.Extend.Entitys.Dto.LqKdKdjlb
  5 +{
  6 + /// <summary>
  7 + /// 会员开单品项列表查询输入
  8 + /// </summary>
  9 + public class BillingItemsByMemberIdQueryInput : PageInputBase
  10 + {
  11 + /// <summary>
  12 + /// 会员ID
  13 + /// </summary>
  14 + public string MemberId { get; set; }
  15 +
  16 + /// <summary>
  17 + /// 品项分类
  18 + /// </summary>
  19 + public string ItemCategory { get; set; }
  20 +
  21 + /// <summary>
  22 + /// 开始时间(业绩时间)
  23 + /// </summary>
  24 + public DateTime? StartTime { get; set; }
  25 +
  26 + /// <summary>
  27 + /// 结束时间(业绩时间)
  28 + /// </summary>
  29 + public DateTime? EndTime { get; set; }
  30 +
  31 + /// <summary>
  32 + /// 最小实付金额
  33 + /// </summary>
  34 + public decimal? MinActualPrice { get; set; }
  35 +
  36 + /// <summary>
  37 + /// 最大实付金额
  38 + /// </summary>
  39 + public decimal? MaxActualPrice { get; set; }
  40 +
  41 + /// <summary>
  42 + /// 开单编号
  43 + /// </summary>
  44 + public string BillingId { get; set; }
  45 + }
  46 +}
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKdKdjlb/HealthCoachStatisticsOutput.cs
... ... @@ -89,5 +89,10 @@ namespace NCC.Extend.Entitys.Dto.LqKdKdjlb
89 89 /// 消耗项目数 - 统计该健康师在指定时间周期内消耗的项目总次数
90 90 /// </summary>
91 91 public decimal projectCount { get; set; }
  92 +
  93 + /// <summary>
  94 + /// 退卡金额 - 统计该健康师在指定时间周期内的退卡业绩总金额
  95 + /// </summary>
  96 + public decimal refundAmount { get; set; }
92 97 }
93 98 }
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKhxx/LqKhxxInfoOutput.cs
... ... @@ -151,6 +151,11 @@ namespace NCC.Extend.Entitys.Dto.LqKhxx
151 151 public int isTechMember { get; set; }
152 152  
153 153 /// <summary>
  154 + /// 是否教育部会员(0-否,1-是)
  155 + /// </summary>
  156 + public int isEducationMember { get; set; }
  157 +
  158 + /// <summary>
154 159 /// 成为生美会员时间
155 160 /// </summary>
156 161 public DateTime? beautyMemberTime { get; set; }
... ... @@ -166,6 +171,11 @@ namespace NCC.Extend.Entitys.Dto.LqKhxx
166 171 public DateTime? techMemberTime { get; set; }
167 172  
168 173 /// <summary>
  174 + /// 成为教育部会员时间
  175 + /// </summary>
  176 + public DateTime? educationMemberTime { get; set; }
  177 +
  178 + /// <summary>
169 179 /// 首次到店时间
170 180 /// </summary>
171 181 public DateTime? firstVisitTime { get; set; }
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKhxx/LqKhxxListOutput.cs
... ... @@ -178,6 +178,11 @@ namespace NCC.Extend.Entitys.Dto.LqKhxx
178 178 public int isTechMember { get; set; }
179 179  
180 180 /// <summary>
  181 + /// 是否教育部会员(0-否,1-是)
  182 + /// </summary>
  183 + public int isEducationMember { get; set; }
  184 +
  185 + /// <summary>
181 186 /// 成为生美会员时间
182 187 /// </summary>
183 188 public DateTime? beautyMemberTime { get; set; }
... ... @@ -193,6 +198,11 @@ namespace NCC.Extend.Entitys.Dto.LqKhxx
193 198 public DateTime? techMemberTime { get; set; }
194 199  
195 200 /// <summary>
  201 + /// 成为教育部会员时间
  202 + /// </summary>
  203 + public DateTime? educationMemberTime { get; set; }
  204 +
  205 + /// <summary>
196 206 /// 首次到店时间
197 207 /// </summary>
198 208 public DateTime? firstVisitTime { get; set; }
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKhxx/LqKhxxListQueryInput.cs
... ... @@ -83,6 +83,15 @@ namespace NCC.Extend.Entitys.Dto.LqKhxx
83 83 /// </summary>
84 84 public string tjr { get; set; }
85 85  
  86 + /// <summary>
  87 + /// 是否科技部会员
  88 + /// </summary>
  89 + public int? IsTechMemberbh { get; set; }
  90 +
  91 + /// <summary>
  92 + /// 是否教育部会员
  93 + /// </summary>
  94 + public int? IsEducationMember { get; set; }
86 95  
87 96 /// <summary>
88 97 /// 进店渠道
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqXhFeedback/LqXhFeedbackListQueryInput.cs
... ... @@ -13,6 +13,11 @@ namespace NCC.Extend.Entitys.Dto.LqXhFeedback
13 13 public string ConsumeId { get; set; }
14 14  
15 15 /// <summary>
  16 + /// 会员ID
  17 + /// </summary>
  18 + public string MemberId { get; set; }
  19 +
  20 + /// <summary>
16 21 /// 添加人ID
17 22 /// </summary>
18 23 public string CreateUser { get; set; }
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_khxx/LqKhxxEntity.cs
... ... @@ -184,6 +184,18 @@ namespace NCC.Extend.Entitys.lq_khxx
184 184 public DateTime? TechMemberTime { get; set; }
185 185  
186 186 /// <summary>
  187 + /// 是否教育部会员(0-否,1-是)
  188 + /// </summary>
  189 + [SugarColumn(ColumnName = "F_IsEducationMember")]
  190 + public int IsEducationMember { get; set; } = 0;
  191 +
  192 + /// <summary>
  193 + /// 成为教育部会员时间
  194 + /// </summary>
  195 + [SugarColumn(ColumnName = "F_EducationMemberTime")]
  196 + public DateTime? EducationMemberTime { get; set; }
  197 +
  198 + /// <summary>
187 199 /// 首次到店时间
188 200 /// </summary>
189 201 [SugarColumn(ColumnName = "F_FirstVisitTime")]
... ...
netcore/src/Modularity/Extend/NCC.Extend/LqHytkHytkService.cs
... ... @@ -435,6 +435,8 @@ namespace NCC.Extend.LqHytkHytk
435 435 F_CreateUser = userInfo.userId,
436 436 CardReturn = lqHytkMxEntity.Id,
437 437 IsEffective = StatusEnum.有效.GetHashCode(),
  438 + ItemCategory = lqHytkMxEntity.ItemCategory,
  439 + ItemId = lqHytkMxEntity.Px,
438 440 }
439 441 );
440 442 }
... ... @@ -462,6 +464,8 @@ namespace NCC.Extend.LqHytkHytk
462 464 F_CreateUser = userInfo.userId,
463 465 CardReturn = lqHytkMxEntity.Id,
464 466 IsEffective = StatusEnum.有效.GetHashCode(),
  467 + ItemCategory = lqHytkMxEntity.ItemCategory,
  468 + ItemId = lqHytkMxEntity.Px,
465 469 }
466 470 );
467 471 }
... ... @@ -586,6 +590,8 @@ namespace NCC.Extend.LqHytkHytk
586 590 F_tkpxNumber = ijks_tem.F_tkpxNumber,
587 591 F_CreateTime = DateTime.Now,
588 592 F_CreateUser = userInfo.userId,
  593 + ItemCategory = lqHytkMxEntity.ItemCategory,
  594 + ItemId = lqHytkMxEntity.Px,
589 595 }
590 596 );
591 597 }
... ... @@ -611,6 +617,8 @@ namespace NCC.Extend.LqHytkHytk
611 617 F_tkpxNumber = ikjbs_tem.F_tkpxNumber,
612 618 F_CreateTime = DateTime.Now,
613 619 F_CreateUser = userInfo.userId,
  620 + ItemCategory = lqHytkMxEntity.ItemCategory,
  621 + ItemId = lqHytkMxEntity.Px,
614 622 }
615 623 );
616 624 }
... ...
netcore/src/Modularity/Extend/NCC.Extend/LqKdKdjlbService.cs
... ... @@ -953,6 +953,8 @@ namespace NCC.Extend.LqKdKdjlb
953 953 Kdpxid = lqKdPxmxEntity.Id,
954 954 IsEffective = StatusEnum.有效.GetHashCode(),
955 955 ActivityId = input.activityId,
  956 + ItemCategory = lqKdPxmxEntity.ItemCategory,
  957 + ItemId = lqKdPxmxEntity.Px,
956 958 });
957 959 }
958 960 }
... ... @@ -974,6 +976,8 @@ namespace NCC.Extend.LqKdKdjlb
974 976 Kdpxid = lqKdPxmxEntity.Id,
975 977 IsEffective = StatusEnum.有效.GetHashCode(),
976 978 ActivityId = input.activityId,
  979 + ItemCategory = lqKdPxmxEntity.ItemCategory,
  980 + ItemId = lqKdPxmxEntity.Px,
977 981 }
978 982 );
979 983 }
... ... @@ -1750,6 +1754,8 @@ namespace NCC.Extend.LqKdKdjlb
1750 1754 Kdpxid = lqKdPxmxEntity.Id,
1751 1755 IsEffective = StatusEnum.有效.GetHashCode(),
1752 1756 ActivityId = input.activityId,
  1757 + ItemCategory = lqKdPxmxEntity.ItemCategory,
  1758 + ItemId = lqKdPxmxEntity.Px,
1753 1759 });
1754 1760 }
1755 1761 }
... ... @@ -1771,6 +1777,8 @@ namespace NCC.Extend.LqKdKdjlb
1771 1777 Kdpxid = lqKdPxmxEntity.Id,
1772 1778 IsEffective = StatusEnum.有效.GetHashCode(),
1773 1779 ActivityId = input.activityId,
  1780 + ItemCategory = lqKdPxmxEntity.ItemCategory,
  1781 + ItemId = lqKdPxmxEntity.Px,
1774 1782 }
1775 1783 );
1776 1784 }
... ... @@ -2308,6 +2316,48 @@ namespace NCC.Extend.LqKdKdjlb
2308 2316 }
2309 2317 #endregion
2310 2318  
  2319 + #region 根据会员id获取会员的开单品项列表
  2320 + /// <summary>
  2321 + /// 根据会员id获取会员的开单品项列表
  2322 + /// </summary>
  2323 + /// <param name="input">查询参数</param>
  2324 + /// <returns>会员的开单品项列表</returns>
  2325 + [HttpPost("GetBillingItemsByMemberId")]
  2326 + public async Task<dynamic> GetBillingItemsByMemberId([FromBody] BillingItemsByMemberIdQueryInput input)
  2327 + {
  2328 + try
  2329 + {
  2330 + var query = _db.Queryable<LqKdPxmxEntity>()
  2331 + .Where(p => p.MemberId == input.MemberId)
  2332 + .WhereIF(!string.IsNullOrEmpty(input.ItemCategory), p => p.ItemCategory == input.ItemCategory)
  2333 + .WhereIF(!string.IsNullOrEmpty(input.BillingId), p => p.Glkdbh == input.BillingId)
  2334 + .WhereIF(input.StartTime.HasValue, p => p.Yjsj >= input.StartTime.Value)
  2335 + .WhereIF(input.EndTime.HasValue, p => p.Yjsj <= input.EndTime.Value)
  2336 + .WhereIF(input.MinActualPrice.HasValue, p => p.ActualPrice >= input.MinActualPrice.Value)
  2337 + .WhereIF(input.MaxActualPrice.HasValue, p => p.ActualPrice <= input.MaxActualPrice.Value)
  2338 + .OrderBy(p => p.Yjsj, OrderByType.Desc);
  2339 +
  2340 + var list = await query.ToPageListAsync(input.currentPage, input.pageSize);
  2341 + var totalCount = await query.CountAsync();
  2342 +
  2343 + return new
  2344 + {
  2345 + list = list,
  2346 + pagination = new
  2347 + {
  2348 + pageIndex = input.currentPage,
  2349 + pageSize = input.pageSize,
  2350 + totalCount = totalCount
  2351 + }
  2352 + };
  2353 + }
  2354 + catch (Exception ex)
  2355 + {
  2356 + throw NCCException.Oh($"获取会员开单品项列表失败: {ex.Message}");
  2357 + }
  2358 + }
  2359 + #endregion
  2360 +
2311 2361 #region 根据开单id获取当前开单欠款信息
2312 2362 /// <summary>
2313 2363 /// 根据开单id获取当前开单欠款信息
... ... @@ -3173,6 +3223,7 @@ namespace NCC.Extend.LqKdKdjlb
3173 3223 /// - HeadCount: 人头(按客户去重)
3174 3224 /// - PersonCount: 人次(按客户+日期去重,同一客户不同天算多次)
3175 3225 /// - ProjectCount: 消耗项目数(项目总次数)
  3226 + /// - RefundAmount: 退卡金额(健康师退卡业绩总金额)
3176 3227 /// </remarks>
3177 3228 /// <param name="input">查询参数</param>
3178 3229 /// <returns>健康师统计数据列表</returns>
... ... @@ -3217,7 +3268,10 @@ namespace NCC.Extend.LqKdKdjlb
3217 3268 CAST(COALESCE(personcount_stats.PersonCount, 0) AS DECIMAL(18,2)) as PersonCount,
3218 3269 CAST(COALESCE(invalid_headcount_stats.HeadCount, 0) AS DECIMAL(18,2)) as InvalidHeadCount,
3219 3270 CAST(COALESCE(invalid_personcount_stats.PersonCount, 0) AS DECIMAL(18,2)) as InvalidPersonCount,
3220   - CAST(COALESCE(consume_stats.ProjectCount, 0) AS DECIMAL(18,2)) as ProjectCount
  3271 + CAST(COALESCE(consume_stats.ProjectCount, 0) AS DECIMAL(18,2)) as ProjectCount,
  3272 +
  3273 + -- 退卡金额
  3274 + COALESCE(refund_stats.RefundAmount, 0) as RefundAmount
3221 3275  
3222 3276 FROM BASE_USER u
3223 3277 LEFT JOIN lq_mdxx md ON u.F_MDID = md.F_Id
... ... @@ -3382,6 +3436,19 @@ namespace NCC.Extend.LqKdKdjlb
3382 3436 GROUP BY F_PersonId
3383 3437 ) invalid_personcount_stats ON u.F_Id = invalid_personcount_stats.EmployeeId
3384 3438  
  3439 + -- 退卡统计子查询
  3440 + LEFT JOIN (
  3441 + SELECT
  3442 + jkszh as EmployeeId,
  3443 + SUM(CAST(jksyj AS DECIMAL(18,2))) as RefundAmount
  3444 + FROM lq_hytk_jksyj
  3445 + WHERE jkszh IS NOT NULL
  3446 + AND F_IsEffective = 1
  3447 + AND tksj >= @startTime
  3448 + AND tksj <= @endTime
  3449 + GROUP BY jkszh
  3450 + ) refund_stats ON u.F_Id = refund_stats.EmployeeId
  3451 +
3385 3452 WHERE u.F_GW = '健康师'
3386 3453 ";
3387 3454  
... ... @@ -3966,5 +4033,101 @@ namespace NCC.Extend.LqKdKdjlb
3966 4033 }
3967 4034 #endregion
3968 4035  
  4036 + #region 清空欠款
  4037 + /// <summary>
  4038 + /// 清空欠款(减免剩余欠款)
  4039 + /// </summary>
  4040 + /// <remarks>
  4041 + /// 返回参数说明:
  4042 + /// - billingId: 开单ID
  4043 + /// - original: 原始数据(zdyj-整单业绩, qk-欠款, paidDebt-已交欠款, remainingDebt-剩余欠款)
  4044 + /// - updated: 更新后数据(zdyj-整单业绩, qk-欠款, paidDebt-已交欠款, remainingDebt-剩余欠款)
  4045 + /// - reducedAmount: 减免金额
  4046 + /// </remarks>
  4047 + /// <param name="id">开单记录ID</param>
  4048 + /// <returns>操作结果</returns>
  4049 + /// <response code="200">成功清空欠款</response>
  4050 + /// <response code="400">参数错误或无剩余欠款</response>
  4051 + /// <response code="404">开单记录不存在</response>
  4052 + /// <response code="500">服务器错误</response>
  4053 + [HttpPost("clear-debt/{id}")]
  4054 + public async Task<dynamic> ClearDebt(string id)
  4055 + {
  4056 + try
  4057 + {
  4058 + // 1. 参数验证
  4059 + if (string.IsNullOrEmpty(id))
  4060 + {
  4061 + throw NCCException.Oh("开单ID不能为空");
  4062 + }
  4063 + // 2. 查询开单记录
  4064 + var entity = await _db.Queryable<LqKdKdjlbEntity>().FirstAsync(p => p.Id == id);
  4065 +
  4066 + if (entity == null)
  4067 + {
  4068 + throw NCCException.Oh("开单记录不存在");
  4069 + }
  4070 + // 3. 验证开单是否有效
  4071 + if (entity.IsEffective != StatusEnum.有效.GetHashCode())
  4072 + {
  4073 + throw NCCException.Oh("开单记录已作废,无法清空欠款");
  4074 + }
  4075 + // 4. 计算剩余欠款
  4076 + var remainingDebt = entity.Qk - entity.PaidDebt;
  4077 +
  4078 + // 5. 验证是否有剩余欠款
  4079 + if (remainingDebt <= 0)
  4080 + {
  4081 + return new
  4082 + {
  4083 + billingId = id,
  4084 + totalDebt = entity.Qk,
  4085 + paidDebt = entity.PaidDebt,
  4086 + remainingDebt = remainingDebt,
  4087 + message = "该开单无剩余欠款,无需清空"
  4088 + };
  4089 + }
  4090 +
  4091 + // 6. 记录原始数据(用于返回)
  4092 + var originalZdyj = entity.Zdyj;
  4093 + var originalQk = entity.Qk;
  4094 + // 7. 执行欠款减免
  4095 + // 从整单业绩中减去剩余欠款
  4096 + entity.Zdyj = entity.Zdyj - remainingDebt;
  4097 + // 欠款金额调整为已交金额(让剩余欠款归零)
  4098 + entity.Qk = entity.PaidDebt;
  4099 + // 更新时间
  4100 + entity.UpdateTime = DateTime.Now;
  4101 + // 8. 更新数据库
  4102 + await _db.Updateable(entity).UpdateColumns(it => new { it.Zdyj, it.Qk, it.UpdateTime }).ExecuteCommandAsync();
  4103 + // 9. 返回结果
  4104 + return new
  4105 + {
  4106 + billingId = id,
  4107 + original = new
  4108 + {
  4109 + zdyj = originalZdyj,
  4110 + qk = originalQk,
  4111 + paidDebt = entity.PaidDebt,
  4112 + remainingDebt = remainingDebt
  4113 + },
  4114 + updated = new
  4115 + {
  4116 + zdyj = entity.Zdyj,
  4117 + qk = entity.Qk,
  4118 + paidDebt = entity.PaidDebt,
  4119 + remainingDebt = entity.Qk - entity.PaidDebt
  4120 + },
  4121 + reducedAmount = remainingDebt
  4122 + };
  4123 + }
  4124 + catch (Exception ex)
  4125 + {
  4126 + _logger.LogError(ex, $"清空欠款失败 - 开单ID: {id}");
  4127 + throw NCCException.Oh($"清空欠款失败: {ex.Message}");
  4128 + }
  4129 + }
  4130 + #endregion
  4131 +
3969 4132 }
3970 4133 }
... ...
netcore/src/Modularity/Extend/NCC.Extend/LqKhxxService.cs
... ... @@ -116,6 +116,8 @@ namespace NCC.Extend.LqKhxx
116 116 DateTime? endZcsj = queryZcsj != null ? Ext.GetDateTime(queryZcsj.Last()) : null;
117 117 var data = await _db.Queryable<LqKhxxEntity>()
118 118 .WhereIF(!string.IsNullOrEmpty(input.keyWord), p => p.Khmc.Contains(input.keyWord) || p.Sjh.Contains(input.keyWord) || p.Dah.Contains(input.keyWord))
  119 + .WhereIF(input.IsTechMemberbh.HasValue, p => p.IsTechMember == input.IsTechMemberbh)
  120 + .WhereIF(input.IsEducationMember.HasValue, p => p.IsEducationMember == input.IsEducationMember)
119 121 .WhereIF(!string.IsNullOrEmpty(input.id), p => p.Id.Contains(input.id))
120 122 .WhereIF(!string.IsNullOrEmpty(input.khmc), p => p.Khmc.Contains(input.khmc))
121 123 .WhereIF(!string.IsNullOrEmpty(input.sjh), p => p.Sjh.Contains(input.sjh))
... ... @@ -163,9 +165,11 @@ namespace NCC.Extend.LqKhxx
163 165 isBeautyMember = it.IsBeautyMember,
164 166 isMedicalMember = it.IsMedicalMember,
165 167 isTechMember = it.IsTechMember,
  168 + isEducationMember = it.IsEducationMember,
166 169 beautyMemberTime = it.BeautyMemberTime,
167 170 medicalMemberTime = it.MedicalMemberTime,
168 171 techMemberTime = it.TechMemberTime,
  172 + educationMemberTime = it.EducationMemberTime,
169 173 firstVisitTime = it.FirstVisitTime,
170 174 lastVisitTime = it.LastVisitTime,
171 175 visitDays = it.VisitDays,
... ... @@ -1636,7 +1640,6 @@ namespace NCC.Extend.LqKhxx
1636 1640 var techThreshold = MemberInfoUpdateConfig.TechMemberAmountThreshold;
1637 1641 var minSleepThreshold = MemberInfoUpdateConfig.SleepDaysThresholds.Min();
1638 1642 var now = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
1639   -
1640 1643 // 构建SQL更新语句
1641 1644 var updateSql = $@"
1642 1645 UPDATE lq_khxx kh
... ...
netcore/src/Modularity/Extend/NCC.Extend/LqTkjlbService.cs
... ... @@ -741,6 +741,24 @@ namespace NCC.Extend.LqTkjlb
741 741 /// <summary>
742 742 /// 获取拓客活动漏斗统计数据(高性能版本 - 分步查询)
743 743 /// </summary>
  744 + /// <remarks>
  745 + /// 统计指定活动的漏斗转化数据
  746 + ///
  747 + /// 返回字段说明:
  748 + /// - store_id: 门店ID
  749 + /// - store_name: 门店名称
  750 + /// - tk_count: 拓客数量(活动拓客总人数)
  751 + /// - yaoy_count: 邀约数量(已邀约人数)
  752 + /// - yy_count: 预约数量(已确认预约人数)
  753 + /// - hk_count: 耗卡数量(实际到店耗卡人数)
  754 + /// - kd_count: 开单数量(实际成交开单人数)
  755 + /// - hk_amount: 耗卡金额(总耗卡业绩)
  756 + /// - kd_amount: 开单金额(总成交业绩)
  757 + /// - yy_conversion_rate: 预约转化率 (预约数量/拓客数量)
  758 + /// - hk_conversion_rate: 耗卡转化率 (耗卡数量/预约数量)
  759 + /// - visit_rate: 到店率 (耗卡数量/拓客数量)
  760 + /// - deal_rate: 成交率 (开单数量/耗卡数量)
  761 + /// </remarks>
744 762 /// <param name="eventId">活动ID</param>
745 763 /// <returns>漏斗统计数据</returns>
746 764 [HttpGet("GetFunnelStatisticsFast/{eventId}")]
... ... @@ -803,6 +821,7 @@ namespace NCC.Extend.LqTkjlb
803 821 var hkDict = hkData.ToDictionary(x => (string)x.F_StoreId, x => new { count = (int)x.hk_count, amount = (decimal)x.hk_amount });
804 822  
805 823 // 第五步:获取开单数据(按会员ID去重,但金额不去重)
  824 + // 修改:增加 sfyj > 0 的条件
806 825 var kdSql = @"
807 826 SELECT
808 827 tk.F_StoreId,
... ... @@ -810,26 +829,40 @@ namespace NCC.Extend.LqTkjlb
810 829 COALESCE(SUM(kd.sfyj), 0) as kd_amount
811 830 FROM lq_tkjlb tk
812 831 LEFT JOIN lq_kd_kdjlb kd ON tk.F_MemberId = kd.kdhy AND kd.F_IsEffective = 1
813   - WHERE tk.F_EventId = @eventId AND kd.F_Id IS NOT NULL
  832 + WHERE tk.F_EventId = @eventId AND kd.F_Id IS NOT NULL AND kd.sfyj > 0
814 833 GROUP BY tk.F_StoreId";
815 834  
816 835 var kdData = await _db.Ado.SqlQueryAsync<dynamic>(kdSql, new { eventId });
817 836 var kdDict = kdData.ToDictionary(x => (string)x.F_StoreId, x => new { count = (int)x.kd_count, amount = (decimal)x.kd_amount });
818 837  
819 838 // 合并结果
820   - var result = tkDict.Values.Select(tk => new
  839 + var result = tkDict.Values.Select(tk =>
821 840 {
822   - store_id = tk.F_StoreId,
823   - store_name = tk.store_name,
824   - tk_count = (int)tk.tk_count,
825   - yaoy_count = yaoyDict.ContainsKey(tk.F_StoreId) ? yaoyDict[tk.F_StoreId] : 0,
826   - yy_count = yyDict.ContainsKey(tk.F_StoreId) ? yyDict[tk.F_StoreId] : 0,
827   - hk_count = hkDict.ContainsKey(tk.F_StoreId) ? hkDict[tk.F_StoreId].count : 0,
828   - kd_count = kdDict.ContainsKey(tk.F_StoreId) ? kdDict[tk.F_StoreId].count : 0,
829   - hk_amount = hkDict.ContainsKey(tk.F_StoreId) ? hkDict[tk.F_StoreId].amount : 0m,
830   - kd_amount = kdDict.ContainsKey(tk.F_StoreId) ? kdDict[tk.F_StoreId].amount : 0m,
831   - yy_conversion_rate = (int)tk.tk_count > 0 ? Math.Round((yyDict.ContainsKey(tk.F_StoreId) ? yyDict[tk.F_StoreId] : 0) * 100.0 / (int)tk.tk_count, 2) : 0,
832   - hk_conversion_rate = (yyDict.ContainsKey(tk.F_StoreId) ? yyDict[tk.F_StoreId] : 0) > 0 ? Math.Round((hkDict.ContainsKey(tk.F_StoreId) ? hkDict[tk.F_StoreId].count : 0) * 100.0 / (yyDict.ContainsKey(tk.F_StoreId) ? yyDict[tk.F_StoreId] : 0), 2) : 0
  841 + var tkCount = (int)tk.tk_count;
  842 + var yyCount = yyDict.ContainsKey(tk.F_StoreId) ? yyDict[tk.F_StoreId] : 0;
  843 + var hkCount = hkDict.ContainsKey(tk.F_StoreId) ? hkDict[tk.F_StoreId].count : 0;
  844 + var kdCount = kdDict.ContainsKey(tk.F_StoreId) ? kdDict[tk.F_StoreId].count : 0;
  845 +
  846 + return new
  847 + {
  848 + store_id = tk.F_StoreId,
  849 + store_name = tk.store_name,
  850 + tk_count = tkCount,
  851 + yaoy_count = yaoyDict.ContainsKey(tk.F_StoreId) ? yaoyDict[tk.F_StoreId] : 0,
  852 + yy_count = yyCount,
  853 + hk_count = hkCount,
  854 + kd_count = kdCount,
  855 + hk_amount = hkDict.ContainsKey(tk.F_StoreId) ? hkDict[tk.F_StoreId].amount : 0m,
  856 + kd_amount = kdDict.ContainsKey(tk.F_StoreId) ? kdDict[tk.F_StoreId].amount : 0m,
  857 + // 预约转化率 = 预约 / 拓客
  858 + yy_conversion_rate = tkCount > 0 ? Math.Round(yyCount * 100.0 / tkCount, 2) : 0,
  859 + // 耗卡转化率 = 耗卡 / 预约
  860 + hk_conversion_rate = yyCount > 0 ? Math.Round(hkCount * 100.0 / yyCount, 2) : 0,
  861 + // 到店率 = 耗卡 / 拓客
  862 + visit_rate = tkCount > 0 ? Math.Round(hkCount * 100.0 / tkCount, 2) : 0,
  863 + // 成交率 = 开单 / 耗卡
  864 + deal_rate = hkCount > 0 ? Math.Round(kdCount * 100.0 / hkCount, 2) : 0
  865 + };
833 866 }).OrderByDescending(x => x.tk_count).ToList();
834 867  
835 868 return new
... ... @@ -868,7 +901,7 @@ namespace NCC.Extend.LqTkjlb
868 901 -- 预约信息
869 902 yy.F_Id as yy_id, -- 预约ID
870 903 yy.F_Status as yy_status, -- 预约状态
871   - yy.F_CreateTime as yy_time, -- 预约时间
  904 + yy.czsj as yy_time, -- 预约操作时间
872 905 yy.yysj as appointment_time, -- 预约到店时间
873 906 -- 耗卡信息(聚合)
874 907 xh_summary.total_consume_amount, -- 耗卡总金额
... ... @@ -891,7 +924,7 @@ namespace NCC.Extend.LqTkjlb
891 924 ELSE '未预约'
892 925 END as appointment_status, -- 预约状态描述
893 926 CASE
894   - WHEN xh_summary.total_consume_amount > 0 THEN '已耗卡'
  927 + WHEN xh_summary.consume_count > 0 THEN '已耗卡'
895 928 ELSE '未耗卡'
896 929 END as consume_status, -- 耗卡状态描述
897 930 CASE
... ... @@ -905,14 +938,14 @@ namespace NCC.Extend.LqTkjlb
905 938 AND yy.F_Status = '已确认'
906 939 LEFT JOIN (
907 940 SELECT
908   - hy as member_id,
  941 + hyzh as member_id,
909 942 SUM(COALESCE(xfje, 0)) as total_consume_amount,
910 943 COUNT(*) as consume_count,
911 944 MIN(hksj) as first_consume_time,
912 945 MAX(hksj) as last_consume_time
913 946 FROM lq_xh_hyhk
914 947 WHERE F_IsEffective = 1
915   - GROUP BY hy
  948 + GROUP BY hyzh
916 949 ) xh_summary ON tk.F_MemberId = xh_summary.member_id
917 950 LEFT JOIN (
918 951 SELECT
... ... @@ -962,6 +995,34 @@ namespace NCC.Extend.LqTkjlb
962 995 /// <summary>
963 996 /// 获取门店拓客活动顾客详情(分页)
964 997 /// </summary>
  998 + /// <remarks>
  999 + /// 返回字段说明:
  1000 + /// - tk_id: 拓客记录ID
  1001 + /// - customer_phone: 顾客手机号
  1002 + /// - member_id: 会员ID
  1003 + /// - customer_name: 顾客姓名
  1004 + /// - tk_time: 拓客时间
  1005 + /// - yaoy_id: 邀约ID
  1006 + /// - yaoy_time: 邀约创建时间
  1007 + /// - yaoy_appointment_time: 邀约预约时间
  1008 + /// - yy_id: 预约ID
  1009 + /// - yy_status: 预约状态
  1010 + /// - yy_time: 预约创建时间
  1011 + /// - appointment_time: 预约到店时间
  1012 + /// - total_consume_amount: 耗卡总金额
  1013 + /// - consume_count: 耗卡次数
  1014 + /// - first_consume_time: 首次耗卡时间
  1015 + /// - last_consume_time: 最后耗卡时间
  1016 + /// - total_billing_amount: 开卡总金额
  1017 + /// - total_debt_amount: 总欠款金额
  1018 + /// - billing_count: 开卡次数
  1019 + /// - first_billing_time: 首次开卡时间
  1020 + /// - last_billing_time: 最后开卡时间
  1021 + /// - invitation_status: 邀约状态描述 (已邀约/未邀约)
  1022 + /// - appointment_status: 预约状态描述 (已预约/未预约)
  1023 + /// - consume_status: 耗卡状态描述 (已耗卡/未耗卡)
  1024 + /// - billing_status: 开卡状态描述 (已开卡/未开卡)
  1025 + /// </remarks>
965 1026 /// <param name="eventId">活动ID</param>
966 1027 /// <param name="storeId">门店ID</param>
967 1028 /// <param name="pageIndex">页码</param>
... ...
netcore/src/Modularity/Extend/NCC.Extend/LqXhFeedbackService.cs
... ... @@ -303,5 +303,79 @@ namespace NCC.Extend
303 303 }
304 304 }
305 305 #endregion
  306 +
  307 + #region 获取耗卡反馈列表
  308 + /// <summary>
  309 + /// 获取耗卡反馈列表
  310 + /// </summary>
  311 + /// <remarks>
  312 + /// 获取耗卡反馈列表,支持分页和条件查询
  313 + ///
  314 + /// 示例请求:
  315 + /// POST /api/Extend/LqXhFeedback/GetList
  316 + /// {
  317 + /// "currentPage": 1,
  318 + /// "pageSize": 10,
  319 + /// "memberId": "会员ID",
  320 + /// "startTime": "2025-01-01",
  321 + /// "endTime": "2025-01-31"
  322 + /// }
  323 + ///
  324 + /// 参数说明:
  325 + /// - currentPage: 当前页码
  326 + /// - pageSize: 每页条数
  327 + /// - memberId: 会员ID(可选)
  328 + /// - consumeId: 耗卡记录ID(可选)
  329 + /// - startTime: 开始时间(可选)
  330 + /// - endTime: 结束时间(可选)
  331 + /// </remarks>
  332 + /// <param name="input">查询参数</param>
  333 + /// <returns>耗卡反馈列表</returns>
  334 + [HttpPost("GetList")]
  335 + public async Task<dynamic> GetList([FromBody] LqXhFeedbackListQueryInput input)
  336 + {
  337 + try
  338 + {
  339 + var query = _db.Queryable<LqXhFeedbackEntity>()
  340 + .WhereIF(!string.IsNullOrEmpty(input.MemberId), p => p.MemberId == input.MemberId)
  341 + .WhereIF(!string.IsNullOrEmpty(input.ConsumeId), p => p.ConsumeId == input.ConsumeId)
  342 + .WhereIF(!string.IsNullOrEmpty(input.CreateUser), p => p.CreateUser == input.CreateUser)
  343 + .WhereIF(input.StartTime.HasValue, p => p.CreateTime >= input.StartTime.Value)
  344 + .WhereIF(input.EndTime.HasValue, p => p.CreateTime <= input.EndTime.Value)
  345 + .OrderBy(p => p.CreateTime, OrderByType.Desc);
  346 +
  347 + var list = await query.ToPageListAsync(input.currentPage, input.pageSize);
  348 + var totalCount = await query.CountAsync();
  349 +
  350 + var data = list.Adapt<List<LqXhFeedbackListOutput>>();
  351 +
  352 + // 填充添加人姓名
  353 + if (data.Any())
  354 + {
  355 + var userIds = data.Select(x => x.createUser).Distinct().ToList();
  356 + var users = await _db.Queryable<UserEntity>().Where(x => userIds.Contains(x.Id)).ToListAsync();
  357 + foreach (var item in data)
  358 + {
  359 + item.createUserName = users.FirstOrDefault(x => x.Id == item.createUser)?.RealName;
  360 + }
  361 + }
  362 +
  363 + return new
  364 + {
  365 + list = data,
  366 + pagination = new
  367 + {
  368 + pageIndex = input.currentPage,
  369 + pageSize = input.pageSize,
  370 + totalCount = totalCount
  371 + }
  372 + };
  373 + }
  374 + catch (Exception ex)
  375 + {
  376 + throw NCCException.Oh($"获取耗卡反馈列表失败: {ex.Message}");
  377 + }
  378 + }
  379 + #endregion
306 380 }
307 381 }
... ...
netcore/src/Modularity/Extend/NCC.Extend/LqXhHyhkService.cs
... ... @@ -968,6 +968,8 @@ namespace NCC.Extend.LqXhHyhk
968 968 IsAccompanied = ijks_tem.isAccompanied,
969 969 AccompaniedProjectNumber = ijks_tem.accompaniedProjectNumber,
970 970 IsEffective = StatusEnum.有效.GetHashCode(),
  971 + ItemCategory = lqXhPxmxEntity.ItemCategory,
  972 + ItemId = lqXhPxmxEntity.Px,
971 973 }
972 974 );
973 975 }
... ... @@ -1003,6 +1005,8 @@ namespace NCC.Extend.LqXhHyhk
1003 1005 OvertimeLaborCost = 0,
1004 1006 LaborCost = ikjbs_tem.laborCost,
1005 1007 IsEffective = StatusEnum.有效.GetHashCode(),
  1008 + ItemCategory = lqXhPxmxEntity.ItemCategory,
  1009 + ItemId = lqXhPxmxEntity.Px,
1006 1010 }
1007 1011 );
1008 1012 }
... ... @@ -1335,6 +1339,8 @@ namespace NCC.Extend.LqXhHyhk
1335 1339 IsEffective = StatusEnum.有效.GetHashCode(),
1336 1340 IsAccompanied = ijks_tem.isAccompanied,
1337 1341 AccompaniedProjectNumber = ijks_tem.accompaniedProjectNumber,
  1342 + ItemCategory = lqXhPxmxEntity.ItemCategory,
  1343 + ItemId = lqXhPxmxEntity.Px,
1338 1344 }
1339 1345 );
1340 1346 }
... ... @@ -1369,6 +1375,8 @@ namespace NCC.Extend.LqXhHyhk
1369 1375 OvertimeLaborCost = 0,
1370 1376 LaborCost = ikjbs_tem.laborCost,
1371 1377 IsEffective = StatusEnum.有效.GetHashCode(),
  1378 + ItemCategory = lqXhPxmxEntity.ItemCategory,
  1379 + ItemId = lqXhPxmxEntity.Px,
1372 1380 });
1373 1381 }
1374 1382 }
... ...