refactor : simplification globale (vague 1 + 2)
- ActorProfileResolver : service unique partage par AbstractAuditSubscriber, EntityVersionService et ModelTypeCategoryConversionService (3 implementations dupliquees+divergentes) - corrige un bug latent : EntityVersionService restoraitsans le fallback Security::getUser, loggant actor=null hors session - machine-clone : clonage des contextFieldValues integre dans cloneComponentLinks/clonePieceLinks, supprime cloneContextFieldValues et son find() en boucle - helpers extraits : serializeProductSlots (EntityVersionService), updateModelTypeCategory (ModelTypeCategoryConversionService) - supprime collectCollectionUpdate() vide + ses appels (AbstractAuditSubscriber) - useMachineDetailData : retire debug ref couplee a isEditMode, componentTypeLabelMap/pieceTypeLabelMap jamais consommes, double assignation machine.productLinks - PieceItem : retire l'init pieceData dans onMounted (deja couvert par reactive() et le watcher) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -739,12 +739,7 @@ watch(
|
|||||||
)
|
)
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
pieceData.name = props.piece.name || ''
|
|
||||||
pieceData.reference = props.piece.reference || ''
|
|
||||||
pieceData.prix = props.piece.prix || ''
|
|
||||||
pieceData.quantity = props.piece.quantity ?? 1
|
|
||||||
loadProducts().catch(() => {})
|
loadProducts().catch(() => {})
|
||||||
if (pieceData.productId) ensureProductLoaded(pieceData.productId)
|
|
||||||
if (!props.piece.documents?.length) refreshDocuments()
|
if (!props.piece.documents?.length) refreshDocuments()
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -119,7 +119,6 @@ export function useMachineDetailData(machineId: string) {
|
|||||||
if (!machineName.value.trim()) return false
|
if (!machineName.value.trim()) return false
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
const debug = ref(false)
|
|
||||||
|
|
||||||
const componentsCollapsed = ref(true)
|
const componentsCollapsed = ref(true)
|
||||||
const collapseToggleToken = ref(0)
|
const collapseToggleToken = ref(0)
|
||||||
@@ -227,22 +226,6 @@ export function useMachineDetailData(machineId: string) {
|
|||||||
const componentTypeOptions = computed(() => componentTypes.value || [])
|
const componentTypeOptions = computed(() => componentTypes.value || [])
|
||||||
const pieceTypeOptions = computed(() => pieceTypes.value || [])
|
const pieceTypeOptions = computed(() => pieceTypes.value || [])
|
||||||
|
|
||||||
const componentTypeLabelMap = computed(() => {
|
|
||||||
const map = new Map<string, string>()
|
|
||||||
componentTypeOptions.value.forEach((type) => {
|
|
||||||
if (type?.id) map.set(type.id as string, (type.name as string) || '')
|
|
||||||
})
|
|
||||||
return map
|
|
||||||
})
|
|
||||||
|
|
||||||
const pieceTypeLabelMap = computed(() => {
|
|
||||||
const map = new Map<string, string>()
|
|
||||||
pieceTypeOptions.value.forEach((type) => {
|
|
||||||
if (type?.id) map.set(type.id as string, (type.name as string) || '')
|
|
||||||
})
|
|
||||||
return map
|
|
||||||
})
|
|
||||||
|
|
||||||
// Machine field methods
|
// Machine field methods
|
||||||
const initMachineFields = () => {
|
const initMachineFields = () => {
|
||||||
if (machine.value) {
|
if (machine.value) {
|
||||||
@@ -306,7 +289,6 @@ export function useMachineDetailData(machineId: string) {
|
|||||||
// UI methods
|
// UI methods
|
||||||
const toggleEditMode = () => {
|
const toggleEditMode = () => {
|
||||||
isEditMode.value = !isEditMode.value
|
isEditMode.value = !isEditMode.value
|
||||||
debug.value = !debug.value
|
|
||||||
if (isEditMode.value && !machineDocumentsLoaded.value) {
|
if (isEditMode.value && !machineDocumentsLoaded.value) {
|
||||||
refreshMachineDocuments()
|
refreshMachineDocuments()
|
||||||
}
|
}
|
||||||
@@ -432,12 +414,6 @@ export function useMachineDetailData(machineId: string) {
|
|||||||
await productsPromise
|
await productsPromise
|
||||||
const linksApplied = applyMachineLinks(machineResult.data)
|
const linksApplied = applyMachineLinks(machineResult.data)
|
||||||
|
|
||||||
if (machine.value) {
|
|
||||||
machine.value.componentLinks = machineComponentLinks.value
|
|
||||||
machine.value.pieceLinks = machinePieceLinks.value
|
|
||||||
machine.value.productLinks = machineProductLinks.value
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!linksApplied) {
|
if (!linksApplied) {
|
||||||
components.value = transformComponentCustomFields(machinePayload.components || [])
|
components.value = transformComponentCustomFields(machinePayload.components || [])
|
||||||
pieces.value = transformCustomFields(machinePayload.pieces || [])
|
pieces.value = transformCustomFields(machinePayload.pieces || [])
|
||||||
@@ -447,6 +423,8 @@ export function useMachineDetailData(machineId: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (machine.value) {
|
if (machine.value) {
|
||||||
|
machine.value.componentLinks = machineComponentLinks.value
|
||||||
|
machine.value.pieceLinks = machinePieceLinks.value
|
||||||
machine.value.productLinks = machineProductLinks.value
|
machine.value.productLinks = machineProductLinks.value
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -496,11 +474,11 @@ export function useMachineDetailData(machineId: string) {
|
|||||||
// UI state
|
// UI state
|
||||||
machineDocumentFiles, machineDocumentsUploading, machineDocumentsLoaded,
|
machineDocumentFiles, machineDocumentsUploading, machineDocumentsLoaded,
|
||||||
machineCustomFields, pendingContextFieldUpdates, previewDocument, previewVisible,
|
machineCustomFields, pendingContextFieldUpdates, previewDocument, previewVisible,
|
||||||
isEditMode, debug,
|
isEditMode,
|
||||||
componentsCollapsed, collapseToggleToken, piecesCollapsed, pieceCollapseToggleToken,
|
componentsCollapsed, collapseToggleToken, piecesCollapsed, pieceCollapseToggleToken,
|
||||||
|
|
||||||
// Computed
|
// Computed
|
||||||
componentTypeOptions, pieceTypeOptions, componentTypeLabelMap, pieceTypeLabelMap,
|
componentTypeOptions, pieceTypeOptions,
|
||||||
productInventory, productById, flattenedComponents, machinePieces,
|
productInventory, productById, flattenedComponents, machinePieces,
|
||||||
machineDirectProducts, machineDocumentsList, visibleMachineCustomFields,
|
machineDirectProducts, machineDocumentsList, visibleMachineCustomFields,
|
||||||
|
|
||||||
|
|||||||
@@ -162,9 +162,6 @@ class MachineStructureController extends AbstractController
|
|||||||
// Copy product links
|
// Copy product links
|
||||||
$this->cloneProductLinks($source, $newMachine, $componentLinkMap, $pieceLinkMap);
|
$this->cloneProductLinks($source, $newMachine, $componentLinkMap, $pieceLinkMap);
|
||||||
|
|
||||||
// Copy context field values
|
|
||||||
$this->cloneContextFieldValues($componentLinkMap, $pieceLinkMap);
|
|
||||||
|
|
||||||
$this->entityManager->flush();
|
$this->entityManager->flush();
|
||||||
|
|
||||||
$componentLinks = $this->machineComponentLinkRepository->findBy(['machine' => $newMachine], ['createdAt' => 'ASC']);
|
$componentLinks = $this->machineComponentLinkRepository->findBy(['machine' => $newMachine], ['createdAt' => 'ASC']);
|
||||||
@@ -230,6 +227,17 @@ class MachineStructureController extends AbstractController
|
|||||||
$newLink->setReferenceOverride($link->getReferenceOverride());
|
$newLink->setReferenceOverride($link->getReferenceOverride());
|
||||||
$newLink->setPrixOverride($link->getPrixOverride());
|
$newLink->setPrixOverride($link->getPrixOverride());
|
||||||
$this->entityManager->persist($newLink);
|
$this->entityManager->persist($newLink);
|
||||||
|
|
||||||
|
foreach ($link->getContextFieldValues() as $cfv) {
|
||||||
|
$newValue = new CustomFieldValue();
|
||||||
|
$newValue->setCustomField($cfv->getCustomField());
|
||||||
|
$newValue->setValue($cfv->getValue());
|
||||||
|
$newValue->setMachineComponentLink($newLink);
|
||||||
|
$newValue->setComposant($newLink->getComposant());
|
||||||
|
$this->entityManager->persist($newValue);
|
||||||
|
$newLink->getContextFieldValues()->add($newValue);
|
||||||
|
}
|
||||||
|
|
||||||
$linkMap[$link->getId()] = $newLink;
|
$linkMap[$link->getId()] = $newLink;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -269,6 +277,17 @@ class MachineStructureController extends AbstractController
|
|||||||
}
|
}
|
||||||
|
|
||||||
$this->entityManager->persist($newLink);
|
$this->entityManager->persist($newLink);
|
||||||
|
|
||||||
|
foreach ($link->getContextFieldValues() as $cfv) {
|
||||||
|
$newValue = new CustomFieldValue();
|
||||||
|
$newValue->setCustomField($cfv->getCustomField());
|
||||||
|
$newValue->setValue($cfv->getValue());
|
||||||
|
$newValue->setMachinePieceLink($newLink);
|
||||||
|
$newValue->setPiece($newLink->getPiece());
|
||||||
|
$this->entityManager->persist($newValue);
|
||||||
|
$newLink->getContextFieldValues()->add($newValue);
|
||||||
|
}
|
||||||
|
|
||||||
$linkMap[$link->getId()] = $newLink;
|
$linkMap[$link->getId()] = $newLink;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -317,47 +336,6 @@ class MachineStructureController extends AbstractController
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param array<string, MachineComponentLink> $componentLinkMap
|
|
||||||
* @param array<string, MachinePieceLink> $pieceLinkMap
|
|
||||||
*/
|
|
||||||
private function cloneContextFieldValues(
|
|
||||||
array $componentLinkMap,
|
|
||||||
array $pieceLinkMap,
|
|
||||||
): void {
|
|
||||||
foreach ($componentLinkMap as $oldLinkId => $newLink) {
|
|
||||||
$oldLink = $this->machineComponentLinkRepository->find($oldLinkId);
|
|
||||||
if (!$oldLink) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
foreach ($oldLink->getContextFieldValues() as $cfv) {
|
|
||||||
$newValue = new CustomFieldValue();
|
|
||||||
$newValue->setCustomField($cfv->getCustomField());
|
|
||||||
$newValue->setValue($cfv->getValue());
|
|
||||||
$newValue->setMachineComponentLink($newLink);
|
|
||||||
$newValue->setComposant($newLink->getComposant());
|
|
||||||
$this->entityManager->persist($newValue);
|
|
||||||
$newLink->getContextFieldValues()->add($newValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($pieceLinkMap as $oldLinkId => $newLink) {
|
|
||||||
$oldLink = $this->machinePieceLinkRepository->find($oldLinkId);
|
|
||||||
if (!$oldLink) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
foreach ($oldLink->getContextFieldValues() as $cfv) {
|
|
||||||
$newValue = new CustomFieldValue();
|
|
||||||
$newValue->setCustomField($cfv->getCustomField());
|
|
||||||
$newValue->setValue($cfv->getValue());
|
|
||||||
$newValue->setMachinePieceLink($newLink);
|
|
||||||
$newValue->setPiece($newLink->getPiece());
|
|
||||||
$this->entityManager->persist($newValue);
|
|
||||||
$newLink->getContextFieldValues()->add($newValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private function normalizePayloadList(mixed $value): array
|
private function normalizePayloadList(mixed $value): array
|
||||||
{
|
{
|
||||||
if (!is_array($value)) {
|
if (!is_array($value)) {
|
||||||
|
|||||||
@@ -11,8 +11,8 @@ use App\Entity\Machine;
|
|||||||
use App\Entity\ModelType;
|
use App\Entity\ModelType;
|
||||||
use App\Entity\Piece;
|
use App\Entity\Piece;
|
||||||
use App\Entity\Product;
|
use App\Entity\Product;
|
||||||
use App\Entity\Profile;
|
|
||||||
use App\Entity\Site;
|
use App\Entity\Site;
|
||||||
|
use App\Service\ActorProfileResolver;
|
||||||
use BackedEnum;
|
use BackedEnum;
|
||||||
use DateTimeInterface;
|
use DateTimeInterface;
|
||||||
use Doctrine\Common\Collections\Collection;
|
use Doctrine\Common\Collections\Collection;
|
||||||
@@ -22,10 +22,6 @@ use Doctrine\ORM\Event\OnFlushEventArgs;
|
|||||||
use Doctrine\ORM\Events;
|
use Doctrine\ORM\Events;
|
||||||
use Doctrine\ORM\UnitOfWork;
|
use Doctrine\ORM\UnitOfWork;
|
||||||
use Error;
|
use Error;
|
||||||
use Symfony\Bundle\SecurityBundle\Security;
|
|
||||||
use Symfony\Component\HttpFoundation\RequestStack;
|
|
||||||
use Symfony\Component\HttpFoundation\Session\SessionInterface;
|
|
||||||
use Throwable;
|
|
||||||
|
|
||||||
use function is_array;
|
use function is_array;
|
||||||
use function is_object;
|
use function is_object;
|
||||||
@@ -35,8 +31,7 @@ use function method_exists;
|
|||||||
abstract class AbstractAuditSubscriber implements EventSubscriber
|
abstract class AbstractAuditSubscriber implements EventSubscriber
|
||||||
{
|
{
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private readonly RequestStack $requestStack,
|
private readonly ActorProfileResolver $actorProfileResolver,
|
||||||
private readonly Security $security,
|
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
public function getSubscribedEvents(): array
|
public function getSubscribedEvents(): array
|
||||||
@@ -61,7 +56,7 @@ abstract class AbstractAuditSubscriber implements EventSubscriber
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$actorProfileId = $this->resolveActorProfileId();
|
$actorProfileId = $this->actorProfileResolver->resolve();
|
||||||
$entityType = $this->entityType();
|
$entityType = $this->entityType();
|
||||||
|
|
||||||
if ($this->hasCollectionTracking()) {
|
if ($this->hasCollectionTracking()) {
|
||||||
@@ -278,28 +273,6 @@ abstract class AbstractAuditSubscriber implements EventSubscriber
|
|||||||
return $entity->getVersion();
|
return $entity->getVersion();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function resolveActorProfileId(): ?string
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
$session = $this->requestStack->getSession();
|
|
||||||
if ($session instanceof SessionInterface) {
|
|
||||||
$profileId = $session->get('profileId');
|
|
||||||
if ($profileId) {
|
|
||||||
return (string) $profileId;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (Throwable) {
|
|
||||||
// No session available (CLI context, etc.)
|
|
||||||
}
|
|
||||||
|
|
||||||
$user = $this->security->getUser();
|
|
||||||
if ($user instanceof Profile) {
|
|
||||||
return $user->getId();
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function onFlushSimple(EntityManagerInterface $em, UnitOfWork $uow, ?string $actorProfileId, string $entityType): void
|
private function onFlushSimple(EntityManagerInterface $em, UnitOfWork $uow, ?string $actorProfileId, string $entityType): void
|
||||||
{
|
{
|
||||||
foreach ($uow->getScheduledEntityInsertions() as $entity) {
|
foreach ($uow->getScheduledEntityInsertions() as $entity) {
|
||||||
@@ -385,13 +358,10 @@ abstract class AbstractAuditSubscriber implements EventSubscriber
|
|||||||
$this->persistAuditLog($em, new AuditLog($entityType, (string) $entity->getId(), 'delete', null, $snapshot, $actorProfileId));
|
$this->persistAuditLog($em, new AuditLog($entityType, (string) $entity->getId(), 'delete', null, $snapshot, $actorProfileId));
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ($uow->getScheduledCollectionUpdates() as $collection) {
|
// Note: scheduled collection updates/deletions are intentionally not
|
||||||
$this->collectCollectionUpdate($collection, $pendingUpdates, $pendingSnapshots, $pendingEntities);
|
// tracked here — constructeurs are now persisted as ConstructeurLink
|
||||||
}
|
// entities (OneToMany), so Doctrine no longer fires collection events
|
||||||
foreach ($uow->getScheduledCollectionDeletions() as $collection) {
|
// for them. Custom field values are handled below.
|
||||||
$this->collectCollectionUpdate($collection, $pendingUpdates, $pendingSnapshots, $pendingEntities);
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->collectCustomFieldValueChanges($uow, $pendingUpdates, $pendingSnapshots, $pendingEntities);
|
$this->collectCustomFieldValueChanges($uow, $pendingUpdates, $pendingSnapshots, $pendingEntities);
|
||||||
|
|
||||||
foreach ($pendingUpdates as $entityId => $diff) {
|
foreach ($pendingUpdates as $entityId => $diff) {
|
||||||
@@ -411,17 +381,6 @@ abstract class AbstractAuditSubscriber implements EventSubscriber
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* No-op: constructeurs are now tracked as ConstructeurLink entities (OneToMany),
|
|
||||||
* so Doctrine no longer fires collection update events for them.
|
|
||||||
*/
|
|
||||||
private function collectCollectionUpdate(
|
|
||||||
object $collection,
|
|
||||||
array &$pendingUpdates,
|
|
||||||
array &$pendingSnapshots,
|
|
||||||
array &$pendingEntities,
|
|
||||||
): void {}
|
|
||||||
|
|
||||||
private function collectCustomFieldValueChanges(
|
private function collectCustomFieldValueChanges(
|
||||||
UnitOfWork $uow,
|
UnitOfWork $uow,
|
||||||
array &$pendingUpdates,
|
array &$pendingUpdates,
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ final class MachineAuditSubscriber extends AbstractAuditSubscriber
|
|||||||
}
|
}
|
||||||
|
|
||||||
$uow = $em->getUnitOfWork();
|
$uow = $em->getUnitOfWork();
|
||||||
$actorProfileId = $this->resolveActorProfileId();
|
$actorProfileId = $this->actorProfileResolver->resolve();
|
||||||
|
|
||||||
$this->processLinkChanges($em, $uow, $actorProfileId);
|
$this->processLinkChanges($em, $uow, $actorProfileId);
|
||||||
}
|
}
|
||||||
|
|||||||
41
src/Service/ActorProfileResolver.php
Normal file
41
src/Service/ActorProfileResolver.php
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Service;
|
||||||
|
|
||||||
|
use App\Entity\Profile;
|
||||||
|
use Symfony\Bundle\SecurityBundle\Security;
|
||||||
|
use Symfony\Component\HttpFoundation\RequestStack;
|
||||||
|
use Symfony\Component\HttpFoundation\Session\SessionInterface;
|
||||||
|
use Throwable;
|
||||||
|
|
||||||
|
final class ActorProfileResolver
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private readonly RequestStack $requestStack,
|
||||||
|
private readonly Security $security,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public function resolve(): ?string
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$session = $this->requestStack->getSession();
|
||||||
|
if ($session instanceof SessionInterface) {
|
||||||
|
$profileId = $session->get('profileId');
|
||||||
|
if ($profileId) {
|
||||||
|
return (string) $profileId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Throwable) {
|
||||||
|
// No session available (CLI context, etc.)
|
||||||
|
}
|
||||||
|
|
||||||
|
$user = $this->security->getUser();
|
||||||
|
if ($user instanceof Profile) {
|
||||||
|
return $user->getId();
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -34,7 +34,6 @@ use DateTimeInterface;
|
|||||||
use Doctrine\ORM\EntityManagerInterface;
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
use InvalidArgumentException;
|
use InvalidArgumentException;
|
||||||
use LogicException;
|
use LogicException;
|
||||||
use Symfony\Component\HttpFoundation\RequestStack;
|
|
||||||
use Throwable;
|
use Throwable;
|
||||||
|
|
||||||
final class EntityVersionService
|
final class EntityVersionService
|
||||||
@@ -56,7 +55,7 @@ final class EntityVersionService
|
|||||||
public function __construct(
|
public function __construct(
|
||||||
private readonly AuditLogRepository $auditLogs,
|
private readonly AuditLogRepository $auditLogs,
|
||||||
private readonly EntityManagerInterface $em,
|
private readonly EntityManagerInterface $em,
|
||||||
private readonly RequestStack $requestStack,
|
private readonly ActorProfileResolver $actorProfileResolver,
|
||||||
private readonly MachineRepository $machines,
|
private readonly MachineRepository $machines,
|
||||||
private readonly ComposantRepository $composants,
|
private readonly ComposantRepository $composants,
|
||||||
private readonly PieceRepository $pieces,
|
private readonly PieceRepository $pieces,
|
||||||
@@ -187,7 +186,7 @@ final class EntityVersionService
|
|||||||
'restore',
|
'restore',
|
||||||
['restoredFromVersion' => $version, 'restoreMode' => $restoreMode],
|
['restoredFromVersion' => $version, 'restoreMode' => $restoreMode],
|
||||||
$this->buildCurrentSnapshot($entityType, $entity),
|
$this->buildCurrentSnapshot($entityType, $entity),
|
||||||
$this->resolveActorProfileId(),
|
$this->actorProfileResolver->resolve(),
|
||||||
$newVersion,
|
$newVersion,
|
||||||
);
|
);
|
||||||
$this->em->persist($restoreAuditLog);
|
$this->em->persist($restoreAuditLog);
|
||||||
@@ -917,25 +916,11 @@ final class EntityVersionService
|
|||||||
'position' => $slot->getPosition(),
|
'position' => $slot->getPosition(),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
$snapshot['productSlots'] = [];
|
$snapshot['productSlots'] = $this->serializeProductSlots($entity->getProductSlots());
|
||||||
foreach ($entity->getProductSlots() as $slot) {
|
|
||||||
$snapshot['productSlots'][] = [
|
|
||||||
'id' => $slot->getId(), 'typeProductId' => $slot->getTypeProduct()?->getId(),
|
|
||||||
'selectedProductId' => $slot->getSelectedProduct()?->getId(),
|
|
||||||
'familyCode' => $slot->getFamilyCode(), 'position' => $slot->getPosition(),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ('piece' === $entityType) {
|
if ('piece' === $entityType) {
|
||||||
$snapshot['productSlots'] = [];
|
$snapshot['productSlots'] = $this->serializeProductSlots($entity->getProductSlots());
|
||||||
foreach ($entity->getProductSlots() as $slot) {
|
|
||||||
$snapshot['productSlots'][] = [
|
|
||||||
'id' => $slot->getId(), 'typeProductId' => $slot->getTypeProduct()?->getId(),
|
|
||||||
'selectedProductId' => $slot->getSelectedProduct()?->getId(),
|
|
||||||
'familyCode' => $slot->getFamilyCode(), 'position' => $slot->getPosition(),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Custom field values
|
// Custom field values
|
||||||
@@ -953,21 +938,24 @@ final class EntityVersionService
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolve the current actor profile ID from the session.
|
* @param iterable<ComposantProductSlot|PieceProductSlot> $slots
|
||||||
* Mirrors AbstractAuditSubscriber::resolveActorProfileId().
|
*
|
||||||
|
* @return list<array<string, mixed>>
|
||||||
*/
|
*/
|
||||||
private function resolveActorProfileId(): ?string
|
private function serializeProductSlots(iterable $slots): array
|
||||||
{
|
{
|
||||||
try {
|
$serialized = [];
|
||||||
$session = $this->requestStack->getSession();
|
foreach ($slots as $slot) {
|
||||||
$profileId = $session->get('profileId');
|
$serialized[] = [
|
||||||
if ($profileId) {
|
'id' => $slot->getId(),
|
||||||
return (string) $profileId;
|
'typeProductId' => $slot->getTypeProduct()?->getId(),
|
||||||
}
|
'selectedProductId' => $slot->getSelectedProduct()?->getId(),
|
||||||
} catch (Throwable) {
|
'familyCode' => $slot->getFamilyCode(),
|
||||||
// No session available (CLI context, etc.)
|
'position' => $slot->getPosition(),
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return $serialized;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,23 +4,17 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace App\Service;
|
namespace App\Service;
|
||||||
|
|
||||||
use App\Entity\Profile;
|
|
||||||
use App\Enum\ModelCategory;
|
use App\Enum\ModelCategory;
|
||||||
use App\Repository\ModelTypeRepository;
|
use App\Repository\ModelTypeRepository;
|
||||||
use DateTimeImmutable;
|
use DateTimeImmutable;
|
||||||
use Doctrine\DBAL\Connection;
|
use Doctrine\DBAL\Connection;
|
||||||
use Symfony\Bundle\SecurityBundle\Security;
|
|
||||||
use Symfony\Component\HttpFoundation\RequestStack;
|
|
||||||
use Symfony\Component\HttpFoundation\Session\SessionInterface;
|
|
||||||
use Throwable;
|
|
||||||
|
|
||||||
final class ModelTypeCategoryConversionService
|
final class ModelTypeCategoryConversionService
|
||||||
{
|
{
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private readonly Connection $connection,
|
private readonly Connection $connection,
|
||||||
private readonly ModelTypeRepository $modelTypes,
|
private readonly ModelTypeRepository $modelTypes,
|
||||||
private readonly RequestStack $requestStack,
|
private readonly ActorProfileResolver $actorProfileResolver,
|
||||||
private readonly Security $security,
|
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -327,17 +321,7 @@ final class ModelTypeCategoryConversionService
|
|||||||
);
|
);
|
||||||
|
|
||||||
// 7. Update ModelType
|
// 7. Update ModelType
|
||||||
$this->connection->executeStatement(
|
$this->updateModelTypeCategory($modelTypeId, ModelCategory::COMPONENT);
|
||||||
'UPDATE model_types
|
|
||||||
SET category = :cat,
|
|
||||||
updatedat = :now
|
|
||||||
WHERE id = :id',
|
|
||||||
[
|
|
||||||
'cat' => ModelCategory::COMPONENT->value,
|
|
||||||
'now' => new DateTimeImmutable()->format('Y-m-d H:i:s'),
|
|
||||||
'id' => $modelTypeId,
|
|
||||||
],
|
|
||||||
);
|
|
||||||
|
|
||||||
return $count;
|
return $count;
|
||||||
}
|
}
|
||||||
@@ -406,19 +390,24 @@ final class ModelTypeCategoryConversionService
|
|||||||
);
|
);
|
||||||
|
|
||||||
// 7. Update ModelType
|
// 7. Update ModelType
|
||||||
|
$this->updateModelTypeCategory($modelTypeId, ModelCategory::PIECE);
|
||||||
|
|
||||||
|
return $count;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function updateModelTypeCategory(string $modelTypeId, ModelCategory $category): void
|
||||||
|
{
|
||||||
$this->connection->executeStatement(
|
$this->connection->executeStatement(
|
||||||
'UPDATE model_types
|
'UPDATE model_types
|
||||||
SET category = :cat,
|
SET category = :cat,
|
||||||
updatedat = :now
|
updatedat = :now
|
||||||
WHERE id = :id',
|
WHERE id = :id',
|
||||||
[
|
[
|
||||||
'cat' => ModelCategory::PIECE->value,
|
'cat' => $category->value,
|
||||||
'now' => new DateTimeImmutable()->format('Y-m-d H:i:s'),
|
'now' => new DateTimeImmutable()->format('Y-m-d H:i:s'),
|
||||||
'id' => $modelTypeId,
|
'id' => $modelTypeId,
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
return $count;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -457,30 +446,10 @@ final class ModelTypeCategoryConversionService
|
|||||||
'action' => 'convert',
|
'action' => 'convert',
|
||||||
'diff' => json_encode($diff),
|
'diff' => json_encode($diff),
|
||||||
'snapshot' => json_encode($snapshot),
|
'snapshot' => json_encode($snapshot),
|
||||||
'actor' => $this->resolveActorProfileId(),
|
'actor' => $this->actorProfileResolver->resolve(),
|
||||||
'now' => $now,
|
'now' => $now,
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function resolveActorProfileId(): ?string
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
$session = $this->requestStack->getSession();
|
|
||||||
if ($session instanceof SessionInterface) {
|
|
||||||
$profileId = $session->get('profileId');
|
|
||||||
if ($profileId) {
|
|
||||||
return (string) $profileId;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (Throwable) {
|
|
||||||
}
|
|
||||||
|
|
||||||
$user = $this->security->getUser();
|
|
||||||
if ($user instanceof Profile) {
|
|
||||||
return $user->getId();
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user