LqAttendanceSummaryService.cs 15.5 KB
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
{
    /// <summary>
    /// 考勤汇总服务
    /// </summary>
    [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<LqAttendanceSummaryService> _logger;

        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="db">数据库客户端</param>
        /// <param name="userManager">用户管理器</param>
        /// <param name="logger">日志记录器</param>
        public LqAttendanceSummaryService(ISqlSugarClient db, IUserManager userManager, ILogger<LqAttendanceSummaryService> logger)
        {
            _db = db;
            _userManager = userManager;
            _logger = logger;
        }

        #region 上传Excel文件导入考勤汇总数据
        /// <summary>
        /// 上传Excel文件导入考勤汇总数据
        /// </summary>
        /// <remarks>
        /// 上传Excel文件批量导入考勤汇总数据
        /// 
        /// Excel文件格式要求:
        /// - 第一行必须是标题行
        /// - 列顺序:员工姓名、员工电话、年份、月份、出勤天数、请假天数、休息天数、备注
        /// - 支持.xlsx和.xls格式
        /// 
        /// 示例请求:
        /// POST /api/Extend/LqAttendanceSummary/ImportAttendanceDataFromExcel
        /// Content-Type: multipart/form-data
        /// 
        /// 参数说明:
        /// - file: Excel文件(必填)
        /// </remarks>
        /// <param name="file">Excel文件</param>
        /// <returns>导入结果</returns>
        /// <response code="200">导入成功</response>
        /// <response code="400">文件格式错误</response>
        /// <response code="500">服务器错误</response>
        [HttpPost("ImportAttendanceDataFromExcel")]
        public async Task<dynamic> 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<LqAttendanceSummaryImportInput>();

                // 保存临时文件
                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 处理导入数据
        /// <summary>
        /// 处理导入数据
        /// </summary>
        /// <param name="importData">导入数据列表</param>
        /// <returns>导入结果</returns>
        private async Task<dynamic> ProcessImportData(List<LqAttendanceSummaryImportInput> importData)
        {
            var successCount = 0;
            var failCount = 0;
            var errorMessages = new List<string>();
            var entitiesToInsert = new List<LqAttendanceSummaryEntity>();
            var entitiesToUpdate = new List<LqAttendanceSummaryEntity>();

            foreach (var item in importData)
            {
                try
                {
                    // 1. 根据员工姓名和电话查找用户ID
                    var user = await _db.Queryable<UserEntity>()
                        .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<LqAttendanceSummaryEntity>()
                        .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 获取考勤汇总列表
        /// <summary>
        /// 获取考勤汇总列表
        /// </summary>
        /// <param name="input">请求参数</param>
        /// <returns></returns>
        [HttpGet("")]
        public async Task<dynamic> 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<LqAttendanceSummaryEntity>()
                .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<UserEntity>().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<UserEntity>().Where(u => u.Id == a.CreateUser).Select(u => u.RealName),
                    createTime = a.CreateTime,
                    updateUser = a.UpdateUser,
                    updateUserName = SqlFunc.Subqueryable<UserEntity>().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<LqAttendanceSummaryListOutput>.SqlSugarPageResult(data);
        }
        #endregion

        #region 清空某一个月的数据
        /// <summary>
        ///  清空某一个月的数据
        /// </summary>
        /// <param name="year">年份</param>
        /// <param name="month">月份</param>
        /// <returns></returns>
        [HttpDelete("DeleteByMonth/{year}/{month}")]
        public async Task<dynamic> DeleteByMonth(string year, string month)
        {
            int yearInt = int.Parse(year);
            int monthInt = int.Parse(month);
            await _db.Deleteable<LqAttendanceSummaryEntity>().Where(p => p.Year == yearInt && p.Month == monthInt).ExecuteCommandAsync();
            return new
            {
                success = true,
                message = "清空成功"
            };
        }
        #endregion


    }
}