docs(custom-fields) : corrige la source de verite (table custom_fields unique)

L'investigation initiale supposait des customFields JSON dans les
skeleton_*_requirements ; en realite SkeletonStructureService traduit
les customFields du payload ModelType en entites CustomField stockees
dans la table custom_fields. Le SQL est donc un simple SELECT DISTINCT.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Matthieu
2026-05-11 13:33:06 +02:00
parent 5e8e7947f0
commit 73c06169f3
2 changed files with 943 additions and 32 deletions

View File

@@ -6,9 +6,10 @@
## Contexte et problème
Aujourd'hui dans Inventory, on définit des "champs personnalisés" (custom fields) à plusieurs endroits :
- Au niveau d'une machine (entité `CustomField` avec FK `machineId`)
- Au niveau d'un ModelType (entité `CustomField` avec FK `typeComposantId` / `typePieceId` / `typeProductId`)
- Au niveau des skeleton requirements d'un ModelType (JSON `customFields` dans `SkeletonPieceRequirement`, `SkeletonSubcomponentRequirement`, `SkeletonProductRequirement`)
- Au niveau d'une **machine** (entité `CustomField` avec FK `machineId`)
- Au niveau d'un **ModelType**, dans 3 contextes (composant / pièce / produit) : entité `CustomField` avec respectivement `typeComposantId`, `typePieceId`, `typeProductId`.
Côté frontend, l'éditeur de structure d'un ModelType expose des `customFields` array sur chaque node, mais lors du save le backend (`SkeletonStructureService::updateCustomFields`) traduit ça en entités `CustomField` persistées dans la table unique `custom_fields`. La table `custom_fields` est donc **l'unique source de vérité** pour tous les noms de champs perso de l'application.
À chaque création/modification, l'utilisateur saisit librement un **nom** dans un `<input>` texte. Conséquence : les mêmes concepts métier finissent écrits différemment (« Numéro de série », « N° série », « Num serie »), ce qui empêche toute uniformisation et complique les rapports/recherches.
@@ -33,20 +34,21 @@ GET /api/custom-fields/names ◄── useCustomFieldNameSuggestions()
│ │ (cache module-level)
│ returns: ["Numéro...", ...] │
▼ ▼
Union SQL : CustomFieldNameInput.vue (wrapper)
custom_fields
+ skeleton_piece_requirements │ utilise
+ skeleton_subcomponent_requirements│
+ skeleton_product_requirements ▼
SELECT DISTINCT name CustomFieldNameInput.vue (wrapper)
FROM custom_fields │
│ utilise
SearchSelect.vue (creatable=true)
│ utilisé par
┌───────────────┼───────────────┐
│ │ │
MachineCustomFieldDefEditor│ StructureNodeEditor
PieceModelStructureEditor
┌───────────────┼─────────────────────
│ │
MachineCustomFieldDef- StructureNodeEditor PieceModelStructure-
Editor (composants) Editor (pièces)
MachineCustomFieldsCard
(édition inline d'une machine)
```
## Backend
@@ -70,29 +72,12 @@ Union SQL : CustomFieldNameInput.vue (wrapper)
Le controller exécute une seule requête SQL brute via `Doctrine\DBAL\Connection` :
```sql
SELECT DISTINCT name FROM (
SELECT name FROM custom_fields
UNION
SELECT jsonb_array_elements(customfields)->>'name' AS name
FROM skeleton_piece_requirements
WHERE customfields IS NOT NULL
UNION
SELECT jsonb_array_elements(customfields)->>'name' AS name
FROM skeleton_subcomponent_requirements
WHERE customfields IS NOT NULL
UNION
SELECT jsonb_array_elements(customfields)->>'name' AS name
FROM skeleton_product_requirements
WHERE customfields IS NOT NULL
) AS all_names
SELECT DISTINCT name FROM custom_fields
WHERE name IS NOT NULL AND name <> ''
ORDER BY name ASC
```
**À vérifier au moment de l'implémentation** :
- Le nom exact de la colonne JSON `customfields` en lowercase dans PostgreSQL (Doctrine = camelCase, PG = lowercase).
- Le nom exact des tables `skeleton_*_requirements` (à confirmer dans les migrations).
- Le type de la colonne (JSON vs JSONB) — `jsonb_array_elements` ne fonctionne que sur JSONB ; pour JSON utiliser `json_array_elements`.
> Toutes les sources de noms (machines, ModelType×composant/pièce/produit) convergent dans la même table `custom_fields` via les FKs `machineId`/`typeComposantId`/`typePieceId`/`typeProductId`. Pas de jointure ni de parsing JSON nécessaire — un simple `SELECT DISTINCT` suffit.
### Pas de cache HTTP