# CLAUDE.md — Socle de gestion

## Projet
**Socle commun à plusieurs sociétés.** Une seule base de code, plusieurs installations : chacune a
son hébergement, sa base, son `.env` et ses réglages. Deux tournent aujourd'hui en production
(IRADA VENTURE et Ruwad Capital Group), toutes deux avec des données comptables réelles.

⚠️ **Ce n'est pas du multi-tenant.** Il n'y a pas de colonne `company_id`, pas de filtrage par
société, pas de portée globale. Les données de deux sociétés ne se croisent nulle part parce
qu'elles ne sont jamais dans la même base. N'introduisez jamais l'un ou l'autre.

Le socle est né du fork d'une application (Ruwad → IRADA, août 2026), auquel s'ajoutent le module
e-mail porté de Mobdie et le module Formation & Incubation.

## Stack
Laravel 12 + Filament 5 (4 panels : Admin `/`, Employé `/espace`, Investisseur `/portail`,
Client `/client`)
+ MySQL + PHP 8.3. spatie/laravel-permission, owen-it/laravel-auditing, maatwebsite/excel,
barryvdh/laravel-dompdf, phpoffice/phpword, spatie/laravel-backup. Queue=database,
cache=file, cron scheduler. Hébergement final : cPanel mutualisé (pas de Redis, pas de
Node en prod). IA via appels HTTP à l'API Anthropic, `ANTHROPIC_MODEL=claude-sonnet-5`
(identifiant exact — ne jamais le remplacer).

## La règle qui prime sur toutes les autres

**Aucune commande ne touche des données sans avoir vérifié pour quelle société elle a été écrite.**

Certains outils sont à usage unique et portent des identifiants d'écritures, des noms de
collaborateurs et des montants relevés sur les données d'UNE installation. Sur la base d'une autre,
ces identifiants désignent d'autres lignes : l'outil déplacerait des opérations au hasard, et son
rapport d'exécution aurait l'air parfaitement normal. Ce n'est pas une erreur qui plante, c'est une
erreur qui réussit.

D'où :

- ces outils vivent dans `app/Console/Commands/Reprise/`, portent le préfixe **`reprise:`** et
  étendent `CommandeDeReprise` ;
- ils déclarent en dur, dans la commande, la société pour laquelle ils sont écrits, **à côté** des
  identifiants qu'ils manipulent ;
- la société attendue n'est **jamais** lue d'un réglage modifiable depuis une interface : on ne
  doit pas pouvoir désarmer la garde par inadvertance en cliquant quelque part ;
- une installation sans code société est refusée au même titre qu'une installation étrangère —
  dans le doute, on n'écrit rien ;
- même garde pour les semences : `database/seeders/Reprise/`, `SemenceDeReprise`.

Les outils **permanents** (contrôles, recalculs, rapprochements) portent le préfixe **`gestion:`**
et tournent partout. Le cron du serveur appelle `schedule:run`, jamais les commandes une par une :
renommer une commande planifiée est donc sans effet sur les hébergements.

**Préfixes de commandes** : les commandes propres à un module portent le préfixe du module
(`courrier:` pour les quatre du Courrier, `formation:`, `timesheets:`, `payroll:`,
`espace-client:`, `pilotage:`, …) ; **`gestion:`
est réservé aux outils transverses** du socle. Ces noms sont des conventions installées, pas des
anomalies : ne « corrigez » jamais le nom d'une commande existante — plusieurs sont planifiées en
production.

## Identité : c'est de la configuration, jamais du code
- Tout ce qui distingue une installation — raison sociale, marque, couleurs, logos, favicon,
  mentions légales — vit dans les réglages `company.*` et se lit par `App\Support\Identite`.
- **Aucun repli ne nomme une société.** Là où un réglage manque, on retombe sur `config('app.name')`
  ou sur une palette neutre. Un socle qui afficherait « IRADA VENTURE » chez Ruwad parce qu'un
  réglage est vide est un socle raté.
- Logos et favicon sont **téléversés** et vivent sur le disque public, jamais dans le dépôt :
  l'archive livrée doit être rigoureusement identique pour toutes les sociétés.
- Les adresses transverses (`MAIL_FROM_ADDRESS`, `MAIL_GERANCE_ADDRESS`, `APP_NAME`) n'ont **aucune**
  valeur par défaut : l'application refuse de démarrer plutôt que d'envoyer un courriel à l'adresse
  d'une autre société. Voir `App\Support\ConfigurationRequise`.
- `company.code` est **immuable** une fois posé : il commande la garde ci-dessus.

## Conventions
- Code, tables, colonnes, variables : **anglais** (héritage conservé). UI, libellés,
  messages, e-mails, commentaires nouveaux : **français**.
- Montants `DECIMAL(14,2)` MAD, format affiché `1 234 567,89 DH`. Dates `d/m/Y`.
  Timezone `Africa/Casablanca`.
- Toute logique de calcul (soldes, coûts, marges, allocations, primes, CCA, RAS) vit dans
  `app/Services/`, jamais dans les ressources Filament ; chaque service a des tests.
