# CLAUDE.md — Repair Management System

Read this file at the start of every session and follow these rules.

## Project

- **Name:** Repair Management System
- **Shop:** Built for one business, **AppleTek**. Not a multi-company system. Branches exist so AppleTek can add more shops later.
- **Live URL:** https://appletek.lk (set as `APP_URL` in the server's `.env`)
- **Server folder:** `/home/mytechno/appletek-app` (the whole Laravel project)
- **Document root:** `/home/mytechno/appletek-app/public` (only `public/` is reachable from the web)
- **What it is:** A repair management system for an electronics repair shop (phones, laptops, tablets and other electronics).
- **Users:** Shop staff use it inside the shop. Customers only see a public tracking page, opened by scanning a QR code.
- **Country:** Sri Lanka
- **Timezone:** `Asia/Colombo`
- **Currency:** LKR, shown as `Rs. 12,500.00`
- **Phone numbers:** Sri Lankan. Users type them as `07XXXXXXXX`. Store them in international format `947XXXXXXXX`.

## Tech stack

Everything must work on **LankaHost cPanel shared hosting**.

- Laravel 12, PHP 8.2+, MySQL/MariaDB
- Blade + Tailwind CSS + Alpine.js (Laravel Breeze, Blade stack). **No React, Vue or Livewire.**
- Assets are built on the developer's PC with `npm run build`. **The server has no Node.js.** Commit/upload the built assets.
- **Releases:** `build-release.bat` makes `release.zip` (no-dev vendor, built assets, no `.env`/tests/storage contents). First install on the server: `php artisan app:install`. Steps: `docs/deploy-lankahost.md`. Live `.env` template: `.env.production.example`.
- **No `storage:link`.** Files are on the private `local` disk and served by logged-in routes. Never use the `public` disk, `Storage::url()` or `asset('storage/...')` (`tests/Feature/DeploymentReadyTest.php` checks this).
- **No Redis, no Supervisor, no long-running processes.**
  - Queues use the `database` driver.
  - Queued jobs are processed by the scheduler. Cron runs `php artisan schedule:run` every minute.
- Image handling with **GD only** (no Imagick).
- Packages:
  - `barryvdh/laravel-dompdf` — PDFs
  - `endroid/qr-code` — QR codes as PNG
  - `intervention/image` — resize photos (GD driver)
  - `spatie/laravel-activitylog` — activity log

## User roles

| Role | Can do |
|------|--------|
| `admin` | Everything, including settings, prices, reports and users |
| `front_desk` | Customers, job cards, quotations, billing, delivery |
| `technician` | **Only jobs assigned to them**, never other technicians' jobs: diagnosis, notes and parts used. **Cannot see invoices, payments, receipts, advances, cost prices, profit or reports.** |

- **No public registration.** Only an admin creates users.
- How this is enforced in code:
  - One job: `RepairJobPolicy::view` (technician only if `assigned_to` is them).
  - Any list, search or count of jobs: always use `RepairJob::visibleTo($user)`.
  - Money (invoices, payments, receipts, advances): the `view-billing` gate (admin, front desk). Wrap money in views with `@can('view-billing')` and protect billing routes with `role:admin,front_desk`.
  - Prices (estimates, quotation amounts, part selling prices): the `view-prices` gate (admin, front desk). **Technicians never see any price or payment.** Quotations are made, edited and sent by admin / front desk only; technicians see the lines and the customer's answer without amounts. Never write an amount into a job note or status history (technicians read the timeline). `tests/Feature/TechnicianNoPricesTest.php` checks every page a technician can open.
  - Device screen lock (PIN / password / pattern): only admin and the assigned technician (`RepairJobPolicy::viewPasscode`). **Never print it in a page, PDF, sticker, message or log.** It is fetched only by the "Show" button (POST `jobs.passcode`), and every view is saved in `passcode_views`.
  - Assigning technicians: admin and front desk (`RepairJobPolicy::assign`) through `App\Actions\AssignTechnician`, which keeps the history in `job_assignments`.
  - Branches: staff only reach records of their own branch, admins every branch (`User::canSeeBranch`). Record policies use the `ChecksBranch` trait; job lists use `RepairJob::visibleTo`; controllers without a policy call `canSeeBranch`. Customers and the parts catalogue are shared. `tests/Feature/SecurityTest.php` checks every route for every role and the branch wall: a new route must pass it.
