Skip to main content

Overview

SushiGo uses a role-based access control (RBAC) system powered by Spatie Laravel Permission. Roles define what employees can do in the system, from operational tasks to administrative functions.
Roles are assigned to the User entity (authentication identity), not directly to the Employee profile. The employee record links to the user via user_id.

Role Architecture

Position Roles vs Other Roles

SushiGo distinguishes between:
  • Position roles: Job titles that define operational responsibilities (e.g., cook, manager)
  • Other roles: Non-position permissions (e.g., inventory-manager)
Employee management focuses on position roles. The system preserves non-position roles when updating employee roles.

Available Position Roles

Operational Roles

Manager

Code: managerBranch manager with operational oversight:
  • Record daily attendance
  • Authorize overtime
  • Manage schedules
  • View reports for their branch

Cook

Code: cookKitchen staff responsible for food preparation:
  • View their own schedule
  • Check-in/check-out (when implemented)
  • View their attendance history

Kitchen Assistant

Code: kitchen-assistantSupport staff in the kitchen:
  • View their own schedule
  • Check-in/check-out (when implemented)
  • View their attendance history

Delivery Driver

Code: delivery-driverResponsible for order deliveries:
  • View their own schedule
  • Check-in/check-out (when implemented)
  • View their attendance history

Acting Manager

Code: acting-managerTemporary manager designation:
  • Same permissions as manager
  • Used for temporary promotions
  • Can be combined with other roles

Administrative Roles

Admin

Code: adminSystem administrator with elevated permissions:
  • Edit historical records
  • Manage catalogs
  • Override system restrictions
  • Access all branches
  • Cannot assign super-admin role

Super Admin

Code: super-adminFull system access:
  • All admin permissions
  • Manage users and permissions
  • Assign any role including super-admin
  • System configuration
  • Privileged: Only assignable by other super-admins

Role Assignment Rules

Creating Employees

When creating an employee:
  • At least one role is required
  • Multiple roles can be assigned simultaneously
  • Non-super-admins cannot assign super-admin role
  • Roles are validated against the actor’s assignable roles list

Updating Employee Roles

When updating an employee’s roles:
1

System determines assignable roles

Based on the acting user’s role:
  • Super-admin: Can manage all roles
  • Others: Can manage all roles except super-admin
2

System filters incoming roles

Only roles the actor can assign are processed. Invalid roles are ignored.
3

System preserves non-manageable roles

Roles outside the actor’s scope are preserved:
  • Non-position roles (e.g., inventory-manager)
  • Privileged roles the actor cannot assign
4

System syncs final role list

Combined list of new roles + preserved roles is synced to the user account.
This protection prevents a non-super-admin from accidentally removing the super-admin role when editing an employee who has it.

Assignable Roles by Actor

Actor RoleCan AssignCannot Assign
Super AdminAll rolesNone
AdminAll except super-adminsuper-admin
ManagerAll except super-adminsuper-admin
Other rolesAll except super-adminsuper-admin

Code Reference

The assignable roles logic is defined in Employee.php:60:
public static function getAssignableRolesFor(?User $user = null): array
{
    // null = unrestricted (seeders, CLI)
    if ($user === null || $user->hasRole(self::ROLE_SUPER_ADMIN)) {
        return self::POSITION_ROLES; // All roles
    }

    // Everyone else: all roles except super-admin
    return array_values(
        array_diff(self::POSITION_ROLES, self::PRIVILEGED_ROLES)
    );
}

Assigning Roles to Employees

During Employee Creation

Roles are provided in the roles array:
curl -X POST "/api/v1/employees" \
  -H "Authorization: Bearer {token}" \
  -H "Content-Type: application/json" \
  -d '{
    "code": "EMP-020",
    "first_name": "Pedro",
    "last_name": "Sánchez",
    "email": "[email protected]",
    "roles": ["cook"],
    "branch_id": 1,
    "start_date": "2026-03-06"
  }'

Updating Employee Roles

To change an employee’s roles, send a PATCH/PUT request:
curl -X PUT "/api/v1/employees/{id}" \
  -H "Authorization: Bearer {token}" \
  -H "Content-Type: application/json" \
  -d '{
    "roles": ["cook", "delivery-driver"]
  }'
You can update roles independently without sending other employee fields. The system performs a partial update.

Role Synchronization Logic

How syncPositionRoles Works