- Soft deletes + audit sur toutes les entités métier. Policies pour tout accès.
- Migrations incrémentales, jamais de modification d'une migration déjà commitée.
- Après chaque phase : `php artisan test` vert + `./vendor/bin/pint`, puis commit + tag.

## Règles métier critiques (ne jamais transgresser)
1. **Règle d'or CCA (CDC §4.2)** : toute dépense saisie porte « payé avec quoi ? » ;
   fonds personnels d'un associé ⇒ création atomique de l'écriture liée « dette de
   financement » créditant son CCA — dans un service unique, non contournable.
2. **RAS (CDC §4.4)** : RAS TVA 75 % (ou 100 %) de la TVA, RAS IS 5 % du HT ; écritures
   débit = crédit (neutres en trésorerie), catégories `RAS TVA`/`RAS IS`, rattachées à la
   facture, comptées dans le CA TTC.
3. **Clé de structure (CDC §4.7)** : charges de fonctionnement réparties = 50 % prorata
   salaires ventilés + 50 % prorata CA TTC encaissé (pool hors TVA/IS), % en réglages.
4. Cautions et transferts inter-comptes ne sont jamais des charges ni des coûts de marché.
5. Solde CCA = apports − récupérations, toujours recalculé, jamais stocké.
6. Aucune inscription publique ; seul le super-admin gère utilisateurs/rôles.
7. Aucun e-mail vers un tiers externe ne part sans validation humaine ; Reply-To et Cci sur
   l'adresse de facturation de l'installation ; alertes internes vers `config('mail.gerance')`.
8. Boîtes e-mail : identifiants IMAP/SMTP chiffrés en base (casts `encrypted`), affectations
   utilisateur ↔ boîte gérées par le seul super-admin ; chacun ne voit que ses boîtes.

⚠️ Les catégories de journal `ras-tva`, `ras-is`, `caution-prov`, `caution-def`,
`fonctionnement-autre`, `banque`, `ca` sont **structurelles** : le code les cherche par leur
`slug`. Elles appartiennent au socle (`CategoriesJournalSeeder`), pas au référentiel d'une société.

## Semences : trois familles, à ne pas mélanger
1. **Socle** — `DatabaseSeeder` : rôles, permissions, catégories, types de congés, projet interne,
   modèles de documents, réglages neutres. S'exécute partout, y compris sur une installation neuve.
2. **Première configuration** — l'écran « Mise en route ». Tant que `company.code` est vide,
   l'application y conduit et nulle part ailleurs. Rien de ce qu'il demande ne peut être deviné :
   ne l'inventez jamais.
3. **Reprise d'une société** — `database/seeders/Reprise/`, sous la garde de la §4.

Aucune semence n'écrase un réglage `company.*` déjà renseigné : on n'écrit que ce qui est vide.

## Modules
- **Formation & Incubation** : livré partout, éteint par défaut (`formation.actif`). Éteint, la
  navigation disparaît, les routes de l'espace apprenant ne sont pas enregistrées, les tâches
  planifiées ne tournent pas. Les tables restent créées. Basculer le réglage vide le cache de
  routes.

## Décisions d'architecture (ne pas revenir dessus)
- **Filament 5** (API Schemas : `Filament\Schemas\Components`, dossiers `Schemas/` + `Tables/`).
- **2FA = MFA natif Filament** (pas de Fortify).
- **Un seul guard `web`** + gating par `type` (`User::canAccessPanel()`), plus un guard séparé
  `apprenant` pour l'espace de formation. `users.type` est un **ENUM en base** : y ajouter une
  valeur exige de modifier la définition de la colonne, jamais seulement l'énumération PHP.
- Back-office en français uniquement, libellés FR en dur dans les ressources (assumé).
- Moteur d'allocation : `app/Services/Allocation/AllocationEngine.php` (méthode du plus
  grand reste, idempotent) — paramétré par les réglages, pas réécrit.
- Vitrine CMS/blog : retirée du routage (l'app ne sert que les panels). Les modèles CMS
  restent en code mort assumé.
- Les fournisseurs de panneaux lisent l'identité par des **fermetures** (`brandName`, `colors`,
  `favicon`) : elles sont évaluées au rendu, jamais à l'enregistrement du panneau — sans quoi une
  base pas encore migrée ferait tomber l'application.
- Un widget du tableau de bord se déclare dans `Dashboard::getWidgets()` ; **la découverte
  automatique ne suffit pas** — dès qu'une page déclare ses widgets, elle les déclare tous.
- Tout écran nouveau a un test qui **rend LA PAGE** que l'utilisateur ouvre, pas seulement le
  composant : un widget parfait sur aucun écran reste un widget sur aucun écran.
- **Espace client** (v4.2.0) : un tableau de bord par marché, configurable, en lecture par lien
  (`/espace/{slug}/{jeton}`) et/ou par comptes nominatifs (panneau `client`). Les deux écrans
  partagent leurs vues (`resources/views/espace-client/**`) ; seuls les boutons de décision
  changent. L'isolement est écrit **une fois**, dans `App\Services\EspaceClient\AccesClient` :
  un client ne voit jamais un autre client, et une URL forgée répond **404**, jamais 403.
