155 lines
6.1 KiB
Markdown
155 lines
6.1 KiB
Markdown
# Machine Context Custom Fields
|
|
|
|
**Date** : 2026-04-02
|
|
**Statut** : Validé
|
|
|
|
## Objectif
|
|
|
|
Permettre de définir des champs personnalisés sur un ModelType (catégorie de pièce/composant) qui ne s'affichent et ne sont remplissables que lorsque l'item est lié à une machine. Les valeurs sont propres au lien machine (une même pièce dans deux machines peut avoir des valeurs différentes).
|
|
|
|
## Périmètre
|
|
|
|
- **Entités concernées** : Composants et Pièces (pas Produits)
|
|
- **Définition** : Sur le ModelType, avec un flag `machineContextOnly`
|
|
- **Valeurs** : Stockées par lien (`MachineComponentLink` / `MachinePieceLink`)
|
|
- **Affichage** : Uniquement dans la vue machine, pas sur les fiches autonomes
|
|
|
|
## Architecture
|
|
|
|
### Approche retenue
|
|
|
|
Extension des entités existantes `CustomField` et `CustomFieldValue` avec :
|
|
- Un flag de filtrage sur la définition
|
|
- Des FK vers les entités de lien pour les valeurs
|
|
|
|
### Alternatives écartées
|
|
|
|
- **Entités séparées** (`MachineContextField` / `MachineContextFieldValue`) — trop de duplication de logique
|
|
- **JSON sur les liens** — contraire au projet de normalisation JSON→tables en cours
|
|
|
|
## Backend
|
|
|
|
### 1. Entité `CustomField`
|
|
|
|
Nouveau champ :
|
|
|
|
```php
|
|
#[ORM\Column(type: 'boolean', options: ['default' => false])]
|
|
#[Groups(['customField:read', 'customField:write'])]
|
|
private bool $machineContextOnly = false;
|
|
```
|
|
|
|
Getter/setter associés.
|
|
|
|
### 2. Entité `CustomFieldValue`
|
|
|
|
Nouvelles FK nullable :
|
|
|
|
```php
|
|
#[ORM\ManyToOne(targetEntity: MachineComponentLink::class, inversedBy: 'contextFieldValues')]
|
|
#[ORM\JoinColumn(nullable: true)]
|
|
private ?MachineComponentLink $machineComponentLink = null;
|
|
|
|
#[ORM\ManyToOne(targetEntity: MachinePieceLink::class, inversedBy: 'contextFieldValues')]
|
|
#[ORM\JoinColumn(nullable: true)]
|
|
private ?MachinePieceLink $machinePieceLink = null;
|
|
```
|
|
|
|
Contrainte métier : quand `machineComponentLink` est set, `composant` reste aussi set (pour faciliter les queries par composant). Idem pour `machinePieceLink` + `piece`.
|
|
|
|
### 3. Entités `MachineComponentLink` / `MachinePieceLink`
|
|
|
|
Nouvelle collection :
|
|
|
|
```php
|
|
#[ORM\OneToMany(targetEntity: CustomFieldValue::class, mappedBy: 'machineComponentLink', cascade: ['persist', 'remove'], orphanRemoval: true)]
|
|
private Collection $contextFieldValues;
|
|
```
|
|
|
|
Idem sur `MachinePieceLink` avec `mappedBy: 'machinePieceLink'`.
|
|
|
|
### 4. Migration
|
|
|
|
```sql
|
|
ALTER TABLE custom_field ADD machine_context_only BOOLEAN DEFAULT false NOT NULL;
|
|
|
|
ALTER TABLE custom_field_value ADD machine_component_link_id VARCHAR(36) DEFAULT NULL;
|
|
ALTER TABLE custom_field_value ADD machine_piece_link_id VARCHAR(36) DEFAULT NULL;
|
|
|
|
ALTER TABLE custom_field_value
|
|
ADD CONSTRAINT fk_cfv_machine_component_link
|
|
FOREIGN KEY (machine_component_link_id) REFERENCES machine_component_link(id) ON DELETE CASCADE;
|
|
|
|
ALTER TABLE custom_field_value
|
|
ADD CONSTRAINT fk_cfv_machine_piece_link
|
|
FOREIGN KEY (machine_piece_link_id) REFERENCES machine_piece_link(id) ON DELETE CASCADE;
|
|
|
|
CREATE INDEX idx_cfv_machine_component_link ON custom_field_value(machine_component_link_id);
|
|
CREATE INDEX idx_cfv_machine_piece_link ON custom_field_value(machine_piece_link_id);
|
|
```
|
|
|
|
### 5. `MachineStructureController`
|
|
|
|
Dans `normalizeComposant()` et `normalizePiece()` :
|
|
- Récupérer les `CustomField` du ModelType où `machineContextOnly = true`
|
|
- Récupérer les `CustomFieldValue` liées au lien via `machineComponentLink` / `machinePieceLink`
|
|
- Ajouter dans la réponse :
|
|
|
|
```json
|
|
{
|
|
"contextCustomFields": [{ "id": "...", "name": "...", "type": "...", ... }],
|
|
"contextCustomFieldValues": [{ "id": "...", "value": "...", "customField": {...} }]
|
|
}
|
|
```
|
|
|
|
Séparé des `customFields` / `customFieldValues` globaux existants.
|
|
|
|
### 6. `CustomFieldValueController`
|
|
|
|
L'upsert existant est étendu pour accepter `machineComponentLink` ou `machinePieceLink` dans le body. Le controller vérifie que si le `CustomField` a `machineContextOnly = true`, un lien machine est obligatoire.
|
|
|
|
### 7. Clonage machine
|
|
|
|
`MachineStructureController::cloneCustomFields()` doit aussi cloner les `contextFieldValues` des liens, en les rattachant aux nouveaux liens créés lors du clone.
|
|
|
|
## Frontend
|
|
|
|
### 1. Page ModelType — Définition des champs
|
|
|
|
Dans l'UI d'édition des custom fields d'un ModelType, ajouter un **toggle/checkbox** "Contexte machine uniquement" sur chaque définition de champ. Cela set `machineContextOnly: true` lors de la sauvegarde.
|
|
|
|
Concerne les custom fields des catégories COMPONENT et PIECE (pas PRODUCT, hors périmètre).
|
|
|
|
### 2. Vue machine — `ComponentItem.vue` / `PieceItem.vue`
|
|
|
|
Nouvelle section "Champs contextuels" affichée sous les custom fields existants :
|
|
- Reçoit `contextCustomFields` et `contextCustomFieldValues` en props
|
|
- Réutilise le composant `CustomFieldDisplay.vue` existant
|
|
- Mode édition : sur blur/change, appel upsert via `CustomFieldValueController` avec le `machineComponentLinkId` ou `machinePieceLinkId`
|
|
|
|
### 3. Fiches autonomes pièce/composant
|
|
|
|
Filtrer les champs `machineContextOnly = true` pour ne pas les afficher :
|
|
- Dans `useEntityCustomFields` : exclure ces champs du `displayedCustomFields`
|
|
- Dans `useMachineDetailCustomFields` : séparer les champs normaux des champs contextuels
|
|
|
|
### 4. Transformation des données (`useMachineDetailCustomFields`)
|
|
|
|
`transformComponentCustomFields()` et `transformCustomFields()` :
|
|
- Extraire `contextCustomFields` / `contextCustomFieldValues` depuis la réponse structure
|
|
- Les passer en propriétés séparées sur l'objet transformé
|
|
|
|
## Tests
|
|
|
|
### Backend
|
|
- Test unitaire : `CustomField` avec `machineContextOnly = true` est correctement sérialisé
|
|
- Test API : upsert d'un `CustomFieldValue` avec `machineComponentLink` fonctionne
|
|
- Test API : upsert d'un `CustomFieldValue` contextuel sans lien machine retourne une erreur
|
|
- Test API : `/api/machines/{id}/structure` retourne les `contextCustomFields` et `contextCustomFieldValues`
|
|
- Test API : clone machine copie les valeurs contextuelles
|
|
|
|
### Frontend
|
|
- Typecheck : 0 erreurs après modifications
|
|
- Vérification manuelle : les champs contextuels apparaissent dans la vue machine
|
|
- Vérification manuelle : les champs contextuels n'apparaissent pas sur les fiches autonomes
|