diff --git a/public/styles.css b/public/styles.css
index 3f07c5c7..61f2ef25 100755
--- a/public/styles.css
+++ b/public/styles.css
@@ -276,6 +276,13 @@ li.step.is-active::after {
margin-bottom: 1rem;
}
+.form-group-middle {
+ display: flex;
+ flex-flow: column;
+ align-items: center;
+ row-gap: 10px;
+}
+
.form-control {
border: none;
box-shadow: 1px 1px 1px rgba(0, 0, 0, 0.3);
@@ -306,10 +313,18 @@ li.step.is-active::after {
color: var(--color-accent);
}
+.hightlight {
+ color: var(--color-accent);
+}
+
.btn[type=submit] {
width: 100%;
}
+.btn-small[type=submit] {
+ width: inherit;
+}
+
.btn-mini[type=submit] {
width: inherit;
}
diff --git a/src/Application/Route.php b/src/Application/Route.php
index 92d692e1..643f5fa7 100755
--- a/src/Application/Route.php
+++ b/src/Application/Route.php
@@ -7,16 +7,19 @@
use kissj\Event\EventController;
use kissj\Export\ExportController;
use kissj\Middleware\AdminsOnlyMiddleware;
-use kissj\Middleware\CheckPatrolLeaderParticipants;
+use kissj\Middleware\CheckLeaderParticipants;
use kissj\Middleware\ChoosedRoleOnlyMiddleware;
use kissj\Middleware\LoggedOnlyMiddleware;
use kissj\Middleware\NonChoosedRoleOnlyMiddleware;
use kissj\Middleware\NonLoggedOnlyMiddleware;
use kissj\Middleware\OpenStatusOnlyMiddleware;
use kissj\Middleware\PatrolLeadersOnlyMiddleware;
+use kissj\Middleware\TroopLeadersOnlyMiddleware;
+use kissj\Middleware\TroopParticipantsOnlyMiddleware;
use kissj\Participant\Admin\AdminController;
use kissj\Participant\ParticipantController;
use kissj\Participant\Patrol\PatrolController;
+use kissj\Participant\Troop\TroopController;
use kissj\User\UserController;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
@@ -85,7 +88,7 @@ public function addRoutesInto(App $app): App
$app->group('/patrol', function (RouteCollectorProxy $app) {
$app->get('/participant/{participantId}/show', PatrolController::class . '::showParticipant')
- ->setName('p-show');
+ ->setName('p-show'); // TODO check if CheckLeaderParticipants is needed here
$app->group('', function (RouteCollectorProxy $app) {
$app->get('/closeRegistration', PatrolController::class . '::showCloseRegistration')
@@ -115,9 +118,31 @@ public function addRoutesInto(App $app): App
$app->post('/delete', PatrolController::class . '::deleteParticipant')
->setName('p-delete');
- })->add(CheckPatrolLeaderParticipants::class);
+ })->add(CheckLeaderParticipants::class);
})->add(OpenStatusOnlyMiddleware::class);
})->add(PatrolLeadersOnlyMiddleware::class);
+
+ $app->group('/troop', function (RouteCollectorProxy $app) {
+ $app->post('/tieParticipantToTroopByLeader', TroopController::class . '::tieParticipantToTroopByLeader')
+ ->setName('tie-tp-by-tl')
+ ->add(TroopLeadersOnlyMiddleware::class)
+ ->add(OpenStatusOnlyMiddleware::class);
+
+ $app->post('/tieParticipantToTroopByParticipant', TroopController::class . '::tieParticipantToTroopByParticipant')
+ ->setName('tie-tp-by-tp')
+ ->add(TroopParticipantsOnlyMiddleware::class);
+
+ $app->group('/participant/{participantId}', function (RouteCollectorProxy $app) {
+ $app->get('/show', TroopController::class . '::showParticipant')
+ ->setName('tp-show');
+
+ $app->get('/showUntie', TroopController::class . '::showUntieParticipant')
+ ->setName('tp-showUntie');
+
+ $app->post('/untie', TroopController::class . '::untieParticipant')
+ ->setName('tp-untie');
+ })->add(CheckLeaderParticipants::class);
+ });
// TODO refactor for patrols
$app->group('/participant', function (RouteCollectorProxy $app) {
diff --git a/src/FlashMessages/FlashMessagesBySession.php b/src/FlashMessages/FlashMessagesBySession.php
index 37d81542..ef6174ef 100644
--- a/src/FlashMessages/FlashMessagesBySession.php
+++ b/src/FlashMessages/FlashMessagesBySession.php
@@ -4,6 +4,7 @@
namespace kissj\FlashMessages;
+// TODO add translator to this class and make all messages translatable
class FlashMessagesBySession implements FlashMessagesInterface
{
public function info(string $message): void
diff --git a/src/Middleware/CheckLeaderParticipants.php b/src/Middleware/CheckLeaderParticipants.php
new file mode 100755
index 00000000..df5cae1a
--- /dev/null
+++ b/src/Middleware/CheckLeaderParticipants.php
@@ -0,0 +1,71 @@
+getRoute();
+ if ($route === null) {
+ throw new \RuntimeException('Cannot access route in CheckLeaderParticipants middleware');
+ }
+
+ $participantId = (int)$route->getArgument('participantId');
+ $leader = $this->participantRepository->getParticipantFromUser($this->getUser($request));
+
+ if ($leader instanceof PatrolLeader) {
+ if (!$this->patrolService->patrolParticipantBelongsPatrolLeader(
+ $this->patrolService->getPatrolParticipant($participantId),
+ $leader,
+ )) {
+ $this->flashMessages->error($this->translator->trans('flash.error.wrongPatrol'));
+
+ return $this->createRedirectResponse($request, 'dashboard');
+ }
+ } elseif ($leader instanceof TroopLeader) {
+ if (!$this->troopService->troopParticipantBelongsTroopLeader(
+ $this->troopParticipantRepository->get($participantId),
+ $leader,
+ )) {
+ $this->flashMessages->error($this->translator->trans('flash.error.wrongTroop'));
+
+ return $this->createRedirectResponse($request, 'dashboard');
+ }
+ } else {
+ $this->flashMessages->error($this->translator->trans('flash.error.notLeader'));
+
+ return $this->createRedirectResponse($request, 'dashboard');
+ }
+
+ return $handler->handle($request);
+ }
+}
diff --git a/src/Middleware/CheckPatrolLeaderParticipants.php b/src/Middleware/CheckPatrolLeaderParticipants.php
deleted file mode 100755
index 6fe9e810..00000000
--- a/src/Middleware/CheckPatrolLeaderParticipants.php
+++ /dev/null
@@ -1,46 +0,0 @@
-getRoute();
- if ($route === null) {
- throw new \RuntimeException('Cannot access route in CheckPatrolLeaderParticipatns middleware');
- }
-
- $participantId = (int)$route->getArgument('participantId');
- if (!$this->patrolService->patrolParticipantBelongsPatrolLeader(
- $this->patrolService->getPatrolParticipant($participantId),
- $this->patrolService->getPatrolLeader($this->getUser($request)),
- )) {
- $this->flashMessages->error($this->translator->trans('flash.error.wrongPatrol'));
-
- return $this->createRedirectResponse($request, 'dashboard');
- }
-
- return $handler->handle($request);
- }
-}
diff --git a/src/Middleware/PatrolLeadersOnlyMiddleware.php b/src/Middleware/PatrolLeadersOnlyMiddleware.php
index 013fe9bb..4c41347b 100755
--- a/src/Middleware/PatrolLeadersOnlyMiddleware.php
+++ b/src/Middleware/PatrolLeadersOnlyMiddleware.php
@@ -30,7 +30,7 @@ public function process(Request $request, ResponseHandler $handler): Response
) {
$this->flashMessages->error($this->translator->trans('flash.error.plOnly'));
- return $this->createRedirectResponse($request, 'loginAskEmail');
+ return $this->createRedirectResponse($request, 'landing');
}
return $handler->handle($request);
diff --git a/src/Middleware/TroopLeadersOnlyMiddleware.php b/src/Middleware/TroopLeadersOnlyMiddleware.php
new file mode 100755
index 00000000..461c2802
--- /dev/null
+++ b/src/Middleware/TroopLeadersOnlyMiddleware.php
@@ -0,0 +1,38 @@
+getAttribute('user');
+
+ if (
+ $user instanceof User
+ && $this->participantRepository->getParticipantFromUser($user)->role !== ParticipantRole::TroopLeader
+ ) {
+ $this->flashMessages->error($this->translator->trans('flash.error.tlOnly'));
+
+ return $this->createRedirectResponse($request, 'landing');
+ }
+
+ return $handler->handle($request);
+ }
+}
diff --git a/src/Middleware/TroopParticipantsOnlyMiddleware.php b/src/Middleware/TroopParticipantsOnlyMiddleware.php
new file mode 100755
index 00000000..73081e2d
--- /dev/null
+++ b/src/Middleware/TroopParticipantsOnlyMiddleware.php
@@ -0,0 +1,38 @@
+getAttribute('user');
+
+ if (
+ $user instanceof User
+ && $this->participantRepository->getParticipantFromUser($user)->role !== ParticipantRole::TroopParticipant
+ ) {
+ $this->flashMessages->error($this->translator->trans('flash.error.tpOnly'));
+
+ return $this->createRedirectResponse($request, 'landing');
+ }
+
+ return $handler->handle($request);
+ }
+}
diff --git a/src/Participant/Admin/AdminController.php b/src/Participant/Admin/AdminController.php
index 64e6a8c3..6fd31df1 100755
--- a/src/Participant/Admin/AdminController.php
+++ b/src/Participant/Admin/AdminController.php
@@ -274,12 +274,6 @@ public function showPayments(
$event,
$user,
),
- 'approvedTroopParticipants' => $this->participantRepository->getAllParticipantsWithStatus(
- [ParticipantRole::TroopParticipant],
- [UserStatus::Approved],
- $event,
- $user,
- ),
]);
}
@@ -331,8 +325,6 @@ public function confirmPayment(int $paymentId, User $user, Request $request, Res
$this->logger->info('Payment ID ' . $paymentId
. ' cannot be confirmed from admin with event id ' . $user->event->id);
} else {
- $participant->registrationCloseDate = DateTimeUtils::getDateTime();
- $this->participantRepository->persist($participant);
$this->paymentService->confirmPayment($payment);
$this->flashMessages->success($this->translator->trans('flash.success.confirmPayment'));
$this->logger->info('Payment ID ' . $paymentId . ' manually confirmed as paid');
diff --git a/src/Participant/Participant.php b/src/Participant/Participant.php
index 7fd3e170..681ac708 100755
--- a/src/Participant/Participant.php
+++ b/src/Participant/Participant.php
@@ -18,8 +18,8 @@
*
* @property int $id
* @property User|null $user m:hasOne
- * @property ParticipantRole|null $role m:passThru(roleFromString|roleToString) needed for DB working (see Mapper.php)
- * @property string|null $patrolName
+ * @property ParticipantRole|null $role m:passThru(roleFromString|roleToString) #needed for DB working (see Mapper.php)
+ * @property string|null $patrolName #used for troops too # TODO move to PatrolLeader + TroopLeader
* @property string|null $contingent
* @property string|null $firstName
* @property string|null $lastName
@@ -71,7 +71,7 @@ class Participant extends EntityDatetime
protected function initDefaults(): void
{
parent::initDefaults();
- $this->tieCode = $this->generateTieCode(6); // TODO check if another code exists
+ $this->tieCode = $this->generateTieCode(6); // TODO check if another code exists in DB
}
public function setUser(User $user): void
diff --git a/src/Participant/ParticipantController.php b/src/Participant/ParticipantController.php
index 192fd981..2a6e418c 100755
--- a/src/Participant/ParticipantController.php
+++ b/src/Participant/ParticipantController.php
@@ -9,6 +9,8 @@
use kissj\Participant\Patrol\PatrolLeader;
use kissj\Participant\Patrol\PatrolParticipant;
use kissj\Participant\Patrol\PatrolParticipantRepository;
+use kissj\Participant\Troop\TroopLeader;
+use kissj\Participant\Troop\TroopParticipantRepository;
use kissj\User\User;
use kissj\User\UserStatus;
use Psr\Http\Message\ResponseInterface as Response;
@@ -20,6 +22,7 @@ public function __construct(
private readonly ParticipantService $participantService,
private readonly ParticipantRepository $participantRepository,
private readonly PatrolParticipantRepository $patrolParticipantRepository,
+ private readonly TroopParticipantRepository $troopParticipantRepository,
) {
}
@@ -96,6 +99,8 @@ private function getTemplateData(Participant $participant): array
$participants = [];
if ($participant instanceof PatrolLeader) {
$participants = $this->patrolParticipantRepository->findAllPatrolParticipantsForPatrolLeader($participant);
+ } elseif ($participant instanceof TroopLeader) {
+ $participants = $this->troopParticipantRepository->findAllTroopParticipantsForTroopLeader($participant);
}
return [
diff --git a/src/Participant/ParticipantService.php b/src/Participant/ParticipantService.php
index 1f357bc1..7494d254 100755
--- a/src/Participant/ParticipantService.php
+++ b/src/Participant/ParticipantService.php
@@ -6,10 +6,12 @@
use kissj\Application\DateTimeUtils;
use kissj\Event\AbstractContentArbiter;
+use kissj\Event\Event;
use kissj\FileHandler\FileHandler;
use kissj\FlashMessages\FlashMessagesBySession;
use kissj\Mailer\PhpMailerWrapper;
use kissj\Participant\Guest\Guest;
+use kissj\Participant\Troop\TroopLeader;
use kissj\Participant\Troop\TroopParticipant;
use kissj\Payment\Payment;
use kissj\Payment\PaymentService;
@@ -141,6 +143,13 @@ public function isCloseRegistrationValid(Participant $participant): bool
$validityFlag = true;
$event = $participant->getUserButNotNull()->event;
+
+ // TODO move check for patrol leader here
+
+ if ($participant instanceof TroopLeader) {
+ $validityFlag = $this->isCloseRegistrationValidForTroopLeader($participant, $event);
+ }
+
if (!$this->isParticipantValidForClose($participant, $this->getContentArbiterForParticipant($participant))) {
$this->flashMessages->warning($this->translator->trans('flash.warning.noLock'));
@@ -177,6 +186,64 @@ public function isCloseRegistrationValid(Participant $participant): bool
return $validityFlag;
}
+ private function isCloseRegistrationValidForTroopLeader(TroopLeader $troopLeader, Event $event): bool
+ {
+ $validityFlag = true;
+ $troopParticipants = $troopLeader->troopParticipants;
+
+ $participantsCount = count($troopParticipants);
+ if ($participantsCount < $event->minimalTroopParticipantsCount) {
+ $this->flashMessages->warning(
+ $this->translator->trans(
+ 'flash.warning.plTooFewParticipantsTroop',
+ ['%minimalTroopParticipantsCount%' => $event->minimalTroopParticipantsCount],
+ )
+ );
+
+ $validityFlag = false;
+ }
+ if ($participantsCount > $event->maximalTroopParticipantsCount) {
+ $this->flashMessages->warning(
+ $this->translator->trans(
+ 'flash.warning.plTooManyParticipantsTroop',
+ ['%maximalTroopParticipantsCount%' => $event->maximalTroopParticipantsCount],
+ )
+ );
+
+ $validityFlag = false;
+ }
+
+ foreach ($troopParticipants as $participant) {
+ if (!$this->isParticipantValidForClose(
+ $participant,
+ $event->getEventType()->getContentArbiterTroopParticipant(),
+ )) {
+ $this->flashMessages->warning(
+ $this->translator->trans(
+ 'flash.warning.tlWrongDataParticipant',
+ ['%participantFullName%' => $participant->getFullName()],
+ )
+ );
+
+ $validityFlag = false;
+ }
+
+ if ($participant->user->status !== UserStatus::Closed) {
+
+ $this->flashMessages->warning(
+ $this->translator->trans(
+ 'flash.warning.tpNotClosed',
+ ['%participantFullName%' => $participant->getFullName()],
+ )
+ );
+
+ $validityFlag = false;
+ }
+ }
+
+ return $validityFlag;
+ }
+
public function isParticipantValidForClose(Participant $p, AbstractContentArbiter $ca): bool
{
if (
diff --git a/src/Participant/Patrol/PatrolParticipantRepository.php b/src/Participant/Patrol/PatrolParticipantRepository.php
index 62a8e84d..ab9de2c0 100755
--- a/src/Participant/Patrol/PatrolParticipantRepository.php
+++ b/src/Participant/Patrol/PatrolParticipantRepository.php
@@ -16,7 +16,6 @@
class PatrolParticipantRepository extends Repository
{
/**
- * @param PatrolLeader $patrolLeader
* @return PatrolParticipant[]
*/
public function findAllPatrolParticipantsForPatrolLeader(PatrolLeader $patrolLeader): array
diff --git a/src/Participant/Patrol/PatrolService.php b/src/Participant/Patrol/PatrolService.php
index 8d43492a..323c27da 100755
--- a/src/Participant/Patrol/PatrolService.php
+++ b/src/Participant/Patrol/PatrolService.php
@@ -56,6 +56,7 @@ public function addPatrolParticipant(PatrolLeader $patrolLeader): PatrolParticip
return $patrolParticipant;
}
+ // TODO refactor to repository->get()
public function getPatrolParticipant(int $patrolParticipantId): PatrolParticipant
{
return $this->patrolParticipantRepository->getOneBy(['id' => $patrolParticipantId]);
diff --git a/src/Participant/Troop/TroopController.php b/src/Participant/Troop/TroopController.php
new file mode 100755
index 00000000..8ad0d935
--- /dev/null
+++ b/src/Participant/Troop/TroopController.php
@@ -0,0 +1,118 @@
+getParameterFromBody($request, 'tieCode');
+
+ $troopLeader = $this->troopLeaderRepository->getFromUser($user);
+ if ($tieCode === $troopLeader->tieCode) {
+ $this->flashMessages->warning($this->translator->trans('flash.warning.cannotTieYourself'));
+
+ return $this->redirect($request, $response, 'getDashboard');
+ }
+
+ $troopParticipant = $this->troopParticipantRepository->findTroopParticipantFromTieCode($tieCode, $event);
+ if ($troopParticipant === null) {
+ $this->flashMessages->warning($this->translator->trans('flash.warning.wrongTieCodeForTroopParticipant'));
+
+ return $this->redirect($request, $response, 'getDashboard');
+ }
+ $this->troopService->tieTroopParticipantToTroopLeader(
+ $troopParticipant,
+ $troopLeader,
+ );
+
+ return $this->redirect(
+ $request,
+ $response,
+ 'getDashboard',
+ );
+ }
+
+ public function tieParticipantToTroopByParticipant(
+ Request $request,
+ Response $response,
+ User $user,
+ Event $event,
+ ): Response {
+ $troopParticipant = $this->troopParticipantRepository->getFromUser($user);
+ if ($troopParticipant->troopLeader !== null) {
+ $this->flashMessages->warning($this->translator->trans('flash.warning.alreadyTied'));
+
+ return $this->redirect($request, $response, 'getDashboard');
+ }
+
+ $tieCode = $this->getParameterFromBody($request, 'tieCode');
+ $troopLeader = $this->troopLeaderRepository->findTroopLeaderFromTieCode($tieCode, $event);
+
+ if ($troopLeader === null) {
+ $this->flashMessages->warning($this->translator->trans('flash.warning.wrongTieCodeForTroopLeader'));
+
+ return $this->redirect($request, $response, 'getDashboard');
+ }
+ $this->troopService->tieTroopParticipantToTroopLeader(
+ $troopParticipant,
+ $troopLeader,
+ );
+
+ return $this->redirect(
+ $request,
+ $response,
+ 'getDashboard',
+ );
+ }
+
+ public function showParticipant(int $participantId, Response $response, User $user): Response
+ {
+ return $this->view->render(
+ $response,
+ 'show-p.twig',
+ [
+ 'pDetail' => $this->troopParticipantRepository->get($participantId),
+ 'ca' => $user->event->eventType->getContentArbiterTroopParticipant(),
+ ]
+ );
+ }
+
+ public function showUntieParticipant(int $participantId, Response $response): Response
+ {
+ $patrolParticipant = $this->troopParticipantRepository->get($participantId);
+
+ return $this->view->render($response, 'delete-tp.twig', ['pDetail' => $patrolParticipant]);
+ }
+
+ public function untieParticipant(int $participantId, Request $request, Response $response): Response
+ {
+ $this->troopService->untieTroopParticipant($participantId);
+ $this->flashMessages->info($this->translator->trans('flash.info.participantUntied'));
+
+ return $this->redirect(
+ $request,
+ $response,
+ 'dashboard',
+ );
+ }
+}
diff --git a/src/Participant/Troop/TroopLeaderRepository.php b/src/Participant/Troop/TroopLeaderRepository.php
index b0b6b44e..b0f0b90c 100755
--- a/src/Participant/Troop/TroopLeaderRepository.php
+++ b/src/Participant/Troop/TroopLeaderRepository.php
@@ -6,6 +6,8 @@
use kissj\Event\Event;
use kissj\Orm\Repository;
+use kissj\Participant\ParticipantRole;
+use kissj\User\User;
/**
* @table participant
@@ -32,4 +34,22 @@ public function findAllWithEvent(Event $event): array
return $troopLeaders;
}
+
+ public function findTroopLeaderFromTieCode(string $tieCode, Event $event): ?TroopLeader
+ {
+ $troopLeader = $this->findOneBy([
+ 'tie_code' => strtoupper($tieCode),
+ 'role' => ParticipantRole::TroopLeader,
+ ]);
+ if ($troopLeader?->user->event->id !== $event->id) {
+ return null;
+ }
+
+ return $troopLeader;
+ }
+
+ public function getFromUser(User $user): TroopLeader
+ {
+ return $this->getOneBy(['user' => $user]);
+ }
}
diff --git a/src/Participant/Troop/TroopParticipantRepository.php b/src/Participant/Troop/TroopParticipantRepository.php
index e84d3113..ec462f52 100755
--- a/src/Participant/Troop/TroopParticipantRepository.php
+++ b/src/Participant/Troop/TroopParticipantRepository.php
@@ -6,6 +6,8 @@
use kissj\Event\Event;
use kissj\Orm\Repository;
+use kissj\Participant\ParticipantRole;
+use kissj\User\User;
/**
* @table participant
@@ -32,4 +34,28 @@ public function findAllWithEvent(Event $event): array
return $troopParticipants;
}
+
+ public function findTroopParticipantFromTieCode(string $tieCode, Event $event): ?TroopParticipant
+ {
+ $troopParticipant = $this->findOneBy([
+ 'tie_code' => strtoupper($tieCode),
+ 'role' => ParticipantRole::TroopParticipant,
+ ]);
+ if ($troopParticipant?->user->event->id !== $event->id) {
+ return null;
+ }
+
+ return $troopParticipant;
+ }
+
+ public function findAllTroopParticipantsForTroopLeader(TroopLeader $troopLeader): array
+ {
+ return $this->findBy(['patrol_leader_id' => $troopLeader->id]);
+ }
+
+ // TODO check why?
+ public function getFromUser(User $user): TroopParticipant
+ {
+ return $this->getOneBy(['user' => $user]);
+ }
}
diff --git a/src/Participant/Troop/TroopService.php b/src/Participant/Troop/TroopService.php
index 6a768ae1..4a1dfe8b 100755
--- a/src/Participant/Troop/TroopService.php
+++ b/src/Participant/Troop/TroopService.php
@@ -5,16 +5,21 @@
namespace kissj\Participant\Troop;
use kissj\Event\Event;
+use kissj\FlashMessages\FlashMessagesInterface;
use kissj\Participant\Admin\StatisticValueObject;
use kissj\Participant\ParticipantRepository;
use kissj\Participant\ParticipantRole;
use kissj\User\User;
use kissj\User\UserStatus;
+use Symfony\Contracts\Translation\TranslatorInterface;
class TroopService
{
public function __construct(
+ private readonly TroopParticipantRepository $troopParticipantRepository,
private readonly ParticipantRepository $participantRepository,
+ private readonly FlashMessagesInterface $flashMessages,
+ private readonly TranslatorInterface $translator,
) {
}
@@ -41,4 +46,45 @@ public function getAllTroopParticipantStatistics(Event $event, User $admin): Sta
return new StatisticValueObject($troopLeaders);
}
+
+ public function tieTroopParticipantToTroopLeader(
+ TroopParticipant $troopParticipant,
+ TroopLeader $troopLeader,
+ ): TroopParticipant {
+ if (
+ $troopLeader->getUserButNotNull()->status !== UserStatus::Open
+ ) {
+ $this->flashMessages->warning($this->translator->trans('flash.warning.troopLeaderNotOpen'));
+
+ return $troopParticipant;
+ }
+
+ if ($troopParticipant->troopLeader?->id === $troopLeader->id) {
+ $this->flashMessages->warning($this->translator->trans('flash.warning.troopParticipantAlreadyTied'));
+
+ return $troopParticipant;
+ }
+
+ $troopParticipant->troopLeader = $troopLeader;
+ $this->troopParticipantRepository->persist($troopParticipant);
+ $this->flashMessages->success($this->translator->trans('flash.success.troopParticipantTiedToTroopLeader'));
+
+ return $troopParticipant;
+ }
+
+ public function troopParticipantBelongsTroopLeader(
+ TroopParticipant $troopParticipant,
+ TroopLeader $troopLeader
+ ): bool {
+ return $troopParticipant->troopLeader->id === $troopLeader->id;
+ }
+
+ public function untieTroopParticipant(int $participantId): TroopParticipant
+ {
+ $troopParticipant = $this->troopParticipantRepository->get($participantId);
+ $troopParticipant->troopLeader = null;
+ $this->troopParticipantRepository->persist($troopParticipant);
+
+ return $troopParticipant;
+ }
}
diff --git a/src/Payment/PaymentService.php b/src/Payment/PaymentService.php
index 08dc6664..43476429 100755
--- a/src/Payment/PaymentService.php
+++ b/src/Payment/PaymentService.php
@@ -13,7 +13,9 @@
use kissj\FlashMessages\FlashMessagesBySession;
use kissj\Mailer\PhpMailerWrapper;
use kissj\Participant\Participant;
+use kissj\Participant\ParticipantRepository;
use kissj\Participant\Patrol\PatrolLeader;
+use kissj\Participant\Troop\TroopLeader;
use kissj\User\UserService;
use Monolog\Logger;
use Symfony\Contracts\Translation\TranslatorInterface;
@@ -24,6 +26,7 @@ public function __construct(
private readonly FioBankPaymentService $bankPaymentService,
private readonly BankPaymentRepository $bankPaymentRepository,
private readonly PaymentRepository $paymentRepository,
+ private readonly ParticipantRepository $participantRepository,
private readonly UserService $userService,
private readonly FlashMessagesBySession $flashMessages,
private readonly PhpMailerWrapper $mailer,
@@ -101,10 +104,26 @@ public function confirmPayment(Payment $payment): Payment
. PaymentStatus::Waiting->value);
}
- $this->userService->setUserPaid($payment->participant->getUserButNotNull());
$payment->status = PaymentStatus::Paid;
$this->paymentRepository->persist($payment);
- $this->mailer->sendRegistrationPaid($payment->participant);
+
+ $participant = $payment->participant;
+ $this->userService->setUserPaid($participant->getUserButNotNull());
+
+ $now = DateTimeUtils::getDateTime();
+ $participant->registrationCloseDate = $now;
+ $this->participantRepository->persist($participant);
+
+ if ($participant instanceof TroopLeader) {
+ foreach ($participant->troopParticipants as $tp) {
+ $this->userService->setUserPaid($tp->getUserButNotNull());
+
+ $tp->registrationCloseDate = $now;
+ $this->participantRepository->persist($tp);
+ }
+ }
+
+ $this->mailer->sendRegistrationPaid($participant);
return $payment;
}
diff --git a/src/Settings/TwigExtension.php b/src/Settings/TwigExtension.php
index ec17820b..39c4bd9a 100644
--- a/src/Settings/TwigExtension.php
+++ b/src/Settings/TwigExtension.php
@@ -7,6 +7,7 @@
use kissj\Participant\Patrol\PatrolLeader;
use kissj\Participant\Troop\TroopLeader;
use kissj\Participant\Troop\TroopParticipant;
+use kissj\User\UserStatus;
use Twig\Extension\AbstractExtension;
use Twig\TwigTest;
@@ -24,12 +25,22 @@ public function getTests(): array
new TwigTest('TroopLeader', function ($participant): bool {
return $participant instanceof TroopLeader;
}),
+ new TwigTest('TroopParticipant', function ($participant): bool {
+ return $participant instanceof TroopParticipant;
+ }),
new TwigTest('Leader', function ($participant): bool {
return $participant instanceof PatrolLeader || $participant instanceof TroopLeader;
}),
new TwigTest('Troop', function ($participant): bool {
return $participant instanceof TroopLeader || $participant instanceof TroopParticipant;
}),
+ new TwigTest('eligibleForShowTieCode', function ($participant): bool {
+ return (
+ $participant instanceof TroopLeader && $participant->user->status === UserStatus::Open
+ ) || (
+ $participant instanceof TroopParticipant && $participant->troopLeader === null
+ );
+ }),
];
}
}
diff --git a/src/Templates/cs.yaml b/src/Templates/cs.yaml
index 488bf51e..0a75b36c 100755
--- a/src/Templates/cs.yaml
+++ b/src/Templates/cs.yaml
@@ -10,6 +10,8 @@ closeRegistration:
userCustomHelp:
statusOpen: "Vyplň všechny potřebné informace informace a poté uzamkni přihlášku tlačítkem dole."
statusOpenPl: "Napiš o sobě všechny uvedené informace, přidej členy své patroly a informace o nich, poté uzamkni vaši přihlášku tlačítkem dole."
+ statusOpenTl: "Chceš připnout účastníka do skupiny? Jsou dvě možnosti - buď pošli svůj kód pro připnutí účastníkovi a on ho vloží do své registrace, nebo on musí poslat svůj kód pro připnutí tobě a ty ho vložíš sem"
+ statusOpenTp: "Chceš se připnout do skupiny? Jsou dvě možnosti - buď pošli svůj kód pro připnutí vedoucímu skupiny a on ho vloží do své registrace, nebo on musí poslat svůj kód pro připnutí tobě a ty ho vložíš sem"
statusClosed: "Tvá přihláška čeká na schválení. Dáme Ti vědět, až bude připravena, a následně Ti pošleme informace k platbě. Pokud schvalování trvá moc dlouho, napiš nám na e-mail "
statusClosedGuest: "Tvá přihláška čeká na schválení. Dáme Ti vědět až bude připravena. Pokud schvalování trvá moc dlouho (v řádu týdnů), napiš nám na e-mail "
statusApproved: "Tvá přihláška byla schválena. Nyní už musíš jen zaplatit registrační poplatek."
@@ -19,13 +21,22 @@ dashboard:
personalInfo: "Osobní údaje"
editDetails: "Vyplnit svoje údaje"
delete: "Smazat"
- youWantDelete: "Opravdu chcete smazat účastníka "
+ youWantDelete: "Opravdu chceš smazat účastníka "
withoutName: "(prozatím bez jména)"
wontBeAbleUndo: "? Toto smazání nepůjde vrátit zpět!"
+ untie: "Odepnout"
+ youWantUntie: "Opravdu chceš odepnout účastníka "
+ youHaveTroop: "Jsi ve skupině %troopName% s vedoucím skupiny %troopLeaderFullName%"
details: "podrobnosti"
tieCode: "Kód pro připnutí"
listOfParticipants: "Seznam účastníků"
- addParticipant: "Přidat účastníka"
+ addParticipant: "Přidej účastníka"
+ tieTroopParticipant: "Připni účastníka"
+ tieToTroopLeader: "Připni se ke skupině"
+ tieCodeLabel: "Kód pro připnutí účastníka"
+ codeForTieToTroop: "Kód skupiny pro připnutí"
+ tieCodeFormat: "šest písmen"
+ withoutFullName: "bez vyplněného jména"
youNeed: "Potřebuješ"
exactly: "přesne"
minimally: "nejméně"
@@ -41,6 +52,10 @@ dashboard:
waiting: "čekáme na zaplacení"
paid: "zaplacená (:"
canceled: "zrušená"
+ userStatus:
+ open: "odemčený ⚠"
+ closed: "uzamčený ✔"
+ paid: "zaplacený (:"
lockRegistration: "Uzamknout registraci"
changeDetails:
editDetails: "Upravit údaje"
@@ -371,6 +386,7 @@ transferPayment-admin:
flash:
info:
participantDeleted: "Účastník byl smazán"
+ participantUntied: "Účastník byl odepnut"
paymentCanceled: "Platba stornována, e-mail s důvodem odeslán"
chooseRoleNeeded: "Nejprve si musíš zvolit svoji roli pro tuto akci"
denied: "účastník odemknut, e-mail byl poslán"
@@ -393,13 +409,18 @@ flash:
tpApproved: "Účastník akce schválen a e-mail odeslán (bez platby)"
adminPairedPayments: "Spárováno bankovních transakcí: "
transfer: "Platba úspěšně přesunuta!"
+ troopParticipantTiedToTroopLeader: "Účastník úspěšně připojen do skupiny!"
warning:
noLock: "Registraci nelze uzamknout - nějaké informace jsou špatně vyplněné nebo chybí (pravděpodobně nějaké datum)"
plWrongData: "Nemůžeme uzamknout registraci - prosím oprav svoje údaje (nejspíš email nebo nějaké datum)"
plWrongDataParticipant: "Nemůžeme uzamknout registraci - prosím oprav údaje účastníka %participantFullName% (nejspíš email nebo nějaké datum)"
+ tlWrongDataParticipant: "Nemůžeme uzamknout registraci - prosím oprav údaje účastníka %participantFullName% (nejspíš email nebo nějaké datum)"
+ tpNotClosed: "Nemůžeme uzamknout registraci - účastník %participantFullName% není uzamčený, popožeň ho prosím, aby svou registraci uzamknul"
plTooFewParticipants: "Nemůžeme uzamknout registraci - v patrole je příliš málo účastníků. Je jich potřeba nejméně %minimalPatrolParticipantsCount%."
plTooManyParticipants: "Nemůžeme uzamknout registraci - v patrole je příliš účastníků. Může jich být nejvíce %maximalPatrolParticipantsCount%."
fullRegistration: "Už máme plno a ty jsi pod čarou, takže tě zatím nemůžeme registrovat. Počkej prosím než navýšíme kapacitu, nebo než někdo zruší svojí přihlášku"
+ plTooFewParticipantsTroop: "Nemůžeme uzamknout registraci - ve skupině je příliš málo účastníků. Je jich potřeba jich připnout nejméně %minimalTroopParticipantsCount%."
+ plTooManyParticipantsTroop: "Nemůžeme uzamknout registraci - ve skupině je příliš účastníků. Může jich být nejvíce %maximalTroopParticipantsCount%, nějaké odepni prosím."
notLogged: "Omlouváme se, ale nejsi přihlášen/a. Přihlaš se prosím vyplněním svého e-mailu"
loggedIn: "Omlouváme se, ale jsi stále přihlášený/á - nejprve se odhlaš kliknutím na \"Odhlásit se\""
roleChoosed: "Omlouváme se, ale už sis zvolil/a roli pro tuto akci"
@@ -417,11 +438,22 @@ flash:
nonexistentEvent: "Tato akce nejspíš neexistuje. Asi špatný odkaz?"
testingSite: "Tato stránka je určena pouze pro testovací účely - nezadávej žádné reálné osobní či citlivé údaje!"
multiplePaymentsNotAllowed: "Pardon, ale generování více plateb pro účastníka není pro tuto akci povoleno."
+ cannotTieYourself: "Připnout se ke své vlastní skupině nedává smysl"
+ wrongTieCodeForTroopParticipant: "S tímto kódem pro připnutí neevidujeme žádného účastníka. Zkontroluj prosím kód pro připnutí, jestli je správně"
+ wrongTieCodeForTroopLeader: "S tímto kódem pro připnutí neevidujeme žádnou skupinu. Zkontroluj prosím kód pro připnutí, jestli je správně"
+ troopLeaderNotOpen: "Vedoucí skupiny není odemčený, proto nelze připojit účastníka ke skupině"
+ troopParticipantAlreadyTied: "Účastník již byl do skupiny přidán"
+ alreadyTied: "Nemůžeš se přidat do skupiny, protože již ve skupině jsi. Jestli chceš k jiné skupině, popros vedoucího o odepnutí"
error:
adminOnly: "Sorry bro, pouze pro administrátory"
plOnly: "Pardon, nejsi přihlášený jako vedoucí patroly"
+ tlOnly: "Pardon, nejsi přihlášený jako vedoucí skupiny"
+ tpOnly: "Pardon, nejsi přihlášený jako účastník skupiny"
wrongPatrol: "Pardon, ale nemůžeš upravovat či si zobrazit informace účastníků mimo tvou patrolu."
+ wrongTroop: "Pardon, ale nemůžeš upravovat či si zobrazit informace účastníků mimo tvou skupinu."
+ notLeader: "Pardon, ale na tuto adresu mohou jen vedoucí patrol či vedoucí skupin"
wrongData: "Přihláška nelze uložit, informace nejsou platné"
mailError: "Pardon, odeslání e-mailu selhalo. Zkuste to prosím znovu za pár minut."
confirmNotAllowed: "Tato platba nepřísluší tvé akce, potvrzení nelze provést"
fioConnectionFailed: "Spojení do Fio banky selhalo, nejspíše kvůli špatně zadanému API klíči"
+ tieParticipantToLeaderError: "Připojení účastníka do skupiny se nepovedlo :("
diff --git a/src/Templates/translatable/admin/open-admin.twig b/src/Templates/translatable/admin/open-admin.twig
index f724add0..e982ab0f 100644
--- a/src/Templates/translatable/admin/open-admin.twig
+++ b/src/Templates/translatable/admin/open-admin.twig
@@ -88,7 +88,6 @@
{% for tp in openTroopParticipants %}
+ {% trans %}dashboard.youWantUntie{% endtrans %} + {% if pDetail.getFirstName is not empty %}{{ pDetail.getFullName }}{% else %} + {% trans %}dashboard.withoutName{% endtrans %}{% endif %}? +
+ +{% trans %}dashboard.youNeed {% endtrans %} - {% if event.minimalPatrolParticipantsCount == event.maximalPatrolParticipantsCount %} - {% trans %}dashboard.exactly {% endtrans %} {{ event.minimalPatrolParticipantsCount }} - {% else %} - {% trans %}dashboard.minimally {% endtrans %} {{ event.minimalPatrolParticipantsCount }} - {% trans %}dashboard.andMaximally {% endtrans %} {{ event.maximalPatrolParticipantsCount }} + {% if person is PatrolLeader %} + {% if event.minimalPatrolParticipantsCount == event.maximalPatrolParticipantsCount %} + {% trans %}dashboard.exactly {% endtrans %} {{ event.minimalPatrolParticipantsCount }} + {% else %} + {% trans %}dashboard.minimally {% endtrans %} {{ event.minimalPatrolParticipantsCount }} + {% trans %}dashboard.andMaximally {% endtrans %} {{ event.maximalPatrolParticipantsCount }} + {% endif %} + {% elseif person is TroopLeader %} + {% if event.minimalTroopParticipantsCount == event.maximalTroopParticipantsCount %} + {% trans %}dashboard.exactly {% endtrans %} {{ event.minimalTroopParticipantsCount }} + {% else %} + {% trans %}dashboard.minimally {% endtrans %} {{ event.minimalTroopParticipantsCount }} + {% trans %}dashboard.andMaximally {% endtrans %} {{ event.maximalTroopParticipantsCount }} + {% endif %} {% endif %} {% trans %}dashboard.pForValidReg {% endtrans %}
{% endif %} + + {# adding new participants into group #} + {% if userStatus == 'open' %} + {% if person is PatrolLeader %} + + {% elseif person is TroopLeader %} + {% include('widgets/troopTieForm.twig') with { + formUrl: url_for('tie-tp-by-tl', {'eventSlug': event.slug}), + textInputLabel: 'dashboard.tieCodeLabel', + buttonLabel: 'dashboard.tieTroopParticipant' + } %} + {% endif %} +