Merge remote-tracking branch 'origin/master' into 616_rapid-action

This commit is contained in:
2024-06-12 16:45:43 +02:00
2249 changed files with 42970 additions and 30730 deletions

View File

@@ -28,3 +28,4 @@ Version 1.5.5
- [activity] replace dropdown for selecting reasons and use chillEntity for reason rendering
- fix bug: error when trying to edit activity of which the type has been deactivated

View File

@@ -20,20 +20,22 @@ use Chill\ActivityBundle\Repository\ActivityTypeCategoryRepository;
use Chill\ActivityBundle\Repository\ActivityTypeRepositoryInterface;
use Chill\ActivityBundle\Security\Authorization\ActivityVoter;
use Chill\MainBundle\Entity\Embeddable\CommentEmbeddable;
use Chill\MainBundle\Entity\UserJob;
use Chill\MainBundle\Pagination\PaginatorFactory;
use Chill\MainBundle\Repository\LocationRepository;
use Chill\MainBundle\Repository\UserRepositoryInterface;
use Chill\MainBundle\Security\Resolver\CenterResolverManagerInterface;
use Chill\MainBundle\Templating\Listing\FilterOrderHelper;
use Chill\MainBundle\Templating\Listing\FilterOrderHelperFactoryInterface;
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Entity\Person;
use Chill\PersonBundle\Privacy\PrivacyEvent;
use Chill\PersonBundle\Repository\AccompanyingPeriodRepository;
use Chill\PersonBundle\Repository\PersonRepository;
use Chill\ThirdPartyBundle\Repository\ThirdPartyRepository;
use DateTime;
use Doctrine\ORM\EntityManagerInterface;
use InvalidArgumentException;
use Psr\Log\LoggerInterface;
use RuntimeException;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
@@ -43,80 +45,37 @@ use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Serializer\SerializerInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
use function array_key_exists;
final class ActivityController extends AbstractController
{
private AccompanyingPeriodRepository $accompanyingPeriodRepository;
private ActivityACLAwareRepositoryInterface $activityACLAwareRepository;
private ActivityRepository $activityRepository;
private ActivityTypeCategoryRepository $activityTypeCategoryRepository;
private ActivityTypeRepositoryInterface $activityTypeRepository;
private CenterResolverManagerInterface $centerResolver;
private EntityManagerInterface $entityManager;
private EventDispatcherInterface $eventDispatcher;
private LocationRepository $locationRepository;
private LoggerInterface $logger;
private PersonRepository $personRepository;
private SerializerInterface $serializer;
private ThirdPartyRepository $thirdPartyRepository;
private TranslatorInterface $translator;
private UserRepositoryInterface $userRepository;
public function __construct(
ActivityACLAwareRepositoryInterface $activityACLAwareRepository,
ActivityTypeRepositoryInterface $activityTypeRepository,
ActivityTypeCategoryRepository $activityTypeCategoryRepository,
PersonRepository $personRepository,
ThirdPartyRepository $thirdPartyRepository,
LocationRepository $locationRepository,
ActivityRepository $activityRepository,
AccompanyingPeriodRepository $accompanyingPeriodRepository,
EntityManagerInterface $entityManager,
EventDispatcherInterface $eventDispatcher,
LoggerInterface $logger,
SerializerInterface $serializer,
UserRepositoryInterface $userRepository,
CenterResolverManagerInterface $centerResolver,
TranslatorInterface $translator
private readonly ActivityACLAwareRepositoryInterface $activityACLAwareRepository,
private readonly ActivityTypeRepositoryInterface $activityTypeRepository,
private readonly ActivityTypeCategoryRepository $activityTypeCategoryRepository,
private readonly PersonRepository $personRepository,
private readonly ThirdPartyRepository $thirdPartyRepository,
private readonly LocationRepository $locationRepository,
private readonly ActivityRepository $activityRepository,
private readonly AccompanyingPeriodRepository $accompanyingPeriodRepository,
private readonly EntityManagerInterface $entityManager,
private readonly EventDispatcherInterface $eventDispatcher,
private readonly LoggerInterface $logger,
private readonly SerializerInterface $serializer,
private readonly UserRepositoryInterface $userRepository,
private readonly CenterResolverManagerInterface $centerResolver,
private readonly TranslatorInterface $translator,
private readonly FilterOrderHelperFactoryInterface $filterOrderHelperFactory,
private readonly TranslatableStringHelperInterface $translatableStringHelper,
private readonly PaginatorFactory $paginatorFactory,
) {
$this->activityACLAwareRepository = $activityACLAwareRepository;
$this->activityTypeRepository = $activityTypeRepository;
$this->activityTypeCategoryRepository = $activityTypeCategoryRepository;
$this->personRepository = $personRepository;
$this->thirdPartyRepository = $thirdPartyRepository;
$this->locationRepository = $locationRepository;
$this->activityRepository = $activityRepository;
$this->accompanyingPeriodRepository = $accompanyingPeriodRepository;
$this->entityManager = $entityManager;
$this->eventDispatcher = $eventDispatcher;
$this->logger = $logger;
$this->serializer = $serializer;
$this->userRepository = $userRepository;
$this->centerResolver = $centerResolver;
$this->translator = $translator;
}
/**
* Deletes a Activity entity.
*
* @param mixed $id
* @\Symfony\Component\Routing\Annotation\Route(path="/{_locale}/activity/{id}/delete", name="chill_activity_activity_delete", methods={"GET", "POST", "DELETE"})
*/
public function deleteAction(Request $request, $id)
public function deleteAction(Request $request, mixed $id)
{
$view = null;
@@ -129,10 +88,10 @@ final class ActivityController extends AbstractController
}
if ($activity->getAccompanyingPeriod() instanceof AccompanyingPeriod) {
$view = 'ChillActivityBundle:Activity:confirm_deleteAccompanyingCourse.html.twig';
$view = '@ChillActivity/Activity/confirm_deleteAccompanyingCourse.html.twig';
$accompanyingPeriod = $activity->getAccompanyingPeriod();
} else {
$view = 'ChillActivityBundle:Activity:confirm_deletePerson.html.twig';
$view = '@ChillActivity/Activity/confirm_deletePerson.html.twig';
}
// TODO
@@ -140,7 +99,7 @@ final class ActivityController extends AbstractController
$form = $this->createDeleteForm($activity->getId(), $person, $accompanyingPeriod);
if ($request->getMethod() === Request::METHOD_DELETE) {
if (Request::METHOD_DELETE === $request->getMethod()) {
$form->handleRequest($request);
if ($form->isValid()) {
@@ -173,10 +132,6 @@ final class ActivityController extends AbstractController
}
}
if (null === $view) {
throw $this->createNotFoundException('Template not found');
}
return $this->render($view, [
'activity' => $activity,
'delete_form' => $form->createView(),
@@ -187,6 +142,8 @@ final class ActivityController extends AbstractController
/**
* Displays a form to edit an existing Activity entity.
*
* @\Symfony\Component\Routing\Annotation\Route(path="/{_locale}/activity/{id}/edit", name="chill_activity_activity_edit", methods={"GET", "POST", "PUT"})
*/
public function editAction(int $id, Request $request): Response
{
@@ -202,10 +159,10 @@ final class ActivityController extends AbstractController
$person = $entity->getPerson();
if ($entity->getAccompanyingPeriod() instanceof AccompanyingPeriod) {
$view = 'ChillActivityBundle:Activity:editAccompanyingCourse.html.twig';
$view = '@ChillActivity/Activity/editAccompanyingCourse.html.twig';
$accompanyingPeriod = $entity->getAccompanyingPeriod();
} else {
$view = 'ChillActivityBundle:Activity:editPerson.html.twig';
$view = '@ChillActivity/Activity/editPerson.html.twig';
}
// TODO
// $this->denyAccessUnlessGranted('CHILL_ACTIVITY_UPDATE', $entity);
@@ -266,10 +223,6 @@ final class ActivityController extends AbstractController
$this->eventDispatcher->dispatch(PrivacyEvent::PERSON_PRIVACY_EVENT, $event);
*/
if (null === $view) {
throw $this->createNotFoundException('Template not found');
}
$activity_array = $this->serializer->normalize($entity, 'json', ['groups' => 'read']);
return $this->render($view, [
@@ -284,34 +237,64 @@ final class ActivityController extends AbstractController
/**
* Lists all Activity entities.
*
* @\Symfony\Component\Routing\Annotation\Route(path="/{_locale}/activity/", name="chill_activity_activity_list")
*/
public function listAction(Request $request): Response
{
$view = null;
$activities = [];
// TODO: add pagination
[$person, $accompanyingPeriod] = $this->getEntity($request);
$filter = $this->buildFilterOrder($person ?? $accompanyingPeriod);
$filterArgs = [
'my_activities' => $filter->getSingleCheckboxData('my_activities'),
'types' => $filter->hasEntityChoice('activity_types') ? $filter->getEntityChoiceData('activity_types') : [],
'jobs' => $filter->hasEntityChoice('jobs') ? $filter->getEntityChoiceData('jobs') : [],
'before' => $filter->getDateRangeData('activity_date')['to'],
'after' => $filter->getDateRangeData('activity_date')['from'],
];
if ($person instanceof Person) {
$this->denyAccessUnlessGranted(ActivityVoter::SEE, $person);
$count = $this->activityACLAwareRepository->countByPerson($person, ActivityVoter::SEE, $filterArgs);
$paginator = $this->paginatorFactory->create($count);
$activities = $this->activityACLAwareRepository
->findByPerson($person, ActivityVoter::SEE, 0, null, ['date' => 'DESC', 'id' => 'DESC']);
->findByPerson(
$person,
ActivityVoter::SEE,
$paginator->getCurrentPageFirstItemNumber(),
$paginator->getItemsPerPage(),
['date' => 'DESC', 'id' => 'DESC'],
$filterArgs
);
$event = new PrivacyEvent($person, [
'element_class' => Activity::class,
'action' => 'list',
]);
$this->eventDispatcher->dispatch(PrivacyEvent::PERSON_PRIVACY_EVENT, $event);
$this->eventDispatcher->dispatch($event, PrivacyEvent::PERSON_PRIVACY_EVENT);
$view = 'ChillActivityBundle:Activity:listPerson.html.twig';
$view = '@ChillActivity/Activity/listPerson.html.twig';
} elseif ($accompanyingPeriod instanceof AccompanyingPeriod) {
$this->denyAccessUnlessGranted(ActivityVoter::SEE, $accompanyingPeriod);
$count = $this->activityACLAwareRepository->countByAccompanyingPeriod($accompanyingPeriod, ActivityVoter::SEE, $filterArgs);
$paginator = $this->paginatorFactory->create($count);
$activities = $this->activityACLAwareRepository
->findByAccompanyingPeriod($accompanyingPeriod, ActivityVoter::SEE, 0, null, ['date' => 'DESC', 'id' => 'DESC']);
->findByAccompanyingPeriod(
$accompanyingPeriod,
ActivityVoter::SEE,
$paginator->getCurrentPageFirstItemNumber(),
$paginator->getItemsPerPage(),
['date' => 'DESC', 'id' => 'DESC'],
$filterArgs
);
$view = 'ChillActivityBundle:Activity:listAccompanyingCourse.html.twig';
$view = '@ChillActivity/Activity/listAccompanyingCourse.html.twig';
} else {
throw new \LogicException('Unsupported');
}
return $this->render(
@@ -320,10 +303,49 @@ final class ActivityController extends AbstractController
'activities' => $activities,
'person' => $person,
'accompanyingCourse' => $accompanyingPeriod,
'filter' => $filter,
'paginator' => $paginator,
]
);
}
private function buildFilterOrder(AccompanyingPeriod|Person $associated): FilterOrderHelper
{
$filterBuilder = $this->filterOrderHelperFactory->create(self::class);
$types = $this->activityACLAwareRepository->findActivityTypeByAssociated($associated);
$jobs = $this->activityACLAwareRepository->findUserJobByAssociated($associated);
$filterBuilder
->addDateRange('activity_date', 'activity.date')
->addSingleCheckbox('my_activities', 'activity_filter.My activities');
if (1 < count($types)) {
$filterBuilder
->addEntityChoice('activity_types', 'activity_filter.Types', \Chill\ActivityBundle\Entity\ActivityType::class, $types, [
'choice_label' => function (\Chill\ActivityBundle\Entity\ActivityType $activityType) {
$text = match ($activityType->hasCategory()) {
true => $this->translatableStringHelper->localize($activityType->getCategory()->getName()).' > ',
false => '',
};
return $text.$this->translatableStringHelper->localize($activityType->getName());
},
]);
}
if (1 < count($jobs)) {
$filterBuilder
->addEntityChoice('jobs', 'activity_filter.Jobs', UserJob::class, $jobs, [
'choice_label' => fn (UserJob $u) => $this->translatableStringHelper->localize($u->getLabel()),
]);
}
return $filterBuilder->build();
}
/**
* @\Symfony\Component\Routing\Annotation\Route(path="/{_locale}/activity/new", name="chill_activity_activity_new", methods={"POST", "GET"})
*/
public function newAction(Request $request): Response
{
$view = null;
@@ -331,16 +353,16 @@ final class ActivityController extends AbstractController
[$person, $accompanyingPeriod] = $this->getEntity($request);
if ($accompanyingPeriod instanceof AccompanyingPeriod) {
$view = 'ChillActivityBundle:Activity:newAccompanyingCourse.html.twig';
$view = '@ChillActivity/Activity/newAccompanyingCourse.html.twig';
} elseif ($person instanceof Person) {
$view = 'ChillActivityBundle:Activity:newPerson.html.twig';
$view = '@ChillActivity/Activity/newPerson.html.twig';
}
$activityType_id = $request->get('activityType_id', 0);
$activityType = $this->activityTypeRepository->find($activityType_id);
if (isset($activityType) && !$activityType->isActive()) {
throw new InvalidArgumentException('Activity type must be active');
throw new \InvalidArgumentException('Activity type must be active');
}
$activityData = null;
@@ -375,45 +397,45 @@ final class ActivityController extends AbstractController
}
$entity->setActivityType($activityType);
$entity->setDate(new DateTime('now'));
$entity->setDate(new \DateTime('now'));
if ($request->query->has('activityData')) {
$activityData = $request->query->get('activityData');
if (array_key_exists('durationTime', $activityData) && $activityType->getDurationTimeVisible() > 0) {
if (\array_key_exists('durationTime', $activityData) && $activityType->getDurationTimeVisible() > 0) {
$durationTimeInMinutes = $activityData['durationTime'];
$hours = floor($durationTimeInMinutes / 60);
$minutes = $durationTimeInMinutes % 60;
$duration = DateTime::createFromFormat('H:i', $hours . ':' . $minutes);
$duration = \DateTime::createFromFormat('H:i', $hours.':'.$minutes);
if ($duration) {
$entity->setDurationTime($duration);
}
}
if (array_key_exists('date', $activityData)) {
$date = DateTime::createFromFormat('Y-m-d', $activityData['date']);
if (\array_key_exists('date', $activityData)) {
$date = \DateTime::createFromFormat('Y-m-d', $activityData['date']);
if ($date) {
$entity->setDate($date);
}
}
if (array_key_exists('personsId', $activityData) && $activityType->getPersonsVisible() > 0) {
if (\array_key_exists('personsId', $activityData) && $activityType->getPersonsVisible() > 0) {
foreach ($activityData['personsId'] as $personId) {
$concernedPerson = $this->personRepository->find($personId);
$entity->addPerson($concernedPerson);
}
}
if (array_key_exists('professionalsId', $activityData) && $activityType->getThirdPartiesVisible() > 0) {
if (\array_key_exists('professionalsId', $activityData) && $activityType->getThirdPartiesVisible() > 0) {
foreach ($activityData['professionalsId'] as $professionalsId) {
$professional = $this->thirdPartyRepository->find($professionalsId);
$entity->addThirdParty($professional);
}
}
if (array_key_exists('usersId', $activityData) && $activityType->getUsersVisible() > 0) {
if (\array_key_exists('usersId', $activityData) && $activityType->getUsersVisible() > 0) {
foreach ($activityData['usersId'] as $userId) {
$user = $this->userRepository->find($userId);
@@ -423,16 +445,16 @@ final class ActivityController extends AbstractController
}
}
if (array_key_exists('location', $activityData) && $activityType->getLocationVisible() > 0) {
if (\array_key_exists('location', $activityData) && $activityType->getLocationVisible() > 0) {
$location = $this->locationRepository->find($activityData['location']);
$entity->setLocation($location);
}
if (array_key_exists('comment', $activityData) && $activityType->getCommentVisible() > 0) {
if (\array_key_exists('comment', $activityData) && $activityType->getCommentVisible() > 0) {
$comment = new CommentEmbeddable();
$comment->setComment($activityData['comment']);
$comment->setUserId($this->getUser()->getid());
$comment->setDate(new DateTime('now'));
$comment->setDate(new \DateTime('now'));
$entity->setComment($comment);
}
}
@@ -504,6 +526,9 @@ final class ActivityController extends AbstractController
]);
}
/**
* @\Symfony\Component\Routing\Annotation\Route(path="/{_locale}/activity/select-type", name="chill_activity_activity_select_type")
*/
public function selectTypeAction(Request $request): Response
{
$view = null;
@@ -511,9 +536,9 @@ final class ActivityController extends AbstractController
[$person, $accompanyingPeriod] = $this->getEntity($request);
if ($accompanyingPeriod instanceof AccompanyingPeriod) {
$view = 'ChillActivityBundle:Activity:selectTypeAccompanyingCourse.html.twig';
$view = '@ChillActivity/Activity/selectTypeAccompanyingCourse.html.twig';
} elseif ($person instanceof Person) {
$view = 'ChillActivityBundle:Activity:selectTypePerson.html.twig';
$view = '@ChillActivity/Activity/selectTypePerson.html.twig';
}
$data = [];
@@ -548,6 +573,9 @@ final class ActivityController extends AbstractController
]);
}
/**
* @\Symfony\Component\Routing\Annotation\Route(path="/{_locale}/activity/{id}/show", name="chill_activity_activity_show")
*/
public function showAction(Request $request, int $id): Response
{
$entity = $this->activityRepository->find($id);
@@ -560,11 +588,11 @@ final class ActivityController extends AbstractController
$person = $entity->getPerson();
if ($accompanyingPeriod instanceof AccompanyingPeriod) {
$view = 'ChillActivityBundle:Activity:showAccompanyingCourse.html.twig';
$view = '@ChillActivity/Activity/showAccompanyingCourse.html.twig';
} elseif ($person instanceof Person) {
$view = 'ChillActivityBundle:Activity:showPerson.html.twig';
$view = '@ChillActivity/Activity/showPerson.html.twig';
} else {
throw new RuntimeException('the activity should be linked with a period or person');
throw new \RuntimeException('the activity should be linked with a period or person');
}
if (null !== $accompanyingPeriod) {
@@ -587,10 +615,6 @@ final class ActivityController extends AbstractController
$this->eventDispatcher->dispatch(PrivacyEvent::PERSON_PRIVACY_EVENT, $event);
*/
if (null === $view) {
throw $this->createNotFoundException('Template not found');
}
return $this->render($view, [
'person' => $person,
'accompanyingCourse' => $accompanyingPeriod,

View File

@@ -24,6 +24,8 @@ class ActivityReasonCategoryController extends AbstractController
{
/**
* Creates a new ActivityReasonCategory entity.
*
* @\Symfony\Component\Routing\Annotation\Route(path="/{_locale}/admin/activityreasoncategory/create", name="chill_activity_activityreasoncategory_create", methods={"POST"})
*/
public function createAction(Request $request)
{
@@ -31,15 +33,15 @@ class ActivityReasonCategoryController extends AbstractController
$form = $this->createCreateForm($entity);
$form->handleRequest($request);
if ($form->isValid()) {
if ($form->isSubmitted() && $form->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($entity);
$em->flush();
return $this->redirect($this->generateUrl('chill_activity_activityreasoncategory_show', ['id' => $entity->getId()]));
return $this->redirectToRoute('chill_activity_activityreasoncategory_show', ['id' => $entity->getId()]);
}
return $this->render('ChillActivityBundle:ActivityReasonCategory:new.html.twig', [
return $this->render('@ChillActivity/ActivityReasonCategory/new.html.twig', [
'entity' => $entity,
'form' => $form->createView(),
]);
@@ -48,13 +50,13 @@ class ActivityReasonCategoryController extends AbstractController
/**
* Displays a form to edit an existing ActivityReasonCategory entity.
*
* @param mixed $id
* @\Symfony\Component\Routing\Annotation\Route(path="/{_locale}/admin/activityreasoncategory/{id}/edit", name="chill_activity_activityreasoncategory_edit")
*/
public function editAction($id)
public function editAction(mixed $id)
{
$em = $this->getDoctrine()->getManager();
$entity = $em->getRepository(\Chill\ActivityBundle\Entity\ActivityReasonCategory::class)->find($id);
$entity = $em->getRepository(ActivityReasonCategory::class)->find($id);
if (!$entity) {
throw $this->createNotFoundException('Unable to find ActivityReasonCategory entity.');
@@ -62,7 +64,7 @@ class ActivityReasonCategoryController extends AbstractController
$editForm = $this->createEditForm($entity);
return $this->render('ChillActivityBundle:ActivityReasonCategory:edit.html.twig', [
return $this->render('@ChillActivity/ActivityReasonCategory/edit.html.twig', [
'entity' => $entity,
'edit_form' => $editForm->createView(),
]);
@@ -70,27 +72,31 @@ class ActivityReasonCategoryController extends AbstractController
/**
* Lists all ActivityReasonCategory entities.
*
* @\Symfony\Component\Routing\Annotation\Route(path="/{_locale}/admin/activityreasoncategory/", name="chill_activity_activityreasoncategory")
*/
public function indexAction()
{
$em = $this->getDoctrine()->getManager();
$entities = $em->getRepository(\Chill\ActivityBundle\Entity\ActivityReasonCategory::class)->findAll();
$entities = $em->getRepository(ActivityReasonCategory::class)->findAll();
return $this->render('ChillActivityBundle:ActivityReasonCategory:index.html.twig', [
return $this->render('@ChillActivity/ActivityReasonCategory/index.html.twig', [
'entities' => $entities,
]);
}
/**
* Displays a form to create a new ActivityReasonCategory entity.
*
* @\Symfony\Component\Routing\Annotation\Route(path="/{_locale}/admin/activityreasoncategory/new", name="chill_activity_activityreasoncategory_new")
*/
public function newAction()
{
$entity = new ActivityReasonCategory();
$form = $this->createCreateForm($entity);
return $this->render('ChillActivityBundle:ActivityReasonCategory:new.html.twig', [
return $this->render('@ChillActivity/ActivityReasonCategory/new.html.twig', [
'entity' => $entity,
'form' => $form->createView(),
]);
@@ -99,19 +105,19 @@ class ActivityReasonCategoryController extends AbstractController
/**
* Finds and displays a ActivityReasonCategory entity.
*
* @param mixed $id
* @\Symfony\Component\Routing\Annotation\Route(path="/{_locale}/admin/activityreasoncategory/{id}/show", name="chill_activity_activityreasoncategory_show")
*/
public function showAction($id)
public function showAction(mixed $id)
{
$em = $this->getDoctrine()->getManager();
$entity = $em->getRepository(\Chill\ActivityBundle\Entity\ActivityReasonCategory::class)->find($id);
$entity = $em->getRepository(ActivityReasonCategory::class)->find($id);
if (!$entity) {
throw $this->createNotFoundException('Unable to find ActivityReasonCategory entity.');
}
return $this->render('ChillActivityBundle:ActivityReasonCategory:show.html.twig', [
return $this->render('@ChillActivity/ActivityReasonCategory/show.html.twig', [
'entity' => $entity,
]);
}
@@ -119,13 +125,13 @@ class ActivityReasonCategoryController extends AbstractController
/**
* Edits an existing ActivityReasonCategory entity.
*
* @param mixed $id
* @\Symfony\Component\Routing\Annotation\Route(path="/{_locale}/admin/activityreasoncategory/{id}/update", name="chill_activity_activityreasoncategory_update", methods={"POST", "PUT"})
*/
public function updateAction(Request $request, $id)
public function updateAction(Request $request, mixed $id)
{
$em = $this->getDoctrine()->getManager();
$entity = $em->getRepository(\Chill\ActivityBundle\Entity\ActivityReasonCategory::class)->find($id);
$entity = $em->getRepository(ActivityReasonCategory::class)->find($id);
if (!$entity) {
throw $this->createNotFoundException('Unable to find ActivityReasonCategory entity.');
@@ -134,13 +140,13 @@ class ActivityReasonCategoryController extends AbstractController
$editForm = $this->createEditForm($entity);
$editForm->handleRequest($request);
if ($editForm->isValid()) {
if ($editForm->isSubmitted() && $editForm->isValid()) {
$em->flush();
return $this->redirect($this->generateUrl('chill_activity_activityreasoncategory_edit', ['id' => $id]));
return $this->redirectToRoute('chill_activity_activityreasoncategory_edit', ['id' => $id]);
}
return $this->render('ChillActivityBundle:ActivityReasonCategory:edit.html.twig', [
return $this->render('@ChillActivity/ActivityReasonCategory/edit.html.twig', [
'entity' => $entity,
'edit_form' => $editForm->createView(),
]);

View File

@@ -24,15 +24,14 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
*/
class ActivityReasonController extends AbstractController
{
private ActivityReasonRepository $activityReasonRepository;
public function __construct(ActivityReasonRepository $activityReasonRepository)
public function __construct(private readonly ActivityReasonRepository $activityReasonRepository)
{
$this->activityReasonRepository = $activityReasonRepository;
}
/**
* Creates a new ActivityReason entity.
*
* @\Symfony\Component\Routing\Annotation\Route(path="/{_locale}/admin/activityreason/create", name="chill_activity_activityreason_create", methods={"POST"})
*/
public function createAction(Request $request)
{
@@ -40,15 +39,15 @@ class ActivityReasonController extends AbstractController
$form = $this->createCreateForm($entity);
$form->handleRequest($request);
if ($form->isValid()) {
if ($form->isSubmitted() && $form->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($entity);
$em->flush();
return $this->redirect($this->generateUrl('chill_activity_activityreason', ['id' => $entity->getId()]));
return $this->redirectToRoute('chill_activity_activityreason', ['id' => $entity->getId()]);
}
return $this->render('ChillActivityBundle:ActivityReason:new.html.twig', [
return $this->render('@ChillActivity/ActivityReason/new.html.twig', [
'entity' => $entity,
'form' => $form->createView(),
]);
@@ -57,13 +56,13 @@ class ActivityReasonController extends AbstractController
/**
* Displays a form to edit an existing ActivityReason entity.
*
* @param mixed $id
* @\Symfony\Component\Routing\Annotation\Route(path="/{_locale}/admin/activityreason/{id}/edit", name="chill_activity_activityreason_edit")
*/
public function editAction($id)
public function editAction(mixed $id)
{
$em = $this->getDoctrine()->getManager();
$entity = $em->getRepository(\Chill\ActivityBundle\Entity\ActivityReason::class)->find($id);
$entity = $em->getRepository(ActivityReason::class)->find($id);
if (null === $entity) {
throw new NotFoundHttpException('Unable to find ActivityReason entity.');
@@ -71,7 +70,7 @@ class ActivityReasonController extends AbstractController
$editForm = $this->createEditForm($entity);
return $this->render('ChillActivityBundle:ActivityReason:edit.html.twig', [
return $this->render('@ChillActivity/ActivityReason/edit.html.twig', [
'entity' => $entity,
'edit_form' => $editForm->createView(),
]);
@@ -79,6 +78,8 @@ class ActivityReasonController extends AbstractController
/**
* Lists all ActivityReason entities.
*
* @\Symfony\Component\Routing\Annotation\Route(path="/{_locale}/admin/activityreason/", name="chill_activity_activityreason")
*/
public function indexAction()
{
@@ -86,20 +87,22 @@ class ActivityReasonController extends AbstractController
$entities = $this->activityReasonRepository->findAll();
return $this->render('ChillActivityBundle:ActivityReason:index.html.twig', [
return $this->render('@ChillActivity/ActivityReason/index.html.twig', [
'entities' => $entities,
]);
}
/**
* Displays a form to create a new ActivityReason entity.
*
* @\Symfony\Component\Routing\Annotation\Route(path="/{_locale}/admin/activityreason/new", name="chill_activity_activityreason_new")
*/
public function newAction()
{
$entity = new ActivityReason();
$form = $this->createCreateForm($entity);
return $this->render('ChillActivityBundle:ActivityReason:new.html.twig', [
return $this->render('@ChillActivity/ActivityReason/new.html.twig', [
'entity' => $entity,
'form' => $form->createView(),
]);
@@ -108,19 +111,19 @@ class ActivityReasonController extends AbstractController
/**
* Finds and displays a ActivityReason entity.
*
* @param mixed $id
* @\Symfony\Component\Routing\Annotation\Route(path="/{_locale}/admin/activityreason/{id}/show", name="chill_activity_activityreason_show")
*/
public function showAction($id)
public function showAction(mixed $id)
{
$em = $this->getDoctrine()->getManager();
$entity = $em->getRepository(\Chill\ActivityBundle\Entity\ActivityReason::class)->find($id);
$entity = $em->getRepository(ActivityReason::class)->find($id);
if (!$entity) {
throw $this->createNotFoundException('Unable to find ActivityReason entity.');
}
return $this->render('ChillActivityBundle:ActivityReason:show.html.twig', [
return $this->render('@ChillActivity/ActivityReason/show.html.twig', [
'entity' => $entity,
]);
}
@@ -128,13 +131,13 @@ class ActivityReasonController extends AbstractController
/**
* Edits an existing ActivityReason entity.
*
* @param mixed $id
* @\Symfony\Component\Routing\Annotation\Route(path="/{_locale}/admin/activityreason/{id}/update", name="chill_activity_activityreason_update", methods={"POST", "PUT"})
*/
public function updateAction(Request $request, $id)
public function updateAction(Request $request, mixed $id)
{
$em = $this->getDoctrine()->getManager();
$entity = $em->getRepository(\Chill\ActivityBundle\Entity\ActivityReason::class)->find($id);
$entity = $em->getRepository(ActivityReason::class)->find($id);
if (!$entity) {
throw $this->createNotFoundException('Unable to find ActivityReason entity.');
@@ -143,13 +146,13 @@ class ActivityReasonController extends AbstractController
$editForm = $this->createEditForm($entity);
$editForm->handleRequest($request);
if ($editForm->isValid()) {
if ($editForm->isSubmitted() && $editForm->isValid()) {
$em->flush();
return $this->redirect($this->generateUrl('chill_activity_activityreason', ['id' => $id]));
return $this->redirectToRoute('chill_activity_activityreason', ['id' => $id]);
}
return $this->render('ChillActivityBundle:ActivityReason:edit.html.twig', [
return $this->render('@ChillActivity/ActivityReason/edit.html.twig', [
'entity' => $entity,
'edit_form' => $editForm->createView(),
]);

View File

@@ -24,7 +24,7 @@ class AdminActivityPresenceController extends CRUDController
*/
protected function orderQuery(string $action, $query, Request $request, PaginatorInterface $paginator)
{
/** @var \Doctrine\ORM\QueryBuilder $query */
/* @var \Doctrine\ORM\QueryBuilder $query */
return $query->orderBy('e.id', 'ASC');
}
}

View File

@@ -24,7 +24,7 @@ class AdminActivityTypeCategoryController extends CRUDController
*/
protected function orderQuery(string $action, $query, Request $request, PaginatorInterface $paginator)
{
/** @var \Doctrine\ORM\QueryBuilder $query */
/* @var \Doctrine\ORM\QueryBuilder $query */
return $query->orderBy('e.ordering', 'ASC');
}
}

View File

@@ -24,7 +24,7 @@ class AdminActivityTypeController extends CRUDController
*/
protected function orderQuery(string $action, $query, Request $request, PaginatorInterface $paginator)
{
/** @var \Doctrine\ORM\QueryBuilder $query */
/* @var \Doctrine\ORM\QueryBuilder $query */
return $query->orderBy('e.ordering', 'ASC')
->addOrderBy('e.id', 'ASC');
}

View File

@@ -18,11 +18,18 @@ use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
*/
class AdminController extends AbstractController
{
/**
* @\Symfony\Component\Routing\Annotation\Route(path="/{_locale}/admin/activity", name="chill_activity_admin_index")
*/
public function indexActivityAction()
{
return $this->render('ChillActivityBundle:Admin:layout_activity.html.twig');
return $this->render('@ChillActivity/Admin/layout_activity.html.twig');
}
/**
* @\Symfony\Component\Routing\Annotation\Route(path="/{_locale}/admin/activity_redirect_to_main", name="chill_admin_aside_activity_redirect_to_admin_index", options={null})
* @\Symfony\Component\Routing\Annotation\Route(path="/{_locale}/admin/activity_redirect_to_main", name="chill_admin_activity_redirect_to_admin_index")
*/
public function redirectToAdminIndexAction()
{
return $this->redirectToRoute('chill_main_admin_central');

View File

@@ -25,17 +25,11 @@ class LoadActivity extends AbstractFixture implements OrderedFixtureInterface
{
use \Symfony\Component\DependencyInjection\ContainerAwareTrait;
private EntityManagerInterface $em;
private readonly \Faker\Generator $faker;
/**
* @var \Faker\Generator
*/
private $faker;
public function __construct(EntityManagerInterface $em)
public function __construct(private readonly EntityManagerInterface $em)
{
$this->faker = FakerFactory::create('fr_FR');
$this->em = $em;
}
public function getOrder()

View File

@@ -52,13 +52,13 @@ class LoadActivityReason extends AbstractFixture implements OrderedFixtureInterf
];
foreach ($reasons as $r) {
echo 'Creating activity reason : ' . $r['name']['en'] . "\n";
echo 'Creating activity reason : '.$r['name']['en']."\n";
$activityReason = (new ActivityReason())
->setName(($r['name']))
->setName($r['name'])
->setActive(true)
->setCategory($this->getReference($r['category']));
$manager->persist($activityReason);
$reference = 'activity_reason_' . $r['name']['en'];
$reference = 'activity_reason_'.$r['name']['en'];
$this->addReference($reference, $activityReason);
static::$references[] = $reference;
}

View File

@@ -34,13 +34,13 @@ class LoadActivityReasonCategory extends AbstractFixture implements OrderedFixtu
];
foreach ($categs as $c) {
echo 'Creating activity reason category : ' . $c['name']['en'] . "\n";
echo 'Creating activity reason category : '.$c['name']['en']."\n";
$activityReasonCategory = (new ActivityReasonCategory())
->setName(($c['name']))
->setName($c['name'])
->setActive(true);
$manager->persist($activityReasonCategory);
$this->addReference(
'cat_' . $c['name']['en'],
'cat_'.$c['name']['en'],
$activityReasonCategory
);
}

View File

@@ -54,14 +54,14 @@ class LoadActivityType extends Fixture implements OrderedFixtureInterface
];
foreach ($types as $t) {
echo 'Creating activity type : ' . $t['name']['fr'] . ' (cat:' . $t['category'] . " \n";
echo 'Creating activity type : '.$t['name']['fr'].' (cat:'.$t['category']." \n";
$activityType = (new ActivityType())
->setName(($t['name']))
->setCategory($this->getReference('activity_type_cat_' . $t['category']))
->setName($t['name'])
->setCategory($this->getReference('activity_type_cat_'.$t['category']))
->setSocialIssuesVisible(1)
->setSocialActionsVisible(1);
$manager->persist($activityType);
$reference = 'activity_type_' . $t['name']['fr'];
$reference = 'activity_type_'.$t['name']['fr'];
$this->addReference($reference, $activityType);
static::$references[] = $reference;
}

View File

@@ -42,13 +42,13 @@ class LoadActivityTypeCategory extends Fixture implements OrderedFixtureInterfac
];
foreach ($categories as $cat) {
echo 'Creating activity type category : ' . $cat['ref'] . "\n";
echo 'Creating activity type category : '.$cat['ref']."\n";
$newCat = (new ActivityTypeCategory())
->setName(($cat['name']));
->setName($cat['name']);
$manager->persist($newCat);
$reference = 'activity_type_cat_' . $cat['ref'];
$reference = 'activity_type_cat_'.$cat['ref'];
$this->addReference($reference, $newCat);
static::$references[] = $reference;

View File

@@ -20,8 +20,6 @@ use Doctrine\Common\DataFixtures\AbstractFixture;
use Doctrine\Common\DataFixtures\OrderedFixtureInterface;
use Doctrine\Persistence\ObjectManager;
use function in_array;
/**
* Add a role CHILL_ACTIVITY_UPDATE & CHILL_ACTIVITY_CREATE for all groups except administrative,
* and a role CHILL_ACTIVITY_SEE for administrative.
@@ -40,10 +38,10 @@ class LoadActivitytACL extends AbstractFixture implements OrderedFixtureInterfac
foreach (LoadScopes::$references as $scopeRef) {
$scope = $this->getReference($scopeRef);
//create permission group
// create permission group
switch ($permissionsGroup->getName()) {
case 'social':
if ($scope->getName()['en'] === 'administrative') {
if ('administrative' === $scope->getName()['en']) {
break 2; // we do not want any power on administrative
}
@@ -51,7 +49,7 @@ class LoadActivitytACL extends AbstractFixture implements OrderedFixtureInterfac
case 'administrative':
case 'direction':
if (in_array($scope->getName()['en'], ['administrative', 'social'], true)) {
if (\in_array($scope->getName()['en'], ['administrative', 'social'], true)) {
break 2; // we do not want any power on social or administrative
}
@@ -60,7 +58,7 @@ class LoadActivitytACL extends AbstractFixture implements OrderedFixtureInterfac
printf(
'Adding CHILL_ACTIVITY_UPDATE & CHILL_ACTIVITY_CREATE & CHILL_ACTIVITY_DELETE, and stats and list permissions to %s '
. "permission group, scope '%s' \n",
."permission group, scope '%s' \n",
$permissionsGroup->getName(),
$scope->getName()['en']
);

View File

@@ -32,7 +32,7 @@ class ChillActivityExtension extends Extension implements PrependExtensionInterf
$container->setParameter('chill_activity.form.time_duration', $config['form']['time_duration']);
$loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__ . '/../config'));
$loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../config'));
$loader->load('services.yaml');
$loader->load('services/export.yaml');
$loader->load('services/repositories.yaml');
@@ -73,7 +73,7 @@ class ChillActivityExtension extends Extension implements PrependExtensionInterf
*/
public function prependRoutes(ContainerBuilder $container)
{
//add routes for custom bundle
// add routes for custom bundle
$container->prependExtensionConfig('chill_main', [
'routing' => [
'resources' => [

View File

@@ -13,7 +13,6 @@ namespace Chill\ActivityBundle\DependencyInjection;
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;
use function is_int;
/**
@@ -59,7 +58,7 @@ class Configuration implements ConfigurationInterface
->info('The number of seconds of this duration. Must be an integer.')
->cannotBeEmpty()
->validate()
->ifTrue(static fn ($data) => !is_int($data))->thenInvalid('The value %s is not a valid integer')
->ifTrue(static fn ($data) => !\is_int($data))->thenInvalid('The value %s is not a valid integer')
->end()
->end()
->scalarNode('label')

View File

@@ -36,7 +36,6 @@ use DateTime;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Serializer\Annotation\DiscriminatorMap;
use Symfony\Component\Serializer\Annotation\Groups;
use Symfony\Component\Serializer\Annotation\SerializedName;
@@ -46,11 +45,15 @@ use Symfony\Component\Validator\Constraints as Assert;
* Class Activity.
*
* @ORM\Entity(repositoryClass="Chill\ActivityBundle\Repository\ActivityRepository")
*
* @ORM\Table(name="activity")
*
* @ORM\HasLifecycleCallbacks
*
* @DiscriminatorMap(typeProperty="type", mapping={
* "activity": Activity::class
* })
*
* @ActivityValidator\ActivityValidity
*
* TODO see if necessary
@@ -65,69 +68,84 @@ class Activity implements AccompanyingPeriodLinkedWithSocialIssuesEntityInterfac
use TrackUpdateTrait;
public const SENTRECEIVED_RECEIVED = 'received';
final public const SENTRECEIVED_RECEIVED = 'received';
public const SENTRECEIVED_SENT = 'sent';
final public const SENTRECEIVED_SENT = 'sent';
/**
* @ORM\ManyToOne(targetEntity="Chill\PersonBundle\Entity\AccompanyingPeriod")
*
* @Groups({"read"})
*/
private ?AccompanyingPeriod $accompanyingPeriod = null;
/**
* @ORM\ManyToOne(targetEntity="Chill\ActivityBundle\Entity\ActivityType")
*
* @Groups({"read", "docgen:read"})
*
* @SerializedName("activityType")
*
* @ORM\JoinColumn(name="type_id")
*/
private ActivityType $activityType;
/**
* @ORM\ManyToOne(targetEntity="Chill\ActivityBundle\Entity\ActivityPresence")
*
* @Groups({"docgen:read"})
*/
private ?ActivityPresence $attendee = null;
/**
* @ORM\Embedded(class="Chill\MainBundle\Entity\Embeddable\CommentEmbeddable", columnPrefix="comment_")
*
* @Groups({"docgen:read"})
*/
private CommentEmbeddable $comment;
/**
* @ORM\Column(type="datetime")
*
* @Groups({"docgen:read"})
*/
private DateTime $date;
private \DateTime $date;
/**
* @ORM\ManyToMany(targetEntity="Chill\DocStoreBundle\Entity\StoredObject", cascade={"persist"})
*
* @Assert\Valid(traverse=true)
*
* @var Collection<StoredObject>
*/
private Collection $documents;
/**
* @ORM\Column(type="time", nullable=true)
*/
private ?DateTime $durationTime = null;
private ?\DateTime $durationTime = null;
/**
* @ORM\Column(type="boolean", options={"default": false})
*
* @Groups({"docgen:read"})
*/
private bool $emergency = false;
/**
* @ORM\Id
*
* @ORM\Column(name="id", type="integer")
*
* @ORM\GeneratedValue(strategy="AUTO")
*
* @Groups({"read", "docgen:read"})
*/
private ?int $id = null;
/**
* @ORM\ManyToOne(targetEntity="Chill\MainBundle\Entity\Location")
*
* @groups({"read", "docgen:read"})
*/
private ?Location $location = null;
@@ -139,9 +157,12 @@ class Activity implements AccompanyingPeriodLinkedWithSocialIssuesEntityInterfac
/**
* @ORM\ManyToMany(targetEntity="Chill\PersonBundle\Entity\Person")
*
* @Groups({"read", "docgen:read"})
*
* @var Collection<Person>
*/
private ?Collection $persons = null;
private Collection $persons;
/**
* @ORM\Embedded(class="Chill\MainBundle\Entity\Embeddable\PrivateCommentEmbeddable", columnPrefix="privateComment_")
@@ -150,58 +171,78 @@ class Activity implements AccompanyingPeriodLinkedWithSocialIssuesEntityInterfac
/**
* @ORM\ManyToMany(targetEntity="Chill\ActivityBundle\Entity\ActivityReason")
*
* @Groups({"docgen:read"})
*
* @var Collection<ActivityReason>
*/
private Collection $reasons;
/**
* @ORM\ManyToOne(targetEntity="Chill\MainBundle\Entity\Scope")
*
* @Groups({"docgen:read"})
*/
private ?Scope $scope = null;
/**
* @ORM\Column(type="string", options={"default": ""})
*
* @Groups({"docgen:read"})
*/
private string $sentReceived = '';
/**
* @ORM\ManyToMany(targetEntity="Chill\PersonBundle\Entity\SocialWork\SocialAction")
*
* @ORM\JoinTable(name="chill_activity_activity_chill_person_socialaction")
*
* @Groups({"read", "docgen:read"})
*
* @var Collection<SocialAction>
*/
private Collection $socialActions;
/**
* @ORM\ManyToMany(targetEntity="Chill\PersonBundle\Entity\SocialWork\SocialIssue")
*
* @ORM\JoinTable(name="chill_activity_activity_chill_person_socialissue")
*
* @Groups({"read", "docgen:read"})
*
* @var Collection<SocialIssue>
*/
private Collection $socialIssues;
/**
* @ORM\ManyToMany(targetEntity="Chill\ThirdPartyBundle\Entity\ThirdParty")
*
* @Groups({"read", "docgen:read"})
*
* @var Collection<ThirdParty>
*/
private ?Collection $thirdParties = null;
private Collection $thirdParties;
/**
* @ORM\Column(type="time", nullable=true)
*/
private ?DateTime $travelTime = null;
private ?\DateTime $travelTime = null;
/**
* @ORM\ManyToOne(targetEntity="Chill\MainBundle\Entity\User")
*
* @Groups({"docgen:read"})
*/
private ?User $user = null;
/**
* @ORM\ManyToMany(targetEntity="Chill\MainBundle\Entity\User")
*
* @Groups({"read", "docgen:read"})
*
* @var Collection<User>
*/
private ?Collection $users = null;
private Collection $users;
public function __construct()
{
@@ -268,7 +309,7 @@ class Activity implements AccompanyingPeriodLinkedWithSocialIssuesEntityInterfac
$this->socialIssues[] = $socialIssue;
}
if ($this->getAccompanyingPeriod() !== null) {
if (null !== $this->getAccompanyingPeriod()) {
$this->getAccompanyingPeriod()->addSocialIssue($socialIssue);
}
@@ -334,7 +375,7 @@ class Activity implements AccompanyingPeriodLinkedWithSocialIssuesEntityInterfac
return $this->comment;
}
public function getDate(): DateTime
public function getDate(): \DateTime
{
return $this->date;
}
@@ -356,7 +397,7 @@ class Activity implements AccompanyingPeriodLinkedWithSocialIssuesEntityInterfac
return (int) round(($this->durationTime->getTimestamp() + $this->durationTime->getOffset()) / 60.0, 0);
}
public function getDurationTime(): ?DateTime
public function getDurationTime(): ?\DateTime
{
return $this->durationTime;
}
@@ -410,7 +451,7 @@ class Activity implements AccompanyingPeriodLinkedWithSocialIssuesEntityInterfac
// TODO better semantic with: return $this->persons->filter(...);
foreach ($this->persons as $person) {
if ($this->accompanyingPeriod->getOpenParticipationContainsPerson($person) === null) {
if (null === $this->accompanyingPeriod->getOpenParticipationContainsPerson($person)) {
$personsNotAssociated[] = $person;
}
}
@@ -469,7 +510,7 @@ class Activity implements AccompanyingPeriodLinkedWithSocialIssuesEntityInterfac
return $this->thirdParties;
}
public function getTravelTime(): ?DateTime
public function getTravelTime(): ?\DateTime
{
return $this->travelTime;
}
@@ -580,7 +621,7 @@ class Activity implements AccompanyingPeriodLinkedWithSocialIssuesEntityInterfac
return $this;
}
public function setDate(DateTime $date): self
public function setDate(\DateTime $date): self
{
$this->date = $date;
@@ -594,7 +635,7 @@ class Activity implements AccompanyingPeriodLinkedWithSocialIssuesEntityInterfac
return $this;
}
public function setDurationTime(?DateTime $durationTime): self
public function setDurationTime(?\DateTime $durationTime): self
{
$this->durationTime = $durationTime;
@@ -664,7 +705,7 @@ class Activity implements AccompanyingPeriodLinkedWithSocialIssuesEntityInterfac
return $this;
}
public function setTravelTime(DateTime $travelTime): self
public function setTravelTime(\DateTime $travelTime): self
{
$this->travelTime = $travelTime;

View File

@@ -18,7 +18,9 @@ use Symfony\Component\Serializer\Annotation as Serializer;
* Class ActivityPresence.
*
* @ORM\Entity
*
* @ORM\Table(name="activitytpresence")
*
* @ORM\HasLifecycleCallbacks
*/
class ActivityPresence
@@ -30,15 +32,20 @@ class ActivityPresence
/**
* @ORM\Id
*
* @ORM\Column(name="id", type="integer")
*
* @ORM\GeneratedValue(strategy="AUTO")
*
* @Serializer\Groups({"docgen:read"})
*/
private ?int $id = null;
/**
* @ORM\Column(type="json")
*
* @Serializer\Groups({"docgen:read"})
*
* @Serializer\Context({"is-translatable": true}, groups={"docgen:read"})
*/
private array $name = [];

View File

@@ -17,39 +17,38 @@ use Doctrine\ORM\Mapping as ORM;
* Class ActivityReason.
*
* @ORM\Entity
*
* @ORM\Table(name="activityreason")
*
* @ORM\HasLifecycleCallbacks
*/
class ActivityReason
{
/**
* @var bool
* @ORM\Column(type="boolean")
*/
private $active = true;
private bool $active = true;
/**
* @var ActivityReasonCategory
* @ORM\ManyToOne(
* targetEntity="Chill\ActivityBundle\Entity\ActivityReasonCategory",
* inversedBy="reasons")
*/
private $category;
private ?ActivityReasonCategory $category = null;
/**
* @var int
*
* @ORM\Id
*
* @ORM\Column(name="id", type="integer")
*
* @ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
private ?int $id = null;
/**
* @var array
* @ORM\Column(type="json")
*/
private $name;
private array $name;
/**
* Get active.
@@ -81,27 +80,9 @@ class ActivityReason
/**
* Get name.
*
* @param mixed|null $locale
*
* @return array | string
*/
public function getName($locale = null)
public function getName(): array
{
if ($locale) {
if (isset($this->name[$locale])) {
return $this->name[$locale];
}
foreach ($this->name as $name) {
if (!empty($name)) {
return $name;
}
}
return '';
}
return $this->name;
}

View File

@@ -12,34 +12,37 @@ declare(strict_types=1);
namespace Chill\ActivityBundle\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
/**
* Class ActivityReasonCategory.
*
* @ORM\Entity
*
* @ORM\Table(name="activityreasoncategory")
*
* @ORM\HasLifecycleCallbacks
*/
class ActivityReasonCategory
class ActivityReasonCategory implements \Stringable
{
/**
* @var bool
* @ORM\Column(type="boolean")
*/
private $active = true;
private bool $active = true;
/**
* @var int
*
* @ORM\Id
*
* @ORM\Column(name="id", type="integer")
*
* @ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
private ?int $id = null;
/**
* @var string
*
* @ORM\Column(type="json")
*/
private $name;
@@ -47,12 +50,13 @@ class ActivityReasonCategory
/**
* Array of ActivityReason.
*
* @var ArrayCollection
* @var Collection<ActivityReason>
*
* @ORM\OneToMany(
* targetEntity="Chill\ActivityBundle\Entity\ActivityReason",
* mappedBy="category")
*/
private $reasons;
private Collection $reasons;
/**
* ActivityReasonCategory constructor.
@@ -62,12 +66,9 @@ class ActivityReasonCategory
$this->reasons = new ArrayCollection();
}
/**
* @return string
*/
public function __toString()
public function __toString(): string
{
return 'ActivityReasonCategory(' . $this->getName('x') . ')';
return 'ActivityReasonCategory('.$this->getName('x').')';
}
/**
@@ -121,11 +122,9 @@ class ActivityReasonCategory
* as unactive, all the reason have this entity as category is also
* set as unactive.
*
* @param bool $active
*
* @return ActivityReasonCategory
*/
public function setActive($active)
public function setActive(bool $active)
{
if ($this->active !== $active && !$active) {
foreach ($this->reasons as $reason) {

View File

@@ -12,7 +12,6 @@ declare(strict_types=1);
namespace Chill\ActivityBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use InvalidArgumentException;
use Symfony\Component\Serializer\Annotation as Serializer;
use Symfony\Component\Serializer\Annotation\Groups;
use Symfony\Component\Validator\Constraints as Assert;
@@ -22,31 +21,36 @@ use Symfony\Component\Validator\Context\ExecutionContextInterface;
* Class ActivityType.
*
* @ORM\Entity
*
* @ORM\Table(name="activitytype")
*
* @ORM\HasLifecycleCallbacks
*/
class ActivityType
{
public const FIELD_INVISIBLE = 0;
final public const FIELD_INVISIBLE = 0;
public const FIELD_OPTIONAL = 1;
final public const FIELD_OPTIONAL = 1;
public const FIELD_REQUIRED = 2;
final public const FIELD_REQUIRED = 2;
/**
* @deprecated not in use
*
* @ORM\Column(type="string", nullable=false, options={"default": ""})
*/
private string $accompanyingPeriodLabel = '';
/**
* @deprecated not in use
*
* @ORM\Column(type="smallint", nullable=false, options={"default": 1})
*/
private int $accompanyingPeriodVisible = self::FIELD_INVISIBLE;
/**
* @ORM\Column(type="boolean")
*
* @Groups({"read"})
*/
private bool $active = true;
@@ -118,8 +122,11 @@ class ActivityType
/**
* @ORM\Id
*
* @ORM\Column(name="id", type="integer")
*
* @ORM\GeneratedValue(strategy="AUTO")
*
* @Groups({"docgen:read"})
*/
private ?int $id = null;
@@ -136,7 +143,9 @@ class ActivityType
/**
* @ORM\Column(type="json")
*
* @Groups({"read", "docgen:read"})
*
* @Serializer\Context({"is-translatable": true}, groups={"docgen:read"})
*/
private array $name = [];
@@ -158,6 +167,7 @@ class ActivityType
/**
* @ORM\Column(type="smallint", nullable=false, options={"default": 1})
*
* @Groups({"read"})
*/
private int $personsVisible = self::FIELD_OPTIONAL;
@@ -238,6 +248,7 @@ class ActivityType
/**
* @ORM\Column(type="smallint", nullable=false, options={"default": 1})
*
* @Groups({"read"})
*/
private int $thirdPartiesVisible = self::FIELD_INVISIBLE;
@@ -264,6 +275,7 @@ class ActivityType
/**
* @ORM\Column(type="smallint", nullable=false, options={"default": 1})
*
* @Groups({"read"})
*/
private int $usersVisible = self::FIELD_OPTIONAL;
@@ -275,13 +287,15 @@ class ActivityType
/**
* @Assert\Callback
*
* @param mixed $payload
*/
public function checkSocialActionsVisibility(ExecutionContextInterface $context, $payload)
public function checkSocialActionsVisibility(ExecutionContextInterface $context, mixed $payload)
{
if ($this->socialIssuesVisible !== $this->socialActionsVisible) {
if (!(2 === $this->socialIssuesVisible && 1 === $this->socialActionsVisible)) {
// if social issues are invisible then social actions cannot be optional or required + if social issues are optional then social actions shouldn't be required
if (
(0 === $this->socialIssuesVisible && (1 === $this->socialActionsVisible || 2 === $this->socialActionsVisible))
|| (1 === $this->socialIssuesVisible && 2 === $this->socialActionsVisible)
) {
$context
->buildViolation('The socialActionsVisible value is not compatible with the socialIssuesVisible value')
->atPath('socialActionsVisible')
@@ -374,13 +388,13 @@ class ActivityType
public function getLabel(string $field): ?string
{
$property = $field . 'Label';
$property = $field.'Label';
if (!property_exists($this, $property)) {
throw new InvalidArgumentException('Field "' . $field . '" not found');
throw new \InvalidArgumentException('Field "'.$field.'" not found');
}
/** @phpstan-ignore-next-line */
/* @phpstan-ignore-next-line */
return $this->{$property};
}
@@ -533,25 +547,25 @@ class ActivityType
public function isRequired(string $field): bool
{
$property = $field . 'Visible';
$property = $field.'Visible';
if (!property_exists($this, $property)) {
throw new InvalidArgumentException('Field "' . $field . '" not found');
throw new \InvalidArgumentException('Field "'.$field.'" not found');
}
/** @phpstan-ignore-next-line */
/* @phpstan-ignore-next-line */
return self::FIELD_REQUIRED === $this->{$property};
}
public function isVisible(string $field): bool
{
$property = $field . 'Visible';
$property = $field.'Visible';
if (!property_exists($this, $property)) {
throw new InvalidArgumentException('Field "' . $field . '" not found');
throw new \InvalidArgumentException('Field "'.$field.'" not found');
}
/** @phpstan-ignore-next-line */
/* @phpstan-ignore-next-line */
return self::FIELD_INVISIBLE !== $this->{$property};
}

View File

@@ -15,7 +15,9 @@ use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity
*
* @ORM\Table(name="activitytypecategory")
*
* @ORM\HasLifecycleCallbacks
*/
class ActivityTypeCategory
@@ -27,7 +29,9 @@ class ActivityTypeCategory
/**
* @ORM\Id
*
* @ORM\Column(name="id", type="integer")
*
* @ORM\GeneratedValue(strategy="AUTO")
*/
private ?int $id = null;

View File

@@ -15,21 +15,12 @@ use Chill\ActivityBundle\Entity\Activity;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWork;
use Chill\PersonBundle\Repository\AccompanyingPeriod\AccompanyingPeriodWorkRepository;
use DateTimeImmutable;
use Doctrine\ORM\EntityManagerInterface;
use function in_array;
class ActivityEntityListener
{
private EntityManagerInterface $em;
private AccompanyingPeriodWorkRepository $workRepository;
public function __construct(EntityManagerInterface $em, AccompanyingPeriodWorkRepository $workRepository)
public function __construct(private readonly EntityManagerInterface $em, private readonly AccompanyingPeriodWorkRepository $workRepository)
{
$this->em = $em;
$this->workRepository = $workRepository;
}
public function persistActionToCourse(Activity $activity)
@@ -39,11 +30,11 @@ class ActivityEntityListener
$accompanyingCourseWorks = $this->workRepository->findByAccompanyingPeriod($period);
$periodActions = [];
$now = new DateTimeImmutable();
$now = new \DateTimeImmutable();
foreach ($accompanyingCourseWorks as $key => $work) {
// take only the actions which are still opened
if ($work->getEndDate() === null || $work->getEndDate() > ($activity->getDate() ?? $now)) {
if (null === $work->getEndDate() || $work->getEndDate() > ($activity->getDate() ?? $now)) {
$periodActions[$key] = spl_object_hash($work->getSocialAction());
}
}
@@ -52,14 +43,14 @@ class ActivityEntityListener
$associatedThirdparties = $activity->getThirdParties();
foreach ($activity->getSocialActions() as $action) {
if (in_array(spl_object_hash($action), $periodActions, true)) {
if (\in_array(spl_object_hash($action), $periodActions, true)) {
continue;
}
$newAction = new AccompanyingPeriodWork();
$newAction->setSocialAction($action);
$period->addWork($newAction);
$date = DateTimeImmutable::createFromMutable($activity->getDate());
$date = \DateTimeImmutable::createFromMutable($activity->getDate());
$newAction->setStartDate($date);
foreach ($associatedPersons as $person) {

View File

@@ -27,7 +27,7 @@ class ByActivityNumberAggregator implements AggregatorInterface
public function alterQuery(QueryBuilder $qb, $data): void
{
$qb
->addSelect('(SELECT COUNT(activity.id) FROM ' . Activity::class . ' activity WHERE activity.accompanyingPeriod = acp) AS activity_by_number_aggregator')
->addSelect('(SELECT COUNT(activity.id) FROM '.Activity::class.' activity WHERE activity.accompanyingPeriod = acp) AS activity_by_number_aggregator')
->addGroupBy('activity_by_number_aggregator');
}
@@ -40,6 +40,7 @@ class ByActivityNumberAggregator implements AggregatorInterface
{
// No form needed
}
public function getFormDefaultData(): array
{
return [];

View File

@@ -0,0 +1,123 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\ActivityBundle\Export\Aggregator\ACPAggregators;
use Chill\ActivityBundle\Entity\Activity;
use Chill\ActivityBundle\Entity\ActivityType;
use Chill\ActivityBundle\Repository\ActivityTypeRepositoryInterface;
use Chill\MainBundle\Export\AggregatorInterface;
use Chill\MainBundle\Form\Type\PickRollingDateType;
use Chill\MainBundle\Service\RollingDate\RollingDateConverterInterface;
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
use Chill\PersonBundle\Export\Declarations;
use Doctrine\ORM\Query\Expr\Join;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface;
final readonly class ByActivityTypeAggregator implements AggregatorInterface
{
private const PREFIX = 'acp_by_activity_type_agg';
public function __construct(
private RollingDateConverterInterface $rollingDateConverter,
private ActivityTypeRepositoryInterface $activityTypeRepository,
private TranslatableStringHelperInterface $translatableStringHelper,
) {
}
public function buildForm(FormBuilderInterface $builder)
{
$builder
->add('after_date', PickRollingDateType::class, [
'required' => false,
'label' => 'export.aggregator.acp.by_activity_type.after_date',
])
->add('before_date', PickRollingDateType::class, [
'required' => false,
'label' => 'export.aggregator.acp.by_activity_type.before_date',
]);
}
public function getFormDefaultData(): array
{
return [
'before_date' => null,
'after_date' => null,
];
}
public function getLabels($key, array $values, mixed $data)
{
return function (int|string|null $value): string {
if ('_header' === $value) {
return 'export.aggregator.acp.by_activity_type.activity_type';
}
if ('' === $value || null === $value || null === $activityType = $this->activityTypeRepository->find($value)) {
return '';
}
return $this->translatableStringHelper->localize($activityType->getName());
};
}
public function getQueryKeys($data)
{
return [self::PREFIX.'_actype_id'];
}
public function getTitle()
{
return 'export.aggregator.acp.by_activity_type.title';
}
public function addRole(): ?string
{
return null;
}
public function alterQuery(QueryBuilder $qb, $data)
{
$p = self::PREFIX;
// we make a left join, with acp having at least one activity of the given type
$exists = 'EXISTS (SELECT 1 FROM '.Activity::class." {$p}_activity WHERE {$p}_activity.accompanyingPeriod = acp AND {$p}_activity.activityType = {$p}_activity_type";
if (null !== $data['after_date']) {
$exists .= " AND {$p}_activity.date > :{$p}_after_date";
$qb->setParameter("{$p}_after_date", $this->rollingDateConverter->convert($data['after_date']));
}
if (null !== $data['before_date']) {
$exists .= " AND {$p}_activity.date < :{$p}_before_date";
$qb->setParameter("{$p}_before_date", $this->rollingDateConverter->convert($data['before_date']));
}
$exists .= ')';
$qb->leftJoin(
ActivityType::class,
"{$p}_activity_type",
Join::WITH,
$exists
);
$qb
->addSelect("{$p}_activity_type.id AS {$p}_actype_id")
->addGroupBy("{$p}_actype_id");
}
public function applyOn()
{
return Declarations::ACP_TYPE;
}
}

View File

@@ -17,20 +17,11 @@ use Chill\PersonBundle\Repository\SocialWork\SocialActionRepository;
use Chill\PersonBundle\Templating\Entity\SocialActionRender;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface;
use function in_array;
class BySocialActionAggregator implements AggregatorInterface
{
private SocialActionRender $actionRender;
private SocialActionRepository $actionRepository;
public function __construct(
SocialActionRender $actionRender,
SocialActionRepository $actionRepository
) {
$this->actionRender = $actionRender;
$this->actionRepository = $actionRepository;
public function __construct(private readonly SocialActionRender $actionRender, private readonly SocialActionRepository $actionRepository)
{
}
public function addRole(): ?string
@@ -40,7 +31,7 @@ class BySocialActionAggregator implements AggregatorInterface
public function alterQuery(QueryBuilder $qb, $data)
{
if (!in_array('actsocialaction', $qb->getAllAliases(), true)) {
if (!\in_array('actsocialaction', $qb->getAllAliases(), true)) {
$qb->leftJoin('activity.socialActions', 'actsocialaction');
}
@@ -57,6 +48,7 @@ class BySocialActionAggregator implements AggregatorInterface
{
// no form
}
public function getFormDefaultData(): array
{
return [];

View File

@@ -17,20 +17,11 @@ use Chill\PersonBundle\Repository\SocialWork\SocialIssueRepository;
use Chill\PersonBundle\Templating\Entity\SocialIssueRender;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface;
use function in_array;
class BySocialIssueAggregator implements AggregatorInterface
{
private SocialIssueRender $issueRender;
private SocialIssueRepository $issueRepository;
public function __construct(
SocialIssueRepository $issueRepository,
SocialIssueRender $issueRender
) {
$this->issueRepository = $issueRepository;
$this->issueRender = $issueRender;
public function __construct(private readonly SocialIssueRepository $issueRepository, private readonly SocialIssueRender $issueRender)
{
}
public function addRole(): ?string
@@ -40,7 +31,7 @@ class BySocialIssueAggregator implements AggregatorInterface
public function alterQuery(QueryBuilder $qb, $data)
{
if (!in_array('actsocialissue', $qb->getAllAliases(), true)) {
if (!\in_array('actsocialissue', $qb->getAllAliases(), true)) {
$qb->leftJoin('activity.socialIssues', 'actsocialissue');
}
@@ -57,6 +48,7 @@ class BySocialIssueAggregator implements AggregatorInterface
{
// no form
}
public function getFormDefaultData(): array
{
return [];

View File

@@ -0,0 +1,76 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\ActivityBundle\Export\Aggregator;
use Chill\ActivityBundle\Export\Declarations;
use Chill\MainBundle\Export\AggregatorInterface;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface;
final readonly class ActivityLocationAggregator implements AggregatorInterface
{
public const KEY = 'activity_location_aggregator';
public function addRole(): ?string
{
return null;
}
public function alterQuery(QueryBuilder $qb, $data)
{
if (!\in_array('actloc', $qb->getAllAliases(), true)) {
$qb->leftJoin('activity.location', 'actloc');
}
$qb->addSelect(sprintf('actloc.name AS %s', self::KEY));
$qb->addGroupBy(self::KEY);
}
public function applyOn(): string
{
return Declarations::ACTIVITY;
}
public function buildForm(FormBuilderInterface $builder)
{
// no form required for this aggregator
}
public function getFormDefaultData(): array
{
return [];
}
public function getLabels($key, array $values, $data): \Closure
{
return function ($value): string {
if ('_header' === $value) {
return 'export.aggregator.activity.by_location.Activity Location';
}
if (null === $value || '' === $value) {
return '';
}
return $value;
};
}
public function getQueryKeys($data): array
{
return [self::KEY];
}
public function getTitle()
{
return 'export.aggregator.activity.by_location.Title';
}
}

View File

@@ -0,0 +1,76 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\ActivityBundle\Export\Aggregator;
use Chill\ActivityBundle\Export\Declarations;
use Chill\ActivityBundle\Repository\ActivityPresenceRepositoryInterface;
use Chill\MainBundle\Export\AggregatorInterface;
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface;
final readonly class ActivityPresenceAggregator implements AggregatorInterface
{
public function __construct(private ActivityPresenceRepositoryInterface $activityPresenceRepository, private TranslatableStringHelperInterface $translatableStringHelper)
{
}
public function buildForm(FormBuilderInterface $builder)
{
}
public function getFormDefaultData(): array
{
return [];
}
public function getLabels($key, array $values, mixed $data)
{
return function (int|string|null $value): string {
if ('_header' === $value) {
return 'export.aggregator.activity.by_activity_presence.header';
}
if (null === $value || '' === $value || null === $presence = $this->activityPresenceRepository->find($value)) {
return '';
}
return $this->translatableStringHelper->localize($presence->getName());
};
}
public function getQueryKeys($data)
{
return ['activity_presence_aggregator_attendee'];
}
public function getTitle(): string
{
return 'export.aggregator.activity.by_activity_presence.Group activity by presence';
}
public function addRole(): ?string
{
return null;
}
public function alterQuery(QueryBuilder $qb, $data): void
{
$qb->addSelect('IDENTITY(activity.attendee) AS activity_presence_aggregator_attendee');
$qb->addGroupBy('activity_presence_aggregator_attendee');
}
public function applyOn()
{
return Declarations::ACTIVITY;
}
}

View File

@@ -9,7 +9,7 @@ declare(strict_types=1);
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\ActivityBundle\Export\Aggregator\PersonAggregators;
namespace Chill\ActivityBundle\Export\Aggregator;
use Chill\ActivityBundle\Export\Declarations;
use Chill\ActivityBundle\Repository\ActivityReasonCategoryRepository;
@@ -17,33 +17,19 @@ use Chill\ActivityBundle\Repository\ActivityReasonRepository;
use Chill\MainBundle\Export\AggregatorInterface;
use Chill\MainBundle\Export\ExportElementValidatedInterface;
use Chill\MainBundle\Templating\TranslatableStringHelper;
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
use Doctrine\ORM\Query\Expr\Join;
use Doctrine\ORM\QueryBuilder;
use RuntimeException;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
use function count;
use function in_array;
class ActivityReasonAggregator implements AggregatorInterface, ExportElementValidatedInterface
{
protected ActivityReasonCategoryRepository $activityReasonCategoryRepository;
protected ActivityReasonRepository $activityReasonRepository;
protected TranslatableStringHelperInterface $translatableStringHelper;
public function __construct(
ActivityReasonCategoryRepository $activityReasonCategoryRepository,
ActivityReasonRepository $activityReasonRepository,
TranslatableStringHelper $translatableStringHelper
protected ActivityReasonCategoryRepository $activityReasonCategoryRepository,
protected ActivityReasonRepository $activityReasonRepository,
protected TranslatableStringHelper $translatableStringHelper
) {
$this->activityReasonCategoryRepository = $activityReasonCategoryRepository;
$this->activityReasonRepository = $activityReasonRepository;
$this->translatableStringHelper = $translatableStringHelper;
}
public function addRole(): ?string
@@ -61,37 +47,30 @@ class ActivityReasonAggregator implements AggregatorInterface, ExportElementVali
$elem = 'actreasoncat.id';
$alias = 'activity_categories_id';
} else {
throw new RuntimeException('The data provided are not recognized.');
throw new \RuntimeException('The data provided are not recognized.');
}
$qb->addSelect($elem . ' as ' . $alias);
$qb->addSelect($elem.' as '.$alias);
// make a jointure only if needed
if (!in_array('actreasons', $qb->getAllAliases(), true)) {
$qb->innerJoin('activity.reasons', 'actreasons');
if (!\in_array('actreasons', $qb->getAllAliases(), true)) {
$qb->leftJoin('activity.reasons', 'actreasons');
}
// join category if necessary
if ('activity_categories_id' === $alias) {
// add join only if needed
if (!in_array('actreasoncat', $qb->getAllAliases(), true)) {
if (!\in_array('actreasoncat', $qb->getAllAliases(), true)) {
$qb->join('actreasons.category', 'actreasoncat');
}
}
// add the "group by" part
$groupBy = $qb->getDQLPart('groupBy');
if (count($groupBy) > 0) {
$qb->addGroupBy($alias);
} else {
$qb->groupBy($alias);
}
$qb->addGroupBy($alias);
}
public function applyOn(): string
{
return Declarations::ACTIVITY_PERSON;
return Declarations::ACTIVITY;
}
public function buildForm(FormBuilderInterface $builder)
@@ -110,29 +89,16 @@ class ActivityReasonAggregator implements AggregatorInterface, ExportElementVali
]
);
}
public function getFormDefaultData(): array
{
return [];
return [
'level' => 'reasons',
];
}
public function getLabels($key, array $values, $data)
{
// for performance reason, we load data from db only once
switch ($data['level']) {
case 'reasons':
$this->activityReasonRepository->findBy(['id' => $values]);
break;
case 'categories':
$this->activityReasonCategoryRepository->findBy(['id' => $values]);
break;
default:
throw new RuntimeException(sprintf("The level data '%s' is invalid.", $data['level']));
}
return function ($value) use ($data) {
if ('_header' === $value) {
return 'reasons' === $data['level'] ? 'Group by reasons' : 'Group by categories of reason';
@@ -171,7 +137,7 @@ class ActivityReasonAggregator implements AggregatorInterface, ExportElementVali
return ['activity_categories_id'];
}
throw new RuntimeException('The data provided are not recognised.');
throw new \RuntimeException('The data provided are not recognised.');
}
public function getTitle()

View File

@@ -15,25 +15,15 @@ use Chill\ActivityBundle\Export\Declarations;
use Chill\ActivityBundle\Repository\ActivityTypeRepositoryInterface;
use Chill\MainBundle\Export\AggregatorInterface;
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
use Closure;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface;
use function in_array;
class ActivityTypeAggregator implements AggregatorInterface
{
public const KEY = 'activity_type_aggregator';
final public const KEY = 'activity_type_aggregator';
protected ActivityTypeRepositoryInterface $activityTypeRepository;
protected TranslatableStringHelperInterface $translatableStringHelper;
public function __construct(
ActivityTypeRepositoryInterface $activityTypeRepository,
TranslatableStringHelperInterface $translatableStringHelper
) {
$this->activityTypeRepository = $activityTypeRepository;
$this->translatableStringHelper = $translatableStringHelper;
public function __construct(protected ActivityTypeRepositoryInterface $activityTypeRepository, protected TranslatableStringHelperInterface $translatableStringHelper)
{
}
public function addRole(): ?string
@@ -43,7 +33,7 @@ class ActivityTypeAggregator implements AggregatorInterface
public function alterQuery(QueryBuilder $qb, $data)
{
if (!in_array('acttype', $qb->getAllAliases(), true)) {
if (!\in_array('acttype', $qb->getAllAliases(), true)) {
$qb->leftJoin('activity.activityType', 'acttype');
}
@@ -60,27 +50,23 @@ class ActivityTypeAggregator implements AggregatorInterface
{
// no form required for this aggregator
}
public function getFormDefaultData(): array
{
return [];
}
public function getLabels($key, array $values, $data): Closure
public function getLabels($key, array $values, $data): \Closure
{
// for performance reason, we load data from db only once
$this->activityTypeRepository->findBy(['id' => $values]);
return function ($value): string {
return function (int|string|null $value): string {
if ('_header' === $value) {
return 'Activity type';
}
if (null === $value || '' === $value) {
if (null === $value || '' === $value || null === $t = $this->activityTypeRepository->find($value)) {
return '';
}
$t = $this->activityTypeRepository->find($value);
return $this->translatableStringHelper->localize($t->getName());
};
}

View File

@@ -15,24 +15,15 @@ use Chill\ActivityBundle\Export\Declarations;
use Chill\MainBundle\Export\AggregatorInterface;
use Chill\MainBundle\Repository\UserRepository;
use Chill\MainBundle\Templating\Entity\UserRender;
use Closure;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface;
class ActivityUserAggregator implements AggregatorInterface
{
public const KEY = 'activity_user_id';
final public const KEY = 'activity_user_id';
private UserRender $userRender;
private UserRepository $userRepository;
public function __construct(
UserRepository $userRepository,
UserRender $userRender
) {
$this->userRepository = $userRepository;
$this->userRender = $userRender;
public function __construct(private readonly UserRepository $userRepository, private readonly UserRender $userRender)
{
}
public function addRole(): ?string
@@ -58,12 +49,13 @@ class ActivityUserAggregator implements AggregatorInterface
{
// nothing to add
}
public function getFormDefaultData(): array
{
return [];
}
public function getLabels($key, $values, $data): Closure
public function getLabels($key, $values, $data): \Closure
{
return function ($value) {
if ('_header' === $value) {

View File

@@ -17,18 +17,11 @@ use Chill\MainBundle\Repository\UserRepositoryInterface;
use Chill\MainBundle\Templating\Entity\UserRender;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface;
use function in_array;
class ActivityUsersAggregator implements AggregatorInterface
{
private UserRender $userRender;
private UserRepositoryInterface $userRepository;
public function __construct(UserRepositoryInterface $userRepository, UserRender $userRender)
public function __construct(private readonly UserRepositoryInterface $userRepository, private readonly UserRender $userRender)
{
$this->userRepository = $userRepository;
$this->userRender = $userRender;
}
public function addRole(): ?string
@@ -38,7 +31,7 @@ class ActivityUsersAggregator implements AggregatorInterface
public function alterQuery(QueryBuilder $qb, $data)
{
if (!in_array('actusers', $qb->getAllAliases(), true)) {
if (!\in_array('actusers', $qb->getAllAliases(), true)) {
$qb->leftJoin('activity.users', 'actusers');
}
@@ -56,6 +49,7 @@ class ActivityUsersAggregator implements AggregatorInterface
{
// nothing to add on the form
}
public function getFormDefaultData(): array
{
return [];

View File

@@ -12,22 +12,22 @@ declare(strict_types=1);
namespace Chill\ActivityBundle\Export\Aggregator;
use Chill\ActivityBundle\Export\Declarations;
use Chill\MainBundle\Entity\User\UserJobHistory;
use Chill\MainBundle\Export\AggregatorInterface;
use Chill\MainBundle\Repository\UserJobRepositoryInterface;
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
use Doctrine\ORM\Query\Expr;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface;
use function in_array;
class ActivityUsersJobAggregator implements \Chill\MainBundle\Export\AggregatorInterface
class ActivityUsersJobAggregator implements AggregatorInterface
{
private TranslatableStringHelperInterface $translatableStringHelper;
private const PREFIX = 'act_agg_user_job';
private UserJobRepositoryInterface $userJobRepository;
public function __construct(UserJobRepositoryInterface $userJobRepository, TranslatableStringHelperInterface $translatableStringHelper)
{
$this->userJobRepository = $userJobRepository;
$this->translatableStringHelper = $translatableStringHelper;
public function __construct(
private readonly UserJobRepositoryInterface $userJobRepository,
private readonly TranslatableStringHelperInterface $translatableStringHelper
) {
}
public function addRole(): ?string
@@ -37,24 +37,39 @@ class ActivityUsersJobAggregator implements \Chill\MainBundle\Export\AggregatorI
public function alterQuery(QueryBuilder $qb, $data)
{
if (!in_array('actusers', $qb->getAllAliases(), true)) {
$qb->leftJoin('activity.users', 'actusers');
}
$p = self::PREFIX;
$qb
->addSelect('IDENTITY(actusers.userJob) AS activity_users_job_aggregator')
->addGroupBy('activity_users_job_aggregator');
->leftJoin('activity.users', "{$p}_user")
->leftJoin(
UserJobHistory::class,
"{$p}_history",
Expr\Join::WITH,
$qb->expr()->eq("{$p}_history.user", "{$p}_user")
)
// job_at based on activity.date
->andWhere(
$qb->expr()->andX(
$qb->expr()->lte("{$p}_history.startDate", 'activity.date'),
$qb->expr()->orX(
$qb->expr()->isNull("{$p}_history.endDate"),
$qb->expr()->gt("{$p}_history.endDate", 'activity.date')
)
)
)
->addSelect("IDENTITY({$p}_history.job) AS {$p}_select")
->addGroupBy("{$p}_select");
}
public function applyOn()
public function applyOn(): string
{
return Declarations::ACTIVITY;
}
public function buildForm(FormBuilderInterface $builder)
{
// nothing to add in the form
}
public function getFormDefaultData(): array
{
return [];
@@ -81,11 +96,11 @@ class ActivityUsersJobAggregator implements \Chill\MainBundle\Export\AggregatorI
public function getQueryKeys($data): array
{
return ['activity_users_job_aggregator'];
return [self::PREFIX.'_select'];
}
public function getTitle()
public function getTitle(): string
{
return 'Aggregate by users job';
return 'export.aggregator.activity.by_user_job.Aggregate by users job';
}
}

View File

@@ -12,22 +12,22 @@ declare(strict_types=1);
namespace Chill\ActivityBundle\Export\Aggregator;
use Chill\ActivityBundle\Export\Declarations;
use Chill\MainBundle\Entity\User\UserScopeHistory;
use Chill\MainBundle\Export\AggregatorInterface;
use Chill\MainBundle\Repository\ScopeRepositoryInterface;
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
use Doctrine\ORM\Query\Expr;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface;
use function in_array;
class ActivityUsersScopeAggregator implements \Chill\MainBundle\Export\AggregatorInterface
class ActivityUsersScopeAggregator implements AggregatorInterface
{
private ScopeRepositoryInterface $scopeRepository;
private const PREFIX = 'act_agg_user_scope';
private TranslatableStringHelperInterface $translatableStringHelper;
public function __construct(ScopeRepositoryInterface $scopeRepository, TranslatableStringHelperInterface $translatableStringHelper)
{
$this->scopeRepository = $scopeRepository;
$this->translatableStringHelper = $translatableStringHelper;
public function __construct(
private readonly ScopeRepositoryInterface $scopeRepository,
private readonly TranslatableStringHelperInterface $translatableStringHelper
) {
}
public function addRole(): ?string
@@ -37,24 +37,39 @@ class ActivityUsersScopeAggregator implements \Chill\MainBundle\Export\Aggregato
public function alterQuery(QueryBuilder $qb, $data)
{
if (!in_array('actusers', $qb->getAllAliases(), true)) {
$qb->leftJoin('activity.users', 'actusers');
}
$p = self::PREFIX;
$qb
->addSelect('IDENTITY(actusers.mainScope) AS activity_users_main_scope_aggregator')
->addGroupBy('activity_users_main_scope_aggregator');
->leftJoin('activity.users', "{$p}_user")
->leftJoin(
UserScopeHistory::class,
"{$p}_history",
Expr\Join::WITH,
$qb->expr()->eq("{$p}_history.user", "{$p}_user")
)
// scope_at based on activity.date
->andWhere(
$qb->expr()->andX(
$qb->expr()->lte("{$p}_history.startDate", 'activity.date'),
$qb->expr()->orX(
$qb->expr()->isNull("{$p}_history.endDate"),
$qb->expr()->gt("{$p}_history.endDate", 'activity.date')
)
)
)
->addSelect("IDENTITY({$p}_history.scope) AS {$p}_select")
->addGroupBy("{$p}_select");
}
public function applyOn()
public function applyOn(): string
{
return Declarations::ACTIVITY;
}
public function buildForm(FormBuilderInterface $builder)
{
// nothing to add in the form
}
public function getFormDefaultData(): array
{
return [];
@@ -81,11 +96,11 @@ class ActivityUsersScopeAggregator implements \Chill\MainBundle\Export\Aggregato
public function getQueryKeys($data): array
{
return ['activity_users_main_scope_aggregator'];
return [self::PREFIX.'_select'];
}
public function getTitle()
public function getTitle(): string
{
return 'Aggregate by users scope';
return 'export.aggregator.activity.by_user_scope.Aggregate by users scope';
}
}

View File

@@ -9,7 +9,7 @@ declare(strict_types=1);
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\ActivityBundle\Export\Aggregator\ACPAggregators;
namespace Chill\ActivityBundle\Export\Aggregator;
use Chill\ActivityBundle\Export\Declarations;
use Chill\MainBundle\Export\AggregatorInterface;
@@ -20,16 +20,8 @@ use Symfony\Component\Form\FormBuilderInterface;
class ByCreatorAggregator implements AggregatorInterface
{
private UserRender $userRender;
private UserRepositoryInterface $userRepository;
public function __construct(
UserRepositoryInterface $userRepository,
UserRender $userRender
) {
$this->userRepository = $userRepository;
$this->userRender = $userRender;
public function __construct(private readonly UserRepositoryInterface $userRepository, private readonly UserRender $userRender)
{
}
public function addRole(): ?string
@@ -45,13 +37,14 @@ class ByCreatorAggregator implements AggregatorInterface
public function applyOn(): string
{
return Declarations::ACTIVITY_ACP;
return Declarations::ACTIVITY;
}
public function buildForm(FormBuilderInterface $builder)
{
// no form
}
public function getFormDefaultData(): array
{
return [];

View File

@@ -9,7 +9,7 @@ declare(strict_types=1);
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\ActivityBundle\Export\Aggregator\ACPAggregators;
namespace Chill\ActivityBundle\Export\Aggregator;
use Chill\ActivityBundle\Export\Declarations;
use Chill\MainBundle\Export\AggregatorInterface;
@@ -17,20 +17,11 @@ use Chill\ThirdPartyBundle\Repository\ThirdPartyRepository;
use Chill\ThirdPartyBundle\Templating\Entity\ThirdPartyRender;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface;
use function in_array;
class ByThirdpartyAggregator implements AggregatorInterface
{
private ThirdPartyRender $thirdPartyRender;
private ThirdPartyRepository $thirdPartyRepository;
public function __construct(
ThirdPartyRepository $thirdPartyRepository,
ThirdPartyRender $thirdPartyRender
) {
$this->thirdPartyRepository = $thirdPartyRepository;
$this->thirdPartyRender = $thirdPartyRender;
public function __construct(private readonly ThirdPartyRepository $thirdPartyRepository, private readonly ThirdPartyRender $thirdPartyRender)
{
}
public function addRole(): ?string
@@ -40,7 +31,7 @@ class ByThirdpartyAggregator implements AggregatorInterface
public function alterQuery(QueryBuilder $qb, $data)
{
if (!in_array('acttparty', $qb->getAllAliases(), true)) {
if (!\in_array('acttparty', $qb->getAllAliases(), true)) {
$qb->leftJoin('activity.thirdParties', 'acttparty');
}
@@ -50,13 +41,14 @@ class ByThirdpartyAggregator implements AggregatorInterface
public function applyOn(): string
{
return Declarations::ACTIVITY_ACP;
return Declarations::ACTIVITY;
}
public function buildForm(FormBuilderInterface $builder)
{
// no form
}
public function getFormDefaultData(): array
{
return [];

View File

@@ -0,0 +1,104 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\ActivityBundle\Export\Aggregator;
use Chill\ActivityBundle\Export\Declarations;
use Chill\MainBundle\Entity\User\UserJobHistory;
use Chill\MainBundle\Export\AggregatorInterface;
use Chill\MainBundle\Repository\UserJobRepositoryInterface;
use Chill\MainBundle\Templating\TranslatableStringHelper;
use Doctrine\ORM\Query\Expr\Join;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface;
class CreatorJobAggregator implements AggregatorInterface
{
private const PREFIX = 'acp_agg_creator_job';
public function __construct(
private readonly UserJobRepositoryInterface $userJobRepository,
private readonly TranslatableStringHelper $translatableStringHelper
) {
}
public function addRole(): ?string
{
return null;
}
public function alterQuery(QueryBuilder $qb, $data)
{
$p = self::PREFIX;
$qb
->leftJoin('activity.createdBy', "{$p}_user")
->leftJoin(
UserJobHistory::class,
"{$p}_history",
Join::WITH,
$qb->expr()->eq("{$p}_history.user", "{$p}_user")
)
// job_at based on activity.date
->andWhere(
$qb->expr()->andX(
$qb->expr()->lte("{$p}_history.startDate", 'activity.date'),
$qb->expr()->orX(
$qb->expr()->isNull("{$p}_history.endDate"),
$qb->expr()->gt("{$p}_history.endDate", 'activity.date')
)
)
)
->addSelect("IDENTITY({$p}_history.job) AS {$p}_select")
->addGroupBy("{$p}_select");
}
public function applyOn(): string
{
return Declarations::ACTIVITY;
}
public function buildForm(FormBuilderInterface $builder)
{
}
public function getFormDefaultData(): array
{
return [];
}
public function getLabels($key, array $values, $data)
{
return function ($value): string {
if ('_header' === $value) {
return 'Job';
}
if (null === $value || '' === $value || null === $s = $this->userJobRepository->find($value)) {
return '';
}
return $this->translatableStringHelper->localize(
$s->getLabel()
);
};
}
public function getQueryKeys($data): array
{
return [self::PREFIX.'_select'];
}
public function getTitle(): string
{
return 'export.aggregator.activity.by_creator_job.Group activity by creator job';
}
}

View File

@@ -9,28 +9,25 @@ declare(strict_types=1);
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\ActivityBundle\Export\Aggregator\ACPAggregators;
namespace Chill\ActivityBundle\Export\Aggregator;
use Chill\ActivityBundle\Export\Declarations;
use Chill\MainBundle\Entity\User\UserScopeHistory;
use Chill\MainBundle\Export\AggregatorInterface;
use Chill\MainBundle\Repository\ScopeRepository;
use Chill\MainBundle\Templating\TranslatableStringHelper;
use Doctrine\ORM\Query\Expr\Join;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface;
use function in_array;
class CreatorScopeAggregator implements AggregatorInterface
{
private ScopeRepository $scopeRepository;
private TranslatableStringHelper $translatableStringHelper;
private const PREFIX = 'acp_agg_creator_scope';
public function __construct(
ScopeRepository $scopeRepository,
TranslatableStringHelper $translatableStringHelper
private readonly ScopeRepository $scopeRepository,
private readonly TranslatableStringHelper $translatableStringHelper
) {
$this->scopeRepository = $scopeRepository;
$this->translatableStringHelper = $translatableStringHelper;
}
public function addRole(): ?string
@@ -40,23 +37,39 @@ class CreatorScopeAggregator implements AggregatorInterface
public function alterQuery(QueryBuilder $qb, $data)
{
if (!in_array('actcreator', $qb->getAllAliases(), true)) {
$qb->leftJoin('activity.createdBy', 'actcreator');
}
$p = self::PREFIX;
$qb->addSelect('IDENTITY(actcreator.mainScope) AS creatorscope_aggregator');
$qb->addGroupBy('creatorscope_aggregator');
$qb
->leftJoin('activity.createdBy', "{$p}_user")
->leftJoin(
UserScopeHistory::class,
"{$p}_history",
Join::WITH,
$qb->expr()->eq("{$p}_history.user", "{$p}_user")
)
// scope_at based on activity.date
->andWhere(
$qb->expr()->andX(
$qb->expr()->lte("{$p}_history.startDate", 'activity.date'),
$qb->expr()->orX(
$qb->expr()->isNull("{$p}_history.endDate"),
$qb->expr()->gt("{$p}_history.endDate", 'activity.date')
)
)
)
->addSelect("IDENTITY({$p}_history.scope) AS {$p}_select")
->addGroupBy("{$p}_select");
}
public function applyOn(): string
{
return Declarations::ACTIVITY_ACP;
return Declarations::ACTIVITY;
}
public function buildForm(FormBuilderInterface $builder)
{
// no form
}
public function getFormDefaultData(): array
{
return [];
@@ -83,11 +96,11 @@ class CreatorScopeAggregator implements AggregatorInterface
public function getQueryKeys($data): array
{
return ['creatorscope_aggregator'];
return [self::PREFIX.'_select'];
}
public function getTitle(): string
{
return 'Group activity by creator scope';
return 'export.aggregator.activity.by_creator_scope.Group activity by creator scope';
}
}

View File

@@ -9,15 +9,13 @@ declare(strict_types=1);
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\ActivityBundle\Export\Aggregator\ACPAggregators;
namespace Chill\ActivityBundle\Export\Aggregator;
use Chill\ActivityBundle\Export\Declarations;
use Chill\MainBundle\Export\AggregatorInterface;
use Doctrine\ORM\QueryBuilder;
use RuntimeException;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
class DateAggregator implements AggregatorInterface
{
@@ -56,7 +54,7 @@ class DateAggregator implements AggregatorInterface
break; // order DESC does not works !
default:
throw new RuntimeException(sprintf("The frequency data '%s' is invalid.", $data['frequency']));
throw new \RuntimeException(sprintf("The frequency data '%s' is invalid.", $data['frequency']));
}
$qb->addSelect(sprintf("TO_CHAR(activity.date, '%s') AS date_aggregator", $fmt));
@@ -66,7 +64,7 @@ class DateAggregator implements AggregatorInterface
public function applyOn(): string
{
return Declarations::ACTIVITY_ACP;
return Declarations::ACTIVITY;
}
public function buildForm(FormBuilderInterface $builder)
@@ -75,9 +73,9 @@ class DateAggregator implements AggregatorInterface
'choices' => self::CHOICES,
'multiple' => false,
'expanded' => true,
'empty_data' => self::DEFAULT_CHOICE,
]);
}
public function getFormDefaultData(): array
{
return ['frequency' => self::DEFAULT_CHOICE];
@@ -87,24 +85,16 @@ class DateAggregator implements AggregatorInterface
{
return static function ($value) use ($data): string {
if ('_header' === $value) {
return 'by ' . $data['frequency'];
return 'by '.$data['frequency'];
}
if (null === $value) {
return '';
}
switch ($data['frequency']) {
case 'month':
case 'week':
//return $this->translator->trans('for week') .' '. $value ;
case 'year':
//return $this->translator->trans('in year') .' '. $value ;
default:
return $value;
}
return match ($data['frequency']) {
default => $value,
};
};
}

View File

@@ -9,7 +9,7 @@ declare(strict_types=1);
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\ActivityBundle\Export\Aggregator\ACPAggregators;
namespace Chill\ActivityBundle\Export\Aggregator;
use Chill\ActivityBundle\Export\Declarations;
use Chill\MainBundle\Export\AggregatorInterface;
@@ -17,20 +17,11 @@ use Chill\MainBundle\Repository\LocationTypeRepository;
use Chill\MainBundle\Templating\TranslatableStringHelper;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface;
use function in_array;
class LocationTypeAggregator implements AggregatorInterface
{
private LocationTypeRepository $locationTypeRepository;
private TranslatableStringHelper $translatableStringHelper;
public function __construct(
LocationTypeRepository $locationTypeRepository,
TranslatableStringHelper $translatableStringHelper
) {
$this->locationTypeRepository = $locationTypeRepository;
$this->translatableStringHelper = $translatableStringHelper;
public function __construct(private readonly LocationTypeRepository $locationTypeRepository, private readonly TranslatableStringHelper $translatableStringHelper)
{
}
public function addRole(): ?string
@@ -40,7 +31,7 @@ class LocationTypeAggregator implements AggregatorInterface
public function alterQuery(QueryBuilder $qb, $data)
{
if (!in_array('actloc', $qb->getAllAliases(), true)) {
if (!\in_array('actloc', $qb->getAllAliases(), true)) {
$qb->leftJoin('activity.location', 'actloc');
}
@@ -50,13 +41,14 @@ class LocationTypeAggregator implements AggregatorInterface
public function applyOn(): string
{
return Declarations::ACTIVITY_ACP;
return Declarations::ACTIVITY;
}
public function buildForm(FormBuilderInterface $builder)
{
// no form
}
public function getFormDefaultData(): array
{
return [];

View File

@@ -0,0 +1,67 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\ActivityBundle\Export\Aggregator\PersonAggregators;
use Chill\ActivityBundle\Export\Declarations;
use Chill\MainBundle\Export\AggregatorInterface;
use Chill\PersonBundle\Export\Helper\LabelPersonHelper;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface;
final readonly class PersonAggregator implements AggregatorInterface
{
public function __construct(private LabelPersonHelper $labelPersonHelper)
{
}
public function buildForm(FormBuilderInterface $builder)
{
// nothing to add here
}
public function getFormDefaultData(): array
{
return [];
}
public function getLabels($key, array $values, mixed $data)
{
return $this->labelPersonHelper->getLabel($key, $values, 'export.aggregator.person.by_person.person');
}
public function getQueryKeys($data)
{
return ['activity_by_person_agg'];
}
public function getTitle()
{
return 'export.aggregator.person.by_person.title';
}
public function addRole(): ?string
{
return null;
}
public function alterQuery(QueryBuilder $qb, $data)
{
$qb
->addSelect('IDENTITY(activity.person) AS activity_by_person_agg')
->addGroupBy('activity_by_person_agg');
}
public function applyOn()
{
return Declarations::ACTIVITY_PERSON;
}
}

View File

@@ -0,0 +1,80 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\ActivityBundle\Export\Aggregator;
use Chill\ActivityBundle\Export\Declarations;
use Chill\ActivityBundle\Tests\Export\Aggregator\PersonsAggregatorTest;
use Chill\MainBundle\Export\AggregatorInterface;
use Chill\PersonBundle\Export\Helper\LabelPersonHelper;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface;
/**
* @see PersonsAggregatorTest
*/
final readonly class PersonsAggregator implements AggregatorInterface
{
private const PREFIX = 'act_persons_agg';
public function __construct(private LabelPersonHelper $labelPersonHelper)
{
}
public function buildForm(FormBuilderInterface $builder)
{
// nothing to add here
}
public function getFormDefaultData(): array
{
return [];
}
public function getLabels($key, array $values, mixed $data)
{
if ($key !== self::PREFIX.'_pid') {
throw new \UnexpectedValueException('this key should not be handled: '.$key);
}
return $this->labelPersonHelper->getLabel($key, $values, 'export.aggregator.activity.by_persons.Persons');
}
public function getQueryKeys($data)
{
return [self::PREFIX.'_pid'];
}
public function getTitle()
{
return 'export.aggregator.activity.by_persons.Group activity by persons';
}
public function addRole(): ?string
{
return null;
}
public function alterQuery(QueryBuilder $qb, $data)
{
$p = self::PREFIX;
$qb
->leftJoin('activity.persons', "{$p}_p")
->addSelect("{$p}_p.id AS {$p}_pid")
->addGroupBy("{$p}_pid");
}
public function applyOn()
{
return Declarations::ACTIVITY;
}
}

View File

@@ -14,17 +14,13 @@ namespace Chill\ActivityBundle\Export\Aggregator;
use Chill\ActivityBundle\Export\Declarations;
use Chill\MainBundle\Export\AggregatorInterface;
use Doctrine\ORM\QueryBuilder;
use LogicException;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
class SentReceivedAggregator implements AggregatorInterface
{
private TranslatorInterface $translator;
public function __construct(TranslatorInterface $translator)
public function __construct(private readonly TranslatorInterface $translator)
{
$this->translator = $translator;
}
public function addRole(): ?string
@@ -47,6 +43,7 @@ class SentReceivedAggregator implements AggregatorInterface
{
// No form needed
}
public function getFormDefaultData(): array
{
return [];
@@ -71,7 +68,7 @@ class SentReceivedAggregator implements AggregatorInterface
return $this->translator->trans('export.aggregator.activity.by_sent_received.is received');
default:
throw new LogicException(sprintf('The value %s is not valid', $value));
throw new \LogicException(sprintf('The value %s is not valid', $value));
}
};
}

View File

@@ -16,9 +16,9 @@ namespace Chill\ActivityBundle\Export;
*/
abstract class Declarations
{
public const ACTIVITY = 'activity';
final public const ACTIVITY = 'activity';
public const ACTIVITY_ACP = 'activity_linked_to_acp';
final public const ACTIVITY_ACP = 'activity_linked_to_acp';
public const ACTIVITY_PERSON = 'activity_linked_to_person';
final public const ACTIVITY_PERSON = 'activity_linked_to_person';
}

View File

@@ -11,34 +11,35 @@ declare(strict_types=1);
namespace Chill\ActivityBundle\Export\Export\LinkedToACP;
use Chill\ActivityBundle\Entity\Activity;
use Chill\ActivityBundle\Export\Declarations;
use Chill\ActivityBundle\Repository\ActivityRepository;
use Chill\ActivityBundle\Security\Authorization\ActivityStatsVoter;
use Chill\MainBundle\Export\AccompanyingCourseExportHelper;
use Chill\MainBundle\Export\ExportInterface;
use Chill\MainBundle\Export\FormatterInterface;
use Chill\MainBundle\Export\GroupedExportInterface;
use Chill\PersonBundle\Entity\AccompanyingPeriodParticipation;
use Chill\PersonBundle\Entity\Person\PersonCenterHistory;
use Chill\PersonBundle\Export\Declarations as PersonDeclarations;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\Query;
use LogicException;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\Form\FormBuilderInterface;
class AvgActivityDuration implements ExportInterface, GroupedExportInterface
{
protected EntityRepository $repository;
private readonly bool $filterStatsByCenters;
public function __construct(
EntityManagerInterface $em
private readonly ActivityRepository $activityRepository,
ParameterBagInterface $parameterBag,
) {
$this->repository = $em->getRepository(Activity::class);
$this->filterStatsByCenters = $parameterBag->get('chill_main')['acl']['filter_stats_by_center'];
}
public function buildForm(FormBuilderInterface $builder)
{
}
public function getFormDefaultData(): array
{
return [];
@@ -62,7 +63,7 @@ class AvgActivityDuration implements ExportInterface, GroupedExportInterface
public function getLabels($key, array $values, $data)
{
if ('export_avg_activity_duration' !== $key) {
throw new LogicException("the key {$key} is not used by this export");
throw new \LogicException("the key {$key} is not used by this export");
}
return static fn ($value) => '_header' === $value ? 'Average activities linked to an accompanying period duration' : $value;
@@ -92,23 +93,27 @@ class AvgActivityDuration implements ExportInterface, GroupedExportInterface
{
$centers = array_map(static fn ($el) => $el['center'], $acl);
$qb = $this->repository->createQueryBuilder('activity');
$qb = $this->activityRepository->createQueryBuilder('activity');
$qb
->join('activity.accompanyingPeriod', 'acp')
->select('AVG(activity.durationTime) as export_avg_activity_duration')
->andWhere($qb->expr()->isNotNull('activity.durationTime'));
$qb
->andWhere(
$qb->expr()->exists(
'SELECT 1 FROM ' . AccompanyingPeriodParticipation::class . ' acl_count_part
JOIN ' . PersonCenterHistory::class . ' acl_count_person_history WITH IDENTITY(acl_count_person_history.person) = IDENTITY(acl_count_part.person)
if ($this->filterStatsByCenters) {
$qb
->andWhere(
$qb->expr()->exists(
'SELECT 1 FROM '.AccompanyingPeriodParticipation::class.' acl_count_part
JOIN '.PersonCenterHistory::class.' acl_count_person_history WITH IDENTITY(acl_count_person_history.person) = IDENTITY(acl_count_part.person)
WHERE acl_count_part.accompanyingPeriod = acp.id AND acl_count_person_history.center IN (:authorized_centers)
'
)
)
)
->setParameter('authorized_centers', $centers);
->setParameter('authorized_centers', $centers);
}
AccompanyingCourseExportHelper::addClosingMotiveExclusionClause($qb);
return $qb;
}

View File

@@ -14,6 +14,7 @@ namespace Chill\ActivityBundle\Export\Export\LinkedToACP;
use Chill\ActivityBundle\Entity\Activity;
use Chill\ActivityBundle\Export\Declarations;
use Chill\ActivityBundle\Security\Authorization\ActivityStatsVoter;
use Chill\MainBundle\Export\AccompanyingCourseExportHelper;
use Chill\MainBundle\Export\ExportInterface;
use Chill\MainBundle\Export\FormatterInterface;
use Chill\MainBundle\Export\GroupedExportInterface;
@@ -23,23 +24,28 @@ use Chill\PersonBundle\Export\Declarations as PersonDeclarations;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\Query;
use LogicException;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\Form\FormBuilderInterface;
class AvgActivityVisitDuration implements ExportInterface, GroupedExportInterface
{
protected EntityRepository $repository;
private readonly bool $filterStatsByCenters;
public function __construct(
EntityManagerInterface $em
EntityManagerInterface $em,
ParameterBagInterface $parameterBag,
) {
$this->repository = $em->getRepository(Activity::class);
$this->filterStatsByCenters = $parameterBag->get('chill_main')['acl']['filter_stats_by_center'];
}
public function buildForm(FormBuilderInterface $builder)
{
// TODO: Implement buildForm() method.
}
public function getFormDefaultData(): array
{
return [];
@@ -63,7 +69,7 @@ class AvgActivityVisitDuration implements ExportInterface, GroupedExportInterfac
public function getLabels($key, array $values, $data)
{
if ('export_avg_activity_visit_duration' !== $key) {
throw new LogicException("the key {$key} is not used by this export");
throw new \LogicException("the key {$key} is not used by this export");
}
return static fn ($value) => '_header' === $value ? 'Average activities linked to an accompanying period visit duration' : $value;
@@ -100,16 +106,20 @@ class AvgActivityVisitDuration implements ExportInterface, GroupedExportInterfac
->select('AVG(activity.travelTime) as export_avg_activity_visit_duration')
->andWhere($qb->expr()->isNotNull('activity.travelTime'));
$qb
->andWhere(
$qb->expr()->exists(
'SELECT 1 FROM ' . AccompanyingPeriodParticipation::class . ' acl_count_part
JOIN ' . PersonCenterHistory::class . ' acl_count_person_history WITH IDENTITY(acl_count_person_history.person) = IDENTITY(acl_count_part.person)
if ($this->filterStatsByCenters) {
$qb
->andWhere(
$qb->expr()->exists(
'SELECT 1 FROM '.AccompanyingPeriodParticipation::class.' acl_count_part
JOIN '.PersonCenterHistory::class.' acl_count_person_history WITH IDENTITY(acl_count_person_history.person) = IDENTITY(acl_count_part.person)
WHERE acl_count_part.accompanyingPeriod = acp.id AND acl_count_person_history.center IN (:authorized_centers)
'
)
)
)
->setParameter('authorized_centers', $centers);
->setParameter('authorized_centers', $centers);
}
AccompanyingCourseExportHelper::addClosingMotiveExclusionClause($qb);
return $qb;
}

View File

@@ -14,6 +14,7 @@ namespace Chill\ActivityBundle\Export\Export\LinkedToACP;
use Chill\ActivityBundle\Entity\Activity;
use Chill\ActivityBundle\Export\Declarations;
use Chill\ActivityBundle\Security\Authorization\ActivityStatsVoter;
use Chill\MainBundle\Export\AccompanyingCourseExportHelper;
use Chill\MainBundle\Export\ExportInterface;
use Chill\MainBundle\Export\FormatterInterface;
use Chill\MainBundle\Export\GroupedExportInterface;
@@ -23,22 +24,27 @@ use Chill\PersonBundle\Export\Declarations as PersonDeclarations;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\Query;
use LogicException;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\Form\FormBuilderInterface;
class CountActivity implements ExportInterface, GroupedExportInterface
{
protected EntityRepository $repository;
private readonly bool $filterStatsByCenters;
public function __construct(
EntityManagerInterface $em
EntityManagerInterface $em,
ParameterBagInterface $parameterBag,
) {
$this->repository = $em->getRepository(Activity::class);
$this->filterStatsByCenters = $parameterBag->get('chill_main')['acl']['filter_stats_by_center'];
}
public function buildForm(FormBuilderInterface $builder)
{
}
public function getFormDefaultData(): array
{
return [];
@@ -62,7 +68,7 @@ class CountActivity implements ExportInterface, GroupedExportInterface
public function getLabels($key, array $values, $data)
{
if ('export_count_activity' !== $key) {
throw new LogicException("the key {$key} is not used by this export");
throw new \LogicException("the key {$key} is not used by this export");
}
return static fn ($value) => '_header' === $value ? 'Number of activities linked to an accompanying period' : $value;
@@ -96,16 +102,20 @@ class CountActivity implements ExportInterface, GroupedExportInterface
->createQueryBuilder('activity')
->join('activity.accompanyingPeriod', 'acp');
$qb
->andWhere(
$qb->expr()->exists(
'SELECT 1 FROM ' . AccompanyingPeriodParticipation::class . ' acl_count_part
JOIN ' . PersonCenterHistory::class . ' acl_count_person_history WITH IDENTITY(acl_count_person_history.person) = IDENTITY(acl_count_part.person)
if ($this->filterStatsByCenters) {
$qb
->andWhere(
$qb->expr()->exists(
'SELECT 1 FROM '.AccompanyingPeriodParticipation::class.' acl_count_part
JOIN '.PersonCenterHistory::class.' acl_count_person_history WITH IDENTITY(acl_count_person_history.person) = IDENTITY(acl_count_part.person)
WHERE acl_count_part.accompanyingPeriod = acp.id AND acl_count_person_history.center IN (:authorized_centers)
'
)
)
)
->setParameter('authorized_centers', $centers);
->setParameter('authorized_centers', $centers);
}
AccompanyingCourseExportHelper::addClosingMotiveExclusionClause($qb);
$qb->select('COUNT(DISTINCT activity.id) as export_count_activity');

View File

@@ -0,0 +1,149 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\ActivityBundle\Export\Export\LinkedToACP;
use Chill\ActivityBundle\Entity\Activity;
use Chill\ActivityBundle\Export\Declarations;
use Chill\ActivityBundle\Security\Authorization\ActivityStatsVoter;
use Chill\MainBundle\Export\AccompanyingCourseExportHelper;
use Chill\MainBundle\Export\ExportInterface;
use Chill\MainBundle\Export\FormatterInterface;
use Chill\MainBundle\Export\GroupedExportInterface;
use Chill\PersonBundle\Entity\AccompanyingPeriodParticipation;
use Chill\PersonBundle\Entity\Household\HouseholdMember;
use Chill\PersonBundle\Entity\Person\PersonCenterHistory;
use Chill\PersonBundle\Export\Declarations as PersonDeclarations;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\Query;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\Form\FormBuilderInterface;
final readonly class CountHouseholdOnActivity implements ExportInterface, GroupedExportInterface
{
private EntityRepository $repository;
private bool $filterStatsByCenters;
public function __construct(
EntityManagerInterface $em,
ParameterBagInterface $parameterBag,
) {
$this->repository = $em->getRepository(Activity::class);
$this->filterStatsByCenters = $parameterBag->get('chill_main')['acl']['filter_stats_by_center'];
}
public function buildForm(FormBuilderInterface $builder)
{
}
public function getFormDefaultData(): array
{
return [];
}
public function getAllowedFormattersTypes(): array
{
return [FormatterInterface::TYPE_TABULAR];
}
public function getDescription(): string
{
return 'export.export.count_household_on_activity.description';
}
public function getGroup(): string
{
return 'Exports of activities linked to an accompanying period';
}
public function getLabels($key, array $values, $data)
{
if ('export_count_activity' !== $key) {
throw new \LogicException("the key {$key} is not used by this export");
}
return static fn ($value) => '_header' === $value ? 'export.export.count_household_on_activity.header' : $value;
}
public function getQueryKeys($data): array
{
return ['export_count_activity'];
}
public function getResult($query, $data)
{
return $query->getQuery()->getResult(Query::HYDRATE_SCALAR);
}
public function getTitle(): string
{
return 'export.export.count_household_on_activity.title';
}
public function getType(): string
{
return Declarations::ACTIVITY;
}
public function initiateQuery(array $requiredModifiers, array $acl, array $data = [])
{
$centers = array_map(static fn ($el) => $el['center'], $acl);
$qb = $this->repository
->createQueryBuilder('activity')
->join('activity.persons', 'person')
->join('activity.accompanyingPeriod', 'acp')
->join(
HouseholdMember::class,
'householdmember',
Query\Expr\Join::WITH,
'person.id = IDENTITY(householdmember.person) AND householdmember.startDate <= activity.date AND (householdmember.endDate IS NULL OR householdmember.endDate > activity.date)'
)
->join('householdmember.household', 'household');
if ($this->filterStatsByCenters) {
$qb
->andWhere(
$qb->expr()->exists(
'SELECT 1 FROM '.AccompanyingPeriodParticipation::class.' acl_count_part
JOIN '.PersonCenterHistory::class.' acl_count_person_history WITH IDENTITY(acl_count_person_history.person) = IDENTITY(acl_count_part.person)
WHERE acl_count_part.accompanyingPeriod = acp.id AND acl_count_person_history.center IN (:authorized_centers)
'
)
)
->setParameter('authorized_centers', $centers);
}
AccompanyingCourseExportHelper::addClosingMotiveExclusionClause($qb);
$qb->select('COUNT(DISTINCT household.id) as export_count_activity');
return $qb;
}
public function requiredRole(): string
{
return ActivityStatsVoter::STATS;
}
public function supportsModifiers(): array
{
return [
Declarations::ACTIVITY,
Declarations::ACTIVITY_ACP,
PersonDeclarations::ACP_TYPE,
PersonDeclarations::PERSON_TYPE,
PersonDeclarations::HOUSEHOLD_TYPE,
];
}
}

View File

@@ -0,0 +1,140 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\ActivityBundle\Export\Export\LinkedToACP;
use Chill\ActivityBundle\Entity\Activity;
use Chill\ActivityBundle\Export\Declarations;
use Chill\ActivityBundle\Security\Authorization\ActivityStatsVoter;
use Chill\MainBundle\Export\AccompanyingCourseExportHelper;
use Chill\MainBundle\Export\ExportInterface;
use Chill\MainBundle\Export\FormatterInterface;
use Chill\MainBundle\Export\GroupedExportInterface;
use Chill\PersonBundle\Entity\AccompanyingPeriodParticipation;
use Chill\PersonBundle\Entity\Person\PersonCenterHistory;
use Chill\PersonBundle\Export\Declarations as PersonDeclarations;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\Query;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\Form\FormBuilderInterface;
class CountPersonsOnActivity implements ExportInterface, GroupedExportInterface
{
protected EntityRepository $repository;
private readonly bool $filterStatsByCenters;
public function __construct(
EntityManagerInterface $em,
ParameterBagInterface $parameterBag,
) {
$this->repository = $em->getRepository(Activity::class);
$this->filterStatsByCenters = $parameterBag->get('chill_main')['acl']['filter_stats_by_center'];
}
public function buildForm(FormBuilderInterface $builder)
{
}
public function getFormDefaultData(): array
{
return [];
}
public function getAllowedFormattersTypes(): array
{
return [FormatterInterface::TYPE_TABULAR];
}
public function getDescription(): string
{
return 'export.export.count_person_on_activity.description';
}
public function getGroup(): string
{
return 'Exports of activities linked to an accompanying period';
}
public function getLabels($key, array $values, $data)
{
if ('export_count_activity' !== $key) {
throw new \LogicException("the key {$key} is not used by this export");
}
return static fn ($value) => '_header' === $value ? 'export.export.count_person_on_activity.header' : $value;
}
public function getQueryKeys($data): array
{
return ['export_count_activity'];
}
public function getResult($query, $data)
{
return $query->getQuery()->getResult(Query::HYDRATE_SCALAR);
}
public function getTitle(): string
{
return 'export.export.count_person_on_activity.title';
}
public function getType(): string
{
return Declarations::ACTIVITY;
}
public function initiateQuery(array $requiredModifiers, array $acl, array $data = [])
{
$centers = array_map(static fn ($el) => $el['center'], $acl);
$qb = $this->repository
->createQueryBuilder('activity')
->join('activity.persons', 'person')
->join('activity.accompanyingPeriod', 'acp');
if ($this->filterStatsByCenters) {
$qb
->andWhere(
$qb->expr()->exists(
'SELECT 1 FROM '.AccompanyingPeriodParticipation::class.' acl_count_part
JOIN '.PersonCenterHistory::class.' acl_count_person_history WITH IDENTITY(acl_count_person_history.person) = IDENTITY(acl_count_part.person)
WHERE acl_count_part.accompanyingPeriod = acp.id AND acl_count_person_history.center IN (:authorized_centers)
'
)
)
->setParameter('authorized_centers', $centers);
}
AccompanyingCourseExportHelper::addClosingMotiveExclusionClause($qb);
$qb->select('COUNT(DISTINCT person.id) as export_count_activity');
return $qb;
}
public function requiredRole(): string
{
return ActivityStatsVoter::STATS;
}
public function supportsModifiers(): array
{
return [
Declarations::ACTIVITY,
Declarations::ACTIVITY_ACP,
PersonDeclarations::ACP_TYPE,
PersonDeclarations::PERSON_TYPE,
];
}
}

View File

@@ -12,38 +12,33 @@ declare(strict_types=1);
namespace Chill\ActivityBundle\Export\Export\LinkedToACP;
use Chill\ActivityBundle\Entity\Activity;
use Chill\ActivityBundle\Export\Declarations;
use Chill\ActivityBundle\Export\Export\ListActivityHelper;
use Chill\ActivityBundle\Security\Authorization\ActivityStatsVoter;
use Chill\MainBundle\Entity\Scope;
use Chill\MainBundle\Export\AccompanyingCourseExportHelper;
use Chill\MainBundle\Export\GroupedExportInterface;
use Chill\MainBundle\Export\Helper\TranslatableStringExportLabelHelper;
use Chill\MainBundle\Export\ListInterface;
use Chill\PersonBundle\Entity\Person\PersonCenterHistory;
use Chill\PersonBundle\Export\Helper\FilterListAccompanyingPeriodHelperInterface;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Form\FormBuilderInterface;
class ListActivity implements ListInterface, GroupedExportInterface
final readonly class ListActivity implements ListInterface, GroupedExportInterface
{
private EntityManagerInterface $entityManager;
private ListActivityHelper $helper;
private TranslatableStringExportLabelHelper $translatableStringExportLabelHelper;
public function __construct(
ListActivityHelper $helper,
EntityManagerInterface $entityManager,
TranslatableStringExportLabelHelper $translatableStringExportLabelHelper
private ListActivityHelper $helper,
private EntityManagerInterface $entityManager,
private TranslatableStringExportLabelHelper $translatableStringExportLabelHelper,
private FilterListAccompanyingPeriodHelperInterface $filterListAccompanyingPeriodHelper,
) {
$this->helper = $helper;
$this->entityManager = $entityManager;
$this->translatableStringExportLabelHelper = $translatableStringExportLabelHelper;
}
public function buildForm(FormBuilderInterface $builder)
{
$this->helper->buildForm($builder);
}
public function getFormDefaultData(): array
{
return [];
@@ -56,7 +51,7 @@ class ListActivity implements ListInterface, GroupedExportInterface
public function getDescription()
{
return ListActivityHelper::MSG_KEY . 'List activities linked to an accompanying course';
return ListActivityHelper::MSG_KEY.'List activities linked to an accompanying course';
}
public function getGroup(): string
@@ -66,22 +61,17 @@ class ListActivity implements ListInterface, GroupedExportInterface
public function getLabels($key, array $values, $data)
{
switch ($key) {
case 'acpId':
return static function ($value) {
if ('_header' === $value) {
return ListActivityHelper::MSG_KEY . 'accompanying course id';
}
return match ($key) {
'acpId' => static function ($value) {
if ('_header' === $value) {
return ListActivityHelper::MSG_KEY.'accompanying course id';
}
return $value ?? '';
};
case 'scopesNames':
return $this->translatableStringExportLabelHelper->getLabelMulti($key, $values, ListActivityHelper::MSG_KEY . 'course circles');
default:
return $this->helper->getLabels($key, $values, $data);
}
return $value ?? '';
},
'scopesNames' => $this->translatableStringExportLabelHelper->getLabelMulti($key, $values, ListActivityHelper::MSG_KEY.'course circles'),
default => $this->helper->getLabels($key, $values, $data),
};
}
public function getQueryKeys($data)
@@ -103,7 +93,7 @@ class ListActivity implements ListInterface, GroupedExportInterface
public function getTitle()
{
return ListActivityHelper::MSG_KEY . 'List activity linked to a course';
return ListActivityHelper::MSG_KEY.'List activity linked to a course';
}
public function getType()
@@ -123,30 +113,26 @@ class ListActivity implements ListInterface, GroupedExportInterface
->join('activity.accompanyingPeriod', 'acp')
->leftJoin('acp.participations', 'acppart')
->leftJoin('acppart.person', 'person')
->andWhere('acppart.startDate != acppart.endDate OR acppart.endDate IS NULL')
->andWhere(
$qb->expr()->exists(
'SELECT 1
FROM ' . PersonCenterHistory::class . ' acl_count_person_history
WHERE acl_count_person_history.person = person
AND acl_count_person_history.center IN (:authorized_centers)
'
)
)
->andWhere('acppart.startDate != acppart.endDate OR acppart.endDate IS NULL');
$this->filterListAccompanyingPeriodHelper->addFilterAccompanyingPeriods($qb, $requiredModifiers, $acl, $data);
$qb
// some grouping are necessary
->addGroupBy('acp.id')
->addOrderBy('activity.date')
->addOrderBy('activity.id')
->setParameter('authorized_centers', $centers);
->addOrderBy('activity.id');
$this->helper->addSelect($qb);
// add select for this step
$qb
->addSelect('acp.id AS acpId')
->addSelect('(SELECT AGGREGATE(acpScope.name) FROM ' . Scope::class . ' acpScope WHERE acpScope MEMBER OF acp.scopes) AS scopesNames')
->addSelect('(SELECT AGGREGATE(acpScope.name) FROM '.Scope::class.' acpScope WHERE acpScope MEMBER OF acp.scopes) AS scopesNames')
->addGroupBy('scopesNames');
AccompanyingCourseExportHelper::addClosingMotiveExclusionClause($qb);
return $qb;
}
@@ -160,6 +146,7 @@ class ListActivity implements ListInterface, GroupedExportInterface
return array_merge(
$this->helper->supportsModifiers(),
[
Declarations::ACTIVITY,
\Chill\PersonBundle\Export\Declarations::ACP_TYPE,
]
);

View File

@@ -14,6 +14,7 @@ namespace Chill\ActivityBundle\Export\Export\LinkedToACP;
use Chill\ActivityBundle\Entity\Activity;
use Chill\ActivityBundle\Export\Declarations;
use Chill\ActivityBundle\Security\Authorization\ActivityStatsVoter;
use Chill\MainBundle\Export\AccompanyingCourseExportHelper;
use Chill\MainBundle\Export\ExportInterface;
use Chill\MainBundle\Export\FormatterInterface;
use Chill\MainBundle\Export\GroupedExportInterface;
@@ -23,23 +24,27 @@ use Chill\PersonBundle\Export\Declarations as PersonDeclarations;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\Query;
use LogicException;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\Form\FormBuilderInterface;
class SumActivityDuration implements ExportInterface, GroupedExportInterface
{
protected EntityRepository $repository;
private readonly bool $filterStatsByCenters;
public function __construct(
EntityManagerInterface $em
EntityManagerInterface $em,
ParameterBagInterface $parameterBag,
) {
$this->repository = $em->getRepository(Activity::class);
$this->filterStatsByCenters = $parameterBag->get('chill_main')['acl']['filter_stats_by_center'];
}
public function buildForm(FormBuilderInterface $builder)
{
// TODO: Implement buildForm() method.
}
public function getFormDefaultData(): array
{
return [];
@@ -63,7 +68,7 @@ class SumActivityDuration implements ExportInterface, GroupedExportInterface
public function getLabels($key, array $values, $data)
{
if ('export_sum_activity_duration' !== $key) {
throw new LogicException("the key {$key} is not used by this export");
throw new \LogicException("the key {$key} is not used by this export");
}
return static fn ($value) => '_header' === $value ? 'Sum activities linked to an accompanying period duration' : $value;
@@ -100,16 +105,20 @@ class SumActivityDuration implements ExportInterface, GroupedExportInterface
$qb->select('SUM(activity.durationTime) as export_sum_activity_duration')
->andWhere($qb->expr()->isNotNull('activity.durationTime'));
$qb
->andWhere(
$qb->expr()->exists(
'SELECT 1 FROM ' . AccompanyingPeriodParticipation::class . ' acl_count_part
JOIN ' . PersonCenterHistory::class . ' acl_count_person_history WITH IDENTITY(acl_count_person_history.person) = IDENTITY(acl_count_part.person)
if ($this->filterStatsByCenters) {
$qb
->andWhere(
$qb->expr()->exists(
'SELECT 1 FROM '.AccompanyingPeriodParticipation::class.' acl_count_part
JOIN '.PersonCenterHistory::class.' acl_count_person_history WITH IDENTITY(acl_count_person_history.person) = IDENTITY(acl_count_part.person)
WHERE acl_count_part.accompanyingPeriod = acp.id AND acl_count_person_history.center IN (:authorized_centers)
'
)
)
)
->setParameter('authorized_centers', $centers);
->setParameter('authorized_centers', $centers);
}
AccompanyingCourseExportHelper::addClosingMotiveExclusionClause($qb);
return $qb;
}

View File

@@ -14,6 +14,7 @@ namespace Chill\ActivityBundle\Export\Export\LinkedToACP;
use Chill\ActivityBundle\Entity\Activity;
use Chill\ActivityBundle\Export\Declarations;
use Chill\ActivityBundle\Security\Authorization\ActivityStatsVoter;
use Chill\MainBundle\Export\AccompanyingCourseExportHelper;
use Chill\MainBundle\Export\ExportInterface;
use Chill\MainBundle\Export\FormatterInterface;
use Chill\MainBundle\Export\GroupedExportInterface;
@@ -23,23 +24,27 @@ use Chill\PersonBundle\Export\Declarations as PersonDeclarations;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\Query;
use LogicException;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\Form\FormBuilderInterface;
class SumActivityVisitDuration implements ExportInterface, GroupedExportInterface
{
protected EntityRepository $repository;
private readonly bool $filterStatsByCenters;
public function __construct(
EntityManagerInterface $em
EntityManagerInterface $em,
ParameterBagInterface $parameterBag,
) {
$this->repository = $em->getRepository(Activity::class);
$this->filterStatsByCenters = $parameterBag->get('chill_main')['acl']['filter_stats_by_center'];
}
public function buildForm(FormBuilderInterface $builder)
{
// TODO: Implement buildForm() method.
}
public function getFormDefaultData(): array
{
return [];
@@ -63,7 +68,7 @@ class SumActivityVisitDuration implements ExportInterface, GroupedExportInterfac
public function getLabels($key, array $values, $data)
{
if ('export_sum_activity_visit_duration' !== $key) {
throw new LogicException("the key {$key} is not used by this export");
throw new \LogicException("the key {$key} is not used by this export");
}
return static fn ($value) => '_header' === $value ? 'Sum activities linked to an accompanying period visit duration' : $value;
@@ -100,16 +105,20 @@ class SumActivityVisitDuration implements ExportInterface, GroupedExportInterfac
$qb->select('SUM(activity.travelTime) as export_sum_activity_visit_duration')
->andWhere($qb->expr()->isNotNull('activity.travelTime'));
$qb
->andWhere(
$qb->expr()->exists(
'SELECT 1 FROM ' . AccompanyingPeriodParticipation::class . ' acl_count_part
JOIN ' . PersonCenterHistory::class . ' acl_count_person_history WITH IDENTITY(acl_count_person_history.person) = IDENTITY(acl_count_part.person)
if ($this->filterStatsByCenters) {
$qb
->andWhere(
$qb->expr()->exists(
'SELECT 1 FROM '.AccompanyingPeriodParticipation::class.' acl_count_part
JOIN '.PersonCenterHistory::class.' acl_count_person_history WITH IDENTITY(acl_count_person_history.person) = IDENTITY(acl_count_part.person)
WHERE acl_count_part.accompanyingPeriod = acp.id AND acl_count_person_history.center IN (:authorized_centers)
'
)
)
)
->setParameter('authorized_centers', $centers);
->setParameter('authorized_centers', $centers);
}
AccompanyingCourseExportHelper::addClosingMotiveExclusionClause($qb);
return $qb;
}

View File

@@ -19,22 +19,24 @@ use Chill\MainBundle\Export\FormatterInterface;
use Chill\MainBundle\Export\GroupedExportInterface;
use Chill\PersonBundle\Export\Declarations as PersonDeclarations;
use Doctrine\ORM\Query;
use LogicException;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\Form\FormBuilderInterface;
class CountActivity implements ExportInterface, GroupedExportInterface
{
protected ActivityRepository $activityRepository;
private readonly bool $filterStatsByCenters;
public function __construct(
ActivityRepository $activityRepository
private readonly ActivityRepository $activityRepository,
ParameterBagInterface $parameterBag,
) {
$this->activityRepository = $activityRepository;
$this->filterStatsByCenters = $parameterBag->get('chill_main')['acl']['filter_stats_by_center'];
}
public function buildForm(FormBuilderInterface $builder)
{
}
public function getFormDefaultData(): array
{
return [];
@@ -58,7 +60,7 @@ class CountActivity implements ExportInterface, GroupedExportInterface
public function getLabels($key, array $values, $data)
{
if ('export_count_activity' !== $key) {
throw new LogicException("the key {$key} is not used by this export");
throw new \LogicException("the key {$key} is not used by this export");
}
return static fn ($value) => '_header' === $value ? 'Number of activities linked to a person' : $value;
@@ -90,23 +92,25 @@ class CountActivity implements ExportInterface, GroupedExportInterface
$qb = $this->activityRepository
->createQueryBuilder('activity')
->join('activity.person', 'person')
->join('person.centerHistory', 'centerHistory');
->join('activity.person', 'person');
$qb->select('COUNT(activity.id) as export_count_activity');
$qb
->where(
$qb->expr()->andX(
$qb->expr()->lte('centerHistory.startDate', 'activity.date'),
$qb->expr()->orX(
$qb->expr()->isNull('centerHistory.endDate'),
$qb->expr()->gt('centerHistory.endDate', 'activity.date')
if ($this->filterStatsByCenters) {
$qb
->join('person.centerHistory', 'centerHistory')
->where(
$qb->expr()->andX(
$qb->expr()->lte('centerHistory.startDate', 'activity.date'),
$qb->expr()->orX(
$qb->expr()->isNull('centerHistory.endDate'),
$qb->expr()->gt('centerHistory.endDate', 'activity.date')
)
)
)
)
->andWhere($qb->expr()->in('centerHistory.center', ':centers'))
->setParameter('centers', $centers);
->andWhere($qb->expr()->in('centerHistory.center', ':centers'))
->setParameter('centers', $centers);
}
return $qb;
}

View File

@@ -0,0 +1,140 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\ActivityBundle\Export\Export\LinkedToPerson;
use Chill\ActivityBundle\Export\Declarations;
use Chill\ActivityBundle\Repository\ActivityRepository;
use Chill\ActivityBundle\Security\Authorization\ActivityStatsVoter;
use Chill\MainBundle\Export\ExportInterface;
use Chill\MainBundle\Export\FormatterInterface;
use Chill\MainBundle\Export\GroupedExportInterface;
use Chill\PersonBundle\Entity\Household\HouseholdMember;
use Chill\PersonBundle\Export\Declarations as PersonDeclarations;
use Doctrine\ORM\Query;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\Form\FormBuilderInterface;
final readonly class CountHouseholdOnActivity implements ExportInterface, GroupedExportInterface
{
private bool $filterStatsByCenters;
public function __construct(
private ActivityRepository $activityRepository,
ParameterBagInterface $parameterBag,
) {
$this->filterStatsByCenters = $parameterBag->get('chill_main')['acl']['filter_stats_by_center'];
}
public function buildForm(FormBuilderInterface $builder)
{
}
public function getFormDefaultData(): array
{
return [];
}
public function getAllowedFormattersTypes()
{
return [FormatterInterface::TYPE_TABULAR];
}
public function getDescription()
{
return 'export.export.count_household_on_activity_person.description';
}
public function getGroup(): string
{
return 'Exports of activities linked to a person';
}
public function getLabels($key, array $values, $data)
{
if ('export_count_activity' !== $key) {
throw new \LogicException("the key {$key} is not used by this export");
}
return static fn ($value) => '_header' === $value ? 'export.export.count_household_on_activity_person.header' : $value;
}
public function getQueryKeys($data)
{
return ['export_count_activity'];
}
public function getResult($query, $data)
{
return $query->getQuery()->getResult(Query::HYDRATE_SCALAR);
}
public function getTitle()
{
return 'export.export.count_household_on_activity_person.title';
}
public function getType(): string
{
return Declarations::ACTIVITY;
}
public function initiateQuery(array $requiredModifiers, array $acl, array $data = [])
{
$centers = array_map(static fn ($el) => $el['center'], $acl);
$qb = $this->activityRepository
->createQueryBuilder('activity')
->join('activity.person', 'person')
->join(
HouseholdMember::class,
'householdmember',
Query\Expr\Join::WITH,
'person = householdmember.person AND householdmember.startDate <= activity.date AND (householdmember.endDate IS NULL OR householdmember.endDate > activity.date)'
)
->join('householdmember.household', 'household');
$qb->select('COUNT(DISTINCT household.id) as export_count_activity');
if ($this->filterStatsByCenters) {
$qb
->join('person.centerHistory', 'centerHistory')
->where(
$qb->expr()->andX(
$qb->expr()->lte('centerHistory.startDate', 'activity.date'),
$qb->expr()->orX(
$qb->expr()->isNull('centerHistory.endDate'),
$qb->expr()->gt('centerHistory.endDate', 'activity.date')
)
)
)
->andWhere($qb->expr()->in('centerHistory.center', ':centers'))
->setParameter('centers', $centers);
}
return $qb;
}
public function requiredRole(): string
{
return ActivityStatsVoter::STATS;
}
public function supportsModifiers()
{
return [
Declarations::ACTIVITY,
Declarations::ACTIVITY_PERSON,
PersonDeclarations::PERSON_TYPE,
PersonDeclarations::HOUSEHOLD_TYPE,
];
}
}

View File

@@ -20,24 +20,18 @@ use Chill\MainBundle\Export\GroupedExportInterface;
use Chill\MainBundle\Export\ListInterface;
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
use Chill\PersonBundle\Export\Declarations as PersonDeclarations;
use DateTime;
use Doctrine\DBAL\Exception\InvalidArgumentException;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Query;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Validator\Constraints\Callback;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
use function array_key_exists;
use function count;
use function in_array;
class ListActivity implements ListInterface, GroupedExportInterface
{
protected EntityManagerInterface $entityManager;
protected array $fields = [
'id',
'date',
@@ -51,23 +45,16 @@ class ListActivity implements ListInterface, GroupedExportInterface
'person_lastname',
'person_id',
];
protected TranslatableStringHelperInterface $translatableStringHelper;
protected TranslatorInterface $translator;
private ActivityRepository $activityRepository;
private readonly bool $filterStatsByCenters;
public function __construct(
EntityManagerInterface $em,
TranslatorInterface $translator,
TranslatableStringHelperInterface $translatableStringHelper,
ActivityRepository $activityRepository
protected EntityManagerInterface $entityManager,
protected TranslatorInterface $translator,
protected TranslatableStringHelperInterface $translatableStringHelper,
private readonly ActivityRepository $activityRepository,
ParameterBagInterface $parameterBag,
) {
$this->entityManager = $em;
$this->translator = $translator;
$this->translatableStringHelper = $translatableStringHelper;
$this->activityRepository = $activityRepository;
$this->filterStatsByCenters = $parameterBag->get('chill_main')['acl']['filter_stats_by_center'];
}
public function buildForm(FormBuilderInterface $builder)
@@ -79,7 +66,7 @@ class ListActivity implements ListInterface, GroupedExportInterface
'label' => 'Fields to include in export',
'constraints' => [new Callback([
'callback' => static function ($selected, ExecutionContextInterface $context) {
if (count($selected) === 0) {
if (0 === \count($selected)) {
$context->buildViolation('You must select at least one element')
->atPath('fields')
->addViolation();
@@ -88,6 +75,7 @@ class ListActivity implements ListInterface, GroupedExportInterface
])],
]);
}
public function getFormDefaultData(): array
{
return [];
@@ -117,7 +105,7 @@ class ListActivity implements ListInterface, GroupedExportInterface
return 'date';
}
$date = DateTime::createFromFormat('Y-m-d H:i:s', $value);
$date = \DateTime::createFromFormat('Y-m-d H:i:s', $value);
return $date->format('d-m-Y');
};
@@ -141,11 +129,11 @@ class ListActivity implements ListInterface, GroupedExportInterface
$activity = $activityRepository->find($value);
return implode(', ', array_map(fn (ActivityReason $r) => '"' .
return implode(', ', array_map(fn (ActivityReason $r) => '"'.
$this->translatableStringHelper->localize($r->getCategory()->getName())
. ' > ' .
.' > '.
$this->translatableStringHelper->localize($r->getName())
. '"', $activity->getReasons()->toArray()));
.'"', $activity->getReasons()->toArray()));
};
case 'circle_name':
@@ -154,7 +142,7 @@ class ListActivity implements ListInterface, GroupedExportInterface
return 'circle';
}
return $this->translatableStringHelper->localize(json_decode($value, true, 512, JSON_THROW_ON_ERROR));
return $this->translatableStringHelper->localize(json_decode((string) $value, true, 512, JSON_THROW_ON_ERROR));
};
case 'type_name':
@@ -163,7 +151,7 @@ class ListActivity implements ListInterface, GroupedExportInterface
return 'activity type';
}
return $this->translatableStringHelper->localize(json_decode($value, true, 512, JSON_THROW_ON_ERROR));
return $this->translatableStringHelper->localize(json_decode((string) $value, true, 512, JSON_THROW_ON_ERROR));
};
default:
@@ -202,7 +190,7 @@ class ListActivity implements ListInterface, GroupedExportInterface
$centers = array_map(static fn ($el) => $el['center'], $acl);
// throw an error if any fields are present
if (!array_key_exists('fields', $data)) {
if (!\array_key_exists('fields', $data)) {
throw new InvalidArgumentException('Any fields have been checked.');
}
@@ -210,23 +198,25 @@ class ListActivity implements ListInterface, GroupedExportInterface
$qb
->from('ChillActivityBundle:Activity', 'activity')
->join('activity.person', 'actperson')
->join('actperson.centerHistory', 'centerHistory');
->join('activity.person', 'actperson');
$qb->where(
$qb->expr()->andX(
$qb->expr()->lte('centerHistory.startDate', 'activity.date'),
$qb->expr()->orX(
$qb->expr()->isNull('centerHistory.endDate'),
$qb->expr()->gt('centerHistory.endDate', 'activity.date')
if ($this->filterStatsByCenters) {
$qb->join('actperson.centerHistory', 'centerHistory');
$qb->where(
$qb->expr()->andX(
$qb->expr()->lte('centerHistory.startDate', 'activity.date'),
$qb->expr()->orX(
$qb->expr()->isNull('centerHistory.endDate'),
$qb->expr()->gt('centerHistory.endDate', 'activity.date')
)
)
)
)
->andWhere($qb->expr()->in('centerHistory.center', ':centers'))
->setParameter('centers', $centers);
->andWhere($qb->expr()->in('centerHistory.center', ':centers'))
->setParameter('centers', $centers);
}
foreach ($this->fields as $f) {
if (in_array($f, $data['fields'], true)) {
if (\in_array($f, $data['fields'], true)) {
switch ($f) {
case 'id':
$qb->addSelect('activity.id AS id');
@@ -299,7 +289,7 @@ class ListActivity implements ListInterface, GroupedExportInterface
return [
Declarations::ACTIVITY,
Declarations::ACTIVITY_PERSON,
//PersonDeclarations::PERSON_TYPE,
PersonDeclarations::PERSON_TYPE,
];
}
}

View File

@@ -20,7 +20,7 @@ use Chill\MainBundle\Export\FormatterInterface;
use Chill\MainBundle\Export\GroupedExportInterface;
use Chill\PersonBundle\Export\Declarations as PersonDeclarations;
use Doctrine\ORM\Query;
use LogicException;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\Form\FormBuilderInterface;
/**
@@ -30,29 +30,27 @@ use Symfony\Component\Form\FormBuilderInterface;
*/
class StatActivityDuration implements ExportInterface, GroupedExportInterface
{
public const SUM = 'sum';
/**
* The action for this report.
*/
protected string $action;
private ActivityRepository $activityRepository;
final public const SUM = 'sum';
private readonly bool $filterStatsByCenters;
/**
* @param string $action the stat to perform
*/
public function __construct(
ActivityRepository $activityRepository,
string $action = 'sum'
private readonly ActivityRepository $activityRepository,
ParameterBagInterface $parameterBag,
/**
* The action for this report.
*/
protected string $action = 'sum'
) {
$this->action = $action;
$this->activityRepository = $activityRepository;
$this->filterStatsByCenters = $parameterBag->get('chill_main')['acl']['filter_stats_by_center'];
}
public function buildForm(FormBuilderInterface $builder)
{
}
public function getFormDefaultData(): array
{
return [];
@@ -69,7 +67,7 @@ class StatActivityDuration implements ExportInterface, GroupedExportInterface
return 'Sum activities linked to a person duration by various parameters.';
}
throw new LogicException('this action is not supported: ' . $this->action);
throw new \LogicException('this action is not supported: '.$this->action);
}
public function getGroup(): string
@@ -80,7 +78,7 @@ class StatActivityDuration implements ExportInterface, GroupedExportInterface
public function getLabels($key, array $values, $data)
{
if ('export_stat_activity' !== $key) {
throw new LogicException(sprintf('The key %s is not used by this export', $key));
throw new \LogicException(sprintf('The key %s is not used by this export', $key));
}
$header = self::SUM === $this->action ? 'Sum activities linked to a person duration' : false;
@@ -104,7 +102,7 @@ class StatActivityDuration implements ExportInterface, GroupedExportInterface
return 'Sum activity linked to a person duration';
}
throw new LogicException('This action is not supported: ' . $this->action);
throw new \LogicException('This action is not supported: '.$this->action);
}
public function getType(): string
@@ -128,21 +126,23 @@ class StatActivityDuration implements ExportInterface, GroupedExportInterface
}
$qb->select($select)
->join('activity.person', 'person')
->join('person.centerHistory', 'centerHistory');
->join('activity.person', 'person');
$qb
->where(
$qb->expr()->andX(
$qb->expr()->lte('centerHistory.startDate', 'activity.date'),
$qb->expr()->orX(
$qb->expr()->isNull('centerHistory.endDate'),
$qb->expr()->gt('centerHistory.endDate', 'activity.date')
if ($this->filterStatsByCenters) {
$qb
->join('person.centerHistory', 'centerHistory')
->where(
$qb->expr()->andX(
$qb->expr()->lte('centerHistory.startDate', 'activity.date'),
$qb->expr()->orX(
$qb->expr()->isNull('centerHistory.endDate'),
$qb->expr()->gt('centerHistory.endDate', 'activity.date')
)
)
)
)
->andWhere($qb->expr()->in('centerHistory.center', ':centers'))
->setParameter('centers', $centers);
->andWhere($qb->expr()->in('centerHistory.center', ':centers'))
->setParameter('centers', $centers);
}
return $qb;
}
@@ -157,7 +157,7 @@ class StatActivityDuration implements ExportInterface, GroupedExportInterface
return [
Declarations::ACTIVITY,
Declarations::ACTIVITY_PERSON,
//PersonDeclarations::PERSON_TYPE,
PersonDeclarations::PERSON_TYPE,
];
}
}

View File

@@ -23,53 +23,24 @@ use Chill\PersonBundle\Export\Helper\LabelPersonHelper;
use Chill\ThirdPartyBundle\Export\Helper\LabelThirdPartyHelper;
use Doctrine\ORM\AbstractQuery;
use Doctrine\ORM\QueryBuilder;
use LogicException;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
use const SORT_NUMERIC;
class ListActivityHelper
{
public const MSG_KEY = 'export.list.activity.';
private ActivityPresenceRepositoryInterface $activityPresenceRepository;
private ActivityTypeRepositoryInterface $activityTypeRepository;
private DateTimeHelper $dateTimeHelper;
private LabelPersonHelper $labelPersonHelper;
private LabelThirdPartyHelper $labelThirdPartyHelper;
private TranslatableStringHelperInterface $translatableStringHelper;
private TranslatableStringExportLabelHelper $translatableStringLabelHelper;
private TranslatorInterface $translator;
private UserHelper $userHelper;
final public const MSG_KEY = 'export.list.activity.';
public function __construct(
ActivityPresenceRepositoryInterface $activityPresenceRepository,
ActivityTypeRepositoryInterface $activityTypeRepository,
DateTimeHelper $dateTimeHelper,
LabelPersonHelper $labelPersonHelper,
LabelThirdPartyHelper $labelThirdPartyHelper,
TranslatorInterface $translator,
TranslatableStringHelperInterface $translatableStringHelper,
TranslatableStringExportLabelHelper $translatableStringLabelHelper,
UserHelper $userHelper
private readonly ActivityPresenceRepositoryInterface $activityPresenceRepository,
private readonly ActivityTypeRepositoryInterface $activityTypeRepository,
private readonly DateTimeHelper $dateTimeHelper,
private readonly LabelPersonHelper $labelPersonHelper,
private readonly LabelThirdPartyHelper $labelThirdPartyHelper,
private readonly TranslatorInterface $translator,
private readonly TranslatableStringHelperInterface $translatableStringHelper,
private readonly TranslatableStringExportLabelHelper $translatableStringLabelHelper,
private readonly UserHelper $userHelper
) {
$this->activityPresenceRepository = $activityPresenceRepository;
$this->activityTypeRepository = $activityTypeRepository;
$this->dateTimeHelper = $dateTimeHelper;
$this->labelPersonHelper = $labelPersonHelper;
$this->labelThirdPartyHelper = $labelThirdPartyHelper;
$this->translator = $translator;
$this->translatableStringHelper = $translatableStringHelper;
$this->translatableStringLabelHelper = $translatableStringLabelHelper;
$this->userHelper = $userHelper;
}
public function addSelect(QueryBuilder $qb): void
@@ -85,7 +56,7 @@ class ListActivityHelper
->addSelect('AGGREGATE(actPerson.id) AS personsNames')
->leftJoin('activity.users', 'users_u')
->addSelect('AGGREGATE(users_u.id) AS usersIds')
->addSelect('AGGREGATE(users_u.id) AS usersNames')
->addSelect('AGGREGATE(JSON_BUILD_OBJECT(\'uid\', users_u.id, \'d\', activity.date)) AS usersNames')
->leftJoin('activity.thirdParties', 'thirdparty')
->addSelect('AGGREGATE(thirdparty.id) AS thirdPartiesIds')
->addSelect('AGGREGATE(thirdparty.id) AS thirdPartiesNames')
@@ -96,9 +67,9 @@ class ListActivityHelper
->leftJoin('activity.location', 'location')
->addSelect('location.name AS locationName')
->addSelect('activity.sentReceived')
->addSelect('IDENTITY(activity.createdBy) AS createdBy')
->addSelect('JSON_BUILD_OBJECT(\'uid\', IDENTITY(activity.createdBy), \'d\', activity.createdAt) AS createdBy')
->addSelect('activity.createdAt')
->addSelect('IDENTITY(activity.updatedBy) AS updatedBy')
->addSelect('JSON_BUILD_OBJECT(\'uid\', IDENTITY(activity.updatedBy), \'d\', activity.updatedAt) AS updatedBy')
->addSelect('activity.updatedAt')
->addGroupBy('activity.id')
->addGroupBy('location.id');
@@ -115,113 +86,78 @@ class ListActivityHelper
public function getLabels($key, array $values, $data)
{
switch ($key) {
case 'createdAt':
case 'updatedAt':
return $this->dateTimeHelper->getLabel($key);
return match ($key) {
'createdAt', 'updatedAt' => $this->dateTimeHelper->getLabel($key),
'createdBy', 'updatedBy' => $this->userHelper->getLabel($key, $values, $key),
'date' => $this->dateTimeHelper->getLabel(self::MSG_KEY.$key),
'attendeeName' => function ($value) {
if ('_header' === $value) {
return 'Attendee';
}
case 'createdBy':
case 'updatedBy':
return $this->userHelper->getLabel($key, $values, $key);
if (null === $value || null === $presence = $this->activityPresenceRepository->find($value)) {
return '';
}
case 'date':
return $this->dateTimeHelper->getLabel(self::MSG_KEY . $key);
return $this->translatableStringHelper->localize($presence->getName());
},
'listReasons' => $this->translatableStringLabelHelper->getLabelMulti($key, $values, 'Activity Reasons'),
'typeName' => function ($value) {
if ('_header' === $value) {
return 'Activity type';
}
case 'attendeeName':
return function ($value) {
if ('_header' === $value) {
return 'Attendee';
}
if (null === $value || null === $type = $this->activityTypeRepository->find($value)) {
return '';
}
if (null === $value || null === $presence = $this->activityPresenceRepository->find($value)) {
return '';
}
return $this->translatableStringHelper->localize($type->getName());
},
'usersNames' => $this->userHelper->getLabelMulti($key, $values, self::MSG_KEY.'users name'),
'usersIds', 'thirdPartiesIds', 'personsIds' => static function ($value) use ($key) {
if ('_header' === $value) {
return match ($key) {
'usersIds' => self::MSG_KEY.'users ids',
'thirdPartiesIds' => self::MSG_KEY.'third parties ids',
'personsIds' => self::MSG_KEY.'persons ids',
};
}
return $this->translatableStringHelper->localize($presence->getName());
};
$decoded = json_decode((string) $value, null, 512, JSON_THROW_ON_ERROR);
case 'listReasons':
return $this->translatableStringLabelHelper->getLabelMulti($key, $values, 'Activity Reasons');
return implode(
'|',
array_unique(
array_filter($decoded, static fn (?int $id) => null !== $id),
\SORT_NUMERIC
)
);
},
'personsNames' => $this->labelPersonHelper->getLabelMulti($key, $values, self::MSG_KEY.'persons name'),
'thirdPartiesNames' => $this->labelThirdPartyHelper->getLabelMulti($key, $values, self::MSG_KEY.'thirds parties'),
'sentReceived' => function ($value) {
if ('_header' === $value) {
return self::MSG_KEY.'sent received';
}
case 'typeName':
return function ($value) {
if ('_header' === $value) {
return 'Activity type';
}
if (null === $value) {
return '';
}
if (null === $value || null === $type = $this->activityTypeRepository->find($value)) {
return '';
}
return $this->translator->trans($value);
},
default => function ($value) use ($key) {
if ('_header' === $value) {
return self::MSG_KEY.$key;
}
return $this->translatableStringHelper->localize($type->getName());
};
if (null === $value) {
return '';
}
case 'usersNames':
return $this->userHelper->getLabelMulti($key, $values, self::MSG_KEY . 'users name');
case 'usersIds':
case 'thirdPartiesIds':
case 'personsIds':
return static function ($value) use ($key) {
if ('_header' === $value) {
switch ($key) {
case 'usersIds':
return self::MSG_KEY . 'users ids';
case 'thirdPartiesIds':
return self::MSG_KEY . 'third parties ids';
case 'personsIds':
return self::MSG_KEY . 'persons ids';
default:
throw new LogicException('key not supported');
}
}
$decoded = json_decode($value, null, 512, JSON_THROW_ON_ERROR);
return implode(
'|',
array_unique(
array_filter($decoded, static fn (?int $id) => null !== $id),
SORT_NUMERIC
)
);
};
case 'personsNames':
return $this->labelPersonHelper->getLabelMulti($key, $values, self::MSG_KEY . 'persons name');
case 'thirdPartiesNames':
return $this->labelThirdPartyHelper->getLabelMulti($key, $values, self::MSG_KEY . 'thirds parties');
case 'sentReceived':
return function ($value) {
if ('_header' === $value) {
return self::MSG_KEY . 'sent received';
}
if (null === $value) {
return '';
}
return $this->translator->trans($value);
};
default:
return function ($value) use ($key) {
if ('_header' === $value) {
return self::MSG_KEY . $key;
}
if (null === $value) {
return '';
}
return $this->translator->trans($value);
};
}
return $this->translator->trans($value);
},
};
}
public function getQueryKeys($data)

View File

@@ -15,24 +15,23 @@ use Chill\ActivityBundle\Entity\Activity;
use Chill\ActivityBundle\Entity\ActivityType;
use Chill\ActivityBundle\Repository\ActivityTypeRepositoryInterface;
use Chill\MainBundle\Export\FilterInterface;
use Chill\MainBundle\Form\Type\PickRollingDateType;
use Chill\MainBundle\Service\RollingDate\RollingDateConverterInterface;
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
use Chill\PersonBundle\Export\Declarations;
use Doctrine\ORM\QueryBuilder;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\FormBuilderInterface;
class ActivityTypeFilter implements FilterInterface
final readonly class ActivityTypeFilter implements FilterInterface
{
private ActivityTypeRepositoryInterface $activityTypeRepository;
private TranslatableStringHelperInterface $translatableStringHelper;
private const BASE_EXISTS = 'SELECT 1 FROM '.Activity::class.' act_type_filter_activity WHERE act_type_filter_activity.accompanyingPeriod = acp';
public function __construct(
ActivityTypeRepositoryInterface $activityTypeRepository,
TranslatableStringHelperInterface $translatableStringHelper
private ActivityTypeRepositoryInterface $activityTypeRepository,
private TranslatableStringHelperInterface $translatableStringHelper,
private RollingDateConverterInterface $rollingDateConverter,
) {
$this->activityTypeRepository = $activityTypeRepository;
$this->translatableStringHelper = $translatableStringHelper;
}
public function addRole(): ?string
@@ -42,13 +41,26 @@ class ActivityTypeFilter implements FilterInterface
public function alterQuery(QueryBuilder $qb, $data)
{
$qb->andWhere(
$qb->expr()->exists(
'SELECT 1 FROM ' . Activity::class . ' act_type_filter_activity
WHERE act_type_filter_activity.activityType IN (:act_type_filter_activity_types) AND act_type_filter_activity.accompanyingPeriod = acp'
)
);
$qb->setParameter('act_type_filter_activity_types', $data['accepted_activitytypes']);
$exists = self::BASE_EXISTS;
if (count($data['accepted_activitytypes']) > 0) {
$exists .= ' AND act_type_filter_activity.activityType IN (:act_type_filter_activity_types)';
$qb->setParameter('act_type_filter_activity_types', $data['accepted_activitytypes']);
}
if (null !== $data['date_after']) {
$exists .= ' AND act_type_filter_activity.date >= :act_type_filter_activity_date_after';
$qb->setParameter('act_type_filter_activity_date_after', $this->rollingDateConverter->convert($data['date_after']));
}
if (null !== $data['date_before']) {
$exists .= ' AND act_type_filter_activity.date >= :act_type_filter_activity_date_before';
$qb->setParameter('act_type_filter_activity_date_before', $this->rollingDateConverter->convert($data['date_before']));
}
if (self::BASE_EXISTS !== $exists) {
$qb->andWhere($qb->expr()->exists($exists));
}
}
public function applyOn()
@@ -61,16 +73,33 @@ class ActivityTypeFilter implements FilterInterface
$builder->add('accepted_activitytypes', EntityType::class, [
'class' => ActivityType::class,
'choices' => $this->activityTypeRepository->findAllActive(),
'choice_label' => fn (ActivityType $aty) => ($aty->hasCategory() ? $this->translatableStringHelper->localize($aty->getCategory()->getName()) . ' > ' : '')
'choice_label' => fn (ActivityType $aty) => ($aty->hasCategory() ? $this->translatableStringHelper->localize($aty->getCategory()->getName()).' > ' : '')
.
$this->translatableStringHelper->localize($aty->getName()),
'multiple' => true,
'expanded' => true,
]);
$builder->add('date_after', PickRollingDateType::class, [
'label' => 'export.filter.activity.acp_by_activity_type.activity after',
'help' => 'export.filter.activity.acp_by_activity_type.activity after help',
'required' => false,
]);
$builder->add('date_before', PickRollingDateType::class, [
'label' => 'export.filter.activity.acp_by_activity_type.activity before',
'help' => 'export.filter.activity.acp_by_activity_type.activity before help',
'required' => false,
]);
}
public function getFormDefaultData(): array
{
return [];
return [
'accepted_activitytypes' => [],
'date_after' => null,
'date_before' => null,
];
}
public function describeAction($data, $format = 'string'): array
@@ -81,8 +110,12 @@ class ActivityTypeFilter implements FilterInterface
$types[] = $this->translatableStringHelper->localize($aty->getName());
}
return ['Filtered by activity types: only %activitytypes%', [
'%activitytypes%' => implode(', ', $types),
return ['export.filter.activity.acp_by_activity_type.acp_containing_at_least_one_activitytypes', [
'activitytypes' => implode(', ', $types),
'has_date_after' => null !== $data['date_after'] ? 1 : 0,
'date_after' => $this->rollingDateConverter->convert($data['date_after']),
'has_date_before' => null !== $data['date_before'] ? 1 : 0,
'date_before' => $this->rollingDateConverter->convert($data['date_before']),
]];
}

View File

@@ -18,15 +18,11 @@ use Chill\PersonBundle\Form\Type\PickSocialActionType;
use Chill\PersonBundle\Templating\Entity\SocialActionRender;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface;
use function in_array;
class BySocialActionFilter implements FilterInterface
{
private SocialActionRender $actionRender;
public function __construct(SocialActionRender $actionRender)
public function __construct(private readonly SocialActionRender $actionRender)
{
$this->actionRender = $actionRender;
}
public function addRole(): ?string
@@ -36,7 +32,7 @@ class BySocialActionFilter implements FilterInterface
public function alterQuery(QueryBuilder $qb, $data)
{
if (!in_array('actsocialaction', $qb->getAllAliases(), true)) {
if (!\in_array('actsocialaction', $qb->getAllAliases(), true)) {
$qb->join('activity.socialActions', 'actsocialaction');
}
@@ -60,6 +56,7 @@ class BySocialActionFilter implements FilterInterface
'multiple' => true,
]);
}
public function getFormDefaultData(): array
{
return [];

View File

@@ -18,15 +18,11 @@ use Chill\PersonBundle\Form\Type\PickSocialIssueType;
use Chill\PersonBundle\Templating\Entity\SocialIssueRender;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface;
use function in_array;
class BySocialIssueFilter implements FilterInterface
{
private SocialIssueRender $issueRender;
public function __construct(SocialIssueRender $issueRender)
public function __construct(private readonly SocialIssueRender $issueRender)
{
$this->issueRender = $issueRender;
}
public function addRole(): ?string
@@ -36,7 +32,7 @@ class BySocialIssueFilter implements FilterInterface
public function alterQuery(QueryBuilder $qb, $data)
{
if (!in_array('actsocialissue', $qb->getAllAliases(), true)) {
if (!\in_array('actsocialissue', $qb->getAllAliases(), true)) {
$qb->join('activity.socialIssues', 'actsocialissue');
}
@@ -60,6 +56,7 @@ class BySocialIssueFilter implements FilterInterface
'multiple' => true,
]);
}
public function getFormDefaultData(): array
{
return [];

View File

@@ -17,6 +17,9 @@ use Chill\PersonBundle\Export\Declarations;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface;
/**
* Filter accompanying periods to keep only the one without any activity.
*/
class HasNoActivityFilter implements FilterInterface
{
public function addRole(): ?string
@@ -29,7 +32,7 @@ class HasNoActivityFilter implements FilterInterface
$qb
->andWhere('
NOT EXISTS (
SELECT 1 FROM ' . Activity::class . ' activity
SELECT 1 FROM '.Activity::class.' activity
WHERE activity.accompanyingPeriod = acp
)
');
@@ -42,8 +45,9 @@ class HasNoActivityFilter implements FilterInterface
public function buildForm(FormBuilderInterface $builder)
{
//no form needed
// no form needed
}
public function getFormDefaultData(): array
{
return [];

View File

@@ -0,0 +1,90 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\ActivityBundle\Export\Filter\ACPFilters;
use Chill\ActivityBundle\Entity\Activity;
use Chill\MainBundle\Export\FilterInterface;
use Chill\MainBundle\Form\Type\PickRollingDateType;
use Chill\MainBundle\Service\RollingDate\RollingDate;
use Chill\MainBundle\Service\RollingDate\RollingDateConverterInterface;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface;
final readonly class PeriodHavingActivityBetweenDatesFilter implements FilterInterface
{
public function __construct(
private RollingDateConverterInterface $rollingDateConverter,
) {
}
public function getTitle()
{
return 'export.filter.activity.course_having_activity_between_date.Title';
}
public function buildForm(FormBuilderInterface $builder)
{
$builder
->add('start_date', PickRollingDateType::class, [
'label' => 'export.filter.activity.course_having_activity_between_date.Receiving an activity after',
])
->add('end_date', PickRollingDateType::class, [
'label' => 'export.filter.activity.course_having_activity_between_date.Receiving an activity before',
]);
}
public function getFormDefaultData(): array
{
return [
'start_date' => new RollingDate(RollingDate::T_YEAR_CURRENT_START),
'end_date' => new RollingDate(RollingDate::T_TODAY),
];
}
public function describeAction($data, $format = 'string')
{
return [
'export.filter.activity.course_having_activity_between_date.Only course having an activity between from and to',
[
'from' => $this->rollingDateConverter->convert($data['start_date']),
'to' => $this->rollingDateConverter->convert($data['end_date']),
],
];
}
public function addRole(): ?string
{
return null;
}
public function alterQuery(QueryBuilder $qb, $data)
{
$alias = 'act_period_having_act_betw_date_alias';
$from = 'act_period_having_act_betw_date_start';
$to = 'act_period_having_act_betw_date_end';
$qb->andWhere(
$qb->expr()->exists(
'SELECT 1 FROM '.Activity::class." {$alias} WHERE {$alias}.date >= :{$from} AND {$alias}.date < :{$to} AND {$alias}.accompanyingPeriod = acp"
)
);
$qb
->setParameter($from, $this->rollingDateConverter->convert($data['start_date']))
->setParameter($to, $this->rollingDateConverter->convert($data['end_date']));
}
public function applyOn()
{
return \Chill\PersonBundle\Export\Declarations::ACP_TYPE;
}
}

View File

@@ -1,98 +0,0 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\ActivityBundle\Export\Filter\ACPFilters;
use Chill\ActivityBundle\Export\Declarations;
use Chill\MainBundle\Entity\Scope;
use Chill\MainBundle\Export\FilterInterface;
use Chill\MainBundle\Templating\TranslatableStringHelper;
use Doctrine\ORM\Query\Expr\Andx;
use Doctrine\ORM\QueryBuilder;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\FormBuilderInterface;
use function in_array;
class UserScopeFilter implements FilterInterface
{
private TranslatableStringHelper $translatableStringHelper;
public function __construct(TranslatableStringHelper $translatableStringHelper)
{
$this->translatableStringHelper = $translatableStringHelper;
}
public function addRole(): ?string
{
return null;
}
public function alterQuery(QueryBuilder $qb, $data)
{
if (!in_array('actuser', $qb->getAllAliases(), true)) {
$qb->join('activity.user', 'actuser');
}
$where = $qb->getDQLPart('where');
$clause = $qb->expr()->in('actuser.mainScope', ':userscope');
if ($where instanceof Andx) {
$where->add($clause);
} else {
$where = $qb->expr()->andX($clause);
}
$qb->add('where', $where);
$qb->setParameter('userscope', $data['accepted_userscope']);
}
public function applyOn(): string
{
return Declarations::ACTIVITY_ACP;
}
public function buildForm(FormBuilderInterface $builder)
{
$builder->add('accepted_userscope', EntityType::class, [
'class' => Scope::class,
'choice_label' => fn (Scope $s) => $this->translatableStringHelper->localize(
$s->getName()
),
'multiple' => true,
'expanded' => true,
]);
}
public function getFormDefaultData(): array
{
return [];
}
public function describeAction($data, $format = 'string'): array
{
$scopes = [];
foreach ($data['accepted_userscope'] as $s) {
$scopes[] = $this->translatableStringHelper->localize(
$s->getName()
);
}
return ['Filtered activity by userscope: only %scopes%', [
'%scopes%' => implode(', ', $scopes),
]];
}
public function getTitle(): string
{
return 'Filter activity by userscope';
}
}

View File

@@ -13,30 +13,18 @@ namespace Chill\ActivityBundle\Export\Filter;
use Chill\ActivityBundle\Export\Declarations;
use Chill\MainBundle\Export\FilterInterface;
use Chill\MainBundle\Form\Type\Export\FilterType;
use Chill\MainBundle\Form\Type\PickRollingDateType;
use Chill\MainBundle\Service\RollingDate\RollingDate;
use Chill\MainBundle\Service\RollingDate\RollingDateConverterInterface;
use Doctrine\ORM\Query\Expr;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormError;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
use Symfony\Contracts\Translation\TranslatorInterface;
class ActivityDateFilter implements FilterInterface
{
protected TranslatorInterface $translator;
private RollingDateConverterInterface $rollingDateConverter;
public function __construct(
TranslatorInterface $translator,
RollingDateConverterInterface $rollingDateConverter
) {
$this->translator = $translator;
$this->rollingDateConverter = $rollingDateConverter;
public function __construct(protected TranslatorInterface $translator, private readonly RollingDateConverterInterface $rollingDateConverter)
{
}
public function addRole(): ?string
@@ -84,47 +72,8 @@ class ActivityDateFilter implements FilterInterface
->add('date_to', PickRollingDateType::class, [
'label' => 'Activities before this date',
]);
$builder->addEventListener(FormEvents::POST_SUBMIT, function (FormEvent $event) {
/** @var \Symfony\Component\Form\FormInterface $filterForm */
$filterForm = $event->getForm()->getParent();
$enabled = $filterForm->get(FilterType::ENABLED_FIELD)->getData();
if (true === $enabled) {
// if the filter is enabled, add some validation
$form = $event->getForm();
$date_from = $form->get('date_from')->getData();
$date_to = $form->get('date_to')->getData();
// check that fields are not empty
if (null === $date_from) {
$form->get('date_from')->addError(new FormError(
$this->translator->trans('This field '
. 'should not be empty')
));
}
if (null === $date_to) {
$form->get('date_to')->addError(new FormError(
$this->translator->trans('This field '
. 'should not be empty')
));
}
// check that date_from is before date_to
if (
(null !== $date_from && null !== $date_to)
&& $date_from >= $date_to
) {
$form->get('date_to')->addError(new FormError(
$this->translator->trans('This date should be after '
. 'the date given in "Implied in an activity after '
. 'this date" field')
));
}
}
});
}
public function getFormDefaultData(): array
{
return ['date_from' => new RollingDate(RollingDate::T_YEAR_PREVIOUS_START), 'date_to' => new RollingDate(RollingDate::T_TODAY)];

View File

@@ -0,0 +1,83 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\ActivityBundle\Export\Filter;
use Chill\ActivityBundle\Entity\ActivityPresence;
use Chill\ActivityBundle\Export\Declarations;
use Chill\MainBundle\Export\FilterInterface;
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\QueryBuilder;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
final readonly class ActivityPresenceFilter implements FilterInterface
{
public function __construct(
private TranslatableStringHelperInterface $translatableStringHelper,
private TranslatorInterface $translator
) {
}
public function getTitle()
{
return 'export.filter.activity.by_presence.Filter activity by activity presence';
}
public function buildForm(FormBuilderInterface $builder)
{
$builder->add('presences', EntityType::class, [
'class' => ActivityPresence::class,
'choice_label' => fn (ActivityPresence $presence) => $this->translatableStringHelper->localize($presence->getName())
.($presence->isActive() ? '' : ' ('.$this->translator->trans('inactive').')'),
'multiple' => true,
'expanded' => true,
'label' => 'export.filter.activity.by_presence.presences',
]);
}
public function getFormDefaultData(): array
{
return [];
}
public function describeAction($data, $format = 'string')
{
$presences = array_map(
fn (ActivityPresence $presence) => $this->translatableStringHelper->localize($presence->getName()),
$data['presences'] instanceof Collection ? $data['presences']->toArray() : $data['presences']
);
return [
'export.filter.activity.by_presence.Filtered by activity presence: only %presences%',
['%presences%' => implode(', ', $presences)],
];
}
public function addRole(): ?string
{
return null;
}
public function alterQuery(QueryBuilder $qb, $data)
{
$qb
->andWhere('activity.attendee IN (:activity_presence_filter_presences)')
->setParameter('activity_presence_filter_presences', $data['presences']);
}
public function applyOn()
{
return Declarations::ACTIVITY;
}
}

View File

@@ -22,20 +22,12 @@ use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
use function count;
class ActivityTypeFilter implements ExportElementValidatedInterface, FilterInterface
{
protected ActivityTypeRepositoryInterface $activityTypeRepository;
protected TranslatableStringHelperInterface $translatableStringHelper;
public function __construct(
TranslatableStringHelperInterface $translatableStringHelper,
ActivityTypeRepositoryInterface $activityTypeRepository
protected TranslatableStringHelperInterface $translatableStringHelper,
protected ActivityTypeRepositoryInterface $activityTypeRepository
) {
$this->translatableStringHelper = $translatableStringHelper;
$this->activityTypeRepository = $activityTypeRepository;
}
public function addRole(): ?string
@@ -61,7 +53,7 @@ class ActivityTypeFilter implements ExportElementValidatedInterface, FilterInter
$builder->add('types', EntityType::class, [
'choices' => $this->activityTypeRepository->findAllActive(),
'class' => ActivityType::class,
'choice_label' => fn (ActivityType $aty) => ($aty->hasCategory() ? $this->translatableStringHelper->localize($aty->getCategory()->getName()) . ' > ' : '')
'choice_label' => fn (ActivityType $aty) => ($aty->hasCategory() ? $this->translatableStringHelper->localize($aty->getCategory()->getName()).' > ' : '')
.
$this->translatableStringHelper->localize($aty->getName()),
'group_by' => function (ActivityType $type) {
@@ -78,6 +70,7 @@ class ActivityTypeFilter implements ExportElementValidatedInterface, FilterInter
],
]);
}
public function getFormDefaultData(): array
{
return [];
@@ -88,7 +81,7 @@ class ActivityTypeFilter implements ExportElementValidatedInterface, FilterInter
// collect all the reasons'name used in this filter in one array
$reasonsNames = array_map(
fn (ActivityType $t): string => $this->translatableStringHelper->localize($t->getName()),
$this->activityTypeRepository->findBy(['id' => $data['types']->toArray()])
$this->activityTypeRepository->findBy(['id' => $data['types'] instanceof \Doctrine\Common\Collections\Collection ? $data['types']->toArray() : $data['types']])
);
return ['Filtered by activity type: only %list%', [
@@ -103,7 +96,7 @@ class ActivityTypeFilter implements ExportElementValidatedInterface, FilterInter
public function validateForm($data, ExecutionContextInterface $context)
{
if (null === $data['types'] || count($data['types']) === 0) {
if (null === $data['types'] || 0 === \count($data['types'])) {
$context
->buildViolation('At least one type must be chosen')
->addViolation();

View File

@@ -20,11 +20,8 @@ use Symfony\Component\Form\FormBuilderInterface;
class ActivityUsersFilter implements FilterInterface
{
private UserRender $userRender;
public function __construct(UserRender $userRender)
public function __construct(private readonly UserRender $userRender)
{
$this->userRender = $userRender;
}
public function addRole(): ?string
@@ -37,8 +34,8 @@ class ActivityUsersFilter implements FilterInterface
$orX = $qb->expr()->orX();
foreach ($data['accepted_users'] as $key => $user) {
$orX->add($qb->expr()->isMemberOf(':activity_users_filter_u' . $key, 'activity.users'));
$qb->setParameter('activity_users_filter_u' . $key, $user);
$orX->add($qb->expr()->isMemberOf(':activity_users_filter_u'.$key, 'activity.users'));
$qb->setParameter('activity_users_filter_u'.$key, $user);
}
$qb->andWhere($orX);
@@ -56,6 +53,7 @@ class ActivityUsersFilter implements FilterInterface
'label' => 'Users',
]);
}
public function getFormDefaultData(): array
{
return [];

View File

@@ -9,7 +9,7 @@ declare(strict_types=1);
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\ActivityBundle\Export\Filter\ACPFilters;
namespace Chill\ActivityBundle\Export\Filter;
use Chill\ActivityBundle\Export\Declarations;
use Chill\MainBundle\Export\FilterInterface;
@@ -20,11 +20,8 @@ use Symfony\Component\Form\FormBuilderInterface;
class ByCreatorFilter implements FilterInterface
{
private UserRender $userRender;
public function __construct(UserRender $userRender)
public function __construct(private readonly UserRender $userRender)
{
$this->userRender = $userRender;
}
public function addRole(): ?string
@@ -43,7 +40,7 @@ class ByCreatorFilter implements FilterInterface
public function applyOn(): string
{
return Declarations::ACTIVITY_ACP;
return Declarations::ACTIVITY;
}
public function buildForm(FormBuilderInterface $builder)
@@ -52,6 +49,7 @@ class ByCreatorFilter implements FilterInterface
'multiple' => true,
]);
}
public function getFormDefaultData(): array
{
return [];

View File

@@ -0,0 +1,117 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\ActivityBundle\Export\Filter;
use Chill\ActivityBundle\Export\Declarations;
use Chill\MainBundle\Entity\User\UserJobHistory;
use Chill\MainBundle\Entity\UserJob;
use Chill\MainBundle\Export\FilterInterface;
use Chill\MainBundle\Repository\UserJobRepositoryInterface;
use Chill\MainBundle\Templating\TranslatableStringHelper;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Query\Expr\Join;
use Doctrine\ORM\QueryBuilder;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
final readonly class CreatorJobFilter implements FilterInterface
{
private const PREFIX = 'acp_act_filter_creator_job';
public function __construct(
private TranslatableStringHelper $translatableStringHelper,
private TranslatorInterface $translator,
private UserJobRepositoryInterface $userJobRepository,
) {
}
public function addRole(): ?string
{
return null;
}
public function alterQuery(QueryBuilder $qb, $data)
{
$p = self::PREFIX;
$qb
->leftJoin('activity.createdBy', "{$p}_user")
->leftJoin(
UserJobHistory::class,
"{$p}_history",
Join::WITH,
$qb->expr()->eq("{$p}_history.user", "{$p}_user")
)
// job_at based on activity.date
->andWhere(
$qb->expr()->andX(
$qb->expr()->lte("{$p}_history.startDate", 'activity.date'),
$qb->expr()->orX(
$qb->expr()->isNull("{$p}_history.endDate"),
$qb->expr()->gt("{$p}_history.endDate", 'activity.date')
)
)
)
->andWhere(
$qb->expr()->in("{$p}_history.job", ":{$p}_jobs")
)
->setParameter(
"{$p}_jobs",
$data['jobs'],
);
}
public function applyOn(): string
{
return Declarations::ACTIVITY;
}
public function buildForm(FormBuilderInterface $builder)
{
$builder
->add('jobs', EntityType::class, [
'choices' => $this->userJobRepository->findAllActive(),
'class' => UserJob::class,
'choice_label' => fn (UserJob $s) => $this->translatableStringHelper->localize(
$s->getLabel()
).($s->isActive() ? '' : '('.$this->translator->trans('inactive').')'),
'label' => 'export.filter.activity.by_creator_job.job_form_label',
'multiple' => true,
'expanded' => true,
]);
}
public function describeAction($data, $format = 'string'): array
{
$jobs = array_map(
fn (UserJob $job) => $this->translatableStringHelper->localize($job->getLabel()),
$data['jobs'] instanceof Collection ? $data['jobs']->toArray() : $data['jobs']
);
return ['export.filter.activity.by_creator_job.Filtered activity by user job: only %jobs%', [
'%jobs%' => implode(', ', $jobs),
]];
}
public function getFormDefaultData(): array
{
return [
'jobs' => [],
];
}
public function getTitle(): string
{
return 'export.filter.activity.by_creator_job.Filter activity by user job';
}
}

View File

@@ -0,0 +1,116 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\ActivityBundle\Export\Filter;
use Chill\ActivityBundle\Export\Declarations;
use Chill\MainBundle\Entity\Scope;
use Chill\MainBundle\Entity\User\UserScopeHistory;
use Chill\MainBundle\Export\FilterInterface;
use Chill\MainBundle\Repository\ScopeRepositoryInterface;
use Chill\MainBundle\Templating\TranslatableStringHelper;
use Doctrine\ORM\Query\Expr\Join;
use Doctrine\ORM\QueryBuilder;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\FormBuilderInterface;
class CreatorScopeFilter implements FilterInterface
{
private const PREFIX = 'acp_act_filter_creator_scope';
public function __construct(
private readonly TranslatableStringHelper $translatableStringHelper,
private readonly ScopeRepositoryInterface $scopeRepository,
) {
}
public function addRole(): ?string
{
return null;
}
public function alterQuery(QueryBuilder $qb, $data)
{
$p = self::PREFIX;
$qb
->leftJoin('activity.createdBy', "{$p}_user")
->leftJoin(
UserScopeHistory::class,
"{$p}_history",
Join::WITH,
$qb->expr()->eq("{$p}_history.user", "{$p}_user")
)
// scope_at based on activity.date
->andWhere(
$qb->expr()->andX(
$qb->expr()->lte("{$p}_history.startDate", 'activity.date'),
$qb->expr()->orX(
$qb->expr()->isNull("{$p}_history.endDate"),
$qb->expr()->gt("{$p}_history.endDate", 'activity.date')
)
)
)
->andWhere(
$qb->expr()->in("{$p}_history.scope", ":{$p}_scopes")
)
->setParameter(
"{$p}_scopes",
$data['scopes'],
);
}
public function applyOn(): string
{
return Declarations::ACTIVITY;
}
public function buildForm(FormBuilderInterface $builder)
{
$builder
->add('scopes', EntityType::class, [
'class' => Scope::class,
'choices' => $this->scopeRepository->findAllActive(),
'choice_label' => fn (Scope $s) => $this->translatableStringHelper->localize(
$s->getName()
),
'multiple' => true,
'expanded' => true,
]);
}
public function describeAction($data, $format = 'string'): array
{
$scopes = [];
foreach ($data['scopes'] as $s) {
$scopes[] = $this->translatableStringHelper->localize(
$s->getName()
);
}
return ['export.filter.activity.by_creator_scope.Filtered activity by user scope: only %scopes%', [
'%scopes%' => implode(', ', $scopes),
]];
}
public function getFormDefaultData(): array
{
return [
'scopes' => [],
];
}
public function getTitle(): string
{
return 'export.filter.activity.by_creator_scope.Filter activity by user scope';
}
}

View File

@@ -9,7 +9,7 @@ declare(strict_types=1);
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\ActivityBundle\Export\Filter\ACPFilters;
namespace Chill\ActivityBundle\Export\Filter;
use Chill\ActivityBundle\Export\Declarations;
use Chill\MainBundle\Export\FilterInterface;
@@ -22,17 +22,14 @@ use Symfony\Contracts\Translation\TranslatorInterface;
class EmergencyFilter implements FilterInterface
{
private const CHOICES = [
'activity is emergency' => true,
'activity is not emergency' => false,
'activity is emergency' => 'true',
'activity is not emergency' => 'false',
];
private const DEFAULT_CHOICE = false;
private const DEFAULT_CHOICE = 'false';
private TranslatorInterface $translator;
public function __construct(TranslatorInterface $translator)
public function __construct(private readonly TranslatorInterface $translator)
{
$this->translator = $translator;
}
public function addRole(): ?string
@@ -58,7 +55,7 @@ class EmergencyFilter implements FilterInterface
public function applyOn(): string
{
return Declarations::ACTIVITY_ACP;
return Declarations::ACTIVITY;
}
public function buildForm(FormBuilderInterface $builder)
@@ -70,6 +67,7 @@ class EmergencyFilter implements FilterInterface
'empty_data' => self::DEFAULT_CHOICE,
]);
}
public function getFormDefaultData(): array
{
return ['accepted_emergency' => self::DEFAULT_CHOICE];

View File

@@ -9,12 +9,11 @@ declare(strict_types=1);
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\ActivityBundle\Export\Filter\ACPFilters;
namespace Chill\ActivityBundle\Export\Filter;
use Chill\ActivityBundle\Export\Declarations;
use Chill\MainBundle\Export\FilterInterface;
use Chill\MainBundle\Form\Type\PickUserLocationType;
use Chill\MainBundle\Templating\TranslatableStringHelper;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface;
@@ -36,7 +35,7 @@ class LocationFilter implements FilterInterface
public function applyOn(): string
{
return Declarations::ACTIVITY_ACP;
return Declarations::ACTIVITY;
}
public function buildForm(FormBuilderInterface $builder)
@@ -46,6 +45,7 @@ class LocationFilter implements FilterInterface
'label' => 'pick location',
]);
}
public function getFormDefaultData(): array
{
return [];

View File

@@ -9,7 +9,7 @@ declare(strict_types=1);
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\ActivityBundle\Export\Filter\ACPFilters;
namespace Chill\ActivityBundle\Export\Filter;
use Chill\ActivityBundle\Export\Declarations;
use Chill\MainBundle\Export\FilterInterface;
@@ -18,15 +18,11 @@ use Chill\MainBundle\Templating\TranslatableStringHelper;
use Doctrine\ORM\Query\Expr\Andx;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface;
use function in_array;
class LocationTypeFilter implements FilterInterface
{
private TranslatableStringHelper $translatableStringHelper;
public function __construct(TranslatableStringHelper $translatableStringHelper)
public function __construct(private readonly TranslatableStringHelper $translatableStringHelper)
{
$this->translatableStringHelper = $translatableStringHelper;
}
public function addRole(): ?string
@@ -36,7 +32,7 @@ class LocationTypeFilter implements FilterInterface
public function alterQuery(QueryBuilder $qb, $data)
{
if (!in_array('actloc', $qb->getAllAliases(), true)) {
if (!\in_array('actloc', $qb->getAllAliases(), true)) {
$qb->join('activity.location', 'actloc');
}
@@ -55,16 +51,17 @@ class LocationTypeFilter implements FilterInterface
public function applyOn(): string
{
return Declarations::ACTIVITY_ACP;
return Declarations::ACTIVITY;
}
public function buildForm(FormBuilderInterface $builder)
{
$builder->add('accepted_locationtype', PickLocationTypeType::class, [
'multiple' => true,
//'label' => false,
// 'label' => false,
]);
}
public function getFormDefaultData(): array
{
return [];

View File

@@ -17,28 +17,17 @@ use Chill\ActivityBundle\Repository\ActivityReasonRepository;
use Chill\MainBundle\Export\ExportElementValidatedInterface;
use Chill\MainBundle\Export\FilterInterface;
use Chill\MainBundle\Templating\TranslatableStringHelper;
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Query\Expr;
use Doctrine\ORM\QueryBuilder;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
use function count;
use function in_array;
class ActivityReasonFilter implements ExportElementValidatedInterface, FilterInterface
{
protected ActivityReasonRepository $activityReasonRepository;
protected TranslatableStringHelperInterface $translatableStringHelper;
public function __construct(
TranslatableStringHelper $helper,
ActivityReasonRepository $activityReasonRepository
) {
$this->translatableStringHelper = $helper;
$this->activityReasonRepository = $activityReasonRepository;
public function __construct(protected TranslatableStringHelper $translatableStringHelper, protected ActivityReasonRepository $activityReasonRepository)
{
}
public function addRole(): ?string
@@ -52,7 +41,7 @@ class ActivityReasonFilter implements ExportElementValidatedInterface, FilterInt
$join = $qb->getDQLPart('join');
$clause = $qb->expr()->in('actreasons', ':selected_activity_reasons');
if (!in_array('actreasons', $qb->getAllAliases(), true)) {
if (!\in_array('actreasons', $qb->getAllAliases(), true)) {
$qb->join('activity.reasons', 'actreasons');
}
@@ -82,6 +71,7 @@ class ActivityReasonFilter implements ExportElementValidatedInterface, FilterInt
'expanded' => false,
]);
}
public function getFormDefaultData(): array
{
return [];
@@ -91,8 +81,8 @@ class ActivityReasonFilter implements ExportElementValidatedInterface, FilterInt
{
// collect all the reasons'name used in this filter in one array
$reasonsNames = array_map(
fn (ActivityReason $r): string => '"' . $this->translatableStringHelper->localize($r->getName()) . '"',
$this->activityReasonRepository->findBy(['id' => $data['reasons']->toArray()])
fn (ActivityReason $r): string => '"'.$this->translatableStringHelper->localize($r->getName()).'"',
$this->activityReasonRepository->findBy(['id' => $data['reasons'] instanceof Collection ? $data['reasons']->toArray() : $data['reasons']])
);
return [
@@ -110,7 +100,7 @@ class ActivityReasonFilter implements ExportElementValidatedInterface, FilterInt
public function validateForm($data, ExecutionContextInterface $context)
{
if (null === $data['reasons'] || count($data['reasons']) === 0) {
if (null === $data['reasons'] || 0 === \count($data['reasons'])) {
$context
->buildViolation('At least one reason must be chosen')
->addViolation();

View File

@@ -16,41 +16,23 @@ use Chill\ActivityBundle\Entity\ActivityReason;
use Chill\ActivityBundle\Repository\ActivityReasonRepository;
use Chill\MainBundle\Export\ExportElementValidatedInterface;
use Chill\MainBundle\Export\FilterInterface;
use Chill\MainBundle\Form\Type\Export\FilterType;
use Chill\MainBundle\Form\Type\PickRollingDateType;
use Chill\MainBundle\Service\RollingDate\RollingDate;
use Chill\MainBundle\Service\RollingDate\RollingDateConverterInterface;
use Chill\MainBundle\Templating\TranslatableStringHelper;
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
use Chill\PersonBundle\Export\Declarations;
use DateTime;
use Doctrine\ORM\Query\Expr;
use Doctrine\ORM\QueryBuilder;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\Extension\Core\Type\DateType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormError;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
use function count;
class PersonHavingActivityBetweenDateFilter implements ExportElementValidatedInterface, FilterInterface
final readonly class PersonHavingActivityBetweenDateFilter implements ExportElementValidatedInterface, FilterInterface
{
protected ActivityReasonRepository $activityReasonRepository;
protected TranslatableStringHelperInterface $translatableStringHelper;
protected TranslatorInterface $translator;
public function __construct(
TranslatableStringHelper $translatableStringHelper,
ActivityReasonRepository $activityReasonRepository,
TranslatorInterface $translator
private TranslatableStringHelper $translatableStringHelper,
private ActivityReasonRepository $activityReasonRepository,
private RollingDateConverterInterface $rollingDateConverter,
) {
$this->translatableStringHelper = $translatableStringHelper;
$this->activityReasonRepository = $activityReasonRepository;
$this->translator = $translator;
}
public function addRole(): ?string
@@ -62,134 +44,97 @@ class PersonHavingActivityBetweenDateFilter implements ExportElementValidatedInt
{
// create a subquery for activity
$sqb = $qb->getEntityManager()->createQueryBuilder();
$sqb->select('person_person_having_activity.id')
$sqb->select('1')
->from(Activity::class, 'activity_person_having_activity')
->join('activity_person_having_activity.person', 'person_person_having_activity');
->leftJoin('activity_person_having_activity.person', 'person_person_having_activity');
// add clause between date
$sqb->where('activity_person_having_activity.date BETWEEN '
. ':person_having_activity_between_date_from'
. ' AND '
. ':person_having_activity_between_date_to');
.':person_having_activity_between_date_from'
.' AND '
.':person_having_activity_between_date_to'
.' AND '
.'(person_person_having_activity.id = person.id OR person MEMBER OF activity_person_having_activity.persons)');
// add clause activity reason
$sqb->join('activity_person_having_activity.reasons', 'reasons_person_having_activity');
if (isset($data['reasons']) && [] !== $data['reasons']) {
// add clause activity reason
$sqb->join('activity_person_having_activity.reasons', 'reasons_person_having_activity');
$sqb->andWhere(
$sqb->expr()->in(
'reasons_person_having_activity',
':person_having_activity_reasons'
)
);
$sqb->andWhere(
$sqb->expr()->in(
'reasons_person_having_activity',
':person_having_activity_reasons'
)
);
$where = $qb->getDQLPart('where');
$clause = $qb->expr()->in('person.id', $sqb->getDQL());
if ($where instanceof Expr\Andx) {
$where->add($clause);
} else {
$where = $qb->expr()->andX($clause);
$qb->setParameter('person_having_activity_reasons', $data['reasons']);
}
$qb->add('where', $where);
$qb->andWhere(
$qb->expr()->exists($sqb->getDQL())
);
$qb->setParameter(
'person_having_activity_between_date_from',
$data['date_from']
$this->rollingDateConverter->convert($data['date_from_rolling'])
);
$qb->setParameter(
'person_having_activity_between_date_to',
$data['date_to']
$this->rollingDateConverter->convert($data['date_to_rolling'])
);
$qb->setParameter('person_having_activity_reasons', $data['reasons']);
}
public function applyOn(): string
{
return Declarations::PERSON_IMPLIED_IN;
return Declarations::PERSON_TYPE;
}
public function buildForm(FormBuilderInterface $builder)
{
$builder->add('date_from', DateType::class, [
'label' => 'Implied in an activity after this date',
'attr' => ['class' => 'datepicker'],
'widget' => 'single_text',
'format' => 'dd-MM-yyyy',
$builder->add('date_from_rolling', PickRollingDateType::class, [
'label' => 'export.filter.activity.person_between_dates.Implied in an activity after this date',
]);
$builder->add('date_to', DateType::class, [
'label' => 'Implied in an activity before this date',
'attr' => ['class' => 'datepicker'],
'widget' => 'single_text',
'format' => 'dd-MM-yyyy',
$builder->add('date_to_rolling', PickRollingDateType::class, [
'label' => 'export.filter.activity.person_between_dates.Implied in an activity before this date',
]);
$builder->add('reasons', EntityType::class, [
'class' => ActivityReason::class,
'choice_label' => fn (ActivityReason $reason): ?string => $this->translatableStringHelper->localize($reason->getName()),
'group_by' => fn (ActivityReason $reason): ?string => $this->translatableStringHelper->localize($reason->getCategory()->getName()),
'multiple' => true,
'expanded' => false,
'label' => 'Activity reasons for those activities',
]);
$builder->addEventListener(FormEvents::POST_SUBMIT, function (FormEvent $event) {
/** @var FormInterface $filterForm */
$filterForm = $event->getForm()->getParent();
$enabled = $filterForm->get(FilterType::ENABLED_FIELD)->getData();
if (true === $enabled) {
// if the filter is enabled, add some validation
$form = $event->getForm();
$date_from = $form->get('date_from')->getData();
$date_to = $form->get('date_to')->getData();
// check that fields are not empty
if (null === $date_from) {
$form->get('date_from')->addError(new FormError(
$this->translator->trans('This field '
. 'should not be empty')
));
}
if (null === $date_to) {
$form->get('date_to')->addError(new FormError(
$this->translator->trans('This field '
. 'should not be empty')
));
}
// check that date_from is before date_to
if (
(null !== $date_from && null !== $date_to)
&& $date_from >= $date_to
) {
$form->get('date_to')->addError(new FormError(
$this->translator->trans('This date '
. 'should be after the date given in "Implied in an '
. 'activity after this date" field')
));
}
}
});
if ([] !== $reasons = $this->activityReasonRepository->findAll()) {
$builder->add('reasons', EntityType::class, [
'class' => ActivityReason::class,
'choices' => $reasons,
'choice_label' => fn (ActivityReason $reason): ?string => $this->translatableStringHelper->localize($reason->getName()),
'group_by' => fn (ActivityReason $reason): ?string => $this->translatableStringHelper->localize($reason->getCategory()->getName()),
'multiple' => true,
'expanded' => false,
'label' => 'export.filter.activity.person_between_dates.Activity reasons for those activities',
'help' => 'export.filter.activity.person_between_dates.if no reasons',
]);
}
}
public function getFormDefaultData(): array
{
return ['date_from' => new DateTime(), 'date_to' => new DateTime(), 'reasons' => $this->activityReasonRepository->findAll()];
return [
'date_from_rolling' => new RollingDate(RollingDate::T_YEAR_PREVIOUS_START),
'date_to_rolling' => new RollingDate(RollingDate::T_TODAY),
'reasons' => [],
];
}
public function describeAction($data, $format = 'string')
{
return [
'Filtered by person having an activity between %date_from% and '
. '%date_to% with reasons %reasons_name%',
[] === $data['reasons'] ?
'export.filter.person_between_dates.describe_action_with_no_subject'
: 'export.filter.person_between_dates.describe_action_with_subject',
[
'%date_from%' => $data['date_from']->format('d-m-Y'),
'%date_to%' => $data['date_to']->format('d-m-Y'),
'%reasons_name%' => implode(
'date_from' => $this->rollingDateConverter->convert($data['date_from_rolling']),
'date_to' => $this->rollingDateConverter->convert($data['date_to_rolling']),
'reasons' => implode(
', ',
array_map(
fn (ActivityReason $r): string => '"' . $this->translatableStringHelper->localize($r->getName()) . '"',
fn (ActivityReason $r): string => '"'.$this->translatableStringHelper->localize($r->getName()).'"',
$data['reasons']
)
),
@@ -199,13 +144,15 @@ class PersonHavingActivityBetweenDateFilter implements ExportElementValidatedInt
public function getTitle()
{
return 'Filter by person having an activity in a period';
return 'export.filter.activity.person_between_dates.title';
}
public function validateForm($data, ExecutionContextInterface $context)
{
if (null === $data['reasons'] || count($data['reasons']) === 0) {
$context->buildViolation('At least one reason must be chosen')
if ($this->rollingDateConverter->convert($data['date_from_rolling'])
>= $this->rollingDateConverter->convert($data['date_to_rolling'])) {
$context->buildViolation('export.filter.activity.person_between_dates.date mismatch')
->setTranslationDomain('messages')
->addViolation();
}
}

View File

@@ -0,0 +1,89 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\ActivityBundle\Export\Filter;
use Chill\ActivityBundle\Export\Declarations;
use Chill\ActivityBundle\Tests\Export\Filter\PersonsFilterTest;
use Chill\MainBundle\Export\FilterInterface;
use Chill\PersonBundle\Form\Type\PickPersonDynamicType;
use Chill\PersonBundle\Templating\Entity\PersonRenderInterface;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface;
/**
* @see PersonsFilterTest
*/
final readonly class PersonsFilter implements FilterInterface
{
private const PREFIX = 'act_persons_filter';
public function __construct(private PersonRenderInterface $personRender)
{
}
public function addRole(): ?string
{
return null;
}
public function alterQuery(QueryBuilder $qb, $data)
{
$p = self::PREFIX;
$orX = $qb->expr()->orX();
foreach (array_values($data['accepted_persons']) as $key => $person) {
$orX->add($qb->expr()->isMemberOf(":{$p}_p_{$key}", 'activity.persons'));
$qb->setParameter(":{$p}_p_{$key}", $person);
}
$qb->andWhere($orX);
}
public function applyOn()
{
return Declarations::ACTIVITY;
}
public function buildForm(FormBuilderInterface $builder)
{
$builder->add('accepted_persons', PickPersonDynamicType::class, [
'multiple' => true,
'label' => 'export.filter.activity.by_persons.persons taking part on the activity',
]);
}
public function getFormDefaultData(): array
{
return [
'accepted_persons' => [],
];
}
public function describeAction($data, $format = 'string')
{
$users = [];
foreach ($data['accepted_persons'] as $u) {
$users[] = $this->personRender->renderString($u, []);
}
return ['export.filter.activity.by_persons.Filtered activity by persons: only %persons%', [
'%persons%' => implode(', ', $users),
]];
}
public function getTitle(): string
{
return 'export.filter.activity.by_persons.Filter activity by persons';
}
}

View File

@@ -9,7 +9,7 @@ declare(strict_types=1);
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\ActivityBundle\Export\Filter\ACPFilters;
namespace Chill\ActivityBundle\Export\Filter;
use Chill\ActivityBundle\Entity\Activity;
use Chill\ActivityBundle\Export\Declarations;
@@ -23,17 +23,14 @@ use Symfony\Contracts\Translation\TranslatorInterface;
class SentReceivedFilter implements FilterInterface
{
private const CHOICES = [
'is sent' => Activity::SENTRECEIVED_SENT,
'is received' => Activity::SENTRECEIVED_RECEIVED,
'export.filter.activity.by_sent_received.is sent' => Activity::SENTRECEIVED_SENT,
'export.filter.activity.by_sent_received.is received' => Activity::SENTRECEIVED_RECEIVED,
];
private const DEFAULT_CHOICE = Activity::SENTRECEIVED_SENT;
private TranslatorInterface $translator;
public function __construct(TranslatorInterface $translator)
public function __construct(private readonly TranslatorInterface $translator)
{
$this->translator = $translator;
}
public function addRole(): ?string
@@ -59,7 +56,7 @@ class SentReceivedFilter implements FilterInterface
public function applyOn(): string
{
return Declarations::ACTIVITY_ACP;
return Declarations::ACTIVITY;
}
public function buildForm(FormBuilderInterface $builder)
@@ -69,8 +66,10 @@ class SentReceivedFilter implements FilterInterface
'multiple' => false,
'expanded' => true,
'empty_data' => self::DEFAULT_CHOICE,
'label' => 'export.filter.activity.by_sent_received.Sent or received',
]);
}
public function getFormDefaultData(): array
{
return ['accepted_sentreceived' => self::DEFAULT_CHOICE];

View File

@@ -9,7 +9,7 @@ declare(strict_types=1);
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\ActivityBundle\Export\Filter\ACPFilters;
namespace Chill\ActivityBundle\Export\Filter;
use Chill\ActivityBundle\Export\Declarations;
use Chill\MainBundle\Export\FilterInterface;
@@ -21,11 +21,8 @@ use Symfony\Component\Form\FormBuilderInterface;
class UserFilter implements FilterInterface
{
private UserRender $userRender;
public function __construct(UserRender $userRender)
public function __construct(private readonly UserRender $userRender)
{
$this->userRender = $userRender;
}
public function addRole(): ?string
@@ -51,7 +48,7 @@ class UserFilter implements FilterInterface
public function applyOn(): string
{
return Declarations::ACTIVITY_ACP;
return Declarations::ACTIVITY;
}
public function buildForm(FormBuilderInterface $builder)
@@ -61,6 +58,7 @@ class UserFilter implements FilterInterface
'label' => 'Creators',
]);
}
public function getFormDefaultData(): array
{
return [];

View File

@@ -13,20 +13,24 @@ namespace Chill\ActivityBundle\Export\Filter;
use Chill\ActivityBundle\Entity\Activity;
use Chill\ActivityBundle\Export\Declarations;
use Chill\MainBundle\Entity\User\UserJobHistory;
use Chill\MainBundle\Entity\UserJob;
use Chill\MainBundle\Export\FilterInterface;
use Chill\MainBundle\Repository\UserJobRepositoryInterface;
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\QueryBuilder;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\FormBuilderInterface;
class UsersJobFilter implements FilterInterface
{
private TranslatableStringHelperInterface $translatableStringHelper;
private const PREFIX = 'act_filter_user_job';
public function __construct(TranslatableStringHelperInterface $translatableStringHelper)
{
$this->translatableStringHelper = $translatableStringHelper;
public function __construct(
private readonly TranslatableStringHelperInterface $translatableStringHelper,
private readonly UserJobRepositoryInterface $userJobRepository
) {
}
public function addRole(): ?string
@@ -36,14 +40,25 @@ class UsersJobFilter implements FilterInterface
public function alterQuery(QueryBuilder $qb, $data)
{
$p = self::PREFIX;
$qb
->andWhere(
$qb->expr()->exists(
'SELECT 1 FROM ' . Activity::class . ' activity_users_job_filter_act
JOIN activity_users_job_filter_act.users users WHERE users.userJob IN (:activity_users_job_filter_jobs) AND activity_users_job_filter_act = activity '
'SELECT 1 FROM '.Activity::class." {$p}_act "
."JOIN {$p}_act.users {$p}_user "
.'JOIN '.UserJobHistory::class." {$p}_history WITH {$p}_history.user = {$p}_user "
."WHERE {$p}_act = activity "
// job_at based on activity.date
."AND {$p}_history.startDate <= activity.date "
."AND ({$p}_history.endDate IS NULL OR {$p}_history.endDate > activity.date) "
."AND {$p}_history.job IN ( :{$p}_jobs )"
)
)
->setParameter('activity_users_job_filter_jobs', $data['jobs']);
->setParameter(
"{$p}_jobs",
$data['jobs']
);
}
public function applyOn()
@@ -53,33 +68,38 @@ class UsersJobFilter implements FilterInterface
public function buildForm(FormBuilderInterface $builder)
{
$builder->add('jobs', EntityType::class, [
'class' => UserJob::class,
'choice_label' => fn (UserJob $j) => $this->translatableStringHelper->localize($j->getLabel()),
'multiple' => true,
'expanded' => true,
]);
}
public function getFormDefaultData(): array
{
return [];
$builder
->add('jobs', EntityType::class, [
'class' => UserJob::class,
'choices' => $this->userJobRepository->findAllActive(),
'choice_label' => fn (UserJob $j) => $this->translatableStringHelper->localize($j->getLabel()),
'multiple' => true,
'expanded' => true,
]);
}
public function describeAction($data, $format = 'string')
{
return ['export.filter.activity.by_usersjob.Filtered activity by users job: only %jobs%', [
return ['export.filter.activity.by_users_job.Filtered activity by users job: only %jobs%', [
'%jobs%' => implode(
', ',
array_map(
fn (UserJob $job) => $this->translatableStringHelper->localize($job->getLabel()),
$data['jobs']->toArray()
$data['jobs'] instanceof Collection ? $data['jobs']->toArray() : $data['jobs']
)
),
]];
}
public function getFormDefaultData(): array
{
return [
'jobs' => [],
];
}
public function getTitle()
{
return 'export.filter.activity.by_usersjob.Filter by users job';
return 'export.filter.activity.by_users_job.Filter by users job';
}
}

View File

@@ -14,25 +14,23 @@ namespace Chill\ActivityBundle\Export\Filter;
use Chill\ActivityBundle\Entity\Activity;
use Chill\ActivityBundle\Export\Declarations;
use Chill\MainBundle\Entity\Scope;
use Chill\MainBundle\Entity\User\UserScopeHistory;
use Chill\MainBundle\Export\FilterInterface;
use Chill\MainBundle\Repository\ScopeRepositoryInterface;
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\QueryBuilder;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\FormBuilderInterface;
class UsersScopeFilter implements FilterInterface
{
private ScopeRepositoryInterface $scopeRepository;
private TranslatableStringHelperInterface $translatableStringHelper;
private const PREFIX = 'act_filter_user_scope';
public function __construct(
ScopeRepositoryInterface $scopeRepository,
TranslatableStringHelperInterface $translatableStringHelper
private readonly ScopeRepositoryInterface $scopeRepository,
private readonly TranslatableStringHelperInterface $translatableStringHelper
) {
$this->scopeRepository = $scopeRepository;
$this->translatableStringHelper = $translatableStringHelper;
}
public function addRole(): ?string
@@ -42,51 +40,66 @@ class UsersScopeFilter implements FilterInterface
public function alterQuery(QueryBuilder $qb, $data)
{
$p = self::PREFIX;
$qb
->andWhere(
$qb->expr()->exists(
'SELECT 1 FROM ' . Activity::class . ' activity_users_scope_filter_act
JOIN activity_users_scope_filter_act.users users WHERE users.mainScope IN (:activity_users_scope_filter_scopes) AND activity_users_scope_filter_act = activity '
'SELECT 1 FROM '.Activity::class." {$p}_act "
."JOIN {$p}_act.users {$p}_user "
.'JOIN '.UserScopeHistory::class." {$p}_history WITH {$p}_history.user = {$p}_user "
."WHERE {$p}_act = activity "
// scope_at based on activity.date
."AND {$p}_history.startDate <= activity.date "
."AND ({$p}_history.endDate IS NULL OR {$p}_history.endDate > activity.date) "
."AND {$p}_history.scope IN ( :{$p}_scopes )"
)
)
->setParameter('activity_users_scope_filter_scopes', $data['scopes']);
->setParameter(
"{$p}_scopes",
$data['scopes']
);
}
public function applyOn()
public function applyOn(): string
{
return Declarations::ACTIVITY;
}
public function buildForm(FormBuilderInterface $builder)
{
$builder->add('scopes', EntityType::class, [
'class' => Scope::class,
'choices' => $this->scopeRepository->findAllActive(),
'choice_label' => fn (Scope $s) => $this->translatableStringHelper->localize($s->getName()),
'multiple' => true,
'expanded' => true,
]);
}
public function getFormDefaultData(): array
{
return [];
$builder
->add('scopes', EntityType::class, [
'class' => Scope::class,
'choices' => $this->scopeRepository->findAllActive(),
'choice_label' => fn (Scope $s) => $this->translatableStringHelper->localize($s->getName()),
'multiple' => true,
'expanded' => true,
]);
}
public function describeAction($data, $format = 'string')
public function describeAction($data, $format = 'string'): array
{
return ['export.filter.activity.by_usersscope.Filtered activity by users scope: only %scopes%', [
return ['export.filter.activity.by_users_scope.Filtered activity by users scope: only %scopes%', [
'%scopes%' => implode(
', ',
array_map(
fn (Scope $s) => $this->translatableStringHelper->localize($s->getName()),
$data['scopes']->toArray()
$data['scopes'] instanceof Collection ? $data['scopes']->toArray() : $data['scopes']
)
),
]];
}
public function getTitle()
public function getFormDefaultData(): array
{
return 'export.filter.activity.by_usersscope.Filter by users scope';
return [
'scopes' => [],
];
}
public function getTitle(): string
{
return 'export.filter.activity.by_users_scope.Filter by users scope';
}
}

View File

@@ -15,11 +15,10 @@ use Chill\ActivityBundle\Entity\Activity;
use Chill\ActivityBundle\Entity\ActivityPresence;
use Chill\ActivityBundle\Form\Type\PickActivityReasonType;
use Chill\ActivityBundle\Security\Authorization\ActivityVoter;
use Chill\DocStoreBundle\Form\StoredObjectType;
use Chill\DocStoreBundle\Form\CollectionStoredObjectType;
use Chill\MainBundle\Entity\Center;
use Chill\MainBundle\Entity\Location;
use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Form\Type\ChillCollectionType;
use Chill\MainBundle\Form\Type\ChillDateType;
use Chill\MainBundle\Form\Type\CommentType;
use Chill\MainBundle\Form\Type\PickUserDynamicType;
@@ -34,12 +33,8 @@ use Chill\PersonBundle\Entity\SocialWork\SocialIssue;
use Chill\PersonBundle\Templating\Entity\SocialActionRender;
use Chill\PersonBundle\Templating\Entity\SocialIssueRender;
use Chill\ThirdPartyBundle\Entity\ThirdParty;
use DateInterval;
use DateTime;
use DateTimeZone;
use Doctrine\ORM\EntityRepository;
use Doctrine\Persistence\ObjectManager;
use RuntimeException;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\CallbackTransformer;
@@ -53,45 +48,24 @@ use Symfony\Component\Form\FormEvents;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\Role\Role;
use function in_array;
class ActivityType extends AbstractType
{
protected AuthorizationHelper $authorizationHelper;
protected ObjectManager $om;
protected SocialActionRender $socialActionRender;
protected SocialIssueRender $socialIssueRender;
protected array $timeChoices;
protected TranslatableStringHelper $translatableStringHelper;
protected User $user;
public function __construct(
TokenStorageInterface $tokenStorage,
AuthorizationHelper $authorizationHelper,
ObjectManager $om,
TranslatableStringHelper $translatableStringHelper,
array $timeChoices,
SocialIssueRender $socialIssueRender,
SocialActionRender $socialActionRender
protected AuthorizationHelper $authorizationHelper,
protected ObjectManager $om,
protected TranslatableStringHelper $translatableStringHelper,
protected array $timeChoices,
protected SocialIssueRender $socialIssueRender,
protected SocialActionRender $socialActionRender
) {
if (!$tokenStorage->getToken()->getUser() instanceof User) {
throw new RuntimeException('you should have a valid user');
throw new \RuntimeException('you should have a valid user');
}
$this->user = $tokenStorage->getToken()->getUser();
$this->authorizationHelper = $authorizationHelper;
$this->om = $om;
$this->translatableStringHelper = $translatableStringHelper;
$this->timeChoices = $timeChoices;
$this->socialIssueRender = $socialIssueRender;
$this->socialActionRender = $socialActionRender;
}
public function buildForm(FormBuilderInterface $builder, array $options): void
@@ -112,8 +86,7 @@ class ActivityType extends AbstractType
/** @var \Chill\ActivityBundle\Entity\ActivityType $activityType */
$activityType = $options['activityType'];
// TODO revoir la gestion des center au niveau du form des activité.
if ($options['center'] instanceof Center && null !== $options['data']->getPerson()) {
if (null !== $options['data']->getPerson()) {
$builder->add('scope', ScopePickerType::class, [
'center' => $options['center'],
'role' => ActivityVoter::CREATE === (string) $options['role'] ? ActivityVoter::CREATE_PERSON : (string) $options['role'],
@@ -121,16 +94,16 @@ class ActivityType extends AbstractType
]);
}
/** @var ? \Chill\PersonBundle\Entity\AccompanyingPeriod $accompanyingPeriod */
/** @var AccompanyingPeriod|null $accompanyingPeriod */
$accompanyingPeriod = null;
if ($options['accompanyingPeriod'] instanceof AccompanyingPeriod) {
$accompanyingPeriod = $options['accompanyingPeriod'];
}
if ($activityType->isVisible('socialIssues') && $accompanyingPeriod) {
if ($activityType->isVisible('socialIssues') && null !== $accompanyingPeriod) {
$builder->add('socialIssues', HiddenType::class, [
'required' => $activityType->getSocialIssuesVisible() === 2,
'required' => 2 === $activityType->getSocialIssuesVisible(),
]);
$builder->get('socialIssues')
->addModelTransformer(new CallbackTransformer(
@@ -156,9 +129,9 @@ class ActivityType extends AbstractType
));
}
if ($activityType->isVisible('socialActions') && $accompanyingPeriod) {
if ($activityType->isVisible('socialActions') && null !== $accompanyingPeriod) {
$builder->add('socialActions', HiddenType::class, [
'required' => $activityType->getSocialActionsVisible() === 2,
'required' => 2 === $activityType->getSocialActionsVisible(),
]);
$builder->get('socialActions')
->addModelTransformer(new CallbackTransformer(
@@ -209,6 +182,7 @@ class ActivityType extends AbstractType
$builder->add('attendee', EntityType::class, [
'label' => $activityType->getLabel('attendee'),
'required' => $activityType->isRequired('attendee'),
'placeholder' => false,
'expanded' => true,
'class' => ActivityPresence::class,
'choice_label' => fn (ActivityPresence $activityPresence) => $this->translatableStringHelper->localize($activityPresence->getName()),
@@ -217,7 +191,7 @@ class ActivityType extends AbstractType
]);
}
if ($activityType->isVisible('user') && $options['center'] instanceof Center) {
if ($activityType->isVisible('user')) {
$builder->add('user', PickUserDynamicType::class, [
'label' => $activityType->getLabel('user'),
'required' => $activityType->isRequired('user'),
@@ -301,16 +275,9 @@ class ActivityType extends AbstractType
}
if ($activityType->isVisible('documents')) {
$builder->add('documents', ChillCollectionType::class, [
'entry_type' => StoredObjectType::class,
$builder->add('documents', CollectionStoredObjectType::class, [
'label' => $activityType->getLabel('documents'),
'required' => $activityType->isRequired('documents'),
'allow_add' => true,
'allow_delete' => true,
'button_add_label' => 'activity.Insert a document',
'button_remove_label' => 'activity.Remove a document',
'empty_collection_explain' => 'No documents',
'entry_options' => ['has_title' => true],
]);
}
@@ -342,7 +309,7 @@ class ActivityType extends AbstractType
if ($activityType->isVisible('location')) {
$builder->add('location', HiddenType::class, [
'required' => $activityType->getLocationVisible() === 2,
'required' => 2 === $activityType->getLocationVisible(),
])
->get('location')
->addModelTransformer(new CallbackTransformer(
@@ -393,16 +360,16 @@ class ActivityType extends AbstractType
) {
// set the timezone to GMT, and fix the difference between current and GMT
// the datetimetransformer will then handle timezone as GMT
$timezoneUTC = new DateTimeZone('GMT');
/** @var DateTime $data */
$data = $formEvent->getData() ?? DateTime::createFromFormat('U', '300');
$timezoneUTC = new \DateTimeZone('GMT');
/** @var \DateTime $data */
$data = $formEvent->getData() ?? \DateTime::createFromFormat('U', '300');
$seconds = $data->getTimezone()->getOffset($data);
$data->setTimeZone($timezoneUTC);
$data->add(new DateInterval('PT' . $seconds . 'S'));
$data->add(new \DateInterval('PT'.$seconds.'S'));
// test if the timestamp is in the choices.
// If not, recreate the field with the new timestamp
if (!in_array($data->getTimestamp(), $timeChoices, true)) {
if (!\in_array($data->getTimestamp(), $timeChoices, true)) {
// the data are not in the possible values. add them
$timeChoices[$data->format('H:i')] = $data->getTimestamp();
$form = $builder->create($fieldName, ChoiceType::class, array_merge(
@@ -427,10 +394,10 @@ class ActivityType extends AbstractType
$resolver
->setRequired(['center', 'role', 'activityType', 'accompanyingPeriod'])
->setAllowedTypes('center', ['null', Center::class])
->setAllowedTypes('role', [Role::class, 'string'])
->setAllowedTypes('center', ['null', Center::class, 'array'])
->setAllowedTypes('role', ['string'])
->setAllowedTypes('activityType', \Chill\ActivityBundle\Entity\ActivityType::class)
->setAllowedTypes('accompanyingPeriod', [\Chill\PersonBundle\Entity\AccompanyingPeriod::class, 'null']);
->setAllowedTypes('accompanyingPeriod', [AccompanyingPeriod::class, 'null']);
}
public function getBlockPrefix(): string

View File

@@ -25,11 +25,8 @@ use Symfony\Component\OptionsResolver\OptionsResolver;
class ActivityTypeType extends AbstractType
{
private TranslatableStringHelper $translatableStringHelper;
public function __construct(TranslatableStringHelper $translatableStringHelper)
public function __construct(private readonly TranslatableStringHelper $translatableStringHelper)
{
$this->translatableStringHelper = $translatableStringHelper;
}
public function buildForm(FormBuilderInterface $builder, array $options)
@@ -61,8 +58,8 @@ class ActivityTypeType extends AbstractType
foreach ($fields as $field) {
$builder
->add($field . 'Visible', ActivityFieldPresence::class)
->add($field . 'Label', TextType::class, [
->add($field.'Visible', ActivityFieldPresence::class)
->add($field.'Label', TextType::class, [
'required' => false,
'empty_data' => '',
]);

View File

@@ -24,20 +24,11 @@ use Symfony\Component\OptionsResolver\OptionsResolver;
*/
class PickActivityReasonType extends AbstractType
{
private ActivityReasonRepository $activityReasonRepository;
private ActivityReasonRender $reasonRender;
private TranslatableStringHelperInterface $translatableStringHelper;
public function __construct(
ActivityReasonRepository $activityReasonRepository,
ActivityReasonRender $reasonRender,
TranslatableStringHelperInterface $translatableStringHelper
private readonly ActivityReasonRepository $activityReasonRepository,
private readonly ActivityReasonRender $reasonRender,
private readonly TranslatableStringHelperInterface $translatableStringHelper
) {
$this->activityReasonRepository = $activityReasonRepository;
$this->reasonRender = $reasonRender;
$this->translatableStringHelper = $translatableStringHelper;
}
public function configureOptions(OptionsResolver $resolver)

View File

@@ -23,14 +23,8 @@ use Symfony\Contracts\Translation\TranslatorInterface;
*/
class TranslatableActivityReasonCategoryType extends AbstractType
{
private TranslatableStringHelperInterface $translatableStringHelper;
private TranslatorInterface $translator;
public function __construct(TranslatableStringHelperInterface $translatableStringHelper, TranslatorInterface $translator)
public function __construct(private readonly TranslatableStringHelperInterface $translatableStringHelper, private readonly TranslatorInterface $translator)
{
$this->translatableStringHelper = $translatableStringHelper;
$this->translator = $translator;
}
public function configureOptions(OptionsResolver $resolver)
@@ -39,16 +33,11 @@ class TranslatableActivityReasonCategoryType extends AbstractType
[
'class' => ActivityReasonCategory::class,
'choice_label' => fn (ActivityReasonCategory $category) => $this->translatableStringHelper->localize($category->getName())
. (!$category->getActive() ? ' (' . $this->translator->trans('inactive') . ')' : ''),
.(!$category->getActive() ? ' ('.$this->translator->trans('inactive').')' : ''),
]
);
}
public function getBlockPrefix()
{
return 'translatable_activity_reason_category';
}
public function getParent()
{
return EntityType::class;

View File

@@ -20,16 +20,8 @@ use Symfony\Component\OptionsResolver\OptionsResolver;
class TranslatableActivityType extends AbstractType
{
protected ActivityTypeRepositoryInterface $activityTypeRepository;
protected TranslatableStringHelperInterface $translatableStringHelper;
public function __construct(
TranslatableStringHelperInterface $helper,
ActivityTypeRepositoryInterface $activityTypeRepository
) {
$this->translatableStringHelper = $helper;
$this->activityTypeRepository = $activityTypeRepository;
public function __construct(protected TranslatableStringHelperInterface $translatableStringHelper, protected ActivityTypeRepositoryInterface $activityTypeRepository)
{
}
public function configureOptions(OptionsResolver $resolver)

View File

@@ -23,16 +23,8 @@ use Symfony\Contracts\Translation\TranslatorInterface;
*/
class AccompanyingCourseMenuBuilder implements LocalMenuBuilderInterface
{
protected Security $security;
protected TranslatorInterface $translator;
public function __construct(
Security $security,
TranslatorInterface $translator
) {
$this->security = $security;
$this->translator = $translator;
public function __construct(protected Security $security, protected TranslatorInterface $translator)
{
}
public function buildMenu($menuId, MenuItem $menu, array $parameters)

View File

@@ -18,13 +18,10 @@ use Symfony\Component\Security\Core\Security;
/**
* @implements LocalMenuBuilderInterface<array>
*/
final class AdminMenuBuilder implements LocalMenuBuilderInterface
final readonly class AdminMenuBuilder implements LocalMenuBuilderInterface
{
private Security $security;
public function __construct(Security $security)
public function __construct(private Security $security)
{
$this->security = $security;
}
public function buildMenu($menuId, MenuItem $menu, array $parameters)

View File

@@ -21,29 +21,15 @@ use Symfony\Contracts\Translation\TranslatorInterface;
/**
* @implements LocalMenuBuilderInterface<array{person: Person}>
*/
final class PersonMenuBuilder implements LocalMenuBuilderInterface
final readonly class PersonMenuBuilder implements LocalMenuBuilderInterface
{
/**
* @var AuthorizationCheckerInterface
*/
private $authorizationChecker;
/**
* @var TranslatorInterface
*/
private $translator;
public function __construct(
AuthorizationCheckerInterface $authorizationChecker,
TranslatorInterface $translator
) {
$this->translator = $translator;
$this->authorizationChecker = $authorizationChecker;
public function __construct(private AuthorizationCheckerInterface $authorizationChecker, private TranslatorInterface $translator)
{
}
public function buildMenu($menuId, MenuItem $menu, array $parameters)
{
/** @var \Chill\PersonBundle\Entity\Person $person */
/** @var Person $person */
$person = $parameters['person'];
if ($this->authorizationChecker->isGranted(ActivityVoter::SEE, $person)) {

View File

@@ -16,13 +16,10 @@ use Chill\ActivityBundle\Repository\ActivityRepository;
use Chill\MainBundle\Entity\Notification;
use Chill\MainBundle\Notification\NotificationHandlerInterface;
final class ActivityNotificationHandler implements NotificationHandlerInterface
final readonly class ActivityNotificationHandler implements NotificationHandlerInterface
{
private ActivityRepository $activityRepository;
public function __construct(ActivityRepository $activityRepository)
public function __construct(private ActivityRepository $activityRepository)
{
$this->activityRepository = $activityRepository;
}
public function getTemplate(Notification $notification, array $options = []): string
@@ -40,6 +37,6 @@ final class ActivityNotificationHandler implements NotificationHandlerInterface
public function supports(Notification $notification, array $options = []): bool
{
return $notification->getRelatedEntityClass() === Activity::class;
return Activity::class === $notification->getRelatedEntityClass();
}
}

View File

@@ -18,65 +18,207 @@ use Chill\ActivityBundle\Security\Authorization\ActivityVoter;
use Chill\MainBundle\Entity\Location;
use Chill\MainBundle\Entity\LocationType;
use Chill\MainBundle\Entity\Scope;
use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
use Chill\MainBundle\Security\Resolver\CenterResolverDispatcherInterface;
use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Entity\UserJob;
use Chill\MainBundle\Security\Authorization\AuthorizationHelperForCurrentUserInterface;
use Chill\MainBundle\Security\Resolver\CenterResolverManagerInterface;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Entity\Person;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\AbstractQuery;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\NonUniqueResultException;
use Doctrine\ORM\NoResultException;
use Doctrine\ORM\Query\Expr\Join;
use Doctrine\ORM\Query\ResultSetMappingBuilder;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\Role\Role;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\Security\Core\Security;
use function count;
use function in_array;
final class ActivityACLAwareRepository implements ActivityACLAwareRepositoryInterface
final readonly class ActivityACLAwareRepository implements ActivityACLAwareRepositoryInterface
{
private AuthorizationHelper $authorizationHelper;
private CenterResolverDispatcherInterface $centerResolverDispatcher;
private EntityManagerInterface $em;
private ActivityRepository $repository;
private Security $security;
private TokenStorageInterface $tokenStorage;
public function __construct(
AuthorizationHelper $authorizationHelper,
CenterResolverDispatcherInterface $centerResolverDispatcher,
TokenStorageInterface $tokenStorage,
ActivityRepository $repository,
EntityManagerInterface $em,
Security $security
private AuthorizationHelperForCurrentUserInterface $authorizationHelper,
private CenterResolverManagerInterface $centerResolverManager,
private ActivityRepository $repository,
private EntityManagerInterface $em,
private Security $security,
private RequestStack $requestStack,
) {
$this->authorizationHelper = $authorizationHelper;
$this->centerResolverDispatcher = $centerResolverDispatcher;
$this->tokenStorage = $tokenStorage;
$this->repository = $repository;
$this->em = $em;
$this->security = $security;
}
public function findByAccompanyingPeriod(AccompanyingPeriod $period, string $role, ?int $start = 0, ?int $limit = 1000, ?array $orderBy = []): array
/**
* @throws NonUniqueResultException
* @throws NoResultException
*/
public function countByAccompanyingPeriod(AccompanyingPeriod $period, string $role, array $filters = []): int
{
$user = $this->security->getUser();
$center = $this->centerResolverDispatcher->resolveCenter($period);
$qb = $this->buildBaseQuery($filters);
if (0 === count($orderBy)) {
$orderBy = ['date' => 'DESC'];
$qb
->select('COUNT(a)')
->andWhere('a.accompanyingPeriod = :period')->setParameter('period', $period);
return $qb->getQuery()->getSingleScalarResult();
}
public function countByPerson(Person $person, string $role, array $filters = []): int
{
$qb = $this->buildBaseQuery($filters);
$qb = $this->filterBaseQueryByPerson($qb, $person, $role);
$qb->select('COUNT(a)');
return $qb->getQuery()->getSingleScalarResult();
}
public function findByAccompanyingPeriod(AccompanyingPeriod $period, string $role, ?int $start = 0, ?int $limit = 1000, array $orderBy = ['date' => 'DESC'], array $filters = []): array
{
$qb = $this->buildBaseQuery($filters);
$qb->andWhere('a.accompanyingPeriod = :period')->setParameter('period', $period);
foreach ($orderBy as $field => $order) {
$qb->addOrderBy('a.'.$field, $order);
}
$scopes = $this->authorizationHelper
->getReachableCircles($user, $role, $center);
if (null !== $start) {
$qb->setFirstResult($start);
}
if (null !== $limit) {
$qb->setMaxResults($limit);
}
return $this->em->getRepository(Activity::class)
->findByAccompanyingPeriod($period, $scopes, true, $limit, $start, $orderBy);
return $qb->getQuery()->getResult();
}
public function buildBaseQuery(array $filters): QueryBuilder
{
$qb = $this->repository
->createQueryBuilder('a')
;
if (($filters['my_activities'] ?? false) and ($user = $this->security->getUser()) instanceof User) {
$qb->andWhere(
$qb->expr()->orX(
'a.createdBy = :user',
'a.user = :user',
':user MEMBER OF a.users'
)
)->setParameter('user', $user);
}
if ([] !== ($types = $filters['types'] ?? [])) {
$qb->andWhere('a.activityType IN (:types)')->setParameter('types', $types);
}
if ([] !== ($jobs = $filters['jobs'] ?? [])) {
$qb
->leftJoin('a.createdBy', 'creator')
->leftJoin('a.user', 'activity_u')
->andWhere(
$qb->expr()->orX(
$qb->expr()->exists(
sprintf(
'SELECT 1 FROM %s ujh_creator WHERE ujh_creator.user = a.createdBy '
.'AND ujh_creator.job IN (:jobs) AND a.createdAt > ujh_creator.startDate '
.'AND (ujh_creator.endDate IS NULL or ujh_creator.endDate > a.date)',
User\UserJobHistory::class
)
),
$qb->expr()->exists(
sprintf(
'SELECT 1 FROM %s ujh_u WHERE ujh_u.user = a.user '
.'AND ujh_u.job IN (:jobs) AND a.createdAt > ujh_u.startDate '
.'AND (ujh_u.endDate IS NULL or ujh_u.endDate > a.date)',
User\UserJobHistory::class
)
),
$qb->expr()->exists(
sprintf(
'SELECT 1 FROM %s ujh_users WHERE ujh_users.user MEMBER OF a.users '
.'AND ujh_users.job IN (:jobs) AND a.createdAt > ujh_users.startDate '
.'AND (ujh_users.endDate IS NULL or ujh_users.endDate > a.date)',
User\UserJobHistory::class
)
),
)
)
->setParameter('jobs', $jobs);
}
if (null !== ($after = $filters['after'] ?? null)) {
$qb->andWhere('a.date >= :after')->setParameter('after', $after);
}
if (null !== ($before = $filters['before'] ?? null)) {
$qb->andWhere('a.date <= :before')->setParameter('before', $before);
}
return $qb;
}
/**
* @return array<ActivityType>
*/
public function findActivityTypeByAssociated(AccompanyingPeriod|Person $associated): array
{
$in = $this->em->createQueryBuilder();
$in
->select('1')
->from(Activity::class, 'a');
if ($associated instanceof Person) {
$in = $this->filterBaseQueryByPerson($in, $associated, ActivityVoter::SEE);
} else {
$in->andWhere('a.accompanyingPeriod = :period')->setParameter('period', $associated);
}
// join between the embedded exist query and the main query
$in->andWhere('a.activityType = t');
$qb = $this->em->createQueryBuilder()->setParameters($in->getParameters());
$qb
->select('t')
->from(ActivityType::class, 't')
->where(
$qb->expr()->exists($in->getDQL())
);
return $qb->getQuery()->getResult();
}
public function findUserJobByAssociated(AccompanyingPeriod|Person $associated): array
{
$in = $this->em->createQueryBuilder();
$in->select('IDENTITY(u.job)')
->distinct()
->from(User\UserJobHistory::class, 'u')
->join(
Activity::class,
'a',
Join::WITH,
'a.createdBy = u.user OR a.user = u.user OR u.user MEMBER OF a.users AND a.date >= u.startDate ANd (u.endDate IS NULL or u.endDate > a.date)'
);
if ($associated instanceof Person) {
$in = $this->filterBaseQueryByPerson($in, $associated, ActivityVoter::SEE);
} else {
$in->andWhere('a.accompanyingPeriod = :associated');
$in->setParameter('associated', $associated);
}
$qb = $this->em->createQueryBuilder()->setParameters($in->getParameters());
$qb->select('ub', 'JSON_EXTRACT(ub.label, :lang) AS HIDDEN lang')
->from(UserJob::class, 'ub')
->where($qb->expr()->in('ub.id', $in->getDQL()))
->setParameter('lang', $this->requestStack->getCurrentRequest()->getLocale())
->orderBy('lang')
;
return $qb->getQuery()->getResult();
}
public function findByAccompanyingPeriodSimplified(AccompanyingPeriod $period, ?int $limit = 1000): array
@@ -101,7 +243,8 @@ final class ActivityACLAwareRepository implements ActivityACLAwareRepositoryInte
thirdparties.thirdpartyids,
persons.personids,
actions.socialactionids,
issues.socialissueids
issues.socialissueids,
a.user_id
FROM activity a
LEFT JOIN chill_main_location location ON a.location_id = location.id
@@ -141,6 +284,7 @@ final class ActivityACLAwareRepository implements ActivityACLAwareRepositoryInte
->addJoinedEntityResult(ActivityPresence::class, 'activityPresence', 'a', 'attendee')
->addFieldResult('activityPresence', 'presence_id', 'id')
->addFieldResult('activityPresence', 'presence_name', 'name')
->addScalarResult('user_id', 'userId', Types::INTEGER)
// results which cannot be mapped into entity
->addScalarResult('comment_comment', 'comment', Types::TEXT)
@@ -159,25 +303,73 @@ final class ActivityACLAwareRepository implements ActivityACLAwareRepositoryInte
return $nq->getResult(AbstractQuery::HYDRATE_ARRAY);
}
/**
* @param array $orderBy
*
* @return Activity[]|array
*/
public function findByPerson(Person $person, string $role, ?int $start = 0, ?int $limit = 1000, ?array $orderBy = []): array
public function findByPerson(Person $person, string $role, ?int $start = 0, ?int $limit = 1000, ?array $orderBy = [], array $filters = []): array
{
$user = $this->security->getUser();
$center = $this->centerResolverDispatcher->resolveCenter($person);
$qb = $this->buildBaseQuery($filters);
if (0 === count($orderBy)) {
$orderBy = ['date' => 'DESC'];
$qb = $this->filterBaseQueryByPerson($qb, $person, $role);
foreach ($orderBy as $field => $direction) {
$qb->addOrderBy('a.'.$field, $direction);
}
$reachableScopes = $this->authorizationHelper
->getReachableCircles($user, $role, $center);
if (null !== $start) {
$qb->setFirstResult($start);
}
if (null !== $limit) {
$qb->setMaxResults($limit);
}
return $this->em->getRepository(Activity::class)
->findByPersonImplied($person, $reachableScopes, $orderBy, $limit, $start);
return $qb->getQuery()->getResult();
}
private function filterBaseQueryByPerson(QueryBuilder $qb, Person $person, string $role): QueryBuilder
{
$orX = $qb->expr()->orX();
$counter = 0;
foreach ($this->centerResolverManager->resolveCenters($person) as $center) {
$scopes = $this->authorizationHelper->getReachableScopes($role, $center);
if ([] === $scopes) {
continue;
}
$orX->add(sprintf('a.person = :person AND a.scope IN (:scopes_%d)', $counter));
$qb->setParameter(sprintf('scopes_%d', $counter), $scopes);
$qb->setParameter('person', $person);
++$counter;
}
foreach ($person->getAccompanyingPeriodParticipations() as $participation) {
if (!$this->security->isGranted(ActivityVoter::SEE, $participation->getAccompanyingPeriod())) {
continue;
}
$and = $qb->expr()->andX(
sprintf('a.accompanyingPeriod = :period_%d', $counter),
sprintf('a.date >= :participation_start_%d', $counter)
);
$qb
->setParameter(sprintf('period_%d', $counter), $participation->getAccompanyingPeriod())
->setParameter(sprintf('participation_start_%d', $counter), $participation->getStartDate());
if (null !== $participation->getEndDate()) {
$and->add(sprintf('a.date < :participation_end_%d', $counter));
$qb
->setParameter(sprintf('participation_end_%d', $counter), $participation->getEndDate());
}
$orX->add($and);
++$counter;
}
if (0 === $orX->count()) {
$qb->andWhere('FALSE = TRUE');
} else {
$qb->andWhere($orX);
}
return $qb;
}
public function queryTimelineIndexer(string $context, array $args = []): array
@@ -189,10 +381,10 @@ final class ActivityACLAwareRepository implements ActivityACLAwareRepositoryInte
return [
'id' => $metadataActivity->getTableName()
. '.' . $metadataActivity->getColumnName('id'),
.'.'.$metadataActivity->getColumnName('id'),
'type' => 'activity',
'date' => $metadataActivity->getTableName()
. '.' . $metadataActivity->getColumnName('date'),
.'.'.$metadataActivity->getColumnName('date'),
'FROM' => $from,
'WHERE' => $where,
'parameters' => $parameters,
@@ -205,12 +397,12 @@ final class ActivityACLAwareRepository implements ActivityACLAwareRepositoryInte
$metadataPerson = $this->em->getClassMetadata(Person::class);
$associationMapping = $metadataActivity->getAssociationMapping('person');
return $metadataActivity->getTableName() . ' JOIN '
. $metadataPerson->getTableName() . ' ON '
. $metadataPerson->getTableName() . '.' .
return $metadataActivity->getTableName().' JOIN '
.$metadataPerson->getTableName().' ON '
.$metadataPerson->getTableName().'.'.
$associationMapping['joinColumns'][0]['referencedColumnName']
. ' = '
. $associationMapping['joinColumns'][0]['name'];
.' = '
.$associationMapping['joinColumns'][0]['name'];
}
private function getWhereClause(string $context, array $args): array
@@ -226,11 +418,10 @@ final class ActivityACLAwareRepository implements ActivityACLAwareRepositoryInte
// acls:
$reachableCenters = $this->authorizationHelper->getReachableCenters(
$this->tokenStorage->getToken()->getUser(),
ActivityVoter::SEE
);
if (count($reachableCenters) === 0) {
if (0 === \count($reachableCenters)) {
// insert a dummy condition
return 'FALSE = TRUE';
}
@@ -247,11 +438,11 @@ final class ActivityACLAwareRepository implements ActivityACLAwareRepositoryInte
foreach ($reachableCenters as $center) {
// we pass if not in centers
if (!in_array($center, $args['centers'], true)) {
if (!\in_array($center, $args['centers'], true)) {
continue;
}
// we get all the reachable scopes for this center
$reachableScopes = $this->authorizationHelper->getReachableScopes($this->tokenStorage->getToken()->getUser(), ActivityVoter::SEE, $center);
$reachableScopes = $this->authorizationHelper->getReachableScopes(ActivityVoter::SEE, $center);
// we get the ids for those scopes
$reachablesScopesId = array_map(
static fn (Scope $scope) => $scope->getId(),
@@ -269,7 +460,7 @@ final class ActivityACLAwareRepository implements ActivityACLAwareRepositoryInte
// begin loop for scopes
$where .= ' AND (';
$scopesI = 0; //like scope#i
$scopesI = 0; // like scope#i
foreach ($reachablesScopesId as $scopeId) {
if (0 < $scopesI) {

View File

@@ -11,15 +11,32 @@ declare(strict_types=1);
namespace Chill\ActivityBundle\Repository;
use Chill\ActivityBundle\Entity\Activity;
use Chill\ActivityBundle\Entity\ActivityType;
use Chill\MainBundle\Entity\UserJob;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Entity\Person;
interface ActivityACLAwareRepositoryInterface
{
/**
* @return Activity[]|array
* Return all the activities associated to an accompanying period and that the user is allowed to apply the given role.
*
* @param array{my_activities?: bool, types?: array<ActivityType>, jobs?: array<UserJob>, after?: \DateTimeImmutable|null, before?: \DateTimeImmutable|null} $filters
*
* @return array<Activity>
*/
public function findByAccompanyingPeriod(AccompanyingPeriod $period, string $role, ?int $start = 0, ?int $limit = 1000, ?array $orderBy = []): array;
public function findByAccompanyingPeriod(AccompanyingPeriod $period, string $role, ?int $start = 0, ?int $limit = 1000, array $orderBy = ['date' => 'DESC'], array $filters = []): array;
/**
* @param array{my_activities?: bool, types?: array<ActivityType>, jobs?: array<UserJob>, after?: \DateTimeImmutable|null, before?: \DateTimeImmutable|null} $filters
*/
public function countByAccompanyingPeriod(AccompanyingPeriod $period, string $role, array $filters = []): int;
/**
* @param array{my_activities?: bool, types?: array<ActivityType>, jobs?: array<UserJob>, after?: \DateTimeImmutable|null, before?: \DateTimeImmutable|null} $filters
*/
public function countByPerson(Person $person, string $role, array $filters = []): int;
/**
* Return a list of activities, simplified as array (not object).
@@ -31,7 +48,28 @@ interface ActivityACLAwareRepositoryInterface
public function findByAccompanyingPeriodSimplified(AccompanyingPeriod $period, ?int $limit = 1000): array;
/**
* @return Activity[]|array
* @param array{my_activities?: bool, types?: array<ActivityType>, jobs?: array<UserJob>, after?: \DateTimeImmutable|null, before?: \DateTimeImmutable|null} $filters
*
* @return array<Activity>
*/
public function findByPerson(Person $person, string $role, ?int $start = 0, ?int $limit = 1000, ?array $orderBy = []): array;
public function findByPerson(Person $person, string $role, ?int $start = 0, ?int $limit = 1000, array $orderBy = ['date' => 'DESC'], array $filters = []): array;
/**
* Return a list of the type for the activities associated to person or accompanying period.
*
* @return array<ActivityType>
*/
public function findActivityTypeByAssociated(AccompanyingPeriod|Person $associated): array;
/**
* Return a list of the user job for the activities associated to person or accompanying period.
*
* Associated mean the job:
* - of the creator;
* - of the user (activity.user)
* - of all the users
*
* @return array<UserJob>
*/
public function findUserJobByAssociated(AccompanyingPeriod|Person $associated): array;
}

View File

@@ -15,25 +15,15 @@ use Chill\ActivityBundle\Entity\Activity;
use Chill\ActivityBundle\Security\Authorization\ActivityVoter;
use Chill\ActivityBundle\Service\GenericDoc\Providers\AccompanyingPeriodActivityGenericDocProvider;
use Chill\ActivityBundle\Service\GenericDoc\Providers\PersonActivityGenericDocProvider;
use Chill\DocStoreBundle\Entity\PersonDocument;
use Chill\DocStoreBundle\Entity\StoredObject;
use Chill\DocStoreBundle\GenericDoc\FetchQuery;
use Chill\DocStoreBundle\GenericDoc\FetchQueryInterface;
use Chill\DocStoreBundle\GenericDoc\Providers\PersonDocumentGenericDocProvider;
use Chill\DocStoreBundle\Repository\PersonDocumentACLAwareRepositoryInterface;
use Chill\DocStoreBundle\Security\Authorization\PersonDocumentVoter;
use Chill\MainBundle\Entity\Scope;
use Chill\MainBundle\Security\Authorization\AuthorizationHelperForCurrentUserInterface;
use Chill\MainBundle\Security\Resolver\CenterResolverManagerInterface;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Entity\Person;
use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodWorkVoter;
use DateTimeImmutable;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Mapping\MappingException;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\HttpKernel\HttpCache\Store;
use Symfony\Component\Security\Core\Security;
final readonly class ActivityDocumentACLAwareRepository implements ActivityDocumentACLAwareRepositoryInterface
@@ -46,14 +36,14 @@ final readonly class ActivityDocumentACLAwareRepository implements ActivityDocum
) {
}
public function buildFetchQueryActivityDocumentLinkedToPersonFromPersonContext(Person $person, ?DateTimeImmutable $startDate = null, ?DateTimeImmutable $endDate = null, ?string $content = null): FetchQueryInterface
public function buildFetchQueryActivityDocumentLinkedToPersonFromPersonContext(Person $person, ?\DateTimeImmutable $startDate = null, ?\DateTimeImmutable $endDate = null, ?string $content = null): FetchQueryInterface
{
$query = $this->buildBaseFetchQueryActivityDocumentLinkedToPersonFromPersonContext($person, $startDate, $endDate, $content);
return $this->addFetchQueryByPersonACL($query, $person);
}
public function buildBaseFetchQueryActivityDocumentLinkedToPersonFromPersonContext(Person $person, ?DateTimeImmutable $startDate = null, ?DateTimeImmutable $endDate = null, ?string $content = null): FetchQuery
public function buildBaseFetchQueryActivityDocumentLinkedToPersonFromPersonContext(Person $person, ?\DateTimeImmutable $startDate = null, ?\DateTimeImmutable $endDate = null, ?string $content = null): FetchQuery
{
$storedObjectMetadata = $this->em->getClassMetadata(StoredObject::class);
$activityMetadata = $this->em->getClassMetadata(Activity::class);
@@ -82,7 +72,7 @@ final readonly class ActivityDocumentACLAwareRepository implements ActivityDocum
return $this->addWhereClauses($query, $startDate, $endDate, $content);
}
public function buildFetchQueryActivityDocumentLinkedToAccompanyingPeriodFromPersonContext(Person $person, ?DateTimeImmutable $startDate = null, ?DateTimeImmutable $endDate = null, ?string $content = null): FetchQuery
public function buildFetchQueryActivityDocumentLinkedToAccompanyingPeriodFromPersonContext(Person $person, ?\DateTimeImmutable $startDate = null, ?\DateTimeImmutable $endDate = null, ?string $content = null): FetchQuery
{
$storedObjectMetadata = $this->em->getClassMetadata(StoredObject::class);
$activityMetadata = $this->em->getClassMetadata(Activity::class);
@@ -117,8 +107,8 @@ final readonly class ActivityDocumentACLAwareRepository implements ActivityDocum
$storedObjectMetadata->getColumnName('createdAt')
);
$orParams = [...$orParams, $participation->getAccompanyingPeriod()->getId(),
DateTimeImmutable::createFromInterface($participation->getStartDate()),
null === $participation->getEndDate() ? null : DateTimeImmutable::createFromInterface($participation->getEndDate())];
\DateTimeImmutable::createFromInterface($participation->getStartDate()),
null === $participation->getEndDate() ? null : \DateTimeImmutable::createFromInterface($participation->getEndDate())];
$orTypes = [...$orTypes, Types::INTEGER, Types::DATE_IMMUTABLE, Types::DATE_IMMUTABLE];
}
@@ -133,7 +123,7 @@ final readonly class ActivityDocumentACLAwareRepository implements ActivityDocum
return $this->addWhereClauses($query, $startDate, $endDate, $content);
}
private function addWhereClauses(FetchQuery $query, ?DateTimeImmutable $startDate = null, ?DateTimeImmutable $endDate = null, ?string $content = null): FetchQuery
private function addWhereClauses(FetchQuery $query, ?\DateTimeImmutable $startDate = null, ?\DateTimeImmutable $endDate = null, ?string $content = null): FetchQuery
{
$storedObjectMetadata = $this->em->getClassMetadata(StoredObject::class);
@@ -156,7 +146,7 @@ final readonly class ActivityDocumentACLAwareRepository implements ActivityDocum
if (null !== $content and '' !== $content) {
$query->addWhereClause(
'stored_obj.title ilike ?',
['%' . $content . '%'],
['%'.$content.'%'],
[Types::STRING]
);
}
@@ -173,7 +163,7 @@ final readonly class ActivityDocumentACLAwareRepository implements ActivityDocum
foreach ($this->centerResolverManager->resolveCenters($person) as $center) {
$reachableScopes = [
...$reachableScopes,
...$this->authorizationHelperForCurrentUser->getReachableScopes(ActivityVoter::SEE, $center)
...$this->authorizationHelperForCurrentUser->getReachableScopes(ActivityVoter::SEE, $center),
];
}

View File

@@ -14,24 +14,23 @@ namespace Chill\ActivityBundle\Repository;
use Chill\DocStoreBundle\GenericDoc\FetchQuery;
use Chill\DocStoreBundle\GenericDoc\FetchQueryInterface;
use Chill\PersonBundle\Entity\Person;
use DateTimeImmutable;
/**
* Gives queries usable for fetching documents, with ACL aware
* Gives queries usable for fetching documents, with ACL aware.
*/
interface ActivityDocumentACLAwareRepositoryInterface
{
/**
* Return a fetch query for querying document's activities for a person
* Return a fetch query for querying document's activities for a person.
*
* This method must check the rights to see a document: the user must be allowed to see the given activities
*/
public function buildFetchQueryActivityDocumentLinkedToPersonFromPersonContext(Person $person, ?DateTimeImmutable $startDate = null, ?DateTimeImmutable $endDate = null, ?string $content = null): FetchQueryInterface;
public function buildFetchQueryActivityDocumentLinkedToPersonFromPersonContext(Person $person, ?\DateTimeImmutable $startDate = null, ?\DateTimeImmutable $endDate = null, ?string $content = null): FetchQueryInterface;
/**
* Return a fetch query for querying document's activities for an activity in accompanying periods, but for a given person
* Return a fetch query for querying document's activities for an activity in accompanying periods, but for a given person.
*
* This method must check the rights to see a document: the user must be allowed to see the given accompanying periods
*/
public function buildFetchQueryActivityDocumentLinkedToAccompanyingPeriodFromPersonContext(Person $person, ?DateTimeImmutable $startDate = null, ?DateTimeImmutable $endDate = null, ?string $content = null): FetchQuery;
public function buildFetchQueryActivityDocumentLinkedToAccompanyingPeriodFromPersonContext(Person $person, ?\DateTimeImmutable $startDate = null, ?\DateTimeImmutable $endDate = null, ?string $content = null): FetchQuery;
}

View File

@@ -17,7 +17,7 @@ use Doctrine\ORM\EntityRepository;
class ActivityPresenceRepository implements ActivityPresenceRepositoryInterface
{
private EntityRepository $repository;
private readonly EntityRepository $repository;
public function __construct(EntityManagerInterface $entityManager)
{

View File

@@ -23,15 +23,11 @@ use Symfony\Component\HttpFoundation\RequestStack;
*/
class ActivityReasonRepository extends ServiceEntityRepository
{
private RequestStack $requestStack;
public function __construct(
ManagerRegistry $registry,
RequestStack $requestStack
private readonly RequestStack $requestStack
) {
parent::__construct($registry, ActivityReason::class);
$this->requestStack = $requestStack;
}
/**

View File

@@ -62,7 +62,7 @@ class ActivityRepository extends ServiceEntityRepository
->setParameter('period', $period);
foreach ($orderBy as $k => $dir) {
$qb->addOrderBy('a.' . $k, $dir);
$qb->addOrderBy('a.'.$k, $dir);
}
$qb->setMaxResults($limit)->setFirstResult($offset);
@@ -90,7 +90,7 @@ class ActivityRepository extends ServiceEntityRepository
->setParameter('person', $person);
foreach ($orderBy as $k => $dir) {
$qb->addOrderBy('a.' . $k, $dir);
$qb->addOrderBy('a.'.$k, $dir);
}
$qb->setMaxResults($limit)->setFirstResult($offset);

View File

@@ -17,7 +17,7 @@ use Doctrine\ORM\EntityRepository;
final class ActivityTypeRepository implements ActivityTypeRepositoryInterface
{
private EntityRepository $repository;
private readonly EntityRepository $repository;
public function __construct(EntityManagerInterface $em)
{

View File

@@ -17,7 +17,7 @@ use Doctrine\Persistence\ObjectRepository;
interface ActivityTypeRepositoryInterface extends ObjectRepository
{
/**
* @return array|ActivityType[]
* @return array<ActivityType>
*/
public function findAllActive(): array;
}

View File

@@ -75,7 +75,7 @@
{% endif %}
</div>
{% include 'ChillActivityBundle:Activity:concernedGroups.html.twig' with {
{% include '@ChillActivity/Activity/concernedGroups.html.twig' with {
'context': context,
'render': 'wrap-list',
'entity': activity,

Some files were not shown because too many files have changed in this diff Show More