Merge remote-tracking branch 'origin/master' into rector/rules-symfony

This commit is contained in:
2023-09-27 15:25:29 +02:00
106 changed files with 1259 additions and 506 deletions

View File

@@ -9,7 +9,7 @@ declare(strict_types=1);
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\ActivityBundle\Export\Aggregator\ACPAggregators;
namespace Chill\ActivityBundle\Export\Aggregator;
use Chill\ActivityBundle\Export\Declarations;
use Chill\MainBundle\Export\AggregatorInterface;
@@ -35,7 +35,7 @@ class ByCreatorAggregator implements AggregatorInterface
public function applyOn(): string
{
return Declarations::ACTIVITY_ACP;
return Declarations::ACTIVITY;
}
public function buildForm(FormBuilderInterface $builder)

View File

@@ -9,7 +9,7 @@ declare(strict_types=1);
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\ActivityBundle\Export\Aggregator\ACPAggregators;
namespace Chill\ActivityBundle\Export\Aggregator;
use Chill\ActivityBundle\Export\Declarations;
use Chill\MainBundle\Export\AggregatorInterface;
@@ -40,7 +40,7 @@ class ByThirdpartyAggregator implements AggregatorInterface
public function applyOn(): string
{
return Declarations::ACTIVITY_ACP;
return Declarations::ACTIVITY;
}
public function buildForm(FormBuilderInterface $builder)

View File

@@ -9,7 +9,7 @@ declare(strict_types=1);
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\ActivityBundle\Export\Aggregator\ACPAggregators;
namespace Chill\ActivityBundle\Export\Aggregator;
use Chill\ActivityBundle\Export\Declarations;
use Chill\MainBundle\Export\AggregatorInterface;
@@ -40,7 +40,7 @@ class CreatorScopeAggregator implements AggregatorInterface
public function applyOn(): string
{
return Declarations::ACTIVITY_ACP;
return Declarations::ACTIVITY;
}
public function buildForm(FormBuilderInterface $builder)

View File

@@ -9,7 +9,7 @@ declare(strict_types=1);
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\ActivityBundle\Export\Aggregator\ACPAggregators;
namespace Chill\ActivityBundle\Export\Aggregator;
use Chill\ActivityBundle\Export\Declarations;
use Chill\MainBundle\Export\AggregatorInterface;
@@ -66,7 +66,7 @@ class DateAggregator implements AggregatorInterface
public function applyOn(): string
{
return Declarations::ACTIVITY_ACP;
return Declarations::ACTIVITY;
}
public function buildForm(FormBuilderInterface $builder)

View File

@@ -9,7 +9,7 @@ declare(strict_types=1);
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\ActivityBundle\Export\Aggregator\ACPAggregators;
namespace Chill\ActivityBundle\Export\Aggregator;
use Chill\ActivityBundle\Export\Declarations;
use Chill\MainBundle\Export\AggregatorInterface;
@@ -40,7 +40,7 @@ class LocationTypeAggregator implements AggregatorInterface
public function applyOn(): string
{
return Declarations::ACTIVITY_ACP;
return Declarations::ACTIVITY;
}
public function buildForm(FormBuilderInterface $builder)

View File

@@ -12,6 +12,7 @@ declare(strict_types=1);
namespace Chill\ActivityBundle\Export\Export\LinkedToACP;
use Chill\ActivityBundle\Entity\Activity;
use Chill\ActivityBundle\Export\Declarations;
use Chill\ActivityBundle\Export\Export\ListActivityHelper;
use Chill\ActivityBundle\Security\Authorization\ActivityStatsVoter;
use Chill\MainBundle\Entity\Scope;
@@ -141,6 +142,7 @@ class ListActivity implements ListInterface, GroupedExportInterface
return array_merge(
$this->helper->supportsModifiers(),
[
Declarations::ACTIVITY,
\Chill\PersonBundle\Export\Declarations::ACP_TYPE,
]
);

View File

@@ -281,7 +281,7 @@ class ListActivity implements ListInterface, GroupedExportInterface
return [
Declarations::ACTIVITY,
Declarations::ACTIVITY_PERSON,
//PersonDeclarations::PERSON_TYPE,
PersonDeclarations::PERSON_TYPE,
];
}
}

View File

@@ -148,7 +148,7 @@ class StatActivityDuration implements ExportInterface, GroupedExportInterface
return [
Declarations::ACTIVITY,
Declarations::ACTIVITY_PERSON,
//PersonDeclarations::PERSON_TYPE,
PersonDeclarations::PERSON_TYPE,
];
}
}

View File

