first commit
This commit is contained in:
@@ -0,0 +1,191 @@
|
||||
using Fitness_core_BE.DTOs.Attendance;
|
||||
using Fitness_core_BE.Interfaces;
|
||||
using Fitness_core_BE.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Fitness_core_BE.Repos
|
||||
{
|
||||
public class AttendanceRepository : IAttendanceRepository
|
||||
{
|
||||
private readonly TenantDbContext _dbContext;
|
||||
|
||||
public AttendanceRepository(TenantDbContext dbContext)
|
||||
{
|
||||
_dbContext = dbContext;
|
||||
}
|
||||
|
||||
public async Task<Attendance?> GetLatestAttendanceAsync(Guid clientId)
|
||||
{
|
||||
return await _dbContext.Attendances
|
||||
.AsNoTracking()
|
||||
.Where(a => a.FKClientId == clientId)
|
||||
.OrderByDescending(a => a.EventTime)
|
||||
.ThenByDescending(a => a.AttendanceId)
|
||||
.FirstOrDefaultAsync();
|
||||
}
|
||||
|
||||
public async Task<HashSet<string>> GetAttendanceKeysAsync(Guid clientId, DateTime fromTime)
|
||||
{
|
||||
var keys = await _dbContext.Attendances
|
||||
.AsNoTracking()
|
||||
.Where(a => a.FKClientId == clientId && a.EventTime >= fromTime)
|
||||
.Select(a => new
|
||||
{
|
||||
a.FKDeviceId,
|
||||
a.FKUserId,
|
||||
a.FKCustomerId,
|
||||
a.DeviceUserId,
|
||||
a.DeviceEmployeeNo,
|
||||
a.EventTime,
|
||||
a.EventType
|
||||
})
|
||||
.ToListAsync();
|
||||
|
||||
var set = new HashSet<string>();
|
||||
foreach (var k in keys)
|
||||
{
|
||||
var userId = k.FKUserId ?? k.FKCustomerId ?? Guid.Empty;
|
||||
var employeeNo = string.IsNullOrWhiteSpace(k.DeviceEmployeeNo)
|
||||
? k.DeviceUserId.ToString()
|
||||
: k.DeviceEmployeeNo;
|
||||
set.Add(BuildKey(
|
||||
k.FKDeviceId ?? Guid.Empty,
|
||||
userId,
|
||||
employeeNo,
|
||||
k.EventTime,
|
||||
k.EventType));
|
||||
}
|
||||
|
||||
return set;
|
||||
}
|
||||
|
||||
public async Task<HashSet<string>> GetSourceEventKeysAsync(
|
||||
IReadOnlyCollection<string> candidateKeys,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
if (candidateKeys.Count == 0)
|
||||
return new HashSet<string>();
|
||||
|
||||
var keys = await _dbContext.Attendances
|
||||
.AsNoTracking()
|
||||
.Where(attendance =>
|
||||
attendance.SourceEventKey != null &&
|
||||
candidateKeys.Contains(attendance.SourceEventKey))
|
||||
.Select(attendance => attendance.SourceEventKey!)
|
||||
.ToListAsync(ct);
|
||||
|
||||
return keys.ToHashSet();
|
||||
}
|
||||
|
||||
public async Task AddAttendancesAsync(IEnumerable<Attendance> attendances)
|
||||
{
|
||||
await _dbContext.Attendances.AddRangeAsync(attendances);
|
||||
}
|
||||
|
||||
public async Task<List<Attendance>> GetLatestPerPersonAsync(Guid clientId)
|
||||
{
|
||||
return await LatestPerPersonQuery(clientId)
|
||||
.AsNoTracking()
|
||||
.Include(a => a.TenantCustomerLink)
|
||||
.Include(a => a.AccessProfile)
|
||||
.OrderByDescending(a => a.EventTime)
|
||||
.ThenByDescending(a => a.CreatedAt)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<List<AttendanceCountDTO>> GetAttendanceCountsAsync(Guid clientId, DateTime fromDate, DateTime toDate)
|
||||
{
|
||||
var records = await _dbContext.Attendances
|
||||
.AsNoTracking()
|
||||
.Include(a => a.TenantCustomerLink)
|
||||
.Where(a => a.FKClientId == clientId && a.FKCustomerId != null
|
||||
&& a.EventTime >= fromDate && a.EventTime <= toDate)
|
||||
.ToListAsync();
|
||||
|
||||
return records
|
||||
.GroupBy(a => a.FKCustomerId)
|
||||
.Select(g => new AttendanceCountDTO
|
||||
{
|
||||
CustomerId = g.Key!.Value,
|
||||
CustomerName = g.First().TenantCustomerLink?.DisplayName ?? "Unknown",
|
||||
AttendanceCount = g.Count()
|
||||
})
|
||||
.ToList();
|
||||
}
|
||||
|
||||
public async Task<List<TodayAttendanceDTO>> GetTodayAttendanceAsync(Guid clientId)
|
||||
{
|
||||
var todayStart = DateTime.UtcNow.Date;
|
||||
var todayEnd = todayStart.AddDays(1);
|
||||
|
||||
var records = await LatestPerPersonQuery(clientId, todayStart, todayEnd)
|
||||
.AsNoTracking()
|
||||
.Include(a => a.TenantCustomerLink)
|
||||
.Include(a => a.AccessProfile)
|
||||
.OrderByDescending(a => a.EventTime)
|
||||
.ThenByDescending(a => a.CreatedAt)
|
||||
.ToListAsync();
|
||||
|
||||
return records.Select(a => new TodayAttendanceDTO
|
||||
{
|
||||
CustomerName = a.TenantCustomerLink?.DisplayName
|
||||
?? a.AccessProfile?.DisplayName
|
||||
?? "Unknown",
|
||||
EventTime = a.EventTime,
|
||||
EventType = a.EventType
|
||||
}).ToList();
|
||||
}
|
||||
|
||||
private IQueryable<Attendance> LatestPerPersonQuery(
|
||||
Guid clientId,
|
||||
DateTime? from = null,
|
||||
DateTime? to = null)
|
||||
{
|
||||
var scoped = _dbContext.Attendances
|
||||
.Where(a => a.FKClientId == clientId);
|
||||
|
||||
if (from.HasValue)
|
||||
scoped = scoped.Where(a => a.EventTime >= from.Value);
|
||||
if (to.HasValue)
|
||||
scoped = scoped.Where(a => a.EventTime < to.Value);
|
||||
|
||||
// Raw device events remain available for reports and counts. This
|
||||
// read query exposes only the newest state for each linked person.
|
||||
// User/customer matching also bridges legacy rows created before
|
||||
// AccessProfileId was populated.
|
||||
return scoped.Where(record =>
|
||||
!scoped.Any(candidate =>
|
||||
(
|
||||
(record.FKUserId.HasValue &&
|
||||
candidate.FKUserId == record.FKUserId) ||
|
||||
(record.FKCustomerId.HasValue &&
|
||||
candidate.FKCustomerId == record.FKCustomerId) ||
|
||||
(record.FKAccessProfileId.HasValue &&
|
||||
candidate.FKAccessProfileId == record.FKAccessProfileId) ||
|
||||
(!record.FKUserId.HasValue &&
|
||||
!record.FKCustomerId.HasValue &&
|
||||
!record.FKAccessProfileId.HasValue &&
|
||||
!candidate.FKUserId.HasValue &&
|
||||
!candidate.FKCustomerId.HasValue &&
|
||||
!candidate.FKAccessProfileId.HasValue &&
|
||||
candidate.FKDeviceId == record.FKDeviceId &&
|
||||
candidate.DeviceEmployeeNo == record.DeviceEmployeeNo)
|
||||
) &&
|
||||
(
|
||||
candidate.EventTime > record.EventTime ||
|
||||
(candidate.EventTime == record.EventTime &&
|
||||
candidate.CreatedAt > record.CreatedAt)
|
||||
)));
|
||||
}
|
||||
|
||||
private static string BuildKey(
|
||||
Guid deviceId,
|
||||
Guid userId,
|
||||
string deviceEmployeeNo,
|
||||
DateTime eventTime,
|
||||
string eventType)
|
||||
{
|
||||
return $"{deviceId}|{userId}|{deviceEmployeeNo}|{eventTime:O}|{eventType}";
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user