RbacSeeder.php 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288
  1. <?php
  2. namespace Database\Seeders;
  3. use App\Models\Permission;
  4. use App\Models\Role;
  5. use App\Models\User;
  6. use App\Services\Access\AccessService;
  7. use Illuminate\Database\Seeder;
  8. use Illuminate\Support\Facades\DB;
  9. class RbacSeeder extends Seeder
  10. {
  11. private const COMMON_CATALOG_PRICE_FIELDS = [
  12. 'builders_price',
  13. 'wholesale_price',
  14. 'recommended_price',
  15. 'retail_price',
  16. 'project_price',
  17. 'project_with_installation_price',
  18. 'pik_price',
  19. 'recommended_plus_10_price',
  20. ];
  21. public function run(): void
  22. {
  23. DB::transaction(function () {
  24. $roles = $this->seedRoles();
  25. $permissions = $this->seedPermissions();
  26. $this->backfillUserRoles($roles);
  27. $this->seedRolePermissions($roles, $permissions);
  28. });
  29. app(AccessService::class)->bumpCacheVersion();
  30. }
  31. private function seedRoles(): array
  32. {
  33. $roles = [];
  34. foreach (Role::NAMES as $slug => $name) {
  35. $roles[$slug] = Role::query()->updateOrCreate(
  36. ['slug' => $slug],
  37. [
  38. 'name' => $name,
  39. 'is_system' => true,
  40. 'is_active' => true,
  41. 'sort' => array_search($slug, Role::VALID_ROLES, true) + 10,
  42. ],
  43. );
  44. }
  45. return $roles;
  46. }
  47. private function seedPermissions(): array
  48. {
  49. $permissions = [];
  50. $sort = 10;
  51. foreach (config('access', []) as $module => $definition) {
  52. foreach (($definition['actions'] ?? []) as $action => $name) {
  53. $slug = "{$module}.{$action}";
  54. $permissions[$slug] = Permission::query()->updateOrCreate(
  55. ['slug' => $slug],
  56. [
  57. 'name' => $name,
  58. 'module' => $module,
  59. 'entity' => $definition['entity'] ?? null,
  60. 'field' => null,
  61. 'action' => $action,
  62. 'type' => Permission::TYPE_ACTION,
  63. 'group' => $definition['name'] ?? $module,
  64. 'sort' => $sort++,
  65. 'is_system' => true,
  66. ],
  67. );
  68. }
  69. foreach (($definition['fields'] ?? []) as $field => $name) {
  70. foreach (['view' => 'Просмотр', 'update' => 'Редактирование'] as $action => $actionName) {
  71. $slug = "{$module}.fields.{$field}.{$action}";
  72. $permissions[$slug] = Permission::query()->updateOrCreate(
  73. ['slug' => $slug],
  74. [
  75. 'name' => "{$name}: {$actionName}",
  76. 'module' => $module,
  77. 'entity' => $definition['entity'] ?? null,
  78. 'field' => $field,
  79. 'action' => $action,
  80. 'type' => Permission::TYPE_FIELD,
  81. 'group' => ($definition['name'] ?? $module).' / Поля',
  82. 'sort' => $sort++,
  83. 'is_system' => true,
  84. ],
  85. );
  86. }
  87. }
  88. }
  89. return $permissions;
  90. }
  91. private function backfillUserRoles(array $roles): void
  92. {
  93. foreach ($roles as $slug => $role) {
  94. User::query()
  95. ->where('role', $slug)
  96. ->whereNull('role_id')
  97. ->withTrashed()
  98. ->update(['role_id' => $role->id]);
  99. }
  100. }
  101. private function seedRolePermissions(array $roles, array $permissions): void
  102. {
  103. $all = array_keys($permissions);
  104. $auth = [
  105. 'orders.view',
  106. 'orders.photos.upload',
  107. 'orders.documents.generate',
  108. 'orders.chat.create',
  109. 'reclamations.view',
  110. 'reclamations.photos.upload',
  111. 'reclamations.chat.create',
  112. 'schedule.view',
  113. 'profile.view',
  114. 'profile.update',
  115. 'profile.delete',
  116. 'notifications.view',
  117. 'notifications.mark_read',
  118. 'areas.ajax.view',
  119. 'catalog.search',
  120. 'common-catalog.view',
  121. 'documents.view',
  122. 'stock.view',
  123. 'stock.reserve',
  124. 'stock.issue',
  125. 'stock.cancel',
  126. 'pricing_codes.search',
  127. 'filters.view',
  128. ];
  129. $commonCatalogPublicFields = array_diff(
  130. array_keys(config('access.common-catalog.fields', [])),
  131. self::COMMON_CATALOG_PRICE_FIELDS,
  132. );
  133. $auth = array_merge($auth, array_map(
  134. fn (string $field): string => "common-catalog.fields.{$field}.view",
  135. $commonCatalogPublicFields,
  136. ));
  137. $manager = array_merge($auth, [
  138. 'orders.update',
  139. 'orders.create',
  140. 'orders.export',
  141. 'orders.documents.upload',
  142. 'orders.documents.delete',
  143. 'orders.documents.generate',
  144. 'orders.photos.delete',
  145. 'reclamations.create',
  146. 'reclamations.update',
  147. 'reclamations.export',
  148. 'reclamations.status.update',
  149. 'reclamations.documents.upload',
  150. 'reclamations.documents.delete',
  151. 'reclamations.documents.generate',
  152. 'reclamations.photos.delete',
  153. 'reclamations.act.upload',
  154. 'reclamations.act.delete',
  155. 'reclamations.spare_parts.manage',
  156. 'catalog.view',
  157. 'maf.view',
  158. 'maf.update',
  159. 'maf.passports.upload',
  160. 'contracts.view',
  161. 'contracts.delete',
  162. 'responsibles.view',
  163. 'responsibles.create',
  164. 'responsibles.update',
  165. 'responsibles.delete',
  166. 'reports.view',
  167. 'spare_parts.view',
  168. 'spare_parts.search',
  169. 'spare_part_orders.view',
  170. 'spare_part_orders.create',
  171. 'spare_part_orders.update',
  172. 'spare_part_orders.ship',
  173. 'spare_part_orders.stock.manage',
  174. 'spare_part_reservations.view',
  175. 'spare_part_reservations.manage',
  176. 'spare_part_reservations.issue',
  177. 'spare_part_reservations.cancel',
  178. 'spare_part_reservations.reassign',
  179. 'spare_part_inventory.view',
  180. 'chat_messages.create',
  181. 'chat_messages.notify',
  182. 'orders.scope.manager',
  183. 'reclamations.scope.manager',
  184. 'schedule.scope.manager',
  185. ]);
  186. $manager = array_merge($manager, $this->fieldPermissions('catalog', ['view'], $permissions));
  187. $brigadier = array_merge($auth, [
  188. 'reclamations.act.upload',
  189. 'orders.scope.brigadier',
  190. 'reclamations.scope.brigadier',
  191. 'schedule.scope.brigadier',
  192. ]);
  193. $warehouseHead = array_merge($auth, [
  194. 'reclamations.act.upload',
  195. 'orders.scope.warehouse_head',
  196. 'reclamations.scope.warehouse_head',
  197. 'schedule.scope.manager',
  198. ]);
  199. $assistantHead = array_values(array_diff(
  200. $all,
  201. $this->commonCatalogAdminOnlyPermissions(),
  202. ));
  203. $rolePermissions = [
  204. Role::ADMIN => $all,
  205. Role::ASSISTANT_HEAD => $assistantHead,
  206. Role::MANAGER => $manager,
  207. Role::BRIGADIER => $brigadier,
  208. Role::WAREHOUSE_HEAD => $warehouseHead,
  209. ];
  210. foreach ($rolePermissions as $roleSlug => $permissionSlugs) {
  211. $role = $roles[$roleSlug] ?? null;
  212. if (! $role) {
  213. continue;
  214. }
  215. $sync = [];
  216. foreach (array_unique($permissionSlugs) as $permissionSlug) {
  217. if (isset($permissions[$permissionSlug])) {
  218. $sync[$permissions[$permissionSlug]->id] = ['effect' => 'allow'];
  219. }
  220. }
  221. if ($this->rolePermissionsChanged($role, $sync)) {
  222. $role->permissions()->sync($sync);
  223. }
  224. }
  225. }
  226. private function rolePermissionsChanged(Role $role, array $sync): bool
  227. {
  228. $current = $role->permissions()
  229. ->pluck('role_permissions.effect', 'permissions.id')
  230. ->all();
  231. $expected = array_map(
  232. fn (array $attributes): string => $attributes['effect'],
  233. $sync
  234. );
  235. ksort($current);
  236. ksort($expected);
  237. return $current !== $expected;
  238. }
  239. private function fieldPermissions(string $module, array $actions, array $permissions): array
  240. {
  241. return array_values(array_filter(
  242. array_keys($permissions),
  243. fn (string $slug): bool => str_starts_with($slug, "{$module}.fields.")
  244. && in_array(substr($slug, strrpos($slug, '.') + 1), $actions, true)
  245. ));
  246. }
  247. private function commonCatalogAdminOnlyPermissions(): array
  248. {
  249. $permissions = ['common-catalog.export'];
  250. foreach (self::COMMON_CATALOG_PRICE_FIELDS as $field) {
  251. $permissions[] = "common-catalog.fields.{$field}.view";
  252. $permissions[] = "common-catalog.fields.{$field}.update";
  253. }
  254. return $permissions;
  255. }
  256. }