diff --git a/src/food-market.api/Controllers/Organizations/EmployeesController.cs b/src/food-market.api/Controllers/Organizations/EmployeesController.cs index f309166..1b2f0b3 100644 --- a/src/food-market.api/Controllers/Organizations/EmployeesController.cs +++ b/src/food-market.api/Controllers/Organizations/EmployeesController.cs @@ -229,6 +229,9 @@ public async Task Update(Guid id, [FromBody] EmployeeInput input, var nowActive = input.IsActive; if (e.IsActive && !nowActive) e.FiredAt = DateTime.UtcNow; if (!e.IsActive && nowActive) e.FiredAt = null; + // Меняем активность сотрудника — синхронизируем логин связанного User + // (деактивация гасит сессии, реактивация возвращает доступ). См. DELETE. + if (e.IsActive != nowActive) await SetLinkedUserActiveAsync(e.UserId, nowActive, ct); e.IsActive = nowActive; // Replace assignments wholesale @@ -294,10 +297,35 @@ public async Task Delete(Guid id, CancellationToken ct) e.IsDeleted = true; e.DeletedAt = DateTime.UtcNow; } + // Увольнение и soft-delete обязаны гасить логин связанного User: иначе + // уволенный сотрудник продолжает входить и обновлять токены (ТЗ 0.4). + // На обоих шагах сотрудник перестаёт быть активным персоналом → гасим. + await DeactivateLinkedUserAsync(e.UserId, ct); await _db.SaveChangesAsync(ct); return NoContent(); } + /// Синхронизирует активность связанного AppUser с активностью + /// сотрудника. При деактивации дополнительно отзывает valid refresh/access + /// токены — без этого у уволенного остаётся рабочий refresh до 30 дней + /// (логин и refresh в AuthorizationController гейтятся на User.IsActive). + private async Task SetLinkedUserActiveAsync(Guid? userId, bool active, CancellationToken ct) + { + if (userId is null) return; + var user = await _db.Users.IgnoreQueryFilters().FirstOrDefaultAsync(u => u.Id == userId.Value, ct); + if (user is null || user.IsActive == active) return; + user.IsActive = active; + if (!active) + { + await _db.Database.ExecuteSqlRawAsync( + "UPDATE \"OpenIddictTokens\" SET \"Status\" = 'revoked' WHERE \"Subject\" = {0} AND \"Status\" = 'valid'", + user.Id.ToString()); + } + } + + private Task DeactivateLinkedUserAsync(Guid? userId, CancellationToken ct) + => SetLinkedUserActiveAsync(userId, active: false, ct); + private static Guid? ParseUserId(string? raw) => Guid.TryParse(raw, out var g) ? g : null; private async Task ProjectAsync(Guid id, CancellationToken ct)