| @@ -586,6 +586,18 @@ paths: | |||||
| style: form | style: form | ||||
| explode: true | explode: true | ||||
| allowReserved: false | allowReserved: false | ||||
| - | |||||
| name: nameSearch | |||||
| in: query | |||||
| description: '' | |||||
| required: false | |||||
| deprecated: false | |||||
| allowEmptyValue: true | |||||
| schema: | |||||
| type: string | |||||
| style: form | |||||
| explode: false | |||||
| allowReserved: false | |||||
| deprecated: false | deprecated: false | ||||
| post: | post: | ||||
| operationId: api_contacts_post | operationId: api_contacts_post | ||||
| @@ -1811,6 +1823,18 @@ paths: | |||||
| style: form | style: form | ||||
| explode: true | explode: true | ||||
| allowReserved: false | allowReserved: false | ||||
| - | |||||
| name: productPartnerUnassigned | |||||
| in: query | |||||
| description: '' | |||||
| required: false | |||||
| deprecated: false | |||||
| allowEmptyValue: true | |||||
| schema: | |||||
| type: string | |||||
| style: form | |||||
| explode: false | |||||
| allowReserved: false | |||||
| deprecated: false | deprecated: false | ||||
| post: | post: | ||||
| operationId: api_partner_products_post | operationId: api_partner_products_post | ||||
| @@ -2633,6 +2657,18 @@ paths: | |||||
| style: form | style: form | ||||
| explode: false | explode: false | ||||
| allowReserved: false | allowReserved: false | ||||
| - | |||||
| name: partnerIdUnassigned | |||||
| in: query | |||||
| description: '' | |||||
| required: false | |||||
| deprecated: false | |||||
| allowEmptyValue: true | |||||
| schema: | |||||
| type: string | |||||
| style: form | |||||
| explode: false | |||||
| allowReserved: false | |||||
| - | - | ||||
| name: 'order[name]' | name: 'order[name]' | ||||
| in: query | in: query | ||||
| @@ -4023,6 +4059,18 @@ paths: | |||||
| style: form | style: form | ||||
| explode: false | explode: false | ||||
| allowReserved: false | allowReserved: false | ||||
| - | |||||
| name: nameSearch | |||||
| in: query | |||||
| description: '' | |||||
| required: false | |||||
| deprecated: false | |||||
| allowEmptyValue: true | |||||
| schema: | |||||
| type: string | |||||
| style: form | |||||
| explode: false | |||||
| allowReserved: false | |||||
| deprecated: false | deprecated: false | ||||
| post: | post: | ||||
| operationId: api_users_post | operationId: api_users_post | ||||
| @@ -6657,6 +6705,16 @@ components: | |||||
| - 'null' | - 'null' | ||||
| format: iri-reference | format: iri-reference | ||||
| example: 'https://example.com/' | example: 'https://example.com/' | ||||
| contact: | |||||
| readOnly: true | |||||
| description: '?ContactApi' | |||||
| $ref: '#/components/schemas/Contact' | |||||
| contactIri: | |||||
| type: | |||||
| - string | |||||
| - 'null' | |||||
| format: iri-reference | |||||
| example: 'https://example.com/' | |||||
| contactType: | contactType: | ||||
| type: string | type: string | ||||
| enum: | enum: | ||||
| @@ -6701,6 +6759,16 @@ components: | |||||
| - 'null' | - 'null' | ||||
| format: iri-reference | format: iri-reference | ||||
| example: 'https://example.com/' | example: 'https://example.com/' | ||||
| contact: | |||||
| readOnly: true | |||||
| description: '?ContactApi' | |||||
| $ref: '#/components/schemas/Contact.jsonhal' | |||||
| contactIri: | |||||
| type: | |||||
| - string | |||||
| - 'null' | |||||
| format: iri-reference | |||||
| example: 'https://example.com/' | |||||
| contactType: | contactType: | ||||
| type: string | type: string | ||||
| enum: | enum: | ||||
| @@ -6759,6 +6827,16 @@ components: | |||||
| - 'null' | - 'null' | ||||
| format: iri-reference | format: iri-reference | ||||
| example: 'https://example.com/' | example: 'https://example.com/' | ||||
| contact: | |||||
| readOnly: true | |||||
| description: '?ContactApi' | |||||
| $ref: '#/components/schemas/Contact.jsonld' | |||||
| contactIri: | |||||
| type: | |||||
| - string | |||||
| - 'null' | |||||
| format: iri-reference | |||||
| example: 'https://example.com/' | |||||
| contactType: | contactType: | ||||
| type: string | type: string | ||||
| enum: | enum: | ||||
| @@ -0,0 +1,35 @@ | |||||
| <?php | |||||
| declare(strict_types=1); | |||||
| namespace DoctrineMigrations; | |||||
| use Doctrine\DBAL\Schema\Schema; | |||||
| use Doctrine\Migrations\AbstractMigration; | |||||
| /** | |||||
| * Auto-generated Migration: Please modify to your needs! | |||||
| */ | |||||
| final class Version20240607125011 extends AbstractMigration | |||||
| { | |||||
| public function getDescription(): string | |||||
| { | |||||
| return ''; | |||||
| } | |||||
| public function up(Schema $schema): void | |||||
| { | |||||
| // this up() migration is auto-generated, please modify it to your needs | |||||
| $this->addSql('ALTER TABLE task_note ADD contact_id INT DEFAULT NULL'); | |||||
| $this->addSql('ALTER TABLE task_note ADD CONSTRAINT FK_BC0E6E6FE7A1254A FOREIGN KEY (contact_id) REFERENCES contact (id)'); | |||||
| $this->addSql('CREATE INDEX IDX_BC0E6E6FE7A1254A ON task_note (contact_id)'); | |||||
| } | |||||
| public function down(Schema $schema): void | |||||
| { | |||||
| // this down() migration is auto-generated, please modify it to your needs | |||||
| $this->addSql('ALTER TABLE task_note DROP FOREIGN KEY FK_BC0E6E6FE7A1254A'); | |||||
| $this->addSql('DROP INDEX IDX_BC0E6E6FE7A1254A ON task_note'); | |||||
| $this->addSql('ALTER TABLE task_note DROP contact_id'); | |||||
| } | |||||
| } | |||||
| @@ -14,9 +14,9 @@ use ApiPlatform\Metadata\ApiProperty; | |||||
| use ApiPlatform\Metadata\ApiResource; | use ApiPlatform\Metadata\ApiResource; | ||||
| use App\Entity\Contact; | use App\Entity\Contact; | ||||
| use App\Entity\MediaObject; | use App\Entity\MediaObject; | ||||
| use App\Filter\ContactNameFilter; | |||||
| use App\State\EntityClassDtoStateProcessor; | use App\State\EntityClassDtoStateProcessor; | ||||
| use App\State\EntityToDtoStateProvider; | use App\State\EntityToDtoStateProvider; | ||||
| use ApiPlatform\Metadata\Delete; | |||||
| use ApiPlatform\Metadata\Get; | use ApiPlatform\Metadata\Get; | ||||
| use ApiPlatform\Metadata\GetCollection; | use ApiPlatform\Metadata\GetCollection; | ||||
| use ApiPlatform\Metadata\Patch; | use ApiPlatform\Metadata\Patch; | ||||
| @@ -51,6 +51,7 @@ use Symfony\Component\Validator\Constraints\NotBlank; | |||||
| stateOptions: new Options(entityClass: Contact::class), | stateOptions: new Options(entityClass: Contact::class), | ||||
| )] | )] | ||||
| #[ApiFilter(SearchFilter::class, properties: ['partner' => 'exact'])] | #[ApiFilter(SearchFilter::class, properties: ['partner' => 'exact'])] | ||||
| #[ApiFilter(ContactNameFilter::class)] | |||||
| class ContactApi | class ContactApi | ||||
| { | { | ||||
| #[ApiProperty(readable: false, writable: false, identifier: true)] | #[ApiProperty(readable: false, writable: false, identifier: true)] | ||||
| @@ -13,6 +13,7 @@ use ApiPlatform\Metadata\ApiFilter; | |||||
| use ApiPlatform\Metadata\ApiProperty; | use ApiPlatform\Metadata\ApiProperty; | ||||
| use ApiPlatform\Metadata\ApiResource; | use ApiPlatform\Metadata\ApiResource; | ||||
| use App\Entity\PartnerProduct; | use App\Entity\PartnerProduct; | ||||
| use App\Filter\PartnerProductUnassignedFilter; | |||||
| use App\State\EntityClassDtoStateProcessor; | use App\State\EntityClassDtoStateProcessor; | ||||
| use App\State\EntityToDtoStateProvider; | use App\State\EntityToDtoStateProvider; | ||||
| use ApiPlatform\Metadata\Delete; | use ApiPlatform\Metadata\Delete; | ||||
| @@ -38,6 +39,7 @@ use Symfony\Component\Validator\Constraints\NotBlank; | |||||
| security: 'is_granted("ROLE_USER")', | security: 'is_granted("ROLE_USER")', | ||||
| ) | ) | ||||
| ], | ], | ||||
| order: ['product.name' => 'ASC'], | |||||
| security: 'is_granted("ROLE_USER")', | security: 'is_granted("ROLE_USER")', | ||||
| provider: EntityToDtoStateProvider::class, | provider: EntityToDtoStateProvider::class, | ||||
| processor: EntityClassDtoStateProcessor::class, | processor: EntityClassDtoStateProcessor::class, | ||||
| @@ -50,6 +52,7 @@ use Symfony\Component\Validator\Constraints\NotBlank; | |||||
| 'product.name' => 'ipartial', | 'product.name' => 'ipartial', | ||||
| 'partner.type' => 'exact' | 'partner.type' => 'exact' | ||||
| ])] | ])] | ||||
| #[ApiFilter(PartnerProductUnassignedFilter::class)] | |||||
| class PartnerProductApi | class PartnerProductApi | ||||
| { | { | ||||
| #[ApiProperty(readable: false, writable: false, identifier: true)] | #[ApiProperty(readable: false, writable: false, identifier: true)] | ||||
| @@ -16,6 +16,7 @@ use ApiPlatform\Metadata\ApiProperty; | |||||
| use ApiPlatform\Metadata\ApiResource; | use ApiPlatform\Metadata\ApiResource; | ||||
| use App\Entity\MediaObject; | use App\Entity\MediaObject; | ||||
| use App\Entity\Product; | use App\Entity\Product; | ||||
| use App\Filter\ProductUnassignedFilter; | |||||
| use App\State\EntityClassDtoStateProcessor; | use App\State\EntityClassDtoStateProcessor; | ||||
| use App\State\EntityToDtoStateProvider; | use App\State\EntityToDtoStateProvider; | ||||
| use ApiPlatform\Metadata\Delete; | use ApiPlatform\Metadata\Delete; | ||||
| @@ -51,6 +52,7 @@ use Symfony\Component\Validator\Constraints\NotBlank; | |||||
| )] | )] | ||||
| #[ApiFilter(SearchFilter::class, properties: ['name' => 'ipartial'])] | #[ApiFilter(SearchFilter::class, properties: ['name' => 'ipartial'])] | ||||
| #[ApiFilter(ProductUnassignedFilter::class)] | |||||
| #[ApiFilter(OrderFilter::class, properties: ['name'], arguments: ['orderParameterName' => 'order'])] | #[ApiFilter(OrderFilter::class, properties: ['name'], arguments: ['orderParameterName' => 'order'])] | ||||
| class ProductApi | class ProductApi | ||||
| { | { | ||||
| @@ -76,6 +76,24 @@ class TaskNoteApi implements OwnerInterface | |||||
| #[NotBlank] | #[NotBlank] | ||||
| public ?TaskApi $taskIri = null; | public ?TaskApi $taskIri = null; | ||||
| /** | |||||
| * @var $owner ?ContactApi | |||||
| */ | |||||
| #[ApiProperty( | |||||
| writable: false, | |||||
| readableLink: true, | |||||
| writableLink: false, | |||||
| builtinTypes: [ | |||||
| new Type( | |||||
| 'object', | |||||
| class: ContactApi::class, | |||||
| ) | |||||
| ] | |||||
| )] | |||||
| public ?ContactApi $contact = null; | |||||
| public ?ContactApi $contactIri = null; | |||||
| #[NotBlank] | #[NotBlank] | ||||
| public TaskNoteContactType $contactType; | public TaskNoteContactType $contactType; | ||||
| @@ -18,6 +18,7 @@ use ApiPlatform\Metadata\Patch; | |||||
| use ApiPlatform\Metadata\Post; | use ApiPlatform\Metadata\Post; | ||||
| use App\Entity\MediaObject; | use App\Entity\MediaObject; | ||||
| use App\Entity\User; | use App\Entity\User; | ||||
| use App\Filter\UserNameFilter; | |||||
| use App\Interface\AdminOrOwnerInterface; | use App\Interface\AdminOrOwnerInterface; | ||||
| use App\State\EntityClassDtoStateProcessor; | use App\State\EntityClassDtoStateProcessor; | ||||
| use App\State\EntityToDtoStateProvider; | use App\State\EntityToDtoStateProvider; | ||||
| @@ -47,6 +48,7 @@ use Symfony\Component\Validator\Constraints as Assert; | |||||
| )] | )] | ||||
| #[ApiFilter(SearchFilter::class, properties: ['firstName' => 'ipartial', 'lastName' => 'ipartial'])] | #[ApiFilter(SearchFilter::class, properties: ['firstName' => 'ipartial', 'lastName' => 'ipartial'])] | ||||
| #[ApiFilter(UserNameFilter::class)] | |||||
| class UserApi | class UserApi | ||||
| { | { | ||||
| #[ApiProperty(readable: false, writable: false, identifier: true)] | #[ApiProperty(readable: false, writable: false, identifier: true)] | ||||
| @@ -54,6 +54,9 @@ class Contact | |||||
| #[ORM\OneToMany(mappedBy: 'contact', targetEntity: ContactPartnerProduct::class)] | #[ORM\OneToMany(mappedBy: 'contact', targetEntity: ContactPartnerProduct::class)] | ||||
| private Collection $contactPartnerProducts; | private Collection $contactPartnerProducts; | ||||
| #[ORM\OneToMany(mappedBy: 'contact', targetEntity: TaskNote::class)] | |||||
| private Collection $taskNotes; | |||||
| public function __construct(Partner $partner, User $createdBy) | public function __construct(Partner $partner, User $createdBy) | ||||
| { | { | ||||
| $this->partner = $partner; | $this->partner = $partner; | ||||
| @@ -61,6 +64,7 @@ class Contact | |||||
| $this->createdAt = new \DateTimeImmutable(); | $this->createdAt = new \DateTimeImmutable(); | ||||
| $this->posts = new ArrayCollection(); | $this->posts = new ArrayCollection(); | ||||
| $this->contactPartnerProducts = new ArrayCollection(); | $this->contactPartnerProducts = new ArrayCollection(); | ||||
| $this->taskNotes = new ArrayCollection(); | |||||
| } | } | ||||
| public function getId(): ?int | public function getId(): ?int | ||||
| @@ -241,4 +245,34 @@ class Contact | |||||
| return $this; | return $this; | ||||
| } | } | ||||
| /** | |||||
| * @return Collection<int, TaskNote> | |||||
| */ | |||||
| public function getTaskNotes(): Collection | |||||
| { | |||||
| return $this->taskNotes; | |||||
| } | |||||
| public function addTaskNote(TaskNote $taskNote): static | |||||
| { | |||||
| if (!$this->taskNotes->contains($taskNote)) { | |||||
| $this->taskNotes->add($taskNote); | |||||
| $taskNote->setContact($this); | |||||
| } | |||||
| return $this; | |||||
| } | |||||
| public function removeTaskNote(TaskNote $taskNote): static | |||||
| { | |||||
| if ($this->taskNotes->removeElement($taskNote)) { | |||||
| // set the owning side to null (unless already changed) | |||||
| if ($taskNote->getContact() === $this) { | |||||
| $taskNote->setContact(null); | |||||
| } | |||||
| } | |||||
| return $this; | |||||
| } | |||||
| } | } | ||||
| @@ -26,12 +26,17 @@ class TaskNote | |||||
| #[ORM\JoinColumn(nullable: false, onDelete: 'CASCADE')] | #[ORM\JoinColumn(nullable: false, onDelete: 'CASCADE')] | ||||
| private ?Task $task = null; | private ?Task $task = null; | ||||
| #[ORM\ManyToOne(inversedBy: 'taskNotes')] | |||||
| private ?Contact $contact = null; | |||||
| #[ORM\Column(type: 'string', enumType: TaskNoteContactType::class)] | #[ORM\Column(type: 'string', enumType: TaskNoteContactType::class)] | ||||
| private TaskNoteContactType $type; | private TaskNoteContactType $type; | ||||
| #[ORM\Column] | #[ORM\Column] | ||||
| private ?\DateTimeImmutable $createdAt = null; | private ?\DateTimeImmutable $createdAt = null; | ||||
| public function __construct(User $owner, Task $task) | public function __construct(User $owner, Task $task) | ||||
| { | { | ||||
| $this->owner = $owner; | $this->owner = $owner; | ||||
| @@ -101,4 +106,16 @@ class TaskNote | |||||
| return $this; | return $this; | ||||
| } | } | ||||
| public function getContact(): ?Contact | |||||
| { | |||||
| return $this->contact; | |||||
| } | |||||
| public function setContact(?Contact $contact): static | |||||
| { | |||||
| $this->contact = $contact; | |||||
| return $this; | |||||
| } | |||||
| } | } | ||||
| @@ -53,6 +53,7 @@ final class TaskNoteFactory extends ModelFactory | |||||
| 'owner' => UserFactory::random(), | 'owner' => UserFactory::random(), | ||||
| 'task' => TaskFactory::random(), | 'task' => TaskFactory::random(), | ||||
| 'type' => self::faker()->randomElement(TaskNoteContactType::cases()), | 'type' => self::faker()->randomElement(TaskNoteContactType::cases()), | ||||
| 'contact' => ContactFactory::random() | |||||
| ]; | ]; | ||||
| } | } | ||||
| @@ -0,0 +1,63 @@ | |||||
| <?php | |||||
| /** | |||||
| * @author Daniel Knudsen <d.knudsen@spawntree.de> | |||||
| * @date 10.06.24 | |||||
| */ | |||||
| namespace App\Filter; | |||||
| use ApiPlatform\Doctrine\Orm\Filter\AbstractFilter; | |||||
| use ApiPlatform\Doctrine\Orm\Util\QueryNameGeneratorInterface; | |||||
| use Doctrine\ORM\QueryBuilder; | |||||
| use ApiPlatform\Metadata\ApiFilter; | |||||
| use Doctrine\Persistence\ManagerRegistry; | |||||
| use Psr\Log\LoggerInterface; | |||||
| #[ApiFilter(ContactNameFilter::class)] | |||||
| class ContactNameFilter extends AbstractFilter | |||||
| { | |||||
| public const FILTER_NAME = "nameSearch"; | |||||
| public function __construct(ManagerRegistry $managerRegistry, ?LoggerInterface $logger = null, ?array $properties = null) | |||||
| { | |||||
| parent::__construct($managerRegistry, $logger, $properties); | |||||
| } | |||||
| protected function filterProperty(string $property, $value, QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, ?\ApiPlatform\Metadata\Operation $operation = null, array $context = []): void | |||||
| { | |||||
| if ($property !== self::FILTER_NAME) { | |||||
| return; | |||||
| } | |||||
| if ($value) { | |||||
| // Alias für User-Tabelle | |||||
| $rootAlias = $queryBuilder->getRootAliases()[0]; | |||||
| // Case-insensitive Suche nach Vorname oder Nachname, der den Wert enthält | |||||
| $queryBuilder->andWhere( | |||||
| $queryBuilder->expr()->orX( | |||||
| $queryBuilder->expr()->like('LOWER(' . $rootAlias . '.firstName)', ':searchTerm'), | |||||
| $queryBuilder->expr()->like('LOWER(' . $rootAlias . '.lastName)', ':searchTerm') | |||||
| ) | |||||
| ) | |||||
| ->setParameter('searchTerm', '%' . strtolower($value) . '%'); | |||||
| } | |||||
| } | |||||
| public function getDescription(string $resourceClass): array | |||||
| { | |||||
| return [ | |||||
| self::FILTER_NAME => [ | |||||
| 'property' => self::FILTER_NAME, | |||||
| 'type' => 'string', | |||||
| 'required' => false, | |||||
| 'swagger' => [ | |||||
| 'description' => 'Filter users that have the search term in their first name or last name', | |||||
| 'type' => 'string', | |||||
| ], | |||||
| ], | |||||
| ]; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,61 @@ | |||||
| <?php | |||||
| /** | |||||
| * @author Daniel Knudsen <d.knudsen@spawntree.de> | |||||
| * @date 10.06.24 | |||||
| */ | |||||
| namespace App\Filter; | |||||
| use ApiPlatform\Doctrine\Orm\Filter\AbstractFilter; | |||||
| use ApiPlatform\Doctrine\Orm\Util\QueryNameGeneratorInterface; | |||||
| use Doctrine\ORM\Query\Expr\Join; | |||||
| use Doctrine\ORM\QueryBuilder; | |||||
| use ApiPlatform\Metadata\ApiFilter; | |||||
| use Doctrine\Persistence\ManagerRegistry; | |||||
| use Psr\Log\LoggerInterface; | |||||
| #[ApiFilter(PartnerProductUnassignedFilter::class)] | |||||
| class PartnerProductUnassignedFilter extends AbstractFilter | |||||
| { | |||||
| public const FILTER_NAME = "productPartnerUnassigned"; | |||||
| public function __construct(ManagerRegistry $managerRegistry, ?LoggerInterface $logger = null, ?array $properties = null) | |||||
| { | |||||
| parent::__construct($managerRegistry, $logger, $properties); | |||||
| } | |||||
| protected function filterProperty(string $property, $value, QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, ?\ApiPlatform\Metadata\Operation $operation = null, array $context = []): void | |||||
| { | |||||
| if ($property !== 'unassignedContactId') { | |||||
| return; | |||||
| } | |||||
| if ($value) { | |||||
| // Alias für das Produkt | |||||
| $rootAlias = $queryBuilder->getRootAliases()[0]; | |||||
| // Join mit PartnerProduct | |||||
| $queryBuilder->leftJoin("$rootAlias.contactPartnerProducts", 'cpp', Join::WITH, 'cpp.contact = :contact_id') | |||||
| ->andWhere('cpp.partnerProduct IS NULL') | |||||
| ->setParameter('contact_id', $value); | |||||
| } | |||||
| } | |||||
| public function getDescription(string $resourceClass): array | |||||
| { | |||||
| return [ | |||||
| self::FILTER_NAME => [ | |||||
| 'property' => self::FILTER_NAME, | |||||
| 'type' => 'boolean', | |||||
| 'required' => false, | |||||
| 'swagger' => [ | |||||
| 'description' => 'Filter partner products that are not assigned to a given contact id', | |||||
| 'type' => 'integer', // Hier wird der Datentyp auf 'integer' gesetzt | |||||
| 'format' => 'contact_id', // Optional: Format 'partner_id' angeben, um klarzustellen, dass es sich um eine Partner-ID handelt | |||||
| ], | |||||
| ], | |||||
| ]; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,61 @@ | |||||
| <?php | |||||
| /** | |||||
| * @author Daniel Knudsen <d.knudsen@spawntree.de> | |||||
| * @date 10.06.24 | |||||
| */ | |||||
| namespace App\Filter; | |||||
| use ApiPlatform\Doctrine\Orm\Filter\AbstractFilter; | |||||
| use ApiPlatform\Doctrine\Orm\Util\QueryNameGeneratorInterface; | |||||
| use Doctrine\ORM\Query\Expr\Join; | |||||
| use Doctrine\ORM\QueryBuilder; | |||||
| use ApiPlatform\Metadata\ApiFilter; | |||||
| use Doctrine\Persistence\ManagerRegistry; | |||||
| use Psr\Log\LoggerInterface; | |||||
| #[ApiFilter(ProductUnassignedFilter::class)] | |||||
| class ProductUnassignedFilter extends AbstractFilter | |||||
| { | |||||
| public const FILTER_NAME = "partnerIdUnassigned"; | |||||
| public function __construct(ManagerRegistry $managerRegistry, ?LoggerInterface $logger = null, ?array $properties = null) | |||||
| { | |||||
| parent::__construct($managerRegistry, $logger, $properties); | |||||
| } | |||||
| protected function filterProperty(string $property, $value, QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, ?\ApiPlatform\Metadata\Operation $operation = null, array $context = []): void | |||||
| { | |||||
| if ($property !== 'unassignedPartnerId') { | |||||
| return; | |||||
| } | |||||
| if ($value) { | |||||
| // Alias für das Produkt | |||||
| $rootAlias = $queryBuilder->getRootAliases()[0]; | |||||
| // Join mit PartnerProduct | |||||
| $queryBuilder->leftJoin("$rootAlias.partnerProducts", 'pp', Join::WITH, 'pp.partner = :partner_id') | |||||
| ->andWhere('pp.product IS NULL') | |||||
| ->setParameter('partner_id', $value); | |||||
| } | |||||
| } | |||||
| public function getDescription(string $resourceClass): array | |||||
| { | |||||
| return [ | |||||
| self::FILTER_NAME => [ | |||||
| 'property' => self::FILTER_NAME, | |||||
| 'type' => 'boolean', | |||||
| 'required' => false, | |||||
| 'swagger' => [ | |||||
| 'description' => 'Filter products that are not assigned to a given partner id', | |||||
| 'type' => 'integer', // Hier wird der Datentyp auf 'integer' gesetzt | |||||
| 'format' => 'partner_id', // Optional: Format 'partner_id' angeben, um klarzustellen, dass es sich um eine Partner-ID handelt | |||||
| ], | |||||
| ], | |||||
| ]; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,64 @@ | |||||
| <?php | |||||
| /** | |||||
| * @author Daniel Knudsen <d.knudsen@spawntree.de> | |||||
| * @date 10.06.24 | |||||
| */ | |||||
| namespace App\Filter; | |||||
| use ApiPlatform\Doctrine\Orm\Filter\AbstractFilter; | |||||
| use ApiPlatform\Doctrine\Orm\Util\QueryNameGeneratorInterface; | |||||
| use Doctrine\ORM\Query\Expr\Join; | |||||
| use Doctrine\ORM\QueryBuilder; | |||||
| use ApiPlatform\Metadata\ApiFilter; | |||||
| use Doctrine\Persistence\ManagerRegistry; | |||||
| use Psr\Log\LoggerInterface; | |||||
| #[ApiFilter(UserNameFilter::class)] | |||||
| class UserNameFilter extends AbstractFilter | |||||
| { | |||||
| public const FILTER_NAME = "nameSearch"; | |||||
| public function __construct(ManagerRegistry $managerRegistry, ?LoggerInterface $logger = null, ?array $properties = null) | |||||
| { | |||||
| parent::__construct($managerRegistry, $logger, $properties); | |||||
| } | |||||
| protected function filterProperty(string $property, $value, QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, ?\ApiPlatform\Metadata\Operation $operation = null, array $context = []): void | |||||
| { | |||||
| if ($property !== self::FILTER_NAME) { | |||||
| return; | |||||
| } | |||||
| if ($value) { | |||||
| // Alias für User-Tabelle | |||||
| $rootAlias = $queryBuilder->getRootAliases()[0]; | |||||
| // Case-insensitive Suche nach Vorname oder Nachname, der den Wert enthält | |||||
| $queryBuilder->andWhere( | |||||
| $queryBuilder->expr()->orX( | |||||
| $queryBuilder->expr()->like('LOWER(' . $rootAlias . '.firstName)', ':searchTerm'), | |||||
| $queryBuilder->expr()->like('LOWER(' . $rootAlias . '.lastName)', ':searchTerm') | |||||
| ) | |||||
| ) | |||||
| ->setParameter('searchTerm', '%' . strtolower($value) . '%'); | |||||
| } | |||||
| } | |||||
| public function getDescription(string $resourceClass): array | |||||
| { | |||||
| return [ | |||||
| self::FILTER_NAME => [ | |||||
| 'property' => 'firstLastName', | |||||
| 'type' => 'string', | |||||
| 'required' => false, | |||||
| 'swagger' => [ | |||||
| 'description' => 'Filter users that have the search term in their first name or last name', | |||||
| 'type' => 'string', | |||||
| ], | |||||
| ], | |||||
| ]; | |||||
| } | |||||
| } | |||||
| @@ -3,6 +3,7 @@ | |||||
| namespace App\Mapper; | namespace App\Mapper; | ||||
| use App\ApiResource\TaskNoteApi; | use App\ApiResource\TaskNoteApi; | ||||
| use App\Entity\Contact; | |||||
| use App\Entity\Task; | use App\Entity\Task; | ||||
| use App\Entity\TaskNote; | use App\Entity\TaskNote; | ||||
| use App\Entity\User; | use App\Entity\User; | ||||
| @@ -59,6 +60,12 @@ class TaskNoteApiToEntityMapper implements MapperInterface | |||||
| assert($entity instanceof TaskNote); | assert($entity instanceof TaskNote); | ||||
| $entity->setMessage($dto->message); | $entity->setMessage($dto->message); | ||||
| $entity->setType($dto->contactType); | $entity->setType($dto->contactType); | ||||
| if ($dto->contactIri) { | |||||
| $entity->setContact($this->microMapper->map($dto->contactIri, Contact::class, [ | |||||
| MicroMapperInterface::MAX_DEPTH => 1, | |||||
| ])); | |||||
| } | |||||
| return $entity; | return $entity; | ||||
| } | } | ||||
| @@ -2,6 +2,7 @@ | |||||
| namespace App\Mapper; | namespace App\Mapper; | ||||
| use App\ApiResource\ContactApi; | |||||
| use App\ApiResource\TaskApi; | use App\ApiResource\TaskApi; | ||||
| use App\ApiResource\TaskNoteApi; | use App\ApiResource\TaskNoteApi; | ||||
| use App\ApiResource\UserApi; | use App\ApiResource\UserApi; | ||||
| @@ -46,6 +47,14 @@ class TaskNoteEntityToApiMapper implements MapperInterface | |||||
| MicroMapperInterface::MAX_DEPTH => 1, | MicroMapperInterface::MAX_DEPTH => 1, | ||||
| ]); | ]); | ||||
| $dto->contact = $this->microMapper->map($entity->getContact(), ContactApi::class, [ | |||||
| MicroMapperInterface::MAX_DEPTH => 1, | |||||
| ]); | |||||
| $dto->contactIri = $this->microMapper->map($entity->getContact(), ContactApi::class, [ | |||||
| MicroMapperInterface::MAX_DEPTH => 1, | |||||
| ]); | |||||
| $dto->contactType = $entity->getType(); | $dto->contactType = $entity->getType(); | ||||
| $dto->createdAt = $entity->getCreatedAt(); | $dto->createdAt = $entity->getCreatedAt(); | ||||