# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Project Overview

**Workshop HUD** — Collision repair management platform built with **Yii2 Advanced Template**, PostgreSQL, and Bootstrap 5. Two separate applications share a common layer:

- **Backend** (`backend/`) — Admin panel for shop staff: manage claims, estimates, customers, vehicles, users, companies/branches, insurance companies. URL: `backend.collision.test` (port 80 vhost).
- **Frontend** (`frontend/`) — Client portal: customers register, submit claims, track status, view/download estimates as PDF. URL: `collision.test` (port 80 vhost).
- **Console** (`console/`) — CLI commands and migrations only.
- **Common** (`common/`) — Shared models, behaviors, mail templates, i18n messages.

## Essential Commands

```bash
# Run all pending migrations
php yii migrate

# Rollback one migration
php yii migrate/down 1

# Generate Gii code (dev only)
php yii gii/...

# Run PHP built-in server (not normally needed — Apache serves via vhosts)
# php yii serve --port=8080
```

**PHP binary**: The web server runs **PHP 8.1** via phpbrew (`/root/.phpbrew/php/php-8.1.34`). Use `php` (which maps to this version) for CLI tasks. Do not use `php7.4`.

**No test suite is configured** — `codeception.yml` exists but tests have not been written. Do not attempt `vendor/bin/codecept run`.

**Dev email**: `useFileTransport = true` → emails are written to `runtime/mail/` as `.eml` files instead of being sent.

## Database

- **Engine**: PostgreSQL 14+, database `shop_estimator_v1`
- **Credentials** (dev): `postgres` / `postgres`
- **Connection config**: `environments/dev/common/config/main-local.php`

Table naming: **plural** (`claims`, `estimates`, `customers`, `vehicles`, `claim_parts`, `claim_photos`, `estimate_items`, `estimate_photos`, `insurance_companies`). Always verify via `tableName()` in the model before writing raw SQL or `innerJoin` calls.

**Initial master user** (created by seeder migration):
- username: `master`, password: `master123`, email: `master@collision.com`

## Multi-Tenancy — Critical Pattern

`common/behaviors/MultiTenantBehavior.php` is the core isolation mechanism. **Every business model** (Customer, Vehicle, Claim, ClaimPart, ClaimPhoto, Estimate, EstimateItem, EstimatePhoto, InsuranceCompany) overrides `find()` like this:

```php
public static function find() {
    return MultiTenantBehavior::applyFilter(parent::find(), 'branch_id');
}
```

`applyFilter` adds `WHERE branch_id = $user->selected_branch_id` for non-master users. **This breaks frontend queries for `client` users**, who have `selected_branch_id = NULL`.

### The fix — always use in frontend controllers:

```php
// WRONG — applies WHERE branch_id = NULL, misses admin-created records
Estimate::find()->where([...])

// CORRECT — bypass the scope entirely
(new \yii\db\ActiveQuery(Estimate::class))->where([...])->one()
```

For joins, use `innerJoin('table_name', ...)` with the actual table name instead of `joinWith('relation')`, since `joinWith` internally calls the model's `find()` and inherits the scope.

When saving from frontend, detach the behavior to avoid blank `branch_id` validation issues:
```php
$model->detachBehavior('multiTenant');
$model->save(false);
```

## RBAC Roles

| Role | Description |
|------|-------------|
| `master` | Full access, bypasses all tenant filters |
| `company_admin` | Manages all branches within assigned companies |
| `branch_admin` | Limited to assigned branches |
| `client` | End customer; frontend only; created on signup |

Backend uses `mdm\admin\components\AccessControl` globally (configured in `backend/config/main.php`). Frontend uses Yii2's `yii\filters\AccessControl` per controller.

`client` users have no `selected_branch_id` or `selected_company_id`. They must never access the backend.

## Frontend Client Portal — Key Behaviors

- **Signup** (`frontend/models/SignupForm.php`): auto-activates user (no email verification), auto-creates `Customer` record with `user_id`, assigns `client` RBAC role.
- **Profile** (`frontend/controllers/ProfileController.php`): updates `Customer` + `User` records; use `ActiveQuery` bypass for `Customer::findOne`.
- **Claims** show in frontend only when `Customer.user_id = current user`. The `Claim::find()` scope must be bypassed with `new ActiveQuery(Claim::class)`.
- **Estimate visibility**: shown on frontend `/claim/view` only when `claim.status` is `sent`, `approved`, or `completed` (not based on estimate status).
- **PDF download** (`/estimate/pdf`): uses mPDF 8.x. Use `Destination::STRING_RETURN` + `sendContentAsFile()` — never `Destination::DOWNLOAD` which corrupts output due to Yii2 response buffering.

## Estimate Items & Totals

`backend/controllers/EstimateController::saveItems()` **deletes all existing items** and recreates from POST arrays:
- `items[description][]`, `items[item_type][]`, `items[unit_cost][]`, `items[quantity][]`

JS in `backend/views/estimate/_form.php` calculates `total_parts`, `total_labor`, `grand_total` in real time and stores them in hidden inputs on the form.

## CSS / Theme

- **Backend**: `backend/web/css/industrial-dark-v3.css` — CSS variables: `--bg-dark`, `--text-main`, `--text-muted`, `--accent-orange`, `--border-color`, etc.
- **Frontend**: `frontend/web/css/app.css` — CSS variables: `--bg-primary`, `--text-primary`, `--text-muted`, `--accent`, `--border`, etc.
- Both use `--text-muted: #a0aab8` (readable gray on dark backgrounds).
- Bootstrap 5 JS requires `yii\bootstrap5\BootstrapPluginAsset` in `$depends` — without it, `data-bs-toggle="dropdown"` does nothing.

## i18n

Translation files: `common/messages/en/app.php` and `common/messages/es/app.php`. All UI strings must use `Yii::t('app', '...')`.

Language switching: `?lang=en` or `?lang=es` query param, stored in session, applied via `LanguageBootstrap` component configured in `frontend/config/main.php` and `backend/config/main.php`.

## File Uploads

- **Claim photos**: saved to `backend/web/images/claims/{claim_id}/`. When displaying in the frontend, construct the URL as `Yii::$app->request->hostInfo . '/backend/web/' . $photo->file_path`.
- **Estimate photos**: saved to `backend/web/uploads/estimates/`. Same URL construction from frontend.
- **Validation**: use `$model->validate(['file'])` (attribute-only) to skip `file_path` required check before the file is saved to disk.

## TimestampBehavior Gotcha

Tables that have only `created_at` (no `updated_at`) must configure the behavior explicitly to avoid "Setting unknown property" errors:

```php
[
    'class' => TimestampBehavior::class,
    'createdAtAttribute' => 'created_at',
    'updatedAtAttribute' => false,
    'value' => new \yii\db\Expression('NOW()'),
],
```

Models affected: `EstimateItem`, `Customer`, `Vehicle`, `InsuranceCompany`.

## Environment Init

After cloning or switching environments:
```bash
php init          # select 0 for Development
composer install
php yii migrate
```
