first commit

This commit is contained in:
Dhananjaya99
2026-09-28 11:44:26 +05:30
parent 721743ae6b
commit bffc9b1686
424 changed files with 46474 additions and 0 deletions
+487
View File
@@ -0,0 +1,487 @@
using Fitness_core_BE.Exceptions;
using Fitness_core_BE.Interfaces;
using Fitness_core_BE.Models;
using Microsoft.EntityFrameworkCore;
namespace Fitness_core_BE.Repos
{
public class AccessDeviceRepo : IAccessDeviceRepo
{
private readonly TenantDbContext _dbContext;
private readonly CentralDbContext _central;
public AccessDeviceRepo(TenantDbContext dbContext, CentralDbContext central)
{
_dbContext = dbContext;
_central = central;
}
public async Task<object> AddDeviceAsync(AccessDevice device)
{
var result = await _dbContext.AddAsync(device);
return result;
}
public async Task<AccessDevice> RemoveDeviceAsync(Guid clientId)
{
AccessDevice? device = await _dbContext.AccessDevices
.Where(ad => ad.FKClientId == clientId)
.FirstOrDefaultAsync();
if (device == null)
throw new NotFoundException($"{clientId} don't have a Device in the System");
device.IsActive = false;
_dbContext.Update(device);
return device;
}
public async Task<AccessDevice> GetDeviceByDId(Guid deviceId)
{
AccessDevice? device = await _dbContext.AccessDevices
.Where(d => d.DeviceId == deviceId && d.IsActive == true)
.FirstOrDefaultAsync();
if (device == null)
throw new NotFoundException($"{deviceId} is not found");
return device;
}
public Task<AccessDevice?> GetDeviceByIdAsync(
Guid deviceId,
CancellationToken ct = default)
{
return _dbContext.AccessDevices
.FirstOrDefaultAsync(device => device.DeviceId == deviceId, ct);
}
public Task<AccessDevice?> GetDeviceByHardwareIdAsync(
Guid clientId,
string hardwareId,
CancellationToken ct = default)
{
return _dbContext.AccessDevices.FirstOrDefaultAsync(device =>
device.FKClientId == clientId &&
device.HardwareId == hardwareId, ct);
}
public Task<AccessDevice?> GetActiveDeviceForClientAsync(
Guid clientId,
Guid deviceId,
CancellationToken ct = default)
{
return _dbContext.AccessDevices.FirstOrDefaultAsync(device =>
device.DeviceId == deviceId &&
device.FKClientId == clientId &&
device.IsActive, ct);
}
public Task<List<AccessDevice>> GetAgentDevicesAsync(
Guid clientId,
Guid agentId,
IReadOnlyCollection<Guid> deviceIds,
CancellationToken ct = default)
{
return _dbContext.AccessDevices
.Where(device =>
device.FKClientId == clientId &&
device.FKAgentId == agentId &&
deviceIds.Contains(device.DeviceId))
.ToListAsync(ct);
}
public async Task<AccessDevice> GetDeviceByCId(Guid clientId)
{
AccessDevice? device = await _dbContext.AccessDevices
.Where(d => d.FKClientId == clientId && d.IsActive == true)
.FirstOrDefaultAsync();
if (device == null)
throw new NotFoundException($"{clientId} don't have a device in the system");
return device;
}
public async Task<CustomerAccess> AddCustomerAccessAsync(CustomerAccess customerAccess)
{
await _dbContext.CustomerAccess.AddAsync(customerAccess);
return customerAccess;
}
public async Task<CustomerAccess> GetByIdAsync(int deviceUserId)
{
CustomerAccess? record = await _dbContext.CustomerAccess
.FindAsync(deviceUserId);
if (record == null)
throw new NotFoundException($"{deviceUserId} cannot find in the system");
return record;
}
// Maps a device's employeeNo (== CustomerAccess.Id) back to its CustomerAccess,
// scoped to the device so the same employeeNo on a different panel can't collide.
// Returns null (rather than throwing) when there is no mapping for this device user.
public async Task<CustomerAccess?> GetByDeviceAndUserIdAsync(Guid deviceId, int deviceUserId)
{
return await _dbContext.CustomerAccess
.FirstOrDefaultAsync(c => c.FKDeviceId == deviceId && c.Id == deviceUserId);
}
public async Task<CustomerAccess> GetByCusGuidAsync(Guid CusId)
{
// No IsActive filter here: this is the lookup WhiteListMember/BlackListMember use
// to find the record whose IsActive flag they're about to flip. Filtering on
// IsActive == true made it impossible to ever find (and re-activate) a customer
// who had already been locked, since locking sets IsActive = false.
CustomerAccess? record = await _dbContext.CustomerAccess
.Where(c => c.FKCustomerId == CusId)
.FirstOrDefaultAsync();
if (record == null)
throw new NotFoundException($"record cannot find in the system");
return record;
}
public async Task<CustomerAccess> UpdateAsync(CustomerAccess customerAccess)
{
_dbContext.CustomerAccess.Update(customerAccess);
return customerAccess;
}
public async Task<List<AccessDevice>> GetDevicesByClientAsync(Guid clientId)
{
return await _dbContext.AccessDevices
.AsNoTracking()
.Where(d => d.FKClientId == clientId && d.IsActive == true)
.ToListAsync();
}
public async Task<bool> DeviceBelongsToClientAsync(Guid deviceId, Guid clientId)
{
return await _dbContext.AccessDevices
.AnyAsync(d => d.DeviceId == deviceId && d.FKClientId == clientId);
}
public Task<Customer?> GetCustomerByIdAsync(
Guid customerId,
CancellationToken ct = default)
{
return _central.Customer
.FirstOrDefaultAsync(customer => customer.CusId == customerId, ct);
}
public async Task<Staff?> GetStaffByIdAsync(
Guid clientId,
Guid staffId,
CancellationToken ct = default)
{
var staff = await _dbContext.Staff
.AsNoTracking()
.FirstOrDefaultAsync(s =>
s.StaffId == staffId &&
s.FKClientId == clientId, ct);
if (staff == null)
return null;
// Staff.User is central-bucket -- attach it in memory so callers reading
// staff.User.* still see the right data.
staff.User = await _central.Users.AsNoTracking().FirstOrDefaultAsync(u => u.UserId == staff.FKUserId, ct);
return staff;
}
// Access on a shared plan is all-or-nothing: the subscription is the unit that was
// paid for, so locking one person on a couple/group plan has to take their co-members
// with it, or the locked member simply walks in on their partner's fingerprint.
public async Task<List<Guid>> GetCoMembersOnSharedSubscriptionsAsync(
Guid clientId,
Guid customerId,
CancellationToken ct = default)
{
var sharedSubscriptionIds = await _dbContext.SubscriptionMembers
.AsNoTracking()
.Where(member =>
member.FKCustomerId == customerId &&
member.IsActive &&
member.Subscription.IsActive &&
member.Subscription.CustomerPlan.FKClientId == clientId &&
member.Subscription.CustomerPlan.PlanScope == "Group")
.Select(member => member.FKSubscriptionId)
.Distinct()
.ToListAsync(ct);
if (sharedSubscriptionIds.Count == 0)
return new List<Guid>();
return await _dbContext.SubscriptionMembers
.AsNoTracking()
.Where(member =>
sharedSubscriptionIds.Contains(member.FKSubscriptionId) &&
member.FKCustomerId != customerId &&
member.IsActive)
.Select(member => member.FKCustomerId)
.Distinct()
.ToListAsync(ct);
}
public Task<bool> IsActiveCustomerMemberAsync(
Guid clientId,
Guid customerId,
CancellationToken ct = default)
{
return _dbContext.SubscriptionMembers.AnyAsync(member =>
member.FKCustomerId == customerId &&
member.IsActive &&
member.Subscription.IsActive &&
member.Subscription.CustomerPlan.FKClientId == clientId, ct);
}
public async Task<Guid?> GetLatestActiveClientIdForCustomerAsync(
Guid customerId,
CancellationToken ct = default)
{
var clientId = await _dbContext.SubscriptionMembers
.Where(member =>
member.FKCustomerId == customerId &&
member.IsActive &&
member.Subscription.IsActive)
.OrderByDescending(member => member.Subscription.OnboardDate)
.Select(member => member.Subscription.CustomerPlan.FKClientId)
.FirstOrDefaultAsync(ct);
return clientId == Guid.Empty ? null : clientId;
}
public Task<AccessProfile?> GetAccessProfileAsync(
Guid clientId,
Guid userId,
CancellationToken ct = default)
{
return _dbContext.AccessProfiles
.Include(profile => profile.DeviceBindings)
.FirstOrDefaultAsync(profile =>
profile.FKClientId == clientId &&
profile.FKUserId == userId, ct);
}
public Task<AccessProfile?> GetAccessStatusProfileAsync(
Guid clientId,
Guid userId,
CancellationToken ct = default)
{
return _dbContext.AccessProfiles
.AsNoTracking()
.Include(profile => profile.DeviceBindings)
.ThenInclude(binding => binding.AccessDevice)
.FirstOrDefaultAsync(profile =>
profile.FKClientId == clientId &&
profile.FKUserId == userId, ct);
}
public Task<List<AccessProfile>> GetAccessProfilesByClientAsync(
Guid clientId,
CancellationToken ct = default)
{
return _dbContext.AccessProfiles
.Include(profile => profile.DeviceBindings)
.Where(profile => profile.FKClientId == clientId)
.ToListAsync(ct);
}
public async Task AddAccessProfileAsync(
AccessProfile profile,
CancellationToken ct = default)
{
await _dbContext.AccessProfiles.AddAsync(profile, ct);
}
public Task<AccessDeviceBinding?> GetBindingAsync(
long accessProfileId,
Guid deviceId,
CancellationToken ct = default)
{
return _dbContext.AccessDeviceBindings.FirstOrDefaultAsync(binding =>
binding.FKAccessProfileId == accessProfileId &&
binding.FKDeviceId == deviceId, ct);
}
public Task<AccessDeviceBinding?> GetBindingByEmployeeNoAsync(
Guid deviceId,
string employeeNo,
CancellationToken ct = default)
{
return _dbContext.AccessDeviceBindings
.Include(binding => binding.AccessProfile)
.FirstOrDefaultAsync(binding =>
binding.FKDeviceId == deviceId &&
binding.DeviceEmployeeNo == employeeNo, ct);
}
public Task<List<AccessDeviceBinding>> GetBindingsForDeviceAsync(
Guid deviceId,
CancellationToken ct = default)
{
return _dbContext.AccessDeviceBindings
.Include(binding => binding.AccessProfile)
.Where(binding => binding.FKDeviceId == deviceId)
.ToListAsync(ct);
}
public async Task AddBindingAsync(
AccessDeviceBinding binding,
CancellationToken ct = default)
{
await _dbContext.AccessDeviceBindings.AddAsync(binding, ct);
}
public Task<bool> IsDeviceEmployeeNoOccupiedAsync(
Guid deviceId,
string employeeNo,
Guid exceptUserId,
CancellationToken ct = default)
{
return _dbContext.AccessDeviceBindings.AnyAsync(binding =>
binding.FKDeviceId == deviceId &&
binding.DeviceEmployeeNo == employeeNo &&
binding.AccessProfile.FKUserId != exceptUserId, ct);
}
public Task<List<UnlinkedDeviceEnrollment>> GetDuplicateUnlinkedEnrollmentsAsync(
Guid deviceId,
string employeeNo,
int deviceUserId,
CancellationToken ct = default)
{
return _dbContext.UnlinkedDeviceEnrollments
.Where(enrollment =>
enrollment.FKDeviceId == deviceId &&
enrollment.LinkedAt == null &&
(enrollment.DeviceEmployeeNo == employeeNo ||
(enrollment.DeviceEmployeeNo == "" &&
enrollment.DeviceUserId == deviceUserId)))
.ToListAsync(ct);
}
public Task<UnlinkedDeviceEnrollment?> GetUnlinkedEnrollmentByEmployeeNoAsync(
Guid deviceId,
string employeeNo,
CancellationToken ct = default)
{
var tracked = _dbContext.UnlinkedDeviceEnrollments.Local
.FirstOrDefault(enrollment =>
enrollment.FKDeviceId == deviceId &&
enrollment.DeviceEmployeeNo == employeeNo &&
enrollment.LinkedAt == null);
return tracked != null
? Task.FromResult<UnlinkedDeviceEnrollment?>(tracked)
: _dbContext.UnlinkedDeviceEnrollments.FirstOrDefaultAsync(enrollment =>
enrollment.FKDeviceId == deviceId &&
enrollment.DeviceEmployeeNo == employeeNo &&
enrollment.LinkedAt == null, ct);
}
public async Task AddUnlinkedEnrollmentAsync(
UnlinkedDeviceEnrollment enrollment,
CancellationToken ct = default)
{
await _dbContext.UnlinkedDeviceEnrollments.AddAsync(enrollment, ct);
}
public async Task<Guid?> GetCustomerIdForUserAtClientAsync(
Guid userId,
Guid clientId,
CancellationToken ct = default)
{
// Central lookup for the customer identity, then a tenant-scoped membership
// check -- these used to be a single cross-table query before the split.
var cusId = await _central.Customer
.Where(customer => customer.FKUserId == userId)
.Select(customer => (Guid?)customer.CusId)
.FirstOrDefaultAsync(ct);
if (cusId == null)
return null;
var belongs = await _dbContext.SubscriptionMembers.AnyAsync(member =>
member.FKCustomerId == cusId.Value &&
member.IsActive &&
member.Subscription.IsActive &&
member.Subscription.CustomerPlan.FKClientId == clientId, ct);
return belongs ? cusId : null;
}
public Task<bool> IsDeviceCommandableAsync(
Guid deviceId,
CancellationToken ct = default)
{
return _dbContext.AccessDevices.AnyAsync(device =>
device.DeviceId == deviceId &&
device.IsActive &&
device.FKAgentId != null, ct);
}
public async Task UpsertUnlinkedEnrollmentAsync(Guid deviceId, int deviceUserId, string? reportedName)
{
// Check already-tracked-but-unsaved entities first: a single attendance-ingest
// batch can carry several events for the same not-yet-linked device user (e.g.
// multiple punches since the last sync), and SaveChanges only runs once after the
// whole batch. A DB-only lookup wouldn't see an earlier Add() in this same batch
// and would insert a duplicate row per repeated punch.
var existing = _dbContext.UnlinkedDeviceEnrollments.Local
.FirstOrDefault(e => e.FKDeviceId == deviceId && e.DeviceUserId == deviceUserId && e.LinkedAt == null)
?? await _dbContext.UnlinkedDeviceEnrollments
.FirstOrDefaultAsync(e => e.FKDeviceId == deviceId && e.DeviceUserId == deviceUserId && e.LinkedAt == null);
if (existing != null)
{
if (!string.IsNullOrWhiteSpace(reportedName))
existing.DeviceReportedName = reportedName;
return;
}
await _dbContext.UnlinkedDeviceEnrollments.AddAsync(new UnlinkedDeviceEnrollment
{
Id = Guid.NewGuid(),
FKDeviceId = deviceId,
DeviceUserId = deviceUserId,
DeviceReportedName = reportedName,
DiscoveredAt = DateTime.UtcNow
});
}
public async Task<List<UnlinkedDeviceEnrollment>> GetUnlinkedEnrollmentsByClientAsync(Guid clientId)
{
var enrollments = await _dbContext.UnlinkedDeviceEnrollments
.AsNoTracking()
.Include(e => e.AccessDevice)
.Where(e => e.LinkedAt == null && e.AccessDevice.FKClientId == clientId)
.OrderByDescending(e => e.DiscoveredAt)
.ToListAsync();
// Collapse any duplicate rows already sitting in the table (from before the
// upsert dedup fix above) so the same physical fingerprint doesn't show up
// multiple times in the link picker; keep the most recently discovered one.
return enrollments
.GroupBy(e => (
e.FKDeviceId,
EmployeeNo: string.IsNullOrWhiteSpace(e.DeviceEmployeeNo)
? e.DeviceUserId.ToString()
: e.DeviceEmployeeNo))
.Select(g => g.First())
.ToList();
}
public async Task<UnlinkedDeviceEnrollment?> GetUnlinkedEnrollmentByIdAsync(Guid id)
{
return await _dbContext.UnlinkedDeviceEnrollments
.Include(e => e.AccessDevice)
.FirstOrDefaultAsync(e => e.Id == id);
}
public async Task<bool> CustomerAccessIdExistsAsync(int id)
{
return await _dbContext.CustomerAccess.AnyAsync(c => c.Id == id);
}
}
}