From b6cd3497434838d449b53f3520d39f956bae0a6e Mon Sep 17 00:00:00 2001 From: Sebastian Date: Mon, 5 Dec 2022 15:35:58 +0100 Subject: [PATCH 01/86] Create Entity --- .../Aggregated/aggregatedPersonRole.php | 151 ++++++++++++++++++ src/Migrations/Version20221205134917.php | 51 ++++++ .../aggregatedPersonRoleRepository.php | 50 ++++++ 3 files changed, 252 insertions(+) create mode 100644 src/Entity/Aggregated/aggregatedPersonRole.php create mode 100644 src/Migrations/Version20221205134917.php create mode 100644 src/Repository/Aggregated/aggregatedPersonRoleRepository.php diff --git a/src/Entity/Aggregated/aggregatedPersonRole.php b/src/Entity/Aggregated/aggregatedPersonRole.php new file mode 100644 index 0000000..f5a787a --- /dev/null +++ b/src/Entity/Aggregated/aggregatedPersonRole.php @@ -0,0 +1,151 @@ +id; + } + + public function getPersonId(): ?Person + { + return $this->person_id; + } + + public function setPersonId(?Person $person_id): self + { + $this->person_id = $person_id; + + return $this; + } + + public function getRoleId(): ?Role + { + return $this->role_id; + } + + public function setRoleId(?Role $role_id): self + { + $this->role_id = $role_id; + + return $this; + } + + public function getGroupId(): ?Group + { + return $this->group_id; + } + + public function setGroupId(?Group $group_id): self + { + $this->group_id = $group_id; + + return $this; + } + + public function getNickname(): ?string + { + return $this->nickname; + } + + public function setNickname(string $nickname): self + { + $this->nickname = $nickname; + + return $this; + } + + public function getStartAt(): ?\DateTimeInterface + { + return $this->start_at; + } + + public function setStartAt(\DateTimeInterface $start_at): self + { + $this->start_at = $start_at; + + return $this; + } + + public function getEndAt(): ?\DateTimeInterface + { + return $this->end_at; + } + + public function setEndAt(\DateTimeInterface $end_at): self + { + $this->end_at = $end_at; + + return $this; + } + + public function getMidataId(): ?PersonRole + { + return $this->midata_id; + } + + public function setMidataId(?PersonRole $midata_id): self + { + $this->midata_id = $midata_id; + + return $this; + } +} diff --git a/src/Migrations/Version20221205134917.php b/src/Migrations/Version20221205134917.php new file mode 100644 index 0000000..8f2a236 --- /dev/null +++ b/src/Migrations/Version20221205134917.php @@ -0,0 +1,51 @@ +addSql('CREATE SEQUENCE aggregated_person_role_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); + $this->addSql( + 'CREATE TABLE hc_aggregated_person_role (id INT NOT NULL, person_id INT DEFAULT NULL, role_id INT NOT NULL, group_id INT NOT NULL, midata_id INT DEFAULT NULL, nickname VARCHAR(255) NOT NULL, start_at DATE NOT NULL, end_at DATE DEFAULT NULL, PRIMARY KEY(id))' + ); + $this->addSql('CREATE INDEX IDX_40B8716BD3728193 ON hc_aggregated_person_role (person_id)'); + $this->addSql('CREATE INDEX IDX_40B8716B88987678 ON hc_aggregated_person_role (role_id)'); + $this->addSql('CREATE INDEX IDX_40B8716B2F68B530 ON hc_aggregated_person_role (group_id)'); + $this->addSql('CREATE INDEX IDX_40B8716B34C0398A ON hc_aggregated_person_role (midata_id)'); + $this->addSql( + 'ALTER TABLE hc_aggregated_person_role ADD CONSTRAINT FK_40B8716BD3728193 FOREIGN KEY (person_id) REFERENCES midata_person (id) NOT DEFERRABLE INITIALLY IMMEDIATE' + ); + $this->addSql( + 'ALTER TABLE hc_aggregated_person_role ADD CONSTRAINT FK_40B8716B88987678 FOREIGN KEY (role_id) REFERENCES midata_role (id) NOT DEFERRABLE INITIALLY IMMEDIATE' + ); + $this->addSql( + 'ALTER TABLE hc_aggregated_person_role ADD CONSTRAINT FK_40B8716B2F68B530 FOREIGN KEY (group_id) REFERENCES midata_group (id) NOT DEFERRABLE INITIALLY IMMEDIATE' + ); + $this->addSql( + 'ALTER TABLE hc_aggregated_person_role ADD CONSTRAINT FK_40B8716B34C0398A FOREIGN KEY (midata_id) REFERENCES midata_person_role (id) NOT DEFERRABLE INITIALLY IMMEDIATE' + ); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('DROP SEQUENCE aggregated_person_role_id_seq CASCADE'); + $this->addSql('DROP TABLE hc_aggregated_person_role'); + } +} diff --git a/src/Repository/Aggregated/aggregatedPersonRoleRepository.php b/src/Repository/Aggregated/aggregatedPersonRoleRepository.php new file mode 100644 index 0000000..4e4046c --- /dev/null +++ b/src/Repository/Aggregated/aggregatedPersonRoleRepository.php @@ -0,0 +1,50 @@ +createQueryBuilder('a') + ->andWhere('a.exampleField = :val') + ->setParameter('val', $value) + ->orderBy('a.id', 'ASC') + ->setMaxResults(10) + ->getQuery() + ->getResult() + ; + } + */ + + /* + public function findOneBySomeField($value): ?aggregatedPersonRole + { + return $this->createQueryBuilder('a') + ->andWhere('a.exampleField = :val') + ->setParameter('val', $value) + ->getQuery() + ->getOneOrNullResult() + ; + } + */ +} From db111146fb57e7a9e3cb9861d5a2ccb0e53fc0d5 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Mon, 5 Dec 2022 15:41:41 +0100 Subject: [PATCH 02/86] Create Query for old values --- src/Migrations/Version20221205134917.php | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/Migrations/Version20221205134917.php b/src/Migrations/Version20221205134917.php index 8f2a236..f3ce1ff 100644 --- a/src/Migrations/Version20221205134917.php +++ b/src/Migrations/Version20221205134917.php @@ -40,6 +40,21 @@ public function up(Schema $schema): void $this->addSql( 'ALTER TABLE hc_aggregated_person_role ADD CONSTRAINT FK_40B8716B34C0398A FOREIGN KEY (midata_id) REFERENCES midata_person_role (id) NOT DEFERRABLE INITIALLY IMMEDIATE' ); + $this->addSql( + "INSERT INTO hc_aggregated_person_role (id, person_id, role_id, group_id, midata_id, nickname, start_at, end_at) + SELECT + nextval('aggregated_person_role_id_seq'), + midata_person_role.person_id, + midata_person_role.role_id, + midata_person_role.group_id, + midata_person_role.id, + midata_person.nickname, + midata_person_role.created_at, + midata_person_role.deleted_at + FROM + midata_person_role + JOIN midata_person ON midata_person_role.person_id = midata_person.id;" + ); } public function down(Schema $schema): void From eed608d12534045651862ae2a2ad428e7801079e Mon Sep 17 00:00:00 2001 From: Sebastian Date: Tue, 6 Dec 2022 09:03:52 +0100 Subject: [PATCH 03/86] Create Aggregator and Injection --- config/services.yaml | 8 ++++-- src/Command/AggregateCommand.php | 3 +-- src/Service/Aggregator/RoleAggregator.php | 33 +++++++++++++++++++++++ 3 files changed, 40 insertions(+), 4 deletions(-) create mode 100644 src/Service/Aggregator/RoleAggregator.php diff --git a/config/services.yaml b/config/services.yaml index 06af17e..1bb4b3d 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -27,7 +27,7 @@ services: # as action arguments even if you don't extend any base controller class App\Controller\: resource: '../src/Controller' - tags: ['controller.service_arguments'] + tags: [ 'controller.service_arguments' ] # add more service definitions when explicit configuration is needed # please note that last definitions always *replace* previous ones @@ -132,10 +132,14 @@ services: tags: - { name: 'widget.aggregator', key: 'widget.quap' } + App\Service\Aggregator\RoleAggregator: + tags: + - { name: 'widget.aggregator', key: 'widget.roles' } + # This must be the last aggregator App\Service\Aggregator\DateAggregator: tags: - { name: 'widget.aggregator', key: 'general.date' } App\Service\Aggregator\AggregatorRegistry: - arguments: [!tagged { tag: 'widget.aggregator', index_by: 'key' }] + arguments: [ !tagged { tag: 'widget.aggregator', index_by: 'key' } ] diff --git a/src/Command/AggregateCommand.php b/src/Command/AggregateCommand.php index 8ba8c2d..17f89d5 100644 --- a/src/Command/AggregateCommand.php +++ b/src/Command/AggregateCommand.php @@ -45,8 +45,7 @@ protected function configure() $this ->setName('app:aggregate-data') ->setDescription('Aggregate data') - ->addArgument('specific', InputArgument::OPTIONAL) - ; + ->addArgument('specific', InputArgument::OPTIONAL); } /** diff --git a/src/Service/Aggregator/RoleAggregator.php b/src/Service/Aggregator/RoleAggregator.php new file mode 100644 index 0000000..1b36728 --- /dev/null +++ b/src/Service/Aggregator/RoleAggregator.php @@ -0,0 +1,33 @@ +em = $em; + } + + public function getName() + { + return self::NAME; + } + + public function aggregate(DateTime $startDate = null) + { + return 0; + } +} From 8c2477fbbe664beffe138f9d6538abac2c040579 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Tue, 6 Dec 2022 14:56:58 +0100 Subject: [PATCH 04/86] Commit before cleanign up entity again. --- src/Command/AggregateCommand.php | 11 +++- src/Migrations/Version20221205134917.php | 66 ----------------------- src/Migrations/Version20221206084529.php | 66 +++++++++++++++++++++++ src/Service/Aggregator/RoleAggregator.php | 33 +++++++++++- 4 files changed, 108 insertions(+), 68 deletions(-) delete mode 100644 src/Migrations/Version20221205134917.php create mode 100644 src/Migrations/Version20221206084529.php diff --git a/src/Command/AggregateCommand.php b/src/Command/AggregateCommand.php index 17f89d5..1769487 100644 --- a/src/Command/AggregateCommand.php +++ b/src/Command/AggregateCommand.php @@ -65,7 +65,16 @@ protected function execute(InputInterface $input, OutputInterface $output) $output->writeln(['Start aggregation...']); $io = new SymfonyStyle($input, $output); - $aggregators = $this->aggregatorRegistry->getAggregators(); + $aggregators = $this->aggregatorRegistry->getAggregators(); + + $index = 0; + foreach ($aggregators as $aggregator) { + $index++; + if ($index === 9) { + $aggregator->aggregateWithOutput($output); + } + } + return 0; $specific = $input->getArgument('specific'); diff --git a/src/Migrations/Version20221205134917.php b/src/Migrations/Version20221205134917.php deleted file mode 100644 index f3ce1ff..0000000 --- a/src/Migrations/Version20221205134917.php +++ /dev/null @@ -1,66 +0,0 @@ -addSql('CREATE SEQUENCE aggregated_person_role_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); - $this->addSql( - 'CREATE TABLE hc_aggregated_person_role (id INT NOT NULL, person_id INT DEFAULT NULL, role_id INT NOT NULL, group_id INT NOT NULL, midata_id INT DEFAULT NULL, nickname VARCHAR(255) NOT NULL, start_at DATE NOT NULL, end_at DATE DEFAULT NULL, PRIMARY KEY(id))' - ); - $this->addSql('CREATE INDEX IDX_40B8716BD3728193 ON hc_aggregated_person_role (person_id)'); - $this->addSql('CREATE INDEX IDX_40B8716B88987678 ON hc_aggregated_person_role (role_id)'); - $this->addSql('CREATE INDEX IDX_40B8716B2F68B530 ON hc_aggregated_person_role (group_id)'); - $this->addSql('CREATE INDEX IDX_40B8716B34C0398A ON hc_aggregated_person_role (midata_id)'); - $this->addSql( - 'ALTER TABLE hc_aggregated_person_role ADD CONSTRAINT FK_40B8716BD3728193 FOREIGN KEY (person_id) REFERENCES midata_person (id) NOT DEFERRABLE INITIALLY IMMEDIATE' - ); - $this->addSql( - 'ALTER TABLE hc_aggregated_person_role ADD CONSTRAINT FK_40B8716B88987678 FOREIGN KEY (role_id) REFERENCES midata_role (id) NOT DEFERRABLE INITIALLY IMMEDIATE' - ); - $this->addSql( - 'ALTER TABLE hc_aggregated_person_role ADD CONSTRAINT FK_40B8716B2F68B530 FOREIGN KEY (group_id) REFERENCES midata_group (id) NOT DEFERRABLE INITIALLY IMMEDIATE' - ); - $this->addSql( - 'ALTER TABLE hc_aggregated_person_role ADD CONSTRAINT FK_40B8716B34C0398A FOREIGN KEY (midata_id) REFERENCES midata_person_role (id) NOT DEFERRABLE INITIALLY IMMEDIATE' - ); - $this->addSql( - "INSERT INTO hc_aggregated_person_role (id, person_id, role_id, group_id, midata_id, nickname, start_at, end_at) - SELECT - nextval('aggregated_person_role_id_seq'), - midata_person_role.person_id, - midata_person_role.role_id, - midata_person_role.group_id, - midata_person_role.id, - midata_person.nickname, - midata_person_role.created_at, - midata_person_role.deleted_at - FROM - midata_person_role - JOIN midata_person ON midata_person_role.person_id = midata_person.id;" - ); - } - - public function down(Schema $schema): void - { - // this down() migration is auto-generated, please modify it to your needs - $this->addSql('DROP SEQUENCE aggregated_person_role_id_seq CASCADE'); - $this->addSql('DROP TABLE hc_aggregated_person_role'); - } -} diff --git a/src/Migrations/Version20221206084529.php b/src/Migrations/Version20221206084529.php new file mode 100644 index 0000000..46c582e --- /dev/null +++ b/src/Migrations/Version20221206084529.php @@ -0,0 +1,66 @@ +addSql('CREATE SEQUENCE aggregated_person_role_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); + $this->addSql( + 'CREATE TABLE aggregated_person_role (id INT NOT NULL, person_id INT DEFAULT NULL, role_id INT NOT NULL, group_id INT NOT NULL, midata_id INT DEFAULT NULL, nickname VARCHAR(255) NOT NULL, start_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, end_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, PRIMARY KEY(id))' + ); + $this->addSql('CREATE INDEX IDX_40B8716B217BBB47 ON aggregated_person_role (person_id)'); + $this->addSql('CREATE INDEX IDX_40B8716BD60322AC ON aggregated_person_role (role_id)'); + $this->addSql('CREATE INDEX IDX_40B8716BFE54D947 ON aggregated_person_role (group_id)'); + $this->addSql('CREATE INDEX IDX_40B8716B2E18B78F ON aggregated_person_role (midata_id)'); + $this->addSql( + 'ALTER TABLE aggregated_person_role ADD CONSTRAINT FK_40B8716B217BBB47 FOREIGN KEY (person_id) REFERENCES midata_person (id) NOT DEFERRABLE INITIALLY IMMEDIATE' + ); + $this->addSql( + 'ALTER TABLE aggregated_person_role ADD CONSTRAINT FK_40B8716BD60322AC FOREIGN KEY (role_id) REFERENCES midata_role (id) NOT DEFERRABLE INITIALLY IMMEDIATE' + ); + $this->addSql( + 'ALTER TABLE aggregated_person_role ADD CONSTRAINT FK_40B8716BFE54D947 FOREIGN KEY (group_id) REFERENCES midata_group (id) NOT DEFERRABLE INITIALLY IMMEDIATE' + ); + $this->addSql( + 'ALTER TABLE aggregated_person_role ADD CONSTRAINT FK_40B8716B2E18B78F FOREIGN KEY (midata_id) REFERENCES midata_person_role (id) NOT DEFERRABLE INITIALLY IMMEDIATE' + ); + $this->addSql( + "INSERT INTO aggregated_person_role (id, person_id, role_id, group_id, midata_id, nickname, start_at, end_at) + SELECT + nextval('aggregated_person_role_id_seq'), + midata_person_role.person_id, + midata_person_role.role_id, + midata_person_role.group_id, + midata_person_role.id, + midata_person.nickname, + midata_person_role.created_at, + midata_person_role.deleted_at + FROM + midata_person_role + JOIN midata_person ON midata_person_role.person_id = midata_person.id;" + ); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('DROP SEQUENCE aggregated_person_role_id_seq CASCADE'); + $this->addSql('DROP TABLE aggregated_person_role'); + } +} diff --git a/src/Service/Aggregator/RoleAggregator.php b/src/Service/Aggregator/RoleAggregator.php index 1b36728..db7da88 100644 --- a/src/Service/Aggregator/RoleAggregator.php +++ b/src/Service/Aggregator/RoleAggregator.php @@ -2,23 +2,35 @@ namespace App\Service\Aggregator; +use App\Repository\Aggregated\AggregatedPersonRoleRepository; use App\Repository\Aggregated\AggregatedQuapRepository; use App\Repository\Midata\GroupRepository; +use App\Repository\Midata\PersonRoleRepository; +use App\Repository\Midata\RoleRepository; use App\Repository\Quap\QuestionnaireRepository; use DateTime; use Doctrine\ORM\EntityManagerInterface; +use Symfony\Component\Console\Output\OutputInterface; class RoleAggregator extends WidgetAggregator { private const NAME = 'widget.roles'; + private AggregatedPersonRoleRepository $personRoleRepository; + + private PersonRoleRepository $midataPersonRoleRepository; + public function __construct( EntityManagerInterface $em, - GroupRepository $groupRepository + GroupRepository $groupRepository, + AggregatedPersonRoleRepository $personRoleRepository, + PersonRoleRepository $midataPersonRoleRepository ) { parent::__construct($groupRepository); $this->em = $em; + $this->personRoleRepository = $personRoleRepository; + $this->midataPersonRoleRepository = $midataPersonRoleRepository; } public function getName() @@ -30,4 +42,23 @@ public function aggregate(DateTime $startDate = null) { return 0; } + + public function aggregateWithOutput(OutputInterface $output) + { + $listOfUnfinished = $this->personRoleRepository->getUnfinished(); + + foreach ($listOfUnfinished as $unfinished) { + $midataObject = $this->midataPersonRoleRepository->find($unfinished->getMidata()); + $deletedAt = $midataObject->getDeletedAt(); + if ($deletedAt !== null) { + // Update $unfinished end_at to the deleted at + $output->writeln('somethings wrong'); + } + } + //flush + + //implement aggregating the new stuff + //implement the deletion stuff + return 0; + } } From 0afa10aa6487ac4ddf1742e5a885d6925b425610 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Tue, 6 Dec 2022 15:12:37 +0100 Subject: [PATCH 05/86] Clean up Entity --- ...ersonRole.php => AggregatedPersonRole.php} | 70 +++++++++---------- ...06084529.php => Version20221206140842.php} | 12 ++-- ...php => AggregatedPersonRoleRepository.php} | 18 ++--- src/Service/Aggregator/RoleAggregator.php | 4 +- 4 files changed, 50 insertions(+), 54 deletions(-) rename src/Entity/Aggregated/{aggregatedPersonRole.php => AggregatedPersonRole.php} (62%) rename src/Migrations/{Version20221206084529.php => Version20221206140842.php} (92%) rename src/Repository/Aggregated/{aggregatedPersonRoleRepository.php => AggregatedPersonRoleRepository.php} (64%) diff --git a/src/Entity/Aggregated/aggregatedPersonRole.php b/src/Entity/Aggregated/AggregatedPersonRole.php similarity index 62% rename from src/Entity/Aggregated/aggregatedPersonRole.php rename to src/Entity/Aggregated/AggregatedPersonRole.php index f5a787a..f0e2d15 100644 --- a/src/Entity/Aggregated/aggregatedPersonRole.php +++ b/src/Entity/Aggregated/AggregatedPersonRole.php @@ -6,14 +6,13 @@ use App\Entity\Midata\Person; use App\Entity\Midata\PersonRole; use App\Entity\Midata\Role; -use App\Repository\Aggregated\aggregatedPersonRoleRepository; +use App\Repository\Aggregated\AggregatedPersonRoleRepository; use Doctrine\ORM\Mapping as ORM; /** - * @ORM\Entity(repositoryClass=aggregatedPersonRoleRepository::class) - * @ORM\Table(name="hc_aggregated_leader_overview_leader") + * @ORM\Entity(repositoryClass=AggregatedPersonRoleRepository::class) */ -class aggregatedPersonRole +class AggregatedPersonRole { /** * @ORM\Id @@ -22,23 +21,27 @@ class aggregatedPersonRole */ private $id; - /** - * @ORM\ManyToOne(targetEntity=Person::class) - */ - private $person_id; - /** * @ORM\ManyToOne(targetEntity=Role::class) * @ORM\JoinColumn(nullable=false) */ - private $role_id; + private $role; /** * @ORM\ManyToOne(targetEntity=Group::class) * @ORM\JoinColumn(nullable=false) */ - private $group_id; + private $group; + /** + * @ORM\ManyToOne(targetEntity=Person::class) + */ + private $person; + + /** + * @ORM\ManyToOne(targetEntity=PersonRole::class) + */ + private $midata; /** * @ORM\Column(type="string", length=255) @@ -46,57 +49,52 @@ class aggregatedPersonRole private $nickname; /** - * @ORM\Column(type="date") + * @ORM\Column(type="datetime") */ private $start_at; /** - * @ORM\Column(type="date", nullable=true) + * @ORM\Column(type="datetime", nullable=true) */ private $end_at; - /** - * @ORM\ManyToOne(targetEntity=PersonRole::class) - */ - private $midata_id; - public function getId(): ?int { return $this->id; } - public function getPersonId(): ?Person + public function getRole(): ?Role { - return $this->person_id; + return $this->role; } - public function setPersonId(?Person $person_id): self + public function setRole(?Role $role): self { - $this->person_id = $person_id; + $this->role = $role; return $this; } - public function getRoleId(): ?Role + public function getPerson(): ?Person { - return $this->role_id; + return $this->person; } - public function setRoleId(?Role $role_id): self + public function setPerson(?Person $person): self { - $this->role_id = $role_id; + $this->person = $person; return $this; } - public function getGroupId(): ?Group + public function getMidata(): ?PersonRole { - return $this->group_id; + return $this->midata; } - public function setGroupId(?Group $group_id): self + public function setMidata(?PersonRole $midata): self { - $this->group_id = $group_id; + $this->midata = $midata; return $this; } @@ -137,15 +135,15 @@ public function setEndAt(\DateTimeInterface $end_at): self return $this; } - public function getMidataId(): ?PersonRole + public function setGroup($group): self { - return $this->midata_id; + $this->group = $group; + + return $this; } - public function setMidataId(?PersonRole $midata_id): self + public function getGroup() { - $this->midata_id = $midata_id; - - return $this; + return $this->group; } } diff --git a/src/Migrations/Version20221206084529.php b/src/Migrations/Version20221206140842.php similarity index 92% rename from src/Migrations/Version20221206084529.php rename to src/Migrations/Version20221206140842.php index 46c582e..263b1de 100644 --- a/src/Migrations/Version20221206084529.php +++ b/src/Migrations/Version20221206140842.php @@ -10,7 +10,7 @@ /** * Auto-generated Migration: Please modify to your needs! */ -final class Version20221206084529 extends AbstractMigration +final class Version20221206140842 extends AbstractMigration { public function getDescription(): string { @@ -22,21 +22,21 @@ public function up(Schema $schema): void // this up() migration is auto-generated, please modify it to your needs $this->addSql('CREATE SEQUENCE aggregated_person_role_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); $this->addSql( - 'CREATE TABLE aggregated_person_role (id INT NOT NULL, person_id INT DEFAULT NULL, role_id INT NOT NULL, group_id INT NOT NULL, midata_id INT DEFAULT NULL, nickname VARCHAR(255) NOT NULL, start_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, end_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, PRIMARY KEY(id))' + 'CREATE TABLE aggregated_person_role (id INT NOT NULL, role_id INT NOT NULL, group_id INT NOT NULL, person_id INT DEFAULT NULL, midata_id INT DEFAULT NULL, nickname VARCHAR(255) NOT NULL, start_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, end_at TIMESTAMP(0) WITHOUT TIME ZONE, PRIMARY KEY(id))' ); - $this->addSql('CREATE INDEX IDX_40B8716B217BBB47 ON aggregated_person_role (person_id)'); $this->addSql('CREATE INDEX IDX_40B8716BD60322AC ON aggregated_person_role (role_id)'); $this->addSql('CREATE INDEX IDX_40B8716BFE54D947 ON aggregated_person_role (group_id)'); + $this->addSql('CREATE INDEX IDX_40B8716B217BBB47 ON aggregated_person_role (person_id)'); $this->addSql('CREATE INDEX IDX_40B8716B2E18B78F ON aggregated_person_role (midata_id)'); - $this->addSql( - 'ALTER TABLE aggregated_person_role ADD CONSTRAINT FK_40B8716B217BBB47 FOREIGN KEY (person_id) REFERENCES midata_person (id) NOT DEFERRABLE INITIALLY IMMEDIATE' - ); $this->addSql( 'ALTER TABLE aggregated_person_role ADD CONSTRAINT FK_40B8716BD60322AC FOREIGN KEY (role_id) REFERENCES midata_role (id) NOT DEFERRABLE INITIALLY IMMEDIATE' ); $this->addSql( 'ALTER TABLE aggregated_person_role ADD CONSTRAINT FK_40B8716BFE54D947 FOREIGN KEY (group_id) REFERENCES midata_group (id) NOT DEFERRABLE INITIALLY IMMEDIATE' ); + $this->addSql( + 'ALTER TABLE aggregated_person_role ADD CONSTRAINT FK_40B8716B217BBB47 FOREIGN KEY (person_id) REFERENCES midata_person (id) NOT DEFERRABLE INITIALLY IMMEDIATE' + ); $this->addSql( 'ALTER TABLE aggregated_person_role ADD CONSTRAINT FK_40B8716B2E18B78F FOREIGN KEY (midata_id) REFERENCES midata_person_role (id) NOT DEFERRABLE INITIALLY IMMEDIATE' ); diff --git a/src/Repository/Aggregated/aggregatedPersonRoleRepository.php b/src/Repository/Aggregated/AggregatedPersonRoleRepository.php similarity index 64% rename from src/Repository/Aggregated/aggregatedPersonRoleRepository.php rename to src/Repository/Aggregated/AggregatedPersonRoleRepository.php index 4e4046c..9d53e78 100644 --- a/src/Repository/Aggregated/aggregatedPersonRoleRepository.php +++ b/src/Repository/Aggregated/AggregatedPersonRoleRepository.php @@ -2,25 +2,25 @@ namespace App\Repository\Aggregated; -use App\Entity\Aggregated\aggregatedPersonRole; +use App\Entity\Aggregated\AggregatedPersonRole; use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; use Doctrine\Persistence\ManagerRegistry; /** - * @method aggregatedPersonRole|null find($id, $lockMode = null, $lockVersion = null) - * @method aggregatedPersonRole|null findOneBy(array $criteria, array $orderBy = null) - * @method aggregatedPersonRole[] findAll() - * @method aggregatedPersonRole[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null) + * @method AggregatedPersonRole|null find($id, $lockMode = null, $lockVersion = null) + * @method AggregatedPersonRole|null findOneBy(array $criteria, array $orderBy = null) + * @method AggregatedPersonRole[] findAll() + * @method AggregatedPersonRole[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null) */ -class aggregatedPersonRoleRepository extends ServiceEntityRepository +class AggregatedPersonRoleRepository extends ServiceEntityRepository { public function __construct(ManagerRegistry $registry) { - parent::__construct($registry, aggregatedPersonRole::class); + parent::__construct($registry, AggregatedPersonRole::class); } // /** - // * @return aggregatedPersonRole[] Returns an array of aggregatedPersonRole objects + // * @return AggregatedPersonRole[] Returns an array of AggregatedPersonRole objects // */ /* public function findByExampleField($value) @@ -37,7 +37,7 @@ public function findByExampleField($value) */ /* - public function findOneBySomeField($value): ?aggregatedPersonRole + public function findOneBySomeField($value): ?AggregatedPersonRole { return $this->createQueryBuilder('a') ->andWhere('a.exampleField = :val') diff --git a/src/Service/Aggregator/RoleAggregator.php b/src/Service/Aggregator/RoleAggregator.php index db7da88..17ad900 100644 --- a/src/Service/Aggregator/RoleAggregator.php +++ b/src/Service/Aggregator/RoleAggregator.php @@ -16,20 +16,18 @@ class RoleAggregator extends WidgetAggregator { private const NAME = 'widget.roles'; - private AggregatedPersonRoleRepository $personRoleRepository; + private string $personRoleRepository; private PersonRoleRepository $midataPersonRoleRepository; public function __construct( EntityManagerInterface $em, GroupRepository $groupRepository, - AggregatedPersonRoleRepository $personRoleRepository, PersonRoleRepository $midataPersonRoleRepository ) { parent::__construct($groupRepository); $this->em = $em; - $this->personRoleRepository = $personRoleRepository; $this->midataPersonRoleRepository = $midataPersonRoleRepository; } From 14ecb8af621ef6e81d16a3fe0b0f09e55697d18f Mon Sep 17 00:00:00 2001 From: Sebastian Date: Tue, 6 Dec 2022 16:25:42 +0100 Subject: [PATCH 06/86] Add aggregation to update end_date when finished --- .../AggregatedPersonRoleRepository.php | 11 +++++++++++ src/Service/Aggregator/RoleAggregator.php | 19 ++++++++++++++----- 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/src/Repository/Aggregated/AggregatedPersonRoleRepository.php b/src/Repository/Aggregated/AggregatedPersonRoleRepository.php index 9d53e78..7b12d08 100644 --- a/src/Repository/Aggregated/AggregatedPersonRoleRepository.php +++ b/src/Repository/Aggregated/AggregatedPersonRoleRepository.php @@ -19,6 +19,17 @@ public function __construct(ManagerRegistry $registry) parent::__construct($registry, AggregatedPersonRole::class); } + /** + * @return AggregatedPersonRole[] + */ + public function getUnfinished(): array + { + return $this->createQueryBuilder('a') + ->where('a.end_at IS NULL') + ->getQuery() + ->getResult(); + } + // /** // * @return AggregatedPersonRole[] Returns an array of AggregatedPersonRole objects // */ diff --git a/src/Service/Aggregator/RoleAggregator.php b/src/Service/Aggregator/RoleAggregator.php index 17ad900..4422794 100644 --- a/src/Service/Aggregator/RoleAggregator.php +++ b/src/Service/Aggregator/RoleAggregator.php @@ -16,18 +16,22 @@ class RoleAggregator extends WidgetAggregator { private const NAME = 'widget.roles'; - private string $personRoleRepository; + private AggregatedPersonRoleRepository $personRoleRepository; private PersonRoleRepository $midataPersonRoleRepository; + private EntityManagerInterface $em; + public function __construct( EntityManagerInterface $em, GroupRepository $groupRepository, + AggregatedPersonRoleRepository $personRoleRepository, PersonRoleRepository $midataPersonRoleRepository ) { parent::__construct($groupRepository); $this->em = $em; + $this->personRoleRepository = $personRoleRepository; $this->midataPersonRoleRepository = $midataPersonRoleRepository; } @@ -48,12 +52,17 @@ public function aggregateWithOutput(OutputInterface $output) foreach ($listOfUnfinished as $unfinished) { $midataObject = $this->midataPersonRoleRepository->find($unfinished->getMidata()); $deletedAt = $midataObject->getDeletedAt(); - if ($deletedAt !== null) { - // Update $unfinished end_at to the deleted at - $output->writeln('somethings wrong'); + if ($unfinished->getId() == 217) { + $output->writeln(json_encode($unfinished->getEndAt())); + $unfinished->setEndAt($deletedAt); + $this->em->persist($unfinished); + + $output->writeln(json_encode($unfinished->getEndAt())); + $output->writeln(json_encode($this->em->isOpen())); } } - //flush + // TODO: Why does this not actually persist the changes? + $this->em->flush(); //implement aggregating the new stuff //implement the deletion stuff From 5dcfd7fbc199d52ea7a0f564fb592d38f327405c Mon Sep 17 00:00:00 2001 From: Sebastian Date: Wed, 7 Dec 2022 08:49:27 +0100 Subject: [PATCH 07/86] Implement Aggregation for new Employments and fixes to Aggregation --- src/Command/AggregateCommand.php | 9 ----- .../Aggregated/AggregatedPersonRole.php | 2 +- .../AggregatedPersonRoleRepository.php | 8 ++++ .../Midata/PersonRoleRepository.php | 36 ++++++++++++++++-- src/Service/Aggregator/RoleAggregator.php | 37 ++++++++++--------- 5 files changed, 60 insertions(+), 32 deletions(-) diff --git a/src/Command/AggregateCommand.php b/src/Command/AggregateCommand.php index 1769487..56bcc16 100644 --- a/src/Command/AggregateCommand.php +++ b/src/Command/AggregateCommand.php @@ -67,15 +67,6 @@ protected function execute(InputInterface $input, OutputInterface $output) $aggregators = $this->aggregatorRegistry->getAggregators(); - $index = 0; - foreach ($aggregators as $aggregator) { - $index++; - if ($index === 9) { - $aggregator->aggregateWithOutput($output); - } - } - return 0; - $specific = $input->getArgument('specific'); /** @var WidgetAggregator $aggregator */ diff --git a/src/Entity/Aggregated/AggregatedPersonRole.php b/src/Entity/Aggregated/AggregatedPersonRole.php index f0e2d15..2385603 100644 --- a/src/Entity/Aggregated/AggregatedPersonRole.php +++ b/src/Entity/Aggregated/AggregatedPersonRole.php @@ -128,7 +128,7 @@ public function getEndAt(): ?\DateTimeInterface return $this->end_at; } - public function setEndAt(\DateTimeInterface $end_at): self + public function setEndAt(?\DateTimeInterface $end_at): self { $this->end_at = $end_at; diff --git a/src/Repository/Aggregated/AggregatedPersonRoleRepository.php b/src/Repository/Aggregated/AggregatedPersonRoleRepository.php index 7b12d08..fa3bdd5 100644 --- a/src/Repository/Aggregated/AggregatedPersonRoleRepository.php +++ b/src/Repository/Aggregated/AggregatedPersonRoleRepository.php @@ -30,6 +30,14 @@ public function getUnfinished(): array ->getResult(); } + public function getHighestAggregatedMidataIndex(): int + { + return $this->createQueryBuilder('a') + ->select('MAX(a.midata)') + ->getQuery() + ->getResult()[0][1]; + } + // /** // * @return AggregatedPersonRole[] Returns an array of AggregatedPersonRole objects // */ diff --git a/src/Repository/Midata/PersonRoleRepository.php b/src/Repository/Midata/PersonRoleRepository.php index 4c11e2c..07df90d 100644 --- a/src/Repository/Midata/PersonRoleRepository.php +++ b/src/Repository/Midata/PersonRoleRepository.php @@ -48,8 +48,14 @@ public function findRolesForPersonInGroup(int $groupId, int $personId) * @return array|PersonRole[] * @throws \Doctrine\DBAL\Exception */ - public function findAllByDate(string $date, array $groupIds, array $groupTypes, array $leaderRoles, array $memberRoles, array $rolePriority): array - { + public function findAllByDate( + string $date, + array $groupIds, + array $groupTypes, + array $leaderRoles, + array $memberRoles, + array $rolePriority + ): array { $connection = $this->_em->getConnection(); $statement = $connection->executeQuery( "SELECT * FROM ( @@ -184,7 +190,15 @@ public function findMemberCountForPeriodByGenderGroupTypeAndGroupIds( where midata_person.group_id in (?) and midata_role.role_type in (?) );", - [$groupIds, $gender, $date, $date, WidgetAggregator::$memberRoleTypes, $groupIds, WidgetAggregator::$leadersRoleTypes], + [ + $groupIds, + $gender, + $date, + $date, + WidgetAggregator::$memberRoleTypes, + $groupIds, + WidgetAggregator::$leadersRoleTypes + ], [ Connection::PARAM_INT_ARRAY, ParameterType::STRING, @@ -252,7 +266,9 @@ public function findPersonRoles(int $personId, string $endDate, array $groupIds) ->where('person.id = :id') ->andWhere('g.id IN (:groupIds)') ->andWhere('role.roleType IN (:roles)') - ->andWhere('(personRole.createdAt < :endDate AND (personRole.deletedAt IS NULL OR personRole.deletedAt > :endDate))') + ->andWhere( + '(personRole.createdAt < :endDate AND (personRole.deletedAt IS NULL OR personRole.deletedAt > :endDate))' + ) ->setParameter('id', $personId) ->setParameter('endDate', $endDate) ->setParameter('groupIds', $groupIds) @@ -724,4 +740,16 @@ public function findAllPersonInGroupByRole(array $groupTypes, array $roleTypes): ); return $statement->fetchAll(); } + + /** + * @return PersonRole[] + */ + public function findAllWithHigherIndex(int $highestAggregatedMidataIndex): array + { + return $this->createQueryBuilder('a') + ->andWhere('a.id > :index') + ->setParameter('index', $highestAggregatedMidataIndex) + ->getQuery() + ->getResult(); + } } diff --git a/src/Service/Aggregator/RoleAggregator.php b/src/Service/Aggregator/RoleAggregator.php index 4422794..a97c21f 100644 --- a/src/Service/Aggregator/RoleAggregator.php +++ b/src/Service/Aggregator/RoleAggregator.php @@ -2,12 +2,10 @@ namespace App\Service\Aggregator; +use App\Entity\Aggregated\AggregatedPersonRole; use App\Repository\Aggregated\AggregatedPersonRoleRepository; -use App\Repository\Aggregated\AggregatedQuapRepository; use App\Repository\Midata\GroupRepository; use App\Repository\Midata\PersonRoleRepository; -use App\Repository\Midata\RoleRepository; -use App\Repository\Quap\QuestionnaireRepository; use DateTime; use Doctrine\ORM\EntityManagerInterface; use Symfony\Component\Console\Output\OutputInterface; @@ -35,37 +33,40 @@ public function __construct( $this->midataPersonRoleRepository = $midataPersonRoleRepository; } - public function getName() + public function getName(): string { return self::NAME; } public function aggregate(DateTime $startDate = null) { - return 0; - } - - public function aggregateWithOutput(OutputInterface $output) - { + // check if People have quit their jobs, and if so aggregate it $listOfUnfinished = $this->personRoleRepository->getUnfinished(); - foreach ($listOfUnfinished as $unfinished) { $midataObject = $this->midataPersonRoleRepository->find($unfinished->getMidata()); $deletedAt = $midataObject->getDeletedAt(); if ($unfinished->getId() == 217) { - $output->writeln(json_encode($unfinished->getEndAt())); $unfinished->setEndAt($deletedAt); $this->em->persist($unfinished); - - $output->writeln(json_encode($unfinished->getEndAt())); - $output->writeln(json_encode($this->em->isOpen())); } } - // TODO: Why does this not actually persist the changes? $this->em->flush(); - //implement aggregating the new stuff - //implement the deletion stuff - return 0; + // check if people have been employed and aggregate it + $highestAggregatedMidataIndex = $this->personRoleRepository->getHighestAggregatedMidataIndex(); + $newPersonRoles = $this->midataPersonRoleRepository->findAllWithHigherIndex($highestAggregatedMidataIndex); + foreach ($newPersonRoles as $newPersonRole) { + $aggregatedPersonRole = new AggregatedPersonRole(); + $aggregatedPersonRole->setMidata($newPersonRole); + $aggregatedPersonRole->setEndAt($newPersonRole->getDeletedAt()); + $aggregatedPersonRole->setGroup($newPersonRole->getGroup()); + $aggregatedPersonRole->setNickname($newPersonRole->getPerson()->getNickname()); + $aggregatedPersonRole->setPerson($newPersonRole->getPerson()); + $aggregatedPersonRole->setRole($newPersonRole->getRole()); + $aggregatedPersonRole->setStartAt($newPersonRole->getCreatedAt()); + + $this->em->persist($aggregatedPersonRole); + } + $this->em->flush(); } } From 8768d2270ed8dae2712053945bd36f423e42a459 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Wed, 7 Dec 2022 08:49:48 +0100 Subject: [PATCH 08/86] Implement Anonymization for deleted Persons --- src/Command/ImportFromJsonCommand.php | 37 +++++++++++++++++++++++---- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/src/Command/ImportFromJsonCommand.php b/src/Command/ImportFromJsonCommand.php index 859511c..bcfa932 100644 --- a/src/Command/ImportFromJsonCommand.php +++ b/src/Command/ImportFromJsonCommand.php @@ -2,6 +2,7 @@ namespace App\Command; +use App\Entity\Aggregated\AggregatedPersonRole; use App\Entity\Midata\Camp; use App\Entity\Midata\CampState; use App\Entity\Midata\Course; @@ -624,22 +625,46 @@ private function importPeople(OutputInterface $output) $person = $personRepository->find($id); - foreach ($this->em->getRepository(PersonEvent::class)->findBy(['person' => $person->getId()]) as $personEvent) { + foreach ( + $this->em->getRepository(PersonEvent::class)->findBy(['person' => $person->getId()]) as $personEvent + ) { $this->em->remove($personEvent); } - foreach ($this->em->getRepository(PersonQualification::class)->findBy(['person' => $person->getId()]) as $personQualification) { + foreach ( + $this->em->getRepository(PersonQualification::class)->findBy(['person' => $person->getId()] + ) as $personQualification + ) { $this->em->remove($personQualification); } - foreach ($this->em->getRepository(PersonRole::class)->findBy(['person' => $person->getId()]) as $personRole) { + foreach ( + $this->em->getRepository(PersonRole::class)->findBy(['person' => $person->getId()]) as $personRole + ) { $this->em->remove($personRole); } - foreach ($this->em->getRepository(Permission::class)->findBy(['person' => $person->getId()]) as $permission) { + foreach ( + $this->em->getRepository(Permission::class)->findBy(['person' => $person->getId()]) as $permission + ) { $this->em->remove($permission); } + /** + * We do not actually delete in aggregatedPersonRole but rather cut all information we have about it and just keep the information that someone once worked there in that role. + * @var $aggregatedPersonRole AggregatedPersonRole + */ + foreach ( + $this->em->getRepository(AggregatedPersonRole::class)->findBy(['person' => $person->getId()] + ) as $aggregatedPersonRole + ) { + $aggregatedPersonRole->setNickname('Deleted'); + $aggregatedPersonRole->setPerson(null); + $aggregatedPersonRole->setMidata(null); + $aggregatedPersonRole->setEndAt(new DateTimeImmutable()); + $this->em->persist($aggregatedPersonRole); + } + $this->em->remove($person); $this->em->flush(); @@ -799,7 +824,9 @@ private function importRoles(OutputInterface $output) if ($r['deleted_at']) { $deletedAt = new DateTimeImmutable($r['deleted_at']); if ($deletedAt < new DateTimeImmutable('0001-01-01T00:00:00+00:00')) { - $this->gelfLogger->warning(new SimpleLogMessage('person_role entity with invalid deleted_at date skipped')); + $this->gelfLogger->warning( + new SimpleLogMessage('person_role entity with invalid deleted_at date skipped') + ); continue; } $personRole->setDeletedAt(new DateTimeImmutable($r['deleted_at'])); From 12270cc6bf62637a3d979e1f628c6c6ba1880a98 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Wed, 7 Dec 2022 09:14:57 +0100 Subject: [PATCH 09/86] Remove unecessary comment --- .../AggregatedPersonRoleRepository.php | 29 ------------------- 1 file changed, 29 deletions(-) diff --git a/src/Repository/Aggregated/AggregatedPersonRoleRepository.php b/src/Repository/Aggregated/AggregatedPersonRoleRepository.php index fa3bdd5..44213e6 100644 --- a/src/Repository/Aggregated/AggregatedPersonRoleRepository.php +++ b/src/Repository/Aggregated/AggregatedPersonRoleRepository.php @@ -37,33 +37,4 @@ public function getHighestAggregatedMidataIndex(): int ->getQuery() ->getResult()[0][1]; } - - // /** - // * @return AggregatedPersonRole[] Returns an array of AggregatedPersonRole objects - // */ - /* - public function findByExampleField($value) - { - return $this->createQueryBuilder('a') - ->andWhere('a.exampleField = :val') - ->setParameter('val', $value) - ->orderBy('a.id', 'ASC') - ->setMaxResults(10) - ->getQuery() - ->getResult() - ; - } - */ - - /* - public function findOneBySomeField($value): ?AggregatedPersonRole - { - return $this->createQueryBuilder('a') - ->andWhere('a.exampleField = :val') - ->setParameter('val', $value) - ->getQuery() - ->getOneOrNullResult() - ; - } - */ } From abf70d8b2ef1acea3a2be441fa055397a78744f7 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Wed, 21 Jun 2023 11:49:08 +0200 Subject: [PATCH 10/86] add user information to sentry and clean up .env files --- .env.dist | 7 +++++++ .env.test | 2 +- src/EventListener/ExceptionListener.php | 4 +--- src/Service/Sentry.php | 23 +++++++++++++++++++++++ 4 files changed, 32 insertions(+), 4 deletions(-) diff --git a/.env.dist b/.env.dist index e7c8893..5916989 100644 --- a/.env.dist +++ b/.env.dist @@ -54,3 +54,10 @@ GRAYLOG_CLIENT_CERT= GRAYLOG_CLIENT_KEY= IMPORT_TARGET_DIR=data + +# Sentry (you can add your local sentry dsn here for easier debugging.) +SENTRY_DSN="" + +# XDebug config +XDEBUG_CONFIG="remote_host=" +PHP_IDE_CONFIG="" diff --git a/.env.test b/.env.test index 35dc886..2876d08 100644 --- a/.env.test +++ b/.env.test @@ -42,4 +42,4 @@ GRAYLOG_CLIENT_KEY= IMPORT_TARGET_DIR=data # Sentry -SENTRY_DSN=https://0a3b51c0452b4e1fa0bc6c54db908f59@sentry.digio.ch/26 +SENTRY_DSN="" diff --git a/src/EventListener/ExceptionListener.php b/src/EventListener/ExceptionListener.php index 822b267..8da1273 100644 --- a/src/EventListener/ExceptionListener.php +++ b/src/EventListener/ExceptionListener.php @@ -52,16 +52,14 @@ public function onKernelException(ExceptionEvent $event): void $exception = $event->getThrowable(); + $apiError = new ApiError(); if ($exception instanceof AccessDeniedHttpException) { - $apiError = new ApiError(); $apiError->setCode(JsonResponse::HTTP_FORBIDDEN); $apiError->setMessage($this->translator->trans('api.error.accessDenied')); } elseif (!($exception instanceof ApiException)) { - $apiError = new ApiError(); $apiError->setCode(JsonResponse::HTTP_INTERNAL_SERVER_ERROR); $apiError->setMessage($this->translator->trans('api.error.unknown')); } else { - $apiError = new ApiError(); $apiError->setCode($exception->getStatusCode()); $apiError->setMessage($exception->getMessage()); } diff --git a/src/Service/Sentry.php b/src/Service/Sentry.php index 63596df..bb15faf 100644 --- a/src/Service/Sentry.php +++ b/src/Service/Sentry.php @@ -2,10 +2,24 @@ namespace App\Service; +use App\DTO\Model\PbsUserDTO; +use Sentry\UserDataBag; use Symfony\Component\Security\Core\Exception\AccessDeniedException; +use Symfony\Component\Security\Core\Security; class Sentry { + + private $security; + + public function __construct(Security $security) + { + $this->security = $security; + } + /** + * Filter out acceptable Excepitons (HTTP 401) and sensitive data. + * @return callable + */ public function sentryFilter(): callable { return function (\Sentry\Event $event, ?\Sentry\EventHint $hint): ?\Sentry\Event { @@ -14,6 +28,15 @@ public function sentryFilter(): callable return null; } + $user = $this->security->getUser(); + if ($user instanceof PbsUserDTO) { + $userBag = new UserDataBag($user->getId(), null, null, null, null); + $userBag->setMetadata('Nickname', $user->getNickName()); + } else { + $userBag = new UserDataBag(null, null, null, $user->getUsername(), null); + $userBag->setMetadata('Is PbsUserDTO', false); + } + $event->setUser($userBag); return $event; }; } From 20e0877924b4383f5cf42dab3521c9a8ce175cac Mon Sep 17 00:00:00 2001 From: Sebastian Date: Wed, 21 Jun 2023 15:05:11 +0200 Subject: [PATCH 11/86] lint --- src/Service/Sentry.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Service/Sentry.php b/src/Service/Sentry.php index bb15faf..05b7360 100644 --- a/src/Service/Sentry.php +++ b/src/Service/Sentry.php @@ -9,7 +9,6 @@ class Sentry { - private $security; public function __construct(Security $security) From 77ba73aae163ea609655468d71520190bb3b8f9a Mon Sep 17 00:00:00 2001 From: Sebastian Date: Wed, 28 Jun 2023 16:37:08 +0200 Subject: [PATCH 12/86] add basic RoleOverview endpoint Colors aren't yet working --- config/packages/sentry.yaml | 3 +- config/routes/apps/widgets.yml | 5 + src/Command/ImportFromJsonCommand.php | 13 +++ .../Apps/Widgets/RoleOverviewController.php | 25 +++++ src/DTO/Mapper/RoleOverviewMapper.php | 63 +++++++++++ .../Widgets/RoleOverview/RoleOccupation.php | 83 ++++++++++++++ .../RoleOverview/RoleOccupationWrapper.php | 103 ++++++++++++++++++ .../Widgets/RoleOverview/RoleOverviewDTO.php | 64 +++++++++++ .../Aggregated/AggregatedPersonRole.php | 2 - src/Entity/General/GroupSettings.php | 68 ++++++++++++ src/Entity/Midata/Group.php | 28 +++++ src/Entity/Midata/Role.php | 22 +--- ...06140842.php => Version20230627132305.php} | 24 ++-- src/Migrations/Version20230628111910.php | 43 ++++++++ .../AggregatedPersonRoleRepository.php | 19 ++++ .../General/GroupSettingsRepository.php | 78 +++++++++++++ .../RoleOverviewDateRangeDataProvider.php | 62 +++++++++++ 17 files changed, 670 insertions(+), 35 deletions(-) create mode 100644 src/Controller/Api/Apps/Widgets/RoleOverviewController.php create mode 100644 src/DTO/Mapper/RoleOverviewMapper.php create mode 100644 src/DTO/Model/Apps/Widgets/RoleOverview/RoleOccupation.php create mode 100644 src/DTO/Model/Apps/Widgets/RoleOverview/RoleOccupationWrapper.php create mode 100644 src/DTO/Model/Apps/Widgets/RoleOverview/RoleOverviewDTO.php create mode 100644 src/Entity/General/GroupSettings.php rename src/Migrations/{Version20221206140842.php => Version20230627132305.php} (61%) create mode 100644 src/Migrations/Version20230628111910.php create mode 100644 src/Repository/General/GroupSettingsRepository.php create mode 100644 src/Service/DataProvider/RoleOverviewDateRangeDataProvider.php diff --git a/config/packages/sentry.yaml b/config/packages/sentry.yaml index 1803ef5..03ac559 100644 --- a/config/packages/sentry.yaml +++ b/config/packages/sentry.yaml @@ -1,8 +1,9 @@ sentry: dsn: "%env(SENTRY_DSN)%" options: + environment: "%env(APP_ENV)%" before_send: 'sentry.callback.before_send' - # send_default_pii: true + send_default_pii: true services: sentry.callback.before_send: diff --git a/config/routes/apps/widgets.yml b/config/routes/apps/widgets.yml index fb0f1d8..3ea75ed 100644 --- a/config/routes/apps/widgets.yml +++ b/config/routes/apps/widgets.yml @@ -43,3 +43,8 @@ geo_location: path: /geo-location methods: GET controller: App\Controller\Api\Apps\Widgets\GeoLocationController::getGeoLocations + +role_overview: + path: /role-overview + methods: GET + controller: App\Controller\Api\Apps\Widgets\RoleOverviewController:getRoleOverview diff --git a/src/Command/ImportFromJsonCommand.php b/src/Command/ImportFromJsonCommand.php index f448a29..ccd9fff 100644 --- a/src/Command/ImportFromJsonCommand.php +++ b/src/Command/ImportFromJsonCommand.php @@ -3,6 +3,7 @@ namespace App\Command; use App\Entity\Aggregated\AggregatedPersonRole; +use App\Entity\General\GroupSettings; use App\Entity\Midata\Camp; use App\Entity\Midata\CampState; use App\Entity\Midata\Course; @@ -390,6 +391,18 @@ private function importGroups(OutputInterface $output) $group->setId($gr['id']); $metadata = $this->em->getClassMetaData(get_class($group)); $metadata->setIdGenerator(new AssignedGenerator()); + + // create group settings + $groupSettings = new GroupSettings(); + $groupSettings->setGroup($group); + if ($group->getGroupType()->getGroupType() === GroupType::DEPARTMENT) { + $groupSettings->setRoleOverviewFilter(GroupSettings::DEFAULT_DEPARMENT_ROLES); + } elseif ($group->getGroupType()->getGroupType() === GroupType::REGION) { + $groupSettings->setRoleOverviewFilter(GroupSettings::DEFAULT_REGION_ROLES); + } elseif ($group->getGroupType()->getGroupType() === GroupType::CANTON) { + $groupSettings->setRoleOverviewFilter(GroupSettings::DEFAULT_CANTONAL_ROLES); + } + $this->em->persist($groupSettings); } $group->setName($gr['name']); diff --git a/src/Controller/Api/Apps/Widgets/RoleOverviewController.php b/src/Controller/Api/Apps/Widgets/RoleOverviewController.php new file mode 100644 index 0000000..0f26dd7 --- /dev/null +++ b/src/Controller/Api/Apps/Widgets/RoleOverviewController.php @@ -0,0 +1,25 @@ +denyAccessUnlessGranted(PermissionVoter::VIEWER, $widgetRequestData->getGroup()); + $result = $roleOverviewDateRangeDataProvider->getData($widgetRequestData->getGroup(), $dateAndDateRangeRequestData->getFrom()->format('Y-m-d'), $dateAndDateRangeRequestData->getTo()->format('Y-m-d')); + return $this->json($result); + } +} diff --git a/src/DTO/Mapper/RoleOverviewMapper.php b/src/DTO/Mapper/RoleOverviewMapper.php new file mode 100644 index 0000000..9c6d860 --- /dev/null +++ b/src/DTO/Mapper/RoleOverviewMapper.php @@ -0,0 +1,63 @@ + '#EEE09F', + 'Group::Woelfe' => '#3BB5DC', + 'Group::Pfadi' => '#9A7A54', + 'Group::Pio' => '#DD1F19', + 'Group::AbteilungsRover' => '#1DA650', + 'Group::Pta' => '#d9b826', + 'Group::Abteilung' => '#929292', + 'leaders' => '#929292' + ]; + + public static function createRoleOverviewDTO(Group $group): RoleOverviewDTO + { + $groupSettings = $group->getGroupSettings(); + $filter = $groupSettings->getRoleOverviewFilter(); + if(!$filter || !sizeof($filter)) { + if ($group->getGroupType()->getGroupType() === GroupType::DEPARTMENT) { + $filter = GroupSettings::DEFAULT_DEPARMENT_ROLES; + } elseif ($group->getGroupType()->getGroupType() === GroupType::REGION) { + $filter = GroupSettings::DEFAULT_REGION_ROLES; + } elseif ($group->getGroupType()->getGroupType() === GroupType::CANTON) { + $filter = GroupSettings::DEFAULT_CANTONAL_ROLES; + } + } + + return new RoleOverviewDTO($filter); + } + + public static function createRoleOccupationWrapper(Role $role, string $locale): RoleOccupationWrapper + { + if (str_contains($locale, 'it')) { + $roleName = $role->getItLabel(); + } elseif (str_contains($locale, 'fr')) { + $roleName = $role->getFrLabel(); + } else { + $roleName = $role->getDeLabel(); + } + return new RoleOccupationWrapper($roleName, $role->getRoleType(), ['#080', '#050']); // Colors not yet implemented + } + + public static function createRoleOccupation(AggregatedPersonRole $aggregatedPersonRole, string $from, string $to): RoleOccupation + { + $start = new \DateTime($from) < $aggregatedPersonRole->getStartAt() ? $aggregatedPersonRole->getStartAt()->format('Y-m-d') : $from; + $end = is_null($aggregatedPersonRole->getEndAt()) || new \DateTime($to) <= $aggregatedPersonRole->getEndAt() ? $to : $aggregatedPersonRole->getEndAt()->format('Y-m-d'); + return new RoleOccupation($aggregatedPersonRole->getNickname(), $start, $end); + } +} diff --git a/src/DTO/Model/Apps/Widgets/RoleOverview/RoleOccupation.php b/src/DTO/Model/Apps/Widgets/RoleOverview/RoleOccupation.php new file mode 100644 index 0000000..11f3741 --- /dev/null +++ b/src/DTO/Model/Apps/Widgets/RoleOverview/RoleOccupation.php @@ -0,0 +1,83 @@ +name = $name; + $this->from = $from; + $this->to = $to; + } + + /** + * @return string + */ + public function getName(): string + { + return $this->name; + } + + /** + * @param string $name + */ + public function setName(string $name): void + { + $this->name = $name; + } + + /** + * @return string + */ + public function getFrom(): string + { + return $this->from; + } + + /** + * @param string $from + */ + public function setFrom(string $from): void + { + $this->from = $from; + } + + /** + * @return string + */ + public function getTo(): string + { + return $this->to; + } + + /** + * @param string $to + */ + public function setTo(string $to): void + { + $this->to = $to; + } + + +} diff --git a/src/DTO/Model/Apps/Widgets/RoleOverview/RoleOccupationWrapper.php b/src/DTO/Model/Apps/Widgets/RoleOverview/RoleOccupationWrapper.php new file mode 100644 index 0000000..734c28a --- /dev/null +++ b/src/DTO/Model/Apps/Widgets/RoleOverview/RoleOccupationWrapper.php @@ -0,0 +1,103 @@ +role = $role; + $this->roleType = $roleType; + $this->colors = $colors; + } + + /** + * @return string + */ + public function getRole(): string + { + return $this->role; + } + + /** + * @param string $role + */ + public function setRole(string $role): void + { + $this->role = $role; + } + + /** + * @return string[] + */ + public function getColors(): array + { + return $this->colors; + } + + /** + * @param string[] $colors + */ + public function setColors(array $colors): void + { + $this->colors = $colors; + } + + /** + * @return RoleOccupation[]|null + */ + public function getData(): array + { + return $this->data; + } + + /** + * @param RoleOccupation[]|null $data + */ + public function setData(array $data): void + { + $this->data = $data; + } + + public function addData(RoleOccupation $occupation): void + { + $this->data[] = $occupation; + } + + /** + * @return string + */ + public function getRoleType(): string + { + return $this->roleType; + } + + /** + * @param string $roleType + */ + public function setRoleType(string $roleType): void + { + $this->roleType = $roleType; + } +} diff --git a/src/DTO/Model/Apps/Widgets/RoleOverview/RoleOverviewDTO.php b/src/DTO/Model/Apps/Widgets/RoleOverview/RoleOverviewDTO.php new file mode 100644 index 0000000..6acf3b0 --- /dev/null +++ b/src/DTO/Model/Apps/Widgets/RoleOverview/RoleOverviewDTO.php @@ -0,0 +1,64 @@ +filter = $filter; + } + + /** + * @return string[]|null + */ + public function getFilter(): ?array + { + return $this->filter; + } + + /** + * @param string[]|null $filter + */ + public function setFilter(?array $filter): void + { + $this->filter = $filter; + } + + + + /** + * @return array + */ + public function getData(): array + { + return $this->data; + } + + /** + * @param array $data + */ + public function setData(array $data): void + { + $this->data = $data; + } + + public function addData(RoleOccupationWrapper $roleOccupationWrapper): void + { + $this->data[] = $roleOccupationWrapper; + } + +} diff --git a/src/Entity/Aggregated/AggregatedPersonRole.php b/src/Entity/Aggregated/AggregatedPersonRole.php index 2385603..7b5be86 100644 --- a/src/Entity/Aggregated/AggregatedPersonRole.php +++ b/src/Entity/Aggregated/AggregatedPersonRole.php @@ -23,13 +23,11 @@ class AggregatedPersonRole /** * @ORM\ManyToOne(targetEntity=Role::class) - * @ORM\JoinColumn(nullable=false) */ private $role; /** * @ORM\ManyToOne(targetEntity=Group::class) - * @ORM\JoinColumn(nullable=false) */ private $group; diff --git a/src/Entity/General/GroupSettings.php b/src/Entity/General/GroupSettings.php new file mode 100644 index 0000000..96410a7 --- /dev/null +++ b/src/Entity/General/GroupSettings.php @@ -0,0 +1,68 @@ +id; + } + + public function getGroup(): ?Group + { + return $this->group; + } + + public function setGroup(?Group $group): self + { + $this->group = $group; + + return $this; + } + + public function getRoleOverviewFilter(): ?array + { + return $this->roleOverviewFilter; + } + + public function setRoleOverviewFilter(?array $roleOverviewFilter): self + { + $this->roleOverviewFilter = $roleOverviewFilter; + + return $this; + } +} diff --git a/src/Entity/Midata/Group.php b/src/Entity/Midata/Group.php index 8c38d93..1a67f64 100644 --- a/src/Entity/Midata/Group.php +++ b/src/Entity/Midata/Group.php @@ -2,6 +2,7 @@ namespace App\Entity\Midata; +use App\Entity\General\GroupSettings; use App\Repository\Midata\GroupRepository; use DateTimeImmutable; use Doctrine\Common\Collections\ArrayCollection; @@ -78,6 +79,11 @@ class Group */ private $personRoles; + /** + * @ORM\OneToOne(targetEntity=GroupSettings::class, mappedBy="group", cascade={"persist", "remove"}) + */ + private $groupSettings; + public function __construct() { $this->events = new ArrayCollection(); @@ -215,4 +221,26 @@ public function __toString() { return (string)$this->id; } + + public function getGroupSettings(): ?GroupSettings + { + return $this->groupSettings; + } + + public function setGroupSettings(?GroupSettings $groupSettings): self + { + // unset the owning side of the relation if necessary + if ($groupSettings === null && $this->groupSettings !== null) { + $this->groupSettings->setGroup(null); + } + + // set the owning side of the relation if necessary + if ($groupSettings !== null && $groupSettings->getGroup() !== $this) { + $groupSettings->setGroup($this); + } + + $this->roleOverviewFilter = $groupSettings; + + return $this; + } } diff --git a/src/Entity/Midata/Role.php b/src/Entity/Midata/Role.php index 0e008c2..fa74574 100644 --- a/src/Entity/Midata/Role.php +++ b/src/Entity/Midata/Role.php @@ -25,6 +25,8 @@ class Role + public const DEPARTMENT_FINANCIER = 'Group::Abteilung::Kassier'; + public const DEPARTMENT_LEADER = 'Group::Abteilung::Abteilungsleitung'; public const DEPARTMENT_LEADER_PTA = 'Group::Abteilung::StufenleitungPta'; public const DEPARTMENT_LEADER_ROVER = 'Group::Abteilung::StufenleitungRover'; public const DEPARTMENT_LEADER_PIO = 'Group::Abteilung::StufenleitungPio'; @@ -33,6 +35,8 @@ class Role public const DEPARTMENT_LEADER_BIBER = 'Group::Abteilung::StufenleitungBiber'; public const CANTONAL_LEADER = 'Group::Kantonalverband::Kantonsleitung'; + public const CANTONAL_FINANCIER = 'Group::Kantonalverband::Kassier'; + public const CANTONAL_COACH = 'Group::Kantonalverband::Coach'; public const CANTONAL_PRESIDENT = 'Group::Kantonalverband::Praesidium'; public const CANTONAL_BIBERSTUFE_V = 'Group::Kantonalverband::VerantwortungBiberstufe'; public const CANTONAL_WOLFSTUFE_V = 'Group::Kantonalverband::VerantwortungWolfstufe'; @@ -58,6 +62,8 @@ class Role public const CANTONAL_NACHHALTIGKEIT_V = 'Group::Kantonalverband::VerantwortungNachhaltigkeit'; public const REGIONAL_LEADER = 'Group::Region::Regionalleitung'; + public const REGIONAL_FINANCIER = 'Group::Region::Kassier'; + public const REGIONAL_COACH = 'Group::Region::Coach'; public const REGIONAL_PRESIDENT = 'Group::Region::Praesidium'; public const REGIONAL_BIBERSTUFE_V = 'Group::Region::VerantwortungBiberstufe'; public const REGIONAL_WOLFSTUFE_V = 'Group::Region::VerantwortungWolfstufe'; @@ -280,20 +286,4 @@ public function setFrLabel(?string $label) { $this->frLabel = $label; } - - /** - * @return null|string - */ - public function getValidity(): ?string - { - return $this->validity; - } - - /** - * @param null|string $validity - */ - public function setValidity(?string $validity) - { - $this->validity = $validity; - } } diff --git a/src/Migrations/Version20221206140842.php b/src/Migrations/Version20230627132305.php similarity index 61% rename from src/Migrations/Version20221206140842.php rename to src/Migrations/Version20230627132305.php index 263b1de..aad23ce 100644 --- a/src/Migrations/Version20221206140842.php +++ b/src/Migrations/Version20230627132305.php @@ -10,7 +10,7 @@ /** * Auto-generated Migration: Please modify to your needs! */ -final class Version20221206140842 extends AbstractMigration +final class Version20230627132305 extends AbstractMigration { public function getDescription(): string { @@ -21,25 +21,16 @@ public function up(Schema $schema): void { // this up() migration is auto-generated, please modify it to your needs $this->addSql('CREATE SEQUENCE aggregated_person_role_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); - $this->addSql( - 'CREATE TABLE aggregated_person_role (id INT NOT NULL, role_id INT NOT NULL, group_id INT NOT NULL, person_id INT DEFAULT NULL, midata_id INT DEFAULT NULL, nickname VARCHAR(255) NOT NULL, start_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, end_at TIMESTAMP(0) WITHOUT TIME ZONE, PRIMARY KEY(id))' - ); + $this->addSql('CREATE TABLE aggregated_person_role (id INT NOT NULL, role_id INT DEFAULT NULL, group_id INT DEFAULT NULL, person_id INT DEFAULT NULL, midata_id INT DEFAULT NULL, nickname VARCHAR(255) NOT NULL, start_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, end_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, PRIMARY KEY(id))'); $this->addSql('CREATE INDEX IDX_40B8716BD60322AC ON aggregated_person_role (role_id)'); $this->addSql('CREATE INDEX IDX_40B8716BFE54D947 ON aggregated_person_role (group_id)'); $this->addSql('CREATE INDEX IDX_40B8716B217BBB47 ON aggregated_person_role (person_id)'); $this->addSql('CREATE INDEX IDX_40B8716B2E18B78F ON aggregated_person_role (midata_id)'); - $this->addSql( - 'ALTER TABLE aggregated_person_role ADD CONSTRAINT FK_40B8716BD60322AC FOREIGN KEY (role_id) REFERENCES midata_role (id) NOT DEFERRABLE INITIALLY IMMEDIATE' - ); - $this->addSql( - 'ALTER TABLE aggregated_person_role ADD CONSTRAINT FK_40B8716BFE54D947 FOREIGN KEY (group_id) REFERENCES midata_group (id) NOT DEFERRABLE INITIALLY IMMEDIATE' - ); - $this->addSql( - 'ALTER TABLE aggregated_person_role ADD CONSTRAINT FK_40B8716B217BBB47 FOREIGN KEY (person_id) REFERENCES midata_person (id) NOT DEFERRABLE INITIALLY IMMEDIATE' - ); - $this->addSql( - 'ALTER TABLE aggregated_person_role ADD CONSTRAINT FK_40B8716B2E18B78F FOREIGN KEY (midata_id) REFERENCES midata_person_role (id) NOT DEFERRABLE INITIALLY IMMEDIATE' - ); + $this->addSql('ALTER TABLE aggregated_person_role ADD CONSTRAINT FK_40B8716BD60322AC FOREIGN KEY (role_id) REFERENCES midata_role (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE aggregated_person_role ADD CONSTRAINT FK_40B8716BFE54D947 FOREIGN KEY (group_id) REFERENCES midata_group (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE aggregated_person_role ADD CONSTRAINT FK_40B8716B217BBB47 FOREIGN KEY (person_id) REFERENCES midata_person (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE aggregated_person_role ADD CONSTRAINT FK_40B8716B2E18B78F FOREIGN KEY (midata_id) REFERENCES midata_person_role (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql( "INSERT INTO aggregated_person_role (id, person_id, role_id, group_id, midata_id, nickname, start_at, end_at) SELECT @@ -60,6 +51,7 @@ public function up(Schema $schema): void public function down(Schema $schema): void { // this down() migration is auto-generated, please modify it to your needs + $this->addSql('CREATE SCHEMA public'); $this->addSql('DROP SEQUENCE aggregated_person_role_id_seq CASCADE'); $this->addSql('DROP TABLE aggregated_person_role'); } diff --git a/src/Migrations/Version20230628111910.php b/src/Migrations/Version20230628111910.php new file mode 100644 index 0000000..d027a58 --- /dev/null +++ b/src/Migrations/Version20230628111910.php @@ -0,0 +1,43 @@ +addSql('CREATE SEQUENCE group_settings_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); + $this->addSql('CREATE TABLE group_settings (id INT NOT NULL, group_id INT DEFAULT NULL, role_overview_filter TEXT DEFAULT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE UNIQUE INDEX UNIQ_EC75ECBAFE54D947 ON group_settings (group_id)'); + $this->addSql('COMMENT ON COLUMN group_settings.role_overview_filter IS \'(DC2Type:array)\''); + $this->addSql('ALTER TABLE group_settings ADD CONSTRAINT FK_EC75ECBAFE54D947 FOREIGN KEY (group_id) REFERENCES midata_group (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql("INSERT INTO group_settings (id, group_id) + SELECT + nextval('group_settings_id_seq'), + midata_group.id + FROM + midata_group;"); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('CREATE SCHEMA public'); + $this->addSql('DROP SEQUENCE group_settings_id_seq CASCADE'); + $this->addSql('DROP TABLE group_settings'); + } +} diff --git a/src/Repository/Aggregated/AggregatedPersonRoleRepository.php b/src/Repository/Aggregated/AggregatedPersonRoleRepository.php index 44213e6..782ab86 100644 --- a/src/Repository/Aggregated/AggregatedPersonRoleRepository.php +++ b/src/Repository/Aggregated/AggregatedPersonRoleRepository.php @@ -3,6 +3,7 @@ namespace App\Repository\Aggregated; use App\Entity\Aggregated\AggregatedPersonRole; +use App\Entity\Midata\Group; use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; use Doctrine\Persistence\ManagerRegistry; @@ -37,4 +38,22 @@ public function getHighestAggregatedMidataIndex(): int ->getQuery() ->getResult()[0][1]; } + + /** + * @param Group $group + * @param $start + * @param $end + * @return AggregatedPersonRole[]|null + */ + public function findByGroupInTimeframe(Group $group, $start, $end) + { + return $this->createQueryBuilder('a') + ->where('a.group = :group_id') + ->andWhere('a.start_at BETWEEN :start AND :end OR a.end_at BETWEEN :start AND :end') + ->setParameter('group_id', $group->getId()) + ->setParameter('start', $start) + ->setParameter('end', $end) + ->getQuery() + ->getResult(); + } } diff --git a/src/Repository/General/GroupSettingsRepository.php b/src/Repository/General/GroupSettingsRepository.php new file mode 100644 index 0000000..1747396 --- /dev/null +++ b/src/Repository/General/GroupSettingsRepository.php @@ -0,0 +1,78 @@ + + * + * @method GroupSettings|null find($id, $lockMode = null, $lockVersion = null) + * @method GroupSettings|null findOneBy(array $criteria, array $orderBy = null) + * @method GroupSettings[] findAll() + * @method GroupSettings[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null) + */ +class GroupSettingsRepository extends ServiceEntityRepository +{ + public function __construct(ManagerRegistry $registry) + { + parent::__construct($registry, GroupSettings::class); + } + + /** + * @throws ORMException + * @throws OptimisticLockException + */ + public function add(GroupSettings $entity, bool $flush = true): void + { + $this->_em->persist($entity); + if ($flush) { + $this->_em->flush(); + } + } + + /** + * @throws ORMException + * @throws OptimisticLockException + */ + public function remove(GroupSettings $entity, bool $flush = true): void + { + $this->_em->remove($entity); + if ($flush) { + $this->_em->flush(); + } + } + + // /** + // * @return GroupSettings[] Returns an array of GroupSettings objects + // */ + /* + public function findByExampleField($value) + { + return $this->createQueryBuilder('g') + ->andWhere('g.exampleField = :val') + ->setParameter('val', $value) + ->orderBy('g.id', 'ASC') + ->setMaxResults(10) + ->getQuery() + ->getResult() + ; + } + */ + + /* + public function findOneBySomeField($value): ?GroupSettings + { + return $this->createQueryBuilder('g') + ->andWhere('g.exampleField = :val') + ->setParameter('val', $value) + ->getQuery() + ->getOneOrNullResult() + ; + } + */ +} diff --git a/src/Service/DataProvider/RoleOverviewDateRangeDataProvider.php b/src/Service/DataProvider/RoleOverviewDateRangeDataProvider.php new file mode 100644 index 0000000..91520d7 --- /dev/null +++ b/src/Service/DataProvider/RoleOverviewDateRangeDataProvider.php @@ -0,0 +1,62 @@ +groupRepository = $groupRepository; + $this->personRoleRepository = $personRoleRepository; + parent::__construct( + $groupRepository, + $groupTypeRepository, + $translator + ); + } + + public function getData(Group $group, string $from, string $to) + { + $dto = RoleOverviewMapper::createRoleOverviewDTO($group); + + $aggregatedPersonRoles = $this->personRoleRepository->findByGroupInTimeframe($group, $from, $to); + /** @var RoleOccupationWrapper[] $roleOccupationWrappers */ + $roleOccupationWrappers = []; + foreach ($aggregatedPersonRoles as $aggregatedPersonRole) { + $wrapperExistsAt = -1; + foreach ($roleOccupationWrappers as $key=>$roleOccupationWrapper) { + if($roleOccupationWrapper->getRoleType() === $aggregatedPersonRole->getRole()->getRoleType()) { + $wrapperExistsAt = $key; + } + } + if ($wrapperExistsAt !== -1) { + $roleOccupationWrappers[$wrapperExistsAt]->addData(RoleOverviewMapper::createRoleOccupation($aggregatedPersonRole, $from, $to)); + } else { + $wrapper = RoleOverviewMapper::createRoleOccupationWrapper($aggregatedPersonRole->getRole(), $this->translator->getLocale()); + $wrapper->addData(RoleOverviewMapper::createRoleOccupation($aggregatedPersonRole, $from, $to)); + $roleOccupationWrappers[] = $wrapper; + } + } + $dto->setData($roleOccupationWrappers); + return $dto; + } +} From 76ba101b2124a8837c34dfb6d7450618e9216a38 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Thu, 29 Jun 2023 15:39:31 +0200 Subject: [PATCH 13/86] add colors to role overview and filter endpoint --- config/routes/app_routes.yml | 6 +++ config/routes/apps/general.yml | 4 ++ .../Api/GroupSettingsController.php | 39 +++++++++++++++++++ src/DTO/Mapper/RoleOverviewMapper.php | 28 ++++++++----- .../AggregatedPersonRoleRepository.php | 1 + src/Service/Sentry.php | 5 ++- 6 files changed, 71 insertions(+), 12 deletions(-) create mode 100644 config/routes/apps/general.yml create mode 100644 src/Controller/Api/GroupSettingsController.php diff --git a/config/routes/app_routes.yml b/config/routes/app_routes.yml index 2f50377..c7db39d 100644 --- a/config/routes/app_routes.yml +++ b/config/routes/app_routes.yml @@ -10,3 +10,9 @@ quap: resource: "apps/quap.yml" prefix: /quap name_prefix: quap_ + +# General routes +general: + resource: "apps/general.yml" + prefix: /general + name_prefix: general_ diff --git a/config/routes/apps/general.yml b/config/routes/apps/general.yml new file mode 100644 index 0000000..59565f6 --- /dev/null +++ b/config/routes/apps/general.yml @@ -0,0 +1,4 @@ +post_role_overview_filter: + path: /filter + methods: POST + controller: App\Controller\Api\GroupSettingsController::postRoleOverviewFilter diff --git a/src/Controller/Api/GroupSettingsController.php b/src/Controller/Api/GroupSettingsController.php new file mode 100644 index 0000000..75b89de --- /dev/null +++ b/src/Controller/Api/GroupSettingsController.php @@ -0,0 +1,39 @@ +denyAccessUnlessGranted(PermissionVoter::VIEWER, $group); + $groupSettings = $group->getGroupSettings(); + $groupSettings->setRoleOverviewFilter(json_decode($request->getContent())); + $entityManager->persist($groupSettings); + $entityManager->flush(); + return new Response('', 204); + } + +} diff --git a/src/DTO/Mapper/RoleOverviewMapper.php b/src/DTO/Mapper/RoleOverviewMapper.php index 9c6d860..e15171c 100644 --- a/src/DTO/Mapper/RoleOverviewMapper.php +++ b/src/DTO/Mapper/RoleOverviewMapper.php @@ -14,15 +14,13 @@ class RoleOverviewMapper { - public const GROUP_TYPE_COLORS = [ - 'Group::Biber' => '#EEE09F', - 'Group::Woelfe' => '#3BB5DC', - 'Group::Pfadi' => '#9A7A54', - 'Group::Pio' => '#DD1F19', - 'Group::AbteilungsRover' => '#1DA650', - 'Group::Pta' => '#d9b826', - 'Group::Abteilung' => '#929292', - 'leaders' => '#929292' + const GROUP_TYPE_COLORS = [ + 'Biber' => ['#EEE09F', '#d6ca8f'], + 'Woelfe' => ['#3BB5DC', '#2f91b0'], + 'Pfadi' => ['#9A7A54', '#7b6243'], + 'Pio' => ['#DD1F19', '#b11914'], + 'Rover' => ['#1DA650', '#178540'], + 'Pta' => ['#d9b826', '#ae931e'], ]; public static function createRoleOverviewDTO(Group $group): RoleOverviewDTO @@ -51,7 +49,17 @@ public static function createRoleOccupationWrapper(Role $role, string $locale): } else { $roleName = $role->getDeLabel(); } - return new RoleOccupationWrapper($roleName, $role->getRoleType(), ['#080', '#050']); // Colors not yet implemented + return new RoleOccupationWrapper($roleName, $role->getRoleType(), self::getRoleColor($role->getRoleType())); // Colors not yet implemented + } + + private static function getRoleColor(string $roleType) + { + foreach (RoleOverviewMapper::GROUP_TYPE_COLORS as $key=>$value) { + if (str_contains($roleType, $key)) { + return $value; + } + } + return ['#da70d6', '#ae5aab']; } public static function createRoleOccupation(AggregatedPersonRole $aggregatedPersonRole, string $from, string $to): RoleOccupation diff --git a/src/Repository/Aggregated/AggregatedPersonRoleRepository.php b/src/Repository/Aggregated/AggregatedPersonRoleRepository.php index 782ab86..0b0ead5 100644 --- a/src/Repository/Aggregated/AggregatedPersonRoleRepository.php +++ b/src/Repository/Aggregated/AggregatedPersonRoleRepository.php @@ -50,6 +50,7 @@ public function findByGroupInTimeframe(Group $group, $start, $end) return $this->createQueryBuilder('a') ->where('a.group = :group_id') ->andWhere('a.start_at BETWEEN :start AND :end OR a.end_at BETWEEN :start AND :end') + ->orderBy('a.start_at') ->setParameter('group_id', $group->getId()) ->setParameter('start', $start) ->setParameter('end', $end) diff --git a/src/Service/Sentry.php b/src/Service/Sentry.php index 05b7360..3df1860 100644 --- a/src/Service/Sentry.php +++ b/src/Service/Sentry.php @@ -31,11 +31,12 @@ public function sentryFilter(): callable if ($user instanceof PbsUserDTO) { $userBag = new UserDataBag($user->getId(), null, null, null, null); $userBag->setMetadata('Nickname', $user->getNickName()); - } else { + $event->setUser($userBag); + } elseif (!is_null($user)) { $userBag = new UserDataBag(null, null, null, $user->getUsername(), null); $userBag->setMetadata('Is PbsUserDTO', false); + $event->setUser($userBag); } - $event->setUser($userBag); return $event; }; } From 3608862d53868eaae0220beddb4a0ca32dc905f6 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Tue, 4 Jul 2023 16:20:47 +0200 Subject: [PATCH 14/86] fix missing canton information and link between groups --- config/services.yaml | 12 +- src/Command/FetchAllGroupsCommand.php | 215 ++++++++++++++++++ src/Command/FetchCensusCommand.php | 29 +++ src/Command/FetchDataCommand.php | 1 + src/Command/ImportFromJsonCommand.php | 2 +- src/Entity/Statistics/GroupGeoLocation.php | 81 +++++++ src/Entity/Statistics/StatisticGroup.php | 185 +++++++++++++++ src/Migrations/Version20230630154534.php | 41 ++++ src/Migrations/Version20230704135822.php | 36 +++ .../Statistics/GroupGeoLocationRepository.php | 91 ++++++++ .../Statistics/StatisticGroupRepository.php | 90 ++++++++ src/Service/CensusAPIService.php | 52 +++++ src/Service/GroupStructureAPIService.php | 53 +++++ 13 files changed, 886 insertions(+), 2 deletions(-) create mode 100644 src/Command/FetchAllGroupsCommand.php create mode 100644 src/Command/FetchCensusCommand.php create mode 100644 src/Entity/Statistics/GroupGeoLocation.php create mode 100644 src/Entity/Statistics/StatisticGroup.php create mode 100644 src/Migrations/Version20230630154534.php create mode 100644 src/Migrations/Version20230704135822.php create mode 100644 src/Repository/Statistics/GroupGeoLocationRepository.php create mode 100644 src/Repository/Statistics/StatisticGroupRepository.php create mode 100644 src/Service/CensusAPIService.php create mode 100644 src/Service/GroupStructureAPIService.php diff --git a/config/services.yaml b/config/services.yaml index 06af17e..af0a927 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -138,4 +138,14 @@ services: - { name: 'widget.aggregator', key: 'general.date' } App\Service\Aggregator\AggregatorRegistry: - arguments: [!tagged { tag: 'widget.aggregator', index_by: 'key' }] + arguments: [ !tagged { tag: 'widget.aggregator', index_by: 'key' } ] + + App\Service\GroupStructureAPIService: + arguments: + $apiToken: '%env(GROUP_STRUCTURE_TOKEN)%' + $url: '%env(GROUP_STRUCTURE_URL)%' + + App\Service\CensusAPIService: + arguments: + $apiToken: '%env(CENSUS_TOKEN)%' + $url: '%env(CENSUS_URL)%' diff --git a/src/Command/FetchAllGroupsCommand.php b/src/Command/FetchAllGroupsCommand.php new file mode 100644 index 0000000..cbf46b1 --- /dev/null +++ b/src/Command/FetchAllGroupsCommand.php @@ -0,0 +1,215 @@ +em = $em; + $this->apiService = $apiService; + $this->statisticGroupRepository = $statisticGroupRepository; + $this->groupTypeRepository = $groupTypeRepository; + $this->groupRepository = $groupRepository; + $this->geoLocationRepository = $geoLocationRepository; + $this->gelfLogger = $gelfLogger; + parent::__construct(); + } + + + public function configure() + { + $this->setName('app:fetch-all-groups') + ->setDescription('Fetches all groups from MiData (' . $this->apiService->getUrl() . ') using the groups endpoint.'); + } + + public function execute(InputInterface $input, OutputInterface $output) + { + $this->io = new SymfonyStyle($input, $output); + + $this->geoLocationRepository->deleteAll(); + $this->statisticGroupRepository->deleteAll(); + $this->fetchGroupRecursive(2); + $this->fetchAllRemaining(); + $this->logMissingBranches(); + + return 1; + } + + private function logMissingBranches() + { + $groups = $this->statisticGroupRepository->findBy(['parent' => null]); + foreach($groups as $group) { + //$this->gelfLogger->warning(new SimpleLogMessage($this->recursiveGetChildrenHumanReadable($group))); + $this->io->warning('This branch is missing the parent: ' . $this->recursiveGetChildrenHumanReadable($group)); + } + } + + private function recursiveGetChildrenHumanReadable(StatisticGroup $group) + { + $result = ' ' . $group->getId() . ': ['; + $children = $group->getChildren(); + foreach ($children as $child) + { + $result .= $this->recursiveGetChildrenHumanReadable($child); + } + return $result . ']'; + } + + private function fetchAllRemaining() + { + for ($i = 2; $i < 11600; $i++) { + $group = $this->statisticGroupRepository->findOneBy(['id' => $i]); + if(is_null($group)) { + try { + $result = $this->apiService->getGroup($i); + $content = $result->getContent()['groups'][0]; + $statisticGroup = new StatisticGroup(); + $rawGroup = $content; + + $name = trim($rawGroup['name']); + $parentGroup = $rawGroup['links']['parent'] ?? null; + if(!is_null($parentGroup)){ + $parentGroup = $this->statisticGroupRepository->findOneBy(['id' => $parentGroup]); + } + /** @var GroupType $groupType */ + $groupType = $this->groupTypeRepository->findOneBy(['deLabel' => $rawGroup['group_type']]); + $statisticGroup->setId($i); + $statisticGroup->setName($name); + $statisticGroup->setParentGroup($parentGroup); + $statisticGroup->setGroupType($groupType); + $this->io->writeln('Adding ' . $i); + $this->statisticGroupRepository->add($statisticGroup); + } catch (ClientException $e) { + $this->io->error('Fetch for ' . $i . ' resultet in an error (' . $e->getCode() . ')'); + }; + } + } + } + + private function fetchGroupRecursive(int $id, ?StatisticGroup $canton = null) + { + $result = $this->apiService->getGroup($id); + if ($result->getStatusCode() !== 200) { + $this->io->error([ + 'API call for group with id ' . $id . ' failed!', + 'HTTP status code: ' . $result->getStatusCode() + ]); + throw new Exception( + 'Got http status code ' . $result->getStatusCode() . ' from API. Stopped fetching data.' + ); + } + $statisticGroup = new StatisticGroup(); + + $rawGroup = $result->getContent()['groups'][0];; + $name = trim($rawGroup['name']); + $parentGroup = $rawGroup['links']['parent'] ?? null; + if(!is_null($parentGroup)){ + $parentGroup = $this->statisticGroupRepository->findOneBy(['id' => $parentGroup]); + } + $children = $rawGroup['links']['children'] ?? []; + /** @var GroupType $groupType */ + $groupType = $this->groupTypeRepository->findOneBy(['deLabel' => $rawGroup['group_type']]); + + $statisticGroup->setId($id); + $statisticGroup->setCanton($canton); + $statisticGroup->setName($name); + $statisticGroup->setParentGroup($parentGroup); + $statisticGroup->setGroupType($groupType); + $this->statisticGroupRepository->add($statisticGroup); + + /** @var array $geoLocations */ + $geoLocations = $result->getContent()['linked']['geolocations']; + $this->createGeoLocations($statisticGroup, $geoLocations); + $this->fillHealthGroupWithStatisticGroup($statisticGroup); + + if($groupType->getGroupType() === GroupType::CANTON) { + $canton = $statisticGroup; + } + foreach ($children as $child) { + $this->fetchGroupRecursive($child, $canton); + } + } + + private function createGeoLocations(StatisticGroup $group, ?array $geoLocations) + { + if(is_null($geoLocations)) return; + foreach ($geoLocations as $rawGeoLocation) { + $geoLocation = new GroupGeoLocation(); + $geoLocation->setId($rawGeoLocation['id']); + $geoLocation->setGroup($group); + $geoLocation->setLat($rawGeoLocation['lat']); + $geoLocation->setLong($rawGeoLocation['long']); + $this->geoLocationRepository->add($geoLocation); + } + } + + private function fillHealthGroupWithStatisticGroup(StatisticGroup $statisticGroup) + { + /** @var Group $group */ + $group = $this->groupRepository->findOneBy(['id' => $statisticGroup->getId()]); + if(!is_null($group)) { + if(!is_null($statisticGroup->getCanton())) { + $group->setCantonId($statisticGroup->getCanton()->getId()); + $group->setCantonName($statisticGroup->getCanton()->getName()); + } + if(!is_null($statisticGroup->getParentGroup())) { + /** @var Group $parent */ + $parent = $this->groupRepository->findOneBy(['id' => $statisticGroup->getParentGroup()->getId()]); + if(!is_null($parent)) { + $group->setParentGroup($parent); + } + } + } + } + + public function getStats(): CommandStatistics + { + return new CommandStatistics(0, 'No statistics availible'); + } +} diff --git a/src/Command/FetchCensusCommand.php b/src/Command/FetchCensusCommand.php new file mode 100644 index 0000000..995c1dc --- /dev/null +++ b/src/Command/FetchCensusCommand.php @@ -0,0 +1,29 @@ +setName('app:fetch-census') + ->setDescription('Not implemented'); + } + + // TODO: Implement the statistics + public function getStats(): CommandStatistics + { + return new CommandStatistics(0, 'Statistics not yet implemented.'); + } +} diff --git a/src/Command/FetchDataCommand.php b/src/Command/FetchDataCommand.php index fedc753..406c275 100644 --- a/src/Command/FetchDataCommand.php +++ b/src/Command/FetchDataCommand.php @@ -3,6 +3,7 @@ namespace App\Command; use App\Model\CommandStatistics; +use App\Repository\Statistics\StatisticGroupRepository; use App\Service\Pbs\PbsApiService; use Exception; use Symfony\Component\Console\Input\InputInterface; diff --git a/src/Command/ImportFromJsonCommand.php b/src/Command/ImportFromJsonCommand.php index e5c81dc..4b69026 100644 --- a/src/Command/ImportFromJsonCommand.php +++ b/src/Command/ImportFromJsonCommand.php @@ -391,7 +391,7 @@ private function importGroups(OutputInterface $output) $metadata->setIdGenerator(new AssignedGenerator()); } - $group->setName($gr['name']); + $group->setName(trim($gr['name'])); $group->setCantonId($gr['canton_id'] ?? null); $group->setCantonName($gr['canton_name'] ?? null); $group->setCreatedAt(new DateTimeImmutable($gr['created_at'])); diff --git a/src/Entity/Statistics/GroupGeoLocation.php b/src/Entity/Statistics/GroupGeoLocation.php new file mode 100644 index 0000000..4b1564a --- /dev/null +++ b/src/Entity/Statistics/GroupGeoLocation.php @@ -0,0 +1,81 @@ +id; + } + + public function setId(int $id): self + { + $this->id = $id; + return $this; + } + + public function getGroup(): ?StatisticGroup + { + return $this->group; + } + + public function setGroup(?StatisticGroup $group): self + { + $this->group = $group; + + return $this; + } + + public function getLat(): ?string + { + return $this->lat; + } + + public function setLat(string $lat): self + { + $this->lat = $lat; + + return $this; + } + + public function getLong(): ?string + { + return $this->long; + } + + public function setLong(string $long): self + { + $this->long = $long; + + return $this; + } +} diff --git a/src/Entity/Statistics/StatisticGroup.php b/src/Entity/Statistics/StatisticGroup.php new file mode 100644 index 0000000..946f8af --- /dev/null +++ b/src/Entity/Statistics/StatisticGroup.php @@ -0,0 +1,185 @@ +children = new ArrayCollection(); + $this->geoLocations = new ArrayCollection(); + } + + public function setId(int $id): self + { + $this->id = $id; + + return $this; + } + + public function getId(): ?int + { + return $this->id; + } + + public function getParentGroup(): ?self + { + return $this->parent_group; + } + + public function setParentGroup(?self $parent_group): self + { + $this->parent_group = $parent_group; + + return $this; + } + + /** + * @return Collection + */ + public function getChildren(): ?Collection + { + return $this->children; + } + + public function addChild(self $child): self + { + if (!$this->children->contains($child)) { + $this->children[] = $child; + $child->setParentGroup($this); + } + + return $this; + } + + public function removeChild(self $child): self + { + if ($this->children->removeElement($child)) { + // set the owning side to null (unless already changed) + if ($child->getParentGroup() === $this) { + $child->setParentGroup(null); + } + } + + return $this; + } + + public function getGroupType(): ?GroupType + { + return $this->group_type; + } + + public function setGroupType(?GroupType $group_type): self + { + $this->group_type = $group_type; + + return $this; + } + + public function getName(): ?string + { + return $this->name; + } + + public function setName(string $name): self + { + $this->name = $name; + + return $this; + } + + public function getCanton(): ?self + { + return $this->canton; + } + + public function setCanton(?self $canton): self + { + $this->canton = $canton; + + return $this; + } + + + /** + * @return Collection + */ + public function getGeoLocations(): Collection + { + return $this->geoLocations; + } + + public function addGeoLocation(GroupGeoLocation $geoLocation): self + { + if (!$this->geoLocations->contains($geoLocation)) { + $this->geoLocations[] = $geoLocation; + $geoLocation->setGroups($this); + } + + return $this; + } + + public function removeGeoLocation(GroupGeoLocation $geoLocation): self + { + if ($this->geoLocations->removeElement($geoLocation)) { + // set the owning side to null (unless already changed) + if ($geoLocation->getGroups() === $this) { + $geoLocation->setGroups(null); + } + } + + return $this; + } + + +} diff --git a/src/Migrations/Version20230630154534.php b/src/Migrations/Version20230630154534.php new file mode 100644 index 0000000..a6a7d00 --- /dev/null +++ b/src/Migrations/Version20230630154534.php @@ -0,0 +1,41 @@ +addSql('CREATE SEQUENCE statistic_group_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); + $this->addSql('CREATE TABLE statistic_group (id INT NOT NULL, parent_group_id INT NULL, group_type_id INT NOT NULL, canton_id INT NULL, name VARCHAR(255) NOT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE INDEX IDX_7F70D6D161997596 ON statistic_group (parent_group_id)'); + $this->addSql('CREATE INDEX IDX_7F70D6D1434CD89F ON statistic_group (group_type_id)'); + $this->addSql('CREATE INDEX IDX_7F70D6D18D070D0B ON statistic_group (canton_id)'); + $this->addSql('ALTER TABLE statistic_group ADD CONSTRAINT FK_7F70D6D161997596 FOREIGN KEY (parent_group_id) REFERENCES statistic_group (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE statistic_group ADD CONSTRAINT FK_7F70D6D1434CD89F FOREIGN KEY (group_type_id) REFERENCES midata_group_type (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE statistic_group ADD CONSTRAINT FK_7F70D6D18D070D0B FOREIGN KEY (canton_id) REFERENCES statistic_group (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('ALTER TABLE statistic_group DROP CONSTRAINT FK_7F70D6D161997596'); + $this->addSql('ALTER TABLE statistic_group DROP CONSTRAINT FK_7F70D6D18D070D0B'); + $this->addSql('DROP SEQUENCE statistic_group_id_seq CASCADE'); + $this->addSql('DROP TABLE statistic_group'); + } +} diff --git a/src/Migrations/Version20230704135822.php b/src/Migrations/Version20230704135822.php new file mode 100644 index 0000000..2d9132a --- /dev/null +++ b/src/Migrations/Version20230704135822.php @@ -0,0 +1,36 @@ +addSql('CREATE SEQUENCE group_geo_location_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); + $this->addSql('CREATE TABLE group_geo_location (id INT NOT NULL, group_id INT DEFAULT NULL, lat VARCHAR(255) NOT NULL, long VARCHAR(255) NOT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE INDEX IDX_5C61B453FE54D947 ON group_geo_location (group_id)'); + $this->addSql('ALTER TABLE group_geo_location ADD CONSTRAINT FK_5C61B453FE54D947 FOREIGN KEY (group_id) REFERENCES statistic_group (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('CREATE SCHEMA public'); + $this->addSql('DROP SEQUENCE group_geo_location_id_seq CASCADE'); + $this->addSql('DROP TABLE group_geo_location'); + } +} diff --git a/src/Repository/Statistics/GroupGeoLocationRepository.php b/src/Repository/Statistics/GroupGeoLocationRepository.php new file mode 100644 index 0000000..8b00034 --- /dev/null +++ b/src/Repository/Statistics/GroupGeoLocationRepository.php @@ -0,0 +1,91 @@ + + * + * @method GroupGeoLocation|null find($id, $lockMode = null, $lockVersion = null) + * @method GroupGeoLocation|null findOneBy(array $criteria, array $orderBy = null) + * @method GroupGeoLocation[] findAll() + * @method GroupGeoLocation[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null) + */ +class GroupGeoLocationRepository extends ServiceEntityRepository +{ + public function __construct(ManagerRegistry $registry) + { + parent::__construct($registry, GroupGeoLocation::class); + } + + /** + * @throws ORMException + * @throws OptimisticLockException + */ + public function add(GroupGeoLocation $entity, bool $flush = true): void + { + $this->_em->persist($entity); + if ($flush) { + $this->_em->flush(); + } + } + + public function deleteAll() + { + $this->_em->createQueryBuilder() + ->delete(GroupGeoLocation::class, 'g') + ->getQuery() + ->execute(); + $this->_em->flush(); + $metadata = $this->_em->getClassMetaData(GroupGeoLocation::class); + $metadata->setIdGenerator(new AssignedGenerator()); + } + + /** + * @throws ORMException + * @throws OptimisticLockException + */ + public function remove(GroupGeoLocation $entity, bool $flush = true): void + { + $this->_em->remove($entity); + if ($flush) { + $this->_em->flush(); + } + } + + // /** + // * @return GroupGeoLocation[] Returns an array of GroupGeoLocation objects + // */ + /* + public function findByExampleField($value) + { + return $this->createQueryBuilder('g') + ->andWhere('g.exampleField = :val') + ->setParameter('val', $value) + ->orderBy('g.id', 'ASC') + ->setMaxResults(10) + ->getQuery() + ->getResult() + ; + } + */ + + /* + public function findOneBySomeField($value): ?GroupGeoLocation + { + return $this->createQueryBuilder('g') + ->andWhere('g.exampleField = :val') + ->setParameter('val', $value) + ->getQuery() + ->getOneOrNullResult() + ; + } + */ +} diff --git a/src/Repository/Statistics/StatisticGroupRepository.php b/src/Repository/Statistics/StatisticGroupRepository.php new file mode 100644 index 0000000..dbf104a --- /dev/null +++ b/src/Repository/Statistics/StatisticGroupRepository.php @@ -0,0 +1,90 @@ + + * + * @method StatisticGroup|null find($id, $lockMode = null, $lockVersion = null) + * @method StatisticGroup|null findOneBy(array $criteria, array $orderBy = null) + * @method StatisticGroup[] findAll() + * @method StatisticGroup[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null) + */ +class StatisticGroupRepository extends ServiceEntityRepository +{ + public function __construct(ManagerRegistry $registry) + { + parent::__construct($registry, StatisticGroup::class); + } + + /** + * @throws ORMException + * @throws OptimisticLockException + */ + public function add(StatisticGroup $entity, bool $flush = true): void + { + $this->_em->persist($entity); + if ($flush) { + $this->_em->flush(); + } + } + + /** + * @throws ORMException + * @throws OptimisticLockException + */ + public function remove(StatisticGroup $entity, bool $flush = true): void + { + $this->_em->remove($entity); + if ($flush) { + $this->_em->flush(); + } + } + + public function deleteAll() + { + $this->_em->createQueryBuilder() + ->delete(StatisticGroup::class, 'g') + ->getQuery() + ->execute(); + $this->_em->flush(); + $metadata = $this->_em->getClassMetaData(StatisticGroup::class); + $metadata->setIdGenerator(new AssignedGenerator()); + } + + // /** + // * @return StatisticGroup[] Returns an array of StatisticGroup objects + // */ + /* + public function findByExampleField($value) + { + return $this->createQueryBuilder('s') + ->andWhere('s.exampleField = :val') + ->setParameter('val', $value) + ->orderBy('s.id', 'ASC') + ->setMaxResults(10) + ->getQuery() + ->getResult() + ; + } + */ + + /* + public function findOneBySomeField($value): ?StatisticGroup + { + return $this->createQueryBuilder('s') + ->andWhere('s.exampleField = :val') + ->setParameter('val', $value) + ->getQuery() + ->getOneOrNullResult() + ; + } + */ +} diff --git a/src/Service/CensusAPIService.php b/src/Service/CensusAPIService.php new file mode 100644 index 0000000..1baab75 --- /dev/null +++ b/src/Service/CensusAPIService.php @@ -0,0 +1,52 @@ +guzzleWrapper = $guzzleWrapper; + $this->url = $url; + $this->apiToken = $apiToken; + } + + + /** + * Fetch a group from the Group Structure API. + * This can return any group regardless of healthcheck opt-out or any other factor. + * @param int $groupId + * @return Http\CurlResponse + */ + public function getGroup(int $groupId): Http\CurlResponse + { + $endpoint = $this->url . '/de/groups/' . $groupId . '.json?token=' . $this->apiToken; + return $this->guzzleWrapper->getJson($endpoint, null, []); + } + + /** + * @return string + */ + public function getUrl(): string + { + return $this->url; + } + +} diff --git a/src/Service/GroupStructureAPIService.php b/src/Service/GroupStructureAPIService.php new file mode 100644 index 0000000..440f794 --- /dev/null +++ b/src/Service/GroupStructureAPIService.php @@ -0,0 +1,53 @@ +guzzleWrapper = $guzzleWrapper; + $this->url = $url; + $this->apiToken = $apiToken; + } + + + /** + * Fetch a group from the Group Structure API. + * This can return any group regardless of healthcheck opt-out or any other factor. + * @param int $groupId + * @return Http\CurlResponse + */ + public function getGroup(int $groupId): Http\CurlResponse + { + $endpoint = $this->url . '/de/groups/' . $groupId . '.json?token=' . $this->apiToken; + return $this->guzzleWrapper->getJson($endpoint, null, []); + } + + /** + * @return string + */ + public function getUrl(): string + { + return $this->url; + } + +} From 53fb5937f1759f75c63fab83ca5fcaa84c08c5a2 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Tue, 4 Jul 2023 16:27:49 +0200 Subject: [PATCH 15/86] enable necessary features only --- src/Command/FetchAllGroupsCommand.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Command/FetchAllGroupsCommand.php b/src/Command/FetchAllGroupsCommand.php index cbf46b1..62b1da7 100644 --- a/src/Command/FetchAllGroupsCommand.php +++ b/src/Command/FetchAllGroupsCommand.php @@ -75,8 +75,8 @@ public function execute(InputInterface $input, OutputInterface $output) $this->geoLocationRepository->deleteAll(); $this->statisticGroupRepository->deleteAll(); $this->fetchGroupRecursive(2); - $this->fetchAllRemaining(); - $this->logMissingBranches(); + //$this->fetchAllRemaining(); + //$this->logMissingBranches(); return 1; } From 98a5a463276a511320cdfa00eb1a13231fe0e776 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Tue, 4 Jul 2023 16:29:52 +0200 Subject: [PATCH 16/86] enable fetching in cronjob --- run-import.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/run-import.sh b/run-import.sh index 22cff94..bc34de9 100755 --- a/run-import.sh +++ b/run-import.sh @@ -14,3 +14,5 @@ set -e /usr/local/bin/php /srv/bin/console app:quap:compute-answers /usr/local/bin/php /srv/bin/console app:compute-permissions + +/usr/local/bin/php /srv/bin/console app:fetch-all-groups From 1c5a708c248f76b6f0009d15b431e5dfcd2fbaf0 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Wed, 5 Jul 2023 08:24:57 +0200 Subject: [PATCH 17/86] lint --- src/Command/FetchAllGroupsCommand.php | 28 ++++++++++++------------ src/Entity/Statistics/StatisticGroup.php | 2 -- src/Service/CensusAPIService.php | 3 --- src/Service/GroupStructureAPIService.php | 3 --- 4 files changed, 14 insertions(+), 22 deletions(-) diff --git a/src/Command/FetchAllGroupsCommand.php b/src/Command/FetchAllGroupsCommand.php index 62b1da7..f110dc1 100644 --- a/src/Command/FetchAllGroupsCommand.php +++ b/src/Command/FetchAllGroupsCommand.php @@ -22,7 +22,6 @@ class FetchAllGroupsCommand extends StatisticsCommand { - protected GroupStructureAPIService $apiService; protected SymfonyStyle $io; @@ -84,7 +83,7 @@ public function execute(InputInterface $input, OutputInterface $output) private function logMissingBranches() { $groups = $this->statisticGroupRepository->findBy(['parent' => null]); - foreach($groups as $group) { + foreach ($groups as $group) { //$this->gelfLogger->warning(new SimpleLogMessage($this->recursiveGetChildrenHumanReadable($group))); $this->io->warning('This branch is missing the parent: ' . $this->recursiveGetChildrenHumanReadable($group)); } @@ -94,8 +93,7 @@ private function recursiveGetChildrenHumanReadable(StatisticGroup $group) { $result = ' ' . $group->getId() . ': ['; $children = $group->getChildren(); - foreach ($children as $child) - { + foreach ($children as $child) { $result .= $this->recursiveGetChildrenHumanReadable($child); } return $result . ']'; @@ -105,7 +103,7 @@ private function fetchAllRemaining() { for ($i = 2; $i < 11600; $i++) { $group = $this->statisticGroupRepository->findOneBy(['id' => $i]); - if(is_null($group)) { + if (is_null($group)) { try { $result = $this->apiService->getGroup($i); $content = $result->getContent()['groups'][0]; @@ -114,7 +112,7 @@ private function fetchAllRemaining() $name = trim($rawGroup['name']); $parentGroup = $rawGroup['links']['parent'] ?? null; - if(!is_null($parentGroup)){ + if (!is_null($parentGroup)) { $parentGroup = $this->statisticGroupRepository->findOneBy(['id' => $parentGroup]); } /** @var GroupType $groupType */ @@ -146,10 +144,10 @@ private function fetchGroupRecursive(int $id, ?StatisticGroup $canton = null) } $statisticGroup = new StatisticGroup(); - $rawGroup = $result->getContent()['groups'][0];; + $rawGroup = $result->getContent()['groups'][0]; $name = trim($rawGroup['name']); $parentGroup = $rawGroup['links']['parent'] ?? null; - if(!is_null($parentGroup)){ + if (!is_null($parentGroup)) { $parentGroup = $this->statisticGroupRepository->findOneBy(['id' => $parentGroup]); } $children = $rawGroup['links']['children'] ?? []; @@ -168,7 +166,7 @@ private function fetchGroupRecursive(int $id, ?StatisticGroup $canton = null) $this->createGeoLocations($statisticGroup, $geoLocations); $this->fillHealthGroupWithStatisticGroup($statisticGroup); - if($groupType->getGroupType() === GroupType::CANTON) { + if ($groupType->getGroupType() === GroupType::CANTON) { $canton = $statisticGroup; } foreach ($children as $child) { @@ -178,7 +176,9 @@ private function fetchGroupRecursive(int $id, ?StatisticGroup $canton = null) private function createGeoLocations(StatisticGroup $group, ?array $geoLocations) { - if(is_null($geoLocations)) return; + if (is_null($geoLocations)) { + return; + } foreach ($geoLocations as $rawGeoLocation) { $geoLocation = new GroupGeoLocation(); $geoLocation->setId($rawGeoLocation['id']); @@ -193,15 +193,15 @@ private function fillHealthGroupWithStatisticGroup(StatisticGroup $statisticGrou { /** @var Group $group */ $group = $this->groupRepository->findOneBy(['id' => $statisticGroup->getId()]); - if(!is_null($group)) { - if(!is_null($statisticGroup->getCanton())) { + if (!is_null($group)) { + if (!is_null($statisticGroup->getCanton())) { $group->setCantonId($statisticGroup->getCanton()->getId()); $group->setCantonName($statisticGroup->getCanton()->getName()); } - if(!is_null($statisticGroup->getParentGroup())) { + if (!is_null($statisticGroup->getParentGroup())) { /** @var Group $parent */ $parent = $this->groupRepository->findOneBy(['id' => $statisticGroup->getParentGroup()->getId()]); - if(!is_null($parent)) { + if (!is_null($parent)) { $group->setParentGroup($parent); } } diff --git a/src/Entity/Statistics/StatisticGroup.php b/src/Entity/Statistics/StatisticGroup.php index 946f8af..5c21559 100644 --- a/src/Entity/Statistics/StatisticGroup.php +++ b/src/Entity/Statistics/StatisticGroup.php @@ -180,6 +180,4 @@ public function removeGeoLocation(GroupGeoLocation $geoLocation): self return $this; } - - } diff --git a/src/Service/CensusAPIService.php b/src/Service/CensusAPIService.php index 1baab75..3540295 100644 --- a/src/Service/CensusAPIService.php +++ b/src/Service/CensusAPIService.php @@ -6,8 +6,6 @@ class CensusAPIService { - - /** @var GuzzleWrapper */ protected $guzzleWrapper; /** @var string */ @@ -48,5 +46,4 @@ public function getUrl(): string { return $this->url; } - } diff --git a/src/Service/GroupStructureAPIService.php b/src/Service/GroupStructureAPIService.php index 440f794..6d5fec0 100644 --- a/src/Service/GroupStructureAPIService.php +++ b/src/Service/GroupStructureAPIService.php @@ -7,8 +7,6 @@ class GroupStructureAPIService { - - /** @var GuzzleWrapper */ protected $guzzleWrapper; /** @var string */ @@ -49,5 +47,4 @@ public function getUrl(): string { return $this->url; } - } From 002e30fa089b39776499e8ed819c5468b7aa1abe Mon Sep 17 00:00:00 2001 From: Sebastian Date: Wed, 5 Jul 2023 11:14:26 +0200 Subject: [PATCH 18/86] fix: deactivate bruteforce group import --- src/Command/FetchAllGroupsCommand.php | 135 +++++++++++------- src/Helper/BatchedRepository.php | 41 ++++++ .../Statistics/GroupGeoLocationRepository.php | 5 + .../Statistics/StatisticGroupRepository.php | 5 + 4 files changed, 132 insertions(+), 54 deletions(-) create mode 100644 src/Helper/BatchedRepository.php diff --git a/src/Command/FetchAllGroupsCommand.php b/src/Command/FetchAllGroupsCommand.php index f110dc1..43c693d 100644 --- a/src/Command/FetchAllGroupsCommand.php +++ b/src/Command/FetchAllGroupsCommand.php @@ -6,6 +6,8 @@ use App\Entity\Midata\GroupType; use App\Entity\Statistics\GroupGeoLocation; use App\Entity\Statistics\StatisticGroup; +use App\Exception\ApiException; +use App\Helper\BatchedRepository; use App\Model\CommandStatistics; use App\Model\LogMessage\SimpleLogMessage; use App\Repository\Midata\GroupRepository; @@ -16,6 +18,7 @@ use Digio\Logging\GelfLogger; use Doctrine\ORM\EntityManagerInterface; use GuzzleHttp\Exception\ClientException; +use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; @@ -38,6 +41,8 @@ class FetchAllGroupsCommand extends StatisticsCommand protected GelfLogger $gelfLogger; + protected $stats = [0, 0]; + /** * @param GroupStructureAPIService $apiService */ @@ -71,85 +76,106 @@ public function execute(InputInterface $input, OutputInterface $output) { $this->io = new SymfonyStyle($input, $output); + $start = microtime(true); $this->geoLocationRepository->deleteAll(); $this->statisticGroupRepository->deleteAll(); - $this->fetchGroupRecursive(2); + + $batchedStatisticsRepository = new BatchedRepository($this->statisticGroupRepository); + $batchedGeoRepository = new BatchedRepository($this->geoLocationRepository); + $this->fetchGroupRecursive(2, null, null, $batchedStatisticsRepository, $batchedGeoRepository); + $batchedStatisticsRepository->flush(); + $batchedGeoRepository->flush(); + //$this->fetchAllRemaining(); - //$this->logMissingBranches(); + $this->logMissingBranches(); - return 1; + $this->stats[0] = microtime(true) - $start; + return Command::SUCCESS; } private function logMissingBranches() { - $groups = $this->statisticGroupRepository->findBy(['parent' => null]); + $groups = $this->statisticGroupRepository->findBy(['parent_group' => null]); foreach ($groups as $group) { - //$this->gelfLogger->warning(new SimpleLogMessage($this->recursiveGetChildrenHumanReadable($group))); - $this->io->warning('This branch is missing the parent: ' . $this->recursiveGetChildrenHumanReadable($group)); + if ($group->getId() === 2) { + continue; + } + $this->gelfLogger->warning(new SimpleLogMessage($this->recursiveGetChildrenAsJsonString($group))); + $this->io->warning('This branch is missing the parent: {' . $this->recursiveGetChildrenAsJsonString($group) . '}'); } } - private function recursiveGetChildrenHumanReadable(StatisticGroup $group) + private function recursiveGetChildrenAsJsonString(StatisticGroup $group) { - $result = ' ' . $group->getId() . ': ['; + $result = '"' . $group->getId(); $children = $group->getChildren(); + if(sizeof($children) === 0) { + return $result . '": null,'; + } + $result .= '": {'; foreach ($children as $child) { - $result .= $this->recursiveGetChildrenHumanReadable($child); + $result .= $this->recursiveGetChildrenAsJsonString($child); } - return $result . ']'; + return $result . '},'; } + /** + * Brutforces through all groups and fetches them. Takes longer because you have to wait for every 404 and you don't + * know which index is the last. Don't use unless necessary + */ private function fetchAllRemaining() { for ($i = 2; $i < 11600; $i++) { $group = $this->statisticGroupRepository->findOneBy(['id' => $i]); if (is_null($group)) { - try { - $result = $this->apiService->getGroup($i); - $content = $result->getContent()['groups'][0]; - $statisticGroup = new StatisticGroup(); - $rawGroup = $content; - - $name = trim($rawGroup['name']); - $parentGroup = $rawGroup['links']['parent'] ?? null; - if (!is_null($parentGroup)) { - $parentGroup = $this->statisticGroupRepository->findOneBy(['id' => $parentGroup]); - } - /** @var GroupType $groupType */ - $groupType = $this->groupTypeRepository->findOneBy(['deLabel' => $rawGroup['group_type']]); - $statisticGroup->setId($i); - $statisticGroup->setName($name); - $statisticGroup->setParentGroup($parentGroup); - $statisticGroup->setGroupType($groupType); - $this->io->writeln('Adding ' . $i); - $this->statisticGroupRepository->add($statisticGroup); - } catch (ClientException $e) { - $this->io->error('Fetch for ' . $i . ' resultet in an error (' . $e->getCode() . ')'); - }; + $result = $this->fetchGroup($i); + $content = $result->getContent()['groups'][0]; + $statisticGroup = new StatisticGroup(); + $rawGroup = $content; + + $name = trim($rawGroup['name']); + $parentGroup = $rawGroup['links']['parent'] ?? null; + if (!is_null($parentGroup)) { + $parentGroup = $this->statisticGroupRepository->findOneBy(['id' => $parentGroup]); + } + /** @var GroupType $groupType */ + $groupType = $this->groupTypeRepository->findOneBy(['deLabel' => $rawGroup['group_type']]); + $statisticGroup->setId($i); + $statisticGroup->setName($name); + $statisticGroup->setParentGroup($parentGroup); + $statisticGroup->setGroupType($groupType); + $this->io->writeln('Adding ' . $i); + $this->statisticGroupRepository->add($statisticGroup); } } } - private function fetchGroupRecursive(int $id, ?StatisticGroup $canton = null) - { - $result = $this->apiService->getGroup($id); - if ($result->getStatusCode() !== 200) { - $this->io->error([ - 'API call for group with id ' . $id . ' failed!', - 'HTTP status code: ' . $result->getStatusCode() - ]); - throw new Exception( - 'Got http status code ' . $result->getStatusCode() . ' from API. Stopped fetching data.' - ); + private function fetchGroup(int $id, bool $stopOnFail = false) { + try { + $result = $this->apiService->getGroup($id); + if ($result->getStatusCode() !== 200) { + $this->io->error([ + 'API call for group with id ' . $id . ' failed!', + 'HTTP status code: ' . $result->getStatusCode() + ]); + } + return $result; + } catch (ClientException $e) { + $this->io->error('Fetch for ' . $id . ' resultet in an error (' . $e->getCode() . ')'); + if ($stopOnFail) { + $this->gelfLogger('Fetching Group (' . $id . ') failed with error code (' . $e->getCode() . '), stopped fetching.'); + throw new ApiException('Fetching Group (' . $id . ') failed, stopping fetching.'); + } + return null; } + } + private function fetchGroupRecursive(int $id, ?StatisticGroup $parent, ?StatisticGroup $canton, BatchedRepository $batchedStatisticsRepository, BatchedRepository $batchedGeoRepository) + { + $result = $this->fetchGroup($id, true); $statisticGroup = new StatisticGroup(); $rawGroup = $result->getContent()['groups'][0]; $name = trim($rawGroup['name']); - $parentGroup = $rawGroup['links']['parent'] ?? null; - if (!is_null($parentGroup)) { - $parentGroup = $this->statisticGroupRepository->findOneBy(['id' => $parentGroup]); - } $children = $rawGroup['links']['children'] ?? []; /** @var GroupType $groupType */ $groupType = $this->groupTypeRepository->findOneBy(['deLabel' => $rawGroup['group_type']]); @@ -157,24 +183,25 @@ private function fetchGroupRecursive(int $id, ?StatisticGroup $canton = null) $statisticGroup->setId($id); $statisticGroup->setCanton($canton); $statisticGroup->setName($name); - $statisticGroup->setParentGroup($parentGroup); + $statisticGroup->setParentGroup($parent); $statisticGroup->setGroupType($groupType); - $this->statisticGroupRepository->add($statisticGroup); + $batchedStatisticsRepository->add($statisticGroup); + $this->stats[1]++; /** @var array $geoLocations */ $geoLocations = $result->getContent()['linked']['geolocations']; - $this->createGeoLocations($statisticGroup, $geoLocations); + $this->createGeoLocations($statisticGroup, $geoLocations, $batchedGeoRepository); $this->fillHealthGroupWithStatisticGroup($statisticGroup); if ($groupType->getGroupType() === GroupType::CANTON) { $canton = $statisticGroup; } foreach ($children as $child) { - $this->fetchGroupRecursive($child, $canton); + $this->fetchGroupRecursive($child, $statisticGroup, $canton, $batchedStatisticsRepository, $batchedGeoRepository); } } - private function createGeoLocations(StatisticGroup $group, ?array $geoLocations) + private function createGeoLocations(StatisticGroup $group, ?array $geoLocations, BatchedRepository $batchedGeoRepository) { if (is_null($geoLocations)) { return; @@ -185,7 +212,7 @@ private function createGeoLocations(StatisticGroup $group, ?array $geoLocations) $geoLocation->setGroup($group); $geoLocation->setLat($rawGeoLocation['lat']); $geoLocation->setLong($rawGeoLocation['long']); - $this->geoLocationRepository->add($geoLocation); + $batchedGeoRepository->add($geoLocation); } } @@ -210,6 +237,6 @@ private function fillHealthGroupWithStatisticGroup(StatisticGroup $statisticGrou public function getStats(): CommandStatistics { - return new CommandStatistics(0, 'No statistics availible'); + return new CommandStatistics($this->stats[0], 'Fetched ' . $this->stats[1] . ' Groups in ' . number_format($this->stats[0], 2) . ' Seconds', $this->stats[1]); } } diff --git a/src/Helper/BatchedRepository.php b/src/Helper/BatchedRepository.php new file mode 100644 index 0000000..f36cccf --- /dev/null +++ b/src/Helper/BatchedRepository.php @@ -0,0 +1,41 @@ +repository = $repository; + $this->batchSize = $batchSize; + } + + public function add($entity) + { + $this->batchCount++; + $this->repository->add($entity, false); + if ($this->batchCount >= $this->batchSize) { + $this->batchCount = 0; + $this->flush(); + } + } + + public function flush() + { + $this->repository->flush(); + } +} diff --git a/src/Repository/Statistics/GroupGeoLocationRepository.php b/src/Repository/Statistics/GroupGeoLocationRepository.php index 8b00034..774b697 100644 --- a/src/Repository/Statistics/GroupGeoLocationRepository.php +++ b/src/Repository/Statistics/GroupGeoLocationRepository.php @@ -60,6 +60,11 @@ public function remove(GroupGeoLocation $entity, bool $flush = true): void } } + public function flush() + { + $this->_em->flush(); + } + // /** // * @return GroupGeoLocation[] Returns an array of GroupGeoLocation objects // */ diff --git a/src/Repository/Statistics/StatisticGroupRepository.php b/src/Repository/Statistics/StatisticGroupRepository.php index dbf104a..1d058d8 100644 --- a/src/Repository/Statistics/StatisticGroupRepository.php +++ b/src/Repository/Statistics/StatisticGroupRepository.php @@ -59,6 +59,11 @@ public function deleteAll() $metadata->setIdGenerator(new AssignedGenerator()); } + public function flush() + { + $this->_em->flush(); + } + // /** // * @return StatisticGroup[] Returns an array of StatisticGroup objects // */ From 16f4a597554e88dbb07248f810bbd4b263df592e Mon Sep 17 00:00:00 2001 From: Sebastian Date: Wed, 5 Jul 2023 11:22:18 +0200 Subject: [PATCH 19/86] lint --- src/Command/FetchAllGroupsCommand.php | 7 ++++--- src/Helper/BatchedRepository.php | 1 - 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Command/FetchAllGroupsCommand.php b/src/Command/FetchAllGroupsCommand.php index 43c693d..955381d 100644 --- a/src/Command/FetchAllGroupsCommand.php +++ b/src/Command/FetchAllGroupsCommand.php @@ -109,7 +109,7 @@ private function recursiveGetChildrenAsJsonString(StatisticGroup $group) { $result = '"' . $group->getId(); $children = $group->getChildren(); - if(sizeof($children) === 0) { + if (sizeof($children) === 0) { return $result . '": null,'; } $result .= '": {'; @@ -150,7 +150,8 @@ private function fetchAllRemaining() } } - private function fetchGroup(int $id, bool $stopOnFail = false) { + private function fetchGroup(int $id, bool $stopOnFail = false) + { try { $result = $this->apiService->getGroup($id); if ($result->getStatusCode() !== 200) { @@ -197,7 +198,7 @@ private function fetchGroupRecursive(int $id, ?StatisticGroup $parent, ?Statisti $canton = $statisticGroup; } foreach ($children as $child) { - $this->fetchGroupRecursive($child, $statisticGroup, $canton, $batchedStatisticsRepository, $batchedGeoRepository); + $this->fetchGroupRecursive($child, $statisticGroup, $canton, $batchedStatisticsRepository, $batchedGeoRepository); } } diff --git a/src/Helper/BatchedRepository.php b/src/Helper/BatchedRepository.php index f36cccf..fdb42e9 100644 --- a/src/Helper/BatchedRepository.php +++ b/src/Helper/BatchedRepository.php @@ -6,7 +6,6 @@ class BatchedRepository { - private ServiceEntityRepository $repository; private int $batchCount = 0; From d63c0c527c45c8e61cc8840c678da622c4fa592f Mon Sep 17 00:00:00 2001 From: Sebastian Date: Wed, 5 Jul 2023 11:28:35 +0200 Subject: [PATCH 20/86] Add some doc for clarity --- src/Command/FetchAllGroupsCommand.php | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/Command/FetchAllGroupsCommand.php b/src/Command/FetchAllGroupsCommand.php index 955381d..c7316e5 100644 --- a/src/Command/FetchAllGroupsCommand.php +++ b/src/Command/FetchAllGroupsCommand.php @@ -105,6 +105,9 @@ private function logMissingBranches() } } + /** + * Return all children of a group as JSON like string + */ private function recursiveGetChildrenAsJsonString(StatisticGroup $group) { $result = '"' . $group->getId(); @@ -170,6 +173,10 @@ private function fetchGroup(int $id, bool $stopOnFail = false) return null; } } + + /** + * Fetch a group and its children recursively + */ private function fetchGroupRecursive(int $id, ?StatisticGroup $parent, ?StatisticGroup $canton, BatchedRepository $batchedStatisticsRepository, BatchedRepository $batchedGeoRepository) { $result = $this->fetchGroup($id, true); @@ -178,7 +185,11 @@ private function fetchGroupRecursive(int $id, ?StatisticGroup $parent, ?Statisti $rawGroup = $result->getContent()['groups'][0]; $name = trim($rawGroup['name']); $children = $rawGroup['links']['children'] ?? []; - /** @var GroupType $groupType */ + /** + * Keep in mind that the group type is not sent via UID (zb. Group::Abteilung) but via the label in the language + * that you are using. In this case we use /de/group... so the german label. + * @var GroupType $groupType + */ $groupType = $this->groupTypeRepository->findOneBy(['deLabel' => $rawGroup['group_type']]); $statisticGroup->setId($id); @@ -217,6 +228,9 @@ private function createGeoLocations(StatisticGroup $group, ?array $geoLocations, } } + /** + * Fills in the parent and canton of the equivalent Health group with the Statistics group + */ private function fillHealthGroupWithStatisticGroup(StatisticGroup $statisticGroup) { /** @var Group $group */ From 29c09fc85e4a594d90796f39af5a3fb387082f3b Mon Sep 17 00:00:00 2001 From: Sebastian Date: Wed, 5 Jul 2023 14:31:27 +0200 Subject: [PATCH 21/86] fix undefined index warning --- src/Command/FetchAllGroupsCommand.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Command/FetchAllGroupsCommand.php b/src/Command/FetchAllGroupsCommand.php index c7316e5..2bbbef6 100644 --- a/src/Command/FetchAllGroupsCommand.php +++ b/src/Command/FetchAllGroupsCommand.php @@ -201,7 +201,7 @@ private function fetchGroupRecursive(int $id, ?StatisticGroup $parent, ?Statisti $this->stats[1]++; /** @var array $geoLocations */ - $geoLocations = $result->getContent()['linked']['geolocations']; + $geoLocations = $result->getContent()['linked']['geolocations'] ?? []; $this->createGeoLocations($statisticGroup, $geoLocations, $batchedGeoRepository); $this->fillHealthGroupWithStatisticGroup($statisticGroup); From e2204b1be861c7b66774ac1eed909994e1cf3d13 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Tue, 11 Jul 2023 16:59:19 +0200 Subject: [PATCH 22/86] implement group meeting locations --- run-import.sh | 3 +- .../AggregatedGeoLocationRepository.php | 20 +++++++++++ .../Aggregator/GeoLocationAggregator.php | 34 +++++++++++++++++-- .../GeoLocationDateDataProvider.php | 7 +++- 4 files changed, 60 insertions(+), 4 deletions(-) diff --git a/run-import.sh b/run-import.sh index bc34de9..a171efa 100755 --- a/run-import.sh +++ b/run-import.sh @@ -10,9 +10,10 @@ set -e /usr/local/bin/php /srv/bin/console app:map-peoples-addresses /usr/local/bin/php /srv/bin/console app:quap:import-questionnaire +/usr/local/bin/php /srv/bin/console app:fetch-all-groups # Also fetches Group Meeting points. + /usr/local/bin/php /srv/bin/console app:aggregate-data /usr/local/bin/php /srv/bin/console app:quap:compute-answers /usr/local/bin/php /srv/bin/console app:compute-permissions -/usr/local/bin/php /srv/bin/console app:fetch-all-groups diff --git a/src/Repository/Aggregated/AggregatedGeoLocationRepository.php b/src/Repository/Aggregated/AggregatedGeoLocationRepository.php index 1017d28..555899e 100644 --- a/src/Repository/Aggregated/AggregatedGeoLocationRepository.php +++ b/src/Repository/Aggregated/AggregatedGeoLocationRepository.php @@ -45,4 +45,24 @@ public function findAllForDateAndGroupType(string $date, string $groupType, int ); return $statement->fetchAllAssociative(); } + + public function findAllMeetingPointsForDate(string $date, int $groupId) + { + $connection = $this->getEntityManager()->getConnection(); + $statement = $connection->executeQuery( + "SELECT * FROM hc_aggregated_geo_location AS location + WHERE location.data_point_date = ? + AND location.shape = 'group_meeting_point' + AND location.group_id = ?;", + [ + $date, + $groupId, + ], + [ + ParameterType::STRING, + ParameterType::INTEGER, + ] + ); + return $statement->fetchAllAssociative(); + } } diff --git a/src/Service/Aggregator/GeoLocationAggregator.php b/src/Service/Aggregator/GeoLocationAggregator.php index 9050aca..d048626 100644 --- a/src/Service/Aggregator/GeoLocationAggregator.php +++ b/src/Service/Aggregator/GeoLocationAggregator.php @@ -5,11 +5,13 @@ use App\Entity\Aggregated\AggregatedGeoLocation; use App\Entity\Midata\Group; use App\Entity\Midata\Role; +use App\Entity\Statistics\GroupGeoLocation; use App\Repository\Aggregated\AggregatedGeoLocationRepository; use App\Repository\Midata\GroupRepository; use App\Repository\Midata\PersonRepository; use App\Repository\Midata\PersonRoleRepository; use App\Repository\Midata\RoleRepository; +use App\Repository\Statistics\GroupGeoLocationRepository; use DateInterval; use DateTime; use Doctrine\ORM\EntityManagerInterface; @@ -36,13 +38,16 @@ class GeoLocationAggregator extends WidgetAggregator /** @var AggregatedGeoLocationRepository $geoLocationRepository */ private $geoLocationRepository; + private GroupGeoLocationRepository $groupGeoLocationRepository; + public function __construct( EntityManagerInterface $em, GroupRepository $groupRepository, PersonRepository $personRepository, PersonRoleRepository $personRoleRepository, RoleRepository $roleRepository, - AggregatedGeoLocationRepository $geoLocationRepository + AggregatedGeoLocationRepository $geoLocationRepository, + GroupGeoLocationRepository $groupGeoLocationRepository ) { parent::__construct($groupRepository); @@ -52,6 +57,7 @@ public function __construct( $this->personRoleRepository = $personRoleRepository; $this->roleRepository = $roleRepository; $this->geoLocationRepository = $geoLocationRepository; + $this->groupGeoLocationRepository = $groupGeoLocationRepository; } /** @@ -76,6 +82,7 @@ public function aggregate(DateTime $startDate = null): void $maxDate = new DateTime(); $startPointDate = clone $minDate; + // von 2014 - heute while ($startPointDate->getTimestamp() < $maxDate->getTimestamp()) { $startPointDate->add(new DateInterval("P1M")); $startPointDate->modify('first day of this month'); @@ -92,6 +99,8 @@ public function aggregate(DateTime $startDate = null): void $this->geoLocationRepository, $mainGroup->getId() ); + // Why are we checking if data for this date exists here and not at the start?! + // Can we even reconstruct passed data?! if ($this->isDataExistsForDate($startPointDate->format('Y-m-d 00:00:00'), $existingData)) { continue; } @@ -109,9 +118,11 @@ public function aggregate(DateTime $startDate = null): void parent::$roleTypePriority ); + $this->aggregateGroupMeetingPoints($mainGroup, $startPointDate); $this->createWidgetsFromData($personGroups, $mainGroup, $startPointDate); - } + $this->em->flush(); + } $this->em->flush(); $this->em->clear(); } @@ -149,6 +160,25 @@ private function createWidgetsFromData(array $data, Group $group, DateTime $date } } + private function aggregateGroupMeetingPoints(Group $group, DateTime $dateTime) + { + $geoLocations = $this->groupGeoLocationRepository->findBy(['group' => $group->getId()]); + foreach ($geoLocations as $geoLocation) { + $widget = new AggregatedGeoLocation(); + $widget->setGroup($group); + $widget->setLabel(''); // no label + $widget->setGroupType($group->getGroupType()->getGroupType()); // no group type + $widget->setPersonType('group_meeting_point'); // isn't a person + $widget->setShape('group_meeting_point'); + $widget->setCreatedAt(new \DateTimeImmutable()); + $widget->setDataPointDate(new \DateTimeImmutable($dateTime->format('Y-m-d'))); + $widget->setLongitude($geoLocation->getLong()); + $widget->setLatitude($geoLocation->getLat()); + + $this->em->persist($widget); + } + } + /** * @param Role $role * @return string|null diff --git a/src/Service/DataProvider/GeoLocationDateDataProvider.php b/src/Service/DataProvider/GeoLocationDateDataProvider.php index 26efc97..d58c1fb 100644 --- a/src/Service/DataProvider/GeoLocationDateDataProvider.php +++ b/src/Service/DataProvider/GeoLocationDateDataProvider.php @@ -37,7 +37,11 @@ public function __construct( public function getData(Group $group, string $date, array $subGroupTypes, array $peopleTypes): array { $result = []; - + $groupMeetingPoints = $this->geoLocationRepository->findAllMeetingPointsForDate($date, $group->getId()); + var_dump($group->getId(), $groupMeetingPoints); + foreach ($groupMeetingPoints as $groupMeetingPoint) { + $result[] = $this->mapGeoLocation($groupMeetingPoint, false); + } foreach ($subGroupTypes as $groupType) { $geoLocations = $this->geoLocationRepository->findAllForDateAndGroupType( $date, @@ -46,6 +50,7 @@ public function getData(Group $group, string $date, array $subGroupTypes, array $peopleTypes ); + $leaders = false; if ($peopleTypes === [ 'leaders' ]) { $leaders = true; From 2b3ddfc915ce9926bad45e1105728e97ee85acea Mon Sep 17 00:00:00 2001 From: Sebastian Date: Tue, 11 Jul 2023 17:05:27 +0200 Subject: [PATCH 23/86] remove var dump --- src/Service/DataProvider/GeoLocationDateDataProvider.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Service/DataProvider/GeoLocationDateDataProvider.php b/src/Service/DataProvider/GeoLocationDateDataProvider.php index d58c1fb..650ef1d 100644 --- a/src/Service/DataProvider/GeoLocationDateDataProvider.php +++ b/src/Service/DataProvider/GeoLocationDateDataProvider.php @@ -38,7 +38,6 @@ public function getData(Group $group, string $date, array $subGroupTypes, array { $result = []; $groupMeetingPoints = $this->geoLocationRepository->findAllMeetingPointsForDate($date, $group->getId()); - var_dump($group->getId(), $groupMeetingPoints); foreach ($groupMeetingPoints as $groupMeetingPoint) { $result[] = $this->mapGeoLocation($groupMeetingPoint, false); } From 5ab45bc4483f6d8d27bbfcf9fa91de7877886cb3 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Tue, 11 Jul 2023 17:10:03 +0200 Subject: [PATCH 24/86] lint --- src/Service/Aggregator/GeoLocationAggregator.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Service/Aggregator/GeoLocationAggregator.php b/src/Service/Aggregator/GeoLocationAggregator.php index d048626..ff38ed9 100644 --- a/src/Service/Aggregator/GeoLocationAggregator.php +++ b/src/Service/Aggregator/GeoLocationAggregator.php @@ -121,7 +121,6 @@ public function aggregate(DateTime $startDate = null): void $this->aggregateGroupMeetingPoints($mainGroup, $startPointDate); $this->createWidgetsFromData($personGroups, $mainGroup, $startPointDate); $this->em->flush(); - } $this->em->flush(); $this->em->clear(); From 942ac60edcec44eaedce71fbfcff86447823f2d1 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Wed, 12 Jul 2023 11:13:44 +0200 Subject: [PATCH 25/86] Sanitize Lat and Long --- src/Service/Aggregator/GeoLocationAggregator.php | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/Service/Aggregator/GeoLocationAggregator.php b/src/Service/Aggregator/GeoLocationAggregator.php index ff38ed9..941db92 100644 --- a/src/Service/Aggregator/GeoLocationAggregator.php +++ b/src/Service/Aggregator/GeoLocationAggregator.php @@ -14,6 +14,7 @@ use App\Repository\Statistics\GroupGeoLocationRepository; use DateInterval; use DateTime; +use Digio\Logging\GelfLogger; use Doctrine\ORM\EntityManagerInterface; class GeoLocationAggregator extends WidgetAggregator @@ -40,6 +41,9 @@ class GeoLocationAggregator extends WidgetAggregator private GroupGeoLocationRepository $groupGeoLocationRepository; + protected GelfLogger $gelfLogger; + + public function __construct( EntityManagerInterface $em, GroupRepository $groupRepository, @@ -47,7 +51,8 @@ public function __construct( PersonRoleRepository $personRoleRepository, RoleRepository $roleRepository, AggregatedGeoLocationRepository $geoLocationRepository, - GroupGeoLocationRepository $groupGeoLocationRepository + GroupGeoLocationRepository $groupGeoLocationRepository, + GelfLogger $gelfLogger ) { parent::__construct($groupRepository); @@ -58,6 +63,7 @@ public function __construct( $this->roleRepository = $roleRepository; $this->geoLocationRepository = $geoLocationRepository; $this->groupGeoLocationRepository = $groupGeoLocationRepository; + $this->gelfLogger = $gelfLogger; } /** @@ -163,6 +169,10 @@ private function aggregateGroupMeetingPoints(Group $group, DateTime $dateTime) { $geoLocations = $this->groupGeoLocationRepository->findBy(['group' => $group->getId()]); foreach ($geoLocations as $geoLocation) { + if (!is_numeric($geoLocation->getLong()) || !is_numeric($geoLocation->getLat())) { + $this->gelfLogger->warning('Geolocation ' . $geoLocation->getId() . ' from group ' . $group->getId() . ' could not be aggregated because lat or long is not numeric. (lat: ' . $geoLocation->getLat() . ' ,long: ' . $geoLocation->getLong() . ')'); + continue; + } $widget = new AggregatedGeoLocation(); $widget->setGroup($group); $widget->setLabel(''); // no label From 9e78a69ee93a6cfca95ad6eefbee1e39ade7fa1b Mon Sep 17 00:00:00 2001 From: Sebastian Date: Wed, 12 Jul 2023 17:19:55 +0200 Subject: [PATCH 26/86] add check to only aggregate meeting points of current date --- src/Service/Aggregator/GeoLocationAggregator.php | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/Service/Aggregator/GeoLocationAggregator.php b/src/Service/Aggregator/GeoLocationAggregator.php index 941db92..083b4da 100644 --- a/src/Service/Aggregator/GeoLocationAggregator.php +++ b/src/Service/Aggregator/GeoLocationAggregator.php @@ -6,6 +6,7 @@ use App\Entity\Midata\Group; use App\Entity\Midata\Role; use App\Entity\Statistics\GroupGeoLocation; +use App\Model\LogMessage\SimpleLogMessage; use App\Repository\Aggregated\AggregatedGeoLocationRepository; use App\Repository\Midata\GroupRepository; use App\Repository\Midata\PersonRepository; @@ -88,7 +89,6 @@ public function aggregate(DateTime $startDate = null): void $maxDate = new DateTime(); $startPointDate = clone $minDate; - // von 2014 - heute while ($startPointDate->getTimestamp() < $maxDate->getTimestamp()) { $startPointDate->add(new DateInterval("P1M")); $startPointDate->modify('first day of this month'); @@ -105,8 +105,7 @@ public function aggregate(DateTime $startDate = null): void $this->geoLocationRepository, $mainGroup->getId() ); - // Why are we checking if data for this date exists here and not at the start?! - // Can we even reconstruct passed data?! + if ($this->isDataExistsForDate($startPointDate->format('Y-m-d 00:00:00'), $existingData)) { continue; } @@ -124,7 +123,9 @@ public function aggregate(DateTime $startDate = null): void parent::$roleTypePriority ); - $this->aggregateGroupMeetingPoints($mainGroup, $startPointDate); + if ($startPointDate->getTimestamp() === $maxDate->getTimestamp()) { + $this->aggregateGroupMeetingPoints($mainGroup, $startPointDate); + } $this->createWidgetsFromData($personGroups, $mainGroup, $startPointDate); $this->em->flush(); } @@ -170,7 +171,7 @@ private function aggregateGroupMeetingPoints(Group $group, DateTime $dateTime) $geoLocations = $this->groupGeoLocationRepository->findBy(['group' => $group->getId()]); foreach ($geoLocations as $geoLocation) { if (!is_numeric($geoLocation->getLong()) || !is_numeric($geoLocation->getLat())) { - $this->gelfLogger->warning('Geolocation ' . $geoLocation->getId() . ' from group ' . $group->getId() . ' could not be aggregated because lat or long is not numeric. (lat: ' . $geoLocation->getLat() . ' ,long: ' . $geoLocation->getLong() . ')'); + $this->gelfLogger->warning(new SimpleLogMessage('Geolocation ' . $geoLocation->getId() . ' from group ' . $group->getId() . ' could not be aggregated because lat or long is not numeric. (lat: ' . $geoLocation->getLat() . ' ,long: ' . $geoLocation->getLong() . ')')); continue; } $widget = new AggregatedGeoLocation(); From e0816d9dc93afd435638540819cb6ad787e7b5a7 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Thu, 13 Jul 2023 10:07:43 +0200 Subject: [PATCH 27/86] Add sorting to ensure newest qualifications get displayed --- src/Service/QualificationProcessor.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/Service/QualificationProcessor.php b/src/Service/QualificationProcessor.php index 1159063..b2ae434 100644 --- a/src/Service/QualificationProcessor.php +++ b/src/Service/QualificationProcessor.php @@ -194,6 +194,14 @@ private function removeUnneeded(array $qualifications): array { /** @var AggregatedLeaderOverviewQualification[] $unique */ $temp = []; + // Sort Qualifications by expiration date descending, and null goes first. + // This is so the newest qualification is sent to the frontend and old ones get filtered out. + usort($qualifications, function (AggregatedLeaderOverviewQualification $a, AggregatedLeaderOverviewQualification $b) { + if(is_null($a->getExpiresAt())) return -1; + if(is_null($b->getExpiresAt())) return 1; + if ($a->getExpiresAt()->getTimestamp() === $b->getExpiresAt()->getTimestamp()) return 0; + return ($a->getExpiresAt()->getTimestamp() < $b->getExpiresAt()->getTimestamp()) ? 1 : -1; + }); /** @var AggregatedLeaderOverviewQualification $qualification */ foreach ($qualifications as $qualification) { $exists = false; From 7e78610010235e6366c9afc27866665a9bd2d29a Mon Sep 17 00:00:00 2001 From: Sebastian Date: Thu, 13 Jul 2023 10:10:02 +0200 Subject: [PATCH 28/86] lint inline control structures --- src/Service/QualificationProcessor.php | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/Service/QualificationProcessor.php b/src/Service/QualificationProcessor.php index b2ae434..501e329 100644 --- a/src/Service/QualificationProcessor.php +++ b/src/Service/QualificationProcessor.php @@ -197,9 +197,15 @@ private function removeUnneeded(array $qualifications): array // Sort Qualifications by expiration date descending, and null goes first. // This is so the newest qualification is sent to the frontend and old ones get filtered out. usort($qualifications, function (AggregatedLeaderOverviewQualification $a, AggregatedLeaderOverviewQualification $b) { - if(is_null($a->getExpiresAt())) return -1; - if(is_null($b->getExpiresAt())) return 1; - if ($a->getExpiresAt()->getTimestamp() === $b->getExpiresAt()->getTimestamp()) return 0; + if (is_null($a->getExpiresAt())) { + return -1; + } + if (is_null($b->getExpiresAt())) { + return 1; + } + if ($a->getExpiresAt()->getTimestamp() === $b->getExpiresAt()->getTimestamp()) { + return 0; + } return ($a->getExpiresAt()->getTimestamp() < $b->getExpiresAt()->getTimestamp()) ? 1 : -1; }); /** @var AggregatedLeaderOverviewQualification $qualification */ From 4aff37d2d9fabb10a9f0254e8c1879582b951fa6 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Mon, 17 Jul 2023 08:43:42 +0200 Subject: [PATCH 29/86] fix .env.test --- .env.test | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.env.test b/.env.test index 35dc886..2876d08 100644 --- a/.env.test +++ b/.env.test @@ -42,4 +42,4 @@ GRAYLOG_CLIENT_KEY= IMPORT_TARGET_DIR=data # Sentry -SENTRY_DSN=https://0a3b51c0452b4e1fa0bc6c54db908f59@sentry.digio.ch/26 +SENTRY_DSN="" From 0e2153d28a297687c7b3b8f18fcfbe4000968dc0 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Tue, 18 Jul 2023 09:11:15 +0200 Subject: [PATCH 30/86] minor fixes --- src/Command/ImportFromJsonCommand.php | 4 +++- src/Service/Aggregator/RoleAggregator.php | 6 ++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Command/ImportFromJsonCommand.php b/src/Command/ImportFromJsonCommand.php index ccd9fff..9156503 100644 --- a/src/Command/ImportFromJsonCommand.php +++ b/src/Command/ImportFromJsonCommand.php @@ -674,7 +674,9 @@ private function importPeople(OutputInterface $output) $aggregatedPersonRole->setNickname('Deleted'); $aggregatedPersonRole->setPerson(null); $aggregatedPersonRole->setMidata(null); - $aggregatedPersonRole->setEndAt(new DateTimeImmutable()); + if (is_null($aggregatedPersonRole->getEndAt())) { + $aggregatedPersonRole->setEndAt(new DateTimeImmutable()); + } $this->em->persist($aggregatedPersonRole); } diff --git a/src/Service/Aggregator/RoleAggregator.php b/src/Service/Aggregator/RoleAggregator.php index a97c21f..e191713 100644 --- a/src/Service/Aggregator/RoleAggregator.php +++ b/src/Service/Aggregator/RoleAggregator.php @@ -45,10 +45,8 @@ public function aggregate(DateTime $startDate = null) foreach ($listOfUnfinished as $unfinished) { $midataObject = $this->midataPersonRoleRepository->find($unfinished->getMidata()); $deletedAt = $midataObject->getDeletedAt(); - if ($unfinished->getId() == 217) { - $unfinished->setEndAt($deletedAt); - $this->em->persist($unfinished); - } + $unfinished->setEndAt($deletedAt); + $this->em->persist($unfinished); } $this->em->flush(); From 3c686b272eff4ee55c1cffd4b4d0bab5b76ba8ab Mon Sep 17 00:00:00 2001 From: Sebastian Date: Tue, 18 Jul 2023 11:43:00 +0200 Subject: [PATCH 31/86] lint --- src/Command/ImportFromJsonCommand.php | 6 ++---- src/Controller/Api/Apps/Widgets/RoleOverviewController.php | 3 +-- src/Controller/Api/GroupSettingsController.php | 3 --- src/DTO/Mapper/RoleOverviewMapper.php | 5 ++--- src/DTO/Model/Apps/Widgets/RoleOverview/RoleOccupation.php | 2 -- .../Model/Apps/Widgets/RoleOverview/RoleOverviewDTO.php | 1 - src/Entity/General/GroupSettings.php | 1 - .../DataProvider/RoleOverviewDateRangeDataProvider.php | 7 +++---- 8 files changed, 8 insertions(+), 20 deletions(-) diff --git a/src/Command/ImportFromJsonCommand.php b/src/Command/ImportFromJsonCommand.php index 9156503..f7e7e25 100644 --- a/src/Command/ImportFromJsonCommand.php +++ b/src/Command/ImportFromJsonCommand.php @@ -645,8 +645,7 @@ private function importPeople(OutputInterface $output) } foreach ( - $this->em->getRepository(PersonQualification::class)->findBy(['person' => $person->getId()] - ) as $personQualification + $this->em->getRepository(PersonQualification::class)->findBy(['person' => $person->getId()]) as $personQualification ) { $this->em->remove($personQualification); } @@ -668,8 +667,7 @@ private function importPeople(OutputInterface $output) * @var $aggregatedPersonRole AggregatedPersonRole */ foreach ( - $this->em->getRepository(AggregatedPersonRole::class)->findBy(['person' => $person->getId()] - ) as $aggregatedPersonRole + $this->em->getRepository(AggregatedPersonRole::class)->findBy(['person' => $person->getId()]) as $aggregatedPersonRole ) { $aggregatedPersonRole->setNickname('Deleted'); $aggregatedPersonRole->setPerson(null); diff --git a/src/Controller/Api/Apps/Widgets/RoleOverviewController.php b/src/Controller/Api/Apps/Widgets/RoleOverviewController.php index 0f26dd7..467192b 100644 --- a/src/Controller/Api/Apps/Widgets/RoleOverviewController.php +++ b/src/Controller/Api/Apps/Widgets/RoleOverviewController.php @@ -12,8 +12,7 @@ class RoleOverviewController extends AbstractController { - public function getRoleOverview - ( + public function getRoleOverview( RoleOverviewDateRangeDataProvider $roleOverviewDateRangeDataProvider, DateAndDateRangeRequestData $dateAndDateRangeRequestData, WidgetRequestData $widgetRequestData diff --git a/src/Controller/Api/GroupSettingsController.php b/src/Controller/Api/GroupSettingsController.php index 75b89de..d16a48b 100644 --- a/src/Controller/Api/GroupSettingsController.php +++ b/src/Controller/Api/GroupSettingsController.php @@ -12,10 +12,8 @@ use Doctrine\ORM\EntityManagerInterface; use Symfony\Component\HttpFoundation\Response; - class GroupSettingsController extends AbstractController { - /** * @param Request $request * @param Group $group @@ -35,5 +33,4 @@ public function postRoleOverviewFilter( $entityManager->flush(); return new Response('', 204); } - } diff --git a/src/DTO/Mapper/RoleOverviewMapper.php b/src/DTO/Mapper/RoleOverviewMapper.php index e15171c..a8c681c 100644 --- a/src/DTO/Mapper/RoleOverviewMapper.php +++ b/src/DTO/Mapper/RoleOverviewMapper.php @@ -13,7 +13,6 @@ class RoleOverviewMapper { - const GROUP_TYPE_COLORS = [ 'Biber' => ['#EEE09F', '#d6ca8f'], 'Woelfe' => ['#3BB5DC', '#2f91b0'], @@ -27,7 +26,7 @@ public static function createRoleOverviewDTO(Group $group): RoleOverviewDTO { $groupSettings = $group->getGroupSettings(); $filter = $groupSettings->getRoleOverviewFilter(); - if(!$filter || !sizeof($filter)) { + if (!$filter || !sizeof($filter)) { if ($group->getGroupType()->getGroupType() === GroupType::DEPARTMENT) { $filter = GroupSettings::DEFAULT_DEPARMENT_ROLES; } elseif ($group->getGroupType()->getGroupType() === GroupType::REGION) { @@ -54,7 +53,7 @@ public static function createRoleOccupationWrapper(Role $role, string $locale): private static function getRoleColor(string $roleType) { - foreach (RoleOverviewMapper::GROUP_TYPE_COLORS as $key=>$value) { + foreach (RoleOverviewMapper::GROUP_TYPE_COLORS as $key => $value) { if (str_contains($roleType, $key)) { return $value; } diff --git a/src/DTO/Model/Apps/Widgets/RoleOverview/RoleOccupation.php b/src/DTO/Model/Apps/Widgets/RoleOverview/RoleOccupation.php index 11f3741..0e93937 100644 --- a/src/DTO/Model/Apps/Widgets/RoleOverview/RoleOccupation.php +++ b/src/DTO/Model/Apps/Widgets/RoleOverview/RoleOccupation.php @@ -78,6 +78,4 @@ public function setTo(string $to): void { $this->to = $to; } - - } diff --git a/src/DTO/Model/Apps/Widgets/RoleOverview/RoleOverviewDTO.php b/src/DTO/Model/Apps/Widgets/RoleOverview/RoleOverviewDTO.php index 6acf3b0..51a9862 100644 --- a/src/DTO/Model/Apps/Widgets/RoleOverview/RoleOverviewDTO.php +++ b/src/DTO/Model/Apps/Widgets/RoleOverview/RoleOverviewDTO.php @@ -60,5 +60,4 @@ public function addData(RoleOccupationWrapper $roleOccupationWrapper): void { $this->data[] = $roleOccupationWrapper; } - } diff --git a/src/Entity/General/GroupSettings.php b/src/Entity/General/GroupSettings.php index 96410a7..2f9604d 100644 --- a/src/Entity/General/GroupSettings.php +++ b/src/Entity/General/GroupSettings.php @@ -12,7 +12,6 @@ */ class GroupSettings { - public const DEFAULT_DEPARMENT_ROLES = [Role::DEPARTMENT_LEADER, Role::DEPARTMENT_COACH, Role::DEPARTMENT_COACH, Role::DEPARTMENT_LEADER_BIBER, Role::DEPARTMENT_LEADER_WOELFE, Role::DEPARTMENT_LEADER_PFADI, Role::DEPARTMENT_LEADER_PIO, Role::DEPARTMENT_LEADER_ROVER]; diff --git a/src/Service/DataProvider/RoleOverviewDateRangeDataProvider.php b/src/Service/DataProvider/RoleOverviewDateRangeDataProvider.php index 91520d7..fc0de24 100644 --- a/src/Service/DataProvider/RoleOverviewDateRangeDataProvider.php +++ b/src/Service/DataProvider/RoleOverviewDateRangeDataProvider.php @@ -17,13 +17,12 @@ class RoleOverviewDateRangeDataProvider extends WidgetDataProvider { - protected $personRoleRepository; public function __construct( GroupRepository $groupRepository, GroupTypeRepository $groupTypeRepository, TranslatorInterface $translator, - AggregatedPersonRoleRepository $personRoleRepository + AggregatedPersonRoleRepository $personRoleRepository ) { $this->groupRepository = $groupRepository; $this->personRoleRepository = $personRoleRepository; @@ -43,8 +42,8 @@ public function getData(Group $group, string $from, string $to) $roleOccupationWrappers = []; foreach ($aggregatedPersonRoles as $aggregatedPersonRole) { $wrapperExistsAt = -1; - foreach ($roleOccupationWrappers as $key=>$roleOccupationWrapper) { - if($roleOccupationWrapper->getRoleType() === $aggregatedPersonRole->getRole()->getRoleType()) { + foreach ($roleOccupationWrappers as $key => $roleOccupationWrapper) { + if ($roleOccupationWrapper->getRoleType() === $aggregatedPersonRole->getRole()->getRoleType()) { $wrapperExistsAt = $key; } } From 1bc30d5ba628fb2000a1f1a258e933bc982c7a2c Mon Sep 17 00:00:00 2001 From: Sebastian Date: Fri, 1 Sep 2023 09:34:27 +0200 Subject: [PATCH 32/86] partial implementation without backend filters --- config/routes/app_routes.yml | 5 + config/routes/apps/census.yml | 9 + config/routes/apps/widgets.yml | 21 + src/Command/FetchCensusCommand.php | 87 +++- src/Command/ImportFromJsonCommand.php | 4 + src/Controller/Api/Apps/CensusController.php | 57 +++ .../Api/Apps/Widgets/FilterController.php | 18 + src/DTO/Mapper/CensusMapper.php | 8 + src/DTO/Mapper/FilterDataMapper.php | 9 + src/DTO/Model/Apps/Census/GroupCensusDTO.php | 297 ++++++++++++ src/DTO/Model/Apps/Census/TableDTO.php | 94 ++++ src/Entity/Midata/CensusGroup.php | 448 ++++++++++++++++++ src/Migrations/Version20230720133258.php | 36 ++ .../AggregatedPersonRoleRepository.php | 2 +- .../Midata/CensusGroupRepository.php | 79 +++ src/Service/CensusAPIService.php | 10 +- .../DataProvider/CensusDataProvider.php | 78 +++ .../DataProvider/FilterDataProvider.php | 6 + 18 files changed, 1257 insertions(+), 11 deletions(-) create mode 100644 config/routes/apps/census.yml create mode 100644 src/Controller/Api/Apps/CensusController.php create mode 100644 src/DTO/Mapper/CensusMapper.php create mode 100644 src/DTO/Model/Apps/Census/GroupCensusDTO.php create mode 100644 src/DTO/Model/Apps/Census/TableDTO.php create mode 100644 src/Entity/Midata/CensusGroup.php create mode 100644 src/Migrations/Version20230720133258.php create mode 100644 src/Repository/Midata/CensusGroupRepository.php create mode 100644 src/Service/DataProvider/CensusDataProvider.php diff --git a/config/routes/app_routes.yml b/config/routes/app_routes.yml index c7db39d..12bb0cb 100644 --- a/config/routes/app_routes.yml +++ b/config/routes/app_routes.yml @@ -16,3 +16,8 @@ general: resource: "apps/general.yml" prefix: /general name_prefix: general_ + +census: + resource: "apps/census.yml" + prefix: /census + name_prefix: census_ diff --git a/config/routes/apps/census.yml b/config/routes/apps/census.yml new file mode 100644 index 0000000..cdaf5fc --- /dev/null +++ b/config/routes/apps/census.yml @@ -0,0 +1,9 @@ +preview: + path: /preview + methods: GET + controller: App\Controller\Api\Apps\CensusController::getPreview + +filter: + path: /filter + methods: GET + controller: App\Controller\Api\Apps\Widgets\FilterController:getGroupTypes diff --git a/config/routes/apps/widgets.yml b/config/routes/apps/widgets.yml index 3ea75ed..c4433c7 100644 --- a/config/routes/apps/widgets.yml +++ b/config/routes/apps/widgets.yml @@ -48,3 +48,24 @@ role_overview: path: /role-overview methods: GET controller: App\Controller\Api\Apps\Widgets\RoleOverviewController:getRoleOverview + +census_table: + path: /census-table + methods: GET + controller: App\Controller\Api\Apps\CensusController::getTableData + +census_members: + path: /census-members + methods: GET + controller: App\Controller\Api\Apps\CensusController::getMembersData + +census_development: + path: /census-development + methods: GET + controller: App\Controller\Api\Apps\CensusController::getMembersData + +census_treemap: + path: /census-treemap + methods: GET + controller: App\Controller\Api\Apps\CensusController::getMembersData + diff --git a/src/Command/FetchCensusCommand.php b/src/Command/FetchCensusCommand.php index 995c1dc..f3aa7ca 100644 --- a/src/Command/FetchCensusCommand.php +++ b/src/Command/FetchCensusCommand.php @@ -2,15 +2,34 @@ namespace App\Command; +use App\Entity\Midata\CensusGroup; use App\Model\CommandStatistics; +use App\Repository\Midata\CensusGroupRepository; +use App\Repository\Midata\GroupTypeRepository; +use App\Service\CensusAPIService; use App\Service\GroupStructureAPIService; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; class FetchCensusCommand extends StatisticsCommand { - protected GroupStructureAPIService $apiService; + protected CensusAPIService $apiService; + protected CensusGroupRepository $censusGroupRepository; + protected GroupTypeRepository $groupTypeRepository; - public function __construct() + private SymfonyStyle $io; + + public function __construct( + CensusAPIService $apiService, + CensusGroupRepository $censusGroupRepository, + GroupTypeRepository $groupTypeRepository + ) { + $this->apiService = $apiService; + $this->censusGroupRepository = $censusGroupRepository; + $this->groupTypeRepository = $groupTypeRepository; parent::__construct(); } @@ -21,6 +40,70 @@ public function configure() ->setDescription('Not implemented'); } + public function execute(InputInterface $input, OutputInterface $output) + { + $this->io = new SymfonyStyle($input, $output); + + $year = (int) date('Y'); + $minYear = $year - 6; + $groupsToAggregate = []; + // Fetch groups + while ($year > $minYear) { + $this->io->writeln('year ' . $year); + $rawCensusData = $this->apiService->getCensusData($year); + $rawCensusGroups = $rawCensusData->getContent()['census_evaluations']['groups']; + foreach ($rawCensusGroups as $rawCensusGroup) { + $exists = $this->censusGroupRepository->findOneBy(['group_id' => $rawCensusGroup['group_id'], 'year' => $year]); + if(is_null($exists)) { + $groupsToAggregate[] = $rawCensusGroup['group_id']; + $this->mapRawCensusGroupToCensusGroup($rawCensusGroup, $year); + } + } + $year--; + } + // Aggregate Groups + foreach (array_unique($groupsToAggregate) as $groupId) { + + } + return Command::SUCCESS; + } + + private function mapRawCensusGroupToCensusGroup(array $rawCensusGroup, int $year) + { + $censusGroup = new CensusGroup(); + $censusGroup->setGroupId($this->sanitizeValue($rawCensusGroup['group_id'])); + $censusGroup->setYear($year); + $censusGroup->setGroupType($this->groupTypeRepository->findOneBy(['groupType' => $rawCensusGroup['group_type']])); + $censusGroup->setName($rawCensusGroup['group_name']); + + $censusGroup->setTotalCount($this->sanitizeValue($rawCensusGroup['total']['total'])); + $censusGroup->setTotalFCount($this->sanitizeValue($rawCensusGroup['total']['f'])); + $censusGroup->setTotalMCount($this->sanitizeValue($rawCensusGroup['total']['m'])); + + $censusGroup->setLeiterFCount($this->sanitizeValue($rawCensusGroup['f']['leiter'])); + $censusGroup->setBiberFCount($this->sanitizeValue($rawCensusGroup['f']['biber'])); + $censusGroup->setWoelfeFCount($this->sanitizeValue($rawCensusGroup['f']['woelfe'])); + $censusGroup->setPfadisFCount($this->sanitizeValue($rawCensusGroup['f']['pfadis'])); + $censusGroup->setPiosFCount($this->sanitizeValue($rawCensusGroup['f']['pios'])); + $censusGroup->setRoverFCount($this->sanitizeValue($rawCensusGroup['f']['rover'])); + $censusGroup->setPtaFCount($this->sanitizeValue($rawCensusGroup['f']['pta'])); + + $censusGroup->setLeiterMCount($this->sanitizeValue($rawCensusGroup['m']['leiter'])); + $censusGroup->setBiberMCount($this->sanitizeValue($rawCensusGroup['m']['biber'])); + $censusGroup->setWoelfeMCount($this->sanitizeValue($rawCensusGroup['m']['woelfe'])); + $censusGroup->setPfadisMCount($this->sanitizeValue($rawCensusGroup['m']['pfadis'])); + $censusGroup->setPiosMCount($this->sanitizeValue($rawCensusGroup['m']['pios'])); + $censusGroup->setRoverMCount($this->sanitizeValue($rawCensusGroup['m']['rover'])); + $censusGroup->setPtaMCount($this->sanitizeValue($rawCensusGroup['m']['pta'])); + $this->censusGroupRepository->add($censusGroup); + } + + private function sanitizeValue($raw): int + { + return is_null($raw) ? 0 : $raw; + } + + // TODO: Implement the statistics public function getStats(): CommandStatistics { diff --git a/src/Command/ImportFromJsonCommand.php b/src/Command/ImportFromJsonCommand.php index 8e79e8a..3624750 100644 --- a/src/Command/ImportFromJsonCommand.php +++ b/src/Command/ImportFromJsonCommand.php @@ -392,6 +392,10 @@ private function importGroups(OutputInterface $output) $metadata = $this->em->getClassMetaData(get_class($group)); $metadata->setIdGenerator(new AssignedGenerator()); + /** @var GroupType $gt */ + $gt = $this->em->getRepository(GroupType::class)->findOneBy(['groupType' => $gr['type']]); + $group->setGroupType($gt); + // create group settings $groupSettings = new GroupSettings(); $groupSettings->setGroup($group); diff --git a/src/Controller/Api/Apps/CensusController.php b/src/Controller/Api/Apps/CensusController.php new file mode 100644 index 0000000..e039732 --- /dev/null +++ b/src/Controller/Api/Apps/CensusController.php @@ -0,0 +1,57 @@ +censusDataProvider = $censusDataProvider; + } + + /** + * @param Group $group + * @return JsonResponse + * + * @ParamConverter("group", options={"mapping": {"groupId": "id"}}) + */ + public function getPreview(Group $group) + { + $this->denyAccessUnlessGranted(PermissionVoter::OWNER, $group); + return $this->json($this->censusDataProvider->getPreviewData($group)); + } + + /** + * @param Group $group + * @return JsonResponse + * + * @ParamConverter("group", options={"mapping": {"groupId": "id"}}) + */ + public function getTableData(Group $group) + { + $this->denyAccessUnlessGranted(PermissionVoter::OWNER, $group); + return $this->json($this->censusDataProvider->getTableData($group)); + } + + /** + * @param Group $group + * @return JsonResponse + * + * @ParamConverter("group", options={"mapping": {"groupId": "id"}}) + */ + public function getMembersData(Group $group) + { + return $this->json([1,2,3,4]); + } +} diff --git a/src/Controller/Api/Apps/Widgets/FilterController.php b/src/Controller/Api/Apps/Widgets/FilterController.php index 6042aa4..27d1bd9 100644 --- a/src/Controller/Api/Apps/Widgets/FilterController.php +++ b/src/Controller/Api/Apps/Widgets/FilterController.php @@ -31,4 +31,22 @@ public function getFilterData( return $this->json($data); } + + /** + * @param Request $request + * @param Group $group + * @param FilterDataProvider $filterDataProvider + * @return JsonResponse + * + * @ParamConverter("group", options={"mapping": {"groupId": "id"}}) + */ + public function getGroupTypes( + Request $request, + Group $group, + FilterDataProvider $filterDataProvider + ): JsonResponse { + $this->denyAccessUnlessGranted(PermissionVoter::VIEWER, $group); + $data = $filterDataProvider->getGroupTypes($group, $request->getLocale()); + return $this->json($data); + } } diff --git a/src/DTO/Mapper/CensusMapper.php b/src/DTO/Mapper/CensusMapper.php new file mode 100644 index 0000000..f1beea4 --- /dev/null +++ b/src/DTO/Mapper/CensusMapper.php @@ -0,0 +1,8 @@ +setGroupTypes($groupTypeDTOs); return $filterData; } + + public static function createGroupTypes(array $groupTypes, string $locale) + { + $groupTypeDTOs = []; + foreach ($groupTypes as $type) { + $groupTypeDTOs[] = GroupTypeMapper::createGroupTypeFromQueryResult($type, $locale); + } + return $groupTypeDTOs; + } } diff --git a/src/DTO/Model/Apps/Census/GroupCensusDTO.php b/src/DTO/Model/Apps/Census/GroupCensusDTO.php new file mode 100644 index 0000000..2933225 --- /dev/null +++ b/src/DTO/Model/Apps/Census/GroupCensusDTO.php @@ -0,0 +1,297 @@ +year; + } + + /** + * @param string $year + */ + public function setYear(string $year): void + { + $this->year = $year; + } + + /** + * @return int + */ + public function getTotalMCount(): int + { + return $this->total_m_count; + } + + /** + * @param int $total_m_count + */ + public function setTotalMCount(int $total_m_count): void + { + $this->total_m_count = $total_m_count; + } + + /** + * @return int + */ + public function getTotalFCount(): int + { + return $this->total_f_count; + } + + /** + * @param int $total_f_count + */ + public function setTotalFCount(int $total_f_count): void + { + $this->total_f_count = $total_f_count; + } + + /** + * @return int + */ + public function getLeiterMCount(): int + { + return $this->leiter_m_count; + } + + /** + * @param int $leiter_m_count + */ + public function setLeiterMCount(int $leiter_m_count): void + { + $this->leiter_m_count = $leiter_m_count; + } + + /** + * @return int + */ + public function getLeiterFCount(): int + { + return $this->leiter_f_count; + } + + /** + * @param int $leiter_f_count + */ + public function setLeiterFCount(int $leiter_f_count): void + { + $this->leiter_f_count = $leiter_f_count; + } + + /** + * @return int + */ + public function getBiberMCount(): int + { + return $this->biber_m_count; + } + + /** + * @param int $biber_m_count + */ + public function setBiberMCount(int $biber_m_count): void + { + $this->biber_m_count = $biber_m_count; + } + + /** + * @return int + */ + public function getBiberFCount(): int + { + return $this->biber_f_count; + } + + /** + * @param int $biber_f_count + */ + public function setBiberFCount(int $biber_f_count): void + { + $this->biber_f_count = $biber_f_count; + } + + /** + * @return int + */ + public function getWoelfeMCount(): int + { + return $this->woelfe_m_count; + } + + /** + * @param int $woelfe_m_count + */ + public function setWoelfeMCount(int $woelfe_m_count): void + { + $this->woelfe_m_count = $woelfe_m_count; + } + + /** + * @return int + */ + public function getWoelfeFCount(): int + { + return $this->woelfe_f_count; + } + + /** + * @param int $woelfe_f_count + */ + public function setWoelfeFCount(int $woelfe_f_count): void + { + $this->woelfe_f_count = $woelfe_f_count; + } + + /** + * @return int + */ + public function getPfadisMCount(): int + { + return $this->pfadis_m_count; + } + + /** + * @param int $pfadis_m_count + */ + public function setPfadisMCount(int $pfadis_m_count): void + { + $this->pfadis_m_count = $pfadis_m_count; + } + + /** + * @return int + */ + public function getPfadisFCount(): int + { + return $this->pfadis_f_count; + } + + /** + * @param int $pfadis_f_count + */ + public function setPfadisFCount(int $pfadis_f_count): void + { + $this->pfadis_f_count = $pfadis_f_count; + } + + /** + * @return int + */ + public function getPiosMCount(): int + { + return $this->pios_m_count; + } + + /** + * @param int $pios_m_count + */ + public function setPiosMCount(int $pios_m_count): void + { + $this->pios_m_count = $pios_m_count; + } + + /** + * @return int + */ + public function getPiosFCount(): int + { + return $this->pios_f_count; + } + + /** + * @param int $pios_f_count + */ + public function setPiosFCount(int $pios_f_count): void + { + $this->pios_f_count = $pios_f_count; + } + + /** + * @return int + */ + public function getRoverMCount(): int + { + return $this->rover_m_count; + } + + /** + * @param int $rover_m_count + */ + public function setRoverMCount(int $rover_m_count): void + { + $this->rover_m_count = $rover_m_count; + } + + /** + * @return int + */ + public function getRoverFCount(): int + { + return $this->rover_f_count; + } + + /** + * @param int $rover_f_count + */ + public function setRoverFCount(int $rover_f_count): void + { + $this->rover_f_count = $rover_f_count; + } + + /** + * @return int + */ + public function getPtaMCount(): int + { + return $this->pta_m_count; + } + + /** + * @param int $pta_m_count + */ + public function setPtaMCount(int $pta_m_count): void + { + $this->pta_m_count = $pta_m_count; + } + + /** + * @return int + */ + public function getPtaFCount(): int + { + return $this->pta_f_count; + } + + /** + * @param int $pta_f_count + */ + public function setPtaFCount(int $pta_f_count): void + { + $this->pta_f_count = $pta_f_count; + } + +} diff --git a/src/DTO/Model/Apps/Census/TableDTO.php b/src/DTO/Model/Apps/Census/TableDTO.php new file mode 100644 index 0000000..ba3c192 --- /dev/null +++ b/src/DTO/Model/Apps/Census/TableDTO.php @@ -0,0 +1,94 @@ +id; + } + + /** + * @param int $id + */ + public function setId(int $id): void + { + $this->id = $id; + } + + /** + * @return string + */ + public function getName(): string + { + return $this->name; + } + + /** + * @param string $name + */ + public function setName(string $name): void + { + $this->name = $name; + } + + /** + * @return string + */ + public function getGroupType(): string + { + return $this->group_type; + } + + /** + * @param string $group_type + */ + public function setGroupType(string $group_type): void + { + $this->group_type = $group_type; + } + + /** + * @return array|null + */ + public function getData(): ?array + { + return $this->data; + } + + /** + * @param array|null $data + */ + public function setData(?array $data): void + { + $this->data = $data; + } + + /** + * @return array|null + */ + public function getChildren(): ?array + { + return $this->children; + } + + /** + * @param array|null $children + */ + public function setChildren(?array $children): void + { + $this->children = $children; + } +} diff --git a/src/Entity/Midata/CensusGroup.php b/src/Entity/Midata/CensusGroup.php new file mode 100644 index 0000000..ffe5d78 --- /dev/null +++ b/src/Entity/Midata/CensusGroup.php @@ -0,0 +1,448 @@ +id; + } + + /** + * @return mixed + */ + public function getGroupType() + { + return $this->group_type; + } + + /** + * @param mixed $group_type + */ + public function setGroupType($group_type): void + { + $this->group_type = $group_type; + } + + /** + * @return mixed + */ + public function getTotalCount() + { + return $this->total_count; + } + + /** + * @param mixed $total_count + */ + public function setTotalCount($total_count): void + { + $this->total_count = $total_count; + } + + /** + * @return mixed + */ + public function getTotalMCount() + { + return $this->total_m_count; + } + + /** + * @param mixed $total_m_count + */ + public function setTotalMCount($total_m_count): void + { + $this->total_m_count = $total_m_count; + } + + /** + * @return mixed + */ + public function getTotalFCount() + { + return $this->total_f_count; + } + + /** + * @param mixed $total_f_count + */ + public function setTotalFCount($total_f_count): void + { + $this->total_f_count = $total_f_count; + } + + /** + * @return mixed + */ + public function getLeiterMCount() + { + return $this->leiter_m_count; + } + + /** + * @param mixed $leiter_m_count + */ + public function setLeiterMCount($leiter_m_count): void + { + $this->leiter_m_count = $leiter_m_count; + } + + /** + * @return mixed + */ + public function getLeiterFCount() + { + return $this->leiter_f_count; + } + + /** + * @param mixed $leiter_f_count + */ + public function setLeiterFCount($leiter_f_count): void + { + $this->leiter_f_count = $leiter_f_count; + } + + /** + * @return mixed + */ + public function getBiberMCount() + { + return $this->biber_m_count; + } + + /** + * @param mixed $biber_m_count + */ + public function setBiberMCount($biber_m_count): void + { + $this->biber_m_count = $biber_m_count; + } + + /** + * @return mixed + */ + public function getBiberFCount() + { + return $this->biber_f_count; + } + + /** + * @param mixed $biber_f_count + */ + public function setBiberFCount($biber_f_count): void + { + $this->biber_f_count = $biber_f_count; + } + + /** + * @return mixed + */ + public function getWoelfeMCount() + { + return $this->woelfe_m_count; + } + + /** + * @param mixed $woelfe_m_count + */ + public function setWoelfeMCount($woelfe_m_count): void + { + $this->woelfe_m_count = $woelfe_m_count; + } + + /** + * @return mixed + */ + public function getWoelfeFCount() + { + return $this->woelfe_f_count; + } + + /** + * @param mixed $woelfe_f_count + */ + public function setWoelfeFCount($woelfe_f_count): void + { + $this->woelfe_f_count = $woelfe_f_count; + } + + /** + * @return mixed + */ + public function getPfadisMCount() + { + return $this->pfadis_m_count; + } + + /** + * @param mixed $pfadis_m_count + */ + public function setPfadisMCount($pfadis_m_count): void + { + $this->pfadis_m_count = $pfadis_m_count; + } + + /** + * @return mixed + */ + public function getPfadisFCount() + { + return $this->pfadis_f_count; + } + + /** + * @param mixed $pfadis_f_count + */ + public function setPfadisFCount($pfadis_f_count): void + { + $this->pfadis_f_count = $pfadis_f_count; + } + + /** + * @return mixed + */ + public function getPiosMCount() + { + return $this->pios_m_count; + } + + /** + * @param mixed $pios_m_count + */ + public function setPiosMCount($pios_m_count): void + { + $this->pios_m_count = $pios_m_count; + } + + /** + * @return mixed + */ + public function getPiosFCount() + { + return $this->pios_f_count; + } + + /** + * @param mixed $pios_f_count + */ + public function setPiosFCount($pios_f_count): void + { + $this->pios_f_count = $pios_f_count; + } + + /** + * @return mixed + */ + public function getRoverMCount() + { + return $this->rover_m_count; + } + + /** + * @param mixed $rover_m_count + */ + public function setRoverMCount($rover_m_count): void + { + $this->rover_m_count = $rover_m_count; + } + + /** + * @return mixed + */ + public function getRoverFCount() + { + return $this->rover_f_count; + } + + /** + * @param mixed $rover_f_count + */ + public function setRoverFCount($rover_f_count): void + { + $this->rover_f_count = $rover_f_count; + } + + /** + * @return mixed + */ + public function getPtaMCount() + { + return $this->pta_m_count; + } + + /** + * @param mixed $pta_m_count + */ + public function setPtaMCount($pta_m_count): void + { + $this->pta_m_count = $pta_m_count; + } + + /** + * @return mixed + */ + public function getPtaFCount() + { + return $this->pta_f_count; + } + + /** + * @param mixed $pta_f_count + */ + public function setPtaFCount($pta_f_count): void + { + $this->pta_f_count = $pta_f_count; + } + + /** + * @return mixed + */ + public function getName() + { + return $this->name; + } + + /** + * @param mixed $name + */ + public function setName($name): void + { + $this->name = $name; + } + + /** + * @return mixed + */ + public function getGroupId() + { + return $this->group_id; + } + + /** + * @param mixed $group_id + */ + public function setGroupId($group_id): void + { + $this->group_id = $group_id; + } + + /** + * @return mixed + */ + public function getYear() + { + return $this->year; + } + + /** + * @param mixed $year + */ + public function setYear($year): void + { + $this->year = $year; + } + +} diff --git a/src/Migrations/Version20230720133258.php b/src/Migrations/Version20230720133258.php new file mode 100644 index 0000000..903a267 --- /dev/null +++ b/src/Migrations/Version20230720133258.php @@ -0,0 +1,36 @@ +addSql('CREATE SEQUENCE census_group_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); + $this->addSql('CREATE TABLE census_group (id INT NOT NULL, group_type_id INT NOT NULL, total_count INT NOT NULL, total_m_count INT NOT NULL, total_f_count INT NOT NULL, leiter_m_count INT NOT NULL, leiter_f_count INT NOT NULL, biber_m_count INT NOT NULL, biber_f_count INT NOT NULL, woelfe_m_count INT NOT NULL, woelfe_f_count INT NOT NULL, pfadis_m_count INT NOT NULL, pfadis_f_count INT NOT NULL, pios_m_count INT NOT NULL, pios_f_count INT NOT NULL, rover_m_count INT NOT NULL, rover_f_count INT NOT NULL, pta_m_count INT NOT NULL, pta_f_count INT NOT NULL, name VARCHAR(255) NOT NULL, group_id INT NOT NULL, year VARCHAR(255) NOT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE INDEX IDX_6C90D586434CD89F ON census_group (group_type_id)'); + $this->addSql('ALTER TABLE census_group ADD CONSTRAINT FK_6C90D586434CD89F FOREIGN KEY (group_type_id) REFERENCES midata_group_type (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('CREATE SCHEMA public'); + $this->addSql('DROP SEQUENCE census_group_id_seq CASCADE'); + $this->addSql('DROP TABLE census_group'); + } +} diff --git a/src/Repository/Aggregated/AggregatedPersonRoleRepository.php b/src/Repository/Aggregated/AggregatedPersonRoleRepository.php index 0b0ead5..6019cdc 100644 --- a/src/Repository/Aggregated/AggregatedPersonRoleRepository.php +++ b/src/Repository/Aggregated/AggregatedPersonRoleRepository.php @@ -36,7 +36,7 @@ public function getHighestAggregatedMidataIndex(): int return $this->createQueryBuilder('a') ->select('MAX(a.midata)') ->getQuery() - ->getResult()[0][1]; + ->getResult()[0][1] ?? 0; } /** diff --git a/src/Repository/Midata/CensusGroupRepository.php b/src/Repository/Midata/CensusGroupRepository.php new file mode 100644 index 0000000..f864645 --- /dev/null +++ b/src/Repository/Midata/CensusGroupRepository.php @@ -0,0 +1,79 @@ + + * + * @method CensusGroup|null find($id, $lockMode = null, $lockVersion = null) + * @method CensusGroup|null findOneBy(array $criteria, array $orderBy = null) + * @method CensusGroup[] findAll() + * @method CensusGroup[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null) + */ +class CensusGroupRepository extends ServiceEntityRepository +{ + public function __construct(ManagerRegistry $registry) + { + parent::__construct($registry, CensusGroup::class); + } + + /** + * @throws ORMException + * @throws OptimisticLockException + */ + public function add(CensusGroup $entity, bool $flush = true): void + { + $this->_em->persist($entity); + if ($flush) { + $this->_em->flush(); + } + } + + /** + * @throws ORMException + * @throws OptimisticLockException + */ + public function remove(CensusGroup $entity, bool $flush = true): void + { + $this->_em->remove($entity); + if ($flush) { + $this->_em->flush(); + } + } + + // /** + // * @return CensusGroup[] Returns an array of CensusGroup objects + // */ + /* + public function findByExampleField($value) + { + return $this->createQueryBuilder('c') + ->andWhere('c.exampleField = :val') + ->setParameter('val', $value) + ->orderBy('c.id', 'ASC') + ->setMaxResults(10) + ->getQuery() + ->getResult() + ; + } + */ + + /* + public function findOneBySomeField($value): ?CensusGroup + { + return $this->createQueryBuilder('c') + ->andWhere('c.exampleField = :val') + ->setParameter('val', $value) + ->getQuery() + ->getOneOrNullResult() + ; + } + */ +} diff --git a/src/Service/CensusAPIService.php b/src/Service/CensusAPIService.php index 3540295..cc73eea 100644 --- a/src/Service/CensusAPIService.php +++ b/src/Service/CensusAPIService.php @@ -27,15 +27,9 @@ public function __construct(GuzzleWrapper $guzzleWrapper, string $url, string $a } - /** - * Fetch a group from the Group Structure API. - * This can return any group regardless of healthcheck opt-out or any other factor. - * @param int $groupId - * @return Http\CurlResponse - */ - public function getGroup(int $groupId): Http\CurlResponse + public function getCensusData(int $year): Http\CurlResponse { - $endpoint = $this->url . '/de/groups/' . $groupId . '.json?token=' . $this->apiToken; + $endpoint = $this->url . '/group_health/census_evaluations.json?token=' . $this->apiToken . '&year=' . $year; return $this->guzzleWrapper->getJson($endpoint, null, []); } diff --git a/src/Service/DataProvider/CensusDataProvider.php b/src/Service/DataProvider/CensusDataProvider.php new file mode 100644 index 0000000..68401f0 --- /dev/null +++ b/src/Service/DataProvider/CensusDataProvider.php @@ -0,0 +1,78 @@ +censusGroupRepository = $censusGroupRepository; + parent::__construct( + $groupRepository, + $groupTypeRepository, + $translator + ); + } + + public function getPreviewData(Group $group) { + $groups = $this->groupRepository->findAllRelevantSubGroupsByParentGroupId($group->getId(), ['Group::Abteilung', 'Group::Kantonalverband', 'Group::Region']); + $return = [ + 'm' => [ + 'leiter' => 0, + 'biber' => 0, + 'woelfe' => 0, + 'pfadis' => 0, + 'rover' => 0, + 'pio' => 0, + 'pta' => 0 + ], + 'f' => [ + 'leiter' => 0, + 'biber' => 0, + 'woelfe' => 0, + 'pfadis' => 0, + 'rover' => 0, + 'pio' => 0, + 'pta' => 0 + ] + ]; + foreach ($groups as $group) { + $censusGroup = $this->censusGroupRepository->findOneBy(['group_id' => $group['id'], 'year' => date('Y')]); + if (!is_null($censusGroup)) { + $return['m']['leiter'] += $censusGroup->getLeiterMCount(); + $return['m']['biber'] += $censusGroup->getBiberMCount(); + $return['m']['woelfe'] += $censusGroup->getWoelfeMCount(); + $return['m']['pfadis'] += $censusGroup->getPfadisMCount(); + $return['m']['rover'] += $censusGroup->getRoverMCount(); + $return['m']['pio'] += $censusGroup->getPiosMCount(); + $return['m']['pta'] += $censusGroup->getPtaMCount(); + + $return['f']['leiter'] += $censusGroup->getLeiterFCount(); + $return['f']['biber'] += $censusGroup->getBiberFCount(); + $return['f']['woelfe'] += $censusGroup->getWoelfeFCount(); + $return['f']['pfadis'] += $censusGroup->getPfadisFCount(); + $return['f']['rover'] += $censusGroup->getRoverFCount(); + $return['f']['pio'] += $censusGroup->getPiosFCount(); + $return['f']['pta'] += $censusGroup->getPtaFCount(); + } + } + return $return; + } + + public function getTableData(Group $group) { + $groups = $this->groupRepository->findAllRelevantSubGroupsByParentGroupId($group->getId(), ['Group::Abteilung', 'Group::Kantonalverband', 'Group::Region']); + return $groups; + } +} diff --git a/src/Service/DataProvider/FilterDataProvider.php b/src/Service/DataProvider/FilterDataProvider.php index 6cdbc6f..9c4a72e 100644 --- a/src/Service/DataProvider/FilterDataProvider.php +++ b/src/Service/DataProvider/FilterDataProvider.php @@ -31,6 +31,12 @@ public function __construct( $this->widgetDateRepository = $widgetDateRepository; } + public function getGroupTypes(Group $group, string $locale) + { + $groupTypes = $this->groupTypeRepository->findGroupTypesForParentGroup($group->getId()); + return FilterDataMapper::createGroupTypes($groupTypes, $locale); + } + /*** * @param Group $group * @param string $locale From 5011724dd213e420a71d2e11736adef967cfd8d3 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Thu, 7 Sep 2023 15:21:26 +0200 Subject: [PATCH 33/86] enable reflections for census --- src/Controller/Api/Apps/CensusController.php | 7 +- src/DTO/Mapper/CensusMapper.php | 59 ++++++++ src/DTO/Model/Apps/Census/TableDTO.php | 92 ++++++++---- .../FilterRequestData/CensusRequestData.php | 102 +++++++++++++ src/Entity/Midata/CensusGroup.php | 21 ++- .../WidgetControllerListener.php | 35 ++++- .../Statistics/StatisticGroupRepository.php | 28 ++++ src/Service/Apps/Census/CensusFilter.php | 76 ++++++++++ .../DataProvider/CensusDataProvider.php | 135 +++++++++++++++++- .../DataProvider/WidgetDataProvider.php | 10 ++ 10 files changed, 528 insertions(+), 37 deletions(-) create mode 100644 src/DTO/Model/FilterRequestData/CensusRequestData.php create mode 100644 src/Service/Apps/Census/CensusFilter.php diff --git a/src/Controller/Api/Apps/CensusController.php b/src/Controller/Api/Apps/CensusController.php index e039732..b090267 100644 --- a/src/Controller/Api/Apps/CensusController.php +++ b/src/Controller/Api/Apps/CensusController.php @@ -2,6 +2,7 @@ namespace App\Controller\Api\Apps; +use App\DTO\Model\FilterRequestData\CensusRequestData; use App\Entity\Midata\Group; use App\Service\DataProvider\CensusDataProvider; use App\Service\Security\PermissionVoter; @@ -38,10 +39,10 @@ public function getPreview(Group $group) * * @ParamConverter("group", options={"mapping": {"groupId": "id"}}) */ - public function getTableData(Group $group) + public function getTableData(Group $group, CensusRequestData $censusRequestData) { $this->denyAccessUnlessGranted(PermissionVoter::OWNER, $group); - return $this->json($this->censusDataProvider->getTableData($group)); + return $this->json($this->censusDataProvider->getTableData($group, $censusRequestData)); } /** @@ -50,7 +51,7 @@ public function getTableData(Group $group) * * @ParamConverter("group", options={"mapping": {"groupId": "id"}}) */ - public function getMembersData(Group $group) + public function getMembersData(Group $group, CensusRequestData $censusRequestData) { return $this->json([1,2,3,4]); } diff --git a/src/DTO/Mapper/CensusMapper.php b/src/DTO/Mapper/CensusMapper.php index f1beea4..ec0d148 100644 --- a/src/DTO/Mapper/CensusMapper.php +++ b/src/DTO/Mapper/CensusMapper.php @@ -2,7 +2,66 @@ namespace App\DTO\Mapper; +use App\DTO\Model\Apps\Census\TableDTO; +use App\Entity\Midata\CensusGroup; +use App\Entity\Statistics\StatisticGroup; +use Symfony\Component\Validator\Constraints\Date; + class CensusMapper { + /** + * @param StatisticGroup $statisticGroup + * @param CensusGroup[] $censusGroups + * @param int[] $relevantYears + * @return void + */ + public static function MapToCensusTable(StatisticGroup $statisticGroup, array $censusGroups, array $relevantYears) { + $dto = new TableDTO(); + $dto->setId($statisticGroup->getId()); + $dto->setName($statisticGroup->getName()); + $dto->setType($statisticGroup->getGroupType()->getGroupType()); + $parent = $statisticGroup->getParentGroup(); + $parentId = !is_null($parent) ? $parent->getId() : null; + $dto->setParentId($parentId); + + if (sizeof($censusGroups) < 1) { + $dto->setMissing(true); + return $dto; + } + $dto->setMissing(false); + + $incomplete = false; + $totalCounts = []; + foreach ($relevantYears as $year) { + $found = false; + foreach ($censusGroups as $censusGroup) { + if ($censusGroup->getYear() == $year) { + $totalCounts[] = $censusGroup->getCalculatedTotal(); + $found = true; + } + } + if (!$found) { + $totalCounts[] = null; + $incomplete = true; + } + } + $dto->setAbsoluteMemberCounts($totalCounts); + $improvementVsLastYear = null; + $improvementVs3YearsAgo = null; + $improvementVsAvg5Years = null; + if(!is_null($totalCounts[count($totalCounts)-1])) { + if (!is_null($totalCounts[count($totalCounts)-2])) { + $improvementVsLastYear = (100 / $totalCounts[count($totalCounts)-2]) * $totalCounts[count($totalCounts)-1] - 100; + } + if (!is_null($totalCounts[count($totalCounts)-4])) { + $improvementVs3YearsAgo = (100 / $totalCounts[count($totalCounts)-4]) * $totalCounts[count($totalCounts)-1] - 100; + } + } + if (!$incomplete) { + $improvementVsAvg5Years = (100/ (($totalCounts[0] + $totalCounts[1] + $totalCounts[2] + $totalCounts[3] + $totalCounts[4]) / 5)) * $totalCounts[count($totalCounts)-1] - 100; + } + $dto->setRelativeMemberCounts([$improvementVsLastYear, $improvementVs3YearsAgo, $improvementVsAvg5Years]); + return $dto; + } } diff --git a/src/DTO/Model/Apps/Census/TableDTO.php b/src/DTO/Model/Apps/Census/TableDTO.php index ba3c192..b84f9f2 100644 --- a/src/DTO/Model/Apps/Census/TableDTO.php +++ b/src/DTO/Model/Apps/Census/TableDTO.php @@ -5,12 +5,18 @@ class TableDTO { private int $id; + private int $parentId; + private string $type; + private bool $missing; private string $name; - private string $group_type; - /** @var GroupCensusDTO[] */ - private ?array $data; - /** @var TableDTO[] */ - private ?array $children; + /** + * @var int[] Array(6) of the last 6 Years + */ + private array $absoluteMemberCounts = []; + /** + * @var int[] Array(3) with percentile changes + */ + private array $relativeMemberCounts = []; /** * @return int @@ -28,67 +34,99 @@ public function setId(int $id): void $this->id = $id; } + /** + * @return int + */ + public function getParentId(): int + { + return $this->parentId; + } + + /** + * @param int $parentId + */ + public function setParentId(int $parentId): void + { + $this->parentId = $parentId; + } + /** * @return string */ - public function getName(): string + public function getType(): string { - return $this->name; + return $this->type; } /** - * @param string $name + * @param string $type */ - public function setName(string $name): void + public function setType(string $type): void { - $this->name = $name; + $this->type = $type; + } + + /** + * @return bool + */ + public function isMissing(): bool + { + return $this->missing; + } + + /** + * @param bool $missing + */ + public function setMissing(bool $missing): void + { + $this->missing = $missing; } /** * @return string */ - public function getGroupType(): string + public function getName(): string { - return $this->group_type; + return $this->name; } /** - * @param string $group_type + * @param string $name */ - public function setGroupType(string $group_type): void + public function setName(string $name): void { - $this->group_type = $group_type; + $this->name = $name; } /** - * @return array|null + * @return array */ - public function getData(): ?array + public function getAbsoluteMemberCounts(): array { - return $this->data; + return $this->absoluteMemberCounts; } /** - * @param array|null $data + * @param array $absoluteMemberCounts */ - public function setData(?array $data): void + public function setAbsoluteMemberCounts(array $absoluteMemberCounts): void { - $this->data = $data; + $this->absoluteMemberCounts = $absoluteMemberCounts; } /** - * @return array|null + * @return array */ - public function getChildren(): ?array + public function getRelativeMemberCounts(): array { - return $this->children; + return $this->relativeMemberCounts; } /** - * @param array|null $children + * @param array $relativeMemberCounts */ - public function setChildren(?array $children): void + public function setRelativeMemberCounts(array $relativeMemberCounts): void { - $this->children = $children; + $this->relativeMemberCounts = $relativeMemberCounts; } } diff --git a/src/DTO/Model/FilterRequestData/CensusRequestData.php b/src/DTO/Model/FilterRequestData/CensusRequestData.php new file mode 100644 index 0000000..6b90de0 --- /dev/null +++ b/src/DTO/Model/FilterRequestData/CensusRequestData.php @@ -0,0 +1,102 @@ +group; + } + + /** + * @param Group $group + */ + public function setGroup(Group $group): void + { + $this->group = $group; + } + + /** + * @return array + */ + public function getRoles(): array + { + return $this->roles; + } + + /** + * @param array $roles + */ + public function setRoles(array $roles): void + { + $this->roles = $roles; + } + + /** + * @return array + */ + public function getGroups(): array + { + return $this->groups; + } + + /** + * @param array $groups + */ + public function setGroups(array $groups): void + { + $this->groups = $groups; + } + + /** + * @return bool + */ + public function isFilterMales(): bool + { + return $this->filterMales; + } + + /** + * @param bool $filterMales + */ + public function setFilterMales(bool $filterMales): void + { + $this->filterMales = $filterMales; + } + + /** + * @return bool + */ + public function isFilterFemales(): bool + { + return $this->filterFemales; + } + + /** + * @param bool $filterFemales + */ + public function setFilterFemales(bool $filterFemales): void + { + $this->filterFemales = $filterFemales; + } +} diff --git a/src/Entity/Midata/CensusGroup.php b/src/Entity/Midata/CensusGroup.php index ffe5d78..409a3b1 100644 --- a/src/Entity/Midata/CensusGroup.php +++ b/src/Entity/Midata/CensusGroup.php @@ -102,8 +102,8 @@ class CensusGroup /** * @ORM\Column(type="string", length=255) */ - private $year; + private $year; public function getId(): ?int { return $this->id; @@ -445,4 +445,23 @@ public function setYear($year): void $this->year = $year; } + public function getCalculatedTotal(): int + { + $total = 0; + $total += $this->getPiosMCount(); + $total += $this->getPiosFCount(); + $total += $this->getPtaMCount(); + $total += $this->getPtaFCount(); + $total += $this->getBiberMCount(); + $total += $this->getBiberFCount(); + $total += $this->getWoelfeMCount(); + $total += $this->getWoelfeFCount(); + $total += $this->getRoverMCount(); + $total += $this->getRoverFCount(); + $total += $this->getLeiterMCount(); + $total += $this->getLeiterFCount(); + $total += $this->getPfadisMCount(); + $total += $this->getPfadisFCount(); + return $total; + } } diff --git a/src/EventListener/WidgetControllerListener.php b/src/EventListener/WidgetControllerListener.php index 54d0eea..47dd909 100644 --- a/src/EventListener/WidgetControllerListener.php +++ b/src/EventListener/WidgetControllerListener.php @@ -2,6 +2,7 @@ namespace App\EventListener; +use App\DTO\Model\FilterRequestData\CensusRequestData; use App\DTO\Model\FilterRequestData\DateAndDateRangeRequestData; use App\DTO\Model\FilterRequestData\DateRangeRequestData; use App\DTO\Model\FilterRequestData\DateRequestData; @@ -75,7 +76,7 @@ private function bindData(callable $controller, Request $request) if (is_null($argument->getClass())) { continue; } - if (!is_a($argument->getClass()->getName(), FilterRequestData::class, true)) { + if (!(is_a($argument->getClass()->getName(), FilterRequestData::class, true) || is_a($argument->getClass()->getName(), CensusRequestData::class, true))) { continue; } $data = $this->validateRequest($request, $argument); @@ -117,6 +118,8 @@ private function validateRequest(Request $request, ReflectionParameter $paramete return $this->validateDateRangeRequest($group, $request); case WidgetRequestData::class: return $this->validateWidgetRequest($group, $request); + case CensusRequestData::class: + return $this->validateCensusRequest($group, $request); } return null; @@ -216,6 +219,36 @@ private function validateWidgetRequest(Group $group, Request $request): WidgetRe return $data; } + private function validateCensusRequest(Group $group, Request $request): CensusRequestData + { + $genders = $request->get('census-filter-genders'); + $groups = $request->get('census-filter-departments'); + $roles = $request->get('census-filter-roles'); + $rolesChoice = new Choice(WidgetDataProvider::CENSUS_ROLES); + $rolesChoice->multiple = true; + $rolesChoice->max = count(WidgetDataProvider::CENSUS_ROLES); + $rolesErrors = $this->validator->validate($roles, $rolesChoice); + + if (count($rolesErrors) > 0) { + $message = $this->translator->trans('api.error.invalidRequest'); + throw new ApiException(Response::HTTP_UNPROCESSABLE_ENTITY, $message); + } + + $data = new CensusRequestData(); + $data->setGroup($group); + $data->setGroups($groups); + $data->setRoles($roles); + if (is_array($genders)) { + if (array_search('m', $genders)) { + $data->setFilterMales(true); + } + if (array_search('f', $genders)) { + $data->setFilterFemales(true); + } + } + return $data; + } + /** * @param $from * @param $to diff --git a/src/Repository/Statistics/StatisticGroupRepository.php b/src/Repository/Statistics/StatisticGroupRepository.php index 1d058d8..911bfc3 100644 --- a/src/Repository/Statistics/StatisticGroupRepository.php +++ b/src/Repository/Statistics/StatisticGroupRepository.php @@ -2,8 +2,11 @@ namespace App\Repository\Statistics; +use App\Entity\Midata\CensusGroup; use App\Entity\Statistics\StatisticGroup; use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; +use Doctrine\DBAL\Exception; +use Doctrine\DBAL\ParameterType; use Doctrine\ORM\Id\AssignedGenerator; use Doctrine\ORM\OptimisticLockException; use Doctrine\ORM\ORMException; @@ -64,6 +67,31 @@ public function flush() $this->_em->flush(); } + /** + * Finds all the children of the group that are group type 2,3 or 8. (Kanton, Region, Abteilung) + * @param int $groupId + * @return int[] + * @throws Exception + */ + public function findAllRelevantChildGroups(int $groupId): array + { + $conn = $this->_em->getConnection(); + $query = $conn->executeQuery( + "WITH RECURSIVE parent as ( + SELECT id + FROM statistic_group + WHERE id = (?) + UNION + SELECT child.id + FROM statistic_group child, parent p + Where child.parent_group_id = p.id AND child.group_type_id IN (2,3,8) + ) SELECT * from parent;", + [$groupId], + [ParameterType::INTEGER] + ); + return $query->fetchFirstColumn(); + } + // /** // * @return StatisticGroup[] Returns an array of StatisticGroup objects // */ diff --git a/src/Service/Apps/Census/CensusFilter.php b/src/Service/Apps/Census/CensusFilter.php new file mode 100644 index 0000000..c5fbe64 --- /dev/null +++ b/src/Service/Apps/Census/CensusFilter.php @@ -0,0 +1,76 @@ +getGroups(); + $roles = $censusRequestData->getRoles(); + foreach ($censusGroups as $group) { + if (array_search($group->getId(), $groups)) { + continue; + } + $filteredGroup = clone $group; + if (array_search('leiter', $roles)) { + $filteredGroup->setLeiterMCount(0); + $filteredGroup->setLeiterFCount(0); + } + if (array_search('biber', $roles)) { + $filteredGroup->setBiberMCount(0); + $filteredGroup->setBiberFCount(0); + } + if (array_search('woelfe', $roles)) { + $filteredGroup->setWoelfeMCount(0); + $filteredGroup->setWoelfeFCount(0); + } + if (array_search('pfadis', $roles)) { + $filteredGroup->setPfadisMCount(0); + $filteredGroup->setPfadisFCount(0); + } + if (array_search('rover', $roles)) { + $filteredGroup->setRoverMCount(0); + $filteredGroup->setRoverFCount(0); + } + if (array_search('pio', $roles)) { + $filteredGroup->setPiosMCount(0); + $filteredGroup->setPiosFCount(0); + } + if (array_search('pta', $roles)) { + $filteredGroup->setPtaMCount(0); + $filteredGroup->setPtaFCount(0); + } + if ($censusRequestData->isFilterMales()) { + $filteredGroup->setLeiterMCount(0); + $filteredGroup->setBiberMCount(0); + $filteredGroup->setWoelfeMCount(0); + $filteredGroup->setPfadisMCount(0); + $filteredGroup->setRoverMCount(0); + $filteredGroup->setPiosMCount(0); + $filteredGroup->setPtaMCount(0); + } + if ($censusRequestData->isFilterFemales()) { + $filteredGroup->setLeiterFCount(0); + $filteredGroup->setBiberFCount(0); + $filteredGroup->setWoelfeFCount(0); + $filteredGroup->setPfadisFCount(0); + $filteredGroup->setRoverFCount(0); + $filteredGroup->setPiosFCount(0); + $filteredGroup->setPtaFCount(0); + } + $filteredGroups[] = $filteredGroup; + } + return $filteredGroups; + } +} diff --git a/src/Service/DataProvider/CensusDataProvider.php b/src/Service/DataProvider/CensusDataProvider.php index 68401f0..98413c6 100644 --- a/src/Service/DataProvider/CensusDataProvider.php +++ b/src/Service/DataProvider/CensusDataProvider.php @@ -2,23 +2,36 @@ namespace App\Service\DataProvider; +use App\DTO\Mapper\CensusMapper; +use App\DTO\Model\Apps\Census\TableDTO; +use App\DTO\Model\FilterRequestData\CensusRequestData; +use App\Entity\Midata\CensusGroup; use App\Entity\Midata\Group; +use App\Entity\Midata\GroupType; +use App\Entity\Statistics\StatisticGroup; use App\Repository\Midata\CensusGroupRepository; use App\Repository\Midata\GroupRepository; use App\Repository\Midata\GroupTypeRepository; +use App\Repository\Statistics\StatisticGroupRepository; +use App\Service\Apps\Census\CensusFilter; +use Doctrine\DBAL\Schema\Table; +use Sentry\Util\JSON; use Symfony\Contracts\Translation\TranslatorInterface; class CensusDataProvider extends WidgetDataProvider { private CensusGroupRepository $censusGroupRepository; + private StatisticGroupRepository $statisticGroupRepository; public function __construct( GroupRepository $groupRepository, GroupTypeRepository $groupTypeRepository, TranslatorInterface $translator, - CensusGroupRepository $censusGroupRepository + CensusGroupRepository $censusGroupRepository, + StatisticGroupRepository $statisticGroupRepository ) { $this->censusGroupRepository = $censusGroupRepository; + $this->statisticGroupRepository = $statisticGroupRepository; parent::__construct( $groupRepository, $groupTypeRepository, @@ -27,7 +40,7 @@ public function __construct( } public function getPreviewData(Group $group) { - $groups = $this->groupRepository->findAllRelevantSubGroupsByParentGroupId($group->getId(), ['Group::Abteilung', 'Group::Kantonalverband', 'Group::Region']); + $groups = $this->groupRepository->findAllRelevantSubGroupsByParentGroupId($group->getId(), ['Group::Abteilung', 'Group::Kantonalverband', 'Group::Region']); // Replace with group endpoint $return = [ 'm' => [ 'leiter' => 0, @@ -71,8 +84,120 @@ public function getPreviewData(Group $group) { return $return; } - public function getTableData(Group $group) { - $groups = $this->groupRepository->findAllRelevantSubGroupsByParentGroupId($group->getId(), ['Group::Abteilung', 'Group::Kantonalverband', 'Group::Region']); - return $groups; + + /** + * Returns a COPY of the group tree that is flattend. + * For example the structure becomes: + * + * Kanton -> Region -> Abteilung + * + * instead of: + * + * Kanton -> Region -> Region -> Abteilung + * + * @param int[] $groups + * @return StatisticGroup[] + */ + public function flattenGroupTree(array $groupIds) { + $groups = []; + foreach ($groupIds as $groupId) { + $groups[] = $this->statisticGroupRepository->findOneBy(['id' => $groupId]); + } + $flattenedGroups = []; + foreach ($groups as $group) { + $newGroup = $this->getNewGroupWithRelevantParent($group); + if (!is_null($newGroup)) { + $flattenedGroups[] = $newGroup; + } + } + return $flattenedGroups; + } + + /** + * @param StatisticGroup $baseGroup + * @return StatisticGroup|null + */ + public function getNewGroupWithRelevantParent(StatisticGroup $baseGroup) { + if ($baseGroup->getGroupType()->getGroupType() === GroupType::DEPARTMENT) { + $clonedGroup = clone $baseGroup; + $relevantParent = $this->findHighestRelevantRegion($clonedGroup); + $clonedGroup->setParentGroup($relevantParent); + return $clonedGroup; + } + if ($baseGroup->getGroupType()->getGroupType() === GroupType::REGION) { + if ($baseGroup->getParentGroup()->getGroupType()->getGroupType() === GroupType::REGION) { + return null; + } + } + if ($baseGroup->getGroupType()->getGroupType() === GroupType::CANTON) { + if ($baseGroup->getParentGroup()->getGroupType()->getGroupType() === GroupType::CANTON) { + return null; + } + } + return $baseGroup; + } + + public function findHighestRelevantRegion(StatisticGroup $group) { + $parentGroup = $group->getParentGroup(); + if (is_null($parentGroup)) { + return $group; + } + if ($parentGroup->getGroupType()->getGroupType() === GroupType::REGION) { + return $this->findHighestRelevantRegion($parentGroup); + } else { + return $group; + } + } + + /** + * @param TableDTO[] $dtos + * @return void + */ + public function sortDTOs(array $dtos) { + $regions = array_filter($dtos, function ($dto) { + return $dto->getType() === GroupType::REGION; + }); + $departments = array_filter($dtos, function ($dto) { + return $dto->getType() === GroupType::DEPARTMENT; + }); + usort($regions, function (TableDTO $a, TableDTO $b) { + return strcmp($a->getName(),$b->getName()); + }); + usort($departments, function (TableDTO $a, TableDTO $b) { + return strcmp($a->getName(),$b->getName()); + }); + $return = []; + foreach ($regions as $region) { + $return[] = $region; + foreach ($departments as $department) { + if ($department->getParentId() === $region->getId()) { + $return[] = $department; + } + } + } + + foreach ($departments as $department) { + if (!array_search($department, $return)) { + $return[] = $department; + } + } + return $return; + } + + public function getTableData(Group $group, CensusRequestData $censusRequestData) { + $groupIds = array_filter($this->statisticGroupRepository->findAllRelevantChildGroups($group->getId()), function ($id) use ($group) { // We need to filter because the function also returns the group itself + return !($id === $group->getId()); + }); + $flattenedGroups = $this->flattenGroupTree($groupIds); + + $dataTransferObjects = []; + $relevantYears = range(date('Y') - 5, date('Y')); + foreach ($flattenedGroups as $flattenedGroup) { + $dataTransferObjects[] = CensusMapper::MapToCensusTable($flattenedGroup, $this->censusGroupRepository->findBy(['group_id' => $flattenedGroup->getId()]), $relevantYears); + } + return [ + 'years' => $relevantYears, + 'data' => $this->sortDTOs($dataTransferObjects), + ]; } } diff --git a/src/Service/DataProvider/WidgetDataProvider.php b/src/Service/DataProvider/WidgetDataProvider.php index ffea5ab..be815da 100644 --- a/src/Service/DataProvider/WidgetDataProvider.php +++ b/src/Service/DataProvider/WidgetDataProvider.php @@ -51,6 +51,16 @@ class WidgetDataProvider 'Group::Pta' ]; + public const CENSUS_ROLES = [ + 'biber', + 'woelfe', + 'pfadis', + 'rover', + 'pio', + 'pta', + 'leiter' + ]; + /** @var string */ public const PEOPLE_TYPE_LEADERS = 'leaders'; /** @var string */ From 0f5bfd35e490817b5bcc42999d740c0e40b57703 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Thu, 7 Sep 2023 18:07:41 +0200 Subject: [PATCH 34/86] fix roleoverview date range filter --- src/Repository/Aggregated/AggregatedPersonRoleRepository.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Repository/Aggregated/AggregatedPersonRoleRepository.php b/src/Repository/Aggregated/AggregatedPersonRoleRepository.php index 0b0ead5..4986b58 100644 --- a/src/Repository/Aggregated/AggregatedPersonRoleRepository.php +++ b/src/Repository/Aggregated/AggregatedPersonRoleRepository.php @@ -49,7 +49,7 @@ public function findByGroupInTimeframe(Group $group, $start, $end) { return $this->createQueryBuilder('a') ->where('a.group = :group_id') - ->andWhere('a.start_at BETWEEN :start AND :end OR a.end_at BETWEEN :start AND :end') + ->andWhere('NOT ((a.end_at IS NOT NULL AND a.end_at < :start) OR a.start_at > :end)') ->orderBy('a.start_at') ->setParameter('group_id', $group->getId()) ->setParameter('start', $start) From dfedf01658a7d8224adad66610b45c80b24d1d8d Mon Sep 17 00:00:00 2001 From: Sebastian Date: Tue, 19 Sep 2023 18:02:59 +0200 Subject: [PATCH 35/86] fix: wrong parameter order --- src/Controller/Api/InviteController.php | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/Controller/Api/InviteController.php b/src/Controller/Api/InviteController.php index 549b440..06f3fe0 100644 --- a/src/Controller/Api/InviteController.php +++ b/src/Controller/Api/InviteController.php @@ -12,6 +12,7 @@ use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Serializer\Normalizer\AbstractNormalizer; use Symfony\Component\Serializer\SerializerInterface; use Symfony\Component\Validator\Validator\ValidatorInterface; @@ -63,27 +64,27 @@ public function createInvite( ]); } catch (\Exception $exception) { throw new ApiException( - $this->translator->trans('api.error.invalidRequest'), - JsonResponse::HTTP_NOT_ACCEPTABLE + Response::HTTP_NOT_ACCEPTABLE, + $this->translator->trans('api.error.invalidRequest') ); } $errors = $validator->validate($inviteDTO); if (count($errors) > 0) { throw new ApiException( - $this->translator->trans('api.error.invalidEntries'), - JsonResponse::HTTP_UNPROCESSABLE_ENTITY + Response::HTTP_UNPROCESSABLE_ENTITY, + $this->translator->trans('api.error.invalidEntries') ); } if ($inviteDTO->getPermissionType() === PermissionVoter::OWNER) { - throw new ApiException('You may not add group Owners.', JsonResponse::HTTP_FORBIDDEN); + throw new ApiException(Response::HTTP_FORBIDDEN,'You may not add group Owners.'); } if ($this->inviteService->inviteExists($group, $inviteDTO->getEmail())) { $invite = $this->translator->trans('api.entity.invite'); $message = $this->translator->trans('api.error.exists', ['entityName' => $invite]); - throw new ApiException($message, JsonResponse::HTTP_UNPROCESSABLE_ENTITY); + throw new ApiException(Response::HTTP_UNPROCESSABLE_ENTITY, $message); } $createdInviteDTO = $this->inviteService->createInvite($group, $inviteDTO); From 7932db5a499108da25dafc906be2bdf308b25078 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Tue, 19 Sep 2023 18:06:56 +0200 Subject: [PATCH 36/86] fix: move groupsetting creation to end of group creation --- src/Command/ImportFromJsonCommand.php | 28 +++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/src/Command/ImportFromJsonCommand.php b/src/Command/ImportFromJsonCommand.php index 8e79e8a..8bf6556 100644 --- a/src/Command/ImportFromJsonCommand.php +++ b/src/Command/ImportFromJsonCommand.php @@ -386,23 +386,13 @@ private function importGroups(OutputInterface $output) $i = 0; foreach ($groups as $gr) { $group = $this->em->getRepository(Group::class)->findOneBy(['id' => $gr['id']]); + $createGroupSettings = false; if (!$group) { $group = new Group(); $group->setId($gr['id']); $metadata = $this->em->getClassMetaData(get_class($group)); $metadata->setIdGenerator(new AssignedGenerator()); - - // create group settings - $groupSettings = new GroupSettings(); - $groupSettings->setGroup($group); - if ($group->getGroupType()->getGroupType() === GroupType::DEPARTMENT) { - $groupSettings->setRoleOverviewFilter(GroupSettings::DEFAULT_DEPARMENT_ROLES); - } elseif ($group->getGroupType()->getGroupType() === GroupType::REGION) { - $groupSettings->setRoleOverviewFilter(GroupSettings::DEFAULT_REGION_ROLES); - } elseif ($group->getGroupType()->getGroupType() === GroupType::CANTON) { - $groupSettings->setRoleOverviewFilter(GroupSettings::DEFAULT_CANTONAL_ROLES); - } - $this->em->persist($groupSettings); + $createGroupSettings = true; } $group->setName(trim($gr['name'])); @@ -417,6 +407,20 @@ private function importGroups(OutputInterface $output) $gt = $this->em->getRepository(GroupType::class)->findOneBy(['groupType' => $gr['type']]); $group->setGroupType($gt); + if ($createGroupSettings) { + // create group settings + $groupSettings = new GroupSettings(); + $groupSettings->setGroup($group); + if ($group->getGroupType()->getGroupType() === GroupType::DEPARTMENT) { + $groupSettings->setRoleOverviewFilter(GroupSettings::DEFAULT_DEPARMENT_ROLES); + } elseif ($group->getGroupType()->getGroupType() === GroupType::REGION) { + $groupSettings->setRoleOverviewFilter(GroupSettings::DEFAULT_REGION_ROLES); + } elseif ($group->getGroupType()->getGroupType() === GroupType::CANTON) { + $groupSettings->setRoleOverviewFilter(GroupSettings::DEFAULT_CANTONAL_ROLES); + } + $this->em->persist($groupSettings); + } + if ($gr['parent_id'] !== null) { $pg = $this->em->getRepository(Group::class)->find($gr['parent_id']); $group->setParentGroup($pg); From 6e54d134a839705d718ebe1aba518538dcc324ce Mon Sep 17 00:00:00 2001 From: Sebastian Date: Tue, 26 Sep 2023 19:07:02 +0200 Subject: [PATCH 37/86] WIP: All widgets without filter --- config/routes/apps/census.yml | 9 +- config/routes/apps/widgets.yml | 4 +- src/Controller/Api/Apps/CensusController.php | 57 +++++++++++- src/DTO/Mapper/CensusMapper.php | 47 +++++++++- src/DTO/Model/Apps/Census/CensusFilterDTO.php | 85 +++++++++++++++++ .../Apps/Census/DevelopmentWidgetDTO.php | 65 +++++++++++++ .../Model/Apps/Census/LineChartDataDTO.php | 41 +++++++++ .../Model/Apps/Census/MembersWidgetDTO.php | 28 ++++++ .../Apps/Census/StackedBarElementDTO.php | 70 ++++++++++++++ .../Model/Apps/Census/TreemapWidgetDTO.php | 77 ++++++++++++++++ .../FilterRequestData/CensusRequestData.php | 23 +++-- src/Entity/General/GroupSettings.php | 68 ++++++++++++++ .../WidgetControllerListener.php | 4 +- src/Migrations/Version20230916160452.php | 40 ++++++++ .../DataProvider/CensusDataProvider.php | 92 ++++++++++++++++++- .../DataProvider/CensusFilterDataProvider.php | 44 +++++++++ 16 files changed, 731 insertions(+), 23 deletions(-) create mode 100644 src/DTO/Model/Apps/Census/CensusFilterDTO.php create mode 100644 src/DTO/Model/Apps/Census/DevelopmentWidgetDTO.php create mode 100644 src/DTO/Model/Apps/Census/LineChartDataDTO.php create mode 100644 src/DTO/Model/Apps/Census/MembersWidgetDTO.php create mode 100644 src/DTO/Model/Apps/Census/StackedBarElementDTO.php create mode 100644 src/DTO/Model/Apps/Census/TreemapWidgetDTO.php create mode 100644 src/Migrations/Version20230916160452.php create mode 100644 src/Service/DataProvider/CensusFilterDataProvider.php diff --git a/config/routes/apps/census.yml b/config/routes/apps/census.yml index cdaf5fc..7ee376e 100644 --- a/config/routes/apps/census.yml +++ b/config/routes/apps/census.yml @@ -3,7 +3,12 @@ preview: methods: GET controller: App\Controller\Api\Apps\CensusController::getPreview -filter: +getFilter: path: /filter methods: GET - controller: App\Controller\Api\Apps\Widgets\FilterController:getGroupTypes + controller: App\Controller\Api\Apps\CensusController:getFilterData + +postFilter: + path: /filter + methods: POST + controller: App\Controller\Api\Apps\CensusController:postFilterData diff --git a/config/routes/apps/widgets.yml b/config/routes/apps/widgets.yml index c4433c7..b98fb6d 100644 --- a/config/routes/apps/widgets.yml +++ b/config/routes/apps/widgets.yml @@ -62,10 +62,10 @@ census_members: census_development: path: /census-development methods: GET - controller: App\Controller\Api\Apps\CensusController::getMembersData + controller: App\Controller\Api\Apps\CensusController::getDevelopmentData census_treemap: path: /census-treemap methods: GET - controller: App\Controller\Api\Apps\CensusController::getMembersData + controller: App\Controller\Api\Apps\CensusController::getTreemapData diff --git a/src/Controller/Api/Apps/CensusController.php b/src/Controller/Api/Apps/CensusController.php index b090267..0dc91c8 100644 --- a/src/Controller/Api/Apps/CensusController.php +++ b/src/Controller/Api/Apps/CensusController.php @@ -5,6 +5,7 @@ use App\DTO\Model\FilterRequestData\CensusRequestData; use App\Entity\Midata\Group; use App\Service\DataProvider\CensusDataProvider; +use App\Service\DataProvider\CensusFilterDataProvider; use App\Service\Security\PermissionVoter; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter; @@ -13,12 +14,15 @@ class CensusController extends AbstractController { private CensusDataProvider $censusDataProvider; + private CensusFilterDataProvider $censusFilterDataProvider; public function __construct ( - CensusDataProvider $censusDataProvider + CensusDataProvider $censusDataProvider, + CensusFilterDataProvider $censusFilterDataProvider ) { $this->censusDataProvider = $censusDataProvider; + $this->censusFilterDataProvider = $censusFilterDataProvider; } /** @@ -29,7 +33,7 @@ public function __construct ( */ public function getPreview(Group $group) { - $this->denyAccessUnlessGranted(PermissionVoter::OWNER, $group); + $this->denyAccessUnlessGranted(PermissionVoter::VIEWER, $group); return $this->json($this->censusDataProvider->getPreviewData($group)); } @@ -41,10 +45,23 @@ public function getPreview(Group $group) */ public function getTableData(Group $group, CensusRequestData $censusRequestData) { - $this->denyAccessUnlessGranted(PermissionVoter::OWNER, $group); + $this->denyAccessUnlessGranted(PermissionVoter::VIEWER, $group); return $this->json($this->censusDataProvider->getTableData($group, $censusRequestData)); } + /** + * @param Group $group + * @return JsonResponse + * + * @ParamConverter("group", options={"mapping": {"groupId": "id"}}) + */ + public function getDevelopmentData(Group $group, CensusRequestData $censusRequestData) + { + $this->denyAccessUnlessGranted(PermissionVoter::VIEWER, $group); + return $this->json($this->censusDataProvider->getDevelopmentData($group, $censusRequestData)); + } + + /** * @param Group $group * @return JsonResponse @@ -53,6 +70,38 @@ public function getTableData(Group $group, CensusRequestData $censusRequestData) */ public function getMembersData(Group $group, CensusRequestData $censusRequestData) { - return $this->json([1,2,3,4]); + $this->denyAccessUnlessGranted(PermissionVoter::VIEWER, $group); + return $this->json($this->censusDataProvider->getMembersData($group, $censusRequestData)); + } + + /** + * @param Group $group + * @return JsonResponse + * + * @ParamConverter("group", options={"mapping": {"groupId": "id"}}) + */ + public function getTreemapData(Group $group, CensusRequestData $censusRequestData) + { + $this->denyAccessUnlessGranted(PermissionVoter::VIEWER, $group); + return $this->json($this->censusDataProvider->getTreemapData($group, $censusRequestData)); + } + + /** + * @param Group $group + * @return JsonResponse + * + * @ParamConverter("group", options={"mapping": {"groupId": "id"}}) + */ + public function getFilterData(Group $group) + { + $this->denyAccessUnlessGranted(PermissionVoter::VIEWER, $group); + return $this->json($this->censusFilterDataProvider->getFilterData($group)); } + + public function postFilterData(Group $group, CensusRequestData $censusRequestData) + { + $this->denyAccessUnlessGranted(PermissionVoter::VIEWER, $group); + return $this->json($this->censusFilterDataProvider->setFilterData($group, $censusRequestData)); + } + } diff --git a/src/DTO/Mapper/CensusMapper.php b/src/DTO/Mapper/CensusMapper.php index ec0d148..23c0d6c 100644 --- a/src/DTO/Mapper/CensusMapper.php +++ b/src/DTO/Mapper/CensusMapper.php @@ -2,6 +2,8 @@ namespace App\DTO\Mapper; +use App\DTO\Model\Apps\Census\DevelopmentWidgetDTO; +use App\DTO\Model\Apps\Census\LineChartDataDTO; use App\DTO\Model\Apps\Census\TableDTO; use App\Entity\Midata\CensusGroup; use App\Entity\Statistics\StatisticGroup; @@ -13,7 +15,7 @@ class CensusMapper * @param StatisticGroup $statisticGroup * @param CensusGroup[] $censusGroups * @param int[] $relevantYears - * @return void + * @return TableDTO */ public static function MapToCensusTable(StatisticGroup $statisticGroup, array $censusGroups, array $relevantYears) { $dto = new TableDTO(); @@ -64,4 +66,47 @@ public static function MapToCensusTable(StatisticGroup $statisticGroup, array $c $dto->setRelativeMemberCounts([$improvementVsLastYear, $improvementVs3YearsAgo, $improvementVsAvg5Years]); return $dto; } + + /** + * @param StatisticGroup $statisticGroup + * @param CensusGroup[] $censusGroups + * @param int[] $relevantYears + */ + public static function MapToLineChart(StatisticGroup $statisticGroup, array $censusGroups, array $relevantYears) { + $groupData = new DevelopmentWidgetDTO(); + $absolute = []; + $relative = []; + $firstRelevantTotal = null; + foreach ($censusGroups as $censusGroup) { + if ($censusGroup->getYear() == $relevantYears[0]) { + $firstRelevantTotal = $censusGroup->getCalculatedTotal(); + } + } + foreach ($relevantYears as $year) { + $found = false; + foreach ($censusGroups as $censusGroup) { + if ($censusGroup->getYear() == $year) { + $found = true; + $absolute[] = $censusGroup->getCalculatedTotal(); + $relative[] = $firstRelevantTotal ? 100 / $firstRelevantTotal * $censusGroup->getCalculatedTotal() - 100 : null; + } + } + if (!$found) { + $absolute[] = null; + $relative[] = null; + } + } + $absoluteDTO = new LineChartDataDTO(); + $relativeDTO = new LineChartDataDTO(); + + $absoluteDTO->setLabel($statisticGroup->getName()); + $absoluteDTO->setData($absolute); + $relativeDTO->setLabel($statisticGroup->getName()); + $relativeDTO->setData($relative); + + $return = new DevelopmentWidgetDTO(); + $return->setAbsolute([$absoluteDTO]); + $return->setRelative([$relativeDTO]); + return $return; + } } diff --git a/src/DTO/Model/Apps/Census/CensusFilterDTO.php b/src/DTO/Model/Apps/Census/CensusFilterDTO.php new file mode 100644 index 0000000..585b5c0 --- /dev/null +++ b/src/DTO/Model/Apps/Census/CensusFilterDTO.php @@ -0,0 +1,85 @@ +roles; + } + + /** + * @param array|null $roles + */ + public function setRoles(?array $roles): void + { + $this->roles = $roles; + } + + /** + * @return array|null + */ + public function getGroups(): ?array + { + return $this->groups; + } + + /** + * @param array|null $groups + */ + public function setGroups(?array $groups): void + { + $this->groups = $groups; + } + + + /** + * @return bool + */ + public function isFilterMales(): bool + { + return $this->filterMales; + } + + /** + * @param bool $filterMales + */ + public function setFilterMales(bool $filterMales): void + { + $this->filterMales = $filterMales; + } + + /** + * @return bool + */ + public function isFilterFemales(): bool + { + return $this->filterFemales; + } + + /** + * @param bool $filterFemales + */ + public function setFilterFemales(bool $filterFemales): void + { + $this->filterFemales = $filterFemales; + } +} diff --git a/src/DTO/Model/Apps/Census/DevelopmentWidgetDTO.php b/src/DTO/Model/Apps/Census/DevelopmentWidgetDTO.php new file mode 100644 index 0000000..fc13df7 --- /dev/null +++ b/src/DTO/Model/Apps/Census/DevelopmentWidgetDTO.php @@ -0,0 +1,65 @@ +years; + } + + /** + * @param array $years + */ + public function setYears(array $years): void + { + $this->years = $years; + } + + /** + * @return array + */ + public function getAbsolute(): array + { + return $this->absolute; + } + + /** + * @param array $absolute + */ + public function setAbsolute(array $absolute): void + { + $this->absolute = $absolute; + } + + /** + * @return array + */ + public function getRelative(): array + { + return $this->relative; + } + + /** + * @param array $relative + */ + public function setRelative(array $relative): void + { + $this->relative = $relative; + } +} diff --git a/src/DTO/Model/Apps/Census/LineChartDataDTO.php b/src/DTO/Model/Apps/Census/LineChartDataDTO.php new file mode 100644 index 0000000..4310752 --- /dev/null +++ b/src/DTO/Model/Apps/Census/LineChartDataDTO.php @@ -0,0 +1,41 @@ +label; + } + + /** + * @param string $label + */ + public function setLabel(string $label): void + { + $this->label = $label; + } + + /** + * @return array + */ + public function getData(): array + { + return $this->data; + } + + /** + * @param array $data + */ + public function setData(array $data): void + { + $this->data = $data; + } +} diff --git a/src/DTO/Model/Apps/Census/MembersWidgetDTO.php b/src/DTO/Model/Apps/Census/MembersWidgetDTO.php new file mode 100644 index 0000000..7b207e9 --- /dev/null +++ b/src/DTO/Model/Apps/Census/MembersWidgetDTO.php @@ -0,0 +1,28 @@ +data; + } + + /** + * @param array $data + */ + public function setData(array $data): void + { + $this->data = $data; + } + +} diff --git a/src/DTO/Model/Apps/Census/StackedBarElementDTO.php b/src/DTO/Model/Apps/Census/StackedBarElementDTO.php new file mode 100644 index 0000000..a6a2fb6 --- /dev/null +++ b/src/DTO/Model/Apps/Census/StackedBarElementDTO.php @@ -0,0 +1,70 @@ +y = $y; + $this->x = $x; + $this->color = $color; + } + + /** + * @return int + */ + public function getY(): int + { + return $this->y; + } + + /** + * @param int $y + */ + public function setY(int $y): void + { + $this->y = $y; + } + + /** + * @return string + */ + public function getX(): string + { + return $this->x; + } + + /** + * @param string $x + */ + public function setX(string $x): void + { + $this->x = $x; + } + + /** + * @return string + */ + public function getColor(): string + { + return $this->color; + } + + /** + * @param string $color + */ + public function setColor(string $color): void + { + $this->color = $color; + } +} diff --git a/src/DTO/Model/Apps/Census/TreemapWidgetDTO.php b/src/DTO/Model/Apps/Census/TreemapWidgetDTO.php new file mode 100644 index 0000000..d68cd19 --- /dev/null +++ b/src/DTO/Model/Apps/Census/TreemapWidgetDTO.php @@ -0,0 +1,77 @@ +name; + } + + /** + * @param string $name + */ + public function setName(string $name): void + { + $this->name = $name; + } + + /** + * @return string + */ + public function getRegion(): string + { + return $this->region; + } + + /** + * @param string $region + */ + public function setRegion(string $region): void + { + $this->region = $region; + } + + /** + * @return string + */ + public function getColor(): string + { + return $this->color; + } + + /** + * @param string $color + */ + public function setColor(string $color): void + { + $this->color = $color; + } + + /** + * @return int + */ + public function getValue(): int + { + return $this->value; + } + + /** + * @param int $value + */ + public function setValue(int $value): void + { + $this->value = $value; + } +} diff --git a/src/DTO/Model/FilterRequestData/CensusRequestData.php b/src/DTO/Model/FilterRequestData/CensusRequestData.php index 6b90de0..5f599aa 100644 --- a/src/DTO/Model/FilterRequestData/CensusRequestData.php +++ b/src/DTO/Model/FilterRequestData/CensusRequestData.php @@ -2,6 +2,8 @@ namespace App\DTO\Model\FilterRequestData; +use App\Entity\Midata\Group; + class CensusRequestData { private Group $group; @@ -9,12 +11,12 @@ class CensusRequestData /** * @var string[] */ - private array $roles; + private ?array $roles; /** * @var int[] */ - private array $groups; + private ?array $groups; private bool $filterMales = false; @@ -37,37 +39,38 @@ public function setGroup(Group $group): void } /** - * @return array + * @return array|null */ - public function getRoles(): array + public function getRoles(): ?array { return $this->roles; } /** - * @param array $roles + * @param array|null $roles */ - public function setRoles(array $roles): void + public function setRoles(?array $roles): void { $this->roles = $roles; } /** - * @return array + * @return array|null */ - public function getGroups(): array + public function getGroups(): ?array { return $this->groups; } /** - * @param array $groups + * @param array|null $groups */ - public function setGroups(array $groups): void + public function setGroups(?array $groups): void { $this->groups = $groups; } + /** * @return bool */ diff --git a/src/Entity/General/GroupSettings.php b/src/Entity/General/GroupSettings.php index 2f9604d..6a22845 100644 --- a/src/Entity/General/GroupSettings.php +++ b/src/Entity/General/GroupSettings.php @@ -36,6 +36,26 @@ class GroupSettings */ private $roleOverviewFilter = []; + /** + * @ORM\Column(type="array", nullable=true) + */ + private $census_roles = []; + + /** + * @ORM\Column(type="array", nullable=true) + */ + private $census_groups = []; + + /** + * @ORM\Column(type="boolean", nullable=true) + */ + private $census_filter_males; + + /** + * @ORM\Column(type="boolean", nullable=true) + */ + private $census_filter_females; + public function getId(): ?int { return $this->id; @@ -64,4 +84,52 @@ public function setRoleOverviewFilter(?array $roleOverviewFilter): self return $this; } + + public function getCensusRoles(): ?array + { + return $this->census_roles; + } + + public function setCensusRoles(?array $census_roles): self + { + $this->census_roles = $census_roles; + + return $this; + } + + public function getCensusGroups(): ?array + { + return $this->census_groups; + } + + public function setCensusGroups(?array $census_groups): self + { + $this->census_groups = $census_groups; + + return $this; + } + + public function getCensusFilterMales(): ?bool + { + return $this->census_filter_males; + } + + public function setCensusFilterMales(?bool $census_filter_males): self + { + $this->census_filter_males = $census_filter_males; + + return $this; + } + + public function getCensusFilterFemales(): ?bool + { + return $this->census_filter_females; + } + + public function setCensusFilterFemales(?bool $census_filter_females): self + { + $this->census_filter_females = $census_filter_females; + + return $this; + } } diff --git a/src/EventListener/WidgetControllerListener.php b/src/EventListener/WidgetControllerListener.php index 47dd909..711a432 100644 --- a/src/EventListener/WidgetControllerListener.php +++ b/src/EventListener/WidgetControllerListener.php @@ -90,9 +90,9 @@ private function bindData(callable $controller, Request $request) /** * @param Request $request * @param ReflectionParameter $parameter - * @return FilterRequestData|null + * @return FilterRequestData|null|CensusRequestData */ - private function validateRequest(Request $request, ReflectionParameter $parameter): ?FilterRequestData + private function validateRequest(Request $request, ReflectionParameter $parameter) { $groupId = $request->get('groupId'); $group = $this->groupRepository->findOneByIdAndType($groupId, [ diff --git a/src/Migrations/Version20230916160452.php b/src/Migrations/Version20230916160452.php new file mode 100644 index 0000000..8378e7d --- /dev/null +++ b/src/Migrations/Version20230916160452.php @@ -0,0 +1,40 @@ +addSql('ALTER TABLE group_settings ADD census_roles TEXT DEFAULT NULL'); + $this->addSql('ALTER TABLE group_settings ADD census_groups TEXT DEFAULT NULL'); + $this->addSql('ALTER TABLE group_settings ADD census_filter_males BOOLEAN DEFAULT NULL'); + $this->addSql('ALTER TABLE group_settings ADD census_filter_females BOOLEAN DEFAULT NULL'); + $this->addSql('COMMENT ON COLUMN group_settings.census_roles IS \'(DC2Type:array)\''); + $this->addSql('COMMENT ON COLUMN group_settings.census_groups IS \'(DC2Type:array)\''); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('CREATE SCHEMA public'); + $this->addSql('ALTER TABLE group_settings DROP census_roles'); + $this->addSql('ALTER TABLE group_settings DROP census_groups'); + $this->addSql('ALTER TABLE group_settings DROP census_filter_males'); + $this->addSql('ALTER TABLE group_settings DROP census_filter_females'); + } +} diff --git a/src/Service/DataProvider/CensusDataProvider.php b/src/Service/DataProvider/CensusDataProvider.php index 98413c6..fae39af 100644 --- a/src/Service/DataProvider/CensusDataProvider.php +++ b/src/Service/DataProvider/CensusDataProvider.php @@ -3,7 +3,11 @@ namespace App\Service\DataProvider; use App\DTO\Mapper\CensusMapper; +use App\DTO\Model\Apps\Census\DevelopmentWidgetDTO; +use App\DTO\Model\Apps\Census\MembersWidgetDTO; +use App\DTO\Model\Apps\Census\StackedBarElementDTO; use App\DTO\Model\Apps\Census\TableDTO; +use App\DTO\Model\Apps\Census\TreemapWidgetDTO; use App\DTO\Model\FilterRequestData\CensusRequestData; use App\Entity\Midata\CensusGroup; use App\Entity\Midata\Group; @@ -184,11 +188,15 @@ public function sortDTOs(array $dtos) { return $return; } - public function getTableData(Group $group, CensusRequestData $censusRequestData) { + public function getRelevantGroups(Group $group) { $groupIds = array_filter($this->statisticGroupRepository->findAllRelevantChildGroups($group->getId()), function ($id) use ($group) { // We need to filter because the function also returns the group itself return !($id === $group->getId()); }); - $flattenedGroups = $this->flattenGroupTree($groupIds); + return $this->flattenGroupTree($groupIds); + } + + public function getTableData(Group $group, CensusRequestData $censusRequestData) { + $flattenedGroups = $this->getRelevantGroups($group); $dataTransferObjects = []; $relevantYears = range(date('Y') - 5, date('Y')); @@ -200,4 +208,84 @@ public function getTableData(Group $group, CensusRequestData $censusRequestData) 'data' => $this->sortDTOs($dataTransferObjects), ]; } + + public function getDevelopmentData(Group $group, CensusRequestData $censusRequestData) { + $relevantGroups = $this->getRelevantGroups($group); + + $absolute = []; + $relative = []; + $relevantYears = range(date('Y') - 5, date('Y')); + foreach ($relevantGroups as $relevantGroup) { + $data = $this->censusGroupRepository->findBy(['group_id' => $relevantGroup->getId()]); + if (!sizeof($data) == 0) { + $dto = CensusMapper::MapToLineChart($relevantGroup, $data, $relevantYears); + $absolute[] = $dto->getAbsolute()[0]; + $relative[] = $dto->getRelative()[0]; + } + } + + $return = new DevelopmentWidgetDTO(); + $return->setYears($relevantYears); + $return->setAbsolute($absolute); + $return->setRelative($relative); + return $return; + } + + public function getMembersData(Group $group, CensusRequestData $censusRequestData): array + { + $relevantGroups = $this->getRelevantGroups($group); + + $rawResults = []; + foreach ($relevantGroups as $relevantGroup) { + $data = $this->censusGroupRepository->findBy(['group_id' => $relevantGroup->getId(), 'year' => date('Y')]); + if (!sizeof($data) == 0) { + $biber = $data[0]->getBiberMCount() + $data[0]->getBiberFCount(); + $woelfe = $data[0]->getWoelfeMCount() + $data[0]->getWoelfeFCount(); + $pfadi = $data[0]->getPfadisMCount() + $data[0]->getPfadisFCount(); + $pio = $data[0]->getPiosMCount() + $data[0]->getPiosFCount(); + $rover = $data[0]->getRoverMCount() + $data[0]->getRoverFCount(); + $pta = $data[0]->getPtaMCount() + $data[0]->getPtaFCount(); + $leaders = $data[0]->getLeiterMCount() + $data[0]->getLeiterFCount(); + $rawResults[0][] = new StackedBarElementDTO($biber, $data[0]->getName(), '#EEE09F'); + $rawResults[1][] = new StackedBarElementDTO($woelfe, $data[0]->getName(), '#3BB5DC'); + $rawResults[2][] = new StackedBarElementDTO($pfadi, $data[0]->getName(), '#9A7A54'); + $rawResults[4][] = new StackedBarElementDTO($rover, $data[0]->getName(), '#1DA650'); + $rawResults[3][] = new StackedBarElementDTO($pio, $data[0]->getName(), '#DD1F19'); + $rawResults[5][] = new StackedBarElementDTO($pta, $data[0]->getName(), '#d9b826'); + $rawResults[6][] = new StackedBarElementDTO($leaders, $data[0]->getName(), '#929292'); + } + } + $return = []; + foreach ($rawResults as $rawResult) { + $dto = new MembersWidgetDTO(); + $dto->setData($rawResult); + $return[] = $dto; + } + return $return; + } + + public function getTreemapData(Group $group, CensusRequestData $censusRequestData) { + $relevantGroups = $this->getRelevantGroups($group); + $return = []; + $colors = ['#EEE09F', '#3BB5DC', '#9A7A54', '#1DA650', '#DD1F19', '#d9b826', '#929292']; + $colorIndex = 0; + $usedColors = []; + foreach ($relevantGroups as $relevantGroup) { + $data = $this->censusGroupRepository->findBy(['group_id' => $relevantGroup->getId(), 'year' => date('Y')]); + if (!sizeof($data) == 0) { + $dto = new TreemapWidgetDTO(); + $dto->setName($relevantGroup->getName()); + $parentName = $relevantGroup->getParentGroup()->getName(); + $dto->setRegion($parentName); + $dto->setValue($data[0]->getCalculatedTotal()); + if (is_null($usedColors[$parentName])) { + $usedColors[$parentName] = $colors[$colorIndex]; + $colorIndex = ($colorIndex + 1) % (sizeof($colors) - 1); + } + $dto->setColor($usedColors[$parentName]); + $return[] = $dto; + } + } + return $return; + } } diff --git a/src/Service/DataProvider/CensusFilterDataProvider.php b/src/Service/DataProvider/CensusFilterDataProvider.php new file mode 100644 index 0000000..219be16 --- /dev/null +++ b/src/Service/DataProvider/CensusFilterDataProvider.php @@ -0,0 +1,44 @@ +groupSettingsRepository = $groupSettingsRepository; + } + + public function getFilterData(Group $group): CensusFilterDTO + { + $groupSettings = $this->groupSettingsRepository->find($group->getId()); + return $this->mapGroupSettingsToCensusFilter($groupSettings); + } + + private function mapGroupSettingsToCensusFilter(GroupSettings $groupSettings):CensusFilterDTO + { + $filterData = new CensusFilterDTO(); + $filterData->setFilterFemales($groupSettings->getCensusFilterFemales() ?? false); + $filterData->setFilterMales($groupSettings->getCensusFilterMales() ?? false); + $filterData->setRoles($groupSettings->getCensusRoles() ?? ['rover']); + $filterData->setGroups($groupSettings->getCensusGroups() ?? []); + return $filterData; + } + + public function setFilterData(Group $group, CensusRequestData $censusRequestData): CensusFilterDTO + { + $groupSettings = $this->groupSettingsRepository->find($group->getId()); + $groupSettings->setCensusGroups($censusRequestData->getGroups()); + $groupSettings->setCensusRoles($censusRequestData->getRoles()); + $groupSettings->setCensusFilterFemales($censusRequestData->isFilterFemales()); + $groupSettings->setCensusFilterMales($censusRequestData->isFilterMales()); + return $this->mapGroupSettingsToCensusFilter($groupSettings); + } +} From 42f6c004ef01c4140028fbb19a952c95f892c87a Mon Sep 17 00:00:00 2001 From: Sebastian Date: Tue, 26 Sep 2023 19:14:59 +0200 Subject: [PATCH 38/86] lint --- src/Command/FetchCensusCommand.php | 6 ++-- src/Controller/Api/Apps/CensusController.php | 6 ++-- src/Controller/Api/InviteController.php | 2 +- src/DTO/Mapper/CensusMapper.php | 18 +++++----- src/DTO/Model/Apps/Census/GroupCensusDTO.php | 1 - .../Model/Apps/Census/MembersWidgetDTO.php | 1 - .../DataProvider/CensusDataProvider.php | 35 ++++++++++++------- .../DataProvider/CensusFilterDataProvider.php | 2 +- 8 files changed, 38 insertions(+), 33 deletions(-) diff --git a/src/Command/FetchCensusCommand.php b/src/Command/FetchCensusCommand.php index f3aa7ca..6382700 100644 --- a/src/Command/FetchCensusCommand.php +++ b/src/Command/FetchCensusCommand.php @@ -25,8 +25,7 @@ public function __construct( CensusAPIService $apiService, CensusGroupRepository $censusGroupRepository, GroupTypeRepository $groupTypeRepository - ) - { + ) { $this->apiService = $apiService; $this->censusGroupRepository = $censusGroupRepository; $this->groupTypeRepository = $groupTypeRepository; @@ -54,7 +53,7 @@ public function execute(InputInterface $input, OutputInterface $output) $rawCensusGroups = $rawCensusData->getContent()['census_evaluations']['groups']; foreach ($rawCensusGroups as $rawCensusGroup) { $exists = $this->censusGroupRepository->findOneBy(['group_id' => $rawCensusGroup['group_id'], 'year' => $year]); - if(is_null($exists)) { + if (is_null($exists)) { $groupsToAggregate[] = $rawCensusGroup['group_id']; $this->mapRawCensusGroupToCensusGroup($rawCensusGroup, $year); } @@ -63,7 +62,6 @@ public function execute(InputInterface $input, OutputInterface $output) } // Aggregate Groups foreach (array_unique($groupsToAggregate) as $groupId) { - } return Command::SUCCESS; } diff --git a/src/Controller/Api/Apps/CensusController.php b/src/Controller/Api/Apps/CensusController.php index 0dc91c8..5d9af26 100644 --- a/src/Controller/Api/Apps/CensusController.php +++ b/src/Controller/Api/Apps/CensusController.php @@ -16,11 +16,10 @@ class CensusController extends AbstractController private CensusDataProvider $censusDataProvider; private CensusFilterDataProvider $censusFilterDataProvider; - public function __construct ( + public function __construct( CensusDataProvider $censusDataProvider, CensusFilterDataProvider $censusFilterDataProvider - ) - { + ) { $this->censusDataProvider = $censusDataProvider; $this->censusFilterDataProvider = $censusFilterDataProvider; } @@ -103,5 +102,4 @@ public function postFilterData(Group $group, CensusRequestData $censusRequestDat $this->denyAccessUnlessGranted(PermissionVoter::VIEWER, $group); return $this->json($this->censusFilterDataProvider->setFilterData($group, $censusRequestData)); } - } diff --git a/src/Controller/Api/InviteController.php b/src/Controller/Api/InviteController.php index 06f3fe0..75b3286 100644 --- a/src/Controller/Api/InviteController.php +++ b/src/Controller/Api/InviteController.php @@ -78,7 +78,7 @@ public function createInvite( } if ($inviteDTO->getPermissionType() === PermissionVoter::OWNER) { - throw new ApiException(Response::HTTP_FORBIDDEN,'You may not add group Owners.'); + throw new ApiException(Response::HTTP_FORBIDDEN, 'You may not add group Owners.'); } if ($this->inviteService->inviteExists($group, $inviteDTO->getEmail())) { diff --git a/src/DTO/Mapper/CensusMapper.php b/src/DTO/Mapper/CensusMapper.php index 23c0d6c..4a9a55a 100644 --- a/src/DTO/Mapper/CensusMapper.php +++ b/src/DTO/Mapper/CensusMapper.php @@ -17,7 +17,8 @@ class CensusMapper * @param int[] $relevantYears * @return TableDTO */ - public static function MapToCensusTable(StatisticGroup $statisticGroup, array $censusGroups, array $relevantYears) { + public static function MapToCensusTable(StatisticGroup $statisticGroup, array $censusGroups, array $relevantYears) + { $dto = new TableDTO(); $dto->setId($statisticGroup->getId()); $dto->setName($statisticGroup->getName()); @@ -52,16 +53,16 @@ public static function MapToCensusTable(StatisticGroup $statisticGroup, array $c $improvementVsLastYear = null; $improvementVs3YearsAgo = null; $improvementVsAvg5Years = null; - if(!is_null($totalCounts[count($totalCounts)-1])) { - if (!is_null($totalCounts[count($totalCounts)-2])) { - $improvementVsLastYear = (100 / $totalCounts[count($totalCounts)-2]) * $totalCounts[count($totalCounts)-1] - 100; + if (!is_null($totalCounts[count($totalCounts) - 1])) { + if (!is_null($totalCounts[count($totalCounts) - 2])) { + $improvementVsLastYear = (100 / $totalCounts[count($totalCounts) - 2]) * $totalCounts[count($totalCounts) - 1] - 100; } - if (!is_null($totalCounts[count($totalCounts)-4])) { - $improvementVs3YearsAgo = (100 / $totalCounts[count($totalCounts)-4]) * $totalCounts[count($totalCounts)-1] - 100; + if (!is_null($totalCounts[count($totalCounts) - 4])) { + $improvementVs3YearsAgo = (100 / $totalCounts[count($totalCounts) - 4]) * $totalCounts[count($totalCounts) - 1] - 100; } } if (!$incomplete) { - $improvementVsAvg5Years = (100/ (($totalCounts[0] + $totalCounts[1] + $totalCounts[2] + $totalCounts[3] + $totalCounts[4]) / 5)) * $totalCounts[count($totalCounts)-1] - 100; + $improvementVsAvg5Years = (100 / (($totalCounts[0] + $totalCounts[1] + $totalCounts[2] + $totalCounts[3] + $totalCounts[4]) / 5)) * $totalCounts[count($totalCounts) - 1] - 100; } $dto->setRelativeMemberCounts([$improvementVsLastYear, $improvementVs3YearsAgo, $improvementVsAvg5Years]); return $dto; @@ -72,7 +73,8 @@ public static function MapToCensusTable(StatisticGroup $statisticGroup, array $c * @param CensusGroup[] $censusGroups * @param int[] $relevantYears */ - public static function MapToLineChart(StatisticGroup $statisticGroup, array $censusGroups, array $relevantYears) { + public static function MapToLineChart(StatisticGroup $statisticGroup, array $censusGroups, array $relevantYears) + { $groupData = new DevelopmentWidgetDTO(); $absolute = []; $relative = []; diff --git a/src/DTO/Model/Apps/Census/GroupCensusDTO.php b/src/DTO/Model/Apps/Census/GroupCensusDTO.php index 2933225..8cb0dbc 100644 --- a/src/DTO/Model/Apps/Census/GroupCensusDTO.php +++ b/src/DTO/Model/Apps/Census/GroupCensusDTO.php @@ -293,5 +293,4 @@ public function setPtaFCount(int $pta_f_count): void { $this->pta_f_count = $pta_f_count; } - } diff --git a/src/DTO/Model/Apps/Census/MembersWidgetDTO.php b/src/DTO/Model/Apps/Census/MembersWidgetDTO.php index 7b207e9..25e601e 100644 --- a/src/DTO/Model/Apps/Census/MembersWidgetDTO.php +++ b/src/DTO/Model/Apps/Census/MembersWidgetDTO.php @@ -24,5 +24,4 @@ public function setData(array $data): void { $this->data = $data; } - } diff --git a/src/Service/DataProvider/CensusDataProvider.php b/src/Service/DataProvider/CensusDataProvider.php index fae39af..123510d 100644 --- a/src/Service/DataProvider/CensusDataProvider.php +++ b/src/Service/DataProvider/CensusDataProvider.php @@ -24,7 +24,6 @@ class CensusDataProvider extends WidgetDataProvider { - private CensusGroupRepository $censusGroupRepository; private StatisticGroupRepository $statisticGroupRepository; public function __construct( @@ -43,7 +42,8 @@ public function __construct( ); } - public function getPreviewData(Group $group) { + public function getPreviewData(Group $group) + { $groups = $this->groupRepository->findAllRelevantSubGroupsByParentGroupId($group->getId(), ['Group::Abteilung', 'Group::Kantonalverband', 'Group::Region']); // Replace with group endpoint $return = [ 'm' => [ @@ -102,7 +102,8 @@ public function getPreviewData(Group $group) { * @param int[] $groups * @return StatisticGroup[] */ - public function flattenGroupTree(array $groupIds) { + public function flattenGroupTree(array $groupIds) + { $groups = []; foreach ($groupIds as $groupId) { $groups[] = $this->statisticGroupRepository->findOneBy(['id' => $groupId]); @@ -121,7 +122,8 @@ public function flattenGroupTree(array $groupIds) { * @param StatisticGroup $baseGroup * @return StatisticGroup|null */ - public function getNewGroupWithRelevantParent(StatisticGroup $baseGroup) { + public function getNewGroupWithRelevantParent(StatisticGroup $baseGroup) + { if ($baseGroup->getGroupType()->getGroupType() === GroupType::DEPARTMENT) { $clonedGroup = clone $baseGroup; $relevantParent = $this->findHighestRelevantRegion($clonedGroup); @@ -141,7 +143,8 @@ public function getNewGroupWithRelevantParent(StatisticGroup $baseGroup) { return $baseGroup; } - public function findHighestRelevantRegion(StatisticGroup $group) { + public function findHighestRelevantRegion(StatisticGroup $group) + { $parentGroup = $group->getParentGroup(); if (is_null($parentGroup)) { return $group; @@ -157,7 +160,8 @@ public function findHighestRelevantRegion(StatisticGroup $group) { * @param TableDTO[] $dtos * @return void */ - public function sortDTOs(array $dtos) { + public function sortDTOs(array $dtos) + { $regions = array_filter($dtos, function ($dto) { return $dto->getType() === GroupType::REGION; }); @@ -165,10 +169,10 @@ public function sortDTOs(array $dtos) { return $dto->getType() === GroupType::DEPARTMENT; }); usort($regions, function (TableDTO $a, TableDTO $b) { - return strcmp($a->getName(),$b->getName()); + return strcmp($a->getName(), $b->getName()); }); usort($departments, function (TableDTO $a, TableDTO $b) { - return strcmp($a->getName(),$b->getName()); + return strcmp($a->getName(), $b->getName()); }); $return = []; foreach ($regions as $region) { @@ -188,14 +192,17 @@ public function sortDTOs(array $dtos) { return $return; } - public function getRelevantGroups(Group $group) { - $groupIds = array_filter($this->statisticGroupRepository->findAllRelevantChildGroups($group->getId()), function ($id) use ($group) { // We need to filter because the function also returns the group itself + public function getRelevantGroups(Group $group) + { + $groupIds = array_filter($this->statisticGroupRepository->findAllRelevantChildGroups($group->getId()), function ($id) use ($group) { + // We need to filter because the function also returns the group itself return !($id === $group->getId()); }); return $this->flattenGroupTree($groupIds); } - public function getTableData(Group $group, CensusRequestData $censusRequestData) { + public function getTableData(Group $group, CensusRequestData $censusRequestData) + { $flattenedGroups = $this->getRelevantGroups($group); $dataTransferObjects = []; @@ -209,7 +216,8 @@ public function getTableData(Group $group, CensusRequestData $censusRequestData) ]; } - public function getDevelopmentData(Group $group, CensusRequestData $censusRequestData) { + public function getDevelopmentData(Group $group, CensusRequestData $censusRequestData) + { $relevantGroups = $this->getRelevantGroups($group); $absolute = []; @@ -264,7 +272,8 @@ public function getMembersData(Group $group, CensusRequestData $censusRequestDat return $return; } - public function getTreemapData(Group $group, CensusRequestData $censusRequestData) { + public function getTreemapData(Group $group, CensusRequestData $censusRequestData) + { $relevantGroups = $this->getRelevantGroups($group); $return = []; $colors = ['#EEE09F', '#3BB5DC', '#9A7A54', '#1DA650', '#DD1F19', '#d9b826', '#929292']; diff --git a/src/Service/DataProvider/CensusFilterDataProvider.php b/src/Service/DataProvider/CensusFilterDataProvider.php index 219be16..747586c 100644 --- a/src/Service/DataProvider/CensusFilterDataProvider.php +++ b/src/Service/DataProvider/CensusFilterDataProvider.php @@ -22,7 +22,7 @@ public function getFilterData(Group $group): CensusFilterDTO return $this->mapGroupSettingsToCensusFilter($groupSettings); } - private function mapGroupSettingsToCensusFilter(GroupSettings $groupSettings):CensusFilterDTO + private function mapGroupSettingsToCensusFilter(GroupSettings $groupSettings): CensusFilterDTO { $filterData = new CensusFilterDTO(); $filterData->setFilterFemales($groupSettings->getCensusFilterFemales() ?? false); From 0d6733efd9799ed07145dea17f7f77e20f6ba0b3 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Tue, 26 Sep 2023 19:20:09 +0200 Subject: [PATCH 39/86] lint --- src/DTO/Mapper/CensusMapper.php | 4 ++-- src/Service/Apps/Census/CensusFilter.php | 2 +- src/Service/DataProvider/CensusDataProvider.php | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/DTO/Mapper/CensusMapper.php b/src/DTO/Mapper/CensusMapper.php index 4a9a55a..732e946 100644 --- a/src/DTO/Mapper/CensusMapper.php +++ b/src/DTO/Mapper/CensusMapper.php @@ -17,7 +17,7 @@ class CensusMapper * @param int[] $relevantYears * @return TableDTO */ - public static function MapToCensusTable(StatisticGroup $statisticGroup, array $censusGroups, array $relevantYears) + public static function mapToCensusTable(StatisticGroup $statisticGroup, array $censusGroups, array $relevantYears) { $dto = new TableDTO(); $dto->setId($statisticGroup->getId()); @@ -73,7 +73,7 @@ public static function MapToCensusTable(StatisticGroup $statisticGroup, array $c * @param CensusGroup[] $censusGroups * @param int[] $relevantYears */ - public static function MapToLineChart(StatisticGroup $statisticGroup, array $censusGroups, array $relevantYears) + public static function mapToLineChart(StatisticGroup $statisticGroup, array $censusGroups, array $relevantYears) { $groupData = new DevelopmentWidgetDTO(); $absolute = []; diff --git a/src/Service/Apps/Census/CensusFilter.php b/src/Service/Apps/Census/CensusFilter.php index c5fbe64..9e83ab1 100644 --- a/src/Service/Apps/Census/CensusFilter.php +++ b/src/Service/Apps/Census/CensusFilter.php @@ -13,7 +13,7 @@ class CensusFilter * @param CensusGroup[] $censusGroups * @return CensusGroup[] */ - public static function FilterCensusGroups(CensusRequestData $censusRequestData, array $censusGroups): array + public static function filterCensusGroups(CensusRequestData $censusRequestData, array $censusGroups): array { $filteredGroups = []; $groups = $censusRequestData->getGroups(); diff --git a/src/Service/DataProvider/CensusDataProvider.php b/src/Service/DataProvider/CensusDataProvider.php index 123510d..67a06d7 100644 --- a/src/Service/DataProvider/CensusDataProvider.php +++ b/src/Service/DataProvider/CensusDataProvider.php @@ -208,7 +208,7 @@ public function getTableData(Group $group, CensusRequestData $censusRequestData) $dataTransferObjects = []; $relevantYears = range(date('Y') - 5, date('Y')); foreach ($flattenedGroups as $flattenedGroup) { - $dataTransferObjects[] = CensusMapper::MapToCensusTable($flattenedGroup, $this->censusGroupRepository->findBy(['group_id' => $flattenedGroup->getId()]), $relevantYears); + $dataTransferObjects[] = CensusMapper::mapToCensusTable($flattenedGroup, $this->censusGroupRepository->findBy(['group_id' => $flattenedGroup->getId()]), $relevantYears); } return [ 'years' => $relevantYears, @@ -226,7 +226,7 @@ public function getDevelopmentData(Group $group, CensusRequestData $censusReques foreach ($relevantGroups as $relevantGroup) { $data = $this->censusGroupRepository->findBy(['group_id' => $relevantGroup->getId()]); if (!sizeof($data) == 0) { - $dto = CensusMapper::MapToLineChart($relevantGroup, $data, $relevantYears); + $dto = CensusMapper::mapToLineChart($relevantGroup, $data, $relevantYears); $absolute[] = $dto->getAbsolute()[0]; $relative[] = $dto->getRelative()[0]; } From e5f1f193c1947f4e83518b79973af4b6a1836db7 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Wed, 27 Sep 2023 23:04:11 +0200 Subject: [PATCH 40/86] Add filter --- src/Controller/Api/Apps/CensusController.php | 7 ++ src/DTO/Mapper/CensusMapper.php | 66 +++++++++++++++++-- .../WidgetControllerListener.php | 14 ++-- .../General/GroupSettingsRepository.php | 4 ++ .../DataProvider/CensusDataProvider.php | 24 ++++++- .../DataProvider/CensusFilterDataProvider.php | 9 +-- 6 files changed, 103 insertions(+), 21 deletions(-) diff --git a/src/Controller/Api/Apps/CensusController.php b/src/Controller/Api/Apps/CensusController.php index 5d9af26..98a3efa 100644 --- a/src/Controller/Api/Apps/CensusController.php +++ b/src/Controller/Api/Apps/CensusController.php @@ -97,6 +97,13 @@ public function getFilterData(Group $group) return $this->json($this->censusFilterDataProvider->getFilterData($group)); } + /** + * @param Group $group + * @param CensusRequestData $censusRequestData + * @return JsonResponse + * + * @ParamConverter("group", options={"mapping": {"groupId": "id"}}) + */ public function postFilterData(Group $group, CensusRequestData $censusRequestData) { $this->denyAccessUnlessGranted(PermissionVoter::VIEWER, $group); diff --git a/src/DTO/Mapper/CensusMapper.php b/src/DTO/Mapper/CensusMapper.php index 732e946..3cf9ec2 100644 --- a/src/DTO/Mapper/CensusMapper.php +++ b/src/DTO/Mapper/CensusMapper.php @@ -5,6 +5,7 @@ use App\DTO\Model\Apps\Census\DevelopmentWidgetDTO; use App\DTO\Model\Apps\Census\LineChartDataDTO; use App\DTO\Model\Apps\Census\TableDTO; +use App\DTO\Model\FilterRequestData\CensusRequestData; use App\Entity\Midata\CensusGroup; use App\Entity\Statistics\StatisticGroup; use Symfony\Component\Validator\Constraints\Date; @@ -17,7 +18,7 @@ class CensusMapper * @param int[] $relevantYears * @return TableDTO */ - public static function mapToCensusTable(StatisticGroup $statisticGroup, array $censusGroups, array $relevantYears) + public static function mapToCensusTable(StatisticGroup $statisticGroup, array $censusGroups, array $relevantYears, CensusRequestData $censusRequestData) { $dto = new TableDTO(); $dto->setId($statisticGroup->getId()); @@ -33,6 +34,10 @@ public static function mapToCensusTable(StatisticGroup $statisticGroup, array $c } $dto->setMissing(false); + foreach ($censusGroups as $censusGroup) { + self::filterCensusGroup($censusGroup, $censusRequestData); + } + $incomplete = false; $totalCounts = []; foreach ($relevantYears as $year) { @@ -53,11 +58,11 @@ public static function mapToCensusTable(StatisticGroup $statisticGroup, array $c $improvementVsLastYear = null; $improvementVs3YearsAgo = null; $improvementVsAvg5Years = null; - if (!is_null($totalCounts[count($totalCounts) - 1])) { - if (!is_null($totalCounts[count($totalCounts) - 2])) { + if (!is_null($totalCounts[count($totalCounts) - 1]) && $totalCounts[count($totalCounts) - 1] !== 0) { + if (!is_null($totalCounts[count($totalCounts) - 2]) && $totalCounts[count($totalCounts) - 2] !== 0) { $improvementVsLastYear = (100 / $totalCounts[count($totalCounts) - 2]) * $totalCounts[count($totalCounts) - 1] - 100; } - if (!is_null($totalCounts[count($totalCounts) - 4])) { + if (!is_null($totalCounts[count($totalCounts) - 4]) && $totalCounts[count($totalCounts) - 4] !== 0) { $improvementVs3YearsAgo = (100 / $totalCounts[count($totalCounts) - 4]) * $totalCounts[count($totalCounts) - 1] - 100; } } @@ -73,13 +78,13 @@ public static function mapToCensusTable(StatisticGroup $statisticGroup, array $c * @param CensusGroup[] $censusGroups * @param int[] $relevantYears */ - public static function mapToLineChart(StatisticGroup $statisticGroup, array $censusGroups, array $relevantYears) + public static function mapToLineChart(StatisticGroup $statisticGroup, array $censusGroups, array $relevantYears, CensusRequestData $censusRequestData) { - $groupData = new DevelopmentWidgetDTO(); $absolute = []; $relative = []; $firstRelevantTotal = null; foreach ($censusGroups as $censusGroup) { + self::filterCensusGroup($censusGroup, $censusRequestData); if ($censusGroup->getYear() == $relevantYears[0]) { $firstRelevantTotal = $censusGroup->getCalculatedTotal(); } @@ -111,4 +116,53 @@ public static function mapToLineChart(StatisticGroup $statisticGroup, array $cen $return->setRelative([$relativeDTO]); return $return; } + + public static function filterCensusGroup(CensusGroup $group, CensusRequestData $censusRequestData) { + if (self::isFiltered('biber', $censusRequestData->getRoles()) || !$censusRequestData->isFilterMales()) { + $group->setPtaMCount(0); + } + if (self::isFiltered('biber', $censusRequestData->getRoles()) || !$censusRequestData->isFilterFemales()) { + $group->setBiberFCount(0); + } + if (self::isFiltered('woelfe', $censusRequestData->getRoles()) || !$censusRequestData->isFilterMales()) { + $group->setWoelfeMCount(0); + } + if (self::isFiltered('woelfe', $censusRequestData->getRoles()) || !$censusRequestData->isFilterFemales()) { + $group->setWoelfeFCount(0); + } + if (self::isFiltered('pfadis', $censusRequestData->getRoles()) || !$censusRequestData->isFilterMales()) { + $group->setPfadisMCount(0); + } + if (self::isFiltered('pfadis', $censusRequestData->getRoles()) || !$censusRequestData->isFilterFemales()) { + $group->setPfadisFCount(0); + } + if (self::isFiltered('rover', $censusRequestData->getRoles()) || !$censusRequestData->isFilterMales()) { + $group->setRoverMCount(0); + } + if (self::isFiltered('rover', $censusRequestData->getRoles()) || !$censusRequestData->isFilterFemales()) { + $group->setRoverFCount(0); + } + if (self::isFiltered('pio', $censusRequestData->getRoles()) || !$censusRequestData->isFilterMales()) { + $group->setPiosMCount(0); + } + if (self::isFiltered('pio', $censusRequestData->getRoles()) || !$censusRequestData->isFilterFemales()) { + $group->setPiosFCount(0); + } + if (self::isFiltered('pta', $censusRequestData->getRoles()) || !$censusRequestData->isFilterMales()) { + $group->setPtaMCount(0); + } + if (self::isFiltered('pta', $censusRequestData->getRoles()) || !$censusRequestData->isFilterFemales()) { + $group->setPtaFCount(0); + } + if (self::isFiltered('leiter', $censusRequestData->getRoles()) || !$censusRequestData->isFilterMales()) { + $group->setLeiterMCount(0); + } + if (self::isFiltered('leiter', $censusRequestData->getRoles()) || !$censusRequestData->isFilterFemales()) { + $group->setLeiterFCount(0); + } + } + + public static function isFiltered($needle, $haystack) { + return stripos(json_encode($haystack ?? []), $needle) !== false; + } } diff --git a/src/EventListener/WidgetControllerListener.php b/src/EventListener/WidgetControllerListener.php index 711a432..3447641 100644 --- a/src/EventListener/WidgetControllerListener.php +++ b/src/EventListener/WidgetControllerListener.php @@ -221,7 +221,8 @@ private function validateWidgetRequest(Group $group, Request $request): WidgetRe private function validateCensusRequest(Group $group, Request $request): CensusRequestData { - $genders = $request->get('census-filter-genders'); + $m = $request->get('census-filter-males'); + $f = $request->get('census-filter-females'); $groups = $request->get('census-filter-departments'); $roles = $request->get('census-filter-roles'); $rolesChoice = new Choice(WidgetDataProvider::CENSUS_ROLES); @@ -238,14 +239,9 @@ private function validateCensusRequest(Group $group, Request $request): CensusRe $data->setGroup($group); $data->setGroups($groups); $data->setRoles($roles); - if (is_array($genders)) { - if (array_search('m', $genders)) { - $data->setFilterMales(true); - } - if (array_search('f', $genders)) { - $data->setFilterFemales(true); - } - } + $data->setFilterMales($m === 'true'); + $data->setFilterFemales($f === 'true'); + return $data; } diff --git a/src/Repository/General/GroupSettingsRepository.php b/src/Repository/General/GroupSettingsRepository.php index 1747396..614425a 100644 --- a/src/Repository/General/GroupSettingsRepository.php +++ b/src/Repository/General/GroupSettingsRepository.php @@ -47,6 +47,10 @@ public function remove(GroupSettings $entity, bool $flush = true): void } } + public function flush() { + $this->_em->flush(); + } + // /** // * @return GroupSettings[] Returns an array of GroupSettings objects // */ diff --git a/src/Service/DataProvider/CensusDataProvider.php b/src/Service/DataProvider/CensusDataProvider.php index 67a06d7..246c08c 100644 --- a/src/Service/DataProvider/CensusDataProvider.php +++ b/src/Service/DataProvider/CensusDataProvider.php @@ -208,7 +208,7 @@ public function getTableData(Group $group, CensusRequestData $censusRequestData) $dataTransferObjects = []; $relevantYears = range(date('Y') - 5, date('Y')); foreach ($flattenedGroups as $flattenedGroup) { - $dataTransferObjects[] = CensusMapper::mapToCensusTable($flattenedGroup, $this->censusGroupRepository->findBy(['group_id' => $flattenedGroup->getId()]), $relevantYears); + $dataTransferObjects[] = CensusMapper::mapToCensusTable($flattenedGroup, $this->censusGroupRepository->findBy(['group_id' => $flattenedGroup->getId()]), $relevantYears, $censusRequestData); } return [ 'years' => $relevantYears, @@ -219,6 +219,7 @@ public function getTableData(Group $group, CensusRequestData $censusRequestData) public function getDevelopmentData(Group $group, CensusRequestData $censusRequestData) { $relevantGroups = $this->getRelevantGroups($group); + $relevantGroups = $this->filterGroups($relevantGroups, $censusRequestData); $absolute = []; $relative = []; @@ -226,7 +227,7 @@ public function getDevelopmentData(Group $group, CensusRequestData $censusReques foreach ($relevantGroups as $relevantGroup) { $data = $this->censusGroupRepository->findBy(['group_id' => $relevantGroup->getId()]); if (!sizeof($data) == 0) { - $dto = CensusMapper::mapToLineChart($relevantGroup, $data, $relevantYears); + $dto = CensusMapper::mapToLineChart($relevantGroup, $data, $relevantYears, $censusRequestData); $absolute[] = $dto->getAbsolute()[0]; $relative[] = $dto->getRelative()[0]; } @@ -242,11 +243,13 @@ public function getDevelopmentData(Group $group, CensusRequestData $censusReques public function getMembersData(Group $group, CensusRequestData $censusRequestData): array { $relevantGroups = $this->getRelevantGroups($group); + $relevantGroups = $this->filterGroups($relevantGroups, $censusRequestData); $rawResults = []; foreach ($relevantGroups as $relevantGroup) { $data = $this->censusGroupRepository->findBy(['group_id' => $relevantGroup->getId(), 'year' => date('Y')]); if (!sizeof($data) == 0) { + CensusMapper::filterCensusGroup($data[0],$censusRequestData); $biber = $data[0]->getBiberMCount() + $data[0]->getBiberFCount(); $woelfe = $data[0]->getWoelfeMCount() + $data[0]->getWoelfeFCount(); $pfadi = $data[0]->getPfadisMCount() + $data[0]->getPfadisFCount(); @@ -275,6 +278,8 @@ public function getMembersData(Group $group, CensusRequestData $censusRequestDat public function getTreemapData(Group $group, CensusRequestData $censusRequestData) { $relevantGroups = $this->getRelevantGroups($group); + $relevantGroups = $this->filterGroups($relevantGroups, $censusRequestData); + $return = []; $colors = ['#EEE09F', '#3BB5DC', '#9A7A54', '#1DA650', '#DD1F19', '#d9b826', '#929292']; $colorIndex = 0; @@ -282,6 +287,7 @@ public function getTreemapData(Group $group, CensusRequestData $censusRequestDat foreach ($relevantGroups as $relevantGroup) { $data = $this->censusGroupRepository->findBy(['group_id' => $relevantGroup->getId(), 'year' => date('Y')]); if (!sizeof($data) == 0) { + CensusMapper::filterCensusGroup($data[0], $censusRequestData); $dto = new TreemapWidgetDTO(); $dto->setName($relevantGroup->getName()); $parentName = $relevantGroup->getParentGroup()->getName(); @@ -297,4 +303,18 @@ public function getTreemapData(Group $group, CensusRequestData $censusRequestDat } return $return; } + + /** + * Filter out groups based on the Frontend Table filter + * @param array $statisticGroups + * @param CensusRequestData $censusRequestData + * @return array + */ + private function filterGroups(array $statisticGroups, CensusRequestData $censusRequestData) { + return array_filter($statisticGroups, function ($group) use ($censusRequestData) { + return sizeof(array_filter($censusRequestData->getGroups(), function ($groupId) use ($group) { + return $groupId == $group->getId(); + })) === 0; + }); + } } diff --git a/src/Service/DataProvider/CensusFilterDataProvider.php b/src/Service/DataProvider/CensusFilterDataProvider.php index 747586c..a495552 100644 --- a/src/Service/DataProvider/CensusFilterDataProvider.php +++ b/src/Service/DataProvider/CensusFilterDataProvider.php @@ -16,7 +16,7 @@ public function __construct(GroupSettingsRepository $groupSettingsRepository) $this->groupSettingsRepository = $groupSettingsRepository; } - public function getFilterData(Group $group): CensusFilterDTO + public function getFilterData(Group $group) { $groupSettings = $this->groupSettingsRepository->find($group->getId()); return $this->mapGroupSettingsToCensusFilter($groupSettings); @@ -25,9 +25,9 @@ public function getFilterData(Group $group): CensusFilterDTO private function mapGroupSettingsToCensusFilter(GroupSettings $groupSettings): CensusFilterDTO { $filterData = new CensusFilterDTO(); - $filterData->setFilterFemales($groupSettings->getCensusFilterFemales() ?? false); - $filterData->setFilterMales($groupSettings->getCensusFilterMales() ?? false); - $filterData->setRoles($groupSettings->getCensusRoles() ?? ['rover']); + $filterData->setFilterFemales(is_null($groupSettings->getCensusFilterFemales()) ? true : $groupSettings->getCensusFilterFemales()); + $filterData->setFilterMales(is_null($groupSettings->getCensusFilterMales()) ? true : $groupSettings->getCensusFilterMales()); + $filterData->setRoles($groupSettings->getCensusRoles() ?? []); $filterData->setGroups($groupSettings->getCensusGroups() ?? []); return $filterData; } @@ -39,6 +39,7 @@ public function setFilterData(Group $group, CensusRequestData $censusRequestData $groupSettings->setCensusRoles($censusRequestData->getRoles()); $groupSettings->setCensusFilterFemales($censusRequestData->isFilterFemales()); $groupSettings->setCensusFilterMales($censusRequestData->isFilterMales()); + $this->groupSettingsRepository->flush(); return $this->mapGroupSettingsToCensusFilter($groupSettings); } } From d5af65a303200980ae91949924927965db5b1743 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Wed, 27 Sep 2023 23:25:39 +0200 Subject: [PATCH 41/86] lint --- src/DTO/Mapper/CensusMapper.php | 6 ++++-- src/Repository/General/GroupSettingsRepository.php | 3 ++- src/Service/DataProvider/CensusDataProvider.php | 7 ++++--- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/DTO/Mapper/CensusMapper.php b/src/DTO/Mapper/CensusMapper.php index 3cf9ec2..47ac7bf 100644 --- a/src/DTO/Mapper/CensusMapper.php +++ b/src/DTO/Mapper/CensusMapper.php @@ -117,7 +117,8 @@ public static function mapToLineChart(StatisticGroup $statisticGroup, array $cen return $return; } - public static function filterCensusGroup(CensusGroup $group, CensusRequestData $censusRequestData) { + public static function filterCensusGroup(CensusGroup $group, CensusRequestData $censusRequestData) + { if (self::isFiltered('biber', $censusRequestData->getRoles()) || !$censusRequestData->isFilterMales()) { $group->setPtaMCount(0); } @@ -162,7 +163,8 @@ public static function filterCensusGroup(CensusGroup $group, CensusRequestData $ } } - public static function isFiltered($needle, $haystack) { + public static function isFiltered($needle, $haystack) + { return stripos(json_encode($haystack ?? []), $needle) !== false; } } diff --git a/src/Repository/General/GroupSettingsRepository.php b/src/Repository/General/GroupSettingsRepository.php index 614425a..342f106 100644 --- a/src/Repository/General/GroupSettingsRepository.php +++ b/src/Repository/General/GroupSettingsRepository.php @@ -47,7 +47,8 @@ public function remove(GroupSettings $entity, bool $flush = true): void } } - public function flush() { + public function flush() + { $this->_em->flush(); } diff --git a/src/Service/DataProvider/CensusDataProvider.php b/src/Service/DataProvider/CensusDataProvider.php index 246c08c..027a239 100644 --- a/src/Service/DataProvider/CensusDataProvider.php +++ b/src/Service/DataProvider/CensusDataProvider.php @@ -249,7 +249,7 @@ public function getMembersData(Group $group, CensusRequestData $censusRequestDat foreach ($relevantGroups as $relevantGroup) { $data = $this->censusGroupRepository->findBy(['group_id' => $relevantGroup->getId(), 'year' => date('Y')]); if (!sizeof($data) == 0) { - CensusMapper::filterCensusGroup($data[0],$censusRequestData); + CensusMapper::filterCensusGroup($data[0], $censusRequestData); $biber = $data[0]->getBiberMCount() + $data[0]->getBiberFCount(); $woelfe = $data[0]->getWoelfeMCount() + $data[0]->getWoelfeFCount(); $pfadi = $data[0]->getPfadisMCount() + $data[0]->getPfadisFCount(); @@ -310,11 +310,12 @@ public function getTreemapData(Group $group, CensusRequestData $censusRequestDat * @param CensusRequestData $censusRequestData * @return array */ - private function filterGroups(array $statisticGroups, CensusRequestData $censusRequestData) { + private function filterGroups(array $statisticGroups, CensusRequestData $censusRequestData) + { return array_filter($statisticGroups, function ($group) use ($censusRequestData) { return sizeof(array_filter($censusRequestData->getGroups(), function ($groupId) use ($group) { return $groupId == $group->getId(); - })) === 0; + })) === 0; }); } } From d3c3d5b40dc03fdfccc0d0350da87742ff9b2413 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Fri, 29 Sep 2023 19:37:43 +0200 Subject: [PATCH 42/86] fix query, fix colors --- src/DTO/Mapper/CensusMapper.php | 7 +++++++ src/DTO/Model/Apps/Census/LineChartDataDTO.php | 18 ++++++++++++++++++ .../Statistics/StatisticGroupRepository.php | 13 +++++++------ .../DataProvider/CensusDataProvider.php | 9 +-------- 4 files changed, 33 insertions(+), 14 deletions(-) diff --git a/src/DTO/Mapper/CensusMapper.php b/src/DTO/Mapper/CensusMapper.php index 47ac7bf..f0f170e 100644 --- a/src/DTO/Mapper/CensusMapper.php +++ b/src/DTO/Mapper/CensusMapper.php @@ -106,8 +106,10 @@ public static function mapToLineChart(StatisticGroup $statisticGroup, array $cen $absoluteDTO = new LineChartDataDTO(); $relativeDTO = new LineChartDataDTO(); + $absoluteDTO->setColor(self::getColorForId($statisticGroup->getId())); $absoluteDTO->setLabel($statisticGroup->getName()); $absoluteDTO->setData($absolute); + $relativeDTO->setColor(self::getColorForId($statisticGroup->getId())); $relativeDTO->setLabel($statisticGroup->getName()); $relativeDTO->setData($relative); @@ -167,4 +169,9 @@ public static function isFiltered($needle, $haystack) { return stripos(json_encode($haystack ?? []), $needle) !== false; } + + public static function getColorForId($id): string + { + return '#'.substr(md5($id), 0, 6); + } } diff --git a/src/DTO/Model/Apps/Census/LineChartDataDTO.php b/src/DTO/Model/Apps/Census/LineChartDataDTO.php index 4310752..05dc0f6 100644 --- a/src/DTO/Model/Apps/Census/LineChartDataDTO.php +++ b/src/DTO/Model/Apps/Census/LineChartDataDTO.php @@ -5,8 +5,26 @@ class LineChartDataDTO { private string $label; + + private string $color; private array $data; + /** + * @return string + */ + public function getColor(): string + { + return $this->color; + } + + /** + * @param string $color + */ + public function setColor(string $color): void + { + $this->color = $color; + } + /** * @return string */ diff --git a/src/Repository/Statistics/StatisticGroupRepository.php b/src/Repository/Statistics/StatisticGroupRepository.php index 911bfc3..3e99309 100644 --- a/src/Repository/Statistics/StatisticGroupRepository.php +++ b/src/Repository/Statistics/StatisticGroupRepository.php @@ -78,14 +78,15 @@ public function findAllRelevantChildGroups(int $groupId): array $conn = $this->_em->getConnection(); $query = $conn->executeQuery( "WITH RECURSIVE parent as ( - SELECT id + SELECT statistic_group.*, midata_group_type.group_type FROM statistic_group - WHERE id = (?) + JOIN midata_group_type ON group_type_id = midata_group_type.id + WHERE statistic_group.id = (?) UNION - SELECT child.id - FROM statistic_group child, parent p - Where child.parent_group_id = p.id AND child.group_type_id IN (2,3,8) - ) SELECT * from parent;", + SELECT child.*, midata_group_type.group_type + FROM statistic_group child, parent p, midata_group_type + Where child.parent_group_id = p.id AND midata_group_type.group_type IN ('Group::Abteilung', 'Group::Region', 'Group::Kantonalverband') AND child.group_type_id = midata_group_type.id + ) SELECT * from parent;", [$groupId], [ParameterType::INTEGER] ); diff --git a/src/Service/DataProvider/CensusDataProvider.php b/src/Service/DataProvider/CensusDataProvider.php index 027a239..10035fc 100644 --- a/src/Service/DataProvider/CensusDataProvider.php +++ b/src/Service/DataProvider/CensusDataProvider.php @@ -281,9 +281,6 @@ public function getTreemapData(Group $group, CensusRequestData $censusRequestDat $relevantGroups = $this->filterGroups($relevantGroups, $censusRequestData); $return = []; - $colors = ['#EEE09F', '#3BB5DC', '#9A7A54', '#1DA650', '#DD1F19', '#d9b826', '#929292']; - $colorIndex = 0; - $usedColors = []; foreach ($relevantGroups as $relevantGroup) { $data = $this->censusGroupRepository->findBy(['group_id' => $relevantGroup->getId(), 'year' => date('Y')]); if (!sizeof($data) == 0) { @@ -293,11 +290,7 @@ public function getTreemapData(Group $group, CensusRequestData $censusRequestDat $parentName = $relevantGroup->getParentGroup()->getName(); $dto->setRegion($parentName); $dto->setValue($data[0]->getCalculatedTotal()); - if (is_null($usedColors[$parentName])) { - $usedColors[$parentName] = $colors[$colorIndex]; - $colorIndex = ($colorIndex + 1) % (sizeof($colors) - 1); - } - $dto->setColor($usedColors[$parentName]); + $dto->setColor(CensusMapper::getColorForId($relevantGroup->getId())); $return[] = $dto; } } From 909379ba869a81997de05cfbc139ea128e159a8b Mon Sep 17 00:00:00 2001 From: Sebastian Date: Fri, 29 Sep 2023 20:12:19 +0200 Subject: [PATCH 43/86] lint --- src/DTO/Mapper/CensusMapper.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/DTO/Mapper/CensusMapper.php b/src/DTO/Mapper/CensusMapper.php index f0f170e..f7668e8 100644 --- a/src/DTO/Mapper/CensusMapper.php +++ b/src/DTO/Mapper/CensusMapper.php @@ -172,6 +172,6 @@ public static function isFiltered($needle, $haystack) public static function getColorForId($id): string { - return '#'.substr(md5($id), 0, 6); + return '#' . substr(md5($id), 0, 6); } } From c0dd1a909606c471270ad52e14399d3dd2ebf2f3 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Fri, 6 Oct 2023 19:29:21 +0200 Subject: [PATCH 44/86] various fixes --- src/DTO/Mapper/CensusMapper.php | 22 +++++++++++- .../DataProvider/CensusDataProvider.php | 35 +++++++++++-------- .../DataProvider/WidgetDataProvider.php | 4 +-- 3 files changed, 44 insertions(+), 17 deletions(-) diff --git a/src/DTO/Mapper/CensusMapper.php b/src/DTO/Mapper/CensusMapper.php index f7668e8..1e7b812 100644 --- a/src/DTO/Mapper/CensusMapper.php +++ b/src/DTO/Mapper/CensusMapper.php @@ -122,7 +122,7 @@ public static function mapToLineChart(StatisticGroup $statisticGroup, array $cen public static function filterCensusGroup(CensusGroup $group, CensusRequestData $censusRequestData) { if (self::isFiltered('biber', $censusRequestData->getRoles()) || !$censusRequestData->isFilterMales()) { - $group->setPtaMCount(0); + $group->setBiberMCount(0); } if (self::isFiltered('biber', $censusRequestData->getRoles()) || !$censusRequestData->isFilterFemales()) { $group->setBiberFCount(0); @@ -174,4 +174,24 @@ public static function getColorForId($id): string { return '#' . substr(md5($id), 0, 6); } + + /** + * Retuns a hex color string where each color (R,G,B) is withing 100-230, so that text is always readable on this color. + * @param int $id + * @return string + */ + public static function getLightColorForId(int $id): string + { + $color = self::getColorForId($id); + $r = hexdec(substr($color, 1, 2)); + $g = hexdec(substr($color, 3, 2)); + $b = hexdec(substr($color, 5, 2)); + if ($r < 100) $r += 100; + if ($r > 230) $r -= 25; + if ($g < 100) $g += 100; + if ($g > 230) $g -= 25; + if ($b < 100) $b += 100; + if ($g > 230) $g -= 25; + return "#". dechex($r) . dechex($g) . dechex($b); + } } diff --git a/src/Service/DataProvider/CensusDataProvider.php b/src/Service/DataProvider/CensusDataProvider.php index 10035fc..83f2d8e 100644 --- a/src/Service/DataProvider/CensusDataProvider.php +++ b/src/Service/DataProvider/CensusDataProvider.php @@ -152,33 +152,38 @@ public function findHighestRelevantRegion(StatisticGroup $group) if ($parentGroup->getGroupType()->getGroupType() === GroupType::REGION) { return $this->findHighestRelevantRegion($parentGroup); } else { + if ($group->getGroupType()->getGroupType() === GroupType::DEPARTMENT && $parentGroup->getGroupType()->getGroupType() === GroupType::CANTON) { + return $this->findHighestRelevantRegion($parentGroup); + } return $group; } } + /** - * @param TableDTO[] $dtos - * @return void + * @param StatisticGroup[] $groups + * @return StatisticGroup[] */ - public function sortDTOs(array $dtos) + public function sortGroups(array $groups) { - $regions = array_filter($dtos, function ($dto) { - return $dto->getType() === GroupType::REGION; + $regions = array_filter($groups, function ($group) { + return $group->getGroupType()->getGroupType() === GroupType::REGION; }); - $departments = array_filter($dtos, function ($dto) { - return $dto->getType() === GroupType::DEPARTMENT; + $departments = array_filter($groups, function ($group) { + return $group->getGroupType()->getGroupType() === GroupType::DEPARTMENT; }); - usort($regions, function (TableDTO $a, TableDTO $b) { + usort($regions, function (StatisticGroup $a, StatisticGroup $b) { return strcmp($a->getName(), $b->getName()); }); - usort($departments, function (TableDTO $a, TableDTO $b) { + usort($departments, function (StatisticGroup $a, StatisticGroup $b) { return strcmp($a->getName(), $b->getName()); }); + $return = []; foreach ($regions as $region) { $return[] = $region; foreach ($departments as $department) { - if ($department->getParentId() === $region->getId()) { + if ($department->getParentGroup()->getId() === $region->getId()) { $return[] = $department; } } @@ -204,7 +209,7 @@ public function getRelevantGroups(Group $group) public function getTableData(Group $group, CensusRequestData $censusRequestData) { $flattenedGroups = $this->getRelevantGroups($group); - + $flattenedGroups = $this->sortGroups($flattenedGroups); $dataTransferObjects = []; $relevantYears = range(date('Y') - 5, date('Y')); foreach ($flattenedGroups as $flattenedGroup) { @@ -212,7 +217,7 @@ public function getTableData(Group $group, CensusRequestData $censusRequestData) } return [ 'years' => $relevantYears, - 'data' => $this->sortDTOs($dataTransferObjects), + 'data' => $dataTransferObjects, ]; } @@ -220,6 +225,7 @@ public function getDevelopmentData(Group $group, CensusRequestData $censusReques { $relevantGroups = $this->getRelevantGroups($group); $relevantGroups = $this->filterGroups($relevantGroups, $censusRequestData); + $relevantGroups = $this->sortGroups($relevantGroups); $absolute = []; $relative = []; @@ -244,6 +250,7 @@ public function getMembersData(Group $group, CensusRequestData $censusRequestDat { $relevantGroups = $this->getRelevantGroups($group); $relevantGroups = $this->filterGroups($relevantGroups, $censusRequestData); + $relevantGroups = $this->sortGroups($relevantGroups); $rawResults = []; foreach ($relevantGroups as $relevantGroup) { @@ -263,7 +270,7 @@ public function getMembersData(Group $group, CensusRequestData $censusRequestDat $rawResults[4][] = new StackedBarElementDTO($rover, $data[0]->getName(), '#1DA650'); $rawResults[3][] = new StackedBarElementDTO($pio, $data[0]->getName(), '#DD1F19'); $rawResults[5][] = new StackedBarElementDTO($pta, $data[0]->getName(), '#d9b826'); - $rawResults[6][] = new StackedBarElementDTO($leaders, $data[0]->getName(), '#929292'); + $rawResults[6][] = new StackedBarElementDTO($leaders, $data[0]->getName(), '#005716'); } } $return = []; @@ -290,7 +297,7 @@ public function getTreemapData(Group $group, CensusRequestData $censusRequestDat $parentName = $relevantGroup->getParentGroup()->getName(); $dto->setRegion($parentName); $dto->setValue($data[0]->getCalculatedTotal()); - $dto->setColor(CensusMapper::getColorForId($relevantGroup->getId())); + $dto->setColor(CensusMapper::getLightColorForId($relevantGroup->getParentGroup()->getId())); $return[] = $dto; } } diff --git a/src/Service/DataProvider/WidgetDataProvider.php b/src/Service/DataProvider/WidgetDataProvider.php index be815da..5c56f6e 100644 --- a/src/Service/DataProvider/WidgetDataProvider.php +++ b/src/Service/DataProvider/WidgetDataProvider.php @@ -37,8 +37,8 @@ class WidgetDataProvider 'Group::Pio' => '#DD1F19', 'Group::AbteilungsRover' => '#1DA650', 'Group::Pta' => '#d9b826', - 'Group::Abteilung' => '#929292', - 'leaders' => '#929292' + 'Group::Abteilung' => '#005716', + 'leaders' => '#005716' ]; /** @var string[] */ From 7c507b88847bd9b8612ccce0bad65fd716ba1c64 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Fri, 6 Oct 2023 19:30:36 +0200 Subject: [PATCH 45/86] lint --- src/DTO/Mapper/CensusMapper.php | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/src/DTO/Mapper/CensusMapper.php b/src/DTO/Mapper/CensusMapper.php index 1e7b812..8cdb308 100644 --- a/src/DTO/Mapper/CensusMapper.php +++ b/src/DTO/Mapper/CensusMapper.php @@ -186,12 +186,24 @@ public static function getLightColorForId(int $id): string $r = hexdec(substr($color, 1, 2)); $g = hexdec(substr($color, 3, 2)); $b = hexdec(substr($color, 5, 2)); - if ($r < 100) $r += 100; - if ($r > 230) $r -= 25; - if ($g < 100) $g += 100; - if ($g > 230) $g -= 25; - if ($b < 100) $b += 100; - if ($g > 230) $g -= 25; - return "#". dechex($r) . dechex($g) . dechex($b); + if ($r < 100) { + $r += 100; + } + if ($r > 230) { + $r -= 25; + } + if ($g < 100) { + $g += 100; + } + if ($g > 230) { + $g -= 25; + } + if ($b < 100) { + $b += 100; + } + if ($g > 230) { + $g -= 25; + } + return "#" . dechex($r) . dechex($g) . dechex($b); } } From f4db67fd5bc26fb5124bafc31a6a53a11e9ab435 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Fri, 27 Oct 2023 14:36:12 +0200 Subject: [PATCH 46/86] fix: reduce census API computation duration by ~93% --- src/Service/DataProvider/CensusDataProvider.php | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/Service/DataProvider/CensusDataProvider.php b/src/Service/DataProvider/CensusDataProvider.php index 83f2d8e..443bef3 100644 --- a/src/Service/DataProvider/CensusDataProvider.php +++ b/src/Service/DataProvider/CensusDataProvider.php @@ -312,10 +312,12 @@ public function getTreemapData(Group $group, CensusRequestData $censusRequestDat */ private function filterGroups(array $statisticGroups, CensusRequestData $censusRequestData) { - return array_filter($statisticGroups, function ($group) use ($censusRequestData) { - return sizeof(array_filter($censusRequestData->getGroups(), function ($groupId) use ($group) { - return $groupId == $group->getId(); - })) === 0; + // For faster lookups we swap array index with value so that array goes from [1 => 23, 2 => 352] to [23 => null, 352 => null] + $groupIdsToFilterOut = array_flip($censusRequestData->getGroups()); + $filteredGroups = array_filter($statisticGroups, function (StatisticGroup $group) use ($groupIdsToFilterOut) { + return !isset($groupIdsToFilterOut[$group->getId()]); }); + // Ensure that they are sequential. + return array_values($filteredGroups); } } From b5ceb1b8eb294741c064d9fc7bc716513a933a93 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Fri, 27 Oct 2023 16:18:52 +0200 Subject: [PATCH 47/86] fix: prevent 0 division and null errors --- src/Controller/Api/Apps/CensusController.php | 12 ++++++++---- src/DTO/Mapper/CensusMapper.php | 5 +++-- src/Service/DataProvider/CensusDataProvider.php | 4 +++- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/Controller/Api/Apps/CensusController.php b/src/Controller/Api/Apps/CensusController.php index 98a3efa..3ebd63a 100644 --- a/src/Controller/Api/Apps/CensusController.php +++ b/src/Controller/Api/Apps/CensusController.php @@ -45,7 +45,8 @@ public function getPreview(Group $group) public function getTableData(Group $group, CensusRequestData $censusRequestData) { $this->denyAccessUnlessGranted(PermissionVoter::VIEWER, $group); - return $this->json($this->censusDataProvider->getTableData($group, $censusRequestData)); + $data = $this->censusDataProvider->getTableData($group, $censusRequestData); + return $this->json($data); } /** @@ -57,7 +58,8 @@ public function getTableData(Group $group, CensusRequestData $censusRequestData) public function getDevelopmentData(Group $group, CensusRequestData $censusRequestData) { $this->denyAccessUnlessGranted(PermissionVoter::VIEWER, $group); - return $this->json($this->censusDataProvider->getDevelopmentData($group, $censusRequestData)); + $data = $this->censusDataProvider->getDevelopmentData($group, $censusRequestData); + return $this->json($data); } @@ -70,7 +72,8 @@ public function getDevelopmentData(Group $group, CensusRequestData $censusReques public function getMembersData(Group $group, CensusRequestData $censusRequestData) { $this->denyAccessUnlessGranted(PermissionVoter::VIEWER, $group); - return $this->json($this->censusDataProvider->getMembersData($group, $censusRequestData)); + $data = $this->censusDataProvider->getMembersData($group, $censusRequestData); + return $this->json($data); } /** @@ -82,7 +85,8 @@ public function getMembersData(Group $group, CensusRequestData $censusRequestDat public function getTreemapData(Group $group, CensusRequestData $censusRequestData) { $this->denyAccessUnlessGranted(PermissionVoter::VIEWER, $group); - return $this->json($this->censusDataProvider->getTreemapData($group, $censusRequestData)); + $data = $this->censusDataProvider->getTreemapData($group, $censusRequestData); + return $this->json($data); } /** diff --git a/src/DTO/Mapper/CensusMapper.php b/src/DTO/Mapper/CensusMapper.php index 8cdb308..4e15067 100644 --- a/src/DTO/Mapper/CensusMapper.php +++ b/src/DTO/Mapper/CensusMapper.php @@ -66,8 +66,9 @@ public static function mapToCensusTable(StatisticGroup $statisticGroup, array $c $improvementVs3YearsAgo = (100 / $totalCounts[count($totalCounts) - 4]) * $totalCounts[count($totalCounts) - 1] - 100; } } - if (!$incomplete) { - $improvementVsAvg5Years = (100 / (($totalCounts[0] + $totalCounts[1] + $totalCounts[2] + $totalCounts[3] + $totalCounts[4]) / 5)) * $totalCounts[count($totalCounts) - 1] - 100; + $fiveYearTotal = $totalCounts[0] + $totalCounts[1] + $totalCounts[2] + $totalCounts[3] + $totalCounts[4]; + if (!$incomplete && $fiveYearTotal !== 0) { + $improvementVsAvg5Years = (100 / ($fiveYearTotal / 5)) * $totalCounts[count($totalCounts) - 1] - 100; } $dto->setRelativeMemberCounts([$improvementVsLastYear, $improvementVs3YearsAgo, $improvementVsAvg5Years]); return $dto; diff --git a/src/Service/DataProvider/CensusDataProvider.php b/src/Service/DataProvider/CensusDataProvider.php index 443bef3..d77ee4f 100644 --- a/src/Service/DataProvider/CensusDataProvider.php +++ b/src/Service/DataProvider/CensusDataProvider.php @@ -238,7 +238,6 @@ public function getDevelopmentData(Group $group, CensusRequestData $censusReques $relative[] = $dto->getRelative()[0]; } } - $return = new DevelopmentWidgetDTO(); $return->setYears($relevantYears); $return->setAbsolute($absolute); @@ -313,6 +312,9 @@ public function getTreemapData(Group $group, CensusRequestData $censusRequestDat private function filterGroups(array $statisticGroups, CensusRequestData $censusRequestData) { // For faster lookups we swap array index with value so that array goes from [1 => 23, 2 => 352] to [23 => null, 352 => null] + if (is_null($censusRequestData->getGroups())) { + return $statisticGroups; + } $groupIdsToFilterOut = array_flip($censusRequestData->getGroups()); $filteredGroups = array_filter($statisticGroups, function (StatisticGroup $group) use ($groupIdsToFilterOut) { return !isset($groupIdsToFilterOut[$group->getId()]); From 8de02049c378db527059e5759dbcfbe9785ab369 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Fri, 27 Oct 2023 16:31:27 +0200 Subject: [PATCH 48/86] fix: use proper entities for preview --- src/Service/DataProvider/CensusDataProvider.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Service/DataProvider/CensusDataProvider.php b/src/Service/DataProvider/CensusDataProvider.php index d77ee4f..bccfa95 100644 --- a/src/Service/DataProvider/CensusDataProvider.php +++ b/src/Service/DataProvider/CensusDataProvider.php @@ -44,7 +44,7 @@ public function __construct( public function getPreviewData(Group $group) { - $groups = $this->groupRepository->findAllRelevantSubGroupsByParentGroupId($group->getId(), ['Group::Abteilung', 'Group::Kantonalverband', 'Group::Region']); // Replace with group endpoint + $flattenedGroups = $this->getRelevantGroups($group); $return = [ 'm' => [ 'leiter' => 0, @@ -65,8 +65,8 @@ public function getPreviewData(Group $group) 'pta' => 0 ] ]; - foreach ($groups as $group) { - $censusGroup = $this->censusGroupRepository->findOneBy(['group_id' => $group['id'], 'year' => date('Y')]); + foreach ($flattenedGroups as $group) { + $censusGroup = $this->censusGroupRepository->findOneBy(['group_id' => $group->getId(), 'year' => date('Y')]); if (!is_null($censusGroup)) { $return['m']['leiter'] += $censusGroup->getLeiterMCount(); $return['m']['biber'] += $censusGroup->getBiberMCount(); From 445e4292911d1981a5e728976f9c6ea69b150f6f Mon Sep 17 00:00:00 2001 From: Sebastian Date: Fri, 27 Oct 2023 16:48:39 +0200 Subject: [PATCH 49/86] add crontab and finish census command --- crontab | 3 +++ src/Command/FetchCensusCommand.php | 12 ++++-------- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/crontab b/crontab index 89059cb..8ef3e02 100644 --- a/crontab +++ b/crontab @@ -3,3 +3,6 @@ # Run every 3 months at 00:00 (AM) 0 0 1 */3 * /usr/local/bin/php /srv/bin/console app:import-geo-addresses + +# Run once a year on the first of february at 01:00 (AM) +0 1 1 2 * /usr/local/bin/php /srv/bin/console app:fetch-census diff --git a/src/Command/FetchCensusCommand.php b/src/Command/FetchCensusCommand.php index 6382700..ccdf6c8 100644 --- a/src/Command/FetchCensusCommand.php +++ b/src/Command/FetchCensusCommand.php @@ -20,6 +20,7 @@ class FetchCensusCommand extends StatisticsCommand protected GroupTypeRepository $groupTypeRepository; private SymfonyStyle $io; + private $start; public function __construct( CensusAPIService $apiService, @@ -36,16 +37,16 @@ public function __construct( public function configure() { $this->setName('app:fetch-census') - ->setDescription('Not implemented'); + ->setDescription('Fetch and aggregate census data'); } public function execute(InputInterface $input, OutputInterface $output) { + $this->start = microtime(true); $this->io = new SymfonyStyle($input, $output); $year = (int) date('Y'); $minYear = $year - 6; - $groupsToAggregate = []; // Fetch groups while ($year > $minYear) { $this->io->writeln('year ' . $year); @@ -54,15 +55,11 @@ public function execute(InputInterface $input, OutputInterface $output) foreach ($rawCensusGroups as $rawCensusGroup) { $exists = $this->censusGroupRepository->findOneBy(['group_id' => $rawCensusGroup['group_id'], 'year' => $year]); if (is_null($exists)) { - $groupsToAggregate[] = $rawCensusGroup['group_id']; $this->mapRawCensusGroupToCensusGroup($rawCensusGroup, $year); } } $year--; } - // Aggregate Groups - foreach (array_unique($groupsToAggregate) as $groupId) { - } return Command::SUCCESS; } @@ -102,9 +99,8 @@ private function sanitizeValue($raw): int } - // TODO: Implement the statistics public function getStats(): CommandStatistics { - return new CommandStatistics(0, 'Statistics not yet implemented.'); + return new CommandStatistics(microtime(true) - $this->start, ''); } } From a5c9b19a2a9c91e171f32cc7be3bb84dfff9ea17 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Tue, 19 Dec 2023 16:43:30 +0100 Subject: [PATCH 50/86] fix: increase contrast between colors in Roleoverview --- src/DTO/Mapper/RoleOverviewMapper.php | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/DTO/Mapper/RoleOverviewMapper.php b/src/DTO/Mapper/RoleOverviewMapper.php index a8c681c..35b38c1 100644 --- a/src/DTO/Mapper/RoleOverviewMapper.php +++ b/src/DTO/Mapper/RoleOverviewMapper.php @@ -14,12 +14,12 @@ class RoleOverviewMapper { const GROUP_TYPE_COLORS = [ - 'Biber' => ['#EEE09F', '#d6ca8f'], - 'Woelfe' => ['#3BB5DC', '#2f91b0'], - 'Pfadi' => ['#9A7A54', '#7b6243'], - 'Pio' => ['#DD1F19', '#b11914'], - 'Rover' => ['#1DA650', '#178540'], - 'Pta' => ['#d9b826', '#ae931e'], + 'Biber' => ['#EEE09F', '#a1976c'], + 'Woelfe' => ['#3BB5DC', '#27758f'], + 'Pfadi' => ['#9A7A54', '#574530'], + 'Pio' => ['#DD1F19', '#6b110c'], + 'Rover' => ['#1DA650', '#127336'], + 'Pta' => ['#d9b826', '#947d16'], ]; public static function createRoleOverviewDTO(Group $group): RoleOverviewDTO @@ -58,7 +58,7 @@ private static function getRoleColor(string $roleType) return $value; } } - return ['#da70d6', '#ae5aab']; + return ['#da70d6', '#8c488a']; } public static function createRoleOccupation(AggregatedPersonRole $aggregatedPersonRole, string $from, string $to): RoleOccupation From 57433a0da0b79f95c1016a40269cede8fa6c5907 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Thu, 8 Feb 2024 15:08:58 +0100 Subject: [PATCH 51/86] fix: prevent unknown grouptypes from causing errors --- src/Command/FetchAllGroupsCommand.php | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/Command/FetchAllGroupsCommand.php b/src/Command/FetchAllGroupsCommand.php index 2bbbef6..aaabdc5 100644 --- a/src/Command/FetchAllGroupsCommand.php +++ b/src/Command/FetchAllGroupsCommand.php @@ -186,11 +186,19 @@ private function fetchGroupRecursive(int $id, ?StatisticGroup $parent, ?Statisti $name = trim($rawGroup['name']); $children = $rawGroup['links']['children'] ?? []; /** - * Keep in mind that the group type is not sent via UID (zb. Group::Abteilung) but via the label in the language - * that you are using. In this case we use /de/group... so the german label. + * Sadly the group type we get from the regular group endpoint (statistic_group) is not the same one, + * that we get from the group type endpoint (JSON file). So here we have to map the german label of a group type + * to the group type key. + * A side effect of this is that we can get Group types which just don't exist. (eg. Erziehungsberechtigter) + * To prevent this from destroying this function we must ignore such groups. * @var GroupType $groupType */ $groupType = $this->groupTypeRepository->findOneBy(['deLabel' => $rawGroup['group_type']]); + $invalid_group_type = is_null($groupType); + if($invalid_group_type) { + $this->io->writeln($rawGroup['group_type']); + return; + } $statisticGroup->setId($id); $statisticGroup->setCanton($canton); From 7e4c4e385c2245f142841f45185bc41c10369058 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Thu, 8 Feb 2024 15:40:51 +0100 Subject: [PATCH 52/86] restructure census services and create census date provider --- src/Command/FetchCensusCommand.php | 3 +-- .../Midata/CensusGroupRepository.php | 8 ++++++++ src/Service/{ => Census}/CensusAPIService.php | 3 ++- src/Service/Census/CensusDateProvider.php | 20 +++++++++++++++++++ .../DataProvider/CensusDataProvider.php | 6 +++++- 5 files changed, 36 insertions(+), 4 deletions(-) rename src/Service/{ => Census}/CensusAPIService.php (94%) create mode 100644 src/Service/Census/CensusDateProvider.php diff --git a/src/Command/FetchCensusCommand.php b/src/Command/FetchCensusCommand.php index ccdf6c8..496d730 100644 --- a/src/Command/FetchCensusCommand.php +++ b/src/Command/FetchCensusCommand.php @@ -6,8 +6,7 @@ use App\Model\CommandStatistics; use App\Repository\Midata\CensusGroupRepository; use App\Repository\Midata\GroupTypeRepository; -use App\Service\CensusAPIService; -use App\Service\GroupStructureAPIService; +use App\Service\Census\CensusAPIService; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; diff --git a/src/Repository/Midata/CensusGroupRepository.php b/src/Repository/Midata/CensusGroupRepository.php index f864645..7d3f173 100644 --- a/src/Repository/Midata/CensusGroupRepository.php +++ b/src/Repository/Midata/CensusGroupRepository.php @@ -48,6 +48,14 @@ public function remove(CensusGroup $entity, bool $flush = true): void } } + public function getLatestYear(): int + { + $query = "SELECT MAX(year) from census_group;"; + $result = $this->_em->createNativeQuery($query)->getOneOrNullResult(); + if (is_null($result)) throw new \Exception("No date found in census table."); + return $result; + } + // /** // * @return CensusGroup[] Returns an array of CensusGroup objects // */ diff --git a/src/Service/CensusAPIService.php b/src/Service/Census/CensusAPIService.php similarity index 94% rename from src/Service/CensusAPIService.php rename to src/Service/Census/CensusAPIService.php index cc73eea..115688c 100644 --- a/src/Service/CensusAPIService.php +++ b/src/Service/Census/CensusAPIService.php @@ -1,7 +1,8 @@ censusGroupRepository = $censusGroupRepository; + } + + public function getLatestYear(): int + { + return $this->censusGroupRepository->getLatestYear(); + } +} diff --git a/src/Service/DataProvider/CensusDataProvider.php b/src/Service/DataProvider/CensusDataProvider.php index bccfa95..de62af8 100644 --- a/src/Service/DataProvider/CensusDataProvider.php +++ b/src/Service/DataProvider/CensusDataProvider.php @@ -18,6 +18,7 @@ use App\Repository\Midata\GroupTypeRepository; use App\Repository\Statistics\StatisticGroupRepository; use App\Service\Apps\Census\CensusFilter; +use App\Service\Census\CensusDateProvider; use Doctrine\DBAL\Schema\Table; use Sentry\Util\JSON; use Symfony\Contracts\Translation\TranslatorInterface; @@ -26,15 +27,18 @@ class CensusDataProvider extends WidgetDataProvider { private CensusGroupRepository $censusGroupRepository; private StatisticGroupRepository $statisticGroupRepository; + private CensusDateProvider $censusDateProvider; public function __construct( GroupRepository $groupRepository, GroupTypeRepository $groupTypeRepository, TranslatorInterface $translator, CensusGroupRepository $censusGroupRepository, - StatisticGroupRepository $statisticGroupRepository + StatisticGroupRepository $statisticGroupRepository, + CensusDateProvider $censusDateProvider ) { $this->censusGroupRepository = $censusGroupRepository; $this->statisticGroupRepository = $statisticGroupRepository; + $this->censusDateProvider = $censusDateProvider; parent::__construct( $groupRepository, $groupTypeRepository, From 9ac50aba6890b7fbd67d12a96088f6156f5e2e84 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Thu, 8 Feb 2024 15:41:09 +0100 Subject: [PATCH 53/86] implement latest year and date range functions --- src/Service/Census/CensusDateProvider.php | 5 +++++ src/Service/DataProvider/CensusDataProvider.php | 10 +++++----- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/Service/Census/CensusDateProvider.php b/src/Service/Census/CensusDateProvider.php index 8dcef59..aec722c 100644 --- a/src/Service/Census/CensusDateProvider.php +++ b/src/Service/Census/CensusDateProvider.php @@ -17,4 +17,9 @@ public function getLatestYear(): int { return $this->censusGroupRepository->getLatestYear(); } + + public function getRelevantDateRange(): array + { + return range($this->getLatestYear() - 5, $this->getLatestYear()); + } } diff --git a/src/Service/DataProvider/CensusDataProvider.php b/src/Service/DataProvider/CensusDataProvider.php index de62af8..0aff9ef 100644 --- a/src/Service/DataProvider/CensusDataProvider.php +++ b/src/Service/DataProvider/CensusDataProvider.php @@ -70,7 +70,7 @@ public function getPreviewData(Group $group) ] ]; foreach ($flattenedGroups as $group) { - $censusGroup = $this->censusGroupRepository->findOneBy(['group_id' => $group->getId(), 'year' => date('Y')]); + $censusGroup = $this->censusGroupRepository->findOneBy(['group_id' => $group->getId(), 'year' => $this->censusDateProvider->getLatestYear()]); if (!is_null($censusGroup)) { $return['m']['leiter'] += $censusGroup->getLeiterMCount(); $return['m']['biber'] += $censusGroup->getBiberMCount(); @@ -215,7 +215,7 @@ public function getTableData(Group $group, CensusRequestData $censusRequestData) $flattenedGroups = $this->getRelevantGroups($group); $flattenedGroups = $this->sortGroups($flattenedGroups); $dataTransferObjects = []; - $relevantYears = range(date('Y') - 5, date('Y')); + $relevantYears = $this->censusDateProvider->getRelevantDateRange(); foreach ($flattenedGroups as $flattenedGroup) { $dataTransferObjects[] = CensusMapper::mapToCensusTable($flattenedGroup, $this->censusGroupRepository->findBy(['group_id' => $flattenedGroup->getId()]), $relevantYears, $censusRequestData); } @@ -233,7 +233,7 @@ public function getDevelopmentData(Group $group, CensusRequestData $censusReques $absolute = []; $relative = []; - $relevantYears = range(date('Y') - 5, date('Y')); + $relevantYears = $this->censusDateProvider->getRelevantDateRange(); foreach ($relevantGroups as $relevantGroup) { $data = $this->censusGroupRepository->findBy(['group_id' => $relevantGroup->getId()]); if (!sizeof($data) == 0) { @@ -257,7 +257,7 @@ public function getMembersData(Group $group, CensusRequestData $censusRequestDat $rawResults = []; foreach ($relevantGroups as $relevantGroup) { - $data = $this->censusGroupRepository->findBy(['group_id' => $relevantGroup->getId(), 'year' => date('Y')]); + $data = $this->censusGroupRepository->findBy(['group_id' => $relevantGroup->getId(), 'year' => $this->censusDateProvider->getLatestYear()]); if (!sizeof($data) == 0) { CensusMapper::filterCensusGroup($data[0], $censusRequestData); $biber = $data[0]->getBiberMCount() + $data[0]->getBiberFCount(); @@ -292,7 +292,7 @@ public function getTreemapData(Group $group, CensusRequestData $censusRequestDat $return = []; foreach ($relevantGroups as $relevantGroup) { - $data = $this->censusGroupRepository->findBy(['group_id' => $relevantGroup->getId(), 'year' => date('Y')]); + $data = $this->censusGroupRepository->findBy(['group_id' => $relevantGroup->getId(), 'year' => $this->censusDateProvider->getLatestYear()]); if (!sizeof($data) == 0) { CensusMapper::filterCensusGroup($data[0], $censusRequestData); $dto = new TreemapWidgetDTO(); From c434c98e993ac69c351cb58b8930ab545bbea9f9 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Thu, 8 Feb 2024 15:43:08 +0100 Subject: [PATCH 54/86] add comment --- src/Service/Census/CensusDateProvider.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/Service/Census/CensusDateProvider.php b/src/Service/Census/CensusDateProvider.php index aec722c..87d3630 100644 --- a/src/Service/Census/CensusDateProvider.php +++ b/src/Service/Census/CensusDateProvider.php @@ -13,6 +13,13 @@ public function __construct(CensusGroupRepository $censusGroupRepository) $this->censusGroupRepository = $censusGroupRepository; } + /** + * @return int + * @throws \Exception + * This funciton returns the latest year we have census data for in the database. + * It was created so that census widgets are still usable in the period between the start of a new year until + * the census data is updated. + */ public function getLatestYear(): int { return $this->censusGroupRepository->getLatestYear(); From 097a58f78b2b73c5fafb18e4bde52a2a35ff2e66 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Thu, 8 Feb 2024 15:43:55 +0100 Subject: [PATCH 55/86] adjust description --- src/Service/Census/CensusDateProvider.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Service/Census/CensusDateProvider.php b/src/Service/Census/CensusDateProvider.php index 87d3630..97e3d82 100644 --- a/src/Service/Census/CensusDateProvider.php +++ b/src/Service/Census/CensusDateProvider.php @@ -14,11 +14,11 @@ public function __construct(CensusGroupRepository $censusGroupRepository) } /** - * @return int - * @throws \Exception * This funciton returns the latest year we have census data for in the database. * It was created so that census widgets are still usable in the period between the start of a new year until - * the census data is updated. + * the census data is updated. + * @return int + * @throws \Exception */ public function getLatestYear(): int { From cd5712fd11383b788aaf53971579cfcce49dc807 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Thu, 8 Feb 2024 16:48:15 +0100 Subject: [PATCH 56/86] fix query --- config/services.yaml | 2 +- src/Repository/Midata/CensusGroupRepository.php | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/config/services.yaml b/config/services.yaml index d0d3352..73e6711 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -149,7 +149,7 @@ services: $apiToken: '%env(GROUP_STRUCTURE_TOKEN)%' $url: '%env(GROUP_STRUCTURE_URL)%' - App\Service\CensusAPIService: + App\Service\Census\CensusAPIService: arguments: $apiToken: '%env(CENSUS_TOKEN)%' $url: '%env(CENSUS_URL)%' diff --git a/src/Repository/Midata/CensusGroupRepository.php b/src/Repository/Midata/CensusGroupRepository.php index 7d3f173..997cdfa 100644 --- a/src/Repository/Midata/CensusGroupRepository.php +++ b/src/Repository/Midata/CensusGroupRepository.php @@ -7,6 +7,7 @@ use Doctrine\ORM\Id\AssignedGenerator; use Doctrine\ORM\OptimisticLockException; use Doctrine\ORM\ORMException; +use Doctrine\ORM\Query\ResultSetMapping; use Doctrine\Persistence\ManagerRegistry; /** @@ -50,8 +51,10 @@ public function remove(CensusGroup $entity, bool $flush = true): void public function getLatestYear(): int { - $query = "SELECT MAX(year) from census_group;"; - $result = $this->_em->createNativeQuery($query)->getOneOrNullResult(); + $rsm = new ResultSetMapping(); + $rsm->addScalarResult('max', 'max', 'integer'); + $query = $this->_em->createNativeQuery('SELECT MAX(year) FROM census_group;', $rsm); + $result = $query->getSingleScalarResult(); if (is_null($result)) throw new \Exception("No date found in census table."); return $result; } From 199c85e71a8ff563074d05b27154f6be7ecc6f10 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Thu, 8 Feb 2024 16:55:49 +0100 Subject: [PATCH 57/86] add dynamic year to widgets with no date information --- src/Service/DataProvider/CensusDataProvider.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Service/DataProvider/CensusDataProvider.php b/src/Service/DataProvider/CensusDataProvider.php index 0aff9ef..f19b8a5 100644 --- a/src/Service/DataProvider/CensusDataProvider.php +++ b/src/Service/DataProvider/CensusDataProvider.php @@ -282,7 +282,7 @@ public function getMembersData(Group $group, CensusRequestData $censusRequestDat $dto->setData($rawResult); $return[] = $dto; } - return $return; + return ['data' =>$return, 'year' => $this->censusDateProvider->getLatestYear()]; } public function getTreemapData(Group $group, CensusRequestData $censusRequestData) @@ -304,7 +304,7 @@ public function getTreemapData(Group $group, CensusRequestData $censusRequestDat $return[] = $dto; } } - return $return; + return ['data' =>$return, 'year' => $this->censusDateProvider->getLatestYear()]; } /** From e02e1f6fca79ca1ef9bf566deb66c70e393d24df Mon Sep 17 00:00:00 2001 From: Sebastian Date: Thu, 8 Feb 2024 17:24:55 +0100 Subject: [PATCH 58/86] sort camps by start date --- .../Aggregated/AggregatedDemographicCampRepository.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Repository/Aggregated/AggregatedDemographicCampRepository.php b/src/Repository/Aggregated/AggregatedDemographicCampRepository.php index 2318dcd..7e7c27e 100644 --- a/src/Repository/Aggregated/AggregatedDemographicCampRepository.php +++ b/src/Repository/Aggregated/AggregatedDemographicCampRepository.php @@ -30,6 +30,7 @@ public function getAllForPeriodAndMainGroup(string $from, string $to, Group $mai ->where('dc.dataPointDate >= :from') ->andWhere('dc.dataPointDate <= :to') ->andWhere('cg.group = :mainGroup') + ->orderBy('dc.startDate') ->setParameter('mainGroup', $mainGroup) ->setParameter('from', $from) ->setParameter('to', $to) From 1cc682dc79f57d9d80c354fc24379447f2ea1416 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Thu, 15 Feb 2024 09:50:41 +0100 Subject: [PATCH 59/86] add logger warning for invalid groups --- src/Command/FetchAllGroupsCommand.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Command/FetchAllGroupsCommand.php b/src/Command/FetchAllGroupsCommand.php index aaabdc5..49ea040 100644 --- a/src/Command/FetchAllGroupsCommand.php +++ b/src/Command/FetchAllGroupsCommand.php @@ -196,7 +196,9 @@ private function fetchGroupRecursive(int $id, ?StatisticGroup $parent, ?Statisti $groupType = $this->groupTypeRepository->findOneBy(['deLabel' => $rawGroup['group_type']]); $invalid_group_type = is_null($groupType); if($invalid_group_type) { - $this->io->writeln($rawGroup['group_type']); + $this->gelfLogger->warning( + new SimpleLogMessage('Invalid grouptype detected, skipping group: '.$id) + ); return; } From 8af93d805a3c827bdcdfc0cac145fa95d0fef8f4 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Thu, 15 Feb 2024 10:10:19 +0100 Subject: [PATCH 60/86] change help text --- imports/questionnaire_imports.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/imports/questionnaire_imports.json b/imports/questionnaire_imports.json index 7f2f5ae..0f59d44 100644 --- a/imports/questionnaire_imports.json +++ b/imports/questionnaire_imports.json @@ -3680,9 +3680,9 @@ "url": "https://pfadi.swiss/it/formazione/" } ], - "help_de": "Folgende in der Regel (inter)kantonal organisierten Kurse werden zurzeit in eurem KV nicht angeboten:\n...\n[auflisten anhand Abfrage]\n...", - "help_fr": "Les cours suivants, qui sont normalement organisés au niveau (inter)cantonal, ne sont actuellement pas proposés au sein de votre AC :\n...\n[énumérer à l'aide de la consultation]\n…", - "help_it": "I corsi seguenti, che sono normalmente organizzati a livello (inter)cantonale, non sono attualmente proposti in seno alla vostra AC: ... [elencare grazie alla consultazione] ..." + "help_de": "Zu den Kursen, die normalerweise von den KVs und Regionen organisiert werden, gehören die Vorbasis-, Basis- und Aufbaukurse, die Spezialisierungen und Einführungskurse.", + "help_fr": "Les cours habituellement organisés par les AC et régions sont les cours préparatoires, les cours de base et de responsable d’unité, les spécialisations et les cours d’introduction.", + "help_it": "I corsi solitamente organizzati dai cantoni sono corsi preparatori, Base, Campo, corsi di intruduzione e specializzazioni." }, "question_de": "In Zusammenarbeit mit anderen Kantonalverbänden bieten wir unseren Leitenden alle im Ausbildungsmodell vorgesehenen kantonalen Kurse an.", "question_fr": "En collaboration avec d'autres associations cantonales, nous proposons à nos responsables tous les cours cantonaux figurant dans le modèle de formation.", From ed5ab3ed064dfbc4a88c06aa3d8812d36f5ca96a Mon Sep 17 00:00:00 2001 From: Sebastian Date: Thu, 15 Feb 2024 10:11:26 +0100 Subject: [PATCH 61/86] Revert "change help text" This reverts commit 8af93d805a3c827bdcdfc0cac145fa95d0fef8f4. --- imports/questionnaire_imports.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/imports/questionnaire_imports.json b/imports/questionnaire_imports.json index 0f59d44..7f2f5ae 100644 --- a/imports/questionnaire_imports.json +++ b/imports/questionnaire_imports.json @@ -3680,9 +3680,9 @@ "url": "https://pfadi.swiss/it/formazione/" } ], - "help_de": "Zu den Kursen, die normalerweise von den KVs und Regionen organisiert werden, gehören die Vorbasis-, Basis- und Aufbaukurse, die Spezialisierungen und Einführungskurse.", - "help_fr": "Les cours habituellement organisés par les AC et régions sont les cours préparatoires, les cours de base et de responsable d’unité, les spécialisations et les cours d’introduction.", - "help_it": "I corsi solitamente organizzati dai cantoni sono corsi preparatori, Base, Campo, corsi di intruduzione e specializzazioni." + "help_de": "Folgende in der Regel (inter)kantonal organisierten Kurse werden zurzeit in eurem KV nicht angeboten:\n...\n[auflisten anhand Abfrage]\n...", + "help_fr": "Les cours suivants, qui sont normalement organisés au niveau (inter)cantonal, ne sont actuellement pas proposés au sein de votre AC :\n...\n[énumérer à l'aide de la consultation]\n…", + "help_it": "I corsi seguenti, che sono normalmente organizzati a livello (inter)cantonale, non sono attualmente proposti in seno alla vostra AC: ... [elencare grazie alla consultazione] ..." }, "question_de": "In Zusammenarbeit mit anderen Kantonalverbänden bieten wir unseren Leitenden alle im Ausbildungsmodell vorgesehenen kantonalen Kurse an.", "question_fr": "En collaboration avec d'autres associations cantonales, nous proposons à nos responsables tous les cours cantonaux figurant dans le modèle de formation.", From 008702f226f6d085fbe5c7a0ffe275d136e1b20e Mon Sep 17 00:00:00 2001 From: Sebastian Date: Thu, 15 Feb 2024 10:12:12 +0100 Subject: [PATCH 62/86] change help text --- imports/questionnaire_imports.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/imports/questionnaire_imports.json b/imports/questionnaire_imports.json index 7f2f5ae..0f59d44 100644 --- a/imports/questionnaire_imports.json +++ b/imports/questionnaire_imports.json @@ -3680,9 +3680,9 @@ "url": "https://pfadi.swiss/it/formazione/" } ], - "help_de": "Folgende in der Regel (inter)kantonal organisierten Kurse werden zurzeit in eurem KV nicht angeboten:\n...\n[auflisten anhand Abfrage]\n...", - "help_fr": "Les cours suivants, qui sont normalement organisés au niveau (inter)cantonal, ne sont actuellement pas proposés au sein de votre AC :\n...\n[énumérer à l'aide de la consultation]\n…", - "help_it": "I corsi seguenti, che sono normalmente organizzati a livello (inter)cantonale, non sono attualmente proposti in seno alla vostra AC: ... [elencare grazie alla consultazione] ..." + "help_de": "Zu den Kursen, die normalerweise von den KVs und Regionen organisiert werden, gehören die Vorbasis-, Basis- und Aufbaukurse, die Spezialisierungen und Einführungskurse.", + "help_fr": "Les cours habituellement organisés par les AC et régions sont les cours préparatoires, les cours de base et de responsable d’unité, les spécialisations et les cours d’introduction.", + "help_it": "I corsi solitamente organizzati dai cantoni sono corsi preparatori, Base, Campo, corsi di intruduzione e specializzazioni." }, "question_de": "In Zusammenarbeit mit anderen Kantonalverbänden bieten wir unseren Leitenden alle im Ausbildungsmodell vorgesehenen kantonalen Kurse an.", "question_fr": "En collaboration avec d'autres associations cantonales, nous proposons à nos responsables tous les cours cantonaux figurant dans le modèle de formation.", From a6f7fca459afdb0a0f4650a91f75618855ae1603 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Thu, 15 Feb 2024 10:53:45 +0100 Subject: [PATCH 63/86] update composer image Hopefully this will fix the current issue with HTTP/2 streams and curl --- docker/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 33eba99..dfa9ae1 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -3,7 +3,7 @@ ARG BUILD_TEST=0 ARG PHP_INI=php.ini # Install dependencies -FROM composer:1.10.19 as dependencies +FROM composer:2.7.1 as dependencies COPY composer.* /app/ WORKDIR /app RUN composer install --no-interaction --ignore-platform-reqs --no-scripts --prefer-dist From ec839c5e372b814058edf4c15ddbe01fc42f35d3 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Thu, 15 Feb 2024 10:58:12 +0100 Subject: [PATCH 64/86] revert version change and enforce http 1 for git --- docker/Dockerfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index dfa9ae1..025f830 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -3,9 +3,10 @@ ARG BUILD_TEST=0 ARG PHP_INI=php.ini # Install dependencies -FROM composer:2.7.1 as dependencies +FROM composer:1.10.19 as dependencies COPY composer.* /app/ WORKDIR /app +RUN git config --global http.version HTTP/1.1 RUN composer install --no-interaction --ignore-platform-reqs --no-scripts --prefer-dist FROM php:7.4.14-fpm From 549e1958cf474b16bbef34cff69f69664892beec Mon Sep 17 00:00:00 2001 From: Sebastian Date: Thu, 15 Feb 2024 11:14:47 +0100 Subject: [PATCH 65/86] try to increase buffer size instead --- docker/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 025f830..50bf3e8 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -6,7 +6,7 @@ ARG PHP_INI=php.ini FROM composer:1.10.19 as dependencies COPY composer.* /app/ WORKDIR /app -RUN git config --global http.version HTTP/1.1 +RUN git config --global http.postBuffer 524288000 RUN composer install --no-interaction --ignore-platform-reqs --no-scripts --prefer-dist FROM php:7.4.14-fpm From 13d2ef1e6d301f8500317c4ae97c39e6dc8e6870 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Thu, 15 Feb 2024 11:50:23 +0100 Subject: [PATCH 66/86] retry with updated gitlab version --- docker/Dockerfile | 1 - 1 file changed, 1 deletion(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 50bf3e8..33eba99 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -6,7 +6,6 @@ ARG PHP_INI=php.ini FROM composer:1.10.19 as dependencies COPY composer.* /app/ WORKDIR /app -RUN git config --global http.postBuffer 524288000 RUN composer install --no-interaction --ignore-platform-reqs --no-scripts --prefer-dist FROM php:7.4.14-fpm From 0d547b26ea1f9af935fdcaf293e314dffa08cfb0 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Thu, 15 Feb 2024 11:54:54 +0100 Subject: [PATCH 67/86] . --- docker/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 33eba99..17e459a 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -6,7 +6,7 @@ ARG PHP_INI=php.ini FROM composer:1.10.19 as dependencies COPY composer.* /app/ WORKDIR /app -RUN composer install --no-interaction --ignore-platform-reqs --no-scripts --prefer-dist +RUN git config --global http.version HTTP/1.1 && composer install --no-interaction --ignore-platform-reqs --no-scripts --prefer-dist FROM php:7.4.14-fpm ARG BUILD_DEBUG From 0f8286ef9cc1b93d1a9fd83b34117806966a7b4b Mon Sep 17 00:00:00 2001 From: Sebastian Date: Thu, 15 Feb 2024 14:35:34 +0100 Subject: [PATCH 68/86] disable proxy for http1 --- docker/Dockerfile | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 17e459a..e184dd1 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -6,7 +6,9 @@ ARG PHP_INI=php.ini FROM composer:1.10.19 as dependencies COPY composer.* /app/ WORKDIR /app -RUN git config --global http.version HTTP/1.1 && composer install --no-interaction --ignore-platform-reqs --no-scripts --prefer-dist +RUN git config --global http.version HTTP/1.1 +RUN git config --global --unset http.proxy +RUN composer install --no-interaction --ignore-platform-reqs --no-scripts --prefer-dist FROM php:7.4.14-fpm ARG BUILD_DEBUG From dd5b2bce092975a1982eee067e29c04096a39a8d Mon Sep 17 00:00:00 2001 From: Sebastian Date: Thu, 15 Feb 2024 14:40:24 +0100 Subject: [PATCH 69/86] enable git tracing --- docker/Dockerfile | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index e184dd1..9c1637f 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -6,8 +6,7 @@ ARG PHP_INI=php.ini FROM composer:1.10.19 as dependencies COPY composer.* /app/ WORKDIR /app -RUN git config --global http.version HTTP/1.1 -RUN git config --global --unset http.proxy +RUN GIT_TRACE_PACKET=1 GIT_TRACE=2 GIT_CURL_VERBOSE=1 RUN composer install --no-interaction --ignore-platform-reqs --no-scripts --prefer-dist FROM php:7.4.14-fpm From 58e998f48e683582a8a1f631577d5406571e649b Mon Sep 17 00:00:00 2001 From: Sebastian Date: Thu, 15 Feb 2024 14:45:35 +0100 Subject: [PATCH 70/86] verbose composer --- docker/Dockerfile | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 9c1637f..a4dc1a1 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -6,8 +6,7 @@ ARG PHP_INI=php.ini FROM composer:1.10.19 as dependencies COPY composer.* /app/ WORKDIR /app -RUN GIT_TRACE_PACKET=1 GIT_TRACE=2 GIT_CURL_VERBOSE=1 -RUN composer install --no-interaction --ignore-platform-reqs --no-scripts --prefer-dist +RUN composer install --no-interaction --ignore-platform-reqs --no-scripts --prefer-dist -vvv FROM php:7.4.14-fpm ARG BUILD_DEBUG From 04db14654e5de78133545fb87d817bb28cebc440 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Thu, 15 Feb 2024 16:04:55 +0100 Subject: [PATCH 71/86] . --- docker/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index a4dc1a1..33eba99 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -6,7 +6,7 @@ ARG PHP_INI=php.ini FROM composer:1.10.19 as dependencies COPY composer.* /app/ WORKDIR /app -RUN composer install --no-interaction --ignore-platform-reqs --no-scripts --prefer-dist -vvv +RUN composer install --no-interaction --ignore-platform-reqs --no-scripts --prefer-dist FROM php:7.4.14-fpm ARG BUILD_DEBUG From 5ab8cd312aa125a06f305b756f22b6ebfdcd655b Mon Sep 17 00:00:00 2001 From: Sebastian Date: Thu, 15 Feb 2024 16:18:33 +0100 Subject: [PATCH 72/86] linting --- src/Repository/Midata/CensusGroupRepository.php | 4 +++- src/Service/DataProvider/CensusDataProvider.php | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Repository/Midata/CensusGroupRepository.php b/src/Repository/Midata/CensusGroupRepository.php index 997cdfa..839331a 100644 --- a/src/Repository/Midata/CensusGroupRepository.php +++ b/src/Repository/Midata/CensusGroupRepository.php @@ -55,7 +55,9 @@ public function getLatestYear(): int $rsm->addScalarResult('max', 'max', 'integer'); $query = $this->_em->createNativeQuery('SELECT MAX(year) FROM census_group;', $rsm); $result = $query->getSingleScalarResult(); - if (is_null($result)) throw new \Exception("No date found in census table."); + if (is_null($result)) { + throw new \Exception("No date found in census table."); + } return $result; } diff --git a/src/Service/DataProvider/CensusDataProvider.php b/src/Service/DataProvider/CensusDataProvider.php index f19b8a5..975e1e6 100644 --- a/src/Service/DataProvider/CensusDataProvider.php +++ b/src/Service/DataProvider/CensusDataProvider.php @@ -282,7 +282,7 @@ public function getMembersData(Group $group, CensusRequestData $censusRequestDat $dto->setData($rawResult); $return[] = $dto; } - return ['data' =>$return, 'year' => $this->censusDateProvider->getLatestYear()]; + return ['data' => $return, 'year' => $this->censusDateProvider->getLatestYear()]; } public function getTreemapData(Group $group, CensusRequestData $censusRequestData) @@ -304,7 +304,7 @@ public function getTreemapData(Group $group, CensusRequestData $censusRequestDat $return[] = $dto; } } - return ['data' =>$return, 'year' => $this->censusDateProvider->getLatestYear()]; + return ['data' => $return, 'year' => $this->censusDateProvider->getLatestYear()]; } /** From 33f80933e7875fd0aa4af15e601c2e5f55d96c58 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Thu, 15 Feb 2024 16:24:16 +0100 Subject: [PATCH 73/86] lint --- src/Command/FetchAllGroupsCommand.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Command/FetchAllGroupsCommand.php b/src/Command/FetchAllGroupsCommand.php index 49ea040..ae40dda 100644 --- a/src/Command/FetchAllGroupsCommand.php +++ b/src/Command/FetchAllGroupsCommand.php @@ -195,9 +195,9 @@ private function fetchGroupRecursive(int $id, ?StatisticGroup $parent, ?Statisti */ $groupType = $this->groupTypeRepository->findOneBy(['deLabel' => $rawGroup['group_type']]); $invalid_group_type = is_null($groupType); - if($invalid_group_type) { + if ($invalid_group_type) { $this->gelfLogger->warning( - new SimpleLogMessage('Invalid grouptype detected, skipping group: '.$id) + new SimpleLogMessage('Invalid grouptype detected, skipping group: ' . $id) ); return; } From 5f3213f5cc1e04ebb9e706b4579162b162de6308 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Fri, 16 Feb 2024 10:48:31 +0100 Subject: [PATCH 74/86] prevent faulty role data from breaking the aggregation --- src/Command/ImportFromJsonCommand.php | 5 +++-- src/DataFixtures/Aggregator/AggregatorTestFixture.php | 5 +++-- src/Entity/Midata/PersonRole.php | 4 ++-- src/Service/Aggregator/DemographicCampAggregator.php | 2 ++ src/Service/Aggregator/RoleAggregator.php | 3 +++ src/Service/Aggregator/WidgetAggregator.php | 6 +++--- 6 files changed, 16 insertions(+), 9 deletions(-) diff --git a/src/Command/ImportFromJsonCommand.php b/src/Command/ImportFromJsonCommand.php index 1bde142..d12b791 100644 --- a/src/Command/ImportFromJsonCommand.php +++ b/src/Command/ImportFromJsonCommand.php @@ -843,9 +843,10 @@ private function importRoles(OutputInterface $output) $personRole->setPerson($person); $role = $this->em->getRepository(Role::class)->getOneByRoleType($r['type']); - if ($role) { - $personRole->setRole($role); + if (is_null($role)) { + continue; } + $personRole->setRole($role); $group = $this->em->getRepository(Group::class)->find($r['group_id']); if ($group) { diff --git a/src/DataFixtures/Aggregator/AggregatorTestFixture.php b/src/DataFixtures/Aggregator/AggregatorTestFixture.php index 39337b3..f110ec6 100644 --- a/src/DataFixtures/Aggregator/AggregatorTestFixture.php +++ b/src/DataFixtures/Aggregator/AggregatorTestFixture.php @@ -278,9 +278,10 @@ protected function importRoles(ObjectManager $em) } $role = $em->getRepository(Role::class)->getOneByRoleType($r['type']); - if ($role) { - $personRole->setRole($role); + if (is_null($role)) { + continue; } + $personRole->setRole($role); $group = $em->getRepository(Group::class)->find($r['group_id']); if ($group) { diff --git a/src/Entity/Midata/PersonRole.php b/src/Entity/Midata/PersonRole.php index 0fe0ba0..64de1e6 100644 --- a/src/Entity/Midata/PersonRole.php +++ b/src/Entity/Midata/PersonRole.php @@ -155,7 +155,7 @@ public function getGroup() /** * @param Role|null $role */ - public function setRole(?Role $role) + public function setRole(Role $role) { $this->role = $role; } @@ -163,7 +163,7 @@ public function setRole(?Role $role) /** * @return Role|null */ - public function getRole(): Role + public function getRole(): ?Role { return $this->role; } diff --git a/src/Service/Aggregator/DemographicCampAggregator.php b/src/Service/Aggregator/DemographicCampAggregator.php index fce604c..e47f842 100644 --- a/src/Service/Aggregator/DemographicCampAggregator.php +++ b/src/Service/Aggregator/DemographicCampAggregator.php @@ -223,7 +223,9 @@ private function getRelevantPersonRole(array $personRoles, DateTimeImmutable $ev if ($personRole->getCreatedAt() > $eventDate) { continue; } + // TODO: Warning this is just to prevent the app from breaking due to unstable API, could mess up the logic if ( + !is_null($personRole->getRole()) && !in_array( $personRole->getRole()->getRoleType(), array_merge(self::$leadersRoleTypes, self::$memberRoleTypes, self::$mainGroupRoleTypes) diff --git a/src/Service/Aggregator/RoleAggregator.php b/src/Service/Aggregator/RoleAggregator.php index e191713..60d17d5 100644 --- a/src/Service/Aggregator/RoleAggregator.php +++ b/src/Service/Aggregator/RoleAggregator.php @@ -54,6 +54,9 @@ public function aggregate(DateTime $startDate = null) $highestAggregatedMidataIndex = $this->personRoleRepository->getHighestAggregatedMidataIndex(); $newPersonRoles = $this->midataPersonRoleRepository->findAllWithHigherIndex($highestAggregatedMidataIndex); foreach ($newPersonRoles as $newPersonRole) { + if (is_null($newPersonRole->getRole())) { + continue; + } $aggregatedPersonRole = new AggregatedPersonRole(); $aggregatedPersonRole->setMidata($newPersonRole); $aggregatedPersonRole->setEndAt($newPersonRole->getDeletedAt()); diff --git a/src/Service/Aggregator/WidgetAggregator.php b/src/Service/Aggregator/WidgetAggregator.php index b0547c3..7ecc765 100644 --- a/src/Service/Aggregator/WidgetAggregator.php +++ b/src/Service/Aggregator/WidgetAggregator.php @@ -221,7 +221,7 @@ public function findLeaderGroupTypeForRoleType(PersonRole $personRole): string { foreach (self::$leaderRoleTypesByGroupType as $groupType => $roleTypes) { foreach ($roleTypes as $roleType) { - if ($roleType === $personRole->getRole()->getRoleType()) { + if (!is_null($personRole->getRole()) && $roleType === $personRole->getRole()->getRoleType()) { return $groupType; } } @@ -238,7 +238,7 @@ public function findGroupAndPersonTypeByPersonRolesHierarchy(array $personRoles) foreach (self::$leadersRoleTypes as $leaderRole) { /** @var PersonRole $personRole */ foreach ($personRoles as $personRole) { - if (trim($personRole->getRole()->getRoleType()) !== $leaderRole) { + if (!is_null($personRole->getRole()) && trim($personRole->getRole()->getRoleType()) !== $leaderRole) { continue; } return ['leaders', $this->findLeaderGroupTypeForRoleType($personRole)]; @@ -247,7 +247,7 @@ public function findGroupAndPersonTypeByPersonRolesHierarchy(array $personRoles) foreach (self::$memberRoleTypes as $memberRole) { /** @var PersonRole $personRole */ foreach ($personRoles as $personRole) { - if (trim($personRole->getRole()->getRoleType()) !== $memberRole) { + if (!is_null($personRole->getRole()) && trim($personRole->getRole()->getRoleType()) !== $memberRole) { continue; } return ['members', $personRole->getGroup()->getGroupType()->getGroupType()]; From 9ca608db4b9148fa25c88855f357bf528231e77f Mon Sep 17 00:00:00 2001 From: Sebastian Date: Fri, 16 Feb 2024 10:52:17 +0100 Subject: [PATCH 75/86] lint --- src/Command/FetchAllGroupsCommand.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Command/FetchAllGroupsCommand.php b/src/Command/FetchAllGroupsCommand.php index 49ea040..ae40dda 100644 --- a/src/Command/FetchAllGroupsCommand.php +++ b/src/Command/FetchAllGroupsCommand.php @@ -195,9 +195,9 @@ private function fetchGroupRecursive(int $id, ?StatisticGroup $parent, ?Statisti */ $groupType = $this->groupTypeRepository->findOneBy(['deLabel' => $rawGroup['group_type']]); $invalid_group_type = is_null($groupType); - if($invalid_group_type) { + if ($invalid_group_type) { $this->gelfLogger->warning( - new SimpleLogMessage('Invalid grouptype detected, skipping group: '.$id) + new SimpleLogMessage('Invalid grouptype detected, skipping group: ' . $id) ); return; } From 98005835acaeacf2e239693877fa70874ac620fe Mon Sep 17 00:00:00 2001 From: Sebastian Date: Mon, 27 May 2024 15:52:03 +0200 Subject: [PATCH 76/86] prevent invalid encoding edgecases and fix buffering --- src/Command/ImportFromJsonCommand.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Command/ImportFromJsonCommand.php b/src/Command/ImportFromJsonCommand.php index d12b791..93b1e05 100644 --- a/src/Command/ImportFromJsonCommand.php +++ b/src/Command/ImportFromJsonCommand.php @@ -534,7 +534,7 @@ private function importCamps(OutputInterface $output) $metadata->setIdGenerator(new AssignedGenerator()); } $camp->setState($c['state']); - $camp->setLocation(substr($c['location'], 0, 255)); + $camp->setLocation(mb_convert_encoding(substr($c['location'], 0, 255),'UTF-8', 'US-ASCII')); if (isset($c['name'])) { $camp->setName($c['name']); @@ -567,7 +567,7 @@ private function importCamps(OutputInterface $output) } $this->em->persist($camp); - if ($i % 10) { + if(0 == $i % 10) { $this->em->flush(); } $i++; From 5dd2323ca6a8430165713e2b4a0b1ee77ca65d0d Mon Sep 17 00:00:00 2001 From: Sebastian Date: Mon, 27 May 2024 15:54:34 +0200 Subject: [PATCH 77/86] add buffering to roles --- src/Command/ImportFromJsonCommand.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Command/ImportFromJsonCommand.php b/src/Command/ImportFromJsonCommand.php index 93b1e05..9995c50 100644 --- a/src/Command/ImportFromJsonCommand.php +++ b/src/Command/ImportFromJsonCommand.php @@ -171,9 +171,12 @@ private function importRoleTypes(OutputInterface $output) $role->setItLabel($roleType['label_it']); $this->em->persist($role); - $this->em->flush(); + if (0 === ($i % $this->batchSize)) { + $this->em->flush(); + } $i++; } + $this->em->flush(); $timeElapsed = microtime(true) - $start; $this->stats[] = ['role_types.json', $timeElapsed, $i]; $output->writeln([sprintf('%s rows imported from roles_types.json', $i)]); From bae8a719676c14cbd8663c8ee9f989a73fe5bcec Mon Sep 17 00:00:00 2001 From: Sebastian Date: Tue, 28 May 2024 08:07:29 +0200 Subject: [PATCH 78/86] lint --- src/Command/ImportFromJsonCommand.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Command/ImportFromJsonCommand.php b/src/Command/ImportFromJsonCommand.php index 9995c50..6c72470 100644 --- a/src/Command/ImportFromJsonCommand.php +++ b/src/Command/ImportFromJsonCommand.php @@ -537,7 +537,7 @@ private function importCamps(OutputInterface $output) $metadata->setIdGenerator(new AssignedGenerator()); } $camp->setState($c['state']); - $camp->setLocation(mb_convert_encoding(substr($c['location'], 0, 255),'UTF-8', 'US-ASCII')); + $camp->setLocation(mb_convert_encoding(substr($c['location'], 0, 255), 'UTF-8', 'US-ASCII')); if (isset($c['name'])) { $camp->setName($c['name']); @@ -570,7 +570,7 @@ private function importCamps(OutputInterface $output) } $this->em->persist($camp); - if(0 == $i % 10) { + if (0 == $i % 10) { $this->em->flush(); } $i++; From 887fd39d19f708404161bf45ab55416c9fe903d4 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Sun, 21 Jul 2024 12:26:39 +0200 Subject: [PATCH 79/86] change grouptype to region --- src/Repository/Midata/GroupRepository.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Repository/Midata/GroupRepository.php b/src/Repository/Midata/GroupRepository.php index e2f8c77..d96b275 100644 --- a/src/Repository/Midata/GroupRepository.php +++ b/src/Repository/Midata/GroupRepository.php @@ -237,7 +237,7 @@ public function findAllDepartmentsForFederation(int $federationId): array ->where('g.parentGroup = :federationId') ->andWhere('gt.groupType IN (:groupType)') ->setParameter('federationId', $federationId) - ->setParameter('groupType', ['Group::Kantonalverband']) + ->setParameter('groupType', ['Group::Region']) ->getQuery() ->getArrayResult(); } From 2bac08cb1dd3c6ba5e2384b920619ef4fef1123f Mon Sep 17 00:00:00 2001 From: Sebastian Date: Wed, 11 Sep 2024 11:50:41 +0200 Subject: [PATCH 80/86] Gamification merger --- config/routes/app_routes.yml | 7 + config/routes/apps/gamification.yml | 29 ++ imports/gamification.json | 283 ++++++++++++++++++ src/Command/ImportGamificationCommand.php | 124 ++++++++ src/Command/PseudonymizeLoginCommand.php | 55 ++++ src/Controller/Api/Apps/QuapController.php | 10 +- src/Controller/Api/GamificationController.php | 82 +++++ src/Controller/Api/InviteController.php | 5 +- src/Controller/AuthController.php | 8 +- src/DTO/Mapper/GamificationGoalMapper.php | 31 ++ src/DTO/Mapper/GamificationLevelMapper.php | 28 ++ .../GamificationPersonProfileMapper.php | 37 +++ src/DTO/Model/Gamification/GoalDTO.php | 110 +++++++ src/DTO/Model/Gamification/LevelDTO.php | 94 ++++++ .../Gamification/PersonGamificationDTO.php | 94 ++++++ .../GamificationPersonProfile.php | 215 +++++++++++++ src/Entity/Gamification/Goal.php | 231 ++++++++++++++ src/Entity/Gamification/Level.php | 202 +++++++++++++ src/Entity/Gamification/LevelUpLog.php | 78 +++++ src/Entity/Gamification/Login.php | 134 +++++++++ src/Entity/Midata/Group.php | 38 +++ src/Entity/Midata/Person.php | 103 +++++++ src/Migrations/Version20240827122505.php | 38 +++ src/Migrations/Version20240911090632.php | 59 ++++ .../GamificationPersonProfileRepository.php | 78 +++++ .../Gamification/GoalRepository.php | 78 +++++ .../Gamification/LevelRepository.php | 88 ++++++ .../Gamification/LevelUpLogRepository.php | 78 +++++ .../Gamification/LoginRepository.php | 103 +++++++ .../Security/PermissionRepository.php | 29 ++ src/Service/Gamification/LoginService.php | 85 ++++++ .../PersonGamificationService.php | 270 +++++++++++++++++ .../Gamification/QuapGamificationService.php | 73 +++++ 33 files changed, 2972 insertions(+), 5 deletions(-) create mode 100644 config/routes/apps/gamification.yml create mode 100644 imports/gamification.json create mode 100644 src/Command/ImportGamificationCommand.php create mode 100644 src/Command/PseudonymizeLoginCommand.php create mode 100644 src/Controller/Api/GamificationController.php create mode 100644 src/DTO/Mapper/GamificationGoalMapper.php create mode 100644 src/DTO/Mapper/GamificationLevelMapper.php create mode 100644 src/DTO/Mapper/GamificationPersonProfileMapper.php create mode 100644 src/DTO/Model/Gamification/GoalDTO.php create mode 100644 src/DTO/Model/Gamification/LevelDTO.php create mode 100644 src/DTO/Model/Gamification/PersonGamificationDTO.php create mode 100644 src/Entity/Gamification/GamificationPersonProfile.php create mode 100644 src/Entity/Gamification/Goal.php create mode 100644 src/Entity/Gamification/Level.php create mode 100644 src/Entity/Gamification/LevelUpLog.php create mode 100644 src/Entity/Gamification/Login.php create mode 100644 src/Migrations/Version20240827122505.php create mode 100644 src/Migrations/Version20240911090632.php create mode 100644 src/Repository/Gamification/GamificationPersonProfileRepository.php create mode 100644 src/Repository/Gamification/GoalRepository.php create mode 100644 src/Repository/Gamification/LevelRepository.php create mode 100644 src/Repository/Gamification/LevelUpLogRepository.php create mode 100644 src/Repository/Gamification/LoginRepository.php create mode 100644 src/Service/Gamification/LoginService.php create mode 100644 src/Service/Gamification/PersonGamificationService.php create mode 100644 src/Service/Gamification/QuapGamificationService.php diff --git a/config/routes/app_routes.yml b/config/routes/app_routes.yml index 12bb0cb..1218533 100644 --- a/config/routes/app_routes.yml +++ b/config/routes/app_routes.yml @@ -17,7 +17,14 @@ general: prefix: /general name_prefix: general_ +# Census census: resource: "apps/census.yml" prefix: /census name_prefix: census_ + +# Gamification +gamification: + resource: "apps/gamification.yml" + prefix: /gamification + name_prefix: gamification_ diff --git a/config/routes/apps/gamification.yml b/config/routes/apps/gamification.yml new file mode 100644 index 0000000..a6d29ad --- /dev/null +++ b/config/routes/apps/gamification.yml @@ -0,0 +1,29 @@ +groupChange: + path: /group-change + methods: POST + controller: App\Controller\Api\GamificationController::postGroupChange + +logCardLayer: + path: /card-layer + methods: PATCH + controller: App\Controller\Api\GamificationController::usedCardLayer + +logDataFilter: + path: /data-filter + methods: PATCH + controller: App\Controller\Api\GamificationController::usedDataFilter + +logTimeFilter: + path: /time-filter + methods: PATCH + controller: App\Controller\Api\GamificationController::usedTimeFilter + +getPersonProfile: + path: /person + methods: GET + controller: App\Controller\Api\GamificationController::getUserProfile + +reset: + path: /reset + methods: POST + controller: App\Controller\Api\GamificationController::resetGamification diff --git a/imports/gamification.json b/imports/gamification.json new file mode 100644 index 0000000..90174d3 --- /dev/null +++ b/imports/gamification.json @@ -0,0 +1,283 @@ +{ + "levels": [ + { + "key": 0, + "next_key": 1, + "type": 0, + "required": 0, + "de_title": "Newcomer*in", + "fr_title": "", + "it_title": "" + }, + { + "key": 1, + "next_key": 2, + "type": 0, + "required": 3, + "de_title": "Datenentdecker*in", + "fr_title": "", + "it_title": "" + }, + { + "key": 2, + "next_key": 3, + "type": 0, + "required": 3, + "de_title": "Statistikforscher*in", + "fr_title": "", + "it_title": "" + }, + { + "key": 3, + "next_key": null, + "type": 0, + "required": 2, + "de_title": "Healthcheck Guru", + "fr_title": "", + "it_title": "" + } + ], + "goals": [ + { + "key": "FIRST_LOGIN", + "level": 1, + "required": true, + "de": { + "title": "Erstes Login", + "information": "Testing: Ist immer erfüllt", + "help": "" + }, + "fr": { + "title": "french", + "information": "", + "help": "" + }, + "it": { + "title": "italian", + "information": "", + "help": "" + } + }, + { + "key": "CARD_LAYERS", + "level": 1, + "required": true, + "de": { + "title": "Unterschiedliche Layer der Karte benutzt", + "information": "", + "help": "" + }, + "fr": { + "title": "french", + "information": "", + "help": "" + }, + "it": { + "title": "italian", + "information": "", + "help": "" + } + }, + { + "key": "DATAFILTER", + "level": 1, + "required": false, + "de": { + "title": "Datenfilter in Übersicht benutzt", + "information": "", + "help": "" + }, + "fr": { + "title": "french", + "information": "", + "help": "" + }, + "it": { + "title": "italian", + "information": "", + "help": "" + } + }, + { + "key": "TIMEFILTER", + "level": 1, + "required": false, + "de": { + "title": "zeitbereichsansicht benutzt", + "information": "", + "help": "" + }, + "fr": { + "title": "french", + "information": "", + "help": "" + }, + "it": { + "title": "italian", + "information": "", + "help": "" + } + }, + { + "key": "SHARE_WITH_PARENTS", + "level": 1, + "required": false, + "de": { + "title": "Freigabe für übergeordnete Ebene.", + "information": "", + "help": "" + }, + "fr": { + "title": "french", + "information": "", + "help": "" + }, + "it": { + "title": "italian", + "information": "", + "help": "" + } + }, + { + "key": "EL_FILL_OUT", + "level": 2, + "required": true, + "de": { + "title": "Alle Erfolgslogik Kasten einer Gruppe Ausgefüllt.", + "information": "Testing: noch nicht freigegeben (WIP)", + "help": "" + }, + "fr": { + "title": "french", + "information": "", + "help": "" + }, + "it": { + "title": "italian", + "information": "", + "help": "" + } + }, + { + "key": "SHARE_1", + "level": 2, + "required": false, + "de": { + "title": "Freigabe für mindestens eine andere Person.", + "information": "", + "help": "" + }, + "fr": { + "title": "french", + "information": "", + "help": "" + }, + "it": { + "title": "italian", + "information": "", + "help": "" + } + }, + { + "key": "EL_IRRELEVANT", + "level": 2, + "required": false, + "de": { + "title": "In der Erfolgslogik nicht relevante Fragen ausgebledet", + "information": "", + "help": "" + }, + "fr": { + "title": "french", + "information": "", + "help": "" + }, + "it": { + "title": "italian", + "information": "", + "help": "" + } + }, + { + "key": "EL_CHANGE", + "level": 2, + "required": false, + "de": { + "title": "Einen Erfolgslogik Kasten überarbeitet.", + "information": "", + "help": "" + }, + "fr": { + "title": "french", + "information": "", + "help": "" + }, + "it": { + "title": "italian", + "information": "", + "help": "" + } + }, + { + "key": "EL_IMPROVE", + "level": 3, + "required": true, + "de": { + "title": "Verbesserung in einem Erfolgslogik Kasten.", + "information": "", + "help": "" + }, + "fr": { + "title": "french", + "information": "", + "help": "" + }, + "it": { + "title": "italian", + "information": "", + "help": "" + } + }, + { + "key": "LOGIN_FOUR_A_YEAR", + "level": 3, + "required": false, + "de": { + "title": "Vier mal innerhalb eines Jahres eingeloggt.", + "information": "", + "help": "" + }, + "fr": { + "title": "french", + "information": "", + "help": "" + }, + "it": { + "title": "italian", + "information": "", + "help": "" + } + }, + { + "key": "SHARE_THREE", + "level": 3, + "required": false, + "de": { + "title": "Eine Abteilung an 3 oder mehr Personen freigegeben.", + "information": "", + "help": "" + }, + "fr": { + "title": "french", + "information": "", + "help": "" + }, + "it": { + "title": "italian", + "information": "", + "help": "" + } + } + ] +} + diff --git a/src/Command/ImportGamificationCommand.php b/src/Command/ImportGamificationCommand.php new file mode 100644 index 0000000..3692fc9 --- /dev/null +++ b/src/Command/ImportGamificationCommand.php @@ -0,0 +1,124 @@ +em = $em; + $this->levelRepository = $levelRepository; + $this->goalRepository = $goalRepository; + } + + protected function configure() + { + $this + ->setName("app:import-gamification"); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $start = microtime( true); + $json = json_decode(file_get_contents($this->pathToJson), true); + + $this->em->getConnection()->executeQuery('DELETE FROM gamification_person_profile'); + $this->em->getConnection()->executeQuery('DELETE FROM goal'); + $this->em->getConnection()->executeQuery('DELETE FROM level'); + if (is_null($json['levels'])) { + $output->writeln('No levels found.'); + return 1; + } + $output->writeln('importing levels'); + $this->importLevels($json['levels'], $output); + + if (is_null($json["goals"])) { + $output->writeln('No goals found.'); + return 1; + } + $this->importGoals($json['goals'], $output); + + $this->duration = microtime(true) - $start; + return 0; + } + + protected function importLevels(array $jsonLevels, OutputInterface $output) { + foreach ($jsonLevels as $jsonLevel) { + $level = $this->levelRepository->findOneBy(["key" => $jsonLevel["key"]]); + if (is_null($level)) { + $level = new Level(); + $level->setKey(intval($jsonLevel["key"])); + if (!is_null($jsonLevel["next_key"])) { + $level->setNextKey(intval($jsonLevel["next_key"])); + } + $output->writeln("Creating " . $jsonLevel["de_title"] . " (" . $jsonLevel["key"] . ")"); + } + $level->setRequired($jsonLevel["required"]); + $level->setType($jsonLevel["type"]); + $level->setDeTitle($jsonLevel["de_title"]); + $level->setFrTitle($jsonLevel["fr_title"]); + $level->setItTitle($jsonLevel["it_title"]); + + $this->em->persist($level); + } + $this->em->flush(); + } + + protected function importGoals(array $jsonGoals, OutputInterface $output) { + foreach ($jsonGoals as $jsonGoal) { + $goal = $this->goalRepository->findOneBy(['key' => $jsonGoal['key']]); + if (is_null($goal)) { + $goal = new Goal(); + $goal->setKey($jsonGoal['key']); + $output->writeln("Creating " . $jsonGoal["de"]["title"] . " (" . $jsonGoal["key"] . ")"); + } + $level = $this->levelRepository->findOneBy(['key' => $jsonGoal['level']]); + $goal->setLevel($level); + $goal->setRequired($jsonGoal['required']); + + $goal->setDeTitle($jsonGoal['de']['title']); + $goal->setDeInformation($jsonGoal['de']['information']); + $goal->setDeHelp($jsonGoal['de']['help']); + $goal->setFrTitle($jsonGoal['fr']['title']); + $goal->setFrInformation($jsonGoal['fr']['information']); + $goal->setFrHelp($jsonGoal['fr']['help']); + $goal->setItTitle($jsonGoal['it']['title']); + $goal->setItInformation($jsonGoal['it']['information']); + $goal->setItHelp($jsonGoal['it']['help']); + + $this->em->persist($goal); + } + $this->em->flush(); + } + + public function getStats(): CommandStatistics + { + return new CommandStatistics($this->duration, ''); + } +} diff --git a/src/Command/PseudonymizeLoginCommand.php b/src/Command/PseudonymizeLoginCommand.php new file mode 100644 index 0000000..0e2e0be --- /dev/null +++ b/src/Command/PseudonymizeLoginCommand.php @@ -0,0 +1,55 @@ +loginRepository = $loginRepository; + } + + protected function configure() + { + $this + ->setName("app:pseudonomize-login") + ->addOption("log", '', InputArgument::OPTIONAL, "List all pseudonymized Logins.", false); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $start = microtime( true); + $pseudonymizedLogins = $this->loginRepository->pseudonymizeAllOlderThan18Months(function ($personId) { return hash('sha256', $personId);}); // sha256 is mostly collision free and currently irreversible, sufficient for our purposes. + $log = $input->getOption('log'); + if ($log) { + $output->writeln('Following Logins (id) have been pseudonymized:'); + foreach ($pseudonymizedLogins as $login) { + $output->writeln('id: ' . $login->getId()); + } + $output->writeln('Total of ' . sizeof($pseudonymizedLogins) . ' logins have been pseudonymized.'); + } + $this->duration = microtime(true) - $start; + return 0; + } + + public function getStats(): CommandStatistics + { + return new CommandStatistics($this->duration, ''); + } +} diff --git a/src/Controller/Api/Apps/QuapController.php b/src/Controller/Api/Apps/QuapController.php index 64a2223..44d80e5 100644 --- a/src/Controller/Api/Apps/QuapController.php +++ b/src/Controller/Api/Apps/QuapController.php @@ -9,6 +9,8 @@ use App\Exception\ApiException; use App\Service\Apps\Quap\QuapService; use App\Service\DataProvider\QuapSubdepartmentDateDataProvider; +use App\Service\Gamification\PersonGamificationService; +use App\Service\Gamification\QuapGamificationService; use App\Service\Security\PermissionVoter; use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; @@ -128,7 +130,8 @@ public function getQuestionnaireData( */ public function submitAnswers( Group $group, - Request $request + Request $request, + QuapGamificationService $quapGamificationService ): JsonResponse { $this->denyAccessUnlessGranted(PermissionVoter::EDITOR, $group); @@ -137,6 +140,7 @@ public function submitAnswers( throw new ApiException(400, "Invalid JSON"); } + $quapGamificationService->processQuapEvent($json, $group, $this->getUser()); // has to be before answers are saved! $savedWidgetQuap = $this->quapService->submitAnswers($group, $json); return $this->json($savedWidgetQuap->getAnswers()); @@ -151,7 +155,8 @@ public function submitAnswers( */ public function setAccess( Group $group, - Request $request + Request $request, + PersonGamificationService $personGamificationService ): JsonResponse { $this->denyAccessUnlessGranted(PermissionVoter::OWNER, $group); @@ -161,6 +166,7 @@ public function setAccess( } $this->quapService->updateAllowAccess($group, $payload['allow_access']); + $personGamificationService->genericGoalProgress($this->getUser(), 'shareEL'); return $this->json([], JsonResponse::HTTP_NO_CONTENT); } diff --git a/src/Controller/Api/GamificationController.php b/src/Controller/Api/GamificationController.php new file mode 100644 index 0000000..4153451 --- /dev/null +++ b/src/Controller/Api/GamificationController.php @@ -0,0 +1,82 @@ +getContent(), true); + if (is_null($json) || is_null($json['group'])) { + throw new ApiException(400, "Invalid JSON"); + } + $group = $groupRepository->find($json['group']); + if (is_null($group)) { + throw new ApiException(400, "Invalid Group"); + } + $this->denyAccessUnlessGranted(PermissionVoter::VIEWER, $group); + $loginService->logByPersonAndGroup($this->getUser(), $group); + return new Response('', 201); + } + + public function usedCardLayer( + Request $request, + PersonGamificationService $personGamificationService + ) + { + $personGamificationService->genericGoalProgress($this->getUser(), 'card'); + return new Response('', 200); + } + + public function usedDataFilter( + Request $request, + PersonGamificationService $personGamificationService + ) + { + $personGamificationService->genericGoalProgress($this->getUser(), 'data'); + return new Response('', 200); + } + + public function usedTimeFilter( + Request $request, + PersonGamificationService $personGamificationService + ) + { + $personGamificationService->genericGoalProgress($this->getUser(), 'time'); + return new Response('', 200); + } + + public function getUserProfile(Request $request, + PersonGamificationService $personGamificationService) + { + $dto = $personGamificationService->getPersonGamificationDTO($this->getUser(), $request->getLocale()); + return $this->json($dto); + } + + public function resetGamification(Request $request, PersonGamificationService $personGamificationService): Response + { + $personGamificationService->reset($this->getUser()); + return new Response(''); + } +} diff --git a/src/Controller/Api/InviteController.php b/src/Controller/Api/InviteController.php index 75b3286..e526e3e 100644 --- a/src/Controller/Api/InviteController.php +++ b/src/Controller/Api/InviteController.php @@ -6,6 +6,7 @@ use App\Entity\Midata\Group; use App\Entity\Security\Permission; use App\Exception\ApiException; +use App\Service\Gamification\PersonGamificationService; use App\Service\PermissionService; use App\Service\Security\PermissionVoter; use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter; @@ -53,7 +54,8 @@ public function createInvite( Request $request, Group $group, SerializerInterface $serializer, - ValidatorInterface $validator + ValidatorInterface $validator, + PersonGamificationService $personGamificationService ): JsonResponse { $this->denyAccessUnlessGranted(PermissionVoter::OWNER, $group); @@ -88,6 +90,7 @@ public function createInvite( } $createdInviteDTO = $this->inviteService->createInvite($group, $inviteDTO); + $personGamificationService->genericGoalProgress($this->getUser(), 'invite'); return $this->json($createdInviteDTO, JsonResponse::HTTP_CREATED); } diff --git a/src/Controller/AuthController.php b/src/Controller/AuthController.php index 28e35a3..25fe745 100644 --- a/src/Controller/AuthController.php +++ b/src/Controller/AuthController.php @@ -4,6 +4,7 @@ use App\DTO\Model\PbsUserDTO; use App\Model\LogMessage\SimpleLogMessage; +use App\Service\Gamification\LoginService; use Digio\Logging\GelfLogger; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\JsonResponse; @@ -18,14 +19,16 @@ class AuthController extends AbstractController * @var GelfLogger */ private $logger; + private LoginService $loginService; /** * AuthController constructor. * @param GelfLogger $logger */ - public function __construct(GelfLogger $logger) + public function __construct(GelfLogger $logger, LoginService $loginService) { $this->logger = $logger; + $this->loginService = $loginService; } public function login() @@ -34,8 +37,9 @@ public function login() $user = $this->getUser(); if ($user instanceof PbsUserDTO) { $this->logger->info(new SimpleLogMessage(md5($user->getNickname()) . ' logged in.')); + $this->loginService->logByUserDTOForLogin($user); } else { - $this->logger->info(new SimpleLogMessage(md5($user->getUsername()) . ' logged in.')); + $this->logger->info('Non User was logged in.'); } return $this->json($user, JsonResponse::HTTP_OK, [], [ diff --git a/src/DTO/Mapper/GamificationGoalMapper.php b/src/DTO/Mapper/GamificationGoalMapper.php new file mode 100644 index 0000000..242dbb9 --- /dev/null +++ b/src/DTO/Mapper/GamificationGoalMapper.php @@ -0,0 +1,31 @@ +setRequired($goal->getRequired()); + $dto->setCompleted($completed); + $dto->setProgress($progress); + if ($locale === 'de') { + $dto->setTitle($goal->getDeTitle()); + $dto->setInformation($goal->getDeInformation()); + $dto->setHelp($goal->getDeHelp()); + } elseif ($locale === 'it') { + $dto->setTitle($goal->getItTitle()); + $dto->setInformation($goal->getItInformation()); + $dto->setHelp($goal->getItHelp()); + } else { + $dto->setTitle($goal->getFrTitle()); + $dto->setInformation($goal->getFrInformation()); + $dto->setHelp($goal->getFrHelp()); + } + return $dto; + } +} diff --git a/src/DTO/Mapper/GamificationLevelMapper.php b/src/DTO/Mapper/GamificationLevelMapper.php new file mode 100644 index 0000000..b8785c1 --- /dev/null +++ b/src/DTO/Mapper/GamificationLevelMapper.php @@ -0,0 +1,28 @@ +setActive(false); + $dto->setKey($level->getKey()); + $dto->setRequired($level->getRequired()); + + if ($locale === 'de') { + $dto->setTitle($level->getDeTitle()); + } elseif ($locale === 'it') { + $dto->setTitle($level->getItTitle()); + } else { + $dto->setTitle($level->getFrTitle()); + } + return $dto; + } +} diff --git a/src/DTO/Mapper/GamificationPersonProfileMapper.php b/src/DTO/Mapper/GamificationPersonProfileMapper.php new file mode 100644 index 0000000..929fe46 --- /dev/null +++ b/src/DTO/Mapper/GamificationPersonProfileMapper.php @@ -0,0 +1,37 @@ +setName($profile->getPerson()->getNickname()); + $dto->setLevelKey($profile->getLevel()->getKey()); + $dto->setLevelUp(self::checkLevelUp($profile)); + + if ($locale === 'de') { + $dto->setTitle($profile->getLevel()->getDeTitle()); + } elseif ($locale === 'it') { + $dto->setTitle($profile->getLevel()->getItTitle()); + } else { + $dto->setTitle($profile->getLevel()->getFrTitle()); + } + return $dto; + } + + public static function checkLevelUp(GamificationPersonProfile $profile) { + $levelUps = $profile->getPerson()->getLevelUps(); + if (count($levelUps) > 0) { + return true; + } + return false; + } +} diff --git a/src/DTO/Model/Gamification/GoalDTO.php b/src/DTO/Model/Gamification/GoalDTO.php new file mode 100644 index 0000000..bc91c4c --- /dev/null +++ b/src/DTO/Model/Gamification/GoalDTO.php @@ -0,0 +1,110 @@ +title; + } + + /** + * @param string $title + */ + public function setTitle(string $title): void + { + $this->title = $title; + } + + /** + * @return string + */ + public function getInformation(): string + { + return $this->information; + } + + /** + * @param string $information + */ + public function setInformation(string $information): void + { + $this->information = $information; + } + + /** + * @return string + */ + public function getHelp(): string + { + return $this->help; + } + + /** + * @param string $help + */ + public function setHelp(string $help): void + { + $this->help = $help; + } + + /** + * @return int + */ + public function getProgress(): int + { + return $this->progress; + } + + /** + * @param int $progress + */ + public function setProgress(int $progress): void + { + $this->progress = $progress; + } + + /** + * @return bool + */ + public function isCompleted(): bool + { + return $this->completed; + } + + /** + * @param bool $completed + */ + public function setCompleted(bool $completed): void + { + $this->completed = $completed; + } + + /** + * @return bool + */ + public function isRequired(): bool + { + return $this->required; + } + + /** + * @param bool $required + */ + public function setRequired(bool $required): void + { + $this->required = $required; + } + +} diff --git a/src/DTO/Model/Gamification/LevelDTO.php b/src/DTO/Model/Gamification/LevelDTO.php new file mode 100644 index 0000000..9fbcdba --- /dev/null +++ b/src/DTO/Model/Gamification/LevelDTO.php @@ -0,0 +1,94 @@ +required; + } + + /** + * @param int $required + */ + public function setRequired(int $required): void + { + $this->required = $required; + } + + /** + * @return bool + */ + public function isActive(): bool + { + return $this->active; + } + + /** + * @param bool $active + */ + public function setActive(bool $active): void + { + $this->active = $active; + } + + /** + * @return string + */ + public function getTitle(): string + { + return $this->title; + } + + /** + * @param string $title + */ + public function setTitle(string $title): void + { + $this->title = $title; + } + + /** + * @return int + */ + public function getKey(): int + { + return $this->key; + } + + /** + * @param int $key + */ + public function setKey(int $key): void + { + $this->key = $key; + } + + /** + * @return array + */ + public function getGoals(): array + { + return $this->goals; + } + + /** + * @param array $goals + */ + public function setGoals(array $goals): void + { + $this->goals = $goals; + } + +} diff --git a/src/DTO/Model/Gamification/PersonGamificationDTO.php b/src/DTO/Model/Gamification/PersonGamificationDTO.php new file mode 100644 index 0000000..180d3fe --- /dev/null +++ b/src/DTO/Model/Gamification/PersonGamificationDTO.php @@ -0,0 +1,94 @@ +title; + } + + /** + * @param string $title + */ + public function setTitle(string $title): void + { + $this->title = $title; + } + + /** + * @return string + */ + public function getName(): string + { + return $this->name; + } + + /** + * @param string $name + */ + public function setName(string $name): void + { + $this->name = $name; + } + + /** + * @return string + */ + public function getLevelKey(): string + { + return $this->levelKey; + } + + /** + * @param string $levelKey + */ + public function setLevelKey(string $levelKey): void + { + $this->levelKey = $levelKey; + } + + /** + * @return bool + */ + public function isLevelUp(): bool + { + return $this->levelUp; + } + + /** + * @param bool $levelUp + */ + public function setLevelUp(bool $levelUp): void + { + $this->levelUp = $levelUp; + } + + /** + * @return array + */ + public function getLevels(): array + { + return $this->levels; + } + + /** + * @param array $levels + */ + public function setLevels(array $levels): void + { + $this->levels = $levels; + } + +} diff --git a/src/Entity/Gamification/GamificationPersonProfile.php b/src/Entity/Gamification/GamificationPersonProfile.php new file mode 100644 index 0000000..3d1fb83 --- /dev/null +++ b/src/Entity/Gamification/GamificationPersonProfile.php @@ -0,0 +1,215 @@ +id; + } + + public function getPerson(): ?Person + { + return $this->person; + } + + public function setPerson(Person $person): self + { + $this->person = $person; + + return $this; + } + + public function getLevel(): ?Level + { + return $this->level; + } + + public function setLevel(?Level $level): self + { + $this->level = $level; + + return $this; + } + + public function getHasUsedCardLayer(): ?bool + { + return $this->has_used_card_layer; + } + + public function setHasUsedCardLayer(bool $has_used_card_layer): self + { + $this->has_used_card_layer = $has_used_card_layer; + + return $this; + } + + public function getHasUsedDatafilter(): ?bool + { + return $this->has_used_datafilter; + } + + public function setHasUsedDatafilter(bool $has_used_datafilter): self + { + $this->has_used_datafilter = $has_used_datafilter; + + return $this; + } + + public function getHasUsedTimefilter(): ?bool + { + return $this->has_used_timefilter; + } + + public function setHasUsedTimefilter(bool $has_used_timefilter): self + { + $this->has_used_timefilter = $has_used_timefilter; + + return $this; + } + + public function getHasSharedEl(): ?bool + { + return $this->has_shared_el; + } + + public function setHasSharedEl(bool $has_shared_el): self + { + $this->has_shared_el = $has_shared_el; + + return $this; + } + + public function getAccessGrantedCount(): ?int + { + return $this->access_granted_count; + } + + public function setAccessGrantedCount(int $access_granted_count): self + { + $this->access_granted_count = $access_granted_count; + + return $this; + } + + public function getElFilledOut(): ?bool + { + return $this->el_filled_out; + } + + public function setElFilledOut(bool $el_filled_out): self + { + $this->el_filled_out = $el_filled_out; + + return $this; + } + + public function getElRevised(): ?bool + { + return $this->el_revised; + } + + public function setElRevised(bool $el_revised): self + { + $this->el_revised = $el_revised; + + return $this; + } + + public function getElIrrelevant(): ?bool + { + return $this->el_irrelevant; + } + + public function setElIrrelevant(bool $el_irrelevant): self + { + $this->el_irrelevant = $el_irrelevant; + + return $this; + } + + public function getElImproved(): ?bool + { + return $this->el_improved; + } + + public function setElImproved(bool $el_improved): self + { + $this->el_improved = $el_improved; + + return $this; + } +} diff --git a/src/Entity/Gamification/Goal.php b/src/Entity/Gamification/Goal.php new file mode 100644 index 0000000..07950e5 --- /dev/null +++ b/src/Entity/Gamification/Goal.php @@ -0,0 +1,231 @@ +id; + } + + public function getLevel(): ?Level + { + return $this->level; + } + + public function setLevel(?Level $level): self + { + $this->level = $level; + + return $this; + } + + public function getRequired(): ?bool + { + return $this->required; + } + + public function setRequired(bool $required): self + { + $this->required = $required; + + return $this; + } + + public function getDeTitle(): ?string + { + return $this->de_title; + } + + public function setDeTitle(string $de_title): self + { + $this->de_title = $de_title; + + return $this; + } + + public function getDeInformation(): ?string + { + return $this->de_information; + } + + public function setDeInformation(string $de_information): self + { + $this->de_information = $de_information; + + return $this; + } + + public function getDeHelp(): ?string + { + return $this->de_help; + } + + public function setDeHelp(?string $de_help): self + { + $this->de_help = $de_help; + + return $this; + } + + public function getFrTitle(): ?string + { + return $this->fr_title; + } + + public function setFrTitle(string $fr_title): self + { + $this->fr_title = $fr_title; + + return $this; + } + + public function getFrInformation(): ?string + { + return $this->fr_information; + } + + public function setFrInformation(string $fr_information): self + { + $this->fr_information = $fr_information; + + return $this; + } + + public function getFrHelp(): ?string + { + return $this->fr_help; + } + + public function setFrHelp(?string $fr_help): self + { + $this->fr_help = $fr_help; + + return $this; + } + + public function getItTitle(): ?string + { + return $this->it_title; + } + + public function setItTitle(string $it_title): self + { + $this->it_title = $it_title; + + return $this; + } + + public function getItInformation(): ?string + { + return $this->it_information; + } + + public function setItInformation(string $it_information): self + { + $this->it_information = $it_information; + + return $this; + } + + public function getItHelp(): ?string + { + return $this->it_help; + } + + public function setItHelp(?string $it_help): self + { + $this->it_help = $it_help; + + return $this; + } + + public function getKey(): ?string + { + return $this->key; + } + + public function setKey(string $key): self + { + $this->key = $key; + + return $this; + } +} diff --git a/src/Entity/Gamification/Level.php b/src/Entity/Gamification/Level.php new file mode 100644 index 0000000..308f09a --- /dev/null +++ b/src/Entity/Gamification/Level.php @@ -0,0 +1,202 @@ +required; + } + + /** + * @param int $required + */ + public function setRequired($required): void + { + $this->required = $required; + } + + /** + * @return mixed + */ + public function getNextKey() + { + return $this->next_key; + } + + /** + * @param mixed $next_key + */ + public function setNextKey($next_key): void + { + $this->next_key = $next_key; + } + + + public function __construct() + { + $this->goals = new ArrayCollection(); + } + + public function getId(): ?int + { + return $this->id; + } + + public function getType(): ?int + { + return $this->type; + } + + public function setType(int $type): self + { + $this->type = $type; + + return $this; + } + + public function getDeTitle(): ?string + { + return $this->de_title; + } + + public function setDeTitle(string $de_title): self + { + $this->de_title = $de_title; + + return $this; + } + + public function getFrTitle(): ?string + { + return $this->fr_title; + } + + public function setFrTitle(string $fr_title): self + { + $this->fr_title = $fr_title; + + return $this; + } + + public function getItTitle(): ?string + { + return $this->it_title; + } + + public function setItTitle(string $it_title): self + { + $this->it_title = $it_title; + + return $this; + } + + /** + * @return Collection + */ + public function getGoals(): Collection + { + return $this->goals; + } + + public function addGoal(Goal $goal): self + { + if (!$this->goals->contains($goal)) { + $this->goals[] = $goal; + $goal->setLevel($this); + } + + return $this; + } + + public function removeGoal(Goal $goal): self + { + if ($this->goals->removeElement($goal)) { + // set the owning side to null (unless already changed) + if ($goal->getLevel() === $this) { + $goal->setLevel(null); + } + } + + return $this; + } + + /** + * @return mixed + */ + public function getKey() + { + return $this->key; + } + + /** + * @param mixed $key + */ + public function setKey($key): void + { + $this->key = $key; + } + + +} diff --git a/src/Entity/Gamification/LevelUpLog.php b/src/Entity/Gamification/LevelUpLog.php new file mode 100644 index 0000000..0d20969 --- /dev/null +++ b/src/Entity/Gamification/LevelUpLog.php @@ -0,0 +1,78 @@ +id; + } + + public function getPerson(): ?Person + { + return $this->person; + } + + public function setPerson(?Person $person): self + { + $this->person = $person; + + return $this; + } + + public function getLevel(): ?Level + { + return $this->level; + } + + public function setLevel(?Level $level): self + { + $this->level = $level; + + return $this; + } + + public function getDate(): ?\DateTimeInterface + { + return $this->date; + } + + public function setDate(\DateTimeInterface $date): self + { + $this->date = $date; + + return $this; + } +} diff --git a/src/Entity/Gamification/Login.php b/src/Entity/Gamification/Login.php new file mode 100644 index 0000000..5e090a5 --- /dev/null +++ b/src/Entity/Gamification/Login.php @@ -0,0 +1,134 @@ +id; + } + + + public function getDate(): ?\DateTimeInterface + { + return $this->date; + } + + public function setDate(\DateTimeInterface $date): self + { + $this->date = $date; + + return $this; + } + + public function getIsGroupChange(): ?bool + { + return $this->is_group_change; + } + + public function setIsGroupChange(bool $group_change): self + { + $this->is_group_change = $group_change; + + return $this; + } + + public function getPerson(): ?Person + { + return $this->person; + } + + public function setPerson(?Person $person): self + { + $this->person = $person; + + return $this; + } + + public function getGroup(): ?Group + { + return $this->group; + } + + public function setGroup(?Group $group): self + { + $this->group = $group; + + return $this; + } + + public function getHashedPersonId(): ?string + { + return $this->hashed_person_id; + } + + public function setHashedPersonId(?string $hashed_person_id): self + { + $this->hashed_person_id = $hashed_person_id; + + return $this; + } + + public function getRole(): ?string + { + return $this->role; + } + + public function setRole(?string $role): self + { + $this->role = $role; + + return $this; + } +} diff --git a/src/Entity/Midata/Group.php b/src/Entity/Midata/Group.php index 1a67f64..dae0ca2 100644 --- a/src/Entity/Midata/Group.php +++ b/src/Entity/Midata/Group.php @@ -2,10 +2,12 @@ namespace App\Entity\Midata; +use App\Entity\Gamification\Login; use App\Entity\General\GroupSettings; use App\Repository\Midata\GroupRepository; use DateTimeImmutable; use Doctrine\Common\Collections\ArrayCollection; +use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping as ORM; /** @@ -84,9 +86,15 @@ class Group */ private $groupSettings; + /** + * @ORM\OneToMany(targetEntity=Login::class, mappedBy="group") + */ + private $logins; + public function __construct() { $this->events = new ArrayCollection(); + $this->logins = new ArrayCollection(); } /** @@ -243,4 +251,34 @@ public function setGroupSettings(?GroupSettings $groupSettings): self return $this; } + + /** + * @return Collection + */ + public function getLogins(): Collection + { + return $this->logins; + } + + public function addLogin(Login $login): self + { + if (!$this->logins->contains($login)) { + $this->logins[] = $login; + $login->setGgroup($this); + } + + return $this; + } + + public function removeLogin(Login $login): self + { + if ($this->logins->removeElement($login)) { + // set the owning side to null (unless already changed) + if ($login->getGgroup() === $this) { + $login->setGgroup(null); + } + } + + return $this; + } } diff --git a/src/Entity/Midata/Person.php b/src/Entity/Midata/Person.php index 344e3cb..53e92fa 100644 --- a/src/Entity/Midata/Person.php +++ b/src/Entity/Midata/Person.php @@ -3,9 +3,14 @@ namespace App\Entity\Midata; use App\Entity\Admin\GeoAddress; +use App\Entity\Gamification\LevelUpLog; +use App\Entity\Gamification\Login; +use App\Entity\Gamification\GamificationPersonProfile; use App\Repository\Midata\PersonRepository; use DateTimeImmutable; use DateTimeInterface; +use Doctrine\Common\Collections\ArrayCollection; +use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping as ORM; /** @@ -104,6 +109,27 @@ class Person */ private $geoAddress; + /** + * @ORM\OneToMany(targetEntity=Login::class, mappedBy="person") + */ + private $logins; + + /** + * @ORM\OneToOne(targetEntity=GamificationPersonProfile::class, mappedBy="person", cascade={"persist", "remove"}) + */ + private $gamification; + + /** + * @ORM\OneToMany(targetEntity=LevelUpLog::class, mappedBy="person", cascade={"persist", "remove"}) + */ + private $levelUps; + + public function __construct() + { + $this->logins = new ArrayCollection(); + $this->levelUps = new ArrayCollection(); + } + /** * @param int $id */ @@ -311,4 +337,81 @@ public function setGeoAddress(GeoAddress $geoAddress): void { $this->geoAddress = $geoAddress; } + + /** + * @return Collection + */ + public function getLogins(): Collection + { + return $this->logins; + } + + public function addLogin(Login $login): self + { + if (!$this->logins->contains($login)) { + $this->logins[] = $login; + $login->setPerson($this); + } + + return $this; + } + + public function removeLogin(Login $login): self + { + if ($this->logins->removeElement($login)) { + // set the owning side to null (unless already changed) + if ($login->getPerson() === $this) { + $login->setPerson(null); + } + } + + return $this; + } + + public function getGamification(): ?GamificationPersonProfile + { + return $this->gamification; + } + + public function setGamification(GamificationPersonProfile $gamification): self + { + // set the owning side of the relation if necessary + if ($gamification->getPerson() !== $this) { + $gamification->setPerson($this); + } + + $this->gamification = $gamification; + + return $this; + } + + /** + * @return Collection + */ + public function getLevelUps(): Collection + { + return $this->levelUps; + } + + public function addLevelUp(LevelUpLog $displayed): self + { + if (!$this->levelUps->contains($displayed)) { + $this->levelUps[] = $displayed; + $displayed->setPerson($this); + } + + return $this; + } + + public function removeLevelUp(LevelUpLog $displayed): self + { + if ($this->levelUps->removeElement($displayed)) { + // set the owning side to null (unless already changed) + if ($displayed->getPerson() === $this) { + $displayed->setPerson(null); + } + } + + return $this; + } } diff --git a/src/Migrations/Version20240827122505.php b/src/Migrations/Version20240827122505.php new file mode 100644 index 0000000..1ab37e6 --- /dev/null +++ b/src/Migrations/Version20240827122505.php @@ -0,0 +1,38 @@ +addSql('CREATE SEQUENCE login_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); + $this->addSql('CREATE TABLE login (id INT NOT NULL, person_id INT DEFAULT NULL, group_id INT DEFAULT NULL, date TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, is_group_change BOOLEAN NOT NULL, hashed_person_id VARCHAR(255) DEFAULT NULL, role VARCHAR(255) NOT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE INDEX IDX_AA08CB10217BBB47 ON login (person_id)'); + $this->addSql('CREATE INDEX IDX_AA08CB10FE54D947 ON login (group_id)'); + $this->addSql('ALTER TABLE login ADD CONSTRAINT FK_AA08CB10217BBB47 FOREIGN KEY (person_id) REFERENCES midata_person (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE login ADD CONSTRAINT FK_AA08CB10FE54D947 FOREIGN KEY (group_id) REFERENCES midata_group (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('CREATE SCHEMA public'); + $this->addSql('DROP SEQUENCE login_id_seq CASCADE'); + $this->addSql('DROP TABLE login'); + } +} diff --git a/src/Migrations/Version20240911090632.php b/src/Migrations/Version20240911090632.php new file mode 100644 index 0000000..79fa102 --- /dev/null +++ b/src/Migrations/Version20240911090632.php @@ -0,0 +1,59 @@ +addSql('CREATE SEQUENCE gamification_person_profile_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); + $this->addSql('CREATE SEQUENCE goal_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); + $this->addSql('CREATE SEQUENCE level_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); + $this->addSql('CREATE SEQUENCE level_up_log_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); + $this->addSql('CREATE TABLE gamification_person_profile (id INT NOT NULL, person_id INT NOT NULL, level_id INT NOT NULL, has_used_card_layer BOOLEAN NOT NULL, has_used_datafilter BOOLEAN NOT NULL, has_used_timefilter BOOLEAN NOT NULL, has_shared_el BOOLEAN NOT NULL, access_granted_count INT NOT NULL, el_filled_out BOOLEAN NOT NULL, el_revised BOOLEAN NOT NULL, el_irrelevant BOOLEAN NOT NULL, el_improved BOOLEAN NOT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE UNIQUE INDEX UNIQ_5649C849217BBB47 ON gamification_person_profile (person_id)'); + $this->addSql('CREATE INDEX IDX_5649C8495FB14BA7 ON gamification_person_profile (level_id)'); + $this->addSql('CREATE TABLE goal (id INT NOT NULL, level_id INT NOT NULL, required BOOLEAN NOT NULL, de_title VARCHAR(255) NOT NULL, de_information TEXT NOT NULL, de_help TEXT DEFAULT NULL, fr_title VARCHAR(255) NOT NULL, fr_information TEXT NOT NULL, fr_help TEXT DEFAULT NULL, it_title VARCHAR(255) NOT NULL, it_information TEXT NOT NULL, it_help TEXT DEFAULT NULL, key VARCHAR(255) NOT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE INDEX IDX_FCDCEB2E5FB14BA7 ON goal (level_id)'); + $this->addSql('CREATE TABLE level (id INT NOT NULL, type INT NOT NULL, de_title VARCHAR(255) NOT NULL, fr_title VARCHAR(255) NOT NULL, it_title VARCHAR(255) NOT NULL, key INT NOT NULL, next_key INT DEFAULT NULL, required INT DEFAULT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE TABLE level_up_log (id INT NOT NULL, person_id INT NOT NULL, level_id INT NOT NULL, date DATE NOT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE INDEX IDX_8959129C217BBB47 ON level_up_log (person_id)'); + $this->addSql('CREATE INDEX IDX_8959129C5FB14BA7 ON level_up_log (level_id)'); + $this->addSql('ALTER TABLE gamification_person_profile ADD CONSTRAINT FK_5649C849217BBB47 FOREIGN KEY (person_id) REFERENCES midata_person (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE gamification_person_profile ADD CONSTRAINT FK_5649C8495FB14BA7 FOREIGN KEY (level_id) REFERENCES level (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE goal ADD CONSTRAINT FK_FCDCEB2E5FB14BA7 FOREIGN KEY (level_id) REFERENCES level (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE level_up_log ADD CONSTRAINT FK_8959129C217BBB47 FOREIGN KEY (person_id) REFERENCES midata_person (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE level_up_log ADD CONSTRAINT FK_8959129C5FB14BA7 FOREIGN KEY (level_id) REFERENCES level (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('CREATE SCHEMA public'); + $this->addSql('ALTER TABLE gamification_person_profile DROP CONSTRAINT FK_5649C8495FB14BA7'); + $this->addSql('ALTER TABLE goal DROP CONSTRAINT FK_FCDCEB2E5FB14BA7'); + $this->addSql('ALTER TABLE level_up_log DROP CONSTRAINT FK_8959129C5FB14BA7'); + $this->addSql('DROP SEQUENCE gamification_person_profile_id_seq CASCADE'); + $this->addSql('DROP SEQUENCE goal_id_seq CASCADE'); + $this->addSql('DROP SEQUENCE level_id_seq CASCADE'); + $this->addSql('DROP SEQUENCE level_up_log_id_seq CASCADE'); + $this->addSql('DROP TABLE gamification_person_profile'); + $this->addSql('DROP TABLE goal'); + $this->addSql('DROP TABLE level'); + $this->addSql('DROP TABLE level_up_log'); + } +} diff --git a/src/Repository/Gamification/GamificationPersonProfileRepository.php b/src/Repository/Gamification/GamificationPersonProfileRepository.php new file mode 100644 index 0000000..9760951 --- /dev/null +++ b/src/Repository/Gamification/GamificationPersonProfileRepository.php @@ -0,0 +1,78 @@ + + * + * @method GamificationPersonProfile|null find($id, $lockMode = null, $lockVersion = null) + * @method GamificationPersonProfile|null findOneBy(array $criteria, array $orderBy = null) + * @method GamificationPersonProfile[] findAll() + * @method GamificationPersonProfile[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null) + */ +class GamificationPersonProfileRepository extends ServiceEntityRepository +{ + public function __construct(ManagerRegistry $registry) + { + parent::__construct($registry, GamificationPersonProfile::class); + } + + /** + * @throws ORMException + * @throws OptimisticLockException + */ + public function add(GamificationPersonProfile $entity, bool $flush = true): void + { + $this->_em->persist($entity); + if ($flush) { + $this->_em->flush(); + } + } + + /** + * @throws ORMException + * @throws OptimisticLockException + */ + public function remove(GamificationPersonProfile $entity, bool $flush = true): void + { + $this->_em->remove($entity); + if ($flush) { + $this->_em->flush(); + } + } + + // /** + // * @return PersonGoal[] Returns an array of PersonGoal objects + // */ + /* + public function findByExampleField($value) + { + return $this->createQueryBuilder('p') + ->andWhere('p.exampleField = :val') + ->setParameter('val', $value) + ->orderBy('p.id', 'ASC') + ->setMaxResults(10) + ->getQuery() + ->getResult() + ; + } + */ + + /* + public function findOneBySomeField($value): ?PersonGoal + { + return $this->createQueryBuilder('p') + ->andWhere('p.exampleField = :val') + ->setParameter('val', $value) + ->getQuery() + ->getOneOrNullResult() + ; + } + */ +} diff --git a/src/Repository/Gamification/GoalRepository.php b/src/Repository/Gamification/GoalRepository.php new file mode 100644 index 0000000..bd384d1 --- /dev/null +++ b/src/Repository/Gamification/GoalRepository.php @@ -0,0 +1,78 @@ + + * + * @method Goal|null find($id, $lockMode = null, $lockVersion = null) + * @method Goal|null findOneBy(array $criteria, array $orderBy = null) + * @method Goal[] findAll() + * @method Goal[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null) + */ +class GoalRepository extends ServiceEntityRepository +{ + public function __construct(ManagerRegistry $registry) + { + parent::__construct($registry, Goal::class); + } + + /** + * @throws ORMException + * @throws OptimisticLockException + */ + public function add(Goal $entity, bool $flush = true): void + { + $this->_em->persist($entity); + if ($flush) { + $this->_em->flush(); + } + } + + /** + * @throws ORMException + * @throws OptimisticLockException + */ + public function remove(Goal $entity, bool $flush = true): void + { + $this->_em->remove($entity); + if ($flush) { + $this->_em->flush(); + } + } + + // /** + // * @return Goal[] Returns an array of Goal objects + // */ + /* + public function findByExampleField($value) + { + return $this->createQueryBuilder('g') + ->andWhere('g.exampleField = :val') + ->setParameter('val', $value) + ->orderBy('g.id', 'ASC') + ->setMaxResults(10) + ->getQuery() + ->getResult() + ; + } + */ + + /* + public function findOneBySomeField($value): ?Goal + { + return $this->createQueryBuilder('g') + ->andWhere('g.exampleField = :val') + ->setParameter('val', $value) + ->getQuery() + ->getOneOrNullResult() + ; + } + */ +} diff --git a/src/Repository/Gamification/LevelRepository.php b/src/Repository/Gamification/LevelRepository.php new file mode 100644 index 0000000..2388b2a --- /dev/null +++ b/src/Repository/Gamification/LevelRepository.php @@ -0,0 +1,88 @@ + + * + * @method Level|null find($id, $lockMode = null, $lockVersion = null) + * @method Level|null findOneBy(array $criteria, array $orderBy = null) + * @method Level[] findAll() + * @method Level[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null) + */ +class LevelRepository extends ServiceEntityRepository +{ + public function __construct(ManagerRegistry $registry) + { + parent::__construct($registry, Level::class); + } + + /** + * @throws ORMException + * @throws OptimisticLockException + */ + public function add(Level $entity, bool $flush = true): void + { + $this->_em->persist($entity); + if ($flush) { + $this->_em->flush(); + } + } + + /** + * @throws ORMException + * @throws OptimisticLockException + */ + public function remove(Level $entity, bool $flush = true): void + { + $this->_em->remove($entity); + if ($flush) { + $this->_em->flush(); + } + } + + public function findNextLevel(Level $level) + { + return $this->createQueryBuilder('l') + ->where('l.key = :key') + ->setParameter('key', $level->getNextKey()) + ->getQuery() + ->getResult(); + } + + + // /** + // * @return Level[] Returns an array of Level objects + // */ + /* + public function findByExampleField($value) + { + return $this->createQueryBuilder('l') + ->andWhere('l.exampleField = :val') + ->setParameter('val', $value) + ->orderBy('l.id', 'ASC') + ->setMaxResults(10) + ->getQuery() + ->getResult() + ; + } + */ + + /* + public function findOneBySomeField($value): ?Level + { + return $this->createQueryBuilder('l') + ->andWhere('l.exampleField = :val') + ->setParameter('val', $value) + ->getQuery() + ->getOneOrNullResult() + ; + } + */ +} diff --git a/src/Repository/Gamification/LevelUpLogRepository.php b/src/Repository/Gamification/LevelUpLogRepository.php new file mode 100644 index 0000000..d4b29a4 --- /dev/null +++ b/src/Repository/Gamification/LevelUpLogRepository.php @@ -0,0 +1,78 @@ + + * + * @method LevelUpLog|null find($id, $lockMode = null, $lockVersion = null) + * @method LevelUpLog|null findOneBy(array $criteria, array $orderBy = null) + * @method LevelUpLog[] findAll() + * @method LevelUpLog[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null) + */ +class LevelUpLogRepository extends ServiceEntityRepository +{ + public function __construct(ManagerRegistry $registry) + { + parent::__construct($registry, LevelUpLog::class); + } + + /** + * @throws ORMException + * @throws OptimisticLockException + */ + public function add(LevelUpLog $entity, bool $flush = true): void + { + $this->_em->persist($entity); + if ($flush) { + $this->_em->flush(); + } + } + + /** + * @throws ORMException + * @throws OptimisticLockException + */ + public function remove(LevelUpLog $entity, bool $flush = true): void + { + $this->_em->remove($entity); + if ($flush) { + $this->_em->flush(); + } + } + + // /** + // * @return LevelUpLog[] Returns an array of LevelUpLog objects + // */ + /* + public function findByExampleField($value) + { + return $this->createQueryBuilder('l') + ->andWhere('l.exampleField = :val') + ->setParameter('val', $value) + ->orderBy('l.id', 'ASC') + ->setMaxResults(10) + ->getQuery() + ->getResult() + ; + } + */ + + /* + public function findOneBySomeField($value): ?LevelUpLog + { + return $this->createQueryBuilder('l') + ->andWhere('l.exampleField = :val') + ->setParameter('val', $value) + ->getQuery() + ->getOneOrNullResult() + ; + } + */ +} diff --git a/src/Repository/Gamification/LoginRepository.php b/src/Repository/Gamification/LoginRepository.php new file mode 100644 index 0000000..7bf2103 --- /dev/null +++ b/src/Repository/Gamification/LoginRepository.php @@ -0,0 +1,103 @@ + + * + * @method Login|null find($id, $lockMode = null, $lockVersion = null) + * @method Login|null findOneBy(array $criteria, array $orderBy = null) + * @method Login[] findAll() + * @method Login[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null) + */ +class LoginRepository extends ServiceEntityRepository +{ + public function __construct(ManagerRegistry $registry) + { + parent::__construct($registry, Login::class); + } + + public function findAllActiveBy(array $criteria, array $orderBy = null, $limit = null, $offset = null) { + $criteria['hashed_id'] = null; + return $this->findBy($criteria,$orderBy, $limit, $offset); + } + + /** + * @throws ORMException + * @throws OptimisticLockException + */ + public function add(Login $entity, bool $flush = true): void + { + $this->_em->persist($entity); + if ($flush) { + $this->_em->flush(); + } + } + + /** + * @throws ORMException + * @throws OptimisticLockException + */ + public function remove(Login $entity, bool $flush = true): void + { + $this->_em->remove($entity); + if ($flush) { + $this->_em->flush(); + } + } + + public function pseudonymizeAllOlderThan18Months(callable $hashFunc) + { + $thresholdDate = new DateTime('-18 months'); + $entities = $this->createQueryBuilder('e') + ->where('e.date < :thresholdDate') + ->setParameter('thresholdDate', $thresholdDate) + ->getQuery() + ->getResult(); + foreach ($entities as $entity) { + $hashedId = $hashFunc($entity->getPerson()->getId()); + $entity->setPerson(null); + $entity->setHashedPersonId($hashedId); + $this->_em->persist($entity); + } + $this->_em->flush(); + return $entities; + } + + // /** + // * @return Login[] Returns an array of Login objects + // */ + /* + public function findByExampleField($value) + { + return $this->createQueryBuilder('l') + ->andWhere('l.exampleField = :val') + ->setParameter('val', $value) + ->orderBy('l.id', 'ASC') + ->setMaxResults(10) + ->getQuery() + ->getResult() + ; + } + */ + + /* + public function findOneBySomeField($value): ?Login + { + return $this->createQueryBuilder('l') + ->andWhere('l.exampleField = :val') + ->setParameter('val', $value) + ->getQuery() + ->getOneOrNullResult() + ; + } + */ +} diff --git a/src/Repository/Security/PermissionRepository.php b/src/Repository/Security/PermissionRepository.php index e63805e..83a19a6 100644 --- a/src/Repository/Security/PermissionRepository.php +++ b/src/Repository/Security/PermissionRepository.php @@ -152,6 +152,33 @@ public function findHighestByIdOrEmail(Group $group, int $id, string $email): ?P ->getOneOrNullResult(); } + /** + * @param Group $group + * @param int $id + * @param string $email + * @return Permission|null + * @throws \Doctrine\ORM\NonUniqueResultException + */ + public function findHighestById(Group $group, int $id): ?Permission + { + $query = $this->createQueryBuilder('permission'); + + return $query + ->where('permission.group = :group') + ->andWhere('permission.person = :person') + ->andWhere($query->expr()->orX( + $query->expr()->gt('permission.expirationDate', ':now'), + $query->expr()->isNull('permission.expirationDate') + )) + ->orderBy('permission.permissionType') + ->setMaxResults(1) + ->setParameter('group', $group->getId()) + ->setParameter('person', $id) + ->setParameter('now', new \DateTime()) + ->getQuery() + ->getOneOrNullResult(); + } + public function endAllOpenPermissions(): void { $now = new \DateTimeImmutable(); @@ -169,4 +196,6 @@ public function endAllOpenPermissions(): void ] ); } + + } diff --git a/src/Service/Gamification/LoginService.php b/src/Service/Gamification/LoginService.php new file mode 100644 index 0000000..6ffd31a --- /dev/null +++ b/src/Service/Gamification/LoginService.php @@ -0,0 +1,85 @@ +personRepository = $personRepository; + $this->loginRepository = $loginRepository; + $this->groupRepository = $groupRepository; + $this->permissionRepository = $permissionRepository; + } + + public function logByUserDTOForLogin(PbsUserDTO $userDTO):Login { + $login = new Login(); + + $activeGroupDTO = $userDTO->getGroups()[0]; // Group 0 is the active group on Login. + $activeGroup = $this->groupRepository->find($activeGroupDTO->getId()); + $user = $this->personRepository->find($userDTO->getId()); + if ($user === null) { + throwException("user couldn't be found."); + } + if (sizeof($userDTO->getRoles()) !== 1) { + throwException("Invalid amount of roles."); + } + $role = $this->permissionRepository->findHighestById($activeGroup, $user->getId()); + $roleKey = 'ROLE_USER'; + if (!is_null($role)) { + $roleKey = $role->getPermissionType()->getKey(); + } + + $login->setPerson($user); + $login->setGroup($activeGroup); + $login->setDate(new \DateTime('now', new \DateTimeZone('Europe/Zurich'))); + $login->setIsGroupChange(false); + $login->setRole($roleKey); + + $this->loginRepository->add($login); + + return $login; + } + + public function logByPersonAndGroup(PbsUserDTO $userDTO, Group $group) { + $login = new Login(); + $person = $this->personRepository->find($userDTO->getId()); + $role = $this->permissionRepository->findHighestById($group, $person->getId()); + $roleKey = 'ROLE_USER'; + if (!is_null($role)) { + $roleKey = $role->getPermissionType()->getKey(); + } + + $login->setPerson($person); + $login->setGroup($group); + $login->setDate(new \DateTime('now', new \DateTimeZone('Europe/Zurich'))); + $login->setIsGroupChange(true); + $login->setRole($roleKey); + + $this->loginRepository->add($login); + + return $login; + } +} diff --git a/src/Service/Gamification/PersonGamificationService.php b/src/Service/Gamification/PersonGamificationService.php new file mode 100644 index 0000000..b583999 --- /dev/null +++ b/src/Service/Gamification/PersonGamificationService.php @@ -0,0 +1,270 @@ +loginRepository = $loginRepository; + $this->levelRepository = $levelRepository; + $this->personRepository = $personRepository; + $this->personGoalRepository = $personGoalRepository; + $this->em = $em; + $this->levelUpLogRepository = $levelUpLogRepository; + } + + public function reset(PbsUserDTO $pbsUserDTO) { + $person = $this->personRepository->find($pbsUserDTO->getId()); + $pgp = $this->getPersonGamification($person); + $this->personGoalRepository->remove($pgp); + } + + public function getPersonGamification(Person $person): GamificationPersonProfile + { + $gamificationProfile = $person->getGamification(); + if(is_null($gamificationProfile)) { + $gamificationProfile = new GamificationPersonProfile(); + $gamificationProfile->setLevel($this->levelRepository->findOneBy(['key' => 0])); + $gamificationProfile->setPerson($person); + $gamificationProfile->setAccessGrantedCount(0); + $gamificationProfile->setElFilledOut(true); + $gamificationProfile->setElImproved(false); + $gamificationProfile->setElIrrelevant(false); + $gamificationProfile->setElRevised(false); + $gamificationProfile->setHasSharedEl(false); + $gamificationProfile->setHasUsedCardLayer(false); // TODO + $gamificationProfile->setHasUsedDatafilter(false); + $gamificationProfile->setHasUsedTimefilter(false); + $this->personGoalRepository->add($gamificationProfile); + } + return $gamificationProfile; + } + + public function genericGoalProgress(PbsUserDTO $pbsUserDTO, string $type) { + $person = $this->personRepository->find($pbsUserDTO->getId()); + $pgp = $this->getPersonGamification($person); + + switch ($type) { + case 'card': + $pgp->setHasUsedCardLayer(true); + break; + case 'time': + $pgp->setHasUsedTimefilter(true); + break; + case 'data': + $pgp->setHasUsedDatafilter(true); + break; + case 'shareEL': + $pgp->setHasSharedEl(true); + break; + case 'invite': + $newCount = $pgp->getAccessGrantedCount() + 1; + $pgp->setAccessGrantedCount($newCount); + break; + case 'revised': + if ($pgp->getLevel()->getKey() >= 2) { + $pgp->setElRevised(true); + } + break; + case 'improvement': + if ($pgp->getLevel()->getKey() >= 3) { + $pgp->setElImproved(true); + } + break; + case 'irrelevant': + if ($pgp->getLevel()->getKey() >= 2) { + $pgp->setElIrrelevant(true); + } + break; + default: + throw new \Exception('typo in type'); + break; + } + + $this->checkLevelUp($pgp); + + $this->em->persist($pgp); + $this->em->flush(); + } + + public function checkLevelUp(GamificationPersonProfile $person) { + $currentLevel = $person->getLevel(); + $nextLevel = $this->levelRepository->findNextLevel($currentLevel); + + if (count($nextLevel) === 0) { + return $person; + } + $nextLevel = $nextLevel[0]; + $levelUp = false; + if ($currentLevel->getKey() === 0) { + if ($person->getHasUsedCardLayer() && ($person->getHasUsedDatafilter() || $person->getHasUsedTimefilter() || $person->getHasSharedEl())) { + $levelUp = true; + } + } + if ($currentLevel->getKey() === 1) { + $completedCounter = 0; + if ($person->getElFilledOut()) { + if ($person->getAccessGrantedCount() >= 1) { + $completedCounter++; + } + if ($person->getElIrrelevant()) { + $completedCounter++; + } + if ($person->getElRevised()) { + $completedCounter++; + } + if ($completedCounter >= 2) { + $levelUp = true; + } + } + } + if ($currentLevel->getKey() === 2) { + if ($person->getElImproved() && ($this->checkLoginGoal($person) || $person->getAccessGrantedCount() >= 3)) { + $levelUp = true; + } + } + + if ($levelUp) { + $person->setLevel($nextLevel); + $log = new LevelUpLog(); + $log->setPerson($person->getPerson()); + $log->setLevel($nextLevel); + $log->setDate(new \DateTime()); + $this->levelUpLogRepository->add($log); + } + $this->em->persist($person); + $this->em->flush(); + + return $person; + } + + private function resetLevelUp(Person $person) { + $levelUps = $this->levelUpLogRepository->findBy(['person' => $person->getId()]); + foreach ($levelUps as $levelUp) { + $this->levelUpLogRepository->remove($levelUp); + } + } + + public function checkLoginGoal(GamificationPersonProfile $profile): bool { + return count($profile->getPerson()->getLogins()) >= 4; + } + + public function getPersonGamificationDTO(PbsUserDTO $pbsUserDTO, String $locale): PersonGamificationDTO + { + $levels = $this->levelRepository->findBy(['type' => Level::USER]); + /** @var Person $person */ + $person = $this->personRepository->find($pbsUserDTO->getId()); + $personGamification = $this->getPersonGamification($person); + $personGamification = $this->checkLevelUp($personGamification); + + $personGamificationDTO = GamificationPersonProfileMapper::createFromEntity($personGamification, $locale); + + if (count($levels) === 0) { + throw new \Exception('no levels found?!'); + } + $levelDtos = []; + foreach ($levels as $level) { + $levelDto = GamificationLevelMapper::createFromEntity($level, $locale); + if ($personGamification->getLevel()->getNextKey() === $level->getKey()) { + $levelDto->setActive(true); + } + $goalDTOs = []; + $goals = $level->getGoals(); + foreach ($goals as $goal) { + switch ($goal->getKey()) { + case 'FIRST_LOGIN': + $goalDTOs[] = GamificationGoalMapper::createFromEntity($goal, $locale, true, 0); + break; + case 'CARD_LAYERS': + $goalDTOs[] = GamificationGoalMapper::createFromEntity($goal, $locale, $personGamification->getHasUsedCardLayer(), 0); + break; + case 'DATAFILTER': + $goalDTOs[] = GamificationGoalMapper::createFromEntity($goal, $locale, $personGamification->getHasUsedDatafilter(), 0); + break; + case 'TIMEFILTER': + $goalDTOs[] = GamificationGoalMapper::createFromEntity($goal, $locale, $personGamification->getHasUsedTimefilter(), 0); + break; + case 'SHARE_WITH_PARENTS': + $goalDTOs[] = GamificationGoalMapper::createFromEntity($goal, $locale, $personGamification->getHasSharedEl(), 0); + break; + case 'EL_FILL_OUT': // TODO add check + $goalDTOs[] = GamificationGoalMapper::createFromEntity($goal, $locale, $personGamification->getElFilledOut(), 0); + break; + case 'SHARE_1': + $completed = $personGamification->getAccessGrantedCount() >= 1; + $goalDTOs[] = GamificationGoalMapper::createFromEntity($goal, $locale, $completed, $personGamification->getAccessGrantedCount()); + break; + case 'EL_IRRELEVANT': // TODO add check + $goalDTOs[] = GamificationGoalMapper::createFromEntity($goal, $locale, $personGamification->getElIrrelevant(), 0); + break; + case 'EL_CHANGE':// TODO add check + $goalDTOs[] = GamificationGoalMapper::createFromEntity($goal, $locale, $personGamification->getElRevised(), 0); + break; + case 'EL_IMPROVE':// TODO add check + $goalDTOs[] = GamificationGoalMapper::createFromEntity($goal, $locale, $personGamification->getElImproved(), 0); + break; + case 'LOGIN_FOUR_A_YEAR': // TODO persist this in $pgp + $logins = $person->getLogins(); + $goalDTOs[] = GamificationGoalMapper::createFromEntity($goal, $locale, $this->checkLoginGoal($personGamification), count($logins)); + break; + case 'SHARE_THREE': + $completed = $personGamification->getAccessGrantedCount() >= 3; + $goalDTOs[] = GamificationGoalMapper::createFromEntity($goal, $locale, $completed, $personGamification->getAccessGrantedCount()); + break; + default: + throw new \Exception('Couldnt find goal'); + break; + } + } + if (count($goalDTOs) !== 0) { + $levelDto->setGoals(array_reverse($goalDTOs)); + $levelDtos[] = $levelDto; + } + } + if ($personGamificationDTO->isLevelUp()) { + $this->resetLevelUp($person); + } + $personGamificationDTO->setLevels($levelDtos); + return $personGamificationDTO; + } +} diff --git a/src/Service/Gamification/QuapGamificationService.php b/src/Service/Gamification/QuapGamificationService.php new file mode 100644 index 0000000..2a470e9 --- /dev/null +++ b/src/Service/Gamification/QuapGamificationService.php @@ -0,0 +1,73 @@ +personGamificationService = $personGamificationService; + $this->aggregatedQuapRepository = $aggregatedQuapRepository; + } + + /** + * Answered State: Erfüllt = 1, Meistens Erfüllt = 2, ... , Nicht Relevant = 5 + * @param array $newAnswers + * @param Group $group + * @return string + */ + public function processQuapEvent(array $newAnswers, Group $group, PbsUserDTO $pbsUserDTO) + { + $aggregatedQuap = $this->aggregatedQuapRepository->findCurrentForGroup($group->getId()); + $oldAnswers = $aggregatedQuap->getAnswers(); + + $changedQuestionnaires = []; + $irrelevant = false; + $improvement = false; + + for ($questionnaireIndex = 0; $questionnaireIndex < count($oldAnswers); $questionnaireIndex++) { + for ($i = 0; $i < count($oldAnswers[$questionnaireIndex]); $i++) { + if ($oldAnswers[$questionnaireIndex][$i] !== $newAnswers[$questionnaireIndex][$i]) { + $changedQuestionnaires[] = $questionnaireIndex; + if ($newAnswers[$questionnaireIndex][$i] === 5) { + $irrelevant = true; + } + if ($newAnswers[$questionnaireIndex][$i] !== 4 && $newAnswers[$questionnaireIndex][$i] < $oldAnswers[$questionnaireIndex][$i]) { + $improvement = true; + } + //$textchanges .= 'Question ' . $questionnaireIndex . '.' . $i . ': ' . $oldAnswers[$questionnaireIndex][$i] . '/' . $newAnswers[$questionnaireIndex][$i] . '; '; + } + } + } + + foreach ($changedQuestionnaires as $index) { + if ($this->isQuestionnaireFullyAnswered($newAnswers[$index])) { + $this->personGamificationService->genericGoalProgress($pbsUserDTO, 'revised'); + } + } + if ($irrelevant) { + $this->personGamificationService->genericGoalProgress($pbsUserDTO, 'irrelevant'); + } + if ($improvement) { + $this->personGamificationService->genericGoalProgress($pbsUserDTO, 'improvement'); + } + } + + private function isQuestionnaireFullyAnswered($questionnaire) { + foreach ($questionnaire as $answer) { + if ($answer === 4 || $answer === 0) { + return false; + } + } + return true; + } +} From faae2e4fc518aa77c614f8b538ded9519c0bd3df Mon Sep 17 00:00:00 2001 From: Sebastian Date: Wed, 11 Sep 2024 11:57:17 +0200 Subject: [PATCH 81/86] Add placeholder text --- imports/gamification.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/imports/gamification.json b/imports/gamification.json index 90174d3..eb7159f 100644 --- a/imports/gamification.json +++ b/imports/gamification.json @@ -265,7 +265,7 @@ "de": { "title": "Eine Abteilung an 3 oder mehr Personen freigegeben.", "information": "", - "help": "" + "help": "Du hast % von 3 Freigaben" }, "fr": { "title": "french", From a22c9af6cdc029faf9b12de73728984356fbe180 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Wed, 11 Sep 2024 13:24:26 +0200 Subject: [PATCH 82/86] adjust level restrictions and change revision goal check --- src/Service/Gamification/PersonGamificationService.php | 6 +++--- src/Service/Gamification/QuapGamificationService.php | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Service/Gamification/PersonGamificationService.php b/src/Service/Gamification/PersonGamificationService.php index b583999..7b6a3f4 100644 --- a/src/Service/Gamification/PersonGamificationService.php +++ b/src/Service/Gamification/PersonGamificationService.php @@ -102,17 +102,17 @@ public function genericGoalProgress(PbsUserDTO $pbsUserDTO, string $type) { $pgp->setAccessGrantedCount($newCount); break; case 'revised': - if ($pgp->getLevel()->getKey() >= 2) { + if ($pgp->getLevel()->getKey() >= 1) { $pgp->setElRevised(true); } break; case 'improvement': - if ($pgp->getLevel()->getKey() >= 3) { + if ($pgp->getLevel()->getKey() >= 2) { $pgp->setElImproved(true); } break; case 'irrelevant': - if ($pgp->getLevel()->getKey() >= 2) { + if ($pgp->getLevel()->getKey() >= 1) { $pgp->setElIrrelevant(true); } break; diff --git a/src/Service/Gamification/QuapGamificationService.php b/src/Service/Gamification/QuapGamificationService.php index 2a470e9..f0e09bc 100644 --- a/src/Service/Gamification/QuapGamificationService.php +++ b/src/Service/Gamification/QuapGamificationService.php @@ -64,7 +64,7 @@ public function processQuapEvent(array $newAnswers, Group $group, PbsUserDTO $pb private function isQuestionnaireFullyAnswered($questionnaire) { foreach ($questionnaire as $answer) { - if ($answer === 4 || $answer === 0) { + if ($answer === 0) { return false; } } From dcd889d16a423118f720aa1a71bbd2240fb54a4b Mon Sep 17 00:00:00 2001 From: Sebastian Date: Wed, 11 Sep 2024 13:37:20 +0200 Subject: [PATCH 83/86] lint --- src/Command/ImportGamificationCommand.php | 9 ++++---- src/Command/PseudonymizeLoginCommand.php | 7 +++--- src/Controller/Api/GamificationController.php | 16 ++++++-------- .../GamificationPersonProfileMapper.php | 4 ++-- src/DTO/Model/Gamification/GoalDTO.php | 1 - src/DTO/Model/Gamification/LevelDTO.php | 1 - .../Gamification/PersonGamificationDTO.php | 1 - .../GamificationPersonProfile.php | 2 +- src/Entity/Gamification/Level.php | 2 -- .../Gamification/LoginRepository.php | 5 +++-- .../Security/PermissionRepository.php | 2 -- src/Service/Gamification/LoginService.php | 9 ++++---- .../PersonGamificationService.php | 22 +++++++++++-------- .../Gamification/QuapGamificationService.php | 6 ++--- 14 files changed, 43 insertions(+), 44 deletions(-) diff --git a/src/Command/ImportGamificationCommand.php b/src/Command/ImportGamificationCommand.php index 3692fc9..a4c488e 100644 --- a/src/Command/ImportGamificationCommand.php +++ b/src/Command/ImportGamificationCommand.php @@ -14,7 +14,6 @@ class ImportGamificationCommand extends StatisticsCommand { - /** @var EntityManagerInterface $em */ private EntityManagerInterface $em; @@ -45,7 +44,7 @@ protected function configure() protected function execute(InputInterface $input, OutputInterface $output): int { - $start = microtime( true); + $start = microtime(true); $json = json_decode(file_get_contents($this->pathToJson), true); $this->em->getConnection()->executeQuery('DELETE FROM gamification_person_profile'); @@ -68,7 +67,8 @@ protected function execute(InputInterface $input, OutputInterface $output): int return 0; } - protected function importLevels(array $jsonLevels, OutputInterface $output) { + protected function importLevels(array $jsonLevels, OutputInterface $output) + { foreach ($jsonLevels as $jsonLevel) { $level = $this->levelRepository->findOneBy(["key" => $jsonLevel["key"]]); if (is_null($level)) { @@ -90,7 +90,8 @@ protected function importLevels(array $jsonLevels, OutputInterface $output) { $this->em->flush(); } - protected function importGoals(array $jsonGoals, OutputInterface $output) { + protected function importGoals(array $jsonGoals, OutputInterface $output) + { foreach ($jsonGoals as $jsonGoal) { $goal = $this->goalRepository->findOneBy(['key' => $jsonGoal['key']]); if (is_null($goal)) { diff --git a/src/Command/PseudonymizeLoginCommand.php b/src/Command/PseudonymizeLoginCommand.php index 0e2e0be..86294ed 100644 --- a/src/Command/PseudonymizeLoginCommand.php +++ b/src/Command/PseudonymizeLoginCommand.php @@ -13,7 +13,6 @@ class PseudonymizeLoginCommand extends StatisticsCommand { - private LoginRepository $loginRepository; private float $duration; @@ -34,8 +33,10 @@ protected function configure() protected function execute(InputInterface $input, OutputInterface $output): int { - $start = microtime( true); - $pseudonymizedLogins = $this->loginRepository->pseudonymizeAllOlderThan18Months(function ($personId) { return hash('sha256', $personId);}); // sha256 is mostly collision free and currently irreversible, sufficient for our purposes. + $start = microtime(true); + $pseudonymizedLogins = $this->loginRepository->pseudonymizeAllOlderThan18Months(function ($personId) { + return hash('sha256', $personId); + }); // sha256 is mostly collision free and currently irreversible, sufficient for our purposes. $log = $input->getOption('log'); if ($log) { $output->writeln('Following Logins (id) have been pseudonymized:'); diff --git a/src/Controller/Api/GamificationController.php b/src/Controller/Api/GamificationController.php index 4153451..c8b87be 100644 --- a/src/Controller/Api/GamificationController.php +++ b/src/Controller/Api/GamificationController.php @@ -43,8 +43,7 @@ public function postGroupChange( public function usedCardLayer( Request $request, PersonGamificationService $personGamificationService - ) - { + ) { $personGamificationService->genericGoalProgress($this->getUser(), 'card'); return new Response('', 200); } @@ -52,8 +51,7 @@ public function usedCardLayer( public function usedDataFilter( Request $request, PersonGamificationService $personGamificationService - ) - { + ) { $personGamificationService->genericGoalProgress($this->getUser(), 'data'); return new Response('', 200); } @@ -61,15 +59,15 @@ public function usedDataFilter( public function usedTimeFilter( Request $request, PersonGamificationService $personGamificationService - ) - { + ) { $personGamificationService->genericGoalProgress($this->getUser(), 'time'); return new Response('', 200); } - public function getUserProfile(Request $request, - PersonGamificationService $personGamificationService) - { + public function getUserProfile( + Request $request, + PersonGamificationService $personGamificationService + ) { $dto = $personGamificationService->getPersonGamificationDTO($this->getUser(), $request->getLocale()); return $this->json($dto); } diff --git a/src/DTO/Mapper/GamificationPersonProfileMapper.php b/src/DTO/Mapper/GamificationPersonProfileMapper.php index 929fe46..9651f10 100644 --- a/src/DTO/Mapper/GamificationPersonProfileMapper.php +++ b/src/DTO/Mapper/GamificationPersonProfileMapper.php @@ -9,7 +9,6 @@ class GamificationPersonProfileMapper { - public static function createFromEntity(GamificationPersonProfile $profile, string $locale): PersonGamificationDTO { $dto = new PersonGamificationDTO(); @@ -27,7 +26,8 @@ public static function createFromEntity(GamificationPersonProfile $profile, stri return $dto; } - public static function checkLevelUp(GamificationPersonProfile $profile) { + public static function checkLevelUp(GamificationPersonProfile $profile) + { $levelUps = $profile->getPerson()->getLevelUps(); if (count($levelUps) > 0) { return true; diff --git a/src/DTO/Model/Gamification/GoalDTO.php b/src/DTO/Model/Gamification/GoalDTO.php index bc91c4c..ae01e89 100644 --- a/src/DTO/Model/Gamification/GoalDTO.php +++ b/src/DTO/Model/Gamification/GoalDTO.php @@ -106,5 +106,4 @@ public function setRequired(bool $required): void { $this->required = $required; } - } diff --git a/src/DTO/Model/Gamification/LevelDTO.php b/src/DTO/Model/Gamification/LevelDTO.php index 9fbcdba..97aa26a 100644 --- a/src/DTO/Model/Gamification/LevelDTO.php +++ b/src/DTO/Model/Gamification/LevelDTO.php @@ -90,5 +90,4 @@ public function setGoals(array $goals): void { $this->goals = $goals; } - } diff --git a/src/DTO/Model/Gamification/PersonGamificationDTO.php b/src/DTO/Model/Gamification/PersonGamificationDTO.php index 180d3fe..042b0e0 100644 --- a/src/DTO/Model/Gamification/PersonGamificationDTO.php +++ b/src/DTO/Model/Gamification/PersonGamificationDTO.php @@ -90,5 +90,4 @@ public function setLevels(array $levels): void { $this->levels = $levels; } - } diff --git a/src/Entity/Gamification/GamificationPersonProfile.php b/src/Entity/Gamification/GamificationPersonProfile.php index 3d1fb83..b50aeb9 100644 --- a/src/Entity/Gamification/GamificationPersonProfile.php +++ b/src/Entity/Gamification/GamificationPersonProfile.php @@ -74,7 +74,7 @@ class GamificationPersonProfile * @ORM\Column(type="boolean") */ private $el_improved; - + public function getId(): ?int { diff --git a/src/Entity/Gamification/Level.php b/src/Entity/Gamification/Level.php index 308f09a..c53a1af 100644 --- a/src/Entity/Gamification/Level.php +++ b/src/Entity/Gamification/Level.php @@ -197,6 +197,4 @@ public function setKey($key): void { $this->key = $key; } - - } diff --git a/src/Repository/Gamification/LoginRepository.php b/src/Repository/Gamification/LoginRepository.php index 7bf2103..f3f33e7 100644 --- a/src/Repository/Gamification/LoginRepository.php +++ b/src/Repository/Gamification/LoginRepository.php @@ -25,9 +25,10 @@ public function __construct(ManagerRegistry $registry) parent::__construct($registry, Login::class); } - public function findAllActiveBy(array $criteria, array $orderBy = null, $limit = null, $offset = null) { + public function findAllActiveBy(array $criteria, array $orderBy = null, $limit = null, $offset = null) + { $criteria['hashed_id'] = null; - return $this->findBy($criteria,$orderBy, $limit, $offset); + return $this->findBy($criteria, $orderBy, $limit, $offset); } /** diff --git a/src/Repository/Security/PermissionRepository.php b/src/Repository/Security/PermissionRepository.php index 83a19a6..eff4d50 100644 --- a/src/Repository/Security/PermissionRepository.php +++ b/src/Repository/Security/PermissionRepository.php @@ -196,6 +196,4 @@ public function endAllOpenPermissions(): void ] ); } - - } diff --git a/src/Service/Gamification/LoginService.php b/src/Service/Gamification/LoginService.php index 6ffd31a..2040d5e 100644 --- a/src/Service/Gamification/LoginService.php +++ b/src/Service/Gamification/LoginService.php @@ -26,15 +26,15 @@ public function __construct( LoginRepository $loginRepository, GroupRepository $groupRepository, PermissionRepository $permissionRepository - ) - { + ) { $this->personRepository = $personRepository; $this->loginRepository = $loginRepository; $this->groupRepository = $groupRepository; $this->permissionRepository = $permissionRepository; } - public function logByUserDTOForLogin(PbsUserDTO $userDTO):Login { + public function logByUserDTOForLogin(PbsUserDTO $userDTO): Login + { $login = new Login(); $activeGroupDTO = $userDTO->getGroups()[0]; // Group 0 is the active group on Login. @@ -63,7 +63,8 @@ public function logByUserDTOForLogin(PbsUserDTO $userDTO):Login { return $login; } - public function logByPersonAndGroup(PbsUserDTO $userDTO, Group $group) { + public function logByPersonAndGroup(PbsUserDTO $userDTO, Group $group) + { $login = new Login(); $person = $this->personRepository->find($userDTO->getId()); $role = $this->permissionRepository->findHighestById($group, $person->getId()); diff --git a/src/Service/Gamification/PersonGamificationService.php b/src/Service/Gamification/PersonGamificationService.php index 7b6a3f4..bd2838c 100644 --- a/src/Service/Gamification/PersonGamificationService.php +++ b/src/Service/Gamification/PersonGamificationService.php @@ -43,8 +43,7 @@ public function __construct( PersonRepository $personRepository, EntityManagerInterface $em, LevelUpLogRepository $levelUpLogRepository - ) - { + ) { $this->loginRepository = $loginRepository; $this->levelRepository = $levelRepository; $this->personRepository = $personRepository; @@ -53,7 +52,8 @@ public function __construct( $this->levelUpLogRepository = $levelUpLogRepository; } - public function reset(PbsUserDTO $pbsUserDTO) { + public function reset(PbsUserDTO $pbsUserDTO) + { $person = $this->personRepository->find($pbsUserDTO->getId()); $pgp = $this->getPersonGamification($person); $this->personGoalRepository->remove($pgp); @@ -62,7 +62,7 @@ public function reset(PbsUserDTO $pbsUserDTO) { public function getPersonGamification(Person $person): GamificationPersonProfile { $gamificationProfile = $person->getGamification(); - if(is_null($gamificationProfile)) { + if (is_null($gamificationProfile)) { $gamificationProfile = new GamificationPersonProfile(); $gamificationProfile->setLevel($this->levelRepository->findOneBy(['key' => 0])); $gamificationProfile->setPerson($person); @@ -80,7 +80,8 @@ public function getPersonGamification(Person $person): GamificationPersonProfile return $gamificationProfile; } - public function genericGoalProgress(PbsUserDTO $pbsUserDTO, string $type) { + public function genericGoalProgress(PbsUserDTO $pbsUserDTO, string $type) + { $person = $this->personRepository->find($pbsUserDTO->getId()); $pgp = $this->getPersonGamification($person); @@ -127,7 +128,8 @@ public function genericGoalProgress(PbsUserDTO $pbsUserDTO, string $type) { $this->em->flush(); } - public function checkLevelUp(GamificationPersonProfile $person) { + public function checkLevelUp(GamificationPersonProfile $person) + { $currentLevel = $person->getLevel(); $nextLevel = $this->levelRepository->findNextLevel($currentLevel); @@ -178,18 +180,20 @@ public function checkLevelUp(GamificationPersonProfile $person) { return $person; } - private function resetLevelUp(Person $person) { + private function resetLevelUp(Person $person) + { $levelUps = $this->levelUpLogRepository->findBy(['person' => $person->getId()]); foreach ($levelUps as $levelUp) { $this->levelUpLogRepository->remove($levelUp); } } - public function checkLoginGoal(GamificationPersonProfile $profile): bool { + public function checkLoginGoal(GamificationPersonProfile $profile): bool + { return count($profile->getPerson()->getLogins()) >= 4; } - public function getPersonGamificationDTO(PbsUserDTO $pbsUserDTO, String $locale): PersonGamificationDTO + public function getPersonGamificationDTO(PbsUserDTO $pbsUserDTO, string $locale): PersonGamificationDTO { $levels = $this->levelRepository->findBy(['type' => Level::USER]); /** @var Person $person */ diff --git a/src/Service/Gamification/QuapGamificationService.php b/src/Service/Gamification/QuapGamificationService.php index f0e09bc..9ab0a79 100644 --- a/src/Service/Gamification/QuapGamificationService.php +++ b/src/Service/Gamification/QuapGamificationService.php @@ -13,8 +13,7 @@ class QuapGamificationService public function __construct( PersonGamificationService $personGamificationService, AggregatedQuapRepository $aggregatedQuapRepository - ) - { + ) { $this->personGamificationService = $personGamificationService; $this->aggregatedQuapRepository = $aggregatedQuapRepository; } @@ -62,7 +61,8 @@ public function processQuapEvent(array $newAnswers, Group $group, PbsUserDTO $pb } } - private function isQuestionnaireFullyAnswered($questionnaire) { + private function isQuestionnaireFullyAnswered($questionnaire) + { foreach ($questionnaire as $answer) { if ($answer === 0) { return false; From 727462ade12990bf21e273dd3066eadb80186c85 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Wed, 11 Sep 2024 13:49:13 +0200 Subject: [PATCH 84/86] add auto linting command to readme --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index c3929d6..cc5a196 100644 --- a/README.md +++ b/README.md @@ -77,6 +77,9 @@ We use the PSR-12 PHP standard. You can check your code using the following comm `docker exec healthcheck-core-local php vendor/bin/phpcs --standard=PSR12 --report=full --ignore=src/Migrations/ --runtime-set ignore_warnings_on_exit 1 src/` +Auto linting: +`docker exec healthcheck-core-local php vendor/bin/phpcbf --standard=PSR12 --report=full --ignore=src/Migrations/ --runtime-set ignore_warnings_on_exit 1 src/` + #### Running Tests To run tests locally make sure to use the `env.test` instead of the created `.env`. You can do that by replacing the @@ -85,4 +88,4 @@ restart the healthcheck-core service container in order for the changes to take Once you are up and running with the new env run: -`docker exec healthcheck-core-local php bin/phpunit` \ No newline at end of file +`docker exec healthcheck-core-local php bin/phpunit` From 094b15e19e3b0bf1c4c6104cbb0608f4eb781657 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Wed, 11 Sep 2024 13:54:15 +0200 Subject: [PATCH 85/86] change "docker-compose" to "docker compose" for test files --- .github/workflows/test.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c7f94ea..324448f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -15,9 +15,9 @@ jobs: - name: setup test env run: cp -f .env.test .env - name: Build test image - run: docker-compose -f docker/docker-compose.yml build --build-arg BUILD_TEST=1 healthcheck-core + run: docker compose -f docker/docker-compose.yml build --build-arg BUILD_TEST=1 healthcheck-core - name: Start compose - run: docker-compose -f docker/docker-compose.yml up -d + run: docker compose -f docker/docker-compose.yml up -d - name: Install dependencies run: docker exec --user=$(id -u) healthcheck-core-local composer install --no-interaction --no-scripts - name: Lint PSR12 @@ -36,6 +36,6 @@ jobs: - name: docker cleanup run: | cp -f .env.test .env - docker-compose -f docker/docker-compose.yml down + docker compose -f docker/docker-compose.yml down docker image prune -f docker volume prune -f From 64bbf4156d636d1e1984932d574082cc5e552bb4 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Wed, 11 Sep 2024 14:16:59 +0200 Subject: [PATCH 86/86] add placeholders --- imports/gamification.json | 46 +++++++++++++++++++-------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/imports/gamification.json b/imports/gamification.json index eb7159f..d6e56ca 100644 --- a/imports/gamification.json +++ b/imports/gamification.json @@ -44,8 +44,8 @@ "required": true, "de": { "title": "Erstes Login", - "information": "Testing: Ist immer erfüllt", - "help": "" + "information": "Login Information (Testing: Ist immer erfüllt)", + "help": "(nicht nötig)" }, "fr": { "title": "french", @@ -64,8 +64,8 @@ "required": true, "de": { "title": "Unterschiedliche Layer der Karte benutzt", - "information": "", - "help": "" + "information": "Kartenlayer information", + "help": "(nicht nötig)" }, "fr": { "title": "french", @@ -84,8 +84,8 @@ "required": false, "de": { "title": "Datenfilter in Übersicht benutzt", - "information": "", - "help": "" + "information": "Datenfilter information", + "help": "(nicht nötig)" }, "fr": { "title": "french", @@ -104,8 +104,8 @@ "required": false, "de": { "title": "zeitbereichsansicht benutzt", - "information": "", - "help": "" + "information": "Zeitbereichsansicht information (Im Zeitfilter Zeitspanne auswählen)", + "help": "(nicht nötig)" }, "fr": { "title": "french", @@ -124,8 +124,8 @@ "required": false, "de": { "title": "Freigabe für übergeordnete Ebene.", - "information": "", - "help": "" + "information": "EL Freigabe information (EL an Kanton freigeben)", + "help": "(nicht nötig)" }, "fr": { "title": "french", @@ -144,8 +144,8 @@ "required": true, "de": { "title": "Alle Erfolgslogik Kasten einer Gruppe Ausgefüllt.", - "information": "Testing: noch nicht freigegeben (WIP)", - "help": "" + "information": "EL alle Kasten information (WIP)", + "help": "EL alle kasten ausfüllen hilfe" }, "fr": { "title": "french", @@ -164,8 +164,8 @@ "required": false, "de": { "title": "Freigabe für mindestens eine andere Person.", - "information": "", - "help": "" + "information": "Freigabe1 information", + "help": "(nicht nötig)" }, "fr": { "title": "french", @@ -184,8 +184,8 @@ "required": false, "de": { "title": "In der Erfolgslogik nicht relevante Fragen ausgebledet", - "information": "", - "help": "" + "information": "EL irrelevant information", + "help": "(nicht nötig)" }, "fr": { "title": "french", @@ -204,8 +204,8 @@ "required": false, "de": { "title": "Einen Erfolgslogik Kasten überarbeitet.", - "information": "", - "help": "" + "information": "El überarbeiten information", + "help": "(nicht nötig)" }, "fr": { "title": "french", @@ -224,8 +224,8 @@ "required": true, "de": { "title": "Verbesserung in einem Erfolgslogik Kasten.", - "information": "", - "help": "" + "information": "El verbessern information", + "help": "(nicht nötig)" }, "fr": { "title": "french", @@ -244,8 +244,8 @@ "required": false, "de": { "title": "Vier mal innerhalb eines Jahres eingeloggt.", - "information": "", - "help": "" + "information": "Login information", + "help": "Du hast dich % mal eingeloggt" }, "fr": { "title": "french", @@ -264,7 +264,7 @@ "required": false, "de": { "title": "Eine Abteilung an 3 oder mehr Personen freigegeben.", - "information": "", + "information": "Freigabe3 information", "help": "Du hast % von 3 Freigaben" }, "fr": {