using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; using Mapster; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; using NCC.Common.Core.Manager; using NCC.Common.Enum; using NCC.Common.Extension; using NCC.Common.Filter; using NCC.Common.Helper; using NCC.Dependency; using NCC.DynamicApiController; using NCC.Extend.Entitys.Dto.LqAttendanceSummary; using NCC.Extend.Entitys.Enum; using NCC.Extend.Entitys.lq_attendance_summary; using NCC.Extend.Interfaces.LqAttendanceSummary; using NCC.FriendlyException; using NCC.System.Entitys.Permission; using SqlSugar; using Yitter.IdGenerator; namespace NCC.Extend { /// /// 考勤汇总服务 /// [ApiDescriptionSettings(Tag = "绿纤考勤汇总服务", Name = "LqAttendanceSummary", Order = 200)] [Route("api/Extend/[controller]")] public class LqAttendanceSummaryService : ILqAttendanceSummaryService, IDynamicApiController, ITransient { private readonly ISqlSugarClient _db; private readonly IUserManager _userManager; private readonly ILogger _logger; /// /// 构造函数 /// /// 数据库客户端 /// 用户管理器 /// 日志记录器 public LqAttendanceSummaryService(ISqlSugarClient db, IUserManager userManager, ILogger logger) { _db = db; _userManager = userManager; _logger = logger; } #region 上传Excel文件导入考勤汇总数据 /// /// 上传Excel文件导入考勤汇总数据 /// /// /// 上传Excel文件批量导入考勤汇总数据 /// /// Excel文件格式要求: /// - 第一行必须是标题行 /// - 列顺序:员工姓名、员工电话、年份、月份、出勤天数、请假天数、休息天数、备注 /// - 支持.xlsx和.xls格式 /// /// 示例请求: /// POST /api/Extend/LqAttendanceSummary/ImportAttendanceDataFromExcel /// Content-Type: multipart/form-data /// /// 参数说明: /// - file: Excel文件(必填) /// /// Excel文件 /// 导入结果 /// 导入成功 /// 文件格式错误 /// 服务器错误 [HttpPost("ImportAttendanceDataFromExcel")] public async Task ImportAttendanceDataFromExcel(IFormFile file) { try { if (file == null || file.Length == 0) { throw NCCException.Oh("请选择要上传的Excel文件"); } // 检查文件格式 var allowedExtensions = new[] { ".xlsx", ".xls" }; var fileExtension = Path.GetExtension(file.FileName).ToLowerInvariant(); if (!allowedExtensions.Contains(fileExtension)) { throw NCCException.Oh("只支持.xlsx和.xls格式的Excel文件"); } var importData = new List(); // 保存临时文件 var tempFilePath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString() + Path.GetExtension(file.FileName)); try { using (var stream = new FileStream(tempFilePath, FileMode.Create)) { await file.CopyToAsync(stream); } // 使用ExcelImportHelper读取Excel文件 var dataTable = ExcelImportHelper.ToDataTable(tempFilePath, 0, 0); if (dataTable.Rows.Count == 0) { throw NCCException.Oh("Excel文件中没有数据行"); } // 从第1行开始读取数据(跳过标题行) for (int i = 1; i < dataTable.Rows.Count; i++) { try { var row = dataTable.Rows[i]; var employeeName = row[0]?.ToString()?.Trim(); var employeePhone = row[1]?.ToString()?.Trim(); var yearText = row[2]?.ToString()?.Trim(); var monthText = row[3]?.ToString()?.Trim(); var workDaysText = row[4]?.ToString()?.Trim(); var leaveDaysText = row[5]?.ToString()?.Trim(); var restDaysText = row[6]?.ToString()?.Trim(); var remark = row[7]?.ToString()?.Trim(); // 跳过空行 if (string.IsNullOrEmpty(employeeName) && string.IsNullOrEmpty(employeePhone)) { continue; } // 验证必填字段 if (string.IsNullOrEmpty(employeeName)) { throw new Exception($"第{i + 1}行:员工姓名不能为空"); } if (string.IsNullOrEmpty(employeePhone)) { throw new Exception($"第{i + 1}行:员工电话不能为空"); } // 解析数值字段 if (!int.TryParse(yearText, out int year)) { throw new Exception($"第{i + 1}行:年份格式错误"); } if (!int.TryParse(monthText, out int month)) { throw new Exception($"第{i + 1}行:月份格式错误"); } decimal.TryParse(workDaysText, out decimal workDays); decimal.TryParse(leaveDaysText, out decimal leaveDays); decimal.TryParse(restDaysText, out decimal restDays); var item = new LqAttendanceSummaryImportInput { EmployeeName = employeeName, EmployeePhone = employeePhone, Year = year, Month = month, WorkDays = workDays, LeaveDays = leaveDays, RestDays = restDays, Remark = remark }; importData.Add(item); } catch (Exception ex) { throw new Exception($"第{i + 1}行数据解析失败: {ex.Message}"); } } } finally { // 清理临时文件 if (File.Exists(tempFilePath)) { File.Delete(tempFilePath); } } if (!importData.Any()) { throw NCCException.Oh("Excel文件中没有有效的数据行"); } // 处理导入数据 return await ProcessImportData(importData); } catch (Exception ex) { _logger.LogError(ex, "上传Excel文件导入考勤汇总数据失败"); throw NCCException.Oh($"上传Excel文件导入考勤汇总数据失败: {ex.Message}"); } } #endregion #region 处理导入数据 /// /// 处理导入数据 /// /// 导入数据列表 /// 导入结果 private async Task ProcessImportData(List importData) { var successCount = 0; var failCount = 0; var errorMessages = new List(); var entitiesToInsert = new List(); var entitiesToUpdate = new List(); foreach (var item in importData) { try { // 1. 根据员工姓名和电话查找用户ID var user = await _db.Queryable() .Where(u => u.RealName == item.EmployeeName && u.MobilePhone == item.EmployeePhone) .FirstAsync(); if (user == null) { errorMessages.Add($"员工 {item.EmployeeName}({item.EmployeePhone}) 不存在"); failCount++; continue; } // 2. 检查是否已存在相同记录 var existingRecord = await _db.Queryable() .Where(a => a.UserId == user.Id && a.Year == item.Year && a.Month == item.Month) .FirstAsync(); if (existingRecord != null) { // 更新现有记录 existingRecord.EmployeeStatus = 1; // 默认在职 existingRecord.WorkDays = item.WorkDays; existingRecord.LeaveDays = item.LeaveDays; existingRecord.RestDays = item.RestDays; existingRecord.Remark = item.Remark; existingRecord.UpdateUser = _userManager.UserId; existingRecord.UpdateTime = DateTime.Now; entitiesToUpdate.Add(existingRecord); } else { // 创建新记录 var entity = new LqAttendanceSummaryEntity { Id = YitIdHelper.NextId().ToString(), UserId = user.Id, Year = item.Year, Month = item.Month, EmployeeStatus = 1, // 默认在职 WorkDays = item.WorkDays, LeaveDays = item.LeaveDays, RestDays = item.RestDays, Remark = item.Remark, CreateUser = _userManager.UserId, CreateTime = DateTime.Now, UpdateUser = _userManager.UserId, UpdateTime = DateTime.Now, IsEffective = 1 }; entitiesToInsert.Add(entity); } successCount++; } catch (Exception ex) { errorMessages.Add($"处理员工 {item.EmployeeName} 数据失败: {ex.Message}"); failCount++; _logger.LogError(ex, $"导入考勤数据失败 - 员工: {item.EmployeeName}"); } } // 批量插入和更新 if (entitiesToInsert.Any()) { await _db.Insertable(entitiesToInsert).ExecuteCommandAsync(); } if (entitiesToUpdate.Any()) { await _db.Updateable(entitiesToUpdate).ExecuteCommandAsync(); } return new { success = true, message = "导入完成", data = new { totalCount = importData.Count, successCount = successCount, failCount = failCount, insertCount = entitiesToInsert.Count, updateCount = entitiesToUpdate.Count, errorMessages = errorMessages } }; } #endregion #region 获取考勤汇总列表 /// /// 获取考勤汇总列表 /// /// 请求参数 /// [HttpGet("")] public async Task GetList([FromQuery] LqAttendanceSummaryListQueryInput input) { var sidx = input.sidx == null ? "id" : input.sidx; var sord = input.sort == null ? "desc" : input.sort; var data = await _db.Queryable() .Where(a => a.IsEffective == StatusEnum.有效.GetHashCode()) .WhereIF(!string.IsNullOrEmpty(input.UserId), a => a.UserId == input.UserId) .WhereIF(input.Year.HasValue, a => a.Year == input.Year) .WhereIF(input.Month.HasValue, a => a.Month == input.Month) .WhereIF(input.EmployeeStatus.HasValue, a => a.EmployeeStatus == input.EmployeeStatus) .Select(a => new LqAttendanceSummaryListOutput { id = a.Id, userId = a.UserId, userName = SqlFunc.Subqueryable().Where(u => u.Id == a.UserId).Select(u => u.RealName), year = a.Year, month = a.Month, employeeStatus = a.EmployeeStatus, workDays = a.WorkDays, leaveDays = a.LeaveDays, restDays = a.RestDays, remark = a.Remark, createUser = a.CreateUser, createUserName = SqlFunc.Subqueryable().Where(u => u.Id == a.CreateUser).Select(u => u.RealName), createTime = a.CreateTime, updateUser = a.UpdateUser, updateUserName = SqlFunc.Subqueryable().Where(u => u.Id == a.UpdateUser).Select(u => u.RealName), updateTime = a.UpdateTime, isEffective = a.IsEffective, }) .MergeTable() .OrderBy(sidx + " " + input.sort) .ToPagedListAsync(input.currentPage, input.pageSize); return PageResult.SqlSugarPageResult(data); } #endregion #region 清空某一个月的数据 /// /// 清空某一个月的数据 /// /// 年份 /// 月份 /// [HttpDelete("DeleteByMonth/{year}/{month}")] public async Task DeleteByMonth(string year, string month) { int yearInt = int.Parse(year); int monthInt = int.Parse(month); await _db.Deleteable().Where(p => p.Year == yearInt && p.Month == monthInt).ExecuteCommandAsync(); return new { success = true, message = "清空成功" }; } #endregion } }