加班系数逻辑说明及修改方案
文档版本: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_OvertimeCoefficient)
最终手工费(sgfy)= 原始手工费(F_OriginalSgfy)+ 加班手工费(F_OvertimeSgfy)
示例(健康师12元 + 科技部40元 = 整单原始52元):
- 原始手工费 = 52元(健康师12 + 科技部40)
- 健康师原始手工费之和 = 12元
- 加班系数 = 0.5
- 加班手工费 = 12 × 0.5 = 6元(仅健康师参与)
- 最终手工费 = 52 + 6 = 58元(= 健康师18 + 科技部40)
📋 品项明细表(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,不参与加班计算。
// 当前代码实现(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(加班手工费)= Σ健康师原始手工费 × 系数 │
│ 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行
流程:
- 接收
LqXhHyhkCrInput参数,包含overtimeCoefficient - 计算主表加班字段(科技部不参与加班,主表加班手工费 = 健康师加班手工费之和):
csharp entity.OvertimeCoefficient = input.overtimeCoefficient ?? 0; entity.OriginalSgfy = input.sgfy; 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; - 遍历品项明细,计算每个品项的加班字段:
csharp OvertimeProjectNumber = (decimal)(entity.OvertimeCoefficient * (item.projectNumber ?? 0)), ProjectNumber = (decimal)((item.projectNumber ?? 0) + (entity.OvertimeCoefficient * (item.projectNumber ?? 0))), - 遍历健康师业绩,计算每个健康师的加班字段:
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行
流程:
- 接收
LqXhHyhkUpInput参数,包含overtimeCoefficient - 更新主表加班字段(与Create方法相同)
- 删除所有明细数据:
csharp await _db.Deleteable<LqXhJksyjEntity>().Where(u => u.Glkdbh == id).ExecuteCommandAsync(); await _db.Deleteable<LqXhKjbsyjEntity>().Where(u => u.Glkdbh == id).ExecuteCommandAsync(); await _db.Deleteable<LqXhPxmxEntity>().Where(u => u.ConsumeInfoId == id).ExecuteCommandAsync(); await _db.Deleteable<LqPersonTimesRecordEntity>().Where(u => u.BusinessId == id).ExecuteCommandAsync(); - 重新插入所有明细数据(与Create方法相同)
问题:
- Update方法需要传入完整的
lqXhPxmxList,不能只修改加班系数 - 如果只想修改加班系数,需要先查询所有明细数据,然后调用Update方法
三、修改加班系数功能设计方案
3.1 需求分析
核心需求:
- 提供一个专门的接口,只修改加班系数
- 修改后自动重新计算所有相关的加班字段
- 不需要传入完整的明细数据,只需要传入新的加班系数
使用场景:
- 用户发现消耗单的加班系数设置错误,需要修改
- 不需要修改其他数据(品项、健康师等),只需要修改加班系数
3.2 设计方案
方案一:新增专门的修改加班系数接口(推荐)⭐
优点:
- 接口职责单一,只负责修改加班系数
- 不需要传入完整的明细数据
- 性能更好,只需要更新相关字段,不需要删除和重新插入
缺点:
- 需要新增一个接口
实现步骤:
新增DTO类:
LqXhHyhkUpdateOvertimeInput.cspublic class LqXhHyhkUpdateOvertimeInput { /// <summary> /// 耗卡编号 /// </summary> public string id { get; set; } /// <summary> /// 新的加班系数(NULL或0表示非加班单,大于0表示加班单,如 0.5、1、1.5) /// </summary> public decimal? overtimeCoefficient { get; set; } }新增Service方法:
UpdateOvertimeCoefficient/// <summary> /// 修改消耗单的加班系数 /// </summary> /// <param name="id">耗卡编号</param> /// <param name="input">参数</param> /// <returns></returns> [HttpPut("{id}/overtime-coefficient")] public async Task UpdateOvertimeCoefficient(string id, [FromBody] LqXhHyhkUpdateOvertimeInput input) { // 1. 查询主表记录 // 2. 更新主表加班系数和相关字段 // 3. 查询所有品项明细,更新加班字段 // 4. 查询所有健康师业绩,更新加班字段 // 5. 查询所有科技部老师业绩,更新加班字段(如果需要) }计算逻辑:
- 主表:重新计算
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
namespace NCC.Extend.Entitys.Dto.LqXhHyhk
{
/// <summary>
/// 修改消耗单加班系数输入参数
/// </summary>
public class LqXhHyhkUpdateOvertimeInput
{
/// <summary>
/// 新的加班系数(NULL或0表示非加班单,大于0表示加班单,如 0.5、1、1.5)
/// </summary>
public decimal? overtimeCoefficient { get; set; }
}
}
步骤2:在Service中新增方法
文件路径:netcore/src/Modularity/Extend/NCC.Extend/LqXhHyhkService.cs
方法位置:在 Update 方法之后添加
实现逻辑:
/// <summary>
/// 修改消耗单的加班系数
/// </summary>
/// <remarks>
/// 只修改加班系数,自动重新计算所有相关的加班字段
///
/// 计算逻辑:
/// 1. 主表(lq_xh_hyhk):
/// - 加班手工费 = 原始手工费 × 新加班系数
/// - 最终手工费 = 原始手工费 + 加班手工费
///
/// 2. 品项明细表(lq_xh_pxmx):
/// - 加班项目次数 = 原始项目次数 × 新加班系数
/// - 最终项目次数 = 原始项目次数 + 加班项目次数
///
/// 3. 健康师业绩表(lq_xh_jksyj):
/// - 加班耗卡品项次数 = 原始耗卡品项次数 × 新加班系数
/// - 最终耗卡品项次数 = 原始耗卡品项次数 + 加班耗卡品项次数
/// - 加班手工费 = 原始手工费 × 新加班系数
/// - 最终手工费 = 原始手工费 + 加班手工费
/// </remarks>
/// <param name="id">耗卡编号</param>
/// <param name="input">参数</param>
/// <returns>无返回值</returns>
/// <response code="200">修改成功</response>
/// <response code="400">参数错误或数据验证失败</response>
/// <response code="500">服务器内部错误</response>
[HttpPut("{id}/overtime-coefficient")]
public async Task UpdateOvertimeCoefficient(string id, [FromBody] LqXhHyhkUpdateOvertimeInput input)
{
try
{
// 开启事务
_db.BeginTran();
// 1. 查询主表记录
var entity = await _db.Queryable<LqXhHyhkEntity>()
.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<LqXhPxmxEntity>()
.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<LqXhJksyjEntity>()
.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<LqXhKjbsyjEntity>()
.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调用示例:
// 修改加班系数
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 后端文件
新增DTO类
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqXhHyhk/LqXhHyhkUpdateOvertimeInput.cs
修改Service类
netcore/src/Modularity/Extend/NCC.Extend/LqXhHyhkService.cs- 新增
UpdateOvertimeCoefficient方法
4.2 前端文件(可选)
如果需要在前端添加修改加班系数的功能,需要修改:
消耗单详情页面
antis-ncc-admin/src/views/lqXhHyhk/detail.vue- 添加修改加班系数的按钮和弹窗
消耗单列表页面
antis-ncc-admin/src/views/lqXhHyhk/index.vue- 添加批量修改加班系数的功能(如果需要)
API接口文件
antis-ncc-admin/src/api/...(如果有统一的API管理文件)
五、测试要点
5.1 功能测试
正常修改加班系数
- 测试场景:将加班系数从 0.5 修改为 1.0
- 验证点:
- 主表的
F_OvertimeSgfy和sgfy是否正确更新 - 品项明细表的
F_OvertimeProjectNumber和F_ProjectNumber是否正确更新 - 健康师业绩表的
F_OvertimeKdpxNumber、F_kdpxNumber、F_OvertimeLaborCost、F_LaborCost是否正确更新
- 主表的
将加班单改为非加班单
- 测试场景:将加班系数从 1.0 修改为 0
- 验证点:所有加班相关字段是否都变为 0
将非加班单改为加班单
- 测试场景:将加班系数从 0 修改为 0.5
- 验证点:所有加班相关字段是否正确计算
边界值测试
- 测试场景:加班系数为 NULL、0、0.5、1、1.5、2 等
- 验证点:计算是否正确
5.2 数据一致性测试
事务回滚测试
- 测试场景:在更新过程中抛出异常
- 验证点:所有数据是否回滚,保持一致性
并发测试
- 测试场景:同时修改同一个消耗单的加班系数
- 验证点:数据是否正确,不会出现脏数据
六、注意事项
6.1 数据一致性
- 原始数据不变:修改加班系数时,所有
F_Original*字段保持不变 - 重新计算:所有
F_Overtime*和最终字段都需要重新计算 - 事务保证:使用事务确保所有更新操作的原子性
6.2 性能考虑
- 批量更新:可以考虑使用批量更新,而不是循环更新
- 索引优化:确保相关表的查询字段有索引
6.3 业务规则
- 权限控制:确保只有有权限的用户才能修改加班系数
- 日志记录:记录修改操作,便于追溯
- 数据验证:验证加班系数的合法性(如不能为负数)
七、总结
7.1 核心要点
加班系数计算公式:
- 加班值 = 原始值 × 加班系数
- 最终值 = 原始值 + 加班值
影响范围:
- 主表:手工费
- 品项明细表:项目次数
- 健康师业绩表:耗卡品项次数、手工费
- 科技部老师业绩表:当前不参与计算
修改方案:
- 推荐新增专门的接口
UpdateOvertimeCoefficient - 只修改加班系数,自动重新计算所有相关字段
- 使用事务保证数据一致性
- 推荐新增专门的接口
7.2 实施步骤
- ✅ 创建DTO类
LqXhHyhkUpdateOvertimeInput - ✅ 在Service中新增
UpdateOvertimeCoefficient方法 - ✅ 实现计算逻辑(主表、品项明细、健康师业绩)
- ✅ 添加事务处理
- ✅ 前端调用接口(可选)
- ✅ 测试验证
文档结束