Roles and Permissions
Role-based access control (RBAC) using spatie/laravel-permission v7 with PHP enums for type-safe permission management.
Overview
Section titled “Overview”The system uses:
- PHP Enums for type-safe roles and permissions
- Spatie Permission package for RBAC infrastructure
- Admin gets every permission via
Role::getPermissions()returningPermission::cases()(no wildcard case) - FilamentPHP trait for automatic resource authorization
Quick Start
Section titled “Quick Start”Assign a Role
Section titled “Assign a Role”use App\Enums\Role;
$user->assignRole(Role::Admin->value);$user->assignRole(Role::SupplierManager->value);Check Permissions
Section titled “Check Permissions”use App\Enums\Permission;
// Using enum (recommended)if ($user->can(Permission::ViewUser->value)) { // User can view users}
// Using stringif ($user->can('view_user')) { // User can view users}Check Role
Section titled “Check Role”use App\Enums\Role;
if ($user->hasRole(Role::Admin->value)) { // User is admin}Full system access. Role::Admin->getPermissions() returns Permission::cases() — every permission in the enum. There is no * wildcard case; the admin simply holds all of them.
| Property | Value |
|---|---|
| Slug | admin |
| Icon | heroicon-o-shield-check |
| Color | danger (red) |
| Scope | Global |
Capabilities: All permissions on all resources.
Supplier Manager
Section titled “Supplier Manager”Limited access to supplier-related resources only.
| Property | Value |
|---|---|
| Slug | supplier-manager |
| Icon | heroicon-o-building-office-2 |
| Color | info (blue) |
| Scope | Supplier-scoped |
Capabilities:
- View and update their own Supplier (no create/delete)
- Full CRUD on Supplier Hotels, Tours, Tour Rates, Activities, Transfers, Services
- Full CRUD on Stop Sales (their own tours)
- Read-only on Supplier Contracts (managed by Volāre admins)
- Read-only on Offers — supplier sees only the offers built on top of their tours (scoped via
supplierTourRate.tour.suppliers); pricing fields are hidden in the offer preview (Offers — Pricing Visibility) - Read-only on Trips to Deliver — operational booking view for trips assigned to their tours (Trips to Deliver)
Restrictions (no access to):
- System config (Users, Activity Logs)
- Travel Products, Bookings (full booking resource — they use the read-only Trips to Deliver view instead)
- Market config (Markets, Market Products, POIs)
- Flights (Flight Search, Flight Bookings)
- Hotels, Airports, Clients, Passengers, Leads, TCAI Profiles
Sales Agent
Section titled “Sales Agent”Sales team member who can view assigned bookings and log interactions (attribution workflow).
| Property | Value |
|---|---|
| Slug | sales-agent |
| Icon | heroicon-o-briefcase |
| Color | success (green) |
| Scope | Global |
Permissions (Role::salesAgentPermissions()): view_booking, view_booking_attribution, log_booking_touchpoint, view_assigned_bookings, view_client, view_offer.
Finance
Section titled “Finance”Finance team member with access to the Revenue Dashboard and the Budget page (including budget upload).
| Property | Value |
|---|---|
| Slug | finance |
| Icon | heroicon-o-banknotes |
| Color | warning |
| Scope | Global |
Permissions: none. Role::financePermissions() returns an empty array — Finance access is gated entirely by page-level canAccess() checks against the role, not by Spatie permissions.
Permission Structure
Section titled “Permission Structure”Naming Convention
Section titled “Naming Convention”{action}_{resource}Actions: view, create, update, delete
Examples:
view_user- View user recordscreate_supplier- Create new supplierupdate_offer- Update offersdelete_travel_product- Delete travel products
Available Permissions (112 total)
Section titled “Available Permissions (112 total)”Standard CRUD permissions (25 resources × 4 = 100) plus 12 non-standard permissions.
| Resource | View | Create | Update | Delete |
|---|---|---|---|---|
| User | view_user |
create_user |
update_user |
delete_user |
| Client | view_client |
create_client |
update_client |
delete_client |
| Passenger | view_passenger |
create_passenger |
update_passenger |
delete_passenger |
| Hotel | view_hotel |
create_hotel |
update_hotel |
delete_hotel |
| Supplier Hotel | view_supplier_hotel |
create_supplier_hotel |
update_supplier_hotel |
delete_supplier_hotel |
| Supplier Tour | view_supplier_tour |
create_supplier_tour |
update_supplier_tour |
delete_supplier_tour |
| Supplier Tour Rate | view_supplier_tour_rate |
create_supplier_tour_rate |
update_supplier_tour_rate |
delete_supplier_tour_rate |
| Supplier Activity | view_supplier_activity |
create_supplier_activity |
update_supplier_activity |
delete_supplier_activity |
| Supplier Transfer | view_supplier_transfer |
create_supplier_transfer |
update_supplier_transfer |
delete_supplier_transfer |
| Supplier Service | view_supplier_service |
create_supplier_service |
update_supplier_service |
delete_supplier_service |
| Airport | view_airport |
create_airport |
update_airport |
delete_airport |
| Flight Search | view_flight_search |
create_flight_search |
update_flight_search |
delete_flight_search |
| Market | view_market |
create_market |
update_market |
delete_market |
| Flight Booking | view_flight_booking |
create_flight_booking |
update_flight_booking |
delete_flight_booking |
| Market Product | view_market_product |
create_market_product |
update_market_product |
delete_market_product |
| Supplier | view_supplier |
create_supplier |
update_supplier |
delete_supplier |
| Offer | view_offer |
create_offer |
update_offer |
delete_offer |
| Travel Product | view_travel_product |
create_travel_product |
update_travel_product |
delete_travel_product |
| Booking | view_booking |
create_booking |
update_booking |
delete_booking |
| POI | view_poi |
create_poi |
update_poi |
delete_poi |
| TCAI Profile | view_tcai_profile |
create_tcai_profile |
update_tcai_profile |
delete_tcai_profile |
| Supplier Contract | view_supplier_contract |
create_supplier_contract |
update_supplier_contract |
delete_supplier_contract |
| Stop Sale | view_stop_sale |
create_stop_sale |
update_stop_sale |
delete_stop_sale |
| Supplier Insurance | view_supplier_insurance |
create_supplier_insurance |
update_supplier_insurance |
delete_supplier_insurance |
| Email Configuration | view_email_configuration |
create_email_configuration |
update_email_configuration |
delete_email_configuration |
Non-Standard Permissions
Section titled “Non-Standard Permissions”| Resource | Permissions | Notes |
|---|---|---|
| Lead | view_lead, delete_lead |
Read-only + delete. No create/update – leads come from the public contact form API. |
| Booking Message | view_booking_message, create_booking_message |
Per-booking VOLĀRE/Supplier conversation. Immutable: read + post only (no update/delete). |
| Insurance Contract | view_insurance_contract |
Read-only view of issued Intermundial policies. |
| Booking Attribution | view_booking_attribution, update_booking_attribution, lock_booking_attribution, view_assigned_bookings, log_booking_touchpoint, view_sales_performance |
6 custom permissions for sales attribution workflow. |
| AI Tools | use_travel_consultant |
Access to AI travel consultant features. |
FilamentPHP Integration
Section titled “FilamentPHP Integration”HasResourcePermissions Trait
Section titled “HasResourcePermissions Trait”Add to Filament resources for automatic permission checks:
use App\Filament\Traits\HasResourcePermissions;
class UserResource extends Resource{ use HasResourcePermissions;
// Resource implementation...}The trait automatically:
- Derives permission prefix from model name (
User->user,FlightBooking->flight_booking) - Hides navigation for unauthorized resources
- Checks
view,create,update,deletepermissions
Adding New Permissions
Section titled “Adding New Permissions”1. Add to Permission Enum
Section titled “1. Add to Permission Enum”enum Permission: string{ // ... existing permissions
// New resource (4 permissions) case ViewInvoice = 'view_invoice'; case CreateInvoice = 'create_invoice'; case UpdateInvoice = 'update_invoice'; case DeleteInvoice = 'delete_invoice';}2. Update Role Permissions (if needed)
Section titled “2. Update Role Permissions (if needed)”// If Supplier Managers should access the new resourcepublic static function supplierManagerPermissions(): array{ return [ // ... existing permissions self::ViewInvoice, self::CreateInvoice, self::UpdateInvoice, self::DeleteInvoice, ];}3. Run Seeder
Section titled “3. Run Seeder”./vendor/bin/sail artisan db:seed --class=RolesAndPermissionsSeederWriting Policies
Section titled “Writing Policies”Use Permission enum for type-safe policy checks:
use App\Enums\Permission;use App\Models\User;
class InvoicePolicy{ public function viewAny(User $user): bool { return $user->can(Permission::ViewInvoice->value); }
public function create(User $user): bool { return $user->can(Permission::CreateInvoice->value); }
public function update(User $user, Invoice $invoice): bool { return $user->can(Permission::UpdateInvoice->value); }
public function delete(User $user, Invoice $invoice): bool { return $user->can(Permission::DeleteInvoice->value); }}Cache Management
Section titled “Cache Management”Spatie Permission caches roles and permissions. Clear cache after changes:
./vendor/bin/sail artisan permission:cache-resetTroubleshooting
Section titled “Troubleshooting”User Can’t Access Resource
Section titled “User Can’t Access Resource”-
Check user has correct role:
$user->getRoleNames(); // ['admin'] or ['supplier-manager'] -
Check role has permission:
$user->getAllPermissions()->pluck('name'); -
Clear permission cache:
Terminal window ./vendor/bin/sail artisan permission:cache-reset
Navigation Not Showing
Section titled “Navigation Not Showing”Ensure resource uses HasResourcePermissions trait:
class DMCResource extends Resource{ use HasResourcePermissions;}| File | Purpose |
|---|---|
app/Enums/Permission.php |
Permission enum with 112 permissions |
app/Enums/Role.php |
Role enum with Admin, SupplierManager, SalesAgent, and Finance |
app/Filament/Traits/HasResourcePermissions.php |
Filament authorization trait |
database/seeders/RolesAndPermissionsSeeder.php |
Seeds roles and permissions |