refactor(api) : remove TypeMachine skeleton system, fix ModelType serialization

- Remove TypeMachine, TypeMachineComponentRequirement, TypeMachinePieceRequirement,
  TypeMachineProductRequirement entities and related repositories/state processor
- Replace MachineSkeletonController with MachineStructureController
- Link CustomField directly to Machine instead of TypeMachine
- Add migration to drop TypeMachine tables and migrate custom fields to machines
- Fix ModelType serialization: Annotation\Groups → Attribute\Groups (Symfony 8 compat)
  and add product:read, composant:read, piece:read groups for embedded category display
- Fix Profile: same Annotation → Attribute import
- Fix SearchFilter: partial → ipartial on Comment and Document
- Update frontend submodule (remove skeleton pages/components, simplify machine creation)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Matthieu
2026-03-05 17:26:16 +01:00
parent f2539099bc
commit 0e11f4ad2d
25 changed files with 571 additions and 1694 deletions

View File

@@ -1,5 +1,22 @@
# Changelog
## [1.8.1] - 2026-03-05
### Refactoring
- **Suppression du systeme TypeMachine (squelettes machines)** : les entites `TypeMachine`, `TypeMachineComponentRequirement`, `TypeMachinePieceRequirement`, `TypeMachineProductRequirement` sont supprimees. Les champs personnalises machines sont desormais lies directement a chaque machine (relation `CustomField → Machine`).
- **Suppression des pages squelettes machines** : pages `/machine-skeleton`, `/type/[id]`, `/type/edit/[id]` et tous les composants associes (`TypeEditForm`, `MachineSkeletonSummary`, `MachineCreatePreview`, selectors de requirements).
- **Simplification de la creation de machines** : plus besoin de selectionner un squelette, ajout direct de composants/pieces/produits.
### Corrections
- **Fix affichage categorie sur les pages edit** : les categories (produit, composant, piece) s'affichent correctement sur les pages d'edition au lieu de "Categorie inconnue". Cause : import `Serializer\Annotation\Groups` obsolete dans `ModelType` (remplace par `Attribute\Groups` pour Symfony 8) + groupes de serialisation manquants (`product:read`, `composant:read`, `piece:read`).
- Fix import `Serializer\Annotation\Groups``Attribute\Groups` dans `Profile`.
- Fix filtre `SearchFilter` : `partial``ipartial` sur `Comment.entityName` et `Document.name`/`Document.filename` pour recherche insensible a la casse.
### Migration requise
```bash
docker compose exec web php bin/console doctrine:migrations:migrate
```
## [1.8.0] - 2026-03-03
### Ajouts

View File

@@ -0,0 +1,158 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
final class Version20260304120000 extends AbstractMigration
{
public function getDescription(): string
{
return 'Remove TypeMachine skeleton system, link custom fields directly to machines';
}
public function up(Schema $schema): void
{
// 1. Drop requirement FK columns on link tables
$this->addSql('ALTER TABLE machine_component_links DROP COLUMN IF EXISTS typemachinecomponentrequirementid');
$this->addSql('ALTER TABLE machine_piece_links DROP COLUMN IF EXISTS typemachinepiecerequirementid');
$this->addSql('ALTER TABLE machine_product_links DROP COLUMN IF EXISTS typemachineproductrequirementid');
// 2. Add machineid column to custom_fields (new direct FK to machines)
$this->addSql('ALTER TABLE custom_fields ADD COLUMN IF NOT EXISTS machineid VARCHAR(36) DEFAULT NULL');
$this->addSql(<<<'SQL'
DO $$ BEGIN
IF NOT EXISTS (
SELECT 1 FROM information_schema.table_constraints
WHERE constraint_name = 'fk_custom_fields_machine' AND table_name = 'custom_fields'
) THEN
ALTER TABLE custom_fields ADD CONSTRAINT fk_custom_fields_machine
FOREIGN KEY (machineid) REFERENCES machines(id) ON DELETE CASCADE;
END IF;
END $$;
SQL);
// 3. Enable pgcrypto for gen_random_bytes (needed for CUID generation)
$this->addSql('CREATE EXTENSION IF NOT EXISTS pgcrypto');
// 4. Migrate existing custom fields: copy from TypeMachine to each linked Machine
$this->addSql(<<<'SQL'
INSERT INTO custom_fields (id, name, type, required, defaultvalue, options, orderindex, machineid, createdat, updatedat)
SELECT
'cl' || encode(gen_random_bytes(12), 'hex'),
cf.name, cf.type, cf.required, cf.defaultvalue, cf.options, cf.orderindex,
m.id,
NOW(), NOW()
FROM custom_fields cf
JOIN machines m ON m.typemachineid = cf.typemachineid
WHERE cf.typemachineid IS NOT NULL
AND NOT EXISTS (
SELECT 1 FROM custom_fields existing
WHERE existing.machineid = m.id AND existing.name = cf.name
)
SQL);
// 4. Delete original TypeMachine-linked custom fields (now migrated)
$this->addSql('DELETE FROM custom_fields WHERE typemachineid IS NOT NULL');
// 5. Drop typemachineid column from custom_fields
$this->addSql('ALTER TABLE custom_fields DROP COLUMN IF EXISTS typemachineid');
// 6. Drop typemachineid column from machines
$this->addSql('ALTER TABLE machines DROP COLUMN IF EXISTS typemachineid');
// 7. Drop requirement tables (order matters: these reference type_machines)
$this->addSql('DROP TABLE IF EXISTS type_machine_component_requirements');
$this->addSql('DROP TABLE IF EXISTS type_machine_piece_requirements');
$this->addSql('DROP TABLE IF EXISTS type_machine_product_requirements');
// 8. Drop type_machines table
$this->addSql('DROP TABLE IF EXISTS type_machines');
}
public function down(Schema $schema): void
{
// Recreate type_machines table
$this->addSql(<<<'SQL'
CREATE TABLE IF NOT EXISTS type_machines (
id VARCHAR(36) NOT NULL PRIMARY KEY,
name VARCHAR(255) NOT NULL UNIQUE,
description TEXT DEFAULT NULL,
category VARCHAR(255) DEFAULT NULL,
maintenancefrequency VARCHAR(255) DEFAULT NULL,
components JSON DEFAULT NULL,
criticalparts JSON DEFAULT NULL,
machinepieces JSON DEFAULT NULL,
specifications JSON DEFAULT NULL,
createdat TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL,
updatedat TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL
)
SQL);
// Recreate requirement tables
$this->addSql(<<<'SQL'
CREATE TABLE IF NOT EXISTS type_machine_component_requirements (
id VARCHAR(36) NOT NULL PRIMARY KEY,
typemachineid VARCHAR(36) NOT NULL REFERENCES type_machines(id) ON DELETE CASCADE,
typecomposantid VARCHAR(36) NOT NULL REFERENCES model_types(id),
label VARCHAR(255) DEFAULT NULL,
mincount INTEGER DEFAULT 1,
maxcount INTEGER DEFAULT NULL,
required BOOLEAN DEFAULT true,
allownewmodels BOOLEAN DEFAULT true,
orderindex INTEGER DEFAULT 0,
createdat TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL,
updatedat TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL
)
SQL);
$this->addSql(<<<'SQL'
CREATE TABLE IF NOT EXISTS type_machine_piece_requirements (
id VARCHAR(36) NOT NULL PRIMARY KEY,
typemachineid VARCHAR(36) NOT NULL REFERENCES type_machines(id) ON DELETE CASCADE,
typepieceid VARCHAR(36) NOT NULL REFERENCES model_types(id),
label VARCHAR(255) DEFAULT NULL,
mincount INTEGER DEFAULT 0,
maxcount INTEGER DEFAULT NULL,
required BOOLEAN DEFAULT false,
allownewmodels BOOLEAN DEFAULT true,
orderindex INTEGER DEFAULT 0,
createdat TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL,
updatedat TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL
)
SQL);
$this->addSql(<<<'SQL'
CREATE TABLE IF NOT EXISTS type_machine_product_requirements (
id VARCHAR(36) NOT NULL PRIMARY KEY,
typemachineid VARCHAR(36) NOT NULL REFERENCES type_machines(id) ON DELETE CASCADE,
typeproductid VARCHAR(36) NOT NULL REFERENCES model_types(id),
label VARCHAR(255) DEFAULT NULL,
mincount INTEGER DEFAULT 0,
maxcount INTEGER DEFAULT NULL,
required BOOLEAN DEFAULT false,
allownewmodels BOOLEAN DEFAULT true,
orderindex INTEGER DEFAULT 0,
createdat TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL,
updatedat TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL
)
SQL);
// Re-add typemachineid to machines
$this->addSql('ALTER TABLE machines ADD COLUMN IF NOT EXISTS typemachineid VARCHAR(36) DEFAULT NULL');
// Re-add typemachineid to custom_fields
$this->addSql('ALTER TABLE custom_fields ADD COLUMN IF NOT EXISTS typemachineid VARCHAR(36) DEFAULT NULL');
// Re-add requirement FK columns to link tables
$this->addSql('ALTER TABLE machine_component_links ADD COLUMN IF NOT EXISTS typemachinecomponentrequirementid VARCHAR(36) DEFAULT NULL');
$this->addSql('ALTER TABLE machine_piece_links ADD COLUMN IF NOT EXISTS typemachinepiecerequirementid VARCHAR(36) DEFAULT NULL');
$this->addSql('ALTER TABLE machine_product_links ADD COLUMN IF NOT EXISTS typemachineproductrequirementid VARCHAR(36) DEFAULT NULL');
// Drop machine FK on custom_fields
$this->addSql('ALTER TABLE custom_fields DROP COLUMN IF EXISTS machineid');
}
}

View File

