feat 人员考勤分析
This commit is contained in:
@@ -0,0 +1,54 @@
|
||||
using Dlhis.ScheduledTask.DeviceUploadLog.Request;
|
||||
using Dlhis.ScheduledTask.DeviceUploadLog.Response;
|
||||
using Dlhis.ScheduledTask.DeviceUploadLog.Services;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Dlhis.ScheduledTask.DeviceUploadLog.Controllers;
|
||||
|
||||
/// <summary>
|
||||
/// 用户考勤数据分析
|
||||
/// </summary>
|
||||
[Authorize]
|
||||
[Route("api/[controller]")]
|
||||
[ApiController]
|
||||
public class UserAttendanceDataAnalysisController : ControllerBase
|
||||
{
|
||||
private readonly IUserAttendanceDataAnalysisService _userAttendanceDataAnalysisService;
|
||||
|
||||
public UserAttendanceDataAnalysisController(IUserAttendanceDataAnalysisService userAttendanceDataAnalysisService)
|
||||
{
|
||||
_userAttendanceDataAnalysisService = userAttendanceDataAnalysisService;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 保存用户(批量)
|
||||
/// </summary>
|
||||
/// <param name="request">用户考勤用户编码请求</param>
|
||||
[HttpPost("save")]
|
||||
public async Task SaveAsync(UserAttendanceUserCodesRequest request)
|
||||
{
|
||||
await _userAttendanceDataAnalysisService.SaveAsync(request);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 删除用户(批量)
|
||||
/// </summary>
|
||||
/// <param name="request">用户考勤用户编码请求</param>
|
||||
[HttpPost("delete")]
|
||||
public async Task DeleteAsync(UserAttendanceUserCodesRequest request)
|
||||
{
|
||||
await _userAttendanceDataAnalysisService.DeleteAsync(request);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 分页获取用户考勤数据分析结果
|
||||
/// </summary>
|
||||
/// <param name="request">分页请求参数</param>
|
||||
/// <returns>用户编码列表</returns>
|
||||
[HttpGet("analysis-data")]
|
||||
public async Task<UserAttendanceDataAnalysisInfoResponse> GetPageAsync([FromQuery] UserAttendanceDataAnalysisPageRequest request)
|
||||
{
|
||||
return await _userAttendanceDataAnalysisService.GetPageAsync(request);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
using Kurisu.AspNetCore.DataAccess.Dto;
|
||||
|
||||
namespace Dlhis.ScheduledTask.DeviceUploadLog.Request;
|
||||
|
||||
public class UserAttendanceDataAnalysisPageRequest : PageDto
|
||||
{
|
||||
/// <summary>
|
||||
/// 查询日期
|
||||
/// </summary>
|
||||
public DateOnly Date { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Dlhis.ScheduledTask.DeviceUploadLog.Request;
|
||||
|
||||
public class UserAttendanceUserCodesRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// 工号
|
||||
/// </summary>
|
||||
[MinLength(1, ErrorMessage = "人员不可为空")]
|
||||
public List<string> UserCodes { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
using Dlhis.Entity.Basic.Enums;
|
||||
using Dlhis.ScheduledTask.DeviceUploadLog.Services.Implements;
|
||||
using Kurisu.AspNetCore.DataAccess.Dto;
|
||||
using Kurisu.AspNetCore.Utils.Extensions;
|
||||
|
||||
namespace Dlhis.ScheduledTask.DeviceUploadLog.Response;
|
||||
|
||||
public class UserAttendanceDataAnalysisInfoResponse
|
||||
{
|
||||
/// <summary>
|
||||
/// 班次时间范围
|
||||
/// </summary>
|
||||
public List<ShiftTimeRange> ShiftTimes { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 截止到当前时间
|
||||
/// </summary>
|
||||
public DateTime LastAnalysisDateTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 人员考勤数据分析结果
|
||||
/// </summary>
|
||||
public Pagination<UserAttendanceDataAnalysisResponse> Pagination { get; set; }
|
||||
}
|
||||
|
||||
public class UserAttendanceDataAnalysisResponse
|
||||
{
|
||||
/// <summary>
|
||||
/// 工号
|
||||
/// </summary>
|
||||
public string UserCode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 名称
|
||||
/// </summary>
|
||||
public string UserName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 部门名称
|
||||
/// </summary>
|
||||
public string DepartmentName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 上班分析结果类型
|
||||
/// </summary>
|
||||
public List<AttendanceDataAnalysisResultEnumType> StartAnalysisResultTypes { get; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// 结果
|
||||
/// </summary>
|
||||
public List<string> StartResults => StartAnalysisResultTypes.Select(x => x.GetDescription()).ToList();
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 下班分析结果类型
|
||||
/// </summary>
|
||||
public List<AttendanceDataAnalysisResultEnumType> EndAnalysisResultTypes { get; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// 结果
|
||||
/// </summary>
|
||||
public List<string> EndResults => EndAnalysisResultTypes.Select(x => x.GetDescription()).ToList();
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
using Dlhis.ScheduledTask.DeviceUploadLog.Request;
|
||||
using Dlhis.ScheduledTask.DeviceUploadLog.Response;
|
||||
using Kurisu.AspNetCore.DataAccess.Dto;
|
||||
|
||||
namespace Dlhis.ScheduledTask.DeviceUploadLog.Services;
|
||||
|
||||
/// <summary>
|
||||
/// 用户考勤数据分析服务接口
|
||||
/// </summary>
|
||||
public interface IUserAttendanceDataAnalysisService : IScopeDependency
|
||||
{
|
||||
/// <summary>
|
||||
/// 异步保存用户考勤数据
|
||||
/// </summary>
|
||||
/// <param name="request">用户考勤用户编码请求</param>
|
||||
Task SaveAsync(UserAttendanceUserCodesRequest request);
|
||||
|
||||
/// <summary>
|
||||
/// 异步删除用户考勤数据
|
||||
/// </summary>
|
||||
/// <param name="request">用户考勤用户编码请求</param>
|
||||
Task DeleteAsync(UserAttendanceUserCodesRequest request);
|
||||
|
||||
/// <summary>
|
||||
/// 分页获取用户考勤数据分析结果
|
||||
/// </summary>
|
||||
/// <param name="request">分页请求参数</param>
|
||||
/// <returns>用户编码列表</returns>
|
||||
Task<UserAttendanceDataAnalysisInfoResponse> GetPageAsync(UserAttendanceDataAnalysisPageRequest request);
|
||||
}
|
||||
@@ -0,0 +1,269 @@
|
||||
using Dlhis.Entity.Basic.Entity;
|
||||
using Dlhis.Entity.Basic.Enums;
|
||||
using Dlhis.Entity.Device.Entities;
|
||||
using Dlhis.Entity.Device.Enums;
|
||||
using Dlhis.ScheduledTask.DeviceUploadLog.Request;
|
||||
using Dlhis.ScheduledTask.DeviceUploadLog.Response;
|
||||
using Kurisu.AspNetCore.DataAccess.SqlSugar.Extensions;
|
||||
using Kurisu.AspNetCore.DataAccess.SqlSugar.Services;
|
||||
using Kurisu.AspNetCore.Utils.Extensions;
|
||||
using Org.BouncyCastle.Asn1.X9;
|
||||
using SqlSugar;
|
||||
|
||||
namespace Dlhis.ScheduledTask.DeviceUploadLog.Services.Implements;
|
||||
|
||||
public class UserAttendanceDataAnalysisService : IUserAttendanceDataAnalysisService
|
||||
{
|
||||
private readonly IDbContext _dbContext;
|
||||
|
||||
public UserAttendanceDataAnalysisService(IDbContext dbContext)
|
||||
{
|
||||
_dbContext = dbContext;
|
||||
}
|
||||
|
||||
|
||||
public async Task SaveAsync(UserAttendanceUserCodesRequest request)
|
||||
{
|
||||
//获取人员时候,自动过滤存在的人员,并且只获取在职的
|
||||
var gainfos = await _dbContext.Queryable<GainfoEntity>()
|
||||
.Where(x => request.UserCodes.Contains(x.Code))
|
||||
.Where(x => SqlFunc.Subqueryable<UserAttendanceDataAnalysisEntity>()
|
||||
.Where(p => p.UserCode == x.Code)
|
||||
.NotAny()
|
||||
)
|
||||
.ToListAsync();
|
||||
|
||||
await _dbContext.InsertAsync(gainfos.Select(x => new UserAttendanceDataAnalysisEntity
|
||||
{
|
||||
UserCode = x.Code
|
||||
}).ToList()
|
||||
);
|
||||
}
|
||||
|
||||
public async Task DeleteAsync(UserAttendanceUserCodesRequest request)
|
||||
{
|
||||
//直接删除
|
||||
await _dbContext.Deleteable<UserAttendanceDataAnalysisEntity>()
|
||||
.Where(x => request.UserCodes.Contains(x.UserCode))
|
||||
.ExecuteCommandAsync();
|
||||
}
|
||||
|
||||
public async Task<UserAttendanceDataAnalysisInfoResponse> GetPageAsync(UserAttendanceDataAnalysisPageRequest request)
|
||||
{
|
||||
//查询日期的时间范围
|
||||
var searchStart = request.Date.ToDateTime(TimeOnly.MinValue);
|
||||
var searchEnd = request.Date.ToDateTime(TimeOnly.MaxValue).AddSeconds(1);
|
||||
|
||||
var page = await _dbContext.Queryable<UserAttendanceDataAnalysisEntity>()
|
||||
.LeftJoin<GainfoEntity>((x, user) => x.UserCode == user.Code)
|
||||
.LeftJoin<DepartmentEntity>((x, user, dept) => user.DepartmentNo == dept.No)
|
||||
.WhereIF(request.Search.IsPresent(), x => x.UserCode == request.Search)
|
||||
.Where(x => SqlFunc.Subqueryable<GainfoEntity>().Where(p => p.Code == x.UserCode).Any())
|
||||
.OrderBy(x => x.CreateTime, OrderByType.Desc)
|
||||
.Select((x, user, dept) => new UserAttendanceDataAnalysisResponse
|
||||
{
|
||||
UserCode = x.UserCode,
|
||||
UserName = user.Name,
|
||||
DepartmentName = dept.Name
|
||||
})
|
||||
.ToPageListAsync(request);
|
||||
|
||||
|
||||
var (shiftStartRange, shiftEndRange) = await GetShiftTimeRangesAsync(request.Date);
|
||||
|
||||
if (await IsRestDayAsync(request.Date))
|
||||
{
|
||||
foreach (var item in page.Data)
|
||||
{
|
||||
item.StartAnalysisResultTypes.Add(AttendanceDataAnalysisResultEnumType.Rest);
|
||||
item.EndAnalysisResultTypes.Add(AttendanceDataAnalysisResultEnumType.Rest);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var userCodes = page.Data.Select(x => x.UserCode).ToList();
|
||||
var logs = await _dbContext.Queryable<DeviceUploadLogEntity>()
|
||||
.Where(x => x.RaiseDateTime >= searchStart && x.RaiseDateTime < searchEnd)
|
||||
.Where(x => x.Type == DeviceUploadLogTypeEnumType.Attendance)
|
||||
.Where(x => userCodes.Contains(x.UserUniqueNo))
|
||||
.ToListAsync();
|
||||
|
||||
foreach (var item in page.Data)
|
||||
{
|
||||
var userLogs = logs.Where(x => x.UserUniqueNo == item.UserCode).ToList();
|
||||
|
||||
//无考勤记录
|
||||
if (userLogs.Count == 0)
|
||||
{
|
||||
item.StartAnalysisResultTypes.Add(AttendanceDataAnalysisResultEnumType.NoAttendanceRecord);
|
||||
item.EndAnalysisResultTypes.Add(AttendanceDataAnalysisResultEnumType.NoAttendanceRecord);
|
||||
continue;
|
||||
}
|
||||
|
||||
HandlerClockIn(item.StartAnalysisResultTypes, userLogs, shiftStartRange);
|
||||
HandlerClockOut(item.EndAnalysisResultTypes, userLogs, shiftEndRange);
|
||||
}
|
||||
}
|
||||
|
||||
return new UserAttendanceDataAnalysisInfoResponse
|
||||
{
|
||||
ShiftTimes = new List<ShiftTimeRange>(2) { shiftStartRange, shiftEndRange },
|
||||
LastAnalysisDateTime = request.Date < DateOnly.FromDateTime(DateTime.Now.Date)
|
||||
? request.Date.ToDateTime(TimeOnly.MaxValue)
|
||||
: DateTime.Now,
|
||||
Pagination = page
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理上班考勤数据分析结果。
|
||||
/// </summary>
|
||||
/// <param name="analysisResultTypes"></param>
|
||||
/// <param name="userLogs"></param>
|
||||
/// <param name="shift"></param>
|
||||
private void HandlerClockIn(List<AttendanceDataAnalysisResultEnumType> analysisResultTypes, List<DeviceUploadLogEntity> userLogs, ShiftTimeRange shift)
|
||||
{
|
||||
var inLogs = userLogs.OrderBy(x => x.RaiseDateTime)
|
||||
.Where(x => x.RaiseDateTime >= shift.Start && x.RaiseDateTime <= shift.End)
|
||||
.ToList();
|
||||
|
||||
if (inLogs.Count == 0)
|
||||
{
|
||||
analysisResultTypes.Add(AttendanceDataAnalysisResultEnumType.NoAttendanceRecord);
|
||||
}
|
||||
else
|
||||
{
|
||||
var isClockIn = inLogs.Any(x => x.RaiseDateTime <= shift.Time);
|
||||
if (!isClockIn)
|
||||
{
|
||||
var isLate = inLogs.Any(x => !(x.RaiseDateTime <= shift.Time) && x.RaiseDateTime > shift.Time && x.RaiseDateTime <= shift.End);
|
||||
analysisResultTypes.Add(
|
||||
isLate
|
||||
? AttendanceDataAnalysisResultEnumType.Late
|
||||
: AttendanceDataAnalysisResultEnumType.Missing
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
analysisResultTypes.Add(AttendanceDataAnalysisResultEnumType.Normal);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理下班考勤数据分析结果。
|
||||
/// </summary>
|
||||
/// <param name="analysisResultTypes"></param>
|
||||
/// <param name="userLogs"></param>
|
||||
/// <param name="shift"></param>
|
||||
private void HandlerClockOut(List<AttendanceDataAnalysisResultEnumType> analysisResultTypes, List<DeviceUploadLogEntity> userLogs, ShiftTimeRange shift)
|
||||
{
|
||||
var outLogs = userLogs.OrderBy(x => x.RaiseDateTime)
|
||||
.Where(x => x.RaiseDateTime >= shift.Start)
|
||||
.ToList();
|
||||
|
||||
if (outLogs.Count == 0)
|
||||
{
|
||||
analysisResultTypes.Add(AttendanceDataAnalysisResultEnumType.NoAttendanceRecord);
|
||||
}
|
||||
else
|
||||
{
|
||||
var isClockOut = outLogs.Any(x => x.RaiseDateTime >= shift.Time);
|
||||
if (!isClockOut)
|
||||
{
|
||||
var isEarly = outLogs.Any(x => x.RaiseDateTime < shift.Time);
|
||||
analysisResultTypes.Add(
|
||||
isEarly
|
||||
? AttendanceDataAnalysisResultEnumType.Early
|
||||
: AttendanceDataAnalysisResultEnumType.Missing
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
analysisResultTypes.Add(AttendanceDataAnalysisResultEnumType.Normal);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<(ShiftTimeRange ShiftStartRange, ShiftTimeRange shiftEndRange)> GetShiftTimeRangesAsync(DateOnly date)
|
||||
{
|
||||
var longDayShifts = await _dbContext.Queryable<BasicTypeEntity>()
|
||||
.Where(x => x.Type.StartsWith("LongDayShift"))
|
||||
.Select(x => new { x.Code, x.Type })
|
||||
.ToListAsync();
|
||||
|
||||
var longDayShift = longDayShifts.FirstOrDefault(x => x.Type == "LongDayShift")?.Code;
|
||||
longDayShift.IsPresent().ThrowIfFalse("请先配置长白班的时间段LongDayShift");
|
||||
|
||||
// 上班时间范围
|
||||
var longDayShiftStart = longDayShifts.FirstOrDefault(x => x.Type == "LongDayShift-Start")?.Code;
|
||||
longDayShiftStart.IsPresent().ThrowIfFalse("请先配置长白班的时间段LongDayShift-Start");
|
||||
var startArr = longDayShiftStart!.Split(",");
|
||||
|
||||
// 下班时间范围
|
||||
var longDayShiftEnd = longDayShifts.FirstOrDefault(x => x.Type == "LongDayShift-End")?.Code;
|
||||
longDayShiftEnd.IsPresent().ThrowIfFalse("请先配置长白班的时间段LongDayShift-End");
|
||||
|
||||
var arr = longDayShift!.Split("-");
|
||||
var shiftStart = TimeOnly.Parse(arr[0]);
|
||||
var shiftEnd = TimeOnly.Parse(arr[1]);
|
||||
|
||||
var startRange = new ShiftTimeRange(date, shiftStart.AddMinutes(int.Parse(startArr[0])), shiftStart, shiftStart.AddMinutes(int.Parse(startArr[1])));
|
||||
var endRange = new ShiftTimeRange(date, shiftEnd.AddMinutes(int.Parse(longDayShiftEnd!)), shiftEnd, TimeOnly.MaxValue);
|
||||
return (startRange, endRange);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查指定日期是否为休息日或节假日。
|
||||
/// </summary>
|
||||
/// <param name="searchDate"></param>
|
||||
/// <returns></returns>
|
||||
private async Task<bool> IsRestDayAsync(DateOnly searchDate)
|
||||
{
|
||||
var now = DateTime.Now;
|
||||
|
||||
var currentHolidays = await _dbContext.Queryable<HolidayEntity>()
|
||||
.Where(x => x.Year == now.Year && searchDate >= x.Start && searchDate <= x.End)
|
||||
.Select(x => new { x.Type, x.Name, x.Start, x.End })
|
||||
.ToListAsync();
|
||||
|
||||
// 优先判断节假日类型
|
||||
if (currentHolidays.Any(x => x.Type == HolidayEnumType.Rest))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (currentHolidays.Any(x => x.Type == HolidayEnumType.NeedWork))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// 否则按周末判断
|
||||
var isWeekend = searchDate.DayOfWeek is DayOfWeek.Saturday or DayOfWeek.Sunday;
|
||||
return isWeekend;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class ShiftTimeRange
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="date"></param>
|
||||
/// <param name="start"></param>
|
||||
/// <param name="time"></param>
|
||||
/// <param name="end"></param>
|
||||
public ShiftTimeRange(DateOnly date, TimeOnly start, TimeOnly time, TimeOnly end)
|
||||
{
|
||||
Start = date.ToDateTime(start);
|
||||
Time = date.ToDateTime(time);
|
||||
End = date.ToDateTime(end);
|
||||
}
|
||||
|
||||
public DateTime Start { get; set; }
|
||||
public DateTime Time { get; set; }
|
||||
public DateTime End { get; set; }
|
||||
}
|
||||
@@ -9,6 +9,10 @@
|
||||
<GenerateDocumentationFile>True</GenerateDocumentationFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||
<NoWarn>1701;1702;1591</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Remove="log\**" />
|
||||
<Content Remove="log\**" />
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using Dlhis.Sys.Api;
|
||||
using Dlhis.Sys.Api.Request;
|
||||
using Dlhis.Jip.Api.ClientHandlers;
|
||||
using Kurisu.AspNetCore.Cache.Extensions;
|
||||
using Kurisu.AspNetCore.Startup;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user