- Logins: strong passwords (`Password::defaults()`: 10+ characters, upper and lower case, number, symbol), no "remember me" (2-hour idle timeout, `SESSION_LIFETIME=120`), 5 tries per email + IP. Security headers: `App\Http\Middleware\SecurityHeaders`.

## Main modules (built in phases)

1. Setup, login, roles, shop settings, branches
2. Customers, devices, job cards, photos, QR code, job card PDF, statuses
3. Public tracking page, email notifications, WhatsApp click-to-send messages
4. Technician assignment, diagnosis, quotations, customer approval
5. Repair parts: catalogue, compatible models, suppliers, stock in, parts used per job, reservations, low stock, returns
6. Billing, payments, invoice PDF, handover, warranty
7. Reports, scheduled reminders, activity log, backups
8. Later: WhatsApp Cloud API, barcode scanning, multi-branch stock

## Job statuses

`received`, `diagnosing`, `waiting_approval`, `waiting_parts`, `in_repair`, `testing`, `ready`, `delivered`, `cancelled`, `returned_unrepaired`

## Rules for writing code

- Simple, clean Laravel.
- Use **Form Requests** for validation.
- Use **Policies** for permissions.
- Use **PHP Enums** for statuses and roles.
- Use **database transactions** for money and stock changes.
- The database design for all phases is in `docs/database.md`. Follow it when writing migrations; if a migration must differ, update that file in the same change.
- Repair jobs live in the `repair_jobs` table (model `RepairJob`), because Laravel's queue already uses `jobs`.
- Stock is never stored as a number. `stock_movements` is the only source of truth for stock.
- Every money or stock event posts a balanced journal entry in the same transaction: through `App\Support\Accounting\Ledger` / `Poster` (never write `journal_entries` directly). Closed months (`books_locked_until`) cannot get entries.
- Money columns: `decimal(12,2)`.
- Every table that stores shop data has a `branch_id`.
- Customer photos and PDFs are stored on a **private disk**, never in `public/`. Staff open them through logged-in routes only.
- The public tracking page uses a **random 32-character token**, never the job ID.
- **Never hard-code the domain** (or `http://…/` links) in code, views, templates or messages. Use `route()` / `url()`, or `config('app.url')` for links that leave the site (QR codes, emails, WhatsApp messages, PDFs), so they are correct in queued jobs too. The domain lives only in `APP_URL`. `tests/Feature/NoHardCodedDomainTest.php` checks this.
- **Mobile-friendly UI:** staff use phones and tablets at the counter. Use simple English labels.
- **AppleTek brand look:** colours come from the logo and are set in `tailwind.config.js`: `brand-*` (navy 900 to bright blue 500), `accent-*` (cyan) and `bg-brand-gradient`. `indigo-*` is mapped to the brand blue, so old and new pages match. Use `brand-*` in new code, never other blues or purples for buttons and links. White sidebar with the logo; the logo is the shop logo from Settings (private disk, `shop_logo_url()`), or else the built-in `resources/branding/appletek-logo.png` (`App\Support\ShopLogo`), which ships in `release.zip`. Icons: `public/favicon.png`, `public/apple-touch-icon.png`, `public/brand-mark.png`.
- Write **Pest** feature tests for important logic.

## After each task

1. Run the tests.
2. Tell the user, in simple English:
   - what you changed, and
   - how to test it in the browser.

## Do not

- Do not change the tech stack.
- Do not add big packages without asking the user first.
