加班系数逻辑说明及修改方案.md 20.5 KB

加班系数逻辑说明及修改方案

文档版本: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行

流程

  1. 接收 LqXhHyhkCrInput 参数,包含 overtimeCoefficient
  2. 计算主表加班字段(科技部不参与加班,主表加班手工费 = 健康师加班手工费之和): 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;
  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<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();
  4. 重新插入所有明细数据(与Create方法相同)

问题

  • Update方法需要传入完整的 lqXhPxmxList,不能只修改加班系数
  • 如果只想修改加班系数,需要先查询所有明细数据,然后调用Update方法

三、修改加班系数功能设计方案

3.1 需求分析

核心需求

  1. 提供一个专门的接口,只修改加班系数
  2. 修改后自动重新计算所有相关的加班字段
  3. 不需要传入完整的明细数据,只需要传入新的加班系数

使用场景

  • 用户发现消耗单的加班系数设置错误,需要修改
  • 不需要修改其他数据(品项、健康师等),只需要修改加班系数

3.2 设计方案

方案一:新增专门的修改加班系数接口(推荐)⭐

优点

  • 接口职责单一,只负责修改加班系数
  • 不需要传入完整的明细数据
  • 性能更好,只需要更新相关字段,不需要删除和重新插入

缺点

  • 需要新增一个接口

实现步骤

  1. 新增DTO类LqXhHyhkUpdateOvertimeInput.cs

    public class LqXhHyhkUpdateOvertimeInput
    {
       /// <summary>
       /// 耗卡编号
       /// </summary>
       public string id { get; set; }
    
       /// <summary>
       /// 新的加班系数(NULL或0表示非加班单,大于0表示加班单,如 0.5、1、1.5)
       /// </summary>
       public decimal? overtimeCoefficient { get; set; }
    }
    
  2. 新增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. 查询所有科技部老师业绩,更新加班字段(如果需要)
    }
    
  3. 计算逻辑

    • 主表:重新计算 F_OvertimeSgfy(= 健康师原始手工费之和 × 系数)和 sgfy(科技部不参与加班)
    • 品项明细表:重新计算 F_OvertimeProjectNumberF_ProjectNumber
    • 健康师业绩表:重新计算 F_OvertimeKdpxNumberF_kdpxNumberF_OvertimeLaborCostF_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 后端文件

  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_OvertimeSgfysgfy 是否正确更新
      • 品项明细表的 F_OvertimeProjectNumberF_ProjectNumber 是否正确更新
      • 健康师业绩表的 F_OvertimeKdpxNumberF_kdpxNumberF_OvertimeLaborCostF_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. ✅ 测试验证

文档结束