| @@ -1,73 +0,0 @@ | |||||
| <?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 Version20240318162352 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('CREATE TABLE comment (id INT AUTO_INCREMENT NOT NULL, owner_id INT NOT NULL, posting_id INT NOT NULL, message LONGTEXT NOT NULL, created_at DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\', INDEX IDX_9474526C7E3C61F9 (owner_id), INDEX IDX_9474526C9AE985F6 (posting_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'); | |||||
| $this->addSql('CREATE TABLE contact (id INT AUTO_INCREMENT NOT NULL, partner_id INT NOT NULL, image_id INT DEFAULT NULL, first_name VARCHAR(255) NOT NULL, last_name VARCHAR(255) NOT NULL, birthday DATE DEFAULT NULL, position VARCHAR(255) DEFAULT NULL, phone VARCHAR(255) DEFAULT NULL, email VARCHAR(255) DEFAULT NULL, created_at DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\', INDEX IDX_4C62E6389393F8FE (partner_id), INDEX IDX_4C62E6383DA5256D (image_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'); | |||||
| $this->addSql('CREATE TABLE media_object (id INT AUTO_INCREMENT NOT NULL, file_path VARCHAR(255) DEFAULT NULL, created_at DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\', PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'); | |||||
| $this->addSql('CREATE TABLE partner (id INT AUTO_INCREMENT NOT NULL, logo_id INT DEFAULT NULL, name VARCHAR(255) NOT NULL, type VARCHAR(255) NOT NULL, street VARCHAR(255) DEFAULT NULL, street_no VARCHAR(255) DEFAULT NULL, zip VARCHAR(255) DEFAULT NULL, city VARCHAR(255) DEFAULT NULL, country VARCHAR(255) DEFAULT NULL, website VARCHAR(255) DEFAULT NULL, created_at DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\', INDEX IDX_312B3E16F98F144A (logo_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'); | |||||
| $this->addSql('CREATE TABLE posting (id INT AUTO_INCREMENT NOT NULL, owner_id INT NOT NULL, partner_id INT NOT NULL, contact_id INT DEFAULT NULL, headline VARCHAR(255) NOT NULL, message LONGTEXT NOT NULL, created_at DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\', INDEX IDX_BD275D737E3C61F9 (owner_id), INDEX IDX_BD275D739393F8FE (partner_id), INDEX IDX_BD275D73E7A1254A (contact_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'); | |||||
| $this->addSql('CREATE TABLE product (id INT AUTO_INCREMENT NOT NULL, image_id INT DEFAULT NULL, name VARCHAR(255) NOT NULL, description LONGTEXT DEFAULT NULL, created_at DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\', INDEX IDX_D34A04AD3DA5256D (image_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'); | |||||
| $this->addSql('CREATE TABLE task (id INT AUTO_INCREMENT NOT NULL, created_by_id INT NOT NULL, assigned_to_id INT NOT NULL, partner_id INT DEFAULT NULL, contact_id INT DEFAULT NULL, headline VARCHAR(255) NOT NULL, description LONGTEXT DEFAULT NULL, due_at DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\', prio VARCHAR(255) NOT NULL, completed TINYINT(1) NOT NULL, created_at DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\', INDEX IDX_527EDB25B03A8386 (created_by_id), INDEX IDX_527EDB25F4BD7827 (assigned_to_id), INDEX IDX_527EDB259393F8FE (partner_id), INDEX IDX_527EDB25E7A1254A (contact_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'); | |||||
| $this->addSql('CREATE TABLE `user` (id INT AUTO_INCREMENT NOT NULL, image_id INT DEFAULT NULL, email VARCHAR(180) NOT NULL, first_name VARCHAR(255) NOT NULL, last_name VARCHAR(255) NOT NULL, roles JSON NOT NULL COMMENT \'(DC2Type:json)\', password VARCHAR(255) NOT NULL, active TINYINT(1) NOT NULL, created_at DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\', UNIQUE INDEX UNIQ_8D93D649E7927C74 (email), INDEX IDX_8D93D6493DA5256D (image_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'); | |||||
| $this->addSql('ALTER TABLE comment ADD CONSTRAINT FK_9474526C7E3C61F9 FOREIGN KEY (owner_id) REFERENCES `user` (id)'); | |||||
| $this->addSql('ALTER TABLE comment ADD CONSTRAINT FK_9474526C9AE985F6 FOREIGN KEY (posting_id) REFERENCES posting (id) ON DELETE CASCADE'); | |||||
| $this->addSql('ALTER TABLE contact ADD CONSTRAINT FK_4C62E6389393F8FE FOREIGN KEY (partner_id) REFERENCES partner (id) ON DELETE CASCADE'); | |||||
| $this->addSql('ALTER TABLE contact ADD CONSTRAINT FK_4C62E6383DA5256D FOREIGN KEY (image_id) REFERENCES media_object (id) ON DELETE SET NULL'); | |||||
| $this->addSql('ALTER TABLE partner ADD CONSTRAINT FK_312B3E16F98F144A FOREIGN KEY (logo_id) REFERENCES media_object (id) ON DELETE SET NULL'); | |||||
| $this->addSql('ALTER TABLE posting ADD CONSTRAINT FK_BD275D737E3C61F9 FOREIGN KEY (owner_id) REFERENCES `user` (id) ON DELETE CASCADE'); | |||||
| $this->addSql('ALTER TABLE posting ADD CONSTRAINT FK_BD275D739393F8FE FOREIGN KEY (partner_id) REFERENCES partner (id) ON DELETE CASCADE'); | |||||
| $this->addSql('ALTER TABLE posting ADD CONSTRAINT FK_BD275D73E7A1254A FOREIGN KEY (contact_id) REFERENCES contact (id) ON DELETE CASCADE'); | |||||
| $this->addSql('ALTER TABLE product ADD CONSTRAINT FK_D34A04AD3DA5256D FOREIGN KEY (image_id) REFERENCES media_object (id) ON DELETE SET NULL'); | |||||
| $this->addSql('ALTER TABLE task ADD CONSTRAINT FK_527EDB25B03A8386 FOREIGN KEY (created_by_id) REFERENCES `user` (id)'); | |||||
| $this->addSql('ALTER TABLE task ADD CONSTRAINT FK_527EDB25F4BD7827 FOREIGN KEY (assigned_to_id) REFERENCES `user` (id)'); | |||||
| $this->addSql('ALTER TABLE task ADD CONSTRAINT FK_527EDB259393F8FE FOREIGN KEY (partner_id) REFERENCES partner (id) ON DELETE CASCADE'); | |||||
| $this->addSql('ALTER TABLE task ADD CONSTRAINT FK_527EDB25E7A1254A FOREIGN KEY (contact_id) REFERENCES contact (id) ON DELETE CASCADE'); | |||||
| $this->addSql('ALTER TABLE `user` ADD CONSTRAINT FK_8D93D6493DA5256D FOREIGN KEY (image_id) REFERENCES media_object (id) ON DELETE SET NULL'); | |||||
| } | |||||
| public function down(Schema $schema): void | |||||
| { | |||||
| // this down() migration is auto-generated, please modify it to your needs | |||||
| $this->addSql('ALTER TABLE comment DROP FOREIGN KEY FK_9474526C7E3C61F9'); | |||||
| $this->addSql('ALTER TABLE comment DROP FOREIGN KEY FK_9474526C9AE985F6'); | |||||
| $this->addSql('ALTER TABLE contact DROP FOREIGN KEY FK_4C62E6389393F8FE'); | |||||
| $this->addSql('ALTER TABLE contact DROP FOREIGN KEY FK_4C62E6383DA5256D'); | |||||
| $this->addSql('ALTER TABLE partner DROP FOREIGN KEY FK_312B3E16F98F144A'); | |||||
| $this->addSql('ALTER TABLE posting DROP FOREIGN KEY FK_BD275D737E3C61F9'); | |||||
| $this->addSql('ALTER TABLE posting DROP FOREIGN KEY FK_BD275D739393F8FE'); | |||||
| $this->addSql('ALTER TABLE posting DROP FOREIGN KEY FK_BD275D73E7A1254A'); | |||||
| $this->addSql('ALTER TABLE product DROP FOREIGN KEY FK_D34A04AD3DA5256D'); | |||||
| $this->addSql('ALTER TABLE task DROP FOREIGN KEY FK_527EDB25B03A8386'); | |||||
| $this->addSql('ALTER TABLE task DROP FOREIGN KEY FK_527EDB25F4BD7827'); | |||||
| $this->addSql('ALTER TABLE task DROP FOREIGN KEY FK_527EDB259393F8FE'); | |||||
| $this->addSql('ALTER TABLE task DROP FOREIGN KEY FK_527EDB25E7A1254A'); | |||||
| $this->addSql('ALTER TABLE `user` DROP FOREIGN KEY FK_8D93D6493DA5256D'); | |||||
| $this->addSql('DROP TABLE comment'); | |||||
| $this->addSql('DROP TABLE contact'); | |||||
| $this->addSql('DROP TABLE media_object'); | |||||
| $this->addSql('DROP TABLE partner'); | |||||
| $this->addSql('DROP TABLE posting'); | |||||
| $this->addSql('DROP TABLE product'); | |||||
| $this->addSql('DROP TABLE task'); | |||||
| $this->addSql('DROP TABLE `user`'); | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,49 @@ | |||||
| <?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 Version20240319114858 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 sale ADD CONSTRAINT FK_E54BC0057E3C61F9 FOREIGN KEY (owner_id) REFERENCES `user` (id) ON DELETE CASCADE'); | |||||
| $this->addSql('ALTER TABLE sale ADD CONSTRAINT FK_E54BC0059393F8FE FOREIGN KEY (partner_id) REFERENCES partner (id) ON DELETE CASCADE'); | |||||
| $this->addSql('ALTER TABLE sale ADD CONSTRAINT FK_E54BC0054584665A FOREIGN KEY (product_id) REFERENCES product (id) ON DELETE CASCADE'); | |||||
| $this->addSql('ALTER TABLE task ADD CONSTRAINT FK_527EDB25B03A8386 FOREIGN KEY (created_by_id) REFERENCES `user` (id)'); | |||||
| $this->addSql('ALTER TABLE task ADD CONSTRAINT FK_527EDB25F4BD7827 FOREIGN KEY (assigned_to_id) REFERENCES `user` (id)'); | |||||
| $this->addSql('ALTER TABLE task ADD CONSTRAINT FK_527EDB259393F8FE FOREIGN KEY (partner_id) REFERENCES partner (id) ON DELETE CASCADE'); | |||||
| $this->addSql('ALTER TABLE task ADD CONSTRAINT FK_527EDB25E7A1254A FOREIGN KEY (contact_id) REFERENCES contact (id) ON DELETE CASCADE'); | |||||
| $this->addSql('ALTER TABLE task_note ADD CONSTRAINT FK_BC0E6E6F7E3C61F9 FOREIGN KEY (owner_id) REFERENCES `user` (id) ON DELETE CASCADE'); | |||||
| $this->addSql('ALTER TABLE task_note ADD CONSTRAINT FK_BC0E6E6F8DB60186 FOREIGN KEY (task_id) REFERENCES task (id) ON DELETE CASCADE'); | |||||
| $this->addSql('ALTER TABLE user ADD CONSTRAINT FK_8D93D6493DA5256D FOREIGN KEY (image_id) REFERENCES media_object (id) ON DELETE SET NULL'); | |||||
| } | |||||
| 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_BC0E6E6F7E3C61F9'); | |||||
| $this->addSql('ALTER TABLE task_note DROP FOREIGN KEY FK_BC0E6E6F8DB60186'); | |||||
| $this->addSql('ALTER TABLE sale DROP FOREIGN KEY FK_E54BC0057E3C61F9'); | |||||
| $this->addSql('ALTER TABLE sale DROP FOREIGN KEY FK_E54BC0059393F8FE'); | |||||
| $this->addSql('ALTER TABLE sale DROP FOREIGN KEY FK_E54BC0054584665A'); | |||||
| $this->addSql('ALTER TABLE task DROP FOREIGN KEY FK_527EDB25B03A8386'); | |||||
| $this->addSql('ALTER TABLE task DROP FOREIGN KEY FK_527EDB25F4BD7827'); | |||||
| $this->addSql('ALTER TABLE task DROP FOREIGN KEY FK_527EDB259393F8FE'); | |||||
| $this->addSql('ALTER TABLE task DROP FOREIGN KEY FK_527EDB25E7A1254A'); | |||||
| $this->addSql('ALTER TABLE `user` DROP FOREIGN KEY FK_8D93D6493DA5256D'); | |||||
| } | |||||
| } | |||||
| @@ -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\Comment; | use App\Entity\Comment; | ||||
| use App\Interface\OwnerInterface; | |||||
| use App\State\EntityClassDtoStateProcessor; | use App\State\EntityClassDtoStateProcessor; | ||||
| use App\State\EntityToDtoStateProvider; | use App\State\EntityToDtoStateProvider; | ||||
| use ApiPlatform\Metadata\Delete; | use ApiPlatform\Metadata\Delete; | ||||
| @@ -30,7 +31,9 @@ use Symfony\Component\Validator\Constraints\NotBlank; | |||||
| new Get( | new Get( | ||||
| security: 'is_granted("ROLE_USER")' | security: 'is_granted("ROLE_USER")' | ||||
| ), | ), | ||||
| new GetCollection(), | |||||
| new GetCollection( | |||||
| security: 'is_granted("ROLE_USER")', | |||||
| ), | |||||
| new Post( | new Post( | ||||
| security: 'is_granted("ROLE_USER")', | security: 'is_granted("ROLE_USER")', | ||||
| ), | ), | ||||
| @@ -47,7 +50,7 @@ use Symfony\Component\Validator\Constraints\NotBlank; | |||||
| stateOptions: new Options(entityClass: Comment::class), | stateOptions: new Options(entityClass: Comment::class), | ||||
| )] | )] | ||||
| #[ApiFilter(SearchFilter::class, properties: ['partner' => 'exact', 'contact' => 'exact'])] | #[ApiFilter(SearchFilter::class, properties: ['partner' => 'exact', 'contact' => 'exact'])] | ||||
| class CommentApi | |||||
| class CommentApi implements OwnerInterface | |||||
| { | { | ||||
| #[ApiProperty(readable: false, writable: false, identifier: true)] | #[ApiProperty(readable: false, writable: false, identifier: true)] | ||||
| public ?int $id = null; | public ?int $id = null; | ||||
| @@ -66,4 +69,8 @@ class CommentApi | |||||
| #[ApiProperty(writable: false)] | #[ApiProperty(writable: false)] | ||||
| public ?\DateTimeImmutable $createdAt = null; | public ?\DateTimeImmutable $createdAt = null; | ||||
| public function getOwner(): UserApi | |||||
| { | |||||
| return $this->owner; | |||||
| } | |||||
| } | } | ||||
| @@ -83,7 +83,7 @@ class PartnerApi | |||||
| public ?\DateTimeImmutable $createdAt = null; | public ?\DateTimeImmutable $createdAt = null; | ||||
| /** | /** | ||||
| * @var array<int, PostingApi> | |||||
| * @var $posts array<int, PostingApi> | |||||
| */ | */ | ||||
| #[ApiProperty(writable: false)] | #[ApiProperty(writable: false)] | ||||
| public array $posts = []; | public array $posts = []; | ||||
| @@ -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\Posting; | use App\Entity\Posting; | ||||
| use App\Interface\OwnerInterface; | |||||
| use App\State\EntityClassDtoStateProcessor; | use App\State\EntityClassDtoStateProcessor; | ||||
| use App\State\EntityToDtoStateProvider; | use App\State\EntityToDtoStateProvider; | ||||
| use ApiPlatform\Metadata\Delete; | use ApiPlatform\Metadata\Delete; | ||||
| @@ -53,7 +54,7 @@ use Symfony\Component\Validator\Constraints\NotBlank; | |||||
| stateOptions: new Options(entityClass: Posting::class) | stateOptions: new Options(entityClass: Posting::class) | ||||
| )] | )] | ||||
| #[ApiFilter(SearchFilter::class, properties: ['partner' => 'exact', 'contact' => 'exact'])] | #[ApiFilter(SearchFilter::class, properties: ['partner' => 'exact', 'contact' => 'exact'])] | ||||
| class PostingApi | |||||
| class PostingApi implements OwnerInterface | |||||
| { | { | ||||
| #[ApiProperty(readable: false, writable: false, identifier: true)] | #[ApiProperty(readable: false, writable: false, identifier: true)] | ||||
| public ?int $id = null; | public ?int $id = null; | ||||
| @@ -81,6 +82,10 @@ class PostingApi | |||||
| #[Groups(['posting:create'])] | #[Groups(['posting:create'])] | ||||
| public ?ContactApi $contact = null; | public ?ContactApi $contact = null; | ||||
| #[ApiProperty(writable: true)] | |||||
| #[Groups(['posting:create'])] | |||||
| public ?SaleApi $sale = null; | |||||
| /** | /** | ||||
| * @var $comments array<int, CommentApi> | * @var $comments array<int, CommentApi> | ||||
| */ | */ | ||||
| @@ -101,4 +106,8 @@ class PostingApi | |||||
| #[ApiProperty(writable: false)] | #[ApiProperty(writable: false)] | ||||
| public ?\DateTimeImmutable $createdAt = null; | public ?\DateTimeImmutable $createdAt = null; | ||||
| public function getOwner(): UserApi | |||||
| { | |||||
| return $this->owner; | |||||
| } | |||||
| } | } | ||||
| @@ -0,0 +1,92 @@ | |||||
| <?php | |||||
| /** | |||||
| * @author Daniel Knudsen <d.knudsen@spawntree.de> | |||||
| * @date 12.12.23 | |||||
| */ | |||||
| namespace App\ApiResource; | |||||
| use ApiPlatform\Doctrine\Orm\Filter\SearchFilter; | |||||
| use ApiPlatform\Doctrine\Orm\State\Options; | |||||
| use ApiPlatform\Metadata\ApiFilter; | |||||
| use ApiPlatform\Metadata\ApiProperty; | |||||
| use ApiPlatform\Metadata\ApiResource; | |||||
| use App\Entity\Sale; | |||||
| use App\Interface\OwnerInterface; | |||||
| use App\State\EntityClassDtoStateProcessor; | |||||
| use App\State\EntityToDtoStateProvider; | |||||
| use ApiPlatform\Metadata\Delete; | |||||
| use ApiPlatform\Metadata\Get; | |||||
| use ApiPlatform\Metadata\GetCollection; | |||||
| use ApiPlatform\Metadata\Patch; | |||||
| use ApiPlatform\Metadata\Post; | |||||
| use Symfony\Component\Validator\Constraints\NotBlank; | |||||
| #[ApiResource( | |||||
| shortName: 'Sale', | |||||
| operations: [ | |||||
| new Get( | |||||
| security: 'is_granted("ROLE_USER")' | |||||
| ), | |||||
| new GetCollection(), | |||||
| new Post( | |||||
| security: 'is_granted("ROLE_USER")', | |||||
| ), | |||||
| new Patch( | |||||
| security: 'is_granted("EDIT", object)', | |||||
| ), | |||||
| new Delete( | |||||
| security: 'is_granted("ROLE_ADMIN")', | |||||
| ) | |||||
| ], | |||||
| security: 'is_granted("ROLE_USER")', | |||||
| provider: EntityToDtoStateProvider::class, | |||||
| processor: EntityClassDtoStateProcessor::class, | |||||
| stateOptions: new Options(entityClass: Sale::class), | |||||
| )] | |||||
| #[ApiFilter(SearchFilter::class, properties: ['owner' => 'exact', 'partner' => 'exact'])] | |||||
| class SaleApi implements OwnerInterface | |||||
| { | |||||
| #[ApiProperty(readable: false, writable: false, identifier: true)] | |||||
| public ?int $id = null; | |||||
| #[ApiProperty(writable: false)] | |||||
| public ?UserApi $owner = null; | |||||
| #[ApiProperty(writable: false)] | |||||
| public ?string $ownerName = null; | |||||
| #[ApiProperty(writable: true)] | |||||
| public ?PartnerApi $partner = null; | |||||
| #[ApiProperty(writable: false)] | |||||
| public ?string $partnerName = null; | |||||
| #[ApiProperty(writable: true)] | |||||
| public ?ProductApi $product = null; | |||||
| #[ApiProperty(writable: false)] | |||||
| public ?string $productName = null; | |||||
| #[NotBlank] | |||||
| public ?int $turnover = null; | |||||
| public ?int $profit = null; | |||||
| public ?string $comment = null; | |||||
| #[ApiProperty(writable: false)] | |||||
| public ?\DateTimeImmutable $createdAt = null; | |||||
| /** | |||||
| * @var $posts array<int, PostingApi> | |||||
| */ | |||||
| #[ApiProperty(writable: false)] | |||||
| public array $posts = []; | |||||
| public function getOwner(): UserApi | |||||
| { | |||||
| return $this->owner; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,47 @@ | |||||
| <?php | |||||
| /** | |||||
| * @author Daniel Knudsen <d.knudsen@spawntree.de> | |||||
| * @date 12.12.23 | |||||
| */ | |||||
| namespace App\ApiResource; | |||||
| use ApiPlatform\Doctrine\Orm\Filter\SearchFilter; | |||||
| use ApiPlatform\Doctrine\Orm\State\Options; | |||||
| use ApiPlatform\Metadata\ApiFilter; | |||||
| use ApiPlatform\Metadata\ApiProperty; | |||||
| use ApiPlatform\Metadata\ApiResource; | |||||
| use App\Entity\Comment; | |||||
| use App\Interface\OwnerInterface; | |||||
| use App\State\EntityClassDtoStateProcessor; | |||||
| use App\State\EntityToDtoStateProvider; | |||||
| use ApiPlatform\Metadata\Delete; | |||||
| use ApiPlatform\Metadata\Get; | |||||
| use ApiPlatform\Metadata\GetCollection; | |||||
| use ApiPlatform\Metadata\Patch; | |||||
| use ApiPlatform\Metadata\Post; | |||||
| use App\State\SaleSummaryStateProvider; | |||||
| use App\Validator\IsValidOwner; | |||||
| use Symfony\Component\PropertyInfo\Type; | |||||
| use Symfony\Component\Serializer\Attribute\Groups; | |||||
| use Symfony\Component\Validator\Constraints\NotBlank; | |||||
| #[ApiResource( | |||||
| shortName: 'SaleSummary', | |||||
| operations: [ | |||||
| new GetCollection(), | |||||
| ], | |||||
| security: 'is_granted("ROLE_USER")', | |||||
| provider: SaleSummaryStateProvider::class, | |||||
| )] | |||||
| class SaleSummary | |||||
| { | |||||
| public ?UserApi $owner = null; | |||||
| public ?string $ownerName = null; | |||||
| public ?int $turnover = null; | |||||
| public ?int $profit = null; | |||||
| } | |||||
| @@ -21,6 +21,7 @@ use App\Entity\Task; | |||||
| use App\Enum\PrioType; | use App\Enum\PrioType; | ||||
| use App\State\EntityClassDtoStateProcessor; | use App\State\EntityClassDtoStateProcessor; | ||||
| use App\State\EntityToDtoStateProvider; | use App\State\EntityToDtoStateProvider; | ||||
| use Symfony\Component\PropertyInfo\Type; | |||||
| use Symfony\Component\Validator\Constraints as Assert; | use Symfony\Component\Validator\Constraints as Assert; | ||||
| #[ApiResource( | #[ApiResource( | ||||
| @@ -49,7 +50,11 @@ use Symfony\Component\Validator\Constraints as Assert; | |||||
| processor: EntityClassDtoStateProcessor::class, | processor: EntityClassDtoStateProcessor::class, | ||||
| stateOptions: new Options(entityClass: Task::class), | stateOptions: new Options(entityClass: Task::class), | ||||
| )] | )] | ||||
| #[ApiFilter(SearchFilter::class, properties: ['partner' => 'exact', 'contact' => 'exact'])] | |||||
| #[ApiFilter(SearchFilter::class, properties: [ | |||||
| 'owner' => 'exact', | |||||
| 'partner' => 'exact', | |||||
| 'contact' => 'exact' | |||||
| ])] | |||||
| class TaskApi | class TaskApi | ||||
| { | { | ||||
| #[ApiProperty(readable: false, writable: false, identifier: true)] | #[ApiProperty(readable: false, writable: false, identifier: true)] | ||||
| @@ -89,8 +94,26 @@ class TaskApi | |||||
| #[Assert\NotBlank] | #[Assert\NotBlank] | ||||
| public PrioType $prio; | public PrioType $prio; | ||||
| #[Assert\NotNull] | |||||
| public ?bool $completed = null; | public ?bool $completed = null; | ||||
| /** | |||||
| * @var $taskNotes array<int, TaskNoteApi> | |||||
| */ | |||||
| #[ApiProperty( | |||||
| readableLink: true, | |||||
| writableLink: true, | |||||
| builtinTypes: [ | |||||
| new Type( | |||||
| 'object', | |||||
| collection: true, | |||||
| collectionKeyType: [new Type('int')], | |||||
| collectionValueType: new Type('object', class: TaskNoteApi::class) | |||||
| ) | |||||
| ] | |||||
| )] | |||||
| public array $taskNotes = []; | |||||
| #[ApiProperty(writable: false)] | #[ApiProperty(writable: false)] | ||||
| public ?\DateTimeImmutable $createdAt = null; | public ?\DateTimeImmutable $createdAt = null; | ||||
| @@ -0,0 +1,72 @@ | |||||
| <?php | |||||
| /** | |||||
| * @author Daniel Knudsen <d.knudsen@spawntree.de> | |||||
| * @date 12.12.23 | |||||
| */ | |||||
| namespace App\ApiResource; | |||||
| use ApiPlatform\Doctrine\Orm\Filter\SearchFilter; | |||||
| use ApiPlatform\Doctrine\Orm\State\Options; | |||||
| use ApiPlatform\Metadata\ApiFilter; | |||||
| use ApiPlatform\Metadata\ApiProperty; | |||||
| use ApiPlatform\Metadata\ApiResource; | |||||
| use App\Entity\TaskNote; | |||||
| use App\Interface\OwnerInterface; | |||||
| use App\State\EntityClassDtoStateProcessor; | |||||
| use App\State\EntityToDtoStateProvider; | |||||
| use ApiPlatform\Metadata\Delete; | |||||
| use ApiPlatform\Metadata\Get; | |||||
| use ApiPlatform\Metadata\GetCollection; | |||||
| use ApiPlatform\Metadata\Patch; | |||||
| use ApiPlatform\Metadata\Post; | |||||
| use Symfony\Component\Validator\Constraints\NotBlank; | |||||
| #[ApiResource( | |||||
| shortName: 'TaskNote', | |||||
| operations: [ | |||||
| new Get( | |||||
| security: 'is_granted("ROLE_USER")' | |||||
| ), | |||||
| new GetCollection(), | |||||
| new Post( | |||||
| security: 'is_granted("ROLE_USER")', | |||||
| ), | |||||
| new Patch( | |||||
| security: 'is_granted("EDIT", object)', | |||||
| ), | |||||
| new Delete( | |||||
| security: 'is_granted("ROLE_ADMIN")', | |||||
| ) | |||||
| ], | |||||
| security: 'is_granted("ROLE_USER")', | |||||
| provider: EntityToDtoStateProvider::class, | |||||
| processor: EntityClassDtoStateProcessor::class, | |||||
| stateOptions: new Options(entityClass: TaskNote::class), | |||||
| )] | |||||
| #[ApiFilter(SearchFilter::class, properties: ['task' => 'exact'])] | |||||
| class TaskNoteApi implements OwnerInterface | |||||
| { | |||||
| #[ApiProperty(readable: false, writable: false, identifier: true)] | |||||
| public ?int $id = null; | |||||
| #[NotBlank] | |||||
| public ?string $message = null; | |||||
| #[ApiProperty(writable: false)] | |||||
| public ?UserApi $owner = null; | |||||
| #[ApiProperty(writable: false)] | |||||
| public ?string $ownerName = null; | |||||
| public ?TaskApi $task = null; | |||||
| #[ApiProperty(writable: false)] | |||||
| public ?\DateTimeImmutable $createdAt = null; | |||||
| public function getOwner(): UserApi | |||||
| { | |||||
| return $this->owner; | |||||
| } | |||||
| } | |||||
| @@ -16,6 +16,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\Interface\AdminOrOwnerInterface; | |||||
| use App\State\EntityClassDtoStateProcessor; | use App\State\EntityClassDtoStateProcessor; | ||||
| use App\State\EntityToDtoStateProvider; | use App\State\EntityToDtoStateProvider; | ||||
| use Symfony\Component\Validator\Constraints as Assert; | use Symfony\Component\Validator\Constraints as Assert; | ||||
| @@ -76,4 +77,5 @@ class UserApi | |||||
| #[ApiProperty(writable: false)] | #[ApiProperty(writable: false)] | ||||
| public ?\DateTimeImmutable $createdAt = null; | public ?\DateTimeImmutable $createdAt = null; | ||||
| } | } | ||||
| @@ -11,7 +11,9 @@ use App\Factory\MediaObjectUserFactory; | |||||
| use App\Factory\PartnerFactory; | use App\Factory\PartnerFactory; | ||||
| use App\Factory\PostingFactory; | use App\Factory\PostingFactory; | ||||
| use App\Factory\ProductFactory; | use App\Factory\ProductFactory; | ||||
| use App\Factory\SaleFactory; | |||||
| use App\Factory\TaskFactory; | use App\Factory\TaskFactory; | ||||
| use App\Factory\TaskNoteFactory; | |||||
| use App\Factory\UserFactory; | use App\Factory\UserFactory; | ||||
| use Doctrine\Bundle\FixturesBundle\Fixture; | use Doctrine\Bundle\FixturesBundle\Fixture; | ||||
| use Doctrine\Persistence\ObjectManager; | use Doctrine\Persistence\ObjectManager; | ||||
| @@ -64,10 +66,12 @@ class AppFixtures extends Fixture | |||||
| PartnerFactory::createMany(100); | PartnerFactory::createMany(100); | ||||
| MediaObjectContactFactory::createMany(50); | MediaObjectContactFactory::createMany(50); | ||||
| ContactFactory::createMany(200); | ContactFactory::createMany(200); | ||||
| ProductFactory::createMany(100); | |||||
| SaleFactory::createMany(50); | |||||
| PostingFactory::createMany(200); | PostingFactory::createMany(200); | ||||
| CommentFactory::createMany(300); | CommentFactory::createMany(300); | ||||
| MediaObjectProductFactory::createMany(50); | MediaObjectProductFactory::createMany(50); | ||||
| ProductFactory::createMany(100); | |||||
| TaskFactory::createMany(50); | TaskFactory::createMany(50); | ||||
| TaskNoteFactory::createMany(100); | |||||
| } | } | ||||
| } | } | ||||
| @@ -54,11 +54,15 @@ class Partner | |||||
| #[ORM\OneToMany(mappedBy: 'partner', targetEntity: Posting::class)] | #[ORM\OneToMany(mappedBy: 'partner', targetEntity: Posting::class)] | ||||
| private Collection $postings; | private Collection $postings; | ||||
| #[ORM\OneToMany(mappedBy: 'partner', targetEntity: Sale::class)] | |||||
| private Collection $sales; | |||||
| public function __construct() | public function __construct() | ||||
| { | { | ||||
| $this->createdAt = new \DateTimeImmutable(); | $this->createdAt = new \DateTimeImmutable(); | ||||
| $this->contacts = new ArrayCollection(); | $this->contacts = new ArrayCollection(); | ||||
| $this->postings = new ArrayCollection(); | $this->postings = new ArrayCollection(); | ||||
| $this->sales = new ArrayCollection(); | |||||
| } | } | ||||
| public function getId(): ?int | public function getId(): ?int | ||||
| @@ -206,4 +210,34 @@ class Partner | |||||
| return $this; | return $this; | ||||
| } | } | ||||
| /** | |||||
| * @return Collection<int, Sale> | |||||
| */ | |||||
| public function getSales(): Collection | |||||
| { | |||||
| return $this->sales; | |||||
| } | |||||
| public function addSale(Sale $sale): static | |||||
| { | |||||
| if (!$this->sales->contains($sale)) { | |||||
| $this->sales->add($sale); | |||||
| $sale->setPartner($this); | |||||
| } | |||||
| return $this; | |||||
| } | |||||
| public function removeSale(Sale $sale): static | |||||
| { | |||||
| if ($this->sales->removeElement($sale)) { | |||||
| // set the owning side to null (unless already changed) | |||||
| if ($sale->getPartner() === $this) { | |||||
| $sale->setPartner(null); | |||||
| } | |||||
| } | |||||
| return $this; | |||||
| } | |||||
| } | } | ||||
| @@ -34,17 +34,26 @@ class Posting | |||||
| #[ORM\JoinColumn(nullable: true, onDelete: "CASCADE")] | #[ORM\JoinColumn(nullable: true, onDelete: "CASCADE")] | ||||
| private ?Contact $contact = null; | private ?Contact $contact = null; | ||||
| #[ORM\ManyToOne(inversedBy: 'postings')] | |||||
| #[ORM\JoinColumn(nullable: true, onDelete: "CASCADE")] | |||||
| private ?Sale $sale = null; | |||||
| #[ORM\Column] | #[ORM\Column] | ||||
| private ?\DateTimeImmutable $createdAt = null; | private ?\DateTimeImmutable $createdAt = null; | ||||
| #[ORM\OneToMany(mappedBy: 'posting', targetEntity: Comment::class)] | #[ORM\OneToMany(mappedBy: 'posting', targetEntity: Comment::class)] | ||||
| private Collection $comments; | private Collection $comments; | ||||
| public function __construct(User $owner, Partner $partner, Contact $contact = null) | |||||
| { | |||||
| public function __construct( | |||||
| User $owner, | |||||
| Partner $partner, | |||||
| Contact $contact = null, | |||||
| Sale $sale = null | |||||
| ) { | |||||
| $this->owner = $owner; | $this->owner = $owner; | ||||
| $this->partner = $partner; | $this->partner = $partner; | ||||
| $this->contact = $contact; | $this->contact = $contact; | ||||
| $this->sale = $sale; | |||||
| $this->createdAt = new \DateTimeImmutable(); | $this->createdAt = new \DateTimeImmutable(); | ||||
| $this->comments = new ArrayCollection(); | $this->comments = new ArrayCollection(); | ||||
| } | } | ||||
| @@ -146,4 +155,16 @@ class Posting | |||||
| return $this; | return $this; | ||||
| } | } | ||||
| public function getSale(): ?Sale | |||||
| { | |||||
| return $this->sale; | |||||
| } | |||||
| public function setSale(?Sale $sale): static | |||||
| { | |||||
| $this->sale = $sale; | |||||
| return $this; | |||||
| } | |||||
| } | } | ||||
| @@ -3,6 +3,8 @@ | |||||
| namespace App\Entity; | namespace App\Entity; | ||||
| use App\Repository\ProductRepository; | use App\Repository\ProductRepository; | ||||
| use Doctrine\Common\Collections\ArrayCollection; | |||||
| use Doctrine\Common\Collections\Collection; | |||||
| use Doctrine\DBAL\Types\Types; | use Doctrine\DBAL\Types\Types; | ||||
| use Doctrine\ORM\Mapping as ORM; | use Doctrine\ORM\Mapping as ORM; | ||||
| @@ -27,9 +29,13 @@ class Product | |||||
| #[ORM\Column] | #[ORM\Column] | ||||
| private ?\DateTimeImmutable $createdAt = null; | private ?\DateTimeImmutable $createdAt = null; | ||||
| #[ORM\OneToMany(mappedBy: 'product', targetEntity: Sale::class)] | |||||
| private Collection $sales; | |||||
| public function __construct() | public function __construct() | ||||
| { | { | ||||
| $this->createdAt = new \DateTimeImmutable(); | $this->createdAt = new \DateTimeImmutable(); | ||||
| $this->sales = new ArrayCollection(); | |||||
| } | } | ||||
| public function getId(): ?int | public function getId(): ?int | ||||
| @@ -77,4 +83,34 @@ class Product | |||||
| { | { | ||||
| return $this->createdAt; | return $this->createdAt; | ||||
| } | } | ||||
| /** | |||||
| * @return Collection<int, Sale> | |||||
| */ | |||||
| public function getSales(): Collection | |||||
| { | |||||
| return $this->sales; | |||||
| } | |||||
| public function addSale(Sale $sale): static | |||||
| { | |||||
| if (!$this->sales->contains($sale)) { | |||||
| $this->sales->add($sale); | |||||
| $sale->setProduct($this); | |||||
| } | |||||
| return $this; | |||||
| } | |||||
| public function removeSale(Sale $sale): static | |||||
| { | |||||
| if ($this->sales->removeElement($sale)) { | |||||
| // set the owning side to null (unless already changed) | |||||
| if ($sale->getProduct() === $this) { | |||||
| $sale->setProduct(null); | |||||
| } | |||||
| } | |||||
| return $this; | |||||
| } | |||||
| } | } | ||||
| @@ -0,0 +1,173 @@ | |||||
| <?php | |||||
| namespace App\Entity; | |||||
| use App\Repository\SaleRepository; | |||||
| use Doctrine\Common\Collections\ArrayCollection; | |||||
| use Doctrine\Common\Collections\Collection; | |||||
| use Doctrine\DBAL\Types\Types; | |||||
| use Doctrine\ORM\Mapping as ORM; | |||||
| #[ORM\Entity(repositoryClass: SaleRepository::class)] | |||||
| class Sale | |||||
| { | |||||
| #[ORM\Id] | |||||
| #[ORM\GeneratedValue] | |||||
| #[ORM\Column] | |||||
| private ?int $id = null; | |||||
| #[ORM\ManyToOne(inversedBy: 'sales')] | |||||
| #[ORM\JoinColumn(nullable: false, onDelete: "CASCADE")] | |||||
| private ?User $owner = null; | |||||
| #[ORM\ManyToOne(inversedBy: 'sales')] | |||||
| #[ORM\JoinColumn(nullable: false, onDelete: "CASCADE")] | |||||
| private ?Partner $partner = null; | |||||
| #[ORM\ManyToOne(inversedBy: 'sales')] | |||||
| #[ORM\JoinColumn(nullable: false, onDelete: "CASCADE")] | |||||
| private ?Product $product = null; | |||||
| #[ORM\Column] | |||||
| private ?int $turnover = null; | |||||
| #[ORM\Column(nullable: true)] | |||||
| private ?int $profit = null; | |||||
| #[ORM\Column(type: Types::TEXT, nullable: true)] | |||||
| private ?string $comment = null; | |||||
| #[ORM\Column] | |||||
| private ?\DateTimeImmutable $createdAt = null; | |||||
| #[ORM\OneToMany(mappedBy: 'sale', targetEntity: Posting::class)] | |||||
| private Collection $postings; | |||||
| public function __construct(User $owner, Partner $partner, Product $product) | |||||
| { | |||||
| $this->owner = $owner; | |||||
| $this->partner = $partner; | |||||
| $this->product = $product; | |||||
| $this->createdAt = new \DateTimeImmutable(); | |||||
| $this->postings = new ArrayCollection(); | |||||
| } | |||||
| public function getId(): ?int | |||||
| { | |||||
| return $this->id; | |||||
| } | |||||
| public function getOwner(): ?User | |||||
| { | |||||
| return $this->owner; | |||||
| } | |||||
| public function setOwner(?User $owner): static | |||||
| { | |||||
| $this->owner = $owner; | |||||
| return $this; | |||||
| } | |||||
| public function getPartner(): ?Partner | |||||
| { | |||||
| return $this->partner; | |||||
| } | |||||
| public function setPartner(?Partner $partner): static | |||||
| { | |||||
| $this->partner = $partner; | |||||
| return $this; | |||||
| } | |||||
| public function getProduct(): ?Product | |||||
| { | |||||
| return $this->product; | |||||
| } | |||||
| public function setProduct(?Product $product): static | |||||
| { | |||||
| $this->product = $product; | |||||
| return $this; | |||||
| } | |||||
| public function getTurnover(): ?int | |||||
| { | |||||
| return $this->turnover; | |||||
| } | |||||
| public function setTurnover(int $turnover): static | |||||
| { | |||||
| $this->turnover = $turnover; | |||||
| return $this; | |||||
| } | |||||
| public function getProfit(): ?int | |||||
| { | |||||
| return $this->profit; | |||||
| } | |||||
| public function setProfit(?int $profit): static | |||||
| { | |||||
| $this->profit = $profit; | |||||
| return $this; | |||||
| } | |||||
| public function getComment(): ?string | |||||
| { | |||||
| return $this->comment; | |||||
| } | |||||
| public function setComment(?string $comment): static | |||||
| { | |||||
| $this->comment = $comment; | |||||
| return $this; | |||||
| } | |||||
| public function getCreatedAt(): ?\DateTimeImmutable | |||||
| { | |||||
| return $this->createdAt; | |||||
| } | |||||
| public function setCreatedAt(\DateTimeImmutable $createdAt): static | |||||
| { | |||||
| $this->createdAt = $createdAt; | |||||
| return $this; | |||||
| } | |||||
| /** | |||||
| * @return Collection<int, Posting> | |||||
| */ | |||||
| public function getPostings(): Collection | |||||
| { | |||||
| return $this->postings; | |||||
| } | |||||
| public function addPosting(Posting $posting): static | |||||
| { | |||||
| if (!$this->postings->contains($posting)) { | |||||
| $this->postings->add($posting); | |||||
| $posting->setSale($this); | |||||
| } | |||||
| return $this; | |||||
| } | |||||
| public function removePosting(Posting $posting): static | |||||
| { | |||||
| if ($this->postings->removeElement($posting)) { | |||||
| // set the owning side to null (unless already changed) | |||||
| if ($posting->getSale() === $this) { | |||||
| $posting->setSale(null); | |||||
| } | |||||
| } | |||||
| return $this; | |||||
| } | |||||
| } | |||||
| @@ -4,6 +4,7 @@ namespace App\Entity; | |||||
| use App\Enum\PrioType; | use App\Enum\PrioType; | ||||
| use App\Repository\TaskRepository; | use App\Repository\TaskRepository; | ||||
| use Doctrine\Common\Collections\Collection; | |||||
| use Doctrine\DBAL\Types\Types; | use Doctrine\DBAL\Types\Types; | ||||
| use Doctrine\ORM\Mapping as ORM; | use Doctrine\ORM\Mapping as ORM; | ||||
| @@ -43,12 +44,15 @@ class Task | |||||
| #[ORM\Column(type: 'string', enumType: PrioType::class)] | #[ORM\Column(type: 'string', enumType: PrioType::class)] | ||||
| private PrioType $prio; | private PrioType $prio; | ||||
| #[ORM\Column] | |||||
| #[ORM\Column(nullable: false)] | |||||
| private ?bool $completed = null; | private ?bool $completed = null; | ||||
| #[ORM\Column] | #[ORM\Column] | ||||
| private ?\DateTimeImmutable $createdAt = null; | private ?\DateTimeImmutable $createdAt = null; | ||||
| #[ORM\OneToMany(mappedBy: 'task', targetEntity: TaskNote::class)] | |||||
| private Collection $taskNotes; | |||||
| public function __construct(User $createdBy, User $assignedTo) | public function __construct(User $createdBy, User $assignedTo) | ||||
| { | { | ||||
| $this->createdBy = $createdBy; | $this->createdBy = $createdBy; | ||||
| @@ -156,4 +160,9 @@ class Task | |||||
| return $this->createdAt; | return $this->createdAt; | ||||
| } | } | ||||
| public function getTaskNotes(): Collection | |||||
| { | |||||
| return $this->taskNotes; | |||||
| } | |||||
| } | } | ||||
| @@ -0,0 +1,90 @@ | |||||
| <?php | |||||
| namespace App\Entity; | |||||
| use App\Repository\TaskNoteRepository; | |||||
| use Doctrine\DBAL\Types\Types; | |||||
| use Doctrine\ORM\Mapping as ORM; | |||||
| #[ORM\Entity(repositoryClass: TaskNoteRepository::class)] | |||||
| class TaskNote | |||||
| { | |||||
| #[ORM\Id] | |||||
| #[ORM\GeneratedValue] | |||||
| #[ORM\Column] | |||||
| private ?int $id = null; | |||||
| #[ORM\Column(type: Types::TEXT)] | |||||
| private ?string $message = null; | |||||
| #[ORM\ManyToOne] | |||||
| #[ORM\JoinColumn(nullable: false, onDelete: 'CASCADE')] | |||||
| private ?User $owner = null; | |||||
| #[ORM\ManyToOne] | |||||
| #[ORM\JoinColumn(nullable: false, onDelete: 'CASCADE')] | |||||
| private ?Task $task = null; | |||||
| #[ORM\Column] | |||||
| private ?\DateTimeImmutable $createdAt = null; | |||||
| public function __construct(User $owner, Task $task) | |||||
| { | |||||
| $this->owner = $owner; | |||||
| $this->task = $task; | |||||
| $this->createdAt = new \DateTimeImmutable(); | |||||
| } | |||||
| public function getId(): ?int | |||||
| { | |||||
| return $this->id; | |||||
| } | |||||
| public function getMessage(): ?string | |||||
| { | |||||
| return $this->message; | |||||
| } | |||||
| public function setMessage(string $message): static | |||||
| { | |||||
| $this->message = $message; | |||||
| return $this; | |||||
| } | |||||
| public function getOwner(): ?User | |||||
| { | |||||
| return $this->owner; | |||||
| } | |||||
| public function setOwner(?User $owner): static | |||||
| { | |||||
| $this->owner = $owner; | |||||
| return $this; | |||||
| } | |||||
| public function getTask(): ?Task | |||||
| { | |||||
| return $this->task; | |||||
| } | |||||
| public function setTask(?Task $task): static | |||||
| { | |||||
| $this->task = $task; | |||||
| return $this; | |||||
| } | |||||
| public function getCreatedAt(): ?\DateTimeImmutable | |||||
| { | |||||
| return $this->createdAt; | |||||
| } | |||||
| public function setCreatedAt(\DateTimeImmutable $createdAt): static | |||||
| { | |||||
| $this->createdAt = $createdAt; | |||||
| return $this; | |||||
| } | |||||
| } | |||||
| @@ -52,6 +52,9 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface | |||||
| #[ORM\OneToMany(mappedBy: 'owner', targetEntity: Comment::class)] | #[ORM\OneToMany(mappedBy: 'owner', targetEntity: Comment::class)] | ||||
| private Collection $comments; | private Collection $comments; | ||||
| #[ORM\OneToMany(mappedBy: 'owner', targetEntity: Sale::class)] | |||||
| private Collection $sales; | |||||
| public function __construct() | public function __construct() | ||||
| { | { | ||||
| @@ -59,6 +62,7 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface | |||||
| $this->postings = new ArrayCollection(); | $this->postings = new ArrayCollection(); | ||||
| $this->active = true; | $this->active = true; | ||||
| $this->comments = new ArrayCollection(); | $this->comments = new ArrayCollection(); | ||||
| $this->sales = new ArrayCollection(); | |||||
| } | } | ||||
| public function getId(): ?int | public function getId(): ?int | ||||
| @@ -246,4 +250,34 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface | |||||
| return $this; | return $this; | ||||
| } | } | ||||
| /** | |||||
| * @return Collection<int, Sale> | |||||
| */ | |||||
| public function getSales(): Collection | |||||
| { | |||||
| return $this->sales; | |||||
| } | |||||
| public function addSale(Sale $sale): static | |||||
| { | |||||
| if (!$this->sales->contains($sale)) { | |||||
| $this->sales->add($sale); | |||||
| $sale->setOwner($this); | |||||
| } | |||||
| return $this; | |||||
| } | |||||
| public function removeSale(Sale $sale): static | |||||
| { | |||||
| if ($this->sales->removeElement($sale)) { | |||||
| // set the owning side to null (unless already changed) | |||||
| if ($sale->getOwner() === $this) { | |||||
| $sale->setOwner(null); | |||||
| } | |||||
| } | |||||
| return $this; | |||||
| } | |||||
| } | } | ||||
| @@ -47,12 +47,14 @@ final class PostingFactory extends ModelFactory | |||||
| protected function getDefaults(): array | protected function getDefaults(): array | ||||
| { | { | ||||
| $randomBoolean = ContactFactory::count() > 0 && (bool)random_int(0, 1); | $randomBoolean = ContactFactory::count() > 0 && (bool)random_int(0, 1); | ||||
| $random4 = SaleFactory::count() > 0 && !(bool)random_int(0, 3); | |||||
| return [ | return [ | ||||
| 'headline' => self::faker()->words(random_int(1, 5), true), | 'headline' => self::faker()->words(random_int(1, 5), true), | ||||
| 'message' => $randomBoolean ? self::faker()->sentence() : self::faker()->sentences(random_int(1, 3), true), | 'message' => $randomBoolean ? self::faker()->sentence() : self::faker()->sentences(random_int(1, 3), true), | ||||
| 'owner' => UserFactory::random(), | 'owner' => UserFactory::random(), | ||||
| 'partner' => PartnerFactory::random(), | 'partner' => PartnerFactory::random(), | ||||
| 'contact' => $randomBoolean ? ContactFactory::random() : null, | 'contact' => $randomBoolean ? ContactFactory::random() : null, | ||||
| 'sale' => !$random4 ? SaleFactory::random() : null, | |||||
| ]; | ]; | ||||
| } | } | ||||
| @@ -0,0 +1,77 @@ | |||||
| <?php | |||||
| namespace App\Factory; | |||||
| use App\Entity\Sale; | |||||
| use App\Repository\SaleRepository; | |||||
| use Zenstruck\Foundry\ModelFactory; | |||||
| use Zenstruck\Foundry\Proxy; | |||||
| use Zenstruck\Foundry\RepositoryProxy; | |||||
| /** | |||||
| * @extends ModelFactory<Sale> | |||||
| * | |||||
| * @method Sale|Proxy create(array|callable $attributes = []) | |||||
| * @method static Sale|Proxy createOne(array $attributes = []) | |||||
| * @method static Sale|Proxy find(object|array|mixed $criteria) | |||||
| * @method static Sale|Proxy findOrCreate(array $attributes) | |||||
| * @method static Sale|Proxy first(string $sortedField = 'id') | |||||
| * @method static Sale|Proxy last(string $sortedField = 'id') | |||||
| * @method static Sale|Proxy random(array $attributes = []) | |||||
| * @method static Sale|Proxy randomOrCreate(array $attributes = []) | |||||
| * @method static SaleRepository|RepositoryProxy repository() | |||||
| * @method static Sale[]|Proxy[] all() | |||||
| * @method static Sale[]|Proxy[] createMany(int $number, array|callable $attributes = []) | |||||
| * @method static Sale[]|Proxy[] createSequence(iterable|callable $sequence) | |||||
| * @method static Sale[]|Proxy[] findBy(array $attributes) | |||||
| * @method static Sale[]|Proxy[] randomRange(int $min, int $max, array $attributes = []) | |||||
| * @method static Sale[]|Proxy[] randomSet(int $number, array $attributes = []) | |||||
| */ | |||||
| final class SaleFactory extends ModelFactory | |||||
| { | |||||
| /** | |||||
| * @see https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html#factories-as-services | |||||
| * | |||||
| * @todo inject services if required | |||||
| */ | |||||
| public function __construct() | |||||
| { | |||||
| parent::__construct(); | |||||
| } | |||||
| /** | |||||
| * @see https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html#model-factories | |||||
| * | |||||
| * @todo add your default values here | |||||
| */ | |||||
| protected function getDefaults(): array | |||||
| { | |||||
| $turnover = self::faker()->randomNumber(5); | |||||
| $randNumber = self::faker()->randomNumber(2); | |||||
| $profit = (int)($turnover * ($randNumber / 100)); | |||||
| return [ | |||||
| 'createdAt' => \DateTimeImmutable::createFromMutable(self::faker()->dateTime()), | |||||
| 'owner' => UserFactory::random(), | |||||
| 'partner' => PartnerFactory::random(), | |||||
| 'product' => ProductFactory::random(), | |||||
| 'turnover' => $turnover, | |||||
| 'profit' => $profit | |||||
| ]; | |||||
| } | |||||
| /** | |||||
| * @see https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html#initialization | |||||
| */ | |||||
| protected function initialize(): self | |||||
| { | |||||
| return $this | |||||
| // ->afterInstantiate(function(Sale $sale): void {}) | |||||
| ; | |||||
| } | |||||
| protected static function getClass(): string | |||||
| { | |||||
| return Sale::class; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,71 @@ | |||||
| <?php | |||||
| namespace App\Factory; | |||||
| use App\Entity\TaskNote; | |||||
| use App\Repository\TaskNoteRepository; | |||||
| use Zenstruck\Foundry\ModelFactory; | |||||
| use Zenstruck\Foundry\Proxy; | |||||
| use Zenstruck\Foundry\RepositoryProxy; | |||||
| /** | |||||
| * @extends ModelFactory<TaskNote> | |||||
| * | |||||
| * @method TaskNote|Proxy create(array|callable $attributes = []) | |||||
| * @method static TaskNote|Proxy createOne(array $attributes = []) | |||||
| * @method static TaskNote|Proxy find(object|array|mixed $criteria) | |||||
| * @method static TaskNote|Proxy findOrCreate(array $attributes) | |||||
| * @method static TaskNote|Proxy first(string $sortedField = 'id') | |||||
| * @method static TaskNote|Proxy last(string $sortedField = 'id') | |||||
| * @method static TaskNote|Proxy random(array $attributes = []) | |||||
| * @method static TaskNote|Proxy randomOrCreate(array $attributes = []) | |||||
| * @method static TaskNoteRepository|RepositoryProxy repository() | |||||
| * @method static TaskNote[]|Proxy[] all() | |||||
| * @method static TaskNote[]|Proxy[] createMany(int $number, array|callable $attributes = []) | |||||
| * @method static TaskNote[]|Proxy[] createSequence(iterable|callable $sequence) | |||||
| * @method static TaskNote[]|Proxy[] findBy(array $attributes) | |||||
| * @method static TaskNote[]|Proxy[] randomRange(int $min, int $max, array $attributes = []) | |||||
| * @method static TaskNote[]|Proxy[] randomSet(int $number, array $attributes = []) | |||||
| */ | |||||
| final class TaskNoteFactory extends ModelFactory | |||||
| { | |||||
| /** | |||||
| * @see https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html#factories-as-services | |||||
| * | |||||
| * @todo inject services if required | |||||
| */ | |||||
| public function __construct() | |||||
| { | |||||
| parent::__construct(); | |||||
| } | |||||
| /** | |||||
| * @see https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html#model-factories | |||||
| * | |||||
| * @todo add your default values here | |||||
| */ | |||||
| protected function getDefaults(): array | |||||
| { | |||||
| return [ | |||||
| 'createdAt' => \DateTimeImmutable::createFromMutable(self::faker()->dateTime()), | |||||
| 'message' => self::faker()->text(), | |||||
| 'owner' => UserFactory::random(), | |||||
| 'task' => TaskFactory::random(), | |||||
| ]; | |||||
| } | |||||
| /** | |||||
| * @see https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html#initialization | |||||
| */ | |||||
| protected function initialize(): self | |||||
| { | |||||
| return $this | |||||
| // ->afterInstantiate(function(TaskNote $taskNote): void {}) | |||||
| ; | |||||
| } | |||||
| protected static function getClass(): string | |||||
| { | |||||
| return TaskNote::class; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,8 @@ | |||||
| <?php | |||||
| namespace App\Interface; | |||||
| use App\ApiResource\UserApi; | |||||
| interface OwnerInterface { | |||||
| public function getOwner(): UserApi; | |||||
| } | |||||
| @@ -4,8 +4,10 @@ namespace App\Mapper; | |||||
| use App\ApiResource\ContactApi; | use App\ApiResource\ContactApi; | ||||
| use App\ApiResource\PostingApi; | use App\ApiResource\PostingApi; | ||||
| use App\ApiResource\SaleApi; | |||||
| use App\Entity\Contact; | use App\Entity\Contact; | ||||
| use App\Entity\Partner; | use App\Entity\Partner; | ||||
| use App\Entity\Sale; | |||||
| use App\Entity\User; | use App\Entity\User; | ||||
| use App\Entity\Posting; | use App\Entity\Posting; | ||||
| use App\Repository\PostingRepository; | use App\Repository\PostingRepository; | ||||
| @@ -49,8 +51,16 @@ class PostingApiToEntityMapper implements MapperInterface | |||||
| MicroMapperInterface::MAX_DEPTH => 1, | MicroMapperInterface::MAX_DEPTH => 1, | ||||
| ]); | ]); | ||||
| } | } | ||||
| $sale = null; | |||||
| if ($dto->sale) { | |||||
| assert($dto->sale instanceof SaleApi); | |||||
| $sale = $this->microMapper->map($dto->sale, Sale::class, [ | |||||
| MicroMapperInterface::MAX_DEPTH => 1, | |||||
| ]); | |||||
| } | |||||
| assert($partner instanceof Partner); | assert($partner instanceof Partner); | ||||
| assert($contact === null || $contact instanceof Contact); | assert($contact === null || $contact instanceof Contact); | ||||
| assert($sale === null || $sale instanceof Sale); | |||||
| $entity = new Posting($user, $partner, $contact); | $entity = new Posting($user, $partner, $contact); | ||||
| } | } | ||||
| @@ -0,0 +1,76 @@ | |||||
| <?php | |||||
| namespace App\Mapper; | |||||
| use App\ApiResource\CommentApi; | |||||
| use App\ApiResource\SaleApi; | |||||
| use App\Entity\Comment; | |||||
| use App\Entity\Partner; | |||||
| use App\Entity\Product; | |||||
| use App\Entity\Sale; | |||||
| use App\Entity\User; | |||||
| use App\Repository\SaleRepository; | |||||
| use Symfony\Bundle\SecurityBundle\Security; | |||||
| use Symfonycasts\MicroMapper\AsMapper; | |||||
| use Symfonycasts\MicroMapper\MapperInterface; | |||||
| use Symfonycasts\MicroMapper\MicroMapperInterface; | |||||
| #[AsMapper(from: CommentApi::class, to: Comment::class)] | |||||
| class SaleApiToEntityMapper implements MapperInterface | |||||
| { | |||||
| public function __construct( | |||||
| private SaleRepository $repository, | |||||
| private Security $security, | |||||
| private MicroMapperInterface $microMapper, | |||||
| ) | |||||
| { | |||||
| } | |||||
| public function load(object $from, string $toClass, array $context): object | |||||
| { | |||||
| $dto = $from; | |||||
| assert($dto instanceof SaleApi); | |||||
| if ($dto->id) { | |||||
| $entity = $this->repository->find($dto->id); | |||||
| } else { | |||||
| $user = $this->security->getUser(); | |||||
| assert($user instanceof User); | |||||
| if ($dto->partner === null) { | |||||
| throw new \Exception('Partner missing'); | |||||
| } | |||||
| $partner = $this->microMapper->map($dto->partner, Partner::class, [ | |||||
| MicroMapperInterface::MAX_DEPTH => 1, | |||||
| ]); | |||||
| if ($dto->product === null) { | |||||
| throw new \Exception('Product missing'); | |||||
| } | |||||
| $product = $this->microMapper->map($dto->product, Product::class, [ | |||||
| MicroMapperInterface::MAX_DEPTH => 1, | |||||
| ]); | |||||
| assert($partner instanceof Partner); | |||||
| assert($product instanceof Product); | |||||
| $entity = new Sale($user, $partner, $product); | |||||
| } | |||||
| if (!$entity) { | |||||
| throw new \Exception('Sale not found'); | |||||
| } | |||||
| return $entity; | |||||
| } | |||||
| public function populate(object $from, object $to, array $context): object | |||||
| { | |||||
| $dto = $from; | |||||
| assert($dto instanceof SaleApi); | |||||
| $entity = $to; | |||||
| assert($entity instanceof Sale); | |||||
| $entity->setTurnover($dto->turnover); | |||||
| $entity->setProfit($dto->profit); | |||||
| $entity->setComment($dto->comment); | |||||
| return $entity; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,74 @@ | |||||
| <?php | |||||
| namespace App\Mapper; | |||||
| use App\ApiResource\CommentApi; | |||||
| use App\ApiResource\PartnerApi; | |||||
| use App\ApiResource\ProductApi; | |||||
| use App\ApiResource\SaleApi; | |||||
| use App\ApiResource\UserApi; | |||||
| use App\ApiResource\PostingApi; | |||||
| use App\Entity\Comment; | |||||
| use App\Entity\Posting; | |||||
| use App\Entity\Sale; | |||||
| use Symfony\Bundle\SecurityBundle\Security; | |||||
| use Symfonycasts\MicroMapper\AsMapper; | |||||
| use Symfonycasts\MicroMapper\MapperInterface; | |||||
| use Symfonycasts\MicroMapper\MicroMapperInterface; | |||||
| #[AsMapper(from: Sale::class, to: SaleApi::class)] | |||||
| class SaleEntityToApiMapper implements MapperInterface | |||||
| { | |||||
| public function __construct( | |||||
| private MicroMapperInterface $microMapper | |||||
| ) | |||||
| { | |||||
| } | |||||
| public function load(object $from, string $toClass, array $context): object | |||||
| { | |||||
| $entity = $from; | |||||
| assert($entity instanceof Sale); | |||||
| $dto = new SaleApi(); | |||||
| $dto->id = $entity->getId(); | |||||
| return $dto; | |||||
| } | |||||
| public function populate(object $from, object $to, array $context): object | |||||
| { | |||||
| $entity = $from; | |||||
| $dto = $to; | |||||
| assert($entity instanceof Sale); | |||||
| assert($dto instanceof SaleApi); | |||||
| $dto->owner = $this->microMapper->map($entity->getOwner(), UserApi::class, [ | |||||
| MicroMapperInterface::MAX_DEPTH => 1, | |||||
| ]); | |||||
| $dto->ownerName = $entity->getOwner()?->getFirstName()." ".$entity->getOwner()?->getLastName(); | |||||
| $dto->partner = $this->microMapper->map($entity->getPartner(), PartnerApi::class, [ | |||||
| MicroMapperInterface::MAX_DEPTH => 1, | |||||
| ]); | |||||
| $dto->partnerName = $entity->getPartner()?->getName(); | |||||
| $dto->product = $this->microMapper->map($entity->getProduct(), ProductApi::class, [ | |||||
| MicroMapperInterface::MAX_DEPTH => 1, | |||||
| ]); | |||||
| $dto->productName = $entity->getProduct()?->getName(); | |||||
| $dto->turnover = $entity->getTurnover(); | |||||
| $dto->profit = $entity->getProfit(); | |||||
| $dto->comment = $entity->getComment(); | |||||
| $dto->createdAt = $entity->getCreatedAt(); | |||||
| $dto->posts = array_map(function(Posting $posting) { | |||||
| return $this->microMapper->map($posting, PostingApi::class, [ | |||||
| MicroMapperInterface::MAX_DEPTH => 1, | |||||
| ]); | |||||
| }, $entity->getPostings()->getValues()); | |||||
| return $dto; | |||||
| } | |||||
| } | |||||
| @@ -6,10 +6,12 @@ use App\ApiResource\CommentApi; | |||||
| use App\ApiResource\ContactApi; | use App\ApiResource\ContactApi; | ||||
| use App\ApiResource\PartnerApi; | use App\ApiResource\PartnerApi; | ||||
| use App\ApiResource\TaskApi; | use App\ApiResource\TaskApi; | ||||
| use App\ApiResource\TaskNoteApi; | |||||
| use App\ApiResource\UserApi; | use App\ApiResource\UserApi; | ||||
| use App\ApiResource\PostingApi; | use App\ApiResource\PostingApi; | ||||
| use App\Entity\Comment; | use App\Entity\Comment; | ||||
| use App\Entity\Task; | use App\Entity\Task; | ||||
| use App\Entity\TaskNote; | |||||
| use Symfony\Bundle\SecurityBundle\Security; | use Symfony\Bundle\SecurityBundle\Security; | ||||
| use Symfonycasts\MicroMapper\AsMapper; | use Symfonycasts\MicroMapper\AsMapper; | ||||
| use Symfonycasts\MicroMapper\MapperInterface; | use Symfonycasts\MicroMapper\MapperInterface; | ||||
| @@ -68,6 +70,12 @@ class TaskEntityToApiMapper implements MapperInterface | |||||
| $dto->contactName = $entity->getContact()?->getFirstName()." ".$entity->getContact()?->getLastName(); | $dto->contactName = $entity->getContact()?->getFirstName()." ".$entity->getContact()?->getLastName(); | ||||
| } | } | ||||
| $dto->taskNotes = array_map(function(TaskNote $taskNote) { | |||||
| return $this->microMapper->map($taskNote, TaskNoteApi::class, [ | |||||
| MicroMapperInterface::MAX_DEPTH => 1, | |||||
| ]); | |||||
| }, $entity->getTaskNotes()->getValues()); | |||||
| $dto->prio = $entity->getPrio(); | $dto->prio = $entity->getPrio(); | ||||
| $dto->completed = $entity->getCompleted(); | $dto->completed = $entity->getCompleted(); | ||||
| $dto->createdAt = $entity->getCreatedAt(); | $dto->createdAt = $entity->getCreatedAt(); | ||||
| @@ -0,0 +1,64 @@ | |||||
| <?php | |||||
| namespace App\Mapper; | |||||
| use App\ApiResource\TaskNoteApi; | |||||
| use App\Entity\Task; | |||||
| use App\Entity\TaskNote; | |||||
| use App\Entity\User; | |||||
| use App\Repository\TaskNoteRepository; | |||||
| use Symfony\Bundle\SecurityBundle\Security; | |||||
| use Symfonycasts\MicroMapper\AsMapper; | |||||
| use Symfonycasts\MicroMapper\MapperInterface; | |||||
| use Symfonycasts\MicroMapper\MicroMapperInterface; | |||||
| #[AsMapper(from: TaskNoteApi::class, to: TaskNote::class)] | |||||
| class TaskNoteApiToEntityMapper implements MapperInterface | |||||
| { | |||||
| public function __construct( | |||||
| private TaskNoteRepository $repository, | |||||
| private Security $security, | |||||
| private MicroMapperInterface $microMapper, | |||||
| ) | |||||
| { | |||||
| } | |||||
| public function load(object $from, string $toClass, array $context): object | |||||
| { | |||||
| $dto = $from; | |||||
| assert($dto instanceof TaskNoteApi); | |||||
| if ($dto->id) { | |||||
| $entity = $this->repository->find($dto->id); | |||||
| } else { | |||||
| $user = $this->security->getUser(); | |||||
| assert($user instanceof User); | |||||
| if ($dto->task === null) { | |||||
| throw new \Exception('Task missing'); | |||||
| } | |||||
| $task = $this->microMapper->map($dto->task, Task::class, [ | |||||
| MicroMapperInterface::MAX_DEPTH => 1, | |||||
| ]); | |||||
| assert($task instanceof Task); | |||||
| $entity = new TaskNote($user, $task); | |||||
| } | |||||
| if (!$entity) { | |||||
| throw new \Exception('TaskNote not found'); | |||||
| } | |||||
| return $entity; | |||||
| } | |||||
| public function populate(object $from, object $to, array $context): object | |||||
| { | |||||
| $dto = $from; | |||||
| assert($dto instanceof TaskNoteApi); | |||||
| $entity = $to; | |||||
| assert($entity instanceof TaskNote); | |||||
| $entity->setMessage($dto->message); | |||||
| return $entity; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,54 @@ | |||||
| <?php | |||||
| namespace App\Mapper; | |||||
| use App\ApiResource\TaskApi; | |||||
| use App\ApiResource\TaskNoteApi; | |||||
| use App\ApiResource\UserApi; | |||||
| use App\Entity\TaskNote; | |||||
| use Symfonycasts\MicroMapper\AsMapper; | |||||
| use Symfonycasts\MicroMapper\MapperInterface; | |||||
| use Symfonycasts\MicroMapper\MicroMapperInterface; | |||||
| #[AsMapper(from: TaskNote::class, to: TaskNoteApi::class)] | |||||
| class TaskNoteEntityToApiMapper implements MapperInterface | |||||
| { | |||||
| public function __construct( | |||||
| private MicroMapperInterface $microMapper | |||||
| ) | |||||
| { | |||||
| } | |||||
| public function load(object $from, string $toClass, array $context): object | |||||
| { | |||||
| $entity = $from; | |||||
| assert($entity instanceof TaskNote); | |||||
| $dto = new TaskNoteApi(); | |||||
| $dto->id = $entity->getId(); | |||||
| return $dto; | |||||
| } | |||||
| public function populate(object $from, object $to, array $context): object | |||||
| { | |||||
| $entity = $from; | |||||
| $dto = $to; | |||||
| assert($entity instanceof TaskNote); | |||||
| assert($dto instanceof TaskNoteApi); | |||||
| $dto->message = $entity->getMessage(); | |||||
| $dto->owner = $this->microMapper->map($entity->getOwner(), UserApi::class, [ | |||||
| MicroMapperInterface::MAX_DEPTH => 1, | |||||
| ]); | |||||
| $dto->ownerName = $entity->getOwner()?->getFirstName()." ".$entity->getOwner()?->getLastName(); | |||||
| $dto->task = $this->microMapper->map($entity->getTask(), TaskApi::class, [ | |||||
| MicroMapperInterface::MAX_DEPTH => 1, | |||||
| ]); | |||||
| $dto->createdAt = $entity->getCreatedAt(); | |||||
| return $dto; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,48 @@ | |||||
| <?php | |||||
| namespace App\Repository; | |||||
| use App\Entity\Sale; | |||||
| use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; | |||||
| use Doctrine\Persistence\ManagerRegistry; | |||||
| /** | |||||
| * @extends ServiceEntityRepository<Sale> | |||||
| * | |||||
| * @method Sale|null find($id, $lockMode = null, $lockVersion = null) | |||||
| * @method Sale|null findOneBy(array $criteria, array $orderBy = null) | |||||
| * @method Sale[] findAll() | |||||
| * @method Sale[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null) | |||||
| */ | |||||
| class SaleRepository extends ServiceEntityRepository | |||||
| { | |||||
| public function __construct(ManagerRegistry $registry) | |||||
| { | |||||
| parent::__construct($registry, Sale::class); | |||||
| } | |||||
| // /** | |||||
| // * @return Sale[] Returns an array of Sale objects | |||||
| // */ | |||||
| // public function findByExampleField($value): array | |||||
| // { | |||||
| // return $this->createQueryBuilder('s') | |||||
| // ->andWhere('s.exampleField = :val') | |||||
| // ->setParameter('val', $value) | |||||
| // ->orderBy('s.id', 'ASC') | |||||
| // ->setMaxResults(10) | |||||
| // ->getQuery() | |||||
| // ->getResult() | |||||
| // ; | |||||
| // } | |||||
| // public function findOneBySomeField($value): ?Sale | |||||
| // { | |||||
| // return $this->createQueryBuilder('s') | |||||
| // ->andWhere('s.exampleField = :val') | |||||
| // ->setParameter('val', $value) | |||||
| // ->getQuery() | |||||
| // ->getOneOrNullResult() | |||||
| // ; | |||||
| // } | |||||
| } | |||||
| @@ -0,0 +1,48 @@ | |||||
| <?php | |||||
| namespace App\Repository; | |||||
| use App\Entity\TaskNote; | |||||
| use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; | |||||
| use Doctrine\Persistence\ManagerRegistry; | |||||
| /** | |||||
| * @extends ServiceEntityRepository<TaskNote> | |||||
| * | |||||
| * @method TaskNote|null find($id, $lockMode = null, $lockVersion = null) | |||||
| * @method TaskNote|null findOneBy(array $criteria, array $orderBy = null) | |||||
| * @method TaskNote[] findAll() | |||||
| * @method TaskNote[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null) | |||||
| */ | |||||
| class TaskNoteRepository extends ServiceEntityRepository | |||||
| { | |||||
| public function __construct(ManagerRegistry $registry) | |||||
| { | |||||
| parent::__construct($registry, TaskNote::class); | |||||
| } | |||||
| // /** | |||||
| // * @return TaskNote[] Returns an array of TaskNote objects | |||||
| // */ | |||||
| // public function findByExampleField($value): array | |||||
| // { | |||||
| // return $this->createQueryBuilder('t') | |||||
| // ->andWhere('t.exampleField = :val') | |||||
| // ->setParameter('val', $value) | |||||
| // ->orderBy('t.id', 'ASC') | |||||
| // ->setMaxResults(10) | |||||
| // ->getQuery() | |||||
| // ->getResult() | |||||
| // ; | |||||
| // } | |||||
| // public function findOneBySomeField($value): ?TaskNote | |||||
| // { | |||||
| // return $this->createQueryBuilder('t') | |||||
| // ->andWhere('t.exampleField = :val') | |||||
| // ->setParameter('val', $value) | |||||
| // ->getQuery() | |||||
| // ->getOneOrNullResult() | |||||
| // ; | |||||
| // } | |||||
| } | |||||
| @@ -0,0 +1,67 @@ | |||||
| <?php | |||||
| namespace App\State; | |||||
| use ApiPlatform\Metadata\CollectionOperationInterface; | |||||
| use ApiPlatform\Metadata\Operation; | |||||
| use ApiPlatform\State\Pagination\Pagination; | |||||
| use ApiPlatform\State\Pagination\TraversablePaginator; | |||||
| use ApiPlatform\State\ProviderInterface; | |||||
| use App\ApiResource\SaleSummary; | |||||
| use App\ApiResource\UserApi; | |||||
| use App\Entity\User; | |||||
| use App\Repository\UserRepository; | |||||
| use Symfonycasts\MicroMapper\MicroMapperInterface; | |||||
| class SaleSummaryStateProvider implements ProviderInterface | |||||
| { | |||||
| public function __construct( | |||||
| private UserRepository$userRepository, | |||||
| private Pagination $pagination, | |||||
| private MicroMapperInterface $microMapper, | |||||
| ) | |||||
| { | |||||
| } | |||||
| public function provide(Operation $operation, array $uriVariables = [], array $context = []): object|array|null | |||||
| { | |||||
| if ($operation instanceof CollectionOperationInterface) { | |||||
| $currentPage = $this->pagination->getPage($context); | |||||
| $itemsPerPage = $this->pagination->getLimit($operation, $context); | |||||
| $offset = $this->pagination->getOffset($operation, $context); | |||||
| $salesSummaries = $this->createSaleSummaries($offset, $itemsPerPage); | |||||
| return new TraversablePaginator( | |||||
| new \ArrayIterator($salesSummaries), | |||||
| $currentPage, | |||||
| $itemsPerPage, | |||||
| count($salesSummaries), | |||||
| ); | |||||
| } | |||||
| $salesSummaries = $this->createSaleSummaries(0); | |||||
| return $this->createSaleSummaries(0, count($salesSummaries)); | |||||
| } | |||||
| private function createSaleSummaries(int $offset, int $limit = 50): array | |||||
| { | |||||
| $users = $this->userRepository->findBy([], [], $limit, $offset); | |||||
| $salesSummaries = []; | |||||
| /** @var User $user */ | |||||
| foreach ($users as $user) { | |||||
| $sales = $user->getSales(); | |||||
| $salesSummary = new SaleSummary(); | |||||
| $salesSummary->owner = $this->microMapper->map($user, UserApi::class); | |||||
| $salesSummary->ownerName = $user->getFirstName() . ' ' . $user->getLastName(); | |||||
| $salesSummary->turnover = 0; | |||||
| $salesSummary->profit = 0; | |||||
| foreach ($sales as $sale) { | |||||
| $salesSummary->turnover += $sale->getTurnover(); | |||||
| $salesSummary->profit += $sale->getProfit(); | |||||
| } | |||||
| $salesSummaries[] = $salesSummary; | |||||
| } | |||||
| return $salesSummaries; | |||||
| } | |||||
| } | |||||
| @@ -1,46 +0,0 @@ | |||||
| <?php | |||||
| namespace App\Voter; | |||||
| use App\ApiResource\CommentApi; | |||||
| use App\ApiResource\PostingApi; | |||||
| use App\Entity\User; | |||||
| use Symfony\Bundle\SecurityBundle\Security; | |||||
| use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; | |||||
| use Symfony\Component\Security\Core\Authorization\Voter\Voter; | |||||
| class CommentApiVoter extends Voter | |||||
| { | |||||
| public const EDIT = 'EDIT'; | |||||
| public function __construct() | |||||
| { | |||||
| } | |||||
| protected function supports(string $attribute, mixed $subject): bool | |||||
| { | |||||
| return $attribute === self::EDIT && $subject instanceof CommentApi; | |||||
| } | |||||
| protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool | |||||
| { | |||||
| $user = $token->getUser(); | |||||
| // if the user is anonymous, do not grant access | |||||
| if (!$user instanceof User) { | |||||
| return false; | |||||
| } | |||||
| assert($subject instanceof CommentApi); | |||||
| // ... (check conditions and return true to grant permission) ... | |||||
| switch ($attribute) { | |||||
| case self::EDIT: | |||||
| if ($subject->owner?->id === $user->getId()) { | |||||
| return true; | |||||
| } | |||||
| break; | |||||
| } | |||||
| return false; | |||||
| } | |||||
| } | |||||
| @@ -2,13 +2,12 @@ | |||||
| namespace App\Voter; | namespace App\Voter; | ||||
| use App\ApiResource\PostingApi; | |||||
| use App\Entity\User; | use App\Entity\User; | ||||
| use Symfony\Bundle\SecurityBundle\Security; | |||||
| use App\Interface\OwnerInterface; | |||||
| use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; | use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; | ||||
| use Symfony\Component\Security\Core\Authorization\Voter\Voter; | use Symfony\Component\Security\Core\Authorization\Voter\Voter; | ||||
| class PostingApiVoter extends Voter | |||||
| class OwnerApiEditVoter extends Voter | |||||
| { | { | ||||
| public const EDIT = 'EDIT'; | public const EDIT = 'EDIT'; | ||||
| @@ -18,7 +17,7 @@ class PostingApiVoter extends Voter | |||||
| protected function supports(string $attribute, mixed $subject): bool | protected function supports(string $attribute, mixed $subject): bool | ||||
| { | { | ||||
| return $attribute === self::EDIT && $subject instanceof PostingApi; | |||||
| return $attribute === self::EDIT && ($subject instanceof OwnerInterface); | |||||
| } | } | ||||
| protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool | protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool | ||||
| @@ -29,12 +28,12 @@ class PostingApiVoter extends Voter | |||||
| return false; | return false; | ||||
| } | } | ||||
| assert($subject instanceof PostingApi); | |||||
| assert($subject instanceof OwnerInterface); | |||||
| // ... (check conditions and return true to grant permission) ... | // ... (check conditions and return true to grant permission) ... | ||||
| switch ($attribute) { | switch ($attribute) { | ||||
| case self::EDIT: | case self::EDIT: | ||||
| if ($subject->owner?->id === $user->getId()) { | |||||
| if ($subject->getOwner()?->id === $user->getId()) { | |||||
| return true; | return true; | ||||
| } | } | ||||
| break; | break; | ||||
| @@ -0,0 +1,92 @@ | |||||
| <?php | |||||
| /** | |||||
| * @author Daniel Knudsen <d.knudsen@spawntree.de> | |||||
| * @date 12.12.23 | |||||
| */ | |||||
| namespace App\Tests\Functional; | |||||
| use App\Enum\PartnerType; | |||||
| use App\Factory\CommentFactory; | |||||
| use App\Factory\MediaObjectLogoFactory; | |||||
| use App\Factory\MediaObjectContactFactory; | |||||
| use App\Factory\PartnerFactory; | |||||
| use App\Factory\PostingFactory; | |||||
| use App\Factory\TaskFactory; | |||||
| use App\Factory\TaskNoteFactory; | |||||
| use App\Factory\UserFactory; | |||||
| use Lexik\Bundle\JWTAuthenticationBundle\Services\JWTTokenManagerInterface; | |||||
| use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; | |||||
| use Zenstruck\Browser\Test\HasBrowser; | |||||
| use Zenstruck\Foundry\Test\Factories; | |||||
| use Zenstruck\Foundry\Test\ResetDatabase; | |||||
| class TaskNoteResourceTest extends KernelTestCase | |||||
| { | |||||
| use HasBrowser; | |||||
| use ResetDatabase; | |||||
| use Factories; | |||||
| private JWTTokenManagerInterface $JWTManager; | |||||
| protected function setUp(): void | |||||
| { | |||||
| parent::setUp(); | |||||
| $this->JWTManager = self::getContainer()->get('lexik_jwt_authentication.jwt_manager'); | |||||
| } | |||||
| public function testPostTaskNote(): void | |||||
| { | |||||
| $user = UserFactory::createOne( | |||||
| [ | |||||
| 'firstName' => 'Peter', | |||||
| 'lastName' => 'Test', | |||||
| 'password' => 'test', | |||||
| 'email' => 'peter@test.de', | |||||
| ] | |||||
| ); | |||||
| PartnerFactory::createOne(); | |||||
| $task = TaskFactory::createOne(); | |||||
| TaskNoteFactory::createOne(); | |||||
| $token = $this->JWTManager->create($user->object()); | |||||
| $this->browser() | |||||
| ->get('/api/task_notes', [ | |||||
| 'headers' => [ | |||||
| 'Authorization' => 'Bearer ' . $token, | |||||
| ], | |||||
| ]) | |||||
| ->assertSuccessful() | |||||
| ->assertJsonMatches('"hydra:totalItems"', 1) | |||||
| ; | |||||
| $this->browser() | |||||
| ->post('/api/task_notes' , [ | |||||
| 'json' => [ | |||||
| 'message' => 'my comment', | |||||
| 'task' => '/api/tasks/' . $task->getId(), | |||||
| ], | |||||
| 'headers' => [ | |||||
| 'Authorization' => 'Bearer ' . $token, | |||||
| ] | |||||
| ]) | |||||
| ->assertSuccessful() | |||||
| ; | |||||
| $this->browser() | |||||
| ->get('/api/task_notes', [ | |||||
| 'headers' => [ | |||||
| 'Authorization' => 'Bearer ' . $token, | |||||
| ], | |||||
| ]) | |||||
| ->assertSuccessful() | |||||
| ->assertJsonMatches('"hydra:totalItems"', 2) | |||||
| ->assertJsonMatches('"hydra:member"[1].message', 'my comment') | |||||
| ; | |||||
| } | |||||
| } | |||||