Перейти до змісту

Додавання нової сутності до прав

Коротка інструкція для розробника. Повний чек-ліст — у відповідних skills: - custom-form SKILL § D — нові master data / документи - new-module SKILL § Checkpoint 12 — нові модулі - new-print-template SKILL § Checkpoint 8 — нові print contracts - new-report SKILL § Checkpoint 4.5 — нові report definitions

Базовий принцип

Closed-by-default. Якщо ти зареєстрував нову модель у EntityRegistry, але не додав її у жодну роль — її побачить лише tenant_admin. Це безпечно (нічого не "тече"), але юзери з іншими ролями отримають 403 → виглядатиме як баг "форма не працює".

Сценарій 1: новий MasterData довідник

Приклад: додаємо WagonType довідник у модуль transport.

Backend: 1. Створи модель WagonType(MasterDataModel), серіалізатор, ViewSet (наслідує TenantFilterMixin). 2. Зареєструй у transport/apps.py:

EntityRegistry.register('wagonTypes', WagonType, WagonTypeSerializer,
                        search_fields=['name', 'code'])
3. HasRolePermission уже спрацює через TenantFilterMixin default + EntityRegistry.code_for_model() reverse lookup. Нічого додавати на ViewSet не треба.

Каталог ролей (role_catalog.py):

Запитай себе: хто мусить керувати цим довідником? Для WagonType: - forwarder — CRUD (експедитор веде типи вагонів) - transport_dispatcher — view (диспетчер бачить, не править) - auditor — view - tenant_admin — autoматично через wildcard

Найшвидший спосіб — додати в існуючу групу. WagonType логічно йде у _SALES_REF чи аналогічну. Якщо такої немає — additions per role:

# у ролі forwarder
'perms': [
    *_crud('railwayRoads'),
    # ...
    *_crud('wagonTypes'),  # ← нова entity
],

# у ролі transport_dispatcher / auditor
'perms': [
    # ...
    ('wagonTypes', VIEW),
],

Застосування: - Розробник: python manage.py seed_demo --only=extras_<tenant> (тенант, на якому тестуєш) - Або через UI: Admin Tools → Roles Matrix → Скинути до каталогу

Сценарій 2: новий TransactionModel документ

Приклад: додаємо WagonRepairOrder у transport.

Same as MasterData, але: 1. Замість _crud() використай _crud_post() для ролей, що створюють/правлять документ — це включає view + create + update + delete + post (бо документ можна провести). 2. Не забудь про сутності-лінії (наприклад wagonRepairOrderLines) — їх теж додавай у ту саму роль із тими самими діями. 3. Якщо документ створює проводки → accountant повинен мати доступ хоча б на view (для ревізії).

'forwarder': {
    'perms': [
        # ...
        *_crud_post('wagonRepairOrders'),
        *_crud('wagonRepairOrderLines'),
    ],
},
'accountant': {
    'perms': [
        # ...
        ('wagonRepairOrders', VIEW),
        ('wagonRepairOrderLines', VIEW),
    ],
},

Сценарій 3: новий звіт

Приклад: WagonUtilizationReport у transport/reports/.

  1. Створи ReportDefinition за патерном з new-report skill.
  2. View вже обгорнута через core.views.reports.ReportRunView із entity_code = 'reports'. Нічого не треба додавати в каталог'reports' псевдо-entity вже у потрібних ролях (accountant, sales_manager, auditor, transport_dispatcher).
  3. Якщо звіт повинен бачити нова роль (наприклад forwarder теж потребує) → додай *_readonly(*_PSEUDO_REPORTS) у roleforwarder. Перевір — у нього вже є.

Сценарій 4: новий PrintContract

  1. Зареєструй у core.print.PrintRegistry за патерном new-print-template skill.
  2. View — стандартна PrintDocumentView із entity_code = 'print'. Нічого додавати в каталог не треба, 'print' уже в усіх робочих ролях.

Сценарій 5: новий APIView без queryset.model

Приклад: WagonAvailabilityView(APIView) — POST з логікою прогнозу, без моделі.

from core.permissions.roles import HasRolePermission

