Compare commits
25 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
adc44b99d3 | ||
|
|
60afeb4cfd | ||
|
|
02ff8b1a96 | ||
|
|
2156df22c6 | ||
|
|
cd2a3fac55 | ||
|
|
6300a3588a | ||
|
|
45213103e4 | ||
|
|
91b8b424d6 | ||
|
|
0d1c9277e5 | ||
|
|
db16d26103 | ||
|
|
0eb64d0975 | ||
|
|
39e503ae18 | ||
|
|
70ed354c42 | ||
|
|
ba98ae37f4 | ||
|
|
906d39793f | ||
|
|
f970c1928d | ||
|
|
2a1d966b87 | ||
|
|
a393b62e9f | ||
|
|
1247f72af6 | ||
|
|
6735bf252c | ||
|
|
508066d39f | ||
|
|
70956c204e | ||
|
|
16a7eac0c6 | ||
|
|
37ac08b182 | ||
|
|
5ef80b362e |
Submodule Inventory_frontend updated: 67af3c9c46...6bed715b7f
@@ -1,7 +1,9 @@
|
|||||||
api_platform:
|
api_platform:
|
||||||
title: Hello API Platform
|
title: Hello API Platform
|
||||||
version: 1.3.0
|
version: 1.4.0
|
||||||
defaults:
|
defaults:
|
||||||
stateless: false
|
stateless: false
|
||||||
cache_headers:
|
cache_headers:
|
||||||
vary: ['Content-Type', 'Authorization', 'Origin']
|
vary: ['Content-Type', 'Authorization', 'Origin']
|
||||||
|
pagination_items_per_page: 30
|
||||||
|
pagination_maximum_items_per_page: 200
|
||||||
|
|||||||
@@ -45,34 +45,17 @@ services:
|
|||||||
- "${POSTGRES_PORT:-5433}:5432"
|
- "${POSTGRES_PORT:-5433}:5432"
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|
||||||
pgadmin:
|
adminer:
|
||||||
container_name: pgadmin-${DOCKER_APP_NAME}
|
container_name: adminer-${DOCKER_APP_NAME}
|
||||||
image: dpage/pgadmin4:latest
|
image: adminer:latest
|
||||||
user: root
|
|
||||||
environment:
|
environment:
|
||||||
PGADMIN_DEFAULT_EMAIL: ${PGADMIN_EMAIL:-admin@admin.com}
|
ADMINER_DEFAULT_SERVER: db
|
||||||
PGADMIN_DEFAULT_PASSWORD: ${PGADMIN_PASSWORD:-admin}
|
ADMINER_DESIGN: dracula
|
||||||
PGADMIN_CONFIG_SERVER_MODE: 'False'
|
|
||||||
PGADMIN_CONFIG_MASTER_PASSWORD_REQUIRED: 'False'
|
|
||||||
PGADMIN_SERVER_JSON_FILE: '/pgadmin4/servers.json'
|
|
||||||
volumes:
|
|
||||||
- pgadmin_data:/var/lib/pgadmin
|
|
||||||
- ./docker/pgadmin/servers.json:/pgadmin4/servers.json:ro
|
|
||||||
- ./docker/pgadmin/pgpass:/pgadmin4/pgpass:ro
|
|
||||||
ports:
|
ports:
|
||||||
- "${PGADMIN_PORT:-5050}:80"
|
- "${ADMINER_PORT:-5050}:8080"
|
||||||
depends_on:
|
depends_on:
|
||||||
- db
|
- db
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
entrypoint: >
|
|
||||||
/bin/sh -c "
|
|
||||||
mkdir -p /var/lib/pgadmin &&
|
|
||||||
cp /pgadmin4/pgpass /var/lib/pgadmin/pgpass &&
|
|
||||||
chmod 600 /var/lib/pgadmin/pgpass &&
|
|
||||||
chown 5050:5050 /var/lib/pgadmin/pgpass &&
|
|
||||||
/entrypoint.sh
|
|
||||||
"
|
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
pg_data:
|
pg_data:
|
||||||
pgadmin_data:
|
|
||||||
|
|||||||
87
src/Controller/ActivityLogController.php
Normal file
87
src/Controller/ActivityLogController.php
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Controller;
|
||||||
|
|
||||||
|
use App\Repository\AuditLogRepository;
|
||||||
|
use App\Repository\ProfileRepository;
|
||||||
|
use DateTimeInterface;
|
||||||
|
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
use Symfony\Component\Routing\Attribute\Route;
|
||||||
|
|
||||||
|
final class ActivityLogController
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private readonly AuditLogRepository $auditLogs,
|
||||||
|
private readonly ProfileRepository $profiles,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
#[Route('/api/activity-logs', name: 'api_activity_logs', methods: ['GET'])]
|
||||||
|
public function __invoke(Request $request): JsonResponse
|
||||||
|
{
|
||||||
|
$page = max(1, $request->query->getInt('page', 1));
|
||||||
|
$itemsPerPage = min(100, max(1, $request->query->getInt('itemsPerPage', 30)));
|
||||||
|
|
||||||
|
$filters = [];
|
||||||
|
if ($entityType = $request->query->get('entityType')) {
|
||||||
|
$filters['entityType'] = $entityType;
|
||||||
|
}
|
||||||
|
if ($action = $request->query->get('action')) {
|
||||||
|
$filters['action'] = $action;
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = $this->auditLogs->findAllPaginated($page, $itemsPerPage, $filters);
|
||||||
|
|
||||||
|
$actorIds = array_values(array_unique(array_filter(array_map(
|
||||||
|
static fn ($log) => $log->getActorProfileId(),
|
||||||
|
$result['items'],
|
||||||
|
))));
|
||||||
|
|
||||||
|
$actorMap = [];
|
||||||
|
if ([] !== $actorIds) {
|
||||||
|
$profiles = $this->profiles->findBy(['id' => $actorIds]);
|
||||||
|
foreach ($profiles as $profile) {
|
||||||
|
$label = trim(sprintf('%s %s', $profile->getFirstName(), $profile->getLastName()));
|
||||||
|
if ('' === $label) {
|
||||||
|
$label = $profile->getEmail() ?? $profile->getId();
|
||||||
|
}
|
||||||
|
$actorMap[$profile->getId()] = $label;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$items = array_map(
|
||||||
|
static function ($log) use ($actorMap) {
|
||||||
|
$actorId = $log->getActorProfileId();
|
||||||
|
$snapshot = $log->getSnapshot();
|
||||||
|
|
||||||
|
return [
|
||||||
|
'id' => $log->getId(),
|
||||||
|
'entityType' => $log->getEntityType(),
|
||||||
|
'entityId' => $log->getEntityId(),
|
||||||
|
'entityName' => $snapshot['name'] ?? null,
|
||||||
|
'entityRef' => $snapshot['reference'] ?? null,
|
||||||
|
'action' => $log->getAction(),
|
||||||
|
'createdAt' => $log->getCreatedAt()->format(DateTimeInterface::ATOM),
|
||||||
|
'actor' => $actorId
|
||||||
|
? [
|
||||||
|
'id' => $actorId,
|
||||||
|
'label' => $actorMap[$actorId] ?? $actorId,
|
||||||
|
]
|
||||||
|
: null,
|
||||||
|
'diff' => $log->getDiff(),
|
||||||
|
'snapshot' => $snapshot,
|
||||||
|
];
|
||||||
|
},
|
||||||
|
$result['items'],
|
||||||
|
);
|
||||||
|
|
||||||
|
return new JsonResponse([
|
||||||
|
'items' => array_values($items),
|
||||||
|
'total' => $result['total'],
|
||||||
|
'page' => $page,
|
||||||
|
'itemsPerPage' => $itemsPerPage,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,6 +7,7 @@ namespace App\Controller;
|
|||||||
use App\Repository\AuditLogRepository;
|
use App\Repository\AuditLogRepository;
|
||||||
use App\Repository\ComposantRepository;
|
use App\Repository\ComposantRepository;
|
||||||
use App\Repository\ProfileRepository;
|
use App\Repository\ProfileRepository;
|
||||||
|
use DateTimeInterface;
|
||||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
use Symfony\Component\Routing\Attribute\Route;
|
use Symfony\Component\Routing\Attribute\Route;
|
||||||
@@ -17,8 +18,7 @@ final class ComposantHistoryController
|
|||||||
private readonly ComposantRepository $components,
|
private readonly ComposantRepository $components,
|
||||||
private readonly AuditLogRepository $auditLogs,
|
private readonly AuditLogRepository $auditLogs,
|
||||||
private readonly ProfileRepository $profiles,
|
private readonly ProfileRepository $profiles,
|
||||||
) {
|
) {}
|
||||||
}
|
|
||||||
|
|
||||||
#[Route('/api/composants/{id}/history', name: 'api_composant_history', methods: ['GET'])]
|
#[Route('/api/composants/{id}/history', name: 'api_composant_history', methods: ['GET'])]
|
||||||
public function __invoke(string $id): JsonResponse
|
public function __invoke(string $id): JsonResponse
|
||||||
@@ -39,11 +39,11 @@ final class ComposantHistoryController
|
|||||||
))));
|
))));
|
||||||
|
|
||||||
$actorMap = [];
|
$actorMap = [];
|
||||||
if ($actorIds !== []) {
|
if ([] !== $actorIds) {
|
||||||
$profiles = $this->profiles->findBy(['id' => $actorIds]);
|
$profiles = $this->profiles->findBy(['id' => $actorIds]);
|
||||||
foreach ($profiles as $profile) {
|
foreach ($profiles as $profile) {
|
||||||
$label = trim(sprintf('%s %s', $profile->getFirstName(), $profile->getLastName()));
|
$label = trim(sprintf('%s %s', $profile->getFirstName(), $profile->getLastName()));
|
||||||
if ($label === '') {
|
if ('' === $label) {
|
||||||
$label = $profile->getEmail() ?? $profile->getId();
|
$label = $profile->getEmail() ?? $profile->getId();
|
||||||
}
|
}
|
||||||
$actorMap[$profile->getId()] = $label;
|
$actorMap[$profile->getId()] = $label;
|
||||||
@@ -57,7 +57,7 @@ final class ComposantHistoryController
|
|||||||
return [
|
return [
|
||||||
'id' => $log->getId(),
|
'id' => $log->getId(),
|
||||||
'action' => $log->getAction(),
|
'action' => $log->getAction(),
|
||||||
'createdAt' => $log->getCreatedAt()->format(\DateTimeInterface::ATOM),
|
'createdAt' => $log->getCreatedAt()->format(DateTimeInterface::ATOM),
|
||||||
'actor' => $actorId
|
'actor' => $actorId
|
||||||
? [
|
? [
|
||||||
'id' => $actorId,
|
'id' => $actorId,
|
||||||
@@ -77,4 +77,3 @@ final class ComposantHistoryController
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -29,8 +29,7 @@ class CustomFieldValueController extends AbstractController
|
|||||||
private readonly ComposantRepository $composantRepository,
|
private readonly ComposantRepository $composantRepository,
|
||||||
private readonly PieceRepository $pieceRepository,
|
private readonly PieceRepository $pieceRepository,
|
||||||
private readonly ProductRepository $productRepository,
|
private readonly ProductRepository $productRepository,
|
||||||
) {
|
) {}
|
||||||
}
|
|
||||||
|
|
||||||
#[Route('', name: 'custom_field_values_create', methods: ['POST'])]
|
#[Route('', name: 'custom_field_values_create', methods: ['POST'])]
|
||||||
public function create(Request $request): JsonResponse
|
public function create(Request $request): JsonResponse
|
||||||
@@ -173,7 +172,7 @@ class CustomFieldValueController extends AbstractController
|
|||||||
private function resolveCustomField(array $payload): CustomField|JsonResponse
|
private function resolveCustomField(array $payload): CustomField|JsonResponse
|
||||||
{
|
{
|
||||||
$customFieldId = isset($payload['customFieldId']) ? trim((string) $payload['customFieldId']) : '';
|
$customFieldId = isset($payload['customFieldId']) ? trim((string) $payload['customFieldId']) : '';
|
||||||
if ($customFieldId !== '') {
|
if ('' !== $customFieldId) {
|
||||||
$customField = $this->customFieldRepository->find($customFieldId);
|
$customField = $this->customFieldRepository->find($customFieldId);
|
||||||
if ($customField instanceof CustomField) {
|
if ($customField instanceof CustomField) {
|
||||||
return $customField;
|
return $customField;
|
||||||
@@ -183,7 +182,7 @@ class CustomFieldValueController extends AbstractController
|
|||||||
}
|
}
|
||||||
|
|
||||||
$customFieldName = isset($payload['customFieldName']) ? trim((string) $payload['customFieldName']) : '';
|
$customFieldName = isset($payload['customFieldName']) ? trim((string) $payload['customFieldName']) : '';
|
||||||
if ($customFieldName === '') {
|
if ('' === $customFieldName) {
|
||||||
return $this->json(['success' => false, 'error' => 'customFieldId or customFieldName is required.'], 400);
|
return $this->json(['success' => false, 'error' => 'customFieldId or customFieldName is required.'], 400);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -207,7 +206,7 @@ class CustomFieldValueController extends AbstractController
|
|||||||
$entityType = isset($payload['entityType']) ? strtolower((string) $payload['entityType']) : '';
|
$entityType = isset($payload['entityType']) ? strtolower((string) $payload['entityType']) : '';
|
||||||
$entityId = isset($payload['entityId']) ? trim((string) $payload['entityId']) : '';
|
$entityId = isset($payload['entityId']) ? trim((string) $payload['entityId']) : '';
|
||||||
|
|
||||||
if ($entityType === '' || $entityId === '') {
|
if ('' === $entityType || '' === $entityId) {
|
||||||
foreach (['machine', 'composant', 'piece', 'product'] as $candidate) {
|
foreach (['machine', 'composant', 'piece', 'product'] as $candidate) {
|
||||||
$key = $candidate.'Id';
|
$key = $candidate.'Id';
|
||||||
if (!isset($payload[$key])) {
|
if (!isset($payload[$key])) {
|
||||||
@@ -215,11 +214,12 @@ class CustomFieldValueController extends AbstractController
|
|||||||
}
|
}
|
||||||
$entityType = $candidate;
|
$entityType = $candidate;
|
||||||
$entityId = trim((string) $payload[$key]);
|
$entityId = trim((string) $payload[$key]);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($entityType === '' || $entityId === '') {
|
if ('' === $entityType || '' === $entityId) {
|
||||||
return $this->json(['success' => false, 'error' => 'Entity target is missing.'], 400);
|
return $this->json(['success' => false, 'error' => 'Entity target is missing.'], 400);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -247,15 +247,22 @@ class CustomFieldValueController extends AbstractController
|
|||||||
switch ($type) {
|
switch ($type) {
|
||||||
case 'machine':
|
case 'machine':
|
||||||
$value->setMachine($entity);
|
$value->setMachine($entity);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'composant':
|
case 'composant':
|
||||||
$value->setComposant($entity);
|
$value->setComposant($entity);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'piece':
|
case 'piece':
|
||||||
$value->setPiece($entity);
|
$value->setPiece($entity);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'product':
|
case 'product':
|
||||||
$value->setProduct($entity);
|
$value->setProduct($entity);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,8 +25,7 @@ class DocumentQueryController extends AbstractController
|
|||||||
private readonly ComposantRepository $composantRepository,
|
private readonly ComposantRepository $composantRepository,
|
||||||
private readonly PieceRepository $pieceRepository,
|
private readonly PieceRepository $pieceRepository,
|
||||||
private readonly ProductRepository $productRepository,
|
private readonly ProductRepository $productRepository,
|
||||||
) {
|
) {}
|
||||||
}
|
|
||||||
|
|
||||||
#[Route('/site/{id}', name: 'documents_by_site', methods: ['GET'])]
|
#[Route('/site/{id}', name: 'documents_by_site', methods: ['GET'])]
|
||||||
public function listBySite(string $id): JsonResponse
|
public function listBySite(string $id): JsonResponse
|
||||||
|
|||||||
@@ -21,8 +21,7 @@ class MachineCustomFieldsController extends AbstractController
|
|||||||
private readonly EntityManagerInterface $entityManager,
|
private readonly EntityManagerInterface $entityManager,
|
||||||
private readonly MachineRepository $machineRepository,
|
private readonly MachineRepository $machineRepository,
|
||||||
private readonly CustomFieldValueRepository $customFieldValueRepository,
|
private readonly CustomFieldValueRepository $customFieldValueRepository,
|
||||||
) {
|
) {}
|
||||||
}
|
|
||||||
|
|
||||||
#[Route('/{id}/add-custom-fields', name: 'machine_add_custom_fields', methods: ['POST'])]
|
#[Route('/{id}/add-custom-fields', name: 'machine_add_custom_fields', methods: ['POST'])]
|
||||||
public function addMissingCustomFields(string $id): JsonResponse
|
public function addMissingCustomFields(string $id): JsonResponse
|
||||||
|
|||||||
79
src/Controller/MachineHistoryController.php
Normal file
79
src/Controller/MachineHistoryController.php
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Controller;
|
||||||
|
|
||||||
|
use App\Repository\AuditLogRepository;
|
||||||
|
use App\Repository\MachineRepository;
|
||||||
|
use App\Repository\ProfileRepository;
|
||||||
|
use DateTimeInterface;
|
||||||
|
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
use Symfony\Component\Routing\Attribute\Route;
|
||||||
|
|
||||||
|
final class MachineHistoryController
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private readonly MachineRepository $machines,
|
||||||
|
private readonly AuditLogRepository $auditLogs,
|
||||||
|
private readonly ProfileRepository $profiles,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
#[Route('/api/machines/{id}/history', name: 'api_machine_history', methods: ['GET'])]
|
||||||
|
public function __invoke(string $id): JsonResponse
|
||||||
|
{
|
||||||
|
$machine = $this->machines->find($id);
|
||||||
|
if (!$machine) {
|
||||||
|
return new JsonResponse(
|
||||||
|
['message' => 'Machine introuvable.'],
|
||||||
|
Response::HTTP_NOT_FOUND,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$logs = $this->auditLogs->findEntityHistory('machine', $id, 200);
|
||||||
|
|
||||||
|
$actorIds = array_values(array_unique(array_filter(array_map(
|
||||||
|
static fn ($log) => $log->getActorProfileId(),
|
||||||
|
$logs,
|
||||||
|
))));
|
||||||
|
|
||||||
|
$actorMap = [];
|
||||||
|
if ([] !== $actorIds) {
|
||||||
|
$profiles = $this->profiles->findBy(['id' => $actorIds]);
|
||||||
|
foreach ($profiles as $profile) {
|
||||||
|
$label = trim(sprintf('%s %s', $profile->getFirstName(), $profile->getLastName()));
|
||||||
|
if ('' === $label) {
|
||||||
|
$label = $profile->getEmail() ?? $profile->getId();
|
||||||
|
}
|
||||||
|
$actorMap[$profile->getId()] = $label;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$items = array_map(
|
||||||
|
static function ($log) use ($actorMap) {
|
||||||
|
$actorId = $log->getActorProfileId();
|
||||||
|
|
||||||
|
return [
|
||||||
|
'id' => $log->getId(),
|
||||||
|
'action' => $log->getAction(),
|
||||||
|
'createdAt' => $log->getCreatedAt()->format(DateTimeInterface::ATOM),
|
||||||
|
'actor' => $actorId
|
||||||
|
? [
|
||||||
|
'id' => $actorId,
|
||||||
|
'label' => $actorMap[$actorId] ?? $actorId,
|
||||||
|
]
|
||||||
|
: null,
|
||||||
|
'diff' => $log->getDiff(),
|
||||||
|
'snapshot' => $log->getSnapshot(),
|
||||||
|
];
|
||||||
|
},
|
||||||
|
$logs,
|
||||||
|
);
|
||||||
|
|
||||||
|
return new JsonResponse([
|
||||||
|
'items' => array_values($items),
|
||||||
|
'total' => count($items),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ declare(strict_types=1);
|
|||||||
namespace App\Controller;
|
namespace App\Controller;
|
||||||
|
|
||||||
use App\Entity\Composant;
|
use App\Entity\Composant;
|
||||||
|
use App\Entity\CustomField;
|
||||||
use App\Entity\Machine;
|
use App\Entity\Machine;
|
||||||
use App\Entity\MachineComponentLink;
|
use App\Entity\MachineComponentLink;
|
||||||
use App\Entity\MachinePieceLink;
|
use App\Entity\MachinePieceLink;
|
||||||
@@ -47,8 +48,7 @@ class MachineSkeletonController extends AbstractController
|
|||||||
private readonly TypeMachineComponentRequirementRepository $componentRequirementRepository,
|
private readonly TypeMachineComponentRequirementRepository $componentRequirementRepository,
|
||||||
private readonly TypeMachinePieceRequirementRepository $pieceRequirementRepository,
|
private readonly TypeMachinePieceRequirementRepository $pieceRequirementRepository,
|
||||||
private readonly TypeMachineProductRequirementRepository $productRequirementRepository,
|
private readonly TypeMachineProductRequirementRepository $productRequirementRepository,
|
||||||
) {
|
) {}
|
||||||
}
|
|
||||||
|
|
||||||
#[Route('/{id}/skeleton', name: 'machine_skeleton_get', methods: ['GET'])]
|
#[Route('/{id}/skeleton', name: 'machine_skeleton_get', methods: ['GET'])]
|
||||||
public function getSkeleton(string $id): JsonResponse
|
public function getSkeleton(string $id): JsonResponse
|
||||||
@@ -117,6 +117,7 @@ class MachineSkeletonController extends AbstractController
|
|||||||
if (!is_array($value)) {
|
if (!is_array($value)) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
return array_values(array_filter($value, static fn ($item) => is_array($item)));
|
return array_values(array_filter($value, static fn ($item) => is_array($item)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -341,21 +342,28 @@ class MachineSkeletonController extends AbstractController
|
|||||||
$componentIndex = $this->indexNormalizedLinks($normalizedComponentLinks);
|
$componentIndex = $this->indexNormalizedLinks($normalizedComponentLinks);
|
||||||
$normalizedPieceLinks = $this->normalizePieceLinks($pieceLinks);
|
$normalizedPieceLinks = $this->normalizePieceLinks($pieceLinks);
|
||||||
|
|
||||||
// Build component hierarchy
|
// Build component hierarchy – track which IDs are children
|
||||||
foreach ($normalizedComponentLinks as &$link) {
|
$childIds = [];
|
||||||
|
foreach ($normalizedComponentLinks as $link) {
|
||||||
$parentId = $link['parentComponentLinkId'] ?? null;
|
$parentId = $link['parentComponentLinkId'] ?? null;
|
||||||
if ($parentId && isset($componentIndex[$parentId])) {
|
if ($parentId && isset($componentIndex[$parentId])) {
|
||||||
$componentIndex[$parentId]['childLinks'][] = &$link;
|
$componentIndex[$parentId]['childLinks'][] = $link;
|
||||||
|
$childIds[$link['id']] = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
unset($link);
|
|
||||||
|
|
||||||
// Add pieces to components recursively
|
// Add pieces to components recursively
|
||||||
$this->attachPiecesToComponents($componentIndex, $normalizedPieceLinks);
|
$this->attachPiecesToComponents($componentIndex, $normalizedPieceLinks);
|
||||||
|
|
||||||
|
// Only return root-level components (exclude children already nested)
|
||||||
|
$rootComponents = array_filter(
|
||||||
|
$componentIndex,
|
||||||
|
static fn (array $link) => !isset($childIds[$link['id']]),
|
||||||
|
);
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'machine' => $this->normalizeMachine($machine),
|
'machine' => $this->normalizeMachine($machine),
|
||||||
'componentLinks' => array_values($componentIndex),
|
'componentLinks' => array_values($rootComponents),
|
||||||
'pieceLinks' => $normalizedPieceLinks,
|
'pieceLinks' => $normalizedPieceLinks,
|
||||||
'productLinks' => $this->normalizeProductLinks($productLinks),
|
'productLinks' => $this->normalizeProductLinks($productLinks),
|
||||||
];
|
];
|
||||||
@@ -653,7 +661,7 @@ class MachineSkeletonController extends AbstractController
|
|||||||
$reference = method_exists($link, 'getReferenceOverride') ? $link->getReferenceOverride() : null;
|
$reference = method_exists($link, 'getReferenceOverride') ? $link->getReferenceOverride() : null;
|
||||||
$prix = method_exists($link, 'getPrixOverride') ? $link->getPrixOverride() : null;
|
$prix = method_exists($link, 'getPrixOverride') ? $link->getPrixOverride() : null;
|
||||||
|
|
||||||
if ($name === null && $reference === null && $prix === null) {
|
if (null === $name && null === $reference && null === $prix) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -683,12 +691,12 @@ class MachineSkeletonController extends AbstractController
|
|||||||
|
|
||||||
private function stringOrNull(mixed $value): ?string
|
private function stringOrNull(mixed $value): ?string
|
||||||
{
|
{
|
||||||
if ($value === null) {
|
if (null === $value) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
$string = trim((string) $value);
|
$string = trim((string) $value);
|
||||||
|
|
||||||
return $string === '' ? null : $string;
|
return '' === $string ? null : $string;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function resolveIdentifier(array $entry, array $keys): ?string
|
private function resolveIdentifier(array $entry, array $keys): ?string
|
||||||
@@ -698,9 +706,10 @@ class MachineSkeletonController extends AbstractController
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
$value = $entry[$key];
|
$value = $entry[$key];
|
||||||
if ($value === null || $value === '') {
|
if (null === $value || '' === $value) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (string) $value;
|
return (string) $value;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -709,6 +718,7 @@ class MachineSkeletonController extends AbstractController
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @param array<array-key, object> $links
|
* @param array<array-key, object> $links
|
||||||
|
*
|
||||||
* @return array<string, object>
|
* @return array<string, object>
|
||||||
*/
|
*/
|
||||||
private function indexLinksById(array $links): array
|
private function indexLinksById(array $links): array
|
||||||
|
|||||||
55
src/Controller/ModelTypeConversionController.php
Normal file
55
src/Controller/ModelTypeConversionController.php
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Controller;
|
||||||
|
|
||||||
|
use App\Repository\ModelTypeRepository;
|
||||||
|
use App\Service\ModelTypeCategoryConversionService;
|
||||||
|
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
use Symfony\Component\Routing\Attribute\Route;
|
||||||
|
|
||||||
|
final class ModelTypeConversionController
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private readonly ModelTypeRepository $modelTypes,
|
||||||
|
private readonly ModelTypeCategoryConversionService $conversionService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
#[Route('/api/model_types/{id}/conversion-check', name: 'api_model_type_conversion_check', methods: ['GET'])]
|
||||||
|
public function check(string $id): JsonResponse
|
||||||
|
{
|
||||||
|
$modelType = $this->modelTypes->find($id);
|
||||||
|
|
||||||
|
if (!$modelType) {
|
||||||
|
return new JsonResponse(
|
||||||
|
['message' => 'Catégorie introuvable.'],
|
||||||
|
Response::HTTP_NOT_FOUND,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new JsonResponse($this->conversionService->checkConversion($id));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Route('/api/model_types/{id}/convert', name: 'api_model_type_convert', methods: ['POST'])]
|
||||||
|
public function convert(string $id): JsonResponse
|
||||||
|
{
|
||||||
|
$modelType = $this->modelTypes->find($id);
|
||||||
|
|
||||||
|
if (!$modelType) {
|
||||||
|
return new JsonResponse(
|
||||||
|
['message' => 'Catégorie introuvable.'],
|
||||||
|
Response::HTTP_NOT_FOUND,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = $this->conversionService->convert($id);
|
||||||
|
|
||||||
|
if (!$result['success']) {
|
||||||
|
return new JsonResponse($result, Response::HTTP_CONFLICT);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new JsonResponse($result);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,6 +7,7 @@ namespace App\Controller;
|
|||||||
use App\Repository\AuditLogRepository;
|
use App\Repository\AuditLogRepository;
|
||||||
use App\Repository\PieceRepository;
|
use App\Repository\PieceRepository;
|
||||||
use App\Repository\ProfileRepository;
|
use App\Repository\ProfileRepository;
|
||||||
|
use DateTimeInterface;
|
||||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
use Symfony\Component\Routing\Attribute\Route;
|
use Symfony\Component\Routing\Attribute\Route;
|
||||||
@@ -17,8 +18,7 @@ final class PieceHistoryController
|
|||||||
private readonly PieceRepository $pieces,
|
private readonly PieceRepository $pieces,
|
||||||
private readonly AuditLogRepository $auditLogs,
|
private readonly AuditLogRepository $auditLogs,
|
||||||
private readonly ProfileRepository $profiles,
|
private readonly ProfileRepository $profiles,
|
||||||
) {
|
) {}
|
||||||
}
|
|
||||||
|
|
||||||
#[Route('/api/pieces/{id}/history', name: 'api_piece_history', methods: ['GET'])]
|
#[Route('/api/pieces/{id}/history', name: 'api_piece_history', methods: ['GET'])]
|
||||||
public function __invoke(string $id): JsonResponse
|
public function __invoke(string $id): JsonResponse
|
||||||
@@ -39,11 +39,11 @@ final class PieceHistoryController
|
|||||||
))));
|
))));
|
||||||
|
|
||||||
$actorMap = [];
|
$actorMap = [];
|
||||||
if ($actorIds !== []) {
|
if ([] !== $actorIds) {
|
||||||
$profiles = $this->profiles->findBy(['id' => $actorIds]);
|
$profiles = $this->profiles->findBy(['id' => $actorIds]);
|
||||||
foreach ($profiles as $profile) {
|
foreach ($profiles as $profile) {
|
||||||
$label = trim(sprintf('%s %s', $profile->getFirstName(), $profile->getLastName()));
|
$label = trim(sprintf('%s %s', $profile->getFirstName(), $profile->getLastName()));
|
||||||
if ($label === '') {
|
if ('' === $label) {
|
||||||
$label = $profile->getEmail() ?? $profile->getId();
|
$label = $profile->getEmail() ?? $profile->getId();
|
||||||
}
|
}
|
||||||
$actorMap[$profile->getId()] = $label;
|
$actorMap[$profile->getId()] = $label;
|
||||||
@@ -57,7 +57,7 @@ final class PieceHistoryController
|
|||||||
return [
|
return [
|
||||||
'id' => $log->getId(),
|
'id' => $log->getId(),
|
||||||
'action' => $log->getAction(),
|
'action' => $log->getAction(),
|
||||||
'createdAt' => $log->getCreatedAt()->format(\DateTimeInterface::ATOM),
|
'createdAt' => $log->getCreatedAt()->format(DateTimeInterface::ATOM),
|
||||||
'actor' => $actorId
|
'actor' => $actorId
|
||||||
? [
|
? [
|
||||||
'id' => $actorId,
|
'id' => $actorId,
|
||||||
@@ -77,4 +77,3 @@ final class PieceHistoryController
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ namespace App\Controller;
|
|||||||
use App\Repository\AuditLogRepository;
|
use App\Repository\AuditLogRepository;
|
||||||
use App\Repository\ProductRepository;
|
use App\Repository\ProductRepository;
|
||||||
use App\Repository\ProfileRepository;
|
use App\Repository\ProfileRepository;
|
||||||
|
use DateTimeInterface;
|
||||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
use Symfony\Component\Routing\Attribute\Route;
|
use Symfony\Component\Routing\Attribute\Route;
|
||||||
@@ -17,8 +18,7 @@ final class ProductHistoryController
|
|||||||
private readonly ProductRepository $products,
|
private readonly ProductRepository $products,
|
||||||
private readonly AuditLogRepository $auditLogs,
|
private readonly AuditLogRepository $auditLogs,
|
||||||
private readonly ProfileRepository $profiles,
|
private readonly ProfileRepository $profiles,
|
||||||
) {
|
) {}
|
||||||
}
|
|
||||||
|
|
||||||
#[Route('/api/products/{id}/history', name: 'api_product_history', methods: ['GET'])]
|
#[Route('/api/products/{id}/history', name: 'api_product_history', methods: ['GET'])]
|
||||||
public function __invoke(string $id): JsonResponse
|
public function __invoke(string $id): JsonResponse
|
||||||
@@ -39,11 +39,11 @@ final class ProductHistoryController
|
|||||||
))));
|
))));
|
||||||
|
|
||||||
$actorMap = [];
|
$actorMap = [];
|
||||||
if ($actorIds !== []) {
|
if ([] !== $actorIds) {
|
||||||
$profiles = $this->profiles->findBy(['id' => $actorIds]);
|
$profiles = $this->profiles->findBy(['id' => $actorIds]);
|
||||||
foreach ($profiles as $profile) {
|
foreach ($profiles as $profile) {
|
||||||
$label = trim(sprintf('%s %s', $profile->getFirstName(), $profile->getLastName()));
|
$label = trim(sprintf('%s %s', $profile->getFirstName(), $profile->getLastName()));
|
||||||
if ($label === '') {
|
if ('' === $label) {
|
||||||
$label = $profile->getEmail() ?? $profile->getId();
|
$label = $profile->getEmail() ?? $profile->getId();
|
||||||
}
|
}
|
||||||
$actorMap[$profile->getId()] = $label;
|
$actorMap[$profile->getId()] = $label;
|
||||||
@@ -57,7 +57,7 @@ final class ProductHistoryController
|
|||||||
return [
|
return [
|
||||||
'id' => $log->getId(),
|
'id' => $log->getId(),
|
||||||
'action' => $log->getAction(),
|
'action' => $log->getAction(),
|
||||||
'createdAt' => $log->getCreatedAt()->format(\DateTimeInterface::ATOM),
|
'createdAt' => $log->getCreatedAt()->format(DateTimeInterface::ATOM),
|
||||||
'actor' => $actorId
|
'actor' => $actorId
|
||||||
? [
|
? [
|
||||||
'id' => $actorId,
|
'id' => $actorId,
|
||||||
@@ -77,4 +77,3 @@ final class ProductHistoryController
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,9 +12,7 @@ use Symfony\Component\Routing\Attribute\Route;
|
|||||||
|
|
||||||
final class SessionProfileController
|
final class SessionProfileController
|
||||||
{
|
{
|
||||||
public function __construct(private readonly ProfileRepository $profiles)
|
public function __construct(private readonly ProfileRepository $profiles) {}
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
#[Route('/api/session/profile', name: 'api_session_profile_get', methods: ['GET'])]
|
#[Route('/api/session/profile', name: 'api_session_profile_get', methods: ['GET'])]
|
||||||
public function getActiveProfile(Request $request): JsonResponse
|
public function getActiveProfile(Request $request): JsonResponse
|
||||||
@@ -32,6 +30,7 @@ final class SessionProfileController
|
|||||||
$profile = $this->profiles->find($profileId);
|
$profile = $this->profiles->find($profileId);
|
||||||
if (!$profile || !$profile->isActive()) {
|
if (!$profile || !$profile->isActive()) {
|
||||||
$session->remove('profileId');
|
$session->remove('profileId');
|
||||||
|
|
||||||
return new JsonResponse(['message' => 'Profil introuvable ou inactif.'], JsonResponse::HTTP_UNAUTHORIZED);
|
return new JsonResponse(['message' => 'Profil introuvable ou inactif.'], JsonResponse::HTTP_UNAUTHORIZED);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,8 +16,7 @@ final class SessionProfilesController
|
|||||||
public function __construct(
|
public function __construct(
|
||||||
private readonly ProfileRepository $profiles,
|
private readonly ProfileRepository $profiles,
|
||||||
private readonly EntityManagerInterface $entityManager
|
private readonly EntityManagerInterface $entityManager
|
||||||
) {
|
) {}
|
||||||
}
|
|
||||||
|
|
||||||
#[Route('/api/session/profiles', name: 'api_session_profiles_list', methods: ['GET'])]
|
#[Route('/api/session/profiles', name: 'api_session_profiles_list', methods: ['GET'])]
|
||||||
public function list(): JsonResponse
|
public function list(): JsonResponse
|
||||||
@@ -27,7 +26,8 @@ final class SessionProfilesController
|
|||||||
->setParameter('active', true)
|
->setParameter('active', true)
|
||||||
->orderBy('p.firstName', 'ASC')
|
->orderBy('p.firstName', 'ASC')
|
||||||
->getQuery()
|
->getQuery()
|
||||||
->getResult();
|
->getResult()
|
||||||
|
;
|
||||||
|
|
||||||
return new JsonResponse(array_map([$this, 'serializeProfile'], $items));
|
return new JsonResponse(array_map([$this, 'serializeProfile'], $items));
|
||||||
}
|
}
|
||||||
@@ -39,7 +39,7 @@ final class SessionProfilesController
|
|||||||
$firstName = trim((string) ($payload['firstName'] ?? ''));
|
$firstName = trim((string) ($payload['firstName'] ?? ''));
|
||||||
$lastName = trim((string) ($payload['lastName'] ?? ''));
|
$lastName = trim((string) ($payload['lastName'] ?? ''));
|
||||||
|
|
||||||
if ($firstName === '' || $lastName === '') {
|
if ('' === $firstName || '' === $lastName) {
|
||||||
return new JsonResponse(['message' => 'firstName et lastName sont requis.'], JsonResponse::HTTP_BAD_REQUEST);
|
return new JsonResponse(['message' => 'firstName et lastName sont requis.'], JsonResponse::HTTP_BAD_REQUEST);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -82,6 +82,7 @@ final class AlwaysQuoteStrategy implements QuoteStrategy
|
|||||||
foreach ($class->identifier as $fieldName) {
|
foreach ($class->identifier as $fieldName) {
|
||||||
if (isset($class->fieldMappings[$fieldName])) {
|
if (isset($class->fieldMappings[$fieldName])) {
|
||||||
$quotedColumnNames[] = $this->getColumnName($fieldName, $class, $platform);
|
$quotedColumnNames[] = $this->getColumnName($fieldName, $class, $platform);
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,7 +104,7 @@ final class AlwaysQuoteStrategy implements QuoteStrategy
|
|||||||
string $columnName,
|
string $columnName,
|
||||||
int $counter,
|
int $counter,
|
||||||
AbstractPlatform $platform,
|
AbstractPlatform $platform,
|
||||||
ClassMetadata|null $class = null,
|
?ClassMetadata $class = null,
|
||||||
): string {
|
): string {
|
||||||
return $this->getSQLResultCasing($platform, $columnName.'_'.$counter);
|
return $this->getSQLResultCasing($platform, $columnName.'_'.$counter);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ class AuditLog
|
|||||||
$this->createdAt = new DateTimeImmutable();
|
$this->createdAt = new DateTimeImmutable();
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->id === null) {
|
if (null === $this->id) {
|
||||||
$this->id = $this->generateCuid();
|
$this->id = $this->generateCuid();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,17 +24,17 @@ use Symfony\Component\Serializer\Attribute\Groups;
|
|||||||
#[ApiResource(
|
#[ApiResource(
|
||||||
normalizationContext: ['groups' => ['composant:read']],
|
normalizationContext: ['groups' => ['composant:read']],
|
||||||
paginationClientItemsPerPage: true,
|
paginationClientItemsPerPage: true,
|
||||||
paginationMaximumItemsPerPage: 500
|
paginationMaximumItemsPerPage: 200
|
||||||
)]
|
)]
|
||||||
class Composant
|
class Composant
|
||||||
{
|
{
|
||||||
#[ORM\Id]
|
#[ORM\Id]
|
||||||
#[ORM\Column(type: Types::STRING, length: 36)]
|
#[ORM\Column(type: Types::STRING, length: 36)]
|
||||||
#[Groups(['composant:read'])]
|
#[Groups(['composant:read', 'document:list'])]
|
||||||
private ?string $id = null;
|
private ?string $id = null;
|
||||||
|
|
||||||
#[ORM\Column(type: Types::STRING, length: 255, unique: true)]
|
#[ORM\Column(type: Types::STRING, length: 255, unique: true)]
|
||||||
#[Groups(['composant:read'])]
|
#[Groups(['composant:read', 'document:list'])]
|
||||||
private string $name;
|
private string $name;
|
||||||
|
|
||||||
#[ORM\Column(type: Types::STRING, length: 255, nullable: true)]
|
#[ORM\Column(type: Types::STRING, length: 255, nullable: true)]
|
||||||
@@ -144,7 +144,7 @@ class Composant
|
|||||||
|
|
||||||
public function setName(string $name): static
|
public function setName(string $name): static
|
||||||
{
|
{
|
||||||
$this->name = $name;
|
$this->name = mb_strtoupper(mb_substr($name, 0, 1)).mb_substr($name, 1);
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ use Doctrine\ORM\Mapping as ORM;
|
|||||||
#[ORM\HasLifecycleCallbacks]
|
#[ORM\HasLifecycleCallbacks]
|
||||||
#[ApiResource(
|
#[ApiResource(
|
||||||
paginationClientItemsPerPage: true,
|
paginationClientItemsPerPage: true,
|
||||||
paginationMaximumItemsPerPage: 500
|
paginationMaximumItemsPerPage: 200
|
||||||
)]
|
)]
|
||||||
class Constructeur
|
class Constructeur
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -6,10 +6,12 @@ namespace App\Entity;
|
|||||||
|
|
||||||
use ApiPlatform\Metadata\ApiResource;
|
use ApiPlatform\Metadata\ApiResource;
|
||||||
use App\Repository\CustomFieldRepository;
|
use App\Repository\CustomFieldRepository;
|
||||||
|
use DateTimeImmutable;
|
||||||
use Doctrine\Common\Collections\ArrayCollection;
|
use Doctrine\Common\Collections\ArrayCollection;
|
||||||
use Doctrine\Common\Collections\Collection;
|
use Doctrine\Common\Collections\Collection;
|
||||||
use Doctrine\DBAL\Types\Types;
|
use Doctrine\DBAL\Types\Types;
|
||||||
use Doctrine\ORM\Mapping as ORM;
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
|
use Symfony\Component\Serializer\Attribute\Groups;
|
||||||
|
|
||||||
#[ORM\Entity(repositoryClass: CustomFieldRepository::class)]
|
#[ORM\Entity(repositoryClass: CustomFieldRepository::class)]
|
||||||
#[ORM\Table(name: 'custom_fields')]
|
#[ORM\Table(name: 'custom_fields')]
|
||||||
@@ -19,24 +21,30 @@ class CustomField
|
|||||||
{
|
{
|
||||||
#[ORM\Id]
|
#[ORM\Id]
|
||||||
#[ORM\Column(type: Types::STRING, length: 36)]
|
#[ORM\Column(type: Types::STRING, length: 36)]
|
||||||
|
#[Groups(['composant:read', 'piece:read', 'product:read', 'machine:read'])]
|
||||||
private ?string $id = null;
|
private ?string $id = null;
|
||||||
|
|
||||||
#[ORM\Column(type: Types::STRING, length: 255)]
|
#[ORM\Column(type: Types::STRING, length: 255)]
|
||||||
|
#[Groups(['composant:read', 'piece:read', 'product:read', 'machine:read'])]
|
||||||
private string $name;
|
private string $name;
|
||||||
|
|
||||||
#[ORM\Column(type: Types::STRING, length: 50)]
|
#[ORM\Column(type: Types::STRING, length: 50)]
|
||||||
|
#[Groups(['composant:read', 'piece:read', 'product:read', 'machine:read'])]
|
||||||
private string $type;
|
private string $type;
|
||||||
|
|
||||||
#[ORM\Column(type: Types::BOOLEAN, options: ['default' => false])]
|
#[ORM\Column(type: Types::BOOLEAN, options: ['default' => false])]
|
||||||
|
#[Groups(['composant:read', 'piece:read', 'product:read', 'machine:read'])]
|
||||||
private bool $required = false;
|
private bool $required = false;
|
||||||
|
|
||||||
#[ORM\Column(type: Types::STRING, length: 255, nullable: true, name: 'defaultValue')]
|
#[ORM\Column(type: Types::STRING, length: 255, nullable: true, name: 'defaultValue')]
|
||||||
private ?string $defaultValue = null;
|
private ?string $defaultValue = null;
|
||||||
|
|
||||||
#[ORM\Column(type: Types::JSON, nullable: true)]
|
#[ORM\Column(type: Types::JSON, nullable: true)]
|
||||||
|
#[Groups(['composant:read', 'piece:read', 'product:read', 'machine:read'])]
|
||||||
private ?array $options = null;
|
private ?array $options = null;
|
||||||
|
|
||||||
#[ORM\Column(type: Types::INTEGER, options: ['default' => 0], name: 'orderIndex')]
|
#[ORM\Column(type: Types::INTEGER, options: ['default' => 0], name: 'orderIndex')]
|
||||||
|
#[Groups(['composant:read', 'piece:read', 'product:read', 'machine:read'])]
|
||||||
private int $orderIndex = 0;
|
private int $orderIndex = 0;
|
||||||
|
|
||||||
#[ORM\ManyToOne(targetEntity: TypeMachine::class, inversedBy: 'customFields')]
|
#[ORM\ManyToOne(targetEntity: TypeMachine::class, inversedBy: 'customFields')]
|
||||||
@@ -62,10 +70,10 @@ class CustomField
|
|||||||
private Collection $customFieldValues;
|
private Collection $customFieldValues;
|
||||||
|
|
||||||
#[ORM\Column(type: Types::DATETIME_IMMUTABLE, name: 'createdAt')]
|
#[ORM\Column(type: Types::DATETIME_IMMUTABLE, name: 'createdAt')]
|
||||||
private \DateTimeImmutable $createdAt;
|
private DateTimeImmutable $createdAt;
|
||||||
|
|
||||||
#[ORM\Column(type: Types::DATETIME_IMMUTABLE, name: 'updatedAt')]
|
#[ORM\Column(type: Types::DATETIME_IMMUTABLE, name: 'updatedAt')]
|
||||||
private \DateTimeImmutable $updatedAt;
|
private DateTimeImmutable $updatedAt;
|
||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
@@ -75,11 +83,11 @@ class CustomField
|
|||||||
#[ORM\PrePersist]
|
#[ORM\PrePersist]
|
||||||
public function setCreatedAtValue(): void
|
public function setCreatedAtValue(): void
|
||||||
{
|
{
|
||||||
$now = new \DateTimeImmutable();
|
$now = new DateTimeImmutable();
|
||||||
$this->createdAt = $now;
|
$this->createdAt = $now;
|
||||||
$this->updatedAt = $now;
|
$this->updatedAt = $now;
|
||||||
|
|
||||||
if ($this->id === null) {
|
if (null === $this->id) {
|
||||||
$this->id = $this->generateCuid();
|
$this->id = $this->generateCuid();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -87,12 +95,7 @@ class CustomField
|
|||||||
#[ORM\PreUpdate]
|
#[ORM\PreUpdate]
|
||||||
public function setUpdatedAtValue(): void
|
public function setUpdatedAtValue(): void
|
||||||
{
|
{
|
||||||
$this->updatedAt = new \DateTimeImmutable();
|
$this->updatedAt = new DateTimeImmutable();
|
||||||
}
|
|
||||||
|
|
||||||
private function generateCuid(): string
|
|
||||||
{
|
|
||||||
return 'cl' . bin2hex(random_bytes(12));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getId(): ?string
|
public function getId(): ?string
|
||||||
@@ -191,13 +194,18 @@ class CustomField
|
|||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getCreatedAt(): \DateTimeImmutable
|
public function getCreatedAt(): DateTimeImmutable
|
||||||
{
|
{
|
||||||
return $this->createdAt;
|
return $this->createdAt;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getUpdatedAt(): \DateTimeImmutable
|
public function getUpdatedAt(): DateTimeImmutable
|
||||||
{
|
{
|
||||||
return $this->updatedAt;
|
return $this->updatedAt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function generateCuid(): string
|
||||||
|
{
|
||||||
|
return 'cl'.bin2hex(random_bytes(12));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,8 +6,10 @@ namespace App\Entity;
|
|||||||
|
|
||||||
use ApiPlatform\Metadata\ApiResource;
|
use ApiPlatform\Metadata\ApiResource;
|
||||||
use App\Repository\CustomFieldValueRepository;
|
use App\Repository\CustomFieldValueRepository;
|
||||||
|
use DateTimeImmutable;
|
||||||
use Doctrine\DBAL\Types\Types;
|
use Doctrine\DBAL\Types\Types;
|
||||||
use Doctrine\ORM\Mapping as ORM;
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
|
use Symfony\Component\Serializer\Attribute\Groups;
|
||||||
|
|
||||||
#[ORM\Entity(repositoryClass: CustomFieldValueRepository::class)]
|
#[ORM\Entity(repositoryClass: CustomFieldValueRepository::class)]
|
||||||
#[ORM\Table(name: 'custom_field_values')]
|
#[ORM\Table(name: 'custom_field_values')]
|
||||||
@@ -17,13 +19,16 @@ class CustomFieldValue
|
|||||||
{
|
{
|
||||||
#[ORM\Id]
|
#[ORM\Id]
|
||||||
#[ORM\Column(type: Types::STRING, length: 36)]
|
#[ORM\Column(type: Types::STRING, length: 36)]
|
||||||
|
#[Groups(['composant:read', 'piece:read', 'product:read', 'machine:read'])]
|
||||||
private ?string $id = null;
|
private ?string $id = null;
|
||||||
|
|
||||||
#[ORM\Column(type: Types::STRING, length: 255)]
|
#[ORM\Column(type: Types::STRING, length: 255)]
|
||||||
|
#[Groups(['composant:read', 'piece:read', 'product:read', 'machine:read'])]
|
||||||
private string $value;
|
private string $value;
|
||||||
|
|
||||||
#[ORM\ManyToOne(targetEntity: CustomField::class, inversedBy: 'customFieldValues')]
|
#[ORM\ManyToOne(targetEntity: CustomField::class, inversedBy: 'customFieldValues')]
|
||||||
#[ORM\JoinColumn(name: 'customFieldId', referencedColumnName: 'id', nullable: false, onDelete: 'CASCADE')]
|
#[ORM\JoinColumn(name: 'customFieldId', referencedColumnName: 'id', nullable: false, onDelete: 'CASCADE')]
|
||||||
|
#[Groups(['composant:read', 'piece:read', 'product:read', 'machine:read'])]
|
||||||
private CustomField $customField;
|
private CustomField $customField;
|
||||||
|
|
||||||
#[ORM\ManyToOne(targetEntity: Machine::class, inversedBy: 'customFieldValues')]
|
#[ORM\ManyToOne(targetEntity: Machine::class, inversedBy: 'customFieldValues')]
|
||||||
@@ -43,19 +48,21 @@ class CustomFieldValue
|
|||||||
private ?Product $product = null;
|
private ?Product $product = null;
|
||||||
|
|
||||||
#[ORM\Column(type: Types::DATETIME_IMMUTABLE, name: 'createdAt')]
|
#[ORM\Column(type: Types::DATETIME_IMMUTABLE, name: 'createdAt')]
|
||||||
private \DateTimeImmutable $createdAt;
|
#[Groups(['composant:read', 'piece:read', 'product:read', 'machine:read'])]
|
||||||
|
private DateTimeImmutable $createdAt;
|
||||||
|
|
||||||
#[ORM\Column(type: Types::DATETIME_IMMUTABLE, name: 'updatedAt')]
|
#[ORM\Column(type: Types::DATETIME_IMMUTABLE, name: 'updatedAt')]
|
||||||
private \DateTimeImmutable $updatedAt;
|
#[Groups(['composant:read', 'piece:read', 'product:read', 'machine:read'])]
|
||||||
|
private DateTimeImmutable $updatedAt;
|
||||||
|
|
||||||
#[ORM\PrePersist]
|
#[ORM\PrePersist]
|
||||||
public function setCreatedAtValue(): void
|
public function setCreatedAtValue(): void
|
||||||
{
|
{
|
||||||
$now = new \DateTimeImmutable();
|
$now = new DateTimeImmutable();
|
||||||
$this->createdAt = $now;
|
$this->createdAt = $now;
|
||||||
$this->updatedAt = $now;
|
$this->updatedAt = $now;
|
||||||
|
|
||||||
if ($this->id === null) {
|
if (null === $this->id) {
|
||||||
$this->id = $this->generateCuid();
|
$this->id = $this->generateCuid();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -63,12 +70,7 @@ class CustomFieldValue
|
|||||||
#[ORM\PreUpdate]
|
#[ORM\PreUpdate]
|
||||||
public function setUpdatedAtValue(): void
|
public function setUpdatedAtValue(): void
|
||||||
{
|
{
|
||||||
$this->updatedAt = new \DateTimeImmutable();
|
$this->updatedAt = new DateTimeImmutable();
|
||||||
}
|
|
||||||
|
|
||||||
private function generateCuid(): string
|
|
||||||
{
|
|
||||||
return 'cl' . bin2hex(random_bytes(12));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getId(): ?string
|
public function getId(): ?string
|
||||||
@@ -155,13 +157,18 @@ class CustomFieldValue
|
|||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getCreatedAt(): \DateTimeImmutable
|
public function getCreatedAt(): DateTimeImmutable
|
||||||
{
|
{
|
||||||
return $this->createdAt;
|
return $this->createdAt;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getUpdatedAt(): \DateTimeImmutable
|
public function getUpdatedAt(): DateTimeImmutable
|
||||||
{
|
{
|
||||||
return $this->updatedAt;
|
return $this->updatedAt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function generateCuid(): string
|
||||||
|
{
|
||||||
|
return 'cl'.bin2hex(random_bytes(12));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,13 @@ declare(strict_types=1);
|
|||||||
namespace App\Entity;
|
namespace App\Entity;
|
||||||
|
|
||||||
use ApiPlatform\Metadata\ApiResource;
|
use ApiPlatform\Metadata\ApiResource;
|
||||||
|
use ApiPlatform\Metadata\Delete;
|
||||||
|
use ApiPlatform\Metadata\Get;
|
||||||
|
use ApiPlatform\Metadata\GetCollection;
|
||||||
|
use ApiPlatform\Metadata\Post;
|
||||||
|
use ApiPlatform\Metadata\Put;
|
||||||
use App\Repository\DocumentRepository;
|
use App\Repository\DocumentRepository;
|
||||||
|
use DateTimeImmutable;
|
||||||
use Doctrine\DBAL\Types\Types;
|
use Doctrine\DBAL\Types\Types;
|
||||||
use Doctrine\ORM\Mapping as ORM;
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
use Symfony\Component\Serializer\Attribute\Groups;
|
use Symfony\Component\Serializer\Attribute\Groups;
|
||||||
@@ -13,68 +19,84 @@ use Symfony\Component\Serializer\Attribute\Groups;
|
|||||||
#[ORM\Entity(repositoryClass: DocumentRepository::class)]
|
#[ORM\Entity(repositoryClass: DocumentRepository::class)]
|
||||||
#[ORM\Table(name: 'documents')]
|
#[ORM\Table(name: 'documents')]
|
||||||
#[ORM\HasLifecycleCallbacks]
|
#[ORM\HasLifecycleCallbacks]
|
||||||
#[ApiResource]
|
#[ApiResource(
|
||||||
|
operations: [
|
||||||
|
new GetCollection(normalizationContext: ['groups' => ['document:list']]),
|
||||||
|
new Get(normalizationContext: ['groups' => ['document:list', 'document:detail']]),
|
||||||
|
new Post(),
|
||||||
|
new Put(),
|
||||||
|
new Delete(),
|
||||||
|
],
|
||||||
|
paginationClientItemsPerPage: true,
|
||||||
|
paginationMaximumItemsPerPage: 200
|
||||||
|
)]
|
||||||
class Document
|
class Document
|
||||||
{
|
{
|
||||||
#[ORM\Id]
|
#[ORM\Id]
|
||||||
#[ORM\Column(type: Types::STRING, length: 36)]
|
#[ORM\Column(type: Types::STRING, length: 36)]
|
||||||
#[Groups(['document:read', 'composant:read', 'piece:read', 'product:read'])]
|
#[Groups(['document:list', 'document:read', 'composant:read', 'piece:read', 'product:read'])]
|
||||||
private ?string $id = null;
|
private ?string $id = null;
|
||||||
|
|
||||||
#[ORM\Column(type: Types::STRING, length: 255)]
|
#[ORM\Column(type: Types::STRING, length: 255)]
|
||||||
#[Groups(['document:read', 'composant:read', 'piece:read', 'product:read'])]
|
#[Groups(['document:list', 'document:read', 'composant:read', 'piece:read', 'product:read'])]
|
||||||
private string $name;
|
private string $name;
|
||||||
|
|
||||||
#[ORM\Column(type: Types::STRING, length: 255)]
|
#[ORM\Column(type: Types::STRING, length: 255)]
|
||||||
#[Groups(['document:read', 'composant:read', 'piece:read', 'product:read'])]
|
#[Groups(['document:list', 'document:read', 'composant:read', 'piece:read', 'product:read'])]
|
||||||
private string $filename;
|
private string $filename;
|
||||||
|
|
||||||
#[ORM\Column(type: Types::TEXT)]
|
#[ORM\Column(type: Types::TEXT)]
|
||||||
#[Groups(['document:read', 'composant:read', 'piece:read', 'product:read'])]
|
#[Groups(['document:detail', 'document:read', 'composant:read', 'piece:read', 'product:read'])]
|
||||||
private string $path;
|
private string $path;
|
||||||
|
|
||||||
#[ORM\Column(type: Types::STRING, length: 100, name: 'mimeType')]
|
#[ORM\Column(type: Types::STRING, length: 100, name: 'mimeType')]
|
||||||
#[Groups(['document:read', 'composant:read', 'piece:read', 'product:read'])]
|
#[Groups(['document:list', 'document:read', 'composant:read', 'piece:read', 'product:read'])]
|
||||||
private string $mimeType;
|
private string $mimeType;
|
||||||
|
|
||||||
#[ORM\Column(type: Types::INTEGER)]
|
#[ORM\Column(type: Types::INTEGER)]
|
||||||
#[Groups(['document:read', 'composant:read', 'piece:read', 'product:read'])]
|
#[Groups(['document:list', 'document:read', 'composant:read', 'piece:read', 'product:read'])]
|
||||||
private int $size;
|
private int $size;
|
||||||
|
|
||||||
#[ORM\ManyToOne(targetEntity: Machine::class, inversedBy: 'documents')]
|
#[ORM\ManyToOne(targetEntity: Machine::class, inversedBy: 'documents')]
|
||||||
#[ORM\JoinColumn(name: 'machineId', referencedColumnName: 'id', nullable: true, onDelete: 'CASCADE')]
|
#[ORM\JoinColumn(name: 'machineId', referencedColumnName: 'id', nullable: true, onDelete: 'CASCADE')]
|
||||||
|
#[Groups(['document:list'])]
|
||||||
private ?Machine $machine = null;
|
private ?Machine $machine = null;
|
||||||
|
|
||||||
#[ORM\ManyToOne(targetEntity: Composant::class, inversedBy: 'documents')]
|
#[ORM\ManyToOne(targetEntity: Composant::class, inversedBy: 'documents')]
|
||||||
#[ORM\JoinColumn(name: 'composantId', referencedColumnName: 'id', nullable: true, onDelete: 'CASCADE')]
|
#[ORM\JoinColumn(name: 'composantId', referencedColumnName: 'id', nullable: true, onDelete: 'CASCADE')]
|
||||||
|
#[Groups(['document:list'])]
|
||||||
private ?Composant $composant = null;
|
private ?Composant $composant = null;
|
||||||
|
|
||||||
#[ORM\ManyToOne(targetEntity: Piece::class, inversedBy: 'documents')]
|
#[ORM\ManyToOne(targetEntity: Piece::class, inversedBy: 'documents')]
|
||||||
#[ORM\JoinColumn(name: 'pieceId', referencedColumnName: 'id', nullable: true, onDelete: 'CASCADE')]
|
#[ORM\JoinColumn(name: 'pieceId', referencedColumnName: 'id', nullable: true, onDelete: 'CASCADE')]
|
||||||
|
#[Groups(['document:list'])]
|
||||||
private ?Piece $piece = null;
|
private ?Piece $piece = null;
|
||||||
|
|
||||||
#[ORM\ManyToOne(targetEntity: Product::class, inversedBy: 'documents')]
|
#[ORM\ManyToOne(targetEntity: Product::class, inversedBy: 'documents')]
|
||||||
#[ORM\JoinColumn(name: 'productId', referencedColumnName: 'id', nullable: true, onDelete: 'CASCADE')]
|
#[ORM\JoinColumn(name: 'productId', referencedColumnName: 'id', nullable: true, onDelete: 'CASCADE')]
|
||||||
|
#[Groups(['document:list'])]
|
||||||
private ?Product $product = null;
|
private ?Product $product = null;
|
||||||
|
|
||||||
#[ORM\ManyToOne(targetEntity: Site::class, inversedBy: 'documents')]
|
#[ORM\ManyToOne(targetEntity: Site::class, inversedBy: 'documents')]
|
||||||
#[ORM\JoinColumn(name: 'siteId', referencedColumnName: 'id', nullable: true, onDelete: 'CASCADE')]
|
#[ORM\JoinColumn(name: 'siteId', referencedColumnName: 'id', nullable: true, onDelete: 'CASCADE')]
|
||||||
|
#[Groups(['document:list'])]
|
||||||
private ?Site $site = null;
|
private ?Site $site = null;
|
||||||
|
|
||||||
#[ORM\Column(type: Types::DATETIME_IMMUTABLE, name: 'createdAt')]
|
#[ORM\Column(type: Types::DATETIME_IMMUTABLE, name: 'createdAt')]
|
||||||
private \DateTimeImmutable $createdAt;
|
#[Groups(['document:list'])]
|
||||||
|
private DateTimeImmutable $createdAt;
|
||||||
|
|
||||||
#[ORM\Column(type: Types::DATETIME_IMMUTABLE, name: 'updatedAt')]
|
#[ORM\Column(type: Types::DATETIME_IMMUTABLE, name: 'updatedAt')]
|
||||||
private \DateTimeImmutable $updatedAt;
|
private DateTimeImmutable $updatedAt;
|
||||||
|
|
||||||
#[ORM\PrePersist]
|
#[ORM\PrePersist]
|
||||||
public function setCreatedAtValue(): void
|
public function setCreatedAtValue(): void
|
||||||
{
|
{
|
||||||
$now = new \DateTimeImmutable();
|
$now = new DateTimeImmutable();
|
||||||
$this->createdAt = $now;
|
$this->createdAt = $now;
|
||||||
$this->updatedAt = $now;
|
$this->updatedAt = $now;
|
||||||
|
|
||||||
if ($this->id === null) {
|
if (null === $this->id) {
|
||||||
$this->id = $this->generateCuid();
|
$this->id = $this->generateCuid();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -82,12 +104,7 @@ class Document
|
|||||||
#[ORM\PreUpdate]
|
#[ORM\PreUpdate]
|
||||||
public function setUpdatedAtValue(): void
|
public function setUpdatedAtValue(): void
|
||||||
{
|
{
|
||||||
$this->updatedAt = new \DateTimeImmutable();
|
$this->updatedAt = new DateTimeImmutable();
|
||||||
}
|
|
||||||
|
|
||||||
private function generateCuid(): string
|
|
||||||
{
|
|
||||||
return 'cl' . bin2hex(random_bytes(12));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getId(): ?string
|
public function getId(): ?string
|
||||||
@@ -222,13 +239,18 @@ class Document
|
|||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getCreatedAt(): \DateTimeImmutable
|
public function getCreatedAt(): DateTimeImmutable
|
||||||
{
|
{
|
||||||
return $this->createdAt;
|
return $this->createdAt;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getUpdatedAt(): \DateTimeImmutable
|
public function getUpdatedAt(): DateTimeImmutable
|
||||||
{
|
{
|
||||||
return $this->updatedAt;
|
return $this->updatedAt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function generateCuid(): string
|
||||||
|
{
|
||||||
|
return 'cl'.bin2hex(random_bytes(12));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,10 +6,12 @@ namespace App\Entity;
|
|||||||
|
|
||||||
use ApiPlatform\Metadata\ApiResource;
|
use ApiPlatform\Metadata\ApiResource;
|
||||||
use App\Repository\MachineRepository;
|
use App\Repository\MachineRepository;
|
||||||
|
use DateTimeImmutable;
|
||||||
use Doctrine\Common\Collections\ArrayCollection;
|
use Doctrine\Common\Collections\ArrayCollection;
|
||||||
use Doctrine\Common\Collections\Collection;
|
use Doctrine\Common\Collections\Collection;
|
||||||
use Doctrine\DBAL\Types\Types;
|
use Doctrine\DBAL\Types\Types;
|
||||||
use Doctrine\ORM\Mapping as ORM;
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
|
use Symfony\Component\Serializer\Attribute\Groups;
|
||||||
|
|
||||||
#[ORM\Entity(repositoryClass: MachineRepository::class)]
|
#[ORM\Entity(repositoryClass: MachineRepository::class)]
|
||||||
#[ORM\Table(name: 'machines')]
|
#[ORM\Table(name: 'machines')]
|
||||||
@@ -19,9 +21,11 @@ class Machine
|
|||||||
{
|
{
|
||||||
#[ORM\Id]
|
#[ORM\Id]
|
||||||
#[ORM\Column(type: Types::STRING, length: 36)]
|
#[ORM\Column(type: Types::STRING, length: 36)]
|
||||||
|
#[Groups(['document:list'])]
|
||||||
private ?string $id = null;
|
private ?string $id = null;
|
||||||
|
|
||||||
#[ORM\Column(type: Types::STRING, length: 255, unique: true)]
|
#[ORM\Column(type: Types::STRING, length: 255, unique: true)]
|
||||||
|
#[Groups(['document:list'])]
|
||||||
private string $name;
|
private string $name;
|
||||||
|
|
||||||
#[ORM\Column(type: Types::STRING, length: 255, nullable: true)]
|
#[ORM\Column(type: Types::STRING, length: 255, nullable: true)]
|
||||||
@@ -80,10 +84,10 @@ class Machine
|
|||||||
private Collection $customFieldValues;
|
private Collection $customFieldValues;
|
||||||
|
|
||||||
#[ORM\Column(type: Types::DATETIME_IMMUTABLE, name: 'createdAt')]
|
#[ORM\Column(type: Types::DATETIME_IMMUTABLE, name: 'createdAt')]
|
||||||
private \DateTimeImmutable $createdAt;
|
private DateTimeImmutable $createdAt;
|
||||||
|
|
||||||
#[ORM\Column(type: Types::DATETIME_IMMUTABLE, name: 'updatedAt')]
|
#[ORM\Column(type: Types::DATETIME_IMMUTABLE, name: 'updatedAt')]
|
||||||
private \DateTimeImmutable $updatedAt;
|
private DateTimeImmutable $updatedAt;
|
||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
@@ -98,11 +102,11 @@ class Machine
|
|||||||
#[ORM\PrePersist]
|
#[ORM\PrePersist]
|
||||||
public function setCreatedAtValue(): void
|
public function setCreatedAtValue(): void
|
||||||
{
|
{
|
||||||
$now = new \DateTimeImmutable();
|
$now = new DateTimeImmutable();
|
||||||
$this->createdAt = $now;
|
$this->createdAt = $now;
|
||||||
$this->updatedAt = $now;
|
$this->updatedAt = $now;
|
||||||
|
|
||||||
if ($this->id === null) {
|
if (null === $this->id) {
|
||||||
$this->id = $this->generateCuid();
|
$this->id = $this->generateCuid();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -110,12 +114,7 @@ class Machine
|
|||||||
#[ORM\PreUpdate]
|
#[ORM\PreUpdate]
|
||||||
public function setUpdatedAtValue(): void
|
public function setUpdatedAtValue(): void
|
||||||
{
|
{
|
||||||
$this->updatedAt = new \DateTimeImmutable();
|
$this->updatedAt = new DateTimeImmutable();
|
||||||
}
|
|
||||||
|
|
||||||
private function generateCuid(): string
|
|
||||||
{
|
|
||||||
return 'cl' . bin2hex(random_bytes(12));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getId(): ?string
|
public function getId(): ?string
|
||||||
@@ -238,13 +237,18 @@ class Machine
|
|||||||
return $this->customFieldValues;
|
return $this->customFieldValues;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getCreatedAt(): \DateTimeImmutable
|
public function getCreatedAt(): DateTimeImmutable
|
||||||
{
|
{
|
||||||
return $this->createdAt;
|
return $this->createdAt;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getUpdatedAt(): \DateTimeImmutable
|
public function getUpdatedAt(): DateTimeImmutable
|
||||||
{
|
{
|
||||||
return $this->updatedAt;
|
return $this->updatedAt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function generateCuid(): string
|
||||||
|
{
|
||||||
|
return 'cl'.bin2hex(random_bytes(12));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ namespace App\Entity;
|
|||||||
|
|
||||||
use ApiPlatform\Metadata\ApiResource;
|
use ApiPlatform\Metadata\ApiResource;
|
||||||
use App\Repository\MachineComponentLinkRepository;
|
use App\Repository\MachineComponentLinkRepository;
|
||||||
|
use DateTimeImmutable;
|
||||||
use Doctrine\Common\Collections\ArrayCollection;
|
use Doctrine\Common\Collections\ArrayCollection;
|
||||||
use Doctrine\Common\Collections\Collection;
|
use Doctrine\Common\Collections\Collection;
|
||||||
use Doctrine\DBAL\Types\Types;
|
use Doctrine\DBAL\Types\Types;
|
||||||
@@ -65,10 +66,10 @@ class MachineComponentLink
|
|||||||
private ?string $prixOverride = null;
|
private ?string $prixOverride = null;
|
||||||
|
|
||||||
#[ORM\Column(type: Types::DATETIME_IMMUTABLE, name: 'createdAt')]
|
#[ORM\Column(type: Types::DATETIME_IMMUTABLE, name: 'createdAt')]
|
||||||
private \DateTimeImmutable $createdAt;
|
private DateTimeImmutable $createdAt;
|
||||||
|
|
||||||
#[ORM\Column(type: Types::DATETIME_IMMUTABLE, name: 'updatedAt')]
|
#[ORM\Column(type: Types::DATETIME_IMMUTABLE, name: 'updatedAt')]
|
||||||
private \DateTimeImmutable $updatedAt;
|
private DateTimeImmutable $updatedAt;
|
||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
@@ -80,11 +81,11 @@ class MachineComponentLink
|
|||||||
#[ORM\PrePersist]
|
#[ORM\PrePersist]
|
||||||
public function setCreatedAtValue(): void
|
public function setCreatedAtValue(): void
|
||||||
{
|
{
|
||||||
$now = new \DateTimeImmutable();
|
$now = new DateTimeImmutable();
|
||||||
$this->createdAt = $now;
|
$this->createdAt = $now;
|
||||||
$this->updatedAt = $now;
|
$this->updatedAt = $now;
|
||||||
|
|
||||||
if ($this->id === null) {
|
if (null === $this->id) {
|
||||||
$this->id = $this->generateCuid();
|
$this->id = $this->generateCuid();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -92,12 +93,7 @@ class MachineComponentLink
|
|||||||
#[ORM\PreUpdate]
|
#[ORM\PreUpdate]
|
||||||
public function setUpdatedAtValue(): void
|
public function setUpdatedAtValue(): void
|
||||||
{
|
{
|
||||||
$this->updatedAt = new \DateTimeImmutable();
|
$this->updatedAt = new DateTimeImmutable();
|
||||||
}
|
|
||||||
|
|
||||||
private function generateCuid(): string
|
|
||||||
{
|
|
||||||
return 'cl' . bin2hex(random_bytes(12));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getId(): ?string
|
public function getId(): ?string
|
||||||
@@ -195,4 +191,9 @@ class MachineComponentLink
|
|||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function generateCuid(): string
|
||||||
|
{
|
||||||
|
return 'cl'.bin2hex(random_bytes(12));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ namespace App\Entity;
|
|||||||
|
|
||||||
use ApiPlatform\Metadata\ApiResource;
|
use ApiPlatform\Metadata\ApiResource;
|
||||||
use App\Repository\MachinePieceLinkRepository;
|
use App\Repository\MachinePieceLinkRepository;
|
||||||
|
use DateTimeImmutable;
|
||||||
use Doctrine\Common\Collections\ArrayCollection;
|
use Doctrine\Common\Collections\ArrayCollection;
|
||||||
use Doctrine\Common\Collections\Collection;
|
use Doctrine\Common\Collections\Collection;
|
||||||
use Doctrine\DBAL\Types\Types;
|
use Doctrine\DBAL\Types\Types;
|
||||||
@@ -53,10 +54,10 @@ class MachinePieceLink
|
|||||||
private ?string $prixOverride = null;
|
private ?string $prixOverride = null;
|
||||||
|
|
||||||
#[ORM\Column(type: Types::DATETIME_IMMUTABLE, name: 'createdAt')]
|
#[ORM\Column(type: Types::DATETIME_IMMUTABLE, name: 'createdAt')]
|
||||||
private \DateTimeImmutable $createdAt;
|
private DateTimeImmutable $createdAt;
|
||||||
|
|
||||||
#[ORM\Column(type: Types::DATETIME_IMMUTABLE, name: 'updatedAt')]
|
#[ORM\Column(type: Types::DATETIME_IMMUTABLE, name: 'updatedAt')]
|
||||||
private \DateTimeImmutable $updatedAt;
|
private DateTimeImmutable $updatedAt;
|
||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
@@ -66,11 +67,11 @@ class MachinePieceLink
|
|||||||
#[ORM\PrePersist]
|
#[ORM\PrePersist]
|
||||||
public function setCreatedAtValue(): void
|
public function setCreatedAtValue(): void
|
||||||
{
|
{
|
||||||
$now = new \DateTimeImmutable();
|
$now = new DateTimeImmutable();
|
||||||
$this->createdAt = $now;
|
$this->createdAt = $now;
|
||||||
$this->updatedAt = $now;
|
$this->updatedAt = $now;
|
||||||
|
|
||||||
if ($this->id === null) {
|
if (null === $this->id) {
|
||||||
$this->id = $this->generateCuid();
|
$this->id = $this->generateCuid();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -78,12 +79,7 @@ class MachinePieceLink
|
|||||||
#[ORM\PreUpdate]
|
#[ORM\PreUpdate]
|
||||||
public function setUpdatedAtValue(): void
|
public function setUpdatedAtValue(): void
|
||||||
{
|
{
|
||||||
$this->updatedAt = new \DateTimeImmutable();
|
$this->updatedAt = new DateTimeImmutable();
|
||||||
}
|
|
||||||
|
|
||||||
private function generateCuid(): string
|
|
||||||
{
|
|
||||||
return 'cl' . bin2hex(random_bytes(12));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getId(): ?string
|
public function getId(): ?string
|
||||||
@@ -181,4 +177,9 @@ class MachinePieceLink
|
|||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function generateCuid(): string
|
||||||
|
{
|
||||||
|
return 'cl'.bin2hex(random_bytes(12));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ namespace App\Entity;
|
|||||||
|
|
||||||
use ApiPlatform\Metadata\ApiResource;
|
use ApiPlatform\Metadata\ApiResource;
|
||||||
use App\Repository\MachineProductLinkRepository;
|
use App\Repository\MachineProductLinkRepository;
|
||||||
|
use DateTimeImmutable;
|
||||||
use Doctrine\Common\Collections\ArrayCollection;
|
use Doctrine\Common\Collections\ArrayCollection;
|
||||||
use Doctrine\Common\Collections\Collection;
|
use Doctrine\Common\Collections\Collection;
|
||||||
use Doctrine\DBAL\Types\Types;
|
use Doctrine\DBAL\Types\Types;
|
||||||
@@ -52,10 +53,10 @@ class MachineProductLink
|
|||||||
private ?MachinePieceLink $parentPieceLink = null;
|
private ?MachinePieceLink $parentPieceLink = null;
|
||||||
|
|
||||||
#[ORM\Column(type: Types::DATETIME_IMMUTABLE, name: 'createdAt')]
|
#[ORM\Column(type: Types::DATETIME_IMMUTABLE, name: 'createdAt')]
|
||||||
private \DateTimeImmutable $createdAt;
|
private DateTimeImmutable $createdAt;
|
||||||
|
|
||||||
#[ORM\Column(type: Types::DATETIME_IMMUTABLE, name: 'updatedAt')]
|
#[ORM\Column(type: Types::DATETIME_IMMUTABLE, name: 'updatedAt')]
|
||||||
private \DateTimeImmutable $updatedAt;
|
private DateTimeImmutable $updatedAt;
|
||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
@@ -65,11 +66,11 @@ class MachineProductLink
|
|||||||
#[ORM\PrePersist]
|
#[ORM\PrePersist]
|
||||||
public function setCreatedAtValue(): void
|
public function setCreatedAtValue(): void
|
||||||
{
|
{
|
||||||
$now = new \DateTimeImmutable();
|
$now = new DateTimeImmutable();
|
||||||
$this->createdAt = $now;
|
$this->createdAt = $now;
|
||||||
$this->updatedAt = $now;
|
$this->updatedAt = $now;
|
||||||
|
|
||||||
if ($this->id === null) {
|
if (null === $this->id) {
|
||||||
$this->id = $this->generateCuid();
|
$this->id = $this->generateCuid();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -77,12 +78,7 @@ class MachineProductLink
|
|||||||
#[ORM\PreUpdate]
|
#[ORM\PreUpdate]
|
||||||
public function setUpdatedAtValue(): void
|
public function setUpdatedAtValue(): void
|
||||||
{
|
{
|
||||||
$this->updatedAt = new \DateTimeImmutable();
|
$this->updatedAt = new DateTimeImmutable();
|
||||||
}
|
|
||||||
|
|
||||||
private function generateCuid(): string
|
|
||||||
{
|
|
||||||
return 'cl' . bin2hex(random_bytes(12));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getId(): ?string
|
public function getId(): ?string
|
||||||
@@ -168,4 +164,9 @@ class MachineProductLink
|
|||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function generateCuid(): string
|
||||||
|
{
|
||||||
|
return 'cl'.bin2hex(random_bytes(12));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace App\Entity;
|
namespace App\Entity;
|
||||||
|
|
||||||
|
use ApiPlatform\Doctrine\Orm\Filter\OrderFilter;
|
||||||
use ApiPlatform\Doctrine\Orm\Filter\SearchFilter;
|
use ApiPlatform\Doctrine\Orm\Filter\SearchFilter;
|
||||||
use ApiPlatform\Metadata\ApiFilter;
|
use ApiPlatform\Metadata\ApiFilter;
|
||||||
use ApiPlatform\Metadata\ApiResource;
|
use ApiPlatform\Metadata\ApiResource;
|
||||||
@@ -21,9 +22,10 @@ use Symfony\Component\Serializer\Annotation\Groups;
|
|||||||
#[ORM\UniqueConstraint(name: 'unique_category_name', columns: ['category', 'name'])]
|
#[ORM\UniqueConstraint(name: 'unique_category_name', columns: ['category', 'name'])]
|
||||||
#[ORM\HasLifecycleCallbacks]
|
#[ORM\HasLifecycleCallbacks]
|
||||||
#[ApiFilter(SearchFilter::class, properties: ['category' => 'exact', 'name' => 'ipartial'])]
|
#[ApiFilter(SearchFilter::class, properties: ['category' => 'exact', 'name' => 'ipartial'])]
|
||||||
|
#[ApiFilter(OrderFilter::class, properties: ['name', 'createdAt'])]
|
||||||
#[ApiResource(
|
#[ApiResource(
|
||||||
paginationClientItemsPerPage: true,
|
paginationClientItemsPerPage: true,
|
||||||
paginationMaximumItemsPerPage: 500
|
paginationMaximumItemsPerPage: 200
|
||||||
)]
|
)]
|
||||||
class ModelType
|
class ModelType
|
||||||
{
|
{
|
||||||
@@ -178,7 +180,7 @@ class ModelType
|
|||||||
|
|
||||||
public function setName(string $name): static
|
public function setName(string $name): static
|
||||||
{
|
{
|
||||||
$this->name = $name;
|
$this->name = mb_strtoupper(mb_substr($name, 0, 1)).mb_substr($name, 1);
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,17 +24,17 @@ use Symfony\Component\Serializer\Attribute\Groups;
|
|||||||
#[ApiResource(
|
#[ApiResource(
|
||||||
normalizationContext: ['groups' => ['piece:read']],
|
normalizationContext: ['groups' => ['piece:read']],
|
||||||
paginationClientItemsPerPage: true,
|
paginationClientItemsPerPage: true,
|
||||||
paginationMaximumItemsPerPage: 500
|
paginationMaximumItemsPerPage: 200
|
||||||
)]
|
)]
|
||||||
class Piece
|
class Piece
|
||||||
{
|
{
|
||||||
#[ORM\Id]
|
#[ORM\Id]
|
||||||
#[ORM\Column(type: Types::STRING, length: 36)]
|
#[ORM\Column(type: Types::STRING, length: 36)]
|
||||||
#[Groups(['piece:read'])]
|
#[Groups(['piece:read', 'document:list'])]
|
||||||
private ?string $id = null;
|
private ?string $id = null;
|
||||||
|
|
||||||
#[ORM\Column(type: Types::STRING, length: 255, unique: true)]
|
#[ORM\Column(type: Types::STRING, length: 255, unique: true)]
|
||||||
#[Groups(['piece:read'])]
|
#[Groups(['piece:read', 'document:list'])]
|
||||||
private string $name;
|
private string $name;
|
||||||
|
|
||||||
#[ORM\Column(type: Types::STRING, length: 255, nullable: true)]
|
#[ORM\Column(type: Types::STRING, length: 255, nullable: true)]
|
||||||
|
|||||||
@@ -24,17 +24,17 @@ use Symfony\Component\Serializer\Attribute\Groups;
|
|||||||
#[ApiResource(
|
#[ApiResource(
|
||||||
normalizationContext: ['groups' => ['product:read']],
|
normalizationContext: ['groups' => ['product:read']],
|
||||||
paginationClientItemsPerPage: true,
|
paginationClientItemsPerPage: true,
|
||||||
paginationMaximumItemsPerPage: 500
|
paginationMaximumItemsPerPage: 200
|
||||||
)]
|
)]
|
||||||
class Product
|
class Product
|
||||||
{
|
{
|
||||||
#[ORM\Id]
|
#[ORM\Id]
|
||||||
#[ORM\Column(type: Types::STRING, length: 36)]
|
#[ORM\Column(type: Types::STRING, length: 36)]
|
||||||
#[Groups(['product:read'])]
|
#[Groups(['product:read', 'document:list'])]
|
||||||
private ?string $id = null;
|
private ?string $id = null;
|
||||||
|
|
||||||
#[ORM\Column(type: Types::STRING, length: 255, unique: true)]
|
#[ORM\Column(type: Types::STRING, length: 255, unique: true)]
|
||||||
#[Groups(['product:read'])]
|
#[Groups(['product:read', 'document:list'])]
|
||||||
private string $name;
|
private string $name;
|
||||||
|
|
||||||
#[ORM\Column(type: Types::STRING, length: 255, nullable: true)]
|
#[ORM\Column(type: Types::STRING, length: 255, nullable: true)]
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ use Doctrine\Common\Collections\ArrayCollection;
|
|||||||
use Doctrine\Common\Collections\Collection;
|
use Doctrine\Common\Collections\Collection;
|
||||||
use Doctrine\DBAL\Types\Types;
|
use Doctrine\DBAL\Types\Types;
|
||||||
use Doctrine\ORM\Mapping as ORM;
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
|
use Symfony\Component\Serializer\Attribute\Groups;
|
||||||
use Symfony\Component\Validator\Constraints as Assert;
|
use Symfony\Component\Validator\Constraints as Assert;
|
||||||
|
|
||||||
#[ORM\Entity(repositoryClass: SiteRepository::class)]
|
#[ORM\Entity(repositoryClass: SiteRepository::class)]
|
||||||
@@ -30,16 +31,18 @@ use Symfony\Component\Validator\Constraints as Assert;
|
|||||||
new Delete(),
|
new Delete(),
|
||||||
],
|
],
|
||||||
paginationClientItemsPerPage: true,
|
paginationClientItemsPerPage: true,
|
||||||
paginationMaximumItemsPerPage: 500
|
paginationMaximumItemsPerPage: 200
|
||||||
)]
|
)]
|
||||||
class Site
|
class Site
|
||||||
{
|
{
|
||||||
#[ORM\Id]
|
#[ORM\Id]
|
||||||
#[ORM\Column(type: Types::STRING, length: 36)]
|
#[ORM\Column(type: Types::STRING, length: 36)]
|
||||||
|
#[Groups(['document:list'])]
|
||||||
private ?string $id = null;
|
private ?string $id = null;
|
||||||
|
|
||||||
#[ORM\Column(type: Types::STRING, length: 255)]
|
#[ORM\Column(type: Types::STRING, length: 255)]
|
||||||
#[Assert\NotBlank]
|
#[Assert\NotBlank]
|
||||||
|
#[Groups(['document:list'])]
|
||||||
private string $name;
|
private string $name;
|
||||||
|
|
||||||
#[ORM\Column(type: Types::STRING, length: 255, options: ['default' => ''], name: 'contactName')]
|
#[ORM\Column(type: Types::STRING, length: 255, options: ['default' => ''], name: 'contactName')]
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ use Symfony\Component\Validator\Constraints as Assert;
|
|||||||
new Delete(),
|
new Delete(),
|
||||||
],
|
],
|
||||||
paginationClientItemsPerPage: true,
|
paginationClientItemsPerPage: true,
|
||||||
paginationMaximumItemsPerPage: 500
|
paginationMaximumItemsPerPage: 200
|
||||||
)]
|
)]
|
||||||
class TypeMachine
|
class TypeMachine
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ namespace App\Entity;
|
|||||||
use ApiPlatform\Metadata\ApiProperty;
|
use ApiPlatform\Metadata\ApiProperty;
|
||||||
use ApiPlatform\Metadata\ApiResource;
|
use ApiPlatform\Metadata\ApiResource;
|
||||||
use App\Repository\TypeMachineComponentRequirementRepository;
|
use App\Repository\TypeMachineComponentRequirementRepository;
|
||||||
|
use DateTimeImmutable;
|
||||||
use Doctrine\Common\Collections\ArrayCollection;
|
use Doctrine\Common\Collections\ArrayCollection;
|
||||||
use Doctrine\Common\Collections\Collection;
|
use Doctrine\Common\Collections\Collection;
|
||||||
use Doctrine\DBAL\Types\Types;
|
use Doctrine\DBAL\Types\Types;
|
||||||
@@ -65,10 +66,10 @@ class TypeMachineComponentRequirement
|
|||||||
private Collection $machineComponentLinks;
|
private Collection $machineComponentLinks;
|
||||||
|
|
||||||
#[ORM\Column(type: Types::DATETIME_IMMUTABLE, name: 'createdAt')]
|
#[ORM\Column(type: Types::DATETIME_IMMUTABLE, name: 'createdAt')]
|
||||||
private \DateTimeImmutable $createdAt;
|
private DateTimeImmutable $createdAt;
|
||||||
|
|
||||||
#[ORM\Column(type: Types::DATETIME_IMMUTABLE, name: 'updatedAt')]
|
#[ORM\Column(type: Types::DATETIME_IMMUTABLE, name: 'updatedAt')]
|
||||||
private \DateTimeImmutable $updatedAt;
|
private DateTimeImmutable $updatedAt;
|
||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
@@ -78,11 +79,11 @@ class TypeMachineComponentRequirement
|
|||||||
#[ORM\PrePersist]
|
#[ORM\PrePersist]
|
||||||
public function setCreatedAtValue(): void
|
public function setCreatedAtValue(): void
|
||||||
{
|
{
|
||||||
$now = new \DateTimeImmutable();
|
$now = new DateTimeImmutable();
|
||||||
$this->createdAt = $now;
|
$this->createdAt = $now;
|
||||||
$this->updatedAt = $now;
|
$this->updatedAt = $now;
|
||||||
|
|
||||||
if ($this->id === null) {
|
if (null === $this->id) {
|
||||||
$this->id = $this->generateCuid();
|
$this->id = $this->generateCuid();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -90,12 +91,7 @@ class TypeMachineComponentRequirement
|
|||||||
#[ORM\PreUpdate]
|
#[ORM\PreUpdate]
|
||||||
public function setUpdatedAtValue(): void
|
public function setUpdatedAtValue(): void
|
||||||
{
|
{
|
||||||
$this->updatedAt = new \DateTimeImmutable();
|
$this->updatedAt = new DateTimeImmutable();
|
||||||
}
|
|
||||||
|
|
||||||
private function generateCuid(): string
|
|
||||||
{
|
|
||||||
return 'cl' . bin2hex(random_bytes(12));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getId(): ?string
|
public function getId(): ?string
|
||||||
@@ -205,4 +201,9 @@ class TypeMachineComponentRequirement
|
|||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function generateCuid(): string
|
||||||
|
{
|
||||||
|
return 'cl'.bin2hex(random_bytes(12));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ namespace App\Entity;
|
|||||||
use ApiPlatform\Metadata\ApiProperty;
|
use ApiPlatform\Metadata\ApiProperty;
|
||||||
use ApiPlatform\Metadata\ApiResource;
|
use ApiPlatform\Metadata\ApiResource;
|
||||||
use App\Repository\TypeMachinePieceRequirementRepository;
|
use App\Repository\TypeMachinePieceRequirementRepository;
|
||||||
|
use DateTimeImmutable;
|
||||||
use Doctrine\Common\Collections\ArrayCollection;
|
use Doctrine\Common\Collections\ArrayCollection;
|
||||||
use Doctrine\Common\Collections\Collection;
|
use Doctrine\Common\Collections\Collection;
|
||||||
use Doctrine\DBAL\Types\Types;
|
use Doctrine\DBAL\Types\Types;
|
||||||
@@ -65,10 +66,10 @@ class TypeMachinePieceRequirement
|
|||||||
private Collection $machinePieceLinks;
|
private Collection $machinePieceLinks;
|
||||||
|
|
||||||
#[ORM\Column(type: Types::DATETIME_IMMUTABLE, name: 'createdAt')]
|
#[ORM\Column(type: Types::DATETIME_IMMUTABLE, name: 'createdAt')]
|
||||||
private \DateTimeImmutable $createdAt;
|
private DateTimeImmutable $createdAt;
|
||||||
|
|
||||||
#[ORM\Column(type: Types::DATETIME_IMMUTABLE, name: 'updatedAt')]
|
#[ORM\Column(type: Types::DATETIME_IMMUTABLE, name: 'updatedAt')]
|
||||||
private \DateTimeImmutable $updatedAt;
|
private DateTimeImmutable $updatedAt;
|
||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
@@ -78,11 +79,11 @@ class TypeMachinePieceRequirement
|
|||||||
#[ORM\PrePersist]
|
#[ORM\PrePersist]
|
||||||
public function setCreatedAtValue(): void
|
public function setCreatedAtValue(): void
|
||||||
{
|
{
|
||||||
$now = new \DateTimeImmutable();
|
$now = new DateTimeImmutable();
|
||||||
$this->createdAt = $now;
|
$this->createdAt = $now;
|
||||||
$this->updatedAt = $now;
|
$this->updatedAt = $now;
|
||||||
|
|
||||||
if ($this->id === null) {
|
if (null === $this->id) {
|
||||||
$this->id = $this->generateCuid();
|
$this->id = $this->generateCuid();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -90,12 +91,7 @@ class TypeMachinePieceRequirement
|
|||||||
#[ORM\PreUpdate]
|
#[ORM\PreUpdate]
|
||||||
public function setUpdatedAtValue(): void
|
public function setUpdatedAtValue(): void
|
||||||
{
|
{
|
||||||
$this->updatedAt = new \DateTimeImmutable();
|
$this->updatedAt = new DateTimeImmutable();
|
||||||
}
|
|
||||||
|
|
||||||
private function generateCuid(): string
|
|
||||||
{
|
|
||||||
return 'cl' . bin2hex(random_bytes(12));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getId(): ?string
|
public function getId(): ?string
|
||||||
@@ -205,4 +201,9 @@ class TypeMachinePieceRequirement
|
|||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function generateCuid(): string
|
||||||
|
{
|
||||||
|
return 'cl'.bin2hex(random_bytes(12));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ namespace App\Entity;
|
|||||||
use ApiPlatform\Metadata\ApiProperty;
|
use ApiPlatform\Metadata\ApiProperty;
|
||||||
use ApiPlatform\Metadata\ApiResource;
|
use ApiPlatform\Metadata\ApiResource;
|
||||||
use App\Repository\TypeMachineProductRequirementRepository;
|
use App\Repository\TypeMachineProductRequirementRepository;
|
||||||
|
use DateTimeImmutable;
|
||||||
use Doctrine\Common\Collections\ArrayCollection;
|
use Doctrine\Common\Collections\ArrayCollection;
|
||||||
use Doctrine\Common\Collections\Collection;
|
use Doctrine\Common\Collections\Collection;
|
||||||
use Doctrine\DBAL\Types\Types;
|
use Doctrine\DBAL\Types\Types;
|
||||||
@@ -65,10 +66,10 @@ class TypeMachineProductRequirement
|
|||||||
private Collection $machineProductLinks;
|
private Collection $machineProductLinks;
|
||||||
|
|
||||||
#[ORM\Column(type: Types::DATETIME_IMMUTABLE, name: 'createdAt')]
|
#[ORM\Column(type: Types::DATETIME_IMMUTABLE, name: 'createdAt')]
|
||||||
private \DateTimeImmutable $createdAt;
|
private DateTimeImmutable $createdAt;
|
||||||
|
|
||||||
#[ORM\Column(type: Types::DATETIME_IMMUTABLE, name: 'updatedAt')]
|
#[ORM\Column(type: Types::DATETIME_IMMUTABLE, name: 'updatedAt')]
|
||||||
private \DateTimeImmutable $updatedAt;
|
private DateTimeImmutable $updatedAt;
|
||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
@@ -78,11 +79,11 @@ class TypeMachineProductRequirement
|
|||||||
#[ORM\PrePersist]
|
#[ORM\PrePersist]
|
||||||
public function setCreatedAtValue(): void
|
public function setCreatedAtValue(): void
|
||||||
{
|
{
|
||||||
$now = new \DateTimeImmutable();
|
$now = new DateTimeImmutable();
|
||||||
$this->createdAt = $now;
|
$this->createdAt = $now;
|
||||||
$this->updatedAt = $now;
|
$this->updatedAt = $now;
|
||||||
|
|
||||||
if ($this->id === null) {
|
if (null === $this->id) {
|
||||||
$this->id = $this->generateCuid();
|
$this->id = $this->generateCuid();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -90,12 +91,7 @@ class TypeMachineProductRequirement
|
|||||||
#[ORM\PreUpdate]
|
#[ORM\PreUpdate]
|
||||||
public function setUpdatedAtValue(): void
|
public function setUpdatedAtValue(): void
|
||||||
{
|
{
|
||||||
$this->updatedAt = new \DateTimeImmutable();
|
$this->updatedAt = new DateTimeImmutable();
|
||||||
}
|
|
||||||
|
|
||||||
private function generateCuid(): string
|
|
||||||
{
|
|
||||||
return 'cl' . bin2hex(random_bytes(12));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getId(): ?string
|
public function getId(): ?string
|
||||||
@@ -205,4 +201,9 @@ class TypeMachineProductRequirement
|
|||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function generateCuid(): string
|
||||||
|
{
|
||||||
|
return 'cl'.bin2hex(random_bytes(12));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,8 +6,11 @@ namespace App\EventSubscriber;
|
|||||||
|
|
||||||
use App\Entity\AuditLog;
|
use App\Entity\AuditLog;
|
||||||
use App\Entity\Composant;
|
use App\Entity\Composant;
|
||||||
|
use App\Entity\CustomFieldValue;
|
||||||
use App\Entity\ModelType;
|
use App\Entity\ModelType;
|
||||||
use App\Entity\Product;
|
use App\Entity\Product;
|
||||||
|
use App\Entity\Profile;
|
||||||
|
use DateTimeInterface;
|
||||||
use Doctrine\Bundle\DoctrineBundle\Attribute\AsDoctrineListener;
|
use Doctrine\Bundle\DoctrineBundle\Attribute\AsDoctrineListener;
|
||||||
use Doctrine\Common\Collections\Collection;
|
use Doctrine\Common\Collections\Collection;
|
||||||
use Doctrine\Common\EventSubscriber;
|
use Doctrine\Common\EventSubscriber;
|
||||||
@@ -15,15 +18,24 @@ use Doctrine\ORM\EntityManagerInterface;
|
|||||||
use Doctrine\ORM\Event\OnFlushEventArgs;
|
use Doctrine\ORM\Event\OnFlushEventArgs;
|
||||||
use Doctrine\ORM\Events;
|
use Doctrine\ORM\Events;
|
||||||
use Doctrine\ORM\PersistentCollection;
|
use Doctrine\ORM\PersistentCollection;
|
||||||
|
use Doctrine\ORM\UnitOfWork;
|
||||||
|
use Symfony\Bundle\SecurityBundle\Security;
|
||||||
use Symfony\Component\HttpFoundation\RequestStack;
|
use Symfony\Component\HttpFoundation\RequestStack;
|
||||||
use Symfony\Component\HttpFoundation\Session\SessionInterface;
|
use Symfony\Component\HttpFoundation\Session\SessionInterface;
|
||||||
|
use Throwable;
|
||||||
|
|
||||||
|
use function is_array;
|
||||||
|
use function is_object;
|
||||||
|
use function is_scalar;
|
||||||
|
use function method_exists;
|
||||||
|
|
||||||
#[AsDoctrineListener(event: Events::onFlush)]
|
#[AsDoctrineListener(event: Events::onFlush)]
|
||||||
final class ComposantAuditSubscriber implements EventSubscriber
|
final class ComposantAuditSubscriber implements EventSubscriber
|
||||||
{
|
{
|
||||||
public function __construct(private readonly RequestStack $requestStack)
|
public function __construct(
|
||||||
{
|
private readonly RequestStack $requestStack,
|
||||||
}
|
private readonly Security $security,
|
||||||
|
) {}
|
||||||
|
|
||||||
public function getSubscribedEvents(): array
|
public function getSubscribedEvents(): array
|
||||||
{
|
{
|
||||||
@@ -61,12 +73,12 @@ final class ComposantAuditSubscriber implements EventSubscriber
|
|||||||
}
|
}
|
||||||
|
|
||||||
$componentId = (string) $entity->getId();
|
$componentId = (string) $entity->getId();
|
||||||
if ($componentId === '') {
|
if ('' === $componentId) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
$diff = $this->buildDiffFromChangeSet($uow->getEntityChangeSet($entity));
|
$diff = $this->buildDiffFromChangeSet($uow->getEntityChangeSet($entity));
|
||||||
if ($diff !== []) {
|
if ([] !== $diff) {
|
||||||
$pendingUpdates[$componentId] = $this->mergeDiffs($pendingUpdates[$componentId] ?? [], $diff);
|
$pendingUpdates[$componentId] = $this->mergeDiffs($pendingUpdates[$componentId] ?? [], $diff);
|
||||||
$pendingSnapshots[$componentId] = $this->snapshotComposant($entity);
|
$pendingSnapshots[$componentId] = $this->snapshotComposant($entity);
|
||||||
$pendingComponents[$componentId] = $entity;
|
$pendingComponents[$componentId] = $entity;
|
||||||
@@ -89,8 +101,10 @@ final class ComposantAuditSubscriber implements EventSubscriber
|
|||||||
$this->collectCollectionUpdate($collection, $pendingUpdates, $pendingSnapshots, $pendingComponents);
|
$this->collectCollectionUpdate($collection, $pendingUpdates, $pendingSnapshots, $pendingComponents);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$this->collectCustomFieldValueChanges($uow, $pendingUpdates, $pendingSnapshots, $pendingComponents);
|
||||||
|
|
||||||
foreach ($pendingUpdates as $componentId => $diff) {
|
foreach ($pendingUpdates as $componentId => $diff) {
|
||||||
if ($diff === []) {
|
if ([] === $diff) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -125,13 +139,13 @@ final class ComposantAuditSubscriber implements EventSubscriber
|
|||||||
}
|
}
|
||||||
|
|
||||||
$componentId = (string) $owner->getId();
|
$componentId = (string) $owner->getId();
|
||||||
if ($componentId === '') {
|
if ('' === $componentId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$mapping = $collection->getMapping();
|
$mapping = $collection->getMapping();
|
||||||
$fieldName = $mapping['fieldName'] ?? null;
|
$fieldName = $mapping['fieldName'] ?? null;
|
||||||
if ($fieldName !== 'constructeurs') {
|
if ('constructeurs' !== $fieldName) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -154,6 +168,75 @@ final class ComposantAuditSubscriber implements EventSubscriber
|
|||||||
$pendingComponents[$componentId] = $owner;
|
$pendingComponents[$componentId] = $owner;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<string, array<string, array{from:mixed, to:mixed}>> $pendingUpdates
|
||||||
|
* @param array<string, array<string, mixed>> $pendingSnapshots
|
||||||
|
* @param array<string, Composant> $pendingComponents
|
||||||
|
*/
|
||||||
|
private function collectCustomFieldValueChanges(
|
||||||
|
UnitOfWork $uow,
|
||||||
|
array &$pendingUpdates,
|
||||||
|
array &$pendingSnapshots,
|
||||||
|
array &$pendingComponents,
|
||||||
|
): void {
|
||||||
|
foreach ($uow->getScheduledEntityInsertions() as $entity) {
|
||||||
|
if ($entity instanceof CustomFieldValue) {
|
||||||
|
$this->trackCustomFieldValueChange($entity, null, $entity->getValue(), $pendingUpdates, $pendingSnapshots, $pendingComponents);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($uow->getScheduledEntityUpdates() as $entity) {
|
||||||
|
if (!$entity instanceof CustomFieldValue) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$changeSet = $uow->getEntityChangeSet($entity);
|
||||||
|
if (!isset($changeSet['value'])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
[$oldVal, $newVal] = $changeSet['value'];
|
||||||
|
if ($oldVal !== $newVal) {
|
||||||
|
$this->trackCustomFieldValueChange($entity, $oldVal, $newVal, $pendingUpdates, $pendingSnapshots, $pendingComponents);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($uow->getScheduledEntityDeletions() as $entity) {
|
||||||
|
if ($entity instanceof CustomFieldValue) {
|
||||||
|
$this->trackCustomFieldValueChange($entity, $entity->getValue(), null, $pendingUpdates, $pendingSnapshots, $pendingComponents);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<string, array<string, array{from:mixed, to:mixed}>> $pendingUpdates
|
||||||
|
* @param array<string, array<string, mixed>> $pendingSnapshots
|
||||||
|
* @param array<string, Composant> $pendingComponents
|
||||||
|
*/
|
||||||
|
private function trackCustomFieldValueChange(
|
||||||
|
CustomFieldValue $cfv,
|
||||||
|
mixed $from,
|
||||||
|
mixed $to,
|
||||||
|
array &$pendingUpdates,
|
||||||
|
array &$pendingSnapshots,
|
||||||
|
array &$pendingComponents,
|
||||||
|
): void {
|
||||||
|
$owner = $cfv->getComposant();
|
||||||
|
if (!$owner instanceof Composant) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$ownerId = (string) $owner->getId();
|
||||||
|
if ('' === $ownerId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$fieldName = 'customField:'.$cfv->getCustomField()->getName();
|
||||||
|
$diff = [$fieldName => ['from' => $from, 'to' => $to]];
|
||||||
|
|
||||||
|
$pendingUpdates[$ownerId] = $this->mergeDiffs($pendingUpdates[$ownerId] ?? [], $diff);
|
||||||
|
$pendingSnapshots[$ownerId] = $this->snapshotComposant($owner);
|
||||||
|
$pendingComponents[$ownerId] = $owner;
|
||||||
|
}
|
||||||
|
|
||||||
private function persistAuditLog(EntityManagerInterface $em, AuditLog $log): void
|
private function persistAuditLog(EntityManagerInterface $em, AuditLog $log): void
|
||||||
{
|
{
|
||||||
$uow = $em->getUnitOfWork();
|
$uow = $em->getUnitOfWork();
|
||||||
@@ -166,13 +249,14 @@ final class ComposantAuditSubscriber implements EventSubscriber
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @param array<string, array{0:mixed, 1:mixed}> $changeSet
|
* @param array<string, array{0:mixed, 1:mixed}> $changeSet
|
||||||
|
*
|
||||||
* @return array<string, array{from:mixed, to:mixed}>
|
* @return array<string, array{from:mixed, to:mixed}>
|
||||||
*/
|
*/
|
||||||
private function buildDiffFromChangeSet(array $changeSet): array
|
private function buildDiffFromChangeSet(array $changeSet): array
|
||||||
{
|
{
|
||||||
$diff = [];
|
$diff = [];
|
||||||
foreach ($changeSet as $field => [$oldValue, $newValue]) {
|
foreach ($changeSet as $field => [$oldValue, $newValue]) {
|
||||||
if ($field === 'updatedAt' || $field === 'createdAt') {
|
if ('updatedAt' === $field || 'createdAt' === $field) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -208,33 +292,39 @@ final class ComposantAuditSubscriber implements EventSubscriber
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @param iterable<mixed> $items
|
* @param iterable<mixed> $items
|
||||||
* @return list<string>
|
*
|
||||||
|
* @return list<array{id: string, name: string}|string>
|
||||||
*/
|
*/
|
||||||
private function normalizeCollection(iterable $items): array
|
private function normalizeCollection(iterable $items): array
|
||||||
{
|
{
|
||||||
$ids = [];
|
$entries = [];
|
||||||
|
$seen = [];
|
||||||
foreach ($items as $item) {
|
foreach ($items as $item) {
|
||||||
if (\is_object($item) && \method_exists($item, 'getId')) {
|
if (is_object($item) && method_exists($item, 'getId')) {
|
||||||
$id = $item->getId();
|
$id = $item->getId();
|
||||||
if ($id !== null && $id !== '') {
|
if (null === $id || '' === $id || isset($seen[(string) $id])) {
|
||||||
$ids[] = (string) $id;
|
continue;
|
||||||
|
}
|
||||||
|
$seen[(string) $id] = true;
|
||||||
|
if (method_exists($item, 'getName')) {
|
||||||
|
$entries[] = ['id' => (string) $id, 'name' => (string) $item->getName()];
|
||||||
|
} else {
|
||||||
|
$entries[] = (string) $id;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sort($ids);
|
return $entries;
|
||||||
|
|
||||||
return array_values(array_unique($ids));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private function normalizeValue(mixed $value): mixed
|
private function normalizeValue(mixed $value): mixed
|
||||||
{
|
{
|
||||||
if ($value === null || \is_scalar($value)) {
|
if (null === $value || is_scalar($value)) {
|
||||||
return $value;
|
return $value;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($value instanceof \DateTimeInterface) {
|
if ($value instanceof DateTimeInterface) {
|
||||||
return $value->format(\DateTimeInterface::ATOM);
|
return $value->format(DateTimeInterface::ATOM);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($value instanceof ModelType) {
|
if ($value instanceof ModelType) {
|
||||||
@@ -257,11 +347,11 @@ final class ComposantAuditSubscriber implements EventSubscriber
|
|||||||
return $this->normalizeCollection($value);
|
return $this->normalizeCollection($value);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (\is_object($value) && \method_exists($value, 'getId')) {
|
if (is_object($value) && method_exists($value, 'getId')) {
|
||||||
return (string) $value->getId();
|
return (string) $value->getId();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (\is_array($value)) {
|
if (is_array($value)) {
|
||||||
return $value;
|
return $value;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -271,6 +361,7 @@ final class ComposantAuditSubscriber implements EventSubscriber
|
|||||||
/**
|
/**
|
||||||
* @param array<string, array{from:mixed, to:mixed}> $base
|
* @param array<string, array{from:mixed, to:mixed}> $base
|
||||||
* @param array<string, array{from:mixed, to:mixed}> $extra
|
* @param array<string, array{from:mixed, to:mixed}> $extra
|
||||||
|
*
|
||||||
* @return array<string, array{from:mixed, to:mixed}>
|
* @return array<string, array{from:mixed, to:mixed}>
|
||||||
*/
|
*/
|
||||||
private function mergeDiffs(array $base, array $extra): array
|
private function mergeDiffs(array $base, array $extra): array
|
||||||
@@ -284,17 +375,23 @@ final class ComposantAuditSubscriber implements EventSubscriber
|
|||||||
|
|
||||||
private function resolveActorProfileId(): ?string
|
private function resolveActorProfileId(): ?string
|
||||||
{
|
{
|
||||||
|
try {
|
||||||
$session = $this->requestStack->getSession();
|
$session = $this->requestStack->getSession();
|
||||||
if (!$session instanceof SessionInterface) {
|
if ($session instanceof SessionInterface) {
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
$profileId = $session->get('profileId');
|
$profileId = $session->get('profileId');
|
||||||
if (!$profileId) {
|
if ($profileId) {
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (string) $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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
168
src/EventSubscriber/ConstructeurAuditSubscriber.php
Normal file
168
src/EventSubscriber/ConstructeurAuditSubscriber.php
Normal file
@@ -0,0 +1,168 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\EventSubscriber;
|
||||||
|
|
||||||
|
use App\Entity\AuditLog;
|
||||||
|
use App\Entity\Constructeur;
|
||||||
|
use App\Entity\Profile;
|
||||||
|
use DateTimeInterface;
|
||||||
|
use Doctrine\Bundle\DoctrineBundle\Attribute\AsDoctrineListener;
|
||||||
|
use Doctrine\Common\EventSubscriber;
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
use Doctrine\ORM\Event\OnFlushEventArgs;
|
||||||
|
use Doctrine\ORM\Events;
|
||||||
|
use Symfony\Bundle\SecurityBundle\Security;
|
||||||
|
use Symfony\Component\HttpFoundation\RequestStack;
|
||||||
|
use Symfony\Component\HttpFoundation\Session\SessionInterface;
|
||||||
|
use Throwable;
|
||||||
|
|
||||||
|
use function is_scalar;
|
||||||
|
|
||||||
|
#[AsDoctrineListener(event: Events::onFlush)]
|
||||||
|
final class ConstructeurAuditSubscriber implements EventSubscriber
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private readonly RequestStack $requestStack,
|
||||||
|
private readonly Security $security,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public function getSubscribedEvents(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
Events::onFlush,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function onFlush(OnFlushEventArgs $args): void
|
||||||
|
{
|
||||||
|
$em = $args->getObjectManager();
|
||||||
|
if (!$em instanceof EntityManagerInterface) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$uow = $em->getUnitOfWork();
|
||||||
|
$actorProfileId = $this->resolveActorProfileId();
|
||||||
|
|
||||||
|
foreach ($uow->getScheduledEntityInsertions() as $entity) {
|
||||||
|
if (!$entity instanceof Constructeur) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$diff = $this->buildDiffFromChangeSet($uow->getEntityChangeSet($entity));
|
||||||
|
$snapshot = $this->snapshotConstructeur($entity);
|
||||||
|
$this->persistAuditLog($em, new AuditLog('constructeur', (string) $entity->getId(), 'create', $diff, $snapshot, $actorProfileId));
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($uow->getScheduledEntityUpdates() as $entity) {
|
||||||
|
if (!$entity instanceof Constructeur) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$id = (string) $entity->getId();
|
||||||
|
if ('' === $id) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$diff = $this->buildDiffFromChangeSet($uow->getEntityChangeSet($entity));
|
||||||
|
if ([] !== $diff) {
|
||||||
|
$snapshot = $this->snapshotConstructeur($entity);
|
||||||
|
$this->persistAuditLog($em, new AuditLog('constructeur', $id, 'update', $diff, $snapshot, $actorProfileId));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($uow->getScheduledEntityDeletions() as $entity) {
|
||||||
|
if (!$entity instanceof Constructeur) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$snapshot = $this->snapshotConstructeur($entity);
|
||||||
|
$this->persistAuditLog($em, new AuditLog('constructeur', (string) $entity->getId(), 'delete', null, $snapshot, $actorProfileId));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function persistAuditLog(EntityManagerInterface $em, AuditLog $log): void
|
||||||
|
{
|
||||||
|
$uow = $em->getUnitOfWork();
|
||||||
|
$log->initializeAuditLog();
|
||||||
|
$em->persist($log);
|
||||||
|
|
||||||
|
$meta = $em->getClassMetadata(AuditLog::class);
|
||||||
|
$uow->computeChangeSet($meta, $log);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<string, array{0:mixed, 1:mixed}> $changeSet
|
||||||
|
*
|
||||||
|
* @return array<string, array{from:mixed, to:mixed}>
|
||||||
|
*/
|
||||||
|
private function buildDiffFromChangeSet(array $changeSet): array
|
||||||
|
{
|
||||||
|
$diff = [];
|
||||||
|
foreach ($changeSet as $field => [$oldValue, $newValue]) {
|
||||||
|
if ('updatedAt' === $field || 'createdAt' === $field) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$normalizedOld = $this->normalizeValue($oldValue);
|
||||||
|
$normalizedNew = $this->normalizeValue($newValue);
|
||||||
|
|
||||||
|
if ($normalizedOld === $normalizedNew) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$diff[$field] = [
|
||||||
|
'from' => $normalizedOld,
|
||||||
|
'to' => $normalizedNew,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $diff;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function snapshotConstructeur(Constructeur $constructeur): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'id' => $constructeur->getId(),
|
||||||
|
'name' => $constructeur->getName(),
|
||||||
|
'email' => $constructeur->getEmail(),
|
||||||
|
'phone' => $constructeur->getPhone(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
private function normalizeValue(mixed $value): mixed
|
||||||
|
{
|
||||||
|
if (null === $value || is_scalar($value)) {
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($value instanceof DateTimeInterface) {
|
||||||
|
return $value->format(DateTimeInterface::ATOM);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (string) $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
192
src/EventSubscriber/DocumentAuditSubscriber.php
Normal file
192
src/EventSubscriber/DocumentAuditSubscriber.php
Normal file
@@ -0,0 +1,192 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\EventSubscriber;
|
||||||
|
|
||||||
|
use App\Entity\AuditLog;
|
||||||
|
use App\Entity\Composant;
|
||||||
|
use App\Entity\Document;
|
||||||
|
use App\Entity\Machine;
|
||||||
|
use App\Entity\Piece;
|
||||||
|
use App\Entity\Product;
|
||||||
|
use App\Entity\Profile;
|
||||||
|
use App\Entity\Site;
|
||||||
|
use DateTimeInterface;
|
||||||
|
use Doctrine\Bundle\DoctrineBundle\Attribute\AsDoctrineListener;
|
||||||
|
use Doctrine\Common\EventSubscriber;
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
use Doctrine\ORM\Event\OnFlushEventArgs;
|
||||||
|
use Doctrine\ORM\Events;
|
||||||
|
use Symfony\Bundle\SecurityBundle\Security;
|
||||||
|
use Symfony\Component\HttpFoundation\RequestStack;
|
||||||
|
use Symfony\Component\HttpFoundation\Session\SessionInterface;
|
||||||
|
use Throwable;
|
||||||
|
|
||||||
|
use function is_object;
|
||||||
|
use function is_scalar;
|
||||||
|
use function method_exists;
|
||||||
|
|
||||||
|
#[AsDoctrineListener(event: Events::onFlush)]
|
||||||
|
final class DocumentAuditSubscriber implements EventSubscriber
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private readonly RequestStack $requestStack,
|
||||||
|
private readonly Security $security,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public function getSubscribedEvents(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
Events::onFlush,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function onFlush(OnFlushEventArgs $args): void
|
||||||
|
{
|
||||||
|
$em = $args->getObjectManager();
|
||||||
|
if (!$em instanceof EntityManagerInterface) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$uow = $em->getUnitOfWork();
|
||||||
|
$actorProfileId = $this->resolveActorProfileId();
|
||||||
|
|
||||||
|
foreach ($uow->getScheduledEntityInsertions() as $entity) {
|
||||||
|
if (!$entity instanceof Document) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$diff = $this->buildDiffFromChangeSet($uow->getEntityChangeSet($entity));
|
||||||
|
$snapshot = $this->snapshotDocument($entity);
|
||||||
|
$this->persistAuditLog($em, new AuditLog('document', (string) $entity->getId(), 'create', $diff, $snapshot, $actorProfileId));
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($uow->getScheduledEntityUpdates() as $entity) {
|
||||||
|
if (!$entity instanceof Document) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$id = (string) $entity->getId();
|
||||||
|
if ('' === $id) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$diff = $this->buildDiffFromChangeSet($uow->getEntityChangeSet($entity));
|
||||||
|
if ([] !== $diff) {
|
||||||
|
$snapshot = $this->snapshotDocument($entity);
|
||||||
|
$this->persistAuditLog($em, new AuditLog('document', $id, 'update', $diff, $snapshot, $actorProfileId));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($uow->getScheduledEntityDeletions() as $entity) {
|
||||||
|
if (!$entity instanceof Document) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$snapshot = $this->snapshotDocument($entity);
|
||||||
|
$this->persistAuditLog($em, new AuditLog('document', (string) $entity->getId(), 'delete', null, $snapshot, $actorProfileId));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function persistAuditLog(EntityManagerInterface $em, AuditLog $log): void
|
||||||
|
{
|
||||||
|
$uow = $em->getUnitOfWork();
|
||||||
|
$log->initializeAuditLog();
|
||||||
|
$em->persist($log);
|
||||||
|
|
||||||
|
$meta = $em->getClassMetadata(AuditLog::class);
|
||||||
|
$uow->computeChangeSet($meta, $log);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<string, array{0:mixed, 1:mixed}> $changeSet
|
||||||
|
*
|
||||||
|
* @return array<string, array{from:mixed, to:mixed}>
|
||||||
|
*/
|
||||||
|
private function buildDiffFromChangeSet(array $changeSet): array
|
||||||
|
{
|
||||||
|
$diff = [];
|
||||||
|
foreach ($changeSet as $field => [$oldValue, $newValue]) {
|
||||||
|
if ('updatedAt' === $field || 'createdAt' === $field) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$normalizedOld = $this->normalizeValue($oldValue);
|
||||||
|
$normalizedNew = $this->normalizeValue($newValue);
|
||||||
|
|
||||||
|
if ($normalizedOld === $normalizedNew) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$diff[$field] = [
|
||||||
|
'from' => $normalizedOld,
|
||||||
|
'to' => $normalizedNew,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $diff;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function snapshotDocument(Document $document): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'id' => $document->getId(),
|
||||||
|
'name' => $document->getName(),
|
||||||
|
'filename' => $document->getFilename(),
|
||||||
|
'mimeType' => $document->getMimeType(),
|
||||||
|
'size' => $document->getSize(),
|
||||||
|
'machine' => $this->normalizeValue($document->getMachine()),
|
||||||
|
'composant' => $this->normalizeValue($document->getComposant()),
|
||||||
|
'piece' => $this->normalizeValue($document->getPiece()),
|
||||||
|
'product' => $this->normalizeValue($document->getProduct()),
|
||||||
|
'site' => $this->normalizeValue($document->getSite()),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
private function normalizeValue(mixed $value): mixed
|
||||||
|
{
|
||||||
|
if (null === $value || is_scalar($value)) {
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($value instanceof DateTimeInterface) {
|
||||||
|
return $value->format(DateTimeInterface::ATOM);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($value instanceof Machine || $value instanceof Composant || $value instanceof Piece || $value instanceof Product || $value instanceof Site) {
|
||||||
|
return [
|
||||||
|
'id' => $value->getId(),
|
||||||
|
'name' => $value->getName(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_object($value) && method_exists($value, 'getId')) {
|
||||||
|
return (string) $value->getId();
|
||||||
|
}
|
||||||
|
|
||||||
|
return (string) $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
412
src/EventSubscriber/MachineAuditSubscriber.php
Normal file
412
src/EventSubscriber/MachineAuditSubscriber.php
Normal file
@@ -0,0 +1,412 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\EventSubscriber;
|
||||||
|
|
||||||
|
use App\Entity\AuditLog;
|
||||||
|
use App\Entity\CustomFieldValue;
|
||||||
|
use App\Entity\Machine;
|
||||||
|
use App\Entity\ModelType;
|
||||||
|
use App\Entity\Product;
|
||||||
|
use App\Entity\Profile;
|
||||||
|
use App\Entity\Site;
|
||||||
|
use App\Entity\TypeMachine;
|
||||||
|
use DateTimeInterface;
|
||||||
|
use Doctrine\Bundle\DoctrineBundle\Attribute\AsDoctrineListener;
|
||||||
|
use Doctrine\Common\Collections\Collection;
|
||||||
|
use Doctrine\Common\EventSubscriber;
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
use Doctrine\ORM\Event\OnFlushEventArgs;
|
||||||
|
use Doctrine\ORM\Events;
|
||||||
|
use Doctrine\ORM\PersistentCollection;
|
||||||
|
use Doctrine\ORM\UnitOfWork;
|
||||||
|
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_object;
|
||||||
|
use function is_scalar;
|
||||||
|
use function method_exists;
|
||||||
|
|
||||||
|
#[AsDoctrineListener(event: Events::onFlush)]
|
||||||
|
final class MachineAuditSubscriber implements EventSubscriber
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private readonly RequestStack $requestStack,
|
||||||
|
private readonly Security $security,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public function getSubscribedEvents(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
Events::onFlush,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function onFlush(OnFlushEventArgs $args): void
|
||||||
|
{
|
||||||
|
$em = $args->getObjectManager();
|
||||||
|
if (!$em instanceof EntityManagerInterface) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$uow = $em->getUnitOfWork();
|
||||||
|
$actorProfileId = $this->resolveActorProfileId();
|
||||||
|
$pendingUpdates = [];
|
||||||
|
$pendingSnapshots = [];
|
||||||
|
$pendingMachines = [];
|
||||||
|
|
||||||
|
foreach ($uow->getScheduledEntityInsertions() as $entity) {
|
||||||
|
if (!$entity instanceof Machine) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$diff = $this->buildDiffFromChangeSet($uow->getEntityChangeSet($entity));
|
||||||
|
$snapshot = $this->snapshotMachine($entity);
|
||||||
|
$this->persistAuditLog($em, new AuditLog('machine', (string) $entity->getId(), 'create', $diff, $snapshot, $actorProfileId));
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($uow->getScheduledEntityUpdates() as $entity) {
|
||||||
|
if (!$entity instanceof Machine) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$machineId = (string) $entity->getId();
|
||||||
|
if ('' === $machineId) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$diff = $this->buildDiffFromChangeSet($uow->getEntityChangeSet($entity));
|
||||||
|
if ([] !== $diff) {
|
||||||
|
$pendingUpdates[$machineId] = $this->mergeDiffs($pendingUpdates[$machineId] ?? [], $diff);
|
||||||
|
$pendingSnapshots[$machineId] = $this->snapshotMachine($entity);
|
||||||
|
$pendingMachines[$machineId] = $entity;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($uow->getScheduledEntityDeletions() as $entity) {
|
||||||
|
if (!$entity instanceof Machine) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$snapshot = $this->snapshotMachine($entity);
|
||||||
|
$this->persistAuditLog($em, new AuditLog('machine', (string) $entity->getId(), 'delete', null, $snapshot, $actorProfileId));
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($uow->getScheduledCollectionUpdates() as $collection) {
|
||||||
|
$this->collectCollectionUpdate($collection, $pendingUpdates, $pendingSnapshots, $pendingMachines);
|
||||||
|
}
|
||||||
|
foreach ($uow->getScheduledCollectionDeletions() as $collection) {
|
||||||
|
$this->collectCollectionUpdate($collection, $pendingUpdates, $pendingSnapshots, $pendingMachines);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->collectCustomFieldValueChanges($uow, $pendingUpdates, $pendingSnapshots, $pendingMachines);
|
||||||
|
|
||||||
|
foreach ($pendingUpdates as $machineId => $diff) {
|
||||||
|
if ([] === $diff) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$machine = $pendingMachines[$machineId] ?? null;
|
||||||
|
if (!$machine instanceof Machine) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$snapshot = $pendingSnapshots[$machineId] ?? $this->snapshotMachine($machine);
|
||||||
|
$this->persistAuditLog($em, new AuditLog('machine', $machineId, 'update', $diff, $snapshot, $actorProfileId));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<string, array<string, array{from:mixed, to:mixed}>> $pendingUpdates
|
||||||
|
* @param array<string, array<string, mixed>> $pendingSnapshots
|
||||||
|
* @param array<string, Machine> $pendingMachines
|
||||||
|
*/
|
||||||
|
private function collectCollectionUpdate(
|
||||||
|
object $collection,
|
||||||
|
array &$pendingUpdates,
|
||||||
|
array &$pendingSnapshots,
|
||||||
|
array &$pendingMachines,
|
||||||
|
): void {
|
||||||
|
if (!$collection instanceof PersistentCollection) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$owner = $collection->getOwner();
|
||||||
|
if (!$owner instanceof Machine) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$machineId = (string) $owner->getId();
|
||||||
|
if ('' === $machineId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$mapping = $collection->getMapping();
|
||||||
|
$fieldName = $mapping['fieldName'] ?? null;
|
||||||
|
if ('constructeurs' !== $fieldName) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$before = $this->normalizeCollection($collection->getSnapshot());
|
||||||
|
$after = $this->normalizeCollection($collection->toArray());
|
||||||
|
|
||||||
|
if ($before === $after) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$diff = [
|
||||||
|
'constructeurIds' => [
|
||||||
|
'from' => $before,
|
||||||
|
'to' => $after,
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
$pendingUpdates[$machineId] = $this->mergeDiffs($pendingUpdates[$machineId] ?? [], $diff);
|
||||||
|
$pendingSnapshots[$machineId] = $this->snapshotMachine($owner);
|
||||||
|
$pendingMachines[$machineId] = $owner;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<string, array<string, array{from:mixed, to:mixed}>> $pendingUpdates
|
||||||
|
* @param array<string, array<string, mixed>> $pendingSnapshots
|
||||||
|
* @param array<string, Machine> $pendingMachines
|
||||||
|
*/
|
||||||
|
private function collectCustomFieldValueChanges(
|
||||||
|
UnitOfWork $uow,
|
||||||
|
array &$pendingUpdates,
|
||||||
|
array &$pendingSnapshots,
|
||||||
|
array &$pendingMachines,
|
||||||
|
): void {
|
||||||
|
foreach ($uow->getScheduledEntityInsertions() as $entity) {
|
||||||
|
if ($entity instanceof CustomFieldValue) {
|
||||||
|
$this->trackCustomFieldValueChange($entity, null, $entity->getValue(), $pendingUpdates, $pendingSnapshots, $pendingMachines);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($uow->getScheduledEntityUpdates() as $entity) {
|
||||||
|
if (!$entity instanceof CustomFieldValue) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$changeSet = $uow->getEntityChangeSet($entity);
|
||||||
|
if (!isset($changeSet['value'])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
[$oldVal, $newVal] = $changeSet['value'];
|
||||||
|
if ($oldVal !== $newVal) {
|
||||||
|
$this->trackCustomFieldValueChange($entity, $oldVal, $newVal, $pendingUpdates, $pendingSnapshots, $pendingMachines);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($uow->getScheduledEntityDeletions() as $entity) {
|
||||||
|
if ($entity instanceof CustomFieldValue) {
|
||||||
|
$this->trackCustomFieldValueChange($entity, $entity->getValue(), null, $pendingUpdates, $pendingSnapshots, $pendingMachines);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<string, array<string, array{from:mixed, to:mixed}>> $pendingUpdates
|
||||||
|
* @param array<string, array<string, mixed>> $pendingSnapshots
|
||||||
|
* @param array<string, Machine> $pendingMachines
|
||||||
|
*/
|
||||||
|
private function trackCustomFieldValueChange(
|
||||||
|
CustomFieldValue $cfv,
|
||||||
|
mixed $from,
|
||||||
|
mixed $to,
|
||||||
|
array &$pendingUpdates,
|
||||||
|
array &$pendingSnapshots,
|
||||||
|
array &$pendingMachines,
|
||||||
|
): void {
|
||||||
|
$owner = $cfv->getMachine();
|
||||||
|
if (!$owner instanceof Machine) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$ownerId = (string) $owner->getId();
|
||||||
|
if ('' === $ownerId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$fieldName = 'customField:'.$cfv->getCustomField()->getName();
|
||||||
|
$diff = [$fieldName => ['from' => $from, 'to' => $to]];
|
||||||
|
|
||||||
|
$pendingUpdates[$ownerId] = $this->mergeDiffs($pendingUpdates[$ownerId] ?? [], $diff);
|
||||||
|
$pendingSnapshots[$ownerId] = $this->snapshotMachine($owner);
|
||||||
|
$pendingMachines[$ownerId] = $owner;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function persistAuditLog(EntityManagerInterface $em, AuditLog $log): void
|
||||||
|
{
|
||||||
|
$uow = $em->getUnitOfWork();
|
||||||
|
$log->initializeAuditLog();
|
||||||
|
$em->persist($log);
|
||||||
|
|
||||||
|
$meta = $em->getClassMetadata(AuditLog::class);
|
||||||
|
$uow->computeChangeSet($meta, $log);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<string, array{0:mixed, 1:mixed}> $changeSet
|
||||||
|
*
|
||||||
|
* @return array<string, array{from:mixed, to:mixed}>
|
||||||
|
*/
|
||||||
|
private function buildDiffFromChangeSet(array $changeSet): array
|
||||||
|
{
|
||||||
|
$diff = [];
|
||||||
|
foreach ($changeSet as $field => [$oldValue, $newValue]) {
|
||||||
|
if ('updatedAt' === $field || 'createdAt' === $field) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$normalizedOld = $this->normalizeValue($oldValue);
|
||||||
|
$normalizedNew = $this->normalizeValue($newValue);
|
||||||
|
|
||||||
|
if ($normalizedOld === $normalizedNew) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$diff[$field] = [
|
||||||
|
'from' => $normalizedOld,
|
||||||
|
'to' => $normalizedNew,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $diff;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function snapshotMachine(Machine $machine): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'id' => $machine->getId(),
|
||||||
|
'name' => $machine->getName(),
|
||||||
|
'reference' => $machine->getReference(),
|
||||||
|
'prix' => $machine->getPrix(),
|
||||||
|
'site' => $this->normalizeValue($machine->getSite()),
|
||||||
|
'typeMachine' => $this->normalizeValue($machine->getTypeMachine()),
|
||||||
|
'constructeurIds' => $this->normalizeCollection($machine->getConstructeurs()),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param iterable<mixed> $items
|
||||||
|
*
|
||||||
|
* @return list<array{id: string, name: string}|string>
|
||||||
|
*/
|
||||||
|
private function normalizeCollection(iterable $items): array
|
||||||
|
{
|
||||||
|
$entries = [];
|
||||||
|
$seen = [];
|
||||||
|
foreach ($items as $item) {
|
||||||
|
if (is_object($item) && method_exists($item, 'getId')) {
|
||||||
|
$id = $item->getId();
|
||||||
|
if (null === $id || '' === $id || isset($seen[(string) $id])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$seen[(string) $id] = true;
|
||||||
|
if (method_exists($item, 'getName')) {
|
||||||
|
$entries[] = ['id' => (string) $id, 'name' => (string) $item->getName()];
|
||||||
|
} else {
|
||||||
|
$entries[] = (string) $id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $entries;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function normalizeValue(mixed $value): mixed
|
||||||
|
{
|
||||||
|
if (null === $value || is_scalar($value)) {
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($value instanceof DateTimeInterface) {
|
||||||
|
return $value->format(DateTimeInterface::ATOM);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($value instanceof Site) {
|
||||||
|
return [
|
||||||
|
'id' => $value->getId(),
|
||||||
|
'name' => $value->getName(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($value instanceof TypeMachine) {
|
||||||
|
return [
|
||||||
|
'id' => $value->getId(),
|
||||||
|
'name' => $value->getName(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($value instanceof ModelType) {
|
||||||
|
return [
|
||||||
|
'id' => $value->getId(),
|
||||||
|
'name' => $value->getName(),
|
||||||
|
'code' => $value->getCode(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($value instanceof Product) {
|
||||||
|
return [
|
||||||
|
'id' => $value->getId(),
|
||||||
|
'name' => $value->getName(),
|
||||||
|
'reference' => $value->getReference(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($value instanceof Collection) {
|
||||||
|
return $this->normalizeCollection($value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_object($value) && method_exists($value, 'getId')) {
|
||||||
|
return (string) $value->getId();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_array($value)) {
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (string) $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<string, array{from:mixed, to:mixed}> $base
|
||||||
|
* @param array<string, array{from:mixed, to:mixed}> $extra
|
||||||
|
*
|
||||||
|
* @return array<string, array{from:mixed, to:mixed}>
|
||||||
|
*/
|
||||||
|
private function mergeDiffs(array $base, array $extra): array
|
||||||
|
{
|
||||||
|
foreach ($extra as $field => $change) {
|
||||||
|
$base[$field] = $change;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $base;
|
||||||
|
}
|
||||||
|
|
||||||
|
private 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
180
src/EventSubscriber/ModelTypeAuditSubscriber.php
Normal file
180
src/EventSubscriber/ModelTypeAuditSubscriber.php
Normal file
@@ -0,0 +1,180 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\EventSubscriber;
|
||||||
|
|
||||||
|
use App\Entity\AuditLog;
|
||||||
|
use App\Entity\ModelType;
|
||||||
|
use App\Entity\Profile;
|
||||||
|
use App\Enum\ModelCategory;
|
||||||
|
use DateTimeInterface;
|
||||||
|
use Doctrine\Bundle\DoctrineBundle\Attribute\AsDoctrineListener;
|
||||||
|
use Doctrine\Common\EventSubscriber;
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
use Doctrine\ORM\Event\OnFlushEventArgs;
|
||||||
|
use Doctrine\ORM\Events;
|
||||||
|
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_scalar;
|
||||||
|
|
||||||
|
#[AsDoctrineListener(event: Events::onFlush)]
|
||||||
|
final class ModelTypeAuditSubscriber implements EventSubscriber
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private readonly RequestStack $requestStack,
|
||||||
|
private readonly Security $security,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public function getSubscribedEvents(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
Events::onFlush,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function onFlush(OnFlushEventArgs $args): void
|
||||||
|
{
|
||||||
|
$em = $args->getObjectManager();
|
||||||
|
if (!$em instanceof EntityManagerInterface) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$uow = $em->getUnitOfWork();
|
||||||
|
$actorProfileId = $this->resolveActorProfileId();
|
||||||
|
|
||||||
|
foreach ($uow->getScheduledEntityInsertions() as $entity) {
|
||||||
|
if (!$entity instanceof ModelType) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$diff = $this->buildDiffFromChangeSet($uow->getEntityChangeSet($entity));
|
||||||
|
$snapshot = $this->snapshotModelType($entity);
|
||||||
|
$this->persistAuditLog($em, new AuditLog('model_type', (string) $entity->getId(), 'create', $diff, $snapshot, $actorProfileId));
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($uow->getScheduledEntityUpdates() as $entity) {
|
||||||
|
if (!$entity instanceof ModelType) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$id = (string) $entity->getId();
|
||||||
|
if ('' === $id) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$diff = $this->buildDiffFromChangeSet($uow->getEntityChangeSet($entity));
|
||||||
|
if ([] !== $diff) {
|
||||||
|
$snapshot = $this->snapshotModelType($entity);
|
||||||
|
$this->persistAuditLog($em, new AuditLog('model_type', $id, 'update', $diff, $snapshot, $actorProfileId));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($uow->getScheduledEntityDeletions() as $entity) {
|
||||||
|
if (!$entity instanceof ModelType) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$snapshot = $this->snapshotModelType($entity);
|
||||||
|
$this->persistAuditLog($em, new AuditLog('model_type', (string) $entity->getId(), 'delete', null, $snapshot, $actorProfileId));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function persistAuditLog(EntityManagerInterface $em, AuditLog $log): void
|
||||||
|
{
|
||||||
|
$uow = $em->getUnitOfWork();
|
||||||
|
$log->initializeAuditLog();
|
||||||
|
$em->persist($log);
|
||||||
|
|
||||||
|
$meta = $em->getClassMetadata(AuditLog::class);
|
||||||
|
$uow->computeChangeSet($meta, $log);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<string, array{0:mixed, 1:mixed}> $changeSet
|
||||||
|
*
|
||||||
|
* @return array<string, array{from:mixed, to:mixed}>
|
||||||
|
*/
|
||||||
|
private function buildDiffFromChangeSet(array $changeSet): array
|
||||||
|
{
|
||||||
|
$diff = [];
|
||||||
|
foreach ($changeSet as $field => [$oldValue, $newValue]) {
|
||||||
|
if ('updatedAt' === $field || 'createdAt' === $field) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$normalizedOld = $this->normalizeValue($oldValue);
|
||||||
|
$normalizedNew = $this->normalizeValue($newValue);
|
||||||
|
|
||||||
|
if ($normalizedOld === $normalizedNew) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$diff[$field] = [
|
||||||
|
'from' => $normalizedOld,
|
||||||
|
'to' => $normalizedNew,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $diff;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function snapshotModelType(ModelType $modelType): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'id' => $modelType->getId(),
|
||||||
|
'name' => $modelType->getName(),
|
||||||
|
'code' => $modelType->getCode(),
|
||||||
|
'category' => $modelType->getCategory()->value,
|
||||||
|
'notes' => $modelType->getNotes(),
|
||||||
|
'description' => $modelType->getDescription(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
private function normalizeValue(mixed $value): mixed
|
||||||
|
{
|
||||||
|
if (null === $value || is_scalar($value)) {
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($value instanceof DateTimeInterface) {
|
||||||
|
return $value->format(DateTimeInterface::ATOM);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($value instanceof ModelCategory) {
|
||||||
|
return $value->value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_array($value)) {
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (string) $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,9 +5,12 @@ declare(strict_types=1);
|
|||||||
namespace App\EventSubscriber;
|
namespace App\EventSubscriber;
|
||||||
|
|
||||||
use App\Entity\AuditLog;
|
use App\Entity\AuditLog;
|
||||||
|
use App\Entity\CustomFieldValue;
|
||||||
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 DateTimeInterface;
|
||||||
use Doctrine\Bundle\DoctrineBundle\Attribute\AsDoctrineListener;
|
use Doctrine\Bundle\DoctrineBundle\Attribute\AsDoctrineListener;
|
||||||
use Doctrine\Common\Collections\Collection;
|
use Doctrine\Common\Collections\Collection;
|
||||||
use Doctrine\Common\EventSubscriber;
|
use Doctrine\Common\EventSubscriber;
|
||||||
@@ -15,15 +18,24 @@ use Doctrine\ORM\EntityManagerInterface;
|
|||||||
use Doctrine\ORM\Event\OnFlushEventArgs;
|
use Doctrine\ORM\Event\OnFlushEventArgs;
|
||||||
use Doctrine\ORM\Events;
|
use Doctrine\ORM\Events;
|
||||||
use Doctrine\ORM\PersistentCollection;
|
use Doctrine\ORM\PersistentCollection;
|
||||||
|
use Doctrine\ORM\UnitOfWork;
|
||||||
|
use Symfony\Bundle\SecurityBundle\Security;
|
||||||
use Symfony\Component\HttpFoundation\RequestStack;
|
use Symfony\Component\HttpFoundation\RequestStack;
|
||||||
use Symfony\Component\HttpFoundation\Session\SessionInterface;
|
use Symfony\Component\HttpFoundation\Session\SessionInterface;
|
||||||
|
use Throwable;
|
||||||
|
|
||||||
|
use function is_array;
|
||||||
|
use function is_object;
|
||||||
|
use function is_scalar;
|
||||||
|
use function method_exists;
|
||||||
|
|
||||||
#[AsDoctrineListener(event: Events::onFlush)]
|
#[AsDoctrineListener(event: Events::onFlush)]
|
||||||
final class PieceAuditSubscriber implements EventSubscriber
|
final class PieceAuditSubscriber implements EventSubscriber
|
||||||
{
|
{
|
||||||
public function __construct(private readonly RequestStack $requestStack)
|
public function __construct(
|
||||||
{
|
private readonly RequestStack $requestStack,
|
||||||
}
|
private readonly Security $security,
|
||||||
|
) {}
|
||||||
|
|
||||||
public function getSubscribedEvents(): array
|
public function getSubscribedEvents(): array
|
||||||
{
|
{
|
||||||
@@ -61,12 +73,12 @@ final class PieceAuditSubscriber implements EventSubscriber
|
|||||||
}
|
}
|
||||||
|
|
||||||
$pieceId = (string) $entity->getId();
|
$pieceId = (string) $entity->getId();
|
||||||
if ($pieceId === '') {
|
if ('' === $pieceId) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
$diff = $this->buildDiffFromChangeSet($uow->getEntityChangeSet($entity));
|
$diff = $this->buildDiffFromChangeSet($uow->getEntityChangeSet($entity));
|
||||||
if ($diff !== []) {
|
if ([] !== $diff) {
|
||||||
$pendingUpdates[$pieceId] = $this->mergeDiffs($pendingUpdates[$pieceId] ?? [], $diff);
|
$pendingUpdates[$pieceId] = $this->mergeDiffs($pendingUpdates[$pieceId] ?? [], $diff);
|
||||||
$pendingSnapshots[$pieceId] = $this->snapshotPiece($entity);
|
$pendingSnapshots[$pieceId] = $this->snapshotPiece($entity);
|
||||||
$pendingPieces[$pieceId] = $entity;
|
$pendingPieces[$pieceId] = $entity;
|
||||||
@@ -89,8 +101,10 @@ final class PieceAuditSubscriber implements EventSubscriber
|
|||||||
$this->collectCollectionUpdate($collection, $pendingUpdates, $pendingSnapshots, $pendingPieces);
|
$this->collectCollectionUpdate($collection, $pendingUpdates, $pendingSnapshots, $pendingPieces);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$this->collectCustomFieldValueChanges($uow, $pendingUpdates, $pendingSnapshots, $pendingPieces);
|
||||||
|
|
||||||
foreach ($pendingUpdates as $pieceId => $diff) {
|
foreach ($pendingUpdates as $pieceId => $diff) {
|
||||||
if ($diff === []) {
|
if ([] === $diff) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -125,13 +139,13 @@ final class PieceAuditSubscriber implements EventSubscriber
|
|||||||
}
|
}
|
||||||
|
|
||||||
$pieceId = (string) $owner->getId();
|
$pieceId = (string) $owner->getId();
|
||||||
if ($pieceId === '') {
|
if ('' === $pieceId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$mapping = $collection->getMapping();
|
$mapping = $collection->getMapping();
|
||||||
$fieldName = $mapping['fieldName'] ?? null;
|
$fieldName = $mapping['fieldName'] ?? null;
|
||||||
if ($fieldName !== 'constructeurs') {
|
if ('constructeurs' !== $fieldName) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -154,6 +168,75 @@ final class PieceAuditSubscriber implements EventSubscriber
|
|||||||
$pendingPieces[$pieceId] = $owner;
|
$pendingPieces[$pieceId] = $owner;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<string, array<string, array{from:mixed, to:mixed}>> $pendingUpdates
|
||||||
|
* @param array<string, array<string, mixed>> $pendingSnapshots
|
||||||
|
* @param array<string, Piece> $pendingPieces
|
||||||
|
*/
|
||||||
|
private function collectCustomFieldValueChanges(
|
||||||
|
UnitOfWork $uow,
|
||||||
|
array &$pendingUpdates,
|
||||||
|
array &$pendingSnapshots,
|
||||||
|
array &$pendingPieces,
|
||||||
|
): void {
|
||||||
|
foreach ($uow->getScheduledEntityInsertions() as $entity) {
|
||||||
|
if ($entity instanceof CustomFieldValue) {
|
||||||
|
$this->trackCustomFieldValueChange($entity, null, $entity->getValue(), $pendingUpdates, $pendingSnapshots, $pendingPieces);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($uow->getScheduledEntityUpdates() as $entity) {
|
||||||
|
if (!$entity instanceof CustomFieldValue) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$changeSet = $uow->getEntityChangeSet($entity);
|
||||||
|
if (!isset($changeSet['value'])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
[$oldVal, $newVal] = $changeSet['value'];
|
||||||
|
if ($oldVal !== $newVal) {
|
||||||
|
$this->trackCustomFieldValueChange($entity, $oldVal, $newVal, $pendingUpdates, $pendingSnapshots, $pendingPieces);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($uow->getScheduledEntityDeletions() as $entity) {
|
||||||
|
if ($entity instanceof CustomFieldValue) {
|
||||||
|
$this->trackCustomFieldValueChange($entity, $entity->getValue(), null, $pendingUpdates, $pendingSnapshots, $pendingPieces);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<string, array<string, array{from:mixed, to:mixed}>> $pendingUpdates
|
||||||
|
* @param array<string, array<string, mixed>> $pendingSnapshots
|
||||||
|
* @param array<string, Piece> $pendingPieces
|
||||||
|
*/
|
||||||
|
private function trackCustomFieldValueChange(
|
||||||
|
CustomFieldValue $cfv,
|
||||||
|
mixed $from,
|
||||||
|
mixed $to,
|
||||||
|
array &$pendingUpdates,
|
||||||
|
array &$pendingSnapshots,
|
||||||
|
array &$pendingPieces,
|
||||||
|
): void {
|
||||||
|
$owner = $cfv->getPiece();
|
||||||
|
if (!$owner instanceof Piece) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$ownerId = (string) $owner->getId();
|
||||||
|
if ('' === $ownerId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$fieldName = 'customField:'.$cfv->getCustomField()->getName();
|
||||||
|
$diff = [$fieldName => ['from' => $from, 'to' => $to]];
|
||||||
|
|
||||||
|
$pendingUpdates[$ownerId] = $this->mergeDiffs($pendingUpdates[$ownerId] ?? [], $diff);
|
||||||
|
$pendingSnapshots[$ownerId] = $this->snapshotPiece($owner);
|
||||||
|
$pendingPieces[$ownerId] = $owner;
|
||||||
|
}
|
||||||
|
|
||||||
private function persistAuditLog(EntityManagerInterface $em, AuditLog $log): void
|
private function persistAuditLog(EntityManagerInterface $em, AuditLog $log): void
|
||||||
{
|
{
|
||||||
$uow = $em->getUnitOfWork();
|
$uow = $em->getUnitOfWork();
|
||||||
@@ -166,13 +249,14 @@ final class PieceAuditSubscriber implements EventSubscriber
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @param array<string, array{0:mixed, 1:mixed}> $changeSet
|
* @param array<string, array{0:mixed, 1:mixed}> $changeSet
|
||||||
|
*
|
||||||
* @return array<string, array{from:mixed, to:mixed}>
|
* @return array<string, array{from:mixed, to:mixed}>
|
||||||
*/
|
*/
|
||||||
private function buildDiffFromChangeSet(array $changeSet): array
|
private function buildDiffFromChangeSet(array $changeSet): array
|
||||||
{
|
{
|
||||||
$diff = [];
|
$diff = [];
|
||||||
foreach ($changeSet as $field => [$oldValue, $newValue]) {
|
foreach ($changeSet as $field => [$oldValue, $newValue]) {
|
||||||
if ($field === 'updatedAt' || $field === 'createdAt') {
|
if ('updatedAt' === $field || 'createdAt' === $field) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -208,33 +292,39 @@ final class PieceAuditSubscriber implements EventSubscriber
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @param iterable<mixed> $items
|
* @param iterable<mixed> $items
|
||||||
* @return list<string>
|
*
|
||||||
|
* @return list<array{id: string, name: string}|string>
|
||||||
*/
|
*/
|
||||||
private function normalizeCollection(iterable $items): array
|
private function normalizeCollection(iterable $items): array
|
||||||
{
|
{
|
||||||
$ids = [];
|
$entries = [];
|
||||||
|
$seen = [];
|
||||||
foreach ($items as $item) {
|
foreach ($items as $item) {
|
||||||
if (\is_object($item) && \method_exists($item, 'getId')) {
|
if (is_object($item) && method_exists($item, 'getId')) {
|
||||||
$id = $item->getId();
|
$id = $item->getId();
|
||||||
if ($id !== null && $id !== '') {
|
if (null === $id || '' === $id || isset($seen[(string) $id])) {
|
||||||
$ids[] = (string) $id;
|
continue;
|
||||||
|
}
|
||||||
|
$seen[(string) $id] = true;
|
||||||
|
if (method_exists($item, 'getName')) {
|
||||||
|
$entries[] = ['id' => (string) $id, 'name' => (string) $item->getName()];
|
||||||
|
} else {
|
||||||
|
$entries[] = (string) $id;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sort($ids);
|
return $entries;
|
||||||
|
|
||||||
return array_values(array_unique($ids));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private function normalizeValue(mixed $value): mixed
|
private function normalizeValue(mixed $value): mixed
|
||||||
{
|
{
|
||||||
if ($value === null || \is_scalar($value)) {
|
if (null === $value || is_scalar($value)) {
|
||||||
return $value;
|
return $value;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($value instanceof \DateTimeInterface) {
|
if ($value instanceof DateTimeInterface) {
|
||||||
return $value->format(\DateTimeInterface::ATOM);
|
return $value->format(DateTimeInterface::ATOM);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($value instanceof ModelType) {
|
if ($value instanceof ModelType) {
|
||||||
@@ -257,11 +347,11 @@ final class PieceAuditSubscriber implements EventSubscriber
|
|||||||
return $this->normalizeCollection($value);
|
return $this->normalizeCollection($value);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (\is_object($value) && \method_exists($value, 'getId')) {
|
if (is_object($value) && method_exists($value, 'getId')) {
|
||||||
return (string) $value->getId();
|
return (string) $value->getId();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (\is_array($value)) {
|
if (is_array($value)) {
|
||||||
return $value;
|
return $value;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -271,6 +361,7 @@ final class PieceAuditSubscriber implements EventSubscriber
|
|||||||
/**
|
/**
|
||||||
* @param array<string, array{from:mixed, to:mixed}> $base
|
* @param array<string, array{from:mixed, to:mixed}> $base
|
||||||
* @param array<string, array{from:mixed, to:mixed}> $extra
|
* @param array<string, array{from:mixed, to:mixed}> $extra
|
||||||
|
*
|
||||||
* @return array<string, array{from:mixed, to:mixed}>
|
* @return array<string, array{from:mixed, to:mixed}>
|
||||||
*/
|
*/
|
||||||
private function mergeDiffs(array $base, array $extra): array
|
private function mergeDiffs(array $base, array $extra): array
|
||||||
@@ -284,17 +375,23 @@ final class PieceAuditSubscriber implements EventSubscriber
|
|||||||
|
|
||||||
private function resolveActorProfileId(): ?string
|
private function resolveActorProfileId(): ?string
|
||||||
{
|
{
|
||||||
|
try {
|
||||||
$session = $this->requestStack->getSession();
|
$session = $this->requestStack->getSession();
|
||||||
if (!$session instanceof SessionInterface) {
|
if ($session instanceof SessionInterface) {
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
$profileId = $session->get('profileId');
|
$profileId = $session->get('profileId');
|
||||||
if (!$profileId) {
|
if ($profileId) {
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (string) $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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -16,9 +16,7 @@ use Doctrine\ORM\Events;
|
|||||||
*/
|
*/
|
||||||
final class PieceProductSyncSubscriber implements EventSubscriber
|
final class PieceProductSyncSubscriber implements EventSubscriber
|
||||||
{
|
{
|
||||||
public function __construct(private readonly ProductRepository $productRepository)
|
public function __construct(private readonly ProductRepository $productRepository) {}
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getSubscribedEvents(): array
|
public function getSubscribedEvents(): array
|
||||||
{
|
{
|
||||||
@@ -56,7 +54,7 @@ final class PieceProductSyncSubscriber implements EventSubscriber
|
|||||||
{
|
{
|
||||||
$productIds = $piece->getProductIds();
|
$productIds = $piece->getProductIds();
|
||||||
|
|
||||||
if ($productIds === []) {
|
if ([] === $productIds) {
|
||||||
// If no explicit list is provided, keep the legacy relation as-is.
|
// If no explicit list is provided, keep the legacy relation as-is.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -77,4 +75,3 @@ final class PieceProductSyncSubscriber implements EventSubscriber
|
|||||||
$piece->setProduct($primaryProduct);
|
$piece->setProduct($primaryProduct);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,8 +5,11 @@ declare(strict_types=1);
|
|||||||
namespace App\EventSubscriber;
|
namespace App\EventSubscriber;
|
||||||
|
|
||||||
use App\Entity\AuditLog;
|
use App\Entity\AuditLog;
|
||||||
|
use App\Entity\CustomFieldValue;
|
||||||
use App\Entity\ModelType;
|
use App\Entity\ModelType;
|
||||||
use App\Entity\Product;
|
use App\Entity\Product;
|
||||||
|
use App\Entity\Profile;
|
||||||
|
use DateTimeInterface;
|
||||||
use Doctrine\Bundle\DoctrineBundle\Attribute\AsDoctrineListener;
|
use Doctrine\Bundle\DoctrineBundle\Attribute\AsDoctrineListener;
|
||||||
use Doctrine\Common\Collections\Collection;
|
use Doctrine\Common\Collections\Collection;
|
||||||
use Doctrine\Common\EventSubscriber;
|
use Doctrine\Common\EventSubscriber;
|
||||||
@@ -14,8 +17,16 @@ use Doctrine\ORM\EntityManagerInterface;
|
|||||||
use Doctrine\ORM\Event\OnFlushEventArgs;
|
use Doctrine\ORM\Event\OnFlushEventArgs;
|
||||||
use Doctrine\ORM\Events;
|
use Doctrine\ORM\Events;
|
||||||
use Doctrine\ORM\PersistentCollection;
|
use Doctrine\ORM\PersistentCollection;
|
||||||
|
use Doctrine\ORM\UnitOfWork;
|
||||||
|
use Symfony\Bundle\SecurityBundle\Security;
|
||||||
use Symfony\Component\HttpFoundation\RequestStack;
|
use Symfony\Component\HttpFoundation\RequestStack;
|
||||||
use Symfony\Component\HttpFoundation\Session\SessionInterface;
|
use Symfony\Component\HttpFoundation\Session\SessionInterface;
|
||||||
|
use Throwable;
|
||||||
|
|
||||||
|
use function is_array;
|
||||||
|
use function is_object;
|
||||||
|
use function is_scalar;
|
||||||
|
use function method_exists;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Record a lightweight, per-product audit trail.
|
* Record a lightweight, per-product audit trail.
|
||||||
@@ -27,9 +38,10 @@ use Symfony\Component\HttpFoundation\Session\SessionInterface;
|
|||||||
#[AsDoctrineListener(event: Events::onFlush)]
|
#[AsDoctrineListener(event: Events::onFlush)]
|
||||||
final class ProductAuditSubscriber implements EventSubscriber
|
final class ProductAuditSubscriber implements EventSubscriber
|
||||||
{
|
{
|
||||||
public function __construct(private readonly RequestStack $requestStack)
|
public function __construct(
|
||||||
{
|
private readonly RequestStack $requestStack,
|
||||||
}
|
private readonly Security $security,
|
||||||
|
) {}
|
||||||
|
|
||||||
public function getSubscribedEvents(): array
|
public function getSubscribedEvents(): array
|
||||||
{
|
{
|
||||||
@@ -67,12 +79,12 @@ final class ProductAuditSubscriber implements EventSubscriber
|
|||||||
}
|
}
|
||||||
|
|
||||||
$productId = (string) $entity->getId();
|
$productId = (string) $entity->getId();
|
||||||
if ($productId === '') {
|
if ('' === $productId) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
$diff = $this->buildDiffFromChangeSet($uow->getEntityChangeSet($entity));
|
$diff = $this->buildDiffFromChangeSet($uow->getEntityChangeSet($entity));
|
||||||
if ($diff !== []) {
|
if ([] !== $diff) {
|
||||||
$pendingUpdates[$productId] = $this->mergeDiffs($pendingUpdates[$productId] ?? [], $diff);
|
$pendingUpdates[$productId] = $this->mergeDiffs($pendingUpdates[$productId] ?? [], $diff);
|
||||||
$pendingSnapshots[$productId] = $this->snapshotProduct($entity);
|
$pendingSnapshots[$productId] = $this->snapshotProduct($entity);
|
||||||
$pendingProducts[$productId] = $entity;
|
$pendingProducts[$productId] = $entity;
|
||||||
@@ -96,8 +108,10 @@ final class ProductAuditSubscriber implements EventSubscriber
|
|||||||
$this->collectCollectionUpdate($collection, $pendingUpdates, $pendingSnapshots, $pendingProducts);
|
$this->collectCollectionUpdate($collection, $pendingUpdates, $pendingSnapshots, $pendingProducts);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$this->collectCustomFieldValueChanges($uow, $pendingUpdates, $pendingSnapshots, $pendingProducts);
|
||||||
|
|
||||||
foreach ($pendingUpdates as $productId => $diff) {
|
foreach ($pendingUpdates as $productId => $diff) {
|
||||||
if ($diff === []) {
|
if ([] === $diff) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -132,13 +146,13 @@ final class ProductAuditSubscriber implements EventSubscriber
|
|||||||
}
|
}
|
||||||
|
|
||||||
$productId = (string) $owner->getId();
|
$productId = (string) $owner->getId();
|
||||||
if ($productId === '') {
|
if ('' === $productId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$mapping = $collection->getMapping();
|
$mapping = $collection->getMapping();
|
||||||
$fieldName = $mapping['fieldName'] ?? null;
|
$fieldName = $mapping['fieldName'] ?? null;
|
||||||
if ($fieldName !== 'constructeurs') {
|
if ('constructeurs' !== $fieldName) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -161,6 +175,75 @@ final class ProductAuditSubscriber implements EventSubscriber
|
|||||||
$pendingProducts[$productId] = $owner;
|
$pendingProducts[$productId] = $owner;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<string, array<string, array{from:mixed, to:mixed}>> $pendingUpdates
|
||||||
|
* @param array<string, array<string, mixed>> $pendingSnapshots
|
||||||
|
* @param array<string, Product> $pendingProducts
|
||||||
|
*/
|
||||||
|
private function collectCustomFieldValueChanges(
|
||||||
|
UnitOfWork $uow,
|
||||||
|
array &$pendingUpdates,
|
||||||
|
array &$pendingSnapshots,
|
||||||
|
array &$pendingProducts,
|
||||||
|
): void {
|
||||||
|
foreach ($uow->getScheduledEntityInsertions() as $entity) {
|
||||||
|
if ($entity instanceof CustomFieldValue) {
|
||||||
|
$this->trackCustomFieldValueChange($entity, null, $entity->getValue(), $pendingUpdates, $pendingSnapshots, $pendingProducts);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($uow->getScheduledEntityUpdates() as $entity) {
|
||||||
|
if (!$entity instanceof CustomFieldValue) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$changeSet = $uow->getEntityChangeSet($entity);
|
||||||
|
if (!isset($changeSet['value'])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
[$oldVal, $newVal] = $changeSet['value'];
|
||||||
|
if ($oldVal !== $newVal) {
|
||||||
|
$this->trackCustomFieldValueChange($entity, $oldVal, $newVal, $pendingUpdates, $pendingSnapshots, $pendingProducts);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($uow->getScheduledEntityDeletions() as $entity) {
|
||||||
|
if ($entity instanceof CustomFieldValue) {
|
||||||
|
$this->trackCustomFieldValueChange($entity, $entity->getValue(), null, $pendingUpdates, $pendingSnapshots, $pendingProducts);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<string, array<string, array{from:mixed, to:mixed}>> $pendingUpdates
|
||||||
|
* @param array<string, array<string, mixed>> $pendingSnapshots
|
||||||
|
* @param array<string, Product> $pendingProducts
|
||||||
|
*/
|
||||||
|
private function trackCustomFieldValueChange(
|
||||||
|
CustomFieldValue $cfv,
|
||||||
|
mixed $from,
|
||||||
|
mixed $to,
|
||||||
|
array &$pendingUpdates,
|
||||||
|
array &$pendingSnapshots,
|
||||||
|
array &$pendingProducts,
|
||||||
|
): void {
|
||||||
|
$owner = $cfv->getProduct();
|
||||||
|
if (!$owner instanceof Product) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$ownerId = (string) $owner->getId();
|
||||||
|
if ('' === $ownerId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$fieldName = 'customField:'.$cfv->getCustomField()->getName();
|
||||||
|
$diff = [$fieldName => ['from' => $from, 'to' => $to]];
|
||||||
|
|
||||||
|
$pendingUpdates[$ownerId] = $this->mergeDiffs($pendingUpdates[$ownerId] ?? [], $diff);
|
||||||
|
$pendingSnapshots[$ownerId] = $this->snapshotProduct($owner);
|
||||||
|
$pendingProducts[$ownerId] = $owner;
|
||||||
|
}
|
||||||
|
|
||||||
private function persistAuditLog(EntityManagerInterface $em, AuditLog $log): void
|
private function persistAuditLog(EntityManagerInterface $em, AuditLog $log): void
|
||||||
{
|
{
|
||||||
$uow = $em->getUnitOfWork();
|
$uow = $em->getUnitOfWork();
|
||||||
@@ -174,6 +257,7 @@ final class ProductAuditSubscriber implements EventSubscriber
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @param array<string, array{0:mixed, 1:mixed}> $changeSet
|
* @param array<string, array{0:mixed, 1:mixed}> $changeSet
|
||||||
|
*
|
||||||
* @return array<string, array{from:mixed, to:mixed}>
|
* @return array<string, array{from:mixed, to:mixed}>
|
||||||
*/
|
*/
|
||||||
private function buildDiffFromChangeSet(array $changeSet): array
|
private function buildDiffFromChangeSet(array $changeSet): array
|
||||||
@@ -181,7 +265,7 @@ final class ProductAuditSubscriber implements EventSubscriber
|
|||||||
$diff = [];
|
$diff = [];
|
||||||
foreach ($changeSet as $field => [$oldValue, $newValue]) {
|
foreach ($changeSet as $field => [$oldValue, $newValue]) {
|
||||||
// Skip noisy timestamps managed automatically.
|
// Skip noisy timestamps managed automatically.
|
||||||
if ($field === 'updatedAt' || $field === 'createdAt') {
|
if ('updatedAt' === $field || 'createdAt' === $field) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -216,6 +300,7 @@ final class ProductAuditSubscriber implements EventSubscriber
|
|||||||
/**
|
/**
|
||||||
* @param array<string, array{from:mixed, to:mixed}> $base
|
* @param array<string, array{from:mixed, to:mixed}> $base
|
||||||
* @param array<string, array{from:mixed, to:mixed}> $extra
|
* @param array<string, array{from:mixed, to:mixed}> $extra
|
||||||
|
*
|
||||||
* @return array<string, array{from:mixed, to:mixed}>
|
* @return array<string, array{from:mixed, to:mixed}>
|
||||||
*/
|
*/
|
||||||
private function mergeDiffs(array $base, array $extra): array
|
private function mergeDiffs(array $base, array $extra): array
|
||||||
@@ -229,33 +314,39 @@ final class ProductAuditSubscriber implements EventSubscriber
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @param iterable<mixed> $items
|
* @param iterable<mixed> $items
|
||||||
* @return list<string>
|
*
|
||||||
|
* @return list<array{id: string, name: string}|string>
|
||||||
*/
|
*/
|
||||||
private function normalizeCollection(iterable $items): array
|
private function normalizeCollection(iterable $items): array
|
||||||
{
|
{
|
||||||
$ids = [];
|
$entries = [];
|
||||||
|
$seen = [];
|
||||||
foreach ($items as $item) {
|
foreach ($items as $item) {
|
||||||
if (\is_object($item) && \method_exists($item, 'getId')) {
|
if (is_object($item) && method_exists($item, 'getId')) {
|
||||||
$id = $item->getId();
|
$id = $item->getId();
|
||||||
if ($id !== null && $id !== '') {
|
if (null === $id || '' === $id || isset($seen[(string) $id])) {
|
||||||
$ids[] = (string) $id;
|
continue;
|
||||||
|
}
|
||||||
|
$seen[(string) $id] = true;
|
||||||
|
if (method_exists($item, 'getName')) {
|
||||||
|
$entries[] = ['id' => (string) $id, 'name' => (string) $item->getName()];
|
||||||
|
} else {
|
||||||
|
$entries[] = (string) $id;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sort($ids);
|
return $entries;
|
||||||
|
|
||||||
return array_values(array_unique($ids));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private function normalizeValue(mixed $value): mixed
|
private function normalizeValue(mixed $value): mixed
|
||||||
{
|
{
|
||||||
if ($value === null || \is_scalar($value)) {
|
if (null === $value || is_scalar($value)) {
|
||||||
return $value;
|
return $value;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($value instanceof \DateTimeInterface) {
|
if ($value instanceof DateTimeInterface) {
|
||||||
return $value->format(\DateTimeInterface::ATOM);
|
return $value->format(DateTimeInterface::ATOM);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($value instanceof ModelType) {
|
if ($value instanceof ModelType) {
|
||||||
@@ -270,11 +361,11 @@ final class ProductAuditSubscriber implements EventSubscriber
|
|||||||
return $this->normalizeCollection($value);
|
return $this->normalizeCollection($value);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (\is_object($value) && \method_exists($value, 'getId')) {
|
if (is_object($value) && method_exists($value, 'getId')) {
|
||||||
return (string) $value->getId();
|
return (string) $value->getId();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (\is_array($value)) {
|
if (is_array($value)) {
|
||||||
return $value;
|
return $value;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -283,16 +374,23 @@ final class ProductAuditSubscriber implements EventSubscriber
|
|||||||
|
|
||||||
private function resolveActorProfileId(): ?string
|
private function resolveActorProfileId(): ?string
|
||||||
{
|
{
|
||||||
|
try {
|
||||||
$session = $this->requestStack->getSession();
|
$session = $this->requestStack->getSession();
|
||||||
if (!$session instanceof SessionInterface) {
|
if ($session instanceof SessionInterface) {
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
$profileId = $session->get('profileId');
|
$profileId = $session->get('profileId');
|
||||||
if (!$profileId) {
|
if ($profileId) {
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (string) $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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
|||||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||||
use Symfony\Component\HttpKernel\Event\ExceptionEvent;
|
use Symfony\Component\HttpKernel\Event\ExceptionEvent;
|
||||||
use Symfony\Component\HttpKernel\KernelEvents;
|
use Symfony\Component\HttpKernel\KernelEvents;
|
||||||
|
use Throwable;
|
||||||
|
|
||||||
final class UniqueConstraintSubscriber implements EventSubscriberInterface
|
final class UniqueConstraintSubscriber implements EventSubscriberInterface
|
||||||
{
|
{
|
||||||
@@ -36,9 +37,9 @@ final class UniqueConstraintSubscriber implements EventSubscriberInterface
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
private function findUniqueConstraintViolation(\Throwable $throwable): ?UniqueConstraintViolationException
|
private function findUniqueConstraintViolation(Throwable $throwable): ?UniqueConstraintViolationException
|
||||||
{
|
{
|
||||||
for ($current = $throwable; $current !== null; $current = $current->getPrevious()) {
|
for ($current = $throwable; null !== $current; $current = $current->getPrevious()) {
|
||||||
if ($current instanceof UniqueConstraintViolationException) {
|
if ($current instanceof UniqueConstraintViolationException) {
|
||||||
return $current;
|
return $current;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,7 +31,46 @@ final class AuditLogRepository extends ServiceEntityRepository
|
|||||||
->orderBy('a.createdAt', 'DESC')
|
->orderBy('a.createdAt', 'DESC')
|
||||||
->setMaxResults($limit)
|
->setMaxResults($limit)
|
||||||
->getQuery()
|
->getQuery()
|
||||||
->getResult();
|
->getResult()
|
||||||
}
|
;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array{entityType?: string, action?: string} $filters
|
||||||
|
*
|
||||||
|
* @return array{items: list<AuditLog>, total: int}
|
||||||
|
*/
|
||||||
|
public function findAllPaginated(int $page = 1, int $itemsPerPage = 30, array $filters = []): array
|
||||||
|
{
|
||||||
|
$qb = $this->createQueryBuilder('a')
|
||||||
|
->orderBy('a.createdAt', 'DESC')
|
||||||
|
;
|
||||||
|
|
||||||
|
if (!empty($filters['entityType'])) {
|
||||||
|
$qb->andWhere('a.entityType = :entityType')
|
||||||
|
->setParameter('entityType', $filters['entityType'])
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($filters['action'])) {
|
||||||
|
$qb->andWhere('a.action = :action')
|
||||||
|
->setParameter('action', $filters['action'])
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
$countQb = clone $qb;
|
||||||
|
$countQb->select('COUNT(a.id)')
|
||||||
|
->resetDQLPart('orderBy')
|
||||||
|
;
|
||||||
|
$total = (int) $countQb->getQuery()->getSingleScalarResult();
|
||||||
|
|
||||||
|
$qb->setFirstResult(($page - 1) * $itemsPerPage)
|
||||||
|
->setMaxResults($itemsPerPage)
|
||||||
|
;
|
||||||
|
|
||||||
|
return [
|
||||||
|
'items' => $qb->getQuery()->getResult(),
|
||||||
|
'total' => $total,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
494
src/Service/ModelTypeCategoryConversionService.php
Normal file
494
src/Service/ModelTypeCategoryConversionService.php
Normal file
@@ -0,0 +1,494 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Service;
|
||||||
|
|
||||||
|
use App\Entity\Profile;
|
||||||
|
use App\Enum\ModelCategory;
|
||||||
|
use App\Repository\ModelTypeRepository;
|
||||||
|
use DateTimeImmutable;
|
||||||
|
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
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private readonly Connection $connection,
|
||||||
|
private readonly ModelTypeRepository $modelTypes,
|
||||||
|
private readonly RequestStack $requestStack,
|
||||||
|
private readonly Security $security,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array{canConvert: bool, direction: null|string, itemCount: int, names: list<string>, blockers: list<string>}
|
||||||
|
*/
|
||||||
|
public function checkConversion(string $modelTypeId): array
|
||||||
|
{
|
||||||
|
$modelType = $this->modelTypes->find($modelTypeId);
|
||||||
|
|
||||||
|
if (!$modelType) {
|
||||||
|
return [
|
||||||
|
'canConvert' => false,
|
||||||
|
'direction' => null,
|
||||||
|
'itemCount' => 0,
|
||||||
|
'names' => [],
|
||||||
|
'blockers' => ['Catégorie introuvable.'],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$category = $modelType->getCategory();
|
||||||
|
|
||||||
|
if (ModelCategory::PRODUCT === $category) {
|
||||||
|
return [
|
||||||
|
'canConvert' => false,
|
||||||
|
'direction' => null,
|
||||||
|
'itemCount' => 0,
|
||||||
|
'names' => [],
|
||||||
|
'blockers' => ['La conversion n\'est pas disponible pour les catégories de produit.'],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ModelCategory::PIECE === $category) {
|
||||||
|
return $this->checkPieceToComponent($modelTypeId, $modelType->getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->checkComponentToPiece($modelTypeId, $modelType->getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array{success: bool, convertedCount: int, error: null|string}
|
||||||
|
*/
|
||||||
|
public function convert(string $modelTypeId): array
|
||||||
|
{
|
||||||
|
$check = $this->checkConversion($modelTypeId);
|
||||||
|
|
||||||
|
if (!$check['canConvert']) {
|
||||||
|
return [
|
||||||
|
'success' => false,
|
||||||
|
'convertedCount' => 0,
|
||||||
|
'error' => implode(' ', $check['blockers']),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$modelType = $this->modelTypes->find($modelTypeId);
|
||||||
|
|
||||||
|
if (!$modelType) {
|
||||||
|
return ['success' => false, 'convertedCount' => 0, 'error' => 'Catégorie introuvable.'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$category = $modelType->getCategory();
|
||||||
|
|
||||||
|
$direction = ModelCategory::PIECE === $category ? 'piece_to_component' : 'component_to_piece';
|
||||||
|
$names = $check['names'];
|
||||||
|
$modelName = $modelType->getName();
|
||||||
|
$modelCode = $modelType->getCode();
|
||||||
|
|
||||||
|
$this->connection->beginTransaction();
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (ModelCategory::PIECE === $category) {
|
||||||
|
$count = $this->convertPieceToComponent($modelTypeId);
|
||||||
|
} else {
|
||||||
|
$count = $this->convertComponentToPiece($modelTypeId);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->logConversionAudit($modelTypeId, $modelName, $modelCode, $direction, $count, $names);
|
||||||
|
|
||||||
|
$this->connection->commit();
|
||||||
|
|
||||||
|
return ['success' => true, 'convertedCount' => $count, 'error' => null];
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
$this->connection->rollBack();
|
||||||
|
|
||||||
|
return ['success' => false, 'convertedCount' => 0, 'error' => $e->getMessage()];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array{canConvert: bool, direction: string, itemCount: int, names: list<string>, blockers: list<string>}
|
||||||
|
*/
|
||||||
|
private function checkPieceToComponent(string $modelTypeId, string $modelTypeName): array
|
||||||
|
{
|
||||||
|
$blockers = [];
|
||||||
|
|
||||||
|
$pieceCount = (int) $this->connection->fetchOne(
|
||||||
|
'SELECT COUNT(*) FROM pieces WHERE typepieceid = :id',
|
||||||
|
['id' => $modelTypeId],
|
||||||
|
);
|
||||||
|
|
||||||
|
$names = $this->connection->fetchFirstColumn(
|
||||||
|
'SELECT name FROM pieces WHERE typepieceid = :id ORDER BY name',
|
||||||
|
['id' => $modelTypeId],
|
||||||
|
);
|
||||||
|
|
||||||
|
// Check machine links
|
||||||
|
$machineLinked = (int) $this->connection->fetchOne(
|
||||||
|
'SELECT COUNT(*) FROM machine_piece_links mpl
|
||||||
|
JOIN pieces p ON mpl.pieceid = p.id
|
||||||
|
WHERE p.typepieceid = :id',
|
||||||
|
['id' => $modelTypeId],
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($machineLinked > 0) {
|
||||||
|
$blockers[] = sprintf('%d pièce(s) liée(s) à des machines.', $machineLinked);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check type machine requirements
|
||||||
|
$requirementCount = (int) $this->connection->fetchOne(
|
||||||
|
'SELECT COUNT(*) FROM type_machine_piece_requirements WHERE typepieceid = :id',
|
||||||
|
['id' => $modelTypeId],
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($requirementCount > 0) {
|
||||||
|
$blockers[] = sprintf('Utilisé dans %d modèle(s) de type de machine.', $requirementCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check name collision with existing composants
|
||||||
|
$collisions = $this->connection->fetchFirstColumn(
|
||||||
|
'SELECT p.name FROM pieces p
|
||||||
|
WHERE p.typepieceid = :id
|
||||||
|
AND p.name IN (SELECT c.name FROM composants c)',
|
||||||
|
['id' => $modelTypeId],
|
||||||
|
);
|
||||||
|
|
||||||
|
if ([] !== $collisions) {
|
||||||
|
$blockers[] = sprintf(
|
||||||
|
'Collision de nom avec des composants existants : %s.',
|
||||||
|
implode(', ', $collisions),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check ModelType name collision
|
||||||
|
$nameCollision = (int) $this->connection->fetchOne(
|
||||||
|
'SELECT COUNT(*) FROM model_types WHERE category = :cat AND name = :name AND id != :id',
|
||||||
|
['cat' => ModelCategory::COMPONENT->value, 'name' => $modelTypeName, 'id' => $modelTypeId],
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($nameCollision > 0) {
|
||||||
|
$blockers[] = sprintf('Une catégorie de composant « %s » existe déjà.', $modelTypeName);
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'canConvert' => [] === $blockers,
|
||||||
|
'direction' => 'piece_to_component',
|
||||||
|
'itemCount' => $pieceCount,
|
||||||
|
'names' => $names,
|
||||||
|
'blockers' => $blockers,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array{canConvert: bool, direction: string, itemCount: int, names: list<string>, blockers: list<string>}
|
||||||
|
*/
|
||||||
|
private function checkComponentToPiece(string $modelTypeId, string $modelTypeName): array
|
||||||
|
{
|
||||||
|
$blockers = [];
|
||||||
|
|
||||||
|
$composantCount = (int) $this->connection->fetchOne(
|
||||||
|
'SELECT COUNT(*) FROM composants WHERE typecomposantid = :id',
|
||||||
|
['id' => $modelTypeId],
|
||||||
|
);
|
||||||
|
|
||||||
|
$names = $this->connection->fetchFirstColumn(
|
||||||
|
'SELECT name FROM composants WHERE typecomposantid = :id ORDER BY name',
|
||||||
|
['id' => $modelTypeId],
|
||||||
|
);
|
||||||
|
|
||||||
|
// Check machine links
|
||||||
|
$machineLinked = (int) $this->connection->fetchOne(
|
||||||
|
'SELECT COUNT(*) FROM machine_component_links mcl
|
||||||
|
JOIN composants c ON mcl.composantid = c.id
|
||||||
|
WHERE c.typecomposantid = :id',
|
||||||
|
['id' => $modelTypeId],
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($machineLinked > 0) {
|
||||||
|
$blockers[] = sprintf('%d composant(s) lié(s) à des machines.', $machineLinked);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check type machine requirements
|
||||||
|
$requirementCount = (int) $this->connection->fetchOne(
|
||||||
|
'SELECT COUNT(*) FROM type_machine_component_requirements WHERE typecomposantid = :id',
|
||||||
|
['id' => $modelTypeId],
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($requirementCount > 0) {
|
||||||
|
$blockers[] = sprintf('Utilisé dans %d modèle(s) de type de machine.', $requirementCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if any composant has pieces or sub-components in structure
|
||||||
|
$withStructure = $this->connection->fetchAllAssociative(
|
||||||
|
'SELECT name, structure FROM composants WHERE typecomposantid = :id AND structure IS NOT NULL',
|
||||||
|
['id' => $modelTypeId],
|
||||||
|
);
|
||||||
|
|
||||||
|
foreach ($withStructure as $row) {
|
||||||
|
$structure = json_decode($row['structure'], true);
|
||||||
|
|
||||||
|
if (!is_array($structure)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$hasPieces = !empty($structure['pieces']);
|
||||||
|
$hasSubcomponents = !empty($structure['subcomponents']);
|
||||||
|
|
||||||
|
if ($hasPieces || $hasSubcomponents) {
|
||||||
|
$parts = [];
|
||||||
|
|
||||||
|
if ($hasPieces) {
|
||||||
|
$parts[] = 'pièces';
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($hasSubcomponents) {
|
||||||
|
$parts[] = 'sous-composants';
|
||||||
|
}
|
||||||
|
|
||||||
|
$blockers[] = sprintf(
|
||||||
|
'Le composant « %s » contient des %s dans sa structure.',
|
||||||
|
$row['name'],
|
||||||
|
implode(' et ', $parts),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check name collision with existing pieces
|
||||||
|
$collisions = $this->connection->fetchFirstColumn(
|
||||||
|
'SELECT c.name FROM composants c
|
||||||
|
WHERE c.typecomposantid = :id
|
||||||
|
AND c.name IN (SELECT p.name FROM pieces p)',
|
||||||
|
['id' => $modelTypeId],
|
||||||
|
);
|
||||||
|
|
||||||
|
if ([] !== $collisions) {
|
||||||
|
$blockers[] = sprintf(
|
||||||
|
'Collision de nom avec des pièces existantes : %s.',
|
||||||
|
implode(', ', $collisions),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check ModelType name collision
|
||||||
|
$nameCollision = (int) $this->connection->fetchOne(
|
||||||
|
'SELECT COUNT(*) FROM model_types WHERE category = :cat AND name = :name AND id != :id',
|
||||||
|
['cat' => ModelCategory::PIECE->value, 'name' => $modelTypeName, 'id' => $modelTypeId],
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($nameCollision > 0) {
|
||||||
|
$blockers[] = sprintf('Une catégorie de pièce « %s » existe déjà.', $modelTypeName);
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'canConvert' => [] === $blockers,
|
||||||
|
'direction' => 'component_to_piece',
|
||||||
|
'itemCount' => $composantCount,
|
||||||
|
'names' => $names,
|
||||||
|
'blockers' => $blockers,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
private function convertPieceToComponent(string $modelTypeId): int
|
||||||
|
{
|
||||||
|
// 1. Insert into composants from pieces
|
||||||
|
$count = $this->connection->executeStatement(
|
||||||
|
'INSERT INTO composants (id, name, reference, prix, structure, typecomposantid, productid, createdat, updatedat)
|
||||||
|
SELECT id, name, reference, prix, NULL, typepieceid, productid, createdat, updatedat
|
||||||
|
FROM pieces
|
||||||
|
WHERE typepieceid = :id',
|
||||||
|
['id' => $modelTypeId],
|
||||||
|
);
|
||||||
|
|
||||||
|
// 2. Transfer constructeur associations
|
||||||
|
$this->connection->executeStatement(
|
||||||
|
'INSERT INTO _composantconstructeurs (a, b)
|
||||||
|
SELECT pc.a, pc.b FROM _piececonstructeurs pc
|
||||||
|
WHERE pc.a IN (SELECT id FROM composants WHERE typecomposantid = :id)',
|
||||||
|
['id' => $modelTypeId],
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->connection->executeStatement(
|
||||||
|
'DELETE FROM _piececonstructeurs
|
||||||
|
WHERE a IN (SELECT id FROM pieces WHERE typepieceid = :id)',
|
||||||
|
['id' => $modelTypeId],
|
||||||
|
);
|
||||||
|
|
||||||
|
// 3. Transfer document references
|
||||||
|
$this->connection->executeStatement(
|
||||||
|
'UPDATE documents SET composantid = pieceid, pieceid = NULL
|
||||||
|
WHERE pieceid IN (SELECT id FROM pieces WHERE typepieceid = :id)',
|
||||||
|
['id' => $modelTypeId],
|
||||||
|
);
|
||||||
|
|
||||||
|
// 4. Transfer custom_field_values references
|
||||||
|
$this->connection->executeStatement(
|
||||||
|
'UPDATE custom_field_values SET composantid = pieceid, pieceid = NULL
|
||||||
|
WHERE pieceid IN (SELECT id FROM pieces WHERE typepieceid = :id)',
|
||||||
|
['id' => $modelTypeId],
|
||||||
|
);
|
||||||
|
|
||||||
|
// 5. Transfer custom_fields from typePiece to typeComposant
|
||||||
|
$this->connection->executeStatement(
|
||||||
|
'UPDATE custom_fields SET typecomposantid = typepieceid, typepieceid = NULL
|
||||||
|
WHERE typepieceid = :id',
|
||||||
|
['id' => $modelTypeId],
|
||||||
|
);
|
||||||
|
|
||||||
|
// 6. Delete original pieces
|
||||||
|
$this->connection->executeStatement(
|
||||||
|
'DELETE FROM pieces WHERE typepieceid = :id',
|
||||||
|
['id' => $modelTypeId],
|
||||||
|
);
|
||||||
|
|
||||||
|
// 7. Update ModelType
|
||||||
|
$this->connection->executeStatement(
|
||||||
|
'UPDATE model_types
|
||||||
|
SET category = :cat,
|
||||||
|
componentskeleton = pieceskeleton,
|
||||||
|
pieceskeleton = NULL,
|
||||||
|
updatedat = :now
|
||||||
|
WHERE id = :id',
|
||||||
|
[
|
||||||
|
'cat' => ModelCategory::COMPONENT->value,
|
||||||
|
'now' => new DateTimeImmutable()->format('Y-m-d H:i:s'),
|
||||||
|
'id' => $modelTypeId,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
return $count;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function convertComponentToPiece(string $modelTypeId): int
|
||||||
|
{
|
||||||
|
// 1. Insert into pieces from composants
|
||||||
|
$count = $this->connection->executeStatement(
|
||||||
|
'INSERT INTO pieces (id, name, reference, prix, productids, typepieceid, productid, createdat, updatedat)
|
||||||
|
SELECT id, name, reference, prix, NULL, typecomposantid, productid, createdat, updatedat
|
||||||
|
FROM composants
|
||||||
|
WHERE typecomposantid = :id',
|
||||||
|
['id' => $modelTypeId],
|
||||||
|
);
|
||||||
|
|
||||||
|
// 2. Transfer constructeur associations
|
||||||
|
$this->connection->executeStatement(
|
||||||
|
'INSERT INTO _piececonstructeurs (a, b)
|
||||||
|
SELECT cc.a, cc.b FROM _composantconstructeurs cc
|
||||||
|
WHERE cc.a IN (SELECT id FROM pieces WHERE typepieceid = :id)',
|
||||||
|
['id' => $modelTypeId],
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->connection->executeStatement(
|
||||||
|
'DELETE FROM _composantconstructeurs
|
||||||
|
WHERE a IN (SELECT id FROM composants WHERE typecomposantid = :id)',
|
||||||
|
['id' => $modelTypeId],
|
||||||
|
);
|
||||||
|
|
||||||
|
// 3. Transfer document references
|
||||||
|
$this->connection->executeStatement(
|
||||||
|
'UPDATE documents SET pieceid = composantid, composantid = NULL
|
||||||
|
WHERE composantid IN (SELECT id FROM composants WHERE typecomposantid = :id)',
|
||||||
|
['id' => $modelTypeId],
|
||||||
|
);
|
||||||
|
|
||||||
|
// 4. Transfer custom_field_values references
|
||||||
|
$this->connection->executeStatement(
|
||||||
|
'UPDATE custom_field_values SET pieceid = composantid, composantid = NULL
|
||||||
|
WHERE composantid IN (SELECT id FROM composants WHERE typecomposantid = :id)',
|
||||||
|
['id' => $modelTypeId],
|
||||||
|
);
|
||||||
|
|
||||||
|
// 5. Transfer custom_fields from typeComposant to typePiece
|
||||||
|
$this->connection->executeStatement(
|
||||||
|
'UPDATE custom_fields SET typepieceid = typecomposantid, typecomposantid = NULL
|
||||||
|
WHERE typecomposantid = :id',
|
||||||
|
['id' => $modelTypeId],
|
||||||
|
);
|
||||||
|
|
||||||
|
// 6. Delete original composants
|
||||||
|
$this->connection->executeStatement(
|
||||||
|
'DELETE FROM composants WHERE typecomposantid = :id',
|
||||||
|
['id' => $modelTypeId],
|
||||||
|
);
|
||||||
|
|
||||||
|
// 7. Update ModelType
|
||||||
|
$this->connection->executeStatement(
|
||||||
|
'UPDATE model_types
|
||||||
|
SET category = :cat,
|
||||||
|
pieceskeleton = componentskeleton,
|
||||||
|
componentskeleton = NULL,
|
||||||
|
updatedat = :now
|
||||||
|
WHERE id = :id',
|
||||||
|
[
|
||||||
|
'cat' => ModelCategory::PIECE->value,
|
||||||
|
'now' => new DateTimeImmutable()->format('Y-m-d H:i:s'),
|
||||||
|
'id' => $modelTypeId,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
return $count;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param list<string> $names
|
||||||
|
*/
|
||||||
|
private function logConversionAudit(
|
||||||
|
string $modelTypeId,
|
||||||
|
string $modelName,
|
||||||
|
string $modelCode,
|
||||||
|
string $direction,
|
||||||
|
int $convertedCount,
|
||||||
|
array $names,
|
||||||
|
): void {
|
||||||
|
$now = new DateTimeImmutable()->format('Y-m-d H:i:s');
|
||||||
|
$id = 'cl'.bin2hex(random_bytes(12));
|
||||||
|
|
||||||
|
$snapshot = [
|
||||||
|
'id' => $modelTypeId,
|
||||||
|
'name' => $modelName,
|
||||||
|
'code' => $modelCode,
|
||||||
|
];
|
||||||
|
|
||||||
|
$diff = [
|
||||||
|
'direction' => ['from' => null, 'to' => $direction],
|
||||||
|
'convertedCount' => ['from' => null, 'to' => $convertedCount],
|
||||||
|
'convertedNames' => ['from' => null, 'to' => $names],
|
||||||
|
];
|
||||||
|
|
||||||
|
$this->connection->executeStatement(
|
||||||
|
'INSERT INTO audit_logs (id, entitytype, entityid, action, diff, snapshot, actorprofileid, createdat)
|
||||||
|
VALUES (:id, :entityType, :entityId, :action, :diff, :snapshot, :actor, :now)',
|
||||||
|
[
|
||||||
|
'id' => $id,
|
||||||
|
'entityType' => 'model_type',
|
||||||
|
'entityId' => $modelTypeId,
|
||||||
|
'action' => 'convert',
|
||||||
|
'diff' => json_encode($diff),
|
||||||
|
'snapshot' => json_encode($snapshot),
|
||||||
|
'actor' => $this->resolveActorProfileId(),
|
||||||
|
'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