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
}
}