2019-03-20 23:16:07 +01:00
< ? php
2019-08-12 15:47:57 +02:00
/**
2019-11-09 00:47:20 +01:00
* This file is part of Part - DB ( https :// github . com / Part - DB / Part - DB - symfony ) .
2019-08-12 15:47:57 +02:00
*
2022-11-29 22:28:53 +01:00
* Copyright ( C ) 2019 - 2022 Jan Böhmer ( https :// github . com / jbtronics )
2019-08-12 15:47:57 +02:00
*
2020-02-22 18:14:36 +01:00
* This program is free software : you can redistribute it and / or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation , either version 3 of the License , or
* ( at your option ) any later version .
2019-08-12 15:47:57 +02:00
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
2020-02-22 18:14:36 +01:00
* GNU Affero General Public License for more details .
2019-08-12 15:47:57 +02:00
*
2020-02-22 18:14:36 +01:00
* You should have received a copy of the GNU Affero General Public License
* along with this program . If not , see < https :// www . gnu . org / licenses />.
2019-08-12 15:47:57 +02:00
*/
2019-03-20 23:16:07 +01:00
declare ( strict_types = 1 );
2019-02-23 22:41:13 +01:00
2019-08-12 15:47:57 +02:00
namespace App\Entity\Parts ;
2019-02-23 22:41:13 +01:00
2024-06-22 00:31:43 +02:00
use Doctrine\Common\Collections\Criteria ;
2024-03-03 19:57:31 +01:00
use ApiPlatform\Doctrine\Common\Filter\DateFilterInterface ;
2023-10-03 16:29:04 +02:00
use ApiPlatform\Doctrine\Orm\Filter\DateFilter ;
use ApiPlatform\Doctrine\Orm\Filter\OrderFilter ;
2023-09-03 17:15:18 +02:00
use ApiPlatform\Metadata\ApiFilter ;
use ApiPlatform\Metadata\ApiProperty ;
use ApiPlatform\Metadata\ApiResource ;
use ApiPlatform\Metadata\Delete ;
use ApiPlatform\Metadata\Get ;
use ApiPlatform\Metadata\GetCollection ;
use ApiPlatform\Metadata\Link ;
2026-07-22 23:47:05 +02:00
use ApiPlatform\Metadata\McpTool ;
use ApiPlatform\Metadata\McpToolCollection ;
2023-09-03 17:15:18 +02:00
use ApiPlatform\Metadata\Patch ;
use ApiPlatform\Metadata\Post ;
2023-10-25 20:40:41 +02:00
use ApiPlatform\OpenApi\Model\Operation ;
2023-09-03 17:15:18 +02:00
use ApiPlatform\Serializer\Filter\PropertyFilter ;
2023-10-03 16:29:04 +02:00
use App\ApiPlatform\Filter\LikeFilter ;
2023-07-19 22:49:40 +02:00
use App\Entity\Attachments\Attachment ;
2023-11-30 19:13:32 +01:00
use App\Entity\EDA\EDACategoryInfo ;
2026-07-22 23:47:05 +02:00
use App\Mcp\DTO\ElementByIdInput ;
use App\Mcp\DTO\StructuralElementOverview ;
use App\Mcp\DTO\StructuralElementSearchInput ;
2023-06-11 14:55:06 +02:00
use App\Repository\Parts\CategoryRepository ;
2026-07-22 23:47:05 +02:00
use App\State\Mcp\GetStructuralElementDetailsProcessor ;
use App\State\Mcp\ListStructuralElementsProcessor ;
2023-06-11 14:55:06 +02:00
use Doctrine\DBAL\Types\Types ;
use Doctrine\Common\Collections\ArrayCollection ;
2019-09-24 13:39:49 +02:00
use App\Entity\Attachments\CategoryAttachment ;
2020-02-01 19:48:07 +01:00
use App\Entity\Base\AbstractPartsContainingDBElement ;
2023-04-15 22:25:03 +02:00
use App\Entity\Base\AbstractStructuralDBElement ;
2020-03-11 21:48:47 +01:00
use App\Entity\Parameters\CategoryParameter ;
2019-09-24 13:39:49 +02:00
use Doctrine\Common\Collections\Collection ;
2019-02-23 22:41:13 +01:00
use Doctrine\ORM\Mapping as ORM ;
2023-03-12 01:12:35 +01:00
use Symfony\Component\Serializer\Annotation\Groups ;
2020-03-11 21:48:47 +01:00
use Symfony\Component\Validator\Constraints as Assert ;
2019-02-23 22:41:13 +01:00
/**
2023-06-13 10:36:34 +02:00
* This entity describes a category , a part can belong to , which is used to group parts by their function .
*
* @ extends AbstractPartsContainingDBElement < CategoryAttachment , CategoryParameter >
2019-02-23 22:41:13 +01:00
*/
2023-06-11 14:55:06 +02:00
#[ORM\Entity(repositoryClass: CategoryRepository::class)]
2023-05-28 01:33:45 +02:00
#[ORM\Table(name: '`categories`')]
2024-03-03 19:57:31 +01:00
#[ORM\Index(columns: ['name'], name: 'category_idx_name')]
#[ORM\Index(columns: ['parent_id', 'name'], name: 'category_idx_parent_name')]
2023-09-03 17:15:18 +02:00
#[ApiResource(
operations : [
new Get ( security : 'is_granted("read", object)' ),
new GetCollection ( security : 'is_granted("@categories.read")' ),
new Post ( securityPostDenormalize : 'is_granted("create", object)' ),
new Patch ( security : 'is_granted("edit", object)' ),
new Delete ( security : 'is_granted("delete", object)' ),
2023-10-25 20:40:41 +02:00
new GetCollection (
2026-06-25 20:16:44 +02:00
uriTemplate : '/categories/{id}/children.{_format}' ,
uriVariables : [ 'id' => new Link ( fromProperty : 'children' , fromClass : Category :: class )],
2023-10-25 20:40:41 +02:00
openapi : new Operation ( summary : 'Retrieves the children elements of a category.' ),
security : 'is_granted("@categories.read")'
2026-06-25 20:16:44 +02:00
),
2023-09-03 17:15:18 +02:00
],
2026-06-25 20:16:44 +02:00
normalizationContext : [ 'groups' => [ 'category:read' , 'api:basic:read' ], 'openapi_definition_name' => 'Read' ],
denormalizationContext : [ 'groups' => [ 'category:write' , 'api:basic:write' , 'attachment:write' , 'parameter:write' ], 'openapi_definition_name' => 'Write' ],
2026-07-22 23:47:05 +02:00
mcp : [
'list_categories' => new McpToolCollection (
title : 'List/search categories' ,
2026-07-25 21:03:05 +02:00
description : 'List all part categories, optionally filtered by a keyword matched against the name and comment. Categories are used to group parts by their function. Each entry includes its full hierarchical path, and results are sorted by that path so parents are immediately followed by their own children, making it easy to derive the tree structure from the flat list.' ,
2026-07-22 23:47:05 +02:00
annotations : [ 'readOnlyHint' => true , 'destructiveHint' => false , 'idempotentHint' => true , 'openWorldHint' => false ],
output : StructuralElementOverview :: class ,
normalizationContext : [ 'groups' => [ 'mcp_structural_overview:read' ]],
input : StructuralElementSearchInput :: class ,
security : 'is_granted("@categories.read")' ,
processor : ListStructuralElementsProcessor :: class ,
),
'get_category_details' => new McpTool (
title : 'Get category details by ID' ,
description : 'Get detailed information about a specific part category by its database ID.' ,
annotations : [ 'readOnlyHint' => true , 'destructiveHint' => false , 'idempotentHint' => true , 'openWorldHint' => false ],
normalizationContext : [ 'groups' => [ 'category:read' , 'api:basic:read' ]],
input : ElementByIdInput :: class ,
security : 'is_granted("@categories.read")' ,
validate : true ,
processor : GetStructuralElementDetailsProcessor :: class ,
),
],
2023-09-03 17:15:18 +02:00
)]
#[ApiFilter(PropertyFilter::class)]
2023-10-03 16:29:04 +02:00
#[ApiFilter(LikeFilter::class, properties: ["name", "comment"])]
2024-03-03 19:57:31 +01:00
#[ApiFilter(DateFilter::class, strategy: DateFilterInterface::EXCLUDE_NULL)]
2023-10-03 16:29:04 +02:00
#[ApiFilter(OrderFilter::class, properties: ['name', 'id', 'addedDate', 'lastModified'])]
2020-02-01 19:48:07 +01:00
class Category extends AbstractPartsContainingDBElement
2019-02-23 22:41:13 +01:00
{
2024-03-03 19:57:31 +01:00
#[ORM\OneToMany(mappedBy: 'parent', targetEntity: self::class)]
2024-06-22 00:31:43 +02:00
#[ORM\OrderBy(['name' => Criteria::ASC])]
2023-04-15 22:25:03 +02:00
protected Collection $children ;
2019-02-23 22:41:13 +01:00
2023-06-13 20:24:54 +02:00
#[ORM\ManyToOne(targetEntity: self::class, inversedBy: 'children')]
2023-05-28 01:33:45 +02:00
#[ORM\JoinColumn(name: 'parent_id')]
2023-09-03 17:15:18 +02:00
#[Groups(['category:read', 'category:write'])]
2026-01-03 22:18:10 +01:00
#[ApiProperty(readableLink: false, writableLink: false)]
2023-04-15 22:25:03 +02:00
protected ? AbstractStructuralDBElement $parent = null ;
2019-02-23 22:41:13 +01:00
2023-09-03 17:15:18 +02:00
#[Groups(['category:read', 'category:write'])]
protected string $comment = '' ;
2019-02-23 22:41:13 +01:00
/**
2023-09-17 12:50:32 +02:00
* @ var string The hint which is shown as hint under the partname field , when a part is created in this category .
2019-02-23 22:41:13 +01:00
*/
2023-09-03 17:15:18 +02:00
#[Groups(['full', 'import', 'category:read', 'category:write'])]
2023-06-11 14:55:06 +02:00
#[ORM\Column(type: Types::TEXT)]
2022-09-18 22:59:31 +02:00
protected string $partname_hint = '' ;
2019-02-23 22:41:13 +01:00
/**
2023-09-17 12:50:32 +02:00
* @ var string The regular expression which is used to validate the partname of a part in this category .
2019-02-23 22:41:13 +01:00
*/
2023-09-03 17:15:18 +02:00
#[Groups(['full', 'import', 'category:read', 'category:write'])]
2023-06-11 14:55:06 +02:00
#[ORM\Column(type: Types::TEXT)]
2022-09-18 22:59:31 +02:00
protected string $partname_regex = '' ;
2019-02-23 22:41:13 +01:00
2025-11-03 00:31:47 +01:00
/**
* @ var string The prefix for ipn generation for created parts in this category .
*/
#[Groups(['full', 'import', 'category:read', 'category:write'])]
#[ORM\Column(type: Types::STRING, length: 255, nullable: false, options: ['default' => ''])]
protected string $part_ipn_prefix = '' ;
2019-02-23 22:41:13 +01:00
/**
2023-09-17 12:50:32 +02:00
* @ var bool Set to true , if the footprints should be disabled for parts this category ( not implemented yet ) .
2019-02-23 22:41:13 +01:00
*/
2023-09-03 17:15:18 +02:00
#[Groups(['full', 'import', 'category:read', 'category:write'])]
2023-06-11 14:55:06 +02:00
#[ORM\Column(type: Types::BOOLEAN)]
2022-09-18 22:59:31 +02:00
protected bool $disable_footprints = false ;
2019-02-23 22:41:13 +01:00
/**
2023-09-17 12:50:32 +02:00
* @ var bool Set to true , if the manufacturers should be disabled for parts this category ( not implemented yet ) .
2019-02-23 22:41:13 +01:00
*/
2023-09-03 17:15:18 +02:00
#[Groups(['full', 'import', 'category:read', 'category:write'])]
2023-06-11 14:55:06 +02:00
#[ORM\Column(type: Types::BOOLEAN)]
2022-09-18 22:59:31 +02:00
protected bool $disable_manufacturers = false ;
2019-02-23 22:41:13 +01:00
/**
2023-09-17 12:50:32 +02:00
* @ var bool Set to true , if the autodatasheets should be disabled for parts this category ( not implemented yet ) .
2019-02-23 22:41:13 +01:00
*/
2023-09-03 17:15:18 +02:00
#[Groups(['full', 'import', 'category:read', 'category:write'])]
2023-06-11 14:55:06 +02:00
#[ORM\Column(type: Types::BOOLEAN)]
2022-09-18 22:59:31 +02:00
protected bool $disable_autodatasheets = false ;
2019-02-23 22:41:13 +01:00
/**
2023-09-17 12:50:32 +02:00
* @ var bool Set to true , if the properties should be disabled for parts this category ( not implemented yet ) .
2019-02-23 22:41:13 +01:00
*/
2023-09-03 17:15:18 +02:00
#[Groups(['full', 'import', 'category:read', 'category:write'])]
2023-06-11 14:55:06 +02:00
#[ORM\Column(type: Types::BOOLEAN)]
2022-09-18 22:59:31 +02:00
protected bool $disable_properties = false ;
2019-02-23 22:41:13 +01:00
/**
2023-09-17 12:50:32 +02:00
* @ var string The default description for parts in this category .
2019-02-23 22:41:13 +01:00
*/
2023-09-03 17:15:18 +02:00
#[Groups(['full', 'import', 'category:read', 'category:write'])]
2023-06-11 14:55:06 +02:00
#[ORM\Column(type: Types::TEXT)]
2022-09-18 22:59:31 +02:00
protected string $default_description = '' ;
2019-02-23 22:41:13 +01:00
/**
2023-09-17 12:50:32 +02:00
* @ var string The default comment for parts in this category .
2019-02-23 22:41:13 +01:00
*/
2023-09-03 17:15:18 +02:00
#[Groups(['full', 'import', 'category:read', 'category:write'])]
2023-06-11 14:55:06 +02:00
#[ORM\Column(type: Types::TEXT)]
2022-09-18 22:59:31 +02:00
protected string $default_comment = '' ;
2023-04-15 22:25:03 +02:00
2020-01-05 22:49:00 +01:00
/**
2020-03-29 23:13:25 +02:00
* @ var Collection < int , CategoryAttachment >
2020-01-05 22:49:00 +01:00
*/
2023-05-28 01:21:05 +02:00
#[Assert\Valid]
2023-09-18 23:38:06 +02:00
#[Groups(['full', 'category:read', 'category:write'])]
2024-03-03 19:57:31 +01:00
#[ORM\OneToMany(mappedBy: 'element', targetEntity: CategoryAttachment::class, cascade: ['persist', 'remove'], orphanRemoval: true)]
2024-06-22 00:31:43 +02:00
#[ORM\OrderBy(['name' => Criteria::ASC])]
2023-04-15 22:25:03 +02:00
protected Collection $attachments ;
2019-02-23 22:41:13 +01:00
2023-07-20 21:54:13 +02:00
#[ORM\ManyToOne(targetEntity: CategoryAttachment::class)]
2023-07-19 22:49:40 +02:00
#[ORM\JoinColumn(name: 'id_preview_attachment', onDelete: 'SET NULL')]
2023-09-18 23:38:06 +02:00
#[Groups(['category:read', 'category:write'])]
2023-07-19 22:49:40 +02:00
protected ? Attachment $master_picture_attachment = null ;
2020-03-29 23:13:25 +02:00
/** @ var Collection < int , CategoryParameter >
2020-03-11 21:48:47 +01:00
*/
2023-05-28 01:21:05 +02:00
#[Assert\Valid]
2023-09-19 11:44:22 +02:00
#[Groups(['full', 'category:read', 'category:write'])]
2024-03-03 19:57:31 +01:00
#[ORM\OneToMany(mappedBy: 'element', targetEntity: CategoryParameter::class, cascade: ['persist', 'remove'], orphanRemoval: true)]
2024-06-22 00:31:43 +02:00
#[ORM\OrderBy(['group' => Criteria::ASC, 'name' => 'ASC'])]
2023-04-15 22:25:03 +02:00
protected Collection $parameters ;
2020-03-11 21:48:47 +01:00
2023-10-04 00:08:10 +02:00
#[Groups(['category:read'])]
2024-06-22 17:36:54 +02:00
protected ? \DateTimeImmutable $addedDate = null ;
2023-10-04 00:08:10 +02:00
#[Groups(['category:read'])]
2024-06-22 17:36:54 +02:00
protected ? \DateTimeImmutable $lastModified = null ;
2023-10-04 00:08:10 +02:00
2023-11-30 19:13:32 +01:00
#[Assert\Valid]
#[ORM\Embedded(class: EDACategoryInfo::class)]
2023-12-03 00:57:11 +01:00
#[Groups(['full', 'category:read', 'category:write'])]
2023-11-30 19:13:32 +01:00
protected EDACategoryInfo $eda_info ;
public function __construct ()
{
parent :: __construct ();
$this -> children = new ArrayCollection ();
$this -> attachments = new ArrayCollection ();
$this -> parameters = new ArrayCollection ();
$this -> eda_info = new EDACategoryInfo ();
}
2023-10-04 00:08:10 +02:00
2026-05-04 22:42:18 +02:00
public function __clone ()
{
if ( $this -> id ) {
//Clone EDA info to prevent changes to the original EDA info when changing the cloned category
$this -> eda_info = clone $this -> eda_info ;
}
parent :: __clone ();
}
2019-04-26 18:05:43 +02:00
public function getPartnameHint () : string
{
return $this -> partname_hint ;
}
2019-11-09 00:47:20 +01:00
public function setPartnameHint ( string $partname_hint ) : self
2019-04-26 18:05:43 +02:00
{
$this -> partname_hint = $partname_hint ;
2019-11-09 00:47:20 +01:00
2019-04-26 18:05:43 +02:00
return $this ;
}
public function getPartnameRegex () : string
{
return $this -> partname_regex ;
}
2019-11-09 00:47:20 +01:00
public function setPartnameRegex ( string $partname_regex ) : self
2019-04-26 18:05:43 +02:00
{
$this -> partname_regex = $partname_regex ;
2019-11-09 00:47:20 +01:00
2019-04-26 18:05:43 +02:00
return $this ;
}
2025-11-03 00:31:47 +01:00
public function getPartIpnPrefix () : string
{
return $this -> part_ipn_prefix ;
}
public function setPartIpnPrefix ( string $part_ipn_prefix ) : void
{
$this -> part_ipn_prefix = $part_ipn_prefix ;
}
2019-04-26 18:05:43 +02:00
public function isDisableFootprints () : bool
{
return $this -> disable_footprints ;
}
2019-11-09 00:47:20 +01:00
public function setDisableFootprints ( bool $disable_footprints ) : self
2019-04-26 18:05:43 +02:00
{
$this -> disable_footprints = $disable_footprints ;
2019-11-09 00:47:20 +01:00
2019-04-26 18:05:43 +02:00
return $this ;
}
public function isDisableManufacturers () : bool
{
return $this -> disable_manufacturers ;
}
2019-11-09 00:47:20 +01:00
public function setDisableManufacturers ( bool $disable_manufacturers ) : self
2019-04-26 18:05:43 +02:00
{
$this -> disable_manufacturers = $disable_manufacturers ;
2019-11-09 00:47:20 +01:00
2019-04-26 18:05:43 +02:00
return $this ;
}
public function isDisableAutodatasheets () : bool
{
return $this -> disable_autodatasheets ;
}
2019-11-09 00:47:20 +01:00
public function setDisableAutodatasheets ( bool $disable_autodatasheets ) : self
2019-04-26 18:05:43 +02:00
{
$this -> disable_autodatasheets = $disable_autodatasheets ;
2019-11-09 00:47:20 +01:00
2019-04-26 18:05:43 +02:00
return $this ;
}
public function isDisableProperties () : bool
{
return $this -> disable_properties ;
}
2019-11-09 00:47:20 +01:00
public function setDisableProperties ( bool $disable_properties ) : self
2019-04-26 18:05:43 +02:00
{
$this -> disable_properties = $disable_properties ;
2019-11-09 00:47:20 +01:00
2019-04-26 18:05:43 +02:00
return $this ;
}
public function getDefaultDescription () : string
{
return $this -> default_description ;
}
2019-11-09 00:47:20 +01:00
public function setDefaultDescription ( string $default_description ) : self
2019-04-26 18:05:43 +02:00
{
$this -> default_description = $default_description ;
2019-11-09 00:47:20 +01:00
2019-04-26 18:05:43 +02:00
return $this ;
}
public function getDefaultComment () : string
{
return $this -> default_comment ;
}
2019-11-09 00:47:20 +01:00
public function setDefaultComment ( string $default_comment ) : self
2019-04-26 18:05:43 +02:00
{
$this -> default_comment = $default_comment ;
return $this ;
}
2023-11-30 19:13:32 +01:00
public function getEdaInfo () : EDACategoryInfo
2023-05-28 01:33:45 +02:00
{
2023-11-30 19:13:32 +01:00
return $this -> eda_info ;
}
public function setEdaInfo ( EDACategoryInfo $eda_info ) : Category
{
$this -> eda_info = $eda_info ;
return $this ;
2023-05-28 01:33:45 +02:00
}
2019-02-23 22:41:13 +01:00
}