Le SDK Sentry (back) valide le TLS de GlitchTip (logs.malio-dev.fr, certificat
signé par la CA interne MALIO auto-signée). Sans cette CA dans le trust store du
conteneur, le handshake échoue et l'event est silencieusement abandonné
(sentry:test -> "Message not sent"). On installe ca-certificates, on copie la
root CA et on lance update-ca-certificates, comme côté Lesstime.
Le serveur MCP lesstime était redéfini en HTTP (405) ; on s'appuie désormais
sur la conf user globale (~/.claude.json, HTTPS) comme les autres projets.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Complète le branchement Sentry/GlitchTip côté déploiement pour que le front
reçoive son DSN et uploade ses source maps en prod.
- infra/prod/Dockerfile (stage frontend-build) : ARG NUXT_PUBLIC_SENTRY_DSN +
SENTRY_URL/ORG/PROJECT/AUTH_TOKEN, passés en préfixe inline du RUN npm run
generate (pas en ENV → token non persisté ; stage intermédiaire jeté de toute
façon). Vides par défaut => module Sentry inerte, pas d'upload.
- .gitea/workflows/build-docker.yml : --build-arg depuis les secrets Gitea
(INVENTORY_SENTRY_DSN_FRONT, SENTRY_URL, SENTRY_ORG, SENTRY_PROJECT,
SENTRY_AUTH_TOKEN).
- infra/prod/.env.example : documente SENTRY_DSN (back, runtime).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Error tracking centralisé (ticket INFRA #146) : remontée des erreurs back
(Symfony) et front (Nuxt) vers l'instance GlitchTip auto-hébergée.
Backend :
- sentry/sentry-symfony ^5.10, bundle enregistré prod-only
- config/packages/sentry.yaml : handler Monolog niveau ERROR+, ignore les
4xx/AccessDenied, pas d'APM, release = %app.version%
- services.yaml importe version.yaml pour exposer app.version au container
- .env : bloc SENTRY_DSN documenté (vide => SDK inerte)
Frontend :
- @sentry/nuxt ^10.61, module chargé uniquement si NUXT_PUBLIC_SENTRY_DSN défini
- runtimeConfig.public.sentry + source maps (hidden) + options d'upload
- sentry.client.config.ts : init côté client gardée par if (dsn)
DSN vides par défaut : aucune erreur n'est envoyée tant que les projets
GlitchTip inventory-api / inventory-front et leurs DSN ne sont pas configurés
en prod. Aucun secret commité.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Nouveau mode de clonage de machine via le paramètre `mode` de
POST /api/machines/{id}/clone :
- mode "full" (défaut) : comportement inchangé (clone complet)
- mode "structure" : ne recopie que les catégories des slots
(modelType), composant/pièce/produit concrets laissés vides
(slots à compléter), sans overrides ni custom field values
Front : sélecteur de mode dans la page de création de machine,
visible uniquement quand une machine source est choisie.
Le bouton Retour (cb49c69) restaurait l'état des listes via router.back(),
mais le fil d'Ariane faisait des liens en chemin nu (sans ?q=...), ce qui
réinitialisait recherche/tri/pagination en cliquant un crumb de liste depuis
une fiche.
- useListQueryMemory : singleton mémorisant la dernière query vue sur chaque
route-liste (SPA).
- AppBreadcrumb : mémorise la query des routes-listes et la réinjecte dans les
crumbs pointant vers une liste (helper listTo). Couvre composants, pièces,
produits et machines, y compris pages catégorie/création.
Un lien machine_piece_links orphelin (pieceid pointant vers une pièce
supprimée) faisait charger les documents via l'id du lien
(GET /documents/piece/{linkId}) → 404 + toast bloquant, et la catégorie
restait affichée à vide.
- front : useEntityDocuments ne charge plus les documents pour un node
pending (refreshDocuments + ensureDocumentsLoaded) + test
- back : migration Version20260529150000 réparant les 2 FK CASCADE vers
pieces (fk_mpl_piece, fk_cfv_piece) jamais appliquées par
Version20260528090000, avec nettoyage des orphelins (1 mpl + 3 cfv)
- DetailHeader / MachineDetailHeader : le bouton Retour utilise router.back()
(restaure l'URL précédente avec la query ?q=...) avec fallback sur le chemin
nu si pas d'historique applicatif. Corrige la perte de recherche/tri/pagination
au retour depuis une page détail (composants, produits, pièces, machines).
- ManagementView : détecte l'annulation via controller.signal.aborted au lieu de
error.name (ofetch encapsule l'AbortError dans une FetchError), supprimant le
toast d'erreur affiché lors d'une nouvelle recherche.
Deux endroits accèdent à $cfv->getCustomField()->getName() à chaque flush
touchant un CustomFieldValue. Si la CustomField a été supprimée et que la
FK n'est pas en ON DELETE CASCADE, le proxy lève EntityNotFoundException
et fait crasher tout le flush (pas juste une lecture, comme dans le crash
côté MachineStructureController).
- ReferenceAutoGenerator::buildValueMap() : skip le CFV orphelin (la ref
auto retombera proprement sur null via le check requiredFields existant).
- AbstractAuditSubscriber::trackCustomFieldValueChange() : skip l'entrée
d'audit pour ce CFV au lieu de propager l'exception.
Le helper ensureCustomFieldExists (commit af13dc0) appelait $cf->getId()
pour déclencher l'init du proxy, mais sur un proxy Doctrine getId() retourne
directement l'identifiant stocké dans le proxy (la clé utilisée pour le
construire) sans appeler __load(). L'EntityNotFoundException n'était donc
jamais levée dans le helper et le crash sortait quand même sur getName()
ligne 973.
Remplacement par EntityManager::initializeObject() qui appelle __load() et
propage bien l'exception. Même correction appliquée à ensurePieceExists()
dans les deux contrôleurs (le bug y était masqué par la migration FK
CASCADE/SET NULL livrée dans le commit 003e419).
Même pattern que la fix Piece (003e419) : helper ensureCustomFieldExists()
qui force l'init du proxy lazy et catch EntityNotFoundException dans
MachineStructureController::normalizeCustomFieldValues() et
CustomFieldValueController::normalizeCustomFieldValue(). Les CFV pointant
vers un CustomField supprimé sont silencieusement skippés au lieu de
crasher la vue machine entière.
- Migration FK CASCADE/SET NULL pour toutes les FK vers pieces.id (miroir
de la fix Composant) + cleanup des orphelins existants avec audit log
- Helper ensurePieceExists() qui catch EntityNotFoundException dans
MachineStructureController et CustomFieldValueController
- Script SQL standalone scripts/cleanup_orphan_piece_refs.sql pour
nettoyer la prod sans attendre la migration
- Affiche les machines (avec leur site) utilisant la pièce avant la
confirmation de suppression
Le nom d'une machine n'est plus unique globalement mais par site :
deux machines peuvent porter le même nom sur des sites différents,
mais le doublon reste interdit sur un même site.
- Machine : contrainte composite (name, siteId) + UniqueEntity (name, site)
- UniqueConstraintSubscriber : message explicite pour uniq_machine_name_site
- Migration : drop index global sur name + create unique index (name, siteid)
- Front : message d'erreur inline explicite à la création (page + modale)
- Tests : 4 scénarios (sites différents / même site / renommage / déplacement)
Le LEFT JOIN sur telephones causait une erreur PostgreSQL 'column must appear in GROUP BY' parce que Doctrine sélectionnait aussi les colonnes des téléphones joints. EXISTS subquery corrélée évite la duplication de lignes sans introduire de GROUP BY.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Affiche les champs perso machine entre Row 1 (titre/prix) et Row 2 (fournisseur/catalogue) de l'entete ComponentItem et PieceItem.
Badges plus gros (text-sm), visibles en lecture ET en edition. Edition complete reste dans la section depliee.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
En mode creatable, modelValue n'est pas dans options donc selectedOption est null.
Le onMounted ecrasait searchTerm a vide apres que le watch immediate l'avait initialise.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sur les pages d'edition de categorie composant/piece, ajoute un
loadCategory() apres updateModelType + syncExecute pour que la formule
mise a jour par propagateCustomFieldRename soit refletee dans le form
sans avoir a recharger la page.
La regex \w+ ne capturait pas les caracteres accentues (ex. {Diametre}
avec 'è'), le placeholder restait litteral dans la reference auto.
Remplace par [^}]+ avec le flag u/gu cote PHP et JS pour matcher
n'importe quel caractere entre les accolades.
useApi() prepend deja apiBaseUrl (= /api), donc l'appel doit etre
/custom-fields/names et non /api/custom-fields/names (sinon 404 sur
/api/api/custom-fields/names).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Apres chaque save reussi de champs perso (machine via
useMachineCustomFieldDefs, ModelType via useEntityTypes), on invalide
le cache useCustomFieldNameSuggestions pour que les noms nouvellement
crees apparaissent dans les futures autocomplete.
Note : le plan mentionnait ModelTypeForm.vue, mais le save reel se
fait dans useEntityTypes (le composant ne fait qu'emit 'submit').
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>