# Machine : Bouton Save Unique + Versioning des Liens

**Date :** 2026-03-26
**Statut :** Approuvé

## Contexte

La page machine utilise actuellement un auto-save au blur pour chaque champ (info, custom fields, constructeurs). Les pages composant/pièce/produit utilisent un bouton unique "Enregistrer les modifications" en bas du formulaire. L'objectif est d'aligner la page machine sur ce pattern.

De plus, les ajouts/suppressions de liens composant/pièce/produit sur une machine ne sont pas tracés dans le versioning. Ils doivent l'être.

## Volet 1 : Bouton Save Unique

### Comportement cible

- En mode édition, tous les champs (info machine, custom field values, custom field definitions, constructeurs) sont modifiés localement sans appel API.
- Un bouton "Enregistrer les modifications" en bas du formulaire sauvegarde tout d'un coup.
- Un bouton "Annuler" réinitialise l'état local et sort du mode édition.
- Les documents restent en upload/suppression immédiate (inchangé).
- Les ajouts/suppressions de liens composant/pièce/produit restent immédiats via modales (inchangé).

### Changements frontend

#### MachineInfoCard.vue
- Supprimer les `@blur` → `$emit('blur-field')` sur les inputs (nom, référence)
- Supprimer le `@change` qui émet `blur-field` sur le select site
- Supprimer les `@blur` → `$emit('update-custom-field', field)` sur tous les champs custom
- Conserver `@input` / `@update:*` / `set-custom-field-value` pour la mise à jour de l'état local
- Le `MachineCustomFieldDefEditor` perd son bouton save propre : l'état est collecté au submit global

#### machine/[id].vue
- Supprimer le handler `@blur-field`
- Supprimer le handler `@update-custom-field`
- `@update:constructeur-ids` met à jour l'état local sans save
- Ajouter le bloc boutons en bas (pattern identique à component/[id]/index.vue) :
  - "Annuler" (btn-ghost) → `cancelEdition()` : réinitialise depuis `machine.value` + sort du mode édition
  - "Enregistrer les modifications" (btn-primary, disabled si `!canSubmit`) → `submitEdition()`

#### useMachineDetailData.ts
- Exposer `saving` ref
- Exposer `submitEdition()` :
  1. `updateMachineInfo()` — PATCH machine (nom, ref, site, constructeurs)
  2. Batch save custom field values (tous les `visibleMachineCustomFields` avec valeur)
  3. Save custom field definitions si modifiées (`fieldDefs.saveDefinitions()`)
  4. `loadMachineData()` pour recharger
  5. Sortie du mode édition + toast succès
- Exposer `cancelEdition()` :
  1. `initMachineFields()` — réinitialise nom, ref, site, constructeurs depuis `machine.value`
  2. `syncMachineCustomFields()` — réinitialise les custom fields
  3. Sort du mode édition

#### useMachineDetailUpdates.ts
- `handleMachineConstructeurChange` ne déclenche plus `updateMachineInfo()`, met juste à jour le ref local

#### useMachineDetailCustomFields.ts
- `updateMachineCustomField` n'est plus appelé au blur — sera appelé en batch par `submitEdition()`
- Ajouter méthode `saveAllMachineCustomFields()` qui itère sur les champs visibles et sauvegarde ceux avec valeur

### Validation (`canSubmit`)
- Machine existe
- Nom non vide
- Pas en cours de sauvegarde (`!saving.value`)
- `canEdit` est true

## Volet 2 : Versioning des Liens Machine

### Comportement cible

Quand un composant, pièce ou produit est ajouté ou supprimé d'une machine, cela doit :
1. Incrémenter la `version` de la Machine
2. Créer une entrée `AuditLog` avec diff et snapshot

### Changements backend

#### MachineAuditSubscriber — enrichir le snapshot
Ajouter au snapshot machine les liens :
```php
'componentLinks' => array_map(fn($link) => [
    'id' => $link->getId(),
    'composantId' => $link->getComposant()->getId(),
    'composantName' => $link->getComposant()->getName(),
], $entity->getComponentLinks()->toArray()),
'pieceLinks' => [...],
'productLinks' => [...],
```

#### Nouveau subscriber ou service : MachineLinkAuditService
Écouter les événements Doctrine `postPersist` et `postRemove` sur les 3 entités link.
Quand un lien est créé/supprimé :
1. Récupérer la Machine parente
2. Incrémenter `$machine->incrementVersion()`
3. Créer un `AuditLog` :
   - `entityType: 'machine'`
   - `entityId: $machine->getId()`
   - `action: 'update'`
   - `diff: { addedComponent: {id, name} }` ou `{ removedPiece: {id, name} }`
   - `snapshot:` snapshot complet de la machine (avec liens mis à jour)
   - `version:` nouvelle version

### Labels pour le diff (frontend)
Ajouter au `historyFieldLabels` de la page machine :
```js
addedComponent: 'Composant ajouté',
removedComponent: 'Composant supprimé',
addedPiece: 'Pièce ajoutée',
removedPiece: 'Pièce supprimée',
addedProduct: 'Produit ajouté',
removedProduct: 'Produit supprimé',
```

## Ce qui ne change PAS

- Upload/suppression de documents (immédiat)
- Pattern read/edit toggle dans le header
- L'affichage des sections composants/pièces/produits
- Les modales d'ajout/suppression de liens (restent immédiates)
- Le versioning des autres entités (composant, pièce, produit)