@@ -23,7 +23,10 @@ use Symfony\Component\Form\FormBuilderInterface;
class ActivityTypeFilter implements FilterInterface
{
public function __construct(private readonly ActivityTypeRepositoryInterface $activityTypeRepository, private readonly TranslatableStringHelperInterface $translatableStringHelper) {}
public function __construct(
private readonly ActivityTypeRepositoryInterface $activityTypeRepository,
private readonly TranslatableStringHelperInterface $translatableStringHelper
) {}
public function addRole(): ?string
{
@@ -71,7 +74,7 @@ class ActivityTypeFilter implements FilterInterface
$types[] = $this->translatableStringHelper->localize($aty->getName());
}
return ['Filtered by activity types: only %activitytypes%', [
return ['export.filter.activity.acp_by_activity_type.acp_containing_at_least_one_%activitytypes%', [
'%activitytypes%' => implode(', ', $types),
]];
}

View File

@@ -17,6 +17,9 @@ use Chill\PersonBundle\Export\Declarations;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface;
/**
* Filter accompanying periods to keep only the one without any activity
*/
class HasNoActivityFilter implements FilterInterface
{
public function addRole(): ?string

View File

@@ -9,7 +9,7 @@ declare(strict_types=1);
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\ActivityBundle\Export\Filter\ACPFilters;
namespace Chill\ActivityBundle\Export\Filter;
use Chill\ActivityBundle\Export\Declarations;
use Chill\MainBundle\Export\FilterInterface;
@@ -38,7 +38,7 @@ class ByCreatorFilter implements FilterInterface
public function applyOn(): string
{
return Declarations::ACTIVITY_ACP;
return Declarations::ACTIVITY;
}
public function buildForm(FormBuilderInterface $builder)

View File

@@ -9,7 +9,7 @@ declare(strict_types=1);
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\ActivityBundle\Export\Filter\ACPFilters;
namespace Chill\ActivityBundle\Export\Filter;
use Chill\ActivityBundle\Export\Declarations;
use Chill\MainBundle\Export\FilterInterface;
@@ -53,7 +53,7 @@ class EmergencyFilter implements FilterInterface
public function applyOn(): string
{
return Declarations::ACTIVITY_ACP;
return Declarations::ACTIVITY;
}
public function buildForm(FormBuilderInterface $builder)

View File

@@ -9,7 +9,7 @@ declare(strict_types=1);
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\ActivityBundle\Export\Filter\ACPFilters;
namespace Chill\ActivityBundle\Export\Filter;
use Chill\ActivityBundle\Export\Declarations;
use Chill\MainBundle\Export\FilterInterface;
@@ -36,7 +36,7 @@ class LocationFilter implements FilterInterface
public function applyOn(): string
{
return Declarations::ACTIVITY_ACP;
return Declarations::ACTIVITY;
}
public function buildForm(FormBuilderInterface $builder)

View File

@@ -9,7 +9,7 @@ declare(strict_types=1);
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\ActivityBundle\Export\Filter\ACPFilters;
namespace Chill\ActivityBundle\Export\Filter;
use Chill\ActivityBundle\Export\Declarations;
use Chill\MainBundle\Export\FilterInterface;
@@ -50,7 +50,7 @@ class LocationTypeFilter implements FilterInterface
public function applyOn(): string
{
return Declarations::ACTIVITY_ACP;
return Declarations::ACTIVITY;
}
public function buildForm(FormBuilderInterface $builder)

View File

@@ -9,7 +9,7 @@ declare(strict_types=1);
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\ActivityBundle\Export\Filter\ACPFilters;
namespace Chill\ActivityBundle\Export\Filter;
use Chill\ActivityBundle\Entity\Activity;
use Chill\ActivityBundle\Export\Declarations;
@@ -54,7 +54,7 @@ class SentReceivedFilter implements FilterInterface
public function applyOn(): string
{
return Declarations::ACTIVITY_ACP;
return Declarations::ACTIVITY;
}
public function buildForm(FormBuilderInterface $builder)

View File

@@ -9,7 +9,7 @@ declare(strict_types=1);
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\ActivityBundle\Export\Filter\ACPFilters;
namespace Chill\ActivityBundle\Export\Filter;
use Chill\ActivityBundle\Export\Declarations;
use Chill\MainBundle\Export\FilterInterface;
@@ -46,7 +46,7 @@ class UserFilter implements FilterInterface
public function applyOn(): string
{
return Declarations::ACTIVITY_ACP;
return Declarations::ACTIVITY;
}
public function buildForm(FormBuilderInterface $builder)

View File

@@ -9,7 +9,7 @@ declare(strict_types=1);
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\ActivityBundle\Export\Filter\ACPFilters;
namespace Chill\ActivityBundle\Export\Filter;
use Chill\ActivityBundle\Export\Declarations;
use Chill\MainBundle\Entity\Scope;
@@ -52,7 +52,7 @@ class UserScopeFilter implements FilterInterface
public function applyOn(): string
{
return Declarations::ACTIVITY_ACP;
return Declarations::ACTIVITY;
}
public function buildForm(FormBuilderInterface $builder)

View File

@@ -12,7 +12,7 @@ declare(strict_types=1);
namespace Chill\ActivityBundle\Tests\Export\Aggregator\ACPAggregators;
use Chill\ActivityBundle\Entity\Activity;
use Chill\ActivityBundle\Export\Aggregator\ACPAggregators\BySocialActionAggregator;
use Chill\ActivityBundle\Export\Aggregator\BySocialActionAggregator;
use Chill\MainBundle\Test\Export\AbstractAggregatorTest;
use Doctrine\ORM\EntityManagerInterface;

View File

@@ -12,7 +12,7 @@ declare(strict_types=1);
namespace Chill\ActivityBundle\Tests\Export\Aggregator\ACPAggregators;
use Chill\ActivityBundle\Entity\Activity;
use Chill\ActivityBundle\Export\Aggregator\ACPAggregators\BySocialIssueAggregator;
use Chill\ActivityBundle\Export\Aggregator\BySocialIssueAggregator;
use Chill\MainBundle\Test\Export\AbstractAggregatorTest;
use Doctrine\ORM\EntityManagerInterface;

View File

@@ -9,10 +9,10 @@ declare(strict_types=1);
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\ActivityBundle\Tests\Export\Aggregator\ACPAggregators;
namespace Chill\ActivityBundle\Tests\Export\Aggregator;
use Chill\ActivityBundle\Entity\Activity;
use Chill\ActivityBundle\Export\Aggregator\ACPAggregators\ByThirdpartyAggregator;
use Chill\ActivityBundle\Export\Aggregator\ByThirdpartyAggregator;
use Chill\MainBundle\Test\Export\AbstractAggregatorTest;
use Doctrine\ORM\EntityManagerInterface;
@@ -28,7 +28,7 @@ final class ByThirdpartyAggregatorTest extends AbstractAggregatorTest
{
self::bootKernel();
$this->aggregator = self::$container->get('chill.activity.export.bythirdparty_aggregator');
$this->aggregator = self::$container->get(ByThirdpartyAggregator::class);
}
public function getAggregator()

View File

@@ -9,10 +9,10 @@ declare(strict_types=1);
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\ActivityBundle\Tests\Export\Aggregator\ACPAggregators;
namespace Chill\ActivityBundle\Tests\Export\Aggregator;
use Chill\ActivityBundle\Entity\Activity;
use Chill\ActivityBundle\Export\Aggregator\ACPAggregators\ByCreatorAggregator;
use Chill\ActivityBundle\Export\Aggregator\ByCreatorAggregator;
use Chill\MainBundle\Test\Export\AbstractAggregatorTest;
use Doctrine\ORM\EntityManagerInterface;

View File

@@ -9,10 +9,10 @@ declare(strict_types=1);
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\ActivityBundle\Tests\Export\Aggregator\ACPAggregators;
namespace Chill\ActivityBundle\Tests\Export\Aggregator;
use Chill\ActivityBundle\Entity\Activity;
use Chill\ActivityBundle\Export\Aggregator\ACPAggregators\CreatorScopeAggregator;
use Chill\ActivityBundle\Export\Aggregator\CreatorScopeAggregator;
use Chill\MainBundle\Test\Export\AbstractAggregatorTest;
use Doctrine\ORM\EntityManagerInterface;
@@ -20,7 +20,7 @@ use Doctrine\ORM\EntityManagerInterface;
* @internal
* @coversNothing
*/
final class UserScopeAggregatorTest extends AbstractAggregatorTest
final class CreatorScopeAggregatorTest extends AbstractAggregatorTest
{
private CreatorScopeAggregator $aggregator;

View File

@@ -9,10 +9,10 @@ declare(strict_types=1);
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\ActivityBundle\Tests\Export\Aggregator\ACPAggregators;
namespace Chill\ActivityBundle\Tests\Export\Aggregator;
use Chill\ActivityBundle\Entity\Activity;
use Chill\ActivityBundle\Export\Aggregator\ACPAggregators\DateAggregator;
use Chill\ActivityBundle\Export\Aggregator\DateAggregator;
use Chill\MainBundle\Test\Export\AbstractAggregatorTest;
use Doctrine\ORM\EntityManagerInterface;
@@ -28,7 +28,7 @@ final class DateAggregatorTest extends AbstractAggregatorTest
{
self::bootKernel();
$this->aggregator = self::$container->get('chill.activity.export.date_aggregator');
$this->aggregator = self::$container->get(DateAggregator::class);
}
public function getAggregator()

View File

@@ -9,10 +9,10 @@ declare(strict_types=1);
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\ActivityBundle\Tests\Export\Aggregator\ACPAggregators;
namespace Chill\ActivityBundle\Tests\Export\Aggregator;
use Chill\ActivityBundle\Entity\Activity;
use Chill\ActivityBundle\Export\Aggregator\ACPAggregators\LocationTypeAggregator;
use Chill\ActivityBundle\Export\Aggregator\LocationTypeAggregator;
use Chill\MainBundle\Test\Export\AbstractAggregatorTest;
use Doctrine\ORM\EntityManagerInterface;
@@ -28,7 +28,7 @@ final class LocationTypeAggregatorTest extends AbstractAggregatorTest
{
self::bootKernel();
$this->aggregator = self::$container->get('chill.activity.export.locationtype_aggregator');
$this->aggregator = self::$container->get(LocationTypeAggregator::class);
}
public function getAggregator()

View File

@@ -13,9 +13,10 @@ namespace Chill\ActivityBundle\Tests\Export\Filter\ACPFilters;
use Chill\ActivityBundle\Entity\Activity;
use Chill\ActivityBundle\Entity\ActivityType;
use Chill\ActivityBundle\Export\Filter\ACPFilters\ActivityTypeFilter;
use Chill\ActivityBundle\Export\Filter\ActivityTypeFilter;
use Chill\MainBundle\Test\Export\AbstractFilterTest;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Query\Expr;
@@ -25,13 +26,13 @@ use Doctrine\ORM\Query\Expr;
*/
final class ActivityTypeFilterTest extends AbstractFilterTest
{
private ActivityTypeFilter $filter;
private \Chill\ActivityBundle\Export\Filter\ACPFilters\ActivityTypeFilter $filter;
protected function setUp(): void
{
self::bootKernel();
$this->filter = self::$container->get('chill.activity.export.filter_activitytype');
$this->filter = self::$container->get(\Chill\ActivityBundle\Export\Filter\ACPFilters\ActivityTypeFilter::class);
}
public function getFilter()
@@ -48,13 +49,14 @@ final class ActivityTypeFilterTest extends AbstractFilterTest
->from(ActivityType::class, 'at')
->select('at')
->getQuery()
->setMaxResults(1)
->getResult();
$data = [];
foreach ($array as $a) {
$data[] = [
'accepted_activitytypes' => $a,
'accepted_activitytypes' => new ArrayCollection([$a]),
];
}

View File

@@ -12,8 +12,7 @@ declare(strict_types=1);
namespace Chill\ActivityBundle\Tests\Export\Filter\ACPFilters;
use Chill\ActivityBundle\Entity\Activity;
use Chill\ActivityBundle\Export\Filter\ACPFilters\ByCreatorFilter;
use Chill\ActivityBundle\Export\Filter\ACPFilters\UserFilter;
use Chill\ActivityBundle\Export\Filter\ByCreatorFilter;
use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Test\Export\AbstractFilterTest;
use Doctrine\ORM\EntityManagerInterface;

View File

@@ -12,7 +12,6 @@ declare(strict_types=1);
namespace Chill\ActivityBundle\Tests\Export\Filter\ACPFilters;
use Chill\ActivityBundle\Entity\Activity;
use Chill\ActivityBundle\Export\Filter\ACPFilters\BySocialActionFilter;
use Chill\MainBundle\Test\Export\AbstractFilterTest;
use Chill\PersonBundle\Entity\SocialWork\SocialAction;
use Doctrine\Common\Collections\ArrayCollection;
@@ -24,13 +23,13 @@ use Doctrine\ORM\EntityManagerInterface;
*/
final class BySocialActionFilterTest extends AbstractFilterTest
{
private BySocialActionFilter $filter;
private \Chill\ActivityBundle\Export\Filter\ACPFilters\BySocialActionFilter $filter;
protected function setUp(): void
{
self::bootKernel();
$this->filter = self::$container->get('chill.activity.export.bysocialaction_filter');
$this->filter = self::$container->get(\Chill\ActivityBundle\Export\Filter\ACPFilters\BySocialActionFilter::class);
}
public function getFilter()
@@ -48,7 +47,6 @@ final class BySocialActionFilterTest extends AbstractFilterTest
->from(SocialAction::class, 'sa')
->select('sa')
->getQuery()
->setMaxResults(4)
->getResult();
$data = [];

View File

@@ -15,6 +15,7 @@ use Chill\ActivityBundle\Entity\Activity;
use Chill\ActivityBundle\Export\Filter\ACPFilters\BySocialIssueFilter;
use Chill\MainBundle\Test\Export\AbstractFilterTest;
use Chill\PersonBundle\Entity\SocialWork\SocialIssue;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\EntityManagerInterface;
/**
@@ -29,7 +30,7 @@ final class BySocialIssueFilterTest extends AbstractFilterTest
{
self::bootKernel();
$this->filter = self::$container->get('chill.activity.export.bysocialissue_filter');
$this->filter = self::$container->get(BySocialIssueFilter::class);
}
public function getFilter()
@@ -39,19 +40,21 @@ final class BySocialIssueFilterTest extends AbstractFilterTest
public function getFormData(): array
{
self::bootKernel();
$em = self::$container->get(EntityManagerInterface::class);
$array = $em->createQueryBuilder()
->from(SocialIssue::class, 'si')
->select('si')
->getQuery()
->setMaxResults(2)
->getResult();
$data = [];
foreach ($array as $a) {
$data[] = [
'accepted_socialissues' => $a,
'accepted_socialissues' => new ArrayCollection([$a]),
];
}

View File

@@ -30,7 +30,7 @@ final class ActivityDateFilterTest extends AbstractFilterTest
{
self::bootKernel();
$this->filter = self::$container->get('chill.activity.export.date_filter');
$this->filter = self::$container->get(ActivityDateFilter::class);
}
public function getFilter()

View File

@@ -0,0 +1,76 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\ActivityBundle\Tests\Export\Filter;
use Chill\ActivityBundle\Entity\Activity;
use Chill\ActivityBundle\Export\Filter\ByCreatorFilter;
use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Test\Export\AbstractFilterTest;
use Doctrine\ORM\EntityManagerInterface;
/**
* @internal
* @coversNothing
*/
final class ByCreatorFilterTest extends AbstractFilterTest
{
private ByCreatorFilter $filter;
protected function setUp(): void
{
self::bootKernel();
$this->filter = self::$container->get(ByCreatorFilter::class);
}
public function getFilter()
{
return $this->filter;
}
public function getFormData(): array
{
$em = self::$container->get(EntityManagerInterface::class);
$array = $em->createQueryBuilder()
->from(User::class, 'u')
->select('u')
->getQuery()
->getResult();
$data = [];
foreach ($array as $a) {
$data[] = [
'accepted_users' => $a,
];
}
return $data;
}
public function getQueryBuilders(): array
{
if (null === self::$kernel) {
self::bootKernel();
}
$em = self::$container->get(EntityManagerInterface::class);
return [
$em->createQueryBuilder()
->select('count(activity.id)')
->from(Activity::class, 'activity')
->join('activity.users', 'actusers'),
];
}
}

View File

@@ -9,10 +9,10 @@ declare(strict_types=1);
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\ActivityBundle\Tests\Export\Filter\ACPFilters;
namespace Chill\ActivityBundle\Tests\Export\Filter;
use Chill\ActivityBundle\Entity\Activity;
use Chill\ActivityBundle\Export\Filter\ACPFilters\EmergencyFilter;
use Chill\ActivityBundle\Export\Filter\EmergencyFilter;
use Chill\MainBundle\Test\Export\AbstractFilterTest;
use Doctrine\ORM\EntityManagerInterface;
@@ -28,7 +28,7 @@ final class EmergencyFilterTest extends AbstractFilterTest
{
self::bootKernel();
$this->filter = self::$container->get('chill.activity.export.emergency_filter');
$this->filter = self::$container->get(EmergencyFilter::class);
}
public function getFilter()

View File

@@ -9,10 +9,10 @@ declare(strict_types=1);
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\ActivityBundle\Tests\Export\Filter\ACPFilters;
namespace Chill\ActivityBundle\Tests\Export\Filter;
use Chill\ActivityBundle\Entity\Activity;
use Chill\ActivityBundle\Export\Filter\ACPFilters\LocationTypeFilter;
use Chill\ActivityBundle\Export\Filter\LocationTypeFilter;
use Chill\MainBundle\Entity\LocationType;
use Chill\MainBundle\Test\Export\AbstractFilterTest;
use Doctrine\ORM\EntityManagerInterface;
@@ -29,7 +29,7 @@ final class LocationTypeFilterTest extends AbstractFilterTest
{
self::bootKernel();
$this->filter = self::$container->get('chill.activity.export.locationtype_filter');
$this->filter = self::$container->get(LocationTypeFilter::class);
}
public function getFilter()

View File

@@ -56,7 +56,7 @@ final class PersonHavingActivityBetweenDateFilterTest extends AbstractFilterTest
$data[] = [
'date_from' => DateTime::createFromFormat('Y-m-d', '2021-07-01'),
'date_to' => DateTime::createFromFormat('Y-m-d', '2022-07-01'),
'reasons' => $a,
'reasons' => [$a],
];
}
@@ -74,7 +74,9 @@ final class PersonHavingActivityBetweenDateFilterTest extends AbstractFilterTest
return [
$em->createQueryBuilder()
->select('count(activity.id)')
->from(Activity::class, 'activity'),
->from(Activity::class, 'activity')
->join('activity.person', 'person')
,
];
}
}

View File

@@ -9,10 +9,10 @@ declare(strict_types=1);
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\ActivityBundle\Tests\Export\Filter\ACPFilters;
namespace Chill\ActivityBundle\Tests\Export\Filter;
use Chill\ActivityBundle\Entity\Activity;
use Chill\ActivityBundle\Export\Filter\ACPFilters\SentReceivedFilter;
use Chill\ActivityBundle\Export\Filter\SentReceivedFilter;
use Chill\MainBundle\Test\Export\AbstractFilterTest;
use Doctrine\ORM\EntityManagerInterface;
@@ -28,7 +28,7 @@ final class SentReceivedFilterTest extends AbstractFilterTest
{
self::bootKernel();
$this->filter = self::$container->get('chill.activity.export.sentreceived_filter');
$this->filter = self::$container->get(SentReceivedFilter::class);
}
public function getFilter()

View File

@@ -9,10 +9,10 @@ declare(strict_types=1);
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\ActivityBundle\Tests\Export\Filter\ACPFilters;
namespace Chill\ActivityBundle\Tests\Export\Filter;
use Chill\ActivityBundle\Entity\Activity;
use Chill\ActivityBundle\Export\Filter\ACPFilters\UserFilter;
use Chill\ActivityBundle\Export\Filter\UserFilter;
use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Test\Export\AbstractFilterTest;
use Doctrine\ORM\EntityManagerInterface;
@@ -29,7 +29,7 @@ final class UserFilterTest extends AbstractFilterTest
{
self::bootKernel();
$this->filter = self::$container->get('chill.activity.export.user_filter');
$this->filter = self::$container->get(UserFilter::class);
}
public function getFilter()

View File

@@ -9,10 +9,10 @@ declare(strict_types=1);
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\ActivityBundle\Tests\Export\Filter\ACPFilters;
namespace Chill\ActivityBundle\Tests\Export\Filter;
use Chill\ActivityBundle\Entity\Activity;
use Chill\ActivityBundle\Export\Filter\ACPFilters\UserScopeFilter;
use Chill\ActivityBundle\Export\Filter\UserScopeFilter;
use Chill\MainBundle\Entity\Scope;
use Chill\MainBundle\Test\Export\AbstractFilterTest;
use Doctrine\ORM\EntityManagerInterface;
@@ -29,7 +29,7 @@ final class UserScopeFilterTest extends AbstractFilterTest
{
self::bootKernel();
$this->filter = self::$container->get('chill.activity.export.userscope_filter');
$this->filter = self::$container->get(UserScopeFilter::class);
}
public function getFilter()

View File

@@ -53,8 +53,7 @@ services:
tags:
- { name: chill.export_filter, alias: 'activity_type_filter' }
chill.activity.export.date_filter:
class: Chill\ActivityBundle\Export\Filter\ActivityDateFilter
Chill\ActivityBundle\Export\Filter\ActivityDateFilter:
tags:
- { name: chill.export_filter, alias: 'activity_date_filter' }
@@ -74,52 +73,43 @@ services:
name: chill.export_filter
alias: 'activity_person_having_ac_bw_date_filter'
chill.activity.export.filter_activitytype:
class: Chill\ActivityBundle\Export\Filter\ACPFilters\ActivityTypeFilter
Chill\ActivityBundle\Export\Filter\ACPFilters\ActivityTypeFilter:
tags:
- { name: chill.export_filter, alias: 'accompanyingcourse_activitytype_filter' }
chill.activity.export.location_filter:
class: Chill\ActivityBundle\Export\Filter\ACPFilters\LocationFilter
Chill\ActivityBundle\Export\Filter\LocationFilter:
tags:
- { name: chill.export_filter, alias: 'activity_location_filter' }
chill.activity.export.locationtype_filter:
class: Chill\ActivityBundle\Export\Filter\ACPFilters\LocationTypeFilter
Chill\ActivityBundle\Export\Filter\LocationTypeFilter:
tags:
- { name: chill.export_filter, alias: 'activity_locationtype_filter' }
Chill\ActivityBundle\Export\Filter\ACPFilters\ByCreatorFilter:
Chill\ActivityBundle\Export\Filter\ByCreatorFilter:
tags:
- { name: chill.export_filter, alias: 'activity_bycreator_filter' }
chill.activity.export.emergency_filter:
class: Chill\ActivityBundle\Export\Filter\ACPFilters\EmergencyFilter
Chill\ActivityBundle\Export\Filter\EmergencyFilter:
tags:
- { name: chill.export_filter, alias: 'activity_emergency_filter' }
chill.activity.export.sentreceived_filter:
class: Chill\ActivityBundle\Export\Filter\ACPFilters\SentReceivedFilter
Chill\ActivityBundle\Export\Filter\SentReceivedFilter:
tags:
- { name: chill.export_filter, alias: 'activity_sentreceived_filter' }
chill.activity.export.bysocialaction_filter:
class: Chill\ActivityBundle\Export\Filter\ACPFilters\BySocialActionFilter
Chill\ActivityBundle\Export\Filter\ACPFilters\BySocialActionFilter:
tags:
- { name: chill.export_filter, alias: 'activity_bysocialaction_filter' }
chill.activity.export.bysocialissue_filter:
class: Chill\ActivityBundle\Export\Filter\ACPFilters\BySocialIssueFilter
Chill\ActivityBundle\Export\Filter\ACPFilters\BySocialIssueFilter:
tags:
- { name: chill.export_filter, alias: 'activity_bysocialissue_filter' }
chill.activity.export.user_filter: # Creator (M2O)
class: Chill\ActivityBundle\Export\Filter\ACPFilters\UserFilter
Chill\ActivityBundle\Export\Filter\UserFilter:
tags:
- { name: chill.export_filter, alias: 'activity_user_filter' }
chill.activity.export.userscope_filter:
class: Chill\ActivityBundle\Export\Filter\ACPFilters\UserScopeFilter
Chill\ActivityBundle\Export\Filter\UserScopeFilter:
tags:
- { name: chill.export_filter, alias: 'activity_userscope_filter' }
@@ -157,22 +147,19 @@ services:
tags:
- { name: chill.export_aggregator, alias: activity_user_aggregator }
chill.activity.export.locationtype_aggregator:
class: Chill\ActivityBundle\Export\Aggregator\ACPAggregators\LocationTypeAggregator
Chill\ActivityBundle\Export\Aggregator\LocationTypeAggregator:
tags:
- { name: chill.export_aggregator, alias: activity_locationtype_aggregator }
chill.activity.export.date_aggregator:
class: Chill\ActivityBundle\Export\Aggregator\ACPAggregators\DateAggregator
Chill\ActivityBundle\Export\Aggregator\DateAggregator:
tags:
- { name: chill.export_aggregator, alias: activity_date_aggregator }
Chill\ActivityBundle\Export\Aggregator\ACPAggregators\ByCreatorAggregator:
Chill\ActivityBundle\Export\Aggregator\ByCreatorAggregator:
tags:
- { name: chill.export_aggregator, alias: activity_by_creator_aggregator }
chill.activity.export.bythirdparty_aggregator:
class: Chill\ActivityBundle\Export\Aggregator\ACPAggregators\ByThirdpartyAggregator
Chill\ActivityBundle\Export\Aggregator\ByThirdpartyAggregator:
tags:
- { name: chill.export_aggregator, alias: activity_bythirdparty_aggregator }
@@ -186,7 +173,7 @@ services:
tags:
- { name: chill.export_aggregator, alias: activity_bysocialissue_aggregator }
Chill\ActivityBundle\Export\Aggregator\ACPAggregators\CreatorScopeAggregator:
Chill\ActivityBundle\Export\Aggregator\CreatorScopeAggregator:
tags:
- { name: chill.export_aggregator, alias: activity_creator_scope_aggregator }

View File

@@ -377,6 +377,8 @@ export:
Title: Filtre les parcours ayant reçu un échange entre deux dates
Receiving an activity after: Ayant reçu un échange après le
Receiving an activity before: Ayant reçu un échange avant le
acp_by_activity_type:
'acp_containing_at_least_one_%activitytypes%': 'Parcours filtrés: uniquement ceux qui contiennent au moins un échange d''un des types suivants: %activitytypes%'
aggregator:

View File

@@ -26,6 +26,7 @@ class AvgAsideActivityDuration implements ExportInterface, GroupedExportInterfac
public function __construct(private readonly AsideActivityRepository $repository) {}
public function buildForm(FormBuilderInterface $builder) {}
public function getFormDefaultData(): array
{
return [];

View File

@@ -28,6 +28,13 @@ class AccompanyingCourseDocument extends Document implements HasScopesInterface,
*/
private ?AccompanyingPeriod $course = null;
/**
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(type="integer")
*/
private ?int $id = null;
public function getCenters(): ?iterable
{
return $this->course->getCenters();
@@ -38,6 +45,11 @@ class AccompanyingCourseDocument extends Document implements HasScopesInterface,
return $this->course;
}
public function getId()
{
return $this->id;
}
public function getScopes(): iterable
{
if (null === $this->course) {
@@ -53,4 +65,5 @@ class AccompanyingCourseDocument extends Document implements HasScopesInterface,
return $this;
}
}

View File

@@ -49,13 +49,6 @@ class Document implements TrackCreationInterface, TrackUpdateInterface
*/
private string $description = '';
/**
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(type="integer")
*/
private ?int $id = null;
/**
* @ORM\ManyToOne(
* targetEntity="Chill\DocStoreBundle\Entity\StoredObject",
@@ -101,11 +94,6 @@ class Document implements TrackCreationInterface, TrackUpdateInterface
return $this->description;
}
public function getId()
{
return $this->id;
}
public function getObject(): ?StoredObject
{
return $this->object;

View File

@@ -23,6 +23,13 @@ use Doctrine\ORM\Mapping as ORM;
*/
class PersonDocument extends Document implements HasCenterInterface, HasScopeInterface
{
/**
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(type="integer")
*/
private ?int $id = null;
/**
* @ORM\ManyToOne(targetEntity="Chill\PersonBundle\Entity\Person")
*/
@@ -40,6 +47,11 @@ class PersonDocument extends Document implements HasCenterInterface, HasScopeInt
return $this->getPerson()->getCenter();
}
public function getId()
{
return $this->id;
}
public function getPerson(): Person
{
return $this->person;

View File

@@ -474,7 +474,7 @@ class CRUDController extends AbstractController
* 6. Launch rendering, the parameter is fetch using `getTemplateFor`
* The parameters may be personnalized using `generateTemplateParameter`.
*
* @param string $formClass
* @param class-string $formClass
* @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
*/
protected function formEditAction(string $action, Request $request, mixed $id, ?string $formClass = null, array $formOptions = []): Response

View File

@@ -11,177 +11,19 @@ declare(strict_types=1);
namespace Chill\MainBundle\Controller;
use Chill\MainBundle\Entity\Center;
use Chill\MainBundle\Form\CenterType;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Chill\MainBundle\CRUD\Controller\CRUDController;
use Chill\MainBundle\Pagination\PaginatorInterface;
use Symfony\Component\HttpFoundation\Request;
/**
* Class CenterController.
*/
class CenterController extends AbstractController
class CenterController extends CRUDController
{
/**
* Creates a new Center entity.
* @\Symfony\Component\Routing\Annotation\Route(path="/{_locale}/admin/center/create", name="admin_center_create", methods={"POST"})
*/
public function createAction(Request $request)
protected function orderQuery(string $action, $query, Request $request, PaginatorInterface $paginator)
{
$center = new Center();
$form = $this->createCreateForm($center);
$form->handleRequest($request);
$query->addOrderBy('e.name', 'ASC');
if ($form->isSubmitted() && $form->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($center);
$em->flush();
return $this->redirectToRoute('admin_center');
}
return $this->render('@ChillMain/Center/new.html.twig', [
'entity' => $center,
'form' => $form->createView(),
]);
}
/**
* Displays a form to edit an existing Center entity.
* @\Symfony\Component\Routing\Annotation\Route(path="/{_locale}/admin/center/{id}/edit", name="admin_center_edit")
*/
public function editAction(mixed $id)
{
$em = $this->getDoctrine()->getManager();
$center = $em->getRepository(\Chill\MainBundle\Entity\Center::class)->find($id);
if (!$center) {
throw $this->createNotFoundException('Unable to find Center entity.');
}
$editForm = $this->createEditForm($center);
return $this->render('@ChillMain/Center/edit.html.twig', [
'entity' => $center,
'edit_form' => $editForm->createView(),
]);
}
/**
* Lists all Center entities.
* @\Symfony\Component\Routing\Annotation\Route(path="/{_locale}/admin/center/", name="admin_center")
*/
public function indexAction()
{
$em = $this->getDoctrine()->getManager();
$entities = $em->getRepository(\Chill\MainBundle\Entity\Center::class)->findAll();
usort($entities, fn (Center $a, Center $b) => $a->getName() <=> $b->getName());
return $this->render('@ChillMain/Center/index.html.twig', [
'entities' => $entities,
]);
}
/**
* Displays a form to create a new Center entity.
* @\Symfony\Component\Routing\Annotation\Route(path="/{_locale}/admin/center/new", name="admin_center_new")
*/
public function newAction()
{
$center = new Center();
$form = $this->createCreateForm($center);
return $this->render('@ChillMain/Center/new.html.twig', [
'entity' => $center,
'form' => $form->createView(),
]);
}
/**
* Finds and displays a Center entity.
*/
public function showAction(mixed $id)
{
$em = $this->getDoctrine()->getManager();
$center = $em->getRepository(\Chill\MainBundle\Entity\Center::class)->find($id);
if (!$center) {
throw $this->createNotFoundException('Unable to find Center entity.');
}
return $this->render('@ChillMain/Center/show.html.twig', [
'entity' => $center,
]);
}
/**
* Edits an existing Center entity.
* @\Symfony\Component\Routing\Annotation\Route(path="/{_locale}/admin/center/{id}/update", name="admin_center_update", methods={"POST", "PUT"})
*/
public function updateAction(Request $request, mixed $id)
{
$em = $this->getDoctrine()->getManager();
$center = $em->getRepository(\Chill\MainBundle\Entity\Center::class)->find($id);
if (!$center) {
throw $this->createNotFoundException('Unable to find Center entity.');
}
$editForm = $this->createEditForm($center);
$editForm->handleRequest($request);
if ($editForm->isSubmitted() && $editForm->isValid()) {
$em->flush();
return $this->redirectToRoute('admin_center_edit', ['id' => $id]);
}
return $this->render('@ChillMain/Center/edit.html.twig', [
'entity' => $center,
'edit_form' => $editForm->createView(),
]);
}
/**
* Creates a form to create a Center entity.
*
* @param Center $center The entity
*
* @return \Symfony\Component\Form\Form The form
*/
private function createCreateForm(Center $center)
{
$form = $this->createForm(CenterType::class, $center, [
'action' => $this->generateUrl('admin_center_create'),
'method' => 'POST',
]);
$form->add('submit', SubmitType::class, ['label' => 'Create']);
return $form;
}
/**
* Creates a form to edit a Center entity.
*
* @param Center $center The entity
*
* @return \Symfony\Component\Form\Form The form
*/
private function createEditForm(Center $center)
{
$form = $this->createForm(CenterType::class, $center, [
'action' => $this->generateUrl('admin_center_update', ['id' => $center->getId()]),
'method' => 'PUT',
]);
$form->add('submit', SubmitType::class, ['label' => 'Update']);
return $form;
return parent::orderQuery($action, $query, $request, $paginator);
}
}

View File

@@ -12,6 +12,7 @@ declare(strict_types=1);
namespace Chill\MainBundle\DependencyInjection;
use Chill\MainBundle\Controller\AddressApiController;
use Chill\MainBundle\Controller\CenterController;
use Chill\MainBundle\Controller\CivilityApiController;
use Chill\MainBundle\Controller\CivilityController;
use Chill\MainBundle\Controller\CountryController;
@@ -44,6 +45,7 @@ use Chill\MainBundle\Doctrine\DQL\Unaccent;
use Chill\MainBundle\Doctrine\ORM\Hydration\FlatHierarchyEntityHydrator;
use Chill\MainBundle\Doctrine\Type\NativeDateIntervalType;
use Chill\MainBundle\Doctrine\Type\PointType;
use Chill\MainBundle\Entity\Center;
use Chill\MainBundle\Entity\Civility;
use Chill\MainBundle\Entity\Country;
use Chill\MainBundle\Entity\GeographicalUnitLayer;
@@ -53,6 +55,7 @@ use Chill\MainBundle\Entity\LocationType;
use Chill\MainBundle\Entity\Regroupment;
use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Entity\UserJob;
use Chill\MainBundle\Form\CenterType;
use Chill\MainBundle\Form\CivilityType;
use Chill\MainBundle\Form\CountryType;
use Chill\MainBundle\Form\LanguageType;
@@ -521,6 +524,27 @@ class ChillMainExtension extends Extension implements
],
],
],
[
'class' => Center::class,
'name' => 'center',
'base_path' => '/admin/center',
'form_class' => CenterType::class,
'controller' => CenterController::class,
'actions' => [
'index' => [
'role' => 'ROLE_ADMIN',
'template' => '@ChillMain/Admin/Center/index.html.twig',
],
'new' => [
'role' => 'ROLE_ADMIN',
'template' => '@ChillMain/Admin/Center/new.html.twig',
],
'edit' => [
'role' => 'ROLE_ADMIN',
'template' => '@ChillMain/Admin/Center/edit.html.twig',
],
],
],
],
'apis' => [
[

View File

@@ -48,6 +48,11 @@ class Center implements HasCenterInterface, \Stringable
*/
private string $name = '';
/**
* @ORM\Column(type="boolean")
*/
private bool $isActive = true;
/**
* @var Collection<Regroupment>
* @ORM\ManyToMany(targetEntity=Regroupment::class, mappedBy="centers")
@@ -121,6 +126,11 @@ class Center implements HasCenterInterface, \Stringable
return $this->regroupments;
}
public function getIsActive(): bool
{
return $this->isActive;
}
/**
* @param $name
*
@@ -132,4 +142,11 @@ class Center implements HasCenterInterface, \Stringable
return $this;
}
public function setIsActive(bool $isActive): self
{
$this->isActive = $isActive;
return $this;
}
}

View File

@@ -11,7 +11,10 @@ declare(strict_types=1);
namespace Chill\MainBundle\Form;
use Chill\MainBundle\Entity\Center;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
@@ -21,24 +24,18 @@ class CenterType extends AbstractType
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name', TextType::class);
->add('name', TextType::class, [
'label' => 'Nom',
])
->add('isActive', CheckboxType::class, [
'label' => 'Actif ?',
'required' => false,
]);
}
/**
* @param OptionsResolverInterface $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => \Chill\MainBundle\Entity\Center::class,
]);
}
/**
* @return string
*/
public function getBlockPrefix()
{
return 'chill_mainbundle_center';
$resolver
->setDefault('class', Center::class);
}
}

View File

@@ -47,12 +47,14 @@ final class PickCenterType extends AbstractType
$export->requiredRole()
);
$centersActive = array_filter($centers, fn (Center $c) => $c->getIsActive());
// order alphabetically
usort($centers, fn (Center $a, Center $b) => $a->getCenter() <=> $b->getName());
usort($centersActive, fn (Center $a, Center $b) => $a->getCenter() <=> $b->getName());
$builder->add('center', EntityType::class, [
'class' => Center::class,
'choices' => $centers,
'choices' => $centersActive,
'label' => 'center',
'multiple' => true,
'expanded' => true,

View File

@@ -47,6 +47,8 @@ class PickCenterType extends AbstractType
{
$centers = $this->getReachableCenters($options['role'], $options['scopes']);
$centersActive = array_filter($centers, fn (Center $c) => $c->getIsActive());
if (count($centers) <= 1) {
$multiple = $options['choice_options']['multiple'] ?? false;
$builder->add('center', HiddenType::class);
@@ -61,7 +63,7 @@ class PickCenterType extends AbstractType
$options['choice_options'],
[
'class' => Center::class,
'choices' => $centers,
'choices' => $centersActive,
]
)
);

View File

@@ -57,6 +57,7 @@ class UserType extends AbstractType
'class' => Center::class,
'query_builder' => static function (EntityRepository $er) {
$qb = $er->createQueryBuilder('c');
$qb->where($qb->expr()->eq('c.isActive', 'true'));
$qb->addOrderBy('c.name');
return $qb;

View File

@@ -29,9 +29,12 @@ final class CenterRepository implements CenterRepositoryInterface
return $this->repository->find($id, $lockMode, $lockVersion);
}
/**
* @return Center[]
*/
public function findActive(): array
{
return $this->findAll();
return $this->repository->findBy(['isActive' => true], ['name' => 'ASC']);
}
/**

View File

@@ -0,0 +1,11 @@
{% extends '@ChillMain/CRUD/Admin/index.html.twig' %}
{% block title %}
{% include('@ChillMain/CRUD/_edit_title.html.twig') %}
{% endblock %}
{% block admin_content %}
{% embed '@ChillMain/CRUD/_edit_content.html.twig' %}
{% block content_form_actions_save_and_show %}{% endblock %}
{% endembed %}
{% endblock admin_content %}

View File

@@ -0,0 +1,39 @@
{% extends '@ChillMain/CRUD/Admin/index.html.twig' %}
{% block admin_content %}
{% embed '@ChillMain/CRUD/_index.html.twig' %}
{% block table_entities_thead_tr %}
<th>{{ 'Label'|trans }}</th>
<th>{{ 'Active'|trans }}</th>
<th>&nbsp;</th>
{% endblock %}
{% block table_entities_tbody %}
{% for entity in entities %}
<tr>
<td>{{ entity.name }}</td>
<td style="text-align:center">
{% if entity.isActive %}
<i class="fa fa-check-square-o"></i>
{% else %}
<i class="fa fa-square-o"></i>
{% endif %}
</td>
<td>
<ul class="record_actions">
<li>
<a href="{{ chill_path_add_return_path('chill_crud_center_edit', { 'id': entity.id }) }}" class="btn btn-edit"></a>
</li>
</ul>
</td>
</tr>
{% endfor %}
{% endblock %}
{% block actions_before %}
<li class='cancel'>
<a href="{{ path('chill_main_admin_central') }}" class="btn btn-cancel">{{'Back to the admin'|trans}}</a>
</li>
{% endblock %}
{% endembed %}
{% endblock %}

View File

@@ -0,0 +1,11 @@
{% extends '@ChillMain/CRUD/Admin/index.html.twig' %}
{% block title %}
{% include('@ChillMain/CRUD/_new_title.html.twig') %}
{% endblock %}
{% block admin_content %}
{% embed '@ChillMain/CRUD/_new_content.html.twig' %}
{% block content_form_actions_save_and_show %}{% endblock %}
{% endembed %}
{% endblock admin_content %}

View File

@@ -1,23 +0,0 @@
{% extends '@ChillMain/Admin/layoutWithVerticalMenu.html.twig' %}
{% block title %}{{ 'Center edit'|trans }}{% endblock %}
{% block admin_content -%}
<h1>{{ 'Center edit'|trans }}</h1>
{{ form_start(edit_form) }}
{{ form_row(edit_form.name) }}
<ul class="record_actions sticky-form-buttons">
<li class='cancel'>
<a href="{{ path('admin_center') }}" class="btn btn-cancel">
{{ 'Back to the list'|trans }}
</a>
</li>
<li>
{{ form_widget(edit_form.submit, { 'attr' : { 'class' : 'btn btn-update' }}) }}
</li>
</ul>
{{ form_end(edit_form) }}
{% endblock %}

View File

@@ -1,50 +0,0 @@
{% extends '@ChillMain/CRUD/Admin/index.html.twig' %}
{% block title %}{{ 'Center list'|trans }}{% endblock %}
{% block admin_content -%}
{% embed '@ChillMain/CRUD/_index.html.twig' %}
{% block index_header %}
<h1>{{ 'Center list'|trans }}</h1>
{% endblock %}
{% block filter_order %}{% endblock %}
{% block table_entities_thead_tr %}
<th>id</th>
<th>{{ 'Name'|trans }}</th>
<th>{{ 'Actions'|trans }}</th>
{% endblock %}
{% block table_entities_tbody %}
{% for entity in entities %}
<tr>
<td>{{ entity.id }}</td>
<td>{{ entity.name }}</td>
<td>
<ul class="record_actions">
<li>
<a href="{{ path('admin_center_edit', { 'id': entity.id }) }}" class="btn btn-edit">{{ 'edit'|trans }}</a>
</li>
</ul>
</td>
</tr>
{% endfor %}
{% endblock %}
{% block pagination %}{% endblock %}
{% block list_actions %}
<ul class="record_actions sticky-form-buttons">
<li class='cancel'>
<a href="{{ path('chill_main_admin_central') }}" class="btn btn-cancel">{{'Back to the admin'|trans}}</a>
</li>
<li>
<a href="{{ path('admin_center_new') }}" class="btn btn-create">{{ 'Create a new center'|trans }}</a>
</li>
</ul>
{% endblock list_actions %}
{% endembed %}
{% endblock %}

View File

@@ -1,23 +0,0 @@
{% extends '@ChillMain/Admin/layoutWithVerticalMenu.html.twig' %}
{% block title %}{{ 'Center creation'|trans }}{% endblock %}
{% block admin_content -%}
<h1>{{ 'Center creation'|trans }}</h1>
{{ form_start(form) }}
{{ form_row(form.name) }}
<ul class="record_actions sticky-form-buttons">
<li class='cancel'>
<a href="{{ path('admin_center') }}" class="btn btn-cancel">
{{ 'Back to the list'|trans }}
</a>
</li>
<li>
{{ form_widget(form.submit, { 'attr' : { 'class' : 'btn btn-save' }}) }}
</li>
</ul>
{{ form_end(form) }}
{% endblock %}

View File

@@ -46,7 +46,7 @@ class AdminUserMenuBuilder implements LocalMenuBuilderInterface
]);
$menu->addChild('Center list', [
'route' => 'admin_center',
'route' => 'chill_crud_center_index',
])->setExtras(['order' => 1010]);
$menu->addChild('Regroupements des centres', [

View File

@@ -58,6 +58,7 @@ class CenterNormalizer implements DenormalizerInterface, NormalizerInterface
'id' => $center->getId(),
'type' => 'center',
'name' => $center->getName(),
'isActive' => $center->getIsActive()
];
}

View File

@@ -0,0 +1,36 @@
<?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\Migrations\Main;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Add property to center
*/
final class Version20230906134410 extends AbstractMigration
{
public function getDescription(): string
{
return 'Add the isActive property to the Center entity';
}
public function up(Schema $schema): void
{
$this->addSql('ALTER TABLE centers ADD isActive BOOLEAN DEFAULT true NOT NULL');
}
public function down(Schema $schema): void
{
$this->addSql('ALTER TABLE centers DROP isActive');
}
}

View File

@@ -425,6 +425,12 @@ crud:
add_new: Ajouter un regroupement
title_new: Nouveau regroupement
title_edit: Modifier un regroupement
center:
index:
title: Liste des centres
add_new: Ajouter un centre
title_new: Nouveau centre
title_edit: Modifier un centre
No entities: Aucun élément

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\Actions\Remove\Handler;
use Chill\PersonBundle\Actions\Remove\PersonMoveSqlHandlerInterface;
use Chill\PersonBundle\Entity\AccompanyingPeriodParticipation;
use Chill\PersonBundle\Entity\Person;
class PersonMoveAccompanyingPeriodParticipationHandler implements PersonMoveSqlHandlerInterface
{
public function supports(string $className, string $field): bool
{
return $className === AccompanyingPeriodParticipation::class;
}
public function getSqls(string $className, string $field, Person $from, Person $to): array
{
$insertSql = sprintf(<<<'SQL'
INSERT INTO chill_person_accompanying_period_participation (person_id, accompanyingperiod_id, id, startdate, enddate)
SELECT %d, accompanyingperiod_id, nextval('chill_person_accompanying_period_id_seq'), startdate, enddate
FROM chill_person_accompanying_period_participation cpapp
WHERE person_id = %d
AND NOT EXISTS (
SELECT 1 FROM chill_person_accompanying_period_participation cpapp2
WHERE
person_id = %d
AND (cpapp.startdate, COALESCE(cpapp.enddate, 'infinity'::date)) OVERLAPS (cpapp2.startdate, COALESCE(cpapp2.enddate, 'infinity'::date))
);
SQL, $to->getId(), $from->getId(), $to->getId());
$deleteSql = sprintf(<<<'SQL'
DELETE FROM chill_person_accompanying_period_participation WHERE person_id = %d;
SQL, $from->getId());
return [$insertSql, $deleteSql];
}
}

View File

@@ -0,0 +1,73 @@
<?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\Actions\Remove\Handler;
use Chill\PersonBundle\Actions\Remove\PersonMoveSqlHandlerInterface;
use Chill\PersonBundle\Entity\Person;
use Chill\PersonBundle\Repository\Person\PersonCenterHistoryRepository;
class PersonMoveCenterHistoryHandler implements PersonMoveSqlHandlerInterface
{
public function __construct(
private PersonCenterHistoryRepository $centerHistoryRepository,
) {}
public function supports(string $className, string $field): bool
{
return $className === Person\PersonCenterHistory::class;
}
public function getSqls(string $className, string $field, Person $from, Person $to): array
{
$sqlStatements = [];
$oldestDateA = null;
$oldestDateB = null;
$oldestCenterHistoryB = null;
$oldestCenterHistoryA = null;
$centerHistoriesA = $this->centerHistoryRepository->findBy(['person' => $from]);
foreach ($centerHistoriesA as $ch) {
if ($oldestDateA === null || ($ch->getStartDate() < $oldestDateA)) {
$oldestDateA = $ch->getStartDate();
$oldestCenterHistoryA = $ch;
}
}
$centerHistoriesB = $this->centerHistoryRepository->findBy(['person' => $to]);
foreach ($centerHistoriesB as $ch) {
if ($oldestDateB === null || ($ch->getStartDate() < $oldestDateB)) {
$oldestDateB = $ch->getStartDate();
$oldestCenterHistoryB = $ch;
}
}
$sqlDelete = sprintf(<<<'SQL'
DELETE FROM chill_person_person_center_history WHERE person_id = %d;
SQL, $from->getId());
$sqlStatements = [$sqlDelete];
if ((null !== $oldestDateA && null !== $oldestDateB) && $oldestDateA <= $oldestDateB) {
$sqlInsert = sprintf(<<<'SQL'
UPDATE chill_person_person_center_history SET startDate = '%s' WHERE id = %d;
SQL, $oldestDateA->format('Y-m-d'), $oldestCenterHistoryB->getId());
$sqlStatements = [$sqlInsert, $sqlDelete];
}
return $sqlStatements;
}
}

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\Actions\Remove\Handler;
use Chill\PersonBundle\Actions\Remove\PersonMoveSqlHandlerInterface;
use Chill\PersonBundle\Entity\Household\HouseholdMember;
use Chill\PersonBundle\Entity\Person;
class PersonMoveHouseholdHandler implements PersonMoveSqlHandlerInterface
{
public function supports(string $className, string $field): bool
{
return $className === HouseholdMember::class;
}
public function getSqls(string $className, string $field, Person $from, Person $to): array
{
$sqlInsert = sprintf(<<<'SQL'
INSERT INTO chill_person_household_members (id, person_id, household_id, startdate, enddate, comment, sharedhousehold, position_id, holder)
SELECT nextval('chill_person_household_members_id_seq'), %d, household_id, startdate, enddate, comment, sharedhousehold, position_id, holder
FROM chill_person_household_members cphm
WHERE person_id = %d
AND NOT EXISTS (
SELECT 1 FROM chill_person_household_members cphm_inner
WHERE
person_id = %d
AND daterange(cphm.startdate, cphm.enddate) && daterange(cphm_inner.startdate, cphm_inner.enddate)
);
SQL, $to->getId(), $from->getId(), $to->getId());
$deleteSql = sprintf(<<<'SQL'
DELETE FROM chill_person_household_members WHERE person_id = %d;
SQL, $from->getId());
return [$sqlInsert, $deleteSql];
}
}

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\Actions\Remove\Handler;
use Chill\PersonBundle\Actions\Remove\PersonMoveSqlHandlerInterface;
use Chill\PersonBundle\Entity\Person;
use Chill\PersonBundle\Entity\Relationships\Relationship;
class PersonMoveRelationHandler implements PersonMoveSqlHandlerInterface
{
public function supports(string $className, string $field): bool
{
return $className === Relationship::class;
}
public function getSqls(string $className, string $field, Person $from, Person $to): array
{
$insertSql = sprintf(<<<'SQL'
INSERT INTO chill_person_relationships (id, relation_id, reverse, fromperson_id, toperson_id)
SELECT nextval('chill_person_relationships_id_seq'), relation_id, reverse, fromperson_id, toperson_id
FROM chill_person_relationships cpr
WHERE fromperson_id = %d OR toperson_id = %d
AND NOT EXISTS (
SELECT 1 FROM chill_person_relationships cpr2
WHERE
cpr2.fromperson_id = %d AND cpr2.toperson_id = %d
OR cpr2.fromperson_id = %d AND cpr2.toperson_id = %d
);
SQL, $from->getId(), $from->getId(), $to->getId(), $from->getId(), $from->getId(), $to->getId());
$deleteSql = [
sprintf("DELETE FROM chill_person_relationships WHERE fromperson_id = %d", $from->getId()),
sprintf("DELETE FROM chill_person_relationships WHERE toperson_id = %d", $from->getId())
];
return [$insertSql, ...$deleteSql];
}
}

View File

@@ -35,23 +35,11 @@ use function in_array;
*/
class PersonMove
{
/**
* @var EntityManagerInterface
*/
protected $em;
/**
* @var EventDispatcherInterface
*/
protected $eventDispatcher;
public function __construct(
EntityManagerInterface $em,
EventDispatcherInterface $eventDispatcher
) {
$this->em = $em;
$this->eventDispatcher = $eventDispatcher;
}
private EntityManagerInterface $em,
private PersonMoveManager $personMoveManager,
private EventDispatcherInterface $eventDispatcher
) {}
/**
* Return the sql used to move or delete entities associated to a person to
@@ -88,9 +76,16 @@ class PersonMove
}
foreach ($metadata->getAssociationMappings() as $field => $mapping) {
if ($this->personMoveManager->hasHandler($metadata->getName(), $field)) {
$sqls = array_merge($sqls, $this->personMoveManager->getSqls($metadata->getName(), $field, $from, $to));
continue;
}
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);
@@ -100,26 +95,18 @@ class PersonMove
$sql,
['to' => $to->getId(), 'original_action' => 'move']
);
$this->eventDispatcher->dispatch($event, ActionEvent::DELETE);
$this->eventDispatcher->dispatch(ActionEvent::DELETE, $event);
$sqls = array_merge($sqls, $event->getPreSql(), [$event->getSqlStatement()], $event->getPostSql());
} else {
$sql = $this->createMoveSQL($metadata, $from, $to, $field);
$event = new ActionEvent(
$from->getId(),
$metadata->getName(),
$sql,
['to' => $to->getId(), 'original_action' => 'move']
);
$this->eventDispatcher->dispatch($event, ActionEvent::MOVE);
$sqls = array_merge($sqls, $this->createMoveSQLs($metadata, $from, $to, $field));
}
$sqls = array_merge($sqls, $event->getPreSql(), [$event->getSqlStatement()], $event->getPostSql());
}
}
}
$personMetadata = $this->em->getClassMetadata(Person::class);
$sqls[] = sprintf(
'DELETE FROM %s WHERE id = %d',
'DELETE FROM %s WHERE id = %d;',
$this->getTableName($personMetadata),
$from->getId()
);
@@ -133,18 +120,24 @@ class PersonMove
$conditions = [];
foreach ($mapping['joinColumns'] as $columns) {
$conditions[] = sprintf('%s = %d', $columns['name'], $from->getId());
if (array_key_exists('joinTable', $mapping)) {
foreach ($mapping['joinTable']['joinColumns'] as $columns) {
$conditions[] = sprintf('%s = %d', $columns['referencedColumnName'], $from->getId());
}
} elseif (array_key_exists('joinColumns', $mapping)) {
foreach ($mapping['joinColumns'] as $columns) {
$conditions[] = sprintf('%s = %d', $columns['name'], $from->getId());
}
}
return sprintf(
'DELETE FROM %s WHERE %s',
'DELETE FROM %s WHERE %s;',
$this->getTableName($metadata),
implode(' AND ', $conditions)
);
}
private function createMoveSQL(ClassMetadata $metadata, Person $from, Person $to, $field): string
private function createMoveSQLs($metadata, Person $from, Person $to, $field): array
{
$mapping = $metadata->getAssociationMapping($field);
@@ -154,9 +147,29 @@ class PersonMove
$tableName = '';
if (array_key_exists('joinTable', $mapping)) {
// there is a join_table: we have to find conflict
$tableName = (null !== ($mapping['joinTable']['schema'] ?? null) ? $mapping['joinTable']['schema'] . '.' : '')
. $mapping['joinTable']['name'];
$sqlInsert = sprintf(
"INSERT INTO %s (%s, %s) SELECT %d, %s FROM %s WHERE %s = %d ON CONFLICT DO NOTHING;",
$tableName,
$mapping['joinTable']['inverseJoinColumns'][0]['name'], // person_id
$mapping['joinTable']['joinColumns'][0]['name'], // something_else_id
$to->getId(),
$mapping['joinTable']['joinColumns'][0]['name'], // something_else_id
$tableName,
$mapping['joinTable']['inverseJoinColumns'][0]['name'], // person_id
$from->getId()
);
$deleteSql = sprintf(
"DELETE FROM %s WHERE %s = %d;",
$tableName,
$mapping['joinTable']['inverseJoinColumns'][0]['name'], // person_id
$from->getId()
);
foreach ($mapping['joinTable']['inverseJoinColumns'] as $columns) {
$sets[] = sprintf('%s = %d', $columns['name'], $to->getId());
}
@@ -164,24 +177,29 @@ class PersonMove
foreach ($mapping['joinTable']['inverseJoinColumns'] as $columns) {
$conditions[] = sprintf('%s = %d', $columns['name'], $from->getId());
}
} elseif (array_key_exists('joinColumns', $mapping)) {
return [
$sqlInsert, $deleteSql
];
}
if (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',
return [sprintf(
'UPDATE %s SET %s WHERE %s;',
$tableName,
implode(' ', $sets),
implode(' AND ', $conditions)
);
)];
}
/**
@@ -191,9 +209,6 @@ class PersonMove
private function getDeleteEntities(): array
{
return [
Person\PersonCenterHistory::class,
HouseholdMember::class,
AccompanyingPeriodParticipation::class,
AccompanyingPeriod\AccompanyingPeriodWork::class,
Relationship::class
];

View File

@@ -0,0 +1,55 @@
<?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\Actions\Remove;
use Chill\PersonBundle\Entity\Person;
class PersonMoveManager
{
public function __construct(
/**
* @var iterable<PersonMoveSqlHandlerInterface>
*/
private iterable $handlers,
) {}
/**
* @param class-string $className
* @param string $field
* @return bool
*/
public function hasHandler(string $className, string $field): bool
{
foreach ($this->handlers as $handler) {
if ($handler->supports($className, $field)) {
return true;
}
}
return false;
}
/**
* @param class-string $className
* @return array<string>
*/
public function getSqls(string $className, string $field, Person $from, Person $to): array
{
foreach ($this->handlers as $handler) {
if ($handler->supports($className, $field)) {
return $handler->getSqls($className, $field, $from, $to);
}
}
return [];
}
}

View File

@@ -0,0 +1,28 @@
<?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\Actions\Remove;
use Chill\PersonBundle\Entity\Person;
interface PersonMoveSqlHandlerInterface
{
/**
* @param class-string $className
*/
public function supports(string $className, string $field): bool;
/**
* @param class-string $className
* @return array<string>
*/
public function getSqls(string $className, string $field, Person $from, Person $to): array;
}

View File

@@ -11,6 +11,7 @@ declare(strict_types=1);
namespace Chill\PersonBundle;
use Chill\PersonBundle\Actions\Remove\PersonMoveSqlHandlerInterface;
use Chill\PersonBundle\DependencyInjection\CompilerPass\AccompanyingPeriodTimelineCompilerPass;
use Chill\PersonBundle\Service\EntityInfo\AccompanyingPeriodInfoUnionQueryPartInterface;
use Chill\PersonBundle\Widget\PersonListWidgetFactory;
@@ -29,5 +30,7 @@ class ChillPersonBundle extends Bundle
$container->addCompilerPass(new AccompanyingPeriodTimelineCompilerPass(), \Symfony\Component\DependencyInjection\Compiler\PassConfig::TYPE_BEFORE_OPTIMIZATION, 0);
$container->registerForAutoconfiguration(AccompanyingPeriodInfoUnionQueryPartInterface::class)
->addTag('chill_person.accompanying_period_info_part');
$container->registerForAutoconfiguration(PersonMoveSqlHandlerInterface::class)
->addTag('chill_person.person_move_handler');
}
}

