feat 人员考勤分析
This commit is contained in:
@@ -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; }
|
||||
}
|
||||
Reference in New Issue
Block a user