Files
Inventory/src/Controller/CustomFieldValueController.php
Matthieu cd2a3fac55 feat(categories) : add bidirectional piece/component category conversion
Backend service and controller for converting piece categories to component
categories (and vice-versa). Uses raw SQL in a transaction to preserve IDs
and transfer all related data (documents, custom fields, constructeurs).
Includes php-cs-fixer formatting pass on existing controllers/entities.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-12 14:27:07 +01:00

295 lines
10 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Controller;
use App\Entity\CustomField;
use App\Entity\CustomFieldValue;
use App\Repository\ComposantRepository;
use App\Repository\CustomFieldRepository;
use App\Repository\CustomFieldValueRepository;
use App\Repository\MachineRepository;
use App\Repository\PieceRepository;
use App\Repository\ProductRepository;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Attribute\Route;
#[Route('/api/custom-fields/values')]
class CustomFieldValueController extends AbstractController
{
public function __construct(
private readonly EntityManagerInterface $entityManager,
private readonly CustomFieldRepository $customFieldRepository,
private readonly CustomFieldValueRepository $customFieldValueRepository,
private readonly MachineRepository $machineRepository,
private readonly ComposantRepository $composantRepository,
private readonly PieceRepository $pieceRepository,
private readonly ProductRepository $productRepository,
) {}
#[Route('', name: 'custom_field_values_create', methods: ['POST'])]
public function create(Request $request): JsonResponse
{
$payload = $this->decodePayload($request);
if ($payload instanceof JsonResponse) {
return $payload;
}
$customField = $this->resolveCustomField($payload);
if ($customField instanceof JsonResponse) {
return $customField;
}
$target = $this->resolveTarget($payload);
if ($target instanceof JsonResponse) {
return $target;
}
$value = new CustomFieldValue();
$value->setCustomField($customField);
$value->setValue((string) ($payload['value'] ?? ''));
$this->applyTarget($value, $target['type'], $target['entity']);
$this->entityManager->persist($value);
$this->entityManager->flush();
return $this->json($this->normalizeCustomFieldValue($value));
}
#[Route('/upsert', name: 'custom_field_values_upsert', methods: ['POST'])]
public function upsert(Request $request): JsonResponse
{
$payload = $this->decodePayload($request);
if ($payload instanceof JsonResponse) {
return $payload;
}
$customField = $this->resolveCustomField($payload);
if ($customField instanceof JsonResponse) {
return $customField;
}
$target = $this->resolveTarget($payload);
if ($target instanceof JsonResponse) {
return $target;
}
$existing = $this->customFieldValueRepository->findOneBy([
'customField' => $customField,
$target['type'] => $target['entity'],
]);
if ($existing instanceof CustomFieldValue) {
$existing->setValue((string) ($payload['value'] ?? ''));
$this->entityManager->flush();
return $this->json($this->normalizeCustomFieldValue($existing));
}
$value = new CustomFieldValue();
$value->setCustomField($customField);
$value->setValue((string) ($payload['value'] ?? ''));
$this->applyTarget($value, $target['type'], $target['entity']);
$this->entityManager->persist($value);
$this->entityManager->flush();
return $this->json($this->normalizeCustomFieldValue($value));
}
#[Route('/{entityType}/{entityId}', name: 'custom_field_values_list', methods: ['GET'])]
public function listByEntity(string $entityType, string $entityId): JsonResponse
{
$target = $this->resolveTarget([
'entityType' => $entityType,
'entityId' => $entityId,
]);
if ($target instanceof JsonResponse) {
return $target;
}
$values = $this->customFieldValueRepository->findBy([
$target['type'] => $target['entity'],
]);
return $this->json(array_map(
fn (CustomFieldValue $value) => $this->normalizeCustomFieldValue($value),
$values
));
}
#[Route('/{id}', name: 'custom_field_values_update', methods: ['PATCH'])]
public function update(string $id, Request $request): JsonResponse
{
$value = $this->customFieldValueRepository->find($id);
if (!$value instanceof CustomFieldValue) {
return $this->json(['success' => false, 'error' => 'Custom field value not found.'], 404);
}
$payload = $this->decodePayload($request);
if ($payload instanceof JsonResponse) {
return $payload;
}
if (array_key_exists('value', $payload)) {
$value->setValue((string) $payload['value']);
}
$this->entityManager->flush();
return $this->json($this->normalizeCustomFieldValue($value));
}
#[Route('/{id}', name: 'custom_field_values_delete', methods: ['DELETE'])]
public function delete(string $id): JsonResponse
{
$value = $this->customFieldValueRepository->find($id);
if (!$value instanceof CustomFieldValue) {
return $this->json(['success' => false, 'error' => 'Custom field value not found.'], 404);
}
$this->entityManager->remove($value);
$this->entityManager->flush();
return $this->json(['success' => true]);
}
private function decodePayload(Request $request): array|JsonResponse
{
$payload = json_decode($request->getContent(), true);
if (!is_array($payload)) {
return $this->json(['success' => false, 'error' => 'Invalid JSON payload.'], 400);
}
return $payload;
}
private function resolveCustomField(array $payload): CustomField|JsonResponse
{
$customFieldId = isset($payload['customFieldId']) ? trim((string) $payload['customFieldId']) : '';
if ('' !== $customFieldId) {
$customField = $this->customFieldRepository->find($customFieldId);
if ($customField instanceof CustomField) {
return $customField;
}
return $this->json(['success' => false, 'error' => 'Custom field not found.'], 404);
}
$customFieldName = isset($payload['customFieldName']) ? trim((string) $payload['customFieldName']) : '';
if ('' === $customFieldName) {
return $this->json(['success' => false, 'error' => 'customFieldId or customFieldName is required.'], 400);
}
$customField = new CustomField();
$customField->setName($customFieldName);
$customField->setType((string) ($payload['customFieldType'] ?? 'text'));
$customField->setRequired((bool) ($payload['customFieldRequired'] ?? false));
$options = $payload['customFieldOptions'] ?? null;
if (is_array($options)) {
$customField->setOptions($options);
}
$this->entityManager->persist($customField);
return $customField;
}
private function resolveTarget(array $payload): array|JsonResponse
{
$entityType = isset($payload['entityType']) ? strtolower((string) $payload['entityType']) : '';
$entityId = isset($payload['entityId']) ? trim((string) $payload['entityId']) : '';
if ('' === $entityType || '' === $entityId) {
foreach (['machine', 'composant', 'piece', 'product'] as $candidate) {
$key = $candidate.'Id';
if (!isset($payload[$key])) {
continue;
}
$entityType = $candidate;
$entityId = trim((string) $payload[$key]);
break;
}
}
if ('' === $entityType || '' === $entityId) {
return $this->json(['success' => false, 'error' => 'Entity target is missing.'], 400);
}
return match ($entityType) {
'machine' => $this->resolveEntity('machine', $entityId, $this->machineRepository),
'composant' => $this->resolveEntity('composant', $entityId, $this->composantRepository),
'piece' => $this->resolveEntity('piece', $entityId, $this->pieceRepository),
'product' => $this->resolveEntity('product', $entityId, $this->productRepository),
default => $this->json(['success' => false, 'error' => 'Unsupported entity type.'], 400),
};
}
private function resolveEntity(string $type, string $id, $repository): array|JsonResponse
{
$entity = $repository->find($id);
if (!$entity) {
return $this->json(['success' => false, 'error' => sprintf('%s not found.', $type)], 404);
}
return ['type' => $type, 'entity' => $entity];
}
private function applyTarget(CustomFieldValue $value, string $type, object $entity): void
{
switch ($type) {
case 'machine':
$value->setMachine($entity);
break;
case 'composant':
$value->setComposant($entity);
break;
case 'piece':
$value->setPiece($entity);
break;
case 'product':
$value->setProduct($entity);
break;
}
}
private function normalizeCustomFieldValue(CustomFieldValue $value): array
{
$customField = $value->getCustomField();
return [
'id' => $value->getId(),
'value' => $value->getValue(),
'customFieldId' => $customField->getId(),
'customField' => [
'id' => $customField->getId(),
'name' => $customField->getName(),
'type' => $customField->getType(),
'required' => $customField->isRequired(),
'options' => $customField->getOptions(),
'orderIndex' => $customField->getOrderIndex(),
],
'machineId' => $value->getMachine()?->getId(),
'composantId' => $value->getComposant()?->getId(),
'pieceId' => $value->getPiece()?->getId(),
'productId' => $value->getProduct()?->getId(),
'createdAt' => $value->getCreatedAt()->format(DATE_ATOM),
'updatedAt' => $value->getUpdatedAt()->format(DATE_ATOM),
];
}
}