# Company-scoped data (tenant isolation)

This document summarizes how `company_id` is enforced to prevent data leakage between tenants.

## Models with global CompanyScope (BelongsToCompany trait)

These models automatically filter by the current user's company when a non–super-admin is logged in (via `CompanyResolver`). No manual `where('company_id', ...)` is needed in normal queries.

- **Department**, **Designation**, **Location**, **Shift**, **LeaveType**
- **Attendance**, **WorkDuration**, **Leave**
- **BiometricDevice**, **AttendancePunch**
- **Payroll**, **PayrollComponent**
- **Role**

Use `Model::withoutGlobalScope(CompanyScope::class)->...` only when intentionally loading across companies (e.g. super-admin or system tasks).

## User model (no global scope)

`User` does **not** use `BelongsToCompany`. Every query that lists or resolves users in a tenant context must explicitly scope by `company_id` when the current user has one.

### Web

- **EmployeeController::index** – `User::...->where('company_id', auth()->user()->company_id)` (when present).
- **EmployeeController::create** – managers dropdown: `User::...->where('company_id', auth()->user()->company_id)` (when present).
- **EmployeeController::show** – employee resolved with `User::...->where('company_id', auth()->user()->company_id)->findOrFail($id)` (when present), so users cannot view other companies’ employees by id.

### API (tenant middleware)

- **Employees API index** – `User::...->where('company_id', $request->user()->company_id)` (when present).
- **Employees API show/update/destroy** – already scoped by `company_id` before `findOrFail`.

### Dashboard

- **DashboardController** – uses `$companyId = auth()->user()->company_id` and passes it to all stats queries (User, Attendance, Leave, Payroll). Super-admin without `company_id` gets empty stats.

## Livewire

- **EmployeeTable** – `User` query includes `where('company_id', auth()->user()->company_id)` when present. Department/Designation are scoped by trait.

## API validation (exists rules)

Rules like `exists:departments,id`, `exists:designations,id`, `exists:locations,id`, `exists:leave_types,id` run through the corresponding Eloquent model, so global scopes apply and only IDs belonging to the current tenant’s company are considered valid (when tenant middleware has set the resolver).

## Super-admin

Users with `is_superadmin = true` and `company_id = null` are not restricted by `CompanyScope`; they use dedicated super-admin routes and APIs for cross-tenant management.
