Merge branch 'master' into 709-notification-eval-action

This commit is contained in:
2023-05-22 10:52:59 +02:00
408 changed files with 3837 additions and 1752 deletions

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\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(): void
{
($this->requestor)();
}
}

View File

@@ -0,0 +1,38 @@
<?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 AccompanyingPeriodRepository $accompanyingPeriodRepository,
private 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 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,88 @@
<?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 \DateInterval $intervalForShortInactive;
private \DateInterval $intervalForLongInactive;
private bool $isMarkInactive;
public function __construct(
private AccompanyingPeriodInfoRepositoryInterface $accompanyingPeriodInfoRepository,
private LoggerInterface $logger,
private 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 EntityManagerInterface $entityManager,
private LoggerInterface $logger,
private 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

@@ -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

@@ -42,10 +42,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)

View File

@@ -169,9 +169,7 @@ final class AccompanyingCourseApiController extends ApiController
$accompanyingPeriods = $person->getCurrentAccompanyingPeriods();
$accompanyingPeriodsChecked = array_filter(
$accompanyingPeriods,
function (AccompanyingPeriod $period) {
return $this->isGranted(AccompanyingPeriodVoter::SEE, $period);
}
fn (AccompanyingPeriod $period) => $this->isGranted(AccompanyingPeriodVoter::SEE, $period)
);
return $this->json(array_values($accompanyingPeriodsChecked), Response::HTTP_OK, [], ['groups' => ['read']]);

View File