@@ -33,12 +33,7 @@ class MachineCustomFieldsController extends AbstractController
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) {
foreach ($machine->getCustomFields() as $customField) {
if (!$customField instanceof CustomField) {
continue;
}

View File

@@ -6,6 +6,7 @@ namespace App\Controller;
use App\Entity\Composant;
use App\Entity\CustomField;
use App\Entity\CustomFieldValue;
use App\Entity\Machine;
use App\Entity\MachineComponentLink;
use App\Entity\MachinePieceLink;
@@ -13,9 +14,6 @@ use App\Entity\MachineProductLink;
use App\Entity\ModelType;
use App\Entity\Piece;
use App\Entity\Product;
use App\Entity\TypeMachineComponentRequirement;
use App\Entity\TypeMachinePieceRequirement;
use App\Entity\TypeMachineProductRequirement;
use App\Repository\ComposantRepository;
use App\Repository\MachineComponentLinkRepository;
use App\Repository\MachinePieceLinkRepository;
@@ -23,9 +21,6 @@ use App\Repository\MachineProductLinkRepository;
use App\Repository\MachineRepository;
use App\Repository\PieceRepository;
use App\Repository\ProductRepository;
use App\Repository\TypeMachineComponentRequirementRepository;
use App\Repository\TypeMachinePieceRequirementRepository;
use App\Repository\TypeMachineProductRequirementRepository;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
@@ -34,7 +29,7 @@ use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Attribute\Route;
#[Route('/api/machines')]
class MachineSkeletonController extends AbstractController
class MachineStructureController extends AbstractController
{
public function __construct(
private readonly EntityManagerInterface $entityManager,
@@ -45,13 +40,10 @@ class MachineSkeletonController extends AbstractController
private readonly ComposantRepository $composantRepository,
private readonly PieceRepository $pieceRepository,
private readonly ProductRepository $productRepository,
private readonly TypeMachineComponentRequirementRepository $componentRequirementRepository,
private readonly TypeMachinePieceRequirementRepository $pieceRequirementRepository,
private readonly TypeMachineProductRequirementRepository $productRequirementRepository,
) {}
#[Route('/{id}/skeleton', name: 'machine_skeleton_get', methods: ['GET'])]
public function getSkeleton(string $id): JsonResponse
#[Route('/{id}/structure', name: 'machine_structure_get', methods: ['GET'])]
public function getStructure(string $id): JsonResponse
{
$this->denyAccessUnlessGranted('ROLE_VIEWER');
@@ -64,7 +56,7 @@ class MachineSkeletonController extends AbstractController
$pieceLinks = $this->machinePieceLinkRepository->findBy(['machine' => $machine]);
$productLinks = $this->machineProductLinkRepository->findBy(['machine' => $machine]);
return $this->json($this->normalizeMachineSkeletonResponse(
return $this->json($this->normalizeStructureResponse(
$machine,
$componentLinks,
$pieceLinks,
@@ -72,8 +64,8 @@ class MachineSkeletonController extends AbstractController
));
}
#[Route('/{id}/skeleton', name: 'machine_skeleton_update', methods: ['PATCH'])]
public function updateSkeleton(string $id, Request $request): JsonResponse
#[Route('/{id}/structure', name: 'machine_structure_update', methods: ['PATCH'])]
public function updateStructure(string $id, Request $request): JsonResponse
{
$this->denyAccessUnlessGranted('ROLE_GESTIONNAIRE');
@@ -108,7 +100,7 @@ class MachineSkeletonController extends AbstractController
$this->entityManager->flush();
return $this->json($this->normalizeMachineSkeletonResponse(
return $this->json($this->normalizeStructureResponse(
$machine,
$componentLinks,
$pieceLinks,
@@ -116,6 +108,194 @@ class MachineSkeletonController extends AbstractController
));
}
#[Route('/{id}/clone', name: 'machine_clone', methods: ['POST'])]
public function cloneMachine(string $id, Request $request): JsonResponse
{
$this->denyAccessUnlessGranted('ROLE_GESTIONNAIRE');
$source = $this->machineRepository->find($id);
if (!$source instanceof Machine) {
return $this->json(['success' => false, 'error' => 'Machine source introuvable.'], 404);
}
$payload = json_decode($request->getContent(), true);
if (!is_array($payload) || empty($payload['name']) || empty($payload['siteId'])) {
return $this->json(['success' => false, 'error' => 'name et siteId sont requis.'], 400);
}
$site = $this->entityManager->getRepository(\App\Entity\Site::class)->find($payload['siteId']);
if (!$site) {
return $this->json(['success' => false, 'error' => 'Site introuvable.'], 404);
}
// Create new machine
$newMachine = new Machine();
$newMachine->setName($payload['name']);
$newMachine->setSite($site);
if (!empty($payload['reference'])) {
$newMachine->setReference($payload['reference']);
}
$newMachine->setPrix($source->getPrix());
// Copy constructeurs
foreach ($source->getConstructeurs() as $constructeur) {
$newMachine->getConstructeurs()->add($constructeur);
}
$this->entityManager->persist($newMachine);
// Copy custom fields and values
$this->cloneCustomFields($source, $newMachine);
// Copy component links (preserving hierarchy)
$componentLinkMap = $this->cloneComponentLinks($source, $newMachine);
// Copy piece links
$pieceLinkMap = $this->clonePieceLinks($source, $newMachine, $componentLinkMap);
// Copy product links
$this->cloneProductLinks($source, $newMachine, $componentLinkMap, $pieceLinkMap);
$this->entityManager->flush();
$componentLinks = $this->machineComponentLinkRepository->findBy(['machine' => $newMachine]);
$pieceLinks = $this->machinePieceLinkRepository->findBy(['machine' => $newMachine]);
$productLinks = $this->machineProductLinkRepository->findBy(['machine' => $newMachine]);
return $this->json($this->normalizeStructureResponse(
$newMachine,
$componentLinks,
$pieceLinks,
$productLinks
), 201);
}
private function cloneCustomFields(Machine $source, Machine $target): void
{
foreach ($source->getCustomFields() as $cf) {
$newCf = new CustomField();
$newCf->setName($cf->getName());
$newCf->setType($cf->getType());
$newCf->setRequired($cf->isRequired());
$newCf->setDefaultValue($cf->getDefaultValue());
$newCf->setOptions($cf->getOptions());
$newCf->setOrderIndex($cf->getOrderIndex());
$newCf->setMachine($target);
$this->entityManager->persist($newCf);
}
foreach ($source->getCustomFieldValues() as $cfv) {
$newValue = new CustomFieldValue();
$newValue->setMachine($target);
$newValue->setCustomField($cfv->getCustomField());
$newValue->setValue($cfv->getValue());
$this->entityManager->persist($newValue);
}
}
/**
* @return array<string, MachineComponentLink> Map of old link ID new link
*/
private function cloneComponentLinks(Machine $source, Machine $target): array
{
$sourceLinks = $this->machineComponentLinkRepository->findBy(['machine' => $source]);
$linkMap = [];
// First pass: create all links without parent relationships
foreach ($sourceLinks as $link) {
$newLink = new MachineComponentLink();
$newLink->setMachine($target);
$newLink->setComposant($link->getComposant());
$newLink->setNameOverride($link->getNameOverride());
$newLink->setReferenceOverride($link->getReferenceOverride());
$newLink->setPrixOverride($link->getPrixOverride());
$this->entityManager->persist($newLink);
$linkMap[$link->getId()] = $newLink;
}
// Second pass: set parent relationships
foreach ($sourceLinks as $link) {
$parent = $link->getParentLink();
if ($parent && isset($linkMap[$parent->getId()])) {
$linkMap[$link->getId()]->setParentLink($linkMap[$parent->getId()]);
}
}
return $linkMap;
}
/**
* @param array<string, MachineComponentLink> $componentLinkMap
*
* @return array<string, MachinePieceLink> Map of old link ID new link
*/
private function clonePieceLinks(Machine $source, Machine $target, array $componentLinkMap): array
{
$sourceLinks = $this->machinePieceLinkRepository->findBy(['machine' => $source]);
$linkMap = [];
foreach ($sourceLinks as $link) {
$newLink = new MachinePieceLink();
$newLink->setMachine($target);
$newLink->setPiece($link->getPiece());
$newLink->setNameOverride($link->getNameOverride());
$newLink->setReferenceOverride($link->getReferenceOverride());
$newLink->setPrixOverride($link->getPrixOverride());
$parent = $link->getParentLink();
if ($parent && isset($componentLinkMap[$parent->getId()])) {
$newLink->setParentLink($componentLinkMap[$parent->getId()]);
}
$this->entityManager->persist($newLink);
$linkMap[$link->getId()] = $newLink;
}
return $linkMap;
}
/**
* @param array<string, MachineComponentLink> $componentLinkMap
* @param array<string, MachinePieceLink> $pieceLinkMap
*/
private function cloneProductLinks(
Machine $source,
Machine $target,
array $componentLinkMap,
array $pieceLinkMap,
): void {
$sourceLinks = $this->machineProductLinkRepository->findBy(['machine' => $source]);
$linkMap = [];
// First pass: create all links
foreach ($sourceLinks as $link) {
$newLink = new MachineProductLink();
$newLink->setMachine($target);
$newLink->setProduct($link->getProduct());
$parentComponent = $link->getParentComponentLink();
if ($parentComponent && isset($componentLinkMap[$parentComponent->getId()])) {
$newLink->setParentComponentLink($componentLinkMap[$parentComponent->getId()]);
}
$parentPiece = $link->getParentPieceLink();
if ($parentPiece && isset($pieceLinkMap[$parentPiece->getId()])) {
$newLink->setParentPieceLink($pieceLinkMap[$parentPiece->getId()]);
}
$this->entityManager->persist($newLink);
$linkMap[$link->getId()] = $newLink;
}
// Second pass: set parent product link relationships
foreach ($sourceLinks as $link) {
$parent = $link->getParentLink();
if ($parent && isset($linkMap[$parent->getId()])) {
$linkMap[$link->getId()]->setParentLink($linkMap[$parent->getId()]);
}
}
}
private function normalizePayloadList(mixed $value): array
{
if (!is_array($value)) {
@@ -144,7 +324,7 @@ class MachineSkeletonController extends AbstractController
$composantId = $this->resolveIdentifier($entry, ['composantId', 'componentId', 'idComposant']);
if (!$composantId) {
return $this->json(['success' => false, 'error' => 'Composant requis pour le squelette.'], 400);
return $this->json(['success' => false, 'error' => 'Composant requis.'], 400);
}
$composant = $this->composantRepository->find($composantId);
if (!$composant instanceof Composant) {
@@ -154,14 +334,6 @@ class MachineSkeletonController extends AbstractController
$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, [
@@ -176,10 +348,7 @@ class MachineSkeletonController extends AbstractController
}
foreach ($pendingParents as $linkId => $parentId) {
if (!$parentId) {
continue;
}
if (!isset($links[$linkId])) {
if (!$parentId || !isset($links[$linkId])) {
continue;
}
$parent = $links[$parentId] ?? $existing[$parentId] ?? null;
@@ -213,7 +382,7 @@ class MachineSkeletonController extends AbstractController
$pieceId = $this->resolveIdentifier($entry, ['pieceId']);
if (!$pieceId) {
return $this->json(['success' => false, 'error' => 'Pièce requise pour le squelette.'], 400);
return $this->json(['success' => false, 'error' => 'Pièce requise.'], 400);
}
$piece = $this->pieceRepository->find($pieceId);
if (!$piece instanceof Piece) {
@@ -223,14 +392,6 @@ class MachineSkeletonController extends AbstractController
$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, [
@@ -245,10 +406,7 @@ class MachineSkeletonController extends AbstractController
}
foreach ($pendingParents as $linkId => $parentId) {
if (!$parentId) {
continue;
}
if (!isset($links[$linkId])) {
if (!$parentId || !isset($links[$linkId])) {
continue;
}
$parent = $componentIndex[$parentId] ?? null;
@@ -287,7 +445,7 @@ class MachineSkeletonController extends AbstractController
$productId = $this->resolveIdentifier($entry, ['productId']);
if (!$productId) {
return $this->json(['success' => false, 'error' => 'Produit requis pour le squelette.'], 400);
return $this->json(['success' => false, 'error' => 'Produit requis.'], 400);
}
$product = $this->productRepository->find($productId);
if (!$product instanceof Product) {
@@ -297,14 +455,6 @@ class MachineSkeletonController extends AbstractController
$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']),
@@ -336,7 +486,7 @@ class MachineSkeletonController extends AbstractController
return array_values($links);
}
private function normalizeMachineSkeletonResponse(
private function normalizeStructureResponse(
Machine $machine,
array $componentLinks,
array $pieceLinks,
@@ -346,7 +496,6 @@ class MachineSkeletonController extends AbstractController
$componentIndex = $this->indexNormalizedLinks($normalizedComponentLinks);
$normalizedPieceLinks = $this->normalizePieceLinks($pieceLinks);
// Build component hierarchy track which IDs are children
$childIds = [];
foreach ($normalizedComponentLinks as $link) {
$parentId = $link['parentComponentLinkId'] ?? null;
@@ -356,10 +505,8 @@ class MachineSkeletonController extends AbstractController
}
}
// Add pieces to components recursively
$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']]),
@@ -382,7 +529,6 @@ class MachineSkeletonController extends AbstractController
}
}
// Recursively attach to child components
foreach ($componentIndex as &$component) {
if (!empty($component['childLinks'])) {
$this->attachPiecesToChildComponents($component['childLinks'], $pieceLinks);
@@ -403,7 +549,6 @@ class MachineSkeletonController extends AbstractController
}
}
// Recursively process nested children
if (!empty($child['childLinks'])) {
$this->attachPiecesToChildComponents($child['childLinks'], $pieceLinks);
}
@@ -412,8 +557,7 @@ class MachineSkeletonController extends AbstractController
private function normalizeMachine(Machine $machine): array
{
$site = $machine->getSite();
$typeMachine = $machine->getTypeMachine();
$site = $machine->getSite();
return [
'id' => $machine->getId(),
@@ -425,24 +569,8 @@ class MachineSkeletonController extends AbstractController
'id' => $site->getId(),
'name' => $site->getName(),
],
'typeMachineId' => $typeMachine?->getId(),
'typeMachine' => $typeMachine ? [
'id' => $typeMachine->getId(),
'name' => $typeMachine->getName(),
'category' => $typeMachine->getCategory(),
'description' => $typeMachine->getDescription(),
'customFields' => $this->normalizeCustomFields($typeMachine->getCustomFields()),
'componentRequirements' => $typeMachine->getComponentRequirements()
->map(fn (TypeMachineComponentRequirement $req) => $this->normalizeComponentRequirement($req))
->toArray(),
'pieceRequirements' => $typeMachine->getPieceRequirements()
->map(fn (TypeMachinePieceRequirement $req) => $this->normalizePieceRequirement($req))
->toArray(),
'productRequirements' => $typeMachine->getProductRequirements()
->map(fn (TypeMachineProductRequirement $req) => $this->normalizeProductRequirement($req))
->toArray(),
] : null,
'constructeurs' => $this->normalizeConstructeurs($machine->getConstructeurs()),
'customFields' => $this->normalizeCustomFields($machine->getCustomFields()),
'documents' => null,
'customFieldValues' => null,
];
@@ -472,26 +600,21 @@ class MachineSkeletonController extends AbstractController
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();
$composant = $link->getComposant();
$parentLink = $link->getParentLink();
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' => [],
'id' => $link->getId(),
'linkId' => $link->getId(),
'machineId' => $link->getMachine()->getId(),
'composantId' => $composant->getId(),
'composant' => $this->normalizeComposant($composant),
'parentLinkId' => $parentLink?->getId(),
'parentComponentLinkId' => $parentLink?->getId(),
'parentComponentId' => $parentLink?->getComposant()->getId(),
'overrides' => $this->normalizeOverrides($link),
'childLinks' => [],
'pieceLinks' => [],
];
}, $links);
}
@@ -499,24 +622,19 @@ class MachineSkeletonController extends AbstractController
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();
$piece = $link->getPiece();
$parentLink = $link->getParentLink();
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),
'id' => $link->getId(),
'linkId' => $link->getId(),
'machineId' => $link->getMachine()->getId(),
'pieceId' => $piece->getId(),
'piece' => $this->normalizePiece($piece),
'parentLinkId' => $parentLink?->getId(),
'parentComponentLinkId' => $parentLink?->getId(),
'parentComponentId' => $parentLink?->getComposant()->getId(),
'overrides' => $this->normalizeOverrides($link),
];
}, $links);
}
@@ -524,55 +642,58 @@ class MachineSkeletonController extends AbstractController
private function normalizeProductLinks(array $links): array
{
return array_map(function (MachineProductLink $link): array {
$product = $link->getProduct();
$requirement = $link->getTypeMachineProductRequirement();
$product = $link->getProduct();
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(),
'id' => $link->getId(),
'linkId' => $link->getId(),
'machineId' => $link->getMachine()->getId(),
'productId' => $product->getId(),
'product' => $this->normalizeProduct($product),
'parentLinkId' => $link->getParentLink()?->getId(),
'parentComponentLinkId' => $link->getParentComponentLink()?->getId(),
'parentPieceLinkId' => $link->getParentPieceLink()?->getId(),
];
}, $links);
}
private function normalizeComposant(Composant $composant): array
{
$type = $composant->getTypeComposant();
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' => [],
'id' => $composant->getId(),
'name' => $composant->getName(),
'reference' => $composant->getReference(),
'prix' => $composant->getPrix(),
'typeComposantId' => $type?->getId(),
'typeComposant' => $this->normalizeModelType($type),
'productId' => $composant->getProduct()?->getId(),
'product' => $composant->getProduct() ? $this->normalizeProduct($composant->getProduct()) : null,
'constructeurs' => $this->normalizeConstructeurs($composant->getConstructeurs()),
'documents' => [],
'customFields' => $type ? $this->normalizeCustomFieldDefinitions($type->getComponentCustomFields()) : [],
'customFieldValues' => $this->normalizeCustomFieldValues($composant->getCustomFieldValues()),
];
}
private function normalizePiece(Piece $piece): array
{
$type = $piece->getTypePiece();
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' => [],
'id' => $piece->getId(),
'name' => $piece->getName(),
'reference' => $piece->getReference(),
'prix' => $piece->getPrix(),
'typePieceId' => $type?->getId(),
'typePiece' => $this->normalizeModelType($type),
'productId' => $piece->getProduct()?->getId(),
'product' => $piece->getProduct() ? $this->normalizeProduct($piece->getProduct()) : null,
'constructeurs' => $this->normalizeConstructeurs($piece->getConstructeurs()),
'documents' => [],
'customFields' => $type ? $this->normalizeCustomFieldDefinitions($type->getPieceCustomFields()) : [],
'customFieldValues' => $this->normalizeCustomFieldValues($piece->getCustomFieldValues()),
];
}
@@ -598,49 +719,11 @@ class MachineSkeletonController extends AbstractController
}
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()),
'id' => $type->getId(),
'name' => $type->getName(),
'code' => $type->getCode(),
'category' => $type->getCategory()->value,
'structure' => $type->getStructure(),
];
}
@@ -659,6 +742,55 @@ class MachineSkeletonController extends AbstractController
return $items;
}
private function normalizeCustomFieldDefinitions(Collection $customFields): array
{
$items = [];
foreach ($customFields as $cf) {
if (!$cf instanceof CustomField) {
continue;
}
$items[] = [
'id' => $cf->getId(),
'name' => $cf->getName(),
'type' => $cf->getType(),
'required' => $cf->isRequired(),
'options' => $cf->getOptions(),
'defaultValue' => $cf->getDefaultValue(),
'orderIndex' => $cf->getOrderIndex(),
];
}
usort($items, static fn (array $a, array $b) => $a['orderIndex'] <=> $b['orderIndex']);
return $items;
}
private function normalizeCustomFieldValues(Collection $customFieldValues): array
{
$items = [];
foreach ($customFieldValues as $cfv) {
if (!$cfv instanceof CustomFieldValue) {
continue;
}
$cf = $cfv->getCustomField();
$items[] = [
'id' => $cfv->getId(),
'value' => $cfv->getValue(),
'customField' => [
'id' => $cf->getId(),
'name' => $cf->getName(),
'type' => $cf->getType(),
'required' => $cf->isRequired(),
'options' => $cf->getOptions(),
'defaultValue' => $cf->getDefaultValue(),
'orderIndex' => $cf->getOrderIndex(),
],
];
}
return $items;
}
private function normalizeOverrides(object $link): ?array
{
$name = method_exists($link, 'getNameOverride') ? $link->getNameOverride() : null;

View File

@@ -20,7 +20,7 @@ use Doctrine\ORM\Mapping as ORM;
#[ORM\Table(name: 'comments')]
#[ORM\Index(columns: ['entity_type', 'entity_id', 'status'], name: 'idx_comment_entity_status')]
#[ORM\HasLifecycleCallbacks]
#[ApiFilter(SearchFilter::class, properties: ['entityType' => 'exact', 'entityId' => 'exact', 'status' => 'exact', 'entityName' => 'partial'])]
#[ApiFilter(SearchFilter::class, properties: ['entityType' => 'exact', 'entityId' => 'exact', 'status' => 'exact', 'entityName' => 'ipartial'])]
#[ApiFilter(OrderFilter::class, properties: ['createdAt', 'authorName', 'status'])]
#[ApiResource(
operations: [

View File

@@ -62,9 +62,9 @@ class CustomField
#[Groups(['composant:read', 'piece:read', 'product:read', 'machine:read'])]
private int $orderIndex = 0;
#[ORM\ManyToOne(targetEntity: TypeMachine::class, inversedBy: 'customFields')]
#[ORM\JoinColumn(name: 'typeMachineId', referencedColumnName: 'id', nullable: true, onDelete: 'CASCADE')]
private ?TypeMachine $typeMachine = null;
#[ORM\ManyToOne(targetEntity: Machine::class, inversedBy: 'customFields')]
#[ORM\JoinColumn(name: 'machineId', referencedColumnName: 'id', nullable: true, onDelete: 'CASCADE')]
private ?Machine $machine = null;
#[ORM\ManyToOne(targetEntity: ModelType::class, inversedBy: 'customFields')]
#[ORM\JoinColumn(name: 'typeComposantId', referencedColumnName: 'id', nullable: true, onDelete: 'CASCADE')]
@@ -197,14 +197,14 @@ class CustomField
return $this;
}
public function getTypeMachine(): ?TypeMachine
public function getMachine(): ?Machine
{
return $this->typeMachine;
return $this->machine;
}
public function setTypeMachine(?TypeMachine $typeMachine): static
public function setMachine(?Machine $machine): static
{
$this->typeMachine = $typeMachine;
$this->machine = $machine;
return $this;
}

View File

@@ -24,7 +24,7 @@ use Symfony\Component\Serializer\Attribute\Groups;
#[ORM\Entity(repositoryClass: DocumentRepository::class)]
#[ORM\Table(name: 'documents')]
#[ORM\HasLifecycleCallbacks]
#[ApiFilter(SearchFilter::class, properties: ['name' => 'partial', 'filename' => 'partial'])]
#[ApiFilter(SearchFilter::class, properties: ['name' => 'ipartial', 'filename' => 'ipartial'])]
#[ApiFilter(ExistsFilter::class, properties: ['site', 'machine', 'composant', 'piece', 'product'])]
#[ApiFilter(OrderFilter::class, properties: ['createdAt', 'name', 'size'])]
#[ApiResource(

View File

@@ -53,10 +53,6 @@ class Machine
#[ORM\JoinColumn(name: 'siteId', referencedColumnName: 'id', nullable: false, onDelete: 'CASCADE')]
private Site $site;
#[ORM\ManyToOne(targetEntity: TypeMachine::class, inversedBy: 'machines')]
#[ORM\JoinColumn(name: 'typeMachineId', referencedColumnName: 'id', nullable: true)]
private ?TypeMachine $typeMachine = null;
/**
* @var Collection<int, Constructeur>
*/
@@ -92,6 +88,12 @@ class Machine
#[ORM\OneToMany(mappedBy: 'machine', targetEntity: Document::class)]
private Collection $documents;
/**
* @var Collection<int, CustomField>
*/
#[ORM\OneToMany(mappedBy: 'machine', targetEntity: CustomField::class, cascade: ['persist', 'remove'])]
private Collection $customFields;
/**
* @var Collection<int, CustomFieldValue>
*/
@@ -111,6 +113,7 @@ class Machine
$this->pieceLinks = new ArrayCollection();
$this->productLinks = new ArrayCollection();
$this->documents = new ArrayCollection();
$this->customFields = new ArrayCollection();
$this->customFieldValues = new ArrayCollection();
}
@@ -192,14 +195,31 @@ class Machine
return $this;
}
public function getTypeMachine(): ?TypeMachine
/**
* @return Collection<int, CustomField>
*/
public function getCustomFields(): Collection
{
return $this->typeMachine;
return $this->customFields;
}
public function setTypeMachine(?TypeMachine $typeMachine): static
public function addCustomField(CustomField $customField): static
{
$this->typeMachine = $typeMachine;
if (!$this->customFields->contains($customField)) {
$this->customFields->add($customField);
$customField->setMachine($this);
}
return $this;
}
public function removeCustomField(CustomField $customField): static
{
if ($this->customFields->removeElement($customField)) {
if ($customField->getMachine() === $this) {
$customField->setMachine(null);
}
}
return $this;
}

