工资导入逻辑说明.md 6.06 KB

工资导入逻辑说明

导入Excel文件结构

Excel文件位置

  • 路径:ExportFiles/工资导入/
  • 文件命名:{岗位名称}工资_{时间戳}.xlsx
  • 工作表名称:与岗位名称对应(如"健康师工资"、"店长工资"等)

Excel文件结构

重要:Excel文件的第一列(A列)必须是 ID(主键)

A列: ID(主键,F_Id)
B列: 门店名称
C列: 员工姓名
... 其他业务字段

当前Excel文件状态

根据查看的文件,目前Excel文件的第一列还不是ID,而是"门店名称"。需要:

  • 修改导出功能,确保第一列是ID
  • 修改导入功能,通过ID来判断更新还是新增

导入逻辑

1. 读取Excel

  • 使用 ExcelImportHelper.ToDataTable() 读取Excel文件
  • 第一列(索引0)为ID字段
  • 从第二行开始读取数据(第一行是表头)

2. 判断是更新还是新增

foreach (var row in dataTable.Rows)
{
    var id = row[0]?.ToString()?.Trim(); // 第一列是ID

    if (!string.IsNullOrWhiteSpace(id))
    {
        // 有ID → 查找现有记录
        var existing = await _db.Queryable<SalaryEntity>()
            .Where(x => x.Id == id)
            .FirstAsync();

        if (existing != null)
        {
            // 记录存在 → 检查是否可以更新
            // ... 更新逻辑
        }
        else
        {
            // 记录不存在 → 新增
            // ... 新增逻辑
        }
    }
    else
    {
        // 没有ID → 新增
        // ... 新增逻辑
    }
}

3. 保护逻辑(已锁定或已确认的记录不能导入覆盖)

if (existing != null)
{
    // 检查是否已锁定(已锁定的不能导入覆盖)
    if (existing.IsLocked == 1)
    {
        failMessages.Add($"员工 {existing.EmployeeName} (ID: {id}) 的工资已锁定,不能导入覆盖");
        failCount++;
        continue; // 跳过
    }

    // 检查是否已确认(已确认的不能导入覆盖)
    if (existing.EmployeeConfirmStatus == 1)
    {
        failMessages.Add($"员工 {existing.EmployeeName} (ID: {id}) 的工资已确认,不能导入覆盖");
        failCount++;
        continue; // 跳过
    }

    // 可以更新 → 覆盖现有记录(未锁定且未确认)
    existing.StoreName = storeName;
    existing.EmployeeName = employeeName;
    // ... 更新所有字段
    // 注意:导入后重置确认状态(如果被覆盖)
    existing.EmployeeConfirmStatus = 0;
    existing.EmployeeConfirmTime = null;
    existing.EmployeeConfirmRemark = null;
    recordsToUpdate.Add(existing);
}
else
{
    // 新增记录
    var newRecord = new SalaryEntity
    {
        Id = string.IsNullOrWhiteSpace(id) ? YitIdHelper.NextId().ToString() : id,
        // ... 其他字段
        EmployeeConfirmStatus = 0,
        IsLocked = 0
    };
    recordsToInsert.Add(newRecord);
}

导出功能修改

需要修改的导出功能

确保导出时,第一列是ID(主键),格式如下:

[HttpGet("Actions/Export")]
public async Task<dynamic> Export([FromQuery] SalaryInput input)
{
    var exportData = await this.GetNoPagingList(input);

    // 配置导出字段,确保第一列是ID
    List<ParamsModel> paramList = new List<ParamsModel>
    {
        new ParamsModel { value = "ID", field = "id" }, // 第一列必须是ID
        new ParamsModel { value = "门店名称", field = "storeName" },
        new ParamsModel { value = "员工姓名", field = "employeeName" },
        // ... 其他字段
    };

    // ... Excel导出逻辑
}

涉及的服务

需要在以下9个工资服务中实现导入功能:

  1. LqSalaryService - 健康师
  2. LqTechTeacherSalaryService - 科技部老师
  3. LqAssistantSalaryService - 店助/店助主任
  4. LqStoreManagerSalaryService - 店长
  5. LqDirectorSalaryService - 主任
  6. LqMajorProjectTeacherSalaryService - 大项目部老师
  7. LqMajorProjectDirectorSalaryService - 大项目主管
  8. LqTechGeneralManagerSalaryService - 科技部总经理
  9. LqBusinessUnitManagerSalaryService - 事业部总经理/经理

实施步骤

  1. ✅ 确认导入逻辑:通过ID判断更新/新增
  2. ⏳ 修改导出功能:确保第一列是ID
  3. ⏳ 实现/修改导入功能:
    • 读取Excel,第一列为ID
    • 有ID且存在 → 检查锁定/确认状态 → 更新
    • 有ID但不存在 → 新增(使用该ID)
    • 无ID → 新增(自动生成ID)
  4. ⏳ 添加保护逻辑:已锁定或已确认的记录跳过

关键点

  1. Excel第一列必须是ID:这样导入时才能准确匹配记录
  2. ID的处理
    • Excel有ID且数据库存在 → 更新(检查锁定/确认状态)
    • Excel有ID但数据库不存在 → 新增(使用Excel中的ID)
    • Excel无ID → 新增(自动生成新ID)
  3. 保护机制
    • 已锁定(IsLocked = 1)的记录不能导入覆盖
    • 已确认(EmployeeConfirmStatus = 1)的记录不能导入覆盖
    • 只有未锁定且未确认的记录才能导入覆盖
  4. 导入后重置确认状态:如果记录被导入覆盖,确认状态会被重置为0

工作流程说明

完整的工资处理流程

  1. 系统计算工资 → 生成工资数据(IsLocked = 0, EmployeeConfirmStatus = 0)
  2. 导出Excel → 第一列是ID,后续列是业务字段
  3. 线下梳理处理 → 在Excel中调整数据
  4. 导入Excel → 通过ID匹配,覆盖未锁定且未确认的记录
  5. 管理员锁定工资 → 设置 IsLocked = 1(准备让员工确认)
  6. 员工查看工资条 → 只能查看已锁定的工资条
  7. 员工确认工资条 → 只能确认已锁定的工资条(IsLocked = 1 且 EmployeeConfirmStatus = 0)
  8. 发工资 → 确认后(EmployeeConfirmStatus = 1)才会去发工资

状态流转

初始状态:IsLocked = 0, EmployeeConfirmStatus = 0
    ↓ 管理员锁定
已锁定状态:IsLocked = 1, EmployeeConfirmStatus = 0
    ↓ 员工确认
已确认状态:IsLocked = 1, EmployeeConfirmStatus = 1
    ↓ 发工资