The ComposantProcessor now reads the structure payload from the frontend and applies selectedPieceId/selectedProductId/selectedComponentId to the scaffolded slots, so user selections are actually saved to the database. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
181 lines
6.2 KiB
PHP
181 lines
6.2 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\State;
|
|
|
|
use ApiPlatform\Metadata\Operation;
|
|
use ApiPlatform\Metadata\Post;
|
|
use ApiPlatform\State\ProcessorInterface;
|
|
use App\Entity\Composant;
|
|
use App\Entity\ComposantPieceSlot;
|
|
use App\Entity\ComposantProductSlot;
|
|
use App\Entity\ComposantSubcomponentSlot;
|
|
use App\Entity\ModelType;
|
|
use App\Entity\Piece;
|
|
use App\Entity\Product;
|
|
use Doctrine\ORM\EntityManagerInterface;
|
|
use Symfony\Component\DependencyInjection\Attribute\Autowire;
|
|
|
|
/**
|
|
* Initializes skeleton slots when a Composant is created with a typeComposant,
|
|
* and applies the piece/product/subcomponent selections from the frontend payload.
|
|
*/
|
|
final class ComposantProcessor implements ProcessorInterface
|
|
{
|
|
public function __construct(
|
|
#[Autowire(service: 'api_platform.doctrine.orm.state.persist_processor')]
|
|
private readonly ProcessorInterface $decorated,
|
|
private readonly EntityManagerInterface $entityManager,
|
|
) {}
|
|
|
|
/**
|
|
* @param array<string, mixed> $uriVariables
|
|
* @param array<string, mixed> $context
|
|
*/
|
|
public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []): mixed
|
|
{
|
|
if (!$data instanceof Composant) {
|
|
return $this->decorated->process($data, $operation, $uriVariables, $context);
|
|
}
|
|
|
|
$pendingStructure = $data->getPendingStructure();
|
|
|
|
// Persist the entity first
|
|
$result = $this->decorated->process($data, $operation, $uriVariables, $context);
|
|
|
|
// On creation, scaffold slots from the ModelType skeleton + apply selections
|
|
if ($operation instanceof Post) {
|
|
$this->initializeSlotsFromSkeleton($data, $pendingStructure);
|
|
$data->clearPendingStructure();
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
private function initializeSlotsFromSkeleton(Composant $composant, ?array $structure): void
|
|
{
|
|
$modelType = $composant->getTypeComposant();
|
|
|
|
if (!$modelType) {
|
|
return;
|
|
}
|
|
|
|
$modelType = $this->entityManager->getRepository(ModelType::class)->find($modelType->getId());
|
|
|
|
if (!$modelType) {
|
|
return;
|
|
}
|
|
|
|
// Index selections from the frontend payload by typePieceId/typeProductId/typeComposantId
|
|
$pieceSelections = $this->indexSelections($structure['pieces'] ?? [], 'typePieceId', 'selectedPieceId');
|
|
$productSelections = $this->indexSelections($structure['products'] ?? [], 'typeProductId', 'selectedProductId');
|
|
$subSelections = $this->indexSelections($structure['subcomponents'] ?? [], 'typeComposantId', 'selectedComponentId');
|
|
|
|
$hasNewSlots = false;
|
|
|
|
// Piece slots
|
|
foreach ($modelType->getSkeletonPieceRequirements() as $req) {
|
|
$typePieceId = $req->getTypePiece()->getId();
|
|
$slot = new ComposantPieceSlot();
|
|
$slot->setComposant($composant);
|
|
$slot->setTypePiece($req->getTypePiece());
|
|
$slot->setPosition($req->getPosition());
|
|
|
|
$selectedId = $this->shiftSelection($pieceSelections, $typePieceId);
|
|
if ($selectedId) {
|
|
$slot->setSelectedPiece($this->entityManager->getReference(Piece::class, $selectedId));
|
|
}
|
|
|
|
$this->entityManager->persist($slot);
|
|
$hasNewSlots = true;
|
|
}
|
|
|
|
// Product slots
|
|
foreach ($modelType->getSkeletonProductRequirements() as $req) {
|
|
$typeProductId = $req->getTypeProduct()->getId();
|
|
$slot = new ComposantProductSlot();
|
|
$slot->setComposant($composant);
|
|
$slot->setTypeProduct($req->getTypeProduct());
|
|
$slot->setFamilyCode($req->getFamilyCode());
|
|
$slot->setPosition($req->getPosition());
|
|
|
|
$selectedId = $this->shiftSelection($productSelections, $typeProductId);
|
|
if ($selectedId) {
|
|
$slot->setSelectedProduct($this->entityManager->getReference(Product::class, $selectedId));
|
|
}
|
|
|
|
$this->entityManager->persist($slot);
|
|
$hasNewSlots = true;
|
|
}
|
|
|
|
// Subcomponent slots
|
|
foreach ($modelType->getSkeletonSubcomponentRequirements() as $req) {
|
|
$typeComposantId = $req->getTypeComposant()?->getId() ?? '';
|
|
$slot = new ComposantSubcomponentSlot();
|
|
$slot->setComposant($composant);
|
|
$slot->setAlias($req->getAlias());
|
|
$slot->setFamilyCode($req->getFamilyCode());
|
|
$slot->setTypeComposant($req->getTypeComposant());
|
|
$slot->setPosition($req->getPosition());
|
|
|
|
$selectedId = $this->shiftSelection($subSelections, $typeComposantId);
|
|
if ($selectedId) {
|
|
$slot->setSelectedComposant($this->entityManager->getReference(Composant::class, $selectedId));
|
|
}
|
|
|
|
$this->entityManager->persist($slot);
|
|
$hasNewSlots = true;
|
|
}
|
|
|
|
if ($hasNewSlots) {
|
|
$this->entityManager->flush();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Build an indexed map of typeId → [selectedId, selectedId, ...] from the payload.
|
|
* Supports both flat format and nested definition format from the frontend.
|
|
*
|
|
* @return array<string, list<string>>
|
|
*/
|
|
private function indexSelections(array $items, string $typeKey, string $selectionKey): array
|
|
{
|
|
$map = [];
|
|
|
|
foreach ($items as $item) {
|
|
if (!is_array($item)) {
|
|
continue;
|
|
}
|
|
|
|
// The typeId can be at top level or inside definition
|
|
$typeId = $item['definition'][$typeKey]
|
|
?? $item[$typeKey]
|
|
?? null;
|
|
$selectedId = $item[$selectionKey] ?? null;
|
|
|
|
if (!is_string($typeId) || '' === $typeId) {
|
|
continue;
|
|
}
|
|
|
|
if (is_string($selectedId) && '' !== $selectedId) {
|
|
$map[$typeId][] = $selectedId;
|
|
}
|
|
}
|
|
|
|
return $map;
|
|
}
|
|
|
|
/**
|
|
* Pop the first selection for a given typeId (handles multiple slots of the same type).
|
|
*/
|
|
private function shiftSelection(array &$selections, string $typeId): ?string
|
|
{
|
|
if (empty($selections[$typeId])) {
|
|
return null;
|
|
}
|
|
|
|
return array_shift($selections[$typeId]);
|
|
}
|
|
}
|