- Les **modules** d'un espace client vivent en JSON, ordonnés : ce sont des réglages que
  l'exploitant réarrange, pas un schéma. Un module ajouté par une version du socle arrive
  **éteint** sur les espaces existants.
- **Primes de pilotage** (v4.4.0) : les points se **déduisent** du journal des tâches et ne se
  saisissent jamais ; aucun point n'est négatif ; **rien n'est versé sans validation** de la
  direction ; **aucun classement par montant**. Les réglages d'un mois sont **photographiés** au
  premier calcul (`bonus_parametres_mois`) : rien dans le module ne relit `Settings` pour un mois
  déjà commencé — c'est ce qui rend une prime rejouable et explicable six mois plus tard. Les
  majorations d'heures supplémentaires vivent dans **Réglages → Paie** (c'est du droit du travail)
  et leurs minima légaux sont écrits en dur : on peut payer plus que la loi, jamais moins.

## Sécurité
- Aucun secret réel dans le dépôt ; tout vit dans le `.env` du serveur.
- `SESSION_ENCRYPT=true` ; `Password::defaults()` = min(8) + uncompromised.
- `EnsureStaffTwoFactor` + `ForcePasswordChange` en `authMiddleware(..., isPersistent: true)`.
- Tests d'étanchéité systématiques : tout nouvel écran doit être testé par accès URL direct
  d'un utilisateur d'un autre `type` (403/redirect attendu) — y compris les actions Livewire.
- Le dépôt de versions (`/socle/versions/*`) exige un jeton Bearer, limite la fréquence et
  journalise chaque téléchargement : l'archive contient tout le code source de plusieurs sociétés.

## Mise à jour automatique
**Construire n'est pas publier.** `deploy/build-zip.sh` fabrique une archive et ne déclenche rien ;
`gestion:publier-version` écrit le manifeste, et c'est lui seul qui met les installations en
mouvement — la nuit suivante, dans leur fenêtre horaire.

L'ordre des étapes n'est pas négociable : empreinte SHA-256 vérifiée, sauvegarde de la base
vérifiée **présente et non vide**, puis seulement maintenance et extraction. Les migrations
tournent dans un processus PHP **neuf** (`gestion:maj-finaliser`) : une application ne peut pas
remplacer ses propres fichiers pendant qu'elle s'exécute.

⚠️ **Jamais de `migrate:rollback` automatique.** Défaire une migration détruit des données ;
restaurer la sauvegarde n'en détruit aucune. C'est la seule stratégie de retour arrière autorisée.

⚠️ Une version dont le manifeste annonce une commande de reprise n'est **jamais** appliquée
automatiquement : elle est téléchargée, signalée, et attend une décision humaine.

## Tests
- La **suite commune** est celle du socle : elle doit passer sur n'importe quelle installation et
  ne cite jamais un montant, une raison sociale ni un identifiant réels.
- Les tests qui affirment les chiffres réels d'une installation portent son groupe (`irada`),
  exclu par `phpunit.xml`, et se lancent à la demande : `php artisan test --group=irada`.
- `Tests\TestCase` pose le code société pour tout le groupe : un test qui rejoint le groupe n'a
  rien à ne pas oublier.

## Reprise des données (IRADA)
Commandes gardées : `reprise:reprendre` (lit `GESTION IRADA_1.xlsx` sur le Mac),
`reprise:generer-dump` (exporte `database/dump-initial.sql` + `database/controle-reprise.json`),
`reprise:charger-dump` (production), `reprise:controler-reprise` (compare base ↔ compteurs).
Ne jamais assainir les données reprises : toute anomalie va dans `reprise_anomalies`.

---

## 📌 Guide de passation (contexte hors-code) — À LIRE AU DÉMARRAGE

Un guide compagnon **`GUIDE-PASSATION.md`** vit à la racine de ce dépôt (et en tête du dossier
« my apps » côté Cowork). Il couvre ce que ce `CLAUDE.md` ne dit pas :

- **Hébergement cPanel réel** : serveurs (saturn/mars), comptes (`mobdiema`/`ruwadcap`), bases, chemins, DNS, binaire PHP 8.3.
- **Déploiement manuel pas à pas** (le gérant applique lui-même ; Claude livre un zip à plat + procédure).
- **Historique récent** : trésorerie IRADA reconstruite via `reprise:repartir-alimentations` (6 leviers, garde société, refus d'écrire hors atterrissage, option `--jusqu-au=2026-07-31`), alignement RUWAD 3.6.2 → 3.8.6, refonte affichage marchés/dashboard v3.8.6.
- **Logique & préférences du gérant** : concision, commandes prêtes à copier-coller, décisions métier à l'exploitant, sécurité d'abord, 0 découvert, IRADA et RUWAD toujours sur la même version.

⚠️ Trésorerie IRADA **déjà reconstruite et écrite en prod** — ne pas relancer `reprise:*` sans demande explicite du gérant.
