feat(machines) : ajoute le clonage par catégorie (structure seule)

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.
This commit is contained in:
Matthieu
2026-06-15 11:16:02 +02:00
parent b775718df6
commit 494298f981
5 changed files with 173 additions and 13 deletions
+49 -12
View File
@@ -132,6 +132,14 @@ class MachineStructureController extends AbstractController
return $this->json(['success' => false, 'error' => 'Site introuvable.'], 404);
}
// Clone mode: 'full' copies concrete components/pieces/products; 'structure'
// only keeps the slots' categories (modelType) with empty concrete entities.
$mode = $payload['mode'] ?? 'full';
if (!in_array($mode, ['full', 'structure'], true)) {
return $this->json(['success' => false, 'error' => 'mode invalide (valeurs autorisées : full, structure).'], 400);
}
$structureOnly = 'structure' === $mode;
// Create new machine
$newMachine = new Machine();
$newMachine->setName($payload['name']);
@@ -156,13 +164,13 @@ class MachineStructureController extends AbstractController
$this->cloneCustomFields($source, $newMachine);
// Copy component links (preserving hierarchy)
$componentLinkMap = $this->cloneComponentLinks($source, $newMachine);
$componentLinkMap = $this->cloneComponentLinks($source, $newMachine, $structureOnly);
// Copy piece links
$pieceLinkMap = $this->clonePieceLinks($source, $newMachine, $componentLinkMap);
$pieceLinkMap = $this->clonePieceLinks($source, $newMachine, $componentLinkMap, $structureOnly);
// Copy product links
$this->cloneProductLinks($source, $newMachine, $componentLinkMap, $pieceLinkMap);
$this->cloneProductLinks($source, $newMachine, $componentLinkMap, $pieceLinkMap, $structureOnly);
$this->entityManager->flush();
@@ -215,7 +223,7 @@ class MachineStructureController extends AbstractController
/**
* @return array<string, MachineComponentLink> Map of old link ID → new link
*/
private function cloneComponentLinks(Machine $source, Machine $target): array
private function cloneComponentLinks(Machine $source, Machine $target, bool $structureOnly = false): array
{
$sourceLinks = $this->machineComponentLinkRepository->findBy(['machine' => $source], ['createdAt' => 'ASC']);
$linkMap = [];
@@ -224,6 +232,16 @@ class MachineStructureController extends AbstractController
foreach ($sourceLinks as $link) {
$newLink = new MachineComponentLink();
$newLink->setMachine($target);
if ($structureOnly) {
// Keep only the slot category; leave the concrete component empty.
$newLink->setModelType($link->getModelType() ?? $link->getComposant()?->getTypeComposant());
$this->entityManager->persist($newLink);
$linkMap[$link->getId()] = $newLink;
continue;
}
$newLink->setComposant($link->getComposant());
$newLink->setNameOverride($link->getNameOverride());
$newLink->setReferenceOverride($link->getReferenceOverride());
@@ -259,7 +277,7 @@ class MachineStructureController extends AbstractController
*
* @return array<string, MachinePieceLink> Map of old link ID → new link
*/
private function clonePieceLinks(Machine $source, Machine $target, array $componentLinkMap): array
private function clonePieceLinks(Machine $source, Machine $target, array $componentLinkMap, bool $structureOnly = false): array
{
$sourceLinks = $this->machinePieceLinkRepository->findBy(['machine' => $source], ['createdAt' => 'ASC']);
$linkMap = [];
@@ -267,17 +285,27 @@ class MachineStructureController extends AbstractController
foreach ($sourceLinks as $link) {
$newLink = new MachinePieceLink();
$newLink->setMachine($target);
$newLink->setPiece($link->getPiece());
$newLink->setNameOverride($link->getNameOverride());
$newLink->setReferenceOverride($link->getReferenceOverride());
$newLink->setPrixOverride($link->getPrixOverride());
$newLink->setQuantity($link->getQuantity());
$parent = $link->getParentLink();
if ($parent && isset($componentLinkMap[$parent->getId()])) {
$newLink->setParentLink($componentLinkMap[$parent->getId()]);
}
if ($structureOnly) {
// Keep only the slot category; leave the concrete piece empty.
$newLink->setModelType($link->getModelType() ?? $link->getPiece()?->getTypePiece());
$this->entityManager->persist($newLink);
$linkMap[$link->getId()] = $newLink;
continue;
}
$newLink->setPiece($link->getPiece());
$newLink->setNameOverride($link->getNameOverride());
$newLink->setReferenceOverride($link->getReferenceOverride());
$newLink->setPrixOverride($link->getPrixOverride());
$newLink->setQuantity($link->getQuantity());
$this->entityManager->persist($newLink);
foreach ($link->getContextFieldValues() as $cfv) {
@@ -305,6 +333,7 @@ class MachineStructureController extends AbstractController
Machine $target,
array $componentLinkMap,
array $pieceLinkMap,
bool $structureOnly = false,
): void {
$sourceLinks = $this->machineProductLinkRepository->findBy(['machine' => $source], ['createdAt' => 'ASC']);
$linkMap = [];
@@ -313,7 +342,13 @@ class MachineStructureController extends AbstractController
foreach ($sourceLinks as $link) {
$newLink = new MachineProductLink();
$newLink->setMachine($target);
$newLink->setProduct($link->getProduct());
if ($structureOnly) {
// Keep only the slot category; leave the concrete product empty.
$newLink->setModelType($link->getModelType() ?? $link->getProduct()?->getTypeProduct());
} else {
$newLink->setProduct($link->getProduct());
}
$parentComponent = $link->getParentComponentLink();
if ($parentComponent && isset($componentLinkMap[$parentComponent->getId()])) {
@@ -774,7 +809,7 @@ class MachineStructureController extends AbstractController
$pieces = [];
foreach ($composant->getPieceSlots() as $slot) {
$selectedPiece = $this->ensurePieceExists($slot->getSelectedPiece());
$pieceData = [
$pieceData = [
'slotId' => $slot->getId(),
'typePieceId' => $slot->getTypePiece()?->getId(),
'typePiece' => $this->normalizeModelType($slot->getTypePiece()),
@@ -824,6 +859,7 @@ class MachineStructureController extends AbstractController
if (null === $piece) {
return null;
}
try {
$this->entityManager->initializeObject($piece);
@@ -844,6 +880,7 @@ class MachineStructureController extends AbstractController
if (null === $cf) {
return null;
}
try {
$this->entityManager->initializeObject($cf);