View File

@@ -55,10 +55,6 @@ class MachineComponentLink
#[ORM\OneToMany(mappedBy: 'parentLink', targetEntity: MachineComponentLink::class)]
private Collection $childLinks;
#[ORM\ManyToOne(targetEntity: TypeMachineComponentRequirement::class, inversedBy: 'machineComponentLinks')]
#[ORM\JoinColumn(name: 'typeMachineComponentRequirementId', referencedColumnName: 'id', nullable: true, onDelete: 'SET NULL')]
private ?TypeMachineComponentRequirement $typeMachineComponentRequirement = null;
/**
* @var Collection<int, MachinePieceLink>
*/
@@ -159,18 +155,6 @@ class MachineComponentLink
return $this;
}
public function getTypeMachineComponentRequirement(): ?TypeMachineComponentRequirement
{
return $this->typeMachineComponentRequirement;
}
public function setTypeMachineComponentRequirement(?TypeMachineComponentRequirement $requirement): static
{
$this->typeMachineComponentRequirement = $requirement;
return $this;
}
public function getNameOverride(): ?string
{
return $this->nameOverride;

View File

@@ -49,10 +49,6 @@ class MachinePieceLink
#[ORM\JoinColumn(name: 'parentLinkId', referencedColumnName: 'id', nullable: true, onDelete: 'CASCADE')]
private ?MachineComponentLink $parentLink = null;
#[ORM\ManyToOne(targetEntity: TypeMachinePieceRequirement::class, inversedBy: 'machinePieceLinks')]
#[ORM\JoinColumn(name: 'typeMachinePieceRequirementId', referencedColumnName: 'id', nullable: true, onDelete: 'SET NULL')]
private ?TypeMachinePieceRequirement $typeMachinePieceRequirement = null;
/**
* @var Collection<int, MachineProductLink>
*/
@@ -145,18 +141,6 @@ class MachinePieceLink
return $this;
}
public function getTypeMachinePieceRequirement(): ?TypeMachinePieceRequirement
{
return $this->typeMachinePieceRequirement;
}
public function setTypeMachinePieceRequirement(?TypeMachinePieceRequirement $requirement): static
{
$this->typeMachinePieceRequirement = $requirement;
return $this;
}
public function getNameOverride(): ?string
{
return $this->nameOverride;

View File

@@ -45,10 +45,6 @@ class MachineProductLink
#[ORM\JoinColumn(name: 'productId', referencedColumnName: 'id', nullable: false, onDelete: 'CASCADE')]
private Product $product;
#[ORM\ManyToOne(targetEntity: TypeMachineProductRequirement::class, inversedBy: 'machineProductLinks')]
#[ORM\JoinColumn(name: 'typeMachineProductRequirementId', referencedColumnName: 'id', nullable: true, onDelete: 'SET NULL')]
private ?TypeMachineProductRequirement $typeMachineProductRequirement = null;
#[ORM\ManyToOne(targetEntity: MachineProductLink::class, inversedBy: 'childLinks')]
#[ORM\JoinColumn(name: 'parentLinkId', referencedColumnName: 'id', nullable: true, onDelete: 'CASCADE')]
private ?MachineProductLink $parentLink = null;
@@ -132,18 +128,6 @@ class MachineProductLink
return $this;
}
public function getTypeMachineProductRequirement(): ?TypeMachineProductRequirement
{
return $this->typeMachineProductRequirement;
}
public function setTypeMachineProductRequirement(?TypeMachineProductRequirement $requirement): static
{
$this->typeMachineProductRequirement = $requirement;
return $this;
}
public function getParentLink(): ?MachineProductLink
{
return $this->parentLink;

View File

@@ -21,7 +21,7 @@ use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Annotation\Groups;
use Symfony\Component\Serializer\Attribute\Groups;
#[ORM\Entity(repositoryClass: ModelTypeRepository::class)]
#[ORM\Table(name: 'model_types')]
@@ -45,11 +45,11 @@ class ModelType
{
#[ORM\Id]
#[ORM\Column(type: Types::STRING, length: 36)]
#[Groups(['type_machine:read', 'model_type:read'])]
#[Groups(['type_machine:read', 'model_type:read', 'product:read', 'composant:read', 'piece:read'])]
private ?string $id = null;
#[ORM\Column(type: Types::STRING, length: 120)]
#[Groups(['type_machine:read', 'model_type:read', 'model_type:write'])]
#[Groups(['type_machine:read', 'model_type:read', 'model_type:write', 'product:read', 'composant:read', 'piece:read'])]
private string $name;
#[ORM\Column(type: Types::STRING, length: 60, unique: true)]
@@ -69,15 +69,15 @@ class ModelType
private ?string $description = null;
#[ORM\Column(type: Types::JSON, nullable: true, name: 'componentSkeleton')]
#[Groups(['model_type:read'])]
#[Groups(['model_type:read', 'composant:read'])]
private ?array $componentSkeleton = null;
#[ORM\Column(type: Types::JSON, nullable: true, name: 'pieceSkeleton')]
#[Groups(['model_type:read'])]
#[Groups(['model_type:read', 'piece:read'])]
private ?array $pieceSkeleton = null;
#[ORM\Column(type: Types::JSON, nullable: true, name: 'productSkeleton')]
#[Groups(['model_type:read'])]
#[Groups(['model_type:read', 'product:read'])]
private ?array $productSkeleton = null;
#[ORM\Column(type: Types::DATETIME_IMMUTABLE, name: 'createdAt')]
@@ -108,24 +108,6 @@ class ModelType
#[ORM\OneToMany(mappedBy: 'typeProduct', targetEntity: Product::class)]
private Collection $products;
/**
* @var Collection<int, TypeMachineComponentRequirement>
*/
#[ORM\OneToMany(mappedBy: 'typeComposant', targetEntity: TypeMachineComponentRequirement::class)]
private Collection $componentRequirements;
/**
* @var Collection<int, TypeMachinePieceRequirement>
*/
#[ORM\OneToMany(mappedBy: 'typePiece', targetEntity: TypeMachinePieceRequirement::class)]
private Collection $pieceRequirements;
/**
* @var Collection<int, TypeMachineProductRequirement>
*/
#[ORM\OneToMany(mappedBy: 'typeProduct', targetEntity: TypeMachineProductRequirement::class)]
private Collection $productRequirements;
/**
* @var Collection<int, CustomField>
*/
@@ -146,15 +128,12 @@ class ModelType
public function __construct()
{
$this->composants = new ArrayCollection();
$this->pieces = new ArrayCollection();
$this->products = new ArrayCollection();
$this->componentRequirements = new ArrayCollection();
$this->pieceRequirements = new ArrayCollection();
$this->productRequirements = new ArrayCollection();
$this->customFields = new ArrayCollection();
$this->pieceCustomFields = new ArrayCollection();
$this->productCustomFields = new ArrayCollection();
$this->composants = new ArrayCollection();
$this->pieces = new ArrayCollection();
$this->products = new ArrayCollection();
$this->customFields = new ArrayCollection();
$this->pieceCustomFields = new ArrayCollection();
$this->productCustomFields = new ArrayCollection();
}
#[ORM\PrePersist]
@@ -288,7 +267,7 @@ class ModelType
return $this;
}
#[Groups(['model_type:read'])]
#[Groups(['model_type:read', 'product:read', 'composant:read', 'piece:read'])]
public function getStructure(): ?array
{
return match ($this->category) {
@@ -312,6 +291,30 @@ class ModelType
return $this;
}
/**
* @return Collection<int, CustomField>
*/
public function getComponentCustomFields(): Collection
{
return $this->customFields;
}
/**
* @return Collection<int, CustomField>
*/
public function getPieceCustomFields(): Collection
{
return $this->pieceCustomFields;
}
/**
* @return Collection<int, CustomField>
*/
public function getProductCustomFields(): Collection
{
return $this->productCustomFields;
}
public function getCreatedAt(): DateTimeImmutable
{
return $this->createdAt;

View File

@@ -17,7 +17,7 @@ use DateTimeImmutable;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Serializer\Annotation\Groups;
use Symfony\Component\Serializer\Attribute\Groups;
use Symfony\Component\Validator\Constraints as Assert;
#[ORM\Entity(repositoryClass: ProfileRepository::class)]

View File

@@ -1,390 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Entity;
use ApiPlatform\Metadata\ApiProperty;
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\TypeMachineRepository;
use App\State\TypeMachinePutProcessor;
use DateTimeImmutable;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Symfony\Component\Serializer\Annotation\Groups;
use Symfony\Component\Validator\Constraints as Assert;
#[ORM\Entity(repositoryClass: TypeMachineRepository::class)]
#[ORM\Table(name: 'type_machines')]
#[ORM\HasLifecycleCallbacks]
#[UniqueEntity(fields: ['name'], message: 'Ce nom de type de machine existe déjà.')]
#[ApiResource(
operations: [
new Get(security: "is_granted('ROLE_VIEWER')"),
new GetCollection(security: "is_granted('ROLE_VIEWER')"),
new Post(security: "is_granted('ROLE_GESTIONNAIRE')"),
new Put(security: "is_granted('ROLE_GESTIONNAIRE')", processor: TypeMachinePutProcessor::class, deserialize: false, validate: false),
new Delete(security: "is_granted('ROLE_GESTIONNAIRE')"),
],
paginationClientItemsPerPage: true,
paginationMaximumItemsPerPage: 200
)]
class TypeMachine
{
#[ORM\Id]
#[ORM\Column(type: Types::STRING, length: 36)]
#[Groups(['type_machine:read'])]
private ?string $id = null;
#[ORM\Column(type: Types::STRING, length: 255, unique: true)]
#[Assert\NotBlank]
#[Groups(['type_machine:read', 'type_machine:write', 'machine:read'])]
private string $name;
#[ORM\Column(type: Types::TEXT, nullable: true)]
#[Groups(['type_machine:read', 'type_machine:write'])]
private ?string $description = null;
#[ORM\Column(type: Types::STRING, length: 255, nullable: true)]
#[Groups(['type_machine:read', 'type_machine:write'])]
private ?string $category = null;
#[ORM\Column(type: Types::STRING, length: 255, nullable: true)]
#[Groups(['type_machine:read', 'type_machine:write'])]
private ?string $maintenanceFrequency = null;
#[ORM\Column(type: Types::JSON, nullable: true)]
#[Groups(['type_machine:read', 'type_machine:write'])]
private ?array $components = null;
#[ORM\Column(type: Types::JSON, nullable: true)]
#[Groups(['type_machine:read', 'type_machine:write'])]
private ?array $criticalParts = null;
#[ORM\Column(type: Types::JSON, nullable: true)]
#[Groups(['type_machine:read', 'type_machine:write'])]
private ?array $machinePieces = null;
#[ORM\Column(type: Types::JSON, nullable: true)]
#[Groups(['type_machine:read', 'type_machine:write'])]
private ?array $specifications = null;
#[ORM\Column(type: Types::DATETIME_IMMUTABLE, name: 'createdAt')]
#[Groups(['type_machine:read'])]
private DateTimeImmutable $createdAt;
#[ORM\Column(type: Types::DATETIME_IMMUTABLE, name: 'updatedAt')]
#[Groups(['type_machine:read'])]
private DateTimeImmutable $updatedAt;
/**
* @var Collection<int, Machine>
*/
#[ORM\OneToMany(targetEntity: Machine::class, mappedBy: 'typeMachine')]
private Collection $machines;
/**
* @var Collection<int, CustomField>
*/
#[ORM\OneToMany(targetEntity: CustomField::class, mappedBy: 'typeMachine', cascade: ['persist', 'remove'])]
#[ApiProperty(readableLink: true, writableLink: true)]
private Collection $customFields;
/**
* @var Collection<int, TypeMachineComponentRequirement>
*/
#[ORM\OneToMany(targetEntity: TypeMachineComponentRequirement::class, mappedBy: 'typeMachine', cascade: ['persist', 'remove'], orphanRemoval: true)]
#[ApiProperty(readableLink: true, writableLink: true)]
private Collection $componentRequirements;
/**
* @var Collection<int, TypeMachinePieceRequirement>
*/
#[ORM\OneToMany(targetEntity: TypeMachinePieceRequirement::class, mappedBy: 'typeMachine', cascade: ['persist', 'remove'], orphanRemoval: true)]
#[ApiProperty(readableLink: true, writableLink: true)]
private Collection $pieceRequirements;
/**
* @var Collection<int, TypeMachineProductRequirement>
*/
#[ORM\OneToMany(targetEntity: TypeMachineProductRequirement::class, mappedBy: 'typeMachine', cascade: ['persist', 'remove'], orphanRemoval: true)]
#[ApiProperty(readableLink: true, writableLink: true)]
private Collection $productRequirements;
public function __construct()
{
$this->id = 'cl'.bin2hex(random_bytes(12));
$this->createdAt = new DateTimeImmutable();
$this->updatedAt = new DateTimeImmutable();
$this->machines = new ArrayCollection();
$this->customFields = new ArrayCollection();
$this->componentRequirements = new ArrayCollection();
$this->pieceRequirements = new ArrayCollection();
$this->productRequirements = new ArrayCollection();
}
public function getId(): ?string
{
return $this->id;
}
public function getName(): string
{
return $this->name;
}
public function setName(string $name): static
{
$this->name = $name;
return $this;
}
public function getDescription(): ?string
{
return $this->description;
}
public function setDescription(?string $description): static
{
$this->description = $description;
return $this;
}
public function getCategory(): ?string
{
return $this->category;
}
public function setCategory(?string $category): static
{
$this->category = $category;
return $this;
}
public function getMaintenanceFrequency(): ?string
{
return $this->maintenanceFrequency;
}
public function setMaintenanceFrequency(?string $maintenanceFrequency): static
{
$this->maintenanceFrequency = $maintenanceFrequency;
return $this;
}
public function getComponents(): ?array
{
return $this->components;
}
public function setComponents(?array $components): static
{
$this->components = $components;
return $this;
}
public function getCriticalParts(): ?array
{
return $this->criticalParts;
}
public function setCriticalParts(?array $criticalParts): static
{
$this->criticalParts = $criticalParts;
return $this;
}
public function getMachinePieces(): ?array
{
return $this->machinePieces;
}
public function setMachinePieces(?array $machinePieces): static
{
$this->machinePieces = $machinePieces;
return $this;
}
public function getSpecifications(): ?array
{
return $this->specifications;
}
public function setSpecifications(?array $specifications): static
{
$this->specifications = $specifications;
return $this;
}
public function getCreatedAt(): DateTimeImmutable
{
return $this->createdAt;
}
public function getUpdatedAt(): DateTimeImmutable
{
return $this->updatedAt;
}
/**
* @return Collection<int, Machine>
*/
public function getMachines(): Collection
{
return $this->machines;
}
public function addMachine(Machine $machine): static
{
if (!$this->machines->contains($machine)) {
$this->machines->add($machine);
$machine->setTypeMachine($this);
}
return $this;
}
public function removeMachine(Machine $machine): static
{
if ($this->machines->removeElement($machine)) {
if ($machine->getTypeMachine() === $this) {
$machine->setTypeMachine(null);
}
}
return $this;
}
/**
* @return Collection<int, CustomField>
*/
public function getCustomFields(): Collection
{
return $this->customFields;
}
public function addCustomField(CustomField $customField): static
{
if (!$this->customFields->contains($customField)) {
$this->customFields->add($customField);
$customField->setTypeMachine($this);
}
return $this;
}
public function removeCustomField(CustomField $customField): static
{
if ($this->customFields->removeElement($customField)) {
if ($customField->getTypeMachine() === $this) {
$customField->setTypeMachine(null);
}
}
return $this;
}
/**
* @return Collection<int, TypeMachineComponentRequirement>
*/
public function getComponentRequirements(): Collection
{
return $this->componentRequirements;
}
public function addComponentRequirement(TypeMachineComponentRequirement $componentRequirement): static
{
if (!$this->componentRequirements->contains($componentRequirement)) {
$this->componentRequirements->add($componentRequirement);
$componentRequirement->setTypeMachine($this);
}
return $this;
}
public function removeComponentRequirement(TypeMachineComponentRequirement $componentRequirement): static
{
$this->componentRequirements->removeElement($componentRequirement);
return $this;
}
/**
* @return Collection<int, TypeMachinePieceRequirement>
*/
public function getPieceRequirements(): Collection
{
return $this->pieceRequirements;
}
public function addPieceRequirement(TypeMachinePieceRequirement $pieceRequirement): static
{
if (!$this->pieceRequirements->contains($pieceRequirement)) {
$this->pieceRequirements->add($pieceRequirement);
$pieceRequirement->setTypeMachine($this);
}
return $this;
}
public function removePieceRequirement(TypeMachinePieceRequirement $pieceRequirement): static
{
$this->pieceRequirements->removeElement($pieceRequirement);
return $this;
}
/**
* @return Collection<int, TypeMachineProductRequirement>
*/
public function getProductRequirements(): Collection
{
return $this->productRequirements;
}
public function addProductRequirement(TypeMachineProductRequirement $productRequirement): static
{
if (!$this->productRequirements->contains($productRequirement)) {
$this->productRequirements->add($productRequirement);
$productRequirement->setTypeMachine($this);
}
return $this;
}
public function removeProductRequirement(TypeMachineProductRequirement $productRequirement): static
{
$this->productRequirements->removeElement($productRequirement);
return $this;
}
#[ORM\PrePersist]
public function setCreatedAtValue(): void
{
$this->createdAt = new DateTimeImmutable();
$this->updatedAt = new DateTimeImmutable();
}
#[ORM\PreUpdate]
public function setUpdatedAtValue(): void
{
$this->updatedAt = new DateTimeImmutable();
}
}

View File

@@ -1,224 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Entity;
use ApiPlatform\Metadata\ApiProperty;
use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\Delete;
use ApiPlatform\Metadata\Get;
use ApiPlatform\Metadata\GetCollection;
use ApiPlatform\Metadata\Patch;
use ApiPlatform\Metadata\Post;
use ApiPlatform\Metadata\Put;
use App\Repository\TypeMachineComponentRequirementRepository;
use DateTimeImmutable;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Annotation\Groups;
#[ORM\Entity(repositoryClass: TypeMachineComponentRequirementRepository::class)]
#[ORM\Table(name: 'type_machine_component_requirements')]
#[ORM\HasLifecycleCallbacks]
#[ApiResource(
operations: [
new Get(security: "is_granted('ROLE_VIEWER')"),
new GetCollection(security: "is_granted('ROLE_VIEWER')"),
new Post(security: "is_granted('ROLE_GESTIONNAIRE')"),
new Put(security: "is_granted('ROLE_GESTIONNAIRE')"),
new Patch(security: "is_granted('ROLE_GESTIONNAIRE')"),
new Delete(security: "is_granted('ROLE_GESTIONNAIRE')"),
]
)]
class TypeMachineComponentRequirement
{
#[ORM\Id]
#[ORM\Column(type: Types::STRING, length: 36)]
#[Groups(['type_machine:read'])]
private ?string $id = null;
#[ORM\Column(type: Types::STRING, length: 255, nullable: true)]
#[Groups(['type_machine:read'])]
private ?string $label = null;
#[ORM\Column(type: Types::INTEGER, options: ['default' => 1], name: 'minCount')]
#[Groups(['type_machine:read'])]
private int $minCount = 1;
#[ORM\Column(type: Types::INTEGER, nullable: true, name: 'maxCount')]
#[Groups(['type_machine:read'])]
private ?int $maxCount = null;
#[ORM\Column(type: Types::BOOLEAN, options: ['default' => true], name: 'required')]
#[Groups(['type_machine:read'])]
private bool $required = true;
#[ORM\Column(type: Types::BOOLEAN, options: ['default' => true], name: 'allowNewModels')]
#[Groups(['type_machine:read'])]
private bool $allowNewModels = true;
#[ORM\Column(type: Types::INTEGER, options: ['default' => 0], name: 'orderIndex')]
#[Groups(['type_machine:read'])]
private int $orderIndex = 0;
#[ORM\ManyToOne(targetEntity: TypeMachine::class, inversedBy: 'componentRequirements')]
#[ORM\JoinColumn(name: 'typeMachineId', referencedColumnName: 'id', nullable: false, onDelete: 'CASCADE')]
private TypeMachine $typeMachine;
#[ORM\ManyToOne(targetEntity: ModelType::class, inversedBy: 'componentRequirements')]
#[ORM\JoinColumn(name: 'typeComposantId', referencedColumnName: 'id', nullable: false)]
#[ApiProperty(readableLink: true)]
#[Groups(['type_machine:read'])]
private ModelType $typeComposant;
/**
* @var Collection<int, MachineComponentLink>
*/
#[ORM\OneToMany(mappedBy: 'typeMachineComponentRequirement', targetEntity: MachineComponentLink::class)]
private Collection $machineComponentLinks;
#[ORM\Column(type: Types::DATETIME_IMMUTABLE, name: 'createdAt')]
private DateTimeImmutable $createdAt;
#[ORM\Column(type: Types::DATETIME_IMMUTABLE, name: 'updatedAt')]
private DateTimeImmutable $updatedAt;
public function __construct()
{
$this->machineComponentLinks = new ArrayCollection();
}
#[ORM\PrePersist]
public function setCreatedAtValue(): void
{
$now = new DateTimeImmutable();
$this->createdAt = $now;
$this->updatedAt = $now;
if (null === $this->id) {
$this->id = $this->generateCuid();
}
}
#[ORM\PreUpdate]
public function setUpdatedAtValue(): void
{
$this->updatedAt = new DateTimeImmutable();
}
public function getId(): ?string
{
return $this->id;
}
public function setId(string $id): static
{
$this->id = $id;
return $this;
}
public function getLabel(): ?string
{
return $this->label;
}
public function setLabel(?string $label): static
{
$this->label = $label;
return $this;
}
public function getMinCount(): int
{
return $this->minCount;
}
public function setMinCount(int $minCount): static
{
$this->minCount = $minCount;
return $this;
}
public function getMaxCount(): ?int
{
return $this->maxCount;
}
public function setMaxCount(?int $maxCount): static
{
$this->maxCount = $maxCount;
return $this;
}
public function isRequired(): bool
{
return $this->required;
}
public function setRequired(bool $required): static
{
$this->required = $required;
return $this;
}
public function isAllowNewModels(): bool
{
return $this->allowNewModels;
}
public function setAllowNewModels(bool $allowNewModels): static
{
$this->allowNewModels = $allowNewModels;
return $this;
}
public function getOrderIndex(): int
{
return $this->orderIndex;
}
public function setOrderIndex(int $orderIndex): static
{
$this->orderIndex = $orderIndex;
return $this;
}
public function getTypeMachine(): TypeMachine
{
return $this->typeMachine;
}
public function setTypeMachine(TypeMachine $typeMachine): static
{
$this->typeMachine = $typeMachine;
return $this;
}
public function getTypeComposant(): ModelType
{
return $this->typeComposant;
}
public function setTypeComposant(ModelType $typeComposant): static
{
$this->typeComposant = $typeComposant;
return $this;
}
private function generateCuid(): string
{
return 'cl'.bin2hex(random_bytes(12));
}
}