View File

@@ -13,6 +13,7 @@ namespace Chill\PersonBundle\Controller;
use Chill\MainBundle\CRUD\Controller\ApiController;
use Chill\MainBundle\Entity\Address;
use Chill\MainBundle\Entity\Center;
use Chill\PersonBundle\Config\ConfigPersonAltNamesHelper;
use Chill\PersonBundle\Entity\AccompanyingPeriodParticipation;
use Chill\PersonBundle\Entity\Person;
@@ -50,7 +51,7 @@ class PersonApiController extends ApiController
['showCenters' => $this->showCenters, 'centers' => $centers],
Response::HTTP_OK,
[],
['gropus' => ['read']]
['groups' => ['read']]
);
}

View File

@@ -86,6 +86,7 @@ class PersonDuplicateController extends \Symfony\Bundle\FrameworkBundle\Controll
$connection->beginTransaction();
foreach ($sqls as $sql) {
dump($sql);
$connection->executeQuery($sql);
}
$connection->commit();

View File

@@ -1595,6 +1595,15 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI
return $this;
}
public function addCenterHistory(PersonCenterHistory $newCenterHistory): self
{
if (!$this->centerHistory->contains($newCenterHistory)) {
$this->centerHistory[] = $newCenterHistory;
$newCenterHistory->setPerson($this);
}
return $this;
}
public function setCFData(?array $cFData): self
{
$this->cFData = $cFData;

View File

@@ -225,7 +225,7 @@ class SocialIssue
/**
* @param array|SocialIssue[] $socialIssues
*/
public static function getDescendantsWithThisForIssues(array $socialIssues): Collection
public static function getDescendantsWithThisForIssues(array|Collection $socialIssues): Collection
{
$unique = [];

View File

@@ -11,7 +11,7 @@ declare(strict_types=1);
namespace Chill\PersonBundle\Form;
use Chill\PersonBundle\Form\Type\PickPersonType;
use Chill\PersonBundle\Form\Type\PickPersonDynamicType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
use Symfony\Component\Form\FormBuilderInterface;
@@ -21,7 +21,7 @@ class PersonFindManuallyDuplicateType extends AbstractType
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('person', PickPersonType::class, [
->add('person', PickPersonDynamicType::class, [
'label' => 'Find duplicate',
'mapped' => false,
])

View File

@@ -66,11 +66,10 @@ final readonly class AccompanyingPeriodACLAwareRepository implements Accompanyin
Join::WITH,
'COALESCE(IDENTITY(person_address.address), IDENTITY(location_history.addressLocation)) = address.id'
)
->join('address.postcode', 'postcode')
->andWhere(
$qb->expr()->in('postcode.code', ':postal_codes')
$qb->expr()->in('address.postcode', ':postal_codes')
)
->setParameter('postal_codes', array_map(fn (PostalCode $postalCode) => $postalCode->getCode(), $postalCodes));
->setParameter('postal_codes', $postalCodes);
}
return $qb;

View File

@@ -329,12 +329,13 @@ export default {
if (this.action !== 'create') {
this.loadData();
} else {
console.log('show centers', this.showCenters);
// console.log('show centers', this.showCenters);
getCentersForPersonCreation()
.then(params => {
this.config.centers = params.centers;
this.config.centers = params.centers.filter(c => c.isActive);
this.showCenters = params.showCenters;
console.log('show centers inside', this.showCenters);
// console.log('centers', this.config.centers)
// console.log('show centers inside', this.showCenters);
if (this.showCenters && this.config.centers.length === 1) {
this.person.center = this.config.centers[0];
}

View File

@@ -4,7 +4,7 @@
<li><b>{{ 'gender'|trans }}</b>:
{{ person.gender|trans }}</li>
<li><b>{{ 'maritalStatus'|trans }}</b>:
{% if person.maritalStatus.name %}{{ person.maritalStatus.name|localize_translatable_string }}{% endif %}</li>
{% if person.maritalStatus %}{{ person.maritalStatus.name|localize_translatable_string }}{% endif %}</li>
<li><b>{{ 'birthdate'|trans }}</b>:
{% if person.birthdate is not null %}{{ person.birthdate|format_date('short') }}{% endif %}</li>
<li><b>{{ 'placeOfBirth'|trans }}</b>:

View File

@@ -8,9 +8,9 @@
{% block content %}
<div class="person-duplicate">
<h1>{{ 'Désigner un dossier doublon'|trans }}</h1>
{{ form_start(form) }}
{{ form_rest(form) }}
@@ -29,3 +29,11 @@
</div>
{% endblock %}
{% block js %}
{{ encore_entry_script_tags('mod_pickentity_type') }}
{% endblock %}
{% block css %}
{{ encore_entry_link_tags('mod_pickentity_type') }}
{% endblock %}

View File

@@ -0,0 +1,275 @@
<?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 Action\Remove;
use Chill\ActivityBundle\Entity\Activity;
use Chill\MainBundle\Entity\Center;
use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Repository\CenterRepositoryInterface;
use Chill\PersonBundle\Actions\Remove\PersonMove;
use Chill\PersonBundle\Actions\Remove\PersonMoveManager;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Entity\AccompanyingPeriodParticipation;
use Chill\PersonBundle\Entity\Household\Household;
use Chill\PersonBundle\Entity\Household\HouseholdMember;
use Chill\PersonBundle\Entity\Person;
use Chill\PersonBundle\Entity\Relationships\Relation;
use Chill\PersonBundle\Entity\Relationships\Relationship;
use Chill\PersonBundle\Repository\PersonRepository;
use Doctrine\DBAL\Connection;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
/**
* @internal
* @coversNothing
*/
class PersonMoveTest extends KernelTestCase
{
private EntityManagerInterface $em;
private PersonMoveManager $personMoveManager;
private EventDispatcherInterface $eventDispatcher;
private CenterRepositoryInterface $centerRepository;
/**
* @var list<array{0: class-string, 1: int}>
*/
private static $entitiesToDelete = [];
public function setUp(): void
{
self::bootKernel();
$this->em = self::$container->get(EntityManagerInterface::class);
$this->personMoveManager = self::$container->get(PersonMoveManager::class);
$this->eventDispatcher = self::$container->get(EventDispatcherInterface::class);
$this->centerRepository = self::$container->get(CenterRepositoryInterface::class);
}
public static function tearDownAfterClass(): void
{
self::bootKernel();
$em = self::$container->get(EntityManagerInterface::class);
foreach (self::$entitiesToDelete as [$class, $id]) {
$entity = $em->find($class, $id);
if (null !== $entity) {
$em->remove($entity);
}
}
$em->flush();
}
/**
* @dataProvider dataProviderMovePerson
*/
public function testMovePersonSimple(Person $personA, Person $personB, string $message): void
{
$move = new PersonMove($this->em, $this->personMoveManager, $this->eventDispatcher);
$sqls = $move->getSQL($personA, $personB);
$this->em->getConnection()->transactional(function (Connection $conn) use ($personA, $personB, $sqls) {
foreach ($sqls as $sql) {
$conn->executeStatement($sql);
}
});
$personsByIdOfA = $this->em->createQuery("SELECT p FROM " . Person::class . " p WHERE p.id = :id")
->setParameter('id', $personA->getId())
->getResult();
$personB = $this->em->find(Person::class, $personB->getId());
self::assertCount(0, $personsByIdOfA);
self::assertNotNull($personB?->getId(), $message);
}
public function testMovePersonCenterHistory(): void
{
$personA = new Person();
$personB = new Person();
[$centerA, $centerB] = $this->centerRepository->findAll();
$this->em->persist($personA);
$this->em->persist($personB);
$personCenterHistoryAFirst = (new Person\PersonCenterHistory())->setCenter($centerA)
->setStartDate(new \DateTimeImmutable('2023-01-01'))
->setEndDate(new \DateTimeImmutable('2023-06-30'));
$personCenterHistoryASecond = (new Person\PersonCenterHistory())->setCenter($centerB)
->setStartDate(new \DateTimeImmutable('2023-06-30'))
->setEndDate(new \DateTimeImmutable('2023-09-30'));
$personCenterHistoryBFirst = (new Person\PersonCenterHistory())->setCenter($centerA)
->setStartDate(new \DateTimeImmutable('2023-03-01'))
->setEndDate(new \DateTimeImmutable('2023-07-15'));
$personCenterHistoryBSecond = (new Person\PersonCenterHistory())->setCenter($centerB)
->setStartDate(new \DateTimeImmutable('2023-07-15'))
->setEndDate(new \DateTimeImmutable('2023-09-30'));
$this->em->persist($personCenterHistoryAFirst);
$this->em->persist($personCenterHistoryASecond);
$this->em->persist($personCenterHistoryBFirst);
$this->em->persist($personCenterHistoryBSecond);
$personA->addCenterHistory($personCenterHistoryAFirst);
$personA->addCenterHistory($personCenterHistoryASecond);
$personB->addCenterHistory($personCenterHistoryBFirst);
$personB->addCenterHistory($personCenterHistoryBSecond);
$this->em->flush();
$this->em->clear();
$move = new PersonMove($this->em, $this->personMoveManager, $this->eventDispatcher);
$sqls = $move->getSQL($personA, $personB);
$this->em->getConnection()->transactional(function (Connection $conn) use ($personA, $personB, $sqls) {
foreach ($sqls as $sql) {
$conn->executeStatement($sql);
}
});
$personsByIdOfA = $this->em->createQuery("SELECT p FROM " . Person::class . " p WHERE p.id = :id")
->setParameter('id', $personA->getId())
->getResult();
/** @var Person $personB */
$personB = $this->em->find(Person::class, $personB->getId());
$message = 'Move persons with overlapping center histories';
$this->em->refresh($personB);
self::assertCount(0, $personsByIdOfA);
self::assertNotNull($personB?->getId(), $message);
$centerHistoriesB = $personB->getCenterHistory();
$oldestDate = new \DateTimeImmutable('2023-01-01');
$this->em->refresh($centerHistoriesB->first());
self::assertCount(2, $centerHistoriesB);
self::assertEquals($oldestDate, $centerHistoriesB->first()->getStartDate());
self::$entitiesToDelete[] = [Person::class, $personA];
self::$entitiesToDelete[] = [Person::class, $personB];
self::$entitiesToDelete[] = [Person\PersonCenterHistory::class, $personCenterHistoryAFirst];
self::$entitiesToDelete[] = [Person\PersonCenterHistory::class, $personCenterHistoryASecond];
self::$entitiesToDelete[] = [Person\PersonCenterHistory::class, $personCenterHistoryBFirst];
self::$entitiesToDelete[] = [Person\PersonCenterHistory::class, $personCenterHistoryBSecond];
}
public function dataProviderMovePerson(): iterable
{
$this->setUp();
$personA = new Person();
$personB = new Person();
$this->em->persist($personA);
$this->em->persist($personB);
self::$entitiesToDelete[] = [Person::class, $personA];
self::$entitiesToDelete[] = [Person::class, $personB];
yield [$personA, $personB, "move 2 people without any associated data"];
$personA = new Person();
$personB = new Person();
$activity = new Activity();
$activity->setDate(new \DateTime('today'));
$activity->addPerson($personA);
$activity->addPerson($personB);
$this->em->persist($personA);
$this->em->persist($personB);
$this->em->persist($activity);
self::$entitiesToDelete[] = [Person::class, $personA];
self::$entitiesToDelete[] = [Person::class, $personB];
self::$entitiesToDelete[] = [Activity::class, $activity];
yield [$personA, $personB, "move 2 people having an activity"];
$personA = new Person();
$personB = new Person();
$household = new Household();
$household->addMember(
$memberA = (new HouseholdMember())->setPerson($personA)->setShareHousehold(true)
->setStartDate(new \DateTimeImmutable('2023-01-01'))
);
$household->addMember(
$memberB = (new HouseholdMember())->setPerson($personB)->setShareHousehold(true)
->setStartDate(new \DateTimeImmutable('2023-01-01'))
);
$this->em->persist($personA);
$this->em->persist($personB);
$this->em->persist($household);
$this->em->persist($memberA);
$this->em->persist($memberB);
self::$entitiesToDelete[] = [Person::class, $personA];
self::$entitiesToDelete[] = [Person::class, $personB];
self::$entitiesToDelete[] = [HouseholdMember::class, $memberA];
self::$entitiesToDelete[] = [HouseholdMember::class, $memberB];
self::$entitiesToDelete[] = [Household::class, $household];
yield [$personA, $personB, "move 2 people having the same household at the same time"];
$personA = new Person();
$personB = new Person();
$parcours = new AccompanyingPeriod();
$parcours->addPerson($personA);
$parcours->addPerson($personB);
$this->em->persist($personA);
$this->em->persist($personB);
$this->em->persist($parcours);
self::$entitiesToDelete[] = [Person::class, $personA];
self::$entitiesToDelete[] = [Person::class, $personB];
self::$entitiesToDelete[] = [AccompanyingPeriod::class, $parcours];
yield [$personA, $personB, "move 2 people participating to the same parcours"];
$personA = new Person();
$personB = new Person();
$relationship = new Relationship();
$relation = new Relation();
$user = (new User())->setUsername(uniqid())->setEmail(uniqid() . '@foo.com');
$relationship->setRelation($relation);
$relationship->setToPerson($personA);
$relationship->setFromPerson($personB);
$relationship->setReverse(false);
$relationship->setCreatedBy($user);
$this->em->persist($personA);
$this->em->persist($personB);
$this->em->persist($relation);
$this->em->persist($user);
$this->em->persist($relationship);
self::$entitiesToDelete[] = [Person::class, $personA];
self::$entitiesToDelete[] = [Person::class, $personB];
self::$entitiesToDelete[] = [Relation::class, $relation];
self::$entitiesToDelete[] = [User::class, $user];
self::$entitiesToDelete[] = [Relationship::class, $relationship];
yield [$personA, $personB, "move 2 people with a relationship"];
$this->em->flush();
$this->em->clear();
}
}

View File

@@ -1,5 +1,13 @@
services:
Chill\PersonBundle\Actions\Remove\PersonMove:
_defaults:
autowire: true
autoconfigure: true
Chill\PersonBundle\Actions\Remove\PersonMove: ~
Chill\PersonBundle\Actions\Remove\PersonMoveManager:
arguments:
$em: '@Doctrine\ORM\EntityManagerInterface'
$eventDispatcher: '@Symfony\Contracts\EventDispatcher\EventDispatcherInterface'
$handlers: !tagged_iterator chill_person.person_move_handler
Chill\PersonBundle\Actions\Remove\Handler\:
resource: '../../Actions/Remove/Handler'

View File

@@ -26,6 +26,7 @@ use Chill\TaskBundle\Event\TaskEvent;
use Chill\TaskBundle\Event\UI\UIEvent;
use Chill\TaskBundle\Form\SingleTaskType;
use Chill\TaskBundle\Repository\SingleTaskAclAwareRepositoryInterface;
use Chill\TaskBundle\Repository\SingleTaskRepository;
use Chill\TaskBundle\Repository\SingleTaskStateRepository;
use Chill\TaskBundle\Security\Authorization\TaskVoter;
use LogicException;
@@ -57,7 +58,8 @@ final class SingleTaskController extends AbstractController
private readonly TimelineBuilder $timelineBuilder,
private readonly LoggerInterface $logger,
private readonly FilterOrderHelperFactoryInterface $filterOrderHelperFactory,
private readonly SingleTaskStateRepository $singleTaskStateRepository
private readonly SingleTaskStateRepository $singleTaskStateRepository,
private readonly SingleTaskRepository $singleTaskRepository,
) {}
/**
@@ -267,7 +269,9 @@ final class SingleTaskController extends AbstractController
) {
$this->denyAccessUnlessGranted(TaskVoter::SHOW, null);
$filterOrder = $this->buildFilterOrder();
$showMissionTypeFilter = $this->singleTaskRepository->countByDistinctTypes() > 1;
$filterOrder = $this->buildFilterOrder(true, $showMissionTypeFilter);
$filteredUsers = $filterOrder->getUserPickerData('userPicker');
@@ -275,9 +279,15 @@ final class SingleTaskController extends AbstractController
$filterOrder->getCheckboxData('status'),
array_map(static fn ($i) => 'state_' . $i, $filterOrder->hasCheckboxData('states') ? $filterOrder->getCheckboxData('states') : [])
);
if ($showMissionTypeFilter) {
$types = $filterOrder->getCheckboxData('missionTypePicker');
}
$nb = $this->singleTaskAclAwareRepository->countByAllViewable(
$filterOrder->getQueryString(),
$flags,
$types ?? [],
$filteredUsers
);
$paginator = $this->paginatorFactory->create($nb);
@@ -286,6 +296,7 @@ final class SingleTaskController extends AbstractController
$tasks = $this->singleTaskAclAwareRepository->findByAllViewable(
$filterOrder->getQueryString(),
$flags,
$types ?? [],
$filteredUsers,
$paginator->getCurrentPageFirstItemNumber(),
$paginator->getItemsPerPage(),
@@ -631,7 +642,7 @@ final class SingleTaskController extends AbstractController
return $form;
}
private function buildFilterOrder($includeFilterByUser = true): FilterOrderHelper
private function buildFilterOrder($includeFilterByUser = true, $includeMissionType = false): FilterOrderHelper
{
$statuses = ['no-alert', 'warning', 'alert'];
$statusTrans = [
@@ -639,6 +650,7 @@ final class SingleTaskController extends AbstractController
'Tasks near deadline',
'Tasks over deadline',
];
$arrayOfTypes = [];
$filterBuilder = $this->filterOrderHelperFactory
->create(self::class)
@@ -658,6 +670,16 @@ final class SingleTaskController extends AbstractController
->addUserPicker('userPicker', 'Filter by user', ['multiple' => true, 'required' => false]);
}
if ($includeMissionType) {
$filterBuilder->addCheckbox(
'missionTypePicker',
array_map(
fn ($row) => $row['type'],
$this->singleTaskRepository->findAllTaskDistinctTypes()
)
);
}
return $filterBuilder->build();
}

View File

@@ -31,6 +31,15 @@ use Doctrine\ORM\Mapping as ORM;
*/
class SingleTaskPlaceEvent extends AbstractTaskPlaceEvent
{
/**
* @var int
*
* @ORM\Column(name="id", type="integer")
* @ORM\Id
* @ORM\GeneratedValue(strategy="AUTO")
*/
private ?int $id = null;
/**
* @var SingleTask
* @ORM\ManyToOne(
@@ -51,4 +60,14 @@ class SingleTaskPlaceEvent extends AbstractTaskPlaceEvent
return $this;
}
/**
* Get id.
*
* @return int
*/
public function getId()
{
return $this->id;
}
}

View File

@@ -34,6 +34,7 @@ final readonly class SingleTaskAclAwareRepository implements SingleTaskAclAwareR
public function buildBaseQuery(
?string $pattern = null,
?array $flags = [],
?array $types = [],
?array $users = []
): QueryBuilder {
$qb = $this->em->createQueryBuilder();
@@ -59,8 +60,12 @@ final readonly class SingleTaskAclAwareRepository implements SingleTaskAclAwareR
if ($orXUser->count() > 0) {
$qb->andWhere($orXUser);
}
}
return $qb;
if (null !== $types && count($types) > 0) {
$qb->andWhere($qb->expr()->in('t.type', ':types'));
$qb->setParameter('types', $types);
}
if (null !== $flags && count($flags) > 0) {
@@ -185,9 +190,10 @@ final readonly class SingleTaskAclAwareRepository implements SingleTaskAclAwareR
public function countByAllViewable(
?string $pattern = null,
?array $flags = [],
?array $types = [],
?array $users = []
): int {
$qb = $this->buildBaseQuery($pattern, $flags, $users);
$qb = $this->buildBaseQuery($pattern, $flags, $types, $users);
return $this
->addACLGlobal($qb)
@@ -233,12 +239,13 @@ final readonly class SingleTaskAclAwareRepository implements SingleTaskAclAwareR
public function findByAllViewable(
?string $pattern = null,
?array $flags = [],
?array $types = [],
?array $users = [],
?int $start = 0,
?int $limit = 50,
?array $orderBy = []
): array {
$qb = $this->buildBaseQuery($pattern, $flags, $users);
$qb = $this->buildBaseQuery($pattern, $flags, $types, $users);
$qb = $this->addACLGlobal($qb);
return $this->getResult($qb, $start, $limit, $orderBy);

View File

@@ -19,6 +19,7 @@ interface SingleTaskAclAwareRepositoryInterface
public function countByAllViewable(
?string $pattern = null,
?array $flags = [],
?array $types = [],
?array $users = []
): int;
@@ -39,6 +40,7 @@ interface SingleTaskAclAwareRepositoryInterface
public function findByAllViewable(
?string $pattern = null,
?array $flags = [],
?array $types = [],
?array $users = [],
?int $start = 0,
?int $limit = 50,

View File

@@ -306,4 +306,28 @@ class SingleTaskRepository extends EntityRepository
->add($qb->expr()->isNull('st.endDate'))
->add($qb->expr()->isNull('st.warningInterval'));
}
public function countByDistinctTypes()
{
$qb = $this->createQueryBuilder('st')
->select('COUNT(DISTINCT st.type)');
$this->buildQuery($qb, []);
return (int) $qb
->getQuery()
->getSingleScalarResult();
}
public function findAllTaskDistinctTypes()
{
$qb = $this->createQueryBuilder('st')
->select('DISTINCT st.type');
$this->buildQuery($qb, []);
return $qb
->getQuery()
->getResult();
}
}