food-market/deploy/recovery-restore-orphan-owners.sql
nns c0824518ab
Some checks are pending
CI / POS (WPF, Windows) (push) Waiting to run
CI / Backend (.NET 8) (push) Successful in 45s
CI / Web (React + Vite) (push) Successful in 40s
Docker API / Build + push API (push) Successful in 1m7s
Docker Web / Build + push Web (push) Successful in 32s
Docker API / Deploy API on stage (push) Successful in 17s
Docker Web / Deploy Web on stage (push) Successful in 12s
feat(employees): главный администратор — терминология + защита роли/активности
— «Владелец» переименован в «Главный администратор» — терминологически
  у нас не «собственник», а тот кто управляет организацией.
  Бейдж в таблице, тексты модалок, серверные сообщения 403 — везде
  единая формулировка.

— PUT /api/organization/employees/{id}: добавлен гард для главного
  администратора:
  · Смена RoleId на не-«Администратор» → 403 «Нельзя сменить роль…»
  · IsActive=false → 403 «Нельзя деактивировать…»
  Раньше юзер мог поменять себе роль на Кладовщик и получить бейдж
  «Владелец» с ролью кладовщика — несостыковка.

— EmployeesPage: при редактировании главного администратора
  · Селект ролей disabled + amber-плашка-объяснение «роль фиксирована»
  · Чекбокс «Активен» disabled + текст «нельзя деактивировать»
  · save() ловит ошибки и показывает их в общей модалке (раньше 403
    «тихо проваливалось» — модалка зависала).

— recovery-restore-orphan-owners.sql добавлен блок: для всех
  Organizations где главный администратор имеет роль не-«Администратор»
  или IsActive=false → восстанавливает «Администратор» и активирует.
  Идемпотентен. Применён на стейдже (0 пострадавших — текущая БД ОК).

Все изменения главного администратора (роль, ФИО, удаление, передача
управления) по архитектурному решению юзера должны идти через очередь
запросов к Супер-администратору платформы. Эта подсистема — отдельная
большая фича (RequestType / RequestQueue / SuperAdmin approval UI),
её план описан в TG-ответе.
2026-04-27 19:12:33 +05:00

73 lines
2.8 KiB
PL/PgSQL
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

-- Recovery: orphan AppUser cleanup.
--
-- Применяется один раз вручную на стейдже/проде после деплоя
-- AuthorizationController + SuperAdminOrganizationsController фиксов
-- (audit 2026-04-27 #1, #2, #7).
--
-- Что делает:
-- 1. Находит users у которых OrganizationId указывает на отсутствующую
-- или архивированную организацию.
-- 2. Деактивирует таких users (IsActive=false), сбрасывает OrganizationId.
-- 3. Отзывает все OpenIddict refresh/access токены этих users
-- (Status='revoked') чтобы существующие сессии оборвались.
--
-- Идемпотентен: повторный запуск ничего не ломает.
-- Не удаляет данные — только статусы. Юзер при необходимости может
-- быть восстановлен ручным UPDATE users SET "IsActive"=true.
BEGIN;
WITH orphan_users AS (
SELECT u."Id"
FROM users u
LEFT JOIN organizations o ON o."Id" = u."OrganizationId"
WHERE u."IsActive" = true
AND (
u."OrganizationId" IS NULL
OR o."Id" IS NULL
OR o."IsArchived" = true
)
AND NOT EXISTS (
-- Не трогаем SuperAdmin'ов — у них org=null это норма.
SELECT 1
FROM "AspNetUserRoles" ur
JOIN roles r ON r."Id" = ur."RoleId"
WHERE ur."UserId" = u."Id" AND r."NormalizedName" = 'SUPERADMIN'
)
)
UPDATE users
SET "IsActive" = false,
"OrganizationId" = NULL
WHERE "Id" IN (SELECT "Id" FROM orphan_users);
UPDATE "OpenIddictTokens" t
SET "Status" = 'revoked'
WHERE t."Status" = 'valid'
AND t."Subject" IN (
SELECT u."Id"::text FROM users u
WHERE u."IsActive" = false
);
-- Owner-Employee должен оставаться в роли «Администратор» и быть IsActive=true.
-- Если кто-то сменил роль владельца на Кладовщика/Менеджера/Кассира или
-- деактивировал — возвращаем «Администратор» и активируем.
WITH admin_role_per_org AS (
SELECT r."OrganizationId", r."Id" AS role_id
FROM employee_roles r
WHERE r."IsSystem" = true AND r."Name" = 'Администратор'
)
UPDATE employees e
SET "RoleId" = ar.role_id,
"IsActive" = true,
"FiredAt" = NULL
FROM organizations o
JOIN admin_role_per_org ar ON ar."OrganizationId" = o."Id"
WHERE e."OrganizationId" = o."Id"
AND e."UserId" = o."AccountOwnerUserId"
AND (
e."RoleId" <> ar.role_id
OR e."IsActive" = false
);
COMMIT;