RbacSeeder.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329
  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. 'schedule-orders.view',
  122. 'schedule-orders.chat.create',
  123. 'documents.view',
  124. 'stock.view',
  125. 'stock.reserve',
  126. 'stock.issue',
  127. 'stock.cancel',
  128. 'pricing_codes.search',
  129. 'filters.view',
  130. ];
  131. $commonCatalogPublicFields = array_diff(
  132. array_keys(config('access.common-catalog.fields', [])),
  133. self::COMMON_CATALOG_PRICE_FIELDS,
  134. );
  135. $auth = array_merge($auth, array_map(
  136. fn (string $field): string => "common-catalog.fields.{$field}.view",
  137. $commonCatalogPublicFields,
  138. ));
  139. $scheduleOrderPublicFields = array_diff(
  140. array_keys(config('access.schedule-orders.fields', [])),
  141. ['application_shipment_date'],
  142. );
  143. $auth = array_merge($auth, array_map(
  144. fn (string $field): string => "schedule-orders.fields.{$field}.view",
  145. $scheduleOrderPublicFields,
  146. ));
  147. $manager = array_merge($auth, [
  148. 'orders.update',
  149. 'orders.create',
  150. 'orders.export',
  151. 'orders.documents.upload',
  152. 'orders.documents.delete',
  153. 'orders.documents.generate',
  154. 'orders.photos.delete',
  155. 'reclamations.create',
  156. 'reclamations.update',
  157. 'reclamations.export',
  158. 'reclamations.status.update',
  159. 'reclamations.documents.upload',
  160. 'reclamations.documents.delete',
  161. 'reclamations.documents.generate',
  162. 'reclamations.photos.delete',
  163. 'reclamations.act.upload',
  164. 'reclamations.act.delete',
  165. 'reclamations.spare_parts.manage',
  166. 'catalog.view',
  167. 'maf.view',
  168. 'maf.update',
  169. 'maf.passports.upload',
  170. 'contracts.view',
  171. 'contracts.delete',
  172. 'responsibles.view',
  173. 'responsibles.create',
  174. 'responsibles.update',
  175. 'responsibles.delete',
  176. 'reports.view',
  177. 'spare_parts.view',
  178. 'spare_parts.search',
  179. 'spare_part_orders.view',
  180. 'spare_part_orders.create',
  181. 'spare_part_orders.update',
  182. 'spare_part_orders.ship',
  183. 'spare_part_orders.stock.manage',
  184. 'spare_part_reservations.view',
  185. 'spare_part_reservations.manage',
  186. 'spare_part_reservations.issue',
  187. 'spare_part_reservations.cancel',
  188. 'spare_part_reservations.reassign',
  189. 'spare_part_inventory.view',
  190. 'chat_messages.create',
  191. 'chat_messages.notify',
  192. 'orders.scope.manager',
  193. 'reclamations.scope.manager',
  194. 'schedule.scope.manager',
  195. ]);
  196. $manager = array_merge($manager, $this->fieldPermissions('catalog', ['view'], $permissions));
  197. $brigadier = array_merge($auth, [
  198. 'reclamations.act.upload',
  199. 'orders.scope.brigadier',
  200. 'reclamations.scope.brigadier',
  201. 'schedule.scope.brigadier',
  202. ]);
  203. $warehouseHead = array_merge($auth, [
  204. 'reclamations.act.upload',
  205. 'orders.scope.warehouse_head',
  206. 'reclamations.scope.warehouse_head',
  207. 'schedule.scope.manager',
  208. ]);
  209. $scheduleOrderPermissions = array_values(array_filter(
  210. $all,
  211. static fn (string $slug): bool => str_starts_with($slug, 'schedule-orders.'),
  212. ));
  213. $scheduleOrderAssistantPermissions = array_merge(
  214. [
  215. 'schedule-orders.view',
  216. 'schedule-orders.update',
  217. 'schedule-orders.deliveries.manage',
  218. 'schedule-orders.installations.manage',
  219. 'schedule-orders.documents.manage',
  220. 'schedule-orders.photos.manage',
  221. 'schedule-orders.passports.manage',
  222. 'schedule-orders.chat.create',
  223. ],
  224. array_map(
  225. fn (string $field): string => "schedule-orders.fields.{$field}.view",
  226. $scheduleOrderPublicFields,
  227. ),
  228. [
  229. 'schedule-orders.fields.object_address.update',
  230. 'schedule-orders.fields.note.update',
  231. 'schedule-orders.fields.factory_number.update',
  232. 'schedule-orders.fields.manufacture_date.update',
  233. ],
  234. );
  235. $assistantHead = array_values(array_unique(array_merge(
  236. array_diff(
  237. $all,
  238. $this->commonCatalogAdminOnlyPermissions(),
  239. $scheduleOrderPermissions,
  240. ),
  241. $scheduleOrderAssistantPermissions,
  242. )));
  243. $rolePermissions = [
  244. Role::ADMIN => $all,
  245. Role::ASSISTANT_HEAD => $assistantHead,
  246. Role::MANAGER => $manager,
  247. Role::BRIGADIER => $brigadier,
  248. Role::WAREHOUSE_HEAD => $warehouseHead,
  249. Role::DRIVER => $auth,
  250. ];
  251. foreach ($rolePermissions as $roleSlug => $permissionSlugs) {
  252. $role = $roles[$roleSlug] ?? null;
  253. if (! $role) {
  254. continue;
  255. }
  256. $sync = [];
  257. foreach (array_unique($permissionSlugs) as $permissionSlug) {
  258. if (isset($permissions[$permissionSlug])) {
  259. $sync[$permissions[$permissionSlug]->id] = ['effect' => 'allow'];
  260. }
  261. }
  262. if ($this->rolePermissionsChanged($role, $sync)) {
  263. $role->permissions()->sync($sync);
  264. }
  265. }
  266. }
  267. private function rolePermissionsChanged(Role $role, array $sync): bool
  268. {
  269. $current = $role->permissions()
  270. ->pluck('role_permissions.effect', 'permissions.id')
  271. ->all();
  272. $expected = array_map(
  273. fn (array $attributes): string => $attributes['effect'],
  274. $sync
  275. );
  276. ksort($current);
  277. ksort($expected);
  278. return $current !== $expected;
  279. }
  280. private function fieldPermissions(string $module, array $actions, array $permissions): array
  281. {
  282. return array_values(array_filter(
  283. array_keys($permissions),
  284. fn (string $slug): bool => str_starts_with($slug, "{$module}.fields.")
  285. && in_array(substr($slug, strrpos($slug, '.') + 1), $actions, true)
  286. ));
  287. }
  288. private function commonCatalogAdminOnlyPermissions(): array
  289. {
  290. $permissions = ['common-catalog.export'];
  291. foreach (self::COMMON_CATALOG_PRICE_FIELDS as $field) {
  292. $permissions[] = "common-catalog.fields.{$field}.view";
  293. $permissions[] = "common-catalog.fields.{$field}.update";
  294. }
  295. return $permissions;
  296. }
  297. }