using Fitness_core_BE.Interfaces; using Fitness_core_BE.Models; using Microsoft.EntityFrameworkCore; namespace Fitness_core_BE.Repos { public class OverdueLockRepository : IOverdueLockRepository { private readonly TenantDbContext _context; private readonly CentralDbContext _central; public OverdueLockRepository(TenantDbContext context, CentralDbContext central) { _context = context; _central = central; } // A null policy now means "use the platform default" (see Client.DefaultLockAfterDaysOverdue) // rather than "auto-lock is off" -- only an explicit 0 disables it for a client. public Task> GetClientsWithOverdueLockPolicyAsync( CancellationToken ct = default) { return _central.Client .AsNoTracking() .Where(client => client.LockAfterDaysOverdue == null || client.LockAfterDaysOverdue > 0) .ToListAsync(ct); } public async Task> GetOverdueCustomerIdsAsync( Guid clientId, DateTime thresholdDate, CancellationToken ct = default) { // IsActive filter: an unapproved renewal invoice (daily sweep, awaiting staff // approve/change-plan/reject) must never count toward auto-locking a member -- they // have no way to pay an invoice that isn't even approved for billing yet. Once it's // approved (IsActive=true) it's a normal invoice and ages into "overdue" like any // other; RolledOver invoices are also excluded since their debt now lives on the // invoice that superseded them, not on this one. var overdueSubscriptionIds = await _context.CustomerInvoices .AsNoTracking() .Where(invoice => invoice.FKClientId == clientId && invoice.Status != "Paid" && invoice.Status != "Cancelled" && invoice.Status != "RolledOver" && invoice.IsActive && invoice.CustomerInvoiceDate <= thresholdDate) .Select(invoice => invoice.FKCustomerSubscriptionId) .Distinct() .ToListAsync(ct); if (overdueSubscriptionIds.Count == 0) return new List(); // One invoice covers the whole subscription, so an unpaid one locks every active // member on it -- not just the primary. On a group/couple plan the secondaries // never hold an invoice of their own, so selecting the subscription's owner alone // would leave them with working access while the plan they share is in arrears // (and would then have them auto-unlocked again by the Except() in OverdueLockJob). return await _context.SubscriptionMembers .AsNoTracking() .Where(member => member.IsActive && overdueSubscriptionIds.Contains(member.FKSubscriptionId)) .Select(member => member.FKCustomerId) .Distinct() .ToListAsync(ct); } public Task GetCustomerByIdAsync( Guid customerId, CancellationToken ct = default) { return _central.Customer .AsNoTracking() .FirstOrDefaultAsync(customer => customer.CusId == customerId, ct); } public Task IsCustomerAccessDisabledAsync( Guid clientId, Guid userId, CancellationToken ct = default) { return _context.AccessProfiles.AnyAsync(profile => profile.FKClientId == clientId && profile.FKUserId == userId && profile.DesiredAccessState == "Disabled", ct); } public Task> GetCustomersLockedForOverdueAsync( Guid clientId, CancellationToken ct = default) { return _context.SubscriptionMembers .AsNoTracking() .Where(member => member.IsActive && member.Subscription.IsActive && member.Subscription.CustomerPlan.FKClientId == clientId && _context.AccessProfiles.Any(profile => profile.FKClientId == clientId && profile.FKUserId == member.TenantCustomerLink.CentralUserId && profile.DesiredAccessState == "Disabled" && profile.LockReason == "Overdue")) .Select(member => member.FKCustomerId) .Distinct() .ToListAsync(ct); } } }