# Conventions

## Langue

- Code (classes, variables, méthodes, routes) : **anglais**.
- UI (labels, messages flash, textes) : **français**.
- Documentation : **français**.

## Nommage

- Entités : PascalCase singulier (`Product`, `CashSession`).
- Tables : snake_case singulier, préfixe `app_` (`app_product`, `app_cash_session`).
- Enums PHP : PascalCase, cases en UPPER_SNAKE (`PaymentMethod::CASH`).
- Controllers : `<Nom>Controller`, un par domaine.
- Templates Twig : `snake_case`, organisés par domaine (`sale/index.html.twig`).
- Routes : `app_<domaine>_<action>` (`app_sale_create`).

## Structure Symfony

```
src/
├── Controller/        # Controllers front de vente
├── Entity/            # Entités Doctrine
├── Enum/              # Enums PHP
├── Repository/        # Repositories Doctrine
├── Service/           # Logique métier
├── Form/              # Form types
├── EventSubscriber/   # Listeners / subscribers
└── Admin/             # Contrôleurs EasyAdmin (CRUD)
templates/
├── sale/              # Front de vente
├── session/           # Gestion sessions de caisse
└── base.html.twig
```

## Patterns

- Logique métier dans des services, pas dans les controllers.
- Filtrage par association via un Doctrine Filter ou un EventSubscriber automatique.
- Snapshots de prix dans `SaleLine` (prix figé au moment de la vente).
- Tout mouvement de stock passe par `StockService` : c'est le seul endroit qui écrit
  `Product.stock`, qui reste ainsi le cumul exact des mouvements. Quantité signée
  (entrée positive, vente négative).
- Enums PHP natifs (PHP 8.1+), pas de constantes string.

## Tester depuis un téléphone

Le conteneur nginx écoute sur `0.0.0.0:8088`, et WSL expose le port sur le réseau local. Un
téléphone sur le même Wi-Fi atteint donc l'application directement, sans redirection de port :

```
http://<ip-lan-du-poste>:8088
```

L'adresse du poste se lit avec `Get-NetIPAddress` côté Windows (interface Wi-Fi). Elle vient du
DHCP de la box et peut changer après un redémarrage.

Deux points à connaître : si la page ne charge pas, c'est le pare-feu Windows ; et en mode `dev`
la barre de debug Symfony s'ancre en bas de l'écran, là où vit la barre d'encaissement.

## Tests

- PHPUnit, suite lancee dans le conteneur : `docker compose exec php php vendor/bin/phpunit`.
- Base de test dediee `recre_test` (suffixe `_test` configure dans `doctrine.yaml`).
  Mise en place, une fois :

  ```sh
  docker compose exec db mariadb -uroot -proot -e "CREATE DATABASE IF NOT EXISTS recre_test; GRANT ALL PRIVILEGES ON recre_test.* TO 'recre'@'%'; FLUSH PRIVILEGES;"
  docker compose exec -e APP_ENV=test php php bin/console doctrine:migrations:migrate --no-interaction
  ```

- Les tests qui touchent la base etendent `App\Tests\DatabaseTestCase` : chaque test tourne
  dans une transaction annulee au teardown, la base reste donc propre entre les tests.
- Les fabriques d'entites de test vivent sur cette classe de base, pas dupliquees test par test.
- Toute regle dure du projet merite un test qui la verrouille : l'isolation par association est
  couverte par `tests/Doctrine/AssociationFilterTest.php`.

- Une vente est tout ou rien : transaction explicite, produits verrouillés en écriture avant
  lecture du stock, refus de la vente entière si un article manque.

## Git

- Commits atomiques, messages en français ou anglais, format libre mais clair.
- Une branche par feature (`feature/<nom>`), merge dans `main`.
- Pas de force push sur `main`.