The syncPositionRoles() method in Employee.php:131 implements safe role management:
public function syncPositionRoles(array $roleNames, ?User $actingUser = null): void
{
    if (!$this->user) {
        return; // No user linked, nothing to sync
    }

    // Determine which roles the acting user can manage
    $manageableRoles = self::getAssignableRolesFor($actingUser);

    // Filter incoming roles to valid + manageable only
    $newPositionRoles = array_intersect($roleNames, $manageableRoles);

    // Get current roles from user
    $currentRoles = $this->user->getRoleNames()->toArray();

    // Preserve roles that are NOT manageable by this actor
    // (includes non-position roles + privileged roles)
    $preserved = array_diff($currentRoles, $manageableRoles);

    // Sync: preserved roles + new position roles
    $this->user->syncRoles(
        array_unique(array_merge($preserved, $newPositionRoles))
    );
}

Example Scenarios

Initial state: Employee has role cookAdmin sends: ["manager", "admin"]Result: Employee has roles ["manager", "admin"]
Clean replacement - admin can manage both roles

Reading Employee Roles

Get Position Roles Only

To retrieve only position roles for an employee:
$positionRoles = $employee->getPositionRoles();
// Returns: ['cook', 'manager']
The method filters the user’s roles to include only those in Employee::POSITION_ROLES:
public function getPositionRoles(): array
{
    if (!$this->user) {
        return [];
    }

    return $this->user
        ->getRoleNames()
        ->filter(fn($r) => in_array($r, self::POSITION_ROLES))
        ->values()
        ->toArray();
}

API Response Format

Employee responses include roles in the user object:
{
  "id": "01JCEQZ8A2KXNM3P4R5S6T7U8V",
  "code": "EMP-020",
  "first_name": "Pedro",
  "last_name": "Sánchez",
  "user": {
    "id": "01JCEQZ8A2WXYZ1A2B3C4D5E6F",
    "name": "Pedro Sánchez",
    "email": "[email protected]",
    "roles": ["cook", "manager"]
  }
}

Role Constants Reference

All role constants are defined in Employee.php:20-46:
// Position roles
const ROLE_MANAGER = 'manager';
const ROLE_COOK = 'cook';
const ROLE_KITCHEN_ASSISTANT = 'kitchen-assistant';
const ROLE_DELIVERY_DRIVER = 'delivery-driver';
const ROLE_ACTING_MANAGER = 'acting-manager';
const ROLE_ADMIN = 'admin';
const ROLE_SUPER_ADMIN = 'super-admin';

// All assignable position roles
const POSITION_ROLES = [
    self::ROLE_MANAGER,
    self::ROLE_COOK,
    self::ROLE_KITCHEN_ASSISTANT,
    self::ROLE_DELIVERY_DRIVER,
    self::ROLE_ACTING_MANAGER,
    self::ROLE_ADMIN,
    self::ROLE_SUPER_ADMIN,
];

// Roles requiring super-admin to assign
const PRIVILEGED_ROLES = [
    self::ROLE_SUPER_ADMIN,
];

Common Validation Errors

{
  "message": "At least one position role is required.",
  "errors": {
    "roles": [
      "At least one position role is required."
    ]
  }
}
Cause: Roles array is empty or not providedSolution: Include at least one valid role in the roles array
{
  "message": "The selected roles.0 is invalid.",
  "errors": {
    "roles.0": [
      "The selected roles.0 is invalid."
    ]
  }
}
Cause: Role not in the assignable roles list for the acting userSolution: Use only valid position role codes that you have permission to assign
{
  "message": "The selected roles.0 is invalid.",
  "errors": {
    "roles.0": [
      "The selected roles.0 is invalid."
    ]
  }
}
Cause: Non-super-admin attempted to assign super-admin roleSolution: Only super-admins can assign the super-admin role

Permission Checking

In Code

Use Spatie Permission helpers to check roles:
// Check if user has specific role
if ($user->hasRole('manager')) {
    // Manager-specific logic
}

// Check if user has any of multiple roles
if ($user->hasAnyRole(['manager', 'admin'])) {
    // Logic for managers or admins
}

// Check if user has all specified roles
if ($user->hasAllRoles(['manager', 'admin'])) {
    // Logic requiring both roles
}

In Policies

public function viewAny(User $user): bool
{
    return $user->hasAnyRole(['manager', 'admin', 'super-admin']);
}

public function update(User $user, Employee $employee): bool
{
    return $user->hasRole(['admin', 'super-admin']);
}

In Blade/Frontend

@role('manager')
    <!-- Manager-only content -->
@endrole

@hasanyrole('manager|admin')
    <!-- Manager or admin content -->
@endhasanyrole

Best Practices

Principle of Least Privilege

Assign only the roles necessary for the employee’s job function

Regular Audits

Periodically review role assignments to ensure they remain appropriate

Document Changes

Use the meta field or audit logs to track why roles were changed

Test Permissions

Verify role changes in a test environment before applying to production

Creating Employees

Learn how to create employees with initial roles

Employee Overview

Understand the employee management system

API Reference

Complete API documentation for updating employees

Spatie Permission Docs

Official Spatie Permission documentation

Build docs developers (and LLMs) love