View File

@@ -1,224 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Entity;
use ApiPlatform\Metadata\ApiProperty;
use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\Delete;
use ApiPlatform\Metadata\Get;
use ApiPlatform\Metadata\GetCollection;
use ApiPlatform\Metadata\Patch;
use ApiPlatform\Metadata\Post;
use ApiPlatform\Metadata\Put;
use App\Repository\TypeMachinePieceRequirementRepository;
use DateTimeImmutable;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Annotation\Groups;
#[ORM\Entity(repositoryClass: TypeMachinePieceRequirementRepository::class)]
#[ORM\Table(name: 'type_machine_piece_requirements')]
#[ORM\HasLifecycleCallbacks]
#[ApiResource(
operations: [
new Get(security: "is_granted('ROLE_VIEWER')"),
new GetCollection(security: "is_granted('ROLE_VIEWER')"),
new Post(security: "is_granted('ROLE_GESTIONNAIRE')"),
new Put(security: "is_granted('ROLE_GESTIONNAIRE')"),
new Patch(security: "is_granted('ROLE_GESTIONNAIRE')"),
new Delete(security: "is_granted('ROLE_GESTIONNAIRE')"),
]
)]
class TypeMachinePieceRequirement
{
#[ORM\Id]
#[ORM\Column(type: Types::STRING, length: 36)]
#[Groups(['type_machine:read'])]
private ?string $id = null;
#[ORM\Column(type: Types::STRING, length: 255, nullable: true)]
#[Groups(['type_machine:read'])]
private ?string $label = null;
#[ORM\Column(type: Types::INTEGER, options: ['default' => 0], name: 'minCount')]
#[Groups(['type_machine:read'])]
private int $minCount = 0;
#[ORM\Column(type: Types::INTEGER, nullable: true, name: 'maxCount')]
#[Groups(['type_machine:read'])]
private ?int $maxCount = null;
#[ORM\Column(type: Types::BOOLEAN, options: ['default' => false])]
#[Groups(['type_machine:read'])]
private bool $required = false;
#[ORM\Column(type: Types::BOOLEAN, options: ['default' => true], name: 'allowNewModels')]
#[Groups(['type_machine:read'])]
private bool $allowNewModels = true;
#[ORM\Column(type: Types::INTEGER, options: ['default' => 0], name: 'orderIndex')]
#[Groups(['type_machine:read'])]
private int $orderIndex = 0;
#[ORM\ManyToOne(targetEntity: TypeMachine::class, inversedBy: 'pieceRequirements')]
#[ORM\JoinColumn(name: 'typeMachineId', referencedColumnName: 'id', nullable: false, onDelete: 'CASCADE')]
private TypeMachine $typeMachine;
#[ORM\ManyToOne(targetEntity: ModelType::class, inversedBy: 'pieceRequirements')]
#[ORM\JoinColumn(name: 'typePieceId', referencedColumnName: 'id', nullable: false)]
#[ApiProperty(readableLink: true)]
#[Groups(['type_machine:read'])]
private ModelType $typePiece;
/**
* @var Collection<int, MachinePieceLink>
*/
#[ORM\OneToMany(mappedBy: 'typeMachinePieceRequirement', targetEntity: MachinePieceLink::class)]
private Collection $machinePieceLinks;
#[ORM\Column(type: Types::DATETIME_IMMUTABLE, name: 'createdAt')]
private DateTimeImmutable $createdAt;
#[ORM\Column(type: Types::DATETIME_IMMUTABLE, name: 'updatedAt')]
private DateTimeImmutable $updatedAt;
public function __construct()
{
$this->machinePieceLinks = new ArrayCollection();
}
#[ORM\PrePersist]
public function setCreatedAtValue(): void
{
$now = new DateTimeImmutable();
$this->createdAt = $now;
$this->updatedAt = $now;
if (null === $this->id) {
$this->id = $this->generateCuid();
}
}
#[ORM\PreUpdate]
public function setUpdatedAtValue(): void
{
$this->updatedAt = new DateTimeImmutable();
}
public function getId(): ?string
{
return $this->id;
}
public function setId(string $id): static
{
$this->id = $id;
return $this;
}
public function getLabel(): ?string
{
return $this->label;
}
public function setLabel(?string $label): static
{
$this->label = $label;
return $this;
}
public function getMinCount(): int
{
return $this->minCount;
}
public function setMinCount(int $minCount): static
{
$this->minCount = $minCount;
return $this;
}
public function getMaxCount(): ?int
{
return $this->maxCount;
}
public function setMaxCount(?int $maxCount): static
{
$this->maxCount = $maxCount;
return $this;
}
public function isRequired(): bool
{
return $this->required;
}
public function setRequired(bool $required): static
{
$this->required = $required;
return $this;
}
public function isAllowNewModels(): bool
{
return $this->allowNewModels;
}
public function setAllowNewModels(bool $allowNewModels): static
{
$this->allowNewModels = $allowNewModels;
return $this;
}
public function getOrderIndex(): int
{
return $this->orderIndex;
}
public function setOrderIndex(int $orderIndex): static
{
$this->orderIndex = $orderIndex;
return $this;
}
public function getTypeMachine(): TypeMachine
{
return $this->typeMachine;
}
public function setTypeMachine(TypeMachine $typeMachine): static
{
$this->typeMachine = $typeMachine;
return $this;
}
public function getTypePiece(): ModelType
{
return $this->typePiece;
}
public function setTypePiece(ModelType $typePiece): static
{
$this->typePiece = $typePiece;
return $this;
}
private function generateCuid(): string
{
return 'cl'.bin2hex(random_bytes(12));
}
}

