feat(reference-auto) : extend auto-reference to composants + formula builder UI

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Matthieu
2026-03-31 09:59:42 +02:00
parent 3f6ce153bb
commit 03c2451990
6 changed files with 186 additions and 33 deletions

View File

@@ -64,6 +64,10 @@ class Composant
#[Groups(['composant:read'])]
private ?string $reference = null;
#[ORM\Column(type: Types::STRING, length: 255, nullable: true)]
#[Groups(['composant:read'])]
private ?string $referenceAuto = null;
#[ORM\Column(type: Types::TEXT, nullable: true)]
#[Groups(['composant:read'])]
private ?string $description = null;
@@ -192,6 +196,21 @@ class Composant
return $this;
}
public function getReferenceAuto(): ?string
{
return $this->referenceAuto;
}
/**
* @internal used by ReferenceAutoSubscriber only — not part of the public API
*/
public function setReferenceAuto(?string $referenceAuto): static
{
$this->referenceAuto = $referenceAuto;
return $this;
}
public function getDescription(): ?string
{
return $this->description;
@@ -364,35 +383,41 @@ class Composant
{
$pieces = [];
foreach ($this->pieceSlots as $slot) {
$pieces[] = [
'slotId' => $slot->getId(),
'typePieceId' => $slot->getTypePiece()?->getId(),
'selectedPieceId' => $slot->getSelectedPiece()?->getId(),
'quantity' => $slot->getQuantity(),
'position' => $slot->getPosition(),
$selectedPiece = $slot->getSelectedPiece();
$pieces[] = [
'slotId' => $slot->getId(),
'typePieceId' => $slot->getTypePiece()?->getId(),
'selectedPieceId' => $selectedPiece?->getId(),
'selectedPieceName' => $selectedPiece?->getName(),
'quantity' => $slot->getQuantity(),
'position' => $slot->getPosition(),
];
}
$products = [];
foreach ($this->productSlots as $slot) {
$products[] = [
'slotId' => $slot->getId(),
'typeProductId' => $slot->getTypeProduct()?->getId(),
'selectedProductId' => $slot->getSelectedProduct()?->getId(),
'familyCode' => $slot->getFamilyCode(),
'position' => $slot->getPosition(),
$selectedProduct = $slot->getSelectedProduct();
$products[] = [
'slotId' => $slot->getId(),
'typeProductId' => $slot->getTypeProduct()?->getId(),
'selectedProductId' => $selectedProduct?->getId(),
'selectedProductName' => $selectedProduct?->getName(),
'familyCode' => $slot->getFamilyCode(),
'position' => $slot->getPosition(),
];
}
$subcomponents = [];
foreach ($this->subcomponentSlots as $slot) {
$subcomponents[] = [
'slotId' => $slot->getId(),
'alias' => $slot->getAlias(),
'familyCode' => $slot->getFamilyCode(),
'typeComposantId' => $slot->getTypeComposant()?->getId(),
'selectedComponentId' => $slot->getSelectedComposant()?->getId(),
'position' => $slot->getPosition(),
$selectedComposant = $slot->getSelectedComposant();
$subcomponents[] = [
'slotId' => $slot->getId(),
'alias' => $slot->getAlias(),
'familyCode' => $slot->getFamilyCode(),
'typeComposantId' => $slot->getTypeComposant()?->getId(),
'selectedComponentId' => $selectedComposant?->getId(),
'selectedComponentName' => $selectedComposant?->getName(),
'position' => $slot->getPosition(),
];
}

View File

@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace App\EventSubscriber;
use App\Entity\Composant;
use App\Entity\CustomFieldValue;
use App\Entity\Piece;
use App\Service\ReferenceAutoGenerator;
@@ -21,56 +22,94 @@ final class ReferenceAutoSubscriber
$em = $args->getObjectManager();
$uow = $em->getUnitOfWork();
/** @var array<string, Piece> */
$piecesToRecalculate = [];
/** @var array<string, Composant> */
$composantsToRecalculate = [];
foreach ($uow->getScheduledEntityInsertions() as $entity) {
if ($entity instanceof Piece) {
$piecesToRecalculate[$entity->getId()] = $entity;
} elseif ($entity instanceof Composant) {
$composantsToRecalculate[$entity->getId()] = $entity;
}
}
foreach ($uow->getScheduledEntityUpdates() as $entity) {
if ($entity instanceof Piece) {
$piecesToRecalculate[$entity->getId()] = $entity;
} elseif ($entity instanceof Composant) {
$composantsToRecalculate[$entity->getId()] = $entity;
}
}
// For CFV insertions: the new CFV is not yet in the DB, so Piece's lazy-loaded
// For CFV insertions: the new CFV is not yet in the DB, so the lazy-loaded
// collection won't contain it. We must add it manually so the generator sees it.
foreach ($uow->getScheduledEntityInsertions() as $entity) {
if ($entity instanceof CustomFieldValue && $entity->getPiece()) {
if (!$entity instanceof CustomFieldValue) {
continue;
}
if ($entity->getPiece()) {
$piece = $entity->getPiece();
if (!$piece->getCustomFieldValues()->contains($entity)) {
$piece->getCustomFieldValues()->add($entity);
}
$piecesToRecalculate[$piece->getId()] = $piece;
} elseif ($entity->getComposant()) {
$composant = $entity->getComposant();
if (!$composant->getCustomFieldValues()->contains($entity)) {
$composant->getCustomFieldValues()->add($entity);
}
$composantsToRecalculate[$composant->getId()] = $composant;
}
}
foreach ($uow->getScheduledEntityUpdates() as $entity) {
if ($entity instanceof CustomFieldValue && $entity->getPiece()) {
if (!$entity instanceof CustomFieldValue) {
continue;
}
if ($entity->getPiece()) {
$piece = $entity->getPiece();
$piecesToRecalculate[$piece->getId()] = $piece;
} elseif ($entity->getComposant()) {
$composant = $entity->getComposant();
$composantsToRecalculate[$composant->getId()] = $composant;
}
}
// For CFV deletions: remove from collection so the generator doesn't see stale values.
foreach ($uow->getScheduledEntityDeletions() as $entity) {
if ($entity instanceof CustomFieldValue && $entity->getPiece()) {
if (!$entity instanceof CustomFieldValue) {
continue;
}
if ($entity->getPiece()) {
$piece = $entity->getPiece();
$piece->getCustomFieldValues()->removeElement($entity);
$piecesToRecalculate[$piece->getId()] = $piece;
} elseif ($entity->getComposant()) {
$composant = $entity->getComposant();
$composant->getCustomFieldValues()->removeElement($entity);
$composantsToRecalculate[$composant->getId()] = $composant;
}
}
$meta = $em->getClassMetadata(Piece::class);
$pieceMeta = $em->getClassMetadata(Piece::class);
$composantMeta = $em->getClassMetadata(Composant::class);
foreach ($piecesToRecalculate as $piece) {
$newRef = $this->generator->generate($piece);
if ($piece->getReferenceAuto() !== $newRef) {
$piece->setReferenceAuto($newRef);
$uow->recomputeSingleEntityChangeSet($meta, $piece);
$uow->recomputeSingleEntityChangeSet($pieceMeta, $piece);
}
}
foreach ($composantsToRecalculate as $composant) {
$newRef = $this->generator->generate($composant);
if ($composant->getReferenceAuto() !== $newRef) {
$composant->setReferenceAuto($newRef);
$uow->recomputeSingleEntityChangeSet($composantMeta, $composant);
}
}
}

View File

@@ -4,20 +4,23 @@ declare(strict_types=1);
namespace App\Service;
use App\Entity\Composant;
use App\Entity\CustomFieldValue;
use App\Entity\Piece;
class ReferenceAutoGenerator
{
public function generate(Piece $piece): ?string
public function generate(Composant|Piece $entity): ?string
{
$modelType = $piece->getTypePiece();
$modelType = $entity instanceof Piece
? $entity->getTypePiece()
: $entity->getTypeComposant();
if (!$modelType || !$modelType->getReferenceFormula()) {
return null;
}
$valueMap = $this->buildValueMap($piece);
$valueMap = $this->buildValueMap($entity);
$requiredFields = $modelType->getRequiredFieldsForReference();
@@ -35,16 +38,16 @@ class ReferenceAutoGenerator
}
/**
* Build a map of fieldName → normalized value from the Piece's CustomFieldValues.
* Build a map of fieldName → normalized value from the entity's CustomFieldValues.
*
* @return array<string, string>
*/
private function buildValueMap(Piece $piece): array
private function buildValueMap(Composant|Piece $entity): array
{
$map = [];
/** @var CustomFieldValue $cfv */
foreach ($piece->getCustomFieldValues() as $cfv) {
foreach ($entity->getCustomFieldValues() as $cfv) {
$normalized = mb_strtoupper(trim($cfv->getValue()));
$map[$cfv->getCustomField()->getName()] = $normalized;
}