diff --git a/config/autoload/doctrine.local.development.php.dist b/config/autoload/doctrine.local.development.php.dist index dc6f9addf0..c0295ca50b 100644 --- a/config/autoload/doctrine.local.development.php.dist +++ b/config/autoload/doctrine.local.development.php.dist @@ -20,6 +20,7 @@ declare(strict_types=1); +use Decision\Extensions\Doctrine\MeetingTypesType; use Doctrine\DBAL\Driver\PDO\MySQL\Driver; return [ @@ -41,6 +42,9 @@ return [ PDO::MYSQL_ATTR_SSL_VERIFY_SERVER_CERT => true, ] : [], ], + 'doctrineTypeMappings' => [ + MeetingTypesType::NAME => MeetingTypesType::NAME, + ], ], ], // Configuration details for the ORM. @@ -73,6 +77,9 @@ return [ ], // Second level cache configuration (see doc to learn about configuration) 'second_level_cache' => [], + 'types' => [ + MeetingTypesType::NAME => MeetingTypesType::class, + ], ], ], 'migrations_configuration' => [ diff --git a/module/Application/src/Extensions/Doctrine/BackedEnumType.php b/module/Application/src/Extensions/Doctrine/BackedEnumType.php new file mode 100644 index 0000000000..1549dba82c --- /dev/null +++ b/module/Application/src/Extensions/Doctrine/BackedEnumType.php @@ -0,0 +1,72 @@ + $enumClass + * @required + */ + public string $enumClass; + + /** + * @required + */ + public const string NAME = ''; + + /** + * {@inheritDoc} + * + * @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingTraversableTypeHintSpecification + */ + public function getSQLDeclaration( + array $column, + AbstractPlatform $platform, + ): string { + return $platform->getStringTypeDeclarationSQL($column); + } + + /** + * @return T|null + */ + public function convertToPHPValue( + mixed $value, + AbstractPlatform $platform, + ) { + if (empty($value)) { + return null; + } + + return call_user_func([$this->enumClass, 'from'], $value); + } + + /** + * @phpcsSuppress SlevomatCodingStandard.TypeHints.ReturnTypeHint.MissingAnyTypeHint + */ + public function convertToDatabaseValue( + mixed $value, + AbstractPlatform $platform, + ) { + return $value instanceof $this->enumClass ? $value->value : $value; + } +} diff --git a/module/Decision/src/Extensions/Doctrine/MeetingTypesType.php b/module/Decision/src/Extensions/Doctrine/MeetingTypesType.php new file mode 100644 index 0000000000..ae9b3f9243 --- /dev/null +++ b/module/Decision/src/Extensions/Doctrine/MeetingTypesType.php @@ -0,0 +1,23 @@ + + */ +class MeetingTypesType extends BackedEnumType +{ + public string $enumClass = MeetingTypes::class; + + public const string NAME = 'meeting_types'; + + public function getName(): string + { + return self::NAME; + } +} diff --git a/module/Decision/src/Model/Decision.php b/module/Decision/src/Model/Decision.php index 75bb14ada7..b54e7bda92 100644 --- a/module/Decision/src/Model/Decision.php +++ b/module/Decision/src/Model/Decision.php @@ -4,6 +4,7 @@ namespace Decision\Model; +use Decision\Extensions\Doctrine\MeetingTypesType; use Decision\Model\Enums\MeetingTypes; use Decision\Model\SubDecision\Annulment; use Doctrine\Common\Collections\ArrayCollection; @@ -48,10 +49,7 @@ class Decision * NOTE: This is a hack to make the meeting a primary key here. */ #[Id] - #[Column( - type: 'string', - enumType: MeetingTypes::class, - )] + #[Column(type: MeetingTypesType::NAME)] protected MeetingTypes $meeting_type; /** diff --git a/module/Decision/src/Model/Meeting.php b/module/Decision/src/Model/Meeting.php index 4908f9e167..d96cb69cbb 100644 --- a/module/Decision/src/Model/Meeting.php +++ b/module/Decision/src/Model/Meeting.php @@ -5,6 +5,7 @@ namespace Decision\Model; use DateTime; +use Decision\Extensions\Doctrine\MeetingTypesType; use Decision\Model\Enums\MeetingTypes; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; @@ -25,10 +26,7 @@ class Meeting * Meeting type. */ #[Id] - #[Column( - type: 'string', - enumType: MeetingTypes::class, - )] + #[Column(type: MeetingTypesType::NAME)] protected MeetingTypes $type; /** diff --git a/module/Decision/src/Model/MeetingMinutes.php b/module/Decision/src/Model/MeetingMinutes.php index 0fbd385b60..bfc8376ad5 100644 --- a/module/Decision/src/Model/MeetingMinutes.php +++ b/module/Decision/src/Model/MeetingMinutes.php @@ -5,6 +5,7 @@ namespace Decision\Model; use Application\Model\Traits\TimestampableTrait; +use Decision\Extensions\Doctrine\MeetingTypesType; use Decision\Model\Enums\MeetingTypes; use Doctrine\ORM\Mapping\Column; use Doctrine\ORM\Mapping\Entity; @@ -27,10 +28,7 @@ class MeetingMinutes implements ResourceInterface * Meeting type. */ #[Id] - #[Column( - type: 'string', - enumType: MeetingTypes::class, - )] + #[Column(type: MeetingTypesType::NAME)] protected MeetingTypes $meeting_type; /** diff --git a/module/Decision/src/Model/SubDecision.php b/module/Decision/src/Model/SubDecision.php index 12e356d247..579367097c 100644 --- a/module/Decision/src/Model/SubDecision.php +++ b/module/Decision/src/Model/SubDecision.php @@ -4,6 +4,7 @@ namespace Decision\Model; +use Decision\Extensions\Doctrine\MeetingTypesType; use Decision\Model\Enums\MeetingTypes; use Decision\Model\SubDecision\Abrogation; use Decision\Model\SubDecision\Annulment; @@ -99,10 +100,7 @@ abstract class SubDecision * NOTE: This is a hack to make the decision a primary key here. */ #[Id] - #[Column( - type: 'string', - enumType: MeetingTypes::class, - )] + #[Column(type: MeetingTypesType::NAME)] protected MeetingTypes $meeting_type; /** diff --git a/module/Decision/test/Seeder/DecisionFixture.php b/module/Decision/test/Seeder/DecisionFixture.php new file mode 100644 index 0000000000..ec2dc76efb --- /dev/null +++ b/module/Decision/test/Seeder/DecisionFixture.php @@ -0,0 +1,276 @@ +setMeeting($this->getReference('meeting-BV-0', Meeting::class)); + $decision->setPoint(1); + $decision->setNumber(1); + $decision->setContent(''); + + $manager->persist($decision); + $this->addReference('decision-BV-0-' . $decision->getPoint() . '-' . $decision->getNumber(), $decision); + + $sequence = 1; + $iSubdecisions = []; + + $foundation = new Foundation(); + $foundation->setAbbr('GETÉST'); + $foundation->setName('GEWIS\'ers Testen Éigenlijk Structureel Te-weinig'); + $foundation->setOrganType(OrganTypes::Committee); + $foundation->setDecision($decision); + $foundation->setSequence($sequence); + $foundation->setContent(sprintf( + '%s %s met afkorting %s wordt opgericht.', + ucfirst($foundation->getOrganType()->value), // shortcut as getting the translator for `getName()` sucks. + $foundation->getName(), + $foundation->getAbbr(), + )); + + $manager->persist($foundation); + $iSubdecisions[] = $foundation; + $this->addReference('foundation-' . $foundation->getSequence(), $foundation); + + // phpcs:disable SlevomatCodingStandard.ControlStructures.EarlyExit.EarlyExitNotUsed + foreach (range(8005, 8024) as $lidnr) { + $sequence++; + $iSubdecisions[] = $this->createInstallation( + 'Lid', + $lidnr, + $sequence, + $foundation, + $decision, + $manager, + ); + + // Additional roles for specific members. + if (8005 === $lidnr) { + $sequence++; + $iSubdecisions[] = $this->createInstallation( + 'Voorzitter', + $lidnr, + $sequence, + $foundation, + $decision, + $manager, + ); + } + + if (8006 === $lidnr) { + $sequence++; + $iSubdecisions[] = $this->createInstallation( + 'Secretaris', + $lidnr, + $sequence, + $foundation, + $decision, + $manager, + ); + } + + // Will be discharged. + if (8020 === $lidnr) { + $sequence++; + $iSubdecisions[] = $this->createInstallation( + 'Penningmeester', + $lidnr, + $sequence, + $foundation, + $decision, + $manager, + ); + } + } + + // phpcs:enable SlevomatCodingStandard.ControlStructures.EarlyExit.EarlyExitNotUsed + + $content = []; + foreach ($decision->getSubdecisions() as $subdecision) { + $content[] = $subdecision->getContent(); + } + + $decision->setContent(implode('. ', $content)); + $manager->persist($decision); + + $manager->flush(); + + // Discharge of members of GETEST + $decision = new Decision(); + $decision->setMeeting($this->getReference('meeting-BV-1', Meeting::class)); + $decision->setPoint(1); + $decision->setNumber(1); + $decision->setContent(''); + + $manager->persist($decision); + $this->addReference('decision-BV-1-' . $decision->getPoint() . '-' . $decision->getNumber(), $decision); + + $sequence = 1; + $dSubdecisions = []; + + foreach (range(8020, 8024) as $lidnr) { + // Order of discharge matters, the discharge from a special function comes before `Lid`. + if (8020 === $lidnr) { + $dSubdecisions[] = $this->createDischarge( + $sequence, + $sequence + 18, // TODO: find a better way to calculate this. + $decision, + $manager, + ); + $sequence++; + } + + $dSubdecisions[] = $this->createDischarge( + $sequence, + $sequence + 18, // TODO: find a better way to calculate this. + $decision, + $manager, + ); + $sequence++; + } + + $content = []; + foreach ($decision->getSubdecisions() as $dSubdecision) { + $content[] = $dSubdecision->getContent(); + } + + $decision->setContent(implode('. ', $content)); + $manager->persist($decision); + + $manager->flush(); + + // Creation of the actual organ and its members here as well. This is because Doctrine sucks and breaks in the + // opposite way with the custom mapping type. + + // Foundation + $organ = new Organ(); + $organ->setName($foundation->getName()); + $organ->setAbbr($foundation->getAbbr()); + $organ->setFoundation($foundation); + $organ->setType($foundation->getOrganType()); + $organ->setFoundationDate($foundation->getDecision()->getMeeting()->getDate()); + + $manager->persist($organ); + $manager->flush(); + + // Installations + foreach ($iSubdecisions as $installation) { + if (!($installation instanceof Installation)) { + continue; + } + + $organMember = new OrganMember(); + $organMember->setOrgan($organ); + $organMember->setMember($installation->getMember()); + $organMember->setInstallation($installation); + $organMember->setFunction($installation->getFunction()); + $organMember->setInstallDate($installation->getFoundation()->getDecision()->getMeeting()->getDate()); + + $manager->persist($organMember); + $this->addReference('organMember-' . $installation->getSequence(), $organMember); + } + + $manager->flush(); + + // Discharges + foreach ($dSubdecisions as $discharge) { + $organMember = $this->getReference( + 'organMember-' . $discharge->getInstallation()->getSequence(), + OrganMember::class, + ); + $organMember->setDischargeDate($discharge->getDecision()->getMeeting()->getDate()); + + $manager->persist($organMember); + } + + $manager->flush(); + } + + private function createInstallation( + string $function, + int $lidnr, + int $sequence, + Foundation $foundation, + Decision $decision, + ObjectManager $manager, + ): Installation { + $installation = new Installation(); + $installation->setFunction($function); + $installation->setMember($this->getReference('member-' . $lidnr, Member::class)); + $installation->setSequence($sequence); + $installation->setFoundation($foundation); + $installation->setDecision($decision); + $installation->setContent( + sprintf( + '%s wordt geïnstalleerd als %s van %s', + $installation->getMember()->getFullName(), + $installation->getFunction(), + $installation->getFoundation()->getAbbr(), + ), + ); + + $manager->persist($installation); + $this->addReference('installation-' . $installation->getSequence(), $installation); + + return $installation; + } + + private function createDischarge( + int $sequence, + int $installationSequence, + Decision $decision, + ObjectManager $manager, + ): Discharge { + $discharge = new Discharge(); + $discharge->setInstallation($this->getReference('installation-' . $installationSequence, Installation::class)); + $discharge->setSequence($sequence); + $discharge->setDecision($decision); + $discharge->setContent( + sprintf( + '%s wordt gedechargeerd als %s van %s', + $discharge->getInstallation()->getMember()->getFullName(), + $discharge->getInstallation()->getFunction(), + $discharge->getInstallation()->getFoundation()->getAbbr(), + ), + ); + + $manager->persist($discharge); + $this->addReference('discharge-' . $discharge->getSequence(), $discharge); + + return $discharge; + } + + /** + * @return class-string[] + */ + public function getDependencies(): array + { + return [ + MeetingFixture::class, + ]; + } +}