View File

@@ -1,224 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Entity;
use ApiPlatform\Metadata\ApiProperty;
use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\Delete;
use ApiPlatform\Metadata\Get;
use ApiPlatform\Metadata\GetCollection;
use ApiPlatform\Metadata\Patch;
use ApiPlatform\Metadata\Post;
use ApiPlatform\Metadata\Put;
use App\Repository\TypeMachineProductRequirementRepository;
use DateTimeImmutable;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Annotation\Groups;
#[ORM\Entity(repositoryClass: TypeMachineProductRequirementRepository::class)]
#[ORM\Table(name: 'type_machine_product_requirements')]
#[ORM\HasLifecycleCallbacks]
#[ApiResource(
operations: [
new Get(security: "is_granted('ROLE_VIEWER')"),
new GetCollection(security: "is_granted('ROLE_VIEWER')"),
new Post(security: "is_granted('ROLE_GESTIONNAIRE')"),
new Put(security: "is_granted('ROLE_GESTIONNAIRE')"),
new Patch(security: "is_granted('ROLE_GESTIONNAIRE')"),
new Delete(security: "is_granted('ROLE_GESTIONNAIRE')"),
]
)]
class TypeMachineProductRequirement
{
#[ORM\Id]
#[ORM\Column(type: Types::STRING, length: 36)]
#[Groups(['type_machine:read'])]
private ?string $id = null;
#[ORM\Column(type: Types::STRING, length: 255, nullable: true)]
#[Groups(['type_machine:read'])]
private ?string $label = null;
#[ORM\Column(type: Types::INTEGER, options: ['default' => 0], name: 'minCount')]
#[Groups(['type_machine:read'])]
private int $minCount = 0;
#[ORM\Column(type: Types::INTEGER, nullable: true, name: 'maxCount')]
#[Groups(['type_machine:read'])]
private ?int $maxCount = null;
#[ORM\Column(type: Types::BOOLEAN, options: ['default' => false])]
#[Groups(['type_machine:read'])]
private bool $required = false;
#[ORM\Column(type: Types::BOOLEAN, options: ['default' => true], name: 'allowNewModels')]
#[Groups(['type_machine:read'])]
private bool $allowNewModels = true;
#[ORM\Column(type: Types::INTEGER, options: ['default' => 0], name: 'orderIndex')]
#[Groups(['type_machine:read'])]
private int $orderIndex = 0;
#[ORM\ManyToOne(targetEntity: TypeMachine::class, inversedBy: 'productRequirements')]
#[ORM\JoinColumn(name: 'typeMachineId', referencedColumnName: 'id', nullable: false, onDelete: 'CASCADE')]
private TypeMachine $typeMachine;
#[ORM\ManyToOne(targetEntity: ModelType::class, inversedBy: 'productRequirements')]
#[ORM\JoinColumn(name: 'typeProductId', referencedColumnName: 'id', nullable: false)]
#[ApiProperty(readableLink: true)]
#[Groups(['type_machine:read'])]
private ModelType $typeProduct;
/**
* @var Collection<int, MachineProductLink>
*/
#[ORM\OneToMany(mappedBy: 'typeMachineProductRequirement', targetEntity: MachineProductLink::class)]
private Collection $machineProductLinks;
#[ORM\Column(type: Types::DATETIME_IMMUTABLE, name: 'createdAt')]
private DateTimeImmutable $createdAt;
#[ORM\Column(type: Types::DATETIME_IMMUTABLE, name: 'updatedAt')]
private DateTimeImmutable $updatedAt;
public function __construct()
{
$this->machineProductLinks = new ArrayCollection();
}
#[ORM\PrePersist]
public function setCreatedAtValue(): void
{
$now = new DateTimeImmutable();
$this->createdAt = $now;
$this->updatedAt = $now;
if (null === $this->id) {
$this->id = $this->generateCuid();
}
}
#[ORM\PreUpdate]
public function setUpdatedAtValue(): void
{
$this->updatedAt = new DateTimeImmutable();
}
public function getId(): ?string
{
return $this->id;
}
public function setId(string $id): static
{
$this->id = $id;
return $this;
}
public function getLabel(): ?string
{
return $this->label;
}
public function setLabel(?string $label): static
{
$this->label = $label;
return $this;
}
public function getMinCount(): int
{
return $this->minCount;
}
public function setMinCount(int $minCount): static
{
$this->minCount = $minCount;
return $this;
}
public function getMaxCount(): ?int
{
return $this->maxCount;
}
public function setMaxCount(?int $maxCount): static
{
$this->maxCount = $maxCount;
return $this;
}
public function isRequired(): bool
{
return $this->required;
}
public function setRequired(bool $required): static
{
$this->required = $required;
return $this;
}
public function isAllowNewModels(): bool
{
return $this->allowNewModels;
}
public function setAllowNewModels(bool $allowNewModels): static
{
$this->allowNewModels = $allowNewModels;
return $this;
}
public function getOrderIndex(): int
{
return $this->orderIndex;
}
public function setOrderIndex(int $orderIndex): static
{
$this->orderIndex = $orderIndex;
return $this;
}
public function getTypeMachine(): TypeMachine
{
return $this->typeMachine;
}
public function setTypeMachine(TypeMachine $typeMachine): static
{
$this->typeMachine = $typeMachine;
return $this;
}
public function getTypeProduct(): ModelType
{
return $this->typeProduct;
}
public function setTypeProduct(ModelType $typeProduct): static
{
$this->typeProduct = $typeProduct;
return $this;
}
private function generateCuid(): string
{
return 'cl'.bin2hex(random_bytes(12));
}
}

