Додавання нової сутності до прав¶
Коротка інструкція для розробника. Повний чек-ліст — у відповідних 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'])
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/.
- Створи
ReportDefinitionза патерном зnew-reportskill. - View вже обгорнута через
core.views.reports.ReportRunViewізentity_code = 'reports'. Нічого не треба додавати в каталог —'reports'псевдо-entity вже у потрібних ролях (accountant, sales_manager, auditor, transport_dispatcher). - Якщо звіт повинен бачити нова роль (наприклад
forwarderтеж потребує) → додай*_readonly(*_PSEUDO_REPORTS)у roleforwarder. Перевір — у нього вже є.
Сценарій 4: новий PrintContract¶
- Зареєструй у
core.print.PrintRegistryза патерномnew-print-templateskill. - 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.
Готово. Юзери з відповідними ролями побачать новий пункт меню одразу після логіну.