diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..834dd81 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,35 @@ +{ + "version": "0.2.0", + "configurations": [ + { + // Use IntelliSense to find out which attributes exist for C# debugging + // Use hover for the description of the existing attributes + // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md + "name": ".NET Core Launch (web)", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build", + // If you have changed target frameworks, make sure to update the program path. + "program": "${workspaceFolder}/netcore/src/Application/NCC.API/bin/Debug/net6.0/NCC.API.dll", + "args": [], + "cwd": "${workspaceFolder}/netcore/src/Application/NCC.API", + "stopAtEntry": false, + // Enable launching a web browser when ASP.NET Core starts. For more information: https://aka.ms/VSCode-CS-LaunchJson-WebBrowser + "serverReadyAction": { + "action": "openExternally", + "pattern": "\\bNow listening on:\\s+(https?://\\S+)" + }, + "env": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "sourceFileMap": { + "/Views": "${workspaceFolder}/Views" + } + }, + { + "name": ".NET Core Attach", + "type": "coreclr", + "request": "attach" + } + ] +} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..02fcf0f --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,42 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "build", + "command": "dotnet", + "type": "process", + "args": [ + "build", + "${workspaceFolder}/netcore/src/Application/NCC.API/NCC.API.csproj", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "publish", + "command": "dotnet", + "type": "process", + "args": [ + "publish", + "${workspaceFolder}/netcore/src/Application/NCC.API/NCC.API.csproj", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "watch", + "command": "dotnet", + "type": "process", + "args": [ + "watch", + "run", + "${workspaceFolder}/netcore/src/Application/NCC.API/NCC.API.csproj", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary" + ], + "problemMatcher": "$msCompile" + } + ] +} \ No newline at end of file diff --git a/PROJECT_RULES.md b/PROJECT_RULES.md index 48c51c9..908782f 100644 --- a/PROJECT_RULES.md +++ b/PROJECT_RULES.md @@ -35,6 +35,7 @@ - **图标显示**: 所有列表数据都要有图标,不同颜色区分类型 - **空值显示**: 没有信息的字段显示"无" - **列表规范**: 列表数据不能换行 +- **弹窗显示**: 弹窗需要使用圆角 12px ### 性能要求 - 启用懒加载和代码分割 diff --git a/antis-ncc-admin/.env.development b/antis-ncc-admin/.env.development index c236b7d..f6590fa 100644 --- a/antis-ncc-admin/.env.development +++ b/antis-ncc-admin/.env.development @@ -2,7 +2,7 @@ VUE_CLI_BABEL_TRANSPILE_MODULES = true # VUE_APP_BASE_API = 'https://erp.lvqianmeiye.com' -VUE_APP_BASE_API = 'http://erp_test.lvqianmeiye.com' -# VUE_APP_BASE_API = 'http://localhost:2011' +# VUE_APP_BASE_API = 'http://erp_test.lvqianmeiye.com' +VUE_APP_BASE_API = 'http://localhost:2011' # VUE_APP_BASE_API = 'http://localhost:2011' VUE_APP_BASE_WSS = 'ws://192.168.110.45:2011/websocket' diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKdKdjlb/BillingItemsByMemberIdQueryInput.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKdKdjlb/BillingItemsByMemberIdQueryInput.cs new file mode 100644 index 0000000..a7c6d60 --- /dev/null +++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKdKdjlb/BillingItemsByMemberIdQueryInput.cs @@ -0,0 +1,46 @@ +using System; +using NCC.Common.Filter; + +namespace NCC.Extend.Entitys.Dto.LqKdKdjlb +{ + /// + /// 会员开单品项列表查询输入 + /// + public class BillingItemsByMemberIdQueryInput : PageInputBase + { + /// + /// 会员ID + /// + public string MemberId { get; set; } + + /// + /// 品项分类 + /// + public string ItemCategory { get; set; } + + /// + /// 开始时间(业绩时间) + /// + public DateTime? StartTime { get; set; } + + /// + /// 结束时间(业绩时间) + /// + public DateTime? EndTime { get; set; } + + /// + /// 最小实付金额 + /// + public decimal? MinActualPrice { get; set; } + + /// + /// 最大实付金额 + /// + public decimal? MaxActualPrice { get; set; } + + /// + /// 开单编号 + /// + public string BillingId { get; set; } + } +} diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKdKdjlb/HealthCoachStatisticsOutput.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKdKdjlb/HealthCoachStatisticsOutput.cs index 235f3c5..7a59134 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKdKdjlb/HealthCoachStatisticsOutput.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKdKdjlb/HealthCoachStatisticsOutput.cs @@ -89,5 +89,10 @@ namespace NCC.Extend.Entitys.Dto.LqKdKdjlb /// 消耗项目数 - 统计该健康师在指定时间周期内消耗的项目总次数 /// public decimal projectCount { get; set; } + + /// + /// 退卡金额 - 统计该健康师在指定时间周期内的退卡业绩总金额 + /// + public decimal refundAmount { get; set; } } } diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKhxx/LqKhxxInfoOutput.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKhxx/LqKhxxInfoOutput.cs index e8762af..17e3ab7 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKhxx/LqKhxxInfoOutput.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKhxx/LqKhxxInfoOutput.cs @@ -151,6 +151,11 @@ namespace NCC.Extend.Entitys.Dto.LqKhxx public int isTechMember { get; set; } /// + /// 是否教育部会员(0-否,1-是) + /// + public int isEducationMember { get; set; } + + /// /// 成为生美会员时间 /// public DateTime? beautyMemberTime { get; set; } @@ -166,6 +171,11 @@ namespace NCC.Extend.Entitys.Dto.LqKhxx public DateTime? techMemberTime { get; set; } /// + /// 成为教育部会员时间 + /// + public DateTime? educationMemberTime { get; set; } + + /// /// 首次到店时间 /// public DateTime? firstVisitTime { get; set; } diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKhxx/LqKhxxListOutput.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKhxx/LqKhxxListOutput.cs index 924fde8..5108881 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKhxx/LqKhxxListOutput.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKhxx/LqKhxxListOutput.cs @@ -178,6 +178,11 @@ namespace NCC.Extend.Entitys.Dto.LqKhxx public int isTechMember { get; set; } /// + /// 是否教育部会员(0-否,1-是) + /// + public int isEducationMember { get; set; } + + /// /// 成为生美会员时间 /// public DateTime? beautyMemberTime { get; set; } @@ -193,6 +198,11 @@ namespace NCC.Extend.Entitys.Dto.LqKhxx public DateTime? techMemberTime { get; set; } /// + /// 成为教育部会员时间 + /// + public DateTime? educationMemberTime { get; set; } + + /// /// 首次到店时间 /// public DateTime? firstVisitTime { get; set; } diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKhxx/LqKhxxListQueryInput.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKhxx/LqKhxxListQueryInput.cs index c60cd12..3e8df55 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKhxx/LqKhxxListQueryInput.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKhxx/LqKhxxListQueryInput.cs @@ -83,6 +83,15 @@ namespace NCC.Extend.Entitys.Dto.LqKhxx /// public string tjr { get; set; } + /// + /// 是否科技部会员 + /// + public int? IsTechMemberbh { get; set; } + + /// + /// 是否教育部会员 + /// + public int? IsEducationMember { get; set; } /// /// 进店渠道 diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqXhFeedback/LqXhFeedbackListQueryInput.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqXhFeedback/LqXhFeedbackListQueryInput.cs index 7b50695..2c810c7 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqXhFeedback/LqXhFeedbackListQueryInput.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqXhFeedback/LqXhFeedbackListQueryInput.cs @@ -13,6 +13,11 @@ namespace NCC.Extend.Entitys.Dto.LqXhFeedback public string ConsumeId { get; set; } /// + /// 会员ID + /// + public string MemberId { get; set; } + + /// /// 添加人ID /// public string CreateUser { get; set; } diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_khxx/LqKhxxEntity.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_khxx/LqKhxxEntity.cs index 64c4b33..bb078a7 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_khxx/LqKhxxEntity.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_khxx/LqKhxxEntity.cs @@ -184,6 +184,18 @@ namespace NCC.Extend.Entitys.lq_khxx public DateTime? TechMemberTime { get; set; } /// + /// 是否教育部会员(0-否,1-是) + /// + [SugarColumn(ColumnName = "F_IsEducationMember")] + public int IsEducationMember { get; set; } = 0; + + /// + /// 成为教育部会员时间 + /// + [SugarColumn(ColumnName = "F_EducationMemberTime")] + public DateTime? EducationMemberTime { get; set; } + + /// /// 首次到店时间 /// [SugarColumn(ColumnName = "F_FirstVisitTime")] diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqHytkHytkService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqHytkHytkService.cs index 2906262..adbe367 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend/LqHytkHytkService.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend/LqHytkHytkService.cs @@ -435,6 +435,8 @@ namespace NCC.Extend.LqHytkHytk F_CreateUser = userInfo.userId, CardReturn = lqHytkMxEntity.Id, IsEffective = StatusEnum.有效.GetHashCode(), + ItemCategory = lqHytkMxEntity.ItemCategory, + ItemId = lqHytkMxEntity.Px, } ); } @@ -462,6 +464,8 @@ namespace NCC.Extend.LqHytkHytk F_CreateUser = userInfo.userId, CardReturn = lqHytkMxEntity.Id, IsEffective = StatusEnum.有效.GetHashCode(), + ItemCategory = lqHytkMxEntity.ItemCategory, + ItemId = lqHytkMxEntity.Px, } ); } @@ -586,6 +590,8 @@ namespace NCC.Extend.LqHytkHytk F_tkpxNumber = ijks_tem.F_tkpxNumber, F_CreateTime = DateTime.Now, F_CreateUser = userInfo.userId, + ItemCategory = lqHytkMxEntity.ItemCategory, + ItemId = lqHytkMxEntity.Px, } ); } @@ -611,6 +617,8 @@ namespace NCC.Extend.LqHytkHytk F_tkpxNumber = ikjbs_tem.F_tkpxNumber, F_CreateTime = DateTime.Now, F_CreateUser = userInfo.userId, + ItemCategory = lqHytkMxEntity.ItemCategory, + ItemId = lqHytkMxEntity.Px, } ); } diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqKdKdjlbService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqKdKdjlbService.cs index 53b99e0..03c555d 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend/LqKdKdjlbService.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend/LqKdKdjlbService.cs @@ -953,6 +953,8 @@ namespace NCC.Extend.LqKdKdjlb Kdpxid = lqKdPxmxEntity.Id, IsEffective = StatusEnum.有效.GetHashCode(), ActivityId = input.activityId, + ItemCategory = lqKdPxmxEntity.ItemCategory, + ItemId = lqKdPxmxEntity.Px, }); } } @@ -974,6 +976,8 @@ namespace NCC.Extend.LqKdKdjlb Kdpxid = lqKdPxmxEntity.Id, IsEffective = StatusEnum.有效.GetHashCode(), ActivityId = input.activityId, + ItemCategory = lqKdPxmxEntity.ItemCategory, + ItemId = lqKdPxmxEntity.Px, } ); } @@ -1750,6 +1754,8 @@ namespace NCC.Extend.LqKdKdjlb Kdpxid = lqKdPxmxEntity.Id, IsEffective = StatusEnum.有效.GetHashCode(), ActivityId = input.activityId, + ItemCategory = lqKdPxmxEntity.ItemCategory, + ItemId = lqKdPxmxEntity.Px, }); } } @@ -1771,6 +1777,8 @@ namespace NCC.Extend.LqKdKdjlb Kdpxid = lqKdPxmxEntity.Id, IsEffective = StatusEnum.有效.GetHashCode(), ActivityId = input.activityId, + ItemCategory = lqKdPxmxEntity.ItemCategory, + ItemId = lqKdPxmxEntity.Px, } ); } @@ -2308,6 +2316,48 @@ namespace NCC.Extend.LqKdKdjlb } #endregion + #region 根据会员id获取会员的开单品项列表 + /// + /// 根据会员id获取会员的开单品项列表 + /// + /// 查询参数 + /// 会员的开单品项列表 + [HttpPost("GetBillingItemsByMemberId")] + public async Task GetBillingItemsByMemberId([FromBody] BillingItemsByMemberIdQueryInput input) + { + try + { + var query = _db.Queryable() + .Where(p => p.MemberId == input.MemberId) + .WhereIF(!string.IsNullOrEmpty(input.ItemCategory), p => p.ItemCategory == input.ItemCategory) + .WhereIF(!string.IsNullOrEmpty(input.BillingId), p => p.Glkdbh == input.BillingId) + .WhereIF(input.StartTime.HasValue, p => p.Yjsj >= input.StartTime.Value) + .WhereIF(input.EndTime.HasValue, p => p.Yjsj <= input.EndTime.Value) + .WhereIF(input.MinActualPrice.HasValue, p => p.ActualPrice >= input.MinActualPrice.Value) + .WhereIF(input.MaxActualPrice.HasValue, p => p.ActualPrice <= input.MaxActualPrice.Value) + .OrderBy(p => p.Yjsj, OrderByType.Desc); + + var list = await query.ToPageListAsync(input.currentPage, input.pageSize); + var totalCount = await query.CountAsync(); + + return new + { + list = list, + pagination = new + { + pageIndex = input.currentPage, + pageSize = input.pageSize, + totalCount = totalCount + } + }; + } + catch (Exception ex) + { + throw NCCException.Oh($"获取会员开单品项列表失败: {ex.Message}"); + } + } + #endregion + #region 根据开单id获取当前开单欠款信息 /// /// 根据开单id获取当前开单欠款信息 @@ -3173,6 +3223,7 @@ namespace NCC.Extend.LqKdKdjlb /// - HeadCount: 人头(按客户去重) /// - PersonCount: 人次(按客户+日期去重,同一客户不同天算多次) /// - ProjectCount: 消耗项目数(项目总次数) + /// - RefundAmount: 退卡金额(健康师退卡业绩总金额) /// /// 查询参数 /// 健康师统计数据列表 @@ -3217,7 +3268,10 @@ namespace NCC.Extend.LqKdKdjlb CAST(COALESCE(personcount_stats.PersonCount, 0) AS DECIMAL(18,2)) as PersonCount, CAST(COALESCE(invalid_headcount_stats.HeadCount, 0) AS DECIMAL(18,2)) as InvalidHeadCount, CAST(COALESCE(invalid_personcount_stats.PersonCount, 0) AS DECIMAL(18,2)) as InvalidPersonCount, - CAST(COALESCE(consume_stats.ProjectCount, 0) AS DECIMAL(18,2)) as ProjectCount + CAST(COALESCE(consume_stats.ProjectCount, 0) AS DECIMAL(18,2)) as ProjectCount, + + -- 退卡金额 + COALESCE(refund_stats.RefundAmount, 0) as RefundAmount FROM BASE_USER u LEFT JOIN lq_mdxx md ON u.F_MDID = md.F_Id @@ -3382,6 +3436,19 @@ namespace NCC.Extend.LqKdKdjlb GROUP BY F_PersonId ) invalid_personcount_stats ON u.F_Id = invalid_personcount_stats.EmployeeId + -- 退卡统计子查询 + LEFT JOIN ( + SELECT + jkszh as EmployeeId, + SUM(CAST(jksyj AS DECIMAL(18,2))) as RefundAmount + FROM lq_hytk_jksyj + WHERE jkszh IS NOT NULL + AND F_IsEffective = 1 + AND tksj >= @startTime + AND tksj <= @endTime + GROUP BY jkszh + ) refund_stats ON u.F_Id = refund_stats.EmployeeId + WHERE u.F_GW = '健康师' "; @@ -3966,5 +4033,101 @@ namespace NCC.Extend.LqKdKdjlb } #endregion + #region 清空欠款 + /// + /// 清空欠款(减免剩余欠款) + /// + /// + /// 返回参数说明: + /// - billingId: 开单ID + /// - original: 原始数据(zdyj-整单业绩, qk-欠款, paidDebt-已交欠款, remainingDebt-剩余欠款) + /// - updated: 更新后数据(zdyj-整单业绩, qk-欠款, paidDebt-已交欠款, remainingDebt-剩余欠款) + /// - reducedAmount: 减免金额 + /// + /// 开单记录ID + /// 操作结果 + /// 成功清空欠款 + /// 参数错误或无剩余欠款 + /// 开单记录不存在 + /// 服务器错误 + [HttpPost("clear-debt/{id}")] + public async Task ClearDebt(string id) + { + try + { + // 1. 参数验证 + if (string.IsNullOrEmpty(id)) + { + throw NCCException.Oh("开单ID不能为空"); + } + // 2. 查询开单记录 + var entity = await _db.Queryable().FirstAsync(p => p.Id == id); + + if (entity == null) + { + throw NCCException.Oh("开单记录不存在"); + } + // 3. 验证开单是否有效 + if (entity.IsEffective != StatusEnum.有效.GetHashCode()) + { + throw NCCException.Oh("开单记录已作废,无法清空欠款"); + } + // 4. 计算剩余欠款 + var remainingDebt = entity.Qk - entity.PaidDebt; + + // 5. 验证是否有剩余欠款 + if (remainingDebt <= 0) + { + return new + { + billingId = id, + totalDebt = entity.Qk, + paidDebt = entity.PaidDebt, + remainingDebt = remainingDebt, + message = "该开单无剩余欠款,无需清空" + }; + } + + // 6. 记录原始数据(用于返回) + var originalZdyj = entity.Zdyj; + var originalQk = entity.Qk; + // 7. 执行欠款减免 + // 从整单业绩中减去剩余欠款 + entity.Zdyj = entity.Zdyj - remainingDebt; + // 欠款金额调整为已交金额(让剩余欠款归零) + entity.Qk = entity.PaidDebt; + // 更新时间 + entity.UpdateTime = DateTime.Now; + // 8. 更新数据库 + await _db.Updateable(entity).UpdateColumns(it => new { it.Zdyj, it.Qk, it.UpdateTime }).ExecuteCommandAsync(); + // 9. 返回结果 + return new + { + billingId = id, + original = new + { + zdyj = originalZdyj, + qk = originalQk, + paidDebt = entity.PaidDebt, + remainingDebt = remainingDebt + }, + updated = new + { + zdyj = entity.Zdyj, + qk = entity.Qk, + paidDebt = entity.PaidDebt, + remainingDebt = entity.Qk - entity.PaidDebt + }, + reducedAmount = remainingDebt + }; + } + catch (Exception ex) + { + _logger.LogError(ex, $"清空欠款失败 - 开单ID: {id}"); + throw NCCException.Oh($"清空欠款失败: {ex.Message}"); + } + } + #endregion + } } diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqKhxxService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqKhxxService.cs index f6a4761..841589c 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend/LqKhxxService.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend/LqKhxxService.cs @@ -116,6 +116,8 @@ namespace NCC.Extend.LqKhxx DateTime? endZcsj = queryZcsj != null ? Ext.GetDateTime(queryZcsj.Last()) : null; var data = await _db.Queryable() .WhereIF(!string.IsNullOrEmpty(input.keyWord), p => p.Khmc.Contains(input.keyWord) || p.Sjh.Contains(input.keyWord) || p.Dah.Contains(input.keyWord)) + .WhereIF(input.IsTechMemberbh.HasValue, p => p.IsTechMember == input.IsTechMemberbh) + .WhereIF(input.IsEducationMember.HasValue, p => p.IsEducationMember == input.IsEducationMember) .WhereIF(!string.IsNullOrEmpty(input.id), p => p.Id.Contains(input.id)) .WhereIF(!string.IsNullOrEmpty(input.khmc), p => p.Khmc.Contains(input.khmc)) .WhereIF(!string.IsNullOrEmpty(input.sjh), p => p.Sjh.Contains(input.sjh)) @@ -163,9 +165,11 @@ namespace NCC.Extend.LqKhxx isBeautyMember = it.IsBeautyMember, isMedicalMember = it.IsMedicalMember, isTechMember = it.IsTechMember, + isEducationMember = it.IsEducationMember, beautyMemberTime = it.BeautyMemberTime, medicalMemberTime = it.MedicalMemberTime, techMemberTime = it.TechMemberTime, + educationMemberTime = it.EducationMemberTime, firstVisitTime = it.FirstVisitTime, lastVisitTime = it.LastVisitTime, visitDays = it.VisitDays, @@ -1636,7 +1640,6 @@ namespace NCC.Extend.LqKhxx var techThreshold = MemberInfoUpdateConfig.TechMemberAmountThreshold; var minSleepThreshold = MemberInfoUpdateConfig.SleepDaysThresholds.Min(); var now = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"); - // 构建SQL更新语句 var updateSql = $@" UPDATE lq_khxx kh diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqTkjlbService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqTkjlbService.cs index 5c106cc..7f3a344 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend/LqTkjlbService.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend/LqTkjlbService.cs @@ -741,6 +741,24 @@ namespace NCC.Extend.LqTkjlb /// /// 获取拓客活动漏斗统计数据(高性能版本 - 分步查询) /// + /// + /// 统计指定活动的漏斗转化数据 + /// + /// 返回字段说明: + /// - store_id: 门店ID + /// - store_name: 门店名称 + /// - tk_count: 拓客数量(活动拓客总人数) + /// - yaoy_count: 邀约数量(已邀约人数) + /// - yy_count: 预约数量(已确认预约人数) + /// - hk_count: 耗卡数量(实际到店耗卡人数) + /// - kd_count: 开单数量(实际成交开单人数) + /// - hk_amount: 耗卡金额(总耗卡业绩) + /// - kd_amount: 开单金额(总成交业绩) + /// - yy_conversion_rate: 预约转化率 (预约数量/拓客数量) + /// - hk_conversion_rate: 耗卡转化率 (耗卡数量/预约数量) + /// - visit_rate: 到店率 (耗卡数量/拓客数量) + /// - deal_rate: 成交率 (开单数量/耗卡数量) + /// /// 活动ID /// 漏斗统计数据 [HttpGet("GetFunnelStatisticsFast/{eventId}")] @@ -803,6 +821,7 @@ namespace NCC.Extend.LqTkjlb var hkDict = hkData.ToDictionary(x => (string)x.F_StoreId, x => new { count = (int)x.hk_count, amount = (decimal)x.hk_amount }); // 第五步:获取开单数据(按会员ID去重,但金额不去重) + // 修改:增加 sfyj > 0 的条件 var kdSql = @" SELECT tk.F_StoreId, @@ -810,26 +829,40 @@ namespace NCC.Extend.LqTkjlb COALESCE(SUM(kd.sfyj), 0) as kd_amount FROM lq_tkjlb tk LEFT JOIN lq_kd_kdjlb kd ON tk.F_MemberId = kd.kdhy AND kd.F_IsEffective = 1 - WHERE tk.F_EventId = @eventId AND kd.F_Id IS NOT NULL + WHERE tk.F_EventId = @eventId AND kd.F_Id IS NOT NULL AND kd.sfyj > 0 GROUP BY tk.F_StoreId"; var kdData = await _db.Ado.SqlQueryAsync(kdSql, new { eventId }); var kdDict = kdData.ToDictionary(x => (string)x.F_StoreId, x => new { count = (int)x.kd_count, amount = (decimal)x.kd_amount }); // 合并结果 - var result = tkDict.Values.Select(tk => new + var result = tkDict.Values.Select(tk => { - store_id = tk.F_StoreId, - store_name = tk.store_name, - tk_count = (int)tk.tk_count, - yaoy_count = yaoyDict.ContainsKey(tk.F_StoreId) ? yaoyDict[tk.F_StoreId] : 0, - yy_count = yyDict.ContainsKey(tk.F_StoreId) ? yyDict[tk.F_StoreId] : 0, - hk_count = hkDict.ContainsKey(tk.F_StoreId) ? hkDict[tk.F_StoreId].count : 0, - kd_count = kdDict.ContainsKey(tk.F_StoreId) ? kdDict[tk.F_StoreId].count : 0, - hk_amount = hkDict.ContainsKey(tk.F_StoreId) ? hkDict[tk.F_StoreId].amount : 0m, - kd_amount = kdDict.ContainsKey(tk.F_StoreId) ? kdDict[tk.F_StoreId].amount : 0m, - 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, - 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 + var tkCount = (int)tk.tk_count; + var yyCount = yyDict.ContainsKey(tk.F_StoreId) ? yyDict[tk.F_StoreId] : 0; + var hkCount = hkDict.ContainsKey(tk.F_StoreId) ? hkDict[tk.F_StoreId].count : 0; + var kdCount = kdDict.ContainsKey(tk.F_StoreId) ? kdDict[tk.F_StoreId].count : 0; + + return new + { + store_id = tk.F_StoreId, + store_name = tk.store_name, + tk_count = tkCount, + yaoy_count = yaoyDict.ContainsKey(tk.F_StoreId) ? yaoyDict[tk.F_StoreId] : 0, + yy_count = yyCount, + hk_count = hkCount, + kd_count = kdCount, + hk_amount = hkDict.ContainsKey(tk.F_StoreId) ? hkDict[tk.F_StoreId].amount : 0m, + kd_amount = kdDict.ContainsKey(tk.F_StoreId) ? kdDict[tk.F_StoreId].amount : 0m, + // 预约转化率 = 预约 / 拓客 + yy_conversion_rate = tkCount > 0 ? Math.Round(yyCount * 100.0 / tkCount, 2) : 0, + // 耗卡转化率 = 耗卡 / 预约 + hk_conversion_rate = yyCount > 0 ? Math.Round(hkCount * 100.0 / yyCount, 2) : 0, + // 到店率 = 耗卡 / 拓客 + visit_rate = tkCount > 0 ? Math.Round(hkCount * 100.0 / tkCount, 2) : 0, + // 成交率 = 开单 / 耗卡 + deal_rate = hkCount > 0 ? Math.Round(kdCount * 100.0 / hkCount, 2) : 0 + }; }).OrderByDescending(x => x.tk_count).ToList(); return new @@ -868,7 +901,7 @@ namespace NCC.Extend.LqTkjlb -- 预约信息 yy.F_Id as yy_id, -- 预约ID yy.F_Status as yy_status, -- 预约状态 - yy.F_CreateTime as yy_time, -- 预约时间 + yy.czsj as yy_time, -- 预约操作时间 yy.yysj as appointment_time, -- 预约到店时间 -- 耗卡信息(聚合) xh_summary.total_consume_amount, -- 耗卡总金额 @@ -891,7 +924,7 @@ namespace NCC.Extend.LqTkjlb ELSE '未预约' END as appointment_status, -- 预约状态描述 CASE - WHEN xh_summary.total_consume_amount > 0 THEN '已耗卡' + WHEN xh_summary.consume_count > 0 THEN '已耗卡' ELSE '未耗卡' END as consume_status, -- 耗卡状态描述 CASE @@ -905,14 +938,14 @@ namespace NCC.Extend.LqTkjlb AND yy.F_Status = '已确认' LEFT JOIN ( SELECT - hy as member_id, + hyzh as member_id, SUM(COALESCE(xfje, 0)) as total_consume_amount, COUNT(*) as consume_count, MIN(hksj) as first_consume_time, MAX(hksj) as last_consume_time FROM lq_xh_hyhk WHERE F_IsEffective = 1 - GROUP BY hy + GROUP BY hyzh ) xh_summary ON tk.F_MemberId = xh_summary.member_id LEFT JOIN ( SELECT @@ -962,6 +995,34 @@ namespace NCC.Extend.LqTkjlb /// /// 获取门店拓客活动顾客详情(分页) /// + /// + /// 返回字段说明: + /// - tk_id: 拓客记录ID + /// - customer_phone: 顾客手机号 + /// - member_id: 会员ID + /// - customer_name: 顾客姓名 + /// - tk_time: 拓客时间 + /// - yaoy_id: 邀约ID + /// - yaoy_time: 邀约创建时间 + /// - yaoy_appointment_time: 邀约预约时间 + /// - yy_id: 预约ID + /// - yy_status: 预约状态 + /// - yy_time: 预约创建时间 + /// - appointment_time: 预约到店时间 + /// - total_consume_amount: 耗卡总金额 + /// - consume_count: 耗卡次数 + /// - first_consume_time: 首次耗卡时间 + /// - last_consume_time: 最后耗卡时间 + /// - total_billing_amount: 开卡总金额 + /// - total_debt_amount: 总欠款金额 + /// - billing_count: 开卡次数 + /// - first_billing_time: 首次开卡时间 + /// - last_billing_time: 最后开卡时间 + /// - invitation_status: 邀约状态描述 (已邀约/未邀约) + /// - appointment_status: 预约状态描述 (已预约/未预约) + /// - consume_status: 耗卡状态描述 (已耗卡/未耗卡) + /// - billing_status: 开卡状态描述 (已开卡/未开卡) + /// /// 活动ID /// 门店ID /// 页码 diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqXhFeedbackService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqXhFeedbackService.cs index e3681fb..b4baecf 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend/LqXhFeedbackService.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend/LqXhFeedbackService.cs @@ -303,5 +303,79 @@ namespace NCC.Extend } } #endregion + + #region 获取耗卡反馈列表 + /// + /// 获取耗卡反馈列表 + /// + /// + /// 获取耗卡反馈列表,支持分页和条件查询 + /// + /// 示例请求: + /// POST /api/Extend/LqXhFeedback/GetList + /// { + /// "currentPage": 1, + /// "pageSize": 10, + /// "memberId": "会员ID", + /// "startTime": "2025-01-01", + /// "endTime": "2025-01-31" + /// } + /// + /// 参数说明: + /// - currentPage: 当前页码 + /// - pageSize: 每页条数 + /// - memberId: 会员ID(可选) + /// - consumeId: 耗卡记录ID(可选) + /// - startTime: 开始时间(可选) + /// - endTime: 结束时间(可选) + /// + /// 查询参数 + /// 耗卡反馈列表 + [HttpPost("GetList")] + public async Task GetList([FromBody] LqXhFeedbackListQueryInput input) + { + try + { + var query = _db.Queryable() + .WhereIF(!string.IsNullOrEmpty(input.MemberId), p => p.MemberId == input.MemberId) + .WhereIF(!string.IsNullOrEmpty(input.ConsumeId), p => p.ConsumeId == input.ConsumeId) + .WhereIF(!string.IsNullOrEmpty(input.CreateUser), p => p.CreateUser == input.CreateUser) + .WhereIF(input.StartTime.HasValue, p => p.CreateTime >= input.StartTime.Value) + .WhereIF(input.EndTime.HasValue, p => p.CreateTime <= input.EndTime.Value) + .OrderBy(p => p.CreateTime, OrderByType.Desc); + + var list = await query.ToPageListAsync(input.currentPage, input.pageSize); + var totalCount = await query.CountAsync(); + + var data = list.Adapt>(); + + // 填充添加人姓名 + if (data.Any()) + { + var userIds = data.Select(x => x.createUser).Distinct().ToList(); + var users = await _db.Queryable().Where(x => userIds.Contains(x.Id)).ToListAsync(); + foreach (var item in data) + { + item.createUserName = users.FirstOrDefault(x => x.Id == item.createUser)?.RealName; + } + } + + return new + { + list = data, + pagination = new + { + pageIndex = input.currentPage, + pageSize = input.pageSize, + totalCount = totalCount + } + }; + } + catch (Exception ex) + { + throw NCCException.Oh($"获取耗卡反馈列表失败: {ex.Message}"); + } + } + #endregion } } diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqXhHyhkService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqXhHyhkService.cs index e13d609..e6a8089 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend/LqXhHyhkService.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend/LqXhHyhkService.cs @@ -968,6 +968,8 @@ namespace NCC.Extend.LqXhHyhk IsAccompanied = ijks_tem.isAccompanied, AccompaniedProjectNumber = ijks_tem.accompaniedProjectNumber, IsEffective = StatusEnum.有效.GetHashCode(), + ItemCategory = lqXhPxmxEntity.ItemCategory, + ItemId = lqXhPxmxEntity.Px, } ); } @@ -1003,6 +1005,8 @@ namespace NCC.Extend.LqXhHyhk OvertimeLaborCost = 0, LaborCost = ikjbs_tem.laborCost, IsEffective = StatusEnum.有效.GetHashCode(), + ItemCategory = lqXhPxmxEntity.ItemCategory, + ItemId = lqXhPxmxEntity.Px, } ); } @@ -1335,6 +1339,8 @@ namespace NCC.Extend.LqXhHyhk IsEffective = StatusEnum.有效.GetHashCode(), IsAccompanied = ijks_tem.isAccompanied, AccompaniedProjectNumber = ijks_tem.accompaniedProjectNumber, + ItemCategory = lqXhPxmxEntity.ItemCategory, + ItemId = lqXhPxmxEntity.Px, } ); } @@ -1369,6 +1375,8 @@ namespace NCC.Extend.LqXhHyhk OvertimeLaborCost = 0, LaborCost = ikjbs_tem.laborCost, IsEffective = StatusEnum.有效.GetHashCode(), + ItemCategory = lqXhPxmxEntity.ItemCategory, + ItemId = lqXhPxmxEntity.Px, }); } }