food-market/src/food-market.domain/Organizations/RolePermissions.cs
nns 2a4ba14d38 feat(auth): эффективные права пользователя в /api/me (единый источник)
- RolePermissions.Effective(role, isSuperAdmin, isAdmin, advancedMode) — флаги с
  учётом роли и простого/расширенного режима; AdvancedOnly — единый список
  advanced-only прав (зеркалит [RequiresAdvancedMode]).
- /api/me теперь отдаёт permissions (имя→bool). Мобильное и веб скрывают одно и то же.
- Веб: хук useCan() читает me.permissions (раньше гейтил только по именам ролей).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 05:39:37 +05:00

126 lines
6.7 KiB
C#
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.

namespace foodmarket.Domain.Organizations;
/// <summary>Набор флагов разрешений роли. Хранится JSON-колонкой
/// (owned by EmployeeRole). Семантика: false = доступ запрещён.</summary>
public class RolePermissions
{
// Каталог
public bool ProductsView { get; set; }
public bool ProductsEdit { get; set; }
public bool ProductsDelete { get; set; }
public bool ProductGroupsManage { get; set; }
public bool PriceTypesManage { get; set; }
public bool UnitsManage { get; set; }
// Закупки
public bool SuppliesView { get; set; }
public bool SuppliesEdit { get; set; }
public bool SuppliesPost { get; set; }
public bool SuppliesDelete { get; set; }
// Продажи (отгрузка контрагенту + POS)
public bool DemandsView { get; set; }
public bool DemandsEdit { get; set; }
public bool DemandsPost { get; set; }
public bool RetailSalesOperate { get; set; }
public bool RetailSalesRefund { get; set; }
// Контрагенты
public bool CounterpartiesView { get; set; }
public bool CounterpartiesEdit { get; set; }
public bool CounterpartiesDelete { get; set; }
// Склад / Остатки
public bool StocksView { get; set; }
public bool InventoryEdit { get; set; }
public bool LossEdit { get; set; }
public bool EnterEdit { get; set; }
public bool TransferEdit { get; set; }
// Отчёты
public bool ReportsView { get; set; }
public bool ReportsFinanceView { get; set; }
public bool ReportsStockView { get; set; }
// Настройки организации
public bool OrgSettingsManage { get; set; }
public bool EmployeesManage { get; set; }
public bool RolesManage { get; set; }
public bool StoresManage { get; set; }
public bool RetailPointsManage { get; set; }
public bool CashRegistersManage { get; set; }
public bool IntegrationsManage { get; set; }
// Sprint 9: лояльность и акции (Admin-only по умолчанию).
public bool LoyaltyManage { get; set; }
public bool PromotionsManage { get; set; }
// Sprint 33 (модуль «Деньги»):
// MoneyView — видеть счета, движения, отчёт «Деньги», долги.
// Кассир — да (видит выручку своей смены); Кладовщик — только через SuppliesView,
// увидит оплаченность приёмки, но не остатки по счетам.
// MoneyEdit — создавать/редактировать платежи, ордера, переводы,
// счета, статьи расходов, корректировки взаиморасчётов.
// Admin-only по умолчанию.
// CashShiftOperate — открыть/закрыть смену, вносить/выплачивать деньги
// в своей смене. Кассир — да.
public bool MoneyView { get; set; }
public bool MoneyEdit { get; set; }
public bool CashShiftOperate { get; set; }
/// <summary>Права, доступные только в расширенном режиме организации
/// (в простом режиме скрыты и на сервере, и в UI). Единый источник правды,
/// зеркалит контроллеры с <c>[RequiresAdvancedMode]</c>: Оптовые отгрузки,
/// Перемещения, Лояльность, Акции, Типы цен.</summary>
public static readonly IReadOnlySet<string> AdvancedOnly = new HashSet<string>(StringComparer.Ordinal)
{
nameof(DemandsView), nameof(DemandsEdit), nameof(DemandsPost),
nameof(TransferEdit), nameof(LoyaltyManage), nameof(PromotionsManage),
nameof(PriceTypesManage),
};
/// <summary>Все bool-флаги прав в виде словаря имя→значение (через рефлексию).</summary>
public Dictionary<string, bool> ToDictionary()
{
var dict = new Dictionary<string, bool>(StringComparer.Ordinal);
foreach (var p in typeof(RolePermissions).GetProperties())
if (p.PropertyType == typeof(bool) && p.CanRead)
dict[p.Name] = (bool)p.GetValue(this)!;
return dict;
}
/// <summary>Эффективные права текущего пользователя для клиента (моб./веб):
/// SuperAdmin — всё без ограничений; Admin — всё, но в простом режиме
/// advanced-only скрыты; обычная роль — её флаги, в простом режиме с
/// обнулением advanced-only. Единый источник, чтобы клиенты скрывали одно и то же.</summary>
public static Dictionary<string, bool> Effective(RolePermissions? role, bool isSuperAdmin, bool isAdmin, bool advancedMode)
{
var basePerms = (isSuperAdmin || isAdmin) ? All() : (role ?? new RolePermissions());
var dict = basePerms.ToDictionary();
// SuperAdmin обходит гейт расширенного режима; остальным в простом режиме
// advanced-only права выключаем.
if (!isSuperAdmin && !advancedMode)
foreach (var name in AdvancedOnly)
dict[name] = false;
return dict;
}
/// <summary>Полный набор всех true — для системной роли «Администратор».</summary>
public static RolePermissions All() => new()
{
ProductsView = true, ProductsEdit = true, ProductsDelete = true,
ProductGroupsManage = true, PriceTypesManage = true, UnitsManage = true,
SuppliesView = true, SuppliesEdit = true, SuppliesPost = true, SuppliesDelete = true,
DemandsView = true, DemandsEdit = true, DemandsPost = true,
RetailSalesOperate = true, RetailSalesRefund = true,
CounterpartiesView = true, CounterpartiesEdit = true, CounterpartiesDelete = true,
StocksView = true, InventoryEdit = true, LossEdit = true, EnterEdit = true, TransferEdit = true,
ReportsView = true, ReportsFinanceView = true, ReportsStockView = true,
OrgSettingsManage = true, EmployeesManage = true, RolesManage = true,
StoresManage = true, RetailPointsManage = true,
CashRegistersManage = true, IntegrationsManage = true,
LoyaltyManage = true, PromotionsManage = true,
MoneyView = true, MoneyEdit = true, CashShiftOperate = true,
};
}