class WagonAvailabilityView(APIView):
    permission_classes = [IsAuthenticated, HasRolePermission]
    entity_code = 'wagons'  # use existing entity OR introduce pseudo-entity

Якщо логіка специфічна і не пасує до жодної моделі — введи псевдо-entity:

# wagonForecast — pseudo-entity, не існує в EntityRegistry
class WagonAvailabilityView(APIView):
    permission_classes = [IsAuthenticated, HasRolePermission]
    entity_code = 'wagonForecast'

Тоді у каталозі додай ('wagonForecast', VIEW) у потрібні ролі. Вибирай назву псевдо-entity descriptive (не transport_extra).

Сценарій 6: PROCESS / APPLICATION у фронт-конфізі

Якщо додаєш PROCESS чи APPLICATION item у frontend/erp/src/config/<module>.tsобов'язково дай requiresEntities:

{
  code: "wagonRotationWizard",
  type: ItemType.PROCESS,
  requiresEntities: ['wagons', 'wagonRepairOrders'],  // OR list — видно якщо хоч одне дозволено
  // ... решта
}

Без requiresEntities PROCESS — closed-by-default → невидимий для всіх крім admin. Це безпечно, але незручно.

Сценарій 7: новий settings-endpoint з секретами

Не використовуй HasRolePermission — він caters лише про відомі catalogues. Settings із auth_token / api_key / тощо мають бути:

from core.permissions.roles import IsTenantAdmin

class MyIntegrationSettingsView(APIView):
    permission_classes = [IsAuthenticated, IsTenantAdmin]

Жоден catalog-grant не дає доступу — тільки tenant_admin. Це свідомий design (як для BAFSyncSettings, EssentialsModuleSettings).

Сценарій 8: нова роль (специфічне робоче місце)

Якщо твій модуль вводить нове робоче місце (наприклад dispatch_assistant для нового модуля логістики) — додай нову системну роль у SYSTEM_ROLES за патерном існуючих:

'dispatch_assistant': {
    'name': 'Dispatch Assistant',
    'name_ua': 'Помічник диспетчера',
    'description': 'Asst-диспетчер: координація рейсів без права post.',
    'perms': [
        *_crud('routes', 'orders'),
        ('waybills', VIEW), ('waybills', CREATE), ('waybills', UPDATE),
        # NB: НЕ post — тільки accountant/transport_dispatcher проводить
        *_readonly('vehicles', 'drivers'),
        *_readonly(*_PSEUDO_PRINT, *_PSEUDO_REPORTS),
    ],
},

Потім — опційно — додай demo-юзера у _roles_<tenant>.py. Якщо роль універсальна (підходить для всіх тенантів) — лиши без демо-юзера, тенант-адмін призначить.

Перевірка

Після правок каталогу запусти smoke-test:

cd backend && python manage.py shell -c "
from core.models import User
from rest_framework_simplejwt.tokens import RefreshToken
from django.test import Client

c = Client()
for username in ['bondarenko', 'melnyk']:  # engineer (denied) vs accountant (allowed)
    u = User.objects.get(username=username)
    h = {'HTTP_AUTHORIZATION': f'Bearer {RefreshToken.for_user(u).access_token}'}
    r = c.get('/api/v1/data/<myEntity>/', **h)
    print(f'{username}: {r.status_code}')
"

Очікуване: - Якщо engineer отримує 403, а melnyk отримує 200 → все правильно. - Якщо обидва 403 → entity не доданa у роль accountant/sales_manager (mlnyk має ці дві). - Якщо обидва 200 → ймовірно дав wildcard зайвий, перевір каталог.

Frontend: оновити i18n + sidebar

Після backend — frontend: 1. Додай item у frontend/erp/src/config/<module>.ts — для MASTERDATA/TRANSACTIONDATA достатньо code що збігається з entity_code, фільтр спрацює automatically. 2. Для PROCESS/APPLICATION — додай requiresEntities. 3. Додай i18n keys в en.json + ua.json.

Готово. Юзери з відповідними ролями побачать новий пункт меню одразу після логіну.