feat(machine) : single save button + link versioning with restore
Backend: - Enrich machine snapshot with componentLinks/pieceLinks/productLinks - Detect link add/remove in MachineAuditSubscriber onFlush - Add link diff comparison in restore preview - Add link restoration in applyRestore for machines - Add integrity warnings for missing linked entities Frontend (submodule update): - Single save button replacing auto-save-on-blur - Link versioning display in version list and restore modal Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -4,14 +4,38 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\EventSubscriber;
|
||||
|
||||
use App\Entity\AuditLog;
|
||||
use App\Entity\CustomFieldValue;
|
||||
use App\Entity\Machine;
|
||||
use App\Entity\MachineComponentLink;
|
||||
use App\Entity\MachinePieceLink;
|
||||
use App\Entity\MachineProductLink;
|
||||
use Doctrine\Bundle\DoctrineBundle\Attribute\AsDoctrineListener;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\Event\OnFlushEventArgs;
|
||||
use Doctrine\ORM\Events;
|
||||
use Doctrine\ORM\UnitOfWork;
|
||||
|
||||
#[AsDoctrineListener(event: Events::onFlush)]
|
||||
final class MachineAuditSubscriber extends AbstractAuditSubscriber
|
||||
{
|
||||
public function onFlush(OnFlushEventArgs $args): void
|
||||
{
|
||||
// Let parent handle regular Machine entity changes (fields, collections, custom fields)
|
||||
parent::onFlush($args);
|
||||
|
||||
// Now handle link entity changes
|
||||
$em = $args->getObjectManager();
|
||||
if (!$em instanceof EntityManagerInterface) {
|
||||
return;
|
||||
}
|
||||
|
||||
$uow = $em->getUnitOfWork();
|
||||
$actorProfileId = $this->resolveActorProfileId();
|
||||
|
||||
$this->processLinkChanges($em, $uow, $actorProfileId);
|
||||
}
|
||||
|
||||
protected function supports(object $entity): bool
|
||||
{
|
||||
return $entity instanceof Machine;
|
||||
@@ -46,6 +70,34 @@ final class MachineAuditSubscriber extends AbstractAuditSubscriber
|
||||
];
|
||||
}
|
||||
|
||||
$componentLinks = [];
|
||||
foreach ($entity->getComponentLinks() as $link) {
|
||||
$componentLinks[] = [
|
||||
'id' => $link->getId(),
|
||||
'composantId' => $link->getComposant()->getId(),
|
||||
'composantName' => $link->getComposant()->getName(),
|
||||
];
|
||||
}
|
||||
|
||||
$pieceLinks = [];
|
||||
foreach ($entity->getPieceLinks() as $link) {
|
||||
$pieceLinks[] = [
|
||||
'id' => $link->getId(),
|
||||
'pieceId' => $link->getPiece()->getId(),
|
||||
'pieceName' => $link->getPiece()->getName(),
|
||||
'quantity' => $link->getQuantity(),
|
||||
];
|
||||
}
|
||||
|
||||
$productLinks = [];
|
||||
foreach ($entity->getProductLinks() as $link) {
|
||||
$productLinks[] = [
|
||||
'id' => $link->getId(),
|
||||
'productId' => $link->getProduct()->getId(),
|
||||
'productName' => $link->getProduct()->getName(),
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'id' => $entity->getId(),
|
||||
'name' => $this->safeGet($entity, 'getName'),
|
||||
@@ -54,7 +106,108 @@ final class MachineAuditSubscriber extends AbstractAuditSubscriber
|
||||
'site' => $this->normalizeValue($this->safeGet($entity, 'getSite')),
|
||||
'constructeurIds' => $this->normalizeCollection($entity->getConstructeurs()),
|
||||
'customFieldValues' => $customFieldValues,
|
||||
'componentLinks' => $componentLinks,
|
||||
'pieceLinks' => $pieceLinks,
|
||||
'productLinks' => $productLinks,
|
||||
'version' => $this->safeGet($entity, 'getVersion'),
|
||||
];
|
||||
}
|
||||
|
||||
private function processLinkChanges(EntityManagerInterface $em, UnitOfWork $uow, ?string $actorProfileId): void
|
||||
{
|
||||
$machineChanges = [];
|
||||
|
||||
// Detect inserted links
|
||||
foreach ($uow->getScheduledEntityInsertions() as $entity) {
|
||||
$info = $this->extractLinkInfo($entity, 'added');
|
||||
if (null === $info) {
|
||||
continue;
|
||||
}
|
||||
$machineId = (string) $info['machine']->getId();
|
||||
if ('' === $machineId) {
|
||||
continue;
|
||||
}
|
||||
$machineChanges[$machineId] ??= ['machine' => $info['machine'], 'diffs' => []];
|
||||
$machineChanges[$machineId]['diffs'][$info['diffKey']] = [
|
||||
'from' => null,
|
||||
'to' => $info['diffValue'],
|
||||
];
|
||||
}
|
||||
|
||||
// Detect deleted links
|
||||
foreach ($uow->getScheduledEntityDeletions() as $entity) {
|
||||
$info = $this->extractLinkInfo($entity, 'removed');
|
||||
if (null === $info) {
|
||||
continue;
|
||||
}
|
||||
$machineId = (string) $info['machine']->getId();
|
||||
if ('' === $machineId) {
|
||||
continue;
|
||||
}
|
||||
$machineChanges[$machineId] ??= ['machine' => $info['machine'], 'diffs' => []];
|
||||
$machineChanges[$machineId]['diffs'][$info['diffKey']] = [
|
||||
'from' => $info['diffValue'],
|
||||
'to' => null,
|
||||
];
|
||||
}
|
||||
|
||||
// Create audit logs for each affected machine
|
||||
foreach ($machineChanges as $machineId => $change) {
|
||||
$machine = $change['machine'];
|
||||
$diff = $change['diffs'];
|
||||
|
||||
if ([] === $diff) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$version = $this->incrementEntityVersion($machine, $em, $uow);
|
||||
$snapshot = $this->snapshotEntity($machine);
|
||||
|
||||
$this->persistAuditLog(
|
||||
$em,
|
||||
new AuditLog('machine', $machineId, 'update', $diff, $snapshot, $actorProfileId, $version),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return null|array{machine: Machine, diffKey: string, diffValue: array{id: string, name: string}}
|
||||
*/
|
||||
private function extractLinkInfo(object $entity, string $action): ?array
|
||||
{
|
||||
if ($entity instanceof MachineComponentLink) {
|
||||
return [
|
||||
'machine' => $entity->getMachine(),
|
||||
'diffKey' => $action.'Component',
|
||||
'diffValue' => [
|
||||
'id' => $entity->getComposant()->getId(),
|
||||
'name' => $entity->getComposant()->getName(),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
if ($entity instanceof MachinePieceLink) {
|
||||
return [
|
||||
'machine' => $entity->getMachine(),
|
||||
'diffKey' => $action.'Piece',
|
||||
'diffValue' => [
|
||||
'id' => $entity->getPiece()->getId(),
|
||||
'name' => $entity->getPiece()->getName(),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
if ($entity instanceof MachineProductLink) {
|
||||
return [
|
||||
'machine' => $entity->getMachine(),
|
||||
'diffKey' => $action.'Product',
|
||||
'diffValue' => [
|
||||
'id' => $entity->getProduct()->getId(),
|
||||
'name' => $entity->getProduct()->getName(),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user