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
new file mode 100644
index 000000000..9a00798a8
--- /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-22.04
+ container:
+ image: skaut/lebeda:8.1
+ 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_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@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
+ 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') }}
+ 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
+ run: echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT
+ - uses: actions/cache@v3
+ with:
+ path: ${{ steps.yarn-cache.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
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 77a8ef8ed..7914ed50b 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 }}
@@ -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:
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/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/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/AdminModule/PaymentsModule/Components/PaymentsGridControl.php b/app/AdminModule/PaymentsModule/Components/PaymentsGridControl.php
index 1a6023f56..b78ab0618 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())
@@ -173,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/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/AdminModule/Presenters/templates/Dashboard/default.latte b/app/AdminModule/Presenters/templates/Dashboard/default.latte
index 3ed3bb43b..eb825a122 100644
--- a/app/AdminModule/Presenters/templates/Dashboard/default.latte
+++ b/app/AdminModule/Presenters/templates/Dashboard/default.latte
@@ -67,8 +67,18 @@
{_admin.menu.users}
@@ -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..6ef9b4c92
--- /dev/null
+++ b/app/AdminModule/Presenters/templates/Users/groups.latte
@@ -0,0 +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
new file mode 100644
index 000000000..38663b633
--- /dev/null
+++ b/app/AdminModule/Presenters/templates/Users/patrols.latte
@@ -0,0 +1,7 @@
+{block main}
+ Družiny
+
+ {control patrolsGrid}
+{/block}
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/UsersModule/Components/IPatrolsGridControlFactory.php b/app/AdminModule/UsersModule/Components/IPatrolsGridControlFactory.php
new file mode 100644
index 000000000..9c77a6562
--- /dev/null
+++ b/app/AdminModule/UsersModule/Components/IPatrolsGridControlFactory.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->patrolRepository->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, 'groupExportPatrols'];
+
+ $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) : '';
+ });
+
+ $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('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');
+ }
+
+ /**
+ * 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
new file mode 100644
index 000000000..bdc1bffb4
--- /dev/null
+++ b/app/AdminModule/UsersModule/Components/TroopsGridControl.php
@@ -0,0 +1,235 @@
+sessionSection = $session->getSection('srs');
+ }
+
+ /**
+ * Vykreslí komponentu.
+ */
+ public function render(): void
+ {
+ $this->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->troopRepository->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, 'groupExportTroops'];
+
+ $grid->addToolbarButton('exportNsjTroops', 'Export NSJ - skupiny');
+
+ $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->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 $t) {
+ $date = $t->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('Opravdu chcete skupinu odstranit?'),
+ ]);
+
+ return $grid;
+ }
+
+ /**
+ * 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.
+ *
+ * @throws AbortException
+ */
+ 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);
+ }
+
+ 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/AdminModule/Components/UsersGridControl.php b/app/AdminModule/UsersModule/Components/UsersGridControl.php
similarity index 91%
rename from app/AdminModule/Components/UsersGridControl.php
rename to app/AdminModule/UsersModule/Components/UsersGridControl.php
index 1d8181ac8..1157aea43 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;
@@ -19,9 +19,11 @@
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;
+use App\Model\User\Queries\TroopsByStateQuery;
use App\Model\User\Repositories\UserRepository;
use App\Model\User\User;
use App\Services\AclService;
@@ -30,6 +32,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 +49,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 +76,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 +155,13 @@ 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->addToolbarButton('exportNsjAttendees', 'Export NSJ - účastníci');
+
+ $grid->addToolbarButton('exportNsjOthers', 'Export NSJ - ostatní');
+
$grid->addColumnText('displayName', 'admin.users.users_name')
->setSortable()
->setFilterText();
@@ -166,6 +178,20 @@ public function createComponentUsersGrid(string $name): DataGrid
->setParameter('rids', (array) $values);
});
+ $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())
->setCondition(static function (QueryBuilder $qb, ArrayHash $values): void {
@@ -712,6 +738,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ů.
*
@@ -820,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/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/UsersModule/Components/templates/patrols_grid.latte b/app/AdminModule/UsersModule/Components/templates/patrols_grid.latte
new file mode 100644
index 000000000..3f5cf26a1
--- /dev/null
+++ b/app/AdminModule/UsersModule/Components/templates/patrols_grid.latte
@@ -0,0 +1 @@
+{control patrolsGrid}
diff --git a/app/AdminModule/UsersModule/Components/templates/troops_grid.latte b/app/AdminModule/UsersModule/Components/templates/troops_grid.latte
new file mode 100644
index 000000000..3f5cf26a1
--- /dev/null
+++ b/app/AdminModule/UsersModule/Components/templates/troops_grid.latte
@@ -0,0 +1 @@
+{control patrolsGrid}
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 90%
rename from app/AdminModule/Presenters/UsersPresenter.php
rename to app/AdminModule/UsersModule/Presenters/UsersPresenter.php
index 155c8080a..eba13dff6 100644
--- a/app/AdminModule/Presenters/UsersPresenter.php
+++ b/app/AdminModule/UsersModule/Presenters/UsersPresenter.php
@@ -2,16 +2,15 @@
declare(strict_types=1);
-namespace App\AdminModule\Presenters;
-
-use App\AdminModule\Components\ApplicationsGridControl;
-use App\AdminModule\Components\IApplicationsGridControlFactory;
-use App\AdminModule\Components\IUsersGridControlFactory;
-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;
@@ -20,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;
@@ -30,7 +28,7 @@
/**
* Presenter obsluhující správu uživatelů.
*/
-class UsersPresenter extends AdminBasePresenter
+class UsersPresenter extends UsersBasePresenter
{
protected string $resource = SrsResource::USERS;
@@ -49,9 +47,6 @@ class UsersPresenter extends AdminBasePresenter
#[Inject]
public IApplicationsGridControlFactory $applicationsGridControlFactory;
- #[Inject]
- public ExcelExportService $excelExportService;
-
#[Inject]
public CustomInputRepository $customInputRepository;
@@ -65,8 +60,6 @@ public function startup(): void
{
parent::startup();
- $this->checkPermission(Permission::MANAGE);
-
$this->template->results = [];
$this->template->editPersonalDetails = false;
$this->template->editSeminar = false;
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
+
+
+ Seznam členů
+
+
+
+ Jméno |
+ Role |
+ Datum narození |
+ Zdravotní údaje |
+
+
+
+
+
+
+ {$userRole->getUser()->getDisplayName()}
+
+ |
+ {$userRole->getRole()->getName()} |
+ {$userRole->getUser()->getBirthdate()|date:"j. n. Y"} |
+ {$userRole->getUser()->getHealthInfo()} |
+
+
+
+
+
+
Družina: {$patrol->getName()}
+
+
+
+ Jméno |
+ Role |
+ Datum 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/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/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..40f79d66c
--- /dev/null
+++ b/app/ExportModule/Presenters/templates/TroopIncomeProof/pdf.latte
@@ -0,0 +1,56 @@
+{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()}),
+ skupina: {$troop->getName()}
+
+
+
+
+
+ Zaplaceno z účtu: {$troop->getPayment()->getAccountNumber()}, variabilní symbol: {$troop->getPayment()->getVariableSymbol()}
+
+
+
+
+
+ {_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/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 099aca455..d9b1a28a7 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;
@@ -67,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,
@@ -77,6 +84,9 @@ class Role
self::LECTOR,
self::ORGANIZER,
self::ADMIN,
+ self::PATROL_LEADER,
+ self::LEADER,
+ self::ESCORT,
];
#[ORM\Id]
#[ORM\GeneratedValue]
@@ -125,6 +135,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 +190,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 +409,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 +511,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/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/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 @@
+
+ */
+ #[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,21 @@ public function getPairedValidApplicationsText(): string
return implode(', ', $usersTexts);
}
+ /**
+ * @return Collection
+ */
+ 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/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/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/Model/User/Commands/ConfirmPatrol.php b/app/Model/User/Commands/ConfirmPatrol.php
new file mode 100644
index 000000000..386bd06f5
--- /dev/null
+++ b/app/Model/User/Commands/ConfirmPatrol.php
@@ -0,0 +1,18 @@
+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..854d39b3c
--- /dev/null
+++ b/app/Model/User/Commands/Handlers/ConfirmTroopHandler.php
@@ -0,0 +1,85 @@
+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);
+
+ $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->getVariableSymbolText(),
+ TemplateVariable::APPLICATION_MATURITY => $troop->getMaturityDateText(),
+ TemplateVariable::BANK_ACCOUNT => $this->queryBus->handle(
+ new SettingStringValueQuery(Settings::ACCOUNT_NUMBER)
+ ),
+ ]);
+ }
+
+ /**
+ * 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/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..4afc11981
--- /dev/null
+++ b/app/Model/User/Commands/Handlers/RemoveTroopHandler.php
@@ -0,0 +1,47 @@
+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);
+ }
+ }
+
+ $command->getTroop()->getLeader()->setTroop(null);
+
+ $this->troopRepository->remove($command->getTroop());
+ });
+ }
+}
diff --git a/app/Model/User/Commands/Handlers/UpdateGroupMembersHandler.php b/app/Model/User/Commands/Handlers/UpdateGroupMembersHandler.php
new file mode 100644
index 000000000..17d5ad9fb
--- /dev/null
+++ b/app/Model/User/Commands/Handlers/UpdateGroupMembersHandler.php
@@ -0,0 +1,158 @@
+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);
+ }
+ }
+
+ 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/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/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/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
new file mode 100644
index 000000000..16104ed36
--- /dev/null
+++ b/app/Model/User/Patrol.php
@@ -0,0 +1,106 @@
+
+ */
+ #[ORM\OneToMany(mappedBy: 'patrol', targetEntity: UserGroupRole::class, cascade: ['persist'])]
+ protected Collection $usersRoles;
+
+ /**
+ * 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
+ {
+ return $this->id;
+ }
+
+ public function getName(): string
+ {
+ return $this->name;
+ }
+
+ public function getTroop(): Troop
+ {
+ return $this->troop;
+ }
+
+ /**
+ * @return Collection
+ */
+ public function getUsersRoles(): Collection
+ {
+ return $this->usersRoles;
+ }
+
+ public function isConfirmed(): bool
+ {
+ return $this->confirmed;
+ }
+
+ public function setConfirmed(bool $confirmed): void
+ {
+ $this->confirmed = $confirmed;
+ }
+
+ /**
+ * @param string[] $roleNames
+ */
+ public function countUsersInRoles(array $roleNames): int
+ {
+ $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/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/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/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/PatrolRepository.php b/app/Model/User/Repositories/PatrolRepository.php
new file mode 100644
index 000000000..f7b8be640
--- /dev/null
+++ b/app/Model/User/Repositories/PatrolRepository.php
@@ -0,0 +1,57 @@
+getRepository()->findOneBy(['id' => $id]);
+ }
+
+ 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);
+ $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
new file mode 100644
index 000000000..a60e90dc3
--- /dev/null
+++ b/app/Model/User/Repositories/TroopRepository.php
@@ -0,0 +1,149 @@
+getRepository()->findOneBy(['id' => $id]);
+ }
+
+ /**
+ * @return Collection
+ */
+ public function findAll(): Collection
+ {
+ $result = $this->getRepository()->findAll();
+
+ 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
+ */
+ public function findByLeaderId(int $leaderId): ?Troop
+ {
+ return $this->getRepository()
+ ->createQueryBuilder('t')
+ ->where('t.leader = :leader_id')
+ ->setParameter('leader_id', $leaderId)
+ ->getQuery()
+ ->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);
+ $this->em->flush();
+ }
+
+ public function remove(Troop $troop): void
+ {
+ $this->em->remove($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
new file mode 100644
index 000000000..43e2faa37
--- /dev/null
+++ b/app/Model/User/Troop.php
@@ -0,0 +1,389 @@
+
+ */
+ #[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', nullable: true)]
+ 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;
+
+ /**
+ * Vedoucí oddílu.
+ */
+ #[ORM\OneToOne(targetEntity: User::class, inversedBy: 'troop', cascade: ['persist'])]
+ protected User $leader;
+
+ /**
+ * 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
+ {
+ 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;
+ }
+
+ /**
+ * 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;
+ }
+
+ 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;
+ }
+
+ public function setApplicationDate(?DateTimeImmutable $applicationDate): void
+ {
+ $this->applicationDate = $applicationDate;
+ }
+
+ 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;
+ }
+
+ 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;
+ }
+
+ 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..45fe0e512 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;
@@ -294,6 +295,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 +349,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 +1169,98 @@ public function isAlternate(Program $program): bool
)
)->isEmpty();
}
+
+ public function getTroop(): ?Troop
+ {
+ return $this->troop;
+ }
+
+ public function setTroop(?Troop $troop): void
+ {
+ $this->troop = $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;
+ }
+
+ /**
+ * 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/Model/User/UserGroupRole.php b/app/Model/User/UserGroupRole.php
new file mode 100644
index 000000000..fc3444b3f
--- /dev/null
+++ b/app/Model/User/UserGroupRole.php
@@ -0,0 +1,98 @@
+user = $user;
+ $this->role = $role;
+ $this->patrol = $patrol;
+ $this->troop = $troop;
+ }
+
+ public function getId(): ?int
+ {
+ return $this->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/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/Services/ApplicationService.php b/app/Services/ApplicationService.php
index ae6d41f56..39140af44 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é.
*
@@ -562,32 +612,57 @@ public function createPayment(
$payment->setAccountName($accountName);
$payment->setMessage($message);
- $pairedApplication = $this->applicationRepository->findValidByVariableSymbol($variableSymbol);
+ $this->pairPayment($payment, $createdBy);
+ });
+ }
+
+ 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 ($pairedApplication) {
+ 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 {
$payment->setState(PaymentState::NOT_PAIRED_VS);
}
+ }
- $this->paymentRepository->save($payment);
- });
+ $this->paymentRepository->save($payment);
}
/**
@@ -608,6 +683,7 @@ public function createPaymentManual(
* Aktualizuje platbu a stav spárovaných přihlášek.
*
* @param Collection $pairedApplications
+ * @param Collection $pairedTroops
*
* @throws Throwable
*/
@@ -617,9 +693,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 +730,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 +773,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 +1101,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/Services/Authenticator.php b/app/Services/Authenticator.php
index cfcba4643..862e46fe6 100644
--- a/app/Services/Authenticator.php
+++ b/app/Services/Authenticator.php
@@ -53,7 +53,8 @@ public function authenticate(string $user, string $password): SimpleIdentity
$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;
@@ -107,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) {
diff --git a/app/Services/ExcelExportService.php b/app/Services/ExcelExportService.php
index 47006f208..6557dfd98 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;
@@ -30,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.
@@ -244,83 +250,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 +359,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 +374,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 +428,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);
@@ -513,6 +524,533 @@ 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 $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
+ */
+ 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);
+ }
+
+ $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;
+
+ foreach ($patrol->getUsersRoles() as $usersRole) {
+ $user = $usersRole->getUser();
+ $i++;
+
+ if ($exportedUsers->contains($user->getId())) {
+ continue;
+ }
+
+ $exportedUsers->add($user->getId());
+
+ $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());
+
+ 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);
+ }
+ }
+ }
+ }
+
+ 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 ($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;
+
+ $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));
+
+ 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);
+ }
+
/**
* @param Collection $blocks
*
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/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/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;
+
+ $this->template->accountNumber = $this->queryBus->handle(new SettingStringValueQuery(Settings::ACCOUNT_NUMBER));
+
+ $step = $this->getPresenter()->getParameter('step');
+ $template->step = $step;
+
+ if ($user->isLoggedIn()) {
+ $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;
+
+ $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]);
+ };
+
+ $form->onRemoveAll[] = function (): void {
+ $p = $this->getPresenter();
+ $p->redirect('this');
+ };
+
+ 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');
+ }
+
+ /**
+ * 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
new file mode 100644
index 000000000..7b0a7d94d
--- /dev/null
+++ b/app/WebModule/Components/templates/troop_application_content.latte
@@ -0,0 +1,234 @@
+
+
+
+
+
+ {if $guestRole}
+
+ {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).
+ {elseif $step === 'members'}
+ {control groupMembersForm}
+ {elseif $step === 'additional_info'}
+ {control groupAdditionalInfoForm}
+ {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.
+
+
+ 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.
+
+
+
+
+
+ {if $registrationAllowed || $troop->getState() !== 'draft'}
+
+
+
Skupina
+
+
+
+
+
+
+
+
+ {foreach $troop->getConfirmedPatrols() as $patrol}
+
+
+
{$patrol->getName()}
+
+
+
+
+
+
+
+ Jméno |
+ Role |
+
+ {foreach $patrol->getUsersRoles() as $userRole}
+
+ {$userRole->getUser()->getDisplayName()} |
+ {$userRole->getRole()->getName()} |
+
+ {/foreach}
+
+
+
+ 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()}
+
+
+
+
+ Jméno |
+ Role |
+
+ {foreach $troop->getUsersRoles() as $userRole}
+
+ {$userRole->getUser()->getDisplayName()} |
+ {$userRole->getRole()->getName()} |
+
+ {/foreach}
+
+
+
+ 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}
+
+
+ {else}
+
+
+
+ Registrace nové skupiny není v tuto chvíli povolena.
+
+
+
+ {/if}
+ {/if}
+
diff --git a/app/WebModule/Forms/ApplicationFormFactory.php b/app/WebModule/Forms/ApplicationFormFactory.php
index 2e4a1f122..f058d8cc2 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.
@@ -435,7 +434,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 +481,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 +507,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/GroupAdditionalInfoForm.php b/app/WebModule/Forms/GroupAdditionalInfoForm.php
new file mode 100644
index 000000000..5bcb7e062
--- /dev/null
+++ b/app/WebModule/Forms/GroupAdditionalInfoForm.php
@@ -0,0 +1,147 @@
+template->setFile(__DIR__ . '/templates/group_additional_info_form.latte');
+
+ $this->resolveUsersRoles();
+
+ $this->template->type = $this->type;
+ $this->template->patrolId = $this->patrolId;
+ $this->template->patrolName = $this->patrolName;
+ $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;
+
+ $this->patrolName = $patrol->getName();
+ } 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..10aab7ce1
--- /dev/null
+++ b/app/WebModule/Forms/GroupConfirmForm.php
@@ -0,0 +1,125 @@
+template->setFile(__DIR__ . '/templates/group_confirm_form.latte');
+
+ $this->resolveUsersRoles();
+
+ $this->template->type = $this->type;
+ $this->template->patrolName = $this->patrolName;
+ $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();
+ }
+
+ $this->patrolName = $patrol->getName();
+ } 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..18d0c2261
--- /dev/null
+++ b/app/WebModule/Forms/GroupMembersForm.php
@@ -0,0 +1,278 @@
+ */
+ private Collection $usersRoles;
+
+ public function __construct(
+ private string $type,
+ private ?int $patrolId,
+ private BaseFormFactory $baseFormFactory,
+ private QueryBus $queryBus,
+ private CommandBus $commandBus,
+ private SkautIsService $skautIsService
+ ) {
+ $this->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->resolveUsersRoles();
+
+ $this->template->type = $this->type;
+ $this->template->patrolName = $this->patrolName;
+ $this->template->units = $this->units;
+ $this->template->members = $this->members;
+
+ $this->template->render();
+ }
+
+ /**
+ * Vytvoří formulář.
+ */
+ public function createComponentForm(): Form
+ {
+ $roles = $this->queryBus->handle(new RolesByTypeQuery($this->type));
+ $roleSelectOptions = $this->getRoleSelectOptions($roles);
+
+ $this->resolveUsersRoles();
+
+ $form = $this->baseFormFactory->create();
+
+ foreach ($this->units as $unit) {
+ foreach ($this->members[$unit->ID] as $member) {
+ $register = false;
+ $role = null;
+
+ if ($this->usersRoles !== null) {
+ foreach ($this->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, sprintf($r->getMinimumAgeWarning() ?: 'Osobě je %2$d, ale musí být min. %1$d let.', $r->getMinimumAge(), $age), $r->getId());
+ } elseif ($age > $r->getMaximumAge()) {
+ $roleSelect
+ ->addConditionOn($registerCheckbox, Form::FILLED)
+ ->addCondition(Form::EQUAL, $r->getId())
+ ->addRule(Form::NOT_EQUAL, sprintf($r->getMaximumAgeWarning() ?: 'Osobě je %2$d, ale musí být max. %1$d let.', $r->getMaximumAge(), $age), $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));
+
+ if (empty($selectedPersons)) {
+ $this->onRemoveAll();
+ }
+
+ $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;
+ }
+
+ private function resolveUsersRoles(): void
+ {
+ $user = $this->queryBus->handle(new UserByIdQuery($this->presenter->user->getId()));
+ $troop = $this->queryBus->handle(new TroopByLeaderQuery($user->getId()));
+ $this->troop = $troop;
+
+ 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) {
+ $this->usersRoles = $patrol->getUsersRoles();
+ $this->patrolId = $patrol->getId();
+ $this->patrolName = $patrol->getName();
+ } else {
+ $this->usersRoles = new ArrayCollection();
+ }
+ } elseif ($this->type === 'troop') {
+ $this->usersRoles = $troop->getUsersRoles();
+ }
+ }
+}
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 @@
+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/WebModule/Forms/TroopApplicationForm.php b/app/WebModule/Forms/TroopApplicationForm.php
new file mode 100644
index 000000000..0bc3a5e75
--- /dev/null
+++ b/app/WebModule/Forms/TroopApplicationForm.php
@@ -0,0 +1,153 @@
+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..585a69a2d
--- /dev/null
+++ b/app/WebModule/Forms/TroopConfirmForm.php
@@ -0,0 +1,141 @@
+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->userLeaderAndEscortError = $this->userLeaderAndEscortError;
+
+ $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', 'Potvrzuji, že jsem si přečetl(a) a souhlasím s podmínkami akce Národní skautské jamboree 2023 a s tím, že v případě porušení těchto podmínek mohu být z akce vyloučen(a) bez náhrady.')
+ ->addRule(Form::FILLED, 'Musíš souhlasit s podmínkami akce.');
+
+ $submit = $form->addSubmit('submit', 'Závazně registrovat')
+ ->setDisabled($this->allCountError || $this->duplicitUsersError || $this->userNotLeaderError || $this->userLeaderAndEscortError);
+
+ 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]);
+ $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;
+
+ $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/group_additional_info_form.latte b/app/WebModule/Forms/templates/group_additional_info_form.latte
new file mode 100644
index 000000000..59e0263df
--- /dev/null
+++ b/app/WebModule/Forms/templates/group_additional_info_form.latte
@@ -0,0 +1,78 @@
+{form form class => form-horizontal}
+
+
+ {if $type === 'patrol'}
+
Úprava družiny {$patrolName}
+ {else}
+ Úprava dospělých průvodců
+ {/if}
+ 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..fad217d0f
--- /dev/null
+++ b/app/WebModule/Forms/templates/group_confirm_form.latte
@@ -0,0 +1,52 @@
+{form form class => form-horizontal}
+
+
+ {if $type === 'patrol'}
+
Úprava družiny {$patrolName}
+ {else}
+ Úprava dospělých průvodců
+ {/if}
+ 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..77774daa6
--- /dev/null
+++ b/app/WebModule/Forms/templates/group_members_form.latte
@@ -0,0 +1,64 @@
+{form form class => form-horizontal}
+
+
+
+
+ {if $type === 'patrol'}
+
Úprava družiny {$patrolName}
+ {else}
+ Úprava dospělých průvodců
+ {/if}
+ 1. Vyber účastníky
+
+
+
+
+
+ {foreach $units as $unit}
+
+
+ Registrovat |
+ Družina |
+ Jméno |
+ Role |
+
+ {foreach $members[$unit->ID] as $member}
+
+ {input 'register_' . $member->ID} |
+ {if $unit->ID !== $member->ID_Unit}{$member->Unit}{/if} |
+ {$member->Person} |
+ {input 'role_' . $member->ID} |
+
+ {else}
+
+ Oddíl nemá žádné členy, které je možné vybrat. |
+
+ {/foreach}
+
+ {/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..7667a6a8c
--- /dev/null
+++ b/app/WebModule/Forms/templates/troop_confirm_form.latte
@@ -0,0 +1,480 @@
+{form form class => form-horizontal}
+
+
+
+
+
+
+
+
+
+
+
+
+ {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/admin.cs_CZ.neon b/app/lang/admin.cs_CZ.neon
index 96be25b97..3e3a601c1 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"
@@ -381,6 +385,11 @@ program:
export_schedule: "Stáhnout harmonogram"
users:
+ menu:
+ persons: "Osoby"
+ troops: "Skupiny"
+ patrols: "Družiny"
+
users_heading: "Uživatelé"
users_name: "Jméno"
users_photo: "Fotka"
@@ -505,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"
@@ -518,7 +533,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?"
@@ -580,6 +596,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/common.cs_CZ.neon b/app/lang/common.cs_CZ.neon
index 187c69e62..824208f48 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"
@@ -117,6 +119,7 @@ application_state:
canceled: "Zrušeno"
paid: "Zaplaceno"
paid_free: "Zaplaceno (zdarma)"
+ draft: "Nepotvrzeno"
calendar_view:
timeGridSeminar: "Na výšku"
@@ -185,12 +188,14 @@ 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"
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/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."
diff --git a/build.xml b/build.xml
index 41ad07f5c..e891c3d1a 100644
--- a/build.xml
+++ b/build.xml
@@ -183,6 +183,11 @@
+
+
+
+
+
diff --git a/migrations/Version20221015133440.php b/migrations/Version20221015133440.php
new file mode 100644
index 000000000..8b36b5d89
--- /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 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)');
+ $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
+ {
+ }
+}
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
+ {
+ }
+}
diff --git a/migrations/Version20221101001721.php b/migrations/Version20221101001721.php
new file mode 100644
index 000000000..692bd520e
--- /dev/null
+++ b/migrations/Version20221101001721.php
@@ -0,0 +1,34 @@
+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\')');
+ $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
+ {
+ }
+}
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
+ {
+ }
+}
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
+ {
+ }
+}
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