using Fitness_core_BE.Infra.Tenancy; using Fitness_core_BE.Infra.UoW; using Fitness_core_BE.Models; using Fitness_core_BE.Repos; namespace Fitness_core_BE.Services { // The per-client overdue-lock algorithm, extracted verbatim from OverdueLockJob so the // admin "trigger now" endpoint (AdminBackgroundJobsController) can call the exact same code // path instead of duplicating it. Pure extraction -- no change to the locking logic itself. // Mirrors the SubscriptionRenewalRunner/SubscriptionRenewalJob split. public class OverdueLockRunner(ILogger logger) { private readonly ILogger _logger = logger; public async Task RunForClientAsync(Client client, DateTime now, TenantSchemaIterator iterator, CancellationToken ct) { await using var schemaCtx = await iterator.OpenSchemaForClientAsync(client.ClientId, ct); if (schemaCtx == null) { _logger.LogWarning( "OverdueLockJob: client {ClientId} has a lock policy but no provisioned tenant schema; skipping.", client.ClientId); return; } var repository = new OverdueLockRepository(schemaCtx.Tenant, schemaCtx.Central); var accessDeviceService = new AccessDeviceService( new EFUnitOfWork(schemaCtx.Central, schemaCtx.Tenant), new AccessDeviceRepo(schemaCtx.Tenant, schemaCtx.Central), new AttendanceRepository(schemaCtx.Tenant), new DeviceCommandRepo(schemaCtx.Tenant), new PairingCodeRepo(schemaCtx.Central)); var effectiveDays = client.LockAfterDaysOverdue ?? Client.DefaultLockAfterDaysOverdue; var thresholdDate = now.AddDays(-effectiveDays); var overdueCustomerIds = await repository.GetOverdueCustomerIdsAsync( client.ClientId, thresholdDate, ct); foreach (var customerId in overdueCustomerIds) { var customer = await repository.GetCustomerByIdAsync(customerId, ct); if (customer == null) continue; var alreadyLocked = await repository.IsCustomerAccessDisabledAsync( client.ClientId, customer.FKUserId, ct); if (alreadyLocked) continue; await accessDeviceService.SetCustomerAccessAsync( client.ClientId, customerId, enabled: false, reason: "Overdue"); _logger.LogInformation("Auto-locked customer {CustomerId} for client {ClientId} (overdue).", customerId, client.ClientId); } // Auto-unlock members who were locked for being overdue but are no longer // (paid, or their overdue invoice fell out of scope) — never touches manual locks. var lockedOverdueCustomerIds = await repository.GetCustomersLockedForOverdueAsync(client.ClientId, ct); foreach (var customerId in lockedOverdueCustomerIds.Except(overdueCustomerIds)) { await accessDeviceService.SetCustomerAccessAsync( client.ClientId, customerId, enabled: true, reason: null); _logger.LogInformation("Auto-unlocked customer {CustomerId} for client {ClientId} (no longer overdue).", customerId, client.ClientId); } } } }