diff --git a/.github/workflows/php_build_and_qa_test.yml b/.github/workflows/php_build_and_qa_test.yml index 2cecac4..b1ac54b 100644 --- a/.github/workflows/php_build_and_qa_test.yml +++ b/.github/workflows/php_build_and_qa_test.yml @@ -95,5 +95,5 @@ jobs: npm run build-only ## —— Testing ———————————————————————————————————————————————————————————— -# - name: Execute tests (Unit & Integration Tests) via PestPHP +# - name: Execute tests (Unit & Feature Tests) via PestPHP # run: ./vendor/bin/pest diff --git a/README.md b/README.md index 1d9d840..16d5aaa 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ Yoshi-Kan Website + Ledenbeheer --- -
+
@@ -12,7 +12,21 @@ Yoshi-Kan Website + Ledenbeheer ## Requirements -... +* A webserver (Apache, NGINX, ...) with PHP 8.2 or higher. +* [Symfony](https://symfony.com/): PHP 8.2 or higher and these PHP extensions (which are installed and enabled by default in most PHP 8 installations): +Ctype, iconv, PCRE, Session, SimpleXML, and Tokenizer. +* [Composer](https://getcomposer.org/download/), which is used to install PHP packages. +* A database server (MySQL, PostgreSQL, SQLite, ... ) that is compatible with [Doctrine](https://www.doctrine-project.org/). +* Node.js,Yarn & NPM for the frontends build process. + +### Third party services + +* https://www.resend.com/ for sending emails concerning the two factor authentication +* https://www.brevo.com/ for sending the transactional emails +* https://www.mollie.com/ for the online payment services +* https://www.sentry.io/ for error logging and monitoring + +You can configure these services in the `.env` file. ## Getting Started diff --git a/application/YoshiKan/Application/Command/Common/SubscriptionItemsFactory.php b/application/YoshiKan/Application/Command/Common/SubscriptionItemsFactory.php index c274208..aa8057f 100644 --- a/application/YoshiKan/Application/Command/Common/SubscriptionItemsFactory.php +++ b/application/YoshiKan/Application/Command/Common/SubscriptionItemsFactory.php @@ -29,6 +29,19 @@ public function __construct( ) { } + public function resetItemsFromSubscription(Subscription $subscription): bool + { + $subscription = $this->subscriptionRepository->getById($subscription->getId()); + $items = $this->subscriptionItemRepository->getBySubscription($subscription); + foreach ($items as $item) { + $this->subscriptionItemRepository->delete($item); + } + $subscription->clearItems(); + $this->subscriptionRepository->save($subscription); + + return true; + } + public function saveItemsFromSubscription( Subscription $subscription, Federation $federation, diff --git a/application/YoshiKan/Application/Command/Member/ChangeLicense/ChangeLicense.php b/application/YoshiKan/Application/Command/Member/ChangeLicense/ChangeLicense.php new file mode 100644 index 0000000..288bb4d --- /dev/null +++ b/application/YoshiKan/Application/Command/Member/ChangeLicense/ChangeLicense.php @@ -0,0 +1,53 @@ +memberId, + $json->federationId, + ); + } + + // ————————————————————————————————————————————————————————————————————————— + // Getters + // ————————————————————————————————————————————————————————————————————————— + + public function getMemberId(): int + { + return $this->memberId; + } + + public function getFederationId(): int + { + return $this->federationId; + } +} diff --git a/application/YoshiKan/Application/Command/Member/ChangeLicense/ChangeLicenseHandler.php b/application/YoshiKan/Application/Command/Member/ChangeLicense/ChangeLicenseHandler.php new file mode 100644 index 0000000..b8c72be --- /dev/null +++ b/application/YoshiKan/Application/Command/Member/ChangeLicense/ChangeLicenseHandler.php @@ -0,0 +1,145 @@ +memberRepository->getById($command->getMemberId()); + $federation = $this->federationRepository->getById($command->getFederationId()); + $settings = $this->settingsRepository->findByCode(SettingsCode::ACTIVE->value); + + $extraTraining = false; + if (3 === $member->getNumberOfTraining()) { + $extraTraining = true; + } + + // -- calculate the new license dates ------------------------------- + + $now = new \DateTimeImmutable(); + $licenseStart = $now->setDate((int) $now->format('Y'), (int) $now->format('m'), 1); + $licenseEnd = $licenseStart->modify('+1 year'); + + // -- create a new subscription ------------------------------------- + + $subscription = Subscription::make( + $this->subscriptionRepository->nextIdentity(), + $member->getContactFirstname(), + $member->getContactLastname(), + $member->getContactEmail(), + $member->getContactPhone(), + $member->getFirstname(), + $member->getLastname(), + $member->getDateOfBirth(), + $member->getGender(), + SubscriptionType::RENEWAL_LICENSE, + $member->getNumberOfTraining(), + $extraTraining, + false, + false, + false, + 'Wijziging vergunning naar '.$federation->getName(), + $member->getLocation(), + json_decode(json_encode(SettingsReadModel::hydrateFromModel($settings)), true), + $federation, + $member->getMemberSubscriptionStart(), + $member->getMemberSubscriptionEnd(), + 0, + false, + false, + true, + $licenseStart, + $licenseEnd, + $federation->getYearlySubscriptionFee(), + true, + false, + ); + + $subscription->setMember($member); + $subscription->changeStatus(SubscriptionStatus::AWAITING_PAYMENT); + $subscription->calculate(); + $this->subscriptionRepository->save($subscription); + $this->entityManager->flush(); + + // -- make some subscription lines ---------------------------------- + + $subscriptionItemFactory = new SubscriptionItemsFactory( + $this->subscriptionRepository, + $this->subscriptionItemRepository + ); + $resultItems = $subscriptionItemFactory->saveItemsFromSubscription( + $subscription, + $federation, + $settings + ); + + // -- flag new dates in the member ---------------------------------- + + $member->setLicenseDates( + $subscription->getLicenseStart(), + $subscription->getLicenseEnd() + ); + $member->syncFromSubscription( + federation: $federation, + numberOfTraining: $subscription->getNumberOfTraining(), + isHalfYearSubscription: $subscription->isMemberSubscriptionIsHalfYear(), + ); + + $this->memberRepository->save($member); + $this->entityManager->flush(); + + // -- compile a result class ---------------------------------------- + + $subscriptionId = $this->subscriptionRepository->getMaxId(); + $result = new \stdClass(); + $result->id = $subscriptionId; + $result->reference = 'YKS-'.$subscriptionId.': '.$member->getFirstname().' '.$member->getLastName(); + + return $result; + } +} diff --git a/application/YoshiKan/Application/Command/Member/ChangeLicense/ChangeLicenseTrait.php b/application/YoshiKan/Application/Command/Member/ChangeLicense/ChangeLicenseTrait.php new file mode 100644 index 0000000..b77b053 --- /dev/null +++ b/application/YoshiKan/Application/Command/Member/ChangeLicense/ChangeLicenseTrait.php @@ -0,0 +1,38 @@ +permission->CheckRole(['ROLE_DEVELOPER', 'ROLE_ADMIN', 'ROLE_CHIEF_EDITOR']); + + $command = ChangeLicense::hydrateFromJson($jsonCommand); + $handler = new ChangeLicenseHandler( + memberRepository: $this->memberRepository, + subscriptionRepository: $this->subscriptionRepository, + subscriptionItemRepository: $this->subscriptionItemRepository, + federationRepository: $this->federationRepository, + locationRepository: $this->locationRepository, + settingsRepository: $this->settingsRepository, + entityManager: $this->entityManager, + ); + + $result = $handler->change($command); + $this->entityManager->flush(); + + return $result; + } +} diff --git a/application/YoshiKan/Application/Command/Member/ChangeSubscriptionDetails/ChangeSubscriptionDetails.php b/application/YoshiKan/Application/Command/Member/ChangeSubscriptionDetails/ChangeSubscriptionDetails.php new file mode 100644 index 0000000..41a14fa --- /dev/null +++ b/application/YoshiKan/Application/Command/Member/ChangeSubscriptionDetails/ChangeSubscriptionDetails.php @@ -0,0 +1,338 @@ +subscriptionId), + intval($json->memberId), + trim($json->type), + intval($json->federationId), + intval($json->locationId), + trim($json->contactFirstname), + trim($json->contactLastname), + trim($json->contactEmail), + trim($json->contactPhone), + trim($json->addressStreet), + trim($json->addressNumber), + trim($json->addressBox), + trim($json->addressZip), + trim($json->addressCity), + trim($json->firstname), + trim($json->lastname), + trim($json->email), + '', + new \DateTimeImmutable($json->dateOfBirth), + trim($json->gender), + new \DateTimeImmutable($json->memberSubscriptionStart), + trim($json->memberSubscriptionStartMM), + trim($json->memberSubscriptionStartYY), + new \DateTimeImmutable($json->memberSubscriptionEnd), + floatval($json->memberSubscriptionTotal), + boolval($json->memberSubscriptionIsPartSubscription), + boolval($json->memberSubscriptionIsHalfYear), + boolval($json->memberSubscriptionIsPayed), + new \DateTimeImmutable($json->licenseStart), + trim($json->licenseStartMM), + trim($json->licenseStartYY), + new \DateTimeImmutable($json->licenseEnd), + floatval($json->licenseTotal), + boolval($json->licenseIsPartSubscription), + boolval($json->licenseIsPayed), + intval($json->numberOfTraining), + boolval($json->isExtraTraining), + boolval($json->isReductionFamily), + floatval($json->total), + trim($json->remarks), + boolval($json->isJudogiBelt), + ); + } + + // ————————————————————————————————————————————————————————————————————————— + // Getters + // ————————————————————————————————————————————————————————————————————————— + + public function getType(): string + { + return $this->type; + } + + public function getFederationId(): int + { + return $this->federationId; + } + + public function getLocationId(): int + { + return $this->locationId; + } + + public function getContactFirstname(): string + { + return $this->contactFirstname; + } + + public function getContactLastname(): string + { + return $this->contactLastname; + } + + public function getContactEmail(): string + { + return $this->contactEmail; + } + + public function getContactPhone(): string + { + return $this->contactPhone; + } + + public function getAddressStreet(): string + { + return $this->addressStreet; + } + + public function getAddressNumber(): string + { + return $this->addressNumber; + } + + public function getAddressBox(): string + { + return $this->addressBox; + } + + public function getAddressZip(): string + { + return $this->addressZip; + } + + public function getAddressCity(): string + { + return $this->addressCity; + } + + public function getFirstname(): string + { + return $this->firstname; + } + + public function getLastname(): string + { + return $this->lastname; + } + + public function getEmail(): string + { + return $this->email; + } + + public function getNationalRegisterNumber(): string + { + return $this->nationalRegisterNumber; + } + + public function getDateOfBirth(): \DateTimeImmutable + { + return $this->dateOfBirth; + } + + public function getGender(): string + { + return $this->gender; + } + + public function getMemberSubscriptionStart(): \DateTimeImmutable + { + return $this->memberSubscriptionStart; + } + + public function getMemberSubscriptionStartMM(): string + { + return $this->memberSubscriptionStartMM; + } + + public function getMemberSubscriptionStartYY(): string + { + return $this->memberSubscriptionStartYY; + } + + public function getMemberSubscriptionEnd(): \DateTimeImmutable + { + return $this->memberSubscriptionEnd; + } + + public function getMemberSubscriptionTotal(): float + { + return $this->memberSubscriptionTotal; + } + + public function getLicenseStart(): \DateTimeImmutable + { + return $this->licenseStart; + } + + public function getLicenseStartMM(): string + { + return $this->licenseStartMM; + } + + public function getLicenseStartYY(): string + { + return $this->licenseStartYY; + } + + public function getLicenseEnd(): \DateTimeImmutable + { + return $this->licenseEnd; + } + + public function getLicenseTotal(): float + { + return $this->licenseTotal; + } + + public function isLicenseIsPartSubscription(): bool + { + return $this->licenseIsPartSubscription; + } + + public function isLicenseIsPayed(): bool + { + return $this->licenseIsPayed; + } + + public function getNumberOfTraining(): int + { + return $this->numberOfTraining; + } + + public function isExtraTraining(): bool + { + return $this->isExtraTraining; + } + + public function isReductionFamily(): bool + { + return $this->isReductionFamily; + } + + public function getTotal(): float + { + return $this->total; + } + + public function getRemarks(): string + { + return $this->remarks; + } + + public function isJudogiBelt(): bool + { + return $this->isJudogiBelt; + } + + public function isMemberSubscriptionIsPartSubscription(): bool + { + return $this->memberSubscriptionIsPartSubscription; + } + + public function isMemberSubscriptionIsHalfYear(): bool + { + return $this->memberSubscriptionIsHalfYear; + } + + public function isMemberSubscriptionIsPayed(): bool + { + return $this->memberSubscriptionIsPayed; + } + + public function getSubscriptionId(): int + { + return $this->subscriptionId; + } + + public function getMemberId(): int + { + return $this->memberId; + } +} diff --git a/application/YoshiKan/Application/Command/Member/ChangeSubscriptionDetails/ChangeSubscriptionDetailsHandler.php b/application/YoshiKan/Application/Command/Member/ChangeSubscriptionDetails/ChangeSubscriptionDetailsHandler.php new file mode 100644 index 0000000..23152ef --- /dev/null +++ b/application/YoshiKan/Application/Command/Member/ChangeSubscriptionDetails/ChangeSubscriptionDetailsHandler.php @@ -0,0 +1,199 @@ +federationRepository->getById($command->getFederationId()); + $location = $this->locationRepository->getById($command->getLocationId()); + $settings = $this->settingsRepository->findByCode(SettingsCode::ACTIVE->value); + $subscription = $this->subscriptionRepository->getById($command->getSubscriptionId()); + $oldTotal = $subscription->getTotal(); + + // -- validate the command ---------------------------------------- + $commandIsValid = $this->isCommandValid($command, $settings); + if (!$commandIsValid) { + throw new \Exception('Membership change command is not valid.'); + } + + if (SubscriptionStatus::PAYED === $subscription->getStatus()) { + throw new \Exception('Membership change command is not possible .'); + } + if (SubscriptionStatus::COMPLETE === $subscription->getStatus()) { + throw new \Exception('Membership change command is not possible .'); + } + if (SubscriptionStatus::ARCHIVED === $subscription->getStatus()) { + throw new \Exception('Membership change command is not possible .'); + } + + $subscription->fullChange( + contactFirstname: $command->getContactFirstname(), + contactLastname: $command->getContactLastname(), + contactEmail: $command->getContactEmail(), + contactPhone: $command->getContactPhone(), + firstname: $command->getFirstname(), + lastname: $command->getLastname(), + dateOfBirth: $command->getDateOfBirth(), + gender: Gender::from($command->getGender()), + type: SubscriptionType::from($command->getType()), + numberOfTraining: $command->getNumberOfTraining(), + isExtraTraining: $command->isExtraTraining(), + isNewMember: $subscription->isNewMember(), + isReductionFamily: $command->isReductionFamily(), + isJudogiBelt: $command->isJudogiBelt(), + remarks: $command->getRemarks(), + location: $location, + federation: $federation, + memberSubscriptionStart: $command->getMemberSubscriptionStart(), + memberSubscriptionEnd: $command->getMemberSubscriptionEnd(), + memberSubscriptionTotal: $command->getMemberSubscriptionTotal(), + memberSubscriptionIsPartSubscription: $subscription->isMemberSubscriptionIsPartSubscription(), + memberSubscriptionIsHalfYear: $command->isMemberSubscriptionIsHalfYear(), + memberSubscriptionIsPayed: $subscription->isMemberSubscriptionIsPayed(), + licenseStart: $command->getLicenseStart(), + licenseEnd: $command->getLicenseEnd(), + licenseTotal: $command->getLicenseTotal(), + licenseIsPartSubscription: $subscription->isLicenseIsPartSubscription(), + licenseIsPayed: $subscription->isLicenseIsPayed(), + ); + $subscription->setNewMemberFields( + nationalRegisterNumber: $command->getNationalRegisterNumber(), + addressStreet: $command->getAddressStreet(), + addressNumber: $command->getAddressNumber(), + addressBox: $command->getAddressBox(), + addressZip: $command->getAddressZip(), + addressCity: $command->getAddressCity(), + ); + + $this->subscriptionRepository->save($subscription); + $this->entityManager->flush(); + + if (0 !== $command->getMemberId()) { + // -- update the member fields -------------------------------- + $member = $this->memberRepository->getById($command->getMemberId()); + + // -- set contact info ---------------------------------------- + $member->setContactInformation( + contactFirstname: $subscription->getContactFirstname(), + contactLastname: $subscription->getContactLastname(), + contactEmail: $subscription->getContactEmail(), + contactPhone: $subscription->getContactPhone() + ); + // -- set subscription dates ---------------------------------- + $member->setSubscriptionDates( + start: $subscription->getMemberSubscriptionStart(), + end: $subscription->getMemberSubscriptionEnd(), + isHalfYearSubscription: $subscription->isMemberSubscriptionIsHalfYear() + ); + // -- set license dates --------------------------------------- + $member->setLicenseDates( + start: $subscription->getLicenseStart(), + end: $subscription->getLicenseEnd() + ); + + // -- save and connect the member ----------------------------- + $this->memberRepository->save($member); + $subscription->setMember($member); + $this->subscriptionRepository->save($subscription); + } + + // -- recalculate subscription and send new mollie link if needed ---- + $subscription->calculate(); + $this->subscriptionRepository->save($subscription); + $this->entityManager->flush(); + + // -- make some new subscription lines --------------------------------- + $subscriptionItemFactory = new SubscriptionItemsFactory( + $this->subscriptionRepository, + $this->subscriptionItemRepository + ); + $resultClearItems = $subscriptionItemFactory->resetItemsFromSubscription($subscription); + $this->entityManager->flush(); + $resultItems = $subscriptionItemFactory->saveItemsFromSubscription( + $subscription, + $federation, + $settings + ); + + $this->subscriptionRepository->save($subscription); + $this->entityManager->flush(); + + // -- make a new mollie link for online payment ---------------- + + if ($oldTotal !== $subscription->getTotal()) { + $mollieCommand = CreateMolliePaymentLink::make($subscription->getId()); + $mollieCommandHandler = new CreateMolliePaymentLinkHandler( + $this->subscriptionRepository, + $this->mollieConfig, + ); + $resultMollie = $mollieCommandHandler->create($mollieCommand, true); + $this->entityManager->flush(); + } + + // -- compile a result class for giving feedback -------------------- + $result = new \stdClass(); + $result->id = $subscription->getId(); + $result->reference = 'YKS-'.$subscription->getId().': '.$command->getFirstName().' '.$command->getLastName(); + + return $result; + } + + private function isCommandValid(ChangeSubscriptionDetails $command, Settings $settings): bool + { + return true; + } +} diff --git a/application/YoshiKan/Application/Command/Member/ChangeSubscriptionDetails/ChangeSubscriptionDetailsTrait.php b/application/YoshiKan/Application/Command/Member/ChangeSubscriptionDetails/ChangeSubscriptionDetailsTrait.php new file mode 100644 index 0000000..7e5021d --- /dev/null +++ b/application/YoshiKan/Application/Command/Member/ChangeSubscriptionDetails/ChangeSubscriptionDetailsTrait.php @@ -0,0 +1,44 @@ +permission->CheckRole(['ROLE_DEVELOPER', 'ROLE_ADMIN', 'ROLE_CHIEF_EDITOR']); + + $handler = new ChangeSubscriptionDetailsHandler( + $this->federationRepository, + $this->locationRepository, + $this->settingsRepository, + $this->memberRepository, + $this->gradeRepository, + $this->subscriptionRepository, + $this->subscriptionItemRepository, + $this->mollieConfig, + $this->entityManager, + ); + + $result = $handler->change($command); + $this->entityManager->flush(); + + return $result; + } +} diff --git a/application/YoshiKan/Application/Command/Member/CreateMolliePaymentLink/CreateMolliePaymentLinkHandler.php b/application/YoshiKan/Application/Command/Member/CreateMolliePaymentLink/CreateMolliePaymentLinkHandler.php index 57a83ef..1bfcc7c 100644 --- a/application/YoshiKan/Application/Command/Member/CreateMolliePaymentLink/CreateMolliePaymentLinkHandler.php +++ b/application/YoshiKan/Application/Command/Member/CreateMolliePaymentLink/CreateMolliePaymentLinkHandler.php @@ -25,12 +25,13 @@ public function __construct( ) { } - public function create(CreateMolliePaymentLink $command): bool + public function create(CreateMolliePaymentLink $command, bool $force = false): bool { $subscription = $this->subscriptionRepository->getById($command->getSubscriptionId()); // -- link already created - if (!(0 === mb_strlen($subscription->getPaymentId()))) { + + if (!$force && !(0 === mb_strlen($subscription->getPaymentId()))) { return false; } diff --git a/application/YoshiKan/Application/Command/Member/NewMemberSubscriptionMail/NewMemberSubscriptionMail.php b/application/YoshiKan/Application/Command/Member/NewMemberSubscriptionMail/NewMemberSubscriptionMail.php index ea27fbe..150417d 100644 --- a/application/YoshiKan/Application/Command/Member/NewMemberSubscriptionMail/NewMemberSubscriptionMail.php +++ b/application/YoshiKan/Application/Command/Member/NewMemberSubscriptionMail/NewMemberSubscriptionMail.php @@ -23,6 +23,7 @@ public function __construct( protected int $subscriptionId, protected string $fromName, protected string $fromEmail, + protected bool $isChange = false, ) { } @@ -44,4 +45,9 @@ public function getFromEmail(): string { return $this->fromEmail; } + + public function isChange(): bool + { + return $this->isChange; + } } diff --git a/application/YoshiKan/Application/Command/Member/NewMemberSubscriptionMail/NewMemberSubscriptionMailHandler.php b/application/YoshiKan/Application/Command/Member/NewMemberSubscriptionMail/NewMemberSubscriptionMailHandler.php index 507f932..24b2672 100644 --- a/application/YoshiKan/Application/Command/Member/NewMemberSubscriptionMail/NewMemberSubscriptionMailHandler.php +++ b/application/YoshiKan/Application/Command/Member/NewMemberSubscriptionMail/NewMemberSubscriptionMailHandler.php @@ -56,16 +56,30 @@ public function go(NewMemberSubscriptionMail $command): bool { $subscription = $this->subscriptionRepository->getById($command->getSubscriptionId()); $items = $this->subscriptionItemRepository->getBySubscription($subscription); - $subject = 'JC Yoshi-Kan: Nieuwe inschrijving voor '.$subscription->getFirstname().' '.$subscription->getLastname(); - $mailTemplate = $this->twig->render( - 'mail/member_newMemberSubscription_mail.html.twig', - [ - 'subject' => $subject, - 'subscription' => $subscription, - 'items' => $items, - 'url' => $_SERVER['REQUEST_SCHEME'].'://'.$_SERVER['HTTP_HOST'], - ] - ); + + if ($command->isChange()) { + $subject = 'JC Yoshi-Kan: Wijziging inschrijving voor '.$subscription->getFirstname().' '.$subscription->getLastname(); + $mailTemplate = $this->twig->render( + 'mail/member_changed_subscription_mail.html.twig', + [ + 'subject' => $subject, + 'subscription' => $subscription, + 'items' => $items, + 'url' => $_SERVER['REQUEST_SCHEME'].'://'.$_SERVER['HTTP_HOST'], + ] + ); + } else { + $subject = 'JC Yoshi-Kan: Nieuwe inschrijving voor '.$subscription->getFirstname().' '.$subscription->getLastname(); + $mailTemplate = $this->twig->render( + 'mail/member_newMemberSubscription_mail.html.twig', + [ + 'subject' => $subject, + 'subscription' => $subscription, + 'items' => $items, + 'url' => $_SERVER['REQUEST_SCHEME'].'://'.$_SERVER['HTTP_HOST'], + ] + ); + } // -- send email ---------------------------------------------------------- diff --git a/application/YoshiKan/Application/Command/Member/NewMemberSubscriptionMail/NewMemberSubscriptionMailTrait.php b/application/YoshiKan/Application/Command/Member/NewMemberSubscriptionMail/NewMemberSubscriptionMailTrait.php index 0c1131b..39f8128 100644 --- a/application/YoshiKan/Application/Command/Member/NewMemberSubscriptionMail/NewMemberSubscriptionMailTrait.php +++ b/application/YoshiKan/Application/Command/Member/NewMemberSubscriptionMail/NewMemberSubscriptionMailTrait.php @@ -17,7 +17,7 @@ trait NewMemberSubscriptionMailTrait { - public function sendMemberNewSubscriptionMail(int $subscriptionId): bool + public function sendMemberNewSubscriptionMail(int $subscriptionId, bool $isChange = false): bool { $this->permission->CheckRole(['ROLE_DEVELOPER', 'ROLE_ADMIN', 'ROLE_CHIEF_EDITOR']); @@ -36,7 +36,8 @@ public function sendMemberNewSubscriptionMail(int $subscriptionId): bool $command = new NewMemberSubscriptionMail( $subscriptionId, Settings::FROM_NAME->value, - Settings::FROM_EMAIL->value + Settings::FROM_EMAIL->value, + $isChange, ); $result = $handler->go($command); diff --git a/application/YoshiKan/Application/MemberCommandBus.php b/application/YoshiKan/Application/MemberCommandBus.php index 745e816..9a812d5 100644 --- a/application/YoshiKan/Application/MemberCommandBus.php +++ b/application/YoshiKan/Application/MemberCommandBus.php @@ -21,11 +21,13 @@ use App\YoshiKan\Application\Command\Member\ChangeFederation\ChangeFederationTrait; use App\YoshiKan\Application\Command\Member\ChangeGrade\ChangeGradeTrait; use App\YoshiKan\Application\Command\Member\ChangeGroup\ChangeGroupTrait; +use App\YoshiKan\Application\Command\Member\ChangeLicense\ChangeLicenseTrait; use App\YoshiKan\Application\Command\Member\ChangeLocation\ChangeLocationTrait; use App\YoshiKan\Application\Command\Member\ChangeMemberDetails\ChangeMemberDetailsTrait; use App\YoshiKan\Application\Command\Member\ChangeMemberGrade\ChangeMemberGradeTrait; use App\YoshiKan\Application\Command\Member\ChangeMemberRemarks\ChangeMemberRemarksTrait; use App\YoshiKan\Application\Command\Member\ChangePeriod\ChangePeriodTrait; +use App\YoshiKan\Application\Command\Member\ChangeSubscriptionDetails\ChangeSubscriptionDetailsTrait; use App\YoshiKan\Application\Command\Member\ConfirmMemberWebSubscription\ConfirmMemberWebSubscriptionTrait; use App\YoshiKan\Application\Command\Member\CreateMolliePaymentLink\CreateMolliePaymentLinkTrait; use App\YoshiKan\Application\Command\Member\DeleteMemberImage\DeleteMemberImageTrait; @@ -113,6 +115,8 @@ class MemberCommandBus use CreateMolliePaymentLinkTrait; use SendPaymentReceivedConfirmationMailTrait; use NewMemberSubscriptionMailTrait; + use ChangeSubscriptionDetailsTrait; + use ChangeLicenseTrait; // -- member images --------------------------------------------------------- use UploadProfileImageTrait; diff --git a/application/YoshiKan/Application/Query/Member/GetMember.php b/application/YoshiKan/Application/Query/Member/GetMember.php index 6e6e469..b4b4db6 100644 --- a/application/YoshiKan/Application/Query/Member/GetMember.php +++ b/application/YoshiKan/Application/Query/Member/GetMember.php @@ -63,7 +63,8 @@ public function search(MemberSearchModel $searchModel): MemberReadModelCollectio $location, $grade, $minYearOfBirth, - $maxYearOfBirth + $maxYearOfBirth, + $searchModel->isActive(), ); // -- covert to readmodel collection ------------------------------------------ diff --git a/application/YoshiKan/Application/Query/Member/GetSubscriptionTrait.php b/application/YoshiKan/Application/Query/Member/GetSubscriptionTrait.php index 6428f83..7f7e496 100644 --- a/application/YoshiKan/Application/Query/Member/GetSubscriptionTrait.php +++ b/application/YoshiKan/Application/Query/Member/GetSubscriptionTrait.php @@ -81,6 +81,7 @@ public function printSubscriptions(array $listIds): bool $this->locationRepository, $this->federationRepository, $this->subscriptionRepository, + $this->settingsRepository, $this->twig, $this->uploadFolder, $this->entityManager, @@ -90,4 +91,23 @@ public function printSubscriptions(array $listIds): bool return true; } + + public function printEmptySubscriptionForm(): bool + { + $this->permission->CheckRole(['ROLE_DEVELOPER', 'ROLE_ADMIN', 'ROLE_CHIEF_EDITOR']); + + $document = new PrintSubscriptions( + $this->locationRepository, + $this->federationRepository, + $this->subscriptionRepository, + $this->settingsRepository, + $this->twig, + $this->uploadFolder, + $this->entityManager, + ); + + $document->printEmptySubscriptionForm(); + + return true; + } } diff --git a/application/YoshiKan/Application/Query/Member/PrintSubscriptions.php b/application/YoshiKan/Application/Query/Member/PrintSubscriptions.php index 9a0d303..4771612 100644 --- a/application/YoshiKan/Application/Query/Member/PrintSubscriptions.php +++ b/application/YoshiKan/Application/Query/Member/PrintSubscriptions.php @@ -15,6 +15,8 @@ use App\YoshiKan\Domain\Model\Member\FederationRepository; use App\YoshiKan\Domain\Model\Member\LocationRepository; +use App\YoshiKan\Domain\Model\Member\SettingsCode; +use App\YoshiKan\Domain\Model\Member\SettingsRepository; use App\YoshiKan\Domain\Model\Member\Subscription; use App\YoshiKan\Domain\Model\Member\SubscriptionRepository; use Doctrine\ORM\EntityManagerInterface; @@ -32,6 +34,7 @@ public function __construct( protected LocationRepository $locationRepository, protected FederationRepository $federationRepository, protected SubscriptionRepository $subscriptionRepository, + protected SettingsRepository $settingsRepository, protected Environment $twig, protected string $uploadFolder, protected EntityManagerInterface $entityManager, @@ -72,4 +75,32 @@ public function printOverview(array $listIds): void exit; } + + public function printEmptySubscriptionForm(): void + { + $settings = $this->settingsRepository->findByCode(SettingsCode::ACTIVE->value); + $federations = $this->federationRepository->getAll(); + $locations = $this->locationRepository->getAll(); + + $data = new \stdClass(); + $data->generatedOn = new \DateTimeImmutable(); + $data->settings = $settings; + $data->federations = $federations; + $data->locations = $locations; + + $pdfHtml = $this->twig->render('pdf/empty_subscription_form.html.twig', ['data' => $data]); + + $options = new Options(); + $options->set('isRemoteEnabled', true); + $options->set('isPhpEnabled', true); + $dompdf = new Dompdf($options); + $dompdf->loadHtml($pdfHtml); + $dompdf->setPaper('A4', 'portrait'); + $dompdf->render(); + + $fileName = $data->generatedOn->format('YmdHis').'_yoshikan_inschrijving_formulier.pdf'; + $dompdf->stream($fileName, ['Attachment' => false]); + + exit; + } } diff --git a/application/YoshiKan/Application/Query/Member/Readmodel/MemberSearchModel.php b/application/YoshiKan/Application/Query/Member/Readmodel/MemberSearchModel.php index c2ae6fe..5570694 100644 --- a/application/YoshiKan/Application/Query/Member/Readmodel/MemberSearchModel.php +++ b/application/YoshiKan/Application/Query/Member/Readmodel/MemberSearchModel.php @@ -25,6 +25,7 @@ private function __construct( protected int $gradeId, protected int $yearOfBirth, protected int $groupId, + protected ?bool $isActive = null, ) { } @@ -39,6 +40,7 @@ public static function hydrateFromJson(\stdClass $json): self $gradeId = 0; $yearOfBirth = 0; $groupId = 0; + $isActive = null; if (isset($json->locationId)) { $locationId = intval($json->locationId); } @@ -51,13 +53,17 @@ public static function hydrateFromJson(\stdClass $json): self if (isset($json->group)) { $groupId = intval($json->group->id); } + if (isset($json->isActive)) { + $isActive = boolval($json->isActive); + } return new self( $keyword, $locationId, $gradeId, $yearOfBirth, - $groupId + $groupId, + $isActive, ); } @@ -89,4 +95,9 @@ public function getGroupId(): int { return $this->groupId; } + + public function isActive(): ?bool + { + return $this->isActive; + } } diff --git a/application/YoshiKan/Domain/Model/Member/Subscription.php b/application/YoshiKan/Domain/Model/Member/Subscription.php index 4a34331..0aee9c7 100644 --- a/application/YoshiKan/Domain/Model/Member/Subscription.php +++ b/application/YoshiKan/Domain/Model/Member/Subscription.php @@ -481,6 +481,11 @@ public function updateSettings(array $settings): void $this->settings = $settings; } + public function clearItems(): void + { + $this->items = new ArrayCollection(); + } + // ————————————————————————————————————————————————————————————————————————— // Payment information setters // ————————————————————————————————————————————————————————————————————————— diff --git a/application/YoshiKan/Infrastructure/Templates/mail/member_changed_subscription_mail.html.twig b/application/YoshiKan/Infrastructure/Templates/mail/member_changed_subscription_mail.html.twig new file mode 100644 index 0000000..bcfb004 --- /dev/null +++ b/application/YoshiKan/Infrastructure/Templates/mail/member_changed_subscription_mail.html.twig @@ -0,0 +1,97 @@ +{% extends 'mail/base_mail.html.twig' %} + +{% block title %}{{ subject }}{% endblock %} + +{% block body %} +
+ Heist op den Berg, {{ 'now' | date('d/m/Y') }} +
++ Beste {{ subscription.contactFirstname }} {{ subscription.contactLastname }}, +
++ We hebben de inschrijving van {{ subscription.firstname }} {{ subscription.lastname }} aangepast. + Hieronder vind je een overzicht van de wijzigingen. +
++ Om de inschrijving definitief te maken en ervoor te zorgen dat je volop kunt genieten + van onze judolessen, verzoeken wij je vriendelijk om een bedrag van {{ subscription.total }} € + over te maken naar onze bankrekening op het volgende nummer: BE37 7330 0101 8328 met vermelding + van volgende referentie + "YKS-{{ subscription.id }} {{ subscription.lastname }} {{ subscription.firstname }}". + Zo kunnen we je betaling snel identificeren. +
+ {% if (subscription.paymentLink is not empty) %} ++ Of betaal online via deze Mollie-link: + {{ subscription.paymentLink }}. +
+ {% endif %} ++ Wanneer we je betaling hebben ontvangen, zullen we je officieel + inschrijven en krijg je toegang tot al onze trainingen en evenementen. + We staan te popelen om je te verwelkomen op de mat. +
++ Mocht je nog vragen hebben of extra informatie nodig hebben, aarzel dan niet om contact + met ons op te nemen via judo.yoshikan@gmail.com. + Ons team staat altijd klaar om je te helpen. +
+ ++
+
{{ item.name }} | +|
{{ item.name }} | +{{ item.price }} € | +
Totaal | +{{ subscription.total }} € + | +
+ {% if(subscription.remarks != '') %} +
+ {{ subscription.remarks }} +
+ {% endif %} +
+ Met sportieve groeten,
+
Team Yoshi-Kan.
+
+
+ Door het uitvoeren van de betaling gaat U akkoord met ons reglement + en privacyverklaring, terug te vinden op onze website + www.yoshi-kan.be +
+ +{% endblock %} diff --git a/application/YoshiKan/Infrastructure/Templates/pdf/empty_subscription_form.html.twig b/application/YoshiKan/Infrastructure/Templates/pdf/empty_subscription_form.html.twig new file mode 100644 index 0000000..a3f7bae --- /dev/null +++ b/application/YoshiKan/Infrastructure/Templates/pdf/empty_subscription_form.html.twig @@ -0,0 +1,322 @@ +{# #} +{# This file is part of the Yoshi-Kan software. #} +{# #} +{# (c) Koen Caerels #} +{# #} +{# For the full copyright and license information, please view the LICENSE #} +{# file that was distributed with this source code. #} +{# #} + + + + + + + +{# ------------------------------------------------------------------------------------------------------------------ #} + + +
+
|
+
+ {# right side of the template -------------------------------------------- #}
+
+
+
+
+
+
+
+
+ Inschrijven kan ook online via onze website: + https://www.yoshikan.be/inschrijven + +
+
+
+
+ JC Yoshi-Kan v.z.w.
+
+
+
+ Secr: Spekstraat 80 + 2220 Heist o/d Berg + + + G 0474 51 13 98 + E judo.yoshikan@gmail.com +
+
+ IBAN: BE37 7330 0101 8328 + BIC: KRED BE BB +
+
+
+
+ Totaal:
+
+
+ ....................... € + +
+ Handtekening ouder of voogd:
+
+
+ + + + + + + + + + + + +
+
+
+
+
+
+ Door het ondertekenen of het uitvoeren van de betaling, gaat U akkoord met ons
+ reglement en privacy verklaring, terug te vinden op onze website
+ www.yoshikan.be
+
+ |
+
+ We hebben de inschrijving van {{ props.command.firstname }} {{ props.command.lastname }} aangepast. + Hieronder vind je een overzicht van de wijzigingen. +
++ Om de inschrijving definitief te maken en ervoor te zorgen dat je volop kunt genieten van onze + judolessen, verzoeken wij je vriendelijk om een bedrag van + {{ props.command.total }} € over te maken + naar onze bankrekening op het volgende nummer: + BE37 7330 0101 8328 + met vermelding van volgende referentie + "YKS-{{props.command.subscriptionId}} + {{ props.command.lastname }} {{ props.command.firstname }}". + Zo kunnen we je betaling snel identificeren. +
++ Of betaal online via deze Mollie-link: + https://paymentlink.mollie.com/payment/xxxxx/. +
++ Wanneer we je betaling hebben ontvangen, zullen we je officieel inschrijven + en krijg je toegang tot al onze trainingen en evenementen. + We staan te popelen om je te verwelkomen op de mat. +
++ Mocht je nog vragen hebben of extra informatie nodig hebben, aarzel dan niet om contact + met ons op te nemen via judo.yoshikan@gmail.com. + Ons team staat altijd klaar om je te helpen. +
+