# CLAUDE.md

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

## Project

**ValiseStock Pro** — a multi-depot inventory management system for a luggage brand (Event Bag / EventBag). Built with Laravel 13 + Filament v3. The admin panel at `/admin` is the entire UI; there are no separate frontend pages.

## Commands

```bash
# First-time setup (install deps, generate key, migrate, build assets)
composer setup

# Full dev server (Laravel + queue worker + pail log viewer + Vite, all in one)
composer dev

# Run all tests
composer test

# Run a single test file
php artisan test --filter ExampleTest

# Lint with Pint
./vendor/bin/pint

# Database seed (creates roles, depots, users, sample products)
php artisan db:seed

# Import products from eventbagdz.com Shopify store
php artisan import:eventbag
php artisan import:eventbag --force        # wipe EventBag products first
php artisan import:eventbag --no-images    # skip image downloads

# Link storage (needed after first deploy or after artisan storage:link)
php artisan storage:link
```

Default database is **MySQL** (production on Plesk). Currency is **Algerian Dinar (DA)**. Default TVA is 19%.

## Architecture

### Core data model

```
Produit (is_bundle flag)
  └── Variation (sku, taille, couleur, prix_detail, prix_gros, stock_alerte)
        └── Stock (variation_id, depot_id, quantite)   ← one row per variation×depot
        └── MouvementStock                              ← append-only audit log

Bundle (independent, not a Produit subclass)
  └── BundleComposant (bundle_id, variation_id, quantite)
```

**All stock mutations must go through `StockService`** (`app/Services/StockService.php`). It runs every operation in a DB transaction and always appends a `MouvementStock` record. Never modify `stocks.quantite` directly. Methods: `entree`, `sortie`, `ajustement`, `transfertSortie`/`transfertEntree`, `venteBundle`, `reapproBundle`.

`Stock::getOrCreate(variationId, depotId)` is the canonical way to get or initialise a stock row.

### Invoicing flow

`FactureService` (`app/Services/FactureService.php`) handles the full lifecycle:
- **brouillon** → `confirmer()` → **confirme** (triggers stock `sortie` for each line via StockService)
- `genererPdf()` uses DomPDF and the `pdf.facture` Blade view, stores to `storage/public/factures/`

Document types: `facture` (FAC-), `bon_vente` (BV-), `bon_livraison` (BL-), `avoir` (AVO-). Numbers are auto-generated via `Facture::genererNumero()`, which is called automatically in the `Facture::boot()` `creating` hook — never set `numero` manually.

**`FactureLigne` auto-calculates** `montant_ht` and `montant_ttc` on every `saving` event via `calculerMontants()`. After each ligne `saved`/`deleted`, the parent `Facture` totals (`sous_total`, `tva_montant`, `remise_montant`, `total_ttc`) are recalculated via `calculerTotaux()` using `updateQuietly` (skips activity log). Never call `$facture->save()` inside `calculerTotaux` — it uses `updateQuietly`.

### Roles and depot scoping

Three roles managed by **Spatie Permission**: `super_admin`, `responsable_depot`, `employe`.

Users have a `depot_id` FK. Non-super-admin users can only see and operate on their own depot. Filament resources and widgets apply this filter through `Auth::user()->isSuperAdmin()` checks. The `Scanner` page and `FactureResource` both restrict the depot select to the user's own depot unless super_admin.

### Filament panel

Panel ID is `admin` (path `/admin`). Brand name is **Event Bag**. Primary color `#8B5E3C`.

Navigation groups (in order): **Stock**, **Ventes**, **Catalogue**, **Configuration** (collapsed).

All resources are auto-discovered from `app/Filament/Resources/`. The `Scanner` page (`app/Filament/Pages/Scanner.php`) is the main barcode/QR scanner — it accepts SKU text, JSON-encoded QR (`{"sku":"..."}`) or plain barcode, then calls StockService on submit.

Dashboard widgets: `StatsOverview`, `AlertesStock`, `MouvementsRecents`.

### Navigation — Stock group

| Sort | Item | URL | Notes |
|------|------|-----|-------|
| 1 | Scanner | `/admin/scanner` | Camera + USB barcode scanner, scan-to-queue then bulk validate |
| 2 | Mouvements | `/admin/mouvement-stocks` | Read-only audit log of all stock movements, filterable by type/depot/date |
| 3 | Inventaires | `/admin/inventaires` | Manual stock-count sessions; validating applies adjustments via StockService |

