⚠️ PRODUCTION ACTIVE — 1 client en ligne depuis avril 2026. Le dépôt n'est plus un projet pré-prod : des données réelles (devis, commandes, BL, factures, FEC) vivent sur une instance client. Conséquences sur toute modification :
- Migrations rétro-compatibles : additives par défaut. Pas de
drop/renamede colonne sans étape de transition (ajout → double écriture → bascule → suppression).- Backup avant migration :
php artisan backup:runpuismigrate --pretendavantmigrate.- Pas de breaking change silencieux sur les URLs, les exports comptables (FEC, factures) ni les fichiers déjà attachés (GED /
LegacyFileController).- Toute régression est visible client : privilégier le correctif ciblé au refactor large.
- Backend : Laravel 12 (PHP 8.2+), architecture MVC classique
- Frontend : React (dominant — composants riches migrés), Blade (layout/shell), Alpine.js (micro-interactions)
- Vue.js : SUPPRIMÉ
- Livewire : SUPPRIMÉ ✅ (reste en vendor uniquement comme dépendance transitive de laravel/pulse)
- CSS : Bootstrap 5.3 via AdminLTE 4 (Tailwind supprimé)
- Bundler : Vite
- Temps réel : Laravel Echo + Redis
- Tests : PHPUnit (backend), aucun test frontend
- Infra : Docker (Nginx + PHP-FPM), docker-compose.yaml
app/Http/Controllers— Contrôleurs web et APIapp/Models— Modèles Eloquentresources/js/components— Composants React (80+ fichiers .jsx)resources/views— Templates Blade + shell pages montant les composants Reactdatabase/migrations— Schéma ERP/MES
- React : tous les composants riches (index, show, lignes, tableaux, dashboards)
- Blade : layout, shell, formulaires simples sans interaction
- Livewire : SUPPRIMÉ ✅
- Alpine.js : micro-interactions uniquement
- Vue.js : SUPPRIMÉ ✅
- ✅ Migrer Laravel Mix → Vite
- ✅ Résoudre le conflit CSS Bootstrap/Tailwind
- ✅ Migrer Vue.js → React (Prompt 4)
- ✅ Migrer QuoteLine vers React
- ✅ Migrer OrderLine vers React
- ✅ Supprimer les derniers composants Livewire résiduels (ArrowSteps, Calendar, ChatLive, LogsViewer, StockCurrent)
| Commande | Description |
|---|---|
php artisan wem:diagnostics |
Vérifie les prérequis de l'environnement (PHP, extensions, APP_KEY, Redis, DB, Pusher) |
php artisan wem:n2p:push-order {orderId} [--sync] |
Pousse une commande vers Nest2Prod (queue par défaut, --sync pour immédiat) |
php artisan wem:pdp:sync [--tenant=] [--events] [--inbound] |
Synchronise la PDP : cycle de vie des factures émises + réception des factures fournisseurs. Indispensable pour les plateformes sans webhooks (SUPER PDP) |
php artisan wem:pdp:directory [--open=] [--date=] [--close=] [--lookup=] [--search=] |
Annuaire : liste/ouvre/ferme notre ligne de réception (prérequis du 1er sept. 2026), et cherche l'adresse de facturation d'un client par SIREN ou raison sociale |
php artisan wem:pdp:seed-sandbox [--force] |
Dev uniquement. Écrit l'identité bac à sable SUPER PDP : vendeur Burger Queen dans factory, client Tricatel dans companies. Écrase l'identité de la société |
php artisan emails:send-auto-reports |
Envoie les rapports email automatiques aux utilisateurs selon l'heure configurée |
php artisan preorders:scan-output [--path=] [--pattern=] [--done-path=] |
Scanne le dossier output et importe les CSV comme pré-commandes |
php artisan stock:recalculate-cump [--dry-run] |
Recalcule le CUMP historique pour tous les emplacements produit (--dry-run pour simuler) |
php artisan quality:dispatch-calibration-alerts |
Notifie les responsables pour les appareils de contrôle qualité à étalonner |
php artisan ldap:import-users |
Importe les utilisateurs depuis l'Active Directory LDAP |
php artisan rgpd:erase-contact |
Anonymise les données personnelles d'un contact |
php artisan rgpd:export-contact |
Exporte les données personnelles d'un contact (droit d'accès RGPD) |
php artisan rgpd:purge |
Purge tokens expirés, email_logs > 1 an, soft-deleted > 90j |
php artisan hr:recompute-absence-days [--dry-run] |
Recalcule le coût en jours de toutes les demandes d'absence (à lancer une fois après migration des soldes de congés, et après tout changement du calendrier des jours fériés) |
php artisan wem:files:import [--dry-run] [--skip-move] |
À lancer une fois après déploiement. Déplace public/{file,photo,drawing,stl,svg,images/products} vers storage/app/private/legacy, renseigne disk/path/kind des lignes files existantes, et convertit les colonnes CAO produit en fichiers attachés |
| Fréquence | Commande | Rôle |
|---|---|---|
| Quotidien à 01h00 | backup:clean |
Supprime les anciennes sauvegardes (rétention config/backup.php) |
| Quotidien à 02h00 | backup:run |
Sauvegarde complète DB + storage/app |
| Quotidien à 09h00 | backup:monitor |
Alerte mail si dernier backup > 2 jours |
| Toutes les 15 min | wem:pdp:sync |
Facturation électronique : statuts des factures émises + factures fournisseurs reçues |
| Hebdomadaire | rgpd:purge |
Purge RGPD (voir tableau ci-dessus) |
| Mensuel | activitylog:clean |
Nettoie les logs d'activité (durée config/activitylog.php) |
Pour que le scheduler fonctionne, ajouter au crontab du serveur :
* * * * * php /var/www/html/artisan schedule:run >> /dev/null 2>&1
# Démarrer le worker (développement)
php artisan queue:work --sleep=3 --tries=3
# Vérifier les jobs en échec
php artisan queue:failed
# Relancer les jobs en échec
php artisan queue:retry all
# Vider la file (⚠️ irréversible)
php artisan queue:flushConfig Supervisor recommandée (/etc/supervisor/conf.d/wem-worker.conf) :
[program:wem-worker]
command=php /var/www/html/artisan queue:work redis --sleep=3 --tries=3 --max-time=3600
autostart=true
autorestart=true
numprocs=2# Cache
php artisan cache:clear # Vider tout le cache (⚠️ si renommage statuts)
php artisan optimize:clear # Vide tout : config, routes, views, cache (à faire avant de recacher)
php artisan config:cache # ✅ Régénérer le cache de config
php artisan view:cache # ✅ Précompiler les vues Blade
# ❌ PAS de php artisan route:cache — casse la localisation mcamara
# (setLocale() gelé au moment du cache CLI → routes figées sur une seule locale,
# /fr/xxx et /en/xxx retournent 404. Cf. routes/web.php:29)
# Base de données
php artisan migrate # Appliquer les migrations
php artisan migrate:status # État des migrations
# Backup manuel (spatie/laravel-backup)
php artisan backup:run # Déclencher un backup immédiat
php artisan backup:list # Lister les sauvegardes disponibles- VPS Ionos : demo + commercial
- VPS OVH (16vCore/64GB) : instances clients
- Installation manuelle par client
- 1 instance client en production depuis avril 2026
php artisan down # fenêtre hors heures ouvrées
php artisan backup:run # backup DB + storage AVANT tout
git pull && composer install --no-dev -o && npm ci && npm run build
php artisan migrate --pretend # relire le SQL avant de l'appliquer
php artisan migrate
php artisan optimize:clear && php artisan config:cache && php artisan view:cache
# ❌ jamais php artisan route:cache (casse la localisation mcamara)
php artisan queue:restart # les workers rechargent le nouveau code
php artisan up- Portainer + Nginx Proxy Manager
- 1 container + 1 base par client
/data/clients/{client}/storage+/data/clients/{client}/mysql
- Laravel Pulse : santé applicative
- Portainer : infrastructure
- UptimeRobot : disponibilité externe
- SQL > 500ms → lente
- HTTP > 1s → dégradé
- HTTP > 3s → critique
- Disque > 80% → alerte
- Secteur : Tôlerie
- Utilisateurs : 1
- Modules : CRM, Devis, Commandes, Pré-commandes IA, BL, Facturation, FEC
- Tarif : 1 mois gratuit → 100€/mois
- Données réelles → obligations RGPD effectives (mentions légales, registre des traitements)
- Point de vigilance : instance mono-utilisateur, donc pas de couverture "test en charge" par l'usage — les régressions se voient directement en exploitation
Toutes les entités qui portent des fichiers (produits, devis, commandes, BL, factures, achats, NC, mouvements de stock, sociétés, opportunités) passent par un seul dispositif.
files:kind(mesh / brep / cad2d / vector / doc / image / sheet / archive / other, déduit de l'extension parApp\Services\Files\FileKindResolver),extension,disk,pathfileables(pivot polymorphe) :role(plan, modele_3d, vectoriel, photo, cam, certificat, controle, autre —App\Services\Files\FileRole) etis_primaryApp\Services\Files\FileableRegistry: liste blanche alias → modèle, pour que le front n'envoie jamais un nom de classe arbitraire
Les alias user et employment-contract portent des données personnelles. Contrairement au
reste de la GED, ils ne sont pas lisibles par toute l'usine :
App\Services\Files\FileConfidentiality restreint la lecture, l'écriture et la suppression
au salarié concerné et aux porteurs de human-resources-menu (plus le rôle Admin).
Le contrôle est posé à deux endroits — FilePolicy::view/update/delete pour l'accès par id
de fichier (files.raw, files.download), et FileApiController::resolveEntity() pour
l'accès par entité (liste, dépôt) — pour qu'aucune des deux portes ne reste ouverte.
Rôles de fichier dédiés : contrat, bulletin_paie, arret_travail, diplome, identite
(FileRole::forHumanResources()).
Hors racine web, sous storage/app/private/files/{aaaa}/{mm}, servi par les routes
authentifiées files.raw (inline) et files.download. Les anciens dossiers publics sont
servis par LegacyFileController sur les mêmes URLs (/drawing/x.pdf...), déclarées en
fin de routes/web.php hors du groupe de locale. Un SVG est renvoyé avec nosniff + CSP
sandbox (fichier utilisateur donc potentiellement exécutable).
- Montage Blade :
@include('include.file-manager-mount', ['fileableType' => 'product', 'fileableId' => $id]) resources/js/components/files/:FileManager(dépose + liste + viewer),FileDropzone,FileViewer(dispatch parkind),viewers/(Mesh, Brep, Dxf, Image, Pdf)- Chaque moteur est en
React.lazy(): ouvrir un PDF ne télécharge pas three.js - STL/OBJ/PLY/3MF/glTF : loaders three.js — STEP/IGES/BREP :
occt-import-js(OpenCascade en WASM, 7,4 Mo chargés à la demande, aucun convertisseur serveur requis) — DXF :dxf-parser(parsing seul, pas de dépendance three) + rendu maison dansresources/js/lib/dxf/toThree.js, caméra orthographique
products.drawing_file / stl_file / svg_file / picture ne sont plus la source de vérité
mais un cache de lecture, resynchronisé par FileStorageService::refreshLegacyColumns()
à chaque attache/détache. Les lignes de devis, lignes de commande, ProductResource et
TaskStatuApp continuent de les lire sans modification.
Réforme française : réception obligatoire pour toutes les entreprises au 1er septembre 2026.
Un contrat, plusieurs plateformes. App\Services\Integrations\Pdp :
Contracts\PdpGateway— émission et suivi (obligatoire)Contracts\PdpInboundGateway— réception des factures fournisseursContracts\PdpCursorSyncGateway— plateformes sans webhooks, synchro par curseurContracts\PdpDirectoryGateway— annuaire : nos lignes de réception, adresses de nos clientsContracts\PdpStatusReportingGateway— statuts déclarés au fournisseur (obligation acheteur)PdpManager— registre des drivers, résolu parconfig('services.pdp.default')PdpInvoiceService/PdpIncomingInvoiceService— orchestration agnostique
Seul PdpGateway est requis ; les quatre autres sont optionnels et testés par
instanceof, ce qui permet à une plateforme pauvre en fonctions de coexister
sans code mort ni méthodes vides.
Les drivers sont enregistrés dans AppServiceProvider::register().
| Driver | Émission | Réception | Suivi | Annuaire | Statuts sortants |
|---|---|---|---|---|---|
qonto |
données structurées, Qonto produit le document | non | polling manuel | non | non |
superpdp |
notre Factur-X déposé tel quel | oui | curseurs + wem:pdp:sync |
oui | oui |
- Un « warning » schematron fait rejeter.
PEPPOL-EN16931-R008porte le libellé « still status warning » et fait pourtant rejeter la facture enfr:213/REJ_SEMAN. Règle retenue : tout ce qui rendis_validfaux bloque le dépôt. - Le rejet arrive des heures après. Constaté : dépôt à 23h58,
fr:213à 03h07. Interroger l'API quelques secondes après un dépôt n'apprend rien — seulwem:pdp:syncfait foi. - Un dépôt accepté n'est pas une facture émise.
POST /invoicesrenvoie 200 etapi:uploadedmême quand le document sera rejeté. Il n'existe aucun signal synchrone. POST /invoices/convertest l'outil de diagnostic : il analyse le document et renvoie l'erreur réelle sans rien déposer, là où/validation_reportsminimise et où/invoiceséchoue en silence.- Le numéro de facture est consommé dès le premier dépôt, même rejeté. Tout renvoi sous le même numéro est refusé (
fr:213, codeDOUBLON). Corriger une facture rejetée impose donc d'en émettre une nouvelle — la carte masque le bouton de dépôt dans ce cas.
API REST https://api.superpdp.tech/v1.beta/, OAuth 2.1 client_credentials
(access_token 30 min). Aucun webhook : la spec OpenAPI n'en déclare pas, la
synchronisation officielle passe par starting_after_id sur /invoices et
/invoice_events, d'où la table pdp_sync_cursors et la tâche planifiée.
Le mode bac à sable / production est porté par les identifiants, pas par l'URL.
Recevoir ne suffit pas : l'acheteur doit renvoyer le cycle de vie du document
(fr:204 prise en charge, fr:205 approuvée, fr:207 litige, fr:210 refus,
fr:211 paiement transmis). Ces statuts remontent aussi à l'administration,
qui en déduit l'exigibilité de la TVA sur les prestations de services.
Menu « Déclarer » sur chaque ligne de la boîte de réception →
PdpIncomingInvoiceService::reportStatus() → PdpStatusReportingGateway.
Un motif est exigé sur les statuts défavorables et transmis tel quel au
fournisseur ; un document déposé à la main n'a pas de destinataire et ne
propose pas l'action.
Une facture reçue entre dans pdp_incoming_invoices (boîte de réception), puis
deux voies :
- Rapprochement (voie normale) : l'écran
/purchases/waiting/invoiceest ouvert avec?companies_id=&incoming_id=, affiche le document du fournisseur en regard des réceptions à cocher, signale l'écart de total, et rattache le document à la facture d'achat créée. - Sans rapprochement : crée l'en-tête seul, pour les factures sans commande ni réception (frais, abonnements).
Les lignes du document reçu ne sont jamais recopiées :
purchase_invoice_lines ne porte ni libellé ni montant, seulement les clés vers
la ligne de commande et la ligne de réception. C'est ce qui garantit qu'on ne
paie que ce qui a été commandé et reçu ; le document du fournisseur reste une
pièce à confronter, conservée dans payload.
App\Services\Invoicing\FacturXBuilder est la source unique du Factur-X
(profil EN 16931, via horstoeko/zugferd) : le PDF téléchargé par le client et
celui déposé sur la plateforme sont le même fichier. Il n'écrit rien sur disque.
assertPartiesAreIdentifiable() refuse en amont les documents dont les parties
ne sont pas identifiables (SIREN, TVA, adresse, acheminement), avec un message
en français listant tout ce qui manque — sinon l'utilisateur reçoit une règle
schematron après un aller-retour jusqu'à la plateforme.
config/invoicing.php porte le contenu normatif : mode de facturation
(BT-23) et mentions légales obligatoires (BT-22 : pénalités de retard,
indemnité de recouvrement, escompte).
leave_types: nature du congé (CP, RTT, récupération, maladie, événement familial, formation, sans solde).counts_against_balancedistingue ce qui consomme un droit de ce qui est seulement tracé (un arrêt maladie est une absence, pas un congé décompté).leave_balances: uniquement le crédit — droit acquis, report N-1, régularisation — par (salarié, nature, période de référence). Le débit n'est jamais stocké là : il est recalculé depuistimes_absencesparLeaveBalanceService, donc les deux ne peuvent pas diverger.times_absences.days_count: coût résolu de la demande, écrit à l'enregistrement parTimesAbsenceObserver(week-ends et jours fériés déjà retirés selonabsence_type_day: calendaire / ouvrable = lun-sam / ouvré = lun-ven). Évite de dérouler chaque plage de dates à la lecture d'un solde.- Période de référence configurable dans
config/hr.php(défaut français : 1er juin → 31 mai ; mettre le mois à 1 pour une année civile). Une absence à cheval sur deux périodes est répartie entre elles, jamais comptée deux fois. - Le solde restant déduit les demandes validées et les demandes en attente, pour qu'un salarié ne puisse pas poser deux fois le même reliquat.
- Écrans : onglet « Soldes de congés » sur la fiche salarié (saisie des droits, RH),
tableau usine sur
human-resources/leave/balances, lecture seule sur le profil du salarié.
Le circuit existe : times_absences.statu vaut 1 = à valider, 2 = validé, 3 = refusé.
Vue salarié = onglet « Demande d'absence » du profil (saisie, puis amendement tant que
la demande est en attente — le bouton disparaît une fois traitée). Vue valideur = onglet
Absence de l'écran Temps, qui porte le select statu.
AbsenceController limite la saisie pour autrui et le changement de statu aux porteurs de
human-resources-menu ; un salarié ne peut amender que sa propre demande tant qu'elle est
en attente. Avant ça la distinction des deux vues était visuelle et non appliquée :
n'importe qui pouvait POSTer statu=2 sur sa demande, et le solde n'aurait rien voulu dire.
Ce qui manque encore : aucune notification (pas d'AbsenceNotification, le valideur doit
aller voir l'écran), et users.supervisor_id est saisi sur la fiche salarié mais ne route
aucune validation — c'est « les RH valident », pas « mon manager valide ».
Il n'existe pas de format d'export standard : la DSN est produite par le logiciel de
paie, pas par l'ERP, et chaque éditeur (Silae, Sage, Cegid, Quadra…) importe sa propre
mise en page. PayrollExportService produit donc le plus petit dénominateur commun, une
ligne par salarié et par rubrique : matricule, période, code rubrique, quantité, unité.
S'adapter à un éditeur = mapper la colonne code, pas réécrire l'export.
- Rubriques émises : une par nature de congé (code =
leave_types.code, en jours, absences validées uniquement),HTRAV(heures badgées) etHPROD(heures sur tâches). - Une absence à cheval sur deux mois est répartie entre les deux bulletins.
users.payroll_numberporte le matricule connu du logiciel de paie ; à défaut l'export retombe surusers.idet le signale.- L'écran liste les anomalies à traiter avant transmission (badgeage non refermé, matricule
absent) et propose CSV ou XLSX via
maatwebsite/excel, sur le patron de l'export FEC.
osh_formations tenait déjà un registre de formations par salarié avec date de péremption,
mais type_of_training était du texte libre — « CACES 3 » et « Caces 3 » ne se regroupaient
pas. training_types fournit le référentiel, training_type_resource relie une
habilitation aux ressources sur lesquelles elle est attendue, et HabilitationService
calcule l'état par salarié : valide / bientôt échue / périmée / non obtenue / non formé
(fenêtre d'alerte dans config/hr.php, 60 jours par défaut). Un renouvellement prime sur la
session périmée ; sans date de fin l'habilitation est à vie.
L'écran est informatif et ne bloque rien. HabilitationService n'est appelé par aucun
chemin d'affectation, de lancement de tâche ni d'ordonnancement : taskAlerts() est calculé
à la demande pour l'écran et se contente de lister les tâches dont l'opérateur n'est pas
couvert. Une habilitation périmée ne doit jamais arrêter une machine — c'est couvert par un
test dédié (SkillsMatrixTest::a_missing_authorisation_does_not_prevent_assigning_the_task).
AttendanceAggregator apparie les événements bruts en temps travaillé, pour les pointages
badgeuse (attendances, in/out) comme pour l'activité de production (task_activities,
start/end par tâche). L'écran présence et l'export paie s'appuient dessus, donc ils ne
peuvent pas diverger. Un badgeage non refermé, une ouverture en double ou une fermeture
orpheline sont comptés en anomalies plutôt qu'ignorés.
⚠️ Corrigé au passage : Carbon 3 renvoie un écart signé, et le code d'origine appelait$fin->diffInSeconds($debut)— l'écran présence cumulait donc des secondes négatives. Régression couverte parAttendanceReportTest.
- ✅ spatie/laravel-backup en place (
backup:run/backup:clean/backup:monitorplanifiés) - Queue worker → Supervisor sur le VPS client : à confirmer + surveiller (jobs en échec,
queue:failednon vide = fonctionnalité silencieusement cassée côté client) - Vérifier que le cron
schedule:runtourne bien sur l'instance client (sinon : pas de backup, pas de purge RGPD, pas de rapports email auto) - Restauration de backup jamais testée en réel → à faire une fois sur une copie
- Ligne d'annuaire : son ouverture se fait dans l'interface de la plateforme, pas dans WEM — c'est l'identité du client vis-à-vis de sa PDP, et l'y dupliquer n'apporterait rien. WEM se contente de constater son absence et d'alerter. La commande
wem:pdp:directory --open=reste disponible pour un déploiement en masse. factory.electronic_addresssans UI : éditable en base uniquement, contrairement à celui des sociétés clientescompanies.sirennon obligatoire à la saisie :FacturXBuilder::assertPartiesAreIdentifiable()bloque désormais l'émission si SIREN/TVA sont absents ou malformés, mais rien n'empêche encore de créer une société sans ces champs
- Notification d'absence : le workflow de validation existe (voir plus haut), mais aucune
notification n'est émise et
users.supervisor_idne route pas la validation - Absences absentes de la capacité planning :
PlanningControllerne tient compte que des jours fériés, pas des congés validés - Alerte de péremption des habilitations :
expiration_dateest affichée sur la matrice mais aucune notification n'est déclenchée, alors que le patron existe juste à côté (quality:dispatch-calibration-alerts) - Mapping des codes rubrique paie par éditeur (Silae, Sage, Cegid…) : l'export sort un format neutre, la correspondance des codes reste à faire côté client
- Doublons de classes
Attendance:App\Models\Attendance(câblé aux routes) etApp\Models\HumanResources\Attendance(inutilisé) pointent sur la même table, idem pour les deuxAttendanceController. À dédoublonner - Périmètre de droits du module RH : le groupe
human-resourcesn'exige quehas.role(n'importe quel rôle), la permissionhuman-resources-menune masque que le menu. Les routes de congés ajoutées la portent, les routes historiques (fiches salarié, contrats, notes de frais) restent à durcir — voir aussiHumanResourcesController@UpdateUserqui attribue un rôle sans contrôle /pointagepublic : hors groupeauth, sans throttle, avec la liste de tous les salariés dans un select- Sélects dynamiques précommande (client/adresse/contact)
- Accessors Eloquent sans cache (formatted_price, TotalTime, Margin)
- Try/catch sans logging
- Tests métier manquants
- Stock projeté (courbe temporelle physique + PO en attente − réservations) : algo
projectionForProduct(daysHorizon), colonne "Rupture prévue à J+X" sur écran Statut du stock, courbe chart.js sur fiche produit - Bug 3 — CUMP dirty read :
StockCalculationService::recalculateAndPersist()faitlockForUpdatesur SLP puis appellegetCurrentStockMove()en 2 sous-requêtes hors lock → réécrire en un SQL agrégé unique dans le lock - Bug — sorting TOCTOU :
StockLocationProductsController::sorting()faitcanDispatch()sans lock ni transaction, même schéma que le transfert avant fix (voir commit f29bad10) - Bug — transfer.destination race :
StockService::transfer()fait unfirstOrCreatesur la destination sans lock → race possible si deux transferts créent la même paire (stock_locations_id, products_id) simultanément - Inventaire physique : table
inventory_detailscréée en 2021, jamais reliée à un contrôleur ni à une UI,typ_move=1 "Inventories"jamais émis. À activer complètement (comptage, écarts, régularisation) - Champ
stock_location_products.reserve_qtymort : jamais écrit, lu par un paramètreincludeReservetrompeur degetCurrentStockMove. À supprimer (source de vérité =stock_reservations) ou à câbler comme cache dénormalisé - Allocation BOM automatique à la clôture de tâche : aujourd'hui
good-qty-stockdoit être appelé manuellement, pas de décrément auto des composants du BOM - FIFO/FEFO automatique sur consommation tâche :
expiration_datedu batch est stockée mais l'allocation dansTaskStatuController@goodQtyStockboucle sans tri - Quarantaine réception :
storeFromPurchaseOrdermet directement en stock, pas d'étape "zone d'attente / contrôle qualité" avant validation - Alerte réappro auto :
mini_qtysert uniquement au coloriage UI, aucun event / notification / mail déclenché quand stock < mini_qty - Validation qty négative sur autres flux stock :
StoreStockMoveRequestdurci (commit f29bad10) maisUpdateStockMoveRequestreste à vérifier - Retirer StockCurrentApp (composant + mount app.js + endpoint
stockCurrentJson) une fois l'écran Statut du stock validé en usage réel
- password exclu des logs d'activité User
- rgpd:purge passe par RgpdAnonymizationService avant force-delete
- Cache::rememberForever() Status IDs → php artisan cache:clear si renommage
- Registre des traitements
- Mentions légales par client déployé (client 1 concerné depuis avril 2026)
- Durées de conservation documentées par type de donnée
- Ownership QuoteLine (9 méthodes) + OrderLine (5 méthodes)
- Auth sur composants Livewire (QuotesIndex, OrdersIndex, etc.)
- Route guest NC avec UUID
- $fillable Orders nettoyé
- Champs révision restreints aux rôles admin/manager
- whereColumn() sur PurchaseLines
- DB::transaction() sur storeOrder()
- Batch load QuoteLines dans storeOrder()
- Cache::remember() sur 5 requêtes SQL menu
- SelectDataService : 16 méthodes cachées
- Index manquants + invoice_status ajoutés
- OrdersObserver : isDirty() avant Cache::forget()
- Products : Status IDs en rememberForever
- HomeController : eager loading N+1 corrigé
- storeOrder() : batch load avant boucle
- ShouldQueue sur tous les listeners
- Events transportent les modèles complets
- broadcastOn() supprimé (channel-name littéral retiré)
- EventServiceProvider nettoyé
- MenuServiceProvider extrait de AppServiceProvider
- Orders::find()->update() → observers déclenchés
- Vite migré (Laravel Mix supprimé)
- Bootstrap/Tailwind conflit résolu (Tailwind supprimé)
- Vue.js entièrement supprimé
- 80+ composants React dans
resources/js/components/ - Migration Livewire → React :
- CRM : LeadsIndex, OpportunitiesIndex, CompaniesIndex, CompanyForm, CompanyContacts, CompanyAddresses, CompanyTimeline, CompanyDashboard
- Devis : QuotesIndex, QuoteLinesIndex, QuoteLinesPage, QuoteChartsTab
- Commandes : OrdersIndex, OrderLinesIndex, OrderLinesPage
- Achats : PurchasesIndex, PurchaseLinesPage, PurchasesQuotationIndex, PurchasesQuotationShow, PurchasesRequest, PurchaseReceiptIndex, PurchaseReceiptLinesPage, PurchasesWaitingReceipt, PurchasesWaitingInvoice, PurchaseInvoicesIndex
- Livraisons : DeliverysIndex, DeliverysRequest, DeliveryLinesTab
- Facturation : InvoicesIndex, InvoicesRequest, InvoiceExportLines, FecExportLines
- Avoirs : CreditNotesIndex, ReturnsIndex, ReturnShow
- Stocks : StockDetailPage, ProductHistory, ProductsIndex
- Tasks : TasksIndex, TaskManagePage, TaskStatuApp, TaskLines, GtdBoard, TodayView
- Atelier : WorkshopReportsApp (rapports atelier : réalisé pointé, rebuts, charge machine, andon)
- Planning : LoadPlanningIndex, KanbanBoard, KanbanSetting
- GMAO : GmaoDashboard
- Qualité : QualityIndex, NonConformitiesIndex, AuditPlannerApp, InspectionProjectsApp
- Dashboard : HomeDashboard, DashboardGrid + widgets (KPI, OTD, NcStats, MoodTracker, TopClients, SupplierDelay, MonthlyStats, RecentItems, Goal, Announcement, OrdersMonthly, DeliveryBoard, QuoteRate)
- Autres : SerialNumbersIndex, MethodsOverview, EstimatedBudgetsIndex, NestingPage, ConstructionSitePage, GanttChart, DocumentTable, SetupWizard, ProcessDiagramApp, NotificationLinePage, UserProfilePage, UserAutoEmailReportsPage, Whiteboard
- NikoNiko (MoodTrackerWidget), ChartJS, SerialNumber
- SoftDeletes : companies, contacts, addresses, users
- LogsActivity sur Users et Companies
- RgpdAnonymizationService
- Commandes : rgpd:erase-contact, rgpd:export-contact, rgpd:purge
- Rétention activity_log : 365 jours
- Purge hebdomadaire automatique