@@ -222,14 +222,10 @@ class AccompanyingPeriodController extends AbstractController
$accompanyingPeriodsRaw = $this->accompanyingPeriodACLAwareRepository
->findByPerson($person, AccompanyingPeriodVoter::SEE);
usort($accompanyingPeriodsRaw, static function ($a, $b) {
return $b->getOpeningDate() > $a->getOpeningDate();
});
usort($accompanyingPeriodsRaw, static fn ($a, $b) => $b->getOpeningDate() > $a->getOpeningDate());
// filter visible or not visible
$accompanyingPeriods = array_filter($accompanyingPeriodsRaw, function (AccompanyingPeriod $ap) {
return $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,
@@ -331,9 +327,7 @@ class AccompanyingPeriodController extends AbstractController
/** @var AccompanyingPeriod $period */
$period = array_filter(
$person->getAccompanyingPeriods(),
static function (AccompanyingPeriod $p) use ($period_id) {
return $p->getId() === ($period_id);
}
static fn (AccompanyingPeriod $p) => $p->getId() === ($period_id)
)[0] ?? null;
if (null === $period) {

View File

@@ -105,12 +105,8 @@ class AccompanyingPeriodRegulationListController
$builder
->add('services', EntityType::class, [
'class' => Scope::class,
'query_builder' => static function (EntityRepository $er) {
return $er->createQueryBuilder('s');
},
'choice_label' => function (Scope $s) {
return $this->translatableStringHelper->localize($s->getName());
},
'query_builder' => static fn (EntityRepository $er) => $er->createQueryBuilder('s'),
'choice_label' => fn (Scope $s) => $this->translatableStringHelper->localize($s->getName()),
'multiple' => true,
'label' => 'Service',
'required' => false,
@@ -123,9 +119,7 @@ class AccompanyingPeriodRegulationListController
return $qb;
},
'choice_label' => function (UserJob $j) {
return $this->translatableStringHelper->localize($j->getLabel());
},
'choice_label' => fn (UserJob $j) => $this->translatableStringHelper->localize($j->getLabel()),
'multiple' => true,
'label' => 'Métier',
'required' => false,
@@ -147,9 +141,7 @@ class AccompanyingPeriodRegulationListController
return $qb;
},
'choice_label' => static function (Location $l) {
return $l->getName();
},
'choice_label' => static fn (Location $l) => $l->getName(),
'multiple' => true,
'group_by' => function (Location $l) {
if (null === $type = $l->getLocationType()) {

View File

@@ -164,9 +164,7 @@ class HouseholdCompositionController extends AbstractController
$isEdit = $request->query->has('edit');
if ($isEdit) {
$householdCompositions = $household->getCompositions()->filter(static function (HouseholdComposition $composition) use ($request) {
return $composition->getId() === $request->query->getInt('edit');
});
$householdCompositions = $household->getCompositions()->filter(static fn (HouseholdComposition $composition) => $composition->getId() === $request->query->getInt('edit'));
if ($householdCompositions->count() !== 1) {
throw new BadRequestHttpException('could not find the composition with this id associated to the household');

View File

@@ -81,9 +81,7 @@ class HouseholdController extends AbstractController
}
}
usort($accompanyingPeriods, static function ($a, $b) {
return $b->getOpeningDate() <=> $a->getOpeningDate();
});
usort($accompanyingPeriods, static fn ($a, $b) => $b->getOpeningDate() <=> $a->getOpeningDate());
$oldMembers = $household->getNonCurrentMembers();
$accompanyingPeriodsOld = [];

View File

@@ -108,14 +108,10 @@ class PersonApiController extends ApiController
$addresses = $person
->getAccompanyingPeriodParticipations()
->filter(
static function (AccompanyingPeriodParticipation $accompanyingPeriodParticipation): bool {
return null !== $accompanyingPeriodParticipation->getAccompanyingPeriod()->getAddressLocation();
}
static fn (AccompanyingPeriodParticipation $accompanyingPeriodParticipation): bool => null !== $accompanyingPeriodParticipation->getAccompanyingPeriod()->getAddressLocation()
)
->map(
static function (AccompanyingPeriodParticipation $accompanyingPeriodParticipation): ?Address {
return $accompanyingPeriodParticipation->getAccompanyingPeriod()->getAddressLocation();
}
static fn (AccompanyingPeriodParticipation $accompanyingPeriodParticipation): ?Address => $accompanyingPeriodParticipation->getAccompanyingPeriod()->getAddressLocation()
)
->filter(
// We remove potential null addresses.

View File

@@ -165,7 +165,7 @@ final class PersonController extends AbstractController
$cFGroup = null;
$cFDefaultGroup = $this->em->getRepository(\Chill\CustomFieldsBundle\Entity\CustomFieldsDefaultGroup::class)
->findOneByEntity('Chill\\PersonBundle\\Entity\\Person');
->findOneByEntity(\Chill\PersonBundle\Entity\Person::class);
if ($cFDefaultGroup) {
$cFGroup = $cFDefaultGroup->getCustomFieldsGroup();

View File

@@ -120,7 +120,7 @@ class ReassignAccompanyingPeriodController extends AbstractController
$assignForm->handleRequest($request);
if ($assignForm->isSubmitted() && $assignForm->isValid()) {
$assignPeriodIds = json_decode($assignForm->get('periods')->getData(), true);
$assignPeriodIds = json_decode($assignForm->get('periods')->getData(), true, 512, JSON_THROW_ON_ERROR);
$userTo = $assignForm->get('userTo')->getData();
$userFrom = $assignForm->get('userFrom')->getData();
@@ -179,7 +179,7 @@ class ReassignAccompanyingPeriodController extends AbstractController
{
$defaultData = [
'userFrom' => $userFrom,
'periods' => json_encode($periodIds),
'periods' => json_encode($periodIds, JSON_THROW_ON_ERROR),
];
$builder = $this->formFactory->createNamedBuilder('reassign', FormType::class, $defaultData);

View File

@@ -44,9 +44,7 @@ class SocialWorkSocialActionApiController extends ApiController
$socialActions = $socialIssue->getRecursiveSocialActions()->toArray();
usort($socialActions, static function (SocialAction $sa, SocialAction $sb) {
return $sa->getOrdering() <=> $sb->getOrdering();
});
usort($socialActions, static fn (SocialAction $sa, SocialAction $sb) => $sa->getOrdering() <=> $sb->getOrdering());
$pagination = $this->paginator->create(count($socialActions));
// max one page

View File

@@ -115,9 +115,7 @@ class LoadCustomFields extends AbstractFixture implements
// get possible values for cfGroup
$choices = array_map(
static function ($a) {
return $a['slug'];
},
static fn ($a) => $a['slug'],
$this->customFieldChoice->getOptions()['choices']
);
// create faker
@@ -125,12 +123,12 @@ class LoadCustomFields extends AbstractFixture implements
// select a set of people and add data
foreach ($personIds as $id) {
// add info on 1 person on 2
if (mt_rand(0, 1) === 1) {
if (random_int(0, 1) === 1) {
/** @var Person $person */
$person = $manager->getRepository(Person::class)->find($id);
$person->setCFData([
'remarques' => $this->createCustomFieldText()
->serialize($faker->text(mt_rand(150, 250)), $this->customFieldText),
->serialize($faker->text(random_int(150, 250)), $this->customFieldText),
'document-d-identite' => $this->createCustomFieldChoice()
->serialize([$choices[array_rand($choices)]], $this->customFieldChoice),
]);

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;
@@ -1006,22 +1007,46 @@ class ChillPersonExtension extends Extension implements PrependExtensionInterfac
'property' => 'step',
],
'supports' => [
'Chill\PersonBundle\Entity\AccompanyingPeriod',
\Chill\PersonBundle\Entity\AccompanyingPeriod::class,
],
'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,
],
],
],
@@ -1043,7 +1068,7 @@ class ChillPersonExtension extends Extension implements PrependExtensionInterfac
$container->prependExtensionConfig(
'chill_custom_fields',
['customizables_entities' => [
['class' => 'Chill\PersonBundle\Entity\Person', 'name' => 'PersonEntity'],
['class' => \Chill\PersonBundle\Entity\Person::class, 'name' => 'PersonEntity'],
],
]
);

View File

@@ -30,7 +30,7 @@ class Configuration implements ConfigurationInterface
public function getConfigTreeBuilder()
{
$treeBuilder = new TreeBuilder('cl_chill_person');
$rootNode = $treeBuilder->getRootNode('cl_chill_person');
$rootNode = $treeBuilder->getRootNode();
$rootNode
->canBeDisabled()
@@ -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
;
@@ -137,7 +146,7 @@ class Configuration implements ConfigurationInterface
private function addFieldNode($key)
{
$tree = new TreeBuilder($key, 'enum');
$node = $tree->getRootNode($key);
$node = $tree->getRootNode();
switch ($key) {
case 'accompanying_period':

View File

@@ -109,6 +109,24 @@ class AccompanyingPeriod implements
*/
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.
*/
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.
*/
public const STEP_CONFIRMED_INACTIVE_LONG = 'CONFIRMED_INACTIVE_LONG';
/**
* Mark an accompanying period as "draft".
*
@@ -340,6 +358,7 @@ class AccompanyingPeriod implements
/**
* @ORM\Column(type="string", length=32, nullable=true)
* @Groups({"read"})
* @var AccompanyingPeriod::STEP_*
*/
private string $step = self::STEP_DRAFT;
@@ -712,11 +731,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]];
}
@@ -828,9 +845,7 @@ class AccompanyingPeriod implements
$collection = $this
->getParticipationsContainsPerson($person)
->filter(
static function (AccompanyingPeriodParticipation $participation): bool {
return null === $participation->getEndDate();
}
static fn (AccompanyingPeriodParticipation $participation): bool => null === $participation->getEndDate()
);
return $collection->count() > 0 ? $collection->first() : null;
@@ -844,9 +859,7 @@ class AccompanyingPeriod implements
return $this
->getParticipations()
->filter(
static function (AccompanyingPeriodParticipation $participation): bool {
return null === $participation->getEndDate();
}
static fn (AccompanyingPeriodParticipation $participation): bool => null === $participation->getEndDate()
);
}

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

@@ -44,20 +44,20 @@ class Comment implements TrackCreationInterface, TrackUpdateInterface
* @Assert\NotBlank
* @Assert\NotNull
*/
private ?string $content;
private ?string $content = null;
/**
* @ORM\Column(type="datetime")
* @Groups({"read", "docgen:read"})
*/
private ?DateTimeInterface $createdAt;
private ?DateTimeInterface $createdAt = null;
/**
* @ORM\ManyToOne(targetEntity=User::class)
* @ORM\JoinColumn(nullable=false)
* @Groups({"read", "docgen:read"})
*/
private ?User $creator;
private ?User $creator = null;
/**
* @ORM\Id
@@ -65,20 +65,20 @@ class Comment implements TrackCreationInterface, TrackUpdateInterface
* @ORM\Column(type="integer")
* @Groups({"read", "docgen:read"})
*/
private ?int $id;
private ?int $id = null;
/**
* @ORM\Column(type="datetime")
* @Groups({"read"})
*/
private ?DateTimeInterface $updatedAt;
private ?DateTimeInterface $updatedAt = null;
/**
* @ORM\ManyToOne(targetEntity=User::class)
* @ORM\JoinColumn(nullable=false)
* @Groups({"read"})
*/
private ?User $updatedBy;
private ?User $updatedBy = null;
public function getAccompanyingPeriod(): ?AccompanyingPeriod
{

View File

@@ -200,13 +200,11 @@ class Household
*/
public function getCurrentAddress(?DateTime $at = null): ?Address
{
$at = null === $at ? new DateTime('today') : $at;
$at ??= new DateTime('today');
$addrs = $this->getAddresses()->filter(static function (Address $a) use ($at) {
return $a->getValidFrom() <= $at && (
null === $a->getValidTo() || $a->getValidTo() > $at
);
});
$addrs = $this->getAddresses()->filter(static fn (Address $a) => $a->getValidFrom() <= $at && (
null === $a->getValidTo() || $a->getValidTo() > $at
));
if ($addrs->count() > 0) {
return $addrs->first();
@@ -215,6 +213,10 @@ class Household
return null;
}
/**
* @Serializer\Groups({"docgen:read"})
* @Serializer\SerializedName("current_composition")
*/
public function getCurrentComposition(?DateTimeImmutable $at = null): ?HouseholdComposition
{
$at ??= new DateTimeImmutable('today');
@@ -338,9 +340,7 @@ class Household
public function getCurrentPersons(?DateTimeImmutable $now = null): ReadableCollection
{
return $this->getCurrentMembers($now)
->map(static function (HouseholdMember $m) {
return $m->getPerson();
});
->map(static fn (HouseholdMember $m) => $m->getPerson());
}
public function getId(): ?int
@@ -367,9 +367,7 @@ class Household
$membership->getStartDate(),
$membership->getEndDate()
)->filter(
static function (HouseholdMember $m) use ($membership) {
return $m->getPerson() !== $membership->getPerson();
}
static fn (HouseholdMember $m) => $m->getPerson() !== $membership->getPerson()
);
}
@@ -412,7 +410,7 @@ class Household
{
$criteria = new Criteria();
$expr = Criteria::expr();
$date = null === $now ? (new DateTimeImmutable('today')) : $now;
$date = $now ?? new DateTimeImmutable('today');
$criteria
->where(
@@ -508,9 +506,7 @@ class Household
usort(
$compositionOrdered,
static function (HouseholdComposition $a, HouseholdComposition $b) {
return $a->getStartDate() <=> $b->getStartDate();
}
static fn (HouseholdComposition $a, HouseholdComposition $b) => $a->getStartDate() <=> $b->getStartDate()
);
$iterator = new ArrayIterator($compositionOrdered);
@@ -634,7 +630,7 @@ class Household
{
$criteria = new Criteria();
$expr = Criteria::expr();
$date = null === $now ? (new DateTimeImmutable('today')) : $now;
$date = $now ?? new DateTimeImmutable('today');
$criteria
->where($expr->orX(

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

@@ -142,7 +142,7 @@ class HouseholdMember
public function isCurrent(?DateTimeImmutable $at = null): bool
{
$at = null === $at ? new DateTimeImmutable('now') : $at;
$at ??= new DateTimeImmutable('now');
return $this->getStartDate() < $at && (
null === $this->getEndDate() || $this->getEndDate() > $at

View File

@@ -775,9 +775,7 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI
): int {
// TODO should be optimized to avoid loading accompanying period ?
return $this->getAccompanyingPeriodInvolved($asParticipantOpen, $asRequestor)
->filter(function (AccompanyingPeriod $p) {
return $p->getStep() !== AccompanyingPeriod::STEP_DRAFT;
})
->filter(fn (AccompanyingPeriod $p) => $p->getStep() !== AccompanyingPeriod::STEP_DRAFT)
->count();
}
@@ -1087,7 +1085,7 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI
{
$criteria = new Criteria();
$expr = Criteria::expr();
$date = null === $at ? new DateTimeImmutable('today') : $at;
$date = $at ?? new DateTimeImmutable('today');
$datef = $date->format('Y-m-d');
if (
@@ -1341,9 +1339,7 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI
return $this->getAccompanyingPeriodParticipations()
->matching($criteria)
->filter(static function (AccompanyingPeriodParticipation $app) {
return AccompanyingPeriod::STEP_CLOSED !== $app->getAccompanyingPeriod()->getStep();
});
->filter(static fn (AccompanyingPeriodParticipation $app) => AccompanyingPeriod::STEP_CLOSED !== $app->getAccompanyingPeriod()->getStep());
}
public function getOtherPhoneNumbers(): Collection

View File

@@ -50,7 +50,7 @@ class PersonCurrentAddress
/**
* @ORM\Column(name="valid_to", type="date_immutable")
*/
protected ?DateTimeImmutable $validTo;
protected ?DateTimeImmutable $validTo = null;
public function getAddress(): Address
{

View File

@@ -55,7 +55,7 @@ class PersonResource implements TrackCreationInterface, TrackUpdateInterface
* @ORM\Column(type="integer")
* @Groups({"read", "docgen:read"})
*/
private ?int $id;
private ?int $id = null;
/**
* @ORM\ManyToOne(targetEntity=PersonResourceKind::class, inversedBy="personResources")

View File

@@ -41,7 +41,7 @@ class PersonPhone
* @ORM\Column(name="id", type="integer")
* @ORM\GeneratedValue(strategy="AUTO")
*/
private ?int $id;
private ?int $id = null;
/**
* @ORM\ManyToOne(
@@ -59,7 +59,7 @@ class PersonPhone
/**
* @ORM\Column(type="text", length=40, nullable=true)
*/
private ?string $type;
private ?string $type = null;
public function __construct()
{

View File

@@ -45,7 +45,7 @@ class Result
/**
* @ORM\Column(type="datetime", nullable=true)
*/
private ?DateTime $desactivationDate;
private ?DateTime $desactivationDate = null;
/**
* @ORM\ManyToMany(targetEntity=Goal::class, mappedBy="results")

View File

@@ -98,7 +98,7 @@ final class GeographicalUnitStatAggregator implements AggregatorInterface
'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']);
@@ -128,9 +128,9 @@ final class GeographicalUnitStatAggregator implements AggregatorInterface
'placeholder' => 'Select a geographical layer',
'class' => GeographicalUnitLayer::class,
'choices' => $this->geographicalUnitLayerRepository->findAllHavingUnits(),
'choice_label' => function (GeographicalUnitLayer $item) {
return $this->translatableStringHelper->localize($item->getName());
},
'choice_label' => fn (GeographicalUnitLayer $item) => $this->translatableStringHelper->localize($item->getName()),
'multiple' => true,
'expanded' => true,
]);
}

View File

@@ -86,13 +86,19 @@ final 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

@@ -147,9 +147,7 @@ final class CountryOfBirthAggregator implements AggregatorInterface, ExportEleme
];
}
return static function (string $value) use ($labels): string {
return $labels[$value];
};
return static fn (string $value): string => $labels[$value];
}
public function getQueryKeys($data)

View File

@@ -100,9 +100,9 @@ class GeographicalUnitAggregator implements AggregatorInterface
'placeholder' => 'Select a geographical layer',
'class' => GeographicalUnitLayer::class,
'choices' => $this->geographicalUnitLayerRepository->findAllHavingUnits(),
'choice_label' => function (GeographicalUnitLayer $item) {
return $this->translatableStringHelper->localize($item->getName());
},
'choice_label' => fn (GeographicalUnitLayer $item) => $this->translatableStringHelper->localize($item->getName()),
'multiple' => true,
'expanded' => true,
]);
}

View File

@@ -141,9 +141,7 @@ final class NationalityAggregator implements AggregatorInterface, ExportElementV
];
}
return static function ($value) use ($labels): string {
return $labels[$value];
};
return static fn ($value): string => $labels[$value];
}
public function getQueryKeys($data)

View File

@@ -64,9 +64,7 @@ class CountAccompanyingCourse implements ExportInterface, GroupedExportInterface
$labels = array_combine($values, $values);
$labels['_header'] = $this->getTitle();
return static function ($value) use ($labels) {
return $labels[$value];
};
return static fn ($value) => $labels[$value];
}
public function getQueryKeys($data): array
@@ -91,9 +89,7 @@ class CountAccompanyingCourse implements ExportInterface, GroupedExportInterface
public function initiateQuery(array $requiredModifiers, array $acl, array $data = []): QueryBuilder
{
$centers = array_map(static function ($el) {
return $el['center'];
}, $acl);
$centers = array_map(static fn ($el) => $el['center'], $acl);
$qb = $this->repository->createQueryBuilder('acp');

View File

@@ -63,9 +63,7 @@ class CountAccompanyingPeriodWork implements ExportInterface, GroupedExportInter
$labels = array_combine($values, $values);
$labels['_header'] = $this->getTitle();
return static function ($value) use ($labels) {
return $labels[$value];
};
return static fn ($value) => $labels[$value];
}
public function getQueryKeys($data): array
@@ -90,9 +88,7 @@ class CountAccompanyingPeriodWork implements ExportInterface, GroupedExportInter
public function initiateQuery(array $requiredModifiers, array $acl, array $data = []): QueryBuilder
{
$centers = array_map(static function ($el) {
return $el['center'];
}, $acl);
$centers = array_map(static fn ($el) => $el['center'], $acl);
$qb = $this->em->createQueryBuilder();

View File

@@ -62,9 +62,7 @@ class CountEvaluation implements ExportInterface, GroupedExportInterface
$labels = array_combine($values, $values);
$labels['_header'] = $this->getTitle();
return static function ($value) use ($labels) {
return $labels[$value];
};
return static fn ($value) => $labels[$value];
}
public function getQueryKeys($data): array
@@ -89,9 +87,7 @@ class CountEvaluation implements ExportInterface, GroupedExportInterface
public function initiateQuery(array $requiredModifiers, array $acl, array $data = [])
{
$centers = array_map(static function ($el) {
return $el['center'];
}, $acl);
$centers = array_map(static fn ($el) => $el['center'], $acl);
$qb = $this->entityManager->createQueryBuilder();

View File

@@ -113,9 +113,7 @@ class CountHousehold implements ExportInterface, GroupedExportInterface
public function initiateQuery(array $requiredModifiers, array $acl, array $data = [])
{
$centers = array_map(static function ($el) {
return $el['center'];
}, $acl);
$centers = array_map(static fn ($el) => $el['center'], $acl);
$qb = $this->entityManager->createQueryBuilder();

View File

@@ -62,9 +62,7 @@ class CountPerson implements ExportInterface, GroupedExportInterface
$labels = array_combine($values, $values);
$labels['_header'] = $this->getTitle();
return static function ($value) use ($labels) {
return $labels[$value];
};
return static fn ($value) => $labels[$value];
}
public function getQueryKeys($data)
@@ -94,9 +92,7 @@ class CountPerson implements ExportInterface, GroupedExportInterface
*/
public function initiateQuery(array $requiredModifiers, array $acl, array $data = [])
{
$centers = array_map(static function ($el) {
return $el['center'];
}, $acl);
$centers = array_map(static fn ($el) => $el['center'], $acl);
$qb = $this->personRepository->createQueryBuilder('person');

View File

@@ -64,9 +64,7 @@ class CountPersonWithAccompanyingCourse implements ExportInterface, GroupedExpor
$labels = array_combine($values, $values);
$labels['_header'] = $this->getTitle();
return static function ($value) use ($labels) {
return $labels[$value];
};
return static fn ($value) => $labels[$value];
}
public function getQueryKeys($data): array
@@ -91,9 +89,7 @@ class CountPersonWithAccompanyingCourse implements ExportInterface, GroupedExpor
public function initiateQuery(array $requiredModifiers, array $acl, array $data = [])
{
$centers = array_map(static function ($el) {
return $el['center'];
}, $acl);
$centers = array_map(static fn ($el) => $el['center'], $acl);
$qb = $this->repository->createQueryBuilder('acp');

View File

@@ -41,6 +41,7 @@ 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
@@ -100,6 +101,8 @@ class ListAccompanyingPeriod implements ListInterface, GroupedExportInterface
private TranslatableStringHelperInterface $translatableStringHelper;
private TranslatorInterface $translator;
private UserHelper $userHelper;
public function __construct(
@@ -113,6 +116,7 @@ class ListAccompanyingPeriod implements ListInterface, GroupedExportInterface
SocialIssueRepository $socialIssueRepository,
SocialIssueRender $socialIssueRender,
TranslatableStringHelperInterface $translatableStringHelper,
TranslatorInterface $translator,
RollingDateConverterInterface $rollingDateConverter,
UserHelper $userHelper
) {
@@ -126,6 +130,7 @@ class ListAccompanyingPeriod implements ListInterface, GroupedExportInterface
$this->thirdPartyRender = $thirdPartyRender;
$this->thirdPartyRepository = $thirdPartyRepository;
$this->translatableStringHelper = $translatableStringHelper;
$this->translator = $translator;
$this->rollingDateConverter = $rollingDateConverter;
$this->userHelper = $userHelper;
}
@@ -182,7 +187,7 @@ class ListAccompanyingPeriod implements ListInterface, GroupedExportInterface
return '';
}
return $this->translatableStringHelper->localize(json_decode($value, true));
return $this->translatableStringHelper->localize(json_decode($value, true, 512, JSON_THROW_ON_ERROR));
};
case 'locationPersonName':
@@ -226,7 +231,7 @@ class ListAccompanyingPeriod implements ListInterface, GroupedExportInterface
'|',
array_map(
fn ($s) => $this->translatableStringHelper->localize($s),
json_decode($value, true)
json_decode($value, true, 512, JSON_THROW_ON_ERROR)
)
);
};
@@ -245,11 +250,32 @@ class ListAccompanyingPeriod implements ListInterface, GroupedExportInterface
'|',
array_map(
fn ($s) => $this->socialIssueRender->renderString($this->socialIssueRepository->find($s), []),
json_decode($value, true)
json_decode($value, true, 512, JSON_THROW_ON_ERROR)
)
);
};
case 'step':
return fn ($value) => match ($value) {
'_header' => 'export.list.acp.step',
null => '',
AccompanyingPeriod::STEP_DRAFT => $this->translator->trans('course.draft'),
AccompanyingPeriod::STEP_CONFIRMED => $this->translator->trans('course.confirmed'),
AccompanyingPeriod::STEP_CLOSED => $this->translator->trans('course.closed'),
AccompanyingPeriod::STEP_CONFIRMED_INACTIVE_SHORT => $this->translator->trans('course.inactive_short'),
AccompanyingPeriod::STEP_CONFIRMED_INACTIVE_LONG => $this->translator->trans('course.inactive_long'),
default => $value,
};
case 'intensity':
return fn ($value) => match ($value) {
'_header' => 'export.list.acp.intensity',
null => '',
AccompanyingPeriod::INTENSITY_OCCASIONAL => $this->translator->trans('occasional'),
AccompanyingPeriod::INTENSITY_REGULAR => $this->translator->trans('regular'),
default => $value,
};
default:
return static function ($value) use ($key) {
if ('_header' === $value) {
@@ -290,9 +316,7 @@ class ListAccompanyingPeriod implements ListInterface, GroupedExportInterface
public function initiateQuery(array $requiredModifiers, array $acl, array $data = [])
{
$centers = array_map(static function ($el) {
return $el['center'];
}, $acl);
$centers = array_map(static fn ($el) => $el['center'], $acl);
$qb = $this->entityManager->createQueryBuilder();

View File

@@ -263,9 +263,7 @@ class ListAccompanyingPeriodWork implements ListInterface, GroupedExportInterfac
public function initiateQuery(array $requiredModifiers, array $acl, array $data = [])
{
$centers = array_map(static function ($el) {
return $el['center'];
}, $acl);
$centers = array_map(static fn ($el) => $el['center'], $acl);
$qb = $this->entityManager->createQueryBuilder();

View File

@@ -241,9 +241,7 @@ class ListEvaluation implements ListInterface, GroupedExportInterface
public function initiateQuery(array $requiredModifiers, array $acl, array $data = [])
{
$centers = array_map(static function ($el) {
return $el['center'];
}, $acl);
$centers = array_map(static fn ($el) => $el['center'], $acl);
$qb = $this->entityManager->createQueryBuilder();

View File

@@ -146,9 +146,7 @@ class ListHouseholdInPeriod implements ListInterface, GroupedExportInterface
public function initiateQuery(array $requiredModifiers, array $acl, array $data = [])
{
$centers = array_map(static function ($el) {
return $el['center'];
}, $acl);
$centers = array_map(static fn ($el) => $el['center'], $acl);
$qb = $this->entityManager->createQueryBuilder();

View File

@@ -191,9 +191,7 @@ class ListPerson implements ExportElementValidatedInterface, ListInterface, Grou
*/
public function initiateQuery(array $requiredModifiers, array $acl, array $data = [])
{
$centers = array_map(static function ($el) {
return $el['center'];
}, $acl);
$centers = array_map(static fn ($el) => $el['center'], $acl);
// throw an error if any fields are present
if (!array_key_exists('fields', $data)) {
@@ -334,7 +332,7 @@ class ListPerson implements ExportElementValidatedInterface, ListInterface, Grou
return $this->customFieldProvider
->getCustomFieldByType($cf->getType())
->render(json_decode($value, true), $cf, 'csv');
->render(json_decode($value, true, 512, JSON_THROW_ON_ERROR), $cf, 'csv');
};
if ($cfType instanceof CustomFieldChoice && $cfType->isMultiple($cf)) {
@@ -344,7 +342,7 @@ class ListPerson implements ExportElementValidatedInterface, ListInterface, Grou
if (null === $value) {
return '';
}
$decoded = json_decode($value, true);
$decoded = json_decode($value, true, 512, JSON_THROW_ON_ERROR);
if ('_header' === $value) {
$label = $cfType->getChoices($cf)[$slugChoice];

View File

@@ -159,9 +159,7 @@ class ListPersonWithAccompanyingPeriod implements ExportElementValidatedInterfac
*/
public function initiateQuery(array $requiredModifiers, array $acl, array $data = [])
{
$centers = array_map(static function ($el) {
return $el['center'];
}, $acl);
$centers = array_map(static fn ($el) => $el['center'], $acl);
// throw an error if any fields are present
if (!array_key_exists('fields', $data)) {

View File

@@ -112,9 +112,7 @@ class StatAccompanyingCourseDuration implements ExportInterface, GroupedExportIn
public function initiateQuery(array $requiredModifiers, array $acl, array $data = []): QueryBuilder
{
$centers = array_map(static function ($el) {
return $el['center'];
}, $acl);
$centers = array_map(static fn ($el) => $el['center'], $acl);
$qb = $this->repository->createQueryBuilder('acp');

View File

@@ -59,9 +59,7 @@ class ClosingMotiveFilter implements FilterInterface
{
$builder->add('accepted_closingmotives', EntityType::class, [
'class' => ClosingMotive::class,
'choice_label' => function (ClosingMotive $cm) {
return $this->translatableStringHelper->localize($cm->getName());
},
'choice_label' => fn (ClosingMotive $cm) => $this->translatableStringHelper->localize($cm->getName()),
'multiple' => true,
'expanded' => true,
]);

View File

@@ -61,11 +61,9 @@ class CreatorJobFilter implements FilterInterface
$builder->add('creator_job', EntityType::class, [
'class' => UserJob::class,
'choices' => $this->userJobRepository->findAllActive(),
'choice_label' => function (UserJob $j) {
return $this->translatableStringHelper->localize(
$j->getLabel()
);
},
'choice_label' => fn (UserJob $j) => $this->translatableStringHelper->localize(
$j->getLabel()
),
'multiple' => true,
'expanded' => true,
'label' => 'Job',

View File

@@ -69,9 +69,7 @@ class EvaluationFilter implements FilterInterface
$builder->add('accepted_evaluations', EntityType::class, [
'class' => Evaluation::class,
'choices' => $this->evaluationRepository->findAllActive(),
'choice_label' => function (Evaluation $ev) {
return $this->translatableStringHelper->localize($ev->getTitle());
},
'choice_label' => fn (Evaluation $ev) => $this->translatableStringHelper->localize($ev->getTitle()),
'multiple' => true,
'expanded' => false,
'attr' => ['class' => 'select2'],

View File

@@ -107,9 +107,7 @@ class GeographicalUnitStatFilter implements FilterInterface
'placeholder' => 'Select a geographical unit',
'choices' => $this->geographicalUnitRepository->findAll(),
'choice_value' => static fn (SimpleGeographicalUnitDTO $item) => $item->id,
'choice_label' => function (SimpleGeographicalUnitDTO $item) {
return $this->translatableStringHelper->localize($this->geographicalUnitLayerRepository->find($item->layerId)->getName()) . ' > ' . $item->unitName;
},
'choice_label' => fn (SimpleGeographicalUnitDTO $item) => $this->translatableStringHelper->localize($this->geographicalUnitLayerRepository->find($item->layerId)->getName()) . ' > ' . $item->unitName,
'attr' => [
'class' => 'select2',
],
@@ -124,9 +122,7 @@ class GeographicalUnitStatFilter implements FilterInterface
'%units' => implode(
', ',
array_map(
function (SimpleGeographicalUnitDTO $item) {
return $this->translatableStringHelper->localize($this->geographicalUnitLayerRepository->find($item->layerId)->getName()) . ' > ' . $item->unitName;
},
fn (SimpleGeographicalUnitDTO $item) => $this->translatableStringHelper->localize($this->geographicalUnitLayerRepository->find($item->layerId)->getName()) . ' > ' . $item->unitName,
$data['units']
)
),

View File

@@ -0,0 +1,91 @@
<?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\Filter\AccompanyingCourseFilters;
use Chill\MainBundle\Export\FilterInterface;
use Chill\MainBundle\Form\Type\DateIntervalType;
use Chill\MainBundle\Form\Type\PickRollingDateType;
use Chill\MainBundle\Service\RollingDate\RollingDate;
use Chill\MainBundle\Service\RollingDate\RollingDateConverterInterface;
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodInfo;
use Chill\PersonBundle\Export\Declarations;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface;
/**
* Filter accompanying course which have a row in AccompanyingPeriodInfo within the given
* interval
*/
final readonly class HavingAnAccompanyingPeriodInfoWithinDatesFilter implements FilterInterface
{
public function __construct(
private RollingDateConverterInterface $rollingDateConverter,
) {
}
public function buildForm(FormBuilderInterface $builder): void
{
$builder
->add('start_date', PickRollingDateType::class, [
'label' => 'export.filter.course.having_info_within_interval.start_date',
'data' => new RollingDate(RollingDate::T_TODAY),
])
->add('end_date', PickRollingDateType::class, [
'label' => 'export.filter.course.having_info_within_interval.end_date',
'data' => new RollingDate(RollingDate::T_TODAY),
])
;
}
public function getTitle(): string
{
return 'export.filter.course.having_info_within_interval.title';
}
public function describeAction($data, $format = 'string'): array
{
return [
'export.filter.course.having_info_within_interval.Only course with events between %startDate% and %endDate%',
[
'%startDate%' => $this->rollingDateConverter->convert($data['start_date'])->format('d-m-Y'),
'%endDate%' => $this->rollingDateConverter->convert($data['end_date'])->format('d-m-Y'),
]
];
}
public function addRole(): ?string
{
return null;
}
public function alterQuery(QueryBuilder $qb, $data): void
{
$ai = 'having_ai_within_interval_acc_info';
$as = 'having_ai_within_interval_start_date';
$ae = 'having_ai_within_interval_end_date';
$qb
->andWhere(
$qb->expr()->exists(
'SELECT 1 FROM ' . AccompanyingPeriodInfo::class . " {$ai} WHERE {$ai}.infoDate BETWEEN :{$as} AND :{$ae} AND IDENTITY({$ai}.accompanyingPeriod) = acp.id"
)
)
->setParameter($as, $this->rollingDateConverter->convert($data['start_date']), Types::DATETIME_IMMUTABLE)
->setParameter($ae, $this->rollingDateConverter->convert($data['end_date']), Types::DATETIME_IMMUTABLE);
}
public function applyOn(): string
{
return Declarations::ACP_TYPE;
}
}

View File

@@ -59,9 +59,7 @@ class OriginFilter implements FilterInterface
{
$builder->add('accepted_origins', EntityType::class, [
'class' => Origin::class,
'choice_label' => function (Origin $o) {
return $this->translatableStringHelper->localize($o->getLabel());
},
'choice_label' => fn (Origin $o) => $this->translatableStringHelper->localize($o->getLabel()),
'multiple' => true,
'expanded' => true,
]);

View File

@@ -32,9 +32,11 @@ class StepFilter implements FilterInterface
private const P = 'acp_step_filter_date';
private const STEPS = [
'Draft' => AccompanyingPeriod::STEP_DRAFT,
'Confirmed' => AccompanyingPeriod::STEP_CONFIRMED,
'Closed' => AccompanyingPeriod::STEP_CLOSED,
'course.draft' => AccompanyingPeriod::STEP_DRAFT,
'course.confirmed' => AccompanyingPeriod::STEP_CONFIRMED,
'course.closed' => AccompanyingPeriod::STEP_CLOSED,
'course.inactive_short' => AccompanyingPeriod::STEP_CONFIRMED_INACTIVE_SHORT,
'course.inactive_long' => AccompanyingPeriod::STEP_CONFIRMED_INACTIVE_LONG,
];
private RollingDateConverterInterface $rollingDateConverter;
@@ -96,7 +98,7 @@ class StepFilter implements FilterInterface
'data' => self::DEFAULT_CHOICE,
])
->add('calc_date', PickRollingDateType::class, [
'label' => 'export.acp.filter.by_step.date_calc',
'label' => 'export.filter.course.by_step.date_calc',
'data' => new RollingDate(RollingDate::T_TODAY),
]);
}

View File

@@ -0,0 +1,88 @@
<?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\Filter\AccompanyingCourseFilters;
use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Export\FilterInterface;
use Chill\MainBundle\Form\Type\PickUserDynamicType;
use Chill\MainBundle\Templating\Entity\UserRender;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Export\Declarations;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface;
/**
* Filter course where a user is "working" on it
*
* Makes use of AccompanyingPeriodInfo
*/
readonly class UserWorkingOnCourseFilter implements FilterInterface
{
private const AI_ALIAS = 'user_working_on_course_filter_acc_info';
private const AI_USERS = 'user_working_on_course_filter_users';
public function __construct(
private UserRender $userRender,
) {
}
public function buildForm(FormBuilderInterface $builder): void
{
$builder
->add('users', PickUserDynamicType::class, [
'multiple' => true,
]);
}
public function getTitle(): string
{
return 'export.filter.course.by_user_working.title';
}
public function describeAction($data, $format = 'string'): array
{
return [
'export.filter.course.by_user_working.Filtered by user working on course: only %users%', [
'%users%' => implode(
', ',
array_map(
fn (User $u) => $this->userRender->renderString($u, []),
$data['users']
)
),
],
];
}
public function addRole(): ?string
{
return null;
}
public function alterQuery(QueryBuilder $qb, $data): void
{
$qb
->andWhere(
$qb->expr()->exists(
"SELECT 1 FROM " . AccompanyingPeriod\AccompanyingPeriodInfo::class . " " . self::AI_ALIAS . " " .
"WHERE " . self::AI_ALIAS . ".user IN (:" . self::AI_USERS .") AND IDENTITY(" . self::AI_ALIAS . ".accompanyingPeriod) = acp.id"
)
)
->setParameter(self::AI_USERS, $data['users'])
;
}
public function applyOn(): string
{
return Declarations::ACP_TYPE;
}
}

View File

@@ -59,9 +59,7 @@ final class EvaluationTypeFilter implements FilterInterface
{
$builder->add('accepted_evaluationtype', EntityType::class, [
'class' => Evaluation::class,
'choice_label' => function (Evaluation $ev): string {
return $this->translatableStringHelper->localize($ev->getTitle());
},
'choice_label' => fn (Evaluation $ev): string => $this->translatableStringHelper->localize($ev->getTitle()),
'multiple' => true,
'expanded' => true,
]);

View File

@@ -78,11 +78,9 @@ class CompositionFilter implements FilterInterface
$builder
->add('accepted_composition', EntityType::class, [
'class' => HouseholdCompositionType::class,
'choice_label' => function (HouseholdCompositionType $type) {
return $this->translatableStringHelper->localize(
$type->getLabel()
);
},
'choice_label' => fn (HouseholdCompositionType $type) => $this->translatableStringHelper->localize(
$type->getLabel()
),
'multiple' => true,
'expanded' => true,
])

View File

@@ -81,9 +81,7 @@ class AddressRefStatusFilter implements \Chill\MainBundle\Export\FilterInterface
->add('ref_statuses', ChoiceType::class, [
'label' => 'export.filter.person.by_address_ref_status.Status',
'choices' => [Address::ADDR_REFERENCE_STATUS_TO_REVIEW, Address::ADDR_REFERENCE_STATUS_REVIEWED, Address::ADDR_REFERENCE_STATUS_MATCH],
'choice_label' => function (string $item) {
return 'export.filter.person.by_address_ref_status.'.$item;
},
'choice_label' => fn (string $item) => 'export.filter.person.by_address_ref_status.'.$item,
'multiple' => true,
'expanded' => true,
'data' => [Address::ADDR_REFERENCE_STATUS_TO_REVIEW]
@@ -99,9 +97,7 @@ class AddressRefStatusFilter implements \Chill\MainBundle\Export\FilterInterface
'%statuses%' => implode(
', ',
array_map(
function (string $item) {
return 'export.filter.person.by_address_ref_status.'.$item;
},
fn (string $item) => 'export.filter.person.by_address_ref_status.'.$item,
$data['ref_statuses'] ?? RollingDate::T_TODAY
)
),

View File

@@ -42,8 +42,8 @@ class AgeFilter implements ExportElementValidatedInterface, FilterInterface
{
$where = $qb->getDQLPart('where');
$min = null !== $data['min_age'] ? $data['min_age'] : 0;
$max = null !== $data['max_age'] ? $data['max_age'] : 150;
$min = $data['min_age'] ?? 0;
$max = $data['max_age'] ?? 150;
$calc = $this->rollingDateConverter->convert($data['date_calc']);
$minDate = $calc->sub(new DateInterval('P' . $max . 'Y'));

View File

@@ -67,9 +67,7 @@ class GenderFilter implements
$qb->add('where', $where);
$qb->setParameter('person_gender', array_filter(
$data['accepted_genders'],
static function ($el) {
return 'null' !== $el;
}
static fn ($el) => 'null' !== $el
));
}

View File

@@ -98,9 +98,7 @@ class GeographicalUnitFilter implements \Chill\MainBundle\Export\FilterInterface
'placeholder' => 'Select a geographical unit',
'choices' => $this->geographicalUnitRepository->findAll(),
'choice_value' => static fn (SimpleGeographicalUnitDTO $item) => $item->id,
'choice_label' => function (SimpleGeographicalUnitDTO $item) {
return $this->translatableStringHelper->localize($this->geographicalUnitLayerRepository->find($item->layerId)->getName()) . ' > ' . $item->unitName;
},
'choice_label' => fn (SimpleGeographicalUnitDTO $item) => $this->translatableStringHelper->localize($this->geographicalUnitLayerRepository->find($item->layerId)->getName()) . ' > ' . $item->unitName,
'attr' => [
'class' => 'select2',
],
@@ -117,9 +115,7 @@ class GeographicalUnitFilter implements \Chill\MainBundle\Export\FilterInterface
'%units%' => implode(
', ',
array_map(
function (SimpleGeographicalUnitDTO $item) {
return $this->translatableStringHelper->localize($this->geographicalUnitLayerRepository->find($item->layerId)->getName()) . ' > ' . $item->unitName;
},
fn (SimpleGeographicalUnitDTO $item) => $this->translatableStringHelper->localize($this->geographicalUnitLayerRepository->find($item->layerId)->getName()) . ' > ' . $item->unitName,
$data['units']
)
),

View File

@@ -49,11 +49,9 @@ class MaritalStatusFilter implements FilterInterface
{
$builder->add('maritalStatus', EntityType::class, [
'class' => MaritalStatus::class,
'choice_label' => function (MaritalStatus $ms) {
return $this->translatableStringHelper->localize(
$ms->getName()
);
},
'choice_label' => fn (MaritalStatus $ms) => $this->translatableStringHelper->localize(
$ms->getName()
),
'multiple' => true,
'expanded' => true,
]);

View File

@@ -72,9 +72,7 @@ class NationalityFilter implements
{
$countries = $data['nationalities'];
$names = array_map(function (Country $c) {
return $this->translatableStringHelper->localize($c->getName());
}, [$countries]);
$names = array_map(fn (Country $c) => $this->translatableStringHelper->localize($c->getName()), [$countries]);
return [
'Filtered by nationality : %nationalities%',

View File

@@ -100,9 +100,7 @@ class ResidentialAddressAtThirdpartyFilter implements FilterInterface
$builder->add('thirdparty_cat', EntityType::class, [
'class' => ThirdPartyCategory::class,
'label' => 'Category',
'choice_label' => function (ThirdPartyCategory $tpc) {
return $this->translatableStringHelper->localize($tpc->getName());
},
'choice_label' => fn (ThirdPartyCategory $tpc) => $this->translatableStringHelper->localize($tpc->getName()),
'multiple' => true,
'expanded' => true,
]);

View File

@@ -0,0 +1,114 @@
<?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\Filter\SocialWorkFilters;
use Chill\MainBundle\Export\FilterInterface;
use Chill\MainBundle\Form\Type\PickRollingDateType;
use Chill\MainBundle\Service\RollingDate\RollingDate;
use Chill\MainBundle\Service\RollingDate\RollingDateConverterInterface;
use Chill\PersonBundle\Export\Declarations;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\FormBuilderInterface;
final readonly class AccompanyingPeriodWorkEndDateBetweenDateFilter implements FilterInterface
{
public function __construct(
private RollingDateConverterInterface $rollingDateConverter,
) {
}
public function buildForm(FormBuilderInterface $builder): void
{
$builder
->add('start_date', PickRollingDateType::class, [
'label' => 'export.filter.work.end_between_dates.start_date',
'data' => new RollingDate(RollingDate::T_TODAY),
])
->add('end_date', PickRollingDateType::class, [
'label' => 'export.filter.work.end_between_dates.end_date',
'data' => new RollingDate(RollingDate::T_TODAY),
])
->add('keep_null', CheckboxType::class, [
'label' => 'export.filter.work.end_between_dates.keep_null',
'help' => 'export.filter.work.end_between_dates.keep_null_help',
])
;
}
public function getTitle(): string
{
return 'export.filter.work.end_between_dates.title';
}
public function describeAction($data, $format = 'string'): array
{
return [
'export.filter.work.end_between_dates.Only where end date is between %endDate% and %endDate%',
[
'%startDate%' => null !== $data['start_date'] ? $this->rollingDateConverter->convert($data['start_date'])->format('d-m-Y') : '',
'%endDate%' => null !== $data['end_date'] ? $this->rollingDateConverter->convert($data['end_date'])->format('d-m-Y') : '',
]
];
}
public function addRole(): ?string
{
return null;
}
public function alterQuery(QueryBuilder $qb, $data): void
{
$as = 'acc_pe_work_end_between_filter_start';
$ae = 'acc_pe_work_end_between_filter_end';
$start = match ($data['keep_null']) {
true => $qb->expr()->orX(
$qb->expr()->lte('acpw.endDate', ':'.$ae),
$qb->expr()->isNull('acpw.endDate')
),
false => $qb->expr()->andX(
$qb->expr()->lte('acpw.endDate', ':'.$ae),
$qb->expr()->isNotNull('acpw.endDate')
),
default => throw new \LogicException("This value is not supported"),
};
$end = match ($data['keep_null']) {
true => $qb->expr()->orX(
$qb->expr()->gt('acpw.endDate', ':'.$as),
$qb->expr()->isNull('acpw.endDate')
),
false => $qb->expr()->andX(
$qb->expr()->gt('acpw.endDate', ':'.$as),
$qb->expr()->isNotNull('acpw.endDate')
),
default => throw new \LogicException("This value is not supported"),
};
if (null !== $data['start_date']) {
$qb
->andWhere($start)
->setParameter($as, $this->rollingDateConverter->convert($data['start_date']));
}
if (null !== $data['end_date']) {
$qb
->andWhere($end)
->setParameter($ae, $this->rollingDateConverter->convert($data['end_date']));
}
}
public function applyOn(): string
{
return Declarations::SOCIAL_WORK_ACTION_TYPE;
}
}

View File

@@ -0,0 +1,114 @@
<?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\Filter\SocialWorkFilters;
use Chill\MainBundle\Export\FilterInterface;
use Chill\MainBundle\Form\Type\PickRollingDateType;
use Chill\MainBundle\Service\RollingDate\RollingDate;
use Chill\MainBundle\Service\RollingDate\RollingDateConverterInterface;
use Chill\PersonBundle\Export\Declarations;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\FormBuilderInterface;
final readonly class AccompanyingPeriodWorkStartDateBetweenDateFilter implements FilterInterface
{
public function __construct(
private RollingDateConverterInterface $rollingDateConverter,
) {
}
public function buildForm(FormBuilderInterface $builder): void
{
$builder
->add('start_date', PickRollingDateType::class, [
'label' => 'export.filter.work.start_between_dates.start_date',
'data' => new RollingDate(RollingDate::T_TODAY),
])
->add('end_date', PickRollingDateType::class, [
'label' => 'export.filter.work.start_between_dates.end_date',
'data' => new RollingDate(RollingDate::T_TODAY),
])
->add('keep_null', CheckboxType::class, [
'label' => 'export.filter.work.start_between_dates.keep_null',
'help' => 'export.filter.work.start_between_dates.keep_null_help',
])
;
}
public function getTitle(): string
{
return 'export.filter.work.start_between_dates.title';
}
public function describeAction($data, $format = 'string'): array
{
return [
'export.filter.work.start_between_dates.Only where start date is between %startDate% and %endDate%',
[
'%startDate%' => null !== $data['start_date'] ? $this->rollingDateConverter->convert($data['start_date'])->format('d-m-Y') : '',
'%endDate%' => null !== $data['end_date'] ? $this->rollingDateConverter->convert($data['end_date'])->format('d-m-Y') : '',
]
];
}
public function addRole(): ?string
{
return null;
}
public function alterQuery(QueryBuilder $qb, $data): void
{
$as = 'acc_pe_work_start_between_filter_start';
$ae = 'acc_pe_work_start_between_filter_end';
$start = match ($data['keep_null']) {
true => $qb->expr()->orX(
$qb->expr()->lte('acpw.startDate', ':'.$ae),
$qb->expr()->isNull('acpw.startDate')
),
false => $qb->expr()->andX(
$qb->expr()->lte('acpw.startDate', ':'.$ae),
$qb->expr()->isNotNull('acpw.startDate')
),
default => throw new \LogicException("This value is not supported"),
};
$end = match ($data['keep_null']) {
true => $qb->expr()->orX(
$qb->expr()->gt('acpw.startDate', ':'.$as),
$qb->expr()->isNull('acpw.startDate')
),
false => $qb->expr()->andX(
$qb->expr()->gt('acpw.startDate', ':'.$as),
$qb->expr()->isNotNull('acpw.startDate')
),
default => throw new \LogicException("This value is not supported"),
};
if (null !== $data['start_date']) {
$qb
->andWhere($start)
->setParameter($as, $this->rollingDateConverter->convert($data['start_date']));
}
if (null !== $data['end_date']) {
$qb
->andWhere($end)
->setParameter($ae, $this->rollingDateConverter->convert($data['end_date']));
}
}
public function applyOn(): string
{
return Declarations::SOCIAL_WORK_ACTION_TYPE;
}
}

View File

@@ -69,11 +69,9 @@ class JobFilter implements FilterInterface
{
$builder->add('job', EntityType::class, [
'class' => UserJob::class,
'choice_label' => function (UserJob $j) {
return $this->translatableStringHelper->localize(
$j->getLabel()
);
},
'choice_label' => fn (UserJob $j) => $this->translatableStringHelper->localize(
$j->getLabel()
),
'multiple' => true,
'expanded' => true,
]);

View File

@@ -69,11 +69,9 @@ class ScopeFilter implements FilterInterface
{
$builder->add('scope', EntityType::class, [
'class' => Scope::class,
'choice_label' => function (Scope $s) {
return $this->translatableStringHelper->localize(
$s->getName()
);
},
'choice_label' => fn (Scope $s) => $this->translatableStringHelper->localize(
$s->getName()
),
'multiple' => true,
'expanded' => true,
]);

View File

@@ -136,7 +136,7 @@ class SocialWorkTypeFilter implements FilterInterface
}
return ['Filtered actions by type, goals and results: %selected%', [
'%selected%' => implode(', ', array_merge($actionTypes, $goals, $results)),
'%selected%' => implode(', ', [...$actionTypes, ...$goals, ...$results]),
]];
}

View File

@@ -39,7 +39,7 @@ class LabelPersonHelper
return '';
}
$decoded = json_decode($value);
$decoded = json_decode($value, null, 512, JSON_THROW_ON_ERROR);
if (0 === count($decoded)) {
return '';

View File

@@ -361,7 +361,7 @@ class ListPersonHelper
return '';
}
$ids = json_decode($value);
$ids = json_decode($value, null, 512, JSON_THROW_ON_ERROR);
return
implode(

View File

@@ -98,7 +98,7 @@ class AccompanyingPeriodType extends AbstractType
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => 'Chill\PersonBundle\Entity\AccompanyingPeriod',
'data_class' => \Chill\PersonBundle\Entity\AccompanyingPeriod::class,
]);
$resolver

View File

@@ -55,9 +55,7 @@ class PersonChoiceLoader implements ChoiceLoaderInterface
{
return new \Symfony\Component\Form\ChoiceList\ArrayChoiceList(
$this->lazyLoadedPersons,
static function (Person $p) use ($value) {
return call_user_func($value, $p);
}
static fn (Person $p) => call_user_func($value, $p)
);
}

View File

@@ -40,9 +40,7 @@ class HouseholdCompositionType extends AbstractType
->add('householdCompositionType', EntityType::class, [
'class' => \Chill\PersonBundle\Entity\Household\HouseholdCompositionType::class,
'choices' => $types,
'choice_label' => function (\Chill\PersonBundle\Entity\Household\HouseholdCompositionType $type) {
return $this->translatableStringHelper->localize($type->getLabel());
},
'choice_label' => fn (\Chill\PersonBundle\Entity\Household\HouseholdCompositionType $type) => $this->translatableStringHelper->localize($type->getLabel()),
'label' => 'household_composition.Household composition',
])
->add('startDate', ChillDateType::class, [

View File

@@ -118,12 +118,8 @@ class PersonType extends AbstractType
]);
$builder->get('placeOfBirth')->addModelTransformer(new CallbackTransformer(
static function ($string) {
return strtoupper((string) $string);
},
static function ($string) {
return strtoupper((string) $string);
}
static fn ($string) => strtoupper((string) $string),
static fn ($string) => strtoupper((string) $string)
));
}
@@ -167,9 +163,7 @@ class PersonType extends AbstractType
'allow_delete' => true,
'by_reference' => false,
'label' => false,
'delete_empty' => static function (?PersonPhone $pp = null) {
return null === $pp || $pp->isEmpty();
},
'delete_empty' => static fn (?PersonPhone $pp = null) => null === $pp || $pp->isEmpty(),
'error_bubbling' => false,
'empty_collection_explain' => 'No additional phone numbers',
]);
@@ -250,7 +244,7 @@ class PersonType extends AbstractType
$resolver->setAllowedTypes(
'cFGroup',
['null', 'Chill\CustomFieldsBundle\Entity\CustomFieldsGroup']
['null', \Chill\CustomFieldsBundle\Entity\CustomFieldsGroup::class]
);
}

View File

@@ -46,9 +46,7 @@ class GoalType extends AbstractType
'class' => Result::class,
'required' => false,
'multiple' => true,
'choice_label' => function (Result $r) {
return $this->translatableStringHelper->localize($r->getTitle());
},
'choice_label' => fn (Result $r) => $this->translatableStringHelper->localize($r->getTitle()),
'attr' => ['class' => 'select2 '],
])
->add('desactivationDate', ChillDateType::class, [

View File

@@ -50,16 +50,12 @@ class SocialActionType extends AbstractType
->add('issue', EntityType::class, [
'class' => SocialIssue::class,
'label' => 'socialAction.socialIssue',
'choice_label' => function (SocialIssue $issue) {
return $this->translatableStringHelper->localize($issue->getTitle());
},
'choice_label' => fn (SocialIssue $issue) => $this->translatableStringHelper->localize($issue->getTitle()),
])
->add('parent', EntityType::class, [
'class' => SocialAction::class,
'required' => false,
'choice_label' => function (SocialAction $issue) {
return $this->translatableStringHelper->localize($issue->getTitle());
},
'choice_label' => fn (SocialAction $issue) => $this->translatableStringHelper->localize($issue->getTitle()),
])
->add('ordering', NumberType::class, [
'required' => true,
@@ -70,9 +66,7 @@ class SocialActionType extends AbstractType
'required' => false,
'multiple' => true,
'attr' => ['class' => 'select2'],
'choice_label' => function (Result $r) {
return $this->translatableStringHelper->localize($r->getTitle());
},
'choice_label' => fn (Result $r) => $this->translatableStringHelper->localize($r->getTitle()),
])
->add('goals', EntityType::class, [
@@ -80,9 +74,7 @@ class SocialActionType extends AbstractType
'required' => false,
'multiple' => true,
'attr' => ['class' => 'select2'],
'choice_label' => function (Goal $g) {
return $this->translatableStringHelper->localize($g->getTitle());
},
'choice_label' => fn (Goal $g) => $this->translatableStringHelper->localize($g->getTitle()),
])
->add('evaluations', EntityType::class, [
@@ -90,9 +82,7 @@ class SocialActionType extends AbstractType
'required' => false,
'multiple' => true,
'attr' => ['class' => 'select2'],
'choice_label' => function (Evaluation $e) {
return $this->translatableStringHelper->localize($e->getTitle());
},
'choice_label' => fn (Evaluation $e) => $this->translatableStringHelper->localize($e->getTitle()),
])
->add('defaultNotificationDelay', DateIntervalType::class, [

View File

@@ -60,18 +60,14 @@ class ClosingMotivePickerType extends AbstractType
'class' => ClosingMotive::class,
'empty_data' => null,
'placeholder' => 'Choose a motive',
'choice_label' => function (ClosingMotive $cm) {
return $this->entityRenderExtension->renderString($cm);
},
'choice_label' => fn (ClosingMotive $cm) => $this->entityRenderExtension->renderString($cm),
'only_leaf' => true,
]);
$resolver
->setAllowedTypes('only_leaf', 'bool')
->setNormalizer('choices', function (Options $options) {
return $this->repository
->getActiveClosingMotive($options['only_leaf']);
});
->setNormalizer('choices', fn (Options $options) => $this->repository
->getActiveClosingMotive($options['only_leaf']));
}
/**

View File

@@ -113,15 +113,11 @@ class PickPersonType extends AbstractType
// add the default options
$resolver->setDefaults([
'class' => Person::class,
'choice_label' => static function (Person $p) {
return $p->getFirstname() . ' ' . $p->getLastname();
},
'choice_label' => static fn (Person $p) => $p->getFirstname() . ' ' . $p->getLastname(),
'placeholder' => 'Pick a person',
'choice_attr' => static function (Person $p) {
return [
'data-center' => $p->getCenter()->getId(),
];
},
'choice_attr' => static fn (Person $p) => [
'data-center' => $p->getCenter()->getId(),
],
'attr' => ['class' => 'select2 '],
'choice_loader' => function (Options $options) {
$centers = $this->filterCentersfom($options);
@@ -139,9 +135,7 @@ class PickPersonType extends AbstractType
protected function filterCentersfom(Options $options)
{
if (null === $options['role']) {
$centers = array_map(static function (GroupCenter $g) {
return $g->getCenter();
}, $this->user->getGroupCenters()->toArray());
$centers = array_map(static fn (GroupCenter $g) => $g->getCenter(), $this->user->getGroupCenters()->toArray());
} else {
$centers = $this->authorizationHelper
->getReachableCenters($this->user, $options['role']->getRole());
@@ -164,9 +158,7 @@ class PickPersonType extends AbstractType
if (
!in_array($c->getId(), array_map(
static function (Center $c) {
return $c->getId();
},
static fn (Center $c) => $c->getId(),
$centers
), true)
) {

View File

@@ -38,9 +38,7 @@ class PickSocialActionType extends AbstractType
->setDefaults([
'class' => SocialAction::class,
'choices' => $this->actionRepository->findAllActive(),
'choice_label' => function (SocialAction $sa) {
return $this->actionRender->renderString($sa, []);
},
'choice_label' => fn (SocialAction $sa) => $this->actionRender->renderString($sa, []),
'placeholder' => 'Pick a social action',
'required' => false,
'attr' => ['class' => 'select2'],

View File

@@ -38,9 +38,7 @@ class PickSocialIssueType extends AbstractType
->setDefaults([
'class' => SocialIssue::class,
'choices' => $this->issueRepository->findAllActive(),
'choice_label' => function (SocialIssue $si) {
return $this->issueRender->renderString($si, []);
},
'choice_label' => fn (SocialIssue $si) => $this->issueRender->renderString($si, []),
'placeholder' => 'Pick a social issue',
'required' => false,
'attr' => ['class' => 'select2'],

View File

@@ -40,13 +40,13 @@ class Select2MaritalStatusType extends AbstractType
public function buildForm(FormBuilderInterface $builder, array $options)
{
$transformer = new ObjectToIdTransformer($this->em, 'Chill\PersonBundle\Entity\MaritalStatus');
$transformer = new ObjectToIdTransformer($this->em, \Chill\PersonBundle\Entity\MaritalStatus::class);
$builder->addModelTransformer($transformer);
}
public function configureOptions(OptionsResolver $resolver)
{
$maritalStatuses = $this->em->getRepository('Chill\PersonBundle\Entity\MaritalStatus')->findAll();
$maritalStatuses = $this->em->getRepository(\Chill\PersonBundle\Entity\MaritalStatus::class)->findAll();
$choices = [];
foreach ($maritalStatuses as $ms) {

View File

@@ -168,7 +168,7 @@ class MembersEditor
$criteria->where(
$expr->andX(
$expr->lt('startDate', $date),
$expr->isNull('endDate', $date)
$expr->isNull('endDate')
)
);

View File

@@ -76,9 +76,7 @@ class PrivacyEventSubscriber implements EventSubscriberInterface
$involved = $this->getInvolved();
$involved['period_id'] = $event->getPeriod()->getId();
$involved['persons'] = $event->getPeriod()->getPersons()
->map(static function (Person $p) {
return $p->getId();
})
->map(static fn (Person $p) => $p->getId())
->toArray();
$this->logger->notice(
@@ -102,9 +100,7 @@ class PrivacyEventSubscriber implements EventSubscriberInterface
if ($event->hasPersons()) {
$involved['persons'] = array_map(
static function (Person $p) {
return $p->getId();
},
static fn (Person $p) => $p->getId(),
$event->getPersons()
);
}

View File

@@ -0,0 +1,93 @@
<?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\Repository\AccompanyingPeriod;
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodInfo;
use DateInterval;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityRepository;
use LogicException;
use Symfony\Component\Clock\ClockInterface;
readonly class AccompanyingPeriodInfoRepository implements AccompanyingPeriodInfoRepositoryInterface
{
private EntityRepository $entityRepository;
public function __construct(
private ClockInterface $clock,
private EntityManagerInterface $em,
) {
$this->entityRepository = $em->getRepository($this->getClassName());
}
public function findAccompanyingPeriodIdInactiveAfter(DateInterval $interval, array $statuses = []): array
{
$query = $this->em->createQuery();
$baseDql = 'SELECT DISTINCT IDENTITY(ai.accompanyingPeriod) FROM '.AccompanyingPeriodInfo::class.' ai JOIN ai.accompanyingPeriod a WHERE NOT EXISTS
(SELECT 1 FROM ' . AccompanyingPeriodInfo::class . ' aiz WHERE aiz.infoDate > :after AND IDENTITY(aiz.accompanyingPeriod) = IDENTITY(ai.accompanyingPeriod))';
if ([] !== $statuses) {
$dql = $baseDql . ' AND a.step IN (:statuses)';
$query->setParameter('statuses', $statuses);
} else {
$dql = $baseDql;
}
return $query->setDQL($dql)
->setParameter('after', $this->clock->now()->sub($interval))
->getSingleColumnResult();
}
public function findAccompanyingPeriodIdActiveSince(DateInterval $interval, array $statuses = []): array
{
$query = $this->em->createQuery();
$baseDql = 'SELECT DISTINCT IDENTITY(ai.accompanyingPeriod) FROM ' . AccompanyingPeriodInfo::class . ' ai
JOIN ai.accompanyingPeriod a WHERE ai.infoDate > :after';
if ([] !== $statuses) {
$dql = $baseDql . ' AND a.step IN (:statuses)';
$query->setParameter('statuses', $statuses);
} else {
$dql = $baseDql;
}
return $query->setDQL($dql)
->setParameter('after', $this->clock->now()->sub($interval))
->getSingleColumnResult();
}
public function find($id): ?AccompanyingPeriodInfo
{
throw new LogicException("Calling an accompanying period info by his id does not make sense");
}
public function findAll(): array
{
return $this->entityRepository->findAll();
}
public function findBy(array $criteria, ?array $orderBy = null, ?int $limit = null, ?int $offset = null): array
{
return $this->entityRepository->findBy($criteria, $orderBy, $limit, $offset);
}
public function findOneBy(array $criteria): ?AccompanyingPeriodInfo
{
return $this->entityRepository->findOneBy($criteria);
}
public function getClassName(): string
{
return AccompanyingPeriodInfo::class;
}
}

View File

@@ -0,0 +1,38 @@
<?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\Repository\AccompanyingPeriod;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodInfo;
use Doctrine\Persistence\ObjectRepository;
/**
* @template-extends ObjectRepository<AccompanyingPeriodInfo>
*/
interface AccompanyingPeriodInfoRepositoryInterface extends ObjectRepository
{
/**
* Return a list of id for inactive accompanying periods
*
* @param \DateInterval $interval
* @param list<AccompanyingPeriod::STEP_*> $statuses
* @return list<int>
*/
public function findAccompanyingPeriodIdInactiveAfter(\DateInterval $interval, array $statuses = []): array;
/**
* @param \DateInterval $interval
* @param list<AccompanyingPeriod::STEP_*> $statuses
* @return list<int>
*/
public function findAccompanyingPeriodIdActiveSince(\DateInterval $interval, array $statuses = []): array;
}

View File

@@ -322,9 +322,7 @@ final class PersonACLAwareRepository implements PersonACLAwareRepositoryInterfac
),
]
),
array_map(static function (Center $c) {
return $c->getId();
}, $authorizedCenters)
array_map(static fn (Center $c) => $c->getId(), $authorizedCenters)
);
}
}

View File

@@ -44,7 +44,7 @@ class ResidentialAddressRepository extends ServiceEntityRepository
public function buildQueryFindCurrentResidentialAddresses(Person $person, ?DateTimeImmutable $at = null): QueryBuilder
{
$date = null === $at ? new DateTimeImmutable('today') : $at;
$date = $at ?? new DateTimeImmutable('today');
$qb = $this->createQueryBuilder('ra');
$dateFilter = $qb->expr()->andX(

View File

@@ -14,7 +14,7 @@
<scopes></scopes>
<referrer></referrer>
<resources></resources>
<start-date v-if="accompanyingCourse.step === 'CONFIRMED'"></start-date>
<start-date v-if="accompanyingCourse.step.startsWith('CONFIRMED')"></start-date>
<comment v-if="accompanyingCourse.step === 'DRAFT'"></comment>
<confirm v-if="accompanyingCourse.step === 'DRAFT'"></confirm>

View File

@@ -11,12 +11,22 @@
{{ $t('course.step.draft') }}
</span>
</span>
<span v-else-if="accompanyingCourse.step === 'CONFIRMED'" class="text-md-end">
<span class="d-md-block mb-md-3">
<span v-else-if="accompanyingCourse.step === 'CONFIRMED' || accompanyingCourse.step === 'CONFIRMED_INACTIVE_SHORT' || accompanyingCourse.step === 'CONFIRMED_INACTIVE_LONG'" class="text-md-end">
<span v-if="accompanyingCourse.step === 'CONFIRMED'" class="d-md-block mb-md-3">
<span class="badge bg-primary">
{{ $t('course.step.active') }}
</span>
</span>
<span v-else-if="accompanyingCourse.step === 'CONFIRMED_INACTIVE_SHORT'" class="d-md-block mb-md-3">
<span class="badge bg-chill-yellow text-primary">
{{ $t('course.step.inactive_short') }}
</span>
</span>
<span v-else-if="accompanyingCourse.step === 'CONFIRMED_INACTIVE_LONG'" class="d-md-block mb-md-3">
<span class="badge bg-chill-pink">
{{ $t('course.step.inactive_long') }}
</span>
</span>
<span class="d-md-block">
<span class="d-md-block ms-3 ms-md-0">
<i>{{ $t('course.open_at') }}{{ $d(accompanyingCourse.openingDate.datetime, 'text') }}</i>

View File

@@ -21,7 +21,9 @@ const appMessages = {
step: {
draft: "Brouillon",
active: "En file active",
closed: "Cloturé"
closed: "Cloturé",
inactive_short: "Hors file active",
inactive_long: "Pré-archivé",
},
open_at: "ouvert le ",
by: "par ",

View File

@@ -52,11 +52,13 @@
{% endif %}
{% if acp.step == 'DRAFT' %}
<span class="badge bg-secondary" style="font-size: 85%;" title="{{ 'course.draft'|trans }}">{{ 'course.draft'|trans }}</span>
{% endif %}
{% if acp.step == 'CLOSED' %}
<span class="badge bg-danger" style="font-size: 85%;" title="{{ 'course.closed'|trans }}">{{ 'course.closed'|trans }}</span>
<span class="badge bg-secondary" style="font-size: 85%;" title="{{ 'course.draft'|trans|e('html_attr') }}">{{ 'course.draft'|trans }}</span>
{% elseif acp.step == 'CLOSED' %}
<span class="badge bg-danger" style="font-size: 85%;" title="{{ 'course.closed'|trans|e('html_attr') }}">{{ 'course.closed'|trans }}</span>
{% elseif acp.step == 'CONFIRMED_INACTIVE_SHORT' %}
<span class="badge bg-chill-yellow text-primary" style="font-size: 85%;" title="{{ 'course.inactive_short'|trans|e('html_attr') }}">{{ 'course.inactive_short'|trans }}</span>
{% elseif acp.step == 'CONFIRMED_INACTIVE_LONG' %}
<span class="badge bg-danger" style="font-size: 85%;" title="{{ 'course.inactive_long'|trans|e('html_attr') }}">{{ 'course.inactive_long'|trans }}</span>
{% endif %}
</div>
</div>

View File

@@ -3,6 +3,14 @@
{{ 'workflow.SocialAction deleted'|trans }}
</div>
{% else %}
<div class="flex-table accompanying-course-work">
{% include '@ChillPerson/AccompanyingCourseWork/_item.html.twig' with {
'w': work,
'displayAction': false,
'displayContent': 'short',
'itemBlocClass': 'bg-chill-light-gray'
} %}
</div>
{% if display_action is defined and display_action == true %}
<ul class="record_actions">
<li>

View File

@@ -173,7 +173,7 @@ class PersonSearch extends AbstractSearch implements HasAdvancedSearchFormInterf
return $data;
}
public function getAdvancedSearchTitle()
public function getAdvancedSearchTitle(): string
{
return 'Search within persons';
}

View File

@@ -97,9 +97,7 @@ class HouseholdVoter extends Voter implements ProvideRoleHierarchyInterface, Chi
private function checkAssociatedMembersRole(Household $household, string $attribute): bool
{
foreach ($household->getCurrentMembers()->map(static function (HouseholdMember $member) {
return $member->getPerson();
}) as $person) {
foreach ($household->getCurrentMembers()->map(static fn (HouseholdMember $member) => $member->getPerson()) as $person) {
if ($this->security->isGranted($attribute, $person)) {
return true;
}

View File

@@ -150,12 +150,8 @@ class AccompanyingPeriodDocGenNormalizer implements ContextAwareNormalizerInterf
$this->closingMotiveRender->renderString($period->getClosingMotive(), []) : '',
'ref' => $this->normalizer->normalize($period->getUser(), $format, $userContext),
'hasRef' => $period->getUser() !== null,
'socialIssuesText' => implode(', ', array_map(function (SocialIssue $s) {
return $this->socialIssueRender->renderString($s, []);
}, $period->getSocialIssues()->toArray())),
'scopesText' => implode(', ', array_map(function (Scope $s) {
return $this->translatableStringHelper->localize($s->getName());
}, $scopes)),
'socialIssuesText' => implode(', ', array_map(fn (SocialIssue $s) => $this->socialIssueRender->renderString($s, []), $period->getSocialIssues()->toArray())),
'scopesText' => implode(', ', array_map(fn (Scope $s) => $this->translatableStringHelper->localize($s->getName()), $scopes)),
'hasRequestor' => $period->getRequestor() !== null,
'requestorKind' => $period->getRequestorKind(),
'hasLocation' => $period->getLocation() !== null,

View File

@@ -48,9 +48,7 @@ class AccompanyingPeriodWorkEvaluationNormalizer implements ContextAwareNormaliz
$initial = $this->normalizer->normalize($object, $format, array_merge(
$context,
[self::IGNORE_EVALUATION => spl_object_hash($object)],
[AbstractNormalizer::CIRCULAR_REFERENCE_HANDLER => static function ($object, $format, $context) {
return $object->getId();
}]
[AbstractNormalizer::CIRCULAR_REFERENCE_HANDLER => static fn ($object, $format, $context) => $object->getId()]
));
// due to bug: https://api-platform.com/docs/core/serialization/#collection-relation

View File

@@ -94,9 +94,7 @@ class PersonDocGenNormalizer implements
// we simplify the list of attributes for the embedded persons
AbstractNormalizer::GROUPS => ['docgen:read'],
// when a person reference the same person... take care of circular references
AbstractNormalizer::CIRCULAR_REFERENCE_HANDLER => function ($object, $format, $context) {
return $this->normalizer->normalize(null, $format, $context);
},
AbstractNormalizer::CIRCULAR_REFERENCE_HANDLER => fn ($object, $format, $context) => $this->normalizer->normalize(null, $format, $context),
]);
if (null === $person) {
@@ -117,9 +115,7 @@ class PersonDocGenNormalizer implements
'altNames' => implode(
', ',
array_map(
static function (PersonAltName $altName) {
return $altName->getLabel();
},
static fn (PersonAltName $altName) => $altName->getLabel(),
$person->getAltNames()->toArray()
)
),

View File

@@ -257,12 +257,10 @@ class PersonJsonNormalizer implements DenormalizerAwareInterface, NormalizerAwar
{
return $altNames
->map(
static function (PersonAltName $personAltName): array {
return [
'key' => $personAltName->getKey(),
'label' => $personAltName->getLabel(),
];
}
static fn (PersonAltName $personAltName): array => [
'key' => $personAltName->getKey(),
'label' => $personAltName->getLabel(),
]
)
->toArray();
}

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