**Mouvements vs Inventaires**: Mouvements is the automatic append-only journal (every scan, entry, exit writes a row). Inventaires is an active operation where staff physically counts items and the system reconciles discrepancies.

### Scanner page specifics

- `@script` content in Filament/Livewire v3 is evaluated by Alpine.js using `new AsyncFunction`. Alpine wraps the content as `(async()=>{ ... })()` **only if** the content starts with `if` or `let`/`const` after trimming. **Never start the `@script` block with a `//` comment** — the parser treats the line after `=` as a comment, causing a SyntaxError and the entire block silently fails to execute (functions never assigned to `window`).
- The scanner uses `{ facingMode: 'environment' }` as fallback when camera enumeration fails (e.g. permission not yet granted).
- Bip sound is generated via Web Audio API (oscillator, not an audio file) to guarantee a single clean beep.
- After a scan: 3-second countdown overlay (3 → 2 → 1 → Scannez !) is shown over the live camera feed; camera stays live (no pause).

### Non-Filament routes

Auth-protected routes (see `routes/web.php`):
- `GET /factures/{facture}/pdf` — PDF download via `FacturePdfController`
- `GET /qrcodes/{type}/{id}` — QR code view
- `GET /barcodes/batch` — batch barcode print
- `GET /labels/variation/{variation}` — single product label
- `GET /labels/batch` — batch label print

### Bundle stock calculation

`Bundle::getStockDisponible(depotId)` returns `floor(min(composant.stock / composant.quantite))` across all composants — the limiting component determines available bundle quantity. `venteBundle` multiplies each component quantity by the number of bundles sold.

### Post-save redirects

All Create and Edit pages for **Transfert, Inventaire, Facture, Produit** override `getRedirectUrl()` to return `getUrl('index')` — they redirect to the list after a successful save instead of reloading the form.

### Transfert — réception

The `recevoir` action modal includes a **"Quantités correctes"** toggle (live) placed above the per-line quantity fields. When enabled it calls `Forms\Set` on every `ligne_{id}_recu` field, filling it with the corresponding `quantite_envoyee` from the sender.

**Lock on received transfers:** statuts `recu` and `recu_avec_ecart` are permanently locked for everyone (super_admin included):
- The Edit table action is hidden via `visible()`.
- `EditTransfert::authorizeAccess()` intercepts direct URL access and redirects to the list with a warning notification.

### Import command

`php artisan import:eventbag` scrapes the Shopify JSON API at `eventbagdz.com` for 16 hardcoded product handles (4 collections × 4 sizes). It auto-detects the color option index, deduplicates SET variants by keeping the highest-priced variant per color, generates QR codes and barcodes for new variations, and creates `Stock` rows for every active non-virtual depot.

### Key packages

| Package | Purpose |
|---|---|
| `filament/filament` v3 | Admin panel |
| `spatie/laravel-permission` | Roles (super_admin, responsable_depot, employe) |
| `spatie/laravel-activitylog` | Audit log on Produit, Variation, Bundle, Facture |
| `barryvdh/laravel-dompdf` | PDF generation |
| `simplesoftwareio/simple-qrcode` | QR code images |
| `picqer/php-barcode-generator` | Barcode images |

### Stock seeders

`InventaireStockSeeder` — creates one validated `Inventaire` per physical depot with realistic quantities, then calls `StockService::ajustement` for each line to set actual stock. Run with `php artisan db:seed --class=InventaireStockSeeder`. **Idempotent only on empty stock** — do not re-run if stock already exists.

| Depot | Inventaire | Gammes | Units |
|---|---|---|---|
| Draria - Alger (1) | INV-202604-0001 | AERO, CORE, FRAME, MARQUIS | ~510 |
| Dépôt El Eulma (2) | INV-202605-0001 | AERO, CORE, FRAME 20"/24" | ~700 |
| Baba Ali - Alger (4) | INV-202606-0001 | AERO, CORE, MARQUIS | ~520 |

### Seeder credentials (dev)

| Role | Email | Password |
|---|---|---|
| super_admin | admin@valisestock.dz | Admin@2024 |
| responsable_depot | resp.alger@valisestock.dz | Resp@2024 |
| employe | employe@valisestock.dz | Employe@2024 |
