-
Notifications
You must be signed in to change notification settings - Fork 0
Closed
Description
🎯 Objective
Create idempotent seeder for predefined roles and permissions (RBAC Phase 4 - #108).
📋 Scope
Predefined Roles
- Admin - Full access to all resources
- Manager - Branch-scoped management (employees, shifts, work instructions)
- Guard - Own data + shift assignments
- Client - Read-only access to location data
- Works Council (Betriebsrat) - BR-specific permissions
Permission Groups
Employees:
employees.read,employees.create,employees.update,employees.deleteemployees.read_salary,employees.read_all_branches,employees.export
Shifts:
shifts.read,shifts.create,shifts.update,shifts.deleteshifts.publish,shifts.approve_as_br
Work Instructions:
work_instructions.read,work_instructions.create,work_instructions.update,work_instructions.deletework_instructions.publish,work_instructions.acknowledge,work_instructions.view_acknowledgments
Roles:
roles.read,roles.create,roles.update,roles.deleteroles.assign_temporary,roles.extend_expiration
Permissions:
permissions.read,permissions.create,permissions.update,permissions.deletepermissions.assign_direct,permissions.revoke_direct
Works Council:
works_council.access_employee_files,works_council.approve_shift_plans
Reports:
reports.generate,reports.view,reports.export
🏗️ Implementation Checklist
Seeder
- Create
RolesAndPermissionsSeeder.php - Idempotent design - uses
firstOrCreate() - Create all permissions first
- Create predefined roles
- Assign permissions to roles
- Add to
DatabaseSeeder.phpcall chain
Idempotency Rules
- Uses
firstOrCreate()- safe to run multiple times - Skips if role/permission already exists
- Updates permissions only if role has none
- Does not overwrite existing data
Role Permission Matrix
- Admin: All permissions (
*) - Manager:
employees.*,shifts.*,work_instructions.*- Excludes:
roles.*,permissions.*,employees.read_all_branches
- Guard:
employees.read(own only),shifts.read,work_instructions.read
- Client:
shifts.read(location-scoped)
- Works Council:
employees.read,shifts.read,shifts.approve_as_brworks_council.*
Documentation
- Add PHPDoc explaining idempotency
- Comment on each role's purpose
- Document permission naming convention
Quality Gates
- PHPStan Level Max: 0 errors
- Laravel Pint: Clean
- Seeder runs successfully
- REUSE compliance
✅ Acceptance Criteria
- Seeder creates 5 predefined roles with correct permissions
- Seeder is idempotent (can run multiple times safely)
- Deleted predefined roles are recreated on next seeder run
- All permissions use
guard_name='sanctum' - No errors when running seeder repeatedly
- PHPStan + Pint clean
📊 Seeder Code Example
<?php
namespace Database\Seeders;
use Illuminate\Database\Seeder;
use Spatie\Permission\Models\Permission;
use Spatie\Permission\Models\Role;
class RolesAndPermissionsSeeder extends Seeder
{
/**
* Run the seeder.
*
* This seeder is IDEMPOTENT - safe to run multiple times.
* Uses firstOrCreate() to skip existing roles/permissions.
* If role deleted, next run recreates it.
*/
public function run(): void
{
// Create permissions first
$permissions = [
// Employees
'employees.read', 'employees.create', 'employees.update', 'employees.delete',
'employees.read_salary', 'employees.read_all_branches', 'employees.export',
// Shifts
'shifts.read', 'shifts.create', 'shifts.update', 'shifts.delete',
'shifts.publish', 'shifts.approve_as_br',
// ... (full list)
];
foreach ($permissions as $permissionName) {
Permission::firstOrCreate(
['name' => $permissionName, 'guard_name' => 'sanctum']
);
}
// Create Admin role
$admin = Role::firstOrCreate(
['name' => 'Admin', 'guard_name' => 'sanctum'],
['description' => 'Full system access']
);
// Sync permissions only if role has none
if ($admin->permissions()->count() === 0) {
$admin->syncPermissions(Permission::all());
}
// Repeat for Manager, Guard, Client, Works Council...
}
}🔗 Related Issues
Part of: #108 - RBAC Phase 4
Depends on:
- PR [DRAFT] feat: RBAC Phase 4 - Role Management CRUD API #131 (Role Management API merged)
- Permission Management API (previous sub-issue)
⏱️ Effort Estimate
Time: 2-3 hours
Breakdown:
- Define permission list: 0.5h
- Seeder implementation: 1h
- Testing idempotency: 0.5h
- Documentation: 0.5h
Complexity: Low (straightforward data seeding)
🎯 Implementation Notes
Why Idempotent?
Benefits:
- ✅ Safe to run in production
- ✅ Safe to run multiple times (CI/CD)
- ✅ Automatically recreates deleted roles
- ✅ No duplicate entries
Protection:
- Roles protected by assignment status (cannot delete if assigned)
- Seeder ensures predefined roles always available (recreates if deleted)
- No runtime restrictions on modification (rename/change permissions freely)
Permission Naming Convention
Format: resource.action
Resources:
employees,shifts,work_instructions,roles,permissionsworks_council,reports
Common Actions:
read,create,update,delete,export
Special Actions:
employees.read_salary- View salary dataemployees.read_all_branches- Cross-branch accessshifts.approve_as_br- Works council approvalroles.assign_temporary- Assign temporal roles
Testing Idempotency
# Run seeder 3 times
ddev exec php artisan db:seed --class=RolesAndPermissionsSeeder
ddev exec php artisan db:seed --class=RolesAndPermissionsSeeder
ddev exec php artisan db:seed --class=RolesAndPermissionsSeeder
# Verify: No duplicates, no errors
ddev exec php artisan tinker
>>> Role::count() // Should be 5
>>> Permission::count() // Should be X (total permissions)📝 Review Checklist
Before creating PR:
- Seeder uses
firstOrCreate()everywhere - Guard name explicit (
sanctum) for all permissions - Permission naming follows
resource.actionconvention - All 5 predefined roles have correct permissions
- Tested running seeder 3+ times (no duplicates)
- PHPDoc explains idempotency
Created: 2025-11-09
Category: Database / RBAC
Size: ~200-300 LOC (seeder + documentation)
Risk: Low (data seeding, no business logic)
Metadata
Metadata
Assignees
Labels
No labels
Type
Projects
Status
✅ Done