View File

@@ -11,7 +11,6 @@ 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;
@@ -286,7 +285,6 @@ final class MachineAuditSubscriber implements EventSubscriber
'reference' => $machine->getReference(),
'prix' => $machine->getPrix(),
'site' => $this->normalizeValue($machine->getSite()),
'typeMachine' => $this->normalizeValue($machine->getTypeMachine()),
'constructeurIds' => $this->normalizeCollection($machine->getConstructeurs()),
];
}
@@ -335,13 +333,6 @@ final class MachineAuditSubscriber implements EventSubscriber
];
}
if ($value instanceof TypeMachine) {
return [
'id' => $value->getId(),
'name' => $value->getName(),
];
}
if ($value instanceof ModelType) {
return [
'id' => $value->getId(),

View File

@@ -1,20 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Repository;
use App\Entity\TypeMachineComponentRequirement;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
/**
* @extends ServiceEntityRepository<TypeMachineComponentRequirement>
*/
class TypeMachineComponentRequirementRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, TypeMachineComponentRequirement::class);
}
}

View File

@@ -1,20 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Repository;
use App\Entity\TypeMachinePieceRequirement;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
/**
* @extends ServiceEntityRepository<TypeMachinePieceRequirement>
*/
class TypeMachinePieceRequirementRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, TypeMachinePieceRequirement::class);
}
}

