- list_slots + update_slots for composant/piece slots - list/add/update/remove machine links (component, piece, product) - get_machine_structure with full hierarchy - clone_machine with all links and custom fields - 52 MCP tests pass total Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
224 lines
8.0 KiB
PHP
224 lines
8.0 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Mcp\Tool\Machine;
|
|
|
|
use App\Entity\CustomField;
|
|
use App\Entity\CustomFieldValue;
|
|
use App\Entity\Machine;
|
|
use App\Entity\MachineComponentLink;
|
|
use App\Entity\MachinePieceLink;
|
|
use App\Entity\MachineProductLink;
|
|
use App\Entity\Site;
|
|
use App\Mcp\Tool\McpToolHelper;
|
|
use App\Repository\MachineComponentLinkRepository;
|
|
use App\Repository\MachinePieceLinkRepository;
|
|
use App\Repository\MachineProductLinkRepository;
|
|
use App\Repository\MachineRepository;
|
|
use Doctrine\ORM\EntityManagerInterface;
|
|
use Mcp\Capability\Attribute\McpTool;
|
|
use Symfony\Bundle\SecurityBundle\Security;
|
|
|
|
#[McpTool(
|
|
name: 'clone_machine',
|
|
description: 'Clone an existing machine with all its links (components, pieces, products), custom fields, and constructeurs. Requires ROLE_GESTIONNAIRE.',
|
|
)]
|
|
class CloneMachineTool
|
|
{
|
|
use McpToolHelper;
|
|
|
|
public function __construct(
|
|
private readonly EntityManagerInterface $entityManager,
|
|
private readonly MachineRepository $machineRepository,
|
|
private readonly MachineComponentLinkRepository $machineComponentLinkRepository,
|
|
private readonly MachinePieceLinkRepository $machinePieceLinkRepository,
|
|
private readonly MachineProductLinkRepository $machineProductLinkRepository,
|
|
private readonly Security $security,
|
|
) {}
|
|
|
|
public function __invoke(
|
|
string $machineId,
|
|
string $name,
|
|
string $siteId,
|
|
string $reference = '',
|
|
): array {
|
|
$this->requireRole($this->security, 'ROLE_GESTIONNAIRE');
|
|
|
|
$source = $this->machineRepository->find($machineId);
|
|
if (!$source instanceof Machine) {
|
|
$this->mcpError('not_found', "Machine not found: {$machineId}");
|
|
}
|
|
|
|
$site = $this->entityManager->getRepository(Site::class)->find($siteId);
|
|
if (!$site) {
|
|
$this->mcpError('not_found', "Site not found: {$siteId}");
|
|
}
|
|
|
|
// Create new machine
|
|
$newMachine = new Machine();
|
|
$newMachine->setName($name);
|
|
$newMachine->setSite($site);
|
|
if ('' !== $reference) {
|
|
$newMachine->setReference($reference);
|
|
}
|
|
$newMachine->setPrix($source->getPrix());
|
|
|
|
// Copy constructeurs
|
|
foreach ($source->getConstructeurs() as $constructeur) {
|
|
$newMachine->getConstructeurs()->add($constructeur);
|
|
}
|
|
|
|
$this->entityManager->persist($newMachine);
|
|
|
|
// Copy custom fields and values
|
|
$this->cloneCustomFields($source, $newMachine);
|
|
|
|
// Copy component links (preserving hierarchy with two-pass)
|
|
$componentLinkMap = $this->cloneComponentLinks($source, $newMachine);
|
|
|
|
// Copy piece links
|
|
$pieceLinkMap = $this->clonePieceLinks($source, $newMachine, $componentLinkMap);
|
|
|
|
// Copy product links
|
|
$this->cloneProductLinks($source, $newMachine, $componentLinkMap, $pieceLinkMap);
|
|
|
|
$this->entityManager->flush();
|
|
|
|
return $this->jsonResponse([
|
|
'id' => $newMachine->getId(),
|
|
'name' => $newMachine->getName(),
|
|
'reference' => $newMachine->getReference(),
|
|
'siteId' => $site->getId(),
|
|
'clonedFrom' => $source->getId(),
|
|
]);
|
|
}
|
|
|
|
private function cloneCustomFields(Machine $source, Machine $target): void
|
|
{
|
|
foreach ($source->getCustomFields() as $cf) {
|
|
$newCf = new CustomField();
|
|
$newCf->setName($cf->getName());
|
|
$newCf->setType($cf->getType());
|
|
$newCf->setRequired($cf->isRequired());
|
|
$newCf->setDefaultValue($cf->getDefaultValue());
|
|
$newCf->setOptions($cf->getOptions());
|
|
$newCf->setOrderIndex($cf->getOrderIndex());
|
|
$newCf->setMachine($target);
|
|
$this->entityManager->persist($newCf);
|
|
}
|
|
|
|
foreach ($source->getCustomFieldValues() as $cfv) {
|
|
$newValue = new CustomFieldValue();
|
|
$newValue->setMachine($target);
|
|
$newValue->setCustomField($cfv->getCustomField());
|
|
$newValue->setValue($cfv->getValue());
|
|
$this->entityManager->persist($newValue);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @return array<string, MachineComponentLink> Map of old link ID to new link
|
|
*/
|
|
private function cloneComponentLinks(Machine $source, Machine $target): array
|
|
{
|
|
$sourceLinks = $this->machineComponentLinkRepository->findBy(['machine' => $source]);
|
|
$linkMap = [];
|
|
|
|
// First pass: create all links without parent relationships
|
|
foreach ($sourceLinks as $link) {
|
|
$newLink = new MachineComponentLink();
|
|
$newLink->setMachine($target);
|
|
$newLink->setComposant($link->getComposant());
|
|
$newLink->setNameOverride($link->getNameOverride());
|
|
$newLink->setReferenceOverride($link->getReferenceOverride());
|
|
$newLink->setPrixOverride($link->getPrixOverride());
|
|
$this->entityManager->persist($newLink);
|
|
$linkMap[$link->getId()] = $newLink;
|
|
}
|
|
|
|
// Second pass: set parent relationships
|
|
foreach ($sourceLinks as $link) {
|
|
$parent = $link->getParentLink();
|
|
if ($parent && isset($linkMap[$parent->getId()])) {
|
|
$linkMap[$link->getId()]->setParentLink($linkMap[$parent->getId()]);
|
|
}
|
|
}
|
|
|
|
return $linkMap;
|
|
}
|
|
|
|
/**
|
|
* @param array<string, MachineComponentLink> $componentLinkMap
|
|
*
|
|
* @return array<string, MachinePieceLink> Map of old link ID to new link
|
|
*/
|
|
private function clonePieceLinks(Machine $source, Machine $target, array $componentLinkMap): array
|
|
{
|
|
$sourceLinks = $this->machinePieceLinkRepository->findBy(['machine' => $source]);
|
|
$linkMap = [];
|
|
|
|
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()]);
|
|
}
|
|
|
|
$this->entityManager->persist($newLink);
|
|
$linkMap[$link->getId()] = $newLink;
|
|
}
|
|
|
|
return $linkMap;
|
|
}
|
|
|
|
/**
|
|
* @param array<string, MachineComponentLink> $componentLinkMap
|
|
* @param array<string, MachinePieceLink> $pieceLinkMap
|
|
*/
|
|
private function cloneProductLinks(
|
|
Machine $source,
|
|
Machine $target,
|
|
array $componentLinkMap,
|
|
array $pieceLinkMap,
|
|
): void {
|
|
$sourceLinks = $this->machineProductLinkRepository->findBy(['machine' => $source]);
|
|
$linkMap = [];
|
|
|
|
// First pass: create all links
|
|
foreach ($sourceLinks as $link) {
|
|
$newLink = new MachineProductLink();
|
|
$newLink->setMachine($target);
|
|
$newLink->setProduct($link->getProduct());
|
|
|
|
$parentComponent = $link->getParentComponentLink();
|
|
if ($parentComponent && isset($componentLinkMap[$parentComponent->getId()])) {
|
|
$newLink->setParentComponentLink($componentLinkMap[$parentComponent->getId()]);
|
|
}
|
|
|
|
$parentPiece = $link->getParentPieceLink();
|
|
if ($parentPiece && isset($pieceLinkMap[$parentPiece->getId()])) {
|
|
$newLink->setParentPieceLink($pieceLinkMap[$parentPiece->getId()]);
|
|
}
|
|
|
|
$this->entityManager->persist($newLink);
|
|
$linkMap[$link->getId()] = $newLink;
|
|
}
|
|
|
|
// Second pass: set parent product link relationships
|
|
foreach ($sourceLinks as $link) {
|
|
$parent = $link->getParentLink();
|
|
if ($parent && isset($linkMap[$parent->getId()])) {
|
|
$linkMap[$link->getId()]->setParentLink($linkMap[$parent->getId()]);
|
|
}
|
|
}
|
|
}
|
|
}
|