6.1 KiB
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 :
#[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 :
#[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 :
#[ORM\OneToMany(targetEntity: CustomFieldValue::class, mappedBy: 'machineComponentLink', cascade: ['persist', 'remove'], orphanRemoval: true)]
private Collection $contextFieldValues;
Idem sur MachinePieceLink avec mappedBy: 'machinePieceLink'.
4. Migration
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
CustomFielddu ModelType oùmachineContextOnly = true - Récupérer les
CustomFieldValueliées au lien viamachineComponentLink/machinePieceLink - Ajouter dans la réponse :
{
"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
contextCustomFieldsetcontextCustomFieldValuesen props - Réutilise le composant
CustomFieldDisplay.vueexistant - Mode édition : sur blur/change, appel upsert via
CustomFieldValueControlleravec lemachineComponentLinkIdoumachinePieceLinkId
3. Fiches autonomes pièce/composant
Filtrer les champs machineContextOnly = true pour ne pas les afficher :
- Dans
useEntityCustomFields: exclure ces champs dudisplayedCustomFields - Dans
useMachineDetailCustomFields: séparer les champs normaux des champs contextuels
4. Transformation des données (useMachineDetailCustomFields)
transformComponentCustomFields() et transformCustomFields() :
- Extraire
contextCustomFields/contextCustomFieldValuesdepuis la réponse structure - Les passer en propriétés séparées sur l'objet transformé
Tests
Backend
- Test unitaire :
CustomFieldavecmachineContextOnly = trueest correctement sérialisé - Test API : upsert d'un
CustomFieldValueavecmachineComponentLinkfonctionne - Test API : upsert d'un
CustomFieldValuecontextuel sans lien machine retourne une erreur - Test API :
/api/machines/{id}/structureretourne lescontextCustomFieldsetcontextCustomFieldValues - 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