From fab1d25871f69420f6e75a3ad85ebd2f900c039f Mon Sep 17 00:00:00 2001 From: r-dev Date: Sun, 11 Jan 2026 17:05:41 +0100 Subject: [PATCH] feat(api): ajouter les endpoints session, documents, champs et squelette --- src/Controller/CustomFieldValueController.php | 287 ++++++++ src/Controller/DocumentQueryController.php | 119 ++++ .../MachineCustomFieldsController.php | 76 ++ src/Controller/MachineSkeletonController.php | 657 ++++++++++++++++++ src/Controller/SessionProfileController.php | 90 +++ src/Controller/SessionProfilesController.php | 80 +++ 6 files changed, 1309 insertions(+) create mode 100644 src/Controller/CustomFieldValueController.php create mode 100644 src/Controller/DocumentQueryController.php create mode 100644 src/Controller/MachineCustomFieldsController.php create mode 100644 src/Controller/MachineSkeletonController.php create mode 100644 src/Controller/SessionProfileController.php create mode 100644 src/Controller/SessionProfilesController.php diff --git a/src/Controller/CustomFieldValueController.php b/src/Controller/CustomFieldValueController.php new file mode 100644 index 0000000..5573876 --- /dev/null +++ b/src/Controller/CustomFieldValueController.php @@ -0,0 +1,287 @@ +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), + ]; + } +} diff --git a/src/Controller/DocumentQueryController.php b/src/Controller/DocumentQueryController.php new file mode 100644 index 0000000..833e888 --- /dev/null +++ b/src/Controller/DocumentQueryController.php @@ -0,0 +1,119 @@ +siteRepository->find($id); + if (!$site) { + return $this->json(['success' => false, 'error' => 'Site not found.'], 404); + } + + $documents = $this->documentRepository->findBy(['site' => $site]); + + return $this->json($this->normalizeDocuments($documents)); + } + + #[Route('/machine/{id}', name: 'documents_by_machine', methods: ['GET'])] + public function listByMachine(string $id): JsonResponse + { + $machine = $this->machineRepository->find($id); + if (!$machine) { + return $this->json(['success' => false, 'error' => 'Machine not found.'], 404); + } + + $documents = $this->documentRepository->findBy(['machine' => $machine]); + + return $this->json($this->normalizeDocuments($documents)); + } + + #[Route('/composant/{id}', name: 'documents_by_composant', methods: ['GET'])] + public function listByComposant(string $id): JsonResponse + { + $composant = $this->composantRepository->find($id); + if (!$composant) { + return $this->json(['success' => false, 'error' => 'Composant not found.'], 404); + } + + $documents = $this->documentRepository->findBy(['composant' => $composant]); + + return $this->json($this->normalizeDocuments($documents)); + } + + #[Route('/piece/{id}', name: 'documents_by_piece', methods: ['GET'])] + public function listByPiece(string $id): JsonResponse + { + $piece = $this->pieceRepository->find($id); + if (!$piece) { + return $this->json(['success' => false, 'error' => 'Piece not found.'], 404); + } + + $documents = $this->documentRepository->findBy(['piece' => $piece]); + + return $this->json($this->normalizeDocuments($documents)); + } + + #[Route('/product/{id}', name: 'documents_by_product', methods: ['GET'])] + public function listByProduct(string $id): JsonResponse + { + $product = $this->productRepository->find($id); + if (!$product) { + return $this->json(['success' => false, 'error' => 'Product not found.'], 404); + } + + $documents = $this->documentRepository->findBy(['product' => $product]); + + return $this->json($this->normalizeDocuments($documents)); + } + + /** + * @param Document[] $documents + */ + private function normalizeDocuments(array $documents): array + { + return array_map(static function (Document $document): array { + return [ + 'id' => $document->getId(), + 'name' => $document->getName(), + 'filename' => $document->getFilename(), + 'path' => $document->getPath(), + 'mimeType' => $document->getMimeType(), + 'size' => $document->getSize(), + 'siteId' => $document->getSite()?->getId(), + 'machineId' => $document->getMachine()?->getId(), + 'composantId' => $document->getComposant()?->getId(), + 'pieceId' => $document->getPiece()?->getId(), + 'productId' => $document->getProduct()?->getId(), + 'createdAt' => $document->getCreatedAt()->format(DATE_ATOM), + 'updatedAt' => $document->getUpdatedAt()->format(DATE_ATOM), + ]; + }, $documents); + } +} diff --git a/src/Controller/MachineCustomFieldsController.php b/src/Controller/MachineCustomFieldsController.php new file mode 100644 index 0000000..734428c --- /dev/null +++ b/src/Controller/MachineCustomFieldsController.php @@ -0,0 +1,76 @@ +machineRepository->find($id); + if (!$machine instanceof Machine) { + return $this->json(['success' => false, 'error' => 'Machine not found.'], 404); + } + + $typeMachine = $machine->getTypeMachine(); + if (!$typeMachine) { + return $this->json(['success' => true, 'machineId' => $machine->getId(), 'customFieldValues' => []]); + } + + foreach ($typeMachine->getCustomFields() as $customField) { + if (!$customField instanceof CustomField) { + continue; + } + $existing = $this->customFieldValueRepository->findOneBy([ + 'machine' => $machine, + 'customField' => $customField, + ]); + if ($existing instanceof CustomFieldValue) { + continue; + } + + $value = new CustomFieldValue(); + $value->setMachine($machine); + $value->setCustomField($customField); + $value->setValue($customField->getDefaultValue() ?? ''); + $this->entityManager->persist($value); + } + + $this->entityManager->flush(); + + $values = $this->customFieldValueRepository->findBy(['machine' => $machine]); + + return $this->json([ + 'success' => true, + 'machineId' => $machine->getId(), + 'customFieldValues' => array_map( + static fn (CustomFieldValue $value) => [ + 'id' => $value->getId(), + 'value' => $value->getValue(), + 'customFieldId' => $value->getCustomField()->getId(), + ], + $values + ), + ]); + } +} diff --git a/src/Controller/MachineSkeletonController.php b/src/Controller/MachineSkeletonController.php new file mode 100644 index 0000000..a85d55a --- /dev/null +++ b/src/Controller/MachineSkeletonController.php @@ -0,0 +1,657 @@ +machineRepository->find($id); + if (!$machine instanceof Machine) { + return $this->json(['success' => false, 'error' => 'Machine not found.'], 404); + } + + $payload = json_decode($request->getContent(), true); + if (!is_array($payload)) { + return $this->json(['success' => false, 'error' => 'Invalid JSON payload.'], 400); + } + + $componentLinksPayload = $this->normalizePayloadList($payload['componentLinks'] ?? []); + $pieceLinksPayload = $this->normalizePayloadList($payload['pieceLinks'] ?? []); + $productLinksPayload = $this->normalizePayloadList($payload['productLinks'] ?? []); + + $componentLinks = $this->applyComponentLinks($machine, $componentLinksPayload); + if ($componentLinks instanceof JsonResponse) { + return $componentLinks; + } + + $pieceLinks = $this->applyPieceLinks($machine, $pieceLinksPayload, $componentLinks); + if ($pieceLinks instanceof JsonResponse) { + return $pieceLinks; + } + + $productLinks = $this->applyProductLinks($machine, $productLinksPayload, $componentLinks, $pieceLinks); + if ($productLinks instanceof JsonResponse) { + return $productLinks; + } + + $this->entityManager->flush(); + + return $this->json($this->normalizeMachineSkeletonResponse( + $machine, + $componentLinks, + $pieceLinks, + $productLinks + )); + } + + private function normalizePayloadList(mixed $value): array + { + if (!is_array($value)) { + return []; + } + return array_values(array_filter($value, static fn ($item) => is_array($item))); + } + + private function applyComponentLinks(Machine $machine, array $payload): array|JsonResponse + { + $existing = $this->indexLinksById($this->machineComponentLinkRepository->findBy(['machine' => $machine])); + $keepIds = []; + $pendingParents = []; + $links = []; + + foreach ($payload as $entry) { + $linkId = $this->resolveIdentifier($entry, ['id', 'linkId']); + $link = $linkId && isset($existing[$linkId]) ? $existing[$linkId] : new MachineComponentLink(); + if (!$linkId) { + $linkId = $this->generateCuid(); + } + if (!$link->getId()) { + $link->setId($linkId); + } + + $composantId = $this->resolveIdentifier($entry, ['composantId', 'componentId', 'idComposant']); + if (!$composantId) { + return $this->json(['success' => false, 'error' => 'Composant requis pour le squelette.'], 400); + } + $composant = $this->composantRepository->find($composantId); + if (!$composant instanceof Composant) { + return $this->json(['success' => false, 'error' => 'Composant introuvable.'], 404); + } + + $link->setMachine($machine); + $link->setComposant($composant); + + $requirementId = $this->resolveIdentifier($entry, ['requirementId', 'typeMachineComponentRequirementId']); + if ($requirementId) { + $requirement = $this->componentRequirementRepository->find($requirementId); + if ($requirement instanceof TypeMachineComponentRequirement) { + $link->setTypeMachineComponentRequirement($requirement); + } + } + + $this->applyOverrides($link, $entry['overrides'] ?? null); + + $pendingParents[$linkId] = $this->resolveIdentifier($entry, [ + 'parentComponentLinkId', + 'parentLinkId', + 'parentMachineComponentLinkId', + ]); + + $this->entityManager->persist($link); + $links[$linkId] = $link; + $keepIds[] = $linkId; + } + + foreach ($pendingParents as $linkId => $parentId) { + if (!$parentId) { + continue; + } + if (!isset($links[$linkId])) { + continue; + } + $parent = $links[$parentId] ?? $existing[$parentId] ?? null; + if ($parent instanceof MachineComponentLink) { + $links[$linkId]->setParentLink($parent); + } + } + + $this->removeMissingLinks($existing, $keepIds); + + return array_values($links); + } + + private function applyPieceLinks(Machine $machine, array $payload, array $componentLinks): array|JsonResponse + { + $existing = $this->indexLinksById($this->machinePieceLinkRepository->findBy(['machine' => $machine])); + $componentIndex = $this->indexLinksById($componentLinks); + $keepIds = []; + $pendingParents = []; + $links = []; + + foreach ($payload as $entry) { + $linkId = $this->resolveIdentifier($entry, ['id', 'linkId']); + $link = $linkId && isset($existing[$linkId]) ? $existing[$linkId] : new MachinePieceLink(); + if (!$linkId) { + $linkId = $this->generateCuid(); + } + if (!$link->getId()) { + $link->setId($linkId); + } + + $pieceId = $this->resolveIdentifier($entry, ['pieceId']); + if (!$pieceId) { + return $this->json(['success' => false, 'error' => 'Pièce requise pour le squelette.'], 400); + } + $piece = $this->pieceRepository->find($pieceId); + if (!$piece instanceof Piece) { + return $this->json(['success' => false, 'error' => 'Pièce introuvable.'], 404); + } + + $link->setMachine($machine); + $link->setPiece($piece); + + $requirementId = $this->resolveIdentifier($entry, ['requirementId', 'typeMachinePieceRequirementId']); + if ($requirementId) { + $requirement = $this->pieceRequirementRepository->find($requirementId); + if ($requirement instanceof TypeMachinePieceRequirement) { + $link->setTypeMachinePieceRequirement($requirement); + } + } + + $this->applyOverrides($link, $entry['overrides'] ?? null); + + $pendingParents[$linkId] = $this->resolveIdentifier($entry, [ + 'parentComponentLinkId', + 'parentLinkId', + 'parentMachineComponentLinkId', + ]); + + $this->entityManager->persist($link); + $links[$linkId] = $link; + $keepIds[] = $linkId; + } + + foreach ($pendingParents as $linkId => $parentId) { + if (!$parentId) { + continue; + } + if (!isset($links[$linkId])) { + continue; + } + $parent = $componentIndex[$parentId] ?? null; + if ($parent instanceof MachineComponentLink) { + $links[$linkId]->setParentLink($parent); + } + } + + $this->removeMissingLinks($existing, $keepIds); + + return array_values($links); + } + + private function applyProductLinks( + Machine $machine, + array $payload, + array $componentLinks, + array $pieceLinks, + ): array|JsonResponse { + $existing = $this->indexLinksById($this->machineProductLinkRepository->findBy(['machine' => $machine])); + $componentIndex = $this->indexLinksById($componentLinks); + $pieceIndex = $this->indexLinksById($pieceLinks); + $keepIds = []; + $pendingParents = []; + $links = []; + + foreach ($payload as $entry) { + $linkId = $this->resolveIdentifier($entry, ['id', 'linkId']); + $link = $linkId && isset($existing[$linkId]) ? $existing[$linkId] : new MachineProductLink(); + if (!$linkId) { + $linkId = $this->generateCuid(); + } + if (!$link->getId()) { + $link->setId($linkId); + } + + $productId = $this->resolveIdentifier($entry, ['productId']); + if (!$productId) { + return $this->json(['success' => false, 'error' => 'Produit requis pour le squelette.'], 400); + } + $product = $this->productRepository->find($productId); + if (!$product instanceof Product) { + return $this->json(['success' => false, 'error' => 'Produit introuvable.'], 404); + } + + $link->setMachine($machine); + $link->setProduct($product); + + $requirementId = $this->resolveIdentifier($entry, ['requirementId', 'typeMachineProductRequirementId']); + if ($requirementId) { + $requirement = $this->productRequirementRepository->find($requirementId); + if ($requirement instanceof TypeMachineProductRequirement) { + $link->setTypeMachineProductRequirement($requirement); + } + } + + $pendingParents[$linkId] = [ + 'parentComponentLinkId' => $this->resolveIdentifier($entry, ['parentComponentLinkId']), + 'parentPieceLinkId' => $this->resolveIdentifier($entry, ['parentPieceLinkId']), + 'parentLinkId' => $this->resolveIdentifier($entry, ['parentLinkId']), + ]; + + $this->entityManager->persist($link); + $links[$linkId] = $link; + $keepIds[] = $linkId; + } + + foreach ($pendingParents as $linkId => $parentIds) { + if (!isset($links[$linkId])) { + continue; + } + if (!empty($parentIds['parentComponentLinkId']) && isset($componentIndex[$parentIds['parentComponentLinkId']])) { + $links[$linkId]->setParentComponentLink($componentIndex[$parentIds['parentComponentLinkId']]); + } + if (!empty($parentIds['parentPieceLinkId']) && isset($pieceIndex[$parentIds['parentPieceLinkId']])) { + $links[$linkId]->setParentPieceLink($pieceIndex[$parentIds['parentPieceLinkId']]); + } + if (!empty($parentIds['parentLinkId']) && isset($links[$parentIds['parentLinkId']])) { + $links[$linkId]->setParentLink($links[$parentIds['parentLinkId']]); + } + } + + $this->removeMissingLinks($existing, $keepIds); + + return array_values($links); + } + + private function normalizeMachineSkeletonResponse( + Machine $machine, + array $componentLinks, + array $pieceLinks, + array $productLinks, + ): array { + $normalizedComponentLinks = $this->normalizeComponentLinks($componentLinks); + $componentIndex = $this->indexNormalizedLinks($normalizedComponentLinks); + $normalizedPieceLinks = $this->normalizePieceLinks($pieceLinks); + + foreach ($normalizedComponentLinks as &$link) { + $parentId = $link['parentComponentLinkId'] ?? null; + if ($parentId && isset($componentIndex[$parentId])) { + $componentIndex[$parentId]['childLinks'][] = $link; + } + } + unset($link); + + foreach ($normalizedPieceLinks as $pieceLink) { + $parentId = $pieceLink['parentComponentLinkId'] ?? null; + if ($parentId && isset($componentIndex[$parentId])) { + $componentIndex[$parentId]['pieceLinks'][] = $pieceLink; + } + } + + return [ + 'machine' => $this->normalizeMachine($machine), + 'componentLinks' => array_values($componentIndex), + 'pieceLinks' => $normalizedPieceLinks, + 'productLinks' => $this->normalizeProductLinks($productLinks), + ]; + } + + private function normalizeMachine(Machine $machine): array + { + return [ + 'id' => $machine->getId(), + 'name' => $machine->getName(), + 'reference' => $machine->getReference(), + 'prix' => $machine->getPrix(), + 'siteId' => $machine->getSite()->getId(), + 'typeMachineId' => $machine->getTypeMachine()?->getId(), + 'documents' => null, + 'customFieldValues' => null, + ]; + } + + private function normalizeComponentLinks(array $links): array + { + return array_map(function (MachineComponentLink $link): array { + $composant = $link->getComposant(); + $requirement = $link->getTypeMachineComponentRequirement(); + $parentLink = $link->getParentLink(); + $parentRequirementId = $parentLink?->getTypeMachineComponentRequirement()?->getId(); + + return [ + 'id' => $link->getId(), + 'linkId' => $link->getId(), + 'machineId' => $link->getMachine()->getId(), + 'composantId' => $composant->getId(), + 'composant' => $this->normalizeComposant($composant), + 'typeMachineComponentRequirementId' => $requirement?->getId(), + 'typeMachineComponentRequirement' => $requirement ? $this->normalizeComponentRequirement($requirement) : null, + 'parentLinkId' => $parentLink?->getId(), + 'parentComponentLinkId' => $parentLink?->getId(), + 'parentComponentId' => $parentLink?->getComposant()->getId(), + 'parentMachineComponentRequirementId' => $parentRequirementId, + 'overrides' => $this->normalizeOverrides($link), + 'childLinks' => [], + 'pieceLinks' => [], + ]; + }, $links); + } + + private function normalizePieceLinks(array $links): array + { + return array_map(function (MachinePieceLink $link): array { + $piece = $link->getPiece(); + $requirement = $link->getTypeMachinePieceRequirement(); + $parentLink = $link->getParentLink(); + $parentRequirementId = $parentLink?->getTypeMachineComponentRequirement()?->getId(); + + return [ + 'id' => $link->getId(), + 'linkId' => $link->getId(), + 'machineId' => $link->getMachine()->getId(), + 'pieceId' => $piece->getId(), + 'piece' => $this->normalizePiece($piece), + 'typeMachinePieceRequirementId' => $requirement?->getId(), + 'typeMachinePieceRequirement' => $requirement ? $this->normalizePieceRequirement($requirement) : null, + 'parentLinkId' => $parentLink?->getId(), + 'parentComponentLinkId' => $parentLink?->getId(), + 'parentComponentId' => $parentLink?->getComposant()->getId(), + 'parentMachineComponentRequirementId' => $parentRequirementId, + 'overrides' => $this->normalizeOverrides($link), + ]; + }, $links); + } + + private function normalizeProductLinks(array $links): array + { + return array_map(function (MachineProductLink $link): array { + $product = $link->getProduct(); + $requirement = $link->getTypeMachineProductRequirement(); + + return [ + 'id' => $link->getId(), + 'linkId' => $link->getId(), + 'machineId' => $link->getMachine()->getId(), + 'productId' => $product->getId(), + 'product' => $this->normalizeProduct($product), + 'typeMachineProductRequirementId' => $requirement?->getId(), + 'typeMachineProductRequirement' => $requirement ? $this->normalizeProductRequirement($requirement) : null, + 'parentLinkId' => $link->getParentLink()?->getId(), + 'parentComponentLinkId' => $link->getParentComponentLink()?->getId(), + 'parentPieceLinkId' => $link->getParentPieceLink()?->getId(), + ]; + }, $links); + } + + private function normalizeComposant(Composant $composant): array + { + return [ + 'id' => $composant->getId(), + 'name' => $composant->getName(), + 'reference' => $composant->getReference(), + 'prix' => $composant->getPrix(), + 'typeComposantId' => $composant->getTypeComposant()?->getId(), + 'typeComposant' => $this->normalizeModelType($composant->getTypeComposant()), + 'productId' => $composant->getProduct()?->getId(), + 'product' => $composant->getProduct() ? $this->normalizeProduct($composant->getProduct()) : null, + 'constructeurs' => $this->normalizeConstructeurs($composant->getConstructeurs()), + 'documents' => [], + 'customFields' => [], + ]; + } + + private function normalizePiece(Piece $piece): array + { + return [ + 'id' => $piece->getId(), + 'name' => $piece->getName(), + 'reference' => $piece->getReference(), + 'prix' => $piece->getPrix(), + 'typePieceId' => $piece->getTypePiece()?->getId(), + 'typePiece' => $this->normalizeModelType($piece->getTypePiece()), + 'productId' => $piece->getProduct()?->getId(), + 'product' => $piece->getProduct() ? $this->normalizeProduct($piece->getProduct()) : null, + 'constructeurs' => $this->normalizeConstructeurs($piece->getConstructeurs()), + 'documents' => [], + 'customFields' => [], + ]; + } + + private function normalizeProduct(Product $product): array + { + return [ + 'id' => $product->getId(), + 'name' => $product->getName(), + 'reference' => $product->getReference(), + 'supplierPrice' => $product->getSupplierPrice(), + 'typeProductId' => $product->getTypeProduct()?->getId(), + 'typeProduct' => $this->normalizeModelType($product->getTypeProduct()), + 'constructeurs' => $this->normalizeConstructeurs($product->getConstructeurs()), + 'documents' => [], + 'customFields' => [], + ]; + } + + private function normalizeModelType(?ModelType $type): ?array + { + if (!$type instanceof ModelType) { + return null; + } + + return [ + 'id' => $type->getId(), + 'name' => $type->getName(), + 'code' => $type->getCode(), + 'category' => $type->getCategory()->value, + ]; + } + + private function normalizeComponentRequirement(TypeMachineComponentRequirement $requirement): array + { + return [ + 'id' => $requirement->getId(), + 'label' => $requirement->getLabel(), + 'minCount' => $requirement->getMinCount(), + 'maxCount' => $requirement->getMaxCount(), + 'required' => $requirement->isRequired(), + 'typeComposantId' => $requirement->getTypeComposant()->getId(), + 'typeComposant' => $this->normalizeModelType($requirement->getTypeComposant()), + ]; + } + + private function normalizePieceRequirement(TypeMachinePieceRequirement $requirement): array + { + return [ + 'id' => $requirement->getId(), + 'label' => $requirement->getLabel(), + 'minCount' => $requirement->getMinCount(), + 'maxCount' => $requirement->getMaxCount(), + 'required' => $requirement->isRequired(), + 'typePieceId' => $requirement->getTypePiece()->getId(), + 'typePiece' => $this->normalizeModelType($requirement->getTypePiece()), + ]; + } + + private function normalizeProductRequirement(TypeMachineProductRequirement $requirement): array + { + return [ + 'id' => $requirement->getId(), + 'label' => $requirement->getLabel(), + 'minCount' => $requirement->getMinCount(), + 'maxCount' => $requirement->getMaxCount(), + 'required' => $requirement->isRequired(), + 'typeProductId' => $requirement->getTypeProduct()->getId(), + 'typeProduct' => $this->normalizeModelType($requirement->getTypeProduct()), + ]; + } + + private function normalizeConstructeurs(Collection $constructeurs): array + { + $items = []; + foreach ($constructeurs as $constructeur) { + $items[] = [ + 'id' => $constructeur->getId(), + 'name' => $constructeur->getName(), + 'email' => $constructeur->getEmail(), + 'phone' => $constructeur->getPhone(), + ]; + } + + return $items; + } + + private function normalizeOverrides(object $link): ?array + { + $name = method_exists($link, 'getNameOverride') ? $link->getNameOverride() : null; + $reference = method_exists($link, 'getReferenceOverride') ? $link->getReferenceOverride() : null; + $prix = method_exists($link, 'getPrixOverride') ? $link->getPrixOverride() : null; + + if ($name === null && $reference === null && $prix === null) { + return null; + } + + return [ + 'name' => $name, + 'reference' => $reference, + 'prix' => $prix, + ]; + } + + private function applyOverrides(object $link, mixed $overrides): void + { + if (!is_array($overrides)) { + return; + } + + if (array_key_exists('name', $overrides) && method_exists($link, 'setNameOverride')) { + $link->setNameOverride($this->stringOrNull($overrides['name'])); + } + if (array_key_exists('reference', $overrides) && method_exists($link, 'setReferenceOverride')) { + $link->setReferenceOverride($this->stringOrNull($overrides['reference'])); + } + if (array_key_exists('prix', $overrides) && method_exists($link, 'setPrixOverride')) { + $link->setPrixOverride($this->stringOrNull($overrides['prix'])); + } + } + + private function stringOrNull(mixed $value): ?string + { + if ($value === null) { + return null; + } + $string = trim((string) $value); + + return $string === '' ? null : $string; + } + + private function resolveIdentifier(array $entry, array $keys): ?string + { + foreach ($keys as $key) { + if (!array_key_exists($key, $entry)) { + continue; + } + $value = $entry[$key]; + if ($value === null || $value === '') { + continue; + } + return (string) $value; + } + + return null; + } + + /** + * @param array $links + * @return array + */ + private function indexLinksById(array $links): array + { + $indexed = []; + foreach ($links as $link) { + if (method_exists($link, 'getId') && $link->getId()) { + $indexed[$link->getId()] = $link; + } + } + + return $indexed; + } + + private function indexNormalizedLinks(array $links): array + { + $indexed = []; + foreach ($links as $link) { + if (is_array($link) && isset($link['id'])) { + $indexed[$link['id']] = $link; + } + } + + return $indexed; + } + + private function removeMissingLinks(array $existing, array $keepIds): void + { + $keep = array_flip($keepIds); + foreach ($existing as $link) { + if (!method_exists($link, 'getId')) { + continue; + } + $id = $link->getId(); + if ($id && !isset($keep[$id])) { + $this->entityManager->remove($link); + } + } + } + + private function generateCuid(): string + { + return 'cl' . bin2hex(random_bytes(12)); + } +} diff --git a/src/Controller/SessionProfileController.php b/src/Controller/SessionProfileController.php new file mode 100644 index 0000000..5ff3600 --- /dev/null +++ b/src/Controller/SessionProfileController.php @@ -0,0 +1,90 @@ +getSession(); + if (!$session instanceof SessionInterface) { + return new JsonResponse(['message' => 'Session indisponible.'], JsonResponse::HTTP_INTERNAL_SERVER_ERROR); + } + + $profileId = $session->get('profileId'); + if (!$profileId) { + return new JsonResponse(['message' => 'Aucun profil actif.'], JsonResponse::HTTP_UNAUTHORIZED); + } + + $profile = $this->profiles->find($profileId); + if (!$profile || !$profile->isActive()) { + $session->remove('profileId'); + return new JsonResponse(['message' => 'Profil introuvable ou inactif.'], JsonResponse::HTTP_UNAUTHORIZED); + } + + return new JsonResponse([ + 'id' => $profile->getId(), + 'firstName' => $profile->getFirstName(), + 'lastName' => $profile->getLastName(), + 'email' => $profile->getEmail(), + 'isActive' => $profile->isActive(), + 'roles' => $profile->getRoles(), + ]); + } + + #[Route('/api/session/profile', name: 'api_session_profile_post', methods: ['POST'])] + public function activateProfile(Request $request): JsonResponse + { + $session = $request->getSession(); + if (!$session instanceof SessionInterface) { + return new JsonResponse(['message' => 'Session indisponible.'], JsonResponse::HTTP_INTERNAL_SERVER_ERROR); + } + + $payload = $request->toArray(); + $profileId = $payload['profileId'] ?? null; + + if (!$profileId) { + return new JsonResponse(['message' => 'profileId est requis.'], JsonResponse::HTTP_BAD_REQUEST); + } + + $profile = $this->profiles->find($profileId); + if (!$profile || !$profile->isActive()) { + return new JsonResponse(['message' => 'Profil introuvable ou inactif.'], JsonResponse::HTTP_UNAUTHORIZED); + } + + $session->set('profileId', $profile->getId()); + + return new JsonResponse([ + 'id' => $profile->getId(), + 'firstName' => $profile->getFirstName(), + 'lastName' => $profile->getLastName(), + 'email' => $profile->getEmail(), + 'isActive' => $profile->isActive(), + 'roles' => $profile->getRoles(), + ]); + } + + #[Route('/api/session/profile', name: 'api_session_profile_delete', methods: ['DELETE'])] + public function logout(Request $request): JsonResponse + { + $session = $request->getSession(); + if ($session instanceof SessionInterface) { + $session->invalidate(); + } + + return new JsonResponse(['success' => true]); + } +} diff --git a/src/Controller/SessionProfilesController.php b/src/Controller/SessionProfilesController.php new file mode 100644 index 0000000..5141ecb --- /dev/null +++ b/src/Controller/SessionProfilesController.php @@ -0,0 +1,80 @@ +profiles->createQueryBuilder('p') + ->andWhere('p.isActive = :active') + ->setParameter('active', true) + ->orderBy('p.firstName', 'ASC') + ->getQuery() + ->getResult(); + + return new JsonResponse(array_map([$this, 'serializeProfile'], $items)); + } + + #[Route('/api/session/profiles', name: 'api_session_profiles_create', methods: ['POST'])] + public function create(Request $request): JsonResponse + { + $payload = $request->toArray(); + $firstName = trim((string) ($payload['firstName'] ?? '')); + $lastName = trim((string) ($payload['lastName'] ?? '')); + + if ($firstName === '' || $lastName === '') { + return new JsonResponse(['message' => 'firstName et lastName sont requis.'], JsonResponse::HTTP_BAD_REQUEST); + } + + $profile = new Profile(); + $profile->setFirstName($firstName); + $profile->setLastName($lastName); + $profile->setIsActive(true); + + $this->entityManager->persist($profile); + $this->entityManager->flush(); + + return new JsonResponse($this->serializeProfile($profile), JsonResponse::HTTP_CREATED); + } + + #[Route('/api/session/profiles/{id}', name: 'api_session_profiles_delete', methods: ['DELETE'])] + public function delete(string $id): JsonResponse + { + $profile = $this->profiles->find($id); + if (!$profile) { + return new JsonResponse(['message' => 'Profil introuvable.'], JsonResponse::HTTP_NOT_FOUND); + } + + $profile->setIsActive(false); + $this->entityManager->flush(); + + return new JsonResponse(['success' => true]); + } + + private function serializeProfile(Profile $profile): array + { + return [ + 'id' => $profile->getId(), + 'firstName' => $profile->getFirstName(), + 'lastName' => $profile->getLastName(), + 'isActive' => $profile->isActive(), + ]; + } +}