Merge branch 'rector/rules-up-to-php82' into rector/rules-symfony

This commit is contained in:
2023-07-19 23:22:57 +02:00
577 changed files with 18339 additions and 2168 deletions

View File

@@ -39,7 +39,10 @@ class UserRefEventSubscriber implements EventSubscriberInterface
public function onStateEntered(EnteredEvent $enteredEvent): void
{
if ($enteredEvent->getMarking()->has(AccompanyingPeriod::STEP_CONFIRMED)) {
if (
$enteredEvent->getMarking()->has(AccompanyingPeriod::STEP_CONFIRMED)
and $enteredEvent->getTransition()->getName() === 'confirm'
) {
$this->onPeriodConfirmed($enteredEvent->getSubject());
}
}

View File

@@ -0,0 +1,48 @@
<?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\PersonBundle\AccompanyingPeriod\Lifecycle;
use Chill\MainBundle\Cron\CronJobInterface;
use Chill\MainBundle\Entity\CronJobExecution;
use Symfony\Component\Clock\ClockInterface;
readonly class AccompanyingPeriodStepChangeCronjob implements CronJobInterface
{
public function __construct(
private ClockInterface $clock,
private AccompanyingPeriodStepChangeRequestor $requestor,
) {
}
public function canRun(?CronJobExecution $cronJobExecution): bool
{
$now = $this->clock->now();
if (null !== $cronJobExecution && $now->sub(new \DateInterval('P1D')) < $cronJobExecution->getLastStart()) {
return false;
}
return in_array((int) $now->format('H'), [1, 2, 3, 4, 5, 6], true);
}
public function getKey(): string
{
return 'accompanying-period-step-change';
}
public function run(array $lastExecutionData): null|array
{
($this->requestor)();
return null;
}
}

View File

@@ -0,0 +1,37 @@
<?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\PersonBundle\AccompanyingPeriod\Lifecycle;
use Chill\PersonBundle\Repository\AccompanyingPeriodRepository;
use Symfony\Component\Messenger\Attribute\AsMessageHandler;
use Symfony\Component\Messenger\Handler\MessageHandlerInterface;
#[AsMessageHandler]
class AccompanyingPeriodStepChangeMessageHandler implements MessageHandlerInterface
{
private const LOG_PREFIX = '[accompanying period step change message handler] ';
public function __construct(
private readonly AccompanyingPeriodRepository $accompanyingPeriodRepository,
private readonly AccompanyingPeriodStepChanger $changer,
) {
}
public function __invoke(AccompanyingPeriodStepChangeRequestMessage $message): void
{
if (null === $period = $this->accompanyingPeriodRepository->find($message->getPeriodId())) {
throw new \RuntimeException(self::LOG_PREFIX . 'Could not find period with this id: '. $message->getPeriodId());
}
($this->changer)($period, $message->getTransition());
}
}

View File

@@ -0,0 +1,47 @@
<?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\PersonBundle\AccompanyingPeriod\Lifecycle;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
/**
* Message which will request a change in the step of accompanying period
*/
class AccompanyingPeriodStepChangeRequestMessage
{
private int $periodId;
public function __construct(
AccompanyingPeriod|int $period,
private readonly string $transition,
) {
if (is_int($period)) {
$this->periodId = $period;
} else {
if (null !== $id = $period->getId()) {
$this->periodId = $id;
}
throw new \LogicException("This AccompanyingPeriod does not have and id yet");
}
}
public function getPeriodId(): int
{
return $this->periodId;
}
public function getTransition(): string
{
return $this->transition;
}
}

View File

@@ -0,0 +1,87 @@
<?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\PersonBundle\AccompanyingPeriod\Lifecycle;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Repository\AccompanyingPeriod\AccompanyingPeriodInfoRepositoryInterface;
use Doctrine\ORM\EntityManagerInterface;
use Psr\Log\LoggerInterface;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\Messenger\MessageBusInterface;
/**
* Gather all the accompanying period which needs a change in step
*/
class AccompanyingPeriodStepChangeRequestor
{
private readonly \DateInterval $intervalForShortInactive;
private readonly \DateInterval $intervalForLongInactive;
private readonly bool $isMarkInactive;
public function __construct(
private readonly AccompanyingPeriodInfoRepositoryInterface $accompanyingPeriodInfoRepository,
private readonly LoggerInterface $logger,
private readonly MessageBusInterface $messageBus,
ParameterBagInterface $parameterBag,
) {
$config = $parameterBag->get('chill_person')['accompanying_period_lifecycle_delays'];
$this->isMarkInactive = $config['mark_inactive'];
$this->intervalForShortInactive = new \DateInterval($config['mark_inactive_short_after']);
$this->intervalForLongInactive = new \DateInterval($config['mark_inactive_long_after']);
}
public function __invoke(): void
{
if (!$this->isMarkInactive) {
return;
}
// get the oldest ones first
foreach (
$olders = $this->accompanyingPeriodInfoRepository->findAccompanyingPeriodIdInactiveAfter(
$this->intervalForLongInactive,
[AccompanyingPeriod::STEP_CONFIRMED, AccompanyingPeriod::STEP_CONFIRMED_INACTIVE_SHORT]
) as $accompanyingPeriodId
) {
$this->logger->debug('request mark period as inactive_short', ['period' => $accompanyingPeriodId]);
$this->messageBus->dispatch(new AccompanyingPeriodStepChangeRequestMessage($accompanyingPeriodId, 'mark_inactive_long'));
}
// the newest
foreach (
$this->accompanyingPeriodInfoRepository->findAccompanyingPeriodIdInactiveAfter(
$this->intervalForShortInactive,
[AccompanyingPeriod::STEP_CONFIRMED]
) as $accompanyingPeriodId
) {
if (in_array($accompanyingPeriodId, $olders, true)) {
continue;
}
$this->logger->debug('request mark period as inactive_long', ['period' => $accompanyingPeriodId]);
$this->messageBus->dispatch(new AccompanyingPeriodStepChangeRequestMessage($accompanyingPeriodId, 'mark_inactive_short'));
}
// a new event has been created => remove inactive long, or short
foreach (
$this->accompanyingPeriodInfoRepository->findAccompanyingPeriodIdActiveSince(
$this->intervalForShortInactive,
[AccompanyingPeriod::STEP_CONFIRMED_INACTIVE_SHORT, AccompanyingPeriod::STEP_CONFIRMED_INACTIVE_LONG]
) as $accompanyingPeriodId
) {
$this->logger->debug('request mark period as active', ['period' => $accompanyingPeriodId]);
$this->messageBus->dispatch(new AccompanyingPeriodStepChangeRequestMessage($accompanyingPeriodId, 'mark_active'));
}
}
}

View File

@@ -0,0 +1,58 @@
<?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\PersonBundle\AccompanyingPeriod\Lifecycle;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Doctrine\ORM\EntityManagerInterface;
use Psr\Log\LoggerInterface;
use Symfony\Component\Workflow\Registry;
/**
* Change the step of an accompanying period
*
* This should be invoked through scripts (not in the in context of an http request, or an
* action from a user).
*/
class AccompanyingPeriodStepChanger
{
private const LOG_PREFIX = '[AccompanyingPeriodStepChanger] ';
public function __construct(
private readonly EntityManagerInterface $entityManager,
private readonly LoggerInterface $logger,
private readonly Registry $workflowRegistry,
) {
}
public function __invoke(AccompanyingPeriod $period, string $transition, ?string $workflowName = null): void
{
$workflow = $this->workflowRegistry->get($period, $workflowName);
if (!$workflow->can($period, $transition)) {
$this->logger->info(self::LOG_PREFIX . 'not able to apply the transition on period', [
'period_id' => $period->getId(),
'transition' => $transition
]);
return;
}
$workflow->apply($period, $transition);
$this->entityManager->flush();
$this->logger->info(self::LOG_PREFIX . 'could apply a transition', [
'period_id' => $period->getId(),
'transition' => $transition
]);
}
}

View File

@@ -13,7 +13,11 @@ namespace Chill\PersonBundle\Actions\Remove;
use Chill\PersonBundle\Actions\ActionEvent;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Entity\AccompanyingPeriodParticipation;
use Chill\PersonBundle\Entity\Household\HouseholdMember;
use Chill\PersonBundle\Entity\Household\PersonHouseholdAddress;
use Chill\PersonBundle\Entity\Person;
use Chill\PersonBundle\Entity\Relationships\Relationship;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Mapping\ClassMetadata;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
@@ -42,7 +46,7 @@ class PersonMove
protected $eventDispatcher;
public function __construct(
EntityManagerInterface $em,
EntityManagerInterface $em,
EventDispatcherInterface $eventDispatcher
) {
$this->em = $em;
@@ -84,8 +88,11 @@ class PersonMove
}
foreach ($metadata->getAssociationMappings() as $field => $mapping) {
if (Person::class === $mapping['targetEntity']) {
if (in_array($metadata->getName(), $toDelete, true)) {
if (in_array($mapping['sourceEntity'], $this->getIgnoredEntities(), true)) {
continue;
}
if (Person::class === $mapping['targetEntity'] and true === $mapping['isOwningSide']) {
if (in_array($mapping['sourceEntity'], $toDelete, true)) {
$sql = $this->createDeleteSQL($metadata, $from, $field);
$event = new ActionEvent(
$from->getId(),
@@ -120,7 +127,7 @@ class PersonMove
return $sqls;
}
protected function createDeleteSQL(ClassMetadata $metadata, Person $from, $field): string
private function createDeleteSQL(ClassMetadata $metadata, Person $from, $field): string
{
$mapping = $metadata->getAssociationMapping($field);
@@ -137,26 +144,41 @@ class PersonMove
);
}
protected function createMoveSQL(ClassMetadata $metadata, Person $from, Person $to, $field): string
private function createMoveSQL(ClassMetadata $metadata, Person $from, Person $to, $field): string
{
$mapping = $metadata->getAssociationMapping($field);
// Set part of the query, aka <here> in "UPDATE table SET <here> "
$sets = [];
foreach ($mapping['joinColumns'] as $columns) {
$sets[] = sprintf('%s = %d', $columns['name'], $to->getId());
}
$conditions = [];
$tableName = '';
foreach ($mapping['joinColumns'] as $columns) {
$conditions[] = sprintf('%s = %d', $columns['name'], $from->getId());
if (array_key_exists('joinTable', $mapping)) {
$tableName = (null !== ($mapping['joinTable']['schema'] ?? null) ? $mapping['joinTable']['schema'] . '.' : '')
. $mapping['joinTable']['name'];
foreach ($mapping['joinTable']['inverseJoinColumns'] as $columns) {
$sets[] = sprintf('%s = %d', $columns['name'], $to->getId());
}
foreach ($mapping['joinTable']['inverseJoinColumns'] as $columns) {
$conditions[] = sprintf('%s = %d', $columns['name'], $from->getId());
}
} elseif (array_key_exists('joinColumns', $mapping)) {
$tableName = $this->getTableName($metadata);
foreach ($mapping['joinColumns'] as $columns) {
$sets[] = sprintf('%s = %d', $columns['name'], $to->getId());
}
foreach ($mapping['joinColumns'] as $columns) {
$conditions[] = sprintf('%s = %d', $columns['name'], $from->getId());
}
}
return sprintf(
'UPDATE %s SET %s WHERE %s',
$this->getTableName($metadata),
$tableName,
implode(' ', $sets),
implode(' AND ', $conditions)
);
@@ -166,10 +188,23 @@ class PersonMove
* return an array of classes where entities should be deleted
* instead of moved.
*/
protected function getDeleteEntities(): array
private function getDeleteEntities(): array
{
return [
AccompanyingPeriod::class,
Person\PersonCenterHistory::class,
HouseholdMember::class,
AccompanyingPeriodParticipation::class,
AccompanyingPeriod\AccompanyingPeriodWork::class,
Relationship::class
];
}
private function getIgnoredEntities(): array
{
return [
Person\PersonCurrentAddress::class,
PersonHouseholdAddress::class,
Person\PersonCenterCurrent::class,
];
}

View File

@@ -12,6 +12,7 @@ declare(strict_types=1);
namespace Chill\PersonBundle;
use Chill\PersonBundle\DependencyInjection\CompilerPass\AccompanyingPeriodTimelineCompilerPass;
use Chill\PersonBundle\Service\EntityInfo\AccompanyingPeriodInfoUnionQueryPartInterface;
use Chill\PersonBundle\Widget\PersonListWidgetFactory;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\Bundle\Bundle;
@@ -26,5 +27,7 @@ class ChillPersonBundle extends Bundle
->addWidgetFactory(new PersonListWidgetFactory());
$container->addCompilerPass(new AccompanyingPeriodTimelineCompilerPass());
$container->registerForAutoconfiguration(AccompanyingPeriodInfoUnionQueryPartInterface::class)
->addTag('chill_person.accompanying_period_info_part');
}
}

View File

@@ -35,10 +35,18 @@ final class ImportSocialWorkMetadata extends Command
protected function configure()
{
$description = 'Imports a structured table containing social issues, social actions, objectives, results and evaluations.';
$help = 'File to csv format, no headers, semi-colon as delimiter, datas sorted by alphabetical order, column after column.'. PHP_EOL
. 'Columns are: social issues parent, social issues child, social actions parent, social actions child, goals, results, evaluations.'. PHP_EOL
. PHP_EOL
. 'See social_work_metadata.csv as example.'. PHP_EOL;
$this
->setName('chill:person:import-socialwork')
->addOption('filepath', 'f', InputOption::VALUE_REQUIRED, 'The file to import.')
->addOption('language', 'l', InputOption::VALUE_OPTIONAL, 'The default language');
->addOption('language', 'l', InputOption::VALUE_OPTIONAL, 'The default language')
->setDescription($description)
->setHelp($help);
}
protected function execute(InputInterface $input, OutputInterface $output): int

View File

@@ -11,25 +11,39 @@ declare(strict_types=1);
namespace Chill\PersonBundle\Controller;
use Chill\MainBundle\Entity\UserJob;
use Chill\MainBundle\Pagination\PaginatorFactory;
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\AccompanyingPeriod\AccompanyingPeriodWork;
use Chill\PersonBundle\Entity\Person;
use Chill\PersonBundle\Entity\SocialWork\SocialAction;
use Chill\PersonBundle\Repository\AccompanyingPeriod\AccompanyingPeriodWorkRepository;
use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodWorkVoter;
use Psr\Log\LoggerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\Form;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Serializer\SerializerInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
class AccompanyingCourseWorkController extends AbstractController
final class AccompanyingCourseWorkController extends AbstractController
{
public function __construct(private readonly TranslatorInterface $trans, private readonly SerializerInterface $serializer, private readonly AccompanyingPeriodWorkRepository $workRepository, private readonly PaginatorFactory $paginator, private readonly LoggerInterface $chillLogger)
{
public function __construct(
private readonly TranslatorInterface $trans,
private readonly SerializerInterface $serializer,
private readonly AccompanyingPeriodWorkRepository $workRepository,
private readonly PaginatorFactory $paginator,
private readonly LoggerInterface $chillLogger,
private readonly TranslatableStringHelperInterface $translatableStringHelper,
private readonly FilterOrderHelperFactoryInterface $filterOrderHelperFactory
) {
}
/**
@@ -142,11 +156,21 @@ class AccompanyingCourseWorkController extends AbstractController
{
$this->denyAccessUnlessGranted(AccompanyingPeriodWorkVoter::SEE, $period);
$filter = $this->buildFilterOrder($period);
$filterData = [
'types' => $filter->hasEntityChoice('typesFilter') ? $filter->getEntityChoiceData('typesFilter') : [],
'before' => $filter->getDateRangeData('dateFilter')['to'],
'after' => $filter->getDateRangeData('dateFilter')['from'],
'user' => $filter->getUserPickerData('userFilter')
];
$totalItems = $this->workRepository->countByAccompanyingPeriod($period);
$paginator = $this->paginator->create($totalItems);
$works = $this->workRepository->findByAccompanyingPeriodOpenFirst(
$period,
$filterData,
$paginator->getItemsPerPage(),
$paginator->getCurrentPageFirstItemNumber()
);
@@ -155,6 +179,7 @@ class AccompanyingCourseWorkController extends AbstractController
'accompanyingCourse' => $period,
'works' => $works,
'paginator' => $paginator,
'filter' => $filter
]);
}
@@ -179,7 +204,7 @@ class AccompanyingCourseWorkController extends AbstractController
]);
}
private function createDeleteForm(int $id): Form
private function createDeleteForm(int $id): FormInterface
{
$params = [];
$params['id'] = $id;
@@ -190,4 +215,26 @@ class AccompanyingCourseWorkController extends AbstractController
->add('submit', SubmitType::class, ['label' => 'Delete'])
->getForm();
}
private function buildFilterOrder($associatedPeriod): FilterOrderHelper
{
$filterBuilder = $this->filterOrderHelperFactory->create(self::class);
$types = $this->workRepository->findActionTypeByPeriod($associatedPeriod);
$filterBuilder
->addDateRange('dateFilter', 'accompanying_course_work.date_filter');
if (1 < count($types)) {
$filterBuilder
->addEntityChoice('typesFilter', 'accompanying_course_work.types_filter', \Chill\PersonBundle\Entity\SocialWork\SocialAction::class, $types, [
'choice_label' => fn (SocialAction $sa) => $this->translatableStringHelper->localize($sa->getTitle())
]);
}
$filterBuilder
->addUserPicker('userFilter', 'accompanying_course_work.user_filter', ['required' => false])
;
return $filterBuilder->build();
}
}

View File

@@ -0,0 +1,46 @@
<?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\PersonBundle\Controller;
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkEvaluationDocument;
use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodWorkVoter;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Core\Security;
class AccompanyingCourseWorkEvaluationDocumentController extends AbstractController
{
public function __construct(private readonly Security $security)
{
}
/**
* @Route(
* "{_locale}/person/accompanying-period/work/evaluation/document/{id}/show",
* name="chill_person_accompanying_period_work_evaluation_document_show",
* methods={"GET"}
* )
*/
public function showAccompanyingCourseWork(AccompanyingPeriodWorkEvaluationDocument $document): Response
{
$work = $document->getAccompanyingPeriodWorkEvaluation()->getAccompanyingPeriodWork();
return $this->redirectToRoute(
$this->security->isGranted(AccompanyingPeriodWorkVoter::UPDATE, $work) ?
'chill_person_accompanying_period_work_edit' : 'chill_person_accompanying_period_work_show',
[
'id' => $work->getId()
]
);
}
}

View File

@@ -216,13 +216,13 @@ class AccompanyingPeriodController extends AbstractController
]);
$this->eventDispatcher->dispatch(PrivacyEvent::PERSON_PRIVACY_EVENT, $event);
$accompanyingPeriodsRaw = $this->accompanyingPeriodACLAwareRepository
->findByPerson($person, AccompanyingPeriodVoter::SEE);
$accompanyingPeriods = $this->accompanyingPeriodACLAwareRepository
->findByPerson($person, AccompanyingPeriodVoter::SEE, ["openingDate" => "DESC", "id" => "DESC"]);
usort($accompanyingPeriodsRaw, static fn ($a, $b) => $b->getOpeningDate() > $a->getOpeningDate());
//usort($accompanyingPeriodsRaw, static fn ($a, $b) => $b->getOpeningDate() <=> $a->getOpeningDate());
// filter visible or not visible
$accompanyingPeriods = array_filter($accompanyingPeriodsRaw, fn (AccompanyingPeriod $ap) => $this->isGranted(AccompanyingPeriodVoter::SEE, $ap));
//$accompanyingPeriods = array_filter($accompanyingPeriodsRaw, fn (AccompanyingPeriod $ap) => $this->isGranted(AccompanyingPeriodVoter::SEE, $ap));
return $this->render('@ChillPerson/AccompanyingPeriod/list.html.twig', [
'accompanying_periods' => $accompanyingPeriods,

View File

@@ -60,6 +60,7 @@ class AccompanyingPeriodRegulationListController
$form['jobs']->getData(),
$form['services']->getData(),
$form['locations']->getData(),
['openingDate' => 'DESC', 'id' => 'DESC'],
$paginator->getItemsPerPage(),
$paginator->getCurrentPageFirstItemNumber()
);

View File

@@ -212,7 +212,7 @@ class PersonDuplicateController extends Controller
);
$duplicatePersons = $this->similarPersonMatcher->
matchPerson($person, $personNotDuplicateRepository, 0.5, SimilarPersonMatcher::SIMILAR_SEARCH_ORDER_BY_ALPHABETICAL);
matchPerson($person, 0.5, SimilarPersonMatcher::SIMILAR_SEARCH_ORDER_BY_ALPHABETICAL, false);
$notDuplicatePersons = $personNotDuplicateRepository->findNotDuplicatePerson($person);
@@ -229,14 +229,14 @@ class PersonDuplicateController extends Controller
$nb_activity = $em->getRepository(Activity::class)->findBy(['person' => $id]);
$nb_document = $em->getRepository(PersonDocument::class)->findBy(['person' => $id]);
$nb_event = $em->getRepository(Participation::class)->findBy(['person' => $id]);
// $nb_event = $em->getRepository(Participation::class)->findBy(['person' => $id]);
$nb_task = $em->getRepository(SingleTask::class)->countByParameters(['person' => $id]);
$person = $em->getRepository(Person::class)->findOneBy(['id' => $id]);
return [
'nb_activity' => count($nb_activity),
'nb_document' => count($nb_document),
'nb_event' => count($nb_event),
// 'nb_event' => count($nb_event),
'nb_task' => $nb_task,
'nb_addresses' => count($person->getAddresses()),
];

View File

@@ -20,6 +20,7 @@ use Chill\MainBundle\Repository\UserRepository;
use Chill\MainBundle\Templating\Entity\UserRender;
use Chill\PersonBundle\Repository\AccompanyingPeriodACLAwareRepositoryInterface;
use Chill\PersonBundle\Repository\AccompanyingPeriodRepository;
use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Form\CallbackTransformer;
@@ -30,6 +31,7 @@ use Symfony\Component\Form\FormFactoryInterface;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
use Symfony\Component\Security\Core\Security;
@@ -49,8 +51,8 @@ class ReassignAccompanyingPeriodController extends AbstractController
*/
public function listAction(Request $request): Response
{
if (!$this->security->isGranted('ROLE_USER') || !$this->security->getUser() instanceof User) {
throw new AccessDeniedException();
if (!$this->security->isGranted(AccompanyingPeriodVoter::REASSIGN_BULK)) {
throw new AccessDeniedHttpException('no right to reassign bulk');
}
$form = $this->buildFilterForm();
@@ -60,7 +62,7 @@ class ReassignAccompanyingPeriodController extends AbstractController
$userFrom = $form['user']->getData();
$postalCodes = $form['postal_code']->getData() instanceof PostalCode ? [$form['postal_code']->getData()] : [];
$total = $this->accompanyingPeriodACLAwareRepository->countByUserOpenedAccompanyingPeriod($userFrom);
$total = $this->accompanyingPeriodACLAwareRepository->countByUserAndPostalCodesOpenedAccompanyingPeriod($userFrom, $postalCodes);
$paginator = $this->paginatorFactory->create($total);
$paginator->setItemsPerPage(50);
$periods = $this->accompanyingPeriodACLAwareRepository

View File

@@ -16,15 +16,19 @@ use Chill\MainBundle\Pagination\PaginatorFactory;
use Chill\MainBundle\Serializer\Model\Collection;
use Chill\PersonBundle\Entity\SocialWork\SocialAction;
use Chill\PersonBundle\Repository\SocialWork\SocialIssueRepository;
use Symfony\Component\Clock\ClockInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use function count;
class SocialWorkSocialActionApiController extends ApiController
final class SocialWorkSocialActionApiController extends ApiController
{
public function __construct(private readonly SocialIssueRepository $socialIssueRepository, private readonly PaginatorFactory $paginator)
{
public function __construct(
private readonly SocialIssueRepository $socialIssueRepository,
private readonly PaginatorFactory $paginator,
private readonly ClockInterface $clock,
) {
}
public function listBySocialIssueApi($id, Request $request)
@@ -36,7 +40,10 @@ class SocialWorkSocialActionApiController extends ApiController
throw $this->createNotFoundException('socialIssue not found');
}
$socialActions = $socialIssue->getRecursiveSocialActions()->toArray();
$socialActions = SocialAction::filterRemoveDeactivatedActions(
$socialIssue->getRecursiveSocialActions()->toArray(),
\DateTime::createFromImmutable($this->clock->now())
);
usort($socialActions, static fn (SocialAction $sa, SocialAction $sb) => $sa->getOrdering() <=> $sb->getOrdering());

View File

@@ -12,9 +12,11 @@ declare(strict_types=1);
namespace Chill\PersonBundle\Controller;
use Chill\MainBundle\Pagination\PaginatorFactory;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Repository\AccompanyingPeriodRepository;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
class UserAccompanyingPeriodController extends AbstractController
@@ -26,12 +28,24 @@ class UserAccompanyingPeriodController extends AbstractController
/**
* @Route("/{_locale}/person/accompanying-periods/my", name="chill_person_accompanying_period_user")
*/
public function listAction(Request $request)
public function listAction(Request $request): Response
{
$total = $this->accompanyingPeriodRepository->countBy(['user' => $this->getUser(), 'step' => ['CONFIRMED', 'CLOSED']]);
$active = $request->query->getBoolean('active', true);
$steps = match ($active) {
true => [
AccompanyingPeriod::STEP_CONFIRMED,
AccompanyingPeriod::STEP_CONFIRMED_INACTIVE_LONG,
AccompanyingPeriod::STEP_CONFIRMED_INACTIVE_SHORT,
],
false => [
AccompanyingPeriod::STEP_CLOSED,
]
};
$total = $this->accompanyingPeriodRepository->countBy(['user' => $this->getUser(), 'step' => $steps]);
$pagination = $this->paginatorFactory->create($total);
$accompanyingPeriods = $this->accompanyingPeriodRepository->findBy(
['user' => $this->getUser(), 'step' => ['CONFIRMED', 'CLOSED']],
['user' => $this->getUser(), 'step' => $steps],
['openingDate' => 'DESC'],
$pagination->getItemsPerPage(),
$pagination->getCurrentPageFirstItemNumber()
@@ -40,13 +54,14 @@ class UserAccompanyingPeriodController extends AbstractController
return $this->render('@ChillPerson/AccompanyingPeriod/user_periods_list.html.twig', [
'accompanyingPeriods' => $accompanyingPeriods,
'pagination' => $pagination,
'active' => $active,
]);
}
/**
* @Route("/{_locale}/person/accompanying-periods/my/drafts", name="chill_person_accompanying_period_draft_user")
*/
public function listDraftsAction(Request $request)
public function listDraftsAction(): Response
{
$total = $this->accompanyingPeriodRepository->countBy(['user' => $this->getUser(), 'step' => 'DRAFT']);
$pagination = $this->paginatorFactory->create($total);

View File

@@ -15,6 +15,7 @@ use Chill\MainBundle\DependencyInjection\MissingBundleException;
use Chill\MainBundle\Security\Authorization\ChillExportVoter;
use Chill\PersonBundle\Controller\HouseholdCompositionTypeApiController;
use Chill\PersonBundle\Doctrine\DQL\AddressPart;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodCommentVoter;
use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodResourceVoter;
use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter;
@@ -982,11 +983,8 @@ class ChillPersonExtension extends Extension implements PrependExtensionInterfac
AccompanyingPeriodVoter::EDIT,
AccompanyingPeriodVoter::DELETE,
],
AccompanyingPeriodVoter::REASSIGN_BULK => [
AccompanyingPeriodVoter::CONFIDENTIAL_CRUD,
],
AccompanyingPeriodVoter::TOGGLE_CONFIDENTIAL => [
AccompanyingPeriodVoter::CONFIDENTIAL_CRUD,
AccompanyingPeriodVoter::TOGGLE_CONFIDENTIAL_ALL => [
AccompanyingPeriodVoter::SEE_CONFIDENTIAL_ALL,
],
],
]);
@@ -1010,18 +1008,42 @@ class ChillPersonExtension extends Extension implements PrependExtensionInterfac
],
'initial_marking' => 'DRAFT',
'places' => [
'DRAFT',
'CONFIRMED',
'CLOSED',
AccompanyingPeriod::STEP_DRAFT,
AccompanyingPeriod::STEP_CONFIRMED,
AccompanyingPeriod::STEP_CONFIRMED_INACTIVE_SHORT,
AccompanyingPeriod::STEP_CONFIRMED_INACTIVE_LONG,
AccompanyingPeriod::STEP_CLOSED,
],
'transitions' => [
'confirm' => [
'from' => 'DRAFT',
'to' => 'CONFIRMED',
'from' => AccompanyingPeriod::STEP_DRAFT,
'to' => AccompanyingPeriod::STEP_CONFIRMED,
],
'mark_inactive_short' => [
'from' => AccompanyingPeriod::STEP_CONFIRMED,
'to' => AccompanyingPeriod::STEP_CONFIRMED_INACTIVE_SHORT,
],
'mark_inactive_long' => [
'from' => [
AccompanyingPeriod::STEP_CONFIRMED,
AccompanyingPeriod::STEP_CONFIRMED_INACTIVE_SHORT
],
'to' => AccompanyingPeriod::STEP_CONFIRMED_INACTIVE_LONG,
],
'mark_active' => [
'from' => [
AccompanyingPeriod::STEP_CONFIRMED_INACTIVE_LONG,
AccompanyingPeriod::STEP_CONFIRMED_INACTIVE_SHORT,
],
'to' => AccompanyingPeriod::STEP_CONFIRMED
],
'close' => [
'from' => 'CONFIRMED',
'to' => 'CLOSED',
'from' => [
AccompanyingPeriod::STEP_CONFIRMED,
AccompanyingPeriod::STEP_CONFIRMED_INACTIVE_SHORT,
AccompanyingPeriod::STEP_CONFIRMED_INACTIVE_LONG,
],
'to' => AccompanyingPeriod::STEP_CLOSED,
],
],
],

View File

@@ -128,6 +128,15 @@ class Configuration implements ConfigurationInterface
->info('Can we have more than one simultaneous accompanying period in the same time. Default false.')
->defaultValue(false)
->end()
->arrayNode('accompanying_period_lifecycle_delays')
->addDefaultsIfNotSet()
->info('Delays before marking an accompanying period as inactive')
->children()
->booleanNode('mark_inactive')->defaultTrue()->end()
->scalarNode('mark_inactive_short_after')->defaultValue('P6M')->end()
->scalarNode('mark_inactive_long_after')->defaultValue('P2Y')->end()
->end()
->end() // end of 'accompanying_period_lifecycle_delays
->end() // children of 'root', parent = root
;

View File

@@ -11,6 +11,7 @@ declare(strict_types=1);
namespace Chill\PersonBundle\Entity;
use Chill\CalendarBundle\Entity\Calendar;
use Chill\MainBundle\Doctrine\Model\TrackCreationInterface;
use Chill\MainBundle\Doctrine\Model\TrackUpdateInterface;
use Chill\MainBundle\Entity\Address;
@@ -43,6 +44,7 @@ use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\Common\Collections\Criteria;
use Doctrine\Common\Collections\ReadableCollection;
use Doctrine\Common\Collections\Selectable;
use Doctrine\ORM\Mapping as ORM;
use Iterator;
use LogicException;
@@ -109,6 +111,24 @@ class AccompanyingPeriod implements
*/
final public const STEP_CONFIRMED = 'CONFIRMED';
/**
* Mark an accompanying period as confirmed, but inactive
*
* this means that the accompanying period **is**
* confirmed, but no activity (Activity, AccompanyingPeriod, ...)
* has been associated, or updated, within this accompanying period.
*/
final public const STEP_CONFIRMED_INACTIVE_SHORT = 'CONFIRMED_INACTIVE_SHORT';
/**
* Mark an accompanying period as confirmed, but inactive
*
* this means that the accompanying period **is**
* confirmed, but no activity (Activity, AccompanyingPeriod, ...)
* has been associated, or updated, within this accompanying period.
*/
final public const STEP_CONFIRMED_INACTIVE_LONG = 'CONFIRMED_INACTIVE_LONG';
/**
* Mark an accompanying period as "draft".
*
@@ -132,13 +152,12 @@ class AccompanyingPeriod implements
private ?Location $administrativeLocation = null;
/**
* @var Collection&Selectable<int, Calendar>
* @ORM\OneToMany(targetEntity="Chill\CalendarBundle\Entity\Calendar", mappedBy="accompanyingPeriod")
*/
private Collection $calendars;
private Collection&Selectable $calendars;
/**
* @var DateTime
*
* @ORM\Column(type="date", nullable=true)
* @Groups({"read", "write", "docgen:read"})
* @Assert\NotBlank(groups={AccompanyingPeriod::STEP_CLOSED})
@@ -167,7 +186,9 @@ class AccompanyingPeriod implements
* cascade={"persist", "remove"},
* orphanRemoval=true
* )
* @ORM\OrderBy({"createdAt": "DESC", "id": "DESC"})
* @Assert\NotBlank(groups={AccompanyingPeriod::STEP_DRAFT})
* @var Collection<Comment>
*/
private Collection $comments;
@@ -222,6 +243,7 @@ class AccompanyingPeriod implements
private ?UserJob $job = null;
/**
* @var Collection<AccompanyingPeriodLocationHistory>
* @ORM\OneToMany(targetEntity=AccompanyingPeriodLocationHistory::class,
* mappedBy="period", cascade={"persist", "remove"}, orphanRemoval=true)
*/
@@ -251,6 +273,7 @@ class AccompanyingPeriod implements
* cascade={"persist", "refresh", "remove", "merge", "detach"})
* @Groups({"read", "docgen:read"})
* @ParticipationOverlap(groups={AccompanyingPeriod::STEP_DRAFT, AccompanyingPeriod::STEP_CONFIRMED})
* @var Collection<AccompanyingPeriodParticipation>
*/
private Collection $participations;
@@ -299,6 +322,7 @@ class AccompanyingPeriod implements
private ?ThirdParty $requestorThirdParty = null;
/**
* @var Collection<Resource>
* @ORM\OneToMany(
* targetEntity="Chill\PersonBundle\Entity\AccompanyingPeriod\Resource",
* mappedBy="accompanyingPeriod",
@@ -311,6 +335,7 @@ class AccompanyingPeriod implements
private Collection $resources;
/**
* @var Collection<Scope>
* @ORM\ManyToMany(
* targetEntity=Scope::class,
* cascade={}
@@ -326,6 +351,7 @@ class AccompanyingPeriod implements
private Collection $scopes;
/**
* @var Collection<SocialIssue>
* @ORM\ManyToMany(
* targetEntity=SocialIssue::class
* )
@@ -340,10 +366,12 @@ class AccompanyingPeriod implements
/**
* @ORM\Column(type="string", length=32, nullable=true)
* @Groups({"read"})
* @var AccompanyingPeriod::STEP_*
*/
private string $step = self::STEP_DRAFT;
/**
* @var Collection<AccompanyingPeriodStepHistory>
* @ORM\OneToMany(targetEntity=AccompanyingPeriodStepHistory::class,
* mappedBy="period", cascade={"persist", "remove"}, orphanRemoval=true)
*/
@@ -372,7 +400,7 @@ class AccompanyingPeriod implements
* @ORM\OneToMany(targetEntity=UserHistory::class, mappedBy="accompanyingPeriod", orphanRemoval=true,
* cascade={"persist", "remove"})
*
* @var Collection|UserHistory[]
* @var Collection<UserHistory>
*/
private Collection $userHistories;
@@ -386,6 +414,7 @@ class AccompanyingPeriod implements
private ?User $userPrevious = null;
/**
* @var Collection<AccompanyingPeriodWork>
* @ORM\OneToMany(
* targetEntity=AccompanyingPeriodWork::class,
* mappedBy="accompanyingPeriod"
@@ -686,10 +715,11 @@ class AccompanyingPeriod implements
->comments
->filter(
static fn (Comment $c): bool => $c !== $pinnedComment
);
)
;
}
public function getCreatedAt(): ?DateTime
public function getCreatedAt(): DateTimeInterface
{
return $this->createdAt;
}
@@ -712,11 +742,9 @@ class AccompanyingPeriod implements
if ($this->getStep() === self::STEP_DRAFT) {
return [[self::STEP_DRAFT]];
}
if ($this->getStep() === self::STEP_CONFIRMED) {
if (str_starts_with($this->getStep(), 'CONFIRM')) {
return [[self::STEP_DRAFT, self::STEP_CONFIRMED]];
}
if ($this->getStep() === self::STEP_CLOSED) {
return [[self::STEP_DRAFT, self::STEP_CONFIRMED, self::STEP_CLOSED]];
}
@@ -765,7 +793,7 @@ class AccompanyingPeriod implements
}
/**
* @return Collection|AccompanyingPeriodLocationHistory[]
* @return Collection<AccompanyingPeriodLocationHistory>
*/
public function getLocationHistories(): Collection
{
@@ -776,6 +804,7 @@ class AccompanyingPeriod implements
* Get where the location is.
*
* @Groups({"read"})
* @return 'person'|'address'|'none'
*/
public function getLocationStatus(): string
{
@@ -790,7 +819,7 @@ class AccompanyingPeriod implements
return 'none';
}
public function getNextCalendarsForPerson(Person $person, $limit = 5): Collection
public function getNextCalendarsForPerson(Person $person, $limit = 5): ReadableCollection
{
$today = new DateTimeImmutable('today');
$criteria = Criteria::create()
@@ -853,6 +882,7 @@ class AccompanyingPeriod implements
/**
* Get Participations Collection.
* @return Collection<AccompanyingPeriodParticipation>
*/
public function getParticipations(): Collection
{
@@ -1186,6 +1216,7 @@ class AccompanyingPeriod implements
$this->addressLocation = $addressLocation;
if (null !== $addressLocation) {
$this->setPersonLocation(null);
$locationHistory = new AccompanyingPeriodLocationHistory();
$locationHistory
->setStartDate(new DateTimeImmutable('now'))
@@ -1303,6 +1334,7 @@ class AccompanyingPeriod implements
$this->personLocation = $person;
if (null !== $person) {
$this->setAddressLocation(null);
$locationHistory = new AccompanyingPeriodLocationHistory();
$locationHistory
->setStartDate(new DateTimeImmutable('now'))

View File

@@ -0,0 +1,79 @@
<?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\PersonBundle\Entity\AccompanyingPeriod;
use Chill\MainBundle\Entity\User;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Doctrine\ORM\Mapping as ORM;
/**
* Informations about AccompanyingPeriod
*
* This entity allow access to some basic information about the AccompanyingPeriod. It is
* populated from a SQL view, dynamically build from various sources.
*
* Usage:
*
* - get the user involved with an accompanying period
*
* @ORM\Entity()
* @ORM\Table(name="view_chill_person_accompanying_period_info")
*/
class AccompanyingPeriodInfo
{
public function __construct(
/**
* @var AccompanyingPeriod
* @ORM\ManyToOne(targetEntity=AccompanyingPeriod::class)
*/
public readonly AccompanyingPeriod $accompanyingPeriod,
/**
* @var string
* @ORM\Column(type="text")
* @ORM\Id
*/
public readonly string $relatedEntity,
/**
* @var int
* @ORM\Column(type="integer")
* @ORM\Id
*/
public readonly int $relatedEntityId,
/**
* @var User
* @ORM\ManyToOne(targetEntity=User::class)
*/
public readonly ?User $user,
/**
* @var \DateTimeImmutable
* @ORM\Column(type="datetime_immutable")
*/
public readonly \DateTimeImmutable $infoDate,
/**
* @var array
* @ORM\Column(type="json")
*/
public readonly array $metadata,
/**
* @var string
* @ORM\Column(type="text")
*/
public readonly string $discriminator,
) {
}
}

View File

@@ -59,6 +59,7 @@ class AccompanyingPeriodWork implements AccompanyingPeriodLinkedWithSocialIssues
* )
* @Serializer\Groups({"read", "docgen:read"})
* @ORM\OrderBy({"startDate": "DESC", "id": "DESC"})
* @var Collection<AccompanyingPeriodWorkEvaluation>
*
* @internal /!\ the serialization for write evaluations is handled in `AccompanyingPeriodWorkDenormalizer`
*/
@@ -101,6 +102,7 @@ class AccompanyingPeriodWork implements AccompanyingPeriodLinkedWithSocialIssues
private ?DateTimeImmutable $endDate = null;
/**
* @var Collection<AccompanyingPeriodWorkGoal>
* @ORM\OneToMany(
* targetEntity=AccompanyingPeriodWorkGoal::class,
* mappedBy="accompanyingPeriodWork",
@@ -136,6 +138,7 @@ class AccompanyingPeriodWork implements AccompanyingPeriodLinkedWithSocialIssues
private string $note = '';
/**
* @var Collection<Person>
* @ORM\ManyToMany(targetEntity=Person::class)
* @ORM\JoinTable(name="chill_person_accompanying_period_work_person")
* @Serializer\Groups({"read", "docgen:read", "read:accompanyingPeriodWork:light"})
@@ -151,6 +154,7 @@ class AccompanyingPeriodWork implements AccompanyingPeriodLinkedWithSocialIssues
private PrivateCommentEmbeddable $privateComment;
/**
* @var Collection<User>
* @ORM\ManyToMany(targetEntity=User::class)
* @ORM\JoinTable(name="chill_person_accompanying_period_work_referrer")
* @Serializer\Groups({"read", "docgen:read", "read:accompanyingPeriodWork:light"})
@@ -160,6 +164,7 @@ class AccompanyingPeriodWork implements AccompanyingPeriodLinkedWithSocialIssues
private Collection $referrers;
/**
* @var Collection<Result>
* @ORM\ManyToMany(targetEntity=Result::class, inversedBy="accompanyingPeriodWorks")
* @ORM\JoinTable(name="chill_person_accompanying_period_work_result")
* @Serializer\Groups({"read", "docgen:read"})
@@ -184,6 +189,7 @@ class AccompanyingPeriodWork implements AccompanyingPeriodLinkedWithSocialIssues
private ?DateTimeImmutable $startDate = null;
/**
* @var Collection<ThirdParty>
* @ORM\ManyToMany(targetEntity=ThirdParty::class)
* @ORM\JoinTable(name="chill_person_accompanying_period_work_third_party")
*
@@ -278,6 +284,9 @@ class AccompanyingPeriodWork implements AccompanyingPeriodLinkedWithSocialIssues
return $this->accompanyingPeriod;
}
/**
* @return Collection<AccompanyingPeriodWorkEvaluation>
*/
public function getAccompanyingPeriodWorkEvaluations(): Collection
{
return $this->accompanyingPeriodWorkEvaluations;

View File

@@ -79,6 +79,7 @@ class AccompanyingPeriodWorkEvaluation implements TrackCreationInterface, TrackU
* )
* @ORM\OrderBy({"createdAt": "DESC", "id": "DESC"})
* @Serializer\Groups({"read"})
* @var Collection<AccompanyingPeriodWorkEvaluationDocument>
*/
private Collection $documents;
@@ -204,7 +205,7 @@ class AccompanyingPeriodWorkEvaluation implements TrackCreationInterface, TrackU
}
/**
* @return Collection
* @return Collection<AccompanyingPeriodWorkEvaluationDocument>
*/
public function getDocuments()
{

View File

@@ -59,6 +59,7 @@ class AccompanyingPeriodWorkGoal
private ?string $note = null;
/**
* @var Collection<Result>
* @ORM\ManyToMany(targetEntity=Result::class, inversedBy="accompanyingPeriodWorkGoals")
* @ORM\JoinTable(name="chill_person_accompanying_period_work_goal_result")
* @Serializer\Groups({"accompanying_period_work:edit"})

View File

@@ -32,6 +32,7 @@ class ClosingMotive
/**
* Child Accompanying periods.
*
* @var Collection<ClosingMotive>
* @ORM\OneToMany(
* targetEntity="Chill\PersonBundle\Entity\AccompanyingPeriod\ClosingMotive",
* mappedBy="parent")

View File

@@ -23,6 +23,7 @@ use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\Common\Collections\Criteria;
use Doctrine\Common\Collections\ReadableCollection;
use Doctrine\Common\Collections\Selectable;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Annotation as Serializer;
use Symfony\Component\Validator\Constraints as Assert;
@@ -45,6 +46,7 @@ class Household
/**
* Addresses.
*
* @var Collection<Address>
* @ORM\ManyToMany(
* targetEntity="Chill\MainBundle\Entity\Address",
* cascade={"persist", "remove", "merge", "detach"})
@@ -60,6 +62,7 @@ class Household
private CommentEmbeddable $commentMembers;
/**
* @var Collection&Selectable<int, HouseholdComposition>
* @ORM\OneToMany(
* targetEntity=HouseholdComposition::class,
* mappedBy="household",
@@ -69,7 +72,7 @@ class Household
* @ORM\OrderBy({"startDate": "DESC"})
* @Assert\Valid(traverse=true, groups={"household_composition"})
*/
private Collection $compositions;
private Collection&Selectable $compositions;
/**
* @ORM\Id
@@ -80,6 +83,7 @@ class Household
private ?int $id = null;
/**
* @var Collection<HouseholdMember>
* @ORM\OneToMany(
* targetEntity=HouseholdMember::class,
* mappedBy="household"
@@ -146,9 +150,9 @@ class Household
*
* @Assert\Callback(methods={"validate"})
*
* @return \Chill\MainBundle\Entity\Address[]
* @return Collection<Address>
*/
public function getAddresses()
public function getAddresses(): Collection
{
return $this->addresses;
}
@@ -213,6 +217,10 @@ class Household
return null;
}
/**
* @Serializer\Groups({"docgen:read"})
* @Serializer\SerializedName("current_composition")
*/
public function getCurrentComposition(?DateTimeImmutable $at = null): ?HouseholdComposition
{
$at ??= new DateTimeImmutable('today');

View File

@@ -44,6 +44,7 @@ class HouseholdComposition implements TrackCreationInterface, TrackUpdateInterfa
/**
* @ORM\Column(type="date_immutable", nullable=true, options={"default": null})
* @Assert\GreaterThanOrEqual(propertyPath="startDate", groups={"Default", "household_composition"})
* @Serializer\Groups({"docgen:read"})
*/
private ?DateTimeImmutable $endDate = null;
@@ -56,6 +57,7 @@ class HouseholdComposition implements TrackCreationInterface, TrackUpdateInterfa
/**
* @ORM\ManyToOne(targetEntity=HouseholdCompositionType::class)
* @ORM\JoinColumn(nullable=false)
* @Serializer\Groups({"docgen:read"})
*/
private ?HouseholdCompositionType $householdCompositionType = null;
@@ -71,12 +73,14 @@ class HouseholdComposition implements TrackCreationInterface, TrackUpdateInterfa
* @ORM\Column(type="integer", nullable=true, options={"default": null})
* @Assert\NotNull
* @Assert\GreaterThanOrEqual(0, groups={"Default", "household_composition"})
* @Serializer\Groups({"docgen:read"})
*/
private ?int $numberOfChildren = null;
/**
* @ORM\Column(type="date_immutable", nullable=false)
* @Assert\NotNull(groups={"Default", "household_composition"})
* @Serializer\Groups({"docgen:read"})
*/
private ?DateTimeImmutable $startDate = null;

View File

@@ -14,6 +14,7 @@ namespace Chill\PersonBundle\Entity;
use ArrayIterator;
use Chill\BudgetBundle\Entity\Charge;
use Chill\BudgetBundle\Entity\Resource;
use Chill\CalendarBundle\Entity\Calendar;
use Chill\MainBundle\Doctrine\Model\TrackCreationInterface;
use Chill\MainBundle\Doctrine\Model\TrackUpdateInterface;
use Chill\MainBundle\Entity\Address;
@@ -22,6 +23,7 @@ use Chill\MainBundle\Entity\Civility;
use Chill\MainBundle\Entity\Country;
use Chill\MainBundle\Entity\Embeddable\CommentEmbeddable;
use Chill\MainBundle\Entity\HasCenterInterface;
use Chill\MainBundle\Entity\Language;
use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Validation\Constraint\PhonenumberConstraint;
use Chill\PersonBundle\Entity\Household\Household;
@@ -110,14 +112,14 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI
/**
* The person's accompanying periods (when the person was accompanied by the center).
*
* @var Collection
* @var Collection<AccompanyingPeriodParticipation>
*
* @ORM\OneToMany(targetEntity=AccompanyingPeriodParticipation::class,
* mappedBy="person",
* cascade={"persist", "remove", "merge", "detach"})
* @ORM\OrderBy({"startDate": "DESC"})
*/
private $accompanyingPeriodParticipations;
private Collection $accompanyingPeriodParticipations;
/**
* The accompanying period requested by the Person.
@@ -125,14 +127,14 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI
* @ORM\OneToMany(targetEntity=AccompanyingPeriod::class,
* mappedBy="requestorPerson")
*
* @var AccompanyingPeriod[]|Collection
* @var Collection<AccompanyingPeriod>
*/
private Collection $accompanyingPeriodRequested;
/**
* Addresses.
*
* @var Collection
* @var Collection<Address>
*
* @ORM\ManyToMany(
* targetEntity="Chill\MainBundle\Entity\Address",
@@ -140,10 +142,10 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI
* @ORM\JoinTable(name="chill_person_persons_to_addresses")
* @ORM\OrderBy({"validFrom": "DESC"})
*/
private $addresses;
private Collection $addresses;
/**
* @var Collection
* @var Collection<PersonAltName>
*
* @ORM\OneToMany(
* targetEntity="Chill\PersonBundle\Entity\PersonAltName",
@@ -151,7 +153,7 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI
* cascade={"persist", "remove", "merge", "detach"},
* orphanRemoval=true)
*/
private $altNames;
private Collection $altNames;
/**
* The person's birthdate.
@@ -164,6 +166,7 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI
private $birthdate;
/**
* @var Collection<Charge>
* @ORM\OneToMany(
* targetEntity=Charge::class,
* mappedBy="person"
@@ -172,6 +175,7 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI
private Collection $budgetCharges;
/**
* @var Collection<Resource>
* @ORM\OneToMany(
* targetEntity=Resource::class,
* mappedBy="person"
@@ -180,6 +184,7 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI
private Collection $budgetResources;
/**
* @var Collection<Calendar>
* @ORM\ManyToMany(
* targetEntity="Chill\CalendarBundle\Entity\Calendar",
* mappedBy="persons"
@@ -204,7 +209,7 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI
/**
* @ORM\OneToMany(targetEntity=PersonCenterHistory::class, mappedBy="person", cascade={"persist"})
*
* @var Collection|PersonCenterHistory[]
* @var Collection<PersonCenterHistory>
*/
private Collection $centerHistory;
@@ -342,6 +347,7 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI
/**
* Read-only field, computed by the database.
*
* @var Collection<PersonHouseholdAddress>
* @ORM\OneToMany(
* targetEntity=PersonHouseholdAddress::class,
* mappedBy="person"
@@ -355,7 +361,7 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI
* mappedBy="person"
* )
*
* @var Collection|HouseholdMember[]
* @var Collection<HouseholdMember>
*/
private Collection $householdParticipations;
@@ -446,7 +452,7 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI
private ?int $numberOfChildren = null;
/**
* @var Collection
* @var Collection<PersonPhone>
*
* @ORM\OneToMany(
* targetEntity="Chill\PersonBundle\Entity\PersonPhone",
@@ -458,13 +464,14 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI
* traverse=true,
* )
*/
private $otherPhoneNumbers;
private Collection $otherPhoneNumbers;
/**
* @ORM\OneToMany(
* targetEntity=AccompanyingPeriod::class,
* mappedBy="personLocation"
* )
* @var Collection<AccompanyingPeriod>
*/
private Collection $periodLocatedOn;
@@ -499,14 +506,14 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI
/**
* @ORM\OneToMany(targetEntity=PersonResource::class, mappedBy="personOwner")
*
* @var Collection|PersonResource[];
* @var Collection<PersonResource>
*/
private Collection $resources;
/**
* The person's spoken languages.
*
* @var ArrayCollection
* @var Collection<Language>
*
* @ORM\ManyToMany(targetEntity="Chill\MainBundle\Entity\Language")
* @ORM\JoinTable(
@@ -515,7 +522,7 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI
* inverseJoinColumns={@ORM\JoinColumn(name="language_id", referencedColumnName="id")}
* )
*/
private $spokenLanguages;
private Collection $spokenLanguages;
/**
* @ORM\Column(type="datetime", nullable=true, options={"default": NULL})
@@ -1361,7 +1368,7 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI
/**
* Get spokenLanguages.
*
* @return ArrayCollection
* @return Collection<Language>
*/
public function getSpokenLanguages()
{

View File

@@ -45,7 +45,7 @@ class PersonCenterCurrent
private ?int $id = null;
/**
* @ORM\ManyToOne(targetEntity=Person::class, inversedBy="centerCurrent")
* @ORM\OneToOne(targetEntity=Person::class, inversedBy="centerCurrent")
*/
private Person $person;

View File

@@ -52,6 +52,7 @@ class Evaluation
private ?DateInterval $notificationDelay = null;
/**
* @var Collection<SocialAction>
* @ORM\ManyToMany(
* targetEntity=SocialAction::class,
* mappedBy="evaluations"
@@ -132,7 +133,7 @@ class Evaluation
public function removeSocialAction(SocialAction $socialAction): self
{
if ($this->socialActions->contains($socialAction)) {
$this->socialActions->remove($socialAction);
$this->socialActions->removeElement($socialAction);
}
return $this;

View File

@@ -43,12 +43,14 @@ class Goal
private ?int $id = null;
/**
* @var Collection<Result>
* @ORM\ManyToMany(targetEntity=Result::class, inversedBy="goals")
* @ORM\JoinTable(name="chill_person_social_work_goal_result")
*/
private Collection $results;
/**
* @var Collection<SocialAction>
* @ORM\ManyToMany(targetEntity=SocialAction::class, mappedBy="goals")
*/
private Collection $socialActions;

View File

@@ -33,11 +33,13 @@ use Symfony\Component\Serializer\Annotation as Serializer;
class Result
{
/**
* @var Collection<AccompanyingPeriodWorkGoal>
* @ORM\ManyToMany(targetEntity=AccompanyingPeriodWorkGoal::class, mappedBy="results")
*/
private Collection $accompanyingPeriodWorkGoals;
/**
* @var Collection<AccompanyingPeriodWork>
* @ORM\ManyToMany(targetEntity=AccompanyingPeriodWork::class, mappedBy="results")
*/
private Collection $accompanyingPeriodWorks;
@@ -48,6 +50,7 @@ class Result
private ?DateTime $desactivationDate = null;
/**
* @var Collection<Goal>
* @ORM\ManyToMany(targetEntity=Goal::class, mappedBy="results")
*/
private Collection $goals;
@@ -61,6 +64,7 @@ class Result
private ?int $id = null;
/**
* @var Collection<SocialAction>
* @ORM\ManyToMany(targetEntity=SocialAction::class, mappedBy="results")
*/
private Collection $socialActions;

View File

@@ -15,6 +15,7 @@ use DateInterval;
use DateTimeInterface;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\Common\Collections\ReadableCollection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Annotation as Serializer;
@@ -31,9 +32,10 @@ use Symfony\Component\Serializer\Annotation as Serializer;
class SocialAction
{
/**
* @var Collection<SocialAction>
* @ORM\OneToMany(targetEntity=SocialAction::class, mappedBy="parent")
*/
private $children;
private Collection $children;
/**
* @ORM\Column(type="dateinterval", nullable=true)
@@ -46,16 +48,18 @@ class SocialAction
private $desactivationDate;
/**
* @var Collection<Evaluation>
* @ORM\ManyToMany(targetEntity=Evaluation::class, inversedBy="socialActions")
* @ORM\JoinTable(name="chill_person_social_work_evaluation_action")
*/
private Collection $evaluations;
/**
* @var Collection<Goal>
* @ORM\ManyToMany(targetEntity=Goal::class, inversedBy="socialActions")
* @ORM\JoinTable(name="chill_person_social_action_goal")
*/
private $goals;
private Collection $goals;
/**
* @ORM\Id
@@ -80,10 +84,11 @@ class SocialAction
private $parent;
/**
* @var Collection<Result>
* @ORM\ManyToMany(targetEntity=Result::class, inversedBy="socialActions")
* @ORM\JoinTable(name="chill_person_social_action_result")
*/
private $results;
private Collection $results;
/**
* @ORM\Column(type="json")
@@ -295,6 +300,19 @@ class SocialAction
return 0 < $this->getChildren()->count();
}
public function isDesactivated(\DateTime $atDate): bool
{
if (null !== $this->desactivationDate && $this->desactivationDate < $atDate) {
return true;
}
if ($this->hasParent()) {
return $this->parent->isDesactivated($atDate);
}
return false;
}
public function hasParent(): bool
{
return $this->getParent() instanceof self;
@@ -401,4 +419,14 @@ class SocialAction
return $this;
}
public static function filterRemoveDeactivatedActions(ReadableCollection|array $actions, \DateTime $comparisonDate): ReadableCollection|array
{
$filterFn = fn (SocialAction $socialAction) => !$socialAction->isDesactivated($comparisonDate);
return match ($actions instanceof ReadableCollection) {
true => $actions->filter($filterFn),
false => array_filter($actions, $filterFn)
};
}
}

View File

@@ -28,9 +28,10 @@ use Symfony\Component\Serializer\Annotation\Groups;
class SocialIssue
{
/**
* @var Collection<SocialIssue>
* @ORM\OneToMany(targetEntity=SocialIssue::class, mappedBy="parent")
*/
private $children;
private Collection $children;
/**
* @ORM\Column(type="datetime", nullable=true)
@@ -55,9 +56,10 @@ class SocialIssue
private $parent;
/**
* @var Collection<SocialAction>
* @ORM\OneToMany(targetEntity=SocialAction::class, mappedBy="issue")
*/
private $socialActions;
private Collection $socialActions;
/**
* @ORM\Column(type="json")
@@ -90,7 +92,7 @@ class SocialIssue
{
if (!$this->socialActions->contains($socialAction)) {
$this->socialActions[] = $socialAction;
$socialAction->setSocialIssue($this);
$socialAction->setIssue($this);
}
return $this;
@@ -252,7 +254,7 @@ class SocialIssue
}
/**
* @return Collection|SocialAction[] All the descendant social actions of all
* @return Collection<SocialAction> All the descendant social actions of all
* the descendants of the entity
*/
public function getRecursiveSocialActions(): Collection
@@ -271,7 +273,7 @@ class SocialIssue
}
/**
* @return Collection|SocialAction[]
* @return Collection<SocialAction>
*/
public function getSocialActions(): Collection
{
@@ -326,8 +328,8 @@ class SocialIssue
{
if ($this->socialActions->removeElement($socialAction)) {
// set the owning side to null (unless already changed)
if ($socialAction->getSocialIssue() === $this) {
$socialAction->setSocialIssue(null);
if ($socialAction->getIssue() === $this) {
$socialAction->setIssue(null);
}
}

View File

@@ -49,6 +49,10 @@ class AdministrativeLocationAggregator implements AggregatorInterface
{
// no form
}
public function getFormDefaultData(): array
{
return [];
}
public function getLabels($key, array $values, $data)
{

View File

@@ -39,6 +39,10 @@ class ByActionNumberAggregator implements AggregatorInterface
{
// No form needed
}
public function getFormDefaultData(): array
{
return [];
}
public function getLabels($key, array $values, $data)
{

View File

@@ -44,6 +44,10 @@ class ClosingMotiveAggregator implements AggregatorInterface
{
// no form
}
public function getFormDefaultData(): array
{
return [];
}
public function getLabels($key, array $values, $data)
{

View File

@@ -44,6 +44,10 @@ class ConfidentialAggregator implements AggregatorInterface
{
// no form
}
public function getFormDefaultData(): array
{
return [];
}
public function getLabels($key, array $values, $data)
{

View File

@@ -49,6 +49,10 @@ class CreatorJobAggregator implements AggregatorInterface
{
// No form needed
}
public function getFormDefaultData(): array
{
return [];
}
public function getLabels($key, array $values, $data)
{

View File

@@ -68,6 +68,10 @@ final readonly class DurationAggregator implements AggregatorInterface
'expanded' => true,
]);
}
public function getFormDefaultData(): array
{
return [];
}
public function getLabels($key, array $values, $data)
{

View File

@@ -44,6 +44,10 @@ class EmergencyAggregator implements AggregatorInterface
{
// no form
}
public function getFormDefaultData(): array
{
return [];
}
public function getLabels($key, array $values, $data)
{

View File

@@ -53,6 +53,10 @@ final readonly class EvaluationAggregator implements AggregatorInterface
{
// no form
}
public function getFormDefaultData(): array
{
return [];
}
public function getLabels($key, array $values, $data)
{

View File

@@ -86,7 +86,7 @@ final readonly class GeographicalUnitStatAggregator implements AggregatorInterfa
'acp_geog_units'
);
$qb->andWhere($qb->expr()->eq('acp_geog_units.layer', ':acp_geog_unit_layer'));
$qb->andWhere($qb->expr()->in('acp_geog_units.layer', ':acp_geog_unit_layer'));
$qb->setParameter('acp_geog_unit_layer', $data['level']);
@@ -109,7 +109,6 @@ final readonly class GeographicalUnitStatAggregator implements AggregatorInterfa
->add('date_calc', PickRollingDateType::class, [
'label' => 'Compute geographical location at date',
'required' => true,
'data' => new RollingDate(RollingDate::T_TODAY),
])
->add('level', EntityType::class, [
'label' => 'Geographical layer',
@@ -117,8 +116,14 @@ final readonly class GeographicalUnitStatAggregator implements AggregatorInterfa
'class' => GeographicalUnitLayer::class,
'choices' => $this->geographicalUnitLayerRepository->findAllHavingUnits(),
'choice_label' => fn (GeographicalUnitLayer $item) => $this->translatableStringHelper->localize($item->getName()),
'multiple' => true,
'expanded' => true,
]);
}
public function getFormDefaultData(): array
{
return ['date_calc' => new RollingDate(RollingDate::T_TODAY)];
}
public function getLabels($key, array $values, $data)
{

View File

@@ -44,6 +44,10 @@ class IntensityAggregator implements AggregatorInterface
{
// no form
}
public function getFormDefaultData(): array
{
return [];
}
public function getLabels($key, array $values, $data)
{

View File

@@ -0,0 +1,100 @@
<?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\PersonBundle\Export\Aggregator\AccompanyingCourseAggregators;
use Chill\MainBundle\Export\AggregatorInterface;
use Chill\MainBundle\Repository\UserJobRepositoryInterface;
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodInfo;
use Chill\PersonBundle\Export\Declarations;
use Doctrine\ORM\Query\Expr\Join;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface;
final readonly class JobWorkingOnCourseAggregator implements AggregatorInterface
{
private const COLUMN_NAME = 'user_working_on_course_job_id';
public function __construct(
private UserJobRepositoryInterface $userJobRepository,
private TranslatableStringHelperInterface $translatableStringHelper,
) {
}
public function buildForm(FormBuilderInterface $builder)
{
// nothing to add here
}
public function getFormDefaultData(): array
{
return [];
}
public function getLabels($key, array $values, $data): \Closure
{
return function (int|string|null $jobId) {
if (null === $jobId || '' === $jobId) {
return '';
}
if ('_header' === $jobId) {
return 'export.aggregator.course.by_job_working.job';
}
if (null === $job = $this->userJobRepository->find((int) $jobId)) {
return '';
}
return $this->translatableStringHelper->localize($job->getLabel());
};
}
public function getQueryKeys($data)
{
return [self::COLUMN_NAME];
}
public function getTitle()
{
return 'export.aggregator.course.by_job_working.title';
}
public function addRole(): ?string
{
return null;
}
public function alterQuery(QueryBuilder $qb, $data)
{
if (!in_array('acpinfo', $qb->getAllAliases(), true)) {
$qb->leftJoin(
AccompanyingPeriodInfo::class,
'acpinfo',
Join::WITH,
'acp.id = IDENTITY(acpinfo.accompanyingPeriod)'
);
}
if (!in_array('acpinfo_user', $qb->getAllAliases(), true)) {
$qb->leftJoin('acpinfo.user', 'acpinfo_user');
}
$qb->addSelect('IDENTITY(acpinfo_user.userJob) AS ' . self::COLUMN_NAME);
$qb->addGroupBy(self::COLUMN_NAME);
}
public function applyOn()
{
return Declarations::ACP_TYPE;
}
}

View File

@@ -56,6 +56,10 @@ final class OriginAggregator implements AggregatorInterface
{
// no form
}
public function getFormDefaultData(): array
{
return [];
}
public function getLabels($key, array $values, $data)
{

View File

@@ -69,11 +69,14 @@ final readonly class ReferrerAggregator implements AggregatorInterface
{
$builder
->add('date_calc', PickRollingDateType::class, [
'data' => new RollingDate(RollingDate::T_TODAY),
'label' => 'export.aggregator.course.by_referrer.Computation date for referrer',
'required' => true,
]);
}
public function getFormDefaultData(): array
{
return ['date_calc' => new RollingDate(RollingDate::T_TODAY)];
}
public function getLabels($key, array $values, $data)
{

View File

@@ -76,11 +76,14 @@ class ReferrerScopeAggregator implements AggregatorInterface
public function buildForm(FormBuilderInterface $builder)
{
$builder->add('date_calc', PickRollingDateType::class, [
'data' => new RollingDate(RollingDate::T_TODAY),
'label' => 'export.aggregator.course.by_user_scope.Computation date for referrer',
'required' => true,
]);
}
public function getFormDefaultData(): array
{
return ['date_calc' => new RollingDate(RollingDate::T_TODAY)];
}
public function getLabels($key, array $values, $data)
{

View File

@@ -65,6 +65,10 @@ final readonly class RequestorAggregator implements AggregatorInterface
{
// no form
}
public function getFormDefaultData(): array
{
return [];
}
public function getLabels($key, array $values, $data)
{

View File

@@ -49,6 +49,10 @@ final readonly class ScopeAggregator implements AggregatorInterface
{
// no form
}
public function getFormDefaultData(): array
{
return [];
}
public function getLabels($key, array $values, $data)
{

View File

@@ -0,0 +1,101 @@
<?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\PersonBundle\Export\Aggregator\AccompanyingCourseAggregators;
use Chill\MainBundle\Export\AggregatorInterface;
use Chill\MainBundle\Repository\ScopeRepositoryInterface;
use Chill\MainBundle\Repository\UserJobRepositoryInterface;
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodInfo;
use Chill\PersonBundle\Export\Declarations;
use Doctrine\ORM\Query\Expr\Join;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface;
final readonly class ScopeWorkingOnCourseAggregator implements AggregatorInterface
{
private const COLUMN_NAME = 'user_working_on_course_scope_id';
public function __construct(
private ScopeRepositoryInterface $scopeRepository,
private TranslatableStringHelperInterface $translatableStringHelper,
) {
}
public function buildForm(FormBuilderInterface $builder)
{
// nothing to add here
}
public function getFormDefaultData(): array
{
return [];
}
public function getLabels($key, array $values, $data): \Closure
{
return function (int|string|null $scopeId) {
if (null === $scopeId || '' === $scopeId) {
return '';
}
if ('_header' === $scopeId) {
return 'export.aggregator.course.by_scope_working.scope';
}
if (null === $scope = $this->scopeRepository->find((int) $scopeId)) {
return '';
}
return $this->translatableStringHelper->localize($scope->getName());
};
}
public function getQueryKeys($data)
{
return [self::COLUMN_NAME];
}
public function getTitle()
{
return 'export.aggregator.course.by_scope_working.title';
}
public function addRole(): ?string
{
return null;
}
public function alterQuery(QueryBuilder $qb, $data)
{
if (!in_array('acpinfo', $qb->getAllAliases(), true)) {
$qb->leftJoin(
AccompanyingPeriodInfo::class,
'acpinfo',
Join::WITH,
'acp.id = IDENTITY(acpinfo.accompanyingPeriod)'
);
}
if (!in_array('acpinfo_user', $qb->getAllAliases(), true)) {
$qb->leftJoin('acpinfo.user', 'acpinfo_user');
}
$qb->addSelect('IDENTITY(acpinfo_user.mainScope) AS ' . self::COLUMN_NAME);
$qb->addGroupBy(self::COLUMN_NAME);
}
public function applyOn()
{
return Declarations::ACP_TYPE;
}
}

View File

@@ -50,6 +50,10 @@ final readonly class SocialActionAggregator implements AggregatorInterface
{
// no form
}
public function getFormDefaultData(): array
{
return [];
}
public function getLabels($key, array $values, $data)
{

View File

@@ -50,6 +50,10 @@ final readonly class SocialIssueAggregator implements AggregatorInterface
{
// no form
}
public function getFormDefaultData(): array
{
return [];
}
public function getLabels($key, array $values, $data)
{

View File

@@ -68,9 +68,11 @@ final readonly class StepAggregator implements AggregatorInterface
public function buildForm(FormBuilderInterface $builder)
{
$builder->add('on_date', PickRollingDateType::class, [
'data' => new RollingDate(RollingDate::T_TODAY),
]);
$builder->add('on_date', PickRollingDateType::class, []);
}
public function getFormDefaultData(): array
{
return ['on_date' => new RollingDate(RollingDate::T_TODAY)];
}
public function getLabels($key, array $values, $data)
@@ -78,13 +80,19 @@ final readonly class StepAggregator implements AggregatorInterface
return function ($value): string {
switch ($value) {
case AccompanyingPeriod::STEP_DRAFT:
return $this->translator->trans('Draft');
return $this->translator->trans('course.draft');
case AccompanyingPeriod::STEP_CONFIRMED:
return $this->translator->trans('Confirmed');
return $this->translator->trans('course.confirmed');
case AccompanyingPeriod::STEP_CLOSED:
return $this->translator->trans('Closed');
return $this->translator->trans('course.closed');
case AccompanyingPeriod::STEP_CONFIRMED_INACTIVE_SHORT:
return $this->translator->trans('course.inactive_short');
case AccompanyingPeriod::STEP_CONFIRMED_INACTIVE_LONG:
return $this->translator->trans('course.inactive_long');
case '_header':
return 'Step';

View File

@@ -49,6 +49,10 @@ final readonly class UserJobAggregator implements AggregatorInterface
{
// no form
}
public function getFormDefaultData(): array
{
return [];
}
public function getLabels($key, array $values, $data)
{

View File

@@ -0,0 +1,100 @@
<?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\PersonBundle\Export\Aggregator\AccompanyingCourseAggregators;
use Chill\MainBundle\Export\AggregatorInterface;
use Chill\MainBundle\Repository\UserRepositoryInterface;
use Chill\MainBundle\Templating\Entity\UserRender;
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodInfo;
use Chill\PersonBundle\Export\Declarations;
use Doctrine\ORM\Query\Expr\Join;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface;
final readonly class UserWorkingOnCourseAggregator implements AggregatorInterface
{
private const COLUMN_NAME = 'user_working_on_course_user_id';
public function __construct(
private UserRender $userRender,
private UserRepositoryInterface $userRepository,
) {
}
public function buildForm(FormBuilderInterface $builder)
{
// nothing to add here
}
public function getFormDefaultData(): array
{
return [];
}
public function getLabels($key, array $values, $data): \Closure
{
return function (int|string|null $userId) {
if (null === $userId || '' === $userId) {
return '';
}
if ('_header' === $userId) {
return 'export.aggregator.course.by_user_working.user';
}
if (null === $user = $this->userRepository->find((int) $userId)) {
return '';
}
return $this->userRender->renderString($user, []);
};
}
public function getQueryKeys($data)
{
return [self::COLUMN_NAME];
}
public function getTitle()
{
return 'export.aggregator.course.by_user_working.title';
}
public function addRole(): ?string
{
return null;
}
public function alterQuery(QueryBuilder $qb, $data)
{
if (!in_array('acpinfo', $qb->getAllAliases(), true)) {
$qb->leftJoin(
AccompanyingPeriodInfo::class,
'acpinfo',
Join::WITH,
'acp.id = IDENTITY(acpinfo.accompanyingPeriod)'
);
}
if (!in_array('acpinfo_user', $qb->getAllAliases(), true)) {
$qb->leftJoin('acpinfo.user', 'acpinfo_user');
}
$qb->addSelect('acpinfo_user.id AS ' . self::COLUMN_NAME);
$qb->addGroupBy('acpinfo_user.id');
}
public function applyOn()
{
return Declarations::ACP_TYPE;
}
}

View File

@@ -19,7 +19,7 @@ use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
class ByEndDateAggregator implements AggregatorInterface
final class ByEndDateAggregator implements AggregatorInterface
{
private const CHOICES = [
'by week' => 'week',
@@ -60,9 +60,12 @@ class ByEndDateAggregator implements AggregatorInterface
'multiple' => false,
'expanded' => true,
'empty_data' => self::DEFAULT_CHOICE,
'data' => self::DEFAULT_CHOICE,
]);
}
public function getFormDefaultData(): array
{
return ['frequency' => self::DEFAULT_CHOICE];
}
public function getLabels($key, array $values, $data)
{

View File

@@ -19,7 +19,7 @@ use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
class ByMaxDateAggregator implements AggregatorInterface
final class ByMaxDateAggregator implements AggregatorInterface
{
private const CHOICES = [
'by week' => 'week',
@@ -60,9 +60,12 @@ class ByMaxDateAggregator implements AggregatorInterface
'multiple' => false,
'expanded' => true,
'empty_data' => self::DEFAULT_CHOICE,
'data' => self::DEFAULT_CHOICE,
]);
}
public function getFormDefaultData(): array
{
return ['frequency' => self::DEFAULT_CHOICE];
}
public function getLabels($key, array $values, $data)
{

View File

@@ -19,7 +19,7 @@ use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
class ByStartDateAggregator implements AggregatorInterface
final class ByStartDateAggregator implements AggregatorInterface
{
private const CHOICES = [
'by week' => 'week',
@@ -60,9 +60,12 @@ class ByStartDateAggregator implements AggregatorInterface
'multiple' => false,
'expanded' => true,
'empty_data' => self::DEFAULT_CHOICE,
'data' => self::DEFAULT_CHOICE,
]);
}
public function getFormDefaultData(): array
{
return ['frequency' => self::DEFAULT_CHOICE];
}
public function getLabels($key, array $values, $data)
{

View File

@@ -44,6 +44,10 @@ class EvaluationTypeAggregator implements AggregatorInterface
{
// no form
}
public function getFormDefaultData(): array
{
return [];
}
public function getLabels($key, array $values, $data)
{

View File

@@ -45,6 +45,10 @@ class HavingEndDateAggregator implements AggregatorInterface
{
// No form needed
}
public function getFormDefaultData(): array
{
return [];
}
public function getLabels($key, array $values, $data)
{

View File

@@ -64,9 +64,11 @@ class ChildrenNumberAggregator implements AggregatorInterface
public function buildForm(FormBuilderInterface $builder)
{
$builder->add('on_date', PickRollingDateType::class, [
'data' => new RollingDate(RollingDate::T_TODAY),
]);
$builder->add('on_date', PickRollingDateType::class, []);
}
public function getFormDefaultData(): array
{
return ['on_date' => new RollingDate(RollingDate::T_TODAY)];
}
public function getLabels($key, array $values, $data)

View File

@@ -65,9 +65,11 @@ class CompositionAggregator implements AggregatorInterface
public function buildForm(FormBuilderInterface $builder)
{
$builder->add('on_date', PickRollingDateType::class, [
'data' => new RollingDate(RollingDate::T_TODAY),
]);
$builder->add('on_date', PickRollingDateType::class, []);
}
public function getFormDefaultData(): array
{
return ['on_date' => new RollingDate(RollingDate::T_TODAY)];
}
public function getLabels($key, array $values, $data)

View File

@@ -13,17 +13,18 @@ namespace Chill\PersonBundle\Export\Aggregator\PersonAggregators;
use Chill\MainBundle\Export\AggregatorInterface;
use Chill\MainBundle\Export\ExportElementValidatedInterface;
use DateTime;
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\Extension\Core\Type\DateType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
final readonly class AgeAggregator implements AggregatorInterface, ExportElementValidatedInterface
{
public function __construct(private TranslatorInterface $translator)
{
public function __construct(
private RollingDateConverterInterface $rollingDateConverter,
) {
}
public function addRole(): ?string
@@ -34,7 +35,7 @@ final readonly class AgeAggregator implements AggregatorInterface, ExportElement
public function alterQuery(QueryBuilder $qb, $data)
{
$qb->addSelect('DATE_DIFF(:date_age_calculation, person.birthdate)/365 as person_age');
$qb->setParameter('date_age_calculation', $data['date_age_calculation']);
$qb->setParameter('date_age_calculation', $this->rollingDateConverter->convert($data['date_age_calculation']));
$qb->addGroupBy('person_age');
}
@@ -45,14 +46,14 @@ final readonly class AgeAggregator implements AggregatorInterface, ExportElement
public function buildForm(FormBuilderInterface $builder)
{
$builder->add('date_age_calculation', DateType::class, [
$builder->add('date_age_calculation', PickRollingDateType::class, [
'label' => 'Calculate age in relation to this date',
'data' => new DateTime(),
'attr' => ['class' => 'datepicker'],
'widget' => 'single_text',
'format' => 'dd-MM-yyyy',
]);
}
public function getFormDefaultData(): array
{
return ['date_age_calculation' => new RollingDate(RollingDate::T_TODAY)];
}
public function getLabels($key, array $values, $data)
{
@@ -61,11 +62,7 @@ final readonly class AgeAggregator implements AggregatorInterface, ExportElement
return 'Age';
}
if (null === $value) {
return $this->translator->trans('without data');
}
return $value;
return $value ?? '';
};
}

View File

@@ -82,9 +82,12 @@ class ByHouseholdCompositionAggregator implements AggregatorInterface
{
$builder->add('date_calc', PickRollingDateType::class, [
'label' => 'export.aggregator.person.by_household_composition.Calc date',
'data' => new RollingDate(RollingDate::T_TODAY),
]);
}
public function getFormDefaultData(): array
{
return ['date_calc' => new RollingDate(RollingDate::T_TODAY)];
}
public function getLabels($key, array $values, $data)
{

View File

@@ -0,0 +1,103 @@
<?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\PersonBundle\Export\Aggregator\PersonAggregators;
use Chill\MainBundle\Export\AggregatorInterface;
use Chill\MainBundle\Form\Type\PickRollingDateType;
use Chill\MainBundle\Repository\CenterRepositoryInterface;
use Chill\MainBundle\Service\RollingDate\RollingDate;
use Chill\MainBundle\Service\RollingDate\RollingDateConverterInterface;
use Chill\PersonBundle\Export\Declarations;
use Closure;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface;
final readonly class CenterAggregator implements AggregatorInterface
{
private const COLUMN_NAME = 'person_center_aggregator';
public function __construct(
private CenterRepositoryInterface $centerRepository,
private RollingDateConverterInterface $rollingDateConverter,
) {
}
public function buildForm(FormBuilderInterface $builder)
{
$builder->add('at_date', PickRollingDateType::class, [
'label' => 'export.aggregator.person.by_center.at_date',
]);
}
public function getFormDefaultData(): array
{
return [
'at_date' => new RollingDate(RollingDate::T_TODAY)
];
}
public function getLabels($key, array $values, $data): Closure
{
return function (int|string|null $value) {
if (null === $value || '' === $value) {
return '';
}
if ('_header' === $value) {
return 'export.aggregator.person.by_center.center';
}
return (string) $this->centerRepository->find((int) $value)?->getName();
};
}
public function getQueryKeys($data)
{
return [self::COLUMN_NAME];
}
public function getTitle()
{
return 'export.aggregator.person.by_center.title';
}
public function addRole(): ?string
{
return null;
}
public function alterQuery(QueryBuilder $qb, $data)
{
$alias = 'pers_center_agg';
$atDate = 'pers_center_agg_at_date';
$qb->leftJoin('person.centerHistory', $alias);
$qb
->andWhere(
$qb->expr()->lte($alias.'.startDate', ':'.$atDate),
)->andWhere(
$qb->expr()->orX(
$qb->expr()->isNull($alias.'.endDate'),
$qb->expr()->gt($alias.'.endDate', ':'.$atDate)
)
);
$qb->setParameter($atDate, $this->rollingDateConverter->convert($data['at_date']));
$qb->addSelect("IDENTITY({$alias}.center) AS " . self::COLUMN_NAME);
$qb->addGroupBy(self::COLUMN_NAME);
}
public function applyOn()
{
return Declarations::PERSON_TYPE;
}
}

View File

@@ -96,6 +96,10 @@ final readonly class CountryOfBirthAggregator implements AggregatorInterface, Ex
'multiple' => false,
]);
}
public function getFormDefaultData(): array
{
return [];
}
public function getLabels($key, array $values, $data)
{

View File

@@ -45,6 +45,10 @@ final readonly class GenderAggregator implements AggregatorInterface
public function buildForm(FormBuilderInterface $builder)
{
}
public function getFormDefaultData(): array
{
return [];
}
public function getLabels($key, array $values, $data)
{

View File

@@ -81,7 +81,6 @@ class GeographicalUnitAggregator implements AggregatorInterface
->add('date_calc', PickRollingDateType::class, [
'label' => 'Address valid at this date',
'required' => true,
'data' => new RollingDate(RollingDate::T_TODAY),
])
->add('level', EntityType::class, [
'label' => 'Geographical layer',
@@ -89,8 +88,14 @@ class GeographicalUnitAggregator implements AggregatorInterface
'class' => GeographicalUnitLayer::class,
'choices' => $this->geographicalUnitLayerRepository->findAllHavingUnits(),
'choice_label' => fn (GeographicalUnitLayer $item) => $this->translatableStringHelper->localize($item->getName()),
'multiple' => true,
'expanded' => true,
]);
}
public function getFormDefaultData(): array
{
return ['date_calc' => new RollingDate(RollingDate::T_TODAY)];
}
public static function getDefaultAlias(): string
{

View File

@@ -74,9 +74,12 @@ final readonly class HouseholdPositionAggregator implements AggregatorInterface,
{
$builder->add('date_position', PickRollingDateType::class, [
'label' => 'Household position in relation to this date',
'data' => new RollingDate(RollingDate::T_TODAY),
]);
}
public function getFormDefaultData(): array
{
return ['date_position' => new RollingDate(RollingDate::T_TODAY)];
}
public function getLabels($key, array $values, $data)
{

View File

@@ -50,6 +50,11 @@ final readonly class MaritalStatusAggregator implements AggregatorInterface
// no form
}
public function getFormDefaultData(): array
{
return [];
}
public function getLabels($key, array $values, $data)
{
return function ($value): string {

View File

@@ -91,6 +91,13 @@ final readonly class NationalityAggregator implements AggregatorInterface, Expor
]);
}
public function getFormDefaultData(): array
{
return [
'group_by_level' => 'country',
];
}
public function getLabels($key, array $values, $data)
{
$labels = [];

View File

@@ -59,6 +59,10 @@ final readonly class ActionTypeAggregator implements AggregatorInterface
{
// no form
}
public function getFormDefaultData(): array
{
return [];
}
public function getLabels($key, array $values, $data)
{

View File

@@ -48,6 +48,10 @@ class CurrentActionAggregator implements AggregatorInterface
{
// No form needed
}
public function getFormDefaultData(): array
{
return [];
}
public function getLabels($key, array $values, $data)
{

View File

@@ -49,6 +49,10 @@ final readonly class GoalAggregator implements AggregatorInterface
{
// no form
}
public function getFormDefaultData(): array
{
return [];
}
public function getLabels($key, array $values, $data)
{

View File

@@ -56,6 +56,10 @@ class GoalResultAggregator implements AggregatorInterface
{
// no form
}
public function getFormDefaultData(): array
{
return [];
}
public function getLabels($key, array $values, $data)
{

View File

@@ -49,6 +49,10 @@ final readonly class JobAggregator implements AggregatorInterface
{
// no form
}
public function getFormDefaultData(): array
{
return [];
}
public function getLabels($key, array $values, $data)
{

View File

@@ -49,6 +49,10 @@ final readonly class ReferrerAggregator implements AggregatorInterface
{
// no form
}
public function getFormDefaultData(): array
{
return [];
}
public function getLabels($key, array $values, $data)
{

View File

@@ -49,6 +49,10 @@ final readonly class ResultAggregator implements AggregatorInterface
{
// no form
}
public function getFormDefaultData(): array
{
return [];
}
public function getLabels($key, array $values, $data)
{

View File

@@ -49,6 +49,10 @@ final readonly class ScopeAggregator implements AggregatorInterface
{
// no form
}
public function getFormDefaultData(): array
{
return [];
}
public function getLabels($key, array $values, $data)
{

View File

@@ -39,6 +39,10 @@ class CountAccompanyingCourse implements ExportInterface, GroupedExportInterface
{
// Nothing to add here
}
public function getFormDefaultData(): array
{
return [];
}
public function getAllowedFormattersTypes(): array
{
@@ -97,7 +101,6 @@ class CountAccompanyingCourse implements ExportInterface, GroupedExportInterface
->andWhere('acp.step != :count_acp_step')
->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

View File

@@ -34,6 +34,10 @@ class CountAccompanyingPeriodWork implements ExportInterface, GroupedExportInter
{
// No form necessary?
}
public function getFormDefaultData(): array
{
return [];
}
public function getAllowedFormattersTypes(): array
{
@@ -93,7 +97,6 @@ class CountAccompanyingPeriodWork implements ExportInterface, GroupedExportInter
->join('acpw.accompanyingPeriod', 'acp')
->join('acp.participations', 'acppart')
->join('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

View File

@@ -33,6 +33,10 @@ class CountEvaluation implements ExportInterface, GroupedExportInterface
{
// TODO: Implement buildForm() method.
}
public function getFormDefaultData(): array
{
return [];
}
public function getAllowedFormattersTypes(): array
{
@@ -93,7 +97,6 @@ class CountEvaluation implements ExportInterface, GroupedExportInterface
->join('acpw.accompanyingPeriod', 'acp')
->join('acp.participations', 'acppart')
->join('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

View File

@@ -38,11 +38,14 @@ class CountHousehold implements ExportInterface, GroupedExportInterface
{
$builder
->add('calc_date', PickRollingDateType::class, [
'data' => new RollingDate(RollingDate::T_TODAY),
'label' => self::TR_PREFIX . 'Date of calculation of household members',
'required' => false,
]);
}
public function getFormDefaultData(): array
{
return ['calc_date' => new RollingDate(RollingDate::T_TODAY)];
}
public function getAllowedFormattersTypes(): array
{

View File

@@ -34,6 +34,11 @@ class CountPerson implements ExportInterface, GroupedExportInterface
// No form necessary
}
public function getFormDefaultData(): array
{
return [];
}
public function getAllowedFormattersTypes()
{
return [FormatterInterface::TYPE_TABULAR];
@@ -111,9 +116,9 @@ class CountPerson implements ExportInterface, GroupedExportInterface
public function supportsModifiers()
{
return [
Declarations::PERSON_TYPE,
Declarations::PERSON_IMPLIED_IN,
//Declarations::ACP_TYPE
'abcde',
//Declarations::PERSON_TYPE,
//Declarations::PERSON_IMPLIED_IN,
];
}
}

View File

@@ -39,6 +39,10 @@ class CountPersonWithAccompanyingCourse implements ExportInterface, GroupedExpor
{
// TODO: Implement buildForm() method.
}
public function getFormDefaultData(): array
{
return [];
}
public function getAllowedFormattersTypes(): array
{

View File

@@ -20,6 +20,7 @@ use Chill\MainBundle\Export\Helper\ExportAddressHelper;
use Chill\MainBundle\Export\Helper\UserHelper;
use Chill\MainBundle\Export\ListInterface;
use Chill\MainBundle\Form\Type\PickRollingDateType;
use Chill\MainBundle\Service\RollingDate\RollingDate;
use Chill\MainBundle\Service\RollingDate\RollingDateConverterInterface;
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
@@ -28,6 +29,7 @@ use Chill\PersonBundle\Entity\Household\PersonHouseholdAddress;
use Chill\PersonBundle\Entity\Person\PersonCenterHistory;
use Chill\PersonBundle\Entity\SocialWork\SocialIssue;
use Chill\PersonBundle\Export\Declarations;
use Chill\PersonBundle\Export\Helper\ListAccompanyingPeriodHelper;
use Chill\PersonBundle\Repository\PersonRepository;
use Chill\PersonBundle\Repository\SocialWork\SocialIssueRepository;
use Chill\PersonBundle\Security\Authorization\PersonVoter;
@@ -41,45 +43,16 @@ use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Query\Expr\Join;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
use function strlen;
class ListAccompanyingPeriod implements ListInterface, GroupedExportInterface
final readonly class ListAccompanyingPeriod implements ListInterface, GroupedExportInterface
{
private const FIELDS = [
'id',
'step',
'stepSince',
'openingDate',
'closingDate',
'referrer',
'referrerSince',
'administrativeLocation',
'locationIsPerson',
'locationIsTemp',
'locationPersonName',
'locationPersonId',
'origin',
'closingMotive',
'confidential',
'emergency',
'intensity',
'job',
'isRequestorPerson',
'isRequestorThirdParty',
'requestorPerson',
'requestorPersonId',
'requestorThirdParty',
'requestorThirdPartyId',
'scopes',
'socialIssues',
'createdAt',
'createdBy',
'updatedAt',
'updatedBy',
];
public function __construct(private readonly ExportAddressHelper $addressHelper, private readonly DateTimeHelper $dateTimeHelper, private readonly EntityManagerInterface $entityManager, private readonly PersonRenderInterface $personRender, private readonly PersonRepository $personRepository, private readonly ThirdPartyRepository $thirdPartyRepository, private readonly ThirdPartyRender $thirdPartyRender, private readonly SocialIssueRepository $socialIssueRepository, private readonly SocialIssueRender $socialIssueRender, private readonly TranslatableStringHelperInterface $translatableStringHelper, private readonly RollingDateConverterInterface $rollingDateConverter, private readonly UserHelper $userHelper)
{
public function __construct(
private EntityManagerInterface $entityManager,
private RollingDateConverterInterface $rollingDateConverter,
private ListAccompanyingPeriodHelper $listAccompanyingPeriodHelper,
) {
}
public function buildForm(FormBuilderInterface $builder)
@@ -91,6 +64,12 @@ class ListAccompanyingPeriod implements ListInterface, GroupedExportInterface
'required' => true,
]);
}
public function getFormDefaultData(): array
{
return [
'calc_date' => new RollingDate(RollingDate::T_TODAY)
];
}
public function getAllowedFormattersTypes()
{
@@ -109,99 +88,12 @@ class ListAccompanyingPeriod implements ListInterface, GroupedExportInterface
public function getLabels($key, array $values, $data)
{
if (str_starts_with($key, 'address_fields')) {
return $this->addressHelper->getLabel($key, $values, $data, 'address_fields');
}
return match ($key) {
'stepSince', 'openingDate', 'closingDate', 'referrerSince', 'createdAt', 'updatedAt' => $this->dateTimeHelper->getLabel('export.list.acp.' . $key),
'origin', 'closingMotive', 'job' => function ($value) use ($key) {
if ('_header' === $value) {
return 'export.list.acp.' . $key;
}
if (null === $value) {
return '';
}
return $this->translatableStringHelper->localize(json_decode($value, true, 512, JSON_THROW_ON_ERROR));
},
'locationPersonName', 'requestorPerson' => function ($value) use ($key) {
if ('_header' === $value) {
return 'export.list.acp.' . $key;
}
if (null === $value || null === $person = $this->personRepository->find($value)) {
return '';
}
return $this->personRender->renderString($person, []);
},
'requestorThirdParty' => function ($value) use ($key) {
if ('_header' === $value) {
return 'export.list.acp.' . $key;
}
if (null === $value || null === $thirdparty = $this->thirdPartyRepository->find($value)) {
return '';
}
return $this->thirdPartyRender->renderString($thirdparty, []);
},
'scopes' => function ($value) use ($key) {
if ('_header' === $value) {
return 'export.list.acp.' . $key;
}
if (null === $value) {
return '';
}
return implode(
'|',
array_map(
fn ($s) => $this->translatableStringHelper->localize($s),
json_decode($value, true, 512, JSON_THROW_ON_ERROR)
)
);
},
'socialIssues' => function ($value) use ($key) {
if ('_header' === $value) {
return 'export.list.acp.' . $key;
}
if (null === $value) {
return '';
}
return implode(
'|',
array_map(
fn ($s) => $this->socialIssueRender->renderString($this->socialIssueRepository->find($s), []),
json_decode($value, true, 512, JSON_THROW_ON_ERROR)
)
);
},
default => static function ($value) use ($key) {
if ('_header' === $value) {
return 'export.list.acp.' . $key;
}
if (null === $value) {
return '';
}
return $value;
},
};
return $this->listAccompanyingPeriodHelper->getLabels($key, $values, $data);
}
public function getQueryKeys($data)
{
return array_merge(
self::FIELDS,
$this->addressHelper->getKeys(ExportAddressHelper::F_ALL, 'address_fields')
);
return $this->listAccompanyingPeriodHelper->getQueryKeys($data);
}
public function getResult($query, $data)
@@ -239,7 +131,12 @@ class ListAccompanyingPeriod implements ListInterface, GroupedExportInterface
->setParameter('list_acp_step', AccompanyingPeriod::STEP_DRAFT)
->setParameter('authorized_centers', $centers);
$this->addSelectClauses($qb, $this->rollingDateConverter->convert($data['calc_date']));
$this->listAccompanyingPeriodHelper->addSelectClauses($qb, $this->rollingDateConverter->convert($data['calc_date']));
$qb
->addOrderBy('acp.openingDate')
->addOrderBy('acp.closingDate')
->addOrderBy('acp.id');
return $qb;
}
@@ -255,95 +152,4 @@ class ListAccompanyingPeriod implements ListInterface, GroupedExportInterface
Declarations::ACP_TYPE,
];
}
private function addSelectClauses(QueryBuilder $qb, DateTimeImmutable $calcDate): void
{
// add the regular fields
foreach (['id', 'openingDate', 'closingDate', 'confidential', 'emergency', 'intensity', 'createdAt', 'updatedAt'] as $field) {
$qb->addSelect(sprintf('acp.%s AS %s', $field, $field));
}
// add the field which are simple association
foreach (['origin' => 'label', 'closingMotive' => 'name', 'job' => 'label', 'createdBy' => 'label', 'updatedBy' => 'label', 'administrativeLocation' => 'name'] as $entity => $field) {
$qb
->leftJoin(sprintf('acp.%s', $entity), "{$entity}_t")
->addSelect(sprintf('%s_t.%s AS %s', $entity, $field, $entity));
}
// step at date
$qb
->addSelect('stepHistory.step AS step')
->addSelect('stepHistory.startDate AS stepSince')
->leftJoin('acp.stepHistories', 'stepHistory')
->andWhere(
$qb->expr()->andX(
$qb->expr()->lte('stepHistory.startDate', ':calcDate'),
$qb->expr()->orX($qb->expr()->isNull('stepHistory.endDate'), $qb->expr()->gt('stepHistory.endDate', ':calcDate'))
)
);
// referree at date
$qb
->addSelect('referrer_t.label AS referrer')
->addSelect('userHistory.startDate AS referrerSince')
->leftJoin('acp.userHistories', 'userHistory')
->leftJoin('userHistory.user', 'referrer_t')
->andWhere(
$qb->expr()->orX(
$qb->expr()->isNull('userHistory'),
$qb->expr()->andX(
$qb->expr()->lte('userHistory.startDate', ':calcDate'),
$qb->expr()->orX($qb->expr()->isNull('userHistory.endDate'), $qb->expr()->gt('userHistory.endDate', ':calcDate'))
)
)
);
// location of the acp
$qb
->addSelect('CASE WHEN locationHistory.personLocation IS NOT NULL THEN 1 ELSE 0 END AS locationIsPerson')
->addSelect('CASE WHEN locationHistory.personLocation IS NOT NULL THEN 0 ELSE 1 END AS locationIsTemp')
->addSelect('IDENTITY(locationHistory.personLocation) AS locationPersonName')
->addSelect('IDENTITY(locationHistory.personLocation) AS locationPersonId')
->leftJoin('acp.locationHistories', 'locationHistory')
->andWhere(
$qb->expr()->orX(
$qb->expr()->isNull('locationHistory'),
$qb->expr()->andX(
$qb->expr()->lte('locationHistory.startDate', ':calcDate'),
$qb->expr()->orX($qb->expr()->isNull('locationHistory.endDate'), $qb->expr()->gt('locationHistory.endDate', ':calcDate'))
)
)
)
->leftJoin(PersonHouseholdAddress::class, 'personAddress', Join::WITH, 'locationHistory.personLocation = personAddress.person')
->andWhere(
$qb->expr()->orX(
$qb->expr()->isNull('personAddress'),
$qb->expr()->andX(
$qb->expr()->lte('personAddress.validFrom', ':calcDate'),
$qb->expr()->orX($qb->expr()->isNull('personAddress.validTo'), $qb->expr()->gt('personAddress.validTo', ':calcDate'))
)
)
)
->leftJoin(Address::class, 'acp_address', Join::WITH, 'COALESCE(IDENTITY(locationHistory.addressLocation), IDENTITY(personAddress.address)) = acp_address.id');
$this->addressHelper->addSelectClauses(ExportAddressHelper::F_ALL, $qb, 'acp_address', 'address_fields');
// requestor
$qb
->addSelect('CASE WHEN acp.requestorPerson IS NULL THEN 1 ELSE 0 END AS isRequestorPerson')
->addSelect('CASE WHEN acp.requestorPerson IS NULL THEN 0 ELSE 1 END AS isRequestorThirdParty')
->addSelect('IDENTITY(acp.requestorPerson) AS requestorPersonId')
->addSelect('IDENTITY(acp.requestorThirdParty) AS requestorThirdPartyId')
->addSelect('IDENTITY(acp.requestorPerson) AS requestorPerson')
->addSelect('IDENTITY(acp.requestorThirdParty) AS requestorThirdParty');
$qb
// scopes
->addSelect('(SELECT AGGREGATE(scope.name) FROM ' . Scope::class . ' scope WHERE scope MEMBER OF acp.scopes) AS scopes')
// social issues
->addSelect('(SELECT AGGREGATE(socialIssue.id) FROM ' . SocialIssue::class . ' socialIssue WHERE socialIssue MEMBER OF acp.socialIssues) AS socialIssues');
// add parameter
$qb->setParameter('calcDate', $calcDate);
}
}

View File

@@ -87,9 +87,12 @@ class ListAccompanyingPeriodWork implements ListInterface, GroupedExportInterfac
'label' => 'export.list.acpw.Date of calculation for associated elements',
'help' => 'export.list.acpw.help_description',
'required' => true,
'data' => new RollingDate(RollingDate::T_TODAY),
]);
}
public function getFormDefaultData(): array
{
return ['calc_date' => new RollingDate(RollingDate::T_TODAY)];
}
public function getAllowedFormattersTypes()
{

View File

@@ -79,9 +79,12 @@ class ListEvaluation implements ListInterface, GroupedExportInterface
'label' => 'export.list.eval.Date of calculation for associated elements',
'help' => 'export.list.eval.help_description',
'required' => true,
'data' => new RollingDate(RollingDate::T_TODAY),
]);
}
public function getFormDefaultData(): array
{
return ['calc_date' => new RollingDate(RollingDate::T_TODAY)];
}
public function getAllowedFormattersTypes()
{

View File

@@ -45,8 +45,13 @@ class ListHouseholdInPeriod implements ListInterface, GroupedExportInterface
'compositionType',
];
public function __construct(private readonly ExportAddressHelper $addressHelper, private readonly AggregateStringHelper $aggregateStringHelper, private readonly EntityManagerInterface $entityManager, private readonly RollingDateConverterInterface $rollingDateConverter, private readonly TranslatableStringExportLabelHelper $translatableStringHelper)
{
public function __construct(
private readonly ExportAddressHelper $addressHelper,
private readonly AggregateStringHelper $aggregateStringHelper,
private readonly EntityManagerInterface $entityManager,
private readonly RollingDateConverterInterface $rollingDateConverter,
private readonly TranslatableStringExportLabelHelper $translatableStringHelper
) {
}
public function buildForm(FormBuilderInterface $builder)
@@ -55,10 +60,13 @@ class ListHouseholdInPeriod implements ListInterface, GroupedExportInterface
->add('calc_date', PickRollingDateType::class, [
'label' => 'export.list.household.Date of calculation for associated elements',
'help' => 'export.list.household.help_description',
'data' => new RollingDate(RollingDate::T_TODAY),
'required' => true,
]);
}
public function getFormDefaultData(): array
{
return ['calc_date' => new RollingDate(RollingDate::T_TODAY)];
}
public function getAllowedFormattersTypes()
{

View File

@@ -87,17 +87,27 @@ class ListPerson implements ExportElementValidatedInterface, ListInterface, Grou
}
},
])],
'data' => array_values($choices),
]);
// add a date field for addresses
$builder->add('address_date', ChillDateType::class, [
'label' => 'Data valid at this date',
'help' => 'Data regarding center, addresses, and so on will be computed at this date',
'data' => new DateTimeImmutable(),
'input' => 'datetime_immutable',
]);
}
public function getFormDefaultData(): array
{
$choices = array_combine(ListPersonHelper::FIELDS, ListPersonHelper::FIELDS);
foreach ($this->getCustomFields() as $cf) {
$choices[$this->translatableStringHelper->localize($cf->getName())]
=
$cf->getSlug();
}
return ['fields' => array_values($choices), 'address_date' => new DateTimeImmutable()];
}
public function getAllowedFormattersTypes()
{

View File

@@ -62,9 +62,12 @@ class ListPersonDuplicate implements DirectExportInterface, ExportElementValidat
{
$builder->add('precision', NumberType::class, [
'label' => 'Precision',
'data' => self::PRECISION_DEFAULT_VALUE,
]);
}
public function getFormDefaultData(): array
{
return ['precision' => self::PRECISION_DEFAULT_VALUE];
}
public function generate(array $acl, array $data = []): Response
{

View File

@@ -35,7 +35,12 @@ use function count;
use function in_array;
use function strlen;
class ListPersonWithAccompanyingPeriod implements ExportElementValidatedInterface, ListInterface, GroupedExportInterface
/**
* List the persons, having an accompanying period.
*
* Details of the accompanying period are not included
*/
class ListPersonHavingAccompanyingPeriod implements ExportElementValidatedInterface, ListInterface, GroupedExportInterface
{
public function __construct(private readonly ExportAddressHelper $addressHelper, private readonly ListPersonHelper $listPersonHelper, private readonly EntityManagerInterface $entityManager)
{
@@ -45,7 +50,6 @@ class ListPersonWithAccompanyingPeriod implements ExportElementValidatedInterfac
{
$choices = array_combine(ListPersonHelper::FIELDS, ListPersonHelper::FIELDS);
// Add a checkbox to select fields
$builder->add('fields', ChoiceType::class, [
'multiple' => true,
'expanded' => true,
@@ -68,17 +72,20 @@ class ListPersonWithAccompanyingPeriod implements ExportElementValidatedInterfac
}
},
])],
'data' => array_values($choices),
]);
// add a date field for addresses
$builder->add('address_date', ChillDateType::class, [
'label' => 'Data valid at this date',
'help' => 'Data regarding center, addresses, and so on will be computed at this date',
'data' => new DateTimeImmutable(),
'input' => 'datetime_immutable',
]);
}
public function getFormDefaultData(): array
{
$choices = array_combine(ListPersonHelper::FIELDS, ListPersonHelper::FIELDS);
return ['fields' => array_values($choices), 'address_date' => new DateTimeImmutable()];
}
public function getAllowedFormattersTypes()
{
@@ -171,6 +178,11 @@ class ListPersonWithAccompanyingPeriod implements ExportElementValidatedInterfac
$this->listPersonHelper->addSelect($qb, $fields, $data['address_date']);
$qb
->addOrderBy('person.lastName')
->addOrderBy('person.firstName')
->addOrderBy('person.id');
return $qb;
}

View File

@@ -0,0 +1,155 @@
<?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\PersonBundle\Export\Export;
use Chill\MainBundle\Export\ExportElementValidatedInterface;
use Chill\MainBundle\Export\FormatterInterface;
use Chill\MainBundle\Export\GroupedExportInterface;
use Chill\MainBundle\Export\Helper\ExportAddressHelper;
use Chill\MainBundle\Export\ListInterface;
use Chill\MainBundle\Form\Type\ChillDateType;
use Chill\MainBundle\Form\Type\PickRollingDateType;
use Chill\MainBundle\Service\RollingDate\RollingDate;
use Chill\MainBundle\Service\RollingDate\RollingDateConverterInterface;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Entity\Person;
use Chill\PersonBundle\Entity\Person\PersonCenterHistory;
use Chill\PersonBundle\Export\Declarations;
use Chill\PersonBundle\Export\Helper\ListAccompanyingPeriodHelper;
use Chill\PersonBundle\Export\Helper\ListPersonHelper;
use Chill\PersonBundle\Security\Authorization\PersonVoter;
use DateTimeImmutable;
use Doctrine\ORM\AbstractQuery;
use Doctrine\ORM\EntityManagerInterface;
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 function array_key_exists;
use function count;
use function in_array;
use function strlen;
/**
* List the persons having an accompanying period, with the accompanying period details
*
*/
final readonly class ListPersonWithAccompanyingPeriodDetails implements ListInterface, GroupedExportInterface
{
public function __construct(
private ListPersonHelper $listPersonHelper,
private ListAccompanyingPeriodHelper $listAccompanyingPeriodHelper,
private EntityManagerInterface $entityManager,
private RollingDateConverterInterface $rollingDateConverter,
) {
}
public function buildForm(FormBuilderInterface $builder)
{
$builder->add('address_date', PickRollingDateType::class, [
'label' => 'Data valid at this date',
'help' => 'Data regarding center, addresses, and so on will be computed at this date',
]);
}
public function getFormDefaultData(): array
{
return ['address_date' => new RollingDate(RollingDate::T_TODAY)];
}
public function getAllowedFormattersTypes()
{
return [FormatterInterface::TYPE_LIST];
}
public function getDescription()
{
return 'export.list.person_with_acp.Create a list of people having an accompaying periods with details of period, according to various filters.';
}
public function getGroup(): string
{
return 'Exports of persons';
}
public function getLabels($key, array $values, $data)
{
if (in_array($key, $this->listPersonHelper->getAllKeys(), true)) {
return $this->listPersonHelper->getLabels($key, $values, $data);
}
return $this->listAccompanyingPeriodHelper->getLabels($key, $values, $data);
}
public function getQueryKeys($data)
{
return array_merge(
$this->listPersonHelper->getAllKeys(),
$this->listAccompanyingPeriodHelper->getQueryKeys($data),
);
}
public function getResult($query, $data)
{
return $query->getQuery()->getResult(AbstractQuery::HYDRATE_SCALAR);
}
public function getTitle()
{
return 'export.list.person_with_acp.List peoples having an accompanying period with period details';
}
public function getType()
{
return Declarations::PERSON_TYPE;
}
/**
* param array{fields: string[], address_date: DateTimeImmutable} $data.
*/
public function initiateQuery(array $requiredModifiers, array $acl, array $data = [])
{
$centers = array_map(static fn ($el) => $el['center'], $acl);
$qb = $this->entityManager->createQueryBuilder();
$qb->from(Person::class, 'person')
->join('person.accompanyingPeriodParticipations', 'acppart')
->join('acppart.accompanyingPeriod', 'acp')
->andWhere($qb->expr()->neq('acp.step', "'" . AccompanyingPeriod::STEP_DRAFT . "'"))
->andWhere(
$qb->expr()->exists(
'SELECT 1 FROM ' . PersonCenterHistory::class . ' pch WHERE pch.person = person.id AND pch.center IN (:authorized_centers)'
)
)->setParameter('authorized_centers', $centers);
$this->listPersonHelper->addSelect($qb, ListPersonHelper::FIELDS, $this->rollingDateConverter->convert($data['address_date']));
$this->listAccompanyingPeriodHelper->addSelectClauses($qb, $this->rollingDateConverter->convert($data['address_date']));
$qb
->addOrderBy('person.lastName')
->addOrderBy('person.firstName')
->addOrderBy('person.id')
->addOrderBy('acp.id');
return $qb;
}
public function requiredRole(): string
{
return PersonVoter::LISTS;
}
public function supportsModifiers()
{
return [Declarations::PERSON_TYPE, Declarations::PERSON_IMPLIED_IN, Declarations::ACP_TYPE];
}
}

View File

@@ -41,9 +41,12 @@ class StatAccompanyingCourseDuration implements ExportInterface, GroupedExportIn
{
$builder->add('closingdate', ChillDateType::class, [
'label' => 'Closingdate to apply',
'data' => new DateTime('now'),
]);
}
public function getFormDefaultData(): array
{
return ['closingdate' => new DateTime('now')];
}
public function getAllowedFormattersTypes(): array
{

View File

@@ -64,9 +64,11 @@ class ActiveOnDateFilter implements FilterInterface
public function buildForm(FormBuilderInterface $builder)
{
$builder
->add('on_date', PickRollingDateType::class, [
'data' => new RollingDate(RollingDate::T_TODAY),
]);
->add('on_date', PickRollingDateType::class, []);
}
public function getFormDefaultData(): array
{
return ['on_date' => new RollingDate(RollingDate::T_TODAY)];
}
public function describeAction($data, $format = 'string'): array

View File

@@ -53,12 +53,12 @@ class ActiveOneDayBetweenDatesFilter implements FilterInterface
public function buildForm(FormBuilderInterface $builder)
{
$builder
->add('date_from', PickRollingDateType::class, [
'data' => new RollingDate(RollingDate::T_YEAR_PREVIOUS_START),
])
->add('date_to', PickRollingDateType::class, [
'data' => new RollingDate(RollingDate::T_TODAY),
]);
->add('date_from', PickRollingDateType::class, [])
->add('date_to', PickRollingDateType::class, []);
}
public function getFormDefaultData(): array
{
return ['date_from' => new RollingDate(RollingDate::T_YEAR_PREVIOUS_START), 'date_to' => new RollingDate(RollingDate::T_TODAY)];
}
public function describeAction($data, $format = 'string'): array

View File

@@ -49,6 +49,10 @@ class AdministrativeLocationFilter implements FilterInterface
'multiple' => true,
]);
}
public function getFormDefaultData(): array
{
return [];
}
public function describeAction($data, $format = 'string'): array
{

View File

@@ -60,6 +60,10 @@ class ClosingMotiveFilter implements FilterInterface
'expanded' => true,
]);
}
public function getFormDefaultData(): array
{
return [];
}
public function describeAction($data, $format = 'string'): array
{

View File

@@ -22,11 +22,11 @@ use Symfony\Contracts\Translation\TranslatorInterface;
class ConfidentialFilter implements FilterInterface
{
private const CHOICES = [
'is not confidential' => false,
'is confidential' => true,
'is not confidential' => 'false',
'is confidential' => 'true',
];
private const DEFAULT_CHOICE = false;
private const DEFAULT_CHOICE = 'false';
public function __construct(private readonly TranslatorInterface $translator)
{
@@ -64,9 +64,12 @@ class ConfidentialFilter implements FilterInterface
'multiple' => false,
'expanded' => true,
'empty_data' => self::DEFAULT_CHOICE,
'data' => self::DEFAULT_CHOICE,
]);
}
public function getFormDefaultData(): array
{
return ['accepted_confidentials' => self::DEFAULT_CHOICE];
}
public function describeAction($data, $format = 'string'): array
{

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