From 4b74fde5c0f9a59082ffba1b1955c69e8cc82c39 Mon Sep 17 00:00:00 2001 From: “wangming” <“wangming@antissoft.com”> Date: Mon, 16 Mar 2026 17:24:06 +0800 Subject: [PATCH] Refactor overtime calculation logic to exclude 科技部 from overtime fees, ensuring accurate computation based on 健康师's original labor costs. Added refund achievement tracking in daily report statistics for comprehensive performance analysis. --- docs/加班系数逻辑说明及修改方案.md | 24 ++++++++++++++---------- netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqDailyReport/TechTeacherDailyStatisticsOutput.cs | 5 +++++ netcore/src/Modularity/Extend/NCC.Extend.Entitys/Mapper/LqReimbursementApplicationMapper.cs | 6 +++++- netcore/src/Modularity/Extend/NCC.Extend/LqDailyReportService.cs | 74 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----- netcore/src/Modularity/Extend/NCC.Extend/LqReimbursementApplicationService.cs | 19 +++++++++---------- netcore/src/Modularity/Extend/NCC.Extend/LqXhHyhkService.cs | 48 +++++++++++++++++++++++++++++++++--------------- netcore/src/Modularity/Message/NCC.Message.Entitys/D:/wesley/project/git/antis-food-alliance/netcore/src/Modularity/Message/NCC.Message.Entitys/NCC.Message.Entitys.xml | 924 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ sql/修复消耗单主表加班手工费历史数据.sql | 129 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ test-get-tech-teacher-daily-statistics.sh | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ test-xh-overtime-apis.sh | 34 ++++++++++++++++++++++++++++++++++ 10 files changed, 1272 insertions(+), 41 deletions(-) create mode 100644 netcore/src/Modularity/Message/NCC.Message.Entitys/D:/wesley/project/git/antis-food-alliance/netcore/src/Modularity/Message/NCC.Message.Entitys/NCC.Message.Entitys.xml create mode 100644 sql/修复消耗单主表加班手工费历史数据.sql create mode 100755 test-get-tech-teacher-daily-statistics.sh create mode 100644 test-xh-overtime-apis.sh diff --git a/docs/加班系数逻辑说明及修改方案.md b/docs/加班系数逻辑说明及修改方案.md index 152e458..a34f693 100644 --- a/docs/加班系数逻辑说明及修改方案.md +++ b/docs/加班系数逻辑说明及修改方案.md @@ -19,16 +19,19 @@ #### 📊 **主表(lq_xh_hyhk)计算** +**重要**:科技部老师不参与加班,主表加班手工费仅来自健康师。 + ``` -加班手工费(F_OvertimeSgfy)= 原始手工费(F_OriginalSgfy)× 加班系数(F_OvertimeCoefficient) +加班手工费(F_OvertimeSgfy)= 健康师原始手工费之和 × 加班系数(F_OvertimeCoefficient) 最终手工费(sgfy)= 原始手工费(F_OriginalSgfy)+ 加班手工费(F_OvertimeSgfy) ``` -**示例**: -- 原始手工费 = 100元 +**示例**(健康师12元 + 科技部40元 = 整单原始52元): +- 原始手工费 = 52元(健康师12 + 科技部40) +- 健康师原始手工费之和 = 12元 - 加班系数 = 0.5 -- 加班手工费 = 100 × 0.5 = 50元 -- 最终手工费 = 100 + 50 = 150元 +- 加班手工费 = 12 × 0.5 = 6元(仅健康师参与) +- 最终手工费 = 52 + 6 = 58元(= 健康师18 + 科技部40) --- @@ -94,8 +97,8 @@ LaborCost = ikjbs_tem.laborCost, ┌─────────────────────────────────────────────────────────────┐ │ lq_xh_hyhk(耗卡主表) │ │ F_OvertimeCoefficient(加班系数) │ -│ F_OriginalSgfy(原始手工费) │ -│ F_OvertimeSgfy(加班手工费)= OriginalSgfy × Coefficient │ +│ F_OriginalSgfy(原始手工费 = 健康师+科技部) │ +│ F_OvertimeSgfy(加班手工费)= Σ健康师原始手工费 × 系数 │ │ sgfy(最终手工费)= OriginalSgfy + OvertimeSgfy │ └─────────────────────────────────────────────────────────────┘ │ @@ -127,11 +130,12 @@ LaborCost = ikjbs_tem.laborCost, **流程**: 1. 接收 `LqXhHyhkCrInput` 参数,包含 `overtimeCoefficient` -2. 计算主表加班字段: +2. 计算主表加班字段(科技部不参与加班,主表加班手工费 = 健康师加班手工费之和): ```csharp entity.OvertimeCoefficient = input.overtimeCoefficient ?? 0; entity.OriginalSgfy = input.sgfy; - entity.OvertimeSgfy = entity.OriginalSgfy * entity.OvertimeCoefficient; + var jksOriginalLaborCostSum = input.lqXhPxmxList?.SelectMany(p => p.lqXhJksyjList ?? ...).Sum(j => j.laborCost ?? 0) ?? 0; + entity.OvertimeSgfy = (decimal)(jksOriginalLaborCostSum * entity.OvertimeCoefficient); entity.Sgfy = entity.OriginalSgfy + entity.OvertimeSgfy; ``` 3. 遍历品项明细,计算每个品项的加班字段: @@ -236,7 +240,7 @@ LaborCost = ikjbs_tem.laborCost, ``` 3. **计算逻辑**: - - 主表:重新计算 `F_OvertimeSgfy` 和 `sgfy` + - 主表:重新计算 `F_OvertimeSgfy`(= 健康师原始手工费之和 × 系数)和 `sgfy`(科技部不参与加班) - 品项明细表:重新计算 `F_OvertimeProjectNumber` 和 `F_ProjectNumber` - 健康师业绩表:重新计算 `F_OvertimeKdpxNumber`、`F_kdpxNumber`、`F_OvertimeLaborCost`、`F_LaborCost` - 科技部老师业绩表:如果需要支持,重新计算相关字段 diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqDailyReport/TechTeacherDailyStatisticsOutput.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqDailyReport/TechTeacherDailyStatisticsOutput.cs index ac3290a..12685fc 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqDailyReport/TechTeacherDailyStatisticsOutput.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqDailyReport/TechTeacherDailyStatisticsOutput.cs @@ -47,6 +47,11 @@ namespace NCC.Extend.Entitys.Dto.LqDailyReport /// 开单业绩 /// public decimal OrderAchievement { get; set; } + + /// + /// 退卡业绩(用于计算净业绩:净业绩 = 耗卡业绩 - 退卡业绩) + /// + public decimal RefundAchievement { get; set; } } } diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Mapper/LqReimbursementApplicationMapper.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Mapper/LqReimbursementApplicationMapper.cs index a4650cd..35c9758 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Mapper/LqReimbursementApplicationMapper.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Mapper/LqReimbursementApplicationMapper.cs @@ -1,4 +1,5 @@ -using NCC.Common.Helper; +using NCC.Common.Helper; +using NCC.Extend.Entitys; using NCC.Extend.Entitys.Dto.LqReimbursementApplication; using Mapster; using System.Collections.Generic; @@ -9,6 +10,9 @@ namespace NCC.Extend.Entitys.Mapper.LqReimbursementApplication { public void Register(TypeAdapterConfig config) { + // 确保 camelCase 的 workflowConfigId 正确映射到 Entity 的 WorkflowConfigId + config.NewConfig() + .Map(d => d.WorkflowConfigId, s => s.workflowConfigId); } } } diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqDailyReportService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqDailyReportService.cs index 6022566..a37a1f9 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend/LqDailyReportService.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend/LqDailyReportService.cs @@ -1159,6 +1159,7 @@ namespace NCC.Extend /// - ConsumeProjectCount: 消耗项目数 /// - ConsumeAchievement: 消耗业绩 /// - OrderAchievement: 开单业绩 + /// - RefundAchievement: 退卡业绩(净业绩 = 耗卡业绩 - 退卡业绩) /// /// 查询参数 /// 科技部老师统计列表 @@ -1193,6 +1194,13 @@ namespace NCC.Extend teacherFilterForOrder = $"AND ord.kjbls IN ('{teacherIdsStr}')"; } + var teacherFilterForRefund = ""; + if (input.TeacherIds != null && input.TeacherIds.Any()) + { + var teacherIdsStr = string.Join("','", input.TeacherIds); + teacherFilterForRefund = $"AND refund.kjbls IN ('{teacherIdsStr}')"; + } + // SQL查询:统计科技部老师的消耗业绩、见客数、项目数 // 注意:GROUP BY 中移除了 user.F_RealName,避免同一老师ID因姓名不同产生重复记录 var consumeSql = $@" @@ -1242,6 +1250,29 @@ namespace NCC.Extend var orderResult = await _db.Ado.SqlQueryAsync(orderSql); + // 查询退卡业绩(与耗卡使用相同过滤条件:门店、时间、人员、科技部) + var refundSql = $@" + SELECT + techDept.F_Id as TechDepartmentId, + techDept.F_FullName as TechDepartmentName, + refund.kjbls as TeacherId, + MAX(user.F_RealName) as TeacherName, + SUM(refund.kjblsyj) as RefundAchievement + FROM lq_hytk_kjbsyj refund + INNER JOIN lq_hytk_hytk hytk ON refund.gltkbh = hytk.F_Id + INNER JOIN lq_mdxx store ON hytk.md = store.F_Id + LEFT JOIN base_organize techDept ON store.kjb = techDept.F_Id + LEFT JOIN BASE_USER user ON refund.kjbls = user.F_Id + WHERE refund.F_IsEffective = 1 + AND hytk.F_IsEffective = 1 + AND DATE(hytk.tksj) >= '{startDate:yyyy-MM-dd}' + AND DATE(hytk.tksj) <= '{endDate:yyyy-MM-dd}' + {techFilter} + {teacherFilterForRefund} + GROUP BY techDept.F_Id, techDept.F_FullName, refund.kjbls"; + + var refundResult = await _db.Ado.SqlQueryAsync(refundSql); + // 合并数据:按员工ID汇总,避免同一员工在多个科技部重复出现 // 使用 TeacherId 作为唯一键,汇总所有科技部的数据 var teacherDict = new Dictionary(); @@ -1287,7 +1318,8 @@ namespace NCC.Extend CustomerCount = 0, ConsumeProjectCount = 0, ConsumeAchievement = 0, - OrderAchievement = 0 + OrderAchievement = 0, + RefundAchievement = 0 }; } @@ -1334,7 +1366,8 @@ namespace NCC.Extend CustomerCount = 0, ConsumeProjectCount = 0, ConsumeAchievement = 0, - OrderAchievement = 0 + OrderAchievement = 0, + RefundAchievement = 0 }; } else @@ -1349,7 +1382,38 @@ namespace NCC.Extend teacherDict[teacherId].OrderAchievement += orderAchievement; } - // 第三步:确定每个员工的主要科技部 + // 第三步:处理退卡业绩,按员工ID汇总 + foreach (var item in refundResult ?? Enumerable.Empty()) + { + var teacherId = item.TeacherId?.ToString(); + var techDeptId = item.TechDepartmentId?.ToString(); + var techDeptName = item.TechDepartmentName?.ToString(); + var refundAchievement = Convert.ToDecimal(item.RefundAchievement); + + if (string.IsNullOrEmpty(teacherId)) + continue; + + if (!teacherDict.ContainsKey(teacherId)) + { + // 仅有退卡数据、无消耗和开单时,也创建记录 + teacherDict[teacherId] = new TechTeacherDailyStatisticsOutput + { + TechDepartmentId = techDeptId, + TechDepartmentName = techDeptName, + TeacherId = teacherId, + TeacherName = item.TeacherName?.ToString(), + CustomerCount = 0, + ConsumeProjectCount = 0, + ConsumeAchievement = 0, + OrderAchievement = 0, + RefundAchievement = 0 + }; + } + + teacherDict[teacherId].RefundAchievement += refundAchievement; + } + + // 第四步:确定每个员工的主要科技部 // 优先按消耗业绩最多的科技部,如果消耗业绩为0,则按开单业绩最多的科技部 foreach (var teacherId in teacherDict.Keys.ToList()) { @@ -1424,7 +1488,7 @@ namespace NCC.Extend } } - // 第四步:重新计算见客数(去重,因为同一个客户可能在多个科技部被统计) + // 第五步:重新计算见客数(去重,因为同一个客户可能在多个科技部被统计) // 由于已经汇总了数据,这里需要重新查询去重后的见客数 var teacherIds = teacherDict.Keys.ToList(); if (teacherIds.Any()) @@ -1453,7 +1517,7 @@ namespace NCC.Extend } } - // 第五步:统一查询所有用户名称,确保所有用户都能获取到名称 + // 第六步:统一查询所有用户名称,确保所有用户都能获取到名称 var teacherNamesSql = $@" SELECT F_Id as TeacherId, diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqReimbursementApplicationService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqReimbursementApplicationService.cs index 8fe1bc3..b19378f 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend/LqReimbursementApplicationService.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend/LqReimbursementApplicationService.cs @@ -282,8 +282,8 @@ namespace NCC.Extend.LqReimbursementApplication // 先查询实体类中有CompletionTime字段且符合条件的申请ID var entitiesWithCompletionTime = await _db.Queryable() - .Where(x => x.CompletionTime.HasValue - && x.CompletionTime.Value >= startDate + .Where(x => x.CompletionTime.HasValue + && x.CompletionTime.Value >= startDate && x.CompletionTime.Value <= endDate) .Select(x => x.Id) .ToListAsync(); @@ -528,11 +528,8 @@ namespace NCC.Extend.LqReimbursementApplication } } - // 2. 设置报销申请初始状态 - if (!string.IsNullOrEmpty(input.workflowConfigId)) - { - entity.WorkflowConfigId = input.workflowConfigId.Trim(); - } + // 2. 设置报销申请初始状态(含流程配置ID,确保入库) + entity.WorkflowConfigId = !string.IsNullOrEmpty(input.workflowConfigId) ? input.workflowConfigId.Trim() : null; entity.NodeCount = input.nodes.Count; entity.CurrentNodeOrder = 0; entity.ApprovalStatus = "待审批"; @@ -546,8 +543,8 @@ namespace NCC.Extend.LqReimbursementApplication entity.ApplicationUserName = userInfo.userName; } - // 3. 保存报销申请表(不使用IgnoreColumns,确保新字段被保存) - var isOk = await _db.Insertable(entity).ExecuteCommandAsync(); + // 3. 保存报销申请表(ignoreNullColumn: false 确保 WorkflowConfigId 等可选字段能正确入库) + var isOk = await _db.Insertable(entity).IgnoreColumns(ignoreNullColumn: false).ExecuteCommandAsync(); if (!(isOk > 0)) throw NCCException.Oh(ErrorCode.COM1000); // 4. 创建节点配置 @@ -825,8 +822,10 @@ namespace NCC.Extend.LqReimbursementApplication .ExecuteCommandAsync(); } - // 更新报销申请表(确保 purchaseRecordsId 字段被正确更新) + // 更新报销申请表(确保 purchaseRecordsId、WorkflowConfigId 等字段被正确更新) var entity = input.Adapt(); + entity.Id = id; // 确保使用路由参数中的主键 + entity.WorkflowConfigId = !string.IsNullOrEmpty(input.workflowConfigId) ? input.workflowConfigId.Trim() : oldEntity?.WorkflowConfigId; var isOk = await _db.Updateable(entity).IgnoreColumns(ignoreAllNullColumns: true).ExecuteCommandAsync(); if (!(isOk > 0)) throw NCCException.Oh(ErrorCode.COM1001); diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqXhHyhkService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqXhHyhkService.cs index d17b4ce..af272f0 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend/LqXhHyhkService.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend/LqXhHyhkService.cs @@ -1104,9 +1104,12 @@ namespace NCC.Extend.LqXhHyhk entity.OvertimeCoefficient = input.overtimeCoefficient ?? 0; entity.OriginalSgfy = input.sgfy; entity.AppointmentId = input.appointmentId; - //加班手工费 = 原始手工费 * 加班系数 - entity.OvertimeSgfy = entity.OriginalSgfy * entity.OvertimeCoefficient; - //最终手工费 = 原始手工费 + 加班手工费 + // 加班手工费 = 健康师原始手工费之和 × 加班系数(科技部不参与加班) + var jksOriginalLaborCostSum = input.lqXhPxmxList? + .SelectMany(p => p.lqXhJksyjList ?? Enumerable.Empty()) + .Sum(j => j.laborCost ?? 0) ?? 0; + entity.OvertimeSgfy = (decimal)(jksOriginalLaborCostSum * entity.OvertimeCoefficient); + // 最终手工费 = 原始手工费 + 加班手工费 entity.Sgfy = entity.OriginalSgfy + entity.OvertimeSgfy; try { @@ -1489,9 +1492,13 @@ namespace NCC.Extend.LqXhHyhk throw NCCException.Oh(ErrorCode.COM1005, "耗卡记录不存在或已作废"); } entity.UpdateTime = DateTime.Now; - entity.OvertimeCoefficient = input.overtimeCoefficient; + entity.OvertimeCoefficient = input.overtimeCoefficient ?? 0; entity.OriginalSgfy = input.sgfy; - entity.OvertimeSgfy = (decimal)(entity.OvertimeCoefficient * input.sgfy); + // 加班手工费 = 健康师原始手工费之和 × 加班系数(科技部不参与加班) + var jksOriginalLaborCostSum = input.lqXhPxmxList? + .SelectMany(p => p.lqXhJksyjList ?? Enumerable.Empty()) + .Sum(j => j.laborCost ?? 0) ?? 0; + entity.OvertimeSgfy = (decimal)(jksOriginalLaborCostSum * (entity.OvertimeCoefficient ?? 0)); entity.Sgfy = entity.OriginalSgfy + entity.OvertimeSgfy; entity.AppointmentId = input.appointmentId; //更新会员耗卡记录 @@ -1823,7 +1830,7 @@ namespace NCC.Extend.LqXhHyhk /// /// 计算逻辑: /// 1. 主表(lq_xh_hyhk): - /// - 加班手工费 = 原始手工费 × 新加班系数 + /// - 加班手工费 = 健康师原始手工费之和 × 新加班系数(科技部不参与加班) /// - 最终手工费 = 原始手工费 + 加班手工费 /// /// 2. 品项明细表(lq_xh_pxmx): @@ -1881,11 +1888,26 @@ namespace NCC.Extend.LqXhHyhk } } - // 2. 更新主表加班系数和相关字段 + // 2. 查询健康师业绩,计算主表加班手工费(科技部不参与加班) + var jksyjList = await _db.Queryable() + .Where(x => x.Glkdbh == id && x.IsEffective == StatusEnum.有效.GetHashCode()) + .ToListAsync(); + var jksOriginalLaborCostSum = jksyjList.Sum(j => + { + var original = j.OriginalLaborCost ?? 0; + if (original == 0 && (j.LaborCost ?? 0) > 0) + { + original = (j.LaborCost ?? 0) - (j.OvertimeLaborCost ?? 0); + if (original < 0) original = 0; + } + return original; + }); + + // 3. 更新主表加班系数和相关字段 var newCoefficient = input.overtimeCoefficient ?? 0; var originalSgfy = entity.OriginalSgfy ?? 0; entity.OvertimeCoefficient = newCoefficient; - entity.OvertimeSgfy = (decimal)(originalSgfy * newCoefficient); + entity.OvertimeSgfy = (decimal)(jksOriginalLaborCostSum * newCoefficient); entity.Sgfy = originalSgfy + (entity.OvertimeSgfy ?? 0); entity.UpdateTime = DateTime.Now; @@ -1893,7 +1915,7 @@ namespace NCC.Extend.LqXhHyhk .UpdateColumns(x => new { x.OvertimeCoefficient, x.OvertimeSgfy, x.Sgfy, x.UpdateTime }) .ExecuteCommandAsync(); - // 3. 查询所有品项明细,更新加班字段 + // 4. 查询所有品项明细,更新加班字段 var pxmxList = await _db.Queryable() .Where(x => x.ConsumeInfoId == id && x.IsEffective == StatusEnum.有效.GetHashCode()) .ToListAsync(); @@ -1922,11 +1944,7 @@ namespace NCC.Extend.LqXhHyhk .ExecuteCommandAsync(); } - // 4. 查询所有健康师业绩,更新加班字段 - var jksyjList = await _db.Queryable() - .Where(x => x.Glkdbh == id && x.IsEffective == StatusEnum.有效.GetHashCode()) - .ToListAsync(); - + // 5. 更新健康师业绩加班字段(jksyjList 已在步骤2查询) foreach (var jksyj in jksyjList) { // 如果原始耗卡品项次数为空,使用当前值作为原始值 @@ -1969,7 +1987,7 @@ namespace NCC.Extend.LqXhHyhk .ExecuteCommandAsync(); } - // 5. 科技部老师业绩表:当前代码中不参与加班计算,保持原值不变 + // 6. 科技部老师业绩表:当前代码中不参与加班计算,保持原值不变 // 如果需要支持,可以取消注释以下代码 /* var kjbsyjList = await _db.Queryable() diff --git a/netcore/src/Modularity/Message/NCC.Message.Entitys/D:/wesley/project/git/antis-food-alliance/netcore/src/Modularity/Message/NCC.Message.Entitys/NCC.Message.Entitys.xml b/netcore/src/Modularity/Message/NCC.Message.Entitys/D:/wesley/project/git/antis-food-alliance/netcore/src/Modularity/Message/NCC.Message.Entitys/NCC.Message.Entitys.xml new file mode 100644 index 0000000..8619b62 --- /dev/null +++ b/netcore/src/Modularity/Message/NCC.Message.Entitys/D:/wesley/project/git/antis-food-alliance/netcore/src/Modularity/Message/NCC.Message.Entitys/NCC.Message.Entitys.xml @@ -0,0 +1,924 @@ + + + + NCC.Message.Entitys + + + + + 主键 + + + + + 发送者 + + + + + + 发送时间 + + + + + + 接收者 + + + + + + 接收时间 + + + + + + 内容 + + + + + + 内容类型:text、img、file + + + + + 状态(0:未读、1:已读) + + + + + + 消息接收类 + + + + + 发送发送客户端ID + + + + + 方法 + + + + + 移动设备 + + + + + Token + + + + + 发送者ID + + + + + 接收者ID + + + + + 消息类型 + + + + + 消息内容 + + + + + 当前页数 + + + + + 分页大小 + + + + + 排序 + + + + + 关键字 + + + + + 房间号 + + + + + 房间名称 + + + + + 是否群组/房间消息 + + + + + 信息图片输入 + + + + + 64进制图片 + + + + + 高度 + + + + + 宽度 + + + + + 在线用户 + 版 本:V1.20.15.0 + 版 权:Wesley(https://www.NCCsoft.com) + 作 者:NCC开发平台组 + 日 期:2017.09.20 + + + + + 用户ID + + + + + 用户账号 + + + + + 用户名称 + + + + + 登录时间 + + + + + 登录IP地址 + + + + + 登录平台设备 + + + + + 租户ID + + + + + 主键 + + + + + 发送者 + + + + + + 发送时间 + + + + + + 房间 /群组好 + + + + + + 内容 + + + + + + 内容类型:text、img、file + + + + + 状态(0:未读、1:已读) + + + + + + 聊天会话列表输出 + + + + + 主键 + + + + + 发送者 + + + + + 接受者 + + + + + 名称 + + + + + 头像 + + + + + 最新消息 + + + + + 最新时间 + + + + + 未读消息 + + + + + 消息类型 + + + + + 账号 + + + + + 聊天会话对象ID + + + + + 对象id + + + + + 最新时间 + + + + + 标题 + + + + + 正文内容 + + + + + 房间号 + + + + + 描述 + + + + + 消息实例ID + + + + + 商品ID + + + + + 店铺Id + + + + + 标题 + + + + + 正文内容 + + + + + id + + + + + 标题 + + + + + 正文内容 + + + + + 发送人员 + + + + + 发送时间 + + + + + + + + + + 类型 + + + + + id + + + + + 标题 + + + + + 正文内容 + + + + + 发送人员 + + + + + 发送时间 + + + + + 是否已读(0-未读,1-已读) + + + + + + + + + + + + + + + + + + + + id + + + + + 标题 + + + + + 发布人员 + + + + + 发布时间 + + + + + 状态(0-存草稿,1-已发布) + + + + + 类型 + + + + + 删除标记 + + + + + id + + + + + 标题 + + + + + 正文内容 + + + + + 发送人员 + + + + + 发送时间 + + + + + id + + + + + 群组/房间 消息在线聊天 + 版 本:V1.20.15 + 版 权:Wesley(https://www.NCCsoft.com) + 作 者:NCC开发平台组 + 日 期:2022-03-16 + + + + + 群组/房间ID + + + + + 发送者 + + + + + + 发送时间 + + + + + + 房间号 + + + + + + 内容 + + + + + + 内容类型:text、img、file + + + + + 状态(0:未读、1:已读) + + + + + + 消息实例ID + + + + + 消息实例ID + + + + + 群组/房间消息 + 版 本:V1.20.15 + 版 权:Wesley(https://www.NCCsoft.com) + 作 者:NCC开发平台组 + 日 期:2022-03-16 + + + + + 房间/群组名 + + + + + 正文 + + + + + 优先 + + + + + 房间号 + + + + + 是否阅读 + + + + + 描述 + + + + + 排序码 + + + + + 消息实例ID + + + + + 商品ID + + + + + 店铺Id + + + + + 在线聊天 + 版 本:V1.20.15 + 版 权:Wesley(https://www.NCCsoft.com) + 作 者:NCC开发平台组 + 日 期:2022-03-16 + + + + + 发送者 + + + + + + 发送时间 + + + + + + 接收者 + + + + + + 接收时间 + + + + + + 内容 + + + + + + 内容类型:text、img、file + + + + + 状态(0:未读、1:已读) + + + + + + 聊天会话 + + + + + 发送者 + + + + + + 接收用户 + + + + + + 接收用户时间 + + + + + + 消息实例 + 版 本:V1.20.15 + 版 权:Wesley(https://www.NCCsoft.com) + 作 者:NCC开发平台组 + 日 期:2022-03-16 + + + + + 类别:1-通知公告,2-系统消息、3-私信消息 + + + + + 标题 + + + + + 正文 + + + + + 优先 + + + + + 收件用户 + + + + + 是否阅读 + + + + + 描述 + + + + + 排序码 + + + + + 消息接收 + 版 本:V1.20.15 + 版 权:Wesley(https://www.NCCsoft.com) + 作 者:NCC开发平台组 + 日 期:2022-03-16 + + + + + 消息主键 + + + + + 用户主键 + + + + + 是否阅读 + + + + + 阅读时间 + + + + + 阅读次数 + + + + + 发送者Id + + + + + 接收者Id + + + + + 未读数量 + + + + + 默认消息 + + + + + 默认消息类型 + + + + + 默认消息时间 + + + + + 在线用户模型 + + + + + 连接ID + + + + + 用户ID + + + + + 最后连接时间 + + + + + 最后登录IP + + + + + 登录平台设备 + + + + + 账号 + + + + + 用户名称 + + + + + 租户id + + + + + WebSocket客户端信息 + + + + + 连接Id + + + + + 用户Id + + + + + 用户账号 + + + + + 头像 + + + + + 用户名称 + + + + + 登录IP + + + + + 登录设备 + + + + + 登录时间 + + + + + 租户Id + + + + + 移动端 + + + + + WebSocket对象 + + + + + 房间ID + + + + + 房间名 + + + + + 发送消息 + + + + + + diff --git a/sql/修复消耗单主表加班手工费历史数据.sql b/sql/修复消耗单主表加班手工费历史数据.sql new file mode 100644 index 0000000..4fc92cd --- /dev/null +++ b/sql/修复消耗单主表加班手工费历史数据.sql @@ -0,0 +1,129 @@ +-- ============================================ +-- 修复消耗单主表(lq_xh_hyhk)F_OvertimeSgfy 和 sgfy 历史数据 +-- ============================================ +-- 背景:主表加班手工费已改为「健康师原始手工费之和 × 加班系数」,科技部不参与加班。 +-- 本脚本修复历史数据,使 F_OvertimeSgfy 和 sgfy 符合新逻辑。 +-- +-- 修复逻辑: +-- F_OvertimeSgfy = 健康师原始手工费之和 × F_OvertimeCoefficient +-- sgfy = F_OriginalSgfy + F_OvertimeSgfy +-- +-- 健康师原始手工费:优先用 F_OriginalLaborCost,为空时用 F_LaborCost - F_OvertimeLaborCost +-- F_OriginalSgfy 为空时:用 健康师原始之和 + 科技部原始之和 补全 +-- +-- 执行前请先备份!建议在测试环境验证后再在生产环境执行。 + +-- ============================================ +-- 1. 预览:查看需要修复的记录(只读,可先执行验证) +-- ============================================ +/* +SELECT + h.F_Id, + h.F_OriginalSgfy AS 当前原始手工费, + h.F_OvertimeSgfy AS 当前加班手工费, + h.sgfy AS 当前最终手工费, + h.F_OvertimeCoefficient AS 加班系数, + COALESCE(j.jks_sum, 0) AS 健康师原始手工费之和, + COALESCE(j.jks_sum, 0) * COALESCE(h.F_OvertimeCoefficient, 0) AS 新加班手工费, + COALESCE(h.F_OriginalSgfy, COALESCE(j.jks_sum, 0) + COALESCE(k.kjb_sum, 0)) + (COALESCE(j.jks_sum, 0) * COALESCE(h.F_OvertimeCoefficient, 0)) AS 新最终手工费 +FROM lq_xh_hyhk h +LEFT JOIN ( + SELECT glkdbh, + SUM(COALESCE(F_OriginalLaborCost, GREATEST(0, COALESCE(F_LaborCost, 0) - COALESCE(F_OvertimeLaborCost, 0)))) AS jks_sum + FROM lq_xh_jksyj + WHERE F_IsEffective = 1 + GROUP BY glkdbh +) j ON h.F_Id = j.glkdbh +LEFT JOIN ( + SELECT glkdbh, + SUM(COALESCE(F_OriginalLaborCost, F_LaborCost)) AS kjb_sum + FROM lq_xh_kjbsyj + WHERE F_IsEffective = 1 + GROUP BY glkdbh +) k ON h.F_Id = k.glkdbh +WHERE h.F_IsEffective = 1 + AND ( + COALESCE(h.F_OvertimeSgfy, 0) != COALESCE(j.jks_sum, 0) * COALESCE(h.F_OvertimeCoefficient, 0) + OR COALESCE(h.sgfy, 0) != COALESCE(h.F_OriginalSgfy, COALESCE(j.jks_sum, 0) + COALESCE(k.kjb_sum, 0)) + (COALESCE(j.jks_sum, 0) * COALESCE(h.F_OvertimeCoefficient, 0)) + ) +LIMIT 50; +*/ + +-- ============================================ +-- 2. 执行修复:更新 F_OvertimeSgfy 和 sgfy +-- ============================================ +UPDATE lq_xh_hyhk h +INNER JOIN ( + SELECT glkdbh, + SUM(COALESCE(F_OriginalLaborCost, GREATEST(0, COALESCE(F_LaborCost, 0) - COALESCE(F_OvertimeLaborCost, 0)))) AS jks_sum + FROM lq_xh_jksyj + WHERE F_IsEffective = 1 + GROUP BY glkdbh +) j ON h.F_Id = j.glkdbh +LEFT JOIN ( + SELECT glkdbh, + SUM(COALESCE(F_OriginalLaborCost, F_LaborCost)) AS kjb_sum + FROM lq_xh_kjbsyj + WHERE F_IsEffective = 1 + GROUP BY glkdbh +) k ON h.F_Id = k.glkdbh +SET + h.F_OvertimeSgfy = COALESCE(j.jks_sum, 0) * COALESCE(h.F_OvertimeCoefficient, 0), + h.sgfy = COALESCE(h.F_OriginalSgfy, COALESCE(j.jks_sum, 0) + COALESCE(k.kjb_sum, 0)) + (COALESCE(j.jks_sum, 0) * COALESCE(h.F_OvertimeCoefficient, 0)) +WHERE h.F_IsEffective = 1; + +-- 说明:上述 UPDATE 使用 INNER JOIN j,因此仅更新「存在健康师业绩」的耗卡记录。 +-- 若耗卡仅有科技部、无健康师,则 j 子查询无结果,该记录不会被更新。 +-- 对于「仅科技部」的耗卡,正确逻辑应为:F_OvertimeSgfy=0,sgfy=F_OriginalSgfy,需单独处理。 + +-- ============================================ +-- 3. 补充修复:仅科技部、无健康师的耗卡(F_OvertimeSgfy 应为 0,sgfy = 原始手工费) +-- ============================================ +UPDATE lq_xh_hyhk h +LEFT JOIN ( + SELECT glkdbh + FROM lq_xh_jksyj + WHERE F_IsEffective = 1 + GROUP BY glkdbh +) j ON h.F_Id = j.glkdbh +LEFT JOIN ( + SELECT glkdbh, + SUM(COALESCE(F_OriginalLaborCost, F_LaborCost)) AS kjb_sum + FROM lq_xh_kjbsyj + WHERE F_IsEffective = 1 + GROUP BY glkdbh +) k ON h.F_Id = k.glkdbh +SET + h.F_OvertimeSgfy = 0, + h.sgfy = COALESCE(h.F_OriginalSgfy, COALESCE(k.kjb_sum, 0), h.sgfy) +WHERE h.F_IsEffective = 1 + AND j.glkdbh IS NULL + AND (COALESCE(h.F_OvertimeSgfy, 0) != 0); + +-- ============================================ +-- 4. 验证修复结果(可选) +-- ============================================ +-- 检查是否仍有不一致记录(应返回 0) +/* +SELECT COUNT(*) AS 仍不一致记录数 +FROM lq_xh_hyhk h +LEFT JOIN ( + SELECT glkdbh, + SUM(COALESCE(F_OriginalLaborCost, GREATEST(0, COALESCE(F_LaborCost, 0) - COALESCE(F_OvertimeLaborCost, 0)))) AS jks_sum + FROM lq_xh_jksyj + WHERE F_IsEffective = 1 + GROUP BY glkdbh +) j ON h.F_Id = j.glkdbh +LEFT JOIN ( + SELECT glkdbh, + SUM(COALESCE(F_OriginalLaborCost, F_LaborCost)) AS kjb_sum + FROM lq_xh_kjbsyj + WHERE F_IsEffective = 1 + GROUP BY glkdbh +) k ON h.F_Id = k.glkdbh +WHERE h.F_IsEffective = 1 + AND ( + COALESCE(h.F_OvertimeSgfy, 0) != COALESCE(j.jks_sum, 0) * COALESCE(h.F_OvertimeCoefficient, 0) + OR COALESCE(h.sgfy, 0) != COALESCE(h.F_OriginalSgfy, COALESCE(j.jks_sum, 0) + COALESCE(k.kjb_sum, 0)) + (COALESCE(j.jks_sum, 0) * COALESCE(h.F_OvertimeCoefficient, 0)) + ); +*/ diff --git a/test-get-tech-teacher-daily-statistics.sh b/test-get-tech-teacher-daily-statistics.sh new file mode 100755 index 0000000..9b64a03 --- /dev/null +++ b/test-get-tech-teacher-daily-statistics.sh @@ -0,0 +1,50 @@ +#!/bin/bash +# GetTechTeacherDailyStatistics 接口测试脚本 +# 使用方式: ./test-get-tech-teacher-daily-statistics.sh [BASE_URL],默认 http://localhost:2011 +# 前置:API 需已启动且已重新编译(含 RefundAchievement 的 DTO 变更) + +BASE_URL="${1:-http://localhost:2011}" + +echo "=== 1. 获取 Token ===" +TOKEN=$(curl -s -X POST "$BASE_URL/api/oauth/Login" \ + -H "Content-Type: application/x-www-form-urlencoded" \ + -d "account=admin&password=e10adc3949ba59abbe56e057f20f883e" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('data',{}).get('token','') if d.get('code')==200 else '')" 2>/dev/null) +if [ -z "$TOKEN" ]; then + echo "获取 Token 失败,请检查 API 是否启动" + exit 1 +fi +echo "Token 获取成功" + +echo "" +echo "=== 2. 调用 GetTechTeacherDailyStatistics 接口 ===" +echo "时间范围: 2026-03-01 ~ 2026-03-16" +RESP=$(curl -s -X POST "$BASE_URL/api/Extend/LqDailyReport/get-tech-teacher-daily-statistics" \ + -H "Authorization: $TOKEN" \ + -H "Content-Type: application/json" \ + -d '{"startTime":"2026-03-01T00:00:00","endTime":"2026-03-16T23:59:59"}') +echo "$RESP" | python3 -m json.tool 2>/dev/null || echo "$RESP" + +CODE=$(echo "$RESP" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('code',-1))" 2>/dev/null) +if [ "$CODE" != "200" ]; then + echo "" + echo "接口返回非 200,若为 500 且提示 set_RefundAchievement,请重启 API 服务后重试" + exit 1 +fi + +echo "" +echo "=== 3. 数据库验证 SQL(MCP MySQL 或手动执行)===" +echo "耗卡业绩汇总(lq_xh_kjbsyj + lq_xh_hyhk):" +echo "SELECT consume.kjbls as TeacherId, SUM(consume.kjblsyj) as ConsumeAchievement" +echo "FROM lq_xh_kjbsyj consume" +echo "INNER JOIN lq_xh_hyhk hyhk ON consume.glkdbh = hyhk.F_Id" +echo "WHERE consume.F_IsEffective = 1 AND hyhk.F_IsEffective = 1" +echo " AND DATE(hyhk.hksj) >= '2026-03-01' AND DATE(hyhk.hksj) <= '2026-03-16'" +echo "GROUP BY consume.kjbls;" +echo "" +echo "退卡业绩汇总(lq_hytk_kjbsyj + lq_hytk_hytk):" +echo "SELECT refund.kjbls as TeacherId, SUM(refund.kjblsyj) as RefundAchievement" +echo "FROM lq_hytk_kjbsyj refund" +echo "INNER JOIN lq_hytk_hytk hytk ON refund.gltkbh = hytk.F_Id" +echo "WHERE refund.F_IsEffective = 1 AND hytk.F_IsEffective = 1" +echo " AND DATE(hytk.tksj) >= '2026-03-01' AND DATE(hytk.tksj) <= '2026-03-16'" +echo "GROUP BY refund.kjbls;" diff --git a/test-xh-overtime-apis.sh b/test-xh-overtime-apis.sh new file mode 100644 index 0000000..3f670b2 --- /dev/null +++ b/test-xh-overtime-apis.sh @@ -0,0 +1,34 @@ +#!/bin/bash +# 消耗单(会员耗卡)加班手工费接口测试脚本 +# 使用方式: ./test-xh-overtime-apis.sh [BASE_URL],默认 http://localhost:2011 +# 前置:API 需已启动 + +BASE_URL="${1:-http://localhost:2011}" + +echo "=== 1. 获取 Token ===" +TOKEN=$(curl -s -X POST "$BASE_URL/api/oauth/Login" \ + -H "Content-Type: application/x-www-form-urlencoded" \ + -d "account=admin&password=e10adc3949ba59abbe56e057f20f883e" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('data',{}).get('token','') if d.get('code')==200 else '')" 2>/dev/null) +if [ -z "$TOKEN" ]; then + echo "获取 Token 失败,请检查 API 是否启动" + exit 1 +fi +echo "Token 获取成功" + +echo "" +echo "=== 2. Create 消耗单(健康师12+科技部40=52,系数0.5)===" +echo "预期:F_OvertimeSgfy=6, sgfy=58" +CREATE_BODY='{"hy":"742276000326354181","md":"1649328471923847174","mdbh":"001","mdmc":"绿纤优品道店","hyzh":"17882541236","hymc":"咕噜","xfje":100,"sgfy":52,"hksj":"2026-03-16T10:00:00","overtimeCoefficient":0.5,"lqXhPxmxList":[{"px":"1","pxmc":"活动陪同","pxjg":100,"projectNumber":1,"sourceType":"耗卡","lqXhJksyjList":[{"jks":"13032810387","jksxm":"范时依","jkszh":"13032810387","jksyj":50,"laborCost":12,"kdpxNumber":1}],"lqXhKjbsyjList":[{"kjbls":"13110190690","kjblsxm":"刘雨佳","kjblszh":"13110190690","kjblsyj":50,"laborCost":40,"hdpxNumber":1}]}]}' +curl -s -X POST "$BASE_URL/api/Extend/LqXhHyhk" -H "Authorization: $TOKEN" -H "Content-Type: application/json" -d "$CREATE_BODY" | python3 -m json.tool 2>/dev/null || echo "请求失败" + +echo "" +echo "=== 3. 创建后需查库验证主表 F_OvertimeSgfy、sgfy ===" +echo "SQL: SELECT F_Id, sgfy, F_OriginalSgfy, F_OvertimeSgfy FROM lq_xh_hyhk WHERE hy='742276000326354181' ORDER BY F_CreateTime DESC LIMIT 1" + +echo "" +echo "=== 4. Update 消耗单(需替换 {id} 为实际耗卡ID,body 必须包含 id 字段)===" +echo "curl -X PUT \"$BASE_URL/api/Extend/LqXhHyhk/{id}\" -H \"Authorization: \$TOKEN\" -H \"Content-Type: application/json\" -d '{\"id\":\"{id}\",\"hy\":\"742276000326354181\",\"md\":\"1649328471923847174\",\"sgfy\":52,\"overtimeCoefficient\":0.5,\"lqXhPxmxList\":[...]}'" + +echo "" +echo "=== 5. UpdateOvertimeCoefficient(需替换 {id} 为实际耗卡ID)===" +echo "curl -X PUT \"$BASE_URL/api/Extend/LqXhHyhk/{id}/overtime-coefficient\" -H \"Authorization: \$TOKEN\" -H \"Content-Type: application/json\" -d '{\"overtimeCoefficient\":1.0}'" -- libgit2 0.21.4