Skip to main content

User Roles

Roles in Vetty come from two places:

  1. users.account_type — distinguishes clinic staff from pet owners.
  2. tenant_user.role — refines what a staff member can do in a particular clinic.

System admins live in a different table (system_admin) and have their own guard.

Matrix

RoleWhere definedCan do
System Adminsystem_admin tableEverything in the Filament panel: create tenants, change plans, manage cross-tenant data
Clinic Admintenant_user.role = 'clinic_admin'Manage all data inside their clinic — staff, patients, appointments, billing, inventory, reminders
Veterinariantenant_user.role = 'veterinarian'See appointment queue, update medical notes, sign off prescriptions; read-only on billing. Can also use the vetdoctor mobile app to see today's appointments and scan owner QR codes to adopt pets into the clinic
Clinic Staff / Receptionisttenant_user.role = 'staff'Check-in, scheduling, invoicing, payment capture, inventory adjustments. Same vetdoctor app access as vets
Pet Ownerusers.account_type = 'owner'See their pets, appointments and invoices via /owner/me/* endpoints — in the React SPA and in the vetty mobile app (which adds vaccination logging, QR share, and nearby-provider search)

How roles are enforced today

  • Route middleware (ResolveTenantMembership) confirms the user belongs to the tenant. It does not yet read tenant_user.role — so any member has full clinic access in practice.
  • The Filament panel is walled off by the systemadmin guard; only SystemAdmin rows can log in there.
  • The /api/v1/owner/me/* endpoints filter strictly by owner_user_id, so owners can only read their own rows regardless of what the UI hides.

How roles are expected to be enforced next

Recommended evolution — Laravel Policies:

// app/Policies/AppointmentPolicy.php
public function update(User $user, Appointment $appointment): bool
{
$role = $user->tenants()
->where('tenants.id', $appointment->tenant_id)
->first()?->pivot->role;

return in_array($role, ['clinic_admin', 'veterinarian', 'staff']);
}

Then controllers do $this->authorize('update', $appointment); before mutating.

Role assignment

  • Clinic Admin — created automatically when POST /api/v1/tenants runs.
  • Additional staff — added via Filament (or a future API endpoint) by inserting rows into users and tenant_user with the desired role.
  • Pet Owner — self-registers via POST /auth/owner/register specifying the clinic's tenant_id. A paired Client row is created.

Multiple clinics per user

A staff user can belong to several clinics (each with its own role). users.current_tenant_id picks the default on login; the SPA's TenantSwitcher lets them hop.

Pet owners typically belong to a single clinic, but the data model doesn't forbid multiple. If an owner adopts a pet from a different clinic, a new usersclients link at that tenant is created.

On vetdoctor, the tenant selected at login is pinned for the whole session — multi-tenant switching is a web-only feature today. A vet with rounds across several clinics uses the web app to change tenant.

Where each role can sign in

RoleReact SPAFilament adminvetty appvetdoctor app
System Admin
Clinic Admin
Veterinarian
Clinic Staff
Pet Owner✅ (owner mode)