diff --git a/src/Bundle/ChillMainBundle/Repository/NotificationRepository.php b/src/Bundle/ChillMainBundle/Repository/NotificationRepository.php index 17cff837f..9cfffb2cf 100644 --- a/src/Bundle/ChillMainBundle/Repository/NotificationRepository.php +++ b/src/Bundle/ChillMainBundle/Repository/NotificationRepository.php @@ -239,7 +239,7 @@ final class NotificationRepository implements ObjectRepository ->setParameter('relatedEntityId', $relatedEntityId) ->setParameter('user', $user); - return $qb->getQuery()->getResult(); + return $qb; } private function queryByAddressee(User $addressee, bool $countQuery = false): QueryBuilder diff --git a/src/Bundle/ChillPersonBundle/Controller/AccompanyingCourseApiController.php b/src/Bundle/ChillPersonBundle/Controller/AccompanyingCourseApiController.php index 3b051a59d..c66488869 100644 --- a/src/Bundle/ChillPersonBundle/Controller/AccompanyingCourseApiController.php +++ b/src/Bundle/ChillPersonBundle/Controller/AccompanyingCourseApiController.php @@ -13,7 +13,9 @@ namespace Chill\PersonBundle\Controller; use Chill\MainBundle\CRUD\Controller\ApiController; use Chill\MainBundle\Entity\Scope; +use Chill\MainBundle\Entity\User; use Chill\MainBundle\Serializer\Model\Collection; +use Chill\MainBundle\Serializer\Model\Counter; use Chill\PersonBundle\AccompanyingPeriod\Suggestion\ReferralsSuggestionInterface; use Chill\PersonBundle\Entity\AccompanyingPeriod; use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWork; @@ -23,8 +25,11 @@ use Chill\PersonBundle\Entity\Person; use Chill\PersonBundle\Entity\SocialWork\SocialIssue; use Chill\PersonBundle\Privacy\AccompanyingPeriodPrivacyEvent; use Chill\PersonBundle\Repository\AccompanyingPeriodACLAwareRepository; +use Chill\PersonBundle\Repository\AccompanyingPeriodRepository; use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter; use Chill\ThirdPartyBundle\Entity\ThirdParty; +use DateInterval; +use DateTimeImmutable; use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter; use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\HttpFoundation\Exception\BadRequestException; @@ -32,13 +37,14 @@ use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Annotation\Route; +use Symfony\Component\Security\Core\Exception\AccessDeniedException; use Symfony\Component\Serializer\Exception\RuntimeException; use Symfony\Component\Serializer\Normalizer\AbstractNormalizer; use Symfony\Component\Validator\ConstraintViolationList; use Symfony\Component\Validator\ConstraintViolationListInterface; + use Symfony\Component\Validator\Validator\ValidatorInterface; use Symfony\Component\Workflow\Registry; - use function array_values; use function count; @@ -46,6 +52,8 @@ final class AccompanyingCourseApiController extends ApiController { private AccompanyingPeriodACLAwareRepository $accompanyingPeriodACLAwareRepository; + private AccompanyingPeriodRepository $accompanyingPeriodRepository; + private EventDispatcherInterface $eventDispatcher; private ReferralsSuggestionInterface $referralAvailable; @@ -55,17 +63,19 @@ final class AccompanyingCourseApiController extends ApiController private ValidatorInterface $validator; public function __construct( - EventDispatcherInterface $eventDispatcher, - ValidatorInterface $validator, - Registry $registry, + AccompanyingPeriodRepository $accompanyingPeriodRepository, AccompanyingPeriodACLAwareRepository $accompanyingPeriodACLAwareRepository, - ReferralsSuggestionInterface $referralAvailable + EventDispatcherInterface $eventDispatcher, + ReferralsSuggestionInterface $referralAvailable, + Registry $registry, + ValidatorInterface $validator ) { - $this->eventDispatcher = $eventDispatcher; - $this->validator = $validator; - $this->registry = $registry; + $this->accompanyingPeriodRepository = $accompanyingPeriodRepository; $this->accompanyingPeriodACLAwareRepository = $accompanyingPeriodACLAwareRepository; + $this->eventDispatcher = $eventDispatcher; $this->referralAvailable = $referralAvailable; + $this->registry = $registry; + $this->validator = $validator; } public function commentApi($id, Request $request, string $_format): Response @@ -99,6 +109,57 @@ final class AccompanyingCourseApiController extends ApiController ]); } + /** + * @Route("/api/1.0/person/accompanying-course/list/by-recent-attributions") + */ + public function findMyRecentCourseAttribution(Request $request): JsonResponse + { + $this->denyAccessUnlessGranted('ROLE_USER'); + $user = $this->getUser(); + + if (!$user instanceof User) { + throw new AccessDeniedException(); + } + + $since = (new DateTimeImmutable('now'))->sub(new DateInterval('P15D')); + + $total = $this->accompanyingPeriodRepository->countByRecentUserHistory($user, $since); + + if ($request->query->getBoolean('countOnly', false)) { + return new JsonResponse( + $this->getSerializer()->serialize(new Counter($total), 'json'), + JsonResponse::HTTP_OK, + [], + true + ); + } + + $paginator = $this->getPaginatorFactory()->create($total); + + if (0 === $total) { + return new JsonResponse( + $this->getSerializer()->serialize(new Collection([], $paginator), 'json'), + JsonResponse::HTTP_OK, + [], + true + ); + } + + $courses = $this->accompanyingPeriodRepository->findByRecentUserHistory( + $user, + $since, + $paginator->getItemsPerPage(), + $paginator->getCurrentPageFirstItemNumber() + ); + + return new JsonResponse( + $this->getSerializer()->serialize(new Collection($courses, $paginator), 'json', ['groups' => ['read']]), + JsonResponse::HTTP_OK, + [], + true + ); + } + /** * @ParamConverter("person", options={"id": "person_id"}) */ diff --git a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod.php b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod.php index c539c458d..24e305bbd 100644 --- a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod.php +++ b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod.php @@ -338,7 +338,8 @@ class AccompanyingPeriod implements private ?User $user = null; /** - * @ORM\OneToMany(targetEntity=UserHistory::class, mappedBy="accompanyingPeriod") + * @ORM\OneToMany(targetEntity=UserHistory::class, mappedBy="accompanyingPeriod", orphanRemoval=true, + * cascade={"persist", "remove"}) * * @var Collection|UserHistory[] */ @@ -1226,15 +1227,15 @@ class AccompanyingPeriod implements if ($this->user !== $user) { $this->userPrevious = $this->user; - if (null !== $user) { - $this->userHistories->add(new UserHistory($this, $user)); - } - foreach ($this->userHistories as $history) { if (null === $history->getEndDate()) { $history->setEndDate(new DateTimeImmutable('now')); } } + + if (null !== $user) { + $this->userHistories->add(new UserHistory($this, $user)); + } } $this->user = $user; diff --git a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/UserHistory.php b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/UserHistory.php index 738d12008..4e0e2493f 100644 --- a/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/UserHistory.php +++ b/src/Bundle/ChillPersonBundle/Entity/AccompanyingPeriod/UserHistory.php @@ -11,16 +11,21 @@ declare(strict_types=1); namespace Chill\PersonBundle\Entity\AccompanyingPeriod; +use Chill\MainBundle\Doctrine\Model\TrackCreationInterface; +use Chill\MainBundle\Doctrine\Model\TrackCreationTrait; use Chill\MainBundle\Entity\User; use Chill\PersonBundle\Entity\AccompanyingPeriod; +use DateTimeImmutable; use Doctrine\ORM\Mapping as ORM; /** * @ORM\Entity * @ORM\Table("chill_person_accompanying_period_user_history") */ -class UserHistory +class UserHistory implements TrackCreationInterface { + use TrackCreationTrait; + /** * @ORM\ManyToOne(targetEntity=AccompanyingPeriod::class, inversedBy="userHistories") * @ORM\JoinColumn(nullable=false) @@ -30,7 +35,7 @@ class UserHistory /** * @ORM\Column(type="datetime_immutable", nullable=true, options={"default": null}) */ - private ?\DateTimeImmutable $endDate = null; + private ?DateTimeImmutable $endDate = null; /** * @ORM\Id @@ -42,7 +47,7 @@ class UserHistory /** * @ORM\Column(type="datetime_immutable", nullable=false) */ - private \DateTimeImmutable $startDate; + private DateTimeImmutable $startDate; /** * @ORM\ManyToOne(targetEntity=User::class) @@ -50,9 +55,9 @@ class UserHistory */ private User $user; - public function __construct(AccompanyingPeriod $accompanyingPeriod, User $user, ?\DateTimeImmutable $startDate = null) + public function __construct(AccompanyingPeriod $accompanyingPeriod, User $user, ?DateTimeImmutable $startDate = null) { - $this->startDate = $startDate ?? new \DateTimeImmutable('now'); + $this->startDate = $startDate ?? new DateTimeImmutable('now'); $this->accompanyingPeriod = $accompanyingPeriod; $this->user = $user; } diff --git a/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriodRepository.php b/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriodRepository.php index b21431554..a0dd472b6 100644 --- a/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriodRepository.php +++ b/src/Bundle/ChillPersonBundle/Repository/AccompanyingPeriodRepository.php @@ -11,7 +11,9 @@ declare(strict_types=1); namespace Chill\PersonBundle\Repository; +use Chill\MainBundle\Entity\User; use Chill\PersonBundle\Entity\AccompanyingPeriod; +use DateTimeImmutable; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityRepository; use Doctrine\ORM\QueryBuilder; @@ -26,6 +28,13 @@ final class AccompanyingPeriodRepository implements ObjectRepository $this->repository = $entityManager->getRepository(AccompanyingPeriod::class); } + public function countByRecentUserHistory(User $user, DateTimeImmutable $since): int + { + $qb = $this->buildQueryByRecentUserHistory($user, $since); + + return $qb->select('count(a)')->getQuery()->getSingleScalarResult(); + } + public function createQueryBuilder(string $alias, ?string $indexBy = null): QueryBuilder { return $this->repository->createQueryBuilder($alias, $indexBy); @@ -49,6 +58,21 @@ final class AccompanyingPeriodRepository implements ObjectRepository return $this->repository->findBy($criteria, $orderBy, $limit, $offset); } + /** + * @return array|AccompanyingPeriod[] + */ + public function findByRecentUserHistory(User $user, DateTimeImmutable $since, ?int $limit = 20, ?int $offset = 0): array + { + $qb = $this->buildQueryByRecentUserHistory($user, $since); + + return $qb->select('a') + ->distinct(true) + ->getQuery() + ->setMaxResults($limit) + ->setFirstResult($offset) + ->getResult(); + } + public function findOneBy(array $criteria): ?AccompanyingPeriod { return $this->findOneBy($criteria); @@ -58,4 +82,19 @@ final class AccompanyingPeriodRepository implements ObjectRepository { return AccompanyingPeriod::class; } + + private function buildQueryByRecentUserHistory(User $user, DateTimeImmutable $since): QueryBuilder + { + $qb = $this->repository->createQueryBuilder('a'); + + $qb + ->join('a.userHistories', 'userHistory') + ->where($qb->expr()->eq('a.user', ':user')) + ->andWhere($qb->expr()->gte('userHistory.startDate', ':since')) + ->andWhere($qb->expr()->isNull('userHistory.endDate')) + ->setParameter('user', $user) + ->setParameter('since', $since); + + return $qb; + } } diff --git a/src/Bundle/ChillPersonBundle/migrations/Version20220128133039.php b/src/Bundle/ChillPersonBundle/migrations/Version20220128133039.php index 0a4b8c933..7b602ba22 100644 --- a/src/Bundle/ChillPersonBundle/migrations/Version20220128133039.php +++ b/src/Bundle/ChillPersonBundle/migrations/Version20220128133039.php @@ -40,5 +40,12 @@ final class Version20220128133039 extends AbstractMigration $this->addSql('ALTER TABLE chill_person_accompanying_period_user_history ADD CHECK (startdate <= enddate)'); $this->addSql('INSERT INTO chill_person_accompanying_period_user_history (id, user_id, accompanyingperiod_id, startDate, endDate) ' . 'SELECT nextval(\'chill_person_accompanying_period_user_history_id_seq\'), user_id, id, openingDate, null FROM chill_person_accompanying_period WHERE user_id IS NOT NULL'); + $this->addSql('ALTER TABLE chill_person_accompanying_period_user_history ADD createdAt TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL;'); + $this->addSql('ALTER TABLE chill_person_accompanying_period_user_history ADD createdBy_id INT DEFAULT NULL;'); + $this->addSql('ALTER TABLE chill_person_accompanying_period_user_history ALTER user_id SET NOT NULL;'); + $this->addSql('ALTER TABLE chill_person_accompanying_period_user_history ALTER accompanyingperiod_id SET NOT NULL;'); + $this->addSql('COMMENT ON COLUMN chill_person_accompanying_period_user_history.createdAt IS \'(DC2Type:datetime_immutable)\';'); + $this->addSql('ALTER TABLE chill_person_accompanying_period_user_history ADD CONSTRAINT FK_6C258C493174800F FOREIGN KEY (createdBy_id) REFERENCES users (id) NOT DEFERRABLE INITIALLY IMMEDIATE;'); + $this->addSql('CREATE INDEX IDX_6C258C493174800F ON chill_person_accompanying_period_user_history (createdBy_id);'); } }