# 加班系数逻辑说明及修改方案 **文档版本**:v1.0 **创建日期**:2025年1月 **文档目的**:说明加班系数的计算逻辑,并提供修改加班系数的功能设计方案 --- ## 一、加班系数计算逻辑说明 ### 1.1 核心概念 **加班系数(OvertimeCoefficient)**: - 存储在 `lq_xh_hyhk` 表的 `F_OvertimeCoefficient` 字段 - **NULL 或 0**:表示非加班单 - **大于 0**(如 0.5、1、1.5):表示加班单,系数值表示加倍的倍数 ### 1.2 计算公式 #### 📊 **主表(lq_xh_hyhk)计算** ``` 加班手工费(F_OvertimeSgfy)= 原始手工费(F_OriginalSgfy)× 加班系数(F_OvertimeCoefficient) 最终手工费(sgfy)= 原始手工费(F_OriginalSgfy)+ 加班手工费(F_OvertimeSgfy) ``` **示例**: - 原始手工费 = 100元 - 加班系数 = 0.5 - 加班手工费 = 100 × 0.5 = 50元 - 最终手工费 = 100 + 50 = 150元 --- #### 📋 **品项明细表(lq_xh_pxmx)计算** ``` 加班项目次数(F_OvertimeProjectNumber)= 原始项目次数(F_OriginalProjectNumber)× 加班系数(F_OvertimeCoefficient) 最终项目次数(F_ProjectNumber)= 原始项目次数(F_OriginalProjectNumber)+ 加班项目次数(F_OvertimeProjectNumber) ``` **示例**: - 原始项目次数 = 2次 - 加班系数 = 0.5 - 加班项目次数 = 2 × 0.5 = 1次 - 最终项目次数 = 2 + 1 = 3次 --- #### 👨‍⚕️ **健康师业绩表(lq_xh_jksyj)计算** **耗卡品项次数**: ``` 加班耗卡品项次数(F_OvertimeKdpxNumber)= 原始耗卡品项次数(F_OriginalKdpxNumber)× 加班系数(F_OvertimeCoefficient) 最终耗卡品项次数(F_kdpxNumber)= 原始耗卡品项次数(F_OriginalKdpxNumber)+ 加班耗卡品项次数(F_OvertimeKdpxNumber)+ 陪同项目次数(AccompaniedProjectNumber) ``` **手工费**: ``` 加班手工费(F_OvertimeLaborCost)= 原始手工费(F_OriginalLaborCost)× 加班系数(F_OvertimeCoefficient) 最终手工费(F_LaborCost)= 原始手工费(F_OriginalLaborCost)+ 加班手工费(F_OvertimeLaborCost) ``` **示例**: - 原始耗卡品项次数 = 2次 - 原始手工费 = 50元 - 加班系数 = 0.5 - 加班耗卡品项次数 = 2 × 0.5 = 1次 - 最终耗卡品项次数 = 2 + 1 = 3次 - 加班手工费 = 50 × 0.5 = 25元 - 最终手工费 = 50 + 25 = 75元 --- #### 👨‍🔬 **科技部老师业绩表(lq_xh_kjbsyj)计算** **注意**:根据当前代码实现,科技部老师的加班相关字段被设置为 0,不参与加班计算。 ```csharp // 当前代码实现(LqXhHyhkService.cs 第1010-1017行) OriginalHdpxNumber = ikjbs_tem.hdpxNumber, OvertimeHdpxNumber = 0, // 固定为0 HdpxNumber = ikjbs_tem.hdpxNumber, OriginalLaborCost = ikjbs_tem.laborCost, OvertimeLaborCost = 0, // 固定为0 LaborCost = ikjbs_tem.laborCost, ``` --- ### 1.3 数据流向图 ``` ┌─────────────────────────────────────────────────────────────┐ │ lq_xh_hyhk(耗卡主表) │ │ F_OvertimeCoefficient(加班系数) │ │ F_OriginalSgfy(原始手工费) │ │ F_OvertimeSgfy(加班手工费)= OriginalSgfy × Coefficient │ │ sgfy(最终手工费)= OriginalSgfy + OvertimeSgfy │ └─────────────────────────────────────────────────────────────┘ │ ├──────────────────────────────────┐ │ │ ▼ ▼ ┌───────────────────────────────┐ ┌───────────────────────────────┐ │ lq_xh_pxmx(品项明细表) │ │ lq_xh_jksyj(健康师业绩表) │ │ F_OriginalProjectNumber │ │ F_OriginalKdpxNumber │ │ F_OvertimeProjectNumber │ │ F_OvertimeKdpxNumber │ │ = Original × Coefficient │ │ = Original × Coefficient │ │ F_ProjectNumber │ │ F_kdpxNumber │ │ = Original + Overtime │ │ = Original + Overtime │ │ │ │ F_OriginalLaborCost │ │ │ │ F_OvertimeLaborCost │ │ │ │ = Original × Coefficient │ │ │ │ F_LaborCost │ │ │ │ = Original + Overtime │ └───────────────────────────────┘ └───────────────────────────────┘ ``` --- ## 二、当前实现分析 ### 2.1 创建消耗单(Create方法) **位置**:`LqXhHyhkService.cs` 第878-1252行 **流程**: 1. 接收 `LqXhHyhkCrInput` 参数,包含 `overtimeCoefficient` 2. 计算主表加班字段: ```csharp entity.OvertimeCoefficient = input.overtimeCoefficient ?? 0; entity.OriginalSgfy = input.sgfy; entity.OvertimeSgfy = entity.OriginalSgfy * entity.OvertimeCoefficient; entity.Sgfy = entity.OriginalSgfy + entity.OvertimeSgfy; ``` 3. 遍历品项明细,计算每个品项的加班字段: ```csharp OvertimeProjectNumber = (decimal)(entity.OvertimeCoefficient * (item.projectNumber ?? 0)), ProjectNumber = (decimal)((item.projectNumber ?? 0) + (entity.OvertimeCoefficient * (item.projectNumber ?? 0))), ``` 4. 遍历健康师业绩,计算每个健康师的加班字段: ```csharp OvertimeKdpxNumber = (decimal)(entity.OvertimeCoefficient * (ijks_tem.kdpxNumber ?? 0)), KdpxNumber = (decimal)((ijks_tem.kdpxNumber ?? 0) + (entity.OvertimeCoefficient * (ijks_tem.kdpxNumber ?? 0))) + (ijks_tem.accompaniedProjectNumber ?? 0), OvertimeLaborCost = (decimal)(entity.OvertimeCoefficient * (ijks_tem.laborCost ?? 0)), LaborCost = (decimal)((ijks_tem.laborCost ?? 0) + (entity.OvertimeCoefficient * (ijks_tem.laborCost ?? 0))), ``` --- ### 2.2 更新消耗单(Update方法) **位置**:`LqXhHyhkService.cs` 第1262-1580行 **流程**: 1. 接收 `LqXhHyhkUpInput` 参数,包含 `overtimeCoefficient` 2. 更新主表加班字段(与Create方法相同) 3. **删除所有明细数据**: ```csharp await _db.Deleteable().Where(u => u.Glkdbh == id).ExecuteCommandAsync(); await _db.Deleteable().Where(u => u.Glkdbh == id).ExecuteCommandAsync(); await _db.Deleteable().Where(u => u.ConsumeInfoId == id).ExecuteCommandAsync(); await _db.Deleteable().Where(u => u.BusinessId == id).ExecuteCommandAsync(); ``` 4. **重新插入所有明细数据**(与Create方法相同) **问题**: - Update方法需要传入完整的 `lqXhPxmxList`,不能只修改加班系数 - 如果只想修改加班系数,需要先查询所有明细数据,然后调用Update方法 --- ## 三、修改加班系数功能设计方案 ### 3.1 需求分析 **核心需求**: 1. 提供一个专门的接口,只修改加班系数 2. 修改后自动重新计算所有相关的加班字段 3. 不需要传入完整的明细数据,只需要传入新的加班系数 **使用场景**: - 用户发现消耗单的加班系数设置错误,需要修改 - 不需要修改其他数据(品项、健康师等),只需要修改加班系数 --- ### 3.2 设计方案 #### 方案一:新增专门的修改加班系数接口(推荐)⭐ **优点**: - 接口职责单一,只负责修改加班系数 - 不需要传入完整的明细数据 - 性能更好,只需要更新相关字段,不需要删除和重新插入 **缺点**: - 需要新增一个接口 **实现步骤**: 1. **新增DTO类**:`LqXhHyhkUpdateOvertimeInput.cs` ```csharp public class LqXhHyhkUpdateOvertimeInput { /// /// 耗卡编号 /// public string id { get; set; } /// /// 新的加班系数(NULL或0表示非加班单,大于0表示加班单,如 0.5、1、1.5) /// public decimal? overtimeCoefficient { get; set; } } ``` 2. **新增Service方法**:`UpdateOvertimeCoefficient` ```csharp /// /// 修改消耗单的加班系数 /// /// 耗卡编号 /// 参数 /// [HttpPut("{id}/overtime-coefficient")] public async Task UpdateOvertimeCoefficient(string id, [FromBody] LqXhHyhkUpdateOvertimeInput input) { // 1. 查询主表记录 // 2. 更新主表加班系数和相关字段 // 3. 查询所有品项明细,更新加班字段 // 4. 查询所有健康师业绩,更新加班字段 // 5. 查询所有科技部老师业绩,更新加班字段(如果需要) } ``` 3. **计算逻辑**: - 主表:重新计算 `F_OvertimeSgfy` 和 `sgfy` - 品项明细表:重新计算 `F_OvertimeProjectNumber` 和 `F_ProjectNumber` - 健康师业绩表:重新计算 `F_OvertimeKdpxNumber`、`F_kdpxNumber`、`F_OvertimeLaborCost`、`F_LaborCost` - 科技部老师业绩表:如果需要支持,重新计算相关字段 --- #### 方案二:修改现有Update方法(不推荐) **优点**: - 不需要新增接口 **缺点**: - Update方法需要传入完整的明细数据,使用复杂 - 如果只想修改加班系数,需要先查询所有明细数据 --- ### 3.3 详细实现步骤 #### 步骤1:创建DTO类 **文件路径**:`netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqXhHyhk/LqXhHyhkUpdateOvertimeInput.cs` ```csharp namespace NCC.Extend.Entitys.Dto.LqXhHyhk { /// /// 修改消耗单加班系数输入参数 /// public class LqXhHyhkUpdateOvertimeInput { /// /// 新的加班系数(NULL或0表示非加班单,大于0表示加班单,如 0.5、1、1.5) /// public decimal? overtimeCoefficient { get; set; } } } ``` --- #### 步骤2:在Service中新增方法 **文件路径**:`netcore/src/Modularity/Extend/NCC.Extend/LqXhHyhkService.cs` **方法位置**:在 `Update` 方法之后添加 **实现逻辑**: ```csharp /// /// 修改消耗单的加班系数 /// /// /// 只修改加班系数,自动重新计算所有相关的加班字段 /// /// 计算逻辑: /// 1. 主表(lq_xh_hyhk): /// - 加班手工费 = 原始手工费 × 新加班系数 /// - 最终手工费 = 原始手工费 + 加班手工费 /// /// 2. 品项明细表(lq_xh_pxmx): /// - 加班项目次数 = 原始项目次数 × 新加班系数 /// - 最终项目次数 = 原始项目次数 + 加班项目次数 /// /// 3. 健康师业绩表(lq_xh_jksyj): /// - 加班耗卡品项次数 = 原始耗卡品项次数 × 新加班系数 /// - 最终耗卡品项次数 = 原始耗卡品项次数 + 加班耗卡品项次数 /// - 加班手工费 = 原始手工费 × 新加班系数 /// - 最终手工费 = 原始手工费 + 加班手工费 /// /// 耗卡编号 /// 参数 /// 无返回值 /// 修改成功 /// 参数错误或数据验证失败 /// 服务器内部错误 [HttpPut("{id}/overtime-coefficient")] public async Task UpdateOvertimeCoefficient(string id, [FromBody] LqXhHyhkUpdateOvertimeInput input) { try { // 开启事务 _db.BeginTran(); // 1. 查询主表记录 var entity = await _db.Queryable() .Where(p => p.Id == id && p.IsEffective == StatusEnum.有效.GetHashCode()) .FirstAsync(); if (entity == null) { throw NCCException.Oh(ErrorCode.COM1005, "耗卡记录不存在或已作废"); } // 2. 更新主表加班系数和相关字段 var newCoefficient = input.overtimeCoefficient ?? 0; entity.OvertimeCoefficient = newCoefficient; entity.OvertimeSgfy = entity.OriginalSgfy * newCoefficient; entity.Sgfy = entity.OriginalSgfy + entity.OvertimeSgfy; entity.UpdateTime = DateTime.Now; await _db.Updateable(entity) .UpdateColumns(x => new { x.OvertimeCoefficient, x.OvertimeSgfy, x.Sgfy, x.UpdateTime }) .ExecuteCommandAsync(); // 3. 查询所有品项明细,更新加班字段 var pxmxList = await _db.Queryable() .Where(x => x.ConsumeInfoId == id && x.IsEffective == StatusEnum.有效.GetHashCode()) .ToListAsync(); foreach (var pxmx in pxmxList) { pxmx.OvertimeProjectNumber = pxmx.OriginalProjectNumber * newCoefficient; pxmx.ProjectNumber = pxmx.OriginalProjectNumber + pxmx.OvertimeProjectNumber; await _db.Updateable(pxmx) .UpdateColumns(x => new { x.OvertimeProjectNumber, x.ProjectNumber }) .ExecuteCommandAsync(); } // 4. 查询所有健康师业绩,更新加班字段 var jksyjList = await _db.Queryable() .Where(x => x.Glkdbh == id && x.IsEffective == StatusEnum.有效.GetHashCode()) .ToListAsync(); foreach (var jksyj in jksyjList) { jksyj.OvertimeKdpxNumber = jksyj.OriginalKdpxNumber * newCoefficient; jksyj.KdpxNumber = jksyj.OriginalKdpxNumber + jksyj.OvertimeKdpxNumber + (jksyj.AccompaniedProjectNumber ?? 0); jksyj.OvertimeLaborCost = jksyj.OriginalLaborCost * newCoefficient; jksyj.LaborCost = jksyj.OriginalLaborCost + jksyj.OvertimeLaborCost; await _db.Updateable(jksyj) .UpdateColumns(x => new { x.OvertimeKdpxNumber, x.KdpxNumber, x.OvertimeLaborCost, x.LaborCost }) .ExecuteCommandAsync(); } // 5. 查询所有科技部老师业绩,更新加班字段(如果需要支持) // 注意:当前代码中科技部老师的加班字段被设置为0,如果需要支持,可以取消注释以下代码 /* var kjbsyjList = await _db.Queryable() .Where(x => x.Glkdbh == id && x.IsEffective == StatusEnum.有效.GetHashCode()) .ToListAsync(); foreach (var kjbsyj in kjbsyjList) { kjbsyj.OvertimeHdpxNumber = kjbsyj.OriginalHdpxNumber * newCoefficient; kjbsyj.HdpxNumber = kjbsyj.OriginalHdpxNumber + kjbsyj.OvertimeHdpxNumber; kjbsyj.OvertimeLaborCost = kjbsyj.OriginalLaborCost * newCoefficient; kjbsyj.LaborCost = kjbsyj.OriginalLaborCost + kjbsyj.OvertimeLaborCost; await _db.Updateable(kjbsyj) .UpdateColumns(x => new { x.OvertimeHdpxNumber, x.HdpxNumber, x.OvertimeLaborCost, x.LaborCost }) .ExecuteCommandAsync(); } */ // 提交事务 _db.CommitTran(); } catch (Exception ex) { _db.RollbackTran(); throw; } } ``` --- #### 步骤3:前端调用接口 **文件路径**:`antis-ncc-admin/src/views/lqXhHyhk/hedge-dialog.vue` 或相关页面 **API调用示例**: ```javascript // 修改加班系数 async updateOvertimeCoefficient(id, newCoefficient) { try { const response = await this.$http.put( `/api/Extend/LqXhHyhk/${id}/overtime-coefficient`, { overtimeCoefficient: newCoefficient } ); if (response.code === 200) { this.$message.success('加班系数修改成功'); // 刷新数据 this.getInfo(); } else { this.$message.error(response.msg || '修改失败'); } } catch (error) { this.$message.error('修改失败:' + error.message); } } ``` --- ## 四、需要修改的文件清单 ### 4.1 后端文件 1. **新增DTO类** - `netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqXhHyhk/LqXhHyhkUpdateOvertimeInput.cs` 2. **修改Service类** - `netcore/src/Modularity/Extend/NCC.Extend/LqXhHyhkService.cs` - 新增 `UpdateOvertimeCoefficient` 方法 --- ### 4.2 前端文件(可选) 如果需要在前端添加修改加班系数的功能,需要修改: 1. **消耗单详情页面** - `antis-ncc-admin/src/views/lqXhHyhk/detail.vue` - 添加修改加班系数的按钮和弹窗 2. **消耗单列表页面** - `antis-ncc-admin/src/views/lqXhHyhk/index.vue` - 添加批量修改加班系数的功能(如果需要) 3. **API接口文件** - `antis-ncc-admin/src/api/...`(如果有统一的API管理文件) --- ## 五、测试要点 ### 5.1 功能测试 1. **正常修改加班系数** - 测试场景:将加班系数从 0.5 修改为 1.0 - 验证点: - 主表的 `F_OvertimeSgfy` 和 `sgfy` 是否正确更新 - 品项明细表的 `F_OvertimeProjectNumber` 和 `F_ProjectNumber` 是否正确更新 - 健康师业绩表的 `F_OvertimeKdpxNumber`、`F_kdpxNumber`、`F_OvertimeLaborCost`、`F_LaborCost` 是否正确更新 2. **将加班单改为非加班单** - 测试场景:将加班系数从 1.0 修改为 0 - 验证点:所有加班相关字段是否都变为 0 3. **将非加班单改为加班单** - 测试场景:将加班系数从 0 修改为 0.5 - 验证点:所有加班相关字段是否正确计算 4. **边界值测试** - 测试场景:加班系数为 NULL、0、0.5、1、1.5、2 等 - 验证点:计算是否正确 --- ### 5.2 数据一致性测试 1. **事务回滚测试** - 测试场景:在更新过程中抛出异常 - 验证点:所有数据是否回滚,保持一致性 2. **并发测试** - 测试场景:同时修改同一个消耗单的加班系数 - 验证点:数据是否正确,不会出现脏数据 --- ## 六、注意事项 ### 6.1 数据一致性 1. **原始数据不变**:修改加班系数时,所有 `F_Original*` 字段保持不变 2. **重新计算**:所有 `F_Overtime*` 和最终字段都需要重新计算 3. **事务保证**:使用事务确保所有更新操作的原子性 ### 6.2 性能考虑 1. **批量更新**:可以考虑使用批量更新,而不是循环更新 2. **索引优化**:确保相关表的查询字段有索引 ### 6.3 业务规则 1. **权限控制**:确保只有有权限的用户才能修改加班系数 2. **日志记录**:记录修改操作,便于追溯 3. **数据验证**:验证加班系数的合法性(如不能为负数) --- ## 七、总结 ### 7.1 核心要点 1. **加班系数计算公式**: - 加班值 = 原始值 × 加班系数 - 最终值 = 原始值 + 加班值 2. **影响范围**: - 主表:手工费 - 品项明细表:项目次数 - 健康师业绩表:耗卡品项次数、手工费 - 科技部老师业绩表:当前不参与计算 3. **修改方案**: - 推荐新增专门的接口 `UpdateOvertimeCoefficient` - 只修改加班系数,自动重新计算所有相关字段 - 使用事务保证数据一致性 ### 7.2 实施步骤 1. ✅ 创建DTO类 `LqXhHyhkUpdateOvertimeInput` 2. ✅ 在Service中新增 `UpdateOvertimeCoefficient` 方法 3. ✅ 实现计算逻辑(主表、品项明细、健康师业绩) 4. ✅ 添加事务处理 5. ✅ 前端调用接口(可选) 6. ✅ 测试验证 --- **文档结束**