# 工资导入逻辑说明 ## 导入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. 判断是更新还是新增 ```csharp foreach (var row in dataTable.Rows) { var id = row[0]?.ToString()?.Trim(); // 第一列是ID if (!string.IsNullOrWhiteSpace(id)) { // 有ID → 查找现有记录 var existing = await _db.Queryable() .Where(x => x.Id == id) .FirstAsync(); if (existing != null) { // 记录存在 → 检查是否可以更新 // ... 更新逻辑 } else { // 记录不存在 → 新增 // ... 新增逻辑 } } else { // 没有ID → 新增 // ... 新增逻辑 } } ``` ### 3. 保护逻辑(已锁定或已确认的记录不能导入覆盖) ```csharp 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(主键),格式如下: ```csharp [HttpGet("Actions/Export")] public async Task Export([FromQuery] SalaryInput input) { var exportData = await this.GetNoPagingList(input); // 配置导出字段,确保第一列是ID List paramList = new List { 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 ↓ 发工资 ```