270 lines
10 KiB
C#
270 lines
10 KiB
C#
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 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 searchDate = DateOnly.FromDateTime(request.SearchDate);
|
|
|
|
//查询日期的时间范围
|
|
var searchStart = searchDate.ToDateTime(TimeOnly.MinValue);
|
|
var searchEnd = searchDate.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(searchDate);
|
|
|
|
if (await IsRestDayAsync(searchDate))
|
|
{
|
|
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 = searchDate < DateOnly.FromDateTime(DateTime.Now.Date)
|
|
? searchDate.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; }
|
|
} |