diff --git a/Makefile b/Makefile index 5693aa39fc..3bee454cd8 100644 --- a/Makefile +++ b/Makefile @@ -42,6 +42,9 @@ rundev: builddev @make replenish @docker compose exec web rm -rf data/cache/module-config-cache.application.config.cache.php +migrate: replenish + @docker compose exec -it web ./orm migrations:migrate + migration-list: replenish @docker compose exec -T web ./orm migrations:list @@ -49,9 +52,6 @@ migration-diff: replenish @docker compose exec -T web ./orm migrations:diff @docker cp "$(shell docker compose ps -q web)":/code/module/Application/migrations ./module/Application/migrations -migration-migrate: replenish - @docker compose exec -it web ./orm migrations:migrate - migration-up: replenish migration-list @read -p "Enter the migration version to execute (e.g., Application\\Migrations\\Version20241020212355 -- note escaping the backslashes is required): " version; \ docker compose exec -it web ./orm migrations:execute --up $$version @@ -60,6 +60,9 @@ migration-down: replenish migration-list @read -p "Enter the migration version to down (e.g., Application\\Migrations\\Version20241020212355 -- note escaping the backslashes is required): " version; \ docker compose exec -it web ./orm migrations:execute --down $$version +seed: replenish + @docker compose exec -T web ./web application:fixtures:load + exec: docker compose exec -it web $(cmd) 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/config/module.config.php b/module/Application/config/module.config.php index 1ee5a56cac..d3128e6a03 100644 --- a/module/Application/config/module.config.php +++ b/module/Application/config/module.config.php @@ -4,6 +4,7 @@ namespace Application; +use Application\Command\LoadFixtures; use Application\Controller\Factory\IndexControllerFactory; use Application\Controller\IndexController; use Application\View\Helper\BootstrapElementError; @@ -148,6 +149,11 @@ 'message_separator_string' => '
  • ', ], ], + 'laminas-cli' => [ + 'commands' => [ + 'application:fixtures:load' => LoadFixtures::class, + ], + ], 'doctrine' => [ 'driver' => [ __NAMESPACE__ . '_driver' => [ diff --git a/module/Application/src/Command/Factory/LoadFixturesFactory.php b/module/Application/src/Command/Factory/LoadFixturesFactory.php new file mode 100644 index 0000000000..86002ca5e2 --- /dev/null +++ b/module/Application/src/Command/Factory/LoadFixturesFactory.php @@ -0,0 +1,27 @@ +get('doctrine.entitymanager.orm_default'); + + return new LoadFixtures($entityManager); + } +} diff --git a/module/Application/src/Command/LoadFixtures.php b/module/Application/src/Command/LoadFixtures.php new file mode 100644 index 0000000000..50e937e57e --- /dev/null +++ b/module/Application/src/Command/LoadFixtures.php @@ -0,0 +1,68 @@ +setPurgeMode(ORMPurger::PURGE_MODE_TRUNCATE); + $executor = new ORMExecutor($this->entityManager, $purger); + + foreach ($this::FIXTURES as $fixture) { + $loader->loadFromDirectory($fixture); + } + + $output->writeln('Loading fixtures into the database...'); + + $connection = $this->entityManager->getConnection(); + try { + // Temporarily disable FK constraint checks. This is necessary because large parts of our database do not have + // explicit CASCADEs set to prevent data loss when syncing with ReportDB (GEWISDB). + // The try-catch is necessary to hide some error messages (because the executeStatement). + $connection->executeStatement('SET FOREIGN_KEY_CHECKS = 0'); + $executor->execute($loader->getFixtures()); + $connection->executeStatement('SET FOREIGN_KEY_CHECKS = 1'); + } catch (Throwable) { + } + + $output->writeln('Loaded fixtures!'); + + return Command::SUCCESS; + } +} diff --git a/module/Application/src/Extensions/Doctrine/BackedEnumType.php b/module/Application/src/Extensions/Doctrine/BackedEnumType.php new file mode 100644 index 0000000000..ad98579961 --- /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); + } + + /** + * @param mixed $value + * + * @return T|null + */ + public function convertToPHPValue( + $value, + AbstractPlatform $platform, + ) { + if (empty($value)) { + return null; + } + + return call_user_func([$this->enumClass, 'from'], $value); + } + + public function convertToDatabaseValue( + $value, + AbstractPlatform $platform, + ) { + return $value instanceof $this->enumClass ? $value->value : $value; + } +} diff --git a/module/Application/src/Module.php b/module/Application/src/Module.php index d60dbf6fe9..7c1bf319f5 100644 --- a/module/Application/src/Module.php +++ b/module/Application/src/Module.php @@ -4,6 +4,8 @@ namespace Application; +use Application\Command\Factory\LoadFixturesFactory as LoadFixturesCommandFactory; +use Application\Command\LoadFixtures as LoadFixturesCommand; use Application\Extensions\CommonMark\CompanyImage\CompanyImageExtension; use Application\Extensions\CommonMark\NoImage\NoImageExtension; use Application\Extensions\CommonMark\VideoIframe\VideoIframeExtension; @@ -266,6 +268,7 @@ public function generateSignature( return new UrlBuilder($config['glide']['base_url'], $signature); }, + LoadFixturesCommand::class => LoadFixturesCommandFactory::class, ], ]; } 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/AssociationYear.php b/module/Decision/src/Model/AssociationYear.php index b5541f970a..b6fa36ccf6 100644 --- a/module/Decision/src/Model/AssociationYear.php +++ b/module/Decision/src/Model/AssociationYear.php @@ -15,8 +15,8 @@ class AssociationYear /** * A GEWIS association year starts 01-07. */ - public const ASSOCIATION_YEAR_START_MONTH = 7; - public const ASSOCIATION_YEAR_START_DAY = 1; + public const int ASSOCIATION_YEAR_START_MONTH = 7; + public const int ASSOCIATION_YEAR_START_DAY = 1; /** @var int the first calendar year of the association year */ protected int $firstYear; diff --git a/module/Decision/src/Model/Decision.php b/module/Decision/src/Model/Decision.php index 815b7b25e3..b54e7bda92 100644 --- a/module/Decision/src/Model/Decision.php +++ b/module/Decision/src/Model/Decision.php @@ -4,8 +4,10 @@ namespace Decision\Model; +use Decision\Extensions\Doctrine\MeetingTypesType; use Decision\Model\Enums\MeetingTypes; use Decision\Model\SubDecision\Annulment; +use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping\Column; use Doctrine\ORM\Mapping\Entity; @@ -47,11 +49,9 @@ 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; + /** * Meeting number. * @@ -103,7 +103,12 @@ enumType: MeetingTypes::class, targetEntity: Annulment::class, mappedBy: 'target', )] - protected Annulment $annulledBy; + protected ?Annulment $annulledBy = null; + + public function __construct() + { + $this->subdecisions = new ArrayCollection(); + } /** * Set the meeting. 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/src/Model/SubDecision/Installation.php b/module/Decision/src/Model/SubDecision/Installation.php index 667cc904f7..61f68d8930 100644 --- a/module/Decision/src/Model/SubDecision/Installation.php +++ b/module/Decision/src/Model/SubDecision/Installation.php @@ -58,7 +58,7 @@ class Installation extends FoundationReference targetEntity: Discharge::class, mappedBy: 'installation', )] - protected Discharge $discharge; + protected ?Discharge $discharge = null; /** * The organmember reference. @@ -114,7 +114,7 @@ public function getReappointments(): Collection /** * Get the discharge, if it exists. */ - public function getDischarge(): Discharge + public function getDischarge(): ?Discharge { return $this->discharge; } diff --git a/module/Decision/test/Seeder/DecisionFixture.php b/module/Decision/test/Seeder/DecisionFixture.php new file mode 100644 index 0000000000..ca3a07c322 --- /dev/null +++ b/module/Decision/test/Seeder/DecisionFixture.php @@ -0,0 +1,274 @@ +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); + + 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, + ); + } + } + + $decision->addSubdecisions($iSubdecisions); + $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++; + } + + $decision->addSubdecisions($dSubdecisions); + $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, + ]; + } +} diff --git a/module/Decision/test/Seeder/MeetingFixture.php b/module/Decision/test/Seeder/MeetingFixture.php new file mode 100644 index 0000000000..4808b6d1dd --- /dev/null +++ b/module/Decision/test/Seeder/MeetingFixture.php @@ -0,0 +1,44 @@ +setType($meetingType); + $meeting->setNumber($meetingNumber); + + // 2 meetings in the past, 1 today, and 1 in the future. + if (3 > $meetingNumber) { + $meetingDate = (clone $today)->modify("-" . (2 - $meetingNumber) . " days"); + } else { + $meetingDate = AssociationYear::fromDate($today)->getEndDate(); + } + + $meeting->setDate($meetingDate); + + $manager->persist($meeting); + $this->addReference('meeting-' . $meetingType->value . '-' . $meetingNumber, $meeting); + } + + $manager->flush(); + } + } +} diff --git a/module/Decision/test/Seeder/MemberFixture.php b/module/Decision/test/Seeder/MemberFixture.php new file mode 100644 index 0000000000..8974b8804a --- /dev/null +++ b/module/Decision/test/Seeder/MemberFixture.php @@ -0,0 +1,275 @@ +faker = FakerFactory::create(); + $this->now = new DateTimeImmutable(); + + // Admins (8000 - 8002) + foreach (range(8000, 8002) as $lidnr) { + $admin = new Member(); + $admin->setLidnr($lidnr); + $admin->setFirstName('ÅDMIN'); + + $admin = $this->setOtherMemberProperties( + $admin, + MembershipTypes::Ordinary, + false, + false, + false, + ); + + $manager->persist($admin); + $this->addReference('member-' . $lidnr, $admin); + } + + $manager->flush(); + + // Company admins (8003 - 8004) + foreach (range(8003, 8004) as $lidnr) { + $companyAdmin = new Member(); + $companyAdmin->setLidnr($lidnr); + $companyAdmin->setFirstName('COMPANY_ADMIN'); + + $companyAdmin = $this->setOtherMemberProperties( + $companyAdmin, + MembershipTypes::Ordinary, + false, + false, + false, + ); + + $manager->persist($companyAdmin); + $this->addReference('member-' . $lidnr, $companyAdmin); + } + + $manager->flush(); + + // Active (ordinary) members (8005 - 8014) + foreach (range(8005, 8014) as $lidnr) { + $member = new Member(); + $member->setLidnr($lidnr); + $member->setFirstName('ORGAN_ORDINARY'); + + $member = $this->setOtherMemberProperties( + $member, + MembershipTypes::Ordinary, + false, + false, + false, + ); + + $manager->persist($member); + $this->addReference('member-' . $lidnr, $member); + } + + $manager->flush(); + + // Active (external) members (8015 - 8019) + foreach (range(8015, 8019) as $lidnr) { + $member = new Member(); + $member->setLidnr($lidnr); + $member->setFirstName('ORGAN_EXTERNAL'); + + $member = $this->setOtherMemberProperties( + $member, + MembershipTypes::External, + false, + false, + false, + ); + + $manager->persist($member); + $this->addReference('member-' . $lidnr, $member); + } + + $manager->flush(); + + // Discharged active members (8020 - 8024) + foreach (range(8020, 8024) as $lidnr) { + $member = new Member(); + $member->setLidnr($lidnr); + $member->setFirstName('ORGAN_DISCHARGED'); + + $member = $this->setOtherMemberProperties( + $member, + MembershipTypes::Ordinary, + false, + false, + false, + ); + + $manager->persist($member); + $this->addReference('member-' . $lidnr, $member); + } + + $manager->flush(); + + // Ordinary members (8025 - 8124) + foreach (range(8025, 8124) as $lidnr) { + $member = new Member(); + $member->setLidnr($lidnr); + $member->setFirstName($this->faker->firstName()); + + $member = $this->setOtherMemberProperties( + $member, + MembershipTypes::Ordinary, + false, + false, + false, + ); + + $manager->persist($member); + $this->addReference('member-' . $lidnr, $member); + } + + $manager->flush(); + + // External members (8125 - 8149) + foreach (range(8125, 8149) as $lidnr) { + $external = new Member(); + $external->setLidnr($lidnr); + $external->setFirstName($this->faker->firstName()); + + $external = $this->setOtherMemberProperties( + $external, + MembershipTypes::External, + false, + false, + false, + ); + + $manager->persist($external); + $this->addReference('member-' . $lidnr, $external); + } + + $manager->flush(); + + // Honorary members (8150 - 8154) + foreach (range(8150, 8154) as $lidnr) { + $honorary = new Member(); + $honorary->setLidnr($lidnr); + $honorary->setFirstName($this->faker->firstName()); + + $honorary = $this->setOtherMemberProperties( + $honorary, + MembershipTypes::Honorary, + false, + false, + false, + ); + + $manager->persist($honorary); + $this->addReference('member-' . $lidnr, $honorary); + } + + $manager->flush(); + + // Graduates (8155 - 8199) + foreach (range(8155, 8199) as $lidnr) { + $graduate = new Member(); + $graduate->setLidnr($lidnr); + $graduate->setFirstName($this->faker->firstName()); + + $graduate = $this->setOtherMemberProperties( + $graduate, + MembershipTypes::Graduate, + $this->faker->boolean(), + false, + false, + ); + + $manager->persist($graduate); + $this->addReference('member-' . $lidnr, $graduate); + } + + $manager->flush(); + } + + private function setOtherMemberProperties( + Member $member, + MembershipTypes $membershipType, + bool $expired, + bool $hidden = false, + bool $deleted = false, + ): Member { + $member->setInitials( + implode( + '.', + array_map( + static function ($name) { + return mb_substr($name, 0, 1); + }, + explode( + ' ', + $member->getFirstName(), + ), + ), + ) . '.', + ); + $member->setMiddleName(''); + $member->setLastName($this->faker->lastName()); + + $member->setEmail($this->faker->email()); + $member->setBirth($this->faker->dateTimeThisCentury('-16 years')); + + $member->setGeneration(intval($this->faker->year())); + $member->setType($membershipType); + + if (!$expired) { + // If not expired, expire next year. + $member->setExpiration( + AssociationYear::fromDate( + DateTime::createFromImmutable($this->now->add(new DateInterval('P1Y'))), + )->getStartDate(), + ); + + if (MembershipTypes::Ordinary === $membershipType) { + $member->setMembershipEndsOn(null); + } else { + $member->setMembershipEndsOn($member->getExpiration()); + } + } else { + $member->setExpiration( + AssociationYear::fromDate( + DateTime::createFromImmutable($this->now->sub(new DateInterval('P2Y'))), + )->getStartDate(), + ); + $member->setMembershipEndsOn($member->getExpiration()->sub(new DateInterval('P1Y'))); + } + + $member->setHidden($hidden); + $member->setDeleted($deleted); + $member->setAuthenticationKey($expired ? null : $this->faker->sha256()); + $member->setChangedOn(DateTime::createFromImmutable($this->now)); + + return $member; + } +} diff --git a/module/User/test/Seeder/UserFixture.php b/module/User/test/Seeder/UserFixture.php new file mode 100644 index 0000000000..5b8640fc8e --- /dev/null +++ b/module/User/test/Seeder/UserFixture.php @@ -0,0 +1,44 @@ +setLidnr($lidnr); + $user->setMember($this->getReference('member-' . $lidnr, Member::class)); + $user->setPassword('$2y$13$j.ggomvkEeev1tcrsg7tEObJdD0LGQpmfT/4k8zwclvyFM5zFxkde'); // == gewiswebgewis + $user->setPasswordChangedOn(new DateTime()); + + $manager->persist($user); + $this->addReference('user-' . $lidnr, $user); + } + + $manager->flush(); + } + + /** + * @return class-string[] + */ + public function getDependencies(): array + { + return [ + MemberFixture::class, + ]; + } +} diff --git a/module/User/test/Seeder/UserRoleFixture.php b/module/User/test/Seeder/UserRoleFixture.php new file mode 100644 index 0000000000..37669e1164 --- /dev/null +++ b/module/User/test/Seeder/UserRoleFixture.php @@ -0,0 +1,54 @@ +setRole(UserRoles::Admin); + $adminRole->setExpiration((new DateTime())->add(new DateInterval('P10Y'))); + $adminRole->setLidnr($this->getReference('user-' . $lidnr, User::class)); + + $manager->persist($adminRole); + } + + // Company admins (8003 - 8004) + foreach (range(8003, 8004) as $lidnr) { + $companyAdminRole = new UserRole(); + $companyAdminRole->setRole(UserRoles::CompanyAdmin); + $companyAdminRole->setExpiration((new DateTime())->add(new DateInterval('P10Y'))); + $companyAdminRole->setLidnr($this->getReference('user-' . $lidnr, User::class)); + + $manager->persist($companyAdminRole); + } + + $manager->flush(); + } + + /** + * @return class-string[] + */ + public function getDependencies(): array + { + return [ + UserFixture::class, + ]; + } +} diff --git a/phpcs.xml.dist b/phpcs.xml.dist index 6ee29c0bf5..783498db45 100644 --- a/phpcs.xml.dist +++ b/phpcs.xml.dist @@ -44,6 +44,7 @@ module/Activity/src/Controller/Factory/ActivityControllerFactory.php module/Activity/src/Controller/Factory/AdminCategoryControllerFactory.php module/Activity/src/Controller/Factory/AdminOptionControllerFactory.php + module/Application/src/Command/Factory/LoadFixturesFactory.php module/Application/src/Router/Factory/LanguageAwareTreeRouteStackFactory.php module/Education/src/Controller/Factory/AdminControllerFactory.php module/Education/src/Controller/Factory/EducationControllerFactory.php