From cca3a5899f1ac4e621cb76e14f1665a4e71b1825 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Stan=C4=9Bk?= Date: Thu, 22 Sep 2022 16:32:30 +0200 Subject: [PATCH 01/41] nsj branch with deployment to dev --- .github/workflows/deploy-nsj-dev.yml | 73 ++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 .github/workflows/deploy-nsj-dev.yml diff --git a/.github/workflows/deploy-nsj-dev.yml b/.github/workflows/deploy-nsj-dev.yml new file mode 100644 index 000000000..fcba325be --- /dev/null +++ b/.github/workflows/deploy-nsj-dev.yml @@ -0,0 +1,73 @@ +name: deploy-nsj-dev + +on: + push: + branches: [nsj/master] + +concurrency: + group: environment-nsj-dev + +jobs: + deploy: + name: "Deploy to srs-dev.skauting.cz" + environment: srs-dev.skauting.cz + runs-on: ubuntu-20.04 + container: + image: skaut/lebeda:8.0 + env: + CONFIG_DATABASE_HOST: ${{ secrets.CONFIG_DATABASE_HOST }} + CONFIG_DATABASE_NAME: ${{ secrets.CONFIG_DATABASE_NAME }} + CONFIG_DATABASE_PASSWORD: ${{ secrets.CONFIG_DATABASE_PASSWORD }} + CONFIG_DATABASE_USER: ${{ secrets.CONFIG_DATABASE_USER }} + CONFIG_MAIL_HOST: + CONFIG_MAIL_PASSWORD: + CONFIG_MAIL_PORT: 0 + CONFIG_MAIL_SECURE: + CONFIG_MAIL_SMTP: false + CONFIG_MAIL_USERNAME: + CONFIG_SKAUTIS_APPLICATION_ID: ${{ secrets.CONFIG_SKAUTIS_APPLICATION_ID }} + CONFIG_SKAUTIS_TEST_MODE: ${{ secrets.CONFIG_SKAUTIS_TEST_MODE }} + CONFIG_RECAPTCHA_SITE_KEY: ${{ secrets.CONFIG_RECAPTCHA_SITE_KEY }} + CONFIG_RECAPTCHA_SECRET_KEY: ${{ secrets.CONFIG_RECAPTCHA_SECRET_KEY }} + DEPLOY_DIRECTORY: ${{ secrets.DEPLOY_DIRECTORY }} + DEPLOY_SSH_HOST: ${{ secrets.DEPLOY_SSH_HOST }} + DEPLOY_SSH_IP: ${{ secrets.DEPLOY_SSH_IP }} + DEPLOY_SSH_KEY: ${{ secrets.DEPLOY_SSH_KEY }} + DEPLOY_SSH_PORT: ${{ secrets.DEPLOY_SSH_PORT }} + DEPLOY_SSH_USERNAME: ${{ secrets.DEPLOY_SSH_USERNAME }} + steps: + - uses: actions/checkout@v2 + # Copy & paste from https://github.com/actions/cache/blob/master/examples.md#php---composer + - name: Get composer cache + id: composer-cache + run: | + echo "::set-output name=dir::$(composer config cache-files-dir)" + - uses: actions/cache@v1 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} + restore-keys: | + ${{ runner.os }}-composer- + - name: Install yarn + run: | + apt-get update + apt-get install -y npm + npm install --global yarn + #Copy & paste from https://github.com/actions/cache/blob/master/examples.md#node---yarn + - name: Get yarn cache + id: yarn-cache-dir-path + run: echo "::set-output name=dir::$(yarn cache dir)" + - uses: actions/cache@v1 + id: yarn-cache + with: + path: ${{ steps.yarn-cache-dir-path.outputs.dir }} + key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} + restore-keys: | + ${{ runner.os }}-yarn- + - name: Setup SSH key and deploy + run: | + mkdir -p /root/.ssh + ssh-keyscan -H "${DEPLOY_SSH_HOST}","${DEPLOY_SSH_IP}" >> /root/.ssh/known_hosts + eval `ssh-agent -s` + echo "${DEPLOY_SSH_KEY}" | tr -d '\r' | ssh-add - + phing deploy From 71a168660c2f4c4521c700f26fb56ea172602907 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Stan=C4=9Bk?= Date: Thu, 22 Sep 2022 19:07:26 +0200 Subject: [PATCH 02/41] php 8.1 image --- .github/workflows/deploy-nsj-dev.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy-nsj-dev.yml b/.github/workflows/deploy-nsj-dev.yml index fcba325be..7fa0437a7 100644 --- a/.github/workflows/deploy-nsj-dev.yml +++ b/.github/workflows/deploy-nsj-dev.yml @@ -13,7 +13,7 @@ jobs: environment: srs-dev.skauting.cz runs-on: ubuntu-20.04 container: - image: skaut/lebeda:8.0 + image: skaut/lebeda:8.1 env: CONFIG_DATABASE_HOST: ${{ secrets.CONFIG_DATABASE_HOST }} CONFIG_DATABASE_NAME: ${{ secrets.CONFIG_DATABASE_NAME }} From 48975fe5904608c13cd475670e7849f53cdd691b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Stan=C4=9Bk?= Date: Thu, 6 Oct 2022 23:25:48 +0200 Subject: [PATCH 03/41] execute test for nsj/master (#887) --- .github/workflows/test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 77a8ef8ed..41ec20390 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -2,9 +2,9 @@ name: test on: push: - branches: [master] + branches: [master, nsj/master] pull_request: - branches: [master] + branches: [master, nsj/master] concurrency: group: test-${{ github.ref }} From da8b12ba0ae27bcc5b08da647ba6d4431b06c521 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Stan=C4=9Bk?= Date: Sat, 15 Oct 2022 19:31:23 +0200 Subject: [PATCH 04/41] =?UTF-8?q?=C3=9Aprava=20datab=C3=A1zov=C3=A9ho=20mo?= =?UTF-8?q?delu=20pro=20skupiny=20(#886)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * db model for groups * role getters * patrol, troop entities * migration * coding standard * type default value * db model for groups * migration * fixes * fixes * warnings nullable --- app/Model/Acl/Role.php | 65 ++++++++ app/Model/Enums/RoleType.php | 30 ++++ app/Model/Payment/Payment.php | 18 +++ app/Model/User/Patrol.php | 80 ++++++++++ app/Model/User/Troop.php | 229 +++++++++++++++++++++++++++ app/Model/User/UserGroupRole.php | 90 +++++++++++ migrations/Version20221015133440.php | 38 +++++ 7 files changed, 550 insertions(+) create mode 100644 app/Model/Enums/RoleType.php create mode 100644 app/Model/User/Patrol.php create mode 100644 app/Model/User/Troop.php create mode 100644 app/Model/User/UserGroupRole.php create mode 100644 migrations/Version20221015133440.php diff --git a/app/Model/Acl/Role.php b/app/Model/Acl/Role.php index 099aca455..a691f06cf 100644 --- a/app/Model/Acl/Role.php +++ b/app/Model/Acl/Role.php @@ -6,6 +6,7 @@ use App\Model\Cms\Page; use App\Model\Cms\Tag; +use App\Model\Enums\RoleType; use App\Model\Program\Category; use App\Model\User\User; use DateTimeImmutable; @@ -125,6 +126,12 @@ class Role #[ORM\Column(type: 'boolean')] protected bool $systemRole = true; + /** + * Typ role - individuální/družinová/skupinová. + */ + #[ORM\Column(type: 'string')] + protected string $type = RoleType::INDIVIDUAL; + /** * Registrovatelná role. Lze vybrat v přihlášce. */ @@ -174,6 +181,24 @@ class Role #[ORM\Column(type: 'integer')] protected int $minimumAge = 0; + /** + * Varování při příliš nízkém věku. + */ + #[ORM\Column(type: 'string', nullable: true)] + protected ?string $minimumAgeWarning; + + /** + * Maximální věk. + */ + #[ORM\Column(type: 'integer')] + protected int $maximumAge = 150; + + /** + * Varování při příliš vysokém věku. + */ + #[ORM\Column(type: 'string', nullable: true)] + protected ?string $maximumAgeWarning; + /** * Synchronizovat účastníky v roli se skautIS. */ @@ -375,6 +400,16 @@ public function setSystemRole(bool $systemRole): void $this->systemRole = $systemRole; } + public function getType(): string + { + return $this->type; + } + + public function setType(string $type): void + { + $this->type = $type; + } + public function isRegisterable(): bool { return $this->registerable; @@ -467,6 +502,36 @@ public function setMinimumAge(int $age): void $this->minimumAge = $age; } + public function getMinimumAgeWarning(): ?string + { + return $this->minimumAgeWarning; + } + + public function setMinimumAgeWarning(?string $minimumAgeWarning): void + { + $this->minimumAgeWarning = $minimumAgeWarning; + } + + public function getMaximumAge(): int + { + return $this->maximumAge; + } + + public function setMaximumAge(int $maximumAge): void + { + $this->maximumAge = $maximumAge; + } + + public function getMaximumAgeWarning(): ?string + { + return $this->maximumAgeWarning; + } + + public function setMaximumAgeWarning(?string $maximumAgeWarning): void + { + $this->maximumAgeWarning = $maximumAgeWarning; + } + public function isSyncedWithSkautIS(): bool { return $this->syncedWithSkautIS; diff --git a/app/Model/Enums/RoleType.php b/app/Model/Enums/RoleType.php new file mode 100644 index 000000000..574a9769e --- /dev/null +++ b/app/Model/Enums/RoleType.php @@ -0,0 +1,30 @@ + + */ + #[ORM\OneToMany(targetEntity: Troop::class, mappedBy: 'payment', cascade: ['persist'])] + protected Collection $pairedTroops; + /** * Stav platby. */ @@ -86,6 +95,7 @@ class Payment public function __construct() { $this->pairedApplications = new ArrayCollection(); + $this->pairedTroops = new ArrayCollection(); } public function getId(): ?int @@ -221,6 +231,14 @@ public function getPairedValidApplicationsText(): string return implode(', ', $usersTexts); } + /** + * @return Collection + */ + public function getPairedTroops(): Collection + { + return $this->pairedTroops; + } + public function getState(): string { return $this->state; diff --git a/app/Model/User/Patrol.php b/app/Model/User/Patrol.php new file mode 100644 index 000000000..8172ede41 --- /dev/null +++ b/app/Model/User/Patrol.php @@ -0,0 +1,80 @@ + + */ + #[ORM\OneToMany(mappedBy: 'patrol', targetEntity: UserGroupRole::class, cascade: ['persist'])] + protected Collection $usersRoles; + + public function __construct() + { + $this->usersRoles = new ArrayCollection(); + } + + public function getId(): ?int + { + return $this->id; + } + + public function getName(): string + { + return $this->name; + } + + public function setName(string $name): void + { + $this->name = $name; + } + + public function getTroop(): Troop + { + return $this->troop; + } + + public function setTroop(Troop $troop): void + { + $this->troop = $troop; + } + + /** + * @return Collection + */ + public function getUsersRoles(): Collection + { + return $this->usersRoles; + } +} diff --git a/app/Model/User/Troop.php b/app/Model/User/Troop.php new file mode 100644 index 000000000..deb6bc8c8 --- /dev/null +++ b/app/Model/User/Troop.php @@ -0,0 +1,229 @@ + + */ + #[ORM\OneToMany(mappedBy: 'troop', targetEntity: Patrol::class, cascade: ['persist'])] + protected Collection $patrols; + + /** + * Uživatelé. + * + * @var Collection + */ + #[ORM\OneToMany(mappedBy: 'troop', targetEntity: UserGroupRole::class, cascade: ['persist'])] + protected Collection $usersRoles; + + /** + * Poplatek za oddíl. + */ + #[ORM\Column(type: 'integer')] + protected int $fee; + + /** + * Variabilní symbol. + */ + #[ORM\ManyToOne(targetEntity: VariableSymbol::class, cascade: ['persist'])] + protected VariableSymbol $variableSymbol; + + /** + * Datum podání přihlášky. + */ + #[ORM\Column(type: 'datetime_immutable')] + protected DateTimeImmutable $applicationDate; + + /** + * Datum splatnosti. + */ + #[ORM\Column(type: 'date_immutable', nullable: true)] + protected ?DateTimeImmutable $maturityDate = null; + + /** + * Platební metoda. + */ + #[ORM\Column(type: 'string', nullable: true)] + protected ?string $paymentMethod = null; + + /** + * Datum zaplacení. + */ + #[ORM\Column(type: 'date_immutable', nullable: true)] + protected ?DateTimeImmutable $paymentDate = null; + + /** + * Spárovaná platba. + */ + #[ORM\ManyToOne(targetEntity: Payment::class, inversedBy: 'pairedTroops', cascade: ['persist'])] + protected ?Payment $payment = null; + + /** + * Příjmový doklad. Používá se pro generování id. + */ + #[ORM\ManyToOne(targetEntity: IncomeProof::class, cascade: ['persist'])] + protected ?IncomeProof $incomeProof = null; + + /** + * Stav přihlášky. + */ + #[ORM\Column(type: 'string')] + protected ?string $state = null; + + public function __construct() + { + $this->patrols = new ArrayCollection(); + $this->usersRoles = new ArrayCollection(); + } + + public function getId(): ?int + { + return $this->id; + } + + public function getName(): string + { + return $this->name; + } + + public function setName(string $name): void + { + $this->name = $name; + } + + /** + * @return Collection + */ + public function getPatrols(): Collection + { + return $this->patrols; + } + + /** + * @return Collection + */ + public function getUsersRoles(): Collection + { + return $this->usersRoles; + } + + public function getFee(): int + { + return $this->fee; + } + + public function setFee(int $fee): void + { + $this->fee = $fee; + } + + public function getVariableSymbol(): VariableSymbol + { + return $this->variableSymbol; + } + + public function setVariableSymbol(VariableSymbol $variableSymbol): void + { + $this->variableSymbol = $variableSymbol; + } + + public function getApplicationDate(): DateTimeImmutable + { + return $this->applicationDate; + } + + public function setApplicationDate(DateTimeImmutable $applicationDate): void + { + $this->applicationDate = $applicationDate; + } + + public function getMaturityDate(): ?DateTimeImmutable + { + return $this->maturityDate; + } + + public function setMaturityDate(?DateTimeImmutable $maturityDate): void + { + $this->maturityDate = $maturityDate; + } + + public function getPaymentMethod(): ?string + { + return $this->paymentMethod; + } + + public function setPaymentMethod(?string $paymentMethod): void + { + $this->paymentMethod = $paymentMethod; + } + + public function getPaymentDate(): ?DateTimeImmutable + { + return $this->paymentDate; + } + + public function setPaymentDate(?DateTimeImmutable $paymentDate): void + { + $this->paymentDate = $paymentDate; + } + + public function getPayment(): ?Payment + { + return $this->payment; + } + + public function setPayment(?Payment $payment): void + { + $this->payment = $payment; + } + + public function getIncomeProof(): ?IncomeProof + { + return $this->incomeProof; + } + + public function setIncomeProof(?IncomeProof $incomeProof): void + { + $this->incomeProof = $incomeProof; + } + + public function getState(): ?string + { + return $this->state; + } + + public function setState(?string $state): void + { + $this->state = $state; + } +} diff --git a/app/Model/User/UserGroupRole.php b/app/Model/User/UserGroupRole.php new file mode 100644 index 000000000..e138039c8 --- /dev/null +++ b/app/Model/User/UserGroupRole.php @@ -0,0 +1,90 @@ +id; + } + + public function getUser(): User + { + return $this->user; + } + + public function setUser(User $user): void + { + $this->user = $user; + } + + public function getTroop(): ?Troop + { + return $this->troop; + } + + public function setTroop(?Troop $troop): void + { + $this->troop = $troop; + } + + public function getPatrol(): ?Patrol + { + return $this->patrol; + } + + public function setPatrol(?Patrol $patrol): void + { + $this->patrol = $patrol; + } + + public function getRole(): Role + { + return $this->role; + } + + public function setRole(Role $role): void + { + $this->role = $role; + } +} diff --git a/migrations/Version20221015133440.php b/migrations/Version20221015133440.php new file mode 100644 index 000000000..e95137183 --- /dev/null +++ b/migrations/Version20221015133440.php @@ -0,0 +1,38 @@ +abortIf($this->connection->getDatabasePlatform()->getName() !== 'mysql', 'Migration can only be executed safely on \'mysql\'.'); + + $this->addSql('CREATE TABLE patrol (id INT AUTO_INCREMENT NOT NULL, troop_id INT DEFAULT NULL, name VARCHAR(255) NOT NULL, INDEX IDX_BFB2371263060AC (troop_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'); + $this->addSql('CREATE TABLE troop (id INT AUTO_INCREMENT NOT NULL, variable_symbol_id INT DEFAULT NULL, payment_id INT DEFAULT NULL, income_proof_id INT DEFAULT NULL, name VARCHAR(255) NOT NULL, fee INT NOT NULL, application_date DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\', maturity_date DATE DEFAULT NULL COMMENT \'(DC2Type:date_immutable)\', payment_method VARCHAR(255) DEFAULT NULL, payment_date DATE DEFAULT NULL COMMENT \'(DC2Type:date_immutable)\', state VARCHAR(255) NOT NULL, INDEX IDX_FAAD534C25813A9D (variable_symbol_id), INDEX IDX_FAAD534C4C3A3BB (payment_id), INDEX IDX_FAAD534CFE69EDFB (income_proof_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'); + $this->addSql('CREATE TABLE user_group_role (id INT AUTO_INCREMENT NOT NULL, user_id INT DEFAULT NULL, troop_id INT DEFAULT NULL, patrol_id INT DEFAULT NULL, role_id INT DEFAULT NULL, INDEX IDX_D95417F6A76ED395 (user_id), INDEX IDX_D95417F6263060AC (troop_id), INDEX IDX_D95417F6A7B49BA9 (patrol_id), INDEX IDX_D95417F6D60322AC (role_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'); + $this->addSql('ALTER TABLE patrol ADD CONSTRAINT FK_BFB2371263060AC FOREIGN KEY (troop_id) REFERENCES troop (id)'); + $this->addSql('ALTER TABLE troop ADD CONSTRAINT FK_FAAD534C25813A9D FOREIGN KEY (variable_symbol_id) REFERENCES variable_symbol (id)'); + $this->addSql('ALTER TABLE troop ADD CONSTRAINT FK_FAAD534C4C3A3BB FOREIGN KEY (payment_id) REFERENCES payment (id)'); + $this->addSql('ALTER TABLE troop ADD CONSTRAINT FK_FAAD534CFE69EDFB FOREIGN KEY (income_proof_id) REFERENCES income_proof (id)'); + $this->addSql('ALTER TABLE user_group_role ADD CONSTRAINT FK_D95417F6A76ED395 FOREIGN KEY (user_id) REFERENCES user (id)'); + $this->addSql('ALTER TABLE user_group_role ADD CONSTRAINT FK_D95417F6263060AC FOREIGN KEY (troop_id) REFERENCES troop (id)'); + $this->addSql('ALTER TABLE user_group_role ADD CONSTRAINT FK_D95417F6A7B49BA9 FOREIGN KEY (patrol_id) REFERENCES patrol (id)'); + $this->addSql('ALTER TABLE user_group_role ADD CONSTRAINT FK_D95417F6D60322AC FOREIGN KEY (role_id) REFERENCES role (id)'); + $this->addSql('ALTER TABLE role ADD type VARCHAR(255) NOT NULL, ADD minimum_age_warning VARCHAR(255) DEFAULT NULL, ADD maximum_age INT NOT NULL, ADD maximum_age_warning VARCHAR(255) DEFAULT NULL'); + } + + public function down(Schema $schema): void + { + } +} From a6f43a1141bdddbdf6ac10392fe4d0de46351797 Mon Sep 17 00:00:00 2001 From: bojovyletoun <662941+bojovyletoun@users.noreply.github.com> Date: Wed, 19 Oct 2022 21:34:11 +0200 Subject: [PATCH 05/41] =?UTF-8?q?=C3=BApravy=20rol=C3=AD=20(#888)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * migration * + Validátor max.věku * Min/Max věk - lokalizace formulářů Přidána lokalizace pro maximální věk a pro formulářové prvky vlastních chybových hlášek omezení věků * + min/max věk + hlášky formuláři rolí -AdminModule * rozšíření kontroly max.věku role v Web Modulu * min/max věk -AdminModule - zahrnutí AddRole rozšíření min/maxAgeWarning dědičnost rolí * refactoring: validateRoles...() -> trait * fix: chybějící anotace Validators::validateRoleMaximumAge * +validátor pro hlášek věkových limitů rolí Tohle je poněkud divočejší úprava existujících funkcí které původně validují věkové limity role. Pořád vrací bool, ale navíc dokážou do 3.optional argumentu vložit ty hlášky. Prosím o zvýšenou kontrolu * Zobrazování chybových hlášek věk.limitůrolí (WebModule) * fix: coding standard, typehint * migration removed * class name fix Co-authored-by: Jan Staněk Co-authored-by: bojovyletoun <> --- app/AdminModule/Forms/AddRoleFormFactory.php | 3 + app/AdminModule/Forms/EditRoleFormFactory.php | 23 ++++ app/Utils/Validators.php | 70 +++++++---- .../Forms/ApplicationFormFactory.php | 68 +---------- app/WebModule/Forms/RolesFormFactory.php | 65 +---------- app/WebModule/Forms/RolesFormFunctions.php | 109 ++++++++++++++++++ app/lang/admin.cs_CZ.neon | 9 ++ app/lang/web.cs_CZ.neon | 4 +- 8 files changed, 205 insertions(+), 146 deletions(-) create mode 100644 app/WebModule/Forms/RolesFormFunctions.php diff --git a/app/AdminModule/Forms/AddRoleFormFactory.php b/app/AdminModule/Forms/AddRoleFormFactory.php index fd41dd690..79573c1ea 100644 --- a/app/AdminModule/Forms/AddRoleFormFactory.php +++ b/app/AdminModule/Forms/AddRoleFormFactory.php @@ -91,6 +91,9 @@ public function processForm(Form $form, stdClass $values): void $role->setFee($parent->getFee()); $role->setCapacity($parent->getCapacity()); $role->setMinimumAge($parent->getMinimumAge()); + $role->setMaximumAge($parent->getMaximumAge()); + $role->setMinimumAgeWarning($parent->getMinimumAgeWarning()); + $role->setMaximumAgeWarning($parent->getMaximumAgeWarning()); $role->setApprovedAfterRegistration($parent->isApprovedAfterRegistration()); $role->setSyncedWithSkautIS($parent->isSyncedWithSkautIS()); $role->setRegisterable($parent->isRegisterable()); diff --git a/app/AdminModule/Forms/EditRoleFormFactory.php b/app/AdminModule/Forms/EditRoleFormFactory.php index 2bac062fa..e14b327a6 100644 --- a/app/AdminModule/Forms/EditRoleFormFactory.php +++ b/app/AdminModule/Forms/EditRoleFormFactory.php @@ -109,6 +109,23 @@ public function create(int $id): Form ->addRule(Form::INTEGER, 'admin.acl.roles.minimum_age.error_format') ->addRule(Form::MIN, 'admin.acl.roles.minimum_age.error_low', 0); + $form->addText('minimumAgeMsg', 'admin.acl.roles.minimum_age.custom_msg_label') + ->setHtmlAttribute('data-toggle', 'tooltip') + ->setHtmlAttribute('data-placement', 'bottom') + ->setHtmlAttribute('title', $form->getTranslator()->translate('admin.acl.roles.minimum_age.custom_msg_note')); + + $form->addText('maximumAge', 'admin.acl.roles.maximum_age.label') + ->setHtmlAttribute('data-toggle', 'tooltip') + ->setHtmlAttribute('data-placement', 'bottom') + ->setHtmlAttribute('title', $form->getTranslator()->translate('admin.acl.roles.maximum_age.note')) + ->addRule(Form::INTEGER, 'admin.acl.roles.maximum_age.error_format') + ->addRule(Form::MIN, 'admin.acl.roles.maximum_age.error_low', 0); + + $form->addText('maximumAgeMsg', 'admin.acl.roles.maximum_age.custom_msg_label') + ->setHtmlAttribute('data-toggle', 'tooltip') + ->setHtmlAttribute('data-placement', 'bottom') + ->setHtmlAttribute('title', $form->getTranslator()->translate('admin.acl.roles.maximum_age.custom_msg_note')); + $form->addMultiSelect('permissions', 'admin.acl.roles_permissions', $this->preparePermissionsOptions()); $pagesOptions = $this->pageRepository->getPagesOptions(); @@ -164,6 +181,9 @@ public function create(int $id): Form 'feeFromSubevents' => $this->role->getFee() === null, 'fee' => $this->role->getFee() ?? 0, 'minimumAge' => $this->role->getMinimumAge(), + 'maximumAge' => $this->role->getMaximumAge(), + 'minimumAgeMsg' => $this->role->getMinimumAgeWarning(), + 'maximumAgeMsg' => $this->role->getMaximumAgeWarning(), 'permissions' => $this->permissionRepository->findPermissionsIds($this->role->getPermissions()), 'pages' => $this->pageRepository->findPagesSlugs($this->role->getPages()), 'redirectAfterLogin' => array_key_exists($redirectAfterLoginValue, $pagesOptions) ? $redirectAfterLoginValue : null, @@ -197,6 +217,9 @@ public function processForm(Form $form, stdClass $values): void $this->role->setCapacity($capacity); $this->role->setApprovedAfterRegistration($values->approvedAfterRegistration); $this->role->setMinimumAge($values->minimumAge); + $this->role->setMaximumAge($values->maximumAge); + $this->role->setMinimumAgeWarning($values->minimumAgeMsg); + $this->role->setMaximumAgeWarning($values->maximumAgeMsg); $this->role->setPermissions($this->permissionRepository->findPermissionsByIds($values->permissions)); $this->role->setPages($this->pageRepository->findPagesBySlugs($values->pages)); $this->role->setRedirectAfterLogin($values->redirectAfterLogin); diff --git a/app/Utils/Validators.php b/app/Utils/Validators.php index be753f87b..420d235e0 100644 --- a/app/Utils/Validators.php +++ b/app/Utils/Validators.php @@ -20,6 +20,7 @@ use function array_map; use function explode; +use function sprintf; use function trim; /** @@ -50,6 +51,54 @@ public function validateRolesNonregistered(Collection $selectedRoles, User $user return true; } + /** + * Ověří požadovaný minimální věk. + * + * @param Collection $selectedRoles + * @param string[] $warnings + * + * @throws SettingsItemNotFoundException + * @throws Throwable + */ + public function validateRolesMinimumAge(Collection $selectedRoles, User $user, array &$warnings = []): bool + { + $age = $this->queryBus->handle(new SettingDateValueQuery(Settings::SEMINAR_FROM_DATE))->diff($user->getBirthdate())->y; + $canary = true; + foreach ($selectedRoles as $role) { + $min = $role->getMinimumAge(); + if ($min > $age) { + $warnings[] = sprintf($role->getMinimumAgeWarning(), $min, $age); + $canary = false; + } + } + + return $canary; + } + + /** + * Ověří požadovaný maximální věk. + * + * @param Collection $selectedRoles + * @param string[] $warnings + * + * @throws SettingsItemNotFoundException + * @throws Throwable + */ + public function validateRolesMaximumAge(Collection $selectedRoles, User $user, array &$warnings = []): bool + { + $age = $this->queryBus->handle(new SettingDateValueQuery(Settings::SEMINAR_TO_DATE))->diff($user->getBirthdate())->y; + $canary = true; + foreach ($selectedRoles as $role) { + $max = $role->getMaximumAge(); + if ($max > 0 && $max < $age) { // Hodnota 0 je bez omezení + $warnings[] = sprintf($role->getMaximumAgeWarning(), $max, $age); + $canary = false; + } + } + + return $canary; + } + /** * Ověří kapacitu rolí. * @@ -122,27 +171,6 @@ public function validateRolesRegisterable(Collection $selectedRoles, User $user) return true; } - /** - * Ověří požadovaný minimální věk. - * - * @param Collection $selectedRoles - * - * @throws SettingsItemNotFoundException - * @throws Throwable - */ - public function validateRolesMinimumAge(Collection $selectedRoles, User $user): bool - { - $age = $this->queryBus->handle(new SettingDateValueQuery(Settings::SEMINAR_FROM_DATE))->diff($user->getBirthdate())->y; - - foreach ($selectedRoles as $role) { - if ($role->getMinimumAge() > $age) { - return false; - } - } - - return true; - } - /** * Ověří kapacitu podakcí. * diff --git a/app/WebModule/Forms/ApplicationFormFactory.php b/app/WebModule/Forms/ApplicationFormFactory.php index 2e4a1f122..f90858c33 100644 --- a/app/WebModule/Forms/ApplicationFormFactory.php +++ b/app/WebModule/Forms/ApplicationFormFactory.php @@ -5,7 +5,6 @@ namespace App\WebModule\Forms; use App\Model\Acl\Repositories\RoleRepository; -use App\Model\Acl\Role; use App\Model\CustomInput\CustomCheckbox; use App\Model\CustomInput\CustomCheckboxValue; use App\Model\CustomInput\CustomDate; @@ -23,7 +22,6 @@ use App\Model\CustomInput\Repositories\CustomInputRepository; use App\Model\CustomInput\Repositories\CustomInputValueRepository; use App\Model\Enums\Sex; -use App\Model\Settings\Exceptions\SettingsItemNotFoundException; use App\Model\Settings\Queries\SettingStringValueQuery; use App\Model\Settings\Settings; use App\Model\Structure\Repositories\SubeventRepository; @@ -70,6 +68,7 @@ class ApplicationFormFactory { use Nette\SmartObject; + use RolesFormFunctions; /** * Přihlášený uživatel. @@ -177,7 +176,8 @@ public function create(int $id): Form 'state' => $this->user->getState(), ]); - $form->onSuccess[] = [$this, 'processForm']; + $form->onSuccess[] = [$this, 'processForm']; + $form->onValidate[] = [$this, 'validateRolesAgeLimits']; return $form; } @@ -435,7 +435,8 @@ private function addRolesSelect(Form $form): void $rolesSelect->addRule(Form::FILLED, 'web.application_content.roles_empty') ->addRule([$this, 'validateRolesCapacities'], 'web.application_content.roles_capacity_occupied') ->addRule([$this, 'validateRolesRegisterable'], 'web.application_content.roles_not_registerable') - ->addRule([$this, 'validateRolesMinimumAge'], 'web.application_content.roles_require_minimum_age'); + ->addRule([$this, 'validateRolesMinimumAge'], 'web.application_content.roles_require_minimum_age') + ->addRule([$this, 'validateRolesMaximumAge'], 'web.application_content.roles_require_maximum_age'); // generovani chybovych hlasek pro vsechny kombinace roli foreach ($this->roleRepository->findFilteredRoles(true, false, true, $this->user) as $role) { @@ -481,16 +482,6 @@ public function validateSubeventsCapacities(MultiSelectBox $field): bool return $this->validators->validateSubeventsCapacities($selectedSubevents, $this->user); } - /** - * Ověří kapacity rolí. - */ - public function validateRolesCapacities(MultiSelectBox $field): bool - { - $selectedRoles = $this->roleRepository->findRolesByIds($field->getValue()); - - return $this->validators->validateRolesCapacities($selectedRoles, $this->user); - } - /** * Ověří kompatibilitu podakcí. * @@ -517,55 +508,6 @@ public function validateSubeventsRequired(MultiSelectBox $field, array $args): b return $this->validators->validateSubeventsRequired($selectedSubevents, $testSubevent); } - /** - * Ověří kompatibilitu rolí. - * - * @param Role[] $args - */ - public function validateRolesIncompatible(MultiSelectBox $field, array $args): bool - { - $selectedRoles = $this->roleRepository->findRolesByIds($field->getValue()); - $testRole = $args[0]; - - return $this->validators->validateRolesIncompatible($selectedRoles, $testRole); - } - - /** - * Ověří výběr požadovaných rolí. - * - * @param Role[] $args - */ - public function validateRolesRequired(MultiSelectBox $field, array $args): bool - { - $selectedRoles = $this->roleRepository->findRolesByIds($field->getValue()); - $testRole = $args[0]; - - return $this->validators->validateRolesRequired($selectedRoles, $testRole); - } - - /** - * Ověří registrovatelnost rolí. - */ - public function validateRolesRegisterable(MultiSelectBox $field): bool - { - $selectedRoles = $this->roleRepository->findRolesByIds($field->getValue()); - - return $this->validators->validateRolesRegisterable($selectedRoles, $this->user); - } - - /** - * Ověří požadovaný minimální věk. - * - * @throws SettingsItemNotFoundException - * @throws Throwable - */ - public function validateRolesMinimumAge(MultiSelectBox $field): bool - { - $selectedRoles = $this->roleRepository->findRolesByIds($field->getValue()); - - return $this->validators->validateRolesMinimumAge($selectedRoles, $this->user); - } - /** * Přepíná povinnost podakcí podle kombinace rolí. diff --git a/app/WebModule/Forms/RolesFormFactory.php b/app/WebModule/Forms/RolesFormFactory.php index 117a76f31..586ebbf9c 100644 --- a/app/WebModule/Forms/RolesFormFactory.php +++ b/app/WebModule/Forms/RolesFormFactory.php @@ -19,7 +19,6 @@ use DateTimeImmutable; use Nette; use Nette\Application\UI\Form; -use Nette\Forms\Controls\MultiSelectBox; use Nette\Localization\Translator; use stdClass; use Throwable; @@ -30,6 +29,7 @@ class RolesFormFactory { use Nette\SmartObject; + use RolesFormFunctions; /** * Přihlášený uživatel. @@ -69,6 +69,7 @@ public function create(int $id): Form ->addRule([$this, 'validateRolesCapacities'], 'web.profile.roles_capacity_occupied') ->addRule([$this, 'validateRolesRegisterable'], 'web.profile.roles_not_registerable') ->addRule([$this, 'validateRolesMinimumAge'], 'web.application_content.roles_require_minimum_age') + ->addRule([$this, 'validateRolesMaximumAge'], 'web.application_content.roles_require_maximum_age') ->setDisabled(! $this->applicationService->isAllowedEditRegistration($this->user)); foreach ($this->roleRepository->findFilteredRoles(true, false, true, $this->user) as $role) { @@ -144,7 +145,8 @@ public function create(int $id): Form 'id' => $id, 'roles' => $this->roleRepository->findRolesIds($this->user->getRoles()), ]); - $form->onSuccess[] = [$this, 'processForm']; + $form->onSuccess[] = [$this, 'processForm']; + $form->onValidate[] = [$this, 'validateRolesAgeLimits']; return $form; } @@ -163,63 +165,4 @@ public function processForm(Form $form, stdClass $values): void $this->applicationService->cancelRegistration($this->user, ApplicationState::CANCELED, $this->user); } } - - /** - * Ověří kapacitu rolí. - */ - public function validateRolesCapacities(MultiSelectBox $field): bool - { - $selectedRoles = $this->roleRepository->findRolesByIds($field->getValue()); - - return $this->validators->validateRolesCapacities($selectedRoles, $this->user); - } - - /** - * Ověří kompatibilitu rolí. - * - * @param Role[] $args - */ - public function validateRolesIncompatible(MultiSelectBox $field, array $args): bool - { - $selectedRoles = $this->roleRepository->findRolesByIds($field->getValue()); - $testRole = $args[0]; - - return $this->validators->validateRolesIncompatible($selectedRoles, $testRole); - } - - /** - * Ověří výběr vyžadovaných rolí. - * - * @param Role[] $args - */ - public function validateRolesRequired(MultiSelectBox $field, array $args): bool - { - $selectedRoles = $this->roleRepository->findRolesByIds($field->getValue()); - $testRole = $args[0]; - - return $this->validators->validateRolesRequired($selectedRoles, $testRole); - } - - /** - * Ověří registrovatelnost rolí. - */ - public function validateRolesRegisterable(MultiSelectBox $field): bool - { - $selectedRoles = $this->roleRepository->findRolesByIds($field->getValue()); - - return $this->validators->validateRolesRegisterable($selectedRoles, $this->user); - } - - /** - * Ověří požadovaný minimální věk. - * - * @throws SettingsItemNotFoundException - * @throws Throwable - */ - public function validateRolesMinimumAge(MultiSelectBox $field): bool - { - $selectedRoles = $this->roleRepository->findRolesByIds($field->getValue()); - - return $this->validators->validateRolesMinimumAge($selectedRoles, $this->user); - } } diff --git a/app/WebModule/Forms/RolesFormFunctions.php b/app/WebModule/Forms/RolesFormFunctions.php new file mode 100644 index 000000000..3c0c873b1 --- /dev/null +++ b/app/WebModule/Forms/RolesFormFunctions.php @@ -0,0 +1,109 @@ +roleRepository->findRolesByIds($values->roles); + $minWarnings = []; + $this->validators->validateRolesMinimumAge($selectedRoles, $this->user, $minWarnings); + foreach ($minWarnings as $error) { + $form->addError($error); + } + + // Max a Min se kontroluje zvlášť, protože rozdíl může být jednou vůči FROM_DATE a pak TO_DATE + $maxWarnings = []; + $this->validators->validateRolesMaximumAge($selectedRoles, $this->user, $maxWarnings); + foreach ($maxWarnings as $error) { + $form->addError($error); + } + } + + /** + * Ověří požadovaný minimální věk. + * + * @throws SettingsItemNotFoundException + * @throws Throwable + */ + public function validateRolesMinimumAge(MultiSelectBox $field): bool + { + $selectedRoles = $this->roleRepository->findRolesByIds($field->getValue()); + + return $this->validators->validateRolesMinimumAge($selectedRoles, $this->user); + } + + /** + * Ověří požadovaný maximální věk. + * + * @throws SettingsItemNotFoundException + * @throws Throwable + */ + public function validateRolesMaximumAge(MultiSelectBox $field): bool + { + $selectedRoles = $this->roleRepository->findRolesByIds($field->getValue()); + + return $this->validators->validateRolesMaximumAge($selectedRoles, $this->user); + } + + /** + * Ověří kapacitu rolí. + */ + public function validateRolesCapacities(MultiSelectBox $field): bool + { + $selectedRoles = $this->roleRepository->findRolesByIds($field->getValue()); + + return $this->validators->validateRolesCapacities($selectedRoles, $this->user); + } + + /** + * Ověří kompatibilitu rolí. + * + * @param Role[] $args + */ + public function validateRolesIncompatible(MultiSelectBox $field, array $args): bool + { + $selectedRoles = $this->roleRepository->findRolesByIds($field->getValue()); + $testRole = $args[0]; + + return $this->validators->validateRolesIncompatible($selectedRoles, $testRole); + } + + /** + * Ověří registrovatelnost rolí. + */ + public function validateRolesRegisterable(MultiSelectBox $field): bool + { + $selectedRoles = $this->roleRepository->findRolesByIds($field->getValue()); + + return $this->validators->validateRolesRegisterable($selectedRoles, $this->user); + } + + /** + * Ověří výběr vyžadovaných rolí. + * + * @param Role[] $args + */ + public function validateRolesRequired(MultiSelectBox $field, array $args): bool + { + $selectedRoles = $this->roleRepository->findRolesByIds($field->getValue()); + $testRole = $args[0]; + + return $this->validators->validateRolesRequired($selectedRoles, $testRole); + } +} diff --git a/app/lang/admin.cs_CZ.neon b/app/lang/admin.cs_CZ.neon index 96be25b97..5f451a5cc 100644 --- a/app/lang/admin.cs_CZ.neon +++ b/app/lang/admin.cs_CZ.neon @@ -580,6 +580,15 @@ acl: note: "Účastník musí minimálního věku dosáhnout první den akce." error_format: "Zadejte číslo." error_low: "Minimální věk nesmí být menší než 0." + custom_msg_label: "Vlastní chybová hláška k [↑]" + custom_msg_note: "Vlastní text chyby, která se zobrazí při nedosažení věku pro tuto roli. Možné použít %d" + maximum_age: + label: "Maximální věk" + note: "Účastník nesmí maximální věk překročit první den akce." + error_format: "Zadejte číslo." + error_low: "Maximální věk nesmí být menší než 0." + custom_msg_label: "Vlastní chybová hláška k [↑]" + custom_msg_note: "Vlastní text chyby, která se zobrazí při překročení věku pro tuto roli. Možné použít %d" mailing: menu: diff --git a/app/lang/web.cs_CZ.neon b/app/lang/web.cs_CZ.neon index 6960bff20..9e5a77086 100644 --- a/app/lang/web.cs_CZ.neon +++ b/app/lang/web.cs_CZ.neon @@ -36,8 +36,9 @@ profile: incompatible_roles_selected: "Není možné kombinovat roli %role% s rolemi: %incompatibleRoles%." roles_empty: "Musí být vybrána alespoň jedna role." roles_not_registerable: "Registrace do některé z rolí již není možná." - roles_capacity_occupied: "Všechna místa v některé roli jsou obsazena." roles_require_minimum_age: "Některá role vyžaduje vyšší věk." + roles_require_maximum_age: "Některá role vyžaduje nižší věk." + roles_capacity_occupied: "Všechna místa v některé roli jsou obsazena." change_roles: "Změnit role" change_roles_confirm: "Pokud nová role vyžaduje schválení organizátora, budete Vám nastavena role \"Neschválený\". Opravdu chcete pokračovat?" change_roles_disabled: "Změna rolí již není možná nebo nejste na seminář registrováni." @@ -166,6 +167,7 @@ application_content: roles_not_registerable: "Registrace do některé z rolí již není možná." roles_capacity_occupied: "Všechna místa v některé roli jsou obsazena." roles_require_minimum_age: "Některá role vyžaduje vyšší věk." + roles_require_maximum_age: "Některá role vyžaduje nižší věk." agreement_empty: "Musíte souhlasit s poskytnutím údajů." register: "Registrovat" register_synchronization_failed: "Synchronizace se skautIS se nepodařila. Zkuste se znovu přihlásit a upravit údaje v profilu." From 21d5f763a84f7b484b0a5c8df06afb706c4fc6c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Stan=C4=9Bk?= Date: Thu, 27 Oct 2022 16:57:45 +0200 Subject: [PATCH 06/41] =?UTF-8?q?Registra=C4=8Dn=C3=AD=20formul=C3=A1?= =?UTF-8?q?=C5=99=20(#889)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * troop application content * role switch * skautis personall * application draft * db model update, commands * forms * forms * 2 more steps of form * coding standard * coding standard * overview, group lists * coding standard * coding standard * cleanup, fixes deploment * deploy from PR * deploy revert, cs fix * fixes * missing lang added * write fee to db * maturity date set * validate age * filter 18+ for troops * patrol attendees count validation * troop count validation * coding standard * leader validation --- .../Handlers/RolesByTypeQueryHandler.php | 25 ++ app/Model/Acl/Queries/RolesByTypeQuery.php | 17 ++ app/Model/Acl/Repositories/RoleRepository.php | 10 + app/Model/Acl/Role.php | 9 + app/Model/Cms/Content.php | 7 + app/Model/Cms/TroopApplicationContent.php | 17 ++ app/Model/Enums/TroopApplicationState.php | 33 +++ app/Model/User/Commands/ConfirmPatrol.php | 18 ++ app/Model/User/Commands/ConfirmTroop.php | 24 ++ .../Handlers/ConfirmPatrolHandler.php | 24 ++ .../Commands/Handlers/ConfirmTroopHandler.php | 71 +++++ .../Handlers/RegisterTroopHandler.php | 50 ++++ .../Handlers/UpdateGroupMembersHandler.php | 146 +++++++++++ app/Model/User/Commands/RegisterTroop.php | 19 ++ .../User/Commands/UpdateGroupMembers.php | 42 +++ app/Model/User/Patrol.php | 46 +++- .../Handlers/PatrolByIdQueryHandler.php | 22 ++ ...trolByTroopAndNotConfirmedQueryHandler.php | 22 ++ .../Handlers/TroopByIdQueryHandler.php | 22 ++ .../Handlers/TroopByLeaderQueryHandler.php | 22 ++ app/Model/User/Queries/PatrolByIdQuery.php | 17 ++ .../PatrolByTroopAndNotConfirmedQuery.php | 17 ++ app/Model/User/Queries/TroopByIdQuery.php | 17 ++ app/Model/User/Queries/TroopByLeaderQuery.php | 17 ++ .../User/Repositories/PatrolRepository.php | 36 +++ .../User/Repositories/TroopRepository.php | 41 +++ .../Repositories/UserGroupRoleRepository.php | 54 ++++ .../User/Repositories/UserRepository.php | 8 + app/Model/User/Troop.php | 159 ++++++++++- app/Model/User/User.php | 120 +++++++++ app/Model/User/UserGroupRole.php | 10 +- app/Services/SkautIsService.php | 68 ++++- ...ITroopApplicationContentControlFactory.php | 13 + .../TroopApplicationContentControl.php | 183 +++++++++++++ .../templates/troop_application_content.latte | 204 +++++++++++++++ .../Forms/GroupAdditionalInfoForm.php | 143 ++++++++++ app/WebModule/Forms/GroupConfirmForm.php | 120 +++++++++ app/WebModule/Forms/GroupMembersForm.php | 247 ++++++++++++++++++ .../Forms/IGroupAdditionalInfoFormFactory.php | 16 ++ .../Forms/IGroupConfirmFormFactory.php | 16 ++ .../Forms/IGroupMembersFormFactory.php | 16 ++ .../Forms/ITroopApplicationFormFactory.php | 16 ++ .../Forms/ITroopConfirmFormFactory.php | 16 ++ app/WebModule/Forms/TroopApplicationForm.php | 153 +++++++++++ app/WebModule/Forms/TroopConfirmForm.php | 134 ++++++++++ .../group_additional_info_form.latte | 73 ++++++ .../Forms/templates/group_confirm_form.latte | 47 ++++ .../Forms/templates/group_members_form.latte | 59 +++++ .../Forms/templates/troop_confirm_form.latte | 81 ++++++ app/WebModule/Presenters/PagePresenter.php | 10 + app/assets/common/main.js | 1 + app/lang/common.cs_CZ.neon | 2 + migrations/Version20221015133440.php | 2 +- migrations/Version20221017191553.php | 28 ++ migrations/Version20221021195625.php | 30 +++ migrations/Version20221022182325.php | 32 +++ 56 files changed, 2825 insertions(+), 27 deletions(-) create mode 100644 app/Model/Acl/Queries/Handlers/RolesByTypeQueryHandler.php create mode 100644 app/Model/Acl/Queries/RolesByTypeQuery.php create mode 100644 app/Model/Cms/TroopApplicationContent.php create mode 100644 app/Model/Enums/TroopApplicationState.php create mode 100644 app/Model/User/Commands/ConfirmPatrol.php create mode 100644 app/Model/User/Commands/ConfirmTroop.php create mode 100644 app/Model/User/Commands/Handlers/ConfirmPatrolHandler.php create mode 100644 app/Model/User/Commands/Handlers/ConfirmTroopHandler.php create mode 100644 app/Model/User/Commands/Handlers/RegisterTroopHandler.php create mode 100644 app/Model/User/Commands/Handlers/UpdateGroupMembersHandler.php create mode 100644 app/Model/User/Commands/RegisterTroop.php create mode 100644 app/Model/User/Commands/UpdateGroupMembers.php create mode 100644 app/Model/User/Queries/Handlers/PatrolByIdQueryHandler.php create mode 100644 app/Model/User/Queries/Handlers/PatrolByTroopAndNotConfirmedQueryHandler.php create mode 100644 app/Model/User/Queries/Handlers/TroopByIdQueryHandler.php create mode 100644 app/Model/User/Queries/Handlers/TroopByLeaderQueryHandler.php create mode 100644 app/Model/User/Queries/PatrolByIdQuery.php create mode 100644 app/Model/User/Queries/PatrolByTroopAndNotConfirmedQuery.php create mode 100644 app/Model/User/Queries/TroopByIdQuery.php create mode 100644 app/Model/User/Queries/TroopByLeaderQuery.php create mode 100644 app/Model/User/Repositories/PatrolRepository.php create mode 100644 app/Model/User/Repositories/TroopRepository.php create mode 100644 app/Model/User/Repositories/UserGroupRoleRepository.php create mode 100644 app/WebModule/Components/ITroopApplicationContentControlFactory.php create mode 100644 app/WebModule/Components/TroopApplicationContentControl.php create mode 100644 app/WebModule/Components/templates/troop_application_content.latte create mode 100644 app/WebModule/Forms/GroupAdditionalInfoForm.php create mode 100644 app/WebModule/Forms/GroupConfirmForm.php create mode 100644 app/WebModule/Forms/GroupMembersForm.php create mode 100644 app/WebModule/Forms/IGroupAdditionalInfoFormFactory.php create mode 100644 app/WebModule/Forms/IGroupConfirmFormFactory.php create mode 100644 app/WebModule/Forms/IGroupMembersFormFactory.php create mode 100644 app/WebModule/Forms/ITroopApplicationFormFactory.php create mode 100644 app/WebModule/Forms/ITroopConfirmFormFactory.php create mode 100644 app/WebModule/Forms/TroopApplicationForm.php create mode 100644 app/WebModule/Forms/TroopConfirmForm.php create mode 100644 app/WebModule/Forms/templates/group_additional_info_form.latte create mode 100644 app/WebModule/Forms/templates/group_confirm_form.latte create mode 100644 app/WebModule/Forms/templates/group_members_form.latte create mode 100644 app/WebModule/Forms/templates/troop_confirm_form.latte create mode 100644 migrations/Version20221017191553.php create mode 100644 migrations/Version20221021195625.php create mode 100644 migrations/Version20221022182325.php diff --git a/app/Model/Acl/Queries/Handlers/RolesByTypeQueryHandler.php b/app/Model/Acl/Queries/Handlers/RolesByTypeQueryHandler.php new file mode 100644 index 000000000..0baa5431d --- /dev/null +++ b/app/Model/Acl/Queries/Handlers/RolesByTypeQueryHandler.php @@ -0,0 +1,25 @@ +roleRepository->findByType($query->getType()); + } +} diff --git a/app/Model/Acl/Queries/RolesByTypeQuery.php b/app/Model/Acl/Queries/RolesByTypeQuery.php new file mode 100644 index 000000000..ffed4e8c5 --- /dev/null +++ b/app/Model/Acl/Queries/RolesByTypeQuery.php @@ -0,0 +1,17 @@ +type; + } +} diff --git a/app/Model/Acl/Repositories/RoleRepository.php b/app/Model/Acl/Repositories/RoleRepository.php index 5391ba225..99670a246 100644 --- a/app/Model/Acl/Repositories/RoleRepository.php +++ b/app/Model/Acl/Repositories/RoleRepository.php @@ -53,6 +53,16 @@ public function findBySystemName(string $name): Role return $this->getRepository()->findOneBy(['systemName' => $name]); } + /** + * Vrací role podle typu. + * + * @return Role[] + */ + public function findByType(string $type): array + { + return $this->getRepository()->findBy(['type' => $type]); + } + /** * Vrací id naposledy přidané role. * diff --git a/app/Model/Acl/Role.php b/app/Model/Acl/Role.php index a691f06cf..d9b1a28a7 100644 --- a/app/Model/Acl/Role.php +++ b/app/Model/Acl/Role.php @@ -68,6 +68,12 @@ class Role */ public const TEST = 'test'; + public const PATROL_LEADER = 'patrol_leader'; + + public const LEADER = 'leader'; + + public const ESCORT = 'escort'; + /** @var string[] */ public static array $roles = [ self::GUEST, @@ -78,6 +84,9 @@ class Role self::LECTOR, self::ORGANIZER, self::ADMIN, + self::PATROL_LEADER, + self::LEADER, + self::ESCORT, ]; #[ORM\Id] #[ORM\GeneratedValue] diff --git a/app/Model/Cms/Content.php b/app/Model/Cms/Content.php index 72e176624..e2b5b2d40 100644 --- a/app/Model/Cms/Content.php +++ b/app/Model/Cms/Content.php @@ -25,6 +25,7 @@ 'text_content' => TextContent::class, 'document_content' => DocumentContent::class, 'application_content' => ApplicationContent::class, + 'troop_application_content' => TroopApplicationContent::class, 'html_content' => HtmlContent::class, 'faq_content' => FaqContent::class, 'news_content' => NewsContent::class, @@ -61,6 +62,11 @@ abstract class Content implements IContent */ public const APPLICATION = 'application'; + /** + * TroopApplicationContent. + */ + public const TROOP_APPLICATION = 'troop_application'; + /** * HtmlContent. */ @@ -140,6 +146,7 @@ abstract class Content implements IContent self::NEWS, self::DOCUMENT, self::APPLICATION, + self::TROOP_APPLICATION, self::PROGRAMS, self::CONTACT_FORM, self::FAQ, diff --git a/app/Model/Cms/TroopApplicationContent.php b/app/Model/Cms/TroopApplicationContent.php new file mode 100644 index 000000000..554b5c091 --- /dev/null +++ b/app/Model/Cms/TroopApplicationContent.php @@ -0,0 +1,17 @@ +patrolId; + } +} diff --git a/app/Model/User/Commands/ConfirmTroop.php b/app/Model/User/Commands/ConfirmTroop.php new file mode 100644 index 000000000..e1332a9d1 --- /dev/null +++ b/app/Model/User/Commands/ConfirmTroop.php @@ -0,0 +1,24 @@ +troop_id; + } + + public function getPairedTroopCode(): ?string + { + return $this->pairedTroopCode; + } +} diff --git a/app/Model/User/Commands/Handlers/ConfirmPatrolHandler.php b/app/Model/User/Commands/Handlers/ConfirmPatrolHandler.php new file mode 100644 index 000000000..54bf1564e --- /dev/null +++ b/app/Model/User/Commands/Handlers/ConfirmPatrolHandler.php @@ -0,0 +1,24 @@ +patrolRepository->findById($command->getPatrolId()); + $patrol->setConfirmed(true); + $this->patrolRepository->save($patrol); + } +} diff --git a/app/Model/User/Commands/Handlers/ConfirmTroopHandler.php b/app/Model/User/Commands/Handlers/ConfirmTroopHandler.php new file mode 100644 index 000000000..5866c747b --- /dev/null +++ b/app/Model/User/Commands/Handlers/ConfirmTroopHandler.php @@ -0,0 +1,71 @@ +troopRepository->findById($command->getTroopId()); + $troop->setState(TroopApplicationState::WAITING_FOR_PAYMENT); + $troop->setPairedTroopCode($command->getPairedTroopCode()); + $troop->setFee($troop->countFee()); + $troop->setApplicationDate(new DateTimeImmutable()); + $troop->setMaturityDate($this->countMaturityDate()); + $this->troopRepository->save($troop); + } + + /** + * Vypočítá datum splatnosti podle zvolené metody. + */ + private function countMaturityDate(): ?DateTimeImmutable + { + switch ( + $this->queryBus->handle( + new SettingStringValueQuery(Settings::MATURITY_TYPE) + ) + ) { + case MaturityType::DATE: + return $this->queryBus->handle(new SettingDateValueQuery(Settings::MATURITY_DATE)); + + case MaturityType::DAYS: + return (new DateTimeImmutable())->modify('+' . $this->queryBus->handle(new SettingIntValueQuery(Settings::MATURITY_DAYS)) . ' days'); + + case MaturityType::WORK_DAYS: + $workDays = $this->queryBus->handle(new SettingIntValueQuery(Settings::MATURITY_WORK_DAYS)); + $date = new DateTimeImmutable(); + + for ($i = 0; $i < $workDays;) { + $date = $date->modify('+1 days'); + $holidays = Yasumi::create('CzechRepublic', (int) $date->format('Y')); + + if ($holidays->isWorkingDay($date)) { + $i++; + } + } + + return $date; + } + + return null; + } +} diff --git a/app/Model/User/Commands/Handlers/RegisterTroopHandler.php b/app/Model/User/Commands/Handlers/RegisterTroopHandler.php new file mode 100644 index 000000000..9f9263397 --- /dev/null +++ b/app/Model/User/Commands/Handlers/RegisterTroopHandler.php @@ -0,0 +1,50 @@ +em->wrapInTransaction(function () use ($command): void { + $variableSymbolCode = $this->queryBus->handle(new SettingStringValueQuery(Settings::VARIABLE_SYMBOL_CODE)); + + $variableSymbol = new VariableSymbol(); + $this->variableSymbolRepository->save($variableSymbol); + + $variableSymbolText = $variableSymbolCode . str_pad(strval($variableSymbol->getId()), 6, '0', STR_PAD_LEFT); + + $variableSymbol->setVariableSymbol($variableSymbolText); + $this->variableSymbolRepository->save($variableSymbol); + + $troop = new Troop($command->getLeader(), $variableSymbol); + $this->troopRepository->save($troop); + }); + } +} diff --git a/app/Model/User/Commands/Handlers/UpdateGroupMembersHandler.php b/app/Model/User/Commands/Handlers/UpdateGroupMembersHandler.php new file mode 100644 index 000000000..eb7a5d9ce --- /dev/null +++ b/app/Model/User/Commands/Handlers/UpdateGroupMembersHandler.php @@ -0,0 +1,146 @@ +em->wrapInTransaction(function () use ($command): void { + $troop = $this->queryBus->handle(new TroopByIdQuery($command->getTroopId())); + + if ($command->getType() === 'patrol') { + if ($command->getPatrolId() !== null) { + $patrol = $this->queryBus->handle(new PatrolByIdQuery($command->getPatrolId())); + } else { + $patrolNumber = $troop->getPatrols()->count() + 1; + $patrol = new Patrol($troop, $troop->getName() . '-' . sprintf('%02d', $patrolNumber)); + $this->patrolRepository->save($patrol); + } + } else { + $patrol = null; + } + + foreach ($command->getPersons() as $person) { + $personId = $person['personId']; + $roleId = $person['roleId']; + + $personDetail = $this->skautIsService->getPersonDetail($personId); + + $user = $this->userRepository->findBySkautISPersonId($personId); + if ($user == null) { + $user = new User(); + $user->setSkautISPersonId($personId); + } + + $user->setFirstName($personDetail->FirstName); + $user->setLastName($personDetail->LastName); + $user->setNickName($personDetail->NickName); + + $user->setMember($personDetail->HasMembership); + + $birthdate = new DateTimeImmutable($personDetail->Birthday); + $user->setBirthdate($birthdate); + $user->setSex($personDetail->ID_Sex); + + if (property_exists($personDetail, 'Phone')) { + $user->setPhone($personDetail->Phone); + } + + if (property_exists($personDetail, 'Email')) { + $user->setEmail($personDetail->Email); + } + + $user->setStreet($personDetail->Street); + $user->setCity($personDetail->City); + $user->setPostcode($personDetail->Postcode); + $user->setState($personDetail->State); + + if ((new DateTimeImmutable())->diff($birthdate)->y < 18) { + $personContacts = $this->skautIsService->getPersonContactAllParent($personId); + + foreach ($personContacts as $contact) { + if ($contact->ID_ContactType === 'telefon_hlavni') { + switch ($contact->ID_ParentType) { + case 'mother': + $user->setMotherName($contact->PersonPersonParent); + $user->setMotherPhone($contact->Value); + break; + case 'father': + $user->setFatherName($contact->PersonPersonParent); + $user->setFatherPhone($contact->Value); + break; + } + } + } + } + + $this->userRepository->save($user); + + $role = $this->roleRepository->findById($roleId); + + if ($command->getType() === 'patrol') { + foreach ($patrol->getUsersRoles() as $usersRole) { + $this->userGroupRoleRepository->remove($usersRole); + } + + $userGroupRoles = $this->userGroupRoleRepository->findByUserAndPatrol($user->getId(), $patrol->getId()); + if ($userGroupRoles->isEmpty()) { + $userGroupRole = new UserGroupRole($user, $role, $patrol); + } else { + $userGroupRole = $userGroupRoles[0]; + $userGroupRole->setRole($role); + } + + $this->userGroupRoleRepository->save($userGroupRole); + } elseif ($command->getType() === 'troop') { + foreach ($troop->getUsersRoles() as $usersRole) { + $this->userGroupRoleRepository->remove($usersRole); + } + + $userGroupRoles = $this->userGroupRoleRepository->findByUserAndTroop($user->getId(), $troop->getId()); + if ($userGroupRoles->isEmpty()) { + $userGroupRole = new UserGroupRole($user, $role, null, $troop); + } else { + $userGroupRole = $userGroupRoles[0]; + $userGroupRole->setRole($role); + } + + $this->userGroupRoleRepository->save($userGroupRole); + } + } + }); + } +} diff --git a/app/Model/User/Commands/RegisterTroop.php b/app/Model/User/Commands/RegisterTroop.php new file mode 100644 index 000000000..b27d45406 --- /dev/null +++ b/app/Model/User/Commands/RegisterTroop.php @@ -0,0 +1,19 @@ +leader; + } +} diff --git a/app/Model/User/Commands/UpdateGroupMembers.php b/app/Model/User/Commands/UpdateGroupMembers.php new file mode 100644 index 000000000..f03c5aaf3 --- /dev/null +++ b/app/Model/User/Commands/UpdateGroupMembers.php @@ -0,0 +1,42 @@ +type; + } + + public function getTroopId(): int + { + return $this->troopId; + } + + public function getPatrolId(): ?int + { + return $this->patrolId; + } + + /** + * @return int[][] + */ + public function getPersons(): array + { + return $this->persons; + } +} diff --git a/app/Model/User/Patrol.php b/app/Model/User/Patrol.php index 8172ede41..16104ed36 100644 --- a/app/Model/User/Patrol.php +++ b/app/Model/User/Patrol.php @@ -8,6 +8,8 @@ use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping as ORM; +use function in_array; + /** * Entita družina. */ @@ -40,9 +42,18 @@ class Patrol #[ORM\OneToMany(mappedBy: 'patrol', targetEntity: UserGroupRole::class, cascade: ['persist'])] protected Collection $usersRoles; - public function __construct() + /** + * Stav přihlášky - nepotvrzená přihláška slouží pro předávání údajů mezi kroky formuláře. + */ + #[ORM\Column(type: 'boolean')] + protected bool $confirmed = false; + + public function __construct(Troop $troop, string $name) { $this->usersRoles = new ArrayCollection(); + + $this->troop = $troop; + $this->name = $name; } public function getId(): ?int @@ -55,26 +66,41 @@ public function getName(): string return $this->name; } - public function setName(string $name): void + public function getTroop(): Troop { - $this->name = $name; + return $this->troop; } - public function getTroop(): Troop + /** + * @return Collection + */ + public function getUsersRoles(): Collection { - return $this->troop; + return $this->usersRoles; } - public function setTroop(Troop $troop): void + public function isConfirmed(): bool { - $this->troop = $troop; + return $this->confirmed; + } + + public function setConfirmed(bool $confirmed): void + { + $this->confirmed = $confirmed; } /** - * @return Collection + * @param string[] $roleNames */ - public function getUsersRoles(): Collection + public function countUsersInRoles(array $roleNames): int { - return $this->usersRoles; + $counter = 0; + foreach ($this->usersRoles as $userRole) { + if (in_array($userRole->getRole()->getSystemName(), $roleNames)) { + $counter++; + } + } + + return $counter; } } diff --git a/app/Model/User/Queries/Handlers/PatrolByIdQueryHandler.php b/app/Model/User/Queries/Handlers/PatrolByIdQueryHandler.php new file mode 100644 index 000000000..f5affe12f --- /dev/null +++ b/app/Model/User/Queries/Handlers/PatrolByIdQueryHandler.php @@ -0,0 +1,22 @@ +patrolRepository->findById($query->getPatrolId()); + } +} diff --git a/app/Model/User/Queries/Handlers/PatrolByTroopAndNotConfirmedQueryHandler.php b/app/Model/User/Queries/Handlers/PatrolByTroopAndNotConfirmedQueryHandler.php new file mode 100644 index 000000000..b14044cef --- /dev/null +++ b/app/Model/User/Queries/Handlers/PatrolByTroopAndNotConfirmedQueryHandler.php @@ -0,0 +1,22 @@ +patrolRepository->findByTroopAndNotConfirmed($query->getTroopId()); + } +} diff --git a/app/Model/User/Queries/Handlers/TroopByIdQueryHandler.php b/app/Model/User/Queries/Handlers/TroopByIdQueryHandler.php new file mode 100644 index 000000000..3d2042122 --- /dev/null +++ b/app/Model/User/Queries/Handlers/TroopByIdQueryHandler.php @@ -0,0 +1,22 @@ +troopRepository->findById($query->getTroopId()); + } +} diff --git a/app/Model/User/Queries/Handlers/TroopByLeaderQueryHandler.php b/app/Model/User/Queries/Handlers/TroopByLeaderQueryHandler.php new file mode 100644 index 000000000..d2757575e --- /dev/null +++ b/app/Model/User/Queries/Handlers/TroopByLeaderQueryHandler.php @@ -0,0 +1,22 @@ +troopRepository->findByLeaderId($query->getLeaderId()); + } +} diff --git a/app/Model/User/Queries/PatrolByIdQuery.php b/app/Model/User/Queries/PatrolByIdQuery.php new file mode 100644 index 000000000..b0dee5c99 --- /dev/null +++ b/app/Model/User/Queries/PatrolByIdQuery.php @@ -0,0 +1,17 @@ +patrolId; + } +} diff --git a/app/Model/User/Queries/PatrolByTroopAndNotConfirmedQuery.php b/app/Model/User/Queries/PatrolByTroopAndNotConfirmedQuery.php new file mode 100644 index 000000000..6a26e8ebb --- /dev/null +++ b/app/Model/User/Queries/PatrolByTroopAndNotConfirmedQuery.php @@ -0,0 +1,17 @@ +troopId; + } +} diff --git a/app/Model/User/Queries/TroopByIdQuery.php b/app/Model/User/Queries/TroopByIdQuery.php new file mode 100644 index 000000000..6c62b4373 --- /dev/null +++ b/app/Model/User/Queries/TroopByIdQuery.php @@ -0,0 +1,17 @@ +troopId; + } +} diff --git a/app/Model/User/Queries/TroopByLeaderQuery.php b/app/Model/User/Queries/TroopByLeaderQuery.php new file mode 100644 index 000000000..59748efff --- /dev/null +++ b/app/Model/User/Queries/TroopByLeaderQuery.php @@ -0,0 +1,17 @@ +leaderId; + } +} diff --git a/app/Model/User/Repositories/PatrolRepository.php b/app/Model/User/Repositories/PatrolRepository.php new file mode 100644 index 000000000..b162dd7bb --- /dev/null +++ b/app/Model/User/Repositories/PatrolRepository.php @@ -0,0 +1,36 @@ +getRepository()->findOneBy(['id' => $id]); + } + + public function findByTroopAndNotConfirmed(int $troopId): ?Patrol + { + return $this->getRepository()->findOneBy(['troop' => $troopId, 'confirmed' => false]); + } + + public function save(Patrol $patrol): void + { + $this->em->persist($patrol); + $this->em->flush(); + } +} diff --git a/app/Model/User/Repositories/TroopRepository.php b/app/Model/User/Repositories/TroopRepository.php new file mode 100644 index 000000000..7f716245f --- /dev/null +++ b/app/Model/User/Repositories/TroopRepository.php @@ -0,0 +1,41 @@ +getRepository()->findOneBy(['id' => $id]); + } + + public function findByLeaderId(int $leaderId): ?Troop + { + return $this->getRepository() + ->createQueryBuilder('t') + ->where('t.leader = :leader_id') + ->setParameter('leader_id', $leaderId) + ->getQuery() + ->getOneOrNullResult(); + } + + public function save(Troop $troop): void + { + $this->em->persist($troop); + $this->em->flush(); + } +} diff --git a/app/Model/User/Repositories/UserGroupRoleRepository.php b/app/Model/User/Repositories/UserGroupRoleRepository.php new file mode 100644 index 000000000..e884f428c --- /dev/null +++ b/app/Model/User/Repositories/UserGroupRoleRepository.php @@ -0,0 +1,54 @@ + + */ + public function findByUserAndPatrol(int $user_id, int $patrol_id): Collection + { + $result = $this->getRepository()->findBy(['user' => $user_id, 'patrol' => $patrol_id]); + + return new ArrayCollection($result); + } + + /** + * @return Collection + */ + public function findByUserAndTroop(int $user_id, int $troop_id): Collection + { + $result = $this->getRepository()->findBy(['user' => $user_id, 'troop' => $troop_id]); + + return new ArrayCollection($result); + } + + public function save(UserGroupRole $userGroupRole): void + { + $this->em->persist($userGroupRole); + $this->em->flush(); + } + + public function remove(UserGroupRole $userGroupRole): void + { + $this->em->remove($userGroupRole); + $this->em->flush(); + } +} diff --git a/app/Model/User/Repositories/UserRepository.php b/app/Model/User/Repositories/UserRepository.php index c913f7b17..b8d23399c 100644 --- a/app/Model/User/Repositories/UserRepository.php +++ b/app/Model/User/Repositories/UserRepository.php @@ -55,6 +55,14 @@ public function findBySkautISUserId(int $skautISUserId): ?User return $this->getRepository()->findOneBy(['skautISUserId' => $skautISUserId]); } + /** + * Vrací uživatele podle skautISPersonId. + */ + public function findBySkautISPersonId(int $skautISUserId): ?User + { + return $this->getRepository()->findOneBy(['skautISPersonId' => $skautISUserId]); + } + /** * Vrací uživatele podle id. * diff --git a/app/Model/User/Troop.php b/app/Model/User/Troop.php index deb6bc8c8..9fe9d5fdc 100644 --- a/app/Model/User/Troop.php +++ b/app/Model/User/Troop.php @@ -6,12 +6,21 @@ use App\Model\Application\IncomeProof; use App\Model\Application\VariableSymbol; +use App\Model\Enums\TroopApplicationState; use App\Model\Payment\Payment; use DateTimeImmutable; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping as ORM; +use function array_key_exists; +use function count; +use function in_array; +use function md5; +use function mt_rand; +use function substr; +use function uniqid; + /** * Entita oddíl. */ @@ -61,8 +70,8 @@ class Troop /** * Datum podání přihlášky. */ - #[ORM\Column(type: 'datetime_immutable')] - protected DateTimeImmutable $applicationDate; + #[ORM\Column(type: 'datetime_immutable', nullable: true)] + protected ?DateTimeImmutable $applicationDate; /** * Datum splatnosti. @@ -98,12 +107,38 @@ class Troop * Stav přihlášky. */ #[ORM\Column(type: 'string')] - protected ?string $state = null; + protected string $state; + + /** + * Vedoucí oddílu. + */ + #[ORM\OneToOne(targetEntity: User::class, inversedBy: 'troop', cascade: ['persist'])] + protected User $leader; - public function __construct() + /** + * Kód pro párování oddílů. + */ + #[ORM\Column(type: 'string')] + protected string $pairingCode; + + /** + * Spárovaný oddíl. + */ + #[ORM\Column(type: 'string', nullable: true)] + protected ?string $pairedTroopCode = null; + + public function __construct(User $leader, VariableSymbol $variableSymbol) { $this->patrols = new ArrayCollection(); $this->usersRoles = new ArrayCollection(); + + $this->leader = $leader; + $this->variableSymbol = $variableSymbol; + + $this->name = 'S-' . $variableSymbol->getVariableSymbol(); + $this->pairingCode = substr(md5(uniqid((string) mt_rand(), true)), 0, 20); + $this->state = TroopApplicationState::DRAFT; + $this->fee = 0; } public function getId(): ?int @@ -152,17 +187,12 @@ public function getVariableSymbol(): VariableSymbol return $this->variableSymbol; } - public function setVariableSymbol(VariableSymbol $variableSymbol): void - { - $this->variableSymbol = $variableSymbol; - } - - public function getApplicationDate(): DateTimeImmutable + public function getApplicationDate(): ?DateTimeImmutable { return $this->applicationDate; } - public function setApplicationDate(DateTimeImmutable $applicationDate): void + public function setApplicationDate(?DateTimeImmutable $applicationDate): void { $this->applicationDate = $applicationDate; } @@ -217,13 +247,116 @@ public function setIncomeProof(?IncomeProof $incomeProof): void $this->incomeProof = $incomeProof; } - public function getState(): ?string + public function getState(): string { return $this->state; } - public function setState(?string $state): void + public function setState(string $state): void { $this->state = $state; } + + public function getLeader(): User + { + return $this->leader; + } + + public function getPairingCode(): string + { + return $this->pairingCode; + } + + public function getPairedTroopCode(): ?string + { + return $this->pairedTroopCode; + } + + public function setPairedTroopCode(?string $pairedTroopCode): void + { + $this->pairedTroopCode = $pairedTroopCode; + } + + /** + * @return Collection + */ + public function getConfirmedPatrols(): Collection + { + return $this->patrols->filter(static fn ($p) => $p->isConfirmed()); + } + + public function getMaxEscortsCount(): int + { + return $this->getMaxAdultsCount() - $this->countUsersInRoles(['leader']); + } + + public function getMaxAdultsCount(): int + { + return $this->getConfirmedPatrols()->count() * 2; + } + + /** + * @param string[] $roleNames + */ + public function countUsersInRoles(array $roleNames): int + { + $users = []; + + // uzivatele v oddile + foreach ($this->usersRoles as $userRole) { + if (in_array($userRole->getRole()->getSystemName(), $roleNames)) { + $users[$userRole->getUser()->getId()] = true; + } + } + + // uzivatele v druzinach + foreach ($this->getConfirmedPatrols() as $patrol) { + foreach ($patrol->getUsersRoles() as $userRole) { + if (in_array($userRole->getRole()->getSystemName(), $roleNames)) { + $users[$userRole->getUser()->getId()] = true; + } + } + } + + return count($users); + } + + public function countFee(): int + { + $rolesFees = []; + $rolesUsers = []; + + // uzivatele v oddile + foreach ($this->usersRoles as $userRole) { + $role = $userRole->getRole(); + $user = $userRole->getUser(); + if (! array_key_exists($role->getId(), $rolesFees)) { + $rolesFees[$role->getId()] = $role->getFee(); + $rolesUsers[$role->getId()] = []; + } + + $rolesUsers[$role->getId()][$user->getId()] = true; + } + + // uzivatele v druzinach + foreach ($this->getConfirmedPatrols() as $patrol) { + foreach ($patrol->getUsersRoles() as $userRole) { + $role = $userRole->getRole(); + $user = $userRole->getUser(); + if (! array_key_exists($role->getId(), $rolesFees)) { + $rolesFees[$role->getId()] = $role->getFee(); + $rolesUsers[$role->getId()] = []; + } + + $rolesUsers[$role->getId()][$user->getId()] = true; + } + } + + $totalFee = 0; + foreach ($rolesFees as $key => $value) { + $totalFee += $value * count($rolesUsers[$key]); + } + + return $totalFee; + } } diff --git a/app/Model/User/User.php b/app/Model/User/User.php index 6c6db3a89..ef4cfd9fe 100644 --- a/app/Model/User/User.php +++ b/app/Model/User/User.php @@ -294,6 +294,52 @@ class User #[ORM\Column(type: 'datetime_immutable', nullable: true)] protected ?DateTimeImmutable $photoUpdate = null; + /** + * Přihláška oddílu. + */ + #[ORM\OneToOne(targetEntity: Troop::class, mappedBy: 'leader', cascade: ['persist'])] + protected Troop $troop; + + /** + * Telefon. + */ + #[ORM\Column(type: 'string', nullable: true)] + protected ?string $phone = null; + + /** + * Jméno matky. + */ + #[ORM\Column(type: 'string', nullable: true)] + protected ?string $motherName = null; + + /** + * Telefon matky. + */ + #[ORM\Column(type: 'string', nullable: true)] + protected ?string $motherPhone = null; + + /** + * Jméno otce. + */ + #[ORM\Column(type: 'string', nullable: true)] + protected ?string $fatherName = null; + + /** + * Telefon otce. + */ + #[ORM\Column(type: 'string', nullable: true)] + protected ?string $fatherPhone = null; + + /** + * Informace o zdravotním stavu - omezení, alergie, léky. + */ + #[ORM\Column(type: 'text', nullable: true)] + protected ?string $healthInfo = null; + + /** @var Collection */ + #[ORM\OneToMany(targetEntity: UserGroupRole::class, cascade: ['persist'], mappedBy: 'user')] + protected Collection $groupRoles; + public function __construct() { $this->applications = new ArrayCollection(); @@ -302,6 +348,7 @@ public function __construct() $this->lecturersBlocks = new ArrayCollection(); $this->notRegisteredMandatoryBlocks = new ArrayCollection(); $this->customInputValues = new ArrayCollection(); + $this->groupRoles = new ArrayCollection(); } public function getId(): ?int @@ -1121,4 +1168,77 @@ public function isAlternate(Program $program): bool ) )->isEmpty(); } + + public function getTroop(): Troop + { + return $this->troop; + } + + public function getPhone(): ?string + { + return $this->phone; + } + + public function setPhone(?string $phone): void + { + $this->phone = $phone; + } + + public function getMotherName(): ?string + { + return $this->motherName; + } + + public function setMotherName(?string $motherName): void + { + $this->motherName = $motherName; + } + + public function getMotherPhone(): ?string + { + return $this->motherPhone; + } + + public function setMotherPhone(?string $motherPhone): void + { + $this->motherPhone = $motherPhone; + } + + public function getFatherName(): ?string + { + return $this->fatherName; + } + + public function setFatherName(?string $fatherName): void + { + $this->fatherName = $fatherName; + } + + public function getFatherPhone(): ?string + { + return $this->fatherPhone; + } + + public function setFatherPhone(?string $fatherPhone): void + { + $this->fatherPhone = $fatherPhone; + } + + public function getHealthInfo(): ?string + { + return $this->healthInfo; + } + + public function setHealthInfo(?string $healthInfo): void + { + $this->healthInfo = $healthInfo; + } + + /** + * @return Collection + */ + public function getGroupRoles(): Collection + { + return $this->groupRoles; + } } diff --git a/app/Model/User/UserGroupRole.php b/app/Model/User/UserGroupRole.php index e138039c8..fc3444b3f 100644 --- a/app/Model/User/UserGroupRole.php +++ b/app/Model/User/UserGroupRole.php @@ -22,7 +22,7 @@ class UserGroupRole /** * Uživatel. */ - #[ORM\ManyToOne(targetEntity: User::class, cascade: ['persist'])] + #[ORM\ManyToOne(targetEntity: User::class, cascade: ['persist'], inversedBy: 'groupRoles')] protected User $user; /** @@ -43,6 +43,14 @@ class UserGroupRole #[ORM\ManyToOne(targetEntity: Role::class, cascade: ['persist'])] protected Role $role; + public function __construct(User $user, Role $role, ?Patrol $patrol = null, ?Troop $troop = null) + { + $this->user = $user; + $this->role = $role; + $this->patrol = $patrol; + $this->troop = $troop; + } + public function getId(): ?int { return $this->id; diff --git a/app/Services/SkautIsService.php b/app/Services/SkautIsService.php index 1993acd36..f2b75c873 100644 --- a/app/Services/SkautIsService.php +++ b/app/Services/SkautIsService.php @@ -12,6 +12,9 @@ use stdClass; use Throwable; +use function array_filter; +use function in_array; + /** * Služba pro komunikaci se skautIS. */ @@ -66,11 +69,13 @@ public function setLoginData(array $data): void /** * Vrátí skautIS role uživatele. * + * @param ?string[] $allowedRoleTypes + * * @return stdClass[] * * @throws Throwable */ - public function getUserRoles(int $userId): array + public function getUserRoles(int $userId, ?array $allowedRoleTypes = null): array { $roles = $this->userRolesCache->load($userId); @@ -82,7 +87,13 @@ public function getUserRoles(int $userId): array $this->userRolesCache->save($userId, $roles); } - return $roles instanceof stdClass ? [] : $roles; + $rolesArray = $roles instanceof stdClass ? [] : $roles; + + if ($allowedRoleTypes !== null) { + return array_filter($rolesArray, static fn (stdClass $r) => isset($r->Key) && in_array($r->Key, $allowedRoleTypes)); + } + + return $rolesArray; } /** @@ -221,4 +232,57 @@ public function getUnitDetail(int $unitId): stdClass 'ID' => $unitId, ]); } + + /** + * @param ?string[] $allowedUnitTypes + * + * @return stdClass[] + */ + public function getUnitAllUnit(?array $allowedUnitTypes = null): array + { + $units = $this->skautIs->org->UnitAllUnit([ + 'ID_Login' => $this->skautIs->getUser()->getLoginId(), + 'ID_Unit' => $this->skautIs->getUser()->getUnitId(), + ], 'unitAllUnitInput'); + + $unitsArray = $units instanceof stdClass ? [] : $units; + + if ($allowedUnitTypes !== null) { + return array_filter($unitsArray, static fn (stdClass $r) => in_array($r->ID_UnitType, $allowedUnitTypes)); + } + + return $unitsArray; + } + + /** + * @return stdClass[] + */ + public function getMembershipAll(int $unitId, ?int $minimalAge = null, ?DateTimeImmutable $date = null): array + { + $memberships = $this->skautIs->org->MembershipAll([ + 'ID_Login' => $this->skautIs->getUser()->getLoginId(), + 'ID_Unit' => $unitId, + 'ID_MembershipType' => 'radne', + 'OnlyDirectMember' => false, + ], 'membershipAllInput'); + + if ($minimalAge !== null) { + return array_filter($memberships, static fn (stdClass $m) => $date->diff(new DateTimeImmutable($m->Birthday))->y >= $minimalAge); + } + + return $memberships instanceof stdClass ? [] : $memberships; + } + + /** + * @return stdClass[] + */ + public function getPersonContactAllParent(int $personId): array + { + $contacts = $this->skautIs->org->PersonContactAllParent([ + 'ID_Login' => $this->skautIs->getUser()->getLoginId(), + 'ID_Person' => $personId, + ], 'personContactAllParentInput'); + + return $contacts instanceof stdClass ? [] : $contacts; + } } diff --git a/app/WebModule/Components/ITroopApplicationContentControlFactory.php b/app/WebModule/Components/ITroopApplicationContentControlFactory.php new file mode 100644 index 000000000..0cc9ac2af --- /dev/null +++ b/app/WebModule/Components/ITroopApplicationContentControlFactory.php @@ -0,0 +1,13 @@ +template; + $template->setFile(__DIR__ . '/templates/troop_application_content.latte'); + + if ($content) { + $template->heading = $content->getHeading(); + } + + $template->backlink = $this->getPresenter()->getHttpRequest()->getUrl()->getPath(); + + $user = $this->getPresenter()->user; + $template->guestRole = $user->isInRole($this->roleRepository->findBySystemName(Role::GUEST)->getName()); + $template->testRole = Role::TEST; + + $step = $this->getPresenter()->getParameter('step'); + $template->step = $step; + + if ($user->isLoggedIn()) { + $dbuser = $this->userRepository->findById($user->id); + $template->dbuser = $dbuser; + + $skautIsUserId = $dbuser->getSkautISUserId(); + $skautIsRoles = $this->skautIsService->getUserRoles($skautIsUserId, self::$ALLOWED_ROLE_TYPES); + $template->skautIsRoles = $skautIsRoles; + + $troop = $this->queryBus->handle(new TroopByLeaderQuery($dbuser->getId())); + if ($troop == null) { + $this->commandBus->handle(new RegisterTroop($dbuser)); + $troop = $this->queryBus->handle(new TroopByLeaderQuery($dbuser->getId())); + } + + $template->troop = $troop; + + if ($step === null) { + $skautIsRoleSelectedId = $this->skautIsService->getUserRoleId(); + $skautIsRoleSelected = array_filter($skautIsRoles, static fn (stdClass $r) => $r->ID === $skautIsRoleSelectedId); + if (empty($skautIsRoleSelected)) { + $template->skautIsRoleSelected = null; + } else { + $template->skautIsRoleSelected = $skautIsRoleSelected[array_keys($skautIsRoleSelected)[0]]; + } + } + } + + $template->render(); + } + + public function renderScripts(): void + { + } + + protected function createComponentGroupMembersForm(): GroupMembersForm + { + $type = $this->getPresenter()->getParameter('type'); + $patrolId = $this->getPresenter()->getParameter('patrol_id'); + if ($patrolId !== null) { + $patrolId = (int) $patrolId; + } + + $form = $this->groupMembersFormFactory->create($type, $patrolId); + + $form->onSave[] = function () use ($type, $patrolId): void { + $this->getPresenter()->redirect('this', ['step' => 'additional_info', 'type' => $type, 'patrol_id' => $patrolId]); + }; + + $form->onError[] = function () use ($type, $patrolId): void { + $p = $this->getPresenter(); + $p->flashMessage('Po potvrzení přihlášky nelze měnit počet účastníků.', 'danger'); + $p->redirect('this', ['step' => 'members', 'type' => $type, 'patrol_id' => $patrolId]); + }; + + return $form; + } + + protected function createComponentGroupAdditionalInfoForm(): GroupAdditionalInfoForm + { + $type = $this->getPresenter()->getParameter('type'); + $patrolId = $this->getPresenter()->getParameter('patrol_id'); + if ($patrolId !== null) { + $patrolId = (int) $patrolId; + } + + $form = $this->groupAdditionalInfoFormFactory->create($type, $patrolId); + + $form->onSave[] = function () use ($type, $patrolId): void { + $this->getPresenter()->redirect('this', ['step' => 'confirm', 'type' => $type, 'patrol_id' => $patrolId]); + }; + + return $form; + } + + protected function createComponentGroupConfirmForm(): GroupConfirmForm + { + $type = $this->getPresenter()->getParameter('type'); + $patrolId = $this->getPresenter()->getParameter('patrol_id'); + if ($patrolId !== null) { + $patrolId = (int) $patrolId; + } + + $form = $this->groupConfirmFormFactory->create($type, $patrolId); + + $form->onSave[] = function (): void { + $this->getPresenter()->redirect('this'); + }; + + return $form; + } + + protected function createComponentTroopConfirmForm(): TroopConfirmForm + { + $form = $this->troopConfirmFormFactory->create(); + + $form->onSave[] = function (): void { + $p = $this->getPresenter(); + $p->flashMessage('Přihláška skupiny byla úspěšně odeslána.', 'success'); + $p->redirect('this'); + }; + + return $form; + } + + public function handleChangeRole(int $roleId): void + { + $this->skautIsService->updateUserRole($roleId); + $this->redirect('this'); + } +} diff --git a/app/WebModule/Components/templates/troop_application_content.latte b/app/WebModule/Components/templates/troop_application_content.latte new file mode 100644 index 000000000..e54dbb2fd --- /dev/null +++ b/app/WebModule/Components/templates/troop_application_content.latte @@ -0,0 +1,204 @@ +
+ +
+
+
+

{$heading}

+
+
+ + {if $guestRole} +
+
+
+ {_web.application_content.login_required_begin} + {_web.application_content.login_required_link}{_web.application_content.login_required_end} +
+
+
+ {elseif empty($skautIsRoles)} + Nemáš správnou roli + {elseif $step === 'members'} + {control groupMembersForm} + {elseif $step === 'additional_info'} + {control groupAdditionalInfoForm} + {elseif $step === 'confirm'} + {control groupConfirmForm} + {else} +
+
+ Pracuješ v roli +
+
+
+ + +
+
+ + + Účastníci do registrace se ti načítají z členů tvé jednotky. + +
+
+ +

Skupina

+
+
+ Jméno +
+
+ {$troop->getName()} +
+
+ +
+
+ Zodpovědný vedoucí +
+
+ {$troop->getLeader()->getDisplayName()} +
+ + + Registrující je vždy zodpovědnou osobou. + +
+
+ +
+
+ Kód pro spojení do Jamooddílu +
+
+ {$troop->getPairingCode()} +
+ + + Kód můžeš nasdílet kamarádům z jiných skupin a my se budeme snažit vás spojit do jamoddílu. Pozor, abyste dohromady nepřekročili maximální počet lidí v jamoddílu (42). + +
+
+ +
+
+

Družiny

+
+ +
+ + {foreach $troop->getConfirmedPatrols() as $patrol} +
+
+
{$patrol->getName()}
+
+ +
+
+
+ + + + + + {foreach $patrol->getUsersRoles() as $userRole} + + + + + {/foreach} +
JménoRole
{$userRole->getUser()->getDisplayName()}{$userRole->getRole()->getName()}
+ +

+ Družina má {$patrol->countUsersInRoles(['attendee', 'patrol_leader'])}/12 účastníků + (z toho {$patrol->countUsersInRoles(['patrol_leader'])}/1 rádců) + a {$patrol->countUsersInRoles(['leader'])}/1 vedoucích. +

+
+
+ {else} +
+
+ + + Začni tím, že přidáš družinu. + +
+
+ {/foreach} + +
+
+

Dospělí průvodci

+
+ +
+ + {if !$troop->getConfirmedPatrols()->isEmpty()} +
+
+ + + + + + {foreach $troop->getUsersRoles() as $userRole} + + + + + {/foreach} +
JménoRole
{$userRole->getUser()->getDisplayName()}{$userRole->getRole()->getName()}
+ +

+ Doprovod má {$troop->countUsersInRoles(['escort'])}/{$troop->getMaxEscortsCount()} členů. + Můžeš mít 2× počet družin dospělých (tedy {$troop->getMaxAdultsCount()}, + z toho máš {$troop->countUsersInRoles(['leader'])} vedoucí + a {$troop->countUsersInRoles(['escort'])} doprovody). +

+
+
+ {else} +
+
+ + + Zatím nemáš družinu, bez ní to nepůjde. + +
+
+ {/if} + +
+
+

Shrnutí

+ {control troopConfirmForm} +
+
+ {/if} +
diff --git a/app/WebModule/Forms/GroupAdditionalInfoForm.php b/app/WebModule/Forms/GroupAdditionalInfoForm.php new file mode 100644 index 000000000..92f83b045 --- /dev/null +++ b/app/WebModule/Forms/GroupAdditionalInfoForm.php @@ -0,0 +1,143 @@ +template->setFile(__DIR__ . '/templates/group_additional_info_form.latte'); + + $this->resolveUsersRoles(); + + $this->template->type = $this->type; + $this->template->patrolId = $this->patrolId; + $this->template->usersRoles = $this->usersRoles; + + $this->template->attendeesCountError = $this->attendeesCountError; + $this->template->groupLeadersCountError = $this->groupLeadersCountError; + $this->template->leadersCountError = $this->leadersCountError; + $this->template->escortsCountError = $this->escortsCountError; + + $this->template->render(); + } + + /** + * Vytvoří formulář. + */ + public function createComponentForm(): Form + { + $this->resolveUsersRoles(); + + $form = $this->baseFormFactory->create(); + + foreach ($this->usersRoles as $userRole) { + $form->addTextArea('health_info_' . $userRole->getUser()->getId(), null, null, 3) + ->setDefaultValue($userRole->getUser()->getHealthInfo()); + } + + $form->addSubmit('submit', 'Pokračovat')->setDisabled($this->attendeesCountError || $this->groupLeadersCountError || $this->leadersCountError || $this->escortsCountError); + + $form->setAction($this->getPresenter()->link('this', ['step' => 'additional_info', 'type' => $this->type, 'patrol_id' => $this->patrolId])); + + $form->onSuccess[] = [$this, 'processForm']; + + return $form; + } + + /** + * Zpracuje formulář. + * + * @throws SettingsItemNotFoundException + * @throws Throwable + * @throws MailingMailCreationException + */ + public function processForm(Form $form, stdClass $values): void + { + foreach ($this->usersRoles as $userRole) { + $user = $userRole->getUser(); + $healthInfoInputName = 'health_info_' . $user->getId(); + $user->setHealthInfo($values->$healthInfoInputName); + $this->userRepository->save($user); + } + + $this->onSave(); + } + + private function resolveUsersRoles(): void + { + $user = $this->queryBus->handle(new UserByIdQuery($this->presenter->user->getId())); + $troop = $this->queryBus->handle(new TroopByLeaderQuery($user->getId())); + + if ($this->type == 'patrol') { + if ($this->patrolId !== null) { + $patrol = $this->queryBus->handle(new PatrolByIdQuery($this->patrolId)); + $this->usersRoles = $patrol->getUsersRoles()->toArray(); + } else { + $patrol = $this->queryBus->handle(new PatrolByTroopAndNotConfirmedQuery($troop->getId())); + $this->patrolId = $patrol->getId(); + $this->usersRoles = $patrol->getUsersRoles()->toArray(); + } + + $attendeesCount = $patrol->countUsersInRoles([Role::ATTENDEE, Role::PATROL_LEADER]); + $this->attendeesCountError = $attendeesCount < 4 || $attendeesCount > 12; + $this->groupLeadersCountError = $patrol->countUsersInRoles([Role::PATROL_LEADER]) > 1; + $this->leadersCountError = $patrol->countUsersInRoles([Role::LEADER]) != 1; + } elseif ($this->type === 'troop') { + $this->usersRoles = $troop->getUsersRoles()->toArray(); + $this->escortsCountError = $troop->countUsersInRoles([Role::ESCORT]) > $troop->getMaxEscortsCount(); + } + + $collator = new Collator('cs_CZ'); + usort($this->usersRoles, static fn ($a, $b) => $collator->compare($a->getUser()->getDisplayName(), $b->getUser()->getDisplayName())); + } +} diff --git a/app/WebModule/Forms/GroupConfirmForm.php b/app/WebModule/Forms/GroupConfirmForm.php new file mode 100644 index 000000000..7c7adf240 --- /dev/null +++ b/app/WebModule/Forms/GroupConfirmForm.php @@ -0,0 +1,120 @@ +template->setFile(__DIR__ . '/templates/group_confirm_form.latte'); + + $this->resolveUsersRoles(); + + $this->template->type = $this->type; + $this->template->patrolId = $this->patrolId; + $this->template->usersRoles = $this->usersRoles; + + $this->template->render(); + } + + /** + * Vytvoří formulář. + */ + public function createComponentForm(): Form + { + $this->resolveUsersRoles(); + + $form = $this->baseFormFactory->create(); + + $form->addSubmit('submit', 'Pokračovat'); + + $form->setAction($this->getPresenter()->link('this', ['step' => 'confirm', 'type' => $this->type, 'patrol_id' => $this->patrolId])); + + $form->onSuccess[] = [$this, 'processForm']; + + return $form; + } + + /** + * Zpracuje formulář. + * + * @throws SettingsItemNotFoundException + * @throws Throwable + * @throws MailingMailCreationException + */ + public function processForm(Form $form, stdClass $values): void + { + if ($this->type == 'patrol') { + $this->commandBus->handle(new ConfirmPatrol($this->patrolId)); + } + + $this->onSave(); + } + + private function resolveUsersRoles(): void + { + $user = $this->queryBus->handle(new UserByIdQuery($this->presenter->user->getId())); + $troop = $this->queryBus->handle(new TroopByLeaderQuery($user->getId())); + + if ($this->type == 'patrol') { + if ($this->patrolId !== null) { + $patrol = $this->queryBus->handle(new PatrolByIdQuery($this->patrolId)); + $this->usersRoles = $patrol->getUsersRoles()->toArray(); + } else { + $patrol = $this->queryBus->handle(new PatrolByTroopAndNotConfirmedQuery($troop->getId())); + $this->patrolId = $patrol->getId(); + $this->usersRoles = $patrol->getUsersRoles()->toArray(); + } + } elseif ($this->type === 'troop') { + $this->usersRoles = $troop->getUsersRoles()->toArray(); + } + + $collator = new Collator('cs_CZ'); + usort($this->usersRoles, static fn ($a, $b) => $collator->compare($a->getUser()->getDisplayName(), $b->getUser()->getDisplayName())); + } +} diff --git a/app/WebModule/Forms/GroupMembersForm.php b/app/WebModule/Forms/GroupMembersForm.php new file mode 100644 index 000000000..8f4657b0b --- /dev/null +++ b/app/WebModule/Forms/GroupMembersForm.php @@ -0,0 +1,247 @@ +seminarStart = $this->queryBus->handle(new SettingDateValueQuery(Settings::SEMINAR_FROM_DATE)); + + $this->units = $this->skautIsService->getUnitAllUnit(self::$ALLOWED_UNIT_TYPES); + $this->members = []; + + $collator = new Collator('cs_CZ'); + foreach ($this->units as $unit) { + $unitMembers = $this->skautIsService->getMembershipAll($unit->ID, $this->type === 'troop' ? 18 : null, $this->seminarStart); + usort($unitMembers, static fn ($a, $b) => $collator->compare($a->Person, $b->Person)); + $this->members[$unit->ID] = $unitMembers; + } + } + + /** + * Vykreslí komponentu. + */ + public function render(): void + { + $this->template->setFile(__DIR__ . '/templates/group_members_form.latte'); + + $this->template->units = $this->units; + $this->template->members = $this->members; + + $this->template->render(); + } + + /** + * Vytvoří formulář. + */ + public function createComponentForm(): Form + { + $user = $this->queryBus->handle(new UserByIdQuery($this->presenter->user->getId())); + + $roles = $this->queryBus->handle(new RolesByTypeQuery($this->type)); + $roleSelectOptions = $this->getRoleSelectOptions($roles); + + $troop = $this->queryBus->handle(new TroopByLeaderQuery($user->getId())); + $this->troop = $troop; + $usersRoles = null; + if ($this->type === 'patrol') { + if ($this->patrolId !== null) { + $patrol = $this->queryBus->handle(new PatrolByIdQuery($this->patrolId)); + } else { + $patrol = $this->queryBus->handle(new PatrolByTroopAndNotConfirmedQuery($troop->getId())); + } + + if ($patrol != null) { + $usersRoles = $patrol->getUsersRoles(); + $this->patrolId = $patrol->getId(); + } + } elseif ($this->type === 'troop') { + $usersRoles = $troop->getUsersRoles(); + } + + $form = $this->baseFormFactory->create(); + + foreach ($this->units as $unit) { + foreach ($this->members[$unit->ID] as $member) { + $register = false; + $role = null; + + if ($usersRoles !== null) { + foreach ($usersRoles as $usersRole) { + if ($usersRole->getUser()->getSkautISPersonId() === $member->ID_Person) { + $register = true; + $role = $usersRole->getRole(); + break; + } + } + } + + $memberId = $member->ID; + $registerCheckbox = $form->addCheckbox('register_' . $memberId) + ->setDefaultValue($register); + $registerCheckbox + ->addCondition(Form::EQUAL, true) + ->toggle('roleselect-' . $memberId); + + $roleSelect = $form->addSelect('role_' . $memberId, null, $roleSelectOptions) + ->setHtmlId('roleselect-' . $memberId) + ->setHtmlAttribute('class', 'form-control-sm ignore-bs-select'); + if ($role != null) { + $roleSelect->setDefaultValue($role->getId()); + } + + $birthdate = new DateTimeImmutable($member->Birthday); + $age = $this->countAgeAt($birthdate, $this->seminarStart); + foreach ($roles as $r) { + if ($age < $r->getMinimumAge()) { + $roleSelect + ->addConditionOn($registerCheckbox, Form::FILLED) + ->addCondition(Form::EQUAL, $r->getId()) + ->addRule(Form::NOT_EQUAL, $r->getMinimumAgeWarning() ? $r->getMinimumAgeWarning() : 'Příliš nízký věk.', $r->getId()); + } elseif ($age > $r->getMaximumAge()) { + $roleSelect + ->addConditionOn($registerCheckbox, Form::FILLED) + ->addCondition(Form::EQUAL, $r->getId()) + ->addRule(Form::NOT_EQUAL, $r->getMinimumAgeWarning() ? $r->getMinimumAgeWarning() : 'Příliš vysoký věk.', $r->getId()); + } + } + } + } + + $form->addSubmit('submit', 'Pokračovat'); + + $form->setAction($this->getPresenter()->link('this', ['step' => 'members', 'type' => $this->type, 'patrol_id' => $this->patrolId])); + + $form->onSuccess[] = [$this, 'processForm']; + + return $form; + } + + /** + * Zpracuje formulář. + * + * @throws SettingsItemNotFoundException + * @throws Throwable + * @throws MailingMailCreationException + */ + public function processForm(Form $form, stdClass $values): void + { + $selectedPersons = []; + + foreach ($this->units as $unit) { + foreach ($this->members[$unit->ID] as $member) { + $memberId = $member->ID; + $registerInputName = 'register_' . $memberId; + $roleInputName = 'role_' . $memberId; + if ($values->$registerInputName) { + $selectedPersons[] = ['roleId' => $values->$roleInputName, 'personId' => $member->ID_Person]; + } + } + } + + if ($this->troop->getState() !== TroopApplicationState::DRAFT) { + if ($this->type === 'patrol') { + $patrol = $this->queryBus->handle(new PatrolByIdQuery($this->patrolId)); + $usersCount = $patrol->getUsersRoles()->count(); + } else { + $usersCount = $this->troop->getUsersRoles()->count(); + } + + if ($usersCount !== count($selectedPersons)) { + $this->onError(); + + return; + } + } + + $this->commandBus->handle(new UpdateGroupMembers($this->type, $this->troop->getId(), $this->patrolId, $selectedPersons)); + + $this->onSave(); + } + + /** + * @param Role[] $roles + * + * @return string[] + */ + private function getRoleSelectOptions($roles): array + { + $options = []; + + foreach ($roles as $role) { + $options[$role->getId()] = $role->getName(); + } + + return $options; + } + + private function countAgeAt(DateTimeImmutable $birthdate, DateTimeImmutable $seminarStart): int + { + return $seminarStart->diff($birthdate)->y; + } +} diff --git a/app/WebModule/Forms/IGroupAdditionalInfoFormFactory.php b/app/WebModule/Forms/IGroupAdditionalInfoFormFactory.php new file mode 100644 index 000000000..3bcc8fdcc --- /dev/null +++ b/app/WebModule/Forms/IGroupAdditionalInfoFormFactory.php @@ -0,0 +1,16 @@ +template->setFile(__DIR__ . '/templates/contact_form.latte'); + $this->template->render(); + } + + /** + * Vytvoří formulář. + */ + public function createComponentForm(): Form + { + $this->user = $this->userRepository->findById($this->presenter->user->getId()); + + $form = $this->baseFormFactory->create(); + + $nameText = $form->addText('name', 'web.contact_form_content.name') + ->addRule(Form::FILLED, 'web.contact_form_content.name_empty'); + + $emailText = $form->addText('email', 'web.contact_form_content.email') + ->addRule(Form::FILLED, 'web.contact_form_content.email_empty') + ->addRule(Form::EMAIL, 'web.contact_form_content.email_format'); + + $form->addTextArea('message', 'web.contact_form_content.message') + ->addRule(Form::FILLED, 'web.contact_form_content.message_empty'); + + $form->addCheckbox('sendCopy', 'web.contact_form_content.send_copy'); + + if ($this->user === null) { + $field = new ReCaptchaField($this->recaptchaProvider); + $field->addRule(Form::FILLED, 'web.contact_form_content.recaptcha_empty'); + $form->addComponent($field, 'recaptcha'); + } + + $form->addSubmit('submit', 'web.contact_form_content.send_message'); + + if ($this->user !== null) { + $nameText->setDisabled(); + $emailText->setDisabled(); + + $form->setDefaults([ + 'name' => $this->user->getDisplayName(), + 'email' => $this->user->getEmail(), + ]); + } + + $form->onSuccess[] = [$this, 'processForm']; + + return $form; + } + + /** + * Zpracuje formulář. + * + * @throws SettingsItemNotFoundException + * @throws Throwable + * @throws MailingMailCreationException + */ + public function processForm(Form $form, stdClass $values): void + { + $recipientsUsers = new ArrayCollection(); + $recipientsEmails = new ArrayCollection(); + + if ($this->user) { + $senderName = $this->user->getDisplayName(); + $senderEmail = $this->user->getEmail(); + if ($values->sendCopy) { + $recipientsUsers->add($this->user); + } + } else { + $senderName = $values->name; + $senderEmail = $values->email; + if ($values->sendCopy) { + $recipientsEmails->add($senderEmail); + } + } + + $recipients = $this->queryBus->handle(new SettingArrayValueQuery(Settings::CONTACT_FORM_RECIPIENTS)); + foreach ($recipients as $recipient) { + $recipientsEmails->add($recipient); + } + + $this->mailService->sendMailFromTemplate( + $recipientsUsers, + $recipientsEmails, + Template::CONTACT_FORM, + [ + TemplateVariable::SEMINAR_NAME => $this->queryBus->handle(new SettingStringValueQuery(Settings::SEMINAR_NAME)), + TemplateVariable::SENDER_NAME => $senderName, + TemplateVariable::SENDER_EMAIL => $senderEmail, + TemplateVariable::MESSAGE => str_replace(["\n", "\r"], '', nl2br($values->message, false)), + ] + ); + + $this->onSave(); + } +} diff --git a/app/WebModule/Forms/TroopConfirmForm.php b/app/WebModule/Forms/TroopConfirmForm.php new file mode 100644 index 000000000..83e017fcf --- /dev/null +++ b/app/WebModule/Forms/TroopConfirmForm.php @@ -0,0 +1,134 @@ +template->setFile(__DIR__ . '/templates/troop_confirm_form.latte'); + + $this->resolveTroop(); + + $this->template->troop = $this->troop; + $this->template->agreement = $this->queryBus->handle(new SettingStringValueQuery(Settings::APPLICATION_AGREEMENT)); + + $this->template->allCountError = $this->allCountError; + $this->template->duplicitUsersError = $this->duplicitUsersError; + $this->template->userNotLeaderError = $this->userNotLeaderError; + + $this->template->render(); + } + + /** + * Vytvoří formulář. + */ + public function createComponentForm(): Form + { + $this->resolveTroop(); + + $form = $this->baseFormFactory->create(); + + $pairedTroopCodeText = $form->addText('pairedTroopCode') + ->setDefaultValue($this->troop->getPairedTroopCode()); + + $agreementCheckbox = $form->addCheckbox('agreement', 'Souhlasím s podmínkami akce.') + ->addRule(Form::FILLED, 'Musíš souhlasit s podmínkami akce.'); + + $submit = $form->addSubmit('submit', 'Závazně registrovat') + ->setDisabled($this->allCountError || $this->duplicitUsersError || $this->userNotLeaderError); + + if ($this->troop->getState() !== TroopApplicationState::DRAFT) { + $pairedTroopCodeText->setHtmlAttribute('readonly'); + $agreementCheckbox->setDisabled(); + $submit->setDisabled(); + } + + $form->setAction($this->getPresenter()->link('this')); + + $form->onSuccess[] = [$this, 'processForm']; + + return $form; + } + + /** + * Zpracuje formulář. + * + * @throws SettingsItemNotFoundException + * @throws Throwable + * @throws MailingMailCreationException + */ + public function processForm(Form $form, stdClass $values): void + { + $this->commandBus->handle(new ConfirmTroop($this->troop->getId(), $values->pairedTroopCode)); + + $this->onSave(); + } + + private function resolveTroop(): void + { + $user = $this->queryBus->handle(new UserByIdQuery($this->presenter->user->getId())); + $this->troop = $this->queryBus->handle(new TroopByLeaderQuery($user->getId())); + + $allCount = $this->troop->countUsersInRoles([Role::ATTENDEE, Role::PATROL_LEADER, Role::LEADER, Role::ESCORT]); + $this->allCountError = $allCount > 42; + + $countFromPatrols = 0; + foreach ($this->troop->getConfirmedPatrols() as $patrol) { + $countFromPatrols += $patrol->countUsersInRoles([Role::ATTENDEE, Role::PATROL_LEADER]); + } + + $countFromTroops = $this->troop->countUsersInRoles([Role::ATTENDEE, Role::PATROL_LEADER]); + $this->duplicitUsersError = $countFromPatrols !== $countFromTroops; + + $this->userNotLeaderError = $user->getGroupRoles() + ->filter(static fn (UserGroupRole $groupRole) => $groupRole->getRole()->getSystemName() === Role::LEADER && $groupRole->getPatrol()->isConfirmed()) + ->count() === 0; + } +} diff --git a/app/WebModule/Forms/templates/group_additional_info_form.latte b/app/WebModule/Forms/templates/group_additional_info_form.latte new file mode 100644 index 000000000..8a7f5cdad --- /dev/null +++ b/app/WebModule/Forms/templates/group_additional_info_form.latte @@ -0,0 +1,73 @@ +{form form class => form-horizontal} +
+
+

2. Doplň údaje

+
+
+ +
+
+ + + Počet účastníků musí být 4-12. + +
+
+ + + Počet rádců musí být 0-1. + +
+
+ + + Vedoucí musí být právě 1. + +
+
+ + + Počet doprovodů je příliš vysoký. + +
+
+ + {foreach $usersRoles as $userRole} + {var $user = $userRole->getUser()} +
+
+
{$user->getDisplayName()}
+
{$userRole->getRole()->getName()}
+
+
+ {$user->getStreet()},
+ {$user->getPostcode()} {$user->getCity()}
+
+ Telefon: {$user->getPhone()}
+ E-mail: {$user->getEmail()}
+ Datum narození: {$user->getBirthdate()|date:'j. n. Y'}
+ {if $user->getMotherPhone() !== null} +
+ Telefon matky: {$user->getMotherPhone()} ({$user->getMotherName()}) + {/if} + {if $user->getFatherPhone() !== null} +
+ Telefon otce: {$user->getFatherPhone()} ({$user->getFatherName()}) + {/if} +
+
+ Zdravotní omezení, alergie, pravidelně užívané léky
+ {input 'health_info_' . $user->getId()} +
+
+
+
+ {/foreach} + +
+
+ Zpět + {input submit class => 'btn-primary button'} +
+
+{/form} diff --git a/app/WebModule/Forms/templates/group_confirm_form.latte b/app/WebModule/Forms/templates/group_confirm_form.latte new file mode 100644 index 000000000..8acb7bf14 --- /dev/null +++ b/app/WebModule/Forms/templates/group_confirm_form.latte @@ -0,0 +1,47 @@ +{form form class => form-horizontal} +
+
+

3. Potvrď

+
+
+ +
+ {foreach $usersRoles as $userRole} + {var $user = $userRole->getUser()} +
+
+
+
{$user->getDisplayName()}
+
{$userRole->getRole()->getName()}
+ {$user->getStreet()},
+ {$user->getPostcode()} {$user->getCity()}
+
+ Telefon: {$user->getPhone()}
+ E-mail: {$user->getEmail()}
+ Datum narození: {$user->getBirthdate()|date:'j. n. Y'}
+ {if $user->getMotherPhone() !== null} +
+ Telefon matky: {$user->getMotherPhone()} ({$user->getMotherName()})
+ {/if} + {if $user->getFatherPhone() !== null} +
+ Telefon otce: {$user->getFatherPhone()} ({$user->getFatherName()})
+ {/if} + {if $user->getHealthInfo()} +
+ Zdravotní omezení, alergie, pravidelně užívané léky:
+

{$user->getHealthInfo()|breakLines}

+ {/if} +
+
+
+ {/foreach} +
+ +
+
+ Zpět + {input submit class => 'btn-primary button'} +
+
+ {/form} diff --git a/app/WebModule/Forms/templates/group_members_form.latte b/app/WebModule/Forms/templates/group_members_form.latte new file mode 100644 index 000000000..0b2791b2f --- /dev/null +++ b/app/WebModule/Forms/templates/group_members_form.latte @@ -0,0 +1,59 @@ +{form form class => form-horizontal} + + +
+
+

1. Vyber účastníky

+
+
+ +
+
+ Oddíl +
+
+ +
+
+ + {foreach $units as $unit} + + + + + + + + {foreach $members[$unit->ID] as $member} + + + + + + + {else} + + + + {/foreach} +
RegistrovatDružinaJménoRole
{input 'register_' . $member->ID}{if $unit->ID !== $member->ID_Unit}{$member->Unit}{/if}{$member->Person}{input 'role_' . $member->ID}
Oddíl nemá žádné členy, které je možné vybrat.
+ {/foreach} + +
+
+ Zpět + {input submit class => 'btn-primary button'} +
+
+ {/form} diff --git a/app/WebModule/Forms/templates/troop_confirm_form.latte b/app/WebModule/Forms/templates/troop_confirm_form.latte new file mode 100644 index 000000000..34f8bdf45 --- /dev/null +++ b/app/WebModule/Forms/templates/troop_confirm_form.latte @@ -0,0 +1,81 @@ +{form form class => form-horizontal} +
+
+ Kód pro spojení do jamooddílu +
+
+ {input pairedTroopCode} + + + Můžeš vložit kód jiné skupiny, se kterou chceš být v jamoddílu společně. + +
+
+ +
+
+ Počet účastníků +
+
+ {$troop->countUsersInRoles(['attendee', 'patrol_leader', 'leader', 'escort'])}/42 + ({$troop->countUsersInRoles(['attendee'])} účastníků, + {$troop->countUsersInRoles(['patrol_leader'])} rádci, + {$troop->countUsersInRoles(['leader']) + $troop->countUsersInRoles(['escort'])} vedoucí a dospělí) + +
+ + + Počet účastníků ve skupině musí být maximálně 42. + +
+ +
+ + + Účastníci a rádci nesmí být zároveň ve více družinách. + +
+ +
+ + + Registrující uživatel musí být vedoucím některé družiny. + +
+
+
+ +
+
+ Cena +
+
+ {$troop->countFee()} Kč +
+ + + Cena je splatná do 30 dnů od odeslání registrace. + +
+
+ +
+
+ +
+
+ +
+
+ {input agreement} +
+
+ +
+
+ {input submit class => 'btn-primary button'} +
+
+ {/form} diff --git a/app/WebModule/Presenters/PagePresenter.php b/app/WebModule/Presenters/PagePresenter.php index 5008119fb..a8d006957 100644 --- a/app/WebModule/Presenters/PagePresenter.php +++ b/app/WebModule/Presenters/PagePresenter.php @@ -27,6 +27,7 @@ use App\WebModule\Components\IProgramsContentControlFactory; use App\WebModule\Components\ISlideshowContentControlFactory; use App\WebModule\Components\ITextContentControlFactory; +use App\WebModule\Components\ITroopApplicationContentControlFactory; use App\WebModule\Components\IUsersContentControlFactory; use App\WebModule\Components\LectorsContentControl; use App\WebModule\Components\NewsContentControl; @@ -35,6 +36,7 @@ use App\WebModule\Components\ProgramsContentControl; use App\WebModule\Components\SlideshowContentControl; use App\WebModule\Components\TextContentControl; +use App\WebModule\Components\TroopApplicationContentControl; use App\WebModule\Components\UsersContentControl; use Nette\Application\BadRequestException; use Nette\DI\Attributes\Inject; @@ -48,6 +50,9 @@ class PagePresenter extends WebBasePresenter #[Inject] public IApplicationContentControlFactory $applicationContentControlFactory; + #[Inject] + public ITroopApplicationContentControlFactory $troopApplicationContentControlFactory; + #[Inject] public IBlocksContentControlFactory $blocksContentControlFactory; @@ -132,6 +137,11 @@ protected function createComponentApplicationContent(): ApplicationContentContro return $this->applicationContentControlFactory->create(); } + protected function createComponentTroopApplicationContent(): TroopApplicationContentControl + { + return $this->troopApplicationContentControlFactory->create(); + } + protected function createComponentBlocksContent(): BlocksContentControl { return $this->blocksContentControlFactory->create(); diff --git a/app/assets/common/main.js b/app/assets/common/main.js index 43dd6dfa2..79247a19c 100644 --- a/app/assets/common/main.js +++ b/app/assets/common/main.js @@ -98,6 +98,7 @@ function initSelects() { .not('.datagrid .row-group-actions select') .not('.datagrid .col-per-page select') .not('.modal-body select') + .not('.ignore-bs-select') .add('select[multiple]') .selectpicker({ noneSelectedText: 'Nic není vybráno', diff --git a/app/lang/common.cs_CZ.neon b/app/lang/common.cs_CZ.neon index 187c69e62..245f192fe 100644 --- a/app/lang/common.cs_CZ.neon +++ b/app/lang/common.cs_CZ.neon @@ -34,6 +34,7 @@ content: image: "Obrázek" document: "Dokumenty" application: "Přihlašovací formulář" + troop_application: "Přihlašovací formulář skupiny" html: "HTML box" faq: "FAQ" news: "Aktuality" @@ -52,6 +53,7 @@ content: image: "" document: "Dokumenty" application: "Přihlašovací formulář" + troop_application: "Přihlašovací formulář skupiny" html: "" faq: "FAQ" news: "Aktuality" diff --git a/migrations/Version20221015133440.php b/migrations/Version20221015133440.php index e95137183..8b36b5d89 100644 --- a/migrations/Version20221015133440.php +++ b/migrations/Version20221015133440.php @@ -19,7 +19,7 @@ public function up(Schema $schema): void $this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'mysql', 'Migration can only be executed safely on \'mysql\'.'); $this->addSql('CREATE TABLE patrol (id INT AUTO_INCREMENT NOT NULL, troop_id INT DEFAULT NULL, name VARCHAR(255) NOT NULL, INDEX IDX_BFB2371263060AC (troop_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'); - $this->addSql('CREATE TABLE troop (id INT AUTO_INCREMENT NOT NULL, variable_symbol_id INT DEFAULT NULL, payment_id INT DEFAULT NULL, income_proof_id INT DEFAULT NULL, name VARCHAR(255) NOT NULL, fee INT NOT NULL, application_date DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\', maturity_date DATE DEFAULT NULL COMMENT \'(DC2Type:date_immutable)\', payment_method VARCHAR(255) DEFAULT NULL, payment_date DATE DEFAULT NULL COMMENT \'(DC2Type:date_immutable)\', state VARCHAR(255) NOT NULL, INDEX IDX_FAAD534C25813A9D (variable_symbol_id), INDEX IDX_FAAD534C4C3A3BB (payment_id), INDEX IDX_FAAD534CFE69EDFB (income_proof_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'); + $this->addSql('CREATE TABLE troop (id INT AUTO_INCREMENT NOT NULL, variable_symbol_id INT DEFAULT NULL, payment_id INT DEFAULT NULL, income_proof_id INT DEFAULT NULL, name VARCHAR(255) NOT NULL, fee INT NOT NULL, application_date DATETIME DEFAULT NULL COMMENT \'(DC2Type:datetime_immutable)\', maturity_date DATE DEFAULT NULL COMMENT \'(DC2Type:date_immutable)\', payment_method VARCHAR(255) DEFAULT NULL, payment_date DATE DEFAULT NULL COMMENT \'(DC2Type:date_immutable)\', state VARCHAR(255) NOT NULL, INDEX IDX_FAAD534C25813A9D (variable_symbol_id), INDEX IDX_FAAD534C4C3A3BB (payment_id), INDEX IDX_FAAD534CFE69EDFB (income_proof_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'); $this->addSql('CREATE TABLE user_group_role (id INT AUTO_INCREMENT NOT NULL, user_id INT DEFAULT NULL, troop_id INT DEFAULT NULL, patrol_id INT DEFAULT NULL, role_id INT DEFAULT NULL, INDEX IDX_D95417F6A76ED395 (user_id), INDEX IDX_D95417F6263060AC (troop_id), INDEX IDX_D95417F6A7B49BA9 (patrol_id), INDEX IDX_D95417F6D60322AC (role_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'); $this->addSql('ALTER TABLE patrol ADD CONSTRAINT FK_BFB2371263060AC FOREIGN KEY (troop_id) REFERENCES troop (id)'); $this->addSql('ALTER TABLE troop ADD CONSTRAINT FK_FAAD534C25813A9D FOREIGN KEY (variable_symbol_id) REFERENCES variable_symbol (id)'); diff --git a/migrations/Version20221017191553.php b/migrations/Version20221017191553.php new file mode 100644 index 000000000..27e23c8fc --- /dev/null +++ b/migrations/Version20221017191553.php @@ -0,0 +1,28 @@ +abortIf($this->connection->getDatabasePlatform()->getName() !== 'mysql', 'Migration can only be executed safely on \'mysql\'.'); + + $this->addSql('CREATE TABLE troop_application_content (id INT NOT NULL, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'); + $this->addSql('ALTER TABLE troop_application_content ADD CONSTRAINT FK_9E479B84BF396750 FOREIGN KEY (id) REFERENCES content (id) ON DELETE CASCADE'); + } + + public function down(Schema $schema): void + { + } +} diff --git a/migrations/Version20221021195625.php b/migrations/Version20221021195625.php new file mode 100644 index 000000000..9a08ad5fa --- /dev/null +++ b/migrations/Version20221021195625.php @@ -0,0 +1,30 @@ +abortIf($this->connection->getDatabasePlatform()->getName() !== 'mysql', 'Migration can only be executed safely on \'mysql\'.'); + + $this->addSql('ALTER TABLE troop ADD leader_id INT DEFAULT NULL, ADD pairing_code VARCHAR(255) NOT NULL, ADD paired_troop_code VARCHAR(255) DEFAULT NULL'); + $this->addSql('ALTER TABLE troop ADD CONSTRAINT FK_FAAD534C73154ED4 FOREIGN KEY (leader_id) REFERENCES user (id)'); + $this->addSql('CREATE UNIQUE INDEX UNIQ_FAAD534C73154ED4 ON troop (leader_id)'); + $this->addSql('ALTER TABLE user ADD phone VARCHAR(255) DEFAULT NULL, ADD mother_name VARCHAR(255) DEFAULT NULL, ADD mother_phone VARCHAR(255) DEFAULT NULL, ADD father_name VARCHAR(255) DEFAULT NULL, ADD father_phone VARCHAR(255) DEFAULT NULL, ADD health_info LONGTEXT DEFAULT NULL'); + } + + public function down(Schema $schema): void + { + } +} diff --git a/migrations/Version20221022182325.php b/migrations/Version20221022182325.php new file mode 100644 index 000000000..3f031f9ee --- /dev/null +++ b/migrations/Version20221022182325.php @@ -0,0 +1,32 @@ +abortIf($this->connection->getDatabasePlatform()->getName() !== 'mysql', 'Migration can only be executed safely on \'mysql\'.'); + + $this->addSql('ALTER TABLE patrol ADD confirmed TINYINT(1) NOT NULL'); + $this->addSql('UPDATE role SET type=\'individual\', maximum_age=150'); + $this->addSql('UPDATE role SET type=\'patrol\', minimum_age=10, maximum_age=16 WHERE name=\'Účastník\''); + $this->addSql('INSERT INTO `role` (`name`, `system_name`, `system_role`, `registerable`, `approved_after_registration`, `synced_with_skaut_is`, `occupancy`, `minimum_age`, `type`, `maximum_age`) VALUES (\'Rádce\', \'patrol_leader\', 1, 1, 1, 0, 0, 10, \'patrol\', 17)'); + $this->addSql('INSERT INTO `role` (`name`, `system_name`, `system_role`, `registerable`, `approved_after_registration`, `synced_with_skaut_is`, `occupancy`, `minimum_age`, `type`, `maximum_age`) VALUES (\'Vedoucí\', \'leader\', 1, 1, 1, 0, 0, 18, \'patrol\', 150)'); + $this->addSql('INSERT INTO `role` (`name`, `system_name`, `system_role`, `registerable`, `approved_after_registration`, `synced_with_skaut_is`, `occupancy`, `minimum_age`, `type`, `maximum_age`) VALUES (\'Dospělý doprovod\', \'escort\', 1, 1, 1, 0, 0, 18, \'troop\', 150)'); + } + + public function down(Schema $schema): void + { + } +} From a9796fc41e0e9f1b5de4f336e41cd57415c06384 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Stan=C4=9Bk?= Date: Tue, 1 Nov 2022 17:05:55 +0100 Subject: [PATCH 07/41] =?UTF-8?q?Opravy=20registra=C4=8Dn=C3=ADho=20formul?= =?UTF-8?q?=C3=A1=C5=99e=20(#893)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fee fix * min/max age warning fixed * allow role change with registered patrols * group name in headline * agreement text * reformat html * confirmed application warning * confirm email * phpstan * headline changed * quotes removed * database dump detailed error * skip db backup Co-authored-by: Jan Staněk --- app/Commands/BackupDatabaseCommand.php | 4 +- app/Model/Mailing/Template.php | 5 + .../Commands/Handlers/ConfirmTroopHandler.php | 16 +- app/Model/User/Troop.php | 6 + .../templates/troop_application_content.latte | 10 +- .../Forms/GroupAdditionalInfoForm.php | 4 + app/WebModule/Forms/GroupConfirmForm.php | 5 + app/WebModule/Forms/GroupMembersForm.php | 70 +-- app/WebModule/Forms/TroopConfirmForm.php | 2 +- .../group_additional_info_form.latte | 5 + .../Forms/templates/group_confirm_form.latte | 5 + .../Forms/templates/group_members_form.latte | 5 + .../Forms/templates/troop_confirm_form.latte | 403 +++++++++++++++++- app/lang/common.cs_CZ.neon | 1 + migrations/Version20221101001721.php | 34 ++ 15 files changed, 539 insertions(+), 36 deletions(-) create mode 100644 migrations/Version20221101001721.php diff --git a/app/Commands/BackupDatabaseCommand.php b/app/Commands/BackupDatabaseCommand.php index 5ed52b505..6d2f3d6fe 100644 --- a/app/Commands/BackupDatabaseCommand.php +++ b/app/Commands/BackupDatabaseCommand.php @@ -50,8 +50,8 @@ protected function execute(InputInterface $input, OutputInterface $output): int $output->writeln('Database dump created successfully.'); return 0; - } catch (Throwable) { - $output->writeln('Database dump creation failed.'); + } catch (Throwable $e) { + $output->writeln('Database dump creation failed: ' . $e); return 1; } diff --git a/app/Model/Mailing/Template.php b/app/Model/Mailing/Template.php index d8a8703a7..49376ac0c 100644 --- a/app/Model/Mailing/Template.php +++ b/app/Model/Mailing/Template.php @@ -25,6 +25,11 @@ class Template */ public const REGISTRATION = 'registration'; + /** + * Potvrzení registrace oddílu. + */ + public const TROOP_REGISTRATION = 'troop_registration'; + /** * Odhlášení ze semináře. */ diff --git a/app/Model/User/Commands/Handlers/ConfirmTroopHandler.php b/app/Model/User/Commands/Handlers/ConfirmTroopHandler.php index 5866c747b..1a7dc9c40 100644 --- a/app/Model/User/Commands/Handlers/ConfirmTroopHandler.php +++ b/app/Model/User/Commands/Handlers/ConfirmTroopHandler.php @@ -6,20 +6,24 @@ use App\Model\Enums\MaturityType; use App\Model\Enums\TroopApplicationState; +use App\Model\Mailing\Template; +use App\Model\Mailing\TemplateVariable; use App\Model\Settings\Queries\SettingDateValueQuery; use App\Model\Settings\Queries\SettingIntValueQuery; use App\Model\Settings\Queries\SettingStringValueQuery; use App\Model\Settings\Settings; use App\Model\User\Commands\ConfirmTroop; use App\Model\User\Repositories\TroopRepository; +use App\Services\MailService; use App\Services\QueryBus; use DateTimeImmutable; +use Doctrine\Common\Collections\ArrayCollection; use Symfony\Component\Messenger\Handler\MessageHandlerInterface; use Yasumi\Yasumi; class ConfirmTroopHandler implements MessageHandlerInterface { - public function __construct(private QueryBus $queryBus, private TroopRepository $troopRepository) + public function __construct(private QueryBus $queryBus, private TroopRepository $troopRepository, private MailService $mailService) { } @@ -32,6 +36,16 @@ public function __invoke(ConfirmTroop $command): void $troop->setApplicationDate(new DateTimeImmutable()); $troop->setMaturityDate($this->countMaturityDate()); $this->troopRepository->save($troop); + + $this->mailService->sendMailFromTemplate(new ArrayCollection([$troop->getLeader()]), null, Template::TROOP_REGISTRATION, [ + TemplateVariable::SEMINAR_NAME => $this->queryBus->handle(new SettingStringValueQuery(Settings::SEMINAR_NAME)), + TemplateVariable::APPLICATION_FEE => (string) $troop->getFee(), + TemplateVariable::APPLICATION_VARIABLE_SYMBOL => $troop->getVariableSymbol()->getVariableSymbol(), + TemplateVariable::APPLICATION_MATURITY => $troop->getMaturityDateText(), + TemplateVariable::BANK_ACCOUNT => $this->queryBus->handle( + new SettingStringValueQuery(Settings::ACCOUNT_NUMBER) + ), + ]); } /** diff --git a/app/Model/User/Troop.php b/app/Model/User/Troop.php index 9fe9d5fdc..57544f9ea 100644 --- a/app/Model/User/Troop.php +++ b/app/Model/User/Troop.php @@ -8,6 +8,7 @@ use App\Model\Application\VariableSymbol; use App\Model\Enums\TroopApplicationState; use App\Model\Payment\Payment; +use App\Utils\Helpers; use DateTimeImmutable; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; @@ -202,6 +203,11 @@ public function getMaturityDate(): ?DateTimeImmutable return $this->maturityDate; } + public function getMaturityDateText(): ?string + { + return $this->maturityDate?->format(Helpers::DATE_FORMAT); + } + public function setMaturityDate(?DateTimeImmutable $maturityDate): void { $this->maturityDate = $maturityDate; diff --git a/app/WebModule/Components/templates/troop_application_content.latte b/app/WebModule/Components/templates/troop_application_content.latte index e54dbb2fd..c6229331e 100644 --- a/app/WebModule/Components/templates/troop_application_content.latte +++ b/app/WebModule/Components/templates/troop_application_content.latte @@ -25,13 +25,21 @@ {elseif $step === 'confirm'} {control groupConfirmForm} {else} +
+
+
+ Přihláška tvé skupiny byla úspěšně odeslána. Nyní můžeš pouze měnit osobu za osobu. +
+
+
+
Pracuješ v roli
-
- {$troop->countFee()} Kč + {$troop->getFee() == null ? $troop->countFee() : $troop->getFee()} Kč
@@ -61,14 +61,405 @@
- +
+
Obecná ustanovení
+
    +
  • + Pořadatelem se rozumí Junák – český skaut, z. s., sídlem Senovážné nám. 24, Praha 1, dále jen + Junák – český skaut nebo Pořadatel. +
  • +
  • + Národní skautské jamboree 2023 organizované Pořadatelem v areálu Park 360 v Hradci Králové se + koná: +
      +
    1. pro mladší účastníky a vedoucí mladších účastníků v termínu 3. - 8. 5. 2023 a
    2. +
    3. pro členy servis týmu a pořadatelského týmu v prodlouženém termínu 29. 4. - + 9. 5. 2023 (prodlouženo o dny přípravy a balení akce v místě konání), +
    4. +
    + dále jen NSJ 2023 či Akce. +
  • +
  • + Není-li konkrétně uvedeno jinak, za účastníky akce se považují všichni mladší + účastníci i jejich vedoucí, stejně tak členové servis týmu a pořadatelského týmu akce, + dále jen Účastník. +
  • +
  • + Účastníci jsou povinni během akce dodržovat Provozní podmínky areálu Park 360, + které budou Pořadatelem zveřejněny nejpozději v den začátku akce na místě jejího + konání. +
  • +
  • + Pořadatel si vyhrazuje právo na úpravu těchto podmínek a pravidel, včetně + podmínek registrace, dojde-li k podstatným změnám okolností organizace akce. O + změnách bude Pořadatel účastníky informovat elektronickou cestou. +
  • +
+ +
Základní pravidla akce
+
    +
  • + Budu se řídit skautským zákonem. +
  • +
  • + Budu respektovat zákony České republiky i vnitřní právo Junáka - českého skauta. +
  • +
  • + Budu se řídit pravidly akce i pokyny, které vydá Pořadatel. +
  • +
  • + Po dobu trvání NSJ 2023 nebudu konzumovat alkohol ani jiné omamné látky. +
  • +
  • + Během konání NSJ 2023 nebudu kouřit. Pokud to však nepůjde jinak, tak budu kouřit + jen na vyhrazených místech a jen pokud je kouření v mém věku legální. +
  • +
  • + Budu ohleduplný a tolerantní k ostatním. +
  • +
  • + Budu dle nejlepšího svědomí a vědomí reprezentovat Junák – český skaut, svůj oddíl + a středisko i celé skautské hnutí a to nejen na akci, ale i mimo ni (například při + cestování). +
  • +
  • + Budu respektovat případné aktuálně platné podmínky a omezení vzhledem + k epidemiologické situaci nemoci covid-19. +
  • +
  • + Souhlasím s platebními i se storno podmínkami (viz níže). +
  • +
  • + Beru na vědomí a souhlasím, že v případě porušení pravidel akce můžu být jako + účastník z NSJ 2023 vyloučen bez nároku na vrácení účastnického poplatku. + Vedoucí mladších členů je pak povinen zajistit dopravu vyloučeného účastníka + neprodleně a na vlastní náklad. +
  • +
+ +
Podmínky účasti
+
    +
  • + NSJ 2023 si klade za jeden z cílů podporu družinového systému, a tak se mladší + účastníci na akci registrují a akce účastní v rámci družin (patrol), ideálně složených z + členů existující družiny, případně členů jednoho oddílu, méně často pak v družině + složené ze členů různých oddílů. +
  • +
  • + Družiny (patroly) se registrují do jamoddílů (samy, nebo jsou později pořadatelem + přiřazeny), ve kterých jsou i dospělí účastníci (dospělí průvodci družin nebo dospělí + vedoucí mladších členů, tj. dospělí členové vedení oddílů). Každá družina musí mít + přiřazenu jednu dospělou osobu, přitom v rámci jamoddílu může být tato jedna + dospělá osoba případně přiřazena i k více družinám současně. Na NSJ 2023 je tedy + snadno možné vyslat i více družin z jednoho oddílu či střediska, které bude + doprovázet jeden či více dospělých členů daného oddílu či střediska. +
  • +
  • + Jamoddíl je tvořen typicky 38 až 42 mladšími účastníky a k nim dalšími dospělými + účastníky (dospělí průvodci družin či dospělí vedoucí mladších členů). Počet + dospělých u jamoddílu je omezen maximálně na “počet družin krát dva”, kdy do + tohoto celkového počtu se započítává i každá dospělá osoba, která je už v rámci + registrace povinně k družině přiřazena. +
  • +
  • + Členové servis týmu a pořadatelského týmu akce se na akci vždy registrují + samostatně, tedy jako jednotlivé osoby. +
  • +
  • + Každý účastník akce musí být v době konání akce řádným členem Junáka - českého + skauta (tedy musí být evidován ve skautISu jako řádný, příp. čestný, člen v některé z + jednotek). +
  • +
  • + Přihlašování všech účastníků na akci probíhá elektronickou formou v rámci systému + na webové stránce akce www.nsj2023.cz ve zveřejněných termínech určených k + registraci na akci pro jednotlivé typy účastníků. Každý účastník musí být řádně na + akci přihlášen a v rámci přihlášky musí mít uvedeny všechny povinné údaje úplně a + pravdivě. +
  • +
  • + Pro účast na akci musí mít každý účastník uhrazen celý účastnický poplatek + odpovídající typu daného účastníka (viz účastnický poplatek a platba). +
  • +
  • + Nutná komunikace s účastníky před akcí probíhá čistě elektronicky prostřednictvím + e-mailové adresy zadané v přihlášce. +
  • +
  • + Výjimku z podmínek účasti či pravidel registrace na akci může ve zvláštních + případech udělit Pořadatel, typicky přímo vedoucí NSJ 2023. +
  • +
+
Mladší účastník (členové a rádci družin)
+
    +
  • + Mladí lidé ve věku od 10 do 16 let (tj. ti, kteří se narodili 8. května 2007 a později, + nejpozději však 2. května 2013) se mohou zúčastnit NSJ 2023 jako členové družin + (patrol). +
  • +
  • V rámci každé družiny (patroly) se může účastnit jeden mladší účastník ve věku od + 10 do 16 let, případně do 18 let (tj. ti, kteří se narodili 8. května 2005 a později, + nejpozději však 2. května 2013), jako rádce družiny. +
  • +
  • Mladší účastníci se registrují a akce účastní ve družinách o 4 až 12 mladších členech + (vč. případného rádce družiny). +
  • +
  • S účastí mladšího člena na akci musí souhlasit jeho zákonný zástupce, tento souhlas + musí být následně vyznačen v přihlášce na akci. Za vyplnění a pravdivost všech + údajů mladšího člena odpovídá vedoucí mladších účastníků přiřazený k dané družině + (patrole). +
  • +
  • Mladší účastník se účastní NSJ 2023 po celou dobu akce v termínu pro mladší + účastníky a vedoucí mladších účastníků. +
  • +
  • V případě nutné komunikace s členy družiny (či jamoddílu) probíhá komunikace před + akcí prostřednictvím přiřazeného vedoucího mladších účastníků a to čistě + elektronicky prostřednictvím e-mailové adresy zadané v přihlášce. +
  • +
+ +
Vedoucí mladších účastníků (průvodci družin, dospělí členové jamoddílů)
+
    +
  • Dospělí lidé starší 18 let (tj. narození před 3. květnem 2005), kteří se chtějí zúčastnit + NSJ 2023 spolu s mladšími účastníky, se mohou registrovat jako vedoucí mladších + účastníků (průvodci družin, dospělí členové jamoddílů) a musí být vždy přiřazeni ke + konkrétní družině (v rámci jamoddílu). +
  • +
  • Jestliže v běžné skautské činnosti nějaký dospělý člen působí jako tzv. “rádce + družiny”, pak se NSJ 2023 může účastnit jako dospělý průvodce družiny (v rámci + jamoddílu), tedy vedoucí mladších účastníků. Na NSJ 2023 nebude označován jako + rádce, neboť rádci jsou na akci pouze mladší členové. +
  • +
  • Vedoucí mladších účastníků, kteří družiny doprovázejí (v rámci jamoddílu), jsou + povinni o mladší účastníky pečovat a podporovat je, jsou odpovědní za jejich + chování, bezpečnost a pohodu na akci i během cest. +
  • +
  • Vedoucí mladších účastníků jsou povinni zajistit pro sebe a pro mladší členy v + družině (či družinách), ke které jsou v rámci jamoddílu přiřazeni, dopravu na a z akce + a také stravování v průběhu celé akce. Pořadatel zajistí na akci vhodné podmínky + pro přípravu a konzumaci jídla. Zajištění potravin ani platba za ně, stejně tak doprava + na a z akce, není součástí účastnického poplatku. +
  • +
  • Za úplnost a správnost údajů v registraci na akci uvedených v přihlášce své osoby i + mladších členů družiny, odpovídá vedoucí mladších členů, který je k ní přiřazen. + Stejně tak odpovídá za včasné a úplné uhrazení příslušných účastnických poplatků. +
  • +
  • Vedoucí mladších účastníků se účastní NSJ 2023 po celou dobu akce v termínu pro + mladší účastníky a vedoucí mladších účastníků. +
  • +
+ +
Člen servis týmu a pořadatelského týmu
+
    +
  • Jednotlivci starší 18 let, respektive starší 15 let (tj. narození před 28. dubnem 2008), + kteří se chtějí účastnit NSJ 2023 a pomoci s pořádáním akce, se mohou registrovat + jako členové servis týmu (dále ST) nebo členové pořadatelského týmu. +
  • +
  • Člen ST vykonává činnosti potřebné pro úspěšné zajištění akce dle svých + schopností, dovedností i znalostí a dle domluvy s odpovědnými členy pořadatelského + týmu. Zvláště členům ST mladším 18 let nemusí být umožněno zapojit se do + zajišťování všech typů příprav či zajišťování všech typů programů (některé činnosti + mohou vyžadovat plnoletost či speciální způsobilost). +
  • +
  • Člen ST pomáhá zajišťovat program pro mladší účastníky a účastní se na zajištění + provozu NSJ 2023 v době konání akce. Navíc se, v souladu se svou přihláškou člena + ST, účastní přípravy akce před akcí samotnou a balení akce po jejím konci. +
  • +
  • Člen pořadatelského týmu se přímo podílí na organizaci akce samotné, rozděluje + úkoly a odpovídá za ně, řídí práci ST v oblasti, která je mu k zajištění akce svěřena + Pořadatelem. +
  • +
  • Člen ST i pořadatelského týmu má v příslušném termínu celého konání akce v + účastnickém poplatku zahrnuto a zajištěno Pořadatelem stravování. +
  • +
  • V rámci registrace na akci člen ST či pořadatelského týmu musí v přihlášce řádně + vyznačit v jakém konkrétním termínu před a po akci se bude účastnit příprav a balení + akce. U členů ST mladších 18 let musí být také pravdivě vyznačeno, že zákonný + zástupce osoby souhlasí s účastí na akci. Za vyplnění a pravdivost údajů, stejně tak + za včasnou a úplnou úhradu účastnického poplatku, odpovídá přímo osoba, která se + na akci registruje. +
  • +
  • Člen ST či pořadatelského týmu se účastní NSJ 2023 v termínu akce pro členy + servistýmu a pořadatelského týmu v souladu s údaji uvedenými v přihlášce. +
  • +
+ +
Účastnický poplatek, termíny registrace a platby
+
    +
  • Vyplněním a odesláním elektronické přihlášky se účastník zavazuje, že v řádných + termínech uhradí příslušný účastnický poplatek a souhlasí s těmito podmínkami. +
  • +
  • Přihláška účastníka se stává závaznou až uhrazením a připsáním celého + účastnického poplatku na bankovní účet Pořadatele. Dokud není celá částka + uhrazena a tím přihláška závazná, může ji Pořadatel dle svého uvážení kdykoli + zrušit. +
  • +
  • V případě, že nebude uhrazen účastnický poplatek do doby splatnosti, bude + registrace účastníka (či účastníků v rámci přihlášené družiny / jamoddílu) + automaticky zrušena bez nároku na vratku již uhrazené části účastnického poplatku, + nebude-li domluveno jinak. +
  • +
+ +
Mladší účastník a vedoucí mladších účastníků (průvodci družin, dospělí členové jamoddílů)
+
    +
  • Účastnický poplatek mladšího člena či vedoucího mladších členů je při závazném + odeslání přihlášky nejpozději do 4. prosince 2022 ve výši 1.300,-Kč za osobu a celé + trvání akce, při pozdějším odeslání přihlášky pak ve výši 1.600,-Kč za osobu za celé + trvání akce. +
  • +
  • Po závazném odeslání přihlášky družiny (mladších účastníků) včetně k družině + přiřazených dospělých účastníků je nutné nejpozději do 30 kalendářních dnů, + nejpozději však do 15. února 2023 (pokud by nastalo dříve), dle e-mailem + obdržených platebních informací uhradit celý účastnický poplatek za všechny + přihlášené členy družiny i přiřazené dospělé účastníky na bankovní účet Pořadatele. +
  • +
  • Podrobné platební informace k úhradě účastnických poplatků obdrží vedoucí + mladších účastníků na e-mailovou adresu uvedenou v přihlášce na akci. +
  • +
  • Pokud se některý z přihlášených účastníků nemůže akce zúčastnit, může nejpozději + do 15. dubna 2023 přiřazený dospělý vedoucí mladších účastníků v registračním + systému takovou osobu zaměnit za jinou. Pozdější změny už nemusí být + Pořadatelem umožněny. +
  • +
  • Upozornění: Účastnický poplatek mladších účastníků a vedoucích mladších + účastníků nezahrnuje jídlo během akce ani dopravu na akci a z ní zpět. +
  • +
+ +
Člen servis týmu a pořadatelského týmu
+
    +
  • Účastnický poplatek pro členy servistýmu a pořadatelského týmu je při závazném + odeslání přihlášky nejpozději do 4. prosince 2022 ve výši 650,-Kč za osobu, při + pozdějším odeslání přihlášky pak ve výši 800,-Kč za osobu za celé trvání akce i dny + před akcí a po akci určené k přípravě a ukončení akce na místě konání. +
  • +
  • Po závazném odeslání přihlášky člena servis týmu nebo pořadatelského týmu je + nutné do 14 kalendářních dnů, nejpozději však do 15. února 2023 (pokud by nastalo + dříve), dle e-mailem obdržených platebních informací uhradit celý účastnický + poplatek za osobu na bankovní účet Pořadatele. +
  • +
  • Podrobné platební informace k úhradě účastnického poplatku obdrží přihlášená + osoba na e-mailovou adresu uvedenou v přihlášce na akci. +
  • +
  • Účastnický poplatek členů ST a pořadatelského týmu již zahrnuje stravování po dobu + akce a to včetně dnů před a po akci určených k přípravě a ukončení akce na místě + konání. Dopravu na místo akce a zpět si hradí každý účastník samostatně. +
  • +
+ +
Storno podmínky
+
    +
  • Po zaplacení účastnického poplatku se přihláška stává závaznou a případné změny + podléhají těmto storno podmínkám. +
  • +
  • Pořadatel má právo zrušit NSJ 2023 v plném rozsahu, pokud nastane závažná + nepředvídaná okolnost. Za případy závažných nepředvídaných okolností se považují + zejména případy, kdy realizace NSJ 2023: +
      +
    1. + není možná, +
    2. +
    3. + je možná pouze za cenu výrazně zvýšených bezpečnostních, zdravotních, + finančních nebo právních rizik spojených s realizací akce nebo za cenu neúměrného + zvýšení finančních nebo organizačních nákladů na realizaci akce. + Jedná se zejména o případy přírodní katastrofy, občanských nepokojů, výrazného + zvýšení rizik nebo opatření příslušných orgánů, která výrazně omezují nebo ztěžují + plánovaný průběh NSJ 2023. Posouzení, zda takové okolnosti nastaly, provede + Pořadatel a následně bude informovat přihlášené účastníky. +
    4. +
    +
  • +
  • Pořadatel má také právo zrušit registraci či účast i pouze některým účastníkům, + pokud nastane závažná nepředvídatelná okolnost, která se týká pouze těchto + účastníků (např. zákaz vlády platící pouze pro účastníky z určitého kraje). +
  • +
  • Jestliže Pořadatel zruší NSJ 2023 nebo zruší registraci či účast pouze některým + účastníkům, bude dotčeným účastníkům vrácena částka ve výši poměrné části + zůstatku rozpočtu po úhradě všech nezbytných nákladů spojených s přípravou + a/nebo zrušením akce. Tato částka bude vrácena do tří měsíců od zrušení a to + bezhotovostním převodem na účet, ze kterého byl účastnický poplatek původně + uhrazen, nebude-li domluveno jinak. +
  • +
  • V případě porušení podmínek ze strany účastníka ztrácí tento účastník jakýkoliv + nárok na vrácení účastnického poplatku. Pokud toto porušení způsobí, že skupina, + ve které je účastník přihlášen, přestane splňovat podmínky registrace, může být tato + skupina z akce rovněž vyloučena bez nároku na vrácení peněz. +
  • +
  • Pokud neúčast (zrušení účasti apod.) jednoho člena znamená, že skupina přestane + splňovat tyto podmínky, může být její registrace považována za neplatnou a může + být ukončena. Ukončení bude posuzováno případ od případu na základě dopadu této + změny na družinu, jamoddíl a celou akci. V případě ukončení nemá skupina nárok na + vrácení účastnického poplatku. +
  • +
  • Jestliže účastník z jakéhokoliv důvodu zruší registraci po zaplacení celého + účastnického poplatku, ztrácí jakýkoliv nárok na vrácení účastnického poplatku. Po + schválení Pořadatelem však může být povolena změna účastníka ve skupině a + účastnický poplatek může být převeden na nového účastníka. I po této změně musí + skupina splňovat podmínky účasti. +
  • +
  • V případě, že se účastník nezúčastní z vážných zdravotních důvodů a informuje o + tom Pořadatele nejpozději v den začátku akce, může Pořadatel rozhodnout o vrácení + části účastnického poplatku. +
  • +
+ +
Zpracování a ochrana osobních údajů a souhlasy účastníků (GDPR)
+
    +
  • Osobní a citlivé údaje všech účastníků jsou zpracovávány Pořadatelem v souladu s + Nařízením Evropského parlamentu a Rady 2016/679 (tzv. GDPR, dále jen nařízení) + a se Zákonem č. 110/2019 Sb., o zpracování osobních údajů, v účinném znění (dále + jen zákon) a dalšími souvisejícími předpisy, v rozsahu nezbytném pro zajištění akce. +
  • +
  • Výslovně souhlasím se zpracováním mých předaných osobních a citlivých údajů + Pořadateli za účelem možnosti účasti na NSJ 2023 a zdárného průběhu akce. +
  • +
  • Beru na vědomí a souhlasím s tím, že Pořadatel v souvislosti s akcí zpracuje + především tyto osobní údaje: identifikační údaje – jméno, příjmení, datum narození, + rodné číslo, přezdívka, případné číslo osobního dokladu; kontaktní údaje na + účastníka a kontaktní osobu (v případě nezletilosti účastníka kontaktní údaje jeho + zákonného zástupce) – kontaktní adresa, telefonická spojení, e-mail. +
  • +
  • Výslovně souhlasím s tím, že Pořadatel v souvislosti s akcí zpracuje i údaje o + zdravotním stavu a dietních omezení účastníka, které zákon označuje jako citlivé + údaje. Jedná se o údaje nezbytné pro posouzení zdravotního stavu při účasti na NSJ + 2023 a též jako informace potřebné v případě nutnosti ošetření. Pro zpracování + těchto citlivých údajů uděluji svůj výslovný souhlas, s vědomím, že tento souhlas + může být kdykoliv odvolán. +
  • +
  • Beru dále na vědomí, že zpracování osobních údajů je v Junák – český skaut dále + upraveno vnitřními předpisy, aby tak byla zajištěna jejich ochrana před zneužitím. + Údaje mohou být v nutném rozsahu zpřístupněny činovníkům Junáka - českého + skauta. +
  • +
  • Výše uvedené osobní údaje budou zpracovávány po dobu přípravy a trvání akce. Na + základě právních povinností jsou některé osobní údaje zpracovávány i po skončení + akce a to po dobu, která je stanovena právními předpisy (např. v souvislosti s + vedením účetnictví). +
  • +
  • Prohlašuji, že veškeré své osobní a citlivé údaje poskytuji dobrovolně. Beru na + vědomí, že na základě písemné žádosti je Junák – český skaut povinen poskytnout + mi informace o osobních a citlivých údajích zpracovávaných o mé osobě, a to jednou + za kalendářní rok bezplatně, jinak kdykoli za přiměřenou úhradu. +
  • +
  • Výslovně dále souhlasím se zpracováním podobizny, obrazových snímků, + obrazových a zvukových záznamů (především fotografií a videí) vznikajících při + dokumentaci činností souvisejících s NSJ 2023; zejména k jejich dalšímu použití v + rámci činnosti Junáka – českého skauta. +
  • +
  • S ohledem na skutečnost, že všichni účastníci akce musí být zároveň členy Junáka - + českého skauta, lze nalézt případné další podrobnosti ke zpracování osobních údajů + i v poučení na přihlášce člena do organizace, neboť těmito pravidly se řídí i + nakládání s osobními údaji v souvislosti s pořádáním akce. +
  • +
+
-
+
{input agreement}
@@ -78,4 +469,4 @@ {input submit class => 'btn-primary button'}
- {/form} +{/form} diff --git a/app/lang/common.cs_CZ.neon b/app/lang/common.cs_CZ.neon index 245f192fe..009d3ebdc 100644 --- a/app/lang/common.cs_CZ.neon +++ b/app/lang/common.cs_CZ.neon @@ -187,6 +187,7 @@ mailing: template_type: sign_in: "Potvrzení přihlášení přes skautIS" registration: "Potvrzení registrace" + troop_registration: "Potvrzení přihlášení skupiny" registration_canceled: "Potvrzení zrušení registrace" registration_canceled_not_paid: "Potvrzení zrušení registrace (nezaplaceno)" registration_approved: "Potvrzení schválení registrace" diff --git a/migrations/Version20221101001721.php b/migrations/Version20221101001721.php new file mode 100644 index 000000000..3c13ba5ee --- /dev/null +++ b/migrations/Version20221101001721.php @@ -0,0 +1,34 @@ +addSql('INSERT INTO `mail_template` (`id`, `type`, `subject`, `text`, `active`, `system_template`) VALUES (NULL, \'troop_registration\', \'Registrace na akci NSJ2023!\', \'Ahoj, děkujeme za registraci na NSJ2023. Tento e-mail je potvrzením, že registrace tvé skupiny byla řádně uložena do systému. Abychom s vámi mohli počítat je nutné nejpozději do 30 kalendářních dnů, nejpozději však do 15. února 2023 (pokud by nastalo dříve) uhradit %poplatek% Kč účastnického poplatku za celou skupinu na účet: %cislo-uctu% s variabilním symbolem: %variabilni-symbol%. S platbou prosím neotálej. Svou skupinu můžeš spravovat po přihlášení na nsj2023.cz. Pokud by se vyskytly jakékoliv potíže, ozvi se nám na registrace@nsj2023.cz. Těšíme se! Tým NSJ2023\', \'1\', \'0\')'); + + $this->addSql('INSERT INTO `template_template_variable` (`template_id`, `template_variable_id`) VALUES (\'15\', \'1\')'); + $this->addSql('INSERT INTO `template_template_variable` (`template_id`, `template_variable_id`) VALUES (\'15\', \'5\')'); + $this->addSql('INSERT INTO `template_template_variable` (`template_id`, `template_variable_id`) VALUES (\'15\', \'9\')'); + $this->addSql('INSERT INTO `template_template_variable` (`template_id`, `template_variable_id`) VALUES (\'15\', \'10\')'); + $this->addSql('INSERT INTO `template_template_variable` (`template_id`, `template_variable_id`) VALUES (\'15\', \'11\')'); + } + + public function down(Schema $schema): void + { + } +} From 3cdd7cc58b96c9cd4d45278c651b113267f9af63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Stan=C4=9Bk?= Date: Tue, 1 Nov 2022 19:13:05 +0100 Subject: [PATCH 08/41] Admin module prehledy - rebased (#895) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * skautis personall * db model update, commands * forms * 2 more steps of form * coding standard * cleanup, fixes deploment * deploy from PR * deploy revert, cs fix * AdminModule:Users - lokalizace a položky pro Dashboard * prehled druzin-demo * prehledy-skupiny-rozdelano * pridani dalsich sloupcu nefunguje mazani, detail a export * Přehledy: jen confirmedPatrols Skupiny - zobrazuje korektní počet v sloupci počet družin, v Družiny - nezobrazuje řádky s nepotvrzenými družinami vůbec * rebase fix * phpstan fix * phpstan + cs fix Co-authored-by: bojovyletoun <> --- .../Components/GroupsGridControl.php | 206 ++++++++++++++++++ .../Components/IGroupsGridControlFactory.php | 16 ++ .../Components/IPatrolsGridControlFactory.php | 16 ++ .../Components/PatrolsGridControl.php | 175 +++++++++++++++ .../Components/templates/groups_grid.latte | 1 + .../Components/templates/patrols_grid.latte | 1 + app/AdminModule/Presenters/UsersPresenter.php | 20 ++ .../templates/Dashboard/default.latte | 14 +- .../Presenters/templates/Users/groups.latte | 4 + .../Presenters/templates/Users/patrols.latte | 4 + app/lang/admin.cs_CZ.neon | 6 + 11 files changed, 461 insertions(+), 2 deletions(-) create mode 100644 app/AdminModule/Components/GroupsGridControl.php create mode 100644 app/AdminModule/Components/IGroupsGridControlFactory.php create mode 100644 app/AdminModule/Components/IPatrolsGridControlFactory.php create mode 100644 app/AdminModule/Components/PatrolsGridControl.php create mode 100644 app/AdminModule/Components/templates/groups_grid.latte create mode 100644 app/AdminModule/Components/templates/patrols_grid.latte create mode 100644 app/AdminModule/Presenters/templates/Users/groups.latte create mode 100644 app/AdminModule/Presenters/templates/Users/patrols.latte diff --git a/app/AdminModule/Components/GroupsGridControl.php b/app/AdminModule/Components/GroupsGridControl.php new file mode 100644 index 000000000..5eea3074b --- /dev/null +++ b/app/AdminModule/Components/GroupsGridControl.php @@ -0,0 +1,206 @@ +sessionSection = $session->getSection('srs'); + } + + /** + * Vykreslí komponentu. + */ + public function render(): void + { + $this->template->setFile(__DIR__ . '/templates/groups_grid.latte'); + $this->template->render(); + } + + /** + * Vytvoří komponentu. + * + * @throws Throwable + * @throws DataGridColumnStatusException + * @throws DataGridException + */ + public function createComponentPatrolsGrid(string $name): DataGrid + { + $grid = new DataGrid($this, $name); + $grid->setTranslator($this->translator); + $grid->setDataSource($this->repository->createQueryBuilder('p')); + $grid->setDefaultSort(['displayName' => 'ASC']); + $grid->setColumnsHideable(); + $grid->setItemsPerPageList([25, 50, 100, 250, 500]); + $grid->setStrictSessionFilterValues(false); + + $grid->addGroupAction('Export seznamu skupin') + ->onSelect[] = [$this, 'groupExportUsers']; + + $grid->addColumnText('id', 'ID') + ->setSortable(); + + $grid->addColumnText('name', 'Název') + ->setSortable() + ->setFilterText(); + + $grid->addColumnText('variableSymbol', 'VS ')->setSortable() // je stejný jako název skupiny + ->setRenderer(static fn ($t) => $t->getVariableSymbol()->getVariableSymbol()) + ->setFilterText(); + + $grid->addColumnText('leader', 'Vedoucí')->setSortable() + ->setRenderer(function (Troop $t) { + $leader = $t->getLeader(); + + return Html::el('a')->setAttribute('href', $this->getPresenter()->link('detail', $leader->getId()))->setText($leader->getDisplayName()); + }) + ->setFilterText(); + + $grid->addColumnDateTime('applicationDate', 'Datum založení') + ->setRenderer(static function (Troop $p) { + $date = $p->getApplicationDate(); + + return $date ? $date->format(Helpers::DATETIME_FORMAT) : ''; + }) + ->setSortable(); + + $grid->addColumnText('pairingCode', 'Kód Jamoddílu')->setFilterText(); + + $grid->addColumnText('fee', 'Cena getFee')->setSortable()->setFilterText(); + + $grid->addColumnText('fee2', 'Cena countFee') + ->setRenderer(static fn (Troop $t) => $t->countFee()); + + $grid->addColumnDateTime('paymentDate', 'Datum zaplacení') + ->setRenderer(static function (Troop $p) { + $date = $p->getPaymentDate(); + + return $date ? $date->format(Helpers::DATETIME_FORMAT) : ''; + }) + ->setSortable(); + + $grid->addColumnDateTime('maturityDate', 'Datum splatnosti') + ->setRenderer(static function (Troop $p) { + $date = $p->getmaturityDate(); + + return $date ? $date->format(Helpers::DATETIME_FORMAT) : ''; + }) + ->setSortable(); + +// $grid->addColumnText('troop', 'Oddíl - přidat link') +// ->setRenderer( function (Patrol $p) { $troop = $p->getTroop(); +// return Html::el("a")->setAttribute("href",$this->link("troopDetail",$troop->getId()))->setText($troop->getName()); + +//}); // link na oddíl + + $grid->addColumnText('numPersons', '# osob') +// ->setSortableCallback(static fn($qb,$vals) =>sort($vals)) + ->setRenderer(static fn (Troop $p) => $p->countUsersInRoles([Role::PATROL_LEADER, Role::LEADER, Role::ESCORT, Role::ATTENDEE])); + + $grid->addColumnText('numChilder', '# rádců') +// ->setSortableCallback(static fn($qb,$vals) =>sort($vals)) + ->setRenderer(static fn (Troop $p) => $p->countUsersInRoles([Role::PATROL_LEADER])); + + $grid->addColumnText('numAdults', '# dospělých') +// ->setSortableCallback(static fn($qb,$vals) =>sort($vals)) + ->setRenderer(static fn (Troop $p) => $p->countUsersInRoles([Role::LEADER])); + + $grid->addColumnText('numPatrols', '# družin') +// ->setSortableCallback(static fn($qb,$vals) =>sort($vals)) + ->setRenderer(static fn (Troop $p) => count($p->getConfirmedPatrols())); + + $grid->addAction('detail', 'admin.common.detail', 'Users:groupDetail') // destinace ,todo group_detail.latte + ->setClass('btn btn-xs btn-primary'); + +// $grid->addAction('delete', '', 'delete!') +// ->setIcon('trash') +// ->setTitle('admin.common.delete') +// ->setClass('btn btn-xs btn-danger') +// ->addAttributes([ +// 'data-toggle' => 'confirmation', +// 'data-content' => $this->translator->translate('admin.users.users_delete_confirm'), +// ]); + + return $grid; + } + +// /** +// * Zpracuje odstranění externí skupiny. +// * +// * @throws AbortException +// */ +// public function handleDelete(int $id): void +// { +// $rec = $this->repository->findById($id); +// +// $this->repository->remove($rec); +// +// $p = $this->getPresenter(); +// $p->flashMessage('Skupina smazána.', 'success'); +// $p->redirect('this'); +// } + + /** + * Hromadně vyexportuje seznam družin. + * + * @param int[] $ids + * + * @throws AbortException + */ + public function groupExportUsers(array $ids): void + { + $this->sessionSection->patrolIds = $ids; + $this->redirect('exportusers'); + } + + /** + * Zpracuje export seznamu družin. + * + * @throws AbortException + * @throws Exception + */ + public function handleExportUsers(): void + { + $ids = $this->session->getSection('srs')->patrolIds; + + $res = $this->repository->createQueryBuilder('p')->where('p.id IN (:ids)') // stejne se v teto class querybuilder pouziva + ->setParameter('ids', $ids)->getQuery()->getResult(); // otestovat , podivat se na vzor (export uzivatelu) + + $users = new ArrayCollection($res); + $response = $this->excelExportService->exportUsersList($users, 'seznam-uzivatelu.xlsx'); // nutna nova metoda + + $this->getPresenter()->sendResponse($response); + } +} diff --git a/app/AdminModule/Components/IGroupsGridControlFactory.php b/app/AdminModule/Components/IGroupsGridControlFactory.php new file mode 100644 index 000000000..a05426e9c --- /dev/null +++ b/app/AdminModule/Components/IGroupsGridControlFactory.php @@ -0,0 +1,16 @@ +sessionSection = $session->getSection('srs'); + } + + /** + * Vykreslí komponentu. + */ + public function render(): void + { + $this->template->setFile(__DIR__ . '/templates/patrols_grid.latte'); + $this->template->render(); + } + + /** + * Vytvoří komponentu. + * + * @throws Throwable + * @throws DataGridColumnStatusException + * @throws DataGridException + */ + public function createComponentPatrolsGrid(string $name): DataGrid + { + $grid = new DataGrid($this, $name); + $grid->setTranslator($this->translator); + $grid->setDataSource($this->repository->createQueryBuilder('p')->where('p.confirmed = true')); + $grid->setDefaultSort(['displayName' => 'ASC']); + $grid->setColumnsHideable(); + $grid->setItemsPerPageList([25, 50, 100, 250, 500]); + $grid->setStrictSessionFilterValues(false); + + $grid->addGroupAction('Export seznamu družin') + ->onSelect[] = [$this, 'groupExportUsers']; + + $grid->addColumnText('id', 'ID') + ->setSortable(); + + $grid->addColumnText('name', 'Název') + ->setSortable() + ->setFilterText(); + +// $grid->addColumnText('leader', 'Vedoucí') +// ->setRenderer(static function (Patrol $p) { +// return $leader = $p->countUsersInRoles([Role::LEADER]); {{ todo +// +// }) +// ->setFilterText(); + + $grid->addColumnDateTime('created', 'Datum založení') + ->setRenderer(static function (Patrol $p) { + $date = $p->getTroop()->getApplicationDate(); + + return $date ? $date->format(Helpers::DATETIME_FORMAT) : ''; + }) + ->setSortable(); + + $grid->addColumnText('troop', 'Oddíl - přidat link') + ->setRenderer(function (Patrol $p) { + $troop = $p->getTroop(); + + return Html::el('a')->setAttribute('href', $this->link('troopDetail', $troop->getId()))->setText($troop->getName()); + }); // link na oddíl + + $grid->addColumnText('userRoles', 'Počet 1') + ->setRenderer(static fn (Patrol $p) => count($p->getUsersRoles())); // je to správné číslo? + +// $grid->addColumnText('notRegisteredMandatoryBlocksCount', 'admin.users.users_not_registered_mandatory_blocks') +// ->setRenderer(static function (User $user) { +// return Html::el('span') +// ->setAttribute('data-toggle', 'tooltip') +// ->setAttribute('title', $user->getNotRegisteredMandatoryBlocksText()) +// ->setText($user->getNotRegisteredMandatoryBlocksCount()); +// }) +// ->setSortable(); + + + $grid->addAction('detail', 'admin.common.detail', 'Users:detail') // destinace + ->setClass('btn btn-xs btn-primary'); + +// $grid->addAction('delete', '', 'delete!') +// ->setIcon('trash') +// ->setTitle('admin.common.delete') +// ->setClass('btn btn-xs btn-danger') +// ->addAttributes([ +// 'data-toggle' => 'confirmation', +// 'data-content' => $this->translator->translate('admin.users.users_delete_confirm'), +// ]); + + return $grid; + } + +// /** +// * Zpracuje odstranění externího uživatele. +// * +// * @throws AbortException +// */ +// public function handleDelete(int $id): void +// { +// $patrol = $this->repository->findById($id); +// +// $this->repository->remove($patrol); +// +// $p = $this->getPresenter(); +// $p->flashMessage('Družina smazána', 'success'); +// $p->redirect('this'); +// } + + /** + * Hromadně vyexportuje seznam družin. + * + * @param int[] $ids + * + * @throws AbortException + */ + public function groupExportUsers(array $ids): void + { + $this->sessionSection->patrolIds = $ids; + $this->redirect('exportusers'); + } + + /** + * Zpracuje export seznamu družin. + * + * @throws AbortException + * @throws Exception + */ + public function handleExportUsers(): void + { + $ids = $this->session->getSection('srs')->patrolIds; + + $res = $this->repository->createQueryBuilder('p')->where('p.id IN (:ids)') // stejne se v teto class querybuilder pouziva + ->setParameter('ids', $ids)->getQuery()->getResult(); // otestovat , podivat se na vzor (export uzivatelu) + + $users = new ArrayCollection($res); + $response = $this->excelExportService->exportUsersList($users, 'seznam-uzivatelu.xlsx'); // nutna nova metoda + + $this->getPresenter()->sendResponse($response); + } +} diff --git a/app/AdminModule/Components/templates/groups_grid.latte b/app/AdminModule/Components/templates/groups_grid.latte new file mode 100644 index 000000000..3f5cf26a1 --- /dev/null +++ b/app/AdminModule/Components/templates/groups_grid.latte @@ -0,0 +1 @@ +{control patrolsGrid} diff --git a/app/AdminModule/Components/templates/patrols_grid.latte b/app/AdminModule/Components/templates/patrols_grid.latte new file mode 100644 index 000000000..3f5cf26a1 --- /dev/null +++ b/app/AdminModule/Components/templates/patrols_grid.latte @@ -0,0 +1 @@ +{control patrolsGrid} diff --git a/app/AdminModule/Presenters/UsersPresenter.php b/app/AdminModule/Presenters/UsersPresenter.php index 155c8080a..aaaaae822 100644 --- a/app/AdminModule/Presenters/UsersPresenter.php +++ b/app/AdminModule/Presenters/UsersPresenter.php @@ -5,8 +5,12 @@ namespace App\AdminModule\Presenters; use App\AdminModule\Components\ApplicationsGridControl; +use App\AdminModule\Components\GroupsGridControl; use App\AdminModule\Components\IApplicationsGridControlFactory; +use App\AdminModule\Components\IGroupsGridControlFactory; +use App\AdminModule\Components\IPatrolsGridControlFactory; use App\AdminModule\Components\IUsersGridControlFactory; +use App\AdminModule\Components\PatrolsGridControl; use App\AdminModule\Components\UsersGridControl; use App\AdminModule\Forms\AddLectorFormFactory; use App\AdminModule\Forms\EditUserPersonalDetailsFormFactory; @@ -37,6 +41,12 @@ class UsersPresenter extends AdminBasePresenter #[Inject] public IUsersGridControlFactory $usersGridControlFactory; + #[Inject] + public IPatrolsGridControlFactory $patrolsGridControlFactory; + + #[Inject] + public IGroupsGridControlFactory $GroupsGridControlFactory; + #[Inject] public AddLectorFormFactory $addLectorFormFactory; @@ -179,6 +189,16 @@ protected function createComponentUsersGrid(): UsersGridControl return $this->usersGridControlFactory->create(); } + protected function createComponentPatrolsGrid(): PatrolsGridControl + { + return $this->patrolsGridControlFactory->create(); + } + + protected function createComponentGroupsGrid(): GroupsGridControl + { + return $this->GroupsGridControlFactory->create(); + } + protected function createComponentAddLectorForm(): Form { $form = $this->addLectorFormFactory->create(); diff --git a/app/AdminModule/Presenters/templates/Dashboard/default.latte b/app/AdminModule/Presenters/templates/Dashboard/default.latte index 3ed3bb43b..70d1d4822 100644 --- a/app/AdminModule/Presenters/templates/Dashboard/default.latte +++ b/app/AdminModule/Presenters/templates/Dashboard/default.latte @@ -68,7 +68,17 @@ @@ -202,4 +212,4 @@
-{/block} \ No newline at end of file +{/block} diff --git a/app/AdminModule/Presenters/templates/Users/groups.latte b/app/AdminModule/Presenters/templates/Users/groups.latte new file mode 100644 index 000000000..e5384b458 --- /dev/null +++ b/app/AdminModule/Presenters/templates/Users/groups.latte @@ -0,0 +1,4 @@ +{block main} +

Skupiny

+ {control groupsGrid} +{/block} diff --git a/app/AdminModule/Presenters/templates/Users/patrols.latte b/app/AdminModule/Presenters/templates/Users/patrols.latte new file mode 100644 index 000000000..491eca487 --- /dev/null +++ b/app/AdminModule/Presenters/templates/Users/patrols.latte @@ -0,0 +1,4 @@ +{block main} +

Družiny

+ {control patrolsGrid} +{/block} diff --git a/app/lang/admin.cs_CZ.neon b/app/lang/admin.cs_CZ.neon index 5f451a5cc..da7370077 100644 --- a/app/lang/admin.cs_CZ.neon +++ b/app/lang/admin.cs_CZ.neon @@ -381,6 +381,12 @@ program: export_schedule: "Stáhnout harmonogram" users: + menu: + persons: "Osoby" + groups: "Skupiny" + troops: "Oddíly" + patrols: "Družiny" + users_heading: "Uživatelé" users_name: "Jméno" users_photo: "Fotka" From a5792c6d8867389f58f8be9791b0ca25b6a170f0 Mon Sep 17 00:00:00 2001 From: David Urban Date: Tue, 1 Nov 2022 20:48:27 +0100 Subject: [PATCH 09/41] Update error message to be more descriptive (#896) --- .../Components/templates/troop_application_content.latte | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/WebModule/Components/templates/troop_application_content.latte b/app/WebModule/Components/templates/troop_application_content.latte index c6229331e..d990c7005 100644 --- a/app/WebModule/Components/templates/troop_application_content.latte +++ b/app/WebModule/Components/templates/troop_application_content.latte @@ -17,7 +17,8 @@ {elseif empty($skautIsRoles)} - Nemáš správnou roli + Ve skautISu nemáš dostatečné oprávnění. Registrace vyžaduje přístup k informacím + o členech (typicky admin oddílu/střediska). {elseif $step === 'members'} {control groupMembersForm} {elseif $step === 'additional_info'} From 6be789e35ca6c6de077484fc226e5b9181e8bb9e Mon Sep 17 00:00:00 2001 From: bojovyletoun <662941+bojovyletoun@users.noreply.github.com> Date: Thu, 3 Nov 2022 08:40:55 +0100 Subject: [PATCH 10/41] =?UTF-8?q?opravy=20p=C5=99ehled=C5=AF=20(#898)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Groups-state-translate doladění sloupců datagrid prehled-Groups,ale chybí jedna věcička: kdyby se nejak povedlo udelat setFilterText na ne-sloupce * -duplicitní sloupec Cena Co-authored-by: bojovyletoun <> --- app/AdminModule/Components/GroupsGridControl.php | 8 ++++++-- app/lang/common.cs_CZ.neon | 1 + 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/app/AdminModule/Components/GroupsGridControl.php b/app/AdminModule/Components/GroupsGridControl.php index 5eea3074b..21ea1fae4 100644 --- a/app/AdminModule/Components/GroupsGridControl.php +++ b/app/AdminModule/Components/GroupsGridControl.php @@ -72,6 +72,10 @@ public function createComponentPatrolsGrid(string $name): DataGrid $grid->addColumnText('id', 'ID') ->setSortable(); + $grid->addColumnText('state', 'Stav')->setSortable() + ->setRenderer(fn ($t) => $this->translator->translate('common.application_state.' . $t->getState())) + ->setFilterText(); + $grid->addColumnText('name', 'Název') ->setSortable() ->setFilterText(); @@ -100,8 +104,8 @@ public function createComponentPatrolsGrid(string $name): DataGrid $grid->addColumnText('fee', 'Cena getFee')->setSortable()->setFilterText(); - $grid->addColumnText('fee2', 'Cena countFee') - ->setRenderer(static fn (Troop $t) => $t->countFee()); +// $grid->addColumnText('fee2', 'Cena countFee') +// ->setRenderer(static fn (Troop $t) => $t->countFee()); $grid->addColumnDateTime('paymentDate', 'Datum zaplacení') ->setRenderer(static function (Troop $p) { diff --git a/app/lang/common.cs_CZ.neon b/app/lang/common.cs_CZ.neon index 009d3ebdc..9364537c2 100644 --- a/app/lang/common.cs_CZ.neon +++ b/app/lang/common.cs_CZ.neon @@ -119,6 +119,7 @@ application_state: canceled: "Zrušeno" paid: "Zaplaceno" paid_free: "Zaplaceno (zdarma)" + draft: "nepotvrzené (draft)" calendar_view: timeGridSeminar: "Na výšku" From 64eade770ecec3d07058af69406928b8b01d1dab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Stan=C4=9Bk?= Date: Thu, 3 Nov 2022 08:41:07 +0100 Subject: [PATCH 11/41] copy menu file during deploy (#899) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Jan Staněk --- build.xml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/build.xml b/build.xml index 41ad07f5c..e891c3d1a 100644 --- a/build.xml +++ b/build.xml @@ -183,6 +183,11 @@ + + + + + From ff4c39014b5ac39fbca0ca6805a6e0d850291bd2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Stan=C4=9Bk?= Date: Thu, 3 Nov 2022 09:27:49 +0100 Subject: [PATCH 12/41] =?UTF-8?q?Oprava=20p=C5=99ihl=C3=A1=C5=A1ky=20servi?= =?UTF-8?q?s=20t=C3=BDmu=20(#900)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * application fix * cs fix Co-authored-by: Jan Staněk --- app/WebModule/Forms/ApplicationFormFactory.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/WebModule/Forms/ApplicationFormFactory.php b/app/WebModule/Forms/ApplicationFormFactory.php index f90858c33..f058d8cc2 100644 --- a/app/WebModule/Forms/ApplicationFormFactory.php +++ b/app/WebModule/Forms/ApplicationFormFactory.php @@ -176,8 +176,7 @@ public function create(int $id): Form 'state' => $this->user->getState(), ]); - $form->onSuccess[] = [$this, 'processForm']; - $form->onValidate[] = [$this, 'validateRolesAgeLimits']; + $form->onSuccess[] = [$this, 'processForm']; return $form; } From 52dddd41a81d85f2247793e0360d14ddc3929447 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Stan=C4=9Bk?= Date: Tue, 8 Nov 2022 20:32:07 +0100 Subject: [PATCH 13/41] payment info in form, buttons hidden (#901) --- .../TroopApplicationContentControl.php | 4 ++++ .../templates/troop_application_content.latte | 13 ++++++++++--- .../Forms/templates/troop_confirm_form.latte | 17 +++++++++-------- 3 files changed, 23 insertions(+), 11 deletions(-) diff --git a/app/WebModule/Components/TroopApplicationContentControl.php b/app/WebModule/Components/TroopApplicationContentControl.php index bc1525805..3ddafc693 100644 --- a/app/WebModule/Components/TroopApplicationContentControl.php +++ b/app/WebModule/Components/TroopApplicationContentControl.php @@ -7,6 +7,8 @@ use App\Model\Acl\Repositories\RoleRepository; use App\Model\Acl\Role; use App\Model\Cms\Dto\ContentDto; +use App\Model\Settings\Queries\SettingStringValueQuery; +use App\Model\Settings\Settings; use App\Model\User\Commands\RegisterTroop; use App\Model\User\Queries\TroopByLeaderQuery; use App\Model\User\Repositories\UserRepository; @@ -68,6 +70,8 @@ public function render(?ContentDto $content = null): void $template->guestRole = $user->isInRole($this->roleRepository->findBySystemName(Role::GUEST)->getName()); $template->testRole = Role::TEST; + $this->template->accountNumber = $this->queryBus->handle(new SettingStringValueQuery(Settings::ACCOUNT_NUMBER)); + $step = $this->getPresenter()->getParameter('step'); $template->step = $step; diff --git a/app/WebModule/Components/templates/troop_application_content.latte b/app/WebModule/Components/templates/troop_application_content.latte index d990c7005..f9ec886fa 100644 --- a/app/WebModule/Components/templates/troop_application_content.latte +++ b/app/WebModule/Components/templates/troop_application_content.latte @@ -29,7 +29,15 @@
- Přihláška tvé skupiny byla úspěšně odeslána. Nyní můžeš pouze měnit osobu za osobu. +

+ Přihláška tvé skupiny byla úspěšně odeslána. Nyní můžeš pouze měnit osobu za osobu. +

+

+ Abychom s vámi mohli počítat je nutné nejpozději do 30 kalendářních dnů, nejpozději však do 15. + února 2023 (pokud by nastalo dříve) uhradit {$troop->getFee()} Kč účastnického + poplatku za celou skupinu na účet: {$accountNumber} s variabilním symbolem: + {$troop->getVariableSymbol()->getVariableSymbol()}. +

@@ -104,8 +112,7 @@

Družiny

diff --git a/app/WebModule/Forms/templates/troop_confirm_form.latte b/app/WebModule/Forms/templates/troop_confirm_form.latte index 347e432fd..900238e64 100644 --- a/app/WebModule/Forms/templates/troop_confirm_form.latte +++ b/app/WebModule/Forms/templates/troop_confirm_form.latte @@ -458,15 +458,16 @@ -
-
- {input agreement} +
+
+
+ {input agreement} +
-
- -
-
- {input submit class => 'btn-primary button'} +
+
+ {input submit class => 'btn-primary button'} +
{/form} From ceb74ac2c33ade28ab87ad6bfee17412f19f7f90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Stan=C4=9Bk?= Date: Tue, 8 Nov 2022 23:34:51 +0100 Subject: [PATCH 14/41] =?UTF-8?q?Nepovolen=C3=AD=20kombinace=20doprovodu?= =?UTF-8?q?=20a=20vedouc=C3=ADho=20dru=C5=BEiny=20(#904)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * user and escort conflict * cs fix --- app/WebModule/Forms/TroopConfirmForm.php | 15 +++++++++++---- .../Forms/templates/troop_confirm_form.latte | 7 +++++++ 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/app/WebModule/Forms/TroopConfirmForm.php b/app/WebModule/Forms/TroopConfirmForm.php index dd835222f..6d3d3da35 100644 --- a/app/WebModule/Forms/TroopConfirmForm.php +++ b/app/WebModule/Forms/TroopConfirmForm.php @@ -32,6 +32,7 @@ class TroopConfirmForm extends UI\Control private bool $allCountError; private bool $duplicitUsersError; private bool $userNotLeaderError; + private bool $userLeaderAndEscortError; /** * Událost při úspěšném odeslání formuláře. @@ -59,9 +60,10 @@ public function render(): void $this->template->troop = $this->troop; $this->template->agreement = $this->queryBus->handle(new SettingStringValueQuery(Settings::APPLICATION_AGREEMENT)); - $this->template->allCountError = $this->allCountError; - $this->template->duplicitUsersError = $this->duplicitUsersError; - $this->template->userNotLeaderError = $this->userNotLeaderError; + $this->template->allCountError = $this->allCountError; + $this->template->duplicitUsersError = $this->duplicitUsersError; + $this->template->userNotLeaderError = $this->userNotLeaderError; + $this->template->userLeaderAndEscortError = $this->userLeaderAndEscortError; $this->template->render(); } @@ -82,7 +84,7 @@ public function createComponentForm(): Form ->addRule(Form::FILLED, 'Musíš souhlasit s podmínkami akce.'); $submit = $form->addSubmit('submit', 'Závazně registrovat') - ->setDisabled($this->allCountError || $this->duplicitUsersError || $this->userNotLeaderError); + ->setDisabled($this->allCountError || $this->duplicitUsersError || $this->userNotLeaderError || $this->userLeaderAndEscortError); if ($this->troop->getState() !== TroopApplicationState::DRAFT) { $pairedTroopCodeText->setHtmlAttribute('readonly'); @@ -130,5 +132,10 @@ private function resolveTroop(): void $this->userNotLeaderError = $user->getGroupRoles() ->filter(static fn (UserGroupRole $groupRole) => $groupRole->getRole()->getSystemName() === Role::LEADER && $groupRole->getPatrol()->isConfirmed()) ->count() === 0; + + $leadersCount = $this->troop->countUsersInRoles([Role::LEADER]); + $escortsCount = $this->troop->countUsersInRoles([Role::ESCORT]); + $leadersOrEscortsCount = $this->troop->countUsersInRoles([Role::LEADER, Role::ESCORT]); + $this->userLeaderAndEscortError = $leadersCount + $escortsCount !== $leadersOrEscortsCount; } } diff --git a/app/WebModule/Forms/templates/troop_confirm_form.latte b/app/WebModule/Forms/templates/troop_confirm_form.latte index 900238e64..7667a6a8c 100644 --- a/app/WebModule/Forms/templates/troop_confirm_form.latte +++ b/app/WebModule/Forms/templates/troop_confirm_form.latte @@ -42,6 +42,13 @@ Registrující uživatel musí být vedoucím některé družiny. + +
+ + + Vedoucí družiny nesmí být zároveň doprovod. + +
From f8af1367b14ca52dd904f57b3b5fdbde0b081377 Mon Sep 17 00:00:00 2001 From: bojovyletoun <662941+bojovyletoun@users.noreply.github.com> Date: Thu, 10 Nov 2022 08:23:09 +0100 Subject: [PATCH 15/41] =?UTF-8?q?oprava=20po=C4=8Dtu=20dosp=C4=9Bl=C3=BDch?= =?UTF-8?q?=20(#906)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: bojovyletoun <> --- app/AdminModule/Components/GroupsGridControl.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/AdminModule/Components/GroupsGridControl.php b/app/AdminModule/Components/GroupsGridControl.php index 21ea1fae4..9759f2e4e 100644 --- a/app/AdminModule/Components/GroupsGridControl.php +++ b/app/AdminModule/Components/GroupsGridControl.php @@ -139,7 +139,7 @@ public function createComponentPatrolsGrid(string $name): DataGrid $grid->addColumnText('numAdults', '# dospělých') // ->setSortableCallback(static fn($qb,$vals) =>sort($vals)) - ->setRenderer(static fn (Troop $p) => $p->countUsersInRoles([Role::LEADER])); + ->setRenderer(static fn (Troop $p) => $p->countUsersInRoles([Role::LEADER, Role::ESCORT])); $grid->addColumnText('numPatrols', '# družin') // ->setSortableCallback(static fn($qb,$vals) =>sort($vals)) From d4e7a807165df840e7db6f16e83676d7781b25a7 Mon Sep 17 00:00:00 2001 From: bojovyletoun <662941+bojovyletoun@users.noreply.github.com> Date: Thu, 10 Nov 2022 08:23:25 +0100 Subject: [PATCH 16/41] Exporty (#903) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Exporty přes DataGrid * lokalizace Export tlačítek a nápovědy Co-authored-by: bojovyletoun <> --- app/AdminModule/Components/GroupsGridControl.php | 5 +++++ app/AdminModule/Components/PatrolsGridControl.php | 5 +++++ app/AdminModule/Presenters/templates/Users/groups.latte | 4 ++++ app/AdminModule/Presenters/templates/Users/patrols.latte | 3 +++ app/lang/admin.cs_CZ.neon | 4 ++++ 5 files changed, 21 insertions(+) diff --git a/app/AdminModule/Components/GroupsGridControl.php b/app/AdminModule/Components/GroupsGridControl.php index 9759f2e4e..a7e0f3033 100644 --- a/app/AdminModule/Components/GroupsGridControl.php +++ b/app/AdminModule/Components/GroupsGridControl.php @@ -23,6 +23,7 @@ use Ublaboo\DataGrid\Exception\DataGridException; use function count; +use function date; /** * Komponenta pro zobrazení datagridu družin. @@ -66,6 +67,10 @@ public function createComponentPatrolsGrid(string $name): DataGrid $grid->setItemsPerPageList([25, 50, 100, 250, 500]); $grid->setStrictSessionFilterValues(false); + $stamp = date('Y-m-d H.m.s'); + $grid->addExportCsv('admin.common.export_all', 'NSJ2023 Skupiny ' . $stamp . 'csv'); + $grid->addExportCsvFiltered('admin.common.export_filter', 'NSJ2023 Skupiny fi ' . $stamp . 'csv'); + $grid->addGroupAction('Export seznamu skupin') ->onSelect[] = [$this, 'groupExportUsers']; diff --git a/app/AdminModule/Components/PatrolsGridControl.php b/app/AdminModule/Components/PatrolsGridControl.php index 0fc780e7c..cf73fdf18 100644 --- a/app/AdminModule/Components/PatrolsGridControl.php +++ b/app/AdminModule/Components/PatrolsGridControl.php @@ -22,6 +22,7 @@ use Ublaboo\DataGrid\Exception\DataGridException; use function count; +use function date; /** * Komponenta pro zobrazení datagridu družin. @@ -65,6 +66,10 @@ public function createComponentPatrolsGrid(string $name): DataGrid $grid->setItemsPerPageList([25, 50, 100, 250, 500]); $grid->setStrictSessionFilterValues(false); + $stamp = date('Y-m-d H.m.s'); + $grid->addExportCsv('admin.common.export_all', 'NSJ2023 Druziny ' . $stamp . 'csv'); + $grid->addExportCsvFiltered('admin.common.export_filter', 'NSJ2023 Druziny fi ' . $stamp . 'csv'); + $grid->addGroupAction('Export seznamu družin') ->onSelect[] = [$this, 'groupExportUsers']; diff --git a/app/AdminModule/Presenters/templates/Users/groups.latte b/app/AdminModule/Presenters/templates/Users/groups.latte index e5384b458..6ef9b4c92 100644 --- a/app/AdminModule/Presenters/templates/Users/groups.latte +++ b/app/AdminModule/Presenters/templates/Users/groups.latte @@ -1,4 +1,8 @@ {block main} +

Skupiny

+ {control groupsGrid} {/block} diff --git a/app/AdminModule/Presenters/templates/Users/patrols.latte b/app/AdminModule/Presenters/templates/Users/patrols.latte index 491eca487..38663b633 100644 --- a/app/AdminModule/Presenters/templates/Users/patrols.latte +++ b/app/AdminModule/Presenters/templates/Users/patrols.latte @@ -1,4 +1,7 @@ {block main}

Družiny

+ {control patrolsGrid} {/block} diff --git a/app/lang/admin.cs_CZ.neon b/app/lang/admin.cs_CZ.neon index da7370077..e5631a769 100644 --- a/app/lang/admin.cs_CZ.neon +++ b/app/lang/admin.cs_CZ.neon @@ -32,6 +32,10 @@ common: role_option: "{0} %role% (%count% uživatelů)|{1} %role% (%count% uživatel)|[2,4] %role% (%count% uživatelé)|[5,Inf[ %role% (%count% uživatelů)" subevent_option: "{0} %subevent% (%count% uživatelů)|{1} %subevent% (%count% uživatel)|[2,4] %subevent% (%count% uživatelé)|[5,Inf[ %subevent% (%count% uživatelů)" + export_note: "Exportují se všechny řádky nebo ty právě zobrazené. Sloupce viz tlačítko vpravo." + export_all: "Export všech" + export_filter: "Export zobrazených" + menu: dashboard: "Úvod" cms: "Web" From 2d329e1ff3d1495522191ab7a5857d1e541eb7b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Stan=C4=9Bk?= Date: Sun, 13 Nov 2022 19:39:50 +0100 Subject: [PATCH 17/41] srs-dev deployment fix --- .github/workflows/deploy-nsj-dev.yml | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/.github/workflows/deploy-nsj-dev.yml b/.github/workflows/deploy-nsj-dev.yml index 7fa0437a7..a3fc6c2b3 100644 --- a/.github/workflows/deploy-nsj-dev.yml +++ b/.github/workflows/deploy-nsj-dev.yml @@ -30,19 +30,19 @@ jobs: CONFIG_RECAPTCHA_SITE_KEY: ${{ secrets.CONFIG_RECAPTCHA_SITE_KEY }} CONFIG_RECAPTCHA_SECRET_KEY: ${{ secrets.CONFIG_RECAPTCHA_SECRET_KEY }} DEPLOY_DIRECTORY: ${{ secrets.DEPLOY_DIRECTORY }} + DEPLOY_LEBEDA: ${{ secrets.DEPLOY_LEBEDA }} DEPLOY_SSH_HOST: ${{ secrets.DEPLOY_SSH_HOST }} DEPLOY_SSH_IP: ${{ secrets.DEPLOY_SSH_IP }} DEPLOY_SSH_KEY: ${{ secrets.DEPLOY_SSH_KEY }} DEPLOY_SSH_PORT: ${{ secrets.DEPLOY_SSH_PORT }} DEPLOY_SSH_USERNAME: ${{ secrets.DEPLOY_SSH_USERNAME }} steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 # Copy & paste from https://github.com/actions/cache/blob/master/examples.md#php---composer - name: Get composer cache id: composer-cache - run: | - echo "::set-output name=dir::$(composer config cache-files-dir)" - - uses: actions/cache@v1 + run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT + - uses: actions/cache@v3 with: path: ${{ steps.composer-cache.outputs.dir }} key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} @@ -55,12 +55,11 @@ jobs: npm install --global yarn #Copy & paste from https://github.com/actions/cache/blob/master/examples.md#node---yarn - name: Get yarn cache - id: yarn-cache-dir-path - run: echo "::set-output name=dir::$(yarn cache dir)" - - uses: actions/cache@v1 id: yarn-cache + run: echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT + - uses: actions/cache@v3 with: - path: ${{ steps.yarn-cache-dir-path.outputs.dir }} + path: ${{ steps.yarn-cache.outputs.dir }} key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} restore-keys: | ${{ runner.os }}-yarn- From 605dd09dd290e156e1ab2f3286032ef430fcd2fb Mon Sep 17 00:00:00 2001 From: bojovyletoun <662941+bojovyletoun@users.noreply.github.com> Date: Sun, 27 Nov 2022 09:25:49 +0100 Subject: [PATCH 18/41] =?UTF-8?q?Exporty=20(upraven=C3=BD=20form=C3=A1t=20?= =?UTF-8?q?data)=20(#907)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Opravy registračního formuláře (#893) * fee fix * min/max age warning fixed * allow role change with registered patrols * group name in headline * agreement text * reformat html * confirmed application warning * confirm email * phpstan * headline changed * quotes removed * database dump detailed error * skip db backup Co-authored-by: Jan Staněk * date format fix * datetime->date windows problem s znakem : Co-authored-by: Jan Staněk Co-authored-by: Jan Staněk Co-authored-by: bojovyletoun <> --- app/AdminModule/Components/GroupsGridControl.php | 6 +++--- app/AdminModule/Components/PatrolsGridControl.php | 6 +++--- .../Components/templates/troop_application_content.latte | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/app/AdminModule/Components/GroupsGridControl.php b/app/AdminModule/Components/GroupsGridControl.php index a7e0f3033..af866c1fb 100644 --- a/app/AdminModule/Components/GroupsGridControl.php +++ b/app/AdminModule/Components/GroupsGridControl.php @@ -67,9 +67,9 @@ public function createComponentPatrolsGrid(string $name): DataGrid $grid->setItemsPerPageList([25, 50, 100, 250, 500]); $grid->setStrictSessionFilterValues(false); - $stamp = date('Y-m-d H.m.s'); - $grid->addExportCsv('admin.common.export_all', 'NSJ2023 Skupiny ' . $stamp . 'csv'); - $grid->addExportCsvFiltered('admin.common.export_filter', 'NSJ2023 Skupiny fi ' . $stamp . 'csv'); + $stamp = date(Helpers::DATE_FORMAT); + $grid->addExportCsv('admin.common.export_all', 'NSJ2023 Skupiny ' . $stamp . '.csv'); + $grid->addExportCsvFiltered('admin.common.export_filter', 'NSJ2023 Skupiny fi ' . $stamp . '.csv'); $grid->addGroupAction('Export seznamu skupin') ->onSelect[] = [$this, 'groupExportUsers']; diff --git a/app/AdminModule/Components/PatrolsGridControl.php b/app/AdminModule/Components/PatrolsGridControl.php index cf73fdf18..9da51ebac 100644 --- a/app/AdminModule/Components/PatrolsGridControl.php +++ b/app/AdminModule/Components/PatrolsGridControl.php @@ -66,9 +66,9 @@ public function createComponentPatrolsGrid(string $name): DataGrid $grid->setItemsPerPageList([25, 50, 100, 250, 500]); $grid->setStrictSessionFilterValues(false); - $stamp = date('Y-m-d H.m.s'); - $grid->addExportCsv('admin.common.export_all', 'NSJ2023 Druziny ' . $stamp . 'csv'); - $grid->addExportCsvFiltered('admin.common.export_filter', 'NSJ2023 Druziny fi ' . $stamp . 'csv'); + $stamp = date(Helpers::DATE_FORMAT); + $grid->addExportCsv('admin.common.export_all', 'NSJ2023 Druziny ' . $stamp . '.csv'); + $grid->addExportCsvFiltered('admin.common.export_filter', 'NSJ2023 Druziny fi ' . $stamp . '.csv'); $grid->addGroupAction('Export seznamu družin') ->onSelect[] = [$this, 'groupExportUsers']; diff --git a/app/WebModule/Components/templates/troop_application_content.latte b/app/WebModule/Components/templates/troop_application_content.latte index f9ec886fa..988c041e2 100644 --- a/app/WebModule/Components/templates/troop_application_content.latte +++ b/app/WebModule/Components/templates/troop_application_content.latte @@ -17,8 +17,8 @@
{elseif empty($skautIsRoles)} - Ve skautISu nemáš dostatečné oprávnění. Registrace vyžaduje přístup k informacím - o členech (typicky admin oddílu/střediska). + Ve skautISu nemáš dostatečné oprávnění. Registrace vyžaduje přístup k informacím + o členech (typicky admin oddílu/střediska). {elseif $step === 'members'} {control groupMembersForm} {elseif $step === 'additional_info'} From 21a7b7e47e368410c387ce3737b7dad2df3e3d45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Stan=C4=9Bk?= Date: Sun, 27 Nov 2022 20:57:11 +0100 Subject: [PATCH 19/41] =?UTF-8?q?P=C3=A1rov=C3=A1n=C3=AD=20plateb=20(#909)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * payment pairing * update payment * paired troops column * payment confirm * email migration * cleanup * cleanup * delete fix * cs fix --- .../Components/GroupsGridControl.php | 2 +- .../Components/PaymentsGridControl.php | 2 + .../Forms/EditPaymentFormFactory.php | 29 ++-- .../Repositories/ApplicationRepository.php | 15 +-- app/Model/Enums/TroopApplicationState.php | 5 - app/Model/Mailing/Template.php | 5 + app/Model/Payment/Payment.php | 7 + .../Commands/Handlers/ConfirmTroopHandler.php | 2 +- .../User/Repositories/TroopRepository.php | 94 +++++++++++++ app/Model/User/Troop.php | 8 ++ app/Services/ApplicationService.php | 125 +++++++++++++++++- .../templates/troop_application_content.latte | 2 +- app/lang/admin.cs_CZ.neon | 3 +- app/lang/common.cs_CZ.neon | 1 + migrations/Version20221101001721.php | 2 +- migrations/Version20221126184510.php | 30 +++++ 16 files changed, 296 insertions(+), 36 deletions(-) create mode 100644 migrations/Version20221126184510.php diff --git a/app/AdminModule/Components/GroupsGridControl.php b/app/AdminModule/Components/GroupsGridControl.php index af866c1fb..1aa9ce0a9 100644 --- a/app/AdminModule/Components/GroupsGridControl.php +++ b/app/AdminModule/Components/GroupsGridControl.php @@ -86,7 +86,7 @@ public function createComponentPatrolsGrid(string $name): DataGrid ->setFilterText(); $grid->addColumnText('variableSymbol', 'VS ')->setSortable() // je stejný jako název skupiny - ->setRenderer(static fn ($t) => $t->getVariableSymbol()->getVariableSymbol()) + ->setRenderer(static fn ($t) => $t->getVariableSymbolText()) ->setFilterText(); $grid->addColumnText('leader', 'Vedoucí')->setSortable() diff --git a/app/AdminModule/PaymentsModule/Components/PaymentsGridControl.php b/app/AdminModule/PaymentsModule/Components/PaymentsGridControl.php index 1a6023f56..27e5c8191 100644 --- a/app/AdminModule/PaymentsModule/Components/PaymentsGridControl.php +++ b/app/AdminModule/PaymentsModule/Components/PaymentsGridControl.php @@ -91,6 +91,8 @@ public function createComponentPaymentsGrid(string $name): void $grid->addColumnText('pairedApplications', 'admin.payments.payments.paired_applications', 'pairedValidApplicationsText'); + $grid->addColumnText('pairedTroops', 'admin.payments.payments.paired_troops', 'pairedTroopsText'); + $grid->addColumnText('state', 'admin.payments.payments.state') ->setRenderer(fn (Payment $payment) => $this->translator->translate('common.payment_state.' . $payment->getState())) ->setFilterMultiSelect($this->preparePaymentStatesOptions()) diff --git a/app/AdminModule/PaymentsModule/Forms/EditPaymentFormFactory.php b/app/AdminModule/PaymentsModule/Forms/EditPaymentFormFactory.php index ef66e3721..9f6ea7182 100644 --- a/app/AdminModule/PaymentsModule/Forms/EditPaymentFormFactory.php +++ b/app/AdminModule/PaymentsModule/Forms/EditPaymentFormFactory.php @@ -8,6 +8,7 @@ use App\Model\Application\Repositories\ApplicationRepository; use App\Model\Payment\Payment; use App\Model\Payment\Repositories\PaymentRepository; +use App\Model\User\Repositories\TroopRepository; use App\Model\User\Repositories\UserRepository; use App\Services\ApplicationService; use Nette; @@ -33,6 +34,7 @@ public function __construct( private PaymentRepository $paymentRepository, private ApplicationRepository $applicationRepository, private UserRepository $userRepository, + private TroopRepository $troopRepository, private ApplicationService $applicationService ) { } @@ -44,6 +46,9 @@ public function create(int $id): Form { $this->payment = $this->paymentRepository->findById($id); + $pairedValidApplications = $this->payment->getPairedValidApplications(); + $pairedTroops = $this->payment->getPairedTroops(); + $form = $this->baseFormFactory->create(); $form->addHidden('id'); @@ -55,7 +60,19 @@ public function create(int $id): Form $inputVariableSymbol = $form->addText('variableSymbol', 'admin.payments.payments.variable_symbol'); - $inputPairedApplication = $form->addMultiSelect('pairedApplications', 'admin.payments.payments.paired_applications', $this->applicationRepository->getApplicationsVariableSymbolsOptions()) + $form->addMultiSelect( + 'pairedApplications', + 'admin.payments.payments.paired_applications', + $this->applicationRepository->getWaitingForPaymentOrPairedApplicationsVariableSymbolsOptions($pairedValidApplications) + ) + ->setHtmlAttribute('class', 'datagrid-multiselect') + ->setHtmlAttribute('data-live-search', 'true'); + + $form->addMultiSelect( + 'pairedTroops', + 'admin.payments.payments.paired_troops', + $this->troopRepository->getWaitingForPaymentOrPairedTroopsVariableSymbolsOptions($pairedTroops) + ) ->setHtmlAttribute('class', 'datagrid-multiselect') ->setHtmlAttribute('data-live-search', 'true'); @@ -81,18 +98,13 @@ public function create(int $id): Form $inputVariableSymbol->setDisabled(); } - $pairedValidApplications = $this->payment->getPairedValidApplications(); - - $inputPairedApplication->setItems( - $this->applicationRepository->getWaitingForPaymentOrPairedApplicationsVariableSymbolsOptions($pairedValidApplications) - ); - $form->setDefaults([ 'id' => $id, 'date' => $this->payment->getDate(), 'amount' => $this->payment->getAmount(), 'variableSymbol' => $this->payment->getVariableSymbol(), 'pairedApplications' => $this->applicationRepository->findApplicationsIds($pairedValidApplications), + 'pairedTroops' => $this->troopRepository->findTroopsIds($pairedTroops), ]); $form->onSuccess[] = [$this, 'processForm']; @@ -111,8 +123,9 @@ public function processForm(Form $form, stdClass $values): void $loggedUser = $this->userRepository->findById($form->getPresenter()->user->id); $pairedApplications = $this->applicationRepository->findApplicationsByIds($values->pairedApplications); + $pairedTroops = $this->troopRepository->findTroopsByIds($values->pairedTroops); - $this->applicationService->updatePayment($this->payment, $values->date, $values->amount, $values->variableSymbol, $pairedApplications, $loggedUser); + $this->applicationService->updatePayment($this->payment, $values->date, $values->amount, $values->variableSymbol, $pairedApplications, $pairedTroops, $loggedUser); } } } diff --git a/app/Model/Application/Repositories/ApplicationRepository.php b/app/Model/Application/Repositories/ApplicationRepository.php index 0ef2e8daa..7f57859c9 100644 --- a/app/Model/Application/Repositories/ApplicationRepository.php +++ b/app/Model/Application/Repositories/ApplicationRepository.php @@ -135,19 +135,6 @@ public function findWaitingForPaymentOrPairedApplications(Collection $pairedAppl return $this->getRepository()->matching($criteria); } - /** - * @return string[] - */ - public function getApplicationsVariableSymbolsOptions(): array - { - $options = []; - foreach ($this->findValid() as $application) { - $options[$application->getId()] = $application->getUser()->getLastName() . ' ' . $application->getUser()->getFirstName() . ' (' . $application->getVariableSymbolText() . ' - ' . $application->getFee() . ')'; - } - - return $options; - } - /** * @param Collection $pairedApplications * @@ -157,7 +144,7 @@ public function getWaitingForPaymentOrPairedApplicationsVariableSymbolsOptions(C { $options = []; foreach ($this->findWaitingForPaymentOrPairedApplications($pairedApplications) as $application) { - $options[$application->getId()] = $application->getUser()->getLastName() . ' ' . $application->getUser()->getFirstName() . ' (' . $application->getVariableSymbolText() . ' - ' . $application->getFee() . ')'; + $options[$application->getId()] = $application->getUser()->getLastName() . ' ' . $application->getUser()->getFirstName() . ' (' . $application->getVariableSymbolText() . ' - ' . $application->getFee() . ' Kč)'; } return $options; diff --git a/app/Model/Enums/TroopApplicationState.php b/app/Model/Enums/TroopApplicationState.php index dea0648a9..9e52aa375 100644 --- a/app/Model/Enums/TroopApplicationState.php +++ b/app/Model/Enums/TroopApplicationState.php @@ -21,11 +21,6 @@ class TroopApplicationState */ public const CANCELED_NOT_PAID = 'canceled_not_paid'; - /** - * Zrušeno. - */ - public const CANCELED = 'canceled'; - /** * Zaplaceno. */ diff --git a/app/Model/Mailing/Template.php b/app/Model/Mailing/Template.php index 49376ac0c..16e1e7e25 100644 --- a/app/Model/Mailing/Template.php +++ b/app/Model/Mailing/Template.php @@ -60,6 +60,11 @@ class Template */ public const PAYMENT_CONFIRMED = 'payment_confirmed'; + /** + * Potvrzení přijetí platby skupiny. + */ + public const TROOP_PAYMENT_CONFIRMED = 'troop_payment_confirmed'; + /** * Upozornění na splatnost. */ diff --git a/app/Model/Payment/Payment.php b/app/Model/Payment/Payment.php index 3ef440ca8..0070fbb4f 100644 --- a/app/Model/Payment/Payment.php +++ b/app/Model/Payment/Payment.php @@ -239,6 +239,13 @@ public function getPairedTroops(): Collection return $this->pairedTroops; } + public function getPairedTroopsText(): string + { + $pairedNames = $this->getPairedTroops()->map(static fn (Troop $troop) => $troop->getName())->toArray(); + + return implode(', ', $pairedNames); + } + public function getState(): string { return $this->state; diff --git a/app/Model/User/Commands/Handlers/ConfirmTroopHandler.php b/app/Model/User/Commands/Handlers/ConfirmTroopHandler.php index 1a7dc9c40..854d39b3c 100644 --- a/app/Model/User/Commands/Handlers/ConfirmTroopHandler.php +++ b/app/Model/User/Commands/Handlers/ConfirmTroopHandler.php @@ -40,7 +40,7 @@ public function __invoke(ConfirmTroop $command): void $this->mailService->sendMailFromTemplate(new ArrayCollection([$troop->getLeader()]), null, Template::TROOP_REGISTRATION, [ TemplateVariable::SEMINAR_NAME => $this->queryBus->handle(new SettingStringValueQuery(Settings::SEMINAR_NAME)), TemplateVariable::APPLICATION_FEE => (string) $troop->getFee(), - TemplateVariable::APPLICATION_VARIABLE_SYMBOL => $troop->getVariableSymbol()->getVariableSymbol(), + TemplateVariable::APPLICATION_VARIABLE_SYMBOL => $troop->getVariableSymbolText(), TemplateVariable::APPLICATION_MATURITY => $troop->getMaturityDateText(), TemplateVariable::BANK_ACCOUNT => $this->queryBus->handle( new SettingStringValueQuery(Settings::ACCOUNT_NUMBER) diff --git a/app/Model/User/Repositories/TroopRepository.php b/app/Model/User/Repositories/TroopRepository.php index 7f716245f..25fdc84e4 100644 --- a/app/Model/User/Repositories/TroopRepository.php +++ b/app/Model/User/Repositories/TroopRepository.php @@ -4,9 +4,16 @@ namespace App\Model\User\Repositories; +use App\Model\Enums\TroopApplicationState; use App\Model\Infrastructure\Repositories\AbstractRepository; use App\Model\User\Troop; +use Doctrine\Common\Collections\ArrayCollection; +use Doctrine\Common\Collections\Collection; +use Doctrine\Common\Collections\Criteria; use Doctrine\ORM\EntityManagerInterface; +use Doctrine\ORM\NonUniqueResultException; + +use function array_map; /** * Třída spravující oddíly. @@ -23,6 +30,19 @@ public function findById(int $id): Troop return $this->getRepository()->findOneBy(['id' => $id]); } + /** + * @return Collection + */ + public function findAll(): Collection + { + $result = $this->getRepository()->findAll(); + + return new ArrayCollection($result); + } + + /** + * @throws NonUniqueResultException + */ public function findByLeaderId(int $leaderId): ?Troop { return $this->getRepository() @@ -33,6 +53,80 @@ public function findByLeaderId(int $leaderId): ?Troop ->getOneOrNullResult(); } + /** + * @throws NonUniqueResultException + */ + public function findByVariableSymbol(?string $variableSymbol): ?Troop + { + $variableSymbolRegex = '^0*' . $variableSymbol . '$'; + + return $this->createQueryBuilder('t') + ->select('t') + ->join('t.variableSymbol', 'v') + ->where('REGEXP(v.variableSymbol, :variableSymbol) = 1')->setParameter('variableSymbol', $variableSymbolRegex) + ->getQuery() + ->getOneOrNullResult(); + } + + /** + * Vrací skupiny podle id. + * + * @param int[] $ids + * + * @return Collection + */ + public function findTroopsByIds(array $ids): Collection + { + $criteria = Criteria::create() + ->where(Criteria::expr()->in('id', $ids)); + + return $this->getRepository()->matching($criteria); + } + + /** + * Vrací id skupin. + * + * @param Collection $troops + * + * @return int[] + */ + public function findTroopsIds(Collection $troops): array + { + return array_map(static fn (Troop $t) => $t->getId(), $troops->toArray()); + } + + /** + * @param Collection $pairedTroops + * + * @return Collection + */ + public function findWaitingForPaymentOrPairedTroops(Collection $pairedTroops): Collection + { + $criteria = Criteria::create() + ->where(Criteria::expr()->orX( + Criteria::expr()->eq('state', TroopApplicationState::WAITING_FOR_PAYMENT), + Criteria::expr()->in('id', $pairedTroops->map(static fn (Troop $troop) => $troop->getId()) + ->toArray()) + )); + + return $this->getRepository()->matching($criteria); + } + + /** + * @param Collection $pairedTroops + * + * @return string[] + */ + public function getWaitingForPaymentOrPairedTroopsVariableSymbolsOptions(Collection $pairedTroops): array + { + $options = []; + foreach ($this->findWaitingForPaymentOrPairedTroops($pairedTroops) as $troop) { + $options[$troop->getId()] = $troop->getName() . ' (' . $troop->getVariableSymbolText() . ' - ' . $troop->getFee() . ' Kč)'; + } + + return $options; + } + public function save(Troop $troop): void { $this->em->persist($troop); diff --git a/app/Model/User/Troop.php b/app/Model/User/Troop.php index 57544f9ea..ff5368b56 100644 --- a/app/Model/User/Troop.php +++ b/app/Model/User/Troop.php @@ -188,6 +188,14 @@ public function getVariableSymbol(): VariableSymbol return $this->variableSymbol; } + /** + * Vrací text variabilního symbolu. + */ + public function getVariableSymbolText(): string + { + return $this->variableSymbol->getVariableSymbol(); + } + public function getApplicationDate(): ?DateTimeImmutable { return $this->applicationDate; diff --git a/app/Services/ApplicationService.php b/app/Services/ApplicationService.php index ae6d41f56..a4fd8529f 100644 --- a/app/Services/ApplicationService.php +++ b/app/Services/ApplicationService.php @@ -19,6 +19,7 @@ use App\Model\Enums\MaturityType; use App\Model\Enums\PaymentState; use App\Model\Enums\PaymentType; +use App\Model\Enums\TroopApplicationState; use App\Model\Mailing\Template; use App\Model\Mailing\TemplateVariable; use App\Model\Payment\Payment; @@ -32,7 +33,9 @@ use App\Model\Settings\Settings; use App\Model\Structure\Repositories\SubeventRepository; use App\Model\Structure\Subevent; +use App\Model\User\Repositories\TroopRepository; use App\Model\User\Repositories\UserRepository; +use App\Model\User\Troop; use App\Model\User\User; use App\Utils\Helpers; use DateTimeImmutable; @@ -78,7 +81,8 @@ public function __construct( private Translator $translator, private PaymentRepository $paymentRepository, private IncomeProofRepository $incomeProofRepository, - private EventBus $eventBus + private EventBus $eventBus, + private TroopRepository $troopRepository, ) { } @@ -536,6 +540,52 @@ public function updateApplicationPayment( } } + /** + * Aktualizuje stav platby přihlášky oddílu. + * + * @throws Throwable + */ + public function updateTroopApplicationPayment( + Troop $troop, + ?string $paymentMethod, + ?DateTimeImmutable $paymentDate, + ?DateTimeImmutable $maturityDate + ): void { + $oldPaymentMethod = $troop->getPaymentMethod(); + $oldPaymentDate = $troop->getPaymentDate(); + $oldMaturityDate = $troop->getMaturityDate(); + + // pokud neni zmena, nic se neprovede + if ($paymentMethod === $oldPaymentMethod && $paymentDate == $oldPaymentDate && $maturityDate == $oldMaturityDate) { + return; + } + + $this->em->wrapInTransaction(function () use ($troop, $paymentMethod, $paymentDate, $maturityDate): void { + $troop->setPaymentMethod($paymentMethod); + $troop->setPaymentDate($paymentDate); + $troop->setMaturityDate($maturityDate); + $troop->setState($this->getTroopApplicationState($troop)); + $this->troopRepository->save($troop); + }); + + if ($paymentDate !== null && $oldPaymentDate === null) { + $this->mailService->sendMailFromTemplate( + new ArrayCollection( + [ + $troop->getLeader(), + ] + ), + null, + Template::TROOP_PAYMENT_CONFIRMED, + [ + TemplateVariable::SEMINAR_NAME => $this->queryBus->handle( + new SettingStringValueQuery(Settings::SEMINAR_NAME) + ), + ] + ); + } + } + /** * Vytvoří platbu a označí spárované přihlášky jako zaplacené. * @@ -583,7 +633,27 @@ public function createPayment( $this->updateApplicationPayment($pairedApplication, PaymentType::BANK, $date, $pairedApplication->getMaturityDate(), $createdBy); } } else { - $payment->setState(PaymentState::NOT_PAIRED_VS); + $pairedTroopApplication = $this->troopRepository->findByVariableSymbol($variableSymbol); + + if ($pairedTroopApplication) { + if ( + $pairedTroopApplication->getState() === TroopApplicationState::PAID + ) { + $payment->setState(PaymentState::NOT_PAIRED_PAID); + } elseif ( + $pairedTroopApplication->getState() === TroopApplicationState::CANCELED_NOT_PAID + ) { + $payment->setState(PaymentState::NOT_PAIRED_CANCELED); + } elseif (abs($pairedTroopApplication->getFee() - $amount) >= 0.01) { + $payment->setState(PaymentState::NOT_PAIRED_FEE); + } else { + $payment->setState(PaymentState::PAIRED_AUTO); + $pairedTroopApplication->setPayment($payment); + $this->updateTroopApplicationPayment($pairedTroopApplication, PaymentType::BANK, $date, $pairedTroopApplication->getMaturityDate()); + } + } else { + $payment->setState(PaymentState::NOT_PAIRED_VS); + } } $this->paymentRepository->save($payment); @@ -608,6 +678,7 @@ public function createPaymentManual( * Aktualizuje platbu a stav spárovaných přihlášek. * * @param Collection $pairedApplications + * @param Collection $pairedTroops * * @throws Throwable */ @@ -617,9 +688,10 @@ public function updatePayment( ?float $amount, ?string $variableSymbol, Collection $pairedApplications, + Collection $pairedTroops, User $createdBy ): void { - $this->em->wrapInTransaction(function () use ($payment, $date, $amount, $variableSymbol, $pairedApplications, $createdBy): void { + $this->em->wrapInTransaction(function () use ($payment, $date, $amount, $variableSymbol, $pairedApplications, $pairedTroops, $createdBy): void { if ($date !== null) { $payment->setDate($date); } @@ -653,8 +725,27 @@ public function updatePayment( } } + $oldPairedTroops = clone $payment->getPairedTroops(); + $newPairedTroops = clone $pairedTroops; + + foreach ($oldPairedTroops as $pairedTroop) { + if (! $newPairedTroops->contains($pairedTroop)) { + $pairedTroop->setPayment(null); + $this->updateTroopApplicationPayment($pairedTroop, null, null, $pairedTroop->getMaturityDate()); + $pairedApplicationsModified = true; + } + } + + foreach ($newPairedTroops as $pairedTroop) { + if (! $oldPairedTroops->contains($pairedTroop)) { + $pairedTroop->setPayment($payment); + $this->updateTroopApplicationPayment($pairedTroop, PaymentType::BANK, $payment->getDate(), $pairedTroop->getMaturityDate()); + $pairedApplicationsModified = true; + } + } + if ($pairedApplicationsModified) { - if ($pairedApplications->isEmpty()) { + if ($pairedApplications->isEmpty() && $pairedTroops->isEmpty()) { $payment->setState(PaymentState::NOT_PAIRED); } else { $payment->setState(PaymentState::PAIRED_MANUAL); @@ -677,6 +768,12 @@ public function removePayment(Payment $payment, User $createdBy): void $this->updateApplicationPayment($pairedApplication, null, null, $pairedApplication->getMaturityDate(), $createdBy); } + foreach ($payment->getPairedTroops() as $pairedTroop) { + $this->updateTroopApplicationPayment($pairedTroop, null, null, $pairedTroop->getMaturityDate()); + $pairedTroop->setPayment(null); + $this->troopRepository->save($pairedTroop); + } + $this->paymentRepository->remove($payment); }); } @@ -999,6 +1096,26 @@ private function getApplicationState(Application $application): string return ApplicationState::WAITING_FOR_PAYMENT; } + /** + * Určí stav přihlášky skupiny. + */ + private function getTroopApplicationState(Troop $troop): string + { + if ($troop->getState() === TroopApplicationState::DRAFT) { + return TroopApplicationState::DRAFT; + } + + if ($troop->getState() === TroopApplicationState::CANCELED_NOT_PAID) { + return TroopApplicationState::CANCELED_NOT_PAID; + } + + if ($troop->getPaymentDate()) { + return TroopApplicationState::PAID; + } + + return TroopApplicationState::WAITING_FOR_PAYMENT; + } + /** * Zvýší obsazenost rolí. * diff --git a/app/WebModule/Components/templates/troop_application_content.latte b/app/WebModule/Components/templates/troop_application_content.latte index 988c041e2..7688c8c5f 100644 --- a/app/WebModule/Components/templates/troop_application_content.latte +++ b/app/WebModule/Components/templates/troop_application_content.latte @@ -36,7 +36,7 @@ Abychom s vámi mohli počítat je nutné nejpozději do 30 kalendářních dnů, nejpozději však do 15. února 2023 (pokud by nastalo dříve) uhradit {$troop->getFee()} Kč účastnického poplatku za celou skupinu na účet: {$accountNumber} s variabilním symbolem: - {$troop->getVariableSymbol()->getVariableSymbol()}. + {$troop->getVariableSymbolText()}.

diff --git a/app/lang/admin.cs_CZ.neon b/app/lang/admin.cs_CZ.neon index e5631a769..9a1a3a944 100644 --- a/app/lang/admin.cs_CZ.neon +++ b/app/lang/admin.cs_CZ.neon @@ -528,7 +528,8 @@ payments: account_number: "Číslo protiúčtu" account_name: "Název protiúčtu" message: "Zpráva pro příjemce" - paired_applications: "Spárované přihlášky" + paired_applications: "Spárované osoby" + paired_troops: "Spárované skupiny" state: "Stav" saved: "Platba byla úspěšně uložena." delete_confirm: "Opravdu chcete platbu odstranit?" diff --git a/app/lang/common.cs_CZ.neon b/app/lang/common.cs_CZ.neon index 9364537c2..3b4a77a88 100644 --- a/app/lang/common.cs_CZ.neon +++ b/app/lang/common.cs_CZ.neon @@ -195,6 +195,7 @@ mailing: roles_changed: "Potvrzení změny rolí" subevents_changed: "Potvrzení změny podakcí" payment_confirmed: "Potvrzení přijetí platby" + troop_payment_confirmed: "Potvrzení přijetí platby skupiny" maturity_reminder: "Připomínka splatnosti" program_registered: "Přihlášení na program" program_unregistered: "Odhlášení z programu" diff --git a/migrations/Version20221101001721.php b/migrations/Version20221101001721.php index 3c13ba5ee..692bd520e 100644 --- a/migrations/Version20221101001721.php +++ b/migrations/Version20221101001721.php @@ -19,7 +19,7 @@ public function getDescription(): string public function up(Schema $schema): void { - $this->addSql('INSERT INTO `mail_template` (`id`, `type`, `subject`, `text`, `active`, `system_template`) VALUES (NULL, \'troop_registration\', \'Registrace na akci NSJ2023!\', \'Ahoj, děkujeme za registraci na NSJ2023. Tento e-mail je potvrzením, že registrace tvé skupiny byla řádně uložena do systému. Abychom s vámi mohli počítat je nutné nejpozději do 30 kalendářních dnů, nejpozději však do 15. února 2023 (pokud by nastalo dříve) uhradit %poplatek% Kč účastnického poplatku za celou skupinu na účet: %cislo-uctu% s variabilním symbolem: %variabilni-symbol%. S platbou prosím neotálej. Svou skupinu můžeš spravovat po přihlášení na nsj2023.cz. Pokud by se vyskytly jakékoliv potíže, ozvi se nám na registrace@nsj2023.cz. Těšíme se! Tým NSJ2023\', \'1\', \'0\')'); + $this->addSql('INSERT INTO `mail_template` (`id`, `type`, `subject`, `text`, `active`, `system_template`) VALUES (15, \'troop_registration\', \'Registrace na akci NSJ2023!\', \'Ahoj, děkujeme za registraci na NSJ2023. Tento e-mail je potvrzením, že registrace tvé skupiny byla řádně uložena do systému. Abychom s vámi mohli počítat je nutné nejpozději do 30 kalendářních dnů, nejpozději však do 15. února 2023 (pokud by nastalo dříve) uhradit %poplatek% Kč účastnického poplatku za celou skupinu na účet: %cislo-uctu% s variabilním symbolem: %variabilni-symbol%. S platbou prosím neotálej. Svou skupinu můžeš spravovat po přihlášení na nsj2023.cz. Pokud by se vyskytly jakékoliv potíže, ozvi se nám na registrace@nsj2023.cz. Těšíme se! Tým NSJ2023\', \'1\', \'0\')'); $this->addSql('INSERT INTO `template_template_variable` (`template_id`, `template_variable_id`) VALUES (\'15\', \'1\')'); $this->addSql('INSERT INTO `template_template_variable` (`template_id`, `template_variable_id`) VALUES (\'15\', \'5\')'); diff --git a/migrations/Version20221126184510.php b/migrations/Version20221126184510.php new file mode 100644 index 000000000..574b6839c --- /dev/null +++ b/migrations/Version20221126184510.php @@ -0,0 +1,30 @@ +addSql('INSERT INTO `mail_template` (`id`, `type`, `subject`, `text`, `active`, `system_template`) VALUES (16, \'troop_payment_confirmed\', \'Potvrzení platby akce NSJ2023!\', \'Ahoj, potvrzujeme přijetí platby za tvou skupinu na NSJ2023. Potvrzení platby si můžeš stáhnout po přihlášení.\', \'1\', \'0\')'); + + $this->addSql('INSERT INTO `template_template_variable` (`template_id`, `template_variable_id`) VALUES (\'16\', \'1\')'); + } + + public function down(Schema $schema): void + { + } +} From 9b480b2d7c7f4998c294a4c1adcc6d0e2fe90c9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Stan=C4=9Bk?= Date: Sun, 27 Nov 2022 23:00:37 +0100 Subject: [PATCH 20/41] =?UTF-8?q?Ru=C4=8Dn=C3=AD=20akce=20pro=20p=C3=A1rov?= =?UTF-8?q?=C3=A1n=C3=AD=20nesp=C3=A1rovan=C3=BDch=20plateb=20(#923)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * pair not paired payments * cs fix --- .../Presenters/PaymentPresenter.php | 34 +++++++++ .../Repositories/PaymentRepository.php | 11 +++ app/Services/ApplicationService.php | 69 ++++++++++--------- 3 files changed, 82 insertions(+), 32 deletions(-) create mode 100644 app/ActionModule/Presenters/PaymentPresenter.php diff --git a/app/ActionModule/Presenters/PaymentPresenter.php b/app/ActionModule/Presenters/PaymentPresenter.php new file mode 100644 index 000000000..29ea542b8 --- /dev/null +++ b/app/ActionModule/Presenters/PaymentPresenter.php @@ -0,0 +1,34 @@ +paymentRepository->findNotPairedVs(); + + foreach ($notPairedPayments as $payment) { + $this->applicationService->pairPayment($payment); + } + + $response = new TextResponse(null); + $this->sendResponse($response); + } +} diff --git a/app/Model/Payment/Repositories/PaymentRepository.php b/app/Model/Payment/Repositories/PaymentRepository.php index f64df21ff..2aae51c75 100644 --- a/app/Model/Payment/Repositories/PaymentRepository.php +++ b/app/Model/Payment/Repositories/PaymentRepository.php @@ -4,8 +4,11 @@ namespace App\Model\Payment\Repositories; +use App\Model\Enums\PaymentState; use App\Model\Infrastructure\Repositories\AbstractRepository; use App\Model\Payment\Payment; +use Doctrine\Common\Collections\ArrayCollection; +use Doctrine\Common\Collections\Collection; use Doctrine\ORM\EntityManagerInterface; /** @@ -34,6 +37,14 @@ public function findByTransactionId(string $transactionId): ?Payment return $this->getRepository()->findOneBy(['transactionId' => $transactionId]); } + /** + * @return Collection + */ + public function findNotPairedVs(): Collection + { + return new ArrayCollection($this->getRepository()->findBy(['state' => PaymentState::NOT_PAIRED_VS])); + } + /** * Uloží platbu. */ diff --git a/app/Services/ApplicationService.php b/app/Services/ApplicationService.php index a4fd8529f..39140af44 100644 --- a/app/Services/ApplicationService.php +++ b/app/Services/ApplicationService.php @@ -612,52 +612,57 @@ public function createPayment( $payment->setAccountName($accountName); $payment->setMessage($message); - $pairedApplication = $this->applicationRepository->findValidByVariableSymbol($variableSymbol); + $this->pairPayment($payment, $createdBy); + }); + } - if ($pairedApplication) { + public function pairPayment(Payment $payment, ?User $createdBy = null): void + { + $pairedApplication = $this->applicationRepository->findValidByVariableSymbol($payment->getVariableSymbol()); + + if ($pairedApplication) { + if ( + $pairedApplication->getState() === ApplicationState::PAID || + $pairedApplication->getState() === ApplicationState::PAID_FREE + ) { + $payment->setState(PaymentState::NOT_PAIRED_PAID); + } elseif ( + $pairedApplication->getState() === ApplicationState::CANCELED || + $pairedApplication->getState() === ApplicationState::CANCELED_NOT_PAID + ) { + $payment->setState(PaymentState::NOT_PAIRED_CANCELED); + } elseif (abs($pairedApplication->getFee() - $payment->getAmount()) >= 0.01) { + $payment->setState(PaymentState::NOT_PAIRED_FEE); + } else { + $payment->setState(PaymentState::PAIRED_AUTO); + $pairedApplication->setPayment($payment); + $this->updateApplicationPayment($pairedApplication, PaymentType::BANK, $payment->getDate(), $pairedApplication->getMaturityDate(), $createdBy); + } + } else { + $pairedTroopApplication = $this->troopRepository->findByVariableSymbol($payment->getVariableSymbol()); + + if ($pairedTroopApplication) { if ( - $pairedApplication->getState() === ApplicationState::PAID || - $pairedApplication->getState() === ApplicationState::PAID_FREE + $pairedTroopApplication->getState() === TroopApplicationState::PAID ) { $payment->setState(PaymentState::NOT_PAIRED_PAID); } elseif ( - $pairedApplication->getState() === ApplicationState::CANCELED || - $pairedApplication->getState() === ApplicationState::CANCELED_NOT_PAID + $pairedTroopApplication->getState() === TroopApplicationState::CANCELED_NOT_PAID ) { $payment->setState(PaymentState::NOT_PAIRED_CANCELED); - } elseif (abs($pairedApplication->getFee() - $amount) >= 0.01) { + } elseif (abs($pairedTroopApplication->getFee() - $payment->getAmount()) >= 0.01) { $payment->setState(PaymentState::NOT_PAIRED_FEE); } else { $payment->setState(PaymentState::PAIRED_AUTO); - $pairedApplication->setPayment($payment); - $this->updateApplicationPayment($pairedApplication, PaymentType::BANK, $date, $pairedApplication->getMaturityDate(), $createdBy); + $pairedTroopApplication->setPayment($payment); + $this->updateTroopApplicationPayment($pairedTroopApplication, PaymentType::BANK, $payment->getDate(), $pairedTroopApplication->getMaturityDate()); } } else { - $pairedTroopApplication = $this->troopRepository->findByVariableSymbol($variableSymbol); - - if ($pairedTroopApplication) { - if ( - $pairedTroopApplication->getState() === TroopApplicationState::PAID - ) { - $payment->setState(PaymentState::NOT_PAIRED_PAID); - } elseif ( - $pairedTroopApplication->getState() === TroopApplicationState::CANCELED_NOT_PAID - ) { - $payment->setState(PaymentState::NOT_PAIRED_CANCELED); - } elseif (abs($pairedTroopApplication->getFee() - $amount) >= 0.01) { - $payment->setState(PaymentState::NOT_PAIRED_FEE); - } else { - $payment->setState(PaymentState::PAIRED_AUTO); - $pairedTroopApplication->setPayment($payment); - $this->updateTroopApplicationPayment($pairedTroopApplication, PaymentType::BANK, $date, $pairedTroopApplication->getMaturityDate()); - } - } else { - $payment->setState(PaymentState::NOT_PAIRED_VS); - } + $payment->setState(PaymentState::NOT_PAIRED_VS); } + } - $this->paymentRepository->save($payment); - }); + $this->paymentRepository->save($payment); } /** From aa2efe4f48867f7efb4692deb70d2c6e395f0f8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Stan=C4=9Bk?= Date: Mon, 28 Nov 2022 07:40:19 +0100 Subject: [PATCH 21/41] =?UTF-8?q?Potvrzen=C3=AD=20platby=20(#922)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * troop payment proof * cs fix --- .../Components/GroupsGridControl.php | 17 ++- .../Presenters/TroopIncomeProofPresenter.php | 111 ++++++++++++++++++ .../templates/TroopIncomeProof/pdf.latte | 49 ++++++++ app/Model/User/Troop.php | 13 ++ .../TroopApplicationContentControl.php | 8 ++ .../templates/troop_application_content.latte | 6 +- 6 files changed, 201 insertions(+), 3 deletions(-) create mode 100644 app/ExportModule/Presenters/TroopIncomeProofPresenter.php create mode 100644 app/ExportModule/Presenters/templates/TroopIncomeProof/pdf.latte diff --git a/app/AdminModule/Components/GroupsGridControl.php b/app/AdminModule/Components/GroupsGridControl.php index 1aa9ce0a9..c38a1c8a8 100644 --- a/app/AdminModule/Components/GroupsGridControl.php +++ b/app/AdminModule/Components/GroupsGridControl.php @@ -150,8 +150,11 @@ public function createComponentPatrolsGrid(string $name): DataGrid // ->setSortableCallback(static fn($qb,$vals) =>sort($vals)) ->setRenderer(static fn (Troop $p) => count($p->getConfirmedPatrols())); - $grid->addAction('detail', 'admin.common.detail', 'Users:groupDetail') // destinace ,todo group_detail.latte - ->setClass('btn btn-xs btn-primary'); + $grid->addAction('generatePaymentProof', 'Doklad', 'generatePaymentProof'); + $grid->allowRowsAction('generatePaymentProof', static fn (Troop $troop) => $troop->getPaymentDate() !== null); + +// $grid->addAction('detail', 'admin.common.detail', 'Users:groupDetail') // destinace ,todo group_detail.latte +// ->setClass('btn btn-xs btn-primary'); // $grid->addAction('delete', '', 'delete!') // ->setIcon('trash') @@ -181,6 +184,16 @@ public function createComponentPatrolsGrid(string $name): DataGrid // $p->redirect('this'); // } + /** + * Vygeneruje potvrzení o přijetí platby. + * + * @throws AbortException + */ + public function handleGeneratePaymentProof(int $id): void + { + $this->presenter->redirect(':Export:TroopIncomeProof:troop', ['id' => $id]); + } + /** * Hromadně vyexportuje seznam družin. * diff --git a/app/ExportModule/Presenters/TroopIncomeProofPresenter.php b/app/ExportModule/Presenters/TroopIncomeProofPresenter.php new file mode 100644 index 000000000..939cc12e5 --- /dev/null +++ b/app/ExportModule/Presenters/TroopIncomeProofPresenter.php @@ -0,0 +1,111 @@ +user->isLoggedIn()) { + throw new ForbiddenRequestException(); + } + } + + public function actionTroop(int $id): void + { + $troops = new ArrayCollection(); + $troop = $this->troopRepository->findById($id); + + if ($troop->getState() === TroopApplicationState::PAID) { + $troops->add($troop); + } + + $this->generateTroopIncomeProofs($troops); + } + + /** + * @param Collection $troops + * + * @throws AbortException + * @throws SettingsItemNotFoundException + * @throws Throwable + * @throws NonUniqueResultException + */ + private function generateTroopIncomeProofs(Collection $troops): void + { + $template = $this->createTemplate(); + assert($template instanceof Template); + $template->setFile(__DIR__ . '/templates/TroopIncomeProof/pdf.latte'); + + $template->troops = $troops; + $template->logo = $this->queryBus->handle(new SettingStringValueQuery(Settings::LOGO)); + $template->seminarName = $this->queryBus->handle(new SettingStringValueQuery(Settings::SEMINAR_NAME)); + $template->company = $this->queryBus->handle(new SettingStringValueQuery(Settings::COMPANY)); + $template->ico = $this->queryBus->handle(new SettingStringValueQuery(Settings::ICO)); + $template->accountNumber = $this->queryBus->handle(new SettingStringValueQuery(Settings::ACCOUNT_NUMBER)); + $template->accountant = $this->queryBus->handle(new SettingStringValueQuery(Settings::ACCOUNTANT)); + $template->date = (new DateTimeImmutable())->format(Helpers::DATE_FORMAT); + + $this->pdfResponse->setTemplate($template); + + $this->pdfResponse->documentTitle = 'potvrzeni-platby'; + $this->pdfResponse->pageFormat = 'A4'; + $this->pdfResponse->getMPDF()->SetProtection(['copy', 'print', 'print-highres'], '', bin2hex(random_bytes(40))); + + $this->sendResponse($this->pdfResponse); + } +} diff --git a/app/ExportModule/Presenters/templates/TroopIncomeProof/pdf.latte b/app/ExportModule/Presenters/templates/TroopIncomeProof/pdf.latte new file mode 100644 index 000000000..bd483fafb --- /dev/null +++ b/app/ExportModule/Presenters/templates/TroopIncomeProof/pdf.latte @@ -0,0 +1,49 @@ +{block content} + {foreach $troops as $troop} +
+
+

{_export.income_proof.bank.heading}

+
+
+ +
+
+ +
+
+
+ {$company|breakLines} +
+ {_export.income_proof.ico} {$ico} +
+
+
+ +
+
+ {_export.income_proof.bank.purpose} {$seminarName} +
+
+ +
+
+ {_export.income_proof.bank.fee} {$troop->getFee()},00 {_export.income_proof.bank.fee_unit} + ({_export.income_proof.bank.fee_words} {$troop->getFeeWords()} {_export.income_proof.bank.fee_unit}) +
+
+ +
+
+ {_export.income_proof.bank.name} {$troop->getLeader()->getFirstName()} {$troop->getLeader()->getLastName()} + ({_export.income_proof.bank.address} {$troop->getLeader()->getStreet()}, {$troop->getLeader()->getPostcode()} {$troop->getLeader()->getCity()}) +
+
+ +
+
+ {_export.income_proof.bank.accountant} {$accountant} {_export.income_proof.bank.date} {$date} +
+
+ {sep}{/sep} + {/foreach} +{/block} \ No newline at end of file diff --git a/app/Model/User/Troop.php b/app/Model/User/Troop.php index ff5368b56..43e2faa37 100644 --- a/app/Model/User/Troop.php +++ b/app/Model/User/Troop.php @@ -13,12 +13,14 @@ use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping as ORM; +use Numbers_Words; use function array_key_exists; use function count; use function in_array; use function md5; use function mt_rand; +use function str_replace; use function substr; use function uniqid; @@ -178,6 +180,17 @@ public function getFee(): int return $this->fee; } + /** + * Vrací poplatek slovy. + */ + public function getFeeWords(): string + { + $numbersWords = new Numbers_Words(); + $feeWord = $numbersWords->toWords($this->getFee(), 'cs'); + + return str_replace(' ', '', $feeWord); + } + public function setFee(int $fee): void { $this->fee = $fee; diff --git a/app/WebModule/Components/TroopApplicationContentControl.php b/app/WebModule/Components/TroopApplicationContentControl.php index 3ddafc693..d10ab7092 100644 --- a/app/WebModule/Components/TroopApplicationContentControl.php +++ b/app/WebModule/Components/TroopApplicationContentControl.php @@ -184,4 +184,12 @@ public function handleChangeRole(int $roleId): void $this->skautIsService->updateUserRole($roleId); $this->redirect('this'); } + + /** + * Vygeneruje potvrzení o přijetí platby. + */ + public function handleGeneratePaymentProof(int $id): void + { + $this->getPresenter()->redirect(':Export:TroopIncomeProof:troop', ['id' => $id]); + } } diff --git a/app/WebModule/Components/templates/troop_application_content.latte b/app/WebModule/Components/templates/troop_application_content.latte index 7688c8c5f..f273ab25e 100644 --- a/app/WebModule/Components/templates/troop_application_content.latte +++ b/app/WebModule/Components/templates/troop_application_content.latte @@ -32,12 +32,16 @@

Přihláška tvé skupiny byla úspěšně odeslána. Nyní můžeš pouze měnit osobu za osobu.

-

+

Abychom s vámi mohli počítat je nutné nejpozději do 30 kalendářních dnů, nejpozději však do 15. února 2023 (pokud by nastalo dříve) uhradit {$troop->getFee()} Kč účastnického poplatku za celou skupinu na účet: {$accountNumber} s variabilním symbolem: {$troop->getVariableSymbolText()}.

+

+ Platba za vaši přihlášku byla přijata. + Stáhnout potvrzení platby. +

From fde5823d31b8e78c275d2ef6729dfb4ced071190 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Stan=C4=9Bk?= Date: Mon, 28 Nov 2022 12:07:56 +0100 Subject: [PATCH 22/41] =?UTF-8?q?Oprava=20doklad=C5=AF=20(#924)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * payment proofs fix * fixes Co-authored-by: Jan Staněk --- app/AdminModule/Components/GroupsGridControl.php | 2 +- .../Components/PaymentsGridControl.php | 12 ++++++++---- .../Presenters/templates/TroopIncomeProof/pdf.latte | 9 ++++++++- 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/app/AdminModule/Components/GroupsGridControl.php b/app/AdminModule/Components/GroupsGridControl.php index c38a1c8a8..252f8c4c1 100644 --- a/app/AdminModule/Components/GroupsGridControl.php +++ b/app/AdminModule/Components/GroupsGridControl.php @@ -150,7 +150,7 @@ public function createComponentPatrolsGrid(string $name): DataGrid // ->setSortableCallback(static fn($qb,$vals) =>sort($vals)) ->setRenderer(static fn (Troop $p) => count($p->getConfirmedPatrols())); - $grid->addAction('generatePaymentProof', 'Doklad', 'generatePaymentProof'); + $grid->addAction('generatePaymentProof', 'Stáhnout potvzrení o přijetí platby', 'generatePaymentProof'); $grid->allowRowsAction('generatePaymentProof', static fn (Troop $troop) => $troop->getPaymentDate() !== null); // $grid->addAction('detail', 'admin.common.detail', 'Users:groupDetail') // destinace ,todo group_detail.latte diff --git a/app/AdminModule/PaymentsModule/Components/PaymentsGridControl.php b/app/AdminModule/PaymentsModule/Components/PaymentsGridControl.php index 27e5c8191..b78ab0618 100644 --- a/app/AdminModule/PaymentsModule/Components/PaymentsGridControl.php +++ b/app/AdminModule/PaymentsModule/Components/PaymentsGridControl.php @@ -175,10 +175,14 @@ public function handleDelete(int $id): void */ public function handleGeneratePaymentProofBank(int $id): void { - $this->session->getSection('srs')->applicationIds = Helpers::getIds( - $this->paymentRepository->findById($id)->getPairedApplications() - ); - $this->presenter->redirect(':Export:IncomeProof:applications'); + $payment = $this->paymentRepository->findById($id); + + if (! $payment->getPairedApplications()->isEmpty()) { + $this->session->getSection('srs')->applicationIds = Helpers::getIds($payment->getPairedApplications()); + $this->presenter->redirect(':Export:IncomeProof:applications'); + } elseif (! $payment->getPairedTroops()->isEmpty()) { + $this->presenter->redirect(':Export:TroopIncomeProof:troop', ['id' => $payment->getPairedTroops()->get(0)->getId()]); + } } /** diff --git a/app/ExportModule/Presenters/templates/TroopIncomeProof/pdf.latte b/app/ExportModule/Presenters/templates/TroopIncomeProof/pdf.latte index bd483fafb..40f79d66c 100644 --- a/app/ExportModule/Presenters/templates/TroopIncomeProof/pdf.latte +++ b/app/ExportModule/Presenters/templates/TroopIncomeProof/pdf.latte @@ -35,7 +35,14 @@
{_export.income_proof.bank.name} {$troop->getLeader()->getFirstName()} {$troop->getLeader()->getLastName()} - ({_export.income_proof.bank.address} {$troop->getLeader()->getStreet()}, {$troop->getLeader()->getPostcode()} {$troop->getLeader()->getCity()}) + ({_export.income_proof.bank.address} {$troop->getLeader()->getStreet()}, {$troop->getLeader()->getPostcode()} {$troop->getLeader()->getCity()}), + skupina: {$troop->getName()} +
+
+ +
+
+ Zaplaceno z účtu: {$troop->getPayment()->getAccountNumber()}, variabilní symbol: {$troop->getPayment()->getVariableSymbol()}
From 5ea7c8da18951ffa8ce9abc31827cc1339e61840 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Stan=C4=9Bk?= Date: Fri, 2 Dec 2022 12:57:11 +0100 Subject: [PATCH 23/41] limit only for attendees (#932) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Jan Staněk --- app/WebModule/Forms/TroopConfirmForm.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/WebModule/Forms/TroopConfirmForm.php b/app/WebModule/Forms/TroopConfirmForm.php index 6d3d3da35..585a69a2d 100644 --- a/app/WebModule/Forms/TroopConfirmForm.php +++ b/app/WebModule/Forms/TroopConfirmForm.php @@ -118,7 +118,7 @@ private function resolveTroop(): void $user = $this->queryBus->handle(new UserByIdQuery($this->presenter->user->getId())); $this->troop = $this->queryBus->handle(new TroopByLeaderQuery($user->getId())); - $allCount = $this->troop->countUsersInRoles([Role::ATTENDEE, Role::PATROL_LEADER, Role::LEADER, Role::ESCORT]); + $allCount = $this->troop->countUsersInRoles([Role::ATTENDEE, Role::PATROL_LEADER]); $this->allCountError = $allCount > 42; $countFromPatrols = 0; From 65bf7340e62e69e9c73b953943dae61208c8b746 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Stan=C4=9Bk?= Date: Sat, 3 Dec 2022 11:11:45 +0100 Subject: [PATCH 24/41] =?UTF-8?q?Oprava=20odstran=C4=9Bn=C3=AD=20posledn?= =?UTF-8?q?=C3=ADho=20=C4=8Dlena=20(#934)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * remove last member fix * dump removal * cs fix * cs fix --- .../Commands/Handlers/UpdateGroupMembersHandler.php | 12 ++++++++++++ .../Components/TroopApplicationContentControl.php | 5 +++++ app/WebModule/Forms/GroupMembersForm.php | 11 +++++++++++ 3 files changed, 28 insertions(+) diff --git a/app/Model/User/Commands/Handlers/UpdateGroupMembersHandler.php b/app/Model/User/Commands/Handlers/UpdateGroupMembersHandler.php index eb7a5d9ce..17d5ad9fb 100644 --- a/app/Model/User/Commands/Handlers/UpdateGroupMembersHandler.php +++ b/app/Model/User/Commands/Handlers/UpdateGroupMembersHandler.php @@ -141,6 +141,18 @@ public function __invoke(UpdateGroupMembers $command): void $this->userGroupRoleRepository->save($userGroupRole); } } + + if (empty($command->getPersons())) { + if ($command->getType() === 'patrol') { + foreach ($patrol->getUsersRoles() as $usersRole) { + $this->userGroupRoleRepository->remove($usersRole); + } + } elseif ($command->getType() === 'troop') { + foreach ($troop->getUsersRoles() as $usersRole) { + $this->userGroupRoleRepository->remove($usersRole); + } + } + } }); } } diff --git a/app/WebModule/Components/TroopApplicationContentControl.php b/app/WebModule/Components/TroopApplicationContentControl.php index d10ab7092..9d2482a73 100644 --- a/app/WebModule/Components/TroopApplicationContentControl.php +++ b/app/WebModule/Components/TroopApplicationContentControl.php @@ -129,6 +129,11 @@ protected function createComponentGroupMembersForm(): GroupMembersForm $p->redirect('this', ['step' => 'members', 'type' => $type, 'patrol_id' => $patrolId]); }; + $form->onRemoveAll[] = function (): void { + $p = $this->getPresenter(); + $p->redirect('this'); + }; + return $form; } diff --git a/app/WebModule/Forms/GroupMembersForm.php b/app/WebModule/Forms/GroupMembersForm.php index 14aef8743..18d0c2261 100644 --- a/app/WebModule/Forms/GroupMembersForm.php +++ b/app/WebModule/Forms/GroupMembersForm.php @@ -66,6 +66,13 @@ class GroupMembersForm extends UI\Control */ public array $onError = []; + /** + * Událost při odstranění všech členů. + * + * @var callable[] + */ + public array $onRemoveAll = []; + private string $patrolName = ''; /** @var Collection */ @@ -216,6 +223,10 @@ public function processForm(Form $form, stdClass $values): void $this->commandBus->handle(new UpdateGroupMembers($this->type, $this->troop->getId(), $this->patrolId, $selectedPersons)); + if (empty($selectedPersons)) { + $this->onRemoveAll(); + } + $this->onSave(); } From bb6613692fe4fa7ed9b26b8533f43941201557b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Stan=C4=9Bk?= Date: Tue, 13 Dec 2022 19:35:33 +0100 Subject: [PATCH 25/41] login fix (#937) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Jan Staněk --- app/Services/Authenticator.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Services/Authenticator.php b/app/Services/Authenticator.php index cfcba4643..c0d174895 100644 --- a/app/Services/Authenticator.php +++ b/app/Services/Authenticator.php @@ -49,7 +49,7 @@ public function authenticate(string $user, string $password): SimpleIdentity { $skautISUser = $this->skautIsService->getUserDetail(); - $user = $this->userRepository->findBySkautISUserId($skautISUser->ID); + $user = $this->userRepository->findBySkautISUserId($skautISUser->ID) ?? $this->userRepository->findBySkautISPersonId($skautISUser->ID_Person); $firstLogin = false; if ($user === null) { From 8ad3f6325d0f9539d57541aad8546236e1e5dbf2 Mon Sep 17 00:00:00 2001 From: bojovyletoun <662941+bojovyletoun@users.noreply.github.com> Date: Tue, 13 Dec 2022 19:35:45 +0100 Subject: [PATCH 26/41] Filtrovani,razeni VS (#936) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Použití atributu column pro sloupce místo Renderer * Použití addColumnLink pro buňky s odkazem * Správná filtrace řetězených sloupců zatím jen VS * Řazení řetězených sloupců zatím jen VS Co-authored-by: bojovyletoun <> --- .../Components/GroupsGridControl.php | 29 ++++++++++++------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/app/AdminModule/Components/GroupsGridControl.php b/app/AdminModule/Components/GroupsGridControl.php index 252f8c4c1..aa34f68c2 100644 --- a/app/AdminModule/Components/GroupsGridControl.php +++ b/app/AdminModule/Components/GroupsGridControl.php @@ -10,13 +10,13 @@ use App\Services\ExcelExportService; use App\Utils\Helpers; use Doctrine\Common\Collections\ArrayCollection; +use Doctrine\ORM\QueryBuilder; use Exception; use Nette\Application\AbortException; use Nette\Application\UI\Control; use Nette\Http\Session; use Nette\Http\SessionSection; use Nette\Localization\Translator; -use Nette\Utils\Html; use Throwable; use Ublaboo\DataGrid\DataGrid; use Ublaboo\DataGrid\Exception\DataGridColumnStatusException; @@ -85,16 +85,23 @@ public function createComponentPatrolsGrid(string $name): DataGrid ->setSortable() ->setFilterText(); - $grid->addColumnText('variableSymbol', 'VS ')->setSortable() // je stejný jako název skupiny - ->setRenderer(static fn ($t) => $t->getVariableSymbolText()) - ->setFilterText(); - - $grid->addColumnText('leader', 'Vedoucí')->setSortable() - ->setRenderer(function (Troop $t) { - $leader = $t->getLeader(); - - return Html::el('a')->setAttribute('href', $this->getPresenter()->link('detail', $leader->getId()))->setText($leader->getDisplayName()); - }) + $grid->addColumnText('variableSymbol', 'VS ', 'variableSymbolText') + ->setSortable() + ->setSortableCallback(static function (QueryBuilder $qb, array $sort): void { + $sortRev = $sort['variableSymbolText'] === 'DESC' ? 'ASC' : 'DESC'; + $qb->join('p.variableSymbol', 'VS') + ->orderBy('VS.variableSymbol', $sortRev); + }) + ->setFilterText() + ->setCondition(static function (QueryBuilder $qb, string $value): void { +// $qb->join('p.applications', 'uAVS') + $qb->join('p.variableSymbol', 'VS') + ->andWhere('VS.variableSymbol LIKE :variableSymbol') + ->setParameter(':variableSymbol', '%' . $value . '%'); + }); + + $grid->addColumnLink('leader', 'Vedoucí', ':detail', 'leader.displayName', ['id' => 'leader.id'])->setSortable() +// return Html::el('a')->setAttribute('href', $this->getPresenter()->link('detail', $leader->getId()))->setText($leader->getDisplayName()); ->setFilterText(); $grid->addColumnDateTime('applicationDate', 'Datum založení') From ad0775c257cded99f180e889774f199810a11270 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Stan=C4=9Bk?= Date: Fri, 16 Dec 2022 03:01:07 +0100 Subject: [PATCH 27/41] =?UTF-8?q?Oprava=20p=C5=99ihl=C3=A1=C5=A1en=C3=AD?= =?UTF-8?q?=20pro=20p=C5=99idan=C3=A9=20=C3=BA=C4=8Dastn=C3=ADky=202=20(#9?= =?UTF-8?q?39)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * group user login fix * cs fix --- app/Services/Authenticator.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/Services/Authenticator.php b/app/Services/Authenticator.php index c0d174895..54bec4846 100644 --- a/app/Services/Authenticator.php +++ b/app/Services/Authenticator.php @@ -49,11 +49,12 @@ public function authenticate(string $user, string $password): SimpleIdentity { $skautISUser = $this->skautIsService->getUserDetail(); - $user = $this->userRepository->findBySkautISUserId($skautISUser->ID) ?? $this->userRepository->findBySkautISPersonId($skautISUser->ID_Person); + $user = $this->userRepository->findBySkautISUserId($skautISUser->ID); $firstLogin = false; if ($user === null) { - $user = new User(); + // nacten ze skautIS pres skupinu + $user = $this->userRepository->findBySkautISPersonId($skautISUser->ID_Person) ?? new User(); $roleNonregistered = $this->roleRepository->findBySystemName(Role::NONREGISTERED); $user->addRole($roleNonregistered); $firstLogin = true; From 831968d55dc7606f8b2df29c872a4b2cf79b4e45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Stan=C4=9Bk?= Date: Sun, 25 Dec 2022 23:37:03 +0100 Subject: [PATCH 28/41] Detail skupiny (#940) * group detail * group detail * users module * detail * cleanup * sort fix * code style fix * cleanup * cleanup * cleanup --- .../Components/GroupsGridControl.php | 235 ------------------ .../Components/IGroupsGridControlFactory.php | 16 -- .../Components/PatrolsGridControl.php | 180 -------------- .../templates/Dashboard/default.latte | 8 +- .../templates/includes/main_menu.latte | 7 +- .../ProgramAttendeesGridControl.php | 2 +- .../Components/ApplicationsGridControl.php | 2 +- .../IApplicationsGridControlFactory.php | 2 +- .../Components/IPatrolsGridControlFactory.php | 2 +- .../Components/ITroopsGridControlFactory.php | 16 ++ .../Components/IUsersGridControlFactory.php | 2 +- .../Components/PatrolsGridControl.php | 98 ++++++++ .../Components/TroopsGridControl.php | 175 +++++++++++++ .../Components/UsersGridControl.php | 2 +- .../templates/applications_grid.latte | 0 .../templates/applications_grid_detail.latte | 0 .../Components/templates/patrols_grid.latte | 0 .../Components/templates/troops_grid.latte} | 0 .../Components/templates/users_grid.latte | 0 .../Forms/AddLectorFormFactory.php | 3 +- .../EditUserPersonalDetailsFormFactory.php | 3 +- .../Forms/EditUserSeminarFormFactory.php | 3 +- .../Presenters/PatrolsPresenter.php | 26 ++ .../Presenters/TroopsPresenter.php | 36 +++ .../Presenters/UsersBasePresenter.php | 28 +++ .../Presenters/UsersPresenter.php | 47 +--- .../Presenters/templates/@layout.latte | 2 + .../templates/Patrols/default.latte | 4 + .../Presenters/templates/Troops/default.latte | 4 + .../Presenters/templates/Troops/detail.latte | 93 +++++++ .../Presenters/templates/Users/add.latte | 0 .../Presenters/templates/Users/default.latte | 0 .../Presenters/templates/Users/detail.latte | 0 .../Presenters/templates/Users/sidebar.latte | 0 .../Presenters/templates/sidebar.latte | 15 ++ app/Router/RouterFactory.php | 7 + app/lang/admin.cs_CZ.neon | 9 +- app/lang/common.cs_CZ.neon | 2 +- 38 files changed, 543 insertions(+), 486 deletions(-) delete mode 100644 app/AdminModule/Components/GroupsGridControl.php delete mode 100644 app/AdminModule/Components/IGroupsGridControlFactory.php delete mode 100644 app/AdminModule/Components/PatrolsGridControl.php rename app/AdminModule/{ => UsersModule}/Components/ApplicationsGridControl.php (99%) rename app/AdminModule/{ => UsersModule}/Components/IApplicationsGridControlFactory.php (82%) rename app/AdminModule/{ => UsersModule}/Components/IPatrolsGridControlFactory.php (81%) create mode 100644 app/AdminModule/UsersModule/Components/ITroopsGridControlFactory.php rename app/AdminModule/{ => UsersModule}/Components/IUsersGridControlFactory.php (81%) create mode 100644 app/AdminModule/UsersModule/Components/PatrolsGridControl.php create mode 100644 app/AdminModule/UsersModule/Components/TroopsGridControl.php rename app/AdminModule/{ => UsersModule}/Components/UsersGridControl.php (99%) rename app/AdminModule/{ => UsersModule}/Components/templates/applications_grid.latte (100%) rename app/AdminModule/{ => UsersModule}/Components/templates/applications_grid_detail.latte (100%) rename app/AdminModule/{ => UsersModule}/Components/templates/patrols_grid.latte (100%) rename app/AdminModule/{Components/templates/groups_grid.latte => UsersModule/Components/templates/troops_grid.latte} (100%) rename app/AdminModule/{ => UsersModule}/Components/templates/users_grid.latte (100%) rename app/AdminModule/{ => UsersModule}/Forms/AddLectorFormFactory.php (98%) rename app/AdminModule/{ => UsersModule}/Forms/EditUserPersonalDetailsFormFactory.php (98%) rename app/AdminModule/{ => UsersModule}/Forms/EditUserSeminarFormFactory.php (99%) create mode 100644 app/AdminModule/UsersModule/Presenters/PatrolsPresenter.php create mode 100644 app/AdminModule/UsersModule/Presenters/TroopsPresenter.php create mode 100644 app/AdminModule/UsersModule/Presenters/UsersBasePresenter.php rename app/AdminModule/{ => UsersModule}/Presenters/UsersPresenter.php (83%) create mode 100644 app/AdminModule/UsersModule/Presenters/templates/@layout.latte create mode 100644 app/AdminModule/UsersModule/Presenters/templates/Patrols/default.latte create mode 100644 app/AdminModule/UsersModule/Presenters/templates/Troops/default.latte create mode 100644 app/AdminModule/UsersModule/Presenters/templates/Troops/detail.latte rename app/AdminModule/{ => UsersModule}/Presenters/templates/Users/add.latte (100%) rename app/AdminModule/{ => UsersModule}/Presenters/templates/Users/default.latte (100%) rename app/AdminModule/{ => UsersModule}/Presenters/templates/Users/detail.latte (100%) rename app/AdminModule/{ => UsersModule}/Presenters/templates/Users/sidebar.latte (100%) create mode 100644 app/AdminModule/UsersModule/Presenters/templates/sidebar.latte diff --git a/app/AdminModule/Components/GroupsGridControl.php b/app/AdminModule/Components/GroupsGridControl.php deleted file mode 100644 index aa34f68c2..000000000 --- a/app/AdminModule/Components/GroupsGridControl.php +++ /dev/null @@ -1,235 +0,0 @@ -sessionSection = $session->getSection('srs'); - } - - /** - * Vykreslí komponentu. - */ - public function render(): void - { - $this->template->setFile(__DIR__ . '/templates/groups_grid.latte'); - $this->template->render(); - } - - /** - * Vytvoří komponentu. - * - * @throws Throwable - * @throws DataGridColumnStatusException - * @throws DataGridException - */ - public function createComponentPatrolsGrid(string $name): DataGrid - { - $grid = new DataGrid($this, $name); - $grid->setTranslator($this->translator); - $grid->setDataSource($this->repository->createQueryBuilder('p')); - $grid->setDefaultSort(['displayName' => 'ASC']); - $grid->setColumnsHideable(); - $grid->setItemsPerPageList([25, 50, 100, 250, 500]); - $grid->setStrictSessionFilterValues(false); - - $stamp = date(Helpers::DATE_FORMAT); - $grid->addExportCsv('admin.common.export_all', 'NSJ2023 Skupiny ' . $stamp . '.csv'); - $grid->addExportCsvFiltered('admin.common.export_filter', 'NSJ2023 Skupiny fi ' . $stamp . '.csv'); - - $grid->addGroupAction('Export seznamu skupin') - ->onSelect[] = [$this, 'groupExportUsers']; - - $grid->addColumnText('id', 'ID') - ->setSortable(); - - $grid->addColumnText('state', 'Stav')->setSortable() - ->setRenderer(fn ($t) => $this->translator->translate('common.application_state.' . $t->getState())) - ->setFilterText(); - - $grid->addColumnText('name', 'Název') - ->setSortable() - ->setFilterText(); - - $grid->addColumnText('variableSymbol', 'VS ', 'variableSymbolText') - ->setSortable() - ->setSortableCallback(static function (QueryBuilder $qb, array $sort): void { - $sortRev = $sort['variableSymbolText'] === 'DESC' ? 'ASC' : 'DESC'; - $qb->join('p.variableSymbol', 'VS') - ->orderBy('VS.variableSymbol', $sortRev); - }) - ->setFilterText() - ->setCondition(static function (QueryBuilder $qb, string $value): void { -// $qb->join('p.applications', 'uAVS') - $qb->join('p.variableSymbol', 'VS') - ->andWhere('VS.variableSymbol LIKE :variableSymbol') - ->setParameter(':variableSymbol', '%' . $value . '%'); - }); - - $grid->addColumnLink('leader', 'Vedoucí', ':detail', 'leader.displayName', ['id' => 'leader.id'])->setSortable() -// return Html::el('a')->setAttribute('href', $this->getPresenter()->link('detail', $leader->getId()))->setText($leader->getDisplayName()); - ->setFilterText(); - - $grid->addColumnDateTime('applicationDate', 'Datum založení') - ->setRenderer(static function (Troop $p) { - $date = $p->getApplicationDate(); - - return $date ? $date->format(Helpers::DATETIME_FORMAT) : ''; - }) - ->setSortable(); - - $grid->addColumnText('pairingCode', 'Kód Jamoddílu')->setFilterText(); - - $grid->addColumnText('fee', 'Cena getFee')->setSortable()->setFilterText(); - -// $grid->addColumnText('fee2', 'Cena countFee') -// ->setRenderer(static fn (Troop $t) => $t->countFee()); - - $grid->addColumnDateTime('paymentDate', 'Datum zaplacení') - ->setRenderer(static function (Troop $p) { - $date = $p->getPaymentDate(); - - return $date ? $date->format(Helpers::DATETIME_FORMAT) : ''; - }) - ->setSortable(); - - $grid->addColumnDateTime('maturityDate', 'Datum splatnosti') - ->setRenderer(static function (Troop $p) { - $date = $p->getmaturityDate(); - - return $date ? $date->format(Helpers::DATETIME_FORMAT) : ''; - }) - ->setSortable(); - -// $grid->addColumnText('troop', 'Oddíl - přidat link') -// ->setRenderer( function (Patrol $p) { $troop = $p->getTroop(); -// return Html::el("a")->setAttribute("href",$this->link("troopDetail",$troop->getId()))->setText($troop->getName()); - -//}); // link na oddíl - - $grid->addColumnText('numPersons', '# osob') -// ->setSortableCallback(static fn($qb,$vals) =>sort($vals)) - ->setRenderer(static fn (Troop $p) => $p->countUsersInRoles([Role::PATROL_LEADER, Role::LEADER, Role::ESCORT, Role::ATTENDEE])); - - $grid->addColumnText('numChilder', '# rádců') -// ->setSortableCallback(static fn($qb,$vals) =>sort($vals)) - ->setRenderer(static fn (Troop $p) => $p->countUsersInRoles([Role::PATROL_LEADER])); - - $grid->addColumnText('numAdults', '# dospělých') -// ->setSortableCallback(static fn($qb,$vals) =>sort($vals)) - ->setRenderer(static fn (Troop $p) => $p->countUsersInRoles([Role::LEADER, Role::ESCORT])); - - $grid->addColumnText('numPatrols', '# družin') -// ->setSortableCallback(static fn($qb,$vals) =>sort($vals)) - ->setRenderer(static fn (Troop $p) => count($p->getConfirmedPatrols())); - - $grid->addAction('generatePaymentProof', 'Stáhnout potvzrení o přijetí platby', 'generatePaymentProof'); - $grid->allowRowsAction('generatePaymentProof', static fn (Troop $troop) => $troop->getPaymentDate() !== null); - -// $grid->addAction('detail', 'admin.common.detail', 'Users:groupDetail') // destinace ,todo group_detail.latte -// ->setClass('btn btn-xs btn-primary'); - -// $grid->addAction('delete', '', 'delete!') -// ->setIcon('trash') -// ->setTitle('admin.common.delete') -// ->setClass('btn btn-xs btn-danger') -// ->addAttributes([ -// 'data-toggle' => 'confirmation', -// 'data-content' => $this->translator->translate('admin.users.users_delete_confirm'), -// ]); - - return $grid; - } - -// /** -// * Zpracuje odstranění externí skupiny. -// * -// * @throws AbortException -// */ -// public function handleDelete(int $id): void -// { -// $rec = $this->repository->findById($id); -// -// $this->repository->remove($rec); -// -// $p = $this->getPresenter(); -// $p->flashMessage('Skupina smazána.', 'success'); -// $p->redirect('this'); -// } - - /** - * Vygeneruje potvrzení o přijetí platby. - * - * @throws AbortException - */ - public function handleGeneratePaymentProof(int $id): void - { - $this->presenter->redirect(':Export:TroopIncomeProof:troop', ['id' => $id]); - } - - /** - * Hromadně vyexportuje seznam družin. - * - * @param int[] $ids - * - * @throws AbortException - */ - public function groupExportUsers(array $ids): void - { - $this->sessionSection->patrolIds = $ids; - $this->redirect('exportusers'); - } - - /** - * Zpracuje export seznamu družin. - * - * @throws AbortException - * @throws Exception - */ - public function handleExportUsers(): void - { - $ids = $this->session->getSection('srs')->patrolIds; - - $res = $this->repository->createQueryBuilder('p')->where('p.id IN (:ids)') // stejne se v teto class querybuilder pouziva - ->setParameter('ids', $ids)->getQuery()->getResult(); // otestovat , podivat se na vzor (export uzivatelu) - - $users = new ArrayCollection($res); - $response = $this->excelExportService->exportUsersList($users, 'seznam-uzivatelu.xlsx'); // nutna nova metoda - - $this->getPresenter()->sendResponse($response); - } -} diff --git a/app/AdminModule/Components/IGroupsGridControlFactory.php b/app/AdminModule/Components/IGroupsGridControlFactory.php deleted file mode 100644 index a05426e9c..000000000 --- a/app/AdminModule/Components/IGroupsGridControlFactory.php +++ /dev/null @@ -1,16 +0,0 @@ -sessionSection = $session->getSection('srs'); - } - - /** - * Vykreslí komponentu. - */ - public function render(): void - { - $this->template->setFile(__DIR__ . '/templates/patrols_grid.latte'); - $this->template->render(); - } - - /** - * Vytvoří komponentu. - * - * @throws Throwable - * @throws DataGridColumnStatusException - * @throws DataGridException - */ - public function createComponentPatrolsGrid(string $name): DataGrid - { - $grid = new DataGrid($this, $name); - $grid->setTranslator($this->translator); - $grid->setDataSource($this->repository->createQueryBuilder('p')->where('p.confirmed = true')); - $grid->setDefaultSort(['displayName' => 'ASC']); - $grid->setColumnsHideable(); - $grid->setItemsPerPageList([25, 50, 100, 250, 500]); - $grid->setStrictSessionFilterValues(false); - - $stamp = date(Helpers::DATE_FORMAT); - $grid->addExportCsv('admin.common.export_all', 'NSJ2023 Druziny ' . $stamp . '.csv'); - $grid->addExportCsvFiltered('admin.common.export_filter', 'NSJ2023 Druziny fi ' . $stamp . '.csv'); - - $grid->addGroupAction('Export seznamu družin') - ->onSelect[] = [$this, 'groupExportUsers']; - - $grid->addColumnText('id', 'ID') - ->setSortable(); - - $grid->addColumnText('name', 'Název') - ->setSortable() - ->setFilterText(); - -// $grid->addColumnText('leader', 'Vedoucí') -// ->setRenderer(static function (Patrol $p) { -// return $leader = $p->countUsersInRoles([Role::LEADER]); {{ todo -// -// }) -// ->setFilterText(); - - $grid->addColumnDateTime('created', 'Datum založení') - ->setRenderer(static function (Patrol $p) { - $date = $p->getTroop()->getApplicationDate(); - - return $date ? $date->format(Helpers::DATETIME_FORMAT) : ''; - }) - ->setSortable(); - - $grid->addColumnText('troop', 'Oddíl - přidat link') - ->setRenderer(function (Patrol $p) { - $troop = $p->getTroop(); - - return Html::el('a')->setAttribute('href', $this->link('troopDetail', $troop->getId()))->setText($troop->getName()); - }); // link na oddíl - - $grid->addColumnText('userRoles', 'Počet 1') - ->setRenderer(static fn (Patrol $p) => count($p->getUsersRoles())); // je to správné číslo? - -// $grid->addColumnText('notRegisteredMandatoryBlocksCount', 'admin.users.users_not_registered_mandatory_blocks') -// ->setRenderer(static function (User $user) { -// return Html::el('span') -// ->setAttribute('data-toggle', 'tooltip') -// ->setAttribute('title', $user->getNotRegisteredMandatoryBlocksText()) -// ->setText($user->getNotRegisteredMandatoryBlocksCount()); -// }) -// ->setSortable(); - - - $grid->addAction('detail', 'admin.common.detail', 'Users:detail') // destinace - ->setClass('btn btn-xs btn-primary'); - -// $grid->addAction('delete', '', 'delete!') -// ->setIcon('trash') -// ->setTitle('admin.common.delete') -// ->setClass('btn btn-xs btn-danger') -// ->addAttributes([ -// 'data-toggle' => 'confirmation', -// 'data-content' => $this->translator->translate('admin.users.users_delete_confirm'), -// ]); - - return $grid; - } - -// /** -// * Zpracuje odstranění externího uživatele. -// * -// * @throws AbortException -// */ -// public function handleDelete(int $id): void -// { -// $patrol = $this->repository->findById($id); -// -// $this->repository->remove($patrol); -// -// $p = $this->getPresenter(); -// $p->flashMessage('Družina smazána', 'success'); -// $p->redirect('this'); -// } - - /** - * Hromadně vyexportuje seznam družin. - * - * @param int[] $ids - * - * @throws AbortException - */ - public function groupExportUsers(array $ids): void - { - $this->sessionSection->patrolIds = $ids; - $this->redirect('exportusers'); - } - - /** - * Zpracuje export seznamu družin. - * - * @throws AbortException - * @throws Exception - */ - public function handleExportUsers(): void - { - $ids = $this->session->getSection('srs')->patrolIds; - - $res = $this->repository->createQueryBuilder('p')->where('p.id IN (:ids)') // stejne se v teto class querybuilder pouziva - ->setParameter('ids', $ids)->getQuery()->getResult(); // otestovat , podivat se na vzor (export uzivatelu) - - $users = new ArrayCollection($res); - $response = $this->excelExportService->exportUsersList($users, 'seznam-uzivatelu.xlsx'); // nutna nova metoda - - $this->getPresenter()->sendResponse($response); - } -} diff --git a/app/AdminModule/Presenters/templates/Dashboard/default.latte b/app/AdminModule/Presenters/templates/Dashboard/default.latte index 70d1d4822..eb825a122 100644 --- a/app/AdminModule/Presenters/templates/Dashboard/default.latte +++ b/app/AdminModule/Presenters/templates/Dashboard/default.latte @@ -67,18 +67,18 @@
{_admin.menu.users}
diff --git a/app/AdminModule/Presenters/templates/includes/main_menu.latte b/app/AdminModule/Presenters/templates/includes/main_menu.latte index 68f22ea40..08f700d77 100644 --- a/app/AdminModule/Presenters/templates/includes/main_menu.latte +++ b/app/AdminModule/Presenters/templates/includes/main_menu.latte @@ -23,8 +23,11 @@ {_admin.menu.program} -
  • - {_admin.menu.users} +
  • + {_admin.menu.users}
  • diff --git a/app/AdminModule/ProgramModule/Components/ProgramAttendeesGridControl.php b/app/AdminModule/ProgramModule/Components/ProgramAttendeesGridControl.php index 32912a2ee..9a4f338ff 100644 --- a/app/AdminModule/ProgramModule/Components/ProgramAttendeesGridControl.php +++ b/app/AdminModule/ProgramModule/Components/ProgramAttendeesGridControl.php @@ -149,7 +149,7 @@ public function createComponentProgramAttendeesGrid(string $name): void $grid->setDefaultFilter(['attends' => 'yes'], false); if ($user->isAllowed(SrsResource::USERS, Permission::MANAGE)) { - $grid->addAction('detail', 'admin.common.detail', ':Admin:Users:detail') + $grid->addAction('detail', 'admin.common.detail', ':Admin:Users:Users:detail') ->setClass('btn btn-xs btn-primary') ->addAttributes(['target' => '_blank']); } diff --git a/app/AdminModule/Components/ApplicationsGridControl.php b/app/AdminModule/UsersModule/Components/ApplicationsGridControl.php similarity index 99% rename from app/AdminModule/Components/ApplicationsGridControl.php rename to app/AdminModule/UsersModule/Components/ApplicationsGridControl.php index 30488b6bd..45bfbb790 100644 --- a/app/AdminModule/Components/ApplicationsGridControl.php +++ b/app/AdminModule/UsersModule/Components/ApplicationsGridControl.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace App\AdminModule\Components; +namespace App\AdminModule\UsersModule\Components; use App\Model\Application\Application; use App\Model\Application\Repositories\ApplicationRepository; diff --git a/app/AdminModule/Components/IApplicationsGridControlFactory.php b/app/AdminModule/UsersModule/Components/IApplicationsGridControlFactory.php similarity index 82% rename from app/AdminModule/Components/IApplicationsGridControlFactory.php rename to app/AdminModule/UsersModule/Components/IApplicationsGridControlFactory.php index 827874ad0..41deaf012 100644 --- a/app/AdminModule/Components/IApplicationsGridControlFactory.php +++ b/app/AdminModule/UsersModule/Components/IApplicationsGridControlFactory.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace App\AdminModule\Components; +namespace App\AdminModule\UsersModule\Components; /** * Factory komponenty pro správu přihlášek. diff --git a/app/AdminModule/Components/IPatrolsGridControlFactory.php b/app/AdminModule/UsersModule/Components/IPatrolsGridControlFactory.php similarity index 81% rename from app/AdminModule/Components/IPatrolsGridControlFactory.php rename to app/AdminModule/UsersModule/Components/IPatrolsGridControlFactory.php index de8d1fbf5..9c77a6562 100644 --- a/app/AdminModule/Components/IPatrolsGridControlFactory.php +++ b/app/AdminModule/UsersModule/Components/IPatrolsGridControlFactory.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace App\AdminModule\Components; +namespace App\AdminModule\UsersModule\Components; /** * Factory komponenty pro správu družin. diff --git a/app/AdminModule/UsersModule/Components/ITroopsGridControlFactory.php b/app/AdminModule/UsersModule/Components/ITroopsGridControlFactory.php new file mode 100644 index 000000000..53f7a5420 --- /dev/null +++ b/app/AdminModule/UsersModule/Components/ITroopsGridControlFactory.php @@ -0,0 +1,16 @@ +template->setFile(__DIR__ . '/templates/patrols_grid.latte'); + $this->template->render(); + } + + /** + * Vytvoří komponentu. + * + * @throws Throwable + * @throws DataGridColumnStatusException + * @throws DataGridException + */ + public function createComponentPatrolsGrid(string $name): DataGrid + { + $grid = new DataGrid($this, $name); + $grid->setTranslator($this->translator); + $grid->setDataSource($this->repository->createQueryBuilder('p')->where('p.confirmed = true')); + $grid->setDefaultSort(['displayName' => 'ASC']); + $grid->setColumnsHideable(); + $grid->setItemsPerPageList([25, 50, 100, 250, 500]); + $grid->setStrictSessionFilterValues(false); + + $stamp = date(Helpers::DATE_FORMAT); + $grid->addExportCsv('admin.common.export_all', 'NSJ2023 Druziny ' . $stamp . '.csv'); + $grid->addExportCsvFiltered('admin.common.export_filter', 'NSJ2023 Druziny fi ' . $stamp . '.csv'); + + $grid->addColumnText('name', 'Název') + ->setSortable() + ->setFilterText(); + + $grid->addColumnText('troop', 'Skupina') + ->setRenderer(function (Patrol $p) { + $troop = $p->getTroop(); + + return Html::el('a')->setAttribute('href', $this->getPresenter()->link('Troops:detail', $troop->getId()))->setText($troop->getName()); + }); + + $grid->addColumnDateTime('created', 'Datum založení') + ->setRenderer(static function (Patrol $p) { + $date = $p->getTroop()->getApplicationDate(); + + return $date ? $date->format(Helpers::DATETIME_FORMAT) : ''; + }) + ->setSortable(); + + $grid->addColumnText('userRoles', 'Počet osob') + ->setRenderer(static fn (Patrol $p) => count($p->getUsersRoles())); // je to správné číslo? + +// $grid->addAction('detail', 'admin.common.detail', 'Patrols:detail') // destinace +// ->setClass('btn btn-xs btn-primary'); + +// $grid->addAction('delete', '', 'delete!') +// ->setIcon('trash') +// ->setTitle('admin.common.delete') +// ->setClass('btn btn-xs btn-danger') +// ->addAttributes([ +// 'data-toggle' => 'confirmation', +// 'data-content' => $this->translator->translate('admin.users.users_delete_confirm'), +// ]); + + return $grid; + } +} diff --git a/app/AdminModule/UsersModule/Components/TroopsGridControl.php b/app/AdminModule/UsersModule/Components/TroopsGridControl.php new file mode 100644 index 000000000..c64587342 --- /dev/null +++ b/app/AdminModule/UsersModule/Components/TroopsGridControl.php @@ -0,0 +1,175 @@ +template->setFile(__DIR__ . '/templates/troops_grid.latte'); + $this->template->render(); + } + + /** + * Vytvoří komponentu. + * + * @throws Throwable + * @throws DataGridColumnStatusException + * @throws DataGridException + */ + public function createComponentPatrolsGrid(string $name): DataGrid + { + $grid = new DataGrid($this, $name); + $grid->setTranslator($this->translator); + $grid->setDataSource($this->repository->createQueryBuilder('p')); + $grid->setDefaultSort(['displayName' => 'ASC']); + $grid->setColumnsHideable(); + $grid->setItemsPerPageList([25, 50, 100, 250, 500]); + $grid->setStrictSessionFilterValues(false); + + $stamp = date(Helpers::DATE_FORMAT); + $grid->addExportCsv('admin.common.export_all', 'NSJ2023 Skupiny ' . $stamp . '.csv'); + $grid->addExportCsvFiltered('admin.common.export_filter', 'NSJ2023 Skupiny fi ' . $stamp . '.csv'); + + $grid->addColumnText('name', 'Název') + ->setSortable() + ->setFilterText(); + + $grid->addColumnText('state', 'Stav') + ->setSortable() + ->setRenderer(fn ($t) => $this->translator->translate('common.application_state.' . $t->getState())) + ->setFilterText(); + + $grid->addColumnText('variableSymbol', 'Variabilní symbol', 'variableSymbolText') + ->setSortable() + ->setSortableCallback(static function (QueryBuilder $qb, array $sort): void { + $sortRev = $sort['variableSymbolText'] === 'DESC' ? 'DESC' : 'ASC'; + $qb->join('p.variableSymbol', 'pVS') + ->orderBy('pVS.variableSymbol', $sortRev); + }) + ->setFilterText() + ->setCondition(static function (QueryBuilder $qb, string $value): void { + $qb->join('p.variableSymbol', 'pVS') + ->andWhere('pVS.variableSymbol LIKE :variableSymbol') + ->setParameter(':variableSymbol', '%' . $value . '%'); + }); + + $grid->addColumnText('leader', 'Vedoucí') + ->setRenderer(function (Troop $t) { + $leader = $t->getLeader(); + + return Html::el('a')->setAttribute('href', $this->getPresenter()->link('Users:detail', $leader->getId()))->setText($leader->getDisplayName()); + }); + + $grid->addColumnDateTime('applicationDate', 'Datum založení') + ->setRenderer(static function (Troop $p) { + $date = $p->getApplicationDate(); + + return $date ? $date->format(Helpers::DATETIME_FORMAT) : ''; + }) + ->setSortable(); + + $grid->addColumnNumber('fee', 'Cena')->setSortable()->setFilterText(); + + $grid->addColumnDateTime('maturityDate', 'Datum splatnosti') + ->setFormat(Helpers::DATE_FORMAT) + ->setSortable(); + + $grid->addColumnDateTime('paymentDate', 'Datum platby') + ->setFormat(Helpers::DATE_FORMAT) + ->setSortable(); + + $grid->addColumnText('pairingCode', 'Kód jamoddílu') + ->setFilterText(); + + $grid->addColumnNumber('numPersons', '# osob') +// ->setSortableCallback(static fn($qb,$vals) =>sort($vals)) + ->setRenderer(static fn (Troop $p) => $p->countUsersInRoles([Role::PATROL_LEADER, Role::LEADER, Role::ESCORT, Role::ATTENDEE])); + + $grid->addColumnNumber('numChilder', '# rádců') +// ->setSortableCallback(static fn($qb,$vals) =>sort($vals)) + ->setRenderer(static fn (Troop $p) => $p->countUsersInRoles([Role::PATROL_LEADER])); + + $grid->addColumnNumber('numAdults', '# dospělých') +// ->setSortableCallback(static fn($qb,$vals) =>sort($vals)) + ->setRenderer(static fn (Troop $p) => $p->countUsersInRoles([Role::LEADER, Role::ESCORT])); + + $grid->addColumnNumber('numPatrols', '# družin') +// ->setSortableCallback(static fn($qb,$vals) =>sort($vals)) + ->setRenderer(static fn (Troop $p) => count($p->getConfirmedPatrols())); + + $grid->addAction('generatePaymentProof', 'Stáhnout potvzrení o přijetí platby', 'generatePaymentProof'); + $grid->allowRowsAction('generatePaymentProof', static fn (Troop $troop) => $troop->getPaymentDate() !== null); + + $grid->addAction('detail', 'admin.common.detail', 'Troops:detail') + ->setClass('btn btn-xs btn-primary'); + +// $grid->addAction('delete', '', 'delete!') +// ->setIcon('trash') +// ->setTitle('admin.common.delete') +// ->setClass('btn btn-xs btn-danger') +// ->addAttributes([ +// 'data-toggle' => 'confirmation', +// 'data-content' => $this->translator->translate('admin.users.users_delete_confirm'), +// ]); + + return $grid; + } + +// /** +// * Zpracuje odstranění externí skupiny. +// * +// * @throws AbortException +// */ +// public function handleDelete(int $id): void +// { +// $rec = $this->repository->findById($id); +// +// $this->repository->remove($rec); +// +// $p = $this->getPresenter(); +// $p->flashMessage('Skupina smazána.', 'success'); +// $p->redirect('this'); +// } + + /** + * Vygeneruje potvrzení o přijetí platby. + * + * @throws AbortException + */ + public function handleGeneratePaymentProof(int $id): void + { + $this->presenter->redirect(':Export:TroopIncomeProof:troop', ['id' => $id]); + } +} diff --git a/app/AdminModule/Components/UsersGridControl.php b/app/AdminModule/UsersModule/Components/UsersGridControl.php similarity index 99% rename from app/AdminModule/Components/UsersGridControl.php rename to app/AdminModule/UsersModule/Components/UsersGridControl.php index 1d8181ac8..d7b3c4b1d 100644 --- a/app/AdminModule/Components/UsersGridControl.php +++ b/app/AdminModule/UsersModule/Components/UsersGridControl.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace App\AdminModule\Components; +namespace App\AdminModule\UsersModule\Components; use App\Model\Acl\Repositories\RoleRepository; use App\Model\Acl\Role; diff --git a/app/AdminModule/Components/templates/applications_grid.latte b/app/AdminModule/UsersModule/Components/templates/applications_grid.latte similarity index 100% rename from app/AdminModule/Components/templates/applications_grid.latte rename to app/AdminModule/UsersModule/Components/templates/applications_grid.latte diff --git a/app/AdminModule/Components/templates/applications_grid_detail.latte b/app/AdminModule/UsersModule/Components/templates/applications_grid_detail.latte similarity index 100% rename from app/AdminModule/Components/templates/applications_grid_detail.latte rename to app/AdminModule/UsersModule/Components/templates/applications_grid_detail.latte diff --git a/app/AdminModule/Components/templates/patrols_grid.latte b/app/AdminModule/UsersModule/Components/templates/patrols_grid.latte similarity index 100% rename from app/AdminModule/Components/templates/patrols_grid.latte rename to app/AdminModule/UsersModule/Components/templates/patrols_grid.latte diff --git a/app/AdminModule/Components/templates/groups_grid.latte b/app/AdminModule/UsersModule/Components/templates/troops_grid.latte similarity index 100% rename from app/AdminModule/Components/templates/groups_grid.latte rename to app/AdminModule/UsersModule/Components/templates/troops_grid.latte diff --git a/app/AdminModule/Components/templates/users_grid.latte b/app/AdminModule/UsersModule/Components/templates/users_grid.latte similarity index 100% rename from app/AdminModule/Components/templates/users_grid.latte rename to app/AdminModule/UsersModule/Components/templates/users_grid.latte diff --git a/app/AdminModule/Forms/AddLectorFormFactory.php b/app/AdminModule/UsersModule/Forms/AddLectorFormFactory.php similarity index 98% rename from app/AdminModule/Forms/AddLectorFormFactory.php rename to app/AdminModule/UsersModule/Forms/AddLectorFormFactory.php index 9bbcbd9c1..73bfd9d90 100644 --- a/app/AdminModule/Forms/AddLectorFormFactory.php +++ b/app/AdminModule/UsersModule/Forms/AddLectorFormFactory.php @@ -2,8 +2,9 @@ declare(strict_types=1); -namespace App\AdminModule\Forms; +namespace App\AdminModule\UsersModule\Forms; +use App\AdminModule\Forms\BaseFormFactory; use App\Model\Acl\Repositories\RoleRepository; use App\Model\Acl\Role; use App\Model\User\Repositories\UserRepository; diff --git a/app/AdminModule/Forms/EditUserPersonalDetailsFormFactory.php b/app/AdminModule/UsersModule/Forms/EditUserPersonalDetailsFormFactory.php similarity index 98% rename from app/AdminModule/Forms/EditUserPersonalDetailsFormFactory.php rename to app/AdminModule/UsersModule/Forms/EditUserPersonalDetailsFormFactory.php index 27de00856..594cca030 100644 --- a/app/AdminModule/Forms/EditUserPersonalDetailsFormFactory.php +++ b/app/AdminModule/UsersModule/Forms/EditUserPersonalDetailsFormFactory.php @@ -2,8 +2,9 @@ declare(strict_types=1); -namespace App\AdminModule\Forms; +namespace App\AdminModule\UsersModule\Forms; +use App\AdminModule\Forms\BaseFormFactory; use App\Model\User\Repositories\UserRepository; use App\Model\User\User; use App\Services\FilesService; diff --git a/app/AdminModule/Forms/EditUserSeminarFormFactory.php b/app/AdminModule/UsersModule/Forms/EditUserSeminarFormFactory.php similarity index 99% rename from app/AdminModule/Forms/EditUserSeminarFormFactory.php rename to app/AdminModule/UsersModule/Forms/EditUserSeminarFormFactory.php index ce0683ba2..4ca83eb67 100644 --- a/app/AdminModule/Forms/EditUserSeminarFormFactory.php +++ b/app/AdminModule/UsersModule/Forms/EditUserSeminarFormFactory.php @@ -2,8 +2,9 @@ declare(strict_types=1); -namespace App\AdminModule\Forms; +namespace App\AdminModule\UsersModule\Forms; +use App\AdminModule\Forms\BaseFormFactory; use App\Model\Acl\Repositories\RoleRepository; use App\Model\Acl\Role; use App\Model\CustomInput\CustomCheckbox; diff --git a/app/AdminModule/UsersModule/Presenters/PatrolsPresenter.php b/app/AdminModule/UsersModule/Presenters/PatrolsPresenter.php new file mode 100644 index 000000000..33a14ef43 --- /dev/null +++ b/app/AdminModule/UsersModule/Presenters/PatrolsPresenter.php @@ -0,0 +1,26 @@ +patrolsGridControlFactory->create(); + } +} diff --git a/app/AdminModule/UsersModule/Presenters/TroopsPresenter.php b/app/AdminModule/UsersModule/Presenters/TroopsPresenter.php new file mode 100644 index 000000000..7cc7912c8 --- /dev/null +++ b/app/AdminModule/UsersModule/Presenters/TroopsPresenter.php @@ -0,0 +1,36 @@ +troopsGridControlFactory->create(); + } + + public function renderDetail(int $id): void + { + $troop = $this->troopRepository->findById($id); + $this->template->troop = $troop; + } +} diff --git a/app/AdminModule/UsersModule/Presenters/UsersBasePresenter.php b/app/AdminModule/UsersModule/Presenters/UsersBasePresenter.php new file mode 100644 index 000000000..033cbb365 --- /dev/null +++ b/app/AdminModule/UsersModule/Presenters/UsersBasePresenter.php @@ -0,0 +1,28 @@ +checkPermission(Permission::MANAGE); + } +} diff --git a/app/AdminModule/Presenters/UsersPresenter.php b/app/AdminModule/UsersModule/Presenters/UsersPresenter.php similarity index 83% rename from app/AdminModule/Presenters/UsersPresenter.php rename to app/AdminModule/UsersModule/Presenters/UsersPresenter.php index aaaaae822..eba13dff6 100644 --- a/app/AdminModule/Presenters/UsersPresenter.php +++ b/app/AdminModule/UsersModule/Presenters/UsersPresenter.php @@ -2,20 +2,15 @@ declare(strict_types=1); -namespace App\AdminModule\Presenters; - -use App\AdminModule\Components\ApplicationsGridControl; -use App\AdminModule\Components\GroupsGridControl; -use App\AdminModule\Components\IApplicationsGridControlFactory; -use App\AdminModule\Components\IGroupsGridControlFactory; -use App\AdminModule\Components\IPatrolsGridControlFactory; -use App\AdminModule\Components\IUsersGridControlFactory; -use App\AdminModule\Components\PatrolsGridControl; -use App\AdminModule\Components\UsersGridControl; -use App\AdminModule\Forms\AddLectorFormFactory; -use App\AdminModule\Forms\EditUserPersonalDetailsFormFactory; -use App\AdminModule\Forms\EditUserSeminarFormFactory; -use App\Model\Acl\Permission; +namespace App\AdminModule\UsersModule\Presenters; + +use App\AdminModule\UsersModule\Components\ApplicationsGridControl; +use App\AdminModule\UsersModule\Components\IApplicationsGridControlFactory; +use App\AdminModule\UsersModule\Components\IUsersGridControlFactory; +use App\AdminModule\UsersModule\Components\UsersGridControl; +use App\AdminModule\UsersModule\Forms\AddLectorFormFactory; +use App\AdminModule\UsersModule\Forms\EditUserPersonalDetailsFormFactory; +use App\AdminModule\UsersModule\Forms\EditUserSeminarFormFactory; use App\Model\Acl\Role; use App\Model\Acl\SrsResource; use App\Model\CustomInput\CustomInput; @@ -24,7 +19,6 @@ use App\Model\Enums\PaymentType; use App\Model\User\Queries\UserAttendsProgramsQuery; use App\Services\ApplicationService; -use App\Services\ExcelExportService; use Nette\Application\AbortException; use Nette\Application\UI\Form; use Nette\DI\Attributes\Inject; @@ -34,19 +28,13 @@ /** * Presenter obsluhující správu uživatelů. */ -class UsersPresenter extends AdminBasePresenter +class UsersPresenter extends UsersBasePresenter { protected string $resource = SrsResource::USERS; #[Inject] public IUsersGridControlFactory $usersGridControlFactory; - #[Inject] - public IPatrolsGridControlFactory $patrolsGridControlFactory; - - #[Inject] - public IGroupsGridControlFactory $GroupsGridControlFactory; - #[Inject] public AddLectorFormFactory $addLectorFormFactory; @@ -59,9 +47,6 @@ class UsersPresenter extends AdminBasePresenter #[Inject] public IApplicationsGridControlFactory $applicationsGridControlFactory; - #[Inject] - public ExcelExportService $excelExportService; - #[Inject] public CustomInputRepository $customInputRepository; @@ -75,8 +60,6 @@ public function startup(): void { parent::startup(); - $this->checkPermission(Permission::MANAGE); - $this->template->results = []; $this->template->editPersonalDetails = false; $this->template->editSeminar = false; @@ -189,16 +172,6 @@ protected function createComponentUsersGrid(): UsersGridControl return $this->usersGridControlFactory->create(); } - protected function createComponentPatrolsGrid(): PatrolsGridControl - { - return $this->patrolsGridControlFactory->create(); - } - - protected function createComponentGroupsGrid(): GroupsGridControl - { - return $this->GroupsGridControlFactory->create(); - } - protected function createComponentAddLectorForm(): Form { $form = $this->addLectorFormFactory->create(); diff --git a/app/AdminModule/UsersModule/Presenters/templates/@layout.latte b/app/AdminModule/UsersModule/Presenters/templates/@layout.latte new file mode 100644 index 000000000..1cc7452c7 --- /dev/null +++ b/app/AdminModule/UsersModule/Presenters/templates/@layout.latte @@ -0,0 +1,2 @@ +{layout '../../../Presenters/templates/@layout.latte'} +{import 'sidebar.latte'} \ No newline at end of file diff --git a/app/AdminModule/UsersModule/Presenters/templates/Patrols/default.latte b/app/AdminModule/UsersModule/Presenters/templates/Patrols/default.latte new file mode 100644 index 000000000..125f2de60 --- /dev/null +++ b/app/AdminModule/UsersModule/Presenters/templates/Patrols/default.latte @@ -0,0 +1,4 @@ +{block main} +

    {_admin.users.patrols.heading}

    + {control patrolsGrid} +{/block} \ No newline at end of file diff --git a/app/AdminModule/UsersModule/Presenters/templates/Troops/default.latte b/app/AdminModule/UsersModule/Presenters/templates/Troops/default.latte new file mode 100644 index 000000000..b355f1152 --- /dev/null +++ b/app/AdminModule/UsersModule/Presenters/templates/Troops/default.latte @@ -0,0 +1,4 @@ +{block main} +

    {_admin.users.troops.heading}

    + {control troopsGrid} +{/block} \ No newline at end of file diff --git a/app/AdminModule/UsersModule/Presenters/templates/Troops/detail.latte b/app/AdminModule/UsersModule/Presenters/templates/Troops/detail.latte new file mode 100644 index 000000000..464e8f240 --- /dev/null +++ b/app/AdminModule/UsersModule/Presenters/templates/Troops/detail.latte @@ -0,0 +1,93 @@ +{block main} +

    Detail skupiny: {$troop->getName()}

    + +

    Základní údaje

    +
    +
    +
    Stav
    +
    {_'common.application_state.' . $troop->getState()}
    +
    +
    +
    Variabilní symbol
    +
    {$troop->getVariableSymbolText()}
    +
    + +
    +
    Datum založení
    +
    {$troop->getApplicationDate()|date:'j. n. Y H:i'}
    +
    +
    +
    Cena
    +
    {$troop->getFee()}
    +
    +
    +
    Datum splatnosti
    +
    {$troop->getMaturityDate()|date:'j. n. Y'}
    +
    +
    +
    Datum platby
    +
    {$troop->getPaymentDate()|date:'j. n. Y'}
    +
    +
    +
    Kód jamooodílu
    +
    {$troop->getPairingCode()}
    +
    +
    + +

    Seznam členů

    + + + + + + + + + + + + + + + + + +
    JménoRoleDatum narozeníZdravotní údaje
    + + {$userRole->getUser()->getDisplayName()} + + {$userRole->getRole()->getName()}{$userRole->getUser()->getBirthdate()|date:"j. n. Y"}{$userRole->getUser()->getHealthInfo()}
    + +
    +

    Družina: {$patrol->getName()}

    + + + + + + + + + + + + + + + + + +
    JménoRoleDatum narozeníZdravotní údaje
    + + {$userRole->getUser()->getDisplayName()} + + {$userRole->getRole()->getName()}{$userRole->getUser()->getBirthdate()|date:"j. n. Y"}{$userRole->getUser()->getHealthInfo()}
    +
    +{/block} diff --git a/app/AdminModule/Presenters/templates/Users/add.latte b/app/AdminModule/UsersModule/Presenters/templates/Users/add.latte similarity index 100% rename from app/AdminModule/Presenters/templates/Users/add.latte rename to app/AdminModule/UsersModule/Presenters/templates/Users/add.latte diff --git a/app/AdminModule/Presenters/templates/Users/default.latte b/app/AdminModule/UsersModule/Presenters/templates/Users/default.latte similarity index 100% rename from app/AdminModule/Presenters/templates/Users/default.latte rename to app/AdminModule/UsersModule/Presenters/templates/Users/default.latte diff --git a/app/AdminModule/Presenters/templates/Users/detail.latte b/app/AdminModule/UsersModule/Presenters/templates/Users/detail.latte similarity index 100% rename from app/AdminModule/Presenters/templates/Users/detail.latte rename to app/AdminModule/UsersModule/Presenters/templates/Users/detail.latte diff --git a/app/AdminModule/Presenters/templates/Users/sidebar.latte b/app/AdminModule/UsersModule/Presenters/templates/Users/sidebar.latte similarity index 100% rename from app/AdminModule/Presenters/templates/Users/sidebar.latte rename to app/AdminModule/UsersModule/Presenters/templates/Users/sidebar.latte diff --git a/app/AdminModule/UsersModule/Presenters/templates/sidebar.latte b/app/AdminModule/UsersModule/Presenters/templates/sidebar.latte new file mode 100644 index 000000000..be6cca607 --- /dev/null +++ b/app/AdminModule/UsersModule/Presenters/templates/sidebar.latte @@ -0,0 +1,15 @@ +{block sidebar} + +{/block} \ No newline at end of file diff --git a/app/Router/RouterFactory.php b/app/Router/RouterFactory.php index 3997ce19a..7e02de8e0 100644 --- a/app/Router/RouterFactory.php +++ b/app/Router/RouterFactory.php @@ -58,6 +58,13 @@ public function createRouter(): RouteList 'id' => null, ]); + $router->addRoute('admin/users//[/]', [ + 'module' => 'Admin:Users', + 'presenter' => 'Users', + 'action' => 'default', + 'id' => null, + ]); + $router->addRoute('admin/payments//[/]', [ 'module' => 'Admin:Payments', 'presenter' => 'Payments', diff --git a/app/lang/admin.cs_CZ.neon b/app/lang/admin.cs_CZ.neon index 9a1a3a944..3e3a601c1 100644 --- a/app/lang/admin.cs_CZ.neon +++ b/app/lang/admin.cs_CZ.neon @@ -387,8 +387,7 @@ program: users: menu: persons: "Osoby" - groups: "Skupiny" - troops: "Oddíly" + troops: "Skupiny" patrols: "Družiny" users_heading: "Uživatelé" @@ -515,6 +514,12 @@ users: users_detail_schedule: "Harmonogram" users_detail_birthdate_age: "%birthdate% (%age% let)" + troops: + heading: Skupiny + + patrols: + heading: Družiny + payments: payments: heading: "Platby" diff --git a/app/lang/common.cs_CZ.neon b/app/lang/common.cs_CZ.neon index 3b4a77a88..824208f48 100644 --- a/app/lang/common.cs_CZ.neon +++ b/app/lang/common.cs_CZ.neon @@ -119,7 +119,7 @@ application_state: canceled: "Zrušeno" paid: "Zaplaceno" paid_free: "Zaplaceno (zdarma)" - draft: "nepotvrzené (draft)" + draft: "Nepotvrzeno" calendar_view: timeGridSeminar: "Na výšku" From fa80ca9b2cdc0c7ded96ded1b88be1fcb06f0e28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Stan=C4=9Bk?= Date: Mon, 26 Dec 2022 09:43:38 +0100 Subject: [PATCH 29/41] =?UTF-8?q?Maz=C3=A1n=C3=AD=20skupin=20a=20dru=C5=BE?= =?UTF-8?q?in=20(#941)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * remove groups * format --- .../Components/PatrolsGridControl.php | 40 +++++++++++---- .../Components/TroopsGridControl.php | 51 ++++++++++--------- .../Commands/Handlers/RemovePatrolHandler.php | 38 ++++++++++++++ .../Commands/Handlers/RemoveTroopHandler.php | 45 ++++++++++++++++ app/Model/User/Commands/RemovePatrol.php | 19 +++++++ app/Model/User/Commands/RemoveTroop.php | 19 +++++++ .../User/Repositories/PatrolRepository.php | 6 +++ .../User/Repositories/TroopRepository.php | 6 +++ 8 files changed, 188 insertions(+), 36 deletions(-) create mode 100644 app/Model/User/Commands/Handlers/RemovePatrolHandler.php create mode 100644 app/Model/User/Commands/Handlers/RemoveTroopHandler.php create mode 100644 app/Model/User/Commands/RemovePatrol.php create mode 100644 app/Model/User/Commands/RemoveTroop.php diff --git a/app/AdminModule/UsersModule/Components/PatrolsGridControl.php b/app/AdminModule/UsersModule/Components/PatrolsGridControl.php index d1d2449c3..9ae6e4a8b 100644 --- a/app/AdminModule/UsersModule/Components/PatrolsGridControl.php +++ b/app/AdminModule/UsersModule/Components/PatrolsGridControl.php @@ -4,9 +4,12 @@ namespace App\AdminModule\UsersModule\Components; +use App\Model\User\Commands\RemovePatrol; use App\Model\User\Patrol; use App\Model\User\Repositories\PatrolRepository; +use App\Services\CommandBus; use App\Utils\Helpers; +use Nette\Application\AbortException; use Nette\Application\UI\Control; use Nette\Localization\Translator; use Nette\Utils\Html; @@ -24,8 +27,9 @@ class PatrolsGridControl extends Control { public function __construct( + private CommandBus $commandBus, private Translator $translator, - private PatrolRepository $repository + private PatrolRepository $patrolRepository ) { } @@ -49,7 +53,7 @@ public function createComponentPatrolsGrid(string $name): DataGrid { $grid = new DataGrid($this, $name); $grid->setTranslator($this->translator); - $grid->setDataSource($this->repository->createQueryBuilder('p')->where('p.confirmed = true')); + $grid->setDataSource($this->patrolRepository->createQueryBuilder('p')->where('p.confirmed = true')); $grid->setDefaultSort(['displayName' => 'ASC']); $grid->setColumnsHideable(); $grid->setItemsPerPageList([25, 50, 100, 250, 500]); @@ -78,21 +82,35 @@ public function createComponentPatrolsGrid(string $name): DataGrid }) ->setSortable(); - $grid->addColumnText('userRoles', 'Počet osob') + $grid->addColumnNumber('userRoles', 'Počet osob') ->setRenderer(static fn (Patrol $p) => count($p->getUsersRoles())); // je to správné číslo? // $grid->addAction('detail', 'admin.common.detail', 'Patrols:detail') // destinace // ->setClass('btn btn-xs btn-primary'); -// $grid->addAction('delete', '', 'delete!') -// ->setIcon('trash') -// ->setTitle('admin.common.delete') -// ->setClass('btn btn-xs btn-danger') -// ->addAttributes([ -// 'data-toggle' => 'confirmation', -// 'data-content' => $this->translator->translate('admin.users.users_delete_confirm'), -// ]); + $grid->addAction('delete', '', 'delete!') + ->setIcon('trash') + ->setTitle('admin.common.delete') + ->setClass('btn btn-xs btn-danger') + ->addAttributes([ + 'data-toggle' => 'confirmation', + 'data-content' => $this->translator->translate('Opravdu chcete družinu odstranit?'), + ]); return $grid; } + + /** + * Zpracuje odstranění družiny. + * + * @throws AbortException + */ + public function handleDelete(int $id): void + { + $patrol = $this->patrolRepository->findById($id); + $this->commandBus->handle(new RemovePatrol($patrol)); + $p = $this->getPresenter(); + $p->flashMessage('Družina byla úspěšně odstraněna.', 'success'); + $p->redirect('this'); + } } diff --git a/app/AdminModule/UsersModule/Components/TroopsGridControl.php b/app/AdminModule/UsersModule/Components/TroopsGridControl.php index c64587342..f02f24d6e 100644 --- a/app/AdminModule/UsersModule/Components/TroopsGridControl.php +++ b/app/AdminModule/UsersModule/Components/TroopsGridControl.php @@ -5,8 +5,10 @@ namespace App\AdminModule\UsersModule\Components; use App\Model\Acl\Role; +use App\Model\User\Commands\RemoveTroop; use App\Model\User\Repositories\TroopRepository; use App\Model\User\Troop; +use App\Services\CommandBus; use App\Utils\Helpers; use Doctrine\ORM\QueryBuilder; use Nette\Application\AbortException; @@ -27,8 +29,9 @@ class TroopsGridControl extends Control { public function __construct( + private CommandBus $commandBus, private Translator $translator, - private TroopRepository $repository + private TroopRepository $troopRepository ) { } @@ -52,7 +55,7 @@ public function createComponentPatrolsGrid(string $name): DataGrid { $grid = new DataGrid($this, $name); $grid->setTranslator($this->translator); - $grid->setDataSource($this->repository->createQueryBuilder('p')); + $grid->setDataSource($this->troopRepository->createQueryBuilder('p')); $grid->setDefaultSort(['displayName' => 'ASC']); $grid->setColumnsHideable(); $grid->setItemsPerPageList([25, 50, 100, 250, 500]); @@ -135,33 +138,31 @@ public function createComponentPatrolsGrid(string $name): DataGrid $grid->addAction('detail', 'admin.common.detail', 'Troops:detail') ->setClass('btn btn-xs btn-primary'); -// $grid->addAction('delete', '', 'delete!') -// ->setIcon('trash') -// ->setTitle('admin.common.delete') -// ->setClass('btn btn-xs btn-danger') -// ->addAttributes([ -// 'data-toggle' => 'confirmation', -// 'data-content' => $this->translator->translate('admin.users.users_delete_confirm'), -// ]); + $grid->addAction('delete', '', 'delete!') + ->setIcon('trash') + ->setTitle('admin.common.delete') + ->setClass('btn btn-xs btn-danger') + ->addAttributes([ + 'data-toggle' => 'confirmation', + 'data-content' => $this->translator->translate('Opravdu chcete skupinu odstranit?'), + ]); return $grid; } -// /** -// * Zpracuje odstranění externí skupiny. -// * -// * @throws AbortException -// */ -// public function handleDelete(int $id): void -// { -// $rec = $this->repository->findById($id); -// -// $this->repository->remove($rec); -// -// $p = $this->getPresenter(); -// $p->flashMessage('Skupina smazána.', 'success'); -// $p->redirect('this'); -// } + /** + * Zpracuje odstranění skupiny. + * + * @throws AbortException + */ + public function handleDelete(int $id): void + { + $troop = $this->troopRepository->findById($id); + $this->commandBus->handle(new RemoveTroop($troop)); + $p = $this->getPresenter(); + $p->flashMessage('Skupina byla úspěšně odstraněna.', 'success'); + $p->redirect('this'); + } /** * Vygeneruje potvrzení o přijetí platby. diff --git a/app/Model/User/Commands/Handlers/RemovePatrolHandler.php b/app/Model/User/Commands/Handlers/RemovePatrolHandler.php new file mode 100644 index 000000000..05767f7de --- /dev/null +++ b/app/Model/User/Commands/Handlers/RemovePatrolHandler.php @@ -0,0 +1,38 @@ +em->wrapInTransaction(function () use ($command): void { + foreach ($command->getPatrol()->getUsersRoles() as $userRole) { + $user = $userRole->getUser(); + $this->userGroupRoleRepository->remove($userRole); + if ($user->getRoles()->isEmpty() && $user->getGroupRoles()->isEmpty()) { + $this->userRepository->remove($user); + } + } + + $this->patrolRepository->remove($command->getPatrol()); + }); + } +} diff --git a/app/Model/User/Commands/Handlers/RemoveTroopHandler.php b/app/Model/User/Commands/Handlers/RemoveTroopHandler.php new file mode 100644 index 000000000..6b707bafe --- /dev/null +++ b/app/Model/User/Commands/Handlers/RemoveTroopHandler.php @@ -0,0 +1,45 @@ +em->wrapInTransaction(function () use ($command): void { + foreach ($command->getTroop()->getPatrols() as $patrol) { + $this->commandBus->handle(new RemovePatrol($patrol)); + } + + foreach ($command->getTroop()->getUsersRoles() as $userRole) { + $user = $userRole->getUser(); + $this->userGroupRoleRepository->remove($userRole); + if ($user->getRoles()->isEmpty() && $user->getGroupRoles()->isEmpty()) { + $this->userRepository->remove($user); + } + } + + $this->troopRepository->remove($command->getTroop()); + }); + } +} diff --git a/app/Model/User/Commands/RemovePatrol.php b/app/Model/User/Commands/RemovePatrol.php new file mode 100644 index 000000000..f59e78218 --- /dev/null +++ b/app/Model/User/Commands/RemovePatrol.php @@ -0,0 +1,19 @@ +patrol; + } +} diff --git a/app/Model/User/Commands/RemoveTroop.php b/app/Model/User/Commands/RemoveTroop.php new file mode 100644 index 000000000..40f2df601 --- /dev/null +++ b/app/Model/User/Commands/RemoveTroop.php @@ -0,0 +1,19 @@ +troop; + } +} diff --git a/app/Model/User/Repositories/PatrolRepository.php b/app/Model/User/Repositories/PatrolRepository.php index b162dd7bb..2c18d0a6c 100644 --- a/app/Model/User/Repositories/PatrolRepository.php +++ b/app/Model/User/Repositories/PatrolRepository.php @@ -33,4 +33,10 @@ public function save(Patrol $patrol): void $this->em->persist($patrol); $this->em->flush(); } + + public function remove(Patrol $patrol): void + { + $this->em->remove($patrol); + $this->em->flush(); + } } diff --git a/app/Model/User/Repositories/TroopRepository.php b/app/Model/User/Repositories/TroopRepository.php index 25fdc84e4..5ca5ecc1d 100644 --- a/app/Model/User/Repositories/TroopRepository.php +++ b/app/Model/User/Repositories/TroopRepository.php @@ -132,4 +132,10 @@ public function save(Troop $troop): void $this->em->persist($troop); $this->em->flush(); } + + public function remove(Troop $troop): void + { + $this->em->remove($troop); + $this->em->flush(); + } } From 148463b0c52deeddb9ece57f95761d325b053526 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Stan=C4=9Bk?= Date: Tue, 10 Jan 2023 21:22:37 +0100 Subject: [PATCH 30/41] =?UTF-8?q?Skupinov=C3=A9=20role=20v=20tabulce=20u?= =?UTF-8?q?=C5=BEivatel=C5=AF=20(#944)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * group roles in users table * return non draft only * coalesce * export * cs fix --- .../Components/UsersGridControl.php | 2 + app/Model/User/User.php | 17 +++ app/Services/ExcelExportService.php | 119 +++++++++--------- 3 files changed, 81 insertions(+), 57 deletions(-) diff --git a/app/AdminModule/UsersModule/Components/UsersGridControl.php b/app/AdminModule/UsersModule/Components/UsersGridControl.php index d7b3c4b1d..118c98658 100644 --- a/app/AdminModule/UsersModule/Components/UsersGridControl.php +++ b/app/AdminModule/UsersModule/Components/UsersGridControl.php @@ -166,6 +166,8 @@ public function createComponentUsersGrid(string $name): DataGrid ->setParameter('rids', (array) $values); }); + $grid->addColumnText('groupRoles', 'Skupinové role', 'groupRolesText'); + $grid->addColumnText('subevents', 'admin.users.users_subevents', 'subeventsText') ->setFilterMultiSelect($this->subeventService->getSubeventsOptions()) ->setCondition(static function (QueryBuilder $qb, ArrayHash $values): void { diff --git a/app/Model/User/User.php b/app/Model/User/User.php index ef4cfd9fe..63aa50694 100644 --- a/app/Model/User/User.php +++ b/app/Model/User/User.php @@ -13,6 +13,7 @@ use App\Model\CustomInput\CustomInput; use App\Model\CustomInput\CustomInputValue; use App\Model\Enums\ApplicationState; +use App\Model\Enums\TroopApplicationState; use App\Model\Program\Block; use App\Model\Program\Program; use App\Model\Program\ProgramApplication; @@ -1241,4 +1242,20 @@ public function getGroupRoles(): Collection { return $this->groupRoles; } + + /** + * Vrátí skupinové role uživatele oddělené čárkou. + */ + public function getGroupRolesText(): string + { + $rolesNames = []; + foreach ($this->groupRoles as $groupRole) { + $troop = $groupRole->getTroop() ?? $groupRole->getPatrol()->getTroop(); + if ($troop->getState() !== TroopApplicationState::DRAFT) { + $rolesNames[] = $groupRole->getRole()->getName(); + } + } + + return implode(', ', $rolesNames); + } } diff --git a/app/Services/ExcelExportService.php b/app/Services/ExcelExportService.php index 47006f208..dafbcf619 100644 --- a/app/Services/ExcelExportService.php +++ b/app/Services/ExcelExportService.php @@ -244,83 +244,88 @@ public function exportUsersList(Collection $users, string $filename): ExcelRespo $row = 1; $column = 1; - $sheet->setCellValueByColumnAndRow($column, $row, $this->translator->translate('common.export.user.display_name')); - $sheet->getStyleByColumnAndRow($column, $row)->getFont()->setBold(true); + $sheet->setCellValue([$column, $row], $this->translator->translate('common.export.user.display_name')); + $sheet->getStyle([$column, $row])->getFont()->setBold(true); $sheet->getColumnDimensionByColumn($column)->setAutoSize(false); $sheet->getColumnDimensionByColumn($column++)->setWidth(30); - $sheet->setCellValueByColumnAndRow($column, $row, $this->translator->translate('common.export.user.username')); - $sheet->getStyleByColumnAndRow($column, $row)->getFont()->setBold(true); + $sheet->setCellValue([$column, $row], $this->translator->translate('common.export.user.username')); + $sheet->getStyle([$column, $row])->getFont()->setBold(true); $sheet->getColumnDimensionByColumn($column)->setAutoSize(false); $sheet->getColumnDimensionByColumn($column++)->setWidth(20); - $sheet->setCellValueByColumnAndRow($column, $row, $this->translator->translate('common.export.user.roles')); - $sheet->getStyleByColumnAndRow($column, $row)->getFont()->setBold(true); + $sheet->setCellValue([$column, $row], $this->translator->translate('common.export.user.roles')); + $sheet->getStyle([$column, $row])->getFont()->setBold(true); $sheet->getColumnDimensionByColumn($column)->setAutoSize(false); $sheet->getColumnDimensionByColumn($column++)->setWidth(30); - $sheet->setCellValueByColumnAndRow($column, $row, $this->translator->translate('common.export.user.subevents')); - $sheet->getStyleByColumnAndRow($column, $row)->getFont()->setBold(true); + $sheet->setCellValue([$column, $row], $this->translator->translate('Skupinové role')); + $sheet->getStyle([$column, $row])->getFont()->setBold(true); $sheet->getColumnDimensionByColumn($column)->setAutoSize(false); $sheet->getColumnDimensionByColumn($column++)->setWidth(30); - $sheet->setCellValueByColumnAndRow($column, $row, $this->translator->translate('common.export.user.approved')); - $sheet->getStyleByColumnAndRow($column, $row)->getFont()->setBold(true); + $sheet->setCellValue([$column, $row], $this->translator->translate('common.export.user.subevents')); + $sheet->getStyle([$column, $row])->getFont()->setBold(true); + $sheet->getColumnDimensionByColumn($column)->setAutoSize(false); + $sheet->getColumnDimensionByColumn($column++)->setWidth(30); + + $sheet->setCellValue([$column, $row], $this->translator->translate('common.export.user.approved')); + $sheet->getStyle([$column, $row])->getFont()->setBold(true); $sheet->getColumnDimensionByColumn($column)->setAutoSize(false); $sheet->getColumnDimensionByColumn($column++)->setWidth(10); - $sheet->setCellValueByColumnAndRow($column, $row, $this->translator->translate('common.export.user.membership')); - $sheet->getStyleByColumnAndRow($column, $row)->getFont()->setBold(true); + $sheet->setCellValue([$column, $row], $this->translator->translate('common.export.user.membership')); + $sheet->getStyle([$column, $row])->getFont()->setBold(true); $sheet->getColumnDimensionByColumn($column)->setAutoSize(false); $sheet->getColumnDimensionByColumn($column++)->setWidth(20); - $sheet->setCellValueByColumnAndRow($column, $row, $this->translator->translate('common.export.user.age')); - $sheet->getStyleByColumnAndRow($column, $row)->getFont()->setBold(true); + $sheet->setCellValue([$column, $row], $this->translator->translate('common.export.user.age')); + $sheet->getStyle([$column, $row])->getFont()->setBold(true); $sheet->getColumnDimensionByColumn($column)->setAutoSize(false); $sheet->getColumnDimensionByColumn($column++)->setWidth(10); - $sheet->setCellValueByColumnAndRow($column, $row, $this->translator->translate('common.export.user.email')); - $sheet->getStyleByColumnAndRow($column, $row)->getFont()->setBold(true); + $sheet->setCellValue([$column, $row], $this->translator->translate('common.export.user.email')); + $sheet->getStyle([$column, $row])->getFont()->setBold(true); $sheet->getColumnDimensionByColumn($column)->setAutoSize(false); $sheet->getColumnDimensionByColumn($column++)->setWidth(30); - $sheet->setCellValueByColumnAndRow($column, $row, $this->translator->translate('common.export.user.city')); - $sheet->getStyleByColumnAndRow($column, $row)->getFont()->setBold(true); + $sheet->setCellValue([$column, $row], $this->translator->translate('common.export.user.city')); + $sheet->getStyle([$column, $row])->getFont()->setBold(true); $sheet->getColumnDimensionByColumn($column)->setAutoSize(false); $sheet->getColumnDimensionByColumn($column++)->setWidth(20); - $sheet->setCellValueByColumnAndRow($column, $row, $this->translator->translate('common.export.user.fee')); - $sheet->getStyleByColumnAndRow($column, $row)->getFont()->setBold(true); + $sheet->setCellValue([$column, $row], $this->translator->translate('common.export.user.fee')); + $sheet->getStyle([$column, $row])->getFont()->setBold(true); $sheet->getColumnDimensionByColumn($column)->setAutoSize(false); $sheet->getColumnDimensionByColumn($column++)->setWidth(15); - $sheet->setCellValueByColumnAndRow($column, $row, $this->translator->translate('common.export.user.fee_remaining')); - $sheet->getStyleByColumnAndRow($column, $row)->getFont()->setBold(true); + $sheet->setCellValue([$column, $row], $this->translator->translate('common.export.user.fee_remaining')); + $sheet->getStyle([$column, $row])->getFont()->setBold(true); $sheet->getColumnDimensionByColumn($column)->setAutoSize(false); $sheet->getColumnDimensionByColumn($column++)->setWidth(15); - $sheet->setCellValueByColumnAndRow($column, $row, $this->translator->translate('common.export.user.variable_symbol')); - $sheet->getStyleByColumnAndRow($column, $row)->getFont()->setBold(true); + $sheet->setCellValue([$column, $row], $this->translator->translate('common.export.user.variable_symbol')); + $sheet->getStyle([$column, $row])->getFont()->setBold(true); $sheet->getColumnDimensionByColumn($column)->setAutoSize(false); $sheet->getColumnDimensionByColumn($column++)->setWidth(25); - $sheet->setCellValueByColumnAndRow($column, $row, $this->translator->translate('common.export.user.payment_method')); - $sheet->getStyleByColumnAndRow($column, $row)->getFont()->setBold(true); + $sheet->setCellValue([$column, $row], $this->translator->translate('common.export.user.payment_method')); + $sheet->getStyle([$column, $row])->getFont()->setBold(true); $sheet->getColumnDimensionByColumn($column)->setAutoSize(false); $sheet->getColumnDimensionByColumn($column++)->setWidth(15); - $sheet->setCellValueByColumnAndRow($column, $row, $this->translator->translate('common.export.user.payment_date')); - $sheet->getStyleByColumnAndRow($column, $row)->getFont()->setBold(true); + $sheet->setCellValue([$column, $row], $this->translator->translate('common.export.user.payment_date')); + $sheet->getStyle([$column, $row])->getFont()->setBold(true); $sheet->getColumnDimensionByColumn($column)->setAutoSize(false); $sheet->getColumnDimensionByColumn($column++)->setWidth(20); - $sheet->setCellValueByColumnAndRow($column, $row, $this->translator->translate('common.export.user.first_application_date')); - $sheet->getStyleByColumnAndRow($column, $row)->getFont()->setBold(true); + $sheet->setCellValue([$column, $row], $this->translator->translate('common.export.user.first_application_date')); + $sheet->getStyle([$column, $row])->getFont()->setBold(true); $sheet->getColumnDimensionByColumn($column)->setAutoSize(false); $sheet->getColumnDimensionByColumn($column++)->setWidth(20); - $sheet->setCellValueByColumnAndRow($column, $row, $this->translator->translate('common.export.user.attended')); - $sheet->getStyleByColumnAndRow($column, $row)->getFont()->setBold(true); + $sheet->setCellValue([$column, $row], $this->translator->translate('common.export.user.attended')); + $sheet->getStyle([$column, $row])->getFont()->setBold(true); $sheet->getColumnDimensionByColumn($column)->setAutoSize(false); $sheet->getColumnDimensionByColumn($column++)->setWidth(10); @@ -348,14 +353,14 @@ public function exportUsersList(Collection $users, string $filename): ExcelRespo throw new InvalidArgumentException(); } - $sheet->setCellValueByColumnAndRow($column, $row, $this->translator->translate($customInput->getName())); - $sheet->getStyleByColumnAndRow($column, $row)->getFont()->setBold(true); + $sheet->setCellValue([$column, $row], $this->translator->translate($customInput->getName())); + $sheet->getStyle([$column, $row])->getFont()->setBold(true); $sheet->getColumnDimensionByColumn($column)->setAutoSize(false); $sheet->getColumnDimensionByColumn($column++)->setWidth($width); } - $sheet->setCellValueByColumnAndRow($column, $row, $this->translator->translate('common.export.user.private_note')); - $sheet->getStyleByColumnAndRow($column, $row)->getFont()->setBold(true); + $sheet->setCellValue([$column, $row], $this->translator->translate('common.export.user.private_note')); + $sheet->getStyle([$column, $row])->getFont()->setBold(true); $sheet->getColumnDimensionByColumn($column)->setAutoSize(false); $sheet->getColumnDimensionByColumn($column++)->setWidth(60); @@ -363,41 +368,41 @@ public function exportUsersList(Collection $users, string $filename): ExcelRespo $row++; $column = 1; - $sheet->setCellValueByColumnAndRow($column++, $row, $user->getDisplayName()); + $sheet->setCellValue([$column++, $row], $user->getDisplayName()); + + $sheet->setCellValue([$column++, $row], $user->getUsername()); - $sheet->setCellValueByColumnAndRow($column++, $row, $user->getUsername()); + $sheet->setCellValue([$column++, $row], $user->getRolesText()); - $sheet->setCellValueByColumnAndRow($column++, $row, $user->getRolesText()); + $sheet->setCellValue([$column++, $row], $user->getGroupRolesText()); - $sheet->setCellValueByColumnAndRow($column++, $row, $user->getSubeventsText()); + $sheet->setCellValue([$column++, $row], $user->getSubeventsText()); - $sheet->setCellValueByColumnAndRow($column++, $row, $user->isApproved() + $sheet->setCellValue([$column++, $row], $user->isApproved() ? $this->translator->translate('common.export.common.yes') : $this->translator->translate('common.export.common.no')); - $sheet->getCellByColumnAndRow($column++, $row) - ->setValueExplicit($this->userService->getMembershipText($user), DataType::TYPE_STRING); + $sheet->getCell([$column++, $row])->setValueExplicit($this->userService->getMembershipText($user)); - $sheet->setCellValueByColumnAndRow($column++, $row, $user->getAge()); + $sheet->setCellValue([$column++, $row], $user->getAge()); - $sheet->setCellValueByColumnAndRow($column++, $row, $user->getEmail()); + $sheet->setCellValue([$column++, $row], $user->getEmail()); - $sheet->setCellValueByColumnAndRow($column++, $row, $user->getCity()); + $sheet->setCellValue([$column++, $row], $user->getCity()); - $sheet->setCellValueByColumnAndRow($column++, $row, $user->getFee()); + $sheet->setCellValue([$column++, $row], $user->getFee()); - $sheet->setCellValueByColumnAndRow($column++, $row, $user->getFeeRemaining()); + $sheet->setCellValue([$column++, $row], $user->getFeeRemaining()); - $sheet->getCellByColumnAndRow($column++, $row) - ->setValueExplicit($user->getVariableSymbolsText(), DataType::TYPE_STRING); + $sheet->getCell([$column++, $row])->setValueExplicit($user->getVariableSymbolsText()); - $sheet->setCellValueByColumnAndRow($column++, $row, $user->getPaymentMethod() ? $this->translator->translate('common.payment.' . $user->getPaymentMethod()) : ''); + $sheet->setCellValue([$column++, $row], $user->getPaymentMethod() ? $this->translator->translate('common.payment.' . $user->getPaymentMethod()) : ''); - $sheet->setCellValueByColumnAndRow($column++, $row, $user->getLastPaymentDate() !== null ? $user->getLastPaymentDate()->format(Helpers::DATE_FORMAT) : ''); + $sheet->setCellValue([$column++, $row], $user->getLastPaymentDate() !== null ? $user->getLastPaymentDate()->format(Helpers::DATE_FORMAT) : ''); - $sheet->setCellValueByColumnAndRow($column++, $row, $user->getRolesApplicationDate() !== null ? $user->getRolesApplicationDate()->format(Helpers::DATE_FORMAT) : ''); + $sheet->setCellValue([$column++, $row], $user->getRolesApplicationDate() !== null ? $user->getRolesApplicationDate()->format(Helpers::DATE_FORMAT) : ''); - $sheet->setCellValueByColumnAndRow($column++, $row, $user->isAttended() + $sheet->setCellValue([$column++, $row], $user->isAttended() ? $this->translator->translate('common.export.common.yes') : $this->translator->translate('common.export.common.no')); @@ -417,11 +422,11 @@ public function exportUsersList(Collection $users, string $filename): ExcelRespo $value = $customInputValue->getValueText(); } - $sheet->setCellValueByColumnAndRow($column++, $row, $value); + $sheet->setCellValue([$column++, $row], $value); } - $sheet->setCellValueByColumnAndRow($column, $row, $user->getNote()); - $sheet->getStyleByColumnAndRow($column++, $row)->getAlignment()->setWrapText(true); + $sheet->setCellValue([$column, $row], $user->getNote()); + $sheet->getStyle([$column++, $row])->getAlignment()->setWrapText(true); } return new ExcelResponse($this->spreadsheet, $filename); From 78c0a729931cbc39b7375f0aab52ace8d9a569f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Stan=C4=9Bk?= Date: Tue, 10 Jan 2023 22:03:58 +0100 Subject: [PATCH 31/41] remove troop fix (#947) --- .../UsersModule/Components/PatrolsGridControl.php | 3 +-- app/Model/User/Commands/Handlers/RemoveTroopHandler.php | 2 ++ app/Model/User/User.php | 9 +++++++-- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/app/AdminModule/UsersModule/Components/PatrolsGridControl.php b/app/AdminModule/UsersModule/Components/PatrolsGridControl.php index 9ae6e4a8b..367a4fa66 100644 --- a/app/AdminModule/UsersModule/Components/PatrolsGridControl.php +++ b/app/AdminModule/UsersModule/Components/PatrolsGridControl.php @@ -79,8 +79,7 @@ public function createComponentPatrolsGrid(string $name): DataGrid $date = $p->getTroop()->getApplicationDate(); return $date ? $date->format(Helpers::DATETIME_FORMAT) : ''; - }) - ->setSortable(); + }); $grid->addColumnNumber('userRoles', 'Počet osob') ->setRenderer(static fn (Patrol $p) => count($p->getUsersRoles())); // je to správné číslo? diff --git a/app/Model/User/Commands/Handlers/RemoveTroopHandler.php b/app/Model/User/Commands/Handlers/RemoveTroopHandler.php index 6b707bafe..4afc11981 100644 --- a/app/Model/User/Commands/Handlers/RemoveTroopHandler.php +++ b/app/Model/User/Commands/Handlers/RemoveTroopHandler.php @@ -39,6 +39,8 @@ public function __invoke(RemoveTroop $command): void } } + $command->getTroop()->getLeader()->setTroop(null); + $this->troopRepository->remove($command->getTroop()); }); } diff --git a/app/Model/User/User.php b/app/Model/User/User.php index 63aa50694..45fe0e512 100644 --- a/app/Model/User/User.php +++ b/app/Model/User/User.php @@ -299,7 +299,7 @@ class User * Přihláška oddílu. */ #[ORM\OneToOne(targetEntity: Troop::class, mappedBy: 'leader', cascade: ['persist'])] - protected Troop $troop; + protected ?Troop $troop; /** * Telefon. @@ -1170,11 +1170,16 @@ public function isAlternate(Program $program): bool )->isEmpty(); } - public function getTroop(): Troop + public function getTroop(): ?Troop { return $this->troop; } + public function setTroop(?Troop $troop): void + { + $this->troop = $troop; + } + public function getPhone(): ?string { return $this->phone; From de81920c36afc8b915a241d7e869df4e0c1a82c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Stan=C4=9Bk?= Date: Wed, 18 Jan 2023 20:52:28 +0100 Subject: [PATCH 32/41] =?UTF-8?q?E-mail=20vedouc=C3=ADho=20skupiny=20(#948?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * troop leader email * link --- .../UsersModule/Components/TroopsGridControl.php | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/app/AdminModule/UsersModule/Components/TroopsGridControl.php b/app/AdminModule/UsersModule/Components/TroopsGridControl.php index f02f24d6e..c71b7760d 100644 --- a/app/AdminModule/UsersModule/Components/TroopsGridControl.php +++ b/app/AdminModule/UsersModule/Components/TroopsGridControl.php @@ -95,9 +95,16 @@ public function createComponentPatrolsGrid(string $name): DataGrid return Html::el('a')->setAttribute('href', $this->getPresenter()->link('Users:detail', $leader->getId()))->setText($leader->getDisplayName()); }); + $grid->addColumnText('leaderEmail', 'E-mail vedoucího') + ->setRenderer(static function (Troop $t) { + $email = $t->getLeader()->getEmail(); + + return Html::el('a')->href('mailto:' . $email)->setText($email); + }); + $grid->addColumnDateTime('applicationDate', 'Datum založení') - ->setRenderer(static function (Troop $p) { - $date = $p->getApplicationDate(); + ->setRenderer(static function (Troop $t) { + $date = $t->getApplicationDate(); return $date ? $date->format(Helpers::DATETIME_FORMAT) : ''; }) From 700b1f01ef5e0c8cf7782dcb99be149bc1ac812a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Stan=C4=9Bk?= Date: Sat, 21 Jan 2023 13:38:50 +0100 Subject: [PATCH 33/41] =?UTF-8?q?Na=C4=8Dten=C3=AD=20=C4=8Dlenstv=C3=AD=20?= =?UTF-8?q?ze=20skautIS=20(#949)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * update membership * fix * fix cs --- .../Components/UsersGridControl.php | 33 +++++++++++++++++++ app/Services/Authenticator.php | 6 +--- 2 files changed, 34 insertions(+), 5 deletions(-) diff --git a/app/AdminModule/UsersModule/Components/UsersGridControl.php b/app/AdminModule/UsersModule/Components/UsersGridControl.php index 118c98658..b09818fdf 100644 --- a/app/AdminModule/UsersModule/Components/UsersGridControl.php +++ b/app/AdminModule/UsersModule/Components/UsersGridControl.php @@ -30,6 +30,7 @@ use App\Services\QueryBus; use App\Services\SkautIsEventEducationService; use App\Services\SkautIsEventGeneralService; +use App\Services\SkautIsService; use App\Services\SubeventService; use App\Services\UserService; use App\Utils\Helpers; @@ -46,6 +47,7 @@ use Nette\Localization\Translator; use Nette\Utils\ArrayHash; use Nette\Utils\Html; +use Skaut\Skautis\Wsdl\WsdlException; use Throwable; use Ublaboo\DataGrid\DataGrid; use Ublaboo\DataGrid\Exception\DataGridColumnStatusException; @@ -72,6 +74,7 @@ public function __construct( private AclService $aclService, private ApplicationService $applicationService, private UserService $userService, + private SkautIsService $skautIsService, private SkautIsEventEducationService $skautIsEventEducationService, private SkautIsEventGeneralService $skautIsEventGeneralService, private SubeventService $subeventService @@ -150,6 +153,9 @@ public function createComponentUsersGrid(string $name): DataGrid $grid->addGroupAction('admin.users.users_group_action_export_schedules') ->onSelect[] = [$this, 'groupExportSchedules']; + $grid->addGroupAction('Načíst členství ze skautIS (admin)') + ->onSelect[] = [$this, 'groupUpdateMembership']; + $grid->addColumnText('displayName', 'admin.users.users_name') ->setSortable() ->setFilterText(); @@ -714,6 +720,33 @@ public function groupExportUsers(array $ids): void $this->redirect('exportusers'); } + /** + * @param int[] $ids + */ + public function groupUpdateMembership(array $ids): void + { + $users = $this->userRepository->findUsersByIds($ids); + $errors = 0; + + foreach ($users as $user) { + try { + $membership = $this->skautIsService->getValidMembership($user->getSkautISPersonId()); + $user->setUnit($membership?->RegistrationNumber); + $this->userRepository->save($user); + } catch (WsdlException $e) { + $errors++; + } + } + + if ($errors > 0) { + $this->getPresenter()->flashMessage('Členství některých účastníků se nepodařilo načíst (oprávnění).', 'warning'); + } else { + $this->getPresenter()->flashMessage('Členství byla úspěšně načtena.', 'success'); + } + + $this->reload(); + } + /** * Zpracuje export seznamu uživatelů. * diff --git a/app/Services/Authenticator.php b/app/Services/Authenticator.php index 54bec4846..862e46fe6 100644 --- a/app/Services/Authenticator.php +++ b/app/Services/Authenticator.php @@ -108,11 +108,7 @@ private function updateUserFromSkautIS(User $user, stdClass $skautISUser): void $user->setMember($skautISUser->HasMembership); $validMembership = $this->skautIsService->getValidMembership($user->getSkautISPersonId()); - if ($validMembership === null) { - $user->setUnit(null); - } else { - $user->setUnit($validMembership->RegistrationNumber); - } + $user->setUnit($validMembership?->RegistrationNumber); $photoUpdate = new DateTimeImmutable($skautISPerson->PhotoUpdate); if ($user->getPhotoUpdate() === null || $photoUpdate->diff($user->getPhotoUpdate())->s > 0) { From 1bd53da49c23749b0984f3c60b90a21b1d3d5151 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Stan=C4=9Bk?= Date: Sat, 21 Jan 2023 14:52:24 +0100 Subject: [PATCH 34/41] =?UTF-8?q?Filtrov=C3=A1n=C3=AD=20skupinov=C3=BDch?= =?UTF-8?q?=20rol=C3=AD=20(#951)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * filter group roles * filter fix * filter fix --- .../UsersModule/Components/UsersGridControl.php | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/app/AdminModule/UsersModule/Components/UsersGridControl.php b/app/AdminModule/UsersModule/Components/UsersGridControl.php index b09818fdf..3d8be9a29 100644 --- a/app/AdminModule/UsersModule/Components/UsersGridControl.php +++ b/app/AdminModule/UsersModule/Components/UsersGridControl.php @@ -19,6 +19,7 @@ use App\Model\Enums\ApplicationState; use App\Model\Enums\PaymentType; use App\Model\Enums\SkautIsEventType; +use App\Model\Enums\TroopApplicationState; use App\Model\Settings\Queries\SettingIntValueQuery; use App\Model\Settings\Queries\SettingStringValueQuery; use App\Model\Settings\Settings; @@ -172,7 +173,19 @@ public function createComponentUsersGrid(string $name): DataGrid ->setParameter('rids', (array) $values); }); - $grid->addColumnText('groupRoles', 'Skupinové role', 'groupRolesText'); + $grid->addColumnText('groupRoles', 'Skupinové role', 'groupRolesText') + ->setFilterMultiSelect($this->aclService->getRolesWithoutRolesOptions([Role::GUEST, Role::UNAPPROVED])) + ->setCondition(static function (QueryBuilder $qb, ArrayHash $values): void { + $qb->join('u.groupRoles', 'uGR') + ->join('uGR.role', 'uGRR') + ->leftJoin('uGR.troop', 'uGRT') + ->leftJoin('uGR.patrol', 'uGRP') + ->leftJoin('uGRP.troop', 'uGRPT') + ->andWhere('uGRR.id IN (:grids)') + ->andWhere('(uGRT.state IS NULL OR uGRT.state != :tas) AND (uGRPT.state IS NULL OR uGRPT.state != :tas)') + ->setParameter('grids', (array) $values) + ->setParameter('tas', TroopApplicationState::DRAFT); + }); $grid->addColumnText('subevents', 'admin.users.users_subevents', 'subeventsText') ->setFilterMultiSelect($this->subeventService->getSubeventsOptions()) From ecc6c58b00a5b603e36eb719d46f7c57c38c2b9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Stan=C4=9Bk?= Date: Tue, 31 Jan 2023 22:51:19 +0100 Subject: [PATCH 35/41] Export troops, patrols to excel (#953) * export to excel * session fix * phpstan fix * cs fix * fixes * cell size --- .../Components/PatrolsGridControl.php | 47 ++++- .../Components/TroopsGridControl.php | 47 ++++- .../User/Repositories/PatrolRepository.php | 15 ++ app/Services/ExcelExportService.php | 168 ++++++++++++++++++ 4 files changed, 267 insertions(+), 10 deletions(-) diff --git a/app/AdminModule/UsersModule/Components/PatrolsGridControl.php b/app/AdminModule/UsersModule/Components/PatrolsGridControl.php index 367a4fa66..c998cddc4 100644 --- a/app/AdminModule/UsersModule/Components/PatrolsGridControl.php +++ b/app/AdminModule/UsersModule/Components/PatrolsGridControl.php @@ -8,9 +8,13 @@ use App\Model\User\Patrol; use App\Model\User\Repositories\PatrolRepository; use App\Services\CommandBus; +use App\Services\ExcelExportService; use App\Utils\Helpers; +use Exception; use Nette\Application\AbortException; use Nette\Application\UI\Control; +use Nette\Http\Session; +use Nette\Http\SessionSection; use Nette\Localization\Translator; use Nette\Utils\Html; use Throwable; @@ -19,18 +23,22 @@ use Ublaboo\DataGrid\Exception\DataGridException; use function count; -use function date; /** * Komponenta pro zobrazení datagridu družin. */ class PatrolsGridControl extends Control { + private SessionSection $sessionSection; + public function __construct( private CommandBus $commandBus, private Translator $translator, - private PatrolRepository $patrolRepository + private PatrolRepository $patrolRepository, + private ExcelExportService $excelExportService, + private Session $session ) { + $this->sessionSection = $session->getSection('srs'); } /** @@ -59,9 +67,8 @@ public function createComponentPatrolsGrid(string $name): DataGrid $grid->setItemsPerPageList([25, 50, 100, 250, 500]); $grid->setStrictSessionFilterValues(false); - $stamp = date(Helpers::DATE_FORMAT); - $grid->addExportCsv('admin.common.export_all', 'NSJ2023 Druziny ' . $stamp . '.csv'); - $grid->addExportCsvFiltered('admin.common.export_filter', 'NSJ2023 Druziny fi ' . $stamp . '.csv'); + $grid->addGroupAction('Export seznamu družin') + ->onSelect[] = [$this, 'groupExportPatrols']; $grid->addColumnText('name', 'Název') ->setSortable() @@ -112,4 +119,34 @@ public function handleDelete(int $id): void $p->flashMessage('Družina byla úspěšně odstraněna.', 'success'); $p->redirect('this'); } + + /** + * Hromadně vyexportuje seznam družin. + * + * @param int[] $ids + * + * @throws AbortException + */ + public function groupExportPatrols(array $ids): void + { + $this->sessionSection->patrolIds = $ids; + $this->redirect('exportpatrols'); + } + + /** + * Zpracuje export seznamu družin. + * + * @throws AbortException + * @throws Exception + */ + public function handleExportPatrols(): void + { + $ids = $this->session->getSection('srs')->patrolIds; + + $patrols = $this->patrolRepository->findPatrolsByIds($ids); + + $response = $this->excelExportService->exportPatrolsList($patrols, 'seznam-druzin.xlsx'); + + $this->getPresenter()->sendResponse($response); + } } diff --git a/app/AdminModule/UsersModule/Components/TroopsGridControl.php b/app/AdminModule/UsersModule/Components/TroopsGridControl.php index c71b7760d..ce4f71e96 100644 --- a/app/AdminModule/UsersModule/Components/TroopsGridControl.php +++ b/app/AdminModule/UsersModule/Components/TroopsGridControl.php @@ -9,10 +9,14 @@ use App\Model\User\Repositories\TroopRepository; use App\Model\User\Troop; use App\Services\CommandBus; +use App\Services\ExcelExportService; use App\Utils\Helpers; use Doctrine\ORM\QueryBuilder; +use Exception; use Nette\Application\AbortException; use Nette\Application\UI\Control; +use Nette\Http\Session; +use Nette\Http\SessionSection; use Nette\Localization\Translator; use Nette\Utils\Html; use Throwable; @@ -21,18 +25,22 @@ use Ublaboo\DataGrid\Exception\DataGridException; use function count; -use function date; /** * Komponenta pro zobrazení datagridu družin. */ class TroopsGridControl extends Control { + private SessionSection $sessionSection; + public function __construct( private CommandBus $commandBus, private Translator $translator, - private TroopRepository $troopRepository + private TroopRepository $troopRepository, + private ExcelExportService $excelExportService, + private Session $session ) { + $this->sessionSection = $session->getSection('srs'); } /** @@ -61,9 +69,8 @@ public function createComponentPatrolsGrid(string $name): DataGrid $grid->setItemsPerPageList([25, 50, 100, 250, 500]); $grid->setStrictSessionFilterValues(false); - $stamp = date(Helpers::DATE_FORMAT); - $grid->addExportCsv('admin.common.export_all', 'NSJ2023 Skupiny ' . $stamp . '.csv'); - $grid->addExportCsvFiltered('admin.common.export_filter', 'NSJ2023 Skupiny fi ' . $stamp . '.csv'); + $grid->addGroupAction('Export seznamu skupin') + ->onSelect[] = [$this, 'groupExportTroops']; $grid->addColumnText('name', 'Název') ->setSortable() @@ -180,4 +187,34 @@ public function handleGeneratePaymentProof(int $id): void { $this->presenter->redirect(':Export:TroopIncomeProof:troop', ['id' => $id]); } + + /** + * Hromadně vyexportuje seznam skupin. + * + * @param int[] $ids + * + * @throws AbortException + */ + public function groupExportTroops(array $ids): void + { + $this->sessionSection->troopIds = $ids; + $this->redirect('exporttroops'); + } + + /** + * Zpracuje export seznamu skupin. + * + * @throws AbortException + * @throws Exception + */ + public function handleExportTroops(): void + { + $ids = $this->session->getSection('srs')->troopIds; + + $troops = $this->troopRepository->findTroopsByIds($ids); + + $response = $this->excelExportService->exportTroopsList($troops, 'seznam-skupin.xlsx'); + + $this->getPresenter()->sendResponse($response); + } } diff --git a/app/Model/User/Repositories/PatrolRepository.php b/app/Model/User/Repositories/PatrolRepository.php index 2c18d0a6c..f7b8be640 100644 --- a/app/Model/User/Repositories/PatrolRepository.php +++ b/app/Model/User/Repositories/PatrolRepository.php @@ -6,6 +6,8 @@ use App\Model\Infrastructure\Repositories\AbstractRepository; use App\Model\User\Patrol; +use Doctrine\Common\Collections\Collection; +use Doctrine\Common\Collections\Criteria; use Doctrine\ORM\EntityManagerInterface; /** @@ -28,6 +30,19 @@ public function findByTroopAndNotConfirmed(int $troopId): ?Patrol return $this->getRepository()->findOneBy(['troop' => $troopId, 'confirmed' => false]); } + /** + * @param int[] $ids + * + * @return Collection + */ + public function findPatrolsByIds(array $ids): Collection + { + $criteria = Criteria::create() + ->where(Criteria::expr()->in('id', $ids)); + + return $this->getRepository()->matching($criteria); + } + public function save(Patrol $patrol): void { $this->em->persist($patrol); diff --git a/app/Services/ExcelExportService.php b/app/Services/ExcelExportService.php index dafbcf619..7104e6e23 100644 --- a/app/Services/ExcelExportService.php +++ b/app/Services/ExcelExportService.php @@ -14,7 +14,9 @@ use App\Model\Program\Repositories\ProgramRepository; use App\Model\Program\Room; use App\Model\Structure\Repositories\SubeventRepository; +use App\Model\User\Patrol; use App\Model\User\Queries\UserAttendsProgramsQuery; +use App\Model\User\Troop; use App\Model\User\User; use App\Utils\Helpers; use Doctrine\Common\Collections\ArrayCollection; @@ -518,6 +520,172 @@ public function exportUsersSubeventsAndCategories(Collection $users, string $fil return new ExcelResponse($this->spreadsheet, $filename); } + /** + * @param Collection $patrols + * + * @throws Exception + */ + public function exportPatrolsList(Collection $patrols, string $filename): ExcelResponse + { + $sheet = $this->spreadsheet->getSheet(0); + + $row = 1; + $column = 1; + + $sheet->setCellValue([$column, $row], $this->translator->translate('Název')); + $sheet->getStyle([$column, $row])->getFont()->setBold(true); + $sheet->getColumnDimensionByColumn($column)->setAutoSize(false); + $sheet->getColumnDimensionByColumn($column++)->setWidth(20); + + $sheet->setCellValue([$column, $row], $this->translator->translate('Skupina')); + $sheet->getStyle([$column, $row])->getFont()->setBold(true); + $sheet->getColumnDimensionByColumn($column)->setAutoSize(false); + $sheet->getColumnDimensionByColumn($column++)->setWidth(15); + + $sheet->setCellValue([$column, $row], $this->translator->translate('Datum založení')); + $sheet->getStyle([$column, $row])->getFont()->setBold(true); + $sheet->getColumnDimensionByColumn($column)->setAutoSize(false); + $sheet->getColumnDimensionByColumn($column++)->setWidth(20); + + $sheet->setCellValue([$column, $row], $this->translator->translate('Počet osob')); + $sheet->getStyle([$column, $row])->getFont()->setBold(true); + $sheet->getColumnDimensionByColumn($column)->setAutoSize(false); + $sheet->getColumnDimensionByColumn($column++)->setWidth(15); + + foreach ($patrols as $patrol) { + $row++; + $column = 1; + + $sheet->setCellValue([$column++, $row], $patrol->getName()); + + $sheet->setCellValue([$column++, $row], $patrol->getTroop()->getName()); + + $sheet->setCellValue([$column++, $row], $patrol->getTroop()->getApplicationDate() !== null ? $patrol->getTroop()->getApplicationDate()->format(Helpers::DATETIME_FORMAT) : ''); + + $sheet->setCellValue([$column++, $row], $patrol->getUsersRoles()->count()); + } + + return new ExcelResponse($this->spreadsheet, $filename); + } + + /** + * @param Collection $troops + * + * @throws Exception + */ + public function exportTroopsList(Collection $troops, string $filename): ExcelResponse + { + $sheet = $this->spreadsheet->getSheet(0); + + $row = 1; + $column = 1; + + $sheet->setCellValue([$column, $row], $this->translator->translate('Název')); + $sheet->getStyle([$column, $row])->getFont()->setBold(true); + $sheet->getColumnDimensionByColumn($column)->setAutoSize(false); + $sheet->getColumnDimensionByColumn($column++)->setWidth(20); + + $sheet->setCellValue([$column, $row], $this->translator->translate('Stav')); + $sheet->getStyle([$column, $row])->getFont()->setBold(true); + $sheet->getColumnDimensionByColumn($column)->setAutoSize(false); + $sheet->getColumnDimensionByColumn($column++)->setWidth(20); + + $sheet->setCellValue([$column, $row], $this->translator->translate('Variabilní symbol')); + $sheet->getStyle([$column, $row])->getFont()->setBold(true); + $sheet->getColumnDimensionByColumn($column)->setAutoSize(false); + $sheet->getColumnDimensionByColumn($column++)->setWidth(20); + + $sheet->setCellValue([$column, $row], $this->translator->translate('Vedoucí')); + $sheet->getStyle([$column, $row])->getFont()->setBold(true); + $sheet->getColumnDimensionByColumn($column)->setAutoSize(false); + $sheet->getColumnDimensionByColumn($column++)->setWidth(30); + + $sheet->setCellValue([$column, $row], $this->translator->translate('E-mail vedoucího')); + $sheet->getStyle([$column, $row])->getFont()->setBold(true); + $sheet->getColumnDimensionByColumn($column)->setAutoSize(false); + $sheet->getColumnDimensionByColumn($column++)->setWidth(30); + + $sheet->setCellValue([$column, $row], $this->translator->translate('Datum založení')); + $sheet->getStyle([$column, $row])->getFont()->setBold(true); + $sheet->getColumnDimensionByColumn($column)->setAutoSize(false); + $sheet->getColumnDimensionByColumn($column++)->setWidth(20); + + $sheet->setCellValue([$column, $row], $this->translator->translate('Cena')); + $sheet->getStyle([$column, $row])->getFont()->setBold(true); + $sheet->getColumnDimensionByColumn($column)->setAutoSize(false); + $sheet->getColumnDimensionByColumn($column++)->setWidth(10); + + $sheet->setCellValue([$column, $row], $this->translator->translate('Datum splatnosti')); + $sheet->getStyle([$column, $row])->getFont()->setBold(true); + $sheet->getColumnDimensionByColumn($column)->setAutoSize(false); + $sheet->getColumnDimensionByColumn($column++)->setWidth(20); + + $sheet->setCellValue([$column, $row], $this->translator->translate('Datum platby')); + $sheet->getStyle([$column, $row])->getFont()->setBold(true); + $sheet->getColumnDimensionByColumn($column)->setAutoSize(false); + $sheet->getColumnDimensionByColumn($column++)->setWidth(20); + + $sheet->setCellValue([$column, $row], $this->translator->translate('Kód jamoddílu')); + $sheet->getStyle([$column, $row])->getFont()->setBold(true); + $sheet->getColumnDimensionByColumn($column)->setAutoSize(false); + $sheet->getColumnDimensionByColumn($column++)->setWidth(30); + + $sheet->setCellValue([$column, $row], $this->translator->translate('Počet osob')); + $sheet->getStyle([$column, $row])->getFont()->setBold(true); + $sheet->getColumnDimensionByColumn($column)->setAutoSize(false); + $sheet->getColumnDimensionByColumn($column++)->setWidth(15); + + $sheet->setCellValue([$column, $row], $this->translator->translate('Počet rádců')); + $sheet->getStyle([$column, $row])->getFont()->setBold(true); + $sheet->getColumnDimensionByColumn($column)->setAutoSize(false); + $sheet->getColumnDimensionByColumn($column++)->setWidth(15); + + $sheet->setCellValue([$column, $row], $this->translator->translate('Počet dospělých')); + $sheet->getStyle([$column, $row])->getFont()->setBold(true); + $sheet->getColumnDimensionByColumn($column)->setAutoSize(false); + $sheet->getColumnDimensionByColumn($column++)->setWidth(15); + + $sheet->setCellValue([$column, $row], $this->translator->translate('Počet družin')); + $sheet->getStyle([$column, $row])->getFont()->setBold(true); + $sheet->getColumnDimensionByColumn($column)->setAutoSize(false); + $sheet->getColumnDimensionByColumn($column++)->setWidth(15); + + foreach ($troops as $troop) { + $row++; + $column = 1; + + $sheet->setCellValue([$column++, $row], $troop->getName()); + + $sheet->setCellValue([$column++, $row], $this->translator->translate('common.application_state.' . $troop->getState())); + + $sheet->setCellValue([$column++, $row], $troop->getVariableSymbolText()); + + $sheet->setCellValue([$column++, $row], $troop->getLeader()->getDisplayName()); + + $sheet->setCellValue([$column++, $row], $troop->getLeader()->getEmail()); + + $sheet->setCellValue([$column++, $row], $troop->getApplicationDate() !== null ? $troop->getApplicationDate()->format(Helpers::DATETIME_FORMAT) : ''); + + $sheet->setCellValue([$column++, $row], $troop->getFee()); + + $sheet->setCellValue([$column++, $row], $troop->getMaturityDate() !== null ? $troop->getMaturityDate()->format(Helpers::DATE_FORMAT) : ''); + + $sheet->setCellValue([$column++, $row], $troop->getPaymentDate() !== null ? $troop->getPaymentDate()->format(Helpers::DATE_FORMAT) : ''); + + $sheet->setCellValue([$column++, $row], $troop->getPairingCode()); + + $sheet->setCellValue([$column++, $row], $troop->countUsersInRoles([Role::PATROL_LEADER, Role::LEADER, Role::ESCORT, Role::ATTENDEE])); + + $sheet->setCellValue([$column++, $row], $troop->countUsersInRoles([Role::PATROL_LEADER])); + + $sheet->setCellValue([$column++, $row], $troop->countUsersInRoles([Role::LEADER, Role::ESCORT])); + + $sheet->setCellValue([$column++, $row], $troop->getConfirmedPatrols()->count()); + } + + return new ExcelResponse($this->spreadsheet, $filename); + } + /** * @param Collection $blocks * From b2387c07318f5123b01d786e16a714f4d14c061b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Stan=C4=9Bk?= Date: Fri, 3 Mar 2023 23:21:19 +0100 Subject: [PATCH 36/41] =?UTF-8?q?Vyp=C3=ADnateln=C3=A1=20registrace=20(#95?= =?UTF-8?q?8)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * disable registration * config property --- .../ConfigurationModule/Forms/SeminarFormFactory.php | 6 ++++++ app/Model/Settings/Settings.php | 5 +++++ .../Components/TroopApplicationContentControl.php | 3 +++ .../templates/troop_application_content.latte | 10 ++++++++++ 4 files changed, 24 insertions(+) diff --git a/app/AdminModule/ConfigurationModule/Forms/SeminarFormFactory.php b/app/AdminModule/ConfigurationModule/Forms/SeminarFormFactory.php index 28c8957b1..0ebf16d1a 100644 --- a/app/AdminModule/ConfigurationModule/Forms/SeminarFormFactory.php +++ b/app/AdminModule/ConfigurationModule/Forms/SeminarFormFactory.php @@ -5,8 +5,10 @@ namespace App\AdminModule\ConfigurationModule\Forms; use App\AdminModule\Forms\BaseFormFactory; +use App\Model\Settings\Commands\SetSettingBoolValue; use App\Model\Settings\Commands\SetSettingDateValue; use App\Model\Settings\Commands\SetSettingStringValue; +use App\Model\Settings\Queries\SettingBoolValueQuery; use App\Model\Settings\Queries\SettingDateValueQuery; use App\Model\Settings\Queries\SettingStringValueQuery; use App\Model\Settings\Settings; @@ -71,6 +73,8 @@ public function create(): Form $seminarToDate->addRule([$this, 'validateSeminarToDate'], 'admin.configuration.seminar_to_date_before_from', [$seminarToDate, $seminarFromDate]); $editRegistrationTo->addRule([$this, 'validateEditRegistrationTo'], 'admin.configuration.edit_registration_to_after_from', [$editRegistrationTo, $seminarFromDate]); + $form->addCheckbox('groupRegistrationAllowed', 'povolit registraci nových skupin'); + $form->addSubmit('submit', 'admin.common.save'); $form->setDefaults([ @@ -78,6 +82,7 @@ public function create(): Form 'seminarFromDate' => $this->queryBus->handle(new SettingDateValueQuery(Settings::SEMINAR_FROM_DATE)), 'seminarToDate' => $this->queryBus->handle(new SettingDateValueQuery(Settings::SEMINAR_TO_DATE)), 'editRegistrationTo' => $this->queryBus->handle(new SettingDateValueQuery(Settings::EDIT_REGISTRATION_TO)), + 'groupRegistrationAllowed' => $this->queryBus->handle(new SettingBoolValueQuery(Settings::GROUP_REGISTRATION_ALLOWED)), ]); $form->onSuccess[] = [$this, 'processForm']; @@ -100,6 +105,7 @@ public function processForm(Form $form, stdClass $values): void $this->commandBus->handle(new SetSettingDateValue(Settings::SEMINAR_FROM_DATE, $values->seminarFromDate)); $this->commandBus->handle(new SetSettingDateValue(Settings::SEMINAR_TO_DATE, $values->seminarToDate)); $this->commandBus->handle(new SetSettingDateValue(Settings::EDIT_REGISTRATION_TO, $values->editRegistrationTo)); + $this->commandBus->handle(new SetSettingBoolValue(Settings::GROUP_REGISTRATION_ALLOWED, $values->groupRegistrationAllowed)); } /** diff --git a/app/Model/Settings/Settings.php b/app/Model/Settings/Settings.php index 255cde18f..de5e9771d 100644 --- a/app/Model/Settings/Settings.php +++ b/app/Model/Settings/Settings.php @@ -234,6 +234,11 @@ class Settings */ public const CONTACT_FORM_GUESTS_ALLOWED = 'contact_form_guests_allowed'; + /** + * Povolit registraci skupiny. + */ + public const GROUP_REGISTRATION_ALLOWED = 'group_registration_allowed'; + /** * Název položky nastavení. */ diff --git a/app/WebModule/Components/TroopApplicationContentControl.php b/app/WebModule/Components/TroopApplicationContentControl.php index 9d2482a73..7b5c3e228 100644 --- a/app/WebModule/Components/TroopApplicationContentControl.php +++ b/app/WebModule/Components/TroopApplicationContentControl.php @@ -7,6 +7,7 @@ use App\Model\Acl\Repositories\RoleRepository; use App\Model\Acl\Role; use App\Model\Cms\Dto\ContentDto; +use App\Model\Settings\Queries\SettingBoolValueQuery; use App\Model\Settings\Queries\SettingStringValueQuery; use App\Model\Settings\Settings; use App\Model\User\Commands\RegisterTroop; @@ -79,6 +80,8 @@ public function render(?ContentDto $content = null): void $dbuser = $this->userRepository->findById($user->id); $template->dbuser = $dbuser; + $template->registrationAllowed = $this->queryBus->handle(new SettingBoolValueQuery(Settings::GROUP_REGISTRATION_ALLOWED)); + $skautIsUserId = $dbuser->getSkautISUserId(); $skautIsRoles = $this->skautIsService->getUserRoles($skautIsUserId, self::$ALLOWED_ROLE_TYPES); $template->skautIsRoles = $skautIsRoles; diff --git a/app/WebModule/Components/templates/troop_application_content.latte b/app/WebModule/Components/templates/troop_application_content.latte index f273ab25e..7b0a7d94d 100644 --- a/app/WebModule/Components/templates/troop_application_content.latte +++ b/app/WebModule/Components/templates/troop_application_content.latte @@ -46,6 +46,7 @@ + {if $registrationAllowed || $troop->getState() !== 'draft'}
    Pracuješ v roli @@ -220,5 +221,14 @@ {control troopConfirmForm}
    + {else} +
    +
    +
    + Registrace nové skupiny není v tuto chvíli povolena. +
    +
    +
    + {/if} {/if} From d6ef49e2c0a55888bf117a73b5eb9c202116157b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Stan=C4=9Bk?= Date: Sat, 4 Mar 2023 00:21:57 +0100 Subject: [PATCH 37/41] =?UTF-8?q?Zapomenut=C3=A1=20migrace=20(#959)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * disable registration * config property * migration added --- migrations/Version20230303221015.php | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 migrations/Version20230303221015.php diff --git a/migrations/Version20230303221015.php b/migrations/Version20230303221015.php new file mode 100644 index 000000000..540369ff8 --- /dev/null +++ b/migrations/Version20230303221015.php @@ -0,0 +1,27 @@ +abortIf($this->connection->getDatabasePlatform()->getName() !== 'mysql', 'Migration can only be executed safely on \'mysql\'.'); + + $this->addSql('INSERT INTO `settings` (`item`, `value`) VALUES (\'group_registration_allowed\', \'0\')'); + } + + public function down(Schema $schema): void + { + } +} From a0720477bb1f245a721ea985b33a7c5b4cbc68b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Stan=C4=9Bk?= Date: Sun, 9 Apr 2023 20:34:17 +0200 Subject: [PATCH 38/41] Oprava deploymentu (NSJ) (#964) * deployment fix * deployment fix --- .github/workflows/deploy-dev.yml | 3 ++- .github/workflows/deploy-manual.yml | 3 ++- .github/workflows/deploy-nsj-dev.yml | 3 ++- .github/workflows/deploy-staging.yml | 3 ++- .github/workflows/release.yml | 2 +- .github/workflows/test.yml | 2 +- 6 files changed, 10 insertions(+), 6 deletions(-) diff --git a/.github/workflows/deploy-dev.yml b/.github/workflows/deploy-dev.yml index c7b7d3f3c..acb2121d5 100644 --- a/.github/workflows/deploy-dev.yml +++ b/.github/workflows/deploy-dev.yml @@ -12,7 +12,7 @@ jobs: deploy: name: "Deploy to test-srs.skauting.cz" environment: test-srs.skauting.cz - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 container: image: skaut/lebeda:8.1 env: @@ -39,6 +39,7 @@ jobs: DEPLOY_SSH_USERNAME: ${{ secrets.DEPLOY_SSH_USERNAME }} steps: - uses: actions/checkout@v3 + - run: git config --global --add safe.directory '*' # Copy & paste from https://github.com/actions/cache/blob/master/examples.md#php---composer - name: Get composer cache id: composer-cache diff --git a/.github/workflows/deploy-manual.yml b/.github/workflows/deploy-manual.yml index 0af3e4f7c..956b6a3b5 100644 --- a/.github/workflows/deploy-manual.yml +++ b/.github/workflows/deploy-manual.yml @@ -15,7 +15,7 @@ jobs: deploy: name: "Manual deploy to ${{ github.event.inputs.environment }}" environment: ${{ github.event.inputs.environment }} - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 container: image: skaut/lebeda:8.1 env: @@ -42,6 +42,7 @@ jobs: DEPLOY_SSH_USERNAME: ${{ secrets.DEPLOY_SSH_USERNAME }} steps: - uses: actions/checkout@v3 + - run: git config --global --add safe.directory '*' # Copy & paste from https://github.com/actions/cache/blob/master/examples.md#php---composer - name: Get composer cache id: composer-cache diff --git a/.github/workflows/deploy-nsj-dev.yml b/.github/workflows/deploy-nsj-dev.yml index a3fc6c2b3..9a00798a8 100644 --- a/.github/workflows/deploy-nsj-dev.yml +++ b/.github/workflows/deploy-nsj-dev.yml @@ -11,7 +11,7 @@ jobs: deploy: name: "Deploy to srs-dev.skauting.cz" environment: srs-dev.skauting.cz - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 container: image: skaut/lebeda:8.1 env: @@ -38,6 +38,7 @@ jobs: DEPLOY_SSH_USERNAME: ${{ secrets.DEPLOY_SSH_USERNAME }} steps: - uses: actions/checkout@v3 + - run: git config --global --add safe.directory '*' # Copy & paste from https://github.com/actions/cache/blob/master/examples.md#php---composer - name: Get composer cache id: composer-cache diff --git a/.github/workflows/deploy-staging.yml b/.github/workflows/deploy-staging.yml index 16087a885..637294871 100644 --- a/.github/workflows/deploy-staging.yml +++ b/.github/workflows/deploy-staging.yml @@ -11,7 +11,7 @@ jobs: deploy: name: "Deploy to srs.skauting.cz" environment: srs.skauting.cz - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 container: image: skaut/lebeda:8.1 env: @@ -38,6 +38,7 @@ jobs: DEPLOY_SSH_USERNAME: ${{ secrets.DEPLOY_SSH_USERNAME }} steps: - uses: actions/checkout@v3 + - run: git config --global --add safe.directory '*' # Copy & paste from https://github.com/actions/cache/blob/master/examples.md#php---composer - name: Get composer cache id: composer-cache diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 00186b065..ad770853d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -7,7 +7,7 @@ on: jobs: package: name: "Create release package" - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 container: image: skaut/lebeda:8.1 steps: diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 41ec20390..7914ed50b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -13,7 +13,7 @@ concurrency: jobs: workdir: name: "Build" - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 container: image: skaut/lebeda:8.1 steps: From ab240b25be2454adede53c8b2b7dd924f6b300e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Stan=C4=9Bk?= Date: Tue, 11 Apr 2023 19:13:14 +0200 Subject: [PATCH 39/41] =?UTF-8?q?Export=20=C3=BA=C4=8Dastn=C3=ADk=C5=AF=20?= =?UTF-8?q?NSJ=20(#965)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * attendees export * skip leaders * export all attendees * export others --- .../Components/UsersGridControl.php | 26 ++ .../Handlers/TroopsByStateQueryHandler.php | 26 ++ app/Model/User/Queries/TroopsByStateQuery.php | 17 ++ .../User/Repositories/TroopRepository.php | 8 + app/Services/ExcelExportService.php | 241 ++++++++++++++++++ 5 files changed, 318 insertions(+) create mode 100644 app/Model/User/Queries/Handlers/TroopsByStateQueryHandler.php create mode 100644 app/Model/User/Queries/TroopsByStateQuery.php diff --git a/app/AdminModule/UsersModule/Components/UsersGridControl.php b/app/AdminModule/UsersModule/Components/UsersGridControl.php index 3d8be9a29..1157aea43 100644 --- a/app/AdminModule/UsersModule/Components/UsersGridControl.php +++ b/app/AdminModule/UsersModule/Components/UsersGridControl.php @@ -23,6 +23,7 @@ use App\Model\Settings\Queries\SettingIntValueQuery; use App\Model\Settings\Queries\SettingStringValueQuery; use App\Model\Settings\Settings; +use App\Model\User\Queries\TroopsByStateQuery; use App\Model\User\Repositories\UserRepository; use App\Model\User\User; use App\Services\AclService; @@ -157,6 +158,10 @@ public function createComponentUsersGrid(string $name): DataGrid $grid->addGroupAction('Načíst členství ze skautIS (admin)') ->onSelect[] = [$this, 'groupUpdateMembership']; + $grid->addToolbarButton('exportNsjAttendees', 'Export NSJ - účastníci'); + + $grid->addToolbarButton('exportNsjOthers', 'Export NSJ - ostatní'); + $grid->addColumnText('displayName', 'admin.users.users_name') ->setSortable() ->setFilterText(); @@ -868,6 +873,27 @@ public function handleExportSchedules(): void $this->getPresenter()->sendResponse($response); } + public function handleExportNsjAttendees(): void + { + $troops = $this->queryBus->handle(new TroopsByStateQuery(TroopApplicationState::PAID)); + + $response = $this->excelExportService->exportNsjAttendees($troops, 'nsj-ucastnici.xlsx'); + + $this->getPresenter()->sendResponse($response); + } + + public function handleExportNsjOthers(): void + { + $nonRegisteredRole = $this->roleRepository->findBySystemName(Role::NONREGISTERED); + $users = $this->userRepository->findAll() + ->filter(static fn (User $u) => $u->getRoles()->count() > 0 && ! $u->isInRole($nonRegisteredRole)) + ->toArray(); + + $response = $this->excelExportService->exportNsjOthers($users, 'nsj-ostatni.xlsx'); + + $this->getPresenter()->sendResponse($response); + } + /** * Vrátí platební metody jako možnosti pro select. Bez prázdné možnosti. * diff --git a/app/Model/User/Queries/Handlers/TroopsByStateQueryHandler.php b/app/Model/User/Queries/Handlers/TroopsByStateQueryHandler.php new file mode 100644 index 000000000..e0fb212f7 --- /dev/null +++ b/app/Model/User/Queries/Handlers/TroopsByStateQueryHandler.php @@ -0,0 +1,26 @@ + + */ + public function __invoke(TroopsByStateQuery $query): Collection + { + return $this->troopRepository->findByState($query->getTroopState()); + } +} diff --git a/app/Model/User/Queries/TroopsByStateQuery.php b/app/Model/User/Queries/TroopsByStateQuery.php new file mode 100644 index 000000000..8bc2e0d4b --- /dev/null +++ b/app/Model/User/Queries/TroopsByStateQuery.php @@ -0,0 +1,17 @@ +troopState; + } +} diff --git a/app/Model/User/Repositories/TroopRepository.php b/app/Model/User/Repositories/TroopRepository.php index 5ca5ecc1d..a60e90dc3 100644 --- a/app/Model/User/Repositories/TroopRepository.php +++ b/app/Model/User/Repositories/TroopRepository.php @@ -40,6 +40,14 @@ public function findAll(): Collection return new ArrayCollection($result); } + /** + * @return Collection + */ + public function findByState(string $state): Collection + { + return $this->getRepository()->matching(Criteria::create()->where(Criteria::expr()->eq('state', $state))); + } + /** * @throws NonUniqueResultException */ diff --git a/app/Services/ExcelExportService.php b/app/Services/ExcelExportService.php index 7104e6e23..c0990c1e1 100644 --- a/app/Services/ExcelExportService.php +++ b/app/Services/ExcelExportService.php @@ -32,6 +32,10 @@ use function implode; use function preg_replace; +use function str_pad; +use function substr; + +use const STR_PAD_LEFT; /** * Služba pro export do formátu XLSX. @@ -686,6 +690,243 @@ public function exportTroopsList(Collection $troops, string $filename): ExcelRes return new ExcelResponse($this->spreadsheet, $filename); } + /** + * @param Troop[] $troops + */ + public function exportNsjAttendees($troops, string $filename): ExcelResponse + { + $sheet = $this->spreadsheet->getSheet(0); + + $row = 1; + $column = 1; + + $sheet->setCellValue([$column, $row], $this->translator->translate('Jméno')); + $sheet->getStyle([$column, $row])->getFont()->setBold(true); + $sheet->getColumnDimensionByColumn($column)->setAutoSize(false); + $sheet->getColumnDimensionByColumn($column++)->setWidth(20); + + $sheet->setCellValue([$column, $row], $this->translator->translate('Příjmení')); + $sheet->getStyle([$column, $row])->getFont()->setBold(true); + $sheet->getColumnDimensionByColumn($column)->setAutoSize(false); + $sheet->getColumnDimensionByColumn($column++)->setWidth(20); + + $sheet->setCellValue([$column, $row], $this->translator->translate('Přezdívka')); + $sheet->getStyle([$column, $row])->getFont()->setBold(true); + $sheet->getColumnDimensionByColumn($column)->setAutoSize(false); + $sheet->getColumnDimensionByColumn($column++)->setWidth(20); + + $sheet->setCellValue([$column, $row], $this->translator->translate('Datum narození')); + $sheet->getStyle([$column, $row])->getFont()->setBold(true); + $sheet->getColumnDimensionByColumn($column)->setAutoSize(false); + $sheet->getColumnDimensionByColumn($column++)->setWidth(20); + + $sheet->setCellValue([$column, $row], $this->translator->translate('Adresa')); + $sheet->getStyle([$column, $row])->getFont()->setBold(true); + $sheet->getColumnDimensionByColumn($column)->setAutoSize(false); + $sheet->getColumnDimensionByColumn($column++)->setWidth(50); + + $sheet->setCellValue([$column, $row], $this->translator->translate('E-mail')); + $sheet->getStyle([$column, $row])->getFont()->setBold(true); + $sheet->getColumnDimensionByColumn($column)->setAutoSize(false); + $sheet->getColumnDimensionByColumn($column++)->setWidth(30); + + $sheet->setCellValue([$column, $row], $this->translator->translate('Telefon')); + $sheet->getStyle([$column, $row])->getFont()->setBold(true); + $sheet->getColumnDimensionByColumn($column)->setAutoSize(false); + $sheet->getColumnDimensionByColumn($column++)->setWidth(15); + + $sheet->setCellValue([$column, $row], $this->translator->translate('Telefon matky')); + $sheet->getStyle([$column, $row])->getFont()->setBold(true); + $sheet->getColumnDimensionByColumn($column)->setAutoSize(false); + $sheet->getColumnDimensionByColumn($column++)->setWidth(15); + + $sheet->setCellValue([$column, $row], $this->translator->translate('Telefon otce')); + $sheet->getStyle([$column, $row])->getFont()->setBold(true); + $sheet->getColumnDimensionByColumn($column)->setAutoSize(false); + $sheet->getColumnDimensionByColumn($column++)->setWidth(15); + + $sheet->setCellValue([$column, $row], $this->translator->translate('O mně')); + $sheet->getStyle([$column, $row])->getFont()->setBold(true); + $sheet->getColumnDimensionByColumn($column)->setAutoSize(false); + $sheet->getColumnDimensionByColumn($column++)->setWidth(20); + + $sheet->setCellValue([$column, $row], $this->translator->translate('Poznámka')); + $sheet->getStyle([$column, $row])->getFont()->setBold(true); + $sheet->getColumnDimensionByColumn($column)->setAutoSize(false); + $sheet->getColumnDimensionByColumn($column++)->setWidth(20); + + $sheet->setCellValue([$column, $row], $this->translator->translate('Zdravotní informace')); + $sheet->getStyle([$column, $row])->getFont()->setBold(true); + $sheet->getColumnDimensionByColumn($column)->setAutoSize(false); + $sheet->getColumnDimensionByColumn($column++)->setWidth(50); + + $sheet->setCellValue([$column, $row], $this->translator->translate('Role')); + $sheet->getStyle([$column, $row])->getFont()->setBold(true); + $sheet->getColumnDimensionByColumn($column)->setAutoSize(false); + $sheet->getColumnDimensionByColumn($column++)->setWidth(15); + + $sheet->setCellValue([$column, $row], $this->translator->translate('Skupina')); + $sheet->getStyle([$column, $row])->getFont()->setBold(true); + $sheet->getColumnDimensionByColumn($column)->setAutoSize(false); + $sheet->getColumnDimensionByColumn($column++)->setWidth(15); + + $sheet->setCellValue([$column, $row], $this->translator->translate('Družina')); + $sheet->getStyle([$column, $row])->getFont()->setBold(true); + $sheet->getColumnDimensionByColumn($column)->setAutoSize(false); + $sheet->getColumnDimensionByColumn($column++)->setWidth(15); + + $sheet->setCellValue([$column, $row], $this->translator->translate('Kód')); + $sheet->getStyle([$column, $row])->getFont()->setBold(true); + $sheet->getColumnDimensionByColumn($column)->setAutoSize(false); + $sheet->getColumnDimensionByColumn($column++)->setWidth(15); + + foreach ($troops as $troop) { + $i = 0; + + foreach ($troop->getUsersRoles() as $usersRole) { + $user = $usersRole->getUser(); + $i++; + + $row++; + $column = 1; + + $sheet->setCellValue([$column++, $row], $user->getFirstName()); + $sheet->setCellValue([$column++, $row], $user->getLastName()); + $sheet->setCellValue([$column++, $row], $user->getNickName()); + $sheet->setCellValue([$column++, $row], $user->getBirthdate()->format(Helpers::DATE_FORMAT)); + $sheet->setCellValue([$column++, $row], $user->getAddress()); + $sheet->setCellValue([$column++, $row], $user->getEmail()); + $sheet->setCellValue([$column++, $row], $user->getPhone()); + $sheet->setCellValue([$column++, $row], $user->getMotherPhone()); + $sheet->setCellValue([$column++, $row], $user->getFatherPhone()); + $sheet->setCellValue([$column++, $row], $user->getAbout()); + $sheet->setCellValue([$column++, $row], $user->getNote()); + $sheet->setCellValue([$column++, $row], $user->getHealthInfo()); + $sheet->setCellValue([$column++, $row], $usersRole->getRole()->getName()); + $sheet->setCellValue([$column++, $row], $troop->getName()); + $sheet->setCellValue([$column++, $row], ''); + + $code = substr($troop->getVariableSymbolText(), -4) . '-00-' + . str_pad((string) $i, 2, '0', STR_PAD_LEFT); + $sheet->setCellValue([$column++, $row], $code); + } + + foreach ($troop->getConfirmedPatrols() as $patrol) { + $i = 0; + + foreach ($patrol->getUsersRoles() as $usersRole) { + $user = $usersRole->getUser(); + $i++; + + $row++; + $column = 1; + + $sheet->setCellValue([$column++, $row], $user->getFirstName()); + $sheet->setCellValue([$column++, $row], $user->getLastName()); + $sheet->setCellValue([$column++, $row], $user->getNickName()); + $sheet->setCellValue([$column++, $row], $user->getBirthdate()->format(Helpers::DATE_FORMAT)); + $sheet->setCellValue([$column++, $row], $user->getAddress()); + $sheet->setCellValue([$column++, $row], $user->getEmail()); + $sheet->setCellValue([$column++, $row], $user->getPhone()); + $sheet->setCellValue([$column++, $row], $user->getMotherPhone()); + $sheet->setCellValue([$column++, $row], $user->getFatherPhone()); + $sheet->setCellValue([$column++, $row], $user->getAbout()); + $sheet->setCellValue([$column++, $row], $user->getNote()); + $sheet->setCellValue([$column++, $row], $user->getHealthInfo()); + $sheet->setCellValue([$column++, $row], $usersRole->getRole()->getName()); + $sheet->setCellValue([$column++, $row], $troop->getName()); + $sheet->setCellValue([$column++, $row], $patrol->getName()); + + $code = substr($troop->getVariableSymbolText(), -4) . '-' + . substr($patrol->getName(), -2) . '-' + . str_pad((string) $i, 2, '0', STR_PAD_LEFT); + $sheet->setCellValue([$column++, $row], $code); + } + } + } + + return new ExcelResponse($this->spreadsheet, $filename); + } + + /** + * @param User[] $users + */ + public function exportNsjOthers($users, string $filename): ExcelResponse + { + $sheet = $this->spreadsheet->getSheet(0); + + $row = 1; + $column = 1; + + $sheet->setCellValue([$column, $row], $this->translator->translate('Jméno')); + $sheet->getStyle([$column, $row])->getFont()->setBold(true); + $sheet->getColumnDimensionByColumn($column)->setAutoSize(false); + $sheet->getColumnDimensionByColumn($column++)->setWidth(20); + + $sheet->setCellValue([$column, $row], $this->translator->translate('Příjmení')); + $sheet->getStyle([$column, $row])->getFont()->setBold(true); + $sheet->getColumnDimensionByColumn($column)->setAutoSize(false); + $sheet->getColumnDimensionByColumn($column++)->setWidth(20); + + $sheet->setCellValue([$column, $row], $this->translator->translate('Přezdívka')); + $sheet->getStyle([$column, $row])->getFont()->setBold(true); + $sheet->getColumnDimensionByColumn($column)->setAutoSize(false); + $sheet->getColumnDimensionByColumn($column++)->setWidth(20); + + $sheet->setCellValue([$column, $row], $this->translator->translate('Datum narození')); + $sheet->getStyle([$column, $row])->getFont()->setBold(true); + $sheet->getColumnDimensionByColumn($column)->setAutoSize(false); + $sheet->getColumnDimensionByColumn($column++)->setWidth(20); + + $sheet->setCellValue([$column, $row], $this->translator->translate('Adresa')); + $sheet->getStyle([$column, $row])->getFont()->setBold(true); + $sheet->getColumnDimensionByColumn($column)->setAutoSize(false); + $sheet->getColumnDimensionByColumn($column++)->setWidth(50); + + $sheet->setCellValue([$column, $row], $this->translator->translate('E-mail')); + $sheet->getStyle([$column, $row])->getFont()->setBold(true); + $sheet->getColumnDimensionByColumn($column)->setAutoSize(false); + $sheet->getColumnDimensionByColumn($column++)->setWidth(30); + + $sheet->setCellValue([$column, $row], $this->translator->translate('O mně')); + $sheet->getStyle([$column, $row])->getFont()->setBold(true); + $sheet->getColumnDimensionByColumn($column)->setAutoSize(false); + $sheet->getColumnDimensionByColumn($column++)->setWidth(20); + + $sheet->setCellValue([$column, $row], $this->translator->translate('Poznámka')); + $sheet->getStyle([$column, $row])->getFont()->setBold(true); + $sheet->getColumnDimensionByColumn($column)->setAutoSize(false); + $sheet->getColumnDimensionByColumn($column++)->setWidth(20); + + $sheet->setCellValue([$column, $row], $this->translator->translate('Role')); + $sheet->getStyle([$column, $row])->getFont()->setBold(true); + $sheet->getColumnDimensionByColumn($column)->setAutoSize(false); + $sheet->getColumnDimensionByColumn($column++)->setWidth(15); + + $sheet->setCellValue([$column, $row], $this->translator->translate('Kód')); + $sheet->getStyle([$column, $row])->getFont()->setBold(true); + $sheet->getColumnDimensionByColumn($column)->setAutoSize(false); + $sheet->getColumnDimensionByColumn($column++)->setWidth(15); + + foreach ($users as $user) { + $row++; + $column = 1; + + $sheet->setCellValue([$column++, $row], $user->getFirstName()); + $sheet->setCellValue([$column++, $row], $user->getLastName()); + $sheet->setCellValue([$column++, $row], $user->getNickName()); + $sheet->setCellValue([$column++, $row], $user->getBirthdate()->format(Helpers::DATE_FORMAT)); + $sheet->setCellValue([$column++, $row], $user->getAddress()); + $sheet->setCellValue([$column++, $row], $user->getEmail()); + $sheet->setCellValue([$column++, $row], $user->getAbout()); + $sheet->setCellValue([$column++, $row], $user->getNote()); + $sheet->setCellValue([$column++, $row], $user->getRolesText()); + $sheet->setCellValue([$column++, $row], substr($user->getRolesApplication()?->getVariableSymbolText() ?: '', -4)); + } + + return new ExcelResponse($this->spreadsheet, $filename); + } + /** * @param Collection $blocks * From 71ac4bf703a989719d624021d57293a0fe06a789 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Stan=C4=9Bk?= Date: Wed, 12 Apr 2023 19:41:00 +0200 Subject: [PATCH 40/41] =?UTF-8?q?Export=20vlastn=C3=ADch=20pol=C3=AD=20pro?= =?UTF-8?q?=20NSJ=20(#966)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * skip leaders * custom inputs export * rebase fix --- app/Services/ExcelExportService.php | 49 +++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/app/Services/ExcelExportService.php b/app/Services/ExcelExportService.php index c0990c1e1..f47fbcc1f 100644 --- a/app/Services/ExcelExportService.php +++ b/app/Services/ExcelExportService.php @@ -908,6 +908,36 @@ public function exportNsjOthers($users, string $filename): ExcelResponse $sheet->getColumnDimensionByColumn($column)->setAutoSize(false); $sheet->getColumnDimensionByColumn($column++)->setWidth(15); + foreach ($this->customInputRepository->findAllOrderedByPosition() as $customInput) { + switch ($customInput->getType()) { + case CustomInput::TEXT: + case CustomInput::SELECT: + case CustomInput::MULTISELECT: + case CustomInput::DATETIME: + $width = 30; + break; + + case CustomInput::DATE: + $width = 20; + break; + + case CustomInput::CHECKBOX: + $width = 15; + break; + + case CustomInput::FILE: + continue 2; + + default: + throw new InvalidArgumentException(); + } + + $sheet->setCellValue([$column, $row], $this->translator->translate($customInput->getName())); + $sheet->getStyle([$column, $row])->getFont()->setBold(true); + $sheet->getColumnDimensionByColumn($column)->setAutoSize(false); + $sheet->getColumnDimensionByColumn($column++)->setWidth($width); + } + foreach ($users as $user) { $row++; $column = 1; @@ -922,6 +952,25 @@ public function exportNsjOthers($users, string $filename): ExcelResponse $sheet->setCellValue([$column++, $row], $user->getNote()); $sheet->setCellValue([$column++, $row], $user->getRolesText()); $sheet->setCellValue([$column++, $row], substr($user->getRolesApplication()?->getVariableSymbolText() ?: '', -4)); + + foreach ($this->customInputRepository->findAllOrderedByPosition() as $customInput) { + $customInputValue = $user->getCustomInputValue($customInput); + + if ($customInputValue === null) { + $column++; + continue; + } + + if ($customInputValue instanceof CustomCheckboxValue) { + $value = $customInputValue->getValue() + ? $this->translator->translate('common.export.common.yes') + : $this->translator->translate('common.export.common.no'); + } else { + $value = $customInputValue->getValueText(); + } + + $sheet->setCellValue([$column++, $row], $value); + } } return new ExcelResponse($this->spreadsheet, $filename); From 3c7d3a430db1dc55377d36b56711656b58f39875 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Stan=C4=9Bk?= Date: Thu, 13 Apr 2023 19:09:16 +0200 Subject: [PATCH 41/41] =?UTF-8?q?Export=20skupin=20pro=20NSJ,=20deduplikac?= =?UTF-8?q?e=20exportu=20u=C5=BEivatel=C5=AF=20(#967)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * export group skautis ids * ignore phpstan bug * ignore phpstan bug --- .../Components/TroopsGridControl.php | 15 ++++ app/Services/ExcelExportService.php | 85 +++++++++++++++++-- phpstan.neon | 3 + 3 files changed, 98 insertions(+), 5 deletions(-) diff --git a/app/AdminModule/UsersModule/Components/TroopsGridControl.php b/app/AdminModule/UsersModule/Components/TroopsGridControl.php index ce4f71e96..bdc1bffb4 100644 --- a/app/AdminModule/UsersModule/Components/TroopsGridControl.php +++ b/app/AdminModule/UsersModule/Components/TroopsGridControl.php @@ -5,11 +5,14 @@ namespace App\AdminModule\UsersModule\Components; use App\Model\Acl\Role; +use App\Model\Enums\TroopApplicationState; use App\Model\User\Commands\RemoveTroop; +use App\Model\User\Queries\TroopsByStateQuery; use App\Model\User\Repositories\TroopRepository; use App\Model\User\Troop; use App\Services\CommandBus; use App\Services\ExcelExportService; +use App\Services\QueryBus; use App\Utils\Helpers; use Doctrine\ORM\QueryBuilder; use Exception; @@ -34,6 +37,7 @@ class TroopsGridControl extends Control private SessionSection $sessionSection; public function __construct( + private QueryBus $queryBus, private CommandBus $commandBus, private Translator $translator, private TroopRepository $troopRepository, @@ -72,6 +76,8 @@ public function createComponentPatrolsGrid(string $name): DataGrid $grid->addGroupAction('Export seznamu skupin') ->onSelect[] = [$this, 'groupExportTroops']; + $grid->addToolbarButton('exportNsjTroops', 'Export NSJ - skupiny'); + $grid->addColumnText('name', 'Název') ->setSortable() ->setFilterText(); @@ -217,4 +223,13 @@ public function handleExportTroops(): void $this->getPresenter()->sendResponse($response); } + + public function handleExportNsjTroops(): void + { + $troops = $this->queryBus->handle(new TroopsByStateQuery(TroopApplicationState::PAID)); + + $response = $this->excelExportService->exportNsjTroops($troops, 'nsj-skupiny.xlsx'); + + $this->getPresenter()->sendResponse($response); + } } diff --git a/app/Services/ExcelExportService.php b/app/Services/ExcelExportService.php index f47fbcc1f..6557dfd98 100644 --- a/app/Services/ExcelExportService.php +++ b/app/Services/ExcelExportService.php @@ -690,6 +690,51 @@ public function exportTroopsList(Collection $troops, string $filename): ExcelRes return new ExcelResponse($this->spreadsheet, $filename); } + /** + * @param Collection $troops + * + * @throws Exception + */ + public function exportNsjTroops(Collection $troops, string $filename): ExcelResponse + { + $sheet = $this->spreadsheet->getSheet(0); + + $row = 1; + $column = 1; + + $sheet->setCellValue([$column, $row], $this->translator->translate('Název')); + $sheet->getStyle([$column, $row])->getFont()->setBold(true); + $sheet->getColumnDimensionByColumn($column)->setAutoSize(false); + $sheet->getColumnDimensionByColumn($column++)->setWidth(20); + + $sheet->setCellValue([$column, $row], $this->translator->translate('Vedoucí - jméno')); + $sheet->getStyle([$column, $row])->getFont()->setBold(true); + $sheet->getColumnDimensionByColumn($column)->setAutoSize(false); + $sheet->getColumnDimensionByColumn($column++)->setWidth(30); + + $sheet->setCellValue([$column, $row], $this->translator->translate('Vedoucí - userId')); + $sheet->getStyle([$column, $row])->getFont()->setBold(true); + $sheet->getColumnDimensionByColumn($column)->setAutoSize(false); + $sheet->getColumnDimensionByColumn($column++)->setWidth(20); + + $sheet->setCellValue([$column, $row], $this->translator->translate('Vedoucí - personId')); + $sheet->getStyle([$column, $row])->getFont()->setBold(true); + $sheet->getColumnDimensionByColumn($column)->setAutoSize(false); + $sheet->getColumnDimensionByColumn($column++)->setWidth(20); + + foreach ($troops as $troop) { + $row++; + $column = 1; + + $sheet->setCellValue([$column++, $row], $troop->getName()); + $sheet->setCellValue([$column++, $row], $troop->getLeader()->getDisplayName()); + $sheet->setCellValue([$column++, $row], $troop->getLeader()->getSkautISUserId()); + $sheet->setCellValue([$column++, $row], $troop->getLeader()->getSkautISPersonId()); + } + + return new ExcelResponse($this->spreadsheet, $filename); + } + /** * @param Troop[] $troops */ @@ -811,6 +856,22 @@ public function exportNsjAttendees($troops, string $filename): ExcelResponse $sheet->setCellValue([$column++, $row], $code); } + $allUsers = new ArrayCollection(); + $duplicitUsers = new ArrayCollection(); + $exportedUsers = new ArrayCollection(); + + foreach ($troop->getConfirmedPatrols() as $patrol) { + foreach ($patrol->getUsersRoles() as $usersRole) { + $userId = $usersRole->getUser()->getId(); + + if ($allUsers->contains($userId)) { + $duplicitUsers->add($userId); + } else { + $allUsers->add($userId); + } + } + } + foreach ($troop->getConfirmedPatrols() as $patrol) { $i = 0; @@ -818,6 +879,12 @@ public function exportNsjAttendees($troops, string $filename): ExcelResponse $user = $usersRole->getUser(); $i++; + if ($exportedUsers->contains($user->getId())) { + continue; + } + + $exportedUsers->add($user->getId()); + $row++; $column = 1; @@ -835,12 +902,20 @@ public function exportNsjAttendees($troops, string $filename): ExcelResponse $sheet->setCellValue([$column++, $row], $user->getHealthInfo()); $sheet->setCellValue([$column++, $row], $usersRole->getRole()->getName()); $sheet->setCellValue([$column++, $row], $troop->getName()); - $sheet->setCellValue([$column++, $row], $patrol->getName()); - $code = substr($troop->getVariableSymbolText(), -4) . '-' - . substr($patrol->getName(), -2) . '-' - . str_pad((string) $i, 2, '0', STR_PAD_LEFT); - $sheet->setCellValue([$column++, $row], $code); + if ($duplicitUsers->contains($user->getId())) { + $sheet->setCellValue([$column++, $row], ''); + + $code = substr($troop->getVariableSymbolText(), -4) . '-00-00'; + $sheet->setCellValue([$column++, $row], $code); + } else { + $sheet->setCellValue([$column++, $row], $patrol->getName()); + + $code = substr($troop->getVariableSymbolText(), -4) . '-' + . substr($patrol->getName(), -2) . '-' + . str_pad((string) $i, 2, '0', STR_PAD_LEFT); + $sheet->setCellValue([$column++, $row], $code); + } } } } diff --git a/phpstan.neon b/phpstan.neon index 333e1067c..6ca9b53a3 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -14,6 +14,9 @@ parameters: - message: '#^Parameter \#1 \$translator of method Ublaboo\\DataGrid\\DataGrid::setTranslator\(\) expects Nette\\Localization\\ITranslator, Nette\\Localization\\Translator given.$#' path: app/*/*GridControl.php + - + message: '#^If condition is always false.$#' + path: app/Services/ExcelExportService.php services: - class: CodeQuality\ObjectIdentityComparisonRule