Source date: 2026-05-14.
This document is the baseline for RBAC migration. The first migration must preserve the current effective access of the five existing roles.
Collected from users.role with soft-deleted users included.
| Role | Total users | Active | Deleted |
|---|---|---|---|
admin |
4 | 3 | 1 |
assistant_head |
2 | 2 | 0 |
brigadier |
9 | 7 | 2 |
manager |
5 | 4 | 1 |
warehouse_head |
2 | 2 | 0 |
Role deletion rule for new RBAC: a role cannot be deleted while any users.role_id points to it, including soft-deleted users.
Current role checks are not a simple equality check.
Role::effectiveRoles() currently expands:
assistant_head -> assistant_head, admin, manager
all other roles -> themselves
Consequences:
assistant_head passes hasRole('admin').assistant_head passes route middleware role:admin.assistant_head passes hasRole('admin,manager').where('role', Role::ADMIN) do not include assistant_head.During RBAC migration assistant_head must receive its own explicit permissions equivalent to current route/helper access. Do not implement role inheritance.
| Module key | Current table id / controller source | Main controller(s) |
|---|---|---|
orders |
orders |
OrderController |
reclamations |
reclamations |
ReclamationController |
schedule |
schedule |
ScheduleController |
catalog |
products |
ProductController |
maf |
product_sku |
ProductSKUController |
maf_orders |
maf_order |
MafOrderController |
contracts |
contracts |
ContractController |
contractors |
controller-level $id |
ContractorController |
responsibles |
responsibles |
ResponsibleController |
reports |
reports |
ReportController |
users |
users |
UserController |
profile |
no table id | UserController |
import |
import |
ImportController |
admin_settings |
no table id | AdminSettingsController |
districts |
controller-level $id |
AdminDistrictController |
areas |
controller-level $id, ajax areas route |
AdminAreaController, AreaController |
clear_data |
no table id | ClearDataController |
year_data |
no table id | YearDataController |
notifications |
notifications |
UserNotificationController |
notification_logs |
notification_logs |
AdminNotificationLogController |
spare_parts |
spare_parts |
SparePartController |
spare_part_orders |
spare_part_orders |
SparePartOrderController |
spare_part_reservations |
ajax/list endpoints | SparePartReservationController |
spare_part_inventory |
spare_part_inventory |
SparePartInventoryController |
pricing_codes |
pricing_codes |
PricingCodeController |
chat_messages |
no table id | ChatMessageController |
filters |
no table id | FilterController |
Legend:
auth means any authenticated user.admin includes assistant_head through effectiveRoles() when checked through hasRole() or role:*.object-level means additional record-level filtering/checking exists and must remain in Policies/scopes.| Module | Current access from routes/code | RBAC module permissions to seed |
|---|---|---|
orders |
index, show, photo upload, photo pack, tech docs, chat create: auth. Edit/store/update/document upload/statement upload/photo/document/statement delete/document generation/reports group: admin,manager. Delete, create, export-one, MAF attach/revert/move, TTN, bulk photo/document/statement delete: admin. Contractor specification: admin,assistant_head. Brigadier and warehouse have object-level filtering in controller. |
orders.view, orders.create, orders.update, orders.delete, orders.export, orders.photos.upload, orders.photos.delete, orders.documents.upload, orders.documents.delete, orders.documents.generate, orders.maf.manage, orders.ttn.create, orders.contractor_specification.create, orders.chat.create, orders.chat.delete |
reclamations |
index, show, photo before/after upload, chat create: auth. Create/update/status/document upload/details/spare parts/delete photos/generate packs: admin,manager. Upload act: admin,manager,brigadier,warehouse_head. Delete act/document/export: admin,manager. Delete reclamation/chat message: admin. Brigadier and warehouse have object-level filtering/checking in controller. |
reclamations.view, reclamations.create, reclamations.update, reclamations.delete, reclamations.export, reclamations.status.update, reclamations.documents.upload, reclamations.documents.delete, reclamations.documents.generate, reclamations.photos.upload, reclamations.photos.delete, reclamations.act.upload, reclamations.act.delete, reclamations.spare_parts.manage, reclamations.chat.create, reclamations.chat.delete |
schedule |
Index: auth. Create from order/reclamation, update, export, delete: admin. Brigadier sees own records through object-level filtering and has controller checks for some actions. |
schedule.view, schedule.create, schedule.update, schedule.delete, schedule.export |
catalog |
Index/show: admin,manager. Create/store/update/delete/export/delete certificate: admin. Upload certificate/thumbnail currently in admin,manager route group, but edit UI shows upload only for admin; seed as admin to match visible UI or audit before migration. Product search endpoint: auth. |
catalog.view, catalog.create, catalog.update, catalog.delete, catalog.export, catalog.certificates.upload, catalog.certificates.delete, catalog.thumbnail.upload, catalog.search |
maf |
Product SKU index/show/update/upload passport: admin,manager. Import/export/delete passport: admin. Blade edit fields mostly admin-only. |
maf.view, maf.update, maf.import, maf.export, maf.passports.upload, maf.passports.delete |
maf_orders |
All routes are inside role:admin, therefore admin and assistant_head currently pass middleware. Blade also uses hasRole('admin,assistant_head'). |
maf_orders.view, maf_orders.create, maf_orders.update, maf_orders.delete, maf_orders.stock.manage |
contracts |
Routes are in role:admin,manager group, but StoreContractRequest currently authorizes only admin. Effective write behavior is stricter than routes. |
contracts.view, contracts.create, contracts.update, contracts.delete |
contractors |
Routes are inside parent admin,manager group and child role:admin,assistant_head. Effective access: admin and assistant_head; manager does not pass child middleware. |
contractors.view, contractors.create, contractors.update, contractors.delete, contractors.prices.update, contractors.prices.import, contractors.prices.export |
responsibles |
Routes and FormRequest: admin,manager. |
responsibles.view, responsibles.create, responsibles.update, responsibles.delete |
reports |
Route: admin,manager. |
reports.view |
users |
Admin route group. Create/update/delete/restore/impersonate: admin through middleware/FormRequest. |
users.view, users.create, users.update, users.delete, users.restore, users.impersonate, users.permissions.manage |
profile |
View/update/delete own profile: auth; object-level own-user rules. |
profile.view, profile.update, profile.delete |
import |
Admin route group. | import.view, import.create |
admin_settings |
Admin route group. | admin.settings.view, admin.settings.update |
districts |
Admin route group with CRUD/import/export/restore. | districts.view, districts.create, districts.update, districts.delete, districts.restore, districts.import, districts.export |
areas |
Admin dictionary route group with CRUD/import/export/restore. Public ajax areas/{district_id?} is auth. |
areas.view, areas.create, areas.update, areas.delete, areas.restore, areas.import, areas.export, areas.ajax.view |
clear_data |
Admin route group. | admin.clear_data.view, admin.clear_data.delete |
year_data |
Admin route group with import/export/download/stats. | admin.year_data.view, admin.year_data.import, admin.year_data.export, admin.year_data.download |
notifications |
User notifications: auth. |
notifications.view, notifications.mark_read |
notification_logs |
Admin route group. | admin.notification_logs.view |
spare_parts |
Index/help/search/show: admin,manager. Create/store/update/delete/export/import/upload image: admin. Some edit fields readonly for non-admin/non-manager. |
spare_parts.view, spare_parts.create, spare_parts.update, spare_parts.delete, spare_parts.import, spare_parts.export, spare_parts.image.upload, spare_parts.search |
spare_part_orders |
Index/create/show/store/update/ship/set stock: admin,manager. Delete/correct: admin. |
spare_part_orders.view, spare_part_orders.create, spare_part_orders.update, spare_part_orders.delete, spare_part_orders.ship, spare_part_orders.stock.manage, spare_part_orders.correct |
spare_part_reservations |
All reservation routes: admin,manager. |
spare_part_reservations.view, spare_part_reservations.manage, spare_part_reservations.issue, spare_part_reservations.cancel, spare_part_reservations.reassign |
spare_part_inventory |
Route: admin,manager. |
spare_part_inventory.view |
pricing_codes |
CRUD routes: admin. get-description and search are currently auth without role restriction. |
pricing_codes.view, pricing_codes.create, pricing_codes.update, pricing_codes.delete, pricing_codes.search |
chat_messages |
Create for orders/reclamations: auth plus object-level checks. Delete: admin. Notifications from chat UI: admin,manager. |
chat_messages.create, chat_messages.delete, chat_messages.notify |
filters |
get-filters: auth; must be filtered by field visibility. |
filters.view |
The seeder must create these role permissions first, then manual RBAC changes can adjust them later.
| Role | Seed rule |
|---|---|
admin |
All action permissions allow. All field view and update permissions allow. Deny is forbidden. |
assistant_head |
Materialize current effectiveRoles() behavior: all permissions that admin and manager receive through role:*/hasRole(), plus direct assistant_head checks. Do not create runtime inheritance. |
manager |
All permissions currently guarded by admin,manager, plus all auth permissions that are available to every authenticated user. Do not include admin-only permissions. |
brigadier |
All auth permissions available to every authenticated user, plus reclamations.act.upload; preserve object-level restrictions for own orders/reclamations/schedule. |
warehouse_head |
All auth permissions available to every authenticated user, plus reclamations.act.upload; preserve current warehouse object-level filtering in orders/reclamations/chat controllers. |
User decisions for implementation:
view and update.assistant_head keeps current effective access on migration; permissions can be manually reduced later.users.role values are valid and match the five system roles.users.role_id can be backfilled directly by roles.slug = users.role.role_id.These are not blockers, but they must not be "fixed" accidentally during the migration:
assistant_head passes role:admin, but direct where('role', Role::ADMIN) queries do not include it.contracts routes allow admin,manager, but StoreContractRequest allows only admin.catalog.upload-certificate and catalog.upload-thumbnail routes are in admin,manager group, while Blade only exposes controls to admin.auth()->check() while routes are stricter. Keep route-level effective behavior during migration.pricing-codes/get-description and pricing-codes/search are role-unrestricted inside auth group even though CRUD is admin-only.