View File

@@ -1,20 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Repository;
use App\Entity\TypeMachineProductRequirement;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
/**
* @extends ServiceEntityRepository<TypeMachineProductRequirement>
*/
class TypeMachineProductRequirementRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, TypeMachineProductRequirement::class);
}
}

View File

@@ -1,38 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Repository;
use App\Entity\TypeMachine;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
/**
* @extends ServiceEntityRepository<TypeMachine>
*/
class TypeMachineRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, TypeMachine::class);
}
public function save(TypeMachine $entity, bool $flush = false): void
{
$this->getEntityManager()->persist($entity);
if ($flush) {
$this->getEntityManager()->flush();
}
}
public function remove(TypeMachine $entity, bool $flush = false): void
{
$this->getEntityManager()->remove($entity);
if ($flush) {
$this->getEntityManager()->flush();
}
}
}

View File

@@ -137,16 +137,6 @@ final class ModelTypeCategoryConversionService
$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
@@ -210,16 +200,6 @@ final class ModelTypeCategoryConversionService
$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',

View File

@@ -1,211 +0,0 @@
<?php
declare(strict_types=1);
namespace App\State;
use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\ProcessorInterface;
use App\Entity\CustomField;
use App\Entity\ModelType;
use App\Entity\TypeMachine;
use App\Entity\TypeMachineComponentRequirement;
use App\Entity\TypeMachinePieceRequirement;
use App\Entity\TypeMachineProductRequirement;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpKernel\Exception\HttpException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use function array_key_exists;
use function is_string;
final class TypeMachinePutProcessor implements ProcessorInterface
{
public function __construct(
private readonly EntityManagerInterface $em,
private readonly RequestStack $requestStack,
) {}
/**
* @param array<string, mixed> $uriVariables
* @param array<string, mixed> $context
*/
public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []): TypeMachine
{
$typeMachine = $this->em->getRepository(TypeMachine::class)->find($uriVariables['id']);
if (!$typeMachine) {
throw new NotFoundHttpException('Type de machine non trouvé.');
}
// Guard: cannot edit if machines are linked
if (!$typeMachine->getMachines()->isEmpty()) {
throw new HttpException(422, 'Ce type de machine ne peut pas être modifié car des machines y sont rattachées.');
}
$request = $this->requestStack->getCurrentRequest();
$payload = json_decode($request->getContent(), true) ?? [];
$this->updateScalarProperties($typeMachine, $payload);
if (array_key_exists('customFields', $payload)) {
$this->replaceCustomFields($typeMachine, $payload['customFields'] ?? []);
}
if (array_key_exists('componentRequirements', $payload)) {
$this->replaceComponentRequirements($typeMachine, $payload['componentRequirements'] ?? []);
}
if (array_key_exists('pieceRequirements', $payload)) {
$this->replacePieceRequirements($typeMachine, $payload['pieceRequirements'] ?? []);
}
if (array_key_exists('productRequirements', $payload)) {
$this->replaceProductRequirements($typeMachine, $payload['productRequirements'] ?? []);
}
$this->em->flush();
return $typeMachine;
}
private function updateScalarProperties(TypeMachine $typeMachine, array $payload): void
{
if (isset($payload['name'])) {
$typeMachine->setName($payload['name']);
}
if (array_key_exists('description', $payload)) {
$typeMachine->setDescription($payload['description']);
}
if (array_key_exists('category', $payload)) {
$typeMachine->setCategory($payload['category']);
}
if (array_key_exists('maintenanceFrequency', $payload)) {
$typeMachine->setMaintenanceFrequency($payload['maintenanceFrequency']);
}
if (array_key_exists('components', $payload)) {
$typeMachine->setComponents($payload['components']);
}
if (array_key_exists('criticalParts', $payload)) {
$typeMachine->setCriticalParts($payload['criticalParts']);
}
if (array_key_exists('machinePieces', $payload)) {
$typeMachine->setMachinePieces($payload['machinePieces']);
}
if (array_key_exists('specifications', $payload)) {
$typeMachine->setSpecifications($payload['specifications']);
}
}
private function replaceCustomFields(TypeMachine $typeMachine, array $fieldsData): void
{
foreach ($typeMachine->getCustomFields()->toArray() as $old) {
$typeMachine->removeCustomField($old);
}
foreach ($fieldsData as $index => $data) {
$field = new CustomField();
$field->setName($data['name'] ?? '');
$field->setType($data['type'] ?? 'text');
$field->setRequired($data['required'] ?? false);
$field->setOptions($data['options'] ?? null);
$field->setOrderIndex($data['orderIndex'] ?? $index);
$typeMachine->addCustomField($field);
}
}
private function replaceComponentRequirements(TypeMachine $typeMachine, array $requirementsData): void
{
foreach ($typeMachine->getComponentRequirements()->toArray() as $old) {
$typeMachine->removeComponentRequirement($old);
}
foreach ($requirementsData as $index => $data) {
$req = new TypeMachineComponentRequirement();
$req->setLabel($data['label'] ?? null);
$req->setMinCount($data['minCount'] ?? 1);
$req->setMaxCount($data['maxCount'] ?? null);
$req->setRequired($data['required'] ?? true);
$req->setAllowNewModels($data['allowNewModels'] ?? true);
$req->setOrderIndex($data['orderIndex'] ?? $index);
$modelType = $this->resolveModelType($data['typeComposant'] ?? null);
if ($modelType) {
$req->setTypeComposant($modelType);
}
$typeMachine->addComponentRequirement($req);
}
}
private function replacePieceRequirements(TypeMachine $typeMachine, array $requirementsData): void
{
foreach ($typeMachine->getPieceRequirements()->toArray() as $old) {
$typeMachine->removePieceRequirement($old);
}
foreach ($requirementsData as $index => $data) {
$req = new TypeMachinePieceRequirement();
$req->setLabel($data['label'] ?? null);
$req->setMinCount($data['minCount'] ?? 0);
$req->setMaxCount($data['maxCount'] ?? null);
$req->setRequired($data['required'] ?? false);
$req->setAllowNewModels($data['allowNewModels'] ?? true);
$req->setOrderIndex($data['orderIndex'] ?? $index);
$modelType = $this->resolveModelType($data['typePiece'] ?? null);
if ($modelType) {
$req->setTypePiece($modelType);
}
$typeMachine->addPieceRequirement($req);
}
}
private function replaceProductRequirements(TypeMachine $typeMachine, array $requirementsData): void
{
foreach ($typeMachine->getProductRequirements()->toArray() as $old) {
$typeMachine->removeProductRequirement($old);
}
foreach ($requirementsData as $index => $data) {
$req = new TypeMachineProductRequirement();
$req->setLabel($data['label'] ?? null);
$req->setMinCount($data['minCount'] ?? 0);
$req->setMaxCount($data['maxCount'] ?? null);
$req->setRequired($data['required'] ?? false);
$req->setAllowNewModels($data['allowNewModels'] ?? true);
$req->setOrderIndex($data['orderIndex'] ?? $index);
$modelType = $this->resolveModelType($data['typeProduct'] ?? null);
if ($modelType) {
$req->setTypeProduct($modelType);
}
$typeMachine->addProductRequirement($req);
}
}
private function resolveModelType(mixed $value): ?ModelType
{
if (!$value) {
return null;
}
$id = $value;
if (is_string($value) && preg_match('#/api/model_types/(.+)$#', $value, $matches)) {
$id = $matches[1];
}
return $this->em->getReference(ModelType::class, $id);
}
}