| @@ -6611,6 +6611,7 @@ components: | |||
| - description | |||
| - assignedTo | |||
| - dueAt | |||
| - product | |||
| - prio | |||
| - completed | |||
| properties: | |||
| @@ -6674,6 +6675,17 @@ components: | |||
| - supplier | |||
| - service | |||
| - null | |||
| product: | |||
| type: | |||
| - string | |||
| - 'null' | |||
| format: iri-reference | |||
| example: 'https://example.com/' | |||
| productName: | |||
| readOnly: true | |||
| type: | |||
| - string | |||
| - 'null' | |||
| contact: | |||
| 'owl:maxCardinality': 1 | |||
| type: | |||
| @@ -6717,6 +6729,7 @@ components: | |||
| - description | |||
| - assignedTo | |||
| - dueAt | |||
| - product | |||
| - prio | |||
| - completed | |||
| properties: | |||
| @@ -6789,6 +6802,17 @@ components: | |||
| - supplier | |||
| - service | |||
| - null | |||
| product: | |||
| type: | |||
| - string | |||
| - 'null' | |||
| format: iri-reference | |||
| example: 'https://example.com/' | |||
| productName: | |||
| readOnly: true | |||
| type: | |||
| - string | |||
| - 'null' | |||
| contact: | |||
| 'owl:maxCardinality': 1 | |||
| type: | |||
| @@ -6832,6 +6856,7 @@ components: | |||
| - description | |||
| - assignedTo | |||
| - dueAt | |||
| - product | |||
| - prio | |||
| - completed | |||
| properties: | |||
| @@ -6918,6 +6943,17 @@ components: | |||
| - supplier | |||
| - service | |||
| - null | |||
| product: | |||
| type: | |||
| - string | |||
| - 'null' | |||
| format: iri-reference | |||
| example: 'https://example.com/' | |||
| productName: | |||
| readOnly: true | |||
| type: | |||
| - string | |||
| - 'null' | |||
| contact: | |||
| 'owl:maxCardinality': 1 | |||
| type: | |||
| @@ -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 Version20240515100541 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 ADD product_id INT NOT NULL'); | |||
| $this->addSql('ALTER TABLE task ADD CONSTRAINT FK_527EDB254584665A FOREIGN KEY (product_id) REFERENCES product (id)'); | |||
| $this->addSql('CREATE INDEX IDX_527EDB254584665A ON task (product_id)'); | |||
| } | |||
| public function down(Schema $schema): void | |||
| { | |||
| // this down() migration is auto-generated, please modify it to your needs | |||
| $this->addSql('ALTER TABLE task DROP FOREIGN KEY FK_527EDB254584665A'); | |||
| $this->addSql('DROP INDEX IDX_527EDB254584665A ON task'); | |||
| $this->addSql('ALTER TABLE task DROP product_id'); | |||
| } | |||
| } | |||
| @@ -24,6 +24,7 @@ use App\State\EntityClassDtoStateProcessor; | |||
| use App\State\EntityToDtoStateProvider; | |||
| use Symfony\Component\PropertyInfo\Type; | |||
| use Symfony\Component\Validator\Constraints as Assert; | |||
| use Symfony\Component\Validator\Constraints\NotBlank; | |||
| #[ApiResource( | |||
| shortName: 'Task', | |||
| @@ -90,6 +91,13 @@ class TaskApi | |||
| #[ApiProperty(writable: false)] | |||
| public ?PartnerType $partnerType = null; | |||
| #[ApiProperty(writable: true)] | |||
| #[NotBlank] | |||
| public ?ProductApi $product = null; | |||
| #[ApiProperty(writable: false)] | |||
| public ?string $productName = null; | |||
| public ?ContactApi $contact = null; | |||
| #[ApiProperty(writable: false)] | |||
| @@ -16,6 +16,9 @@ class Product | |||
| #[ORM\Column] | |||
| private ?int $id = null; | |||
| #[ORM\Column(type: "integer", unique: true, nullable: true, options: ["unsigned" => true])] | |||
| protected int $navisionId; | |||
| #[ORM\Column(length: 255)] | |||
| private ?string $name = null; | |||
| @@ -48,6 +51,9 @@ class Product | |||
| #[ORM\OneToMany(mappedBy: 'product', targetEntity: UserProduct::class)] | |||
| private Collection $userProducts; | |||
| #[ORM\OneToMany(mappedBy: 'product', targetEntity: Task::class)] | |||
| private Collection $tasks; | |||
| public function __construct(User $createdBy) | |||
| { | |||
| $this->createdBy = $createdBy; | |||
| @@ -57,6 +63,7 @@ class Product | |||
| $this->postings = new ArrayCollection(); | |||
| $this->partnerProducts = new ArrayCollection(); | |||
| $this->userProducts = new ArrayCollection(); | |||
| $this->tasks = new ArrayCollection(); | |||
| } | |||
| public function getId(): ?int | |||
| @@ -259,4 +266,34 @@ class Product | |||
| return $this; | |||
| } | |||
| /** | |||
| * @return Collection<int, Task> | |||
| */ | |||
| public function getTasks(): Collection | |||
| { | |||
| return $this->tasks; | |||
| } | |||
| public function addTask(Task $task): static | |||
| { | |||
| if (!$this->tasks->contains($task)) { | |||
| $this->tasks->add($task); | |||
| $task->setProduct($this); | |||
| } | |||
| return $this; | |||
| } | |||
| public function removeTask(Task $task): static | |||
| { | |||
| if ($this->tasks->removeElement($task)) { | |||
| // set the owning side to null (unless already changed) | |||
| if ($task->getProduct() === $this) { | |||
| $task->setProduct(null); | |||
| } | |||
| } | |||
| return $this; | |||
| } | |||
| } | |||
| @@ -34,6 +34,10 @@ class Task | |||
| #[ORM\Column] | |||
| private ?\DateTimeImmutable $dueAt = null; | |||
| #[ORM\ManyToOne(inversedBy: 'tasks')] | |||
| #[ORM\JoinColumn(nullable: false)] | |||
| private ?Product $product = null; | |||
| #[ORM\ManyToOne] | |||
| #[ORM\JoinColumn(nullable: true, onDelete: "CASCADE")] | |||
| private ?Partner $partner = null; | |||
| @@ -54,10 +58,11 @@ class Task | |||
| #[ORM\OneToMany(mappedBy: 'task', targetEntity: TaskNote::class)] | |||
| private Collection $taskNotes; | |||
| public function __construct(User $createdBy, User $assignedTo) | |||
| public function __construct(User $createdBy, User $assignedTo, Product $product) | |||
| { | |||
| $this->createdBy = $createdBy; | |||
| $this->assignedTo = $assignedTo; | |||
| $this->product = $product; | |||
| $this->createdAt = new \DateTimeImmutable(); | |||
| $this->taskNotes = new ArrayCollection(); | |||
| } | |||
| @@ -167,4 +172,16 @@ class Task | |||
| return $this->taskNotes; | |||
| } | |||
| public function getProduct(): ?Product | |||
| { | |||
| return $this->product; | |||
| } | |||
| public function setProduct(?Product $product): static | |||
| { | |||
| $this->product = $product; | |||
| return $this; | |||
| } | |||
| } | |||
| @@ -54,6 +54,7 @@ final class TaskFactory extends ModelFactory | |||
| 'description' => self::faker()->sentence(), | |||
| 'createdBy' => UserFactory::random(), | |||
| 'assignedTo' => UserFactory::random(), | |||
| 'product' => ProductFactory::random(), | |||
| 'partner' => PartnerFactory::random(), | |||
| 'contact' => $randomBoolean ? ContactFactory::random() : null, | |||
| 'dueAt' => \DateTimeImmutable::createFromMutable(self::faker()->dateTime()), | |||
| @@ -7,6 +7,7 @@ use App\ApiResource\PartnerApi; | |||
| use App\ApiResource\TaskApi; | |||
| use App\Entity\Contact; | |||
| use App\Entity\Partner; | |||
| use App\Entity\Product; | |||
| use App\Entity\Task; | |||
| use App\Entity\User; | |||
| use App\Repository\TaskRepository; | |||
| @@ -44,9 +45,17 @@ class TaskApiToEntityMapper implements MapperInterface | |||
| $entity->setAssignedTo($assignedTo); | |||
| } else { | |||
| $user = $this->security->getUser(); | |||
| if ($dto->product === null) { | |||
| throw new \Exception('Product missing'); | |||
| } | |||
| $product = $this->microMapper->map($dto->product, Product::class, [ | |||
| MicroMapperInterface::MAX_DEPTH => 1, | |||
| ]); | |||
| assert($product instanceof Product); | |||
| assert($user instanceof User); | |||
| assert($assignedTo instanceof User); | |||
| $entity = new Task($user, $assignedTo); | |||
| $entity = new Task($user, $assignedTo, $product); | |||
| } | |||
| if (!$entity) { | |||
| @@ -5,6 +5,7 @@ namespace App\Mapper; | |||
| use App\ApiResource\CommentApi; | |||
| use App\ApiResource\ContactApi; | |||
| use App\ApiResource\PartnerApi; | |||
| use App\ApiResource\ProductApi; | |||
| use App\ApiResource\TaskApi; | |||
| use App\ApiResource\TaskNoteApi; | |||
| use App\ApiResource\UserApi; | |||
| @@ -72,6 +73,11 @@ class TaskEntityToApiMapper implements MapperInterface | |||
| $dto->contactName = $entity->getContact()?->getFirstName()." ".$entity->getContact()?->getLastName(); | |||
| } | |||
| $dto->product = $this->microMapper->map($entity->getProduct(), ProductApi::class, [ | |||
| MicroMapperInterface::MAX_DEPTH => 1, | |||
| ]); | |||
| $dto->productName = $entity->getProduct()?->getName(); | |||
| $dto->taskNotes = array_map(function(TaskNote $taskNote) { | |||
| return $this->microMapper->map($taskNote, TaskNoteApi::class, [ | |||
| MicroMapperInterface::MAX_DEPTH => 1, | |||