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/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}'"