Merge branch 'testing' of gitlab.com:Chill-Projet/chill-bundles into testing

This commit is contained in:
Julie Lenaerts 2022-11-15 13:44:14 +01:00
commit 2f765a3aa5
32 changed files with 751 additions and 718 deletions

View File

@ -25,10 +25,12 @@ use Chill\MainBundle\DependencyInjection\Widget\Factory\WidgetFactoryInterface;
use Chill\MainBundle\Doctrine\DQL\Age;
use Chill\MainBundle\Doctrine\DQL\Extract;
use Chill\MainBundle\Doctrine\DQL\GetJsonFieldByKey;
use Chill\MainBundle\Doctrine\DQL\Greatest;
use Chill\MainBundle\Doctrine\DQL\JsonAggregate;
use Chill\MainBundle\Doctrine\DQL\JsonbArrayLength;
use Chill\MainBundle\Doctrine\DQL\JsonbExistsInArray;
use Chill\MainBundle\Doctrine\DQL\JsonExtract;
use Chill\MainBundle\Doctrine\DQL\Least;
use Chill\MainBundle\Doctrine\DQL\OverlapsI;
use Chill\MainBundle\Doctrine\DQL\Replace;
use Chill\MainBundle\Doctrine\DQL\Similarity;
@ -249,6 +251,8 @@ class ChillMainExtension extends Extension implements
'JSONB_ARRAY_LENGTH' => JsonbArrayLength::class,
'ST_X' => STX::class,
'ST_Y' => STY::class,
'GREATEST' => Greatest::class,
'LEAST' => LEAST::class,
],
'datetime_functions' => [
'EXTRACT' => Extract::class,

View File

@ -0,0 +1,57 @@
<?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\MainBundle\Doctrine\DQL;
use Doctrine\ORM\Query\AST\Functions\FunctionNode;
use Doctrine\ORM\Query\AST\Node;
use Doctrine\ORM\Query\Lexer;
use Doctrine\ORM\Query\Parser;
use Doctrine\ORM\Query\SqlWalker;
/**
* Postgresql GREATEST function.
*
* Borrowed from https://github.com/beberlei/DoctrineExtensions/blob/master/src/Query/Postgresql/Greatest.php
* (https://github.com/beberlei/DoctrineExtensions/blob/master/LICENSE) and
* https://gist.github.com/olimsaidov/4bbd530b1b645ce75e1bbb781b5dd91f
*/
class Greatest extends FunctionNode
{
/**
* @var array|Node[]
*/
private array $exprs = [];
public function getSql(SqlWalker $sqlWalker)
{
return 'GREATEST(' . implode(', ', array_map(static function (Node $expr) use ($sqlWalker) {
return $expr->dispatch($sqlWalker);
}, $this->exprs)) . ')';
}
public function parse(Parser $parser)
{
$this->exprs = [];
$lexer = $parser->getLexer();
$parser->match(Lexer::T_IDENTIFIER);
$parser->match(Lexer::T_OPEN_PARENTHESIS);
$this->exprs[] = $parser->ArithmeticPrimary();
while (Lexer::T_COMMA === $lexer->lookahead['type']) {
$parser->match(Lexer::T_COMMA);
$this->exprs[] = $parser->ArithmeticPrimary();
}
$parser->match(Lexer::T_CLOSE_PARENTHESIS);
}
}

View File

@ -0,0 +1,57 @@
<?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\MainBundle\Doctrine\DQL;
use Doctrine\ORM\Query\AST\Functions\FunctionNode;
use Doctrine\ORM\Query\AST\Node;
use Doctrine\ORM\Query\Lexer;
use Doctrine\ORM\Query\Parser;
use Doctrine\ORM\Query\SqlWalker;
/**
* Postgresql LEAST function.
*
* Borrowed from https://github.com/beberlei/DoctrineExtensions/blob/master/src/Query/Postgresql/Least.php
* (https://github.com/beberlei/DoctrineExtensions/blob/master/LICENSE) and
* https://gist.github.com/olimsaidov/4bbd530b1b645ce75e1bbb781b5dd91f
*/
class Least extends FunctionNode
{
/**
* @var array|Node[]
*/
private array $exprs = [];
public function getSql(SqlWalker $sqlWalker)
{
return 'LEAST(' . implode(', ', array_map(static function (Node $expr) use ($sqlWalker) {
return $expr->dispatch($sqlWalker);
}, $this->exprs)) . ')';
}
public function parse(Parser $parser)
{
$this->exprs = [];
$lexer = $parser->getLexer();
$parser->match(Lexer::T_IDENTIFIER);
$parser->match(Lexer::T_OPEN_PARENTHESIS);
$this->exprs[] = $parser->ArithmeticPrimary();
while (Lexer::T_COMMA === $lexer->lookahead['type']) {
$parser->match(Lexer::T_COMMA);
$this->exprs[] = $parser->ArithmeticPrimary();
}
$parser->match(Lexer::T_CLOSE_PARENTHESIS);
}
}

View File

@ -15,6 +15,8 @@ use Chill\MainBundle\Doctrine\Model\Point;
use Chill\ThirdPartyBundle\Entity\ThirdParty;
use DateTime;
use DateTimeInterface;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Annotation\Groups;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
@ -97,6 +99,23 @@ class Address
*/
private $floor;
/**
* List of geographical units and addresses.
*
* This list is computed by a materialized view. It won't be populated until a refresh is done
* on the materialized view.
*
* @var Collection<int, GeographicalUnit>|GeographicalUnit[]
* @readonly
* @ORM\ManyToMany(targetEntity=GeographicalUnit::class)
* @ORM\JoinTable(
* name="view_chill_main_address_geographical_unit",
* joinColumns={@ORM\JoinColumn(name="address_id")},
* inverseJoinColumns={@ORM\JoinColumn(name="geographical_unit_id")}
* )
*/
private Collection $geographicalUnits;
/**
* @var int
*
@ -104,8 +123,9 @@ class Address
* @ORM\Column(name="id", type="integer")
* @ORM\GeneratedValue(strategy="AUTO")
* @Groups({"write"})
* @readonly
*/
private $id;
private ?int $id = null;
/**
* True if the address is a "no address", aka homeless person, ...
@ -190,6 +210,7 @@ class Address
public function __construct()
{
$this->validFrom = new DateTime();
$this->geographicalUnits = new ArrayCollection();
}
public static function createFromAddress(Address $original): Address
@ -273,6 +294,14 @@ class Address
return $this->floor;
}
/**
* @return Collection<int, GeographicalUnit>|GeographicalUnit[]
*/
public function getGeographicalUnits(): Collection
{
return $this->geographicalUnits;
}
/**
* Get id.
*

View File

@ -0,0 +1,48 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\MainBundle\Tests\Doctrine\DQL;
use Chill\MainBundle\Entity\Address;
use DateTimeImmutable;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
/**
* @internal
* @coversNothing
*/
final class GreatestTest extends KernelTestCase
{
private EntityManagerInterface $entityManager;
protected function setUp(): void
{
self::bootKernel();
$this->entityManager = self::$container->get(EntityManagerInterface::class);
}
public function testGreatestInDQL()
{
$dql = 'SELECT GREATEST(a.validFrom, a.validTo, :now) AS g FROM ' . Address::class . ' a WHERE a.validTo < :now and a.validFrom < :now';
$actual = $this->entityManager
->createQuery($dql)
->setParameter('now', $now = new DateTimeImmutable('now'), Types::DATE_IMMUTABLE)
->setMaxResults(3)
->getResult();
$this->assertIsArray($actual);
$this->assertEquals($now->format('Y-m-d'), $actual[0]['g']);
}
}

View File

@ -0,0 +1,48 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\MainBundle\Tests\Doctrine\DQL;
use Chill\MainBundle\Entity\Address;
use DateTimeImmutable;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
/**
* @internal
* @coversNothing
*/
final class LeastTest extends KernelTestCase
{
private EntityManagerInterface $entityManager;
protected function setUp(): void
{
self::bootKernel();
$this->entityManager = self::$container->get(EntityManagerInterface::class);
}
public function testGreatestInDQL()
{
$dql = 'SELECT LEAST(a.validFrom, a.validTo, :now) AS g FROM ' . Address::class . ' a WHERE a.validTo < :now and a.validFrom < :now';
$actual = $this->entityManager
->createQuery($dql)
->setParameter('now', $now = new DateTimeImmutable('now'), Types::DATE_IMMUTABLE)
->setMaxResults(3)
->getResult();
$this->assertIsArray($actual);
$this->assertNotEquals($now->format('Y-m-d'), $actual[0]['g']);
}
}

View File

@ -0,0 +1,42 @@
<?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;
final class Version20221114105345 extends AbstractMigration
{
public function down(Schema $schema): void
{
$this->addSql('DROP MATERIALIZED VIEW view_chill_main_address_geographical_unit');
}
public function getDescription(): string
{
return 'Create materialized view to store GeographicalUnitAddress';
}
public function up(Schema $schema): void
{
$this->addSql('CREATE MATERIALIZED VIEW view_chill_main_address_geographical_unit (address_id, geographical_unit_id) AS
SELECT
address.id AS address_id,
geographical_unit.id AS geographical_unit_id
FROM chill_main_address AS address
JOIN chill_main_geographical_unit AS geographical_unit ON ST_CONTAINS(geographical_unit.geom, address.point)
');
$this->addSql('CREATE INDEX IDX_BD42692CF5B7AF75 ON view_chill_main_address_geographical_unit (address_id)');
$this->addSql('CREATE INDEX IDX_BD42692CDAA4DAB8 ON view_chill_main_address_geographical_unit (geographical_unit_id)');
}
}

View File

@ -97,11 +97,11 @@ class ChillPersonExtension extends Extension implements PrependExtensionInterfac
$loader->load('services/exports_person.yaml');
if ($container->getParameter('chill_person.accompanying_period') !== 'hidden') {
$loader->load('services/exports_accompanying_period.yaml');
$loader->load('services/exports_accompanying_course.yaml');
$loader->load('services/exports_social_actions.yaml');
$loader->load('services/exports_evaluation.yaml');
}
$loader->load('services/exports_accompanying_course.yaml');
$loader->load('services/exports_social_actions.yaml');
$loader->load('services/exports_evaluation.yaml');
$loader->load('services/exports_household.yaml');
}
@ -944,10 +944,8 @@ class ChillPersonExtension extends Extension implements PrependExtensionInterfac
/**
* Add a widget "add a person" on the homepage, automatically.
*
* @param \Chill\PersonBundle\DependencyInjection\containerBuilder $container
*/
protected function prependHomepageWidget(containerBuilder $container)
protected function prependHomepageWidget(ContainerBuilder $container)
{
$container->prependExtensionConfig('chill_main', [
'widgets' => [

View File

@ -12,7 +12,6 @@ declare(strict_types=1);
namespace Chill\PersonBundle\Export\Aggregator\AccompanyingCourseAggregators;
use Chill\MainBundle\Entity\Address;
use Chill\MainBundle\Entity\GeographicalUnit;
use Chill\MainBundle\Entity\GeographicalUnitLayer;
use Chill\MainBundle\Export\AggregatorInterface;
use Chill\MainBundle\Form\Type\ChillDateType;
@ -26,7 +25,6 @@ use Doctrine\ORM\QueryBuilder;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\FormBuilderInterface;
use UnexpectedValueException;
use function in_array;
final class GeographicalUnitStatAggregator implements AggregatorInterface
{
@ -49,57 +47,49 @@ final class GeographicalUnitStatAggregator implements AggregatorInterface
public function alterQuery(QueryBuilder $qb, $data)
{
if (!in_array('acp_geog_agg_location_history', $qb->getAllAliases(), true)) {
$qb->leftJoin('acp.locationHistories', 'acp_geog_agg_location_history');
$qb->leftJoin('acp.locationHistories', 'acp_geog_agg_location_history');
$qb->andWhere(
$qb->expr()->andX(
'acp_geog_agg_location_history.startDate <= :acp_geog_aggregator_date',
$qb->expr()->orX(
$qb->andWhere(
$qb->expr()->andX(
'acp_geog_agg_location_history.startDate <= :acp_geog_aggregator_date',
$qb->expr()->orX(
'acp_geog_agg_location_history.endDate IS NULL',
'acp_geog_agg_location_history.endDate > :acp_geog_aggregator_date'
)
)
);
)
);
$qb->setParameter('acp_geog_aggregator_date', $data['date_calc']);
}
$qb->setParameter('acp_geog_aggregator_date', $data['date_calc']);
// link between location history and person
if (!in_array('acp_geog_agg_address_person_location', $qb->getAllAliases(), true)) {
$qb->leftJoin(
PersonHouseholdAddress::class,
'acp_geog_agg_address_person_location',
Join::WITH,
$qb->expr()->andX(
'IDENTITY(acp_geog_agg_address_person_location.person) = IDENTITY(acp_geog_agg_location_history.personLocation)',
'acp_geog_agg_address_person_location.validFrom < :acp_geog_aggregator_date',
$qb->expr()->orX(
$qb->leftJoin(
PersonHouseholdAddress::class,
'acp_geog_agg_address_person_location',
Join::WITH,
$qb->expr()->andX(
'IDENTITY(acp_geog_agg_address_person_location.person) = IDENTITY(acp_geog_agg_location_history.personLocation)',
'acp_geog_agg_address_person_location.validFrom < :acp_geog_aggregator_date',
$qb->expr()->orX(
'acp_geog_agg_address_person_location.validTo > :acp_geog_aggregator_date',
$qb->expr()->isNull('acp_geog_agg_address_person_location.validTo')
)
)
);
)
);
$qb->setParameter('acp_geog_aggregator_date', $data['date_calc']);
}
$qb->setParameter('acp_geog_aggregator_date', $data['date_calc']);
// we finally find an address
if (!in_array('acp_geog_agg_address', $qb->getAllAliases(), true)) {
$qb->leftJoin(
Address::class,
'acp_geog_agg_address',
Join::WITH,
'COALESCE(IDENTITY(acp_geog_agg_address_person_location.address), IDENTITY(acp_geog_agg_location_history.addressLocation)) = acp_geog_agg_address.id'
);
}
$qb->leftJoin(
Address::class,
'acp_geog_agg_address',
Join::WITH,
'COALESCE(IDENTITY(acp_geog_agg_address_person_location.address), IDENTITY(acp_geog_agg_location_history.addressLocation)) = acp_geog_agg_address.id'
);
// and we do a join with units
$qb->leftJoin(
GeographicalUnit::class,
'acp_geog_units',
Join::WITH,
'ST_CONTAINS(acp_geog_units.geom, acp_geog_agg_address.point) = TRUE'
'acp_geog_agg_address.geographicalUnits',
'acp_geog_units'
);
$qb->andWhere($qb->expr()->eq('acp_geog_units.layer', ':acp_geog_unit_layer'));

View File

@ -9,20 +9,18 @@ declare(strict_types=1);
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\PersonBundle\Export\Aggregator\AccompanyingCourseAggregators;
namespace Chill\PersonBundle\Export\Aggregator\PersonAggregators;
use Chill\MainBundle\Export\AggregatorInterface;
use Chill\MainBundle\Form\Type\ChillDateType;
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
use Chill\PersonBundle\Entity\Household\HouseholdComposition;
use Chill\PersonBundle\Entity\Household\HouseholdMember;
use Chill\PersonBundle\Export\Declarations;
use Chill\PersonBundle\Repository\Household\HouseholdCompositionTypeRepositoryInterface;
use DateTimeImmutable;
use Doctrine\ORM\Query\Expr\Join;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface;
use function in_array;
class ByHouseholdCompositionAggregator implements AggregatorInterface
{
@ -47,30 +45,20 @@ class ByHouseholdCompositionAggregator implements AggregatorInterface
{
$p = self::PREFIX;
if (!in_array('acppart', $qb->getAllAliases(), true)) {
$qb->leftJoin('acp.participations', 'acppart');
}
$qb
->leftJoin(
HouseholdMember::class,
"{$p}_hm",
Join::WITH,
$qb->expr()->orX(
$qb->expr()->isNull("{$p}_hm"),
$qb->expr()->andX(
$qb->expr()->lte("{$p}_hm.startDate", ":{$p}_date"),
$qb->expr()->orX(
$qb->expr()->isNull("{$p}_hm.endDate"),
$qb->expr()->gt("{$p}_hm.endDate", ":{$p}_date")
)
)
)
'person.householdParticipations',
"{$p}_hm"
)
->leftJoin(
HouseholdComposition::class,
"{$p}_compo",
Join::WITH,
$qb->expr()->eq("{$p}_hm.household", "{$p}_compo.household"),
)
->andWhere("{$p}_hm.startDate <= :{$p}_date AND ({$p}_hm.endDate IS NULL OR {$p}_hm.endDate > :{$p}_date)")
->andWhere("{$p}_hm.shareHousehold = 'TRUE'")
->andWhere(
$qb->expr()->orX(
$qb->expr()->isNull("{$p}_compo"),
$qb->expr()->andX(
@ -89,13 +77,13 @@ class ByHouseholdCompositionAggregator implements AggregatorInterface
public function applyOn()
{
return Declarations::ACP_TYPE;
return Declarations::PERSON_TYPE;
}
public function buildForm(FormBuilderInterface $builder)
{
$builder->add('date_calc', ChillDateType::class, [
'label' => 'export.aggregator.course.by_household_composition.Calc date',
'label' => 'export.aggregator.person.by_household_composition.Calc date',
'input_format' => 'datetime_immutable',
'data' => new DateTimeImmutable('now'),
]);
@ -105,7 +93,7 @@ class ByHouseholdCompositionAggregator implements AggregatorInterface
{
return function ($value) {
if ('_header' === $value) {
return 'export.aggregator.course.by_household_composition.Household composition';
return 'export.aggregator.person.by_household_composition.Household composition';
}
if (null === $value) {
@ -127,6 +115,6 @@ class ByHouseholdCompositionAggregator implements AggregatorInterface
public function getTitle()
{
return 'export.aggregator.course.by_household_composition.Group course by household composition';
return 'export.aggregator.person.by_household_composition.Group course by household composition';
}
}

View File

@ -11,7 +11,6 @@ declare(strict_types=1);
namespace Chill\PersonBundle\Export\Aggregator\PersonAggregators;
use Chill\MainBundle\Entity\GeographicalUnit;
use Chill\MainBundle\Entity\GeographicalUnitLayer;
use Chill\MainBundle\Export\AggregatorInterface;
use Chill\MainBundle\Form\Type\ChillDateType;
@ -19,7 +18,6 @@ use Chill\MainBundle\Repository\GeographicalUnitLayerRepositoryInterface;
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
use Chill\PersonBundle\Export\Declarations;
use DateTimeImmutable;
use Doctrine\ORM\Query\Expr\Join;
use Doctrine\ORM\QueryBuilder;
use LogicException;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
@ -49,7 +47,7 @@ class GeographicalUnitAggregator implements AggregatorInterface
$qb
->leftJoin('person.householdAddresses', 'person_geog_agg_current_household_address')
->leftJoin('person_geog_agg_current_household_address.address', 'person_geog_agg_address')
->leftJoin(GeographicalUnit::class, 'person_geog_agg_geog_unit', Join::WITH, 'ST_CONTAINS(person_geog_agg_geog_unit.geom, person_geog_agg_address.point) = TRUE')
->leftJoin('person_geog_agg_address.geographicalUnits', 'person_geog_agg_geog_unit')
->andWhere(
$qb->expr()->orX(
$qb->expr()->isNull('person_geog_agg_current_household_address'),

View File

@ -14,8 +14,11 @@ namespace Chill\PersonBundle\Export\Aggregator\SocialWorkAggregators;
use Chill\MainBundle\Export\AggregatorInterface;
use Chill\PersonBundle\Export\Declarations;
use Chill\PersonBundle\Repository\SocialWork\SocialActionRepository;
use Chill\PersonBundle\Repository\SocialWork\SocialIssueRepository;
use Chill\PersonBundle\Templating\Entity\SocialActionRender;
use Chill\PersonBundle\Templating\Entity\SocialIssueRender;
use Doctrine\ORM\QueryBuilder;
use LogicException;
use Symfony\Component\Form\FormBuilderInterface;
use function in_array;
@ -25,12 +28,20 @@ final class ActionTypeAggregator implements AggregatorInterface
private SocialActionRepository $socialActionRepository;
private SocialIssueRender $socialIssueRender;
private SocialIssueRepository $socialIssueRepository;
public function __construct(
SocialActionRepository $socialActionRepository,
SocialActionRender $actionRender
SocialActionRender $actionRender,
SocialIssueRender $socialIssueRender,
SocialIssueRepository $socialIssueRepository
) {
$this->socialActionRepository = $socialActionRepository;
$this->actionRender = $actionRender;
$this->socialIssueRender = $socialIssueRender;
$this->socialIssueRepository = $socialIssueRepository;
}
public function addRole(): ?string
@ -44,8 +55,15 @@ final class ActionTypeAggregator implements AggregatorInterface
$qb->leftJoin('acpw.socialAction', 'acpwsocialaction');
}
$qb->addSelect('acpwsocialaction.id as action_type_aggregator');
$qb->addGroupBy('action_type_aggregator');
if (!in_array('acpwsocialissue', $qb->getAllAliases(), true)) {
$qb->leftJoin('acpwsocialaction.issue', 'acpwsocialissue');
}
$qb
->addSelect('acpwsocialissue.id as social_action_type_aggregator')
->addSelect('acpwsocialaction.id as action_type_aggregator')
->addGroupBy('action_type_aggregator')
->addGroupBy('social_action_type_aggregator');
}
public function applyOn()
@ -60,24 +78,41 @@ final class ActionTypeAggregator implements AggregatorInterface
public function getLabels($key, array $values, $data)
{
return function ($value): string {
if ('_header' === $value) {
return 'Social Action Type';
}
switch ($key) {
case 'action_type_aggregator':
return function ($value): string {
if ('_header' === $value) {
return 'Social Action Type';
}
if (null === $value) {
return '';
}
if (null === $value || null === $sa = $this->socialActionRepository->find($value)) {
return '';
}
$sa = $this->socialActionRepository->find($value);
return $this->actionRender->renderString($sa, []);
};
return $this->actionRender->renderString($sa, []);
};
case 'social_action_type_aggregator':
return function ($value): string {
if ('_header' === $value) {
return 'Social Issue';
}
if (null === $value || null === $si = $this->socialIssueRepository->find($value)) {
return '';
}
return $this->socialIssueRender->renderString($si, []);
};
default:
throw new LogicException('this key is not supported: ' . $key);
}
}
public function getQueryKeys($data)
{
return ['action_type_aggregator'];
return ['social_action_type_aggregator', 'action_type_aggregator'];
}
public function getTitle()

View File

@ -15,7 +15,6 @@ use Chill\MainBundle\Export\ExportInterface;
use Chill\MainBundle\Export\FormatterInterface;
use Chill\MainBundle\Export\GroupedExportInterface;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Entity\AccompanyingPeriodParticipation;
use Chill\PersonBundle\Entity\Person\PersonCenterHistory;
use Chill\PersonBundle\Export\Declarations;
use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter;
@ -100,11 +99,13 @@ class CountAccompanyingCourse implements ExportInterface, GroupedExportInterface
$qb
->andWhere('acp.step != :count_acp_step')
->leftJoin('acp.participations', 'acppart')
->leftJoin('acppart.person', 'person')
->andWhere('acppart.startDate != acppart.endDate OR acppart.endDate IS NULL')
->andWhere(
$qb->expr()->exists(
'SELECT 1 FROM ' . AccompanyingPeriodParticipation::class . ' acl_count_part
JOIN ' . PersonCenterHistory::class . ' acl_count_person_history WITH IDENTITY(acl_count_person_history.person) = IDENTITY(acl_count_part.person)
WHERE acl_count_part.accompanyingPeriod = acp.id AND acl_count_person_history.center IN (:authorized_centers)
'SELECT 1 FROM ' . PersonCenterHistory::class . ' acl_count_person_history WHERE acl_count_person_history.person = person
AND acl_count_person_history.center IN (:authorized_centers)
'
)
)
@ -125,6 +126,7 @@ class CountAccompanyingCourse implements ExportInterface, GroupedExportInterface
{
return [
Declarations::ACP_TYPE,
Declarations::PERSON_TYPE,
];
}
}

View File

@ -15,25 +15,22 @@ use Chill\MainBundle\Export\ExportInterface;
use Chill\MainBundle\Export\FormatterInterface;
use Chill\MainBundle\Export\GroupedExportInterface;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Entity\AccompanyingPeriodParticipation;
use Chill\PersonBundle\Entity\Person\PersonCenterHistory;
use Chill\PersonBundle\Export\Declarations;
use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\Query;
use LogicException;
use Symfony\Component\Form\FormBuilderInterface;
use function in_array;
class CountEvaluation implements ExportInterface, GroupedExportInterface
{
private EntityRepository $repository;
private EntityManagerInterface $entityManager;
public function __construct(
EntityManagerInterface $em
) {
$this->repository = $em->getRepository(AccompanyingPeriod::class);
$this->entityManager = $em;
}
public function buildForm(FormBuilderInterface $builder)
@ -96,22 +93,19 @@ class CountEvaluation implements ExportInterface, GroupedExportInterface
return $el['center'];
}, $acl);
$qb = $this->repository->createQueryBuilder('acp');
if (!in_array('acpw', $qb->getAllAliases(), true)) {
$qb->join('acp.works', 'acpw');
}
if (!in_array('workeval', $qb->getAllAliases(), true)) {
$qb->join('acpw.accompanyingPeriodWorkEvaluations', 'workeval');
}
$qb = $this->entityManager->createQueryBuilder();
$qb
->from(AccompanyingPeriod\AccompanyingPeriodWorkEvaluation::class, 'workeval')
->join('workeval.accompanyingPeriodWork', 'acpw')
->join('acpw.accompanyingPeriod', 'acp')
->join('acp.participations', 'acppart')
->join('acppart.person', 'person')
->andWhere('acppart.startDate != acppart.endDate OR acppart.endDate IS NULL')
->andWhere(
$qb->expr()->exists(
'SELECT 1 FROM ' . AccompanyingPeriodParticipation::class . ' acl_count_part
JOIN ' . PersonCenterHistory::class . ' acl_count_person_history WITH IDENTITY(acl_count_person_history.person) = IDENTITY(acl_count_part.person)
WHERE acl_count_part.accompanyingPeriod = acp.id AND acl_count_person_history.center IN (:authorized_centers)
'SELECT 1 FROM ' . PersonCenterHistory::class . ' acl_count_person_history WHERE acl_count_person_history.person = person
AND acl_count_person_history.center IN (:authorized_centers)
'
)
)
@ -133,6 +127,7 @@ class CountEvaluation implements ExportInterface, GroupedExportInterface
Declarations::EVAL_TYPE,
Declarations::SOCIAL_WORK_ACTION_TYPE,
Declarations::ACP_TYPE,
Declarations::PERSON_TYPE,
];
}
}

View File

@ -14,31 +14,42 @@ namespace Chill\PersonBundle\Export\Export;
use Chill\MainBundle\Export\ExportInterface;
use Chill\MainBundle\Export\FormatterInterface;
use Chill\MainBundle\Export\GroupedExportInterface;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Entity\AccompanyingPeriodParticipation;
use Chill\PersonBundle\Entity\Household\HouseholdMember;
use Chill\MainBundle\Form\Type\PickRollingDateType;
use Chill\MainBundle\Service\RollingDate\RollingDate;
use Chill\MainBundle\Service\RollingDate\RollingDateConverterInterface;
use Chill\PersonBundle\Entity\Household\Household;
use Chill\PersonBundle\Entity\Person\PersonCenterHistory;
use Chill\PersonBundle\Export\Declarations;
use Chill\PersonBundle\Security\Authorization\HouseholdVoter;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\Query;
use LogicException;
use Symfony\Component\Form\FormBuilderInterface;
class CountHousehold implements ExportInterface, GroupedExportInterface
{
private EntityRepository $repository;
private const TR_PREFIX = 'export.export.nb_household_with_course.';
private EntityManagerInterface $entityManager;
private RollingDateConverterInterface $rollingDateConverter;
public function __construct(
EntityManagerInterface $em
EntityManagerInterface $em,
RollingDateConverterInterface $rollingDateConverter
) {
$this->repository = $em->getRepository(AccompanyingPeriod::class);
$this->entityManager = $em;
$this->rollingDateConverter = $rollingDateConverter;
}
public function buildForm(FormBuilderInterface $builder)
{
// TODO: Implement buildForm() method.
$builder
->add('calc_date', PickRollingDateType::class, [
'data' => new RollingDate(RollingDate::T_TODAY),
'label' => self::TR_PREFIX . 'Date of calculation of household members',
'required' => false,
]);
}
public function getAllowedFormattersTypes(): array
@ -48,7 +59,7 @@ class CountHousehold implements ExportInterface, GroupedExportInterface
public function getDescription(): string
{
return 'Count household by various parameters.';
return self::TR_PREFIX . 'Count household with accompanying course by various parameters.';
}
public function getGroup(): string
@ -58,21 +69,31 @@ class CountHousehold implements ExportInterface, GroupedExportInterface
public function getLabels($key, array $values, $data)
{
if ('export_result' !== $key) {
throw new LogicException("the key {$key} is not used by this export");
}
return static function ($value) use ($key) {
if ('_header' === $value) {
switch ($key) {
case 'household_export_result':
return self::TR_PREFIX . 'Count households';
$labels = array_combine($values, $values);
$labels['_header'] = $this->getTitle();
case 'acp_export_result':
return self::TR_PREFIX . 'Count accompanying periods';
return static function ($value) use ($labels) {
return $labels[$value];
default:
throw new LogicException('Key not supported: ' . $key);
}
}
if (null === $value) {
return '';
}
return $value;
};
}
public function getQueryKeys($data): array
{
return ['export_result'];
return ['household_export_result', 'acp_export_result'];
}
public function getResult($query, $data)
@ -82,7 +103,7 @@ class CountHousehold implements ExportInterface, GroupedExportInterface
public function getTitle(): string
{
return 'Count households';
return self::TR_PREFIX . 'Count households with accompanying course';
}
public function getType(): string
@ -96,25 +117,29 @@ class CountHousehold implements ExportInterface, GroupedExportInterface
return $el['center'];
}, $acl);
$qb = $this->repository
->createQueryBuilder('acp')
->join('acp.participations', 'acppart')
// little optimization: we remove joins and make a direct join between participations and household members
->join(HouseholdMember::class, 'member', Query\Expr\Join::WITH, 'IDENTITY(acppart.person) = IDENTITY(member.person)')
->join('member.household', 'household');
$qb = $this->entityManager->createQueryBuilder();
$qb
->from(Household::class, 'household')
->join('household.members', 'hmember')
->join('hmember.person', 'person')
->join('person.accompanyingPeriodParticipations', 'acppart')
->join('acppart.accompanyingPeriod', 'acp')
->andWhere('acppart.startDate != acppart.endDate OR acppart.endDate IS NULL')
->andWhere(
$qb->expr()->exists(
'SELECT 1 FROM ' . AccompanyingPeriodParticipation::class . ' acl_count_part
JOIN ' . PersonCenterHistory::class . ' acl_count_person_history WITH IDENTITY(acl_count_person_history.person) = IDENTITY(acl_count_part.person)
WHERE acl_count_part.accompanyingPeriod = acp.id AND acl_count_person_history.center IN (:authorized_centers)
'SELECT 1 FROM ' . PersonCenterHistory::class . ' acl_count_person_history WHERE acl_count_person_history.person = person
AND acl_count_person_history.center IN (:authorized_centers)
'
)
)
->setParameter('authorized_centers', $centers);
->andWhere('hmember.startDate <= :count_household_at_date AND (hmember.endDate IS NULL OR hmember.endDate > :count_household_at_date)')
->setParameter('authorized_centers', $centers)
->setParameter('count_household_at_date', $this->rollingDateConverter->convert($data['calc_date']));
$qb->select('COUNT(DISTINCT household.id) AS export_result');
$qb
->select('COUNT(DISTINCT household.id) AS household_export_result')
->addSelect('COUNT(DISTINCT acp.id) AS acp_export_result');
return $qb;
}
@ -129,6 +154,7 @@ class CountHousehold implements ExportInterface, GroupedExportInterface
return [
Declarations::HOUSEHOLD_TYPE,
Declarations::ACP_TYPE,
Declarations::PERSON_TYPE,
];
}
}

View File

@ -100,7 +100,7 @@ class CountPerson implements ExportInterface, GroupedExportInterface
$qb = $this->personRepository->createQueryBuilder('person');
$qb->select('COUNT(person.id) AS export_result')
$qb->select('COUNT(DISTINCT person.id) AS export_result')
->andWhere(
$qb->expr()->exists(
'SELECT 1 FROM ' . PersonCenterHistory::class . ' pch WHERE pch.person = person.id AND pch.center IN (:authorized_centers)'

View File

@ -15,12 +15,10 @@ use Chill\MainBundle\Export\ExportInterface;
use Chill\MainBundle\Export\FormatterInterface;
use Chill\MainBundle\Export\GroupedExportInterface;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Entity\AccompanyingPeriodParticipation;
use Chill\PersonBundle\Entity\Person\PersonCenterHistory;
use Chill\PersonBundle\Export\Declarations;
use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\Query;
use Doctrine\ORM\QueryBuilder;
use LogicException;
@ -28,12 +26,12 @@ use Symfony\Component\Form\FormBuilderInterface;
class CountSocialWorkActions implements ExportInterface, GroupedExportInterface
{
protected EntityRepository $repository;
protected EntityManagerInterface $em;
public function __construct(
EntityManagerInterface $em
) {
$this->repository = $em->getRepository(AccompanyingPeriod::class);
$this->em = $em;
}
public function buildForm(FormBuilderInterface $builder): void
@ -96,15 +94,18 @@ class CountSocialWorkActions implements ExportInterface, GroupedExportInterface
return $el['center'];
}, $acl);
$qb = $this->repository->createQueryBuilder('acp')
->join('acp.works', 'acpw');
$qb = $this->em->createQueryBuilder();
$qb
->from(AccompanyingPeriod\AccompanyingPeriodWork::class, 'acpw')
->join('acpw.accompanyingPeriod', 'acp')
->join('acp.participations', 'acppart')
->join('acppart.person', 'person')
->andWhere('acppart.startDate != acppart.endDate OR acppart.endDate IS NULL')
->andWhere(
$qb->expr()->exists(
'SELECT 1 FROM ' . AccompanyingPeriodParticipation::class . ' acl_count_part
JOIN ' . PersonCenterHistory::class . ' acl_count_person_history WITH IDENTITY(acl_count_person_history.person) = IDENTITY(acl_count_part.person)
WHERE acl_count_part.accompanyingPeriod = acp.id AND acl_count_person_history.center IN (:authorized_centers)
'SELECT 1 FROM ' . PersonCenterHistory::class . ' acl_count_person_history WHERE acl_count_person_history.person = person
AND acl_count_person_history.center IN (:authorized_centers)
'
)
)
@ -125,6 +126,7 @@ class CountSocialWorkActions implements ExportInterface, GroupedExportInterface
return [
Declarations::SOCIAL_WORK_ACTION_TYPE,
Declarations::ACP_TYPE,
Declarations::PERSON_TYPE,
];
}
}

View File

@ -16,7 +16,6 @@ use Chill\MainBundle\Export\FormatterInterface;
use Chill\MainBundle\Export\GroupedExportInterface;
use Chill\MainBundle\Form\Type\ChillDateType;
use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Entity\AccompanyingPeriodParticipation;
use Chill\PersonBundle\Entity\Person\PersonCenterHistory;
use Chill\PersonBundle\Export\Declarations;
use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter;
@ -53,7 +52,7 @@ class StatAccompanyingCourseDuration implements ExportInterface, GroupedExportIn
public function getDescription(): string
{
return 'Create an average of accompanying courses duration according to various filters';
return 'Create an average of accompanying courses duration of each person participation to accompanying course, according to filters on persons, accompanying course';
}
public function getGroup(): string
@ -63,21 +62,37 @@ class StatAccompanyingCourseDuration implements ExportInterface, GroupedExportIn
public function getLabels($key, array $values, $data)
{
if ('export_result' !== $key) {
throw new LogicException("the key {$key} is not used by this export");
}
return static function ($value) use ($key) {
if ('_header' === $value) {
switch ($key) {
case 'avg_export_result':
return 'export.export.acp_stats.avg_duration';
$labels = array_combine($values, $values);
$labels['_header'] = $this->getTitle();
case 'count_acppart_export_result':
return 'export.export.acp_stats.count_participations';
return static function ($value) use ($labels) {
return $labels[$value];
case 'count_acp_export_result':
return 'export.export.acp_stats.count_acps';
case 'count_pers_export_result':
return 'export.export.acp_stats.count_persons';
default:
throw new LogicException('key not supported: ' . $key);
}
}
if (null === $value) {
return '';
}
return $value;
};
}
public function getQueryKeys($data): array
{
return ['export_result'];
return ['avg_export_result', 'count_acp_export_result', 'count_acppart_export_result', 'count_pers_export_result'];
}
public function getResult($query, $data)
@ -87,7 +102,7 @@ class StatAccompanyingCourseDuration implements ExportInterface, GroupedExportIn
public function getTitle(): string
{
return 'Accompanying courses duration';
return 'Accompanying courses participation duration and number of participations';
}
public function getType(): string
@ -104,22 +119,28 @@ class StatAccompanyingCourseDuration implements ExportInterface, GroupedExportIn
$qb = $this->repository->createQueryBuilder('acp');
$qb
->select('AVG(
LEAST(acppart.endDate, COALESCE(acp.closingDate, :force_closingDate))
- GREATEST(acppart.startDate, COALESCE(acp.openingDate, :force_closingDate))
) AS avg_export_result')
->addSelect('COUNT(DISTINCT acppart.id) AS count_acppart_export_result')
->addSelect('COUNT(DISTINCT person.id) AS count_pers_export_result')
->addSelect('COUNT(DISTINCT acp.id) AS count_acp_export_result')
->setParameter('force_closingDate', $data['closingdate'])
->leftJoin('acp.participations', 'acppart')
->leftJoin('acppart.person', 'person')
->andWhere('acp.step != :count_acp_step')
->andWhere('acppart.startDate != acppart.endDate OR acppart.endDate IS NULL')
->andWhere(
$qb->expr()->exists(
'SELECT 1 FROM ' . AccompanyingPeriodParticipation::class . ' acl_count_part
JOIN ' . PersonCenterHistory::class . ' acl_count_person_history WITH IDENTITY(acl_count_person_history.person) = IDENTITY(acl_count_part.person)
WHERE acl_count_part.accompanyingPeriod = acp.id AND acl_count_person_history.center IN (:authorized_centers)
'SELECT 1 FROM ' . PersonCenterHistory::class . ' acl_count_person_history WHERE acl_count_person_history.person = person
AND acl_count_person_history.center IN (:authorized_centers)
'
)
)
->setParameter('count_acp_step', AccompanyingPeriod::STEP_DRAFT)
->setParameter('authorized_centers', $centers);
$qb
->select('AVG(
COALESCE(acp.closingDate, :force_closingDate) - acp.openingDate
) AS export_result')
->setParameter('force_closingDate', $data['closingdate']);
return $qb;
}
@ -132,6 +153,7 @@ class StatAccompanyingCourseDuration implements ExportInterface, GroupedExportIn
{
return [
Declarations::ACP_TYPE,
Declarations::PERSON_TYPE,
];
}
}

View File

@ -12,7 +12,6 @@ declare(strict_types=1);
namespace Chill\PersonBundle\Export\Filter\AccompanyingCourseFilters;
use Chill\MainBundle\Entity\Address;
use Chill\MainBundle\Entity\GeographicalUnit;
use Chill\MainBundle\Entity\GeographicalUnit\SimpleGeographicalUnitDTO;
use Chill\MainBundle\Export\FilterInterface;
use Chill\MainBundle\Form\Type\ChillDateType;
@ -23,7 +22,6 @@ use Chill\PersonBundle\Entity\AccompanyingPeriod;
use Chill\PersonBundle\Entity\Household\PersonHouseholdAddress;
use Chill\PersonBundle\Export\Declarations;
use DateTimeImmutable;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\FormBuilderInterface;
@ -33,8 +31,6 @@ use Symfony\Component\Form\FormBuilderInterface;
*/
class GeographicalUnitStatFilter implements FilterInterface
{
private EntityManagerInterface $em;
private GeographicalUnitLayerRepositoryInterface $geographicalUnitLayerRepository;
private GeographicalUnitRepositoryInterface $geographicalUnitRepository;
@ -42,12 +38,10 @@ class GeographicalUnitStatFilter implements FilterInterface
private TranslatableStringHelperInterface $translatableStringHelper;
public function __construct(
EntityManagerInterface $em,
GeographicalUnitRepositoryInterface $geographicalUnitRepository,
GeographicalUnitLayerRepositoryInterface $geographicalUnitLayerRepository,
TranslatableStringHelperInterface $translatableStringHelper
) {
$this->em = $em;
$this->geographicalUnitRepository = $geographicalUnitRepository;
$this->geographicalUnitLayerRepository = $geographicalUnitLayerRepository;
$this->translatableStringHelper = $translatableStringHelper;
@ -68,7 +62,7 @@ class GeographicalUnitStatFilter implements FilterInterface
WITH IDENTITY(acp_geog_filter_location_history.personLocation) = IDENTITY(acp_geog_filter_address_person_location.person)
LEFT JOIN ' . Address::class . ' acp_geog_filter_address
WITH COALESCE(IDENTITY(acp_geog_filter_address_person_location.address), IDENTITY(acp_geog_filter_location_history.addressLocation)) = acp_geog_filter_address.id
LEFT JOIN ' . GeographicalUnit::class . ' acp_geog_filter_units WITH ST_CONTAINS(acp_geog_filter_units.geom, acp_geog_filter_address.point) = TRUE
LEFT JOIN acp_geog_filter_address.geographicalUnits acp_geog_filter_units
WHERE
(acp_geog_filter_location_history.startDate <= :acp_geog_filter_date AND (
acp_geog_filter_location_history.endDate IS NULL OR acp_geog_filter_location_history.endDate < :acp_geog_filter_date
@ -120,7 +114,7 @@ class GeographicalUnitStatFilter implements FilterInterface
{
return ['Filtered by geographic unit: computed at %date%, only in %units%', [
'%date%' => $data['date_calc']->format('d-m-Y'),
'%units' => implode(
'%units%' => implode(
', ',
array_map(
function (SimpleGeographicalUnitDTO $item) {

View File

@ -1,78 +0,0 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\PersonBundle\Export\Filter\PersonFilters;
use Chill\MainBundle\Export\FilterInterface;
use Chill\MainBundle\Form\Type\ChillDateType;
use Chill\PersonBundle\Export\AbstractAccompanyingPeriodExportElement;
use Chill\PersonBundle\Export\Declarations;
use DateTime;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface;
class AccompanyingPeriodClosingFilter extends AbstractAccompanyingPeriodExportElement implements FilterInterface
{
public function addRole(): ?string
{
return null;
}
public function alterQuery(QueryBuilder $qb, $data)
{
$this->addJoinAccompanyingPeriod($qb);
$clause = $qb->expr()->andX(
$qb->expr()->lte('acp.closingDate', ':date_to'),
$qb->expr()->gte('acp.closingDate', ':date_from')
);
$qb->andWhere($clause);
$qb->setParameter('date_from', $data['date_from'], Types::DATE_MUTABLE);
$qb->setParameter('date_to', $data['date_to'], Types::DATE_MUTABLE);
}
public function applyOn(): string
{
return Declarations::PERSON_TYPE;
}
public function buildForm(FormBuilderInterface $builder)
{
$builder->add('date_from', ChillDateType::class, [
'label' => 'Having an accompanying period closed after this date',
'data' => new DateTime('-1 month'),
]);
$builder->add('date_to', ChillDateType::class, [
'label' => 'Having an accompanying period closed before this date',
'data' => new DateTime(),
]);
}
public function describeAction($data, $format = 'string')
{
return [
'Filtered by accompanying period: persons having an accompanying period'
. ' closed between the %date_from% and %date_to%',
[
'%date_from%' => $data['date_from']->format('d-m-Y'),
'%date_to%' => $data['date_to']->format('d-m-Y'),
],
];
}
public function getTitle(): string
{
return 'Filter by accompanying period: closed between two dates';
}
}

View File

@ -1,87 +0,0 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\PersonBundle\Export\Filter\PersonFilters;
use Chill\MainBundle\Export\FilterInterface;
use Chill\MainBundle\Form\Type\ChillDateType;
use Chill\PersonBundle\Export\AbstractAccompanyingPeriodExportElement;
use Chill\PersonBundle\Export\Declarations;
use DateTime;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface;
class AccompanyingPeriodFilter extends AbstractAccompanyingPeriodExportElement implements FilterInterface
{
public function addRole(): ?string
{
return null;
}
public function alterQuery(QueryBuilder $qb, $data)
{
$this->addJoinAccompanyingPeriod($qb);
$clause = $qb->expr()->andX();
$clause->add(
$qb->expr()->lte('acp.openingDate', ':date_to')
);
$clause->add(
$qb->expr()->orX(
$qb->expr()->gte('acp.closingDate', ':date_from'),
$qb->expr()->isNull('acp.closingDate')
)
);
$qb->andWhere($clause);
$qb->setParameter('date_from', $data['date_from'], Types::DATE_MUTABLE);
$qb->setParameter('date_to', $data['date_to'], Types::DATE_MUTABLE);
}
public function applyOn(): string
{
return Declarations::PERSON_TYPE;
}
public function buildForm(FormBuilderInterface $builder)
{
$builder->add('date_from', ChillDateType::class, [
'label' => 'Having an accompanying period opened after this date',
'data' => new DateTime('-1 month'),
]);
$builder->add('date_to', ChillDateType::class, [
'label' => 'Having an accompanying period ending before this date, or '
. 'still opened at this date',
'data' => new DateTime(),
]);
}
public function describeAction($data, $format = 'string')
{
return [
'Filtered by accompanying period: persons having an accompanying period'
. ' opened after the %date_from% and closed before the %date_to% (or still opened '
. 'at the %date_to%)',
[
'%date_from%' => $data['date_from']->format('d-m-Y'),
'%date_to%' => $data['date_to']->format('d-m-Y'),
],
];
}
public function getTitle(): string
{
return 'Filter by accompanying period: active period';
}
}

View File

@ -1,78 +0,0 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\PersonBundle\Export\Filter\PersonFilters;
use Chill\MainBundle\Export\FilterInterface;
use Chill\MainBundle\Form\Type\ChillDateType;
use Chill\PersonBundle\Export\AbstractAccompanyingPeriodExportElement;
use Chill\PersonBundle\Export\Declarations;
use DateTime;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface;
class AccompanyingPeriodOpeningFilter extends AbstractAccompanyingPeriodExportElement implements FilterInterface
{
public function addRole(): ?string
{
return null;
}
public function alterQuery(QueryBuilder $qb, $data)
{
$this->addJoinAccompanyingPeriod($qb);
$clause = $qb->expr()->andX(
$qb->expr()->lte('acp.openingDate', ':date_to'),
$qb->expr()->gte('acp.openingDate', ':date_from')
);
$qb->andWhere($clause);
$qb->setParameter('date_from', $data['date_from'], Types::DATE_MUTABLE);
$qb->setParameter('date_to', $data['date_to'], Types::DATE_MUTABLE);
}
public function applyOn(): string
{
return Declarations::PERSON_TYPE;
}
public function buildForm(FormBuilderInterface $builder)
{
$builder->add('date_from', ChillDateType::class, [
'label' => 'Having an accompanying period opened after this date',
'data' => new DateTime('-1 month'),
]);
$builder->add('date_to', ChillDateType::class, [
'label' => 'Having an accompanying period opened before this date',
'data' => new DateTime(),
]);
}
public function describeAction($data, $format = 'string')
{
return [
'Filtered by accompanying period: persons having an accompanying period'
. ' opened between the %date_from% and %date_to%',
[
'%date_from%' => $data['date_from']->format('d-m-Y'),
'%date_to%' => $data['date_to']->format('d-m-Y'),
],
];
}
public function getTitle(): string
{
return 'Filter by accompanying period: starting between two dates';
}
}

View File

@ -0,0 +1,110 @@
<?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\PersonFilters;
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\MainBundle\Templating\TranslatableStringHelperInterface;
use Chill\PersonBundle\Entity\Household\HouseholdComposition;
use Chill\PersonBundle\Entity\Household\HouseholdCompositionType;
use Chill\PersonBundle\Entity\Household\HouseholdMember;
use Chill\PersonBundle\Export\Declarations;
use Chill\PersonBundle\Repository\Household\HouseholdCompositionTypeRepositoryInterface;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\QueryBuilder;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\FormBuilderInterface;
class ByHouseholdCompositionFilter implements FilterInterface
{
private HouseholdCompositionTypeRepositoryInterface $householdCompositionTypeRepository;
private RollingDateConverterInterface $rollingDateConverter;
private TranslatableStringHelperInterface $translatableStringHelper;
public function __construct(
HouseholdCompositionTypeRepositoryInterface $householdCompositionTypeRepository,
RollingDateConverterInterface $rollingDateConverter,
TranslatableStringHelperInterface $translatableStringHelper
) {
$this->householdCompositionTypeRepository = $householdCompositionTypeRepository;
$this->rollingDateConverter = $rollingDateConverter;
$this->translatableStringHelper = $translatableStringHelper;
}
public function addRole(): ?string
{
return null;
}
public function alterQuery(QueryBuilder $qb, $data)
{
$p = 'person_by_household_compo_filter';
$qb
->andWhere(
$qb->expr()->exists(
'SELECT 1 FROM ' . HouseholdMember::class . " {$p}_hm " .
'JOIN ' . HouseholdComposition::class . " {$p}_compo WITH {$p}_hm.household = {$p}_compo.household " .
"WHERE {$p}_hm.person = person AND {$p}_hm.shareHousehold = 'TRUE' " .
"AND ({$p}_hm.startDate <= :{$p}_date AND ({$p}_hm.endDate IS NULL OR {$p}_hm.endDate > :{$p}_date)) " .
"AND ({$p}_compo.startDate <= :{$p}_date AND ({$p}_compo.endDate IS NULL OR {$p}_compo.endDate > :{$p}_date)) " .
"AND {$p}_compo.householdCompositionType IN (:{$p}_accepted)"
)
)
->setParameter("{$p}_accepted", $data['compositions'])
->setParameter("{$p}_date", $this->rollingDateConverter->convert($data['calc_date']), Types::DATE_IMMUTABLE);
}
public function applyOn()
{
return Declarations::PERSON_TYPE;
}
public function buildForm(FormBuilderInterface $builder)
{
$builder
->add('compositions', EntityType::class, [
'class' => HouseholdCompositionType::class,
'choices' => $this->householdCompositionTypeRepository->findAllActive(),
'choice_label' => fn (HouseholdCompositionType $compositionType) => $this->translatableStringHelper->localize($compositionType->getLabel()),
'label' => 'export.filter.person.by_composition.Accepted compositions',
'multiple' => true,
'expanded' => true,
])
->add('calc_date', PickRollingDateType::class, [
'label' => 'export.filter.person.by_composition.Date calc',
'data' => new RollingDate(RollingDate::T_TODAY),
]);
}
public function describeAction($data, $format = 'string')
{
$compos = array_map(
fn (HouseholdCompositionType $compositionType) => $this->translatableStringHelper->localize($compositionType->getLabel()),
$data['compositions']->toArray()
);
return ['export.filter.person.by_composition.Filtered by composition at %date%: only %compositions%', [
'%compositions%' => implode(', ', $compos),
'%date%' => $this->rollingDateConverter->convert($data['calc_date'])->format('d-m-Y'),
]];
}
public function getTitle()
{
return 'export.filter.person.by_composition.Filter by household composition';
}
}

View File

@ -11,7 +11,6 @@ declare(strict_types=1);
namespace Chill\PersonBundle\Export\Filter\PersonFilters;
use Chill\MainBundle\Entity\GeographicalUnit;
use Chill\MainBundle\Entity\GeographicalUnit\SimpleGeographicalUnitDTO;
use Chill\MainBundle\Form\Type\ChillDateType;
use Chill\MainBundle\Repository\GeographicalUnitLayerRepositoryInterface;
@ -53,8 +52,7 @@ class GeographicalUnitFilter implements \Chill\MainBundle\Export\FilterInterface
'SELECT 1
FROM ' . PersonHouseholdAddress::class . ' person_filter_geog_person_household_address
JOIN person_filter_geog_person_household_address.address person_filter_geog_address
JOIN ' . GeographicalUnit::class . ' person_filter_geog_unit
WITH ST_CONTAINS(person_filter_geog_unit.geom, person_filter_geog_address.point) = TRUE
JOIN person_filter_geog_address.geographicalUnits person_filter_geog_unit
WHERE
person_filter_geog_person_household_address.validFrom <= :person_filter_geog_date
AND

View File

@ -0,0 +1,84 @@
<?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\PersonFilters;
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\Entity\Household\HouseholdComposition;
use Chill\PersonBundle\Entity\Household\HouseholdMember;
use Chill\PersonBundle\Export\Declarations;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface;
class WithoutHouseholdComposition implements FilterInterface
{
private RollingDateConverterInterface $rollingDateConverter;
public function __construct(
RollingDateConverterInterface $rollingDateConverter
) {
$this->rollingDateConverter = $rollingDateConverter;
}
public function addRole(): ?string
{
return null;
}
public function alterQuery(QueryBuilder $qb, $data)
{
$p = 'person_by_household_no_compo_filter';
$qb
->andWhere(
$qb->expr()->not(
$qb->expr()->exists(
'SELECT 1 FROM ' . HouseholdMember::class . " {$p}_hm " .
'JOIN ' . HouseholdComposition::class . " {$p}_compo WITH {$p}_hm.household = {$p}_compo.household " .
"WHERE {$p}_hm.person = person AND {$p}_hm.shareHousehold = 'TRUE' " .
"AND ({$p}_hm.startDate <= :{$p}_date AND ({$p}_hm.endDate IS NULL OR {$p}_hm.endDate > :{$p}_date)) " .
"AND ({$p}_compo.startDate <= :{$p}_date AND ({$p}_compo.endDate IS NULL OR {$p}_compo.endDate > :{$p}_date)) "
)
)
)
->setParameter("{$p}_date", $this->rollingDateConverter->convert($data['calc_date']), Types::DATE_IMMUTABLE);
}
public function applyOn()
{
return Declarations::PERSON_TYPE;
}
public function buildForm(FormBuilderInterface $builder)
{
$builder
->add('calc_date', PickRollingDateType::class, [
'label' => 'export.filter.person.by_no_composition.Date calc',
'data' => new RollingDate(RollingDate::T_TODAY),
]);
}
public function describeAction($data, $format = 'string')
{
return ['export.filter.person.by_no_composition.Persons filtered by no composition at %date%', [
'%date%' => $this->rollingDateConverter->convert($data['calc_date'])->format('d-m-Y'),
]];
}
public function getTitle()
{
return 'export.filter.person.by_no_composition.Filter persons without household composition';
}
}

View File

@ -1,90 +0,0 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\PersonBundle\Tests\Export\Filter\PersonFilters;
use Chill\MainBundle\Test\Export\AbstractFilterTest;
use Chill\PersonBundle\Export\Filter\PersonFilters\AccompanyingPeriodClosingFilter;
use DateTime;
use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;
/**
* @internal
* @coversNothing
*/
final class AccompanyingPeriodClosingFilterTest extends AbstractFilterTest
{
private AccompanyingPeriodClosingFilter $filter;
protected function setUp(): void
{
self::bootKernel();
try {
$this->filter = self::$container->get('chill.person.export.filter_accompanying_period_closing');
} catch (ServiceNotFoundException $e) {
$this->markTestSkipped('The current configuration does not use accompanying_periods');
}
}
public function getFilter()
{
return $this->filter;
}
public function getFormData(): array
{
return [
[
'date_from' => DateTime::createFromFormat('Y-m-d', '2000-01-01'),
'date_to' => DateTime::createFromFormat('Y-m-d', '2010-01-01'),
],
];
}
public function getQueryBuilders(): array
{
if (null === self::$kernel) {
self::bootKernel();
}
$em = self::$container
->get('doctrine.orm.entity_manager');
return [
$em->createQueryBuilder()
->select('person.firstName')
->from('ChillPersonBundle:Person', 'person'),
$em->createQueryBuilder()
->select('person.firstName')
->from('ChillPersonBundle:Person', 'person')
// add a dummy where clause
->where('person.firstname IS NOT NULL'),
$em->createQueryBuilder()
->select('count(IDENTITY(p))')
->from('ChillPersonBundle:Person', 'person')
// add a dummy where clause
->where('person.firstname IS NOT NULL'),
$em->createQueryBuilder()
->select('count(IDENTITY(p))')
->from('ChillPersonBundle:Person', 'person')
->join('person.accompanyingPeriods', 'accompanying_period')
// add a dummy where clause
->where('person.firstname IS NOT NULL'),
$em->createQueryBuilder()
->select('activity.date AS date')
->select('activity.attendee as attendee')
->from('ChillActivityBundle:Activity', 'activity')
->join('activity.person', 'person')
->join('person.center', 'center'),
];
}
}

View File

@ -1,90 +0,0 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\PersonBundle\Tests\Export\Filter\PersonFilters;
use Chill\MainBundle\Test\Export\AbstractFilterTest;
use Chill\PersonBundle\Export\Filter\PersonFilters\AccompanyingPeriodFilter;
use DateTime;
use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;
/**
* @internal
* @coversNothing
*/
final class AccompanyingPeriodFilterTest extends AbstractFilterTest
{
private AccompanyingPeriodFilter $filter;
protected function setUp(): void
{
self::bootKernel();
try {
$this->filter = self::$container->get('chill.person.export.filter_accompanying_period');
} catch (ServiceNotFoundException $e) {
$this->markTestSkipped('The current configuration does not use accompanying_periods');
}
}
public function getFilter()
{
return $this->filter;
}
public function getFormData()
{
return [
[
'date_from' => DateTime::createFromFormat('Y-m-d', '2000-01-01'),
'date_to' => DateTime::createFromFormat('Y-m-d', '2010-01-01'),
],
];
}
public function getQueryBuilders()
{
if (null === self::$kernel) {
self::bootKernel();
}
$em = self::$container
->get('doctrine.orm.entity_manager');
return [
$em->createQueryBuilder()
->select('person.firstName')
->from('ChillPersonBundle:Person', 'person'),
$em->createQueryBuilder()
->select('person.firstName')
->from('ChillPersonBundle:Person', 'person')
// add a dummy where clause
->where('person.firstname IS NOT NULL'),
$em->createQueryBuilder()
->select('count(IDENTITY(p))')
->from('ChillPersonBundle:Person', 'person')
// add a dummy where clause
->where('person.firstname IS NOT NULL'),
$em->createQueryBuilder()
->select('count(IDENTITY(p))')
->from('ChillPersonBundle:Person', 'person')
->join('person.accompanyingPeriods', 'accompanying_period')
// add a dummy where clause
->where('person.firstname IS NOT NULL'),
$em->createQueryBuilder()
->select('activity.date AS date')
->select('activity.attendee as attendee')
->from('ChillActivityBundle:Activity', 'activity')
->join('activity.person', 'person')
->join('person.center', 'center'),
];
}
}

View File

@ -1,90 +0,0 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\PersonBundle\Tests\Export\Filter\PersonFilters;
use Chill\MainBundle\Test\Export\AbstractFilterTest;
use Chill\PersonBundle\Export\Filter\PersonFilters\AccompanyingPeriodOpeningFilter;
use DateTime;
use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;
/**
* @internal
* @coversNothing
*/
final class AccompanyingPeriodOpeningFilterTest extends AbstractFilterTest
{
private AccompanyingPeriodOpeningFilter $filter;
protected function setUp(): void
{
self::bootKernel();
try {
$this->filter = self::$container->get('chill.person.export.filter_accompanying_period_opening');
} catch (ServiceNotFoundException $e) {
$this->markTestSkipped('The current configuration does not use accompanying_periods');
}
}
public function getFilter()
{
return $this->filter;
}
public function getFormData()
{
return [
[
'date_from' => DateTime::createFromFormat('Y-m-d', '2000-01-01'),
'date_to' => DateTime::createFromFormat('Y-m-d', '2010-01-01'),
],
];
}
public function getQueryBuilders()
{
if (null === self::$kernel) {
self::bootKernel();
}
$em = self::$container
->get('doctrine.orm.entity_manager');
return [
$em->createQueryBuilder()
->select('person.firstName')
->from('ChillPersonBundle:Person', 'person'),
$em->createQueryBuilder()
->select('person.firstName')
->from('ChillPersonBundle:Person', 'person')
// add a dummy where clause
->where('person.firstname IS NOT NULL'),
$em->createQueryBuilder()
->select('count(IDENTITY(p))')
->from('ChillPersonBundle:Person', 'person')
// add a dummy where clause
->where('person.firstname IS NOT NULL'),
$em->createQueryBuilder()
->select('count(IDENTITY(p))')
->from('ChillPersonBundle:Person', 'person')
->join('person.accompanyingPeriods', 'accompanying_period')
// add a dummy where clause
->where('person.firstname IS NOT NULL'),
$em->createQueryBuilder()
->select('activity.date AS date')
->select('activity.attendee as attendee')
->from('ChillActivityBundle:Activity', 'activity')
->join('activity.person', 'person')
->join('person.center', 'center'),
];
}
}

View File

@ -214,10 +214,6 @@ services:
tags:
- { name: chill.export_aggregator, alias: accompanyingcourse_ref_scope_aggregator }
Chill\PersonBundle\Export\Aggregator\AccompanyingCourseAggregators\ByHouseholdCompositionAggregator:
tags:
- { name: chill.export_aggregator, alias: accompanyingcourse_by_household_compo_aggregator }
Chill\PersonBundle\Export\Aggregator\AccompanyingCourseAggregators\ByActionNumberAggregator:
tags:
- { name: chill.export_aggregator, alias: accompanyingcourse_by_action_number_aggregator }

View File

@ -1,16 +0,0 @@
services:
chill.person.export.filter_accompanying_period:
class: Chill\PersonBundle\Export\Filter\PersonFilters\AccompanyingPeriodFilter
tags:
- { name: chill.export_filter, alias: person_accc_period_filter }
chill.person.export.filter_accompanying_period_opening:
class: Chill\PersonBundle\Export\Filter\PersonFilters\AccompanyingPeriodOpeningFilter
tags:
- { name: chill.export_filter, alias: person_acc_pe_op_filter }
chill.person.export.filter_accompanying_period_closing:
class: Chill\PersonBundle\Export\Filter\PersonFilters\AccompanyingPeriodClosingFilter
tags:
- { name: chill.export_filter, alias: person_acc_pe_cl_filter }

View File

@ -1,4 +1,7 @@
services:
_defaults:
autoconfigure: true
autowire: true
## Indicators
chill.person.export.count_person:
@ -107,6 +110,14 @@ services:
tags:
- { name: chill.export_filter, alias: person_geog_filter }
Chill\PersonBundle\Export\Filter\PersonFilters\ByHouseholdCompositionFilter:
tags:
- { name: chill.export_filter, alias: person_by_household_composition_filter }
Chill\PersonBundle\Export\Filter\PersonFilters\WithoutHouseholdComposition:
tags:
- { name: chill.export_filter, alias: person_without_household_composition_filter }
## Aggregators
chill.person.export.aggregator_nationality:
class: Chill\PersonBundle\Export\Aggregator\PersonAggregators\NationalityAggregator
@ -156,3 +167,7 @@ services:
tags:
- { name: chill.export_aggregator, alias: person_geog_aggregator }
Chill\PersonBundle\Export\Aggregator\PersonAggregators\ByHouseholdCompositionAggregator:
tags:
- { name: chill.export_aggregator, alias: person_household_compo_aggregator }

View File

@ -359,8 +359,8 @@ Count people participating in an accompanying course by various parameters.: Com
Exports of accompanying courses: Exports des parcours d'accompagnement
Count accompanying courses: Nombre de parcours
Count accompanying courses by various parameters: Compte le nombre de parcours en fonction de différents filtres.
Accompanying courses duration: Durée moyenne des parcours
Create an average of accompanying courses duration according to various filters: Moyenne de la durée des parcours en jours, selon différents filtres.
Accompanying courses participation duration and number of participations: Durée moyenne et nombre des participation des usagers aux parcours
Create an average of accompanying courses duration of each person participation to accompanying course, according to filters on persons, accompanying course: Crée un rapport qui comptabilise la moyenne de la durée de participation de chaque usager concerné aux parcours, avec différents filtres, notamment sur les usagers concernés.
Closingdate to apply: Date de fin à prendre en compte lorsque le parcours n'est pas clotûré
Exports of social work actions: Exports des actions d'accompagnement
@ -373,8 +373,6 @@ Count evaluations: Nombre d'évaluations
Count evaluation by various parameters.: Compte le nombre d'évaluations selon différents filtres.
Exports of households: Exports des ménages
Count households: Nombre de ménages
Count household by various parameters.: Compte le nombre de ménages impliqués dans un parcours selon différents filtres.
## persons filters
Filter by person gender: Filtrer les personnes par genre
@ -588,6 +586,8 @@ end period date: Date de fin de la période
Filter by current evaluations: Filtrer les évaluations en cours
"Filtered by current evaluations": "Filtré: uniquement les évaluations en cours"
'Filtered by geographic unit: computed at %date%, only in %units%': 'Filtré par unité géographique: adresse le %date%, seulement les unités %units%'
## social actions filters/aggr
Filter by treating agent scope: Filtrer les actions par service de l'agent traitant
"Filtered by treating agent scope: only %scopes%": "Filtré par service de l'agent traitant: uniquement %scopes%"
@ -989,7 +989,24 @@ notification:
Notify any: Notifier d'autres utilisateurs
export:
export:
acp_stats:
avg_duration: Moyenne de la durée de participation de chaque usager concerné
count_participations: Nombre de participations distinctes
count_persons: Nombre d'usagers concernés distincts
count_acps: Nombre de parcours distincts
nb_household_with_course:
Count households with accompanying course: Nombre de ménages impliqués dans un parcours
Count households: Nombre de ménages
Count accompanying periods: Nombre de parcours
Count household with accompanying course by various parameters.: Compte le nombre de ménages impliqués dans un parcours selon différents filtres.
Date of calculation of household members: Date à laquelle les membres du ménages sont comptabilisés
aggregator:
person:
by_household_composition:
Household composition: Composition du ménage
Group course by household composition: Grouper les personnes par composition familiale
Calc date: Date de calcul de la composition du ménage
course:
by_referrer:
Computation date for referrer: Date à laquelle le référent était actif
@ -1002,10 +1019,6 @@ export:
week: Durée du parcours en semaines
month: Durée du parcours en mois
Precision: Unité de la durée
by_household_composition:
Household composition: Composition du ménage
Group course by household composition: Grouper les parcours par composition familiale des ménages des usagers concernés
Calc date: Date de calcul de la composition du ménage
by_number_of_action:
Number of actions: Nombre d'actions
by_creator_job:
@ -1032,6 +1045,17 @@ export:
Max date: Date d'échéance
filter:
person:
by_composition:
Filter by household composition: Filtrer les personnes par composition du ménage
Accepted compositions: Composition de ménages
Date calc: Date de calcul
'Filtered by composition at %date%: only %compositions%': 'Filtré par composition du ménage, le %date%, seulement %compositions%'
by_no_composition:
Filter persons without household composition: Filtrer les personnes sans composition de ménage (ni ménage)
Persons filtered by no composition at %date%: Uniquement les personnes sans composition de ménage à la date du %date%
Date calc: Date de calcul
course:
by_user_scope:
Computation date for referrer: Date à laquelle le référent était actif