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
#region 生成健康师考勤模拟数据
///
/// 生成健康师考勤模拟数据
///
///
/// 为所有健康师生成指定年月的考勤模拟数据
///
/// 规则:
/// - 80%以上的健康师出勤天数 >= 21天
/// - 其他健康师出勤天数随机生成(0-20天)
/// - 员工状态默认为1(在职)
///
/// 示例请求:
/// ```json
/// {
/// "year": 2025,
/// "month": 11
/// }
/// ```
///
/// 年份
/// 月份
/// 生成结果
/// 成功生成考勤模拟数据
/// 参数错误
/// 生成失败
[HttpPost("GenerateMockData/{year}/{month}")]
public async Task GenerateMockData(int year, int month)
{
try
{
// 验证参数
if (year < 2000 || year > 3000)
{
throw NCCException.Oh("年份参数无效");
}
if (month < 1 || month > 12)
{
throw NCCException.Oh("月份参数无效,应在1-12之间");
}
// 查询所有健康师
var healthCoaches = await _db.Queryable()
.Where(u => u.Gw == "健康师" && u.EnabledMark == 1 && (u.DeleteMark == null || u.DeleteMark == 0))
.Select(u => new { u.Id, u.RealName })
.ToListAsync();
if (!healthCoaches.Any())
{
throw NCCException.Oh("未找到健康师用户");
}
// 检查是否已存在该年月的考勤数据
var existingRecords = await _db.Queryable()
.Where(a => a.Year == year && a.Month == month)
.CountAsync();
if (existingRecords > 0)
{
throw NCCException.Oh($"已存在{year}年{month}月的考勤数据,请先清空后再生成");
}
var totalCount = healthCoaches.Count;
var highAttendanceCount = (int)Math.Ceiling(totalCount * 0.8); // 80%以上,向上取整
var lowAttendanceCount = totalCount - highAttendanceCount;
var random = new Random();
var entitiesToInsert = new List();
var now = DateTime.Now;
// 生成考勤数据
for (int i = 0; i < totalCount; i++)
{
var healthCoach = healthCoaches[i];
decimal workDays;
decimal leaveDays;
decimal restDays;
if (i < highAttendanceCount)
{
// 80%以上的健康师:出勤天数 >= 21天
workDays = random.Next(21, 31); // 21-30天
var remainingDays = 30 - workDays;
// 剩余天数分配给请假和休息
if (remainingDays > 0)
{
leaveDays = random.Next(0, (int)remainingDays + 1);
restDays = remainingDays - leaveDays;
}
else
{
leaveDays = 0;
restDays = 0;
}
}
else
{
// 其他20%的健康师:出勤天数 < 21天
workDays = random.Next(0, 21); // 0-20天
var remainingDays = 30 - workDays;
// 剩余天数分配给请假和休息
if (remainingDays > 0)
{
leaveDays = random.Next(0, (int)remainingDays + 1);
restDays = remainingDays - leaveDays;
}
else
{
leaveDays = 0;
restDays = 0;
}
}
var entity = new LqAttendanceSummaryEntity
{
Id = YitIdHelper.NextId().ToString(),
UserId = healthCoach.Id,
Year = year,
Month = month,
EmployeeStatus = 1, // 在职
WorkDays = workDays,
LeaveDays = leaveDays,
RestDays = restDays,
Remark = "模拟数据",
CreateUser = _userManager.UserId ?? "system",
CreateTime = now,
UpdateUser = _userManager.UserId ?? "system",
UpdateTime = now,
IsEffective = 1
};
entitiesToInsert.Add(entity);
}
// 批量插入
await _db.Insertable(entitiesToInsert).ExecuteCommandAsync();
// 统计结果
var highAttendanceActual = entitiesToInsert.Count(e => e.WorkDays >= 21);
var highAttendancePercentage = (decimal)highAttendanceActual / totalCount * 100;
return new
{
success = true,
message = "生成考勤模拟数据成功",
data = new
{
year = year,
month = month,
totalCount = totalCount,
highAttendanceCount = highAttendanceActual,
highAttendancePercentage = Math.Round(highAttendancePercentage, 2),
generatedCount = entitiesToInsert.Count
}
};
}
catch (Exception ex)
{
_logger.LogError(ex, $"生成健康师考勤模拟数据失败 - 年份: {year}, 月份: {month}");
throw NCCException.Oh($"生成健康师考勤模拟数据失败:{ex.Message}");
}
}
#endregion
}
}