fix(roles): keep only Admin + Cashier as system, demote others to custom + migration

После ревью UX оказалось что 6 системных ролей — перебор. Перешли на
схему «два системных + остальные шаблоны»:

- Администратор (IsSystem=true) — RolePermissions.All().
- Кассир (IsSystem=true) — POS-only набор:
  ProductsView + StocksView + RetailSalesOperate. Без RetailSalesRefund
  (админ включит при необходимости). Это маркер для будущего POS-app —
  не имеет доступа к веб-админке.
- Менеджер / Кладовщик / Закупщик / Бухгалтер — IsSystem=false
  (кастомные). Можно удалить если не нужны или подкрутить под себя.

Сидер на чистой БД сразу создаёт роли в правильных статусах. Для
существующих установок миграция Phase4b_RolesSimplify идемпотентно
делает UPDATE: демоутит лишние и приводит permissions Кассира к
правильному набору. Down() — no-op (юзер мог переименовать).

На стенде sql применил вручную + записал в __EFMigrationsHistory.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
nns 2026-04-26 12:41:13 +05:00
parent d2305b7d40
commit 1d9fd7297c
3 changed files with 2044 additions and 6 deletions

View file

@ -187,12 +187,14 @@ private static async Task SeedEmployeeRolesAsync(AppDbContext db, Guid orgId, Ca
IsSystem = true, SortOrder = 0,
Permissions = RolePermissions.All(),
};
// Менеджер/Кладовщик/Закупщик/Бухгалтер — кастомные шаблоны (IsSystem=false),
// юзер может удалить или подкрутить под себя. Системные только Администратор + Кассир.
var manager = new EmployeeRole
{
OrganizationId = orgId,
Name = "Менеджер",
Description = "Управление каталогом, документами и контрагентами",
IsSystem = true, SortOrder = 10,
IsSystem = false, SortOrder = 10,
Permissions = new RolePermissions
{
ProductsView = true, ProductsEdit = true, ProductGroupsManage = true, PriceTypesManage = true,
@ -206,7 +208,7 @@ private static async Task SeedEmployeeRolesAsync(AppDbContext db, Guid orgId, Ca
OrganizationId = orgId,
Name = "Кладовщик",
Description = "Приёмки, инвентаризация, остатки",
IsSystem = true, SortOrder = 20,
IsSystem = false, SortOrder = 20,
Permissions = new RolePermissions
{
ProductsView = true,
@ -218,12 +220,14 @@ private static async Task SeedEmployeeRolesAsync(AppDbContext db, Guid orgId, Ca
{
OrganizationId = orgId,
Name = "Кассир",
Description = "Продажи на кассе. Привязка к конкретной кассе обязательна.",
Description = "Только работа на кассе. Без доступа к веб-админке.",
IsSystem = true, SortOrder = 30,
Permissions = new RolePermissions
{
ProductsView = true,
RetailSalesOperate = true, RetailSalesRefund = true,
StocksView = true,
RetailSalesOperate = true,
// RetailSalesRefund по умолчанию false — админ включит при необходимости
},
};
var buyer = new EmployeeRole
@ -231,7 +235,7 @@ private static async Task SeedEmployeeRolesAsync(AppDbContext db, Guid orgId, Ca
OrganizationId = orgId,
Name = "Закупщик",
Description = "Заказы поставщикам и приёмка товара",
IsSystem = true, SortOrder = 40,
IsSystem = false, SortOrder = 40,
Permissions = new RolePermissions
{
ProductsView = true,
@ -244,7 +248,7 @@ private static async Task SeedEmployeeRolesAsync(AppDbContext db, Guid orgId, Ca
OrganizationId = orgId,
Name = "Бухгалтер",
Description = "Просмотр всех данных и отчётов, без редактирования",
IsSystem = true, SortOrder = 50,
IsSystem = false, SortOrder = 50,
Permissions = new RolePermissions
{
ProductsView = true,

View file

@ -0,0 +1,34 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace foodmarket.Infrastructure.Persistence.Migrations
{
/// <summary>Идемпотентный фикс: системными остаются только «Администратор»
/// и «Кассир», остальные демоутятся в кастомные. Кассирский набор прав
/// перетряхивается на минимально-достаточный POS-набор. На чистой БД
/// сидер сразу создаст роли в правильных статусах; миграция нужна для
/// баз, где Phase4 уже отработал по старой схеме.</summary>
public partial class Phase4b_RolesSimplify : Migration
{
protected override void Up(MigrationBuilder b)
{
b.Sql(@"
UPDATE public.employee_roles
SET ""IsSystem"" = false
WHERE ""Name"" NOT IN ('Администратор', 'Кассир');
");
b.Sql(@"
UPDATE public.employee_roles
SET permissions = '{""productsView"":true,""stocksView"":true,""retailSalesOperate"":true}'::jsonb
WHERE ""Name"" = 'Кассир' AND ""IsSystem"" = true;
");
}
protected override void Down(MigrationBuilder b)
{
// No-op: возвращать роли в IsSystem=true автоматически нельзя,
// юзер мог переименовать или подкрутить permissions.
}
}
}