Merge remote-tracking branch 'origin/111_exports_suite' into testing

This commit is contained in:
Julien Fastré 2022-09-20 12:18:00 +02:00
commit 5af492b2db
51 changed files with 1058 additions and 444 deletions

View File

@ -11,10 +11,10 @@ AccompanyingPeriod::class,,,acp
,User::class,acp.user,acpuser
AccompanyingPeriodWork::class,,,acpw
,AccompanyingPeriodWorkEvaluation::class,acpw.accompanyingPeriodWorkEvaluations,workeval
,Goal::class,acpw.goals,goal
,User::class,acpw.referrers,acpwuser
,Result::class,acpw.results,acpwresult
,SocialAction::class,acpw.socialAction,acpwsocialaction
,Goal::class,acpw.goals,goal
,Result::class,acpw.results,result
AccompanyingPeriodParticipation::class,,,acppart
,Person::class,acppart.person,partperson
AccompanyingPeriodWorkEvaluation::class,,,workeval
@ -23,17 +23,17 @@ Goal::class,,,goal
,Result::class,goal.results,goalresult
Person::class,,,person
,Center::class,person.center,center
,HouseholdMember::class,partperson.householdParticipations,member
,HouseholdMember::class,partperson.householdParticipations,householdmember
,MaritalStatus::class,person.maritalStatus,personmarital
,VendeePerson::class,,vp
,VendeePersonMineur::class,,vpm
ResidentialAddress::class,,,resaddr
,Person::class,resaddr.person,resaddrperson
,Center::class,resaddrperson.center,resaddrcenter
,ThirdParty::class,resaddr.hostThirdParty,tparty
ThirdParty::class,,,tparty
,ThirdPartyCategory::class,tparty.categories,tpartycat
HouseholdMember::class,,,member
,Household::class,member.household,household
,Person::class,member.person,memberperson
HouseholdMember::class,,,householdmember
,Household::class,householdmember.household,household
,Person::class,householdmember.person,memberperson
,,memberperson.center,membercenter
Household::class,,,household
,HouseholdComposition::class,household.compositions,composition
@ -58,11 +58,6 @@ Calendar::class,,,cal
,Location::class,cal.location,calloc
,User::class,cal.user,caluser
VendeePerson::class,,,vp
,Person::class,vp.person,vpperson
,Center::class,vpperson.center,vpcenter
,SituationProfessionelle::class,vp.situationProfessionelle,vpprof
,StatutLogement::class,vp.statutLogement,vplog
,TempsDeTravail::class,vp.tempsDeTravail,vptt
VendeePersonMineur::class,,,vpm
,Person::class,vpm.person,vpmperson
,Center::class,vpmperson.center,vpmcenter

1 Entity Join Attribute Alias
11 User::class acp.user acpuser
12 AccompanyingPeriodWork::class acpw
13 AccompanyingPeriodWorkEvaluation::class acpw.accompanyingPeriodWorkEvaluations workeval
Goal::class acpw.goals goal
14 User::class acpw.referrers acpwuser
Result::class acpw.results acpwresult
15 SocialAction::class acpw.socialAction acpwsocialaction
16 Goal::class acpw.goals goal
17 Result::class acpw.results result
18 AccompanyingPeriodParticipation::class acppart
19 Person::class acppart.person partperson
20 AccompanyingPeriodWorkEvaluation::class workeval
23 Result::class goal.results goalresult
24 Person::class person
25 Center::class person.center center
26 HouseholdMember::class partperson.householdParticipations member householdmember
27 MaritalStatus::class person.maritalStatus personmarital
28 VendeePerson::class vp
29 VendeePersonMineur::class vpm
30 ResidentialAddress::class resaddr
Person::class resaddr.person resaddrperson
Center::class resaddrperson.center resaddrcenter
31 ThirdParty::class resaddr.hostThirdParty tparty
32 ThirdParty::class tparty
33 ThirdPartyCategory::class tparty.categories tpartycat
34 HouseholdMember::class member householdmember
35 Household::class member.household householdmember.household household
36 Person::class member.person householdmember.person memberperson
37 memberperson.center membercenter
38 Household::class household
39 HouseholdComposition::class household.compositions composition
58 Location::class cal.location calloc
59 User::class cal.user caluser
60 VendeePerson::class vp
Person::class vp.person vpperson
Center::class vpperson.center vpcenter
61 SituationProfessionelle::class vp.situationProfessionelle vpprof
62 StatutLogement::class vp.statutLogement vplog
63 TempsDeTravail::class vp.tempsDeTravail vptt
VendeePersonMineur::class vpm
Person::class vpm.person vpmperson
Center::class vpmperson.center vpmcenter

View File

@ -5,72 +5,67 @@ Add condition with distinct alias on each export join clauses (Indicators + Filt
These are alias conventions :
| Entity | Join | Attribute | Alias |
| :--- | :--- |:-------------------------------------------|:----------------------------------|
| AccompanyingPeriod::class | | | acp |
| | AccompanyingPeriodWork::class | acp.works | acpw |
| | AccompanyingPeriodParticipation::class | acp.participations | acppart |
| | Location::class | acp.administrativeLocation | acploc |
| | ClosingMotive::class | acp.closingMotive | acpmotive |
| | UserJob::class | acp.job | acpjob |
| | Origin::class | acp.origin | acporigin |
| | Scope::class | acp.scopes | acpscope |
| | SocialIssue::class | acp.socialIssues | acpsocialissue |
| | User::class | acp.user | acpuser |
| AccompanyingPeriodWork::class | | | acpw |
| | AccompanyingPeriodWorkEvaluation::class | acpw.accompanyingPeriodWorkEvaluations | workeval |
| | Goal::class | acpw.goals | goal |
| | User::class | acpw.referrers | acpwuser |
| | Result::class | acpw.results | acpwresult |
| | SocialAction::class | acpw.socialAction | acpwsocialaction |
| AccompanyingPeriodParticipation::class | | | acppart |
| | Person::class | acppart.person | partperson |
| AccompanyingPeriodWorkEvaluation::class | | | workeval |
| | Evaluation::class | workeval.evaluation | eval |
| Goal::class | | | goal |
| | Result::class | goal.results | goalresult |
| Person::class | | | person |
| | Center::class | person.center | center |
| | HouseholdMember::class | partperson.householdParticipations | member |
| | MaritalStatus::class | person.maritalStatus | personmarital |
| ResidentialAddress::class | | | resaddr |
| | Person::class | resaddr.person | resaddrperson |
| | Center::class | resaddrperson.center | resaddrcenter |
| | ThirdParty::class | resaddr.hostThirdParty | tparty |
| ThirdParty::class | | | tparty |
| | ThirdPartyCategory::class | tparty.categories | tpartycat |
| HouseholdMember::class | | | member |
| | Household::class | member.household | household |
| | Person::class | member.person | memberperson |
| | | memberperson.center | membercenter |
| Household::class | | | household |
| | HouseholdComposition::class | household.compositions | composition |
| Activity::class | | | activity |
| | Person::class | activity.person | actperson |
| | AccompanyingPeriod::class | activity.accompanyingPeriod | actacp |
| | Person::class | activity\_person\_having\_activity.person | person\_person\_having\_activity |
| | ActivityReason::class | activity\_person\_having\_activity.reasons | reasons\_person\_having\_activity |
| | ActivityType::class | activity.activityType | acttype |
| | Location::class | activity.location | actloc |
| | SocialAction::class | activity.socialActions | actsocialaction |
| | SocialIssue::class | activity.socialIssues | actsocialssue |
| | ThirdParty::class | activity.thirdParties | acttparty |
| | User::class | activity.user | actuser |
| | User::class | activity.users | actusers |
| | ActivityReason::class | activity.reasons | actreasons |
| | Center::class | actperson.center | actcenter |
| ActivityReason::class | | | actreasons |
| | ActivityReasonCategory::class | actreason.category | actreasoncat |
| Calendar::class | | | cal |
| | CancelReason::class | cal.cancelReason | calcancel |
| | Location::class | cal.location | calloc |
| | User::class | cal.user | caluser |
| VendeePerson::class | | | vp |
| | Person::class | vp.person | vpperson |
| | Center::class | vpperson.center | vpcenter |
| | SituationProfessionelle::class | vp.situationProfessionelle | vpprof |
| | StatutLogement::class | vp.statutLogement | vplog |
| | TempsDeTravail::class | vp.tempsDeTravail | vptt |
| VendeePersonMineur::class | | | vpm |
| | Person::class | vpm.person | vpmperson |
| | Center::class | vpmperson.center | vpmcenter |
| Entity | Join | Attribute | Alias |
|:----------------------------------------|:----------------------------------------|:-------------------------------------------|:----------------------------------|
| AccompanyingPeriod::class | | | acp |
| | AccompanyingPeriodWork::class | acp.works | acpw |
| | AccompanyingPeriodParticipation::class | acp.participations | acppart |
| | Location::class | acp.administrativeLocation | acploc |
| | ClosingMotive::class | acp.closingMotive | acpmotive |
| | UserJob::class | acp.job | acpjob |
| | Origin::class | acp.origin | acporigin |
| | Scope::class | acp.scopes | acpscope |
| | SocialIssue::class | acp.socialIssues | acpsocialissue |
| | User::class | acp.user | acpuser |
| AccompanyingPeriodWork::class | | | acpw |
| | AccompanyingPeriodWorkEvaluation::class | acpw.accompanyingPeriodWorkEvaluations | workeval |
| | User::class | acpw.referrers | acpwuser |
| | SocialAction::class | acpw.socialAction | acpwsocialaction |
| | Goal::class | acpw.goals | goal |
| | Result::class | acpw.results | result |
| AccompanyingPeriodParticipation::class | | | acppart |
| | Person::class | acppart.person | partperson |
| AccompanyingPeriodWorkEvaluation::class | | | workeval |
| | Evaluation::class | workeval.evaluation | eval |
| Goal::class | | | goal |
| | Result::class | goal.results | goalresult |
| Person::class | | | person |
| | Center::class | person.center | center |
| | HouseholdMember::class | partperson.householdParticipations | householdmember |
| | MaritalStatus::class | person.maritalStatus | personmarital |
| | VendeePerson::class | | vp |
| | VendeePersonMineur::class | | vpm |
| ResidentialAddress::class | | | resaddr |
| | ThirdParty::class | resaddr.hostThirdParty | tparty |
| ThirdParty::class | | | tparty |
| | ThirdPartyCategory::class | tparty.categories | tpartycat |
| HouseholdMember::class | | | householdmember |
| | Household::class | householdmember.household | household |
| | Person::class | householdmember.person | memberperson |
| | | memberperson.center | membercenter |
| Household::class | | | household |
| | HouseholdComposition::class | household.compositions | composition |
| Activity::class | | | activity |
| | Person::class | activity.person | actperson |
| | AccompanyingPeriod::class | activity.accompanyingPeriod | actacp |
| | Person::class | activity\_person\_having\_activity.person | person\_person\_having\_activity |
| | ActivityReason::class | activity\_person\_having\_activity.reasons | reasons\_person\_having\_activity |
| | ActivityType::class | activity.activityType | acttype |
| | Location::class | activity.location | actloc |
| | SocialAction::class | activity.socialActions | actsocialaction |
| | SocialIssue::class | activity.socialIssues | actsocialssue |
| | ThirdParty::class | activity.thirdParties | acttparty |
| | User::class | activity.user | actuser |
| | User::class | activity.users | actusers |
| | ActivityReason::class | activity.reasons | actreasons |
| | Center::class | actperson.center | actcenter |
| ActivityReason::class | | | actreasons |
| | ActivityReasonCategory::class | actreason.category | actreasoncat |
| Calendar::class | | | cal |
| | CancelReason::class | cal.cancelReason | calcancel |
| | Location::class | cal.location | calloc |
| | User::class | cal.user | caluser |
| VendeePerson::class | | | vp |
| | SituationProfessionelle::class | vp.situationProfessionelle | vpprof |
| | StatutLogement::class | vp.statutLogement | vplog |
| | TempsDeTravail::class | vp.tempsDeTravail | vptt |

View File

@ -101,7 +101,7 @@ class AvgActivityDuration implements ExportInterface, GroupedExportInterface
return [
Declarations::ACTIVITY,
Declarations::ACTIVITY_ACP,
//PersonDeclarations::ACP_TYPE,
PersonDeclarations::ACP_TYPE,
];
}
}

View File

@ -101,7 +101,7 @@ class AvgActivityVisitDuration implements ExportInterface, GroupedExportInterfac
return [
Declarations::ACTIVITY,
Declarations::ACTIVITY_ACP,
//PersonDeclarations::ACP_TYPE,
PersonDeclarations::ACP_TYPE,
];
}
}

View File

@ -105,7 +105,7 @@ class CountActivity implements ExportInterface, GroupedExportInterface
return [
Declarations::ACTIVITY,
Declarations::ACTIVITY_ACP,
//PersonDeclarations::ACP_TYPE,
PersonDeclarations::ACP_TYPE,
];
}
}

View File

@ -104,7 +104,7 @@ class SumActivityDuration implements ExportInterface, GroupedExportInterface
return [
Declarations::ACTIVITY,
Declarations::ACTIVITY_ACP,
//PersonDeclarations::ACP_TYPE,
PersonDeclarations::ACP_TYPE,
];
}
}

View File

@ -104,7 +104,7 @@ class SumActivityVisitDuration implements ExportInterface, GroupedExportInterfac
return [
Declarations::ACTIVITY,
Declarations::ACTIVITY_ACP,
//PersonDeclarations::ACP_TYPE,
PersonDeclarations::ACP_TYPE,
];
}
}

View File

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

View File

@ -9,13 +9,14 @@
declare(strict_types=1);
namespace Chill\PersonBundle\Export\Filter\AccompanyingCourseFilters;
namespace Chill\ActivityBundle\Export\Filter\ACPFilters;
use Chill\ActivityBundle\Entity\Activity;
use Chill\ActivityBundle\Entity\ActivityType;
use Chill\MainBundle\Export\FilterInterface;
use Chill\MainBundle\Templating\TranslatableStringHelper;
use Chill\PersonBundle\Export\Declarations;
use Doctrine\ORM\Query\Expr;
use Doctrine\ORM\Query\Expr\Andx;
use Doctrine\ORM\QueryBuilder;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
@ -40,14 +41,8 @@ class ActivityTypeFilter implements FilterInterface
public function alterQuery(QueryBuilder $qb, $data)
{
// One2many between activity and accompanyingperiod is not reversed !
// we replace indicator 'from' clause by 'act', and put 'acp' in a join
$qb->resetDQLPart('from');
$qb->from(Activity::class, 'activity');
if (!in_array('actacp', $qb->getAllAliases(), true)) {
$qb->join('activity.accompanyingPeriod', 'actacp');
if (!in_array('activity', $qb->getAllAliases(), true)) {
$qb->join(Activity::class, 'activity', Expr\Join::WITH, 'activity.accompanyingPeriod = acp');
}
if (!in_array('acttype', $qb->getAllAliases(), true)) {

View File

@ -197,7 +197,7 @@ class PersonHavingActivityBetweenDateFilter implements ExportElementValidatedInt
public function getTitle()
{
return 'Filtered by person having an activity in a period';
return 'Filter by person having an activity in a period';
}
public function validateForm($data, ExecutionContextInterface $context)

View File

@ -13,10 +13,10 @@ namespace Chill\ActivityBundle\Security\Authorization;
use Chill\MainBundle\Entity\Center;
use Chill\MainBundle\Security\Authorization\AbstractChillVoter;
use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
use Chill\MainBundle\Security\Authorization\VoterHelperFactoryInterface;
use Chill\MainBundle\Security\Authorization\VoterHelperInterface;
use Chill\MainBundle\Security\ProvideRoleHierarchyInterface;
use function in_array;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
class ActivityStatsVoter extends AbstractChillVoter implements ProvideRoleHierarchyInterface
{
@ -24,14 +24,14 @@ class ActivityStatsVoter extends AbstractChillVoter implements ProvideRoleHierar
public const STATS = 'CHILL_ACTIVITY_STATS';
/**
* @var AuthorizationHelper
*/
protected $helper;
protected VoterHelperInterface $helper;
public function __construct(AuthorizationHelper $helper)
public function __construct(VoterHelperFactoryInterface $voterHelperFactory)
{
$this->helper = $helper;
$this->helper = $voterHelperFactory
->generate(self::class)
->addCheckFor(Center::class, [self::STATS, self::LISTS])
->build();
}
public function getRoles(): array
@ -49,30 +49,14 @@ class ActivityStatsVoter extends AbstractChillVoter implements ProvideRoleHierar
return $this->getAttributes();
}
protected function getSupportedClasses()
protected function voteOnAttribute($attribute, $subject, TokenInterface $token)
{
return [Center::class];
}
protected function isGranted($attribute, $object, $user = null)
{
if (!$user instanceof \Symfony\Component\Security\Core\User\UserInterface) {
return false;
}
return $this->helper->userHasAccess($user, $object, $attribute);
return $this->helper->voteOnAttribute($attribute, $subject, $token);
}
protected function supports($attribute, $subject)
{
if (
$subject instanceof Center
&& in_array($attribute, $this->getAttributes(), true)
) {
return true;
}
return false;
return $this->helper->supports($attribute, $subject);
}
private function getAttributes()

View File

@ -67,6 +67,11 @@ services:
name: chill.export_filter
alias: 'activity_person_having_ac_bw_date_filter'
chill.person.export.filter_activitytype:
class: Chill\ActivityBundle\Export\Filter\ACPFilters\ActivityTypeFilter
tags:
- { name: chill.export_filter, alias: accompanyingcourse_activitytype_filter }
chill.activity.export.locationtype_filter:
class: Chill\ActivityBundle\Export\Filter\ACPFilters\LocationTypeFilter
tags:

View File

@ -23,6 +23,7 @@ use Symfony\Component\Form\Extension\Core\Type\FormType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\FormFactoryInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
@ -142,10 +143,8 @@ class ExportController extends AbstractController
/**
* Render the list of available exports.
*
* @return \Symfony\Component\HttpFoundation\Response
*/
public function indexAction(Request $request)
public function indexAction(): Response
{
$exportManager = $this->exportManager;

View File

@ -14,6 +14,7 @@ namespace Chill\MainBundle\Export;
use Chill\MainBundle\Form\Type\Export\ExportType;
use Chill\MainBundle\Form\Type\Export\PickCenterType;
use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
use Chill\MainBundle\Security\Authorization\AuthorizationHelperInterface;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\QueryBuilder;
use Generator;
@ -42,52 +43,38 @@ class ExportManager
/**
* The collected aggregators, injected by DI.
*
* @var AggregatorInterface[]
* @var array|AggregatorInterface[]
*/
private $aggregators = [];
private array $aggregators = [];
/**
* @var AuthorizationChecker
*/
private $authorizationChecker;
private AuthorizationCheckerInterface $authorizationChecker;
/**
* @var AuthorizationHelper
*/
private $authorizationHelper;
private AuthorizationHelperInterface $authorizationHelper;
/**
* @var EntityManagerInterface
*/
private $em;
private EntityManagerInterface $em;
/**
* Collected Exports, injected by DI.
*
* @var ExportInterface[]
* @var array|ExportInterface[]
*/
private $exports = [];
private array $exports = [];
/**
* The collected filters, injected by DI.
*
* @var FilterInterface[]
* @var array|FilterInterface[]
*/
private $filters = [];
private array $filters = [];
/**
* Collected Formatters, injected by DI.
*
* @var FormatterInterface[]
* @var array|FormatterInterface[]
*/
private $formatters = [];
private array $formatters = [];
/**
* a logger.
*
* @var LoggerInterface
*/
private $logger;
private LoggerInterface $logger;
/**
* @var \Symfony\Component\Security\Core\User\UserInterface
@ -98,7 +85,7 @@ class ExportManager
LoggerInterface $logger,
EntityManagerInterface $em,
AuthorizationCheckerInterface $authorizationChecker,
AuthorizationHelper $authorizationHelper,
AuthorizationHelperInterface $authorizationHelper,
TokenStorageInterface $tokenStorage
) {
$this->logger = $logger;
@ -547,19 +534,16 @@ class ExportManager
. 'an ExportInterface.');
}
if (null === $centers) {
$centers = $this->authorizationHelper->getReachableCenters(
if (null === $centers || [] === $centers) {
// we want to try if at least one center is reachable
return [] !== $this->authorizationHelper->getReachableCenters(
$this->user,
$role
);
}
if (count($centers) === 0) {
return false;
}
foreach ($centers as $center) {
if ($this->authorizationChecker->isGranted($role, $center) === false) {
if (false === $this->authorizationChecker->isGranted($role, $center)) {
//debugging
$this->logger->debug('user has no access to element', [
'method' => __METHOD__,
@ -568,10 +552,6 @@ class ExportManager
'role' => $role,
]);
///// Bypasse les autorisations qui empêche d'afficher les nouveaux exports
return true;
///// TODO supprimer le return true
return false;
}
}

View File

@ -230,7 +230,8 @@ class SpreadSheetFormatter implements FormatterInterface
$worksheet->fromArray(
$sortedResults,
null,
'A' . $line
'A' . $line,
true
);
return $line + count($sortedResults) + 1;

View File

@ -15,6 +15,7 @@ use Chill\MainBundle\Center\GroupingCenterInterface;
use Chill\MainBundle\Entity\Center;
use Chill\MainBundle\Export\ExportManager;
use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
use Chill\MainBundle\Security\Authorization\AuthorizationHelperInterface;
use Doctrine\ORM\EntityRepository;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
@ -24,6 +25,7 @@ use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\User\UserInterface;
use function array_intersect;
use function array_key_exists;
use function array_merge;
@ -38,30 +40,24 @@ class PickCenterType extends AbstractType
{
public const CENTERS_IDENTIFIERS = 'c';
/**
* @var AuthorizationHelper
*/
protected $authorizationHelper;
protected AuthorizationHelperInterface $authorizationHelper;
protected ExportManager $exportManager;
/**
* @var ExportManager
* @var array|GroupingCenterInterface[]
*/
protected $exportManager;
/**
* @var GroupingCenterInterface[]
*/
protected $groupingCenters = [];
protected array $groupingCenters = [];
/**
* @var \Symfony\Component\Security\Core\User\UserInterface
*/
protected $user;
protected UserInterface $user;
public function __construct(
TokenStorageInterface $tokenStorage,
ExportManager $exportManager,
AuthorizationHelper $authorizationHelper
AuthorizationHelperInterface $authorizationHelper
) {
$this->exportManager = $exportManager;
$this->user = $tokenStorage->getToken()->getUser();
@ -78,22 +74,12 @@ class PickCenterType extends AbstractType
$export = $this->exportManager->getExport($options['export_alias']);
$centers = $this->authorizationHelper->getReachableCenters(
$this->user,
(string) $export->requiredRole()
$export->requiredRole()
);
$builder->add(self::CENTERS_IDENTIFIERS, EntityType::class, [
'class' => Center::class,
'query_builder' => static function (EntityRepository $er) use ($centers) {
$qb = $er->createQueryBuilder('c');
$ids = array_map(
static function (Center $el) {
return $el->getId();
},
$centers
);
return $qb->where($qb->expr()->in('c.id', $ids));
},
'choices' => $centers,
'multiple' => true,
'expanded' => true,
'choice_label' => static function (Center $c) {

View File

@ -16,7 +16,7 @@ use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityRepository;
use Doctrine\Persistence\ObjectRepository;
final class CenterRepository implements ObjectRepository
final class CenterRepository implements CenterRepositoryInterface
{
private EntityRepository $repository;
@ -30,6 +30,11 @@ final class CenterRepository implements ObjectRepository
return $this->repository->find($id, $lockMode, $lockVersion);
}
public function findActive(): array
{
return $this->findAll();
}
/**
* @return Center[]
*/

View File

@ -0,0 +1,18 @@
<?php
namespace Chill\MainBundle\Repository;
use Chill\MainBundle\Entity\Center;
use Doctrine\Persistence\ObjectRepository;
interface CenterRepositoryInterface extends ObjectRepository
{
/**
* Return all active centers
*
* Note: this is a teaser: active will comes later on center entity
*
* @return Center[]
*/
public function findActive(): array;
}

View File

@ -517,3 +517,9 @@ div.popover {
div.v-toast {
z-index: 10000!important;
}
div.grouped {
padding: 1em;
border: 1px solid black;
margin-bottom: 2em;
}

View File

@ -0,0 +1,6 @@
<h6>
<a href="{{ path('chill_main_export_index') }}" title="{{ 'Back to the list'|trans }}">
<i class="fa fa-folder-open-o fa-fw"></i>
</a>
{{ export_group|trans }}
</h6>

View File

@ -36,10 +36,7 @@ window.addEventListener("DOMContentLoaded", function(e) {
{% block content %}
<div class="col-md-10">
<h6>
<i class="fa fa-folder-open-o fa-fw"></i>
{{ export_group|trans }}
</h6>
{{ include('@ChillMain/Export/_breadcrumb.html.twig') }}
<h1>{{ export.title|trans }}</h1>
<h2>{{ "Download export"|trans }}</h2>

View File

@ -22,15 +22,15 @@
{% block js %}
{{ encore_entry_script_tags('page_export') }}
{% if export_alias == 'count_social_work_actions' %}
{{ encore_entry_script_tags('vue_export_action_goal_result') }}
{% endif %}
{% endblock js %}
{% block content %}
<div class="col-md-10">
<h6>
<i class="fa fa-folder-open-o fa-fw"></i>
{{ export_group|trans }}
</h6>
{{ include('@ChillMain/Export/_breadcrumb.html.twig') }}
<h1>{{ export.title|trans }}</h1>

View File

@ -22,11 +22,8 @@
{% block content %}
<div class="col-md-10">
<h6>
<i class="fa fa-folder-open-o fa-fw"></i>
{{ export_group|trans }}
</h6>
{{ include('@ChillMain/Export/_breadcrumb.html.twig') }}
<h1>{{ export.title|trans }}</h1>

View File

@ -23,10 +23,7 @@
{% block content %}
<div class="col-md-10">
<h6>
<i class="fa fa-folder-open-o fa-fw"></i>
{{ export_group|trans }}
</h6>
{{ include('@ChillMain/Export/_breadcrumb.html.twig') }}
<h1>{{ export.title|trans }}</h1>
@ -36,19 +33,21 @@
<section class="formatter mb-4">
<h2>{{ 'Formatter'| trans }}</h2>
<div>
{% if form.children.formatter.children|length == 0 %}
<p>
<span class="chill-no-data-statement">{{ "No options availables. Your report is fully configured."|trans }}</span>
</p>
{{ form_widget(form.children.formatter) }}
{% else %}
{# we always have to render children, to mark as rendered #}
{% for input in form.children.formatter.children %}
{{ form_row(input) }}
{% endfor %}
<div class="container py-4">
{# we always have to render children, to mark as rendered #}
{% for input in form.children.formatter.children %}
<div class="row">
{{ form_row(input) }}
</div>
{% endfor %}
</div>
{% endif %}
</div>
</section>
<div class="mb-4">

View File

@ -19,24 +19,23 @@ class ChillExportVoter extends Voter
{
public const EXPORT = 'chill_export';
protected AuthorizationHelperInterface $authorizationHelper;
private VoterHelperInterface $helper;
public function __construct(AuthorizationHelperInterface $authorizationHelper)
public function __construct(VoterHelperFactoryInterface $voterHelperFactory)
{
$this->authorizationHelper = $authorizationHelper;
$this->helper = $voterHelperFactory
->generate(self::class)
->addCheckFor(null, [self::EXPORT])
->build();
}
protected function supports($attribute, $subject): bool
{
return self::EXPORT === $attribute;
return $this->helper->supports($attribute, $subject);
}
protected function voteOnAttribute($attribute, $subject, TokenInterface $token): bool
{
if (!$token->getUser() instanceof User) {
return false;
}
return [] !== $this->authorizationHelper->getReachableCenters($token->getUser(), $attribute);
return $this->helper->voteOnAttribute($attribute, $subject, $token);
}
}

View File

@ -18,6 +18,7 @@ namespace Chill\MainBundle\Test;
* and use tearDownTrait after usage.
*
* @codeCoverageIgnore
* @deprecated use @class{Prophecy\PhpUnit\ProphecyTrait} instead
*/
trait ProphecyTrait
{

View File

@ -89,12 +89,8 @@ services:
- { name: validator.constraint_validator, alias: 'role_scope_scope_presence' }
Chill\MainBundle\Export\ExportManager:
arguments:
- "@logger"
- "@doctrine.orm.entity_manager"
- "@security.authorization_checker"
- "@chill.main.security.authorization.helper"
- "@security.token_storage"
autoconfigure: true
autowire: true
Chill\MainBundle\Security\Resolver\CenterResolverDispatcherInterface: '@Chill\MainBundle\Security\Resolver\CenterResolverDispatcher'

View File

@ -81,12 +81,8 @@ services:
chill.main.form.pick_centers_type:
class: Chill\MainBundle\Form\Type\Export\PickCenterType
arguments:
- "@security.token_storage"
- '@Chill\MainBundle\Export\ExportManager'
- "@chill.main.security.authorization.helper"
tags:
- { name: form.type }
autowire: true
autoconfigure: true
chill.main.form.formatter_type:
class: Chill\MainBundle\Form\Type\Export\FormatterType

View File

@ -45,7 +45,7 @@ final class OriginAggregator implements AggregatorInterface
$qb->join('acp.origin', 'acporigin');
}
$qb->addSelect('o.id AS origin_aggregator');
$qb->addSelect('acporigin.id AS origin_aggregator');
$groupby = $qb->getDQLPart('groupBy');

View File

@ -16,8 +16,10 @@ use Chill\MainBundle\Export\ExportElementValidatedInterface;
use Chill\MainBundle\Form\Type\ChillDateType;
use Chill\MainBundle\Templating\TranslatableStringHelper;
use Chill\PersonBundle\Entity\Household\HouseholdMember;
use Chill\PersonBundle\Export\Declarations;
use Chill\PersonBundle\Repository\Household\PositionRepository;
use DateTime;
use Doctrine\ORM\Query\Expr;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
@ -31,8 +33,11 @@ final class HouseholdPositionAggregator implements AggregatorInterface, ExportEl
private TranslatorInterface $translator;
public function __construct(TranslatorInterface $translator, TranslatableStringHelper $translatableStringHelper, PositionRepository $positionRepository)
{
public function __construct(
TranslatorInterface $translator,
TranslatableStringHelper $translatableStringHelper,
PositionRepository $positionRepository
) {
$this->translator = $translator;
$this->positionRepository = $positionRepository;
$this->translatableStringHelper = $translatableStringHelper;
@ -45,28 +50,25 @@ final class HouseholdPositionAggregator implements AggregatorInterface, ExportEl
public function alterQuery(QueryBuilder $qb, $data)
{
$qb->resetDQLPart('from');
$qb->from(HouseholdMember::class, 'member');
if (!in_array('memberperson', $qb->getAllAliases(), true)) {
$qb->join('member.person', 'memberperson');
if (!in_array('householdmember', $qb->getAllAliases(), true)) {
$qb->join(HouseholdMember::class, 'householdmember', Expr\Join::WITH, 'householdmember.person = person');
}
if (!in_array('membercenter', $qb->getAllAliases(), true)) {
$qb->join('memberperson.center', 'membercenter');
if (!in_array('center', $qb->getAllAliases(), true)) {
$qb->join('person.center', 'center');
}
$qb->andWhere($qb->expr()->andX(
$qb->expr()->lte('member.startDate', ':date'),
$qb->expr()->lte('householdmember.startDate', ':date'),
$qb->expr()->orX(
$qb->expr()->isNull('member.endDate'),
$qb->expr()->gte('member.endDate', ':date')
$qb->expr()->isNull('householdmember.endDate'),
$qb->expr()->gte('householdmember.endDate', ':date')
)
));
$qb->setParameter('date', $data['date_position']);
$qb->addSelect('IDENTITY(member.position) AS household_position_aggregator');
$qb->addSelect('IDENTITY(householdmember.position) AS household_position_aggregator');
$groupBy = $qb->getDQLPart('groupBy');
@ -79,7 +81,7 @@ final class HouseholdPositionAggregator implements AggregatorInterface, ExportEl
public function applyOn()
{
return 'person';
return Declarations::PERSON_TYPE;
}
public function buildForm(FormBuilderInterface $builder)

View File

@ -29,6 +29,7 @@ final class ActionTypeAggregator implements AggregatorInterface
SocialActionRender $actionRender
) {
$this->socialActionRepository = $socialActionRepository;
$this->actionRender = $actionRender;
}
public function addRole(): ?string

View File

@ -41,7 +41,7 @@ final class GoalAggregator implements AggregatorInterface
$qb->join('acpw.goals', 'goal');
}
$qb->addSelect('goal.id as goal_aggregator');
$qb->addSelect('IDENTITY(goal.goal) as goal_aggregator');
$groupBy = $qb->getDQLPart('groupBy');
@ -52,7 +52,7 @@ final class GoalAggregator implements AggregatorInterface
}
}
public function applyOn()
public function applyOn(): string
{
return Declarations::SOCIAL_WORK_ACTION_TYPE;
}
@ -70,17 +70,19 @@ final class GoalAggregator implements AggregatorInterface
}
$g = $this->goalRepository->find($value);
return $this->translatableStringHelper->localize($g->getTitle());
return $this->translatableStringHelper->localize(
$g->getTitle()
);
};
}
public function getQueryKeys($data)
public function getQueryKeys($data): array
{
return ['goal_aggregator'];
}
public function getTitle()
public function getTitle(): string
{
return 'Group social work actions by goal';
}

View File

@ -0,0 +1,138 @@
<?php
namespace Chill\PersonBundle\Export\Aggregator\SocialWorkAggregators;
use Chill\MainBundle\Export\AggregatorInterface;
use Chill\MainBundle\Templating\TranslatableStringHelper;
use Chill\PersonBundle\Export\Declarations;
use Chill\PersonBundle\Repository\SocialWork\GoalRepository;
use Chill\PersonBundle\Repository\SocialWork\ResultRepository;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\FormBuilderInterface;
class GoalResultAggregator implements AggregatorInterface
{
private ResultRepository $resultRepository;
private GoalRepository $goalRepository;
private TranslatableStringHelper $translatableStringHelper;
public function __construct(
ResultRepository $resultRepository,
GoalRepository $goalRepository,
TranslatableStringHelper $translatableStringHelper
) {
$this->resultRepository = $resultRepository;
$this->goalRepository = $goalRepository;
$this->translatableStringHelper = $translatableStringHelper;
}
/**
* @inheritDoc
*/
public function getLabels($key, array $values, $data)
{
return function ($value) use ($key): string {
switch ($key) {
case 'goal_aggregator':
if ('_header' === $value) {
return 'Goal Type';
}
$g = $this->goalRepository->find($value);
return $this->translatableStringHelper->localize(
$g->getTitle()
);
case 'result_aggregator':
if ('_header' === $value) {
return 'Result Type';
}
$r = $this->resultRepository->find($value);
return $this->translatableStringHelper->localize(
$r->getTitle()
);
default:
throw new \LogicException();
}
};
}
/**
* @inheritDoc
*/
public function getQueryKeys($data): array
{
return [
'goal_aggregator',
'result_aggregator'
];
}
/**
* @inheritDoc
*/
public function buildForm(FormBuilderInterface $builder)
{
// no form
}
/**
* @inheritDoc
*/
public function getTitle(): string
{
return 'Group social work actions by goal and result';
}
/**
* @inheritDoc
*/
public function addRole(): ?string
{
return null;
}
/**
* @inheritDoc
*/
public function alterQuery(QueryBuilder $qb, $data)
{
if (!in_array('goal', $qb->getAllAliases(), true)) {
$qb->join('acpw.goals', 'goal');
}
if (!in_array('goalresult', $qb->getAllAliases(), true)) {
$qb->join('goal.results', 'goalresult');
}
$qb->addSelect('IDENTITY(goal.goal) as goal_aggregator');
$qb->addSelect('goalresult.id as result_aggregator');
$groupBy = $qb->getDQLPart('groupBy');
if (!empty($groupBy)) {
$qb->addGroupBy('goal_aggregator');
} else {
$qb->groupBy('goal_aggregator');
}
$qb->addGroupBy('result_aggregator');
}
/**
* @inheritDoc
*/
public function applyOn(): string
{
return Declarations::SOCIAL_WORK_ACTION_TYPE;
}
}

View File

@ -37,19 +37,11 @@ final class ResultAggregator implements AggregatorInterface
public function alterQuery(QueryBuilder $qb, $data)
{
if (!in_array('acpwresult', $qb->getAllAliases(), true)) {
$qb->join('acpw.results', 'acpwresult');
if (!in_array('result', $qb->getAllAliases(), true)) {
$qb->join('acpw.results', 'result');
}
if (!in_array('goal', $qb->getAllAliases(), true)) {
$qb->join('acpw.goals', 'goal');
}
if (!in_array('goalresult', $qb->getAllAliases(), true)) {
$qb->join('goal.results', 'goalresult');
}
$qb->addSelect('acpwresult.id, IDENTITY(goal.results) as result_aggregator');
$qb->addSelect('result.id as result_aggregator');
$groupBy = $qb->getDQLPart('groupBy');
@ -60,7 +52,7 @@ final class ResultAggregator implements AggregatorInterface
}
}
public function applyOn()
public function applyOn(): string
{
return Declarations::SOCIAL_WORK_ACTION_TYPE;
}
@ -77,18 +69,20 @@ final class ResultAggregator implements AggregatorInterface
return 'Result Type';
}
$g = $this->resultRepository->find($value);
$r = $this->resultRepository->find($value);
return $this->translatableStringHelper->localize($g->getTitle());
return $this->translatableStringHelper->localize(
$r->getTitle()
);
};
}
public function getQueryKeys($data)
public function getQueryKeys($data): array
{
return ['result_aggregator'];
}
public function getTitle()
public function getTitle(): string
{
return 'Group social work actions by result';
}

View File

@ -112,8 +112,8 @@ class CountEvaluation implements ExportInterface, GroupedExportInterface
{
return [
Declarations::EVAL_TYPE,
//Declarations::ACP_TYPE,
//Declarations::SOCIAL_WORK_ACTION_TYPE,
Declarations::SOCIAL_WORK_ACTION_TYPE,
Declarations::ACP_TYPE,
];
}
}

View File

@ -121,7 +121,7 @@ class CountHousehold implements ExportInterface, GroupedExportInterface
{
return [
Declarations::HOUSEHOLD_TYPE,
//Declarations::ACP_TYPE
Declarations::ACP_TYPE,
];
}
}

View File

@ -111,8 +111,8 @@ class CountPersonWithAccompanyingCourse implements ExportInterface, GroupedExpor
public function supportsModifiers(): array
{
return [
Declarations::ACP_TYPE,
Declarations::PERSON_TYPE,
Declarations::ACP_TYPE,
];
}
}

View File

@ -110,6 +110,7 @@ class CountSocialWorkActions implements ExportInterface, GroupedExportInterface
{
return [
Declarations::SOCIAL_WORK_ACTION_TYPE,
Declarations::ACP_TYPE,
];
}
}

View File

@ -108,6 +108,6 @@ class DeadOrAliveFilter implements FilterInterface
public function getTitle()
{
return 'Filtered by person\'s that are alive or have deceased at a certain date';
return "Filter by person's that are alive or have deceased at a certain date";
}
}

View File

@ -39,15 +39,12 @@ class ResidentialAddressAtThirdpartyFilter implements FilterInterface
public function alterQuery(QueryBuilder $qb, $data)
{
$qb->resetDQLPart('from');
$qb->from(ResidentialAddress::class, 'resaddr');
if (!in_array('resaddrperson', $qb->getAllAliases(), true)) {
$qb->join('resaddr.person', 'resaddrperson');
if (!in_array('resaddr', $qb->getAllAliases(), true)) {
$qb->join(ResidentialAddress::class, 'resaddr', Expr\Join::WITH, 'resaddr.person = person');
}
if (!in_array('resaddrcenter', $qb->getAllAliases(), true)) {
$qb->join('resaddrperson.center', 'resaddrcenter');
if (!in_array('center', $qb->getAllAliases(), true)) {
$qb->join('person.center', 'center');
}
if (!in_array('tparty', $qb->getAllAliases(), true)) {
@ -109,6 +106,6 @@ class ResidentialAddressAtThirdpartyFilter implements FilterInterface
public function getTitle()
{
return 'Filtered by person\'s who have a residential address located at a thirdparty of type';
return "Filter by person's who have a residential address located at a thirdparty of type";
}
}

View File

@ -14,6 +14,7 @@ namespace Chill\PersonBundle\Export\Filter\PersonFilters;
use Chill\MainBundle\Export\FilterInterface;
use Chill\PersonBundle\Entity\Person\ResidentialAddress;
use Chill\PersonBundle\Export\Declarations;
use Doctrine\ORM\Query\Expr;
use Doctrine\ORM\Query\Expr\Andx;
use Doctrine\ORM\QueryBuilder;
@ -26,15 +27,12 @@ class ResidentialAddressAtUserFilter implements FilterInterface
public function alterQuery(QueryBuilder $qb, $data)
{
$qb->resetDQLPart('from');
$qb->from(ResidentialAddress::class, 'resaddr');
if (!in_array('resaddrperson', $qb->getAllAliases(), true)) {
$qb->join('resaddr.person', 'resaddrperson');
if (!in_array('resaddr', $qb->getAllAliases(), true)) {
$qb->join(ResidentialAddress::class, 'resaddr', Expr\Join::WITH, 'resaddr.person = person');
}
if (!in_array('resaddrcenter', $qb->getAllAliases(), true)) {
$qb->join('resaddrperson.center', 'resaddrcenter');
if (!in_array('center', $qb->getAllAliases(), true)) {
$qb->join('person.center', 'center');
}
$where = $qb->getDQLPart('where');
@ -61,11 +59,11 @@ class ResidentialAddressAtUserFilter implements FilterInterface
public function describeAction($data, $format = 'string')
{
return ['Filtered by person\'s who have a residential address located at another user'];
return ["Filtered by person's who have a residential address located at another user"];
}
public function getTitle()
{
return 'Filtered by person\'s who have a residential address located at another user';
return "Filter by person's who have a residential address located at another user";
}
}

View File

@ -13,35 +13,119 @@ namespace Chill\PersonBundle\Export\Filter\SocialWorkFilters;
use Chill\MainBundle\Export\FilterInterface;
use Chill\MainBundle\Templating\TranslatableStringHelper;
use Chill\PersonBundle\Entity\SocialWork\Goal;
use Chill\PersonBundle\Entity\SocialWork\SocialAction;
use Chill\PersonBundle\Entity\SocialWork\Goal;
use Chill\PersonBundle\Entity\SocialWork\Result;
use Chill\PersonBundle\Export\Declarations;
use Chill\PersonBundle\Repository\SocialWork\SocialActionRepository;
use Chill\PersonBundle\Templating\Entity\SocialActionRender;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Query\Expr\Andx;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\CallbackTransformer;
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\Form\FormInterface;
use Chill\PersonBundle\Templating\Entity\SocialActionRender;
class SocialWorkTypeFilter implements FilterInterface
{
private SocialActionRender $socialActionRender;
private SocialActionRepository $socialActionRepository;
private TranslatableStringHelper $translatableStringHelper;
public function __construct(
private EntityManagerInterface $em;
public function __construct
(
SocialActionRender $socialActionRender,
TranslatableStringHelper $translatableStringHelper,
SocialActionRepository $socialActionRepository
) {
EntityManagerInterface $em
)
{
$this->socialActionRender = $socialActionRender;
$this->translatableStringHelper = $translatableStringHelper;
$this->socialActionRepository = $socialActionRepository;
$this->em = $em;
}
public function buildForm(FormBuilderInterface $builder)
{
$builder
->add('actionType', HiddenType::class)
->get('actionType')
->addModelTransformer(
$this->iterableToIdTransformer(SocialAction::class)
)
;
$builder
->add('goal', HiddenType::class)
->get('goal')
->addModelTransformer(
$this->iterableToIdTransformer(Goal::class)
)
;
$builder
->add('result', HiddenType::class)
->get('result')
->addModelTransformer(
$this->iterableToIdTransformer(Result::class)
)
;
}
private function iterableToIdTransformer(string $entity): CallbackTransformer
{
return new CallbackTransformer(
static function (?iterable $asIterable): string {
if (null === $asIterable) { return ''; }
$ids = [];
foreach ($asIterable as $value) {
$ids[] = $value->getId();
}
return implode(',', $ids);
},
function (?string $asString) use ($entity): array {
if (null === $asString) { return []; }
return array_map(
fn (string $id)
=> $this->em
->getRepository($entity)
->findOneBy(['id' => (int) $id]),
explode(',', $asString)
);
}
);
}
public function getTitle(): string
{
return 'Filter by type of action, goals and results';
}
public function describeAction($data, $format = 'string'): array
{
$actionTypes = [];
$goals = [];
$results = [];
foreach ($data['actionType'] as $at) {
$actionTypes[] = $this->translatableStringHelper->localize(
$at->getTitle()
);
}
foreach ($data['goal'] as $g) {
$goals[] = $this->translatableStringHelper->localize(
$g->getTitle()
);
}
foreach ($data['result'] as $r) {
$results[] = $this->translatableStringHelper->localize(
$r->getTitle()
);
}
return ['Filtered actions by type, goals and results: %selected%', [
'%selected%' => implode(', ', array_merge($actionTypes, $goals, $results))
]];
}
public function addRole(): ?string
@ -52,93 +136,48 @@ class SocialWorkTypeFilter implements FilterInterface
public function alterQuery(QueryBuilder $qb, $data)
{
$where = $qb->getDQLPart('where');
$clause = $qb->expr()->in('r', ':referrers');
if ($where instanceof Andx) {
$where->add($clause);
} else {
$where = $qb->expr()->andX($clause);
if (count($data['actionType']) > 0) {
$clause = $qb->expr()->in('acpw.socialAction', ':actionType');
if ($where instanceof Andx) {
$where->add($clause);
} else {
$where = $qb->expr()->andX($clause);
}
$qb->setParameter('actionType', $data['actionType']);
}
if (count($data['goal']) > 0) {
if (!in_array('goal', $qb->getAllAliases(), true)) {
$qb->join('acpw.goals', 'goal');
}
$where->add(
$qb->expr()->in('goal.id', ':goals')
);
$qb->setParameter('goals', $data['goal']);
}
if (count($data['result']) > 0) {
if (!in_array('result', $qb->getAllAliases(), true)) {
$qb->join('acpw.results', 'result');
}
$where->add(
$qb->expr()->in('result.id', ':results')
);
$qb->setParameter('results', $data['result']);
}
$qb->add('where', $where);
$qb->setParameter('referrers', $data['referrers']);
}
public function applyOn(): string
{
return Declarations::SOCIAL_WORK_ACTION_TYPE;
}
public function buildForm(FormBuilderInterface $builder)
{
$socialActions = $this->socialActionRepository->findAll();
$builder->add('actionType', ChoiceType::class, [
'choices' => $socialActions,
'choice_label' => function (SocialAction $sa) {
return $this->socialActionRender->renderString($sa, []);
},
'multiple' => true,
'expanded' => true,
]);
/*
$refreshGoals = function (FormInterface $form, SocialAction $actionType = null) {
$goals = null === $actionType ? [] : $actionType->getGoals();
$form->add('goal', ChoiceType::class, [
'placeholder' => '',
'choices' => $goals,
'choice_label' => function (Goal $g) {
return $this->translatableStringHelper->localize($g->getTitle());
},
]);
};
$builder->addEventListener(FormEvents::PRE_SUBMIT, function (FormEvent $event) use ($refreshGoals) {
$data = $event->getData();
dump($data);
$refreshGoals($event->getForm(), $data);
});
$builder->get('actionType')->addEventListener(
FormEvents::POST_SUBMIT,
function (FormEvent $event) use ($refreshGoals) {
$actionType = $event->getForm()->getData();
dump($actionType);
$refreshGoals($event->getForm()->getParent(), $actionType);
}
);
*/
}
public function describeAction($data, $format = 'string'): array
{
$actionTypes = [];
$objectives = [];
$results = [];
foreach ($data['actionType'] as $at) {
$actionTypes[] = $at->getTitle();
}
foreach ($data['objectives'] as $o) {
$objectives[] = $o->getTitle();
}
foreach ($data['results'] as $r) {
$results[] = $r->getTitle();
}
return ['Filtered by referrers: only %actionTypes%', [
'%actionTypes%' => implode(', ou ', $actionTypes),
]];
}
public function getTitle(): string
{
return 'Filter by type of action, objectives and results';
}
}

View File

@ -0,0 +1,381 @@
<template>
<teleport to="#export_filters_social_work_type_filter_form">
<fieldset class="mb-3" id="actionType">
<div class="row">
<legend class="col-sm-4 col-form-label">{{ $t('action.label')}}</legend>
<div class="col-sm-8">
<VueMultiselect
v-model="action"
:options="actions.options"
@select="selectAction"
@remove="unselectAction"
:multiple="true"
:close-on-select="false"
:placeholder="$t('action.placeholder')"
label="text"
track-by="id"
:searchable="true"
></VueMultiselect>
</div>
</div>
</fieldset>
<fieldset class="mb-3" id="goal">
<div class="row">
<legend class="col-sm-4 col-form-label">{{ $t('goal.label')}}</legend>
<div class="col-sm-8">
<VueMultiselect
v-model="goal"
:options="goals.options"
@select="selectGoal"
@remove="unselectGoal"
:multiple="true"
:close-on-select="false"
:placeholder="$t('goal.placeholder')"
label="title"
:custom-label="transTitle"
track-by="id"
:searchable="true"
></VueMultiselect>
</div>
</div>
</fieldset>
<fieldset class="mb-3" id="result">
<div class="row">
<legend class="col-sm-4 col-form-label">{{ $t('result.label')}}</legend>
<div class="col-sm-8">
<VueMultiselect
v-model="result"
:options="results.options"
@select="selectResult"
@remove="unselectResult"
:multiple="true"
:close-on-select="false"
:placeholder="$t('result.placeholder')"
label="title"
:custom-label="transTitle"
track-by="id"
:searchable="true"
></VueMultiselect>
</div>
</div>
</fieldset>
</teleport>
</template>
<script>
import VueMultiselect from 'vue-multiselect';
import { getSocialActions, getGoalByAction, getResultByAction, getResultByGoal } from './api';
export default {
name: "App",
components: {
VueMultiselect
},
i18n: {
messages: {
fr: {
action: {
label: 'Types d\'actions',
placeholder: 'Choisissez une ou plusieurs actions',
},
goal: {
label: 'Objectifs',
placeholder: 'Choisissez un ou plusieurs objectifs',
},
result: {
label: 'Résultats',
placeholder: 'Choisissez un ou plusieurs résultats',
}
}
}
},
data() {
return {
actions: {
options: [], // array with multiselect options
value: [], // array with selected values
hiddenField: document.getElementById(
'export_filters_social_work_type_filter_form_actionType'),
},
goals: {
options: [],
value: [],
hiddenField: document.getElementById(
'export_filters_social_work_type_filter_form_goal'),
},
results: {
options: [],
value: [],
hiddenField: document.getElementById(
'export_filters_social_work_type_filter_form_result'),
},
}
},
computed: {
action: {
get() {
return this.actions.value;
},
set(value) {
this.actions.value = value;
this.rebuildHiddenFieldValues('actions');
}
},
goal: {
get() {
return this.goals.value;
},
set(value) {
this.goals.value = value;
this.rebuildHiddenFieldValues('goals');
}
},
result: {
get() {
return this.results.value;
},
set(value) {
this.results.value = value;
this.rebuildHiddenFieldValues('results');
}
},
},
mounted() {
this.getSocialActionsList();
this.actions.hiddenField.value = '';
this.goals.hiddenField.value = '';
this.results.hiddenField.value = '';
//console.log(this.actions.hiddenField, this.goals.hiddenField, this.results.hiddenField);
},
methods: {
async getSocialActionsList() {
this.actions.options = await getSocialActions();
},
/**
* Select/unselect in Action Multiselect
* @param value
*/
selectAction(value) {
//console.log('----'); console.log('select action', value.id);
let children = this.getChildrensFromParent(value);
this.addSelectedElement('actions', children);
let parentAndChildren = [...[value], ...children];
parentAndChildren.forEach(elem => {
getGoalByAction(elem.id).then(response => new Promise((resolve, reject) => {
this.addElementInData('goals', response.results);
resolve();
})).catch;
getResultByAction(elem.id).then(response => new Promise((resolve, reject) => {
this.addElementInData('results', response.results);
resolve();
})).catch;
});
},
unselectAction(value) {
//console.log('----'); console.log('unselect action', value.id);
getGoalByAction(value.id).then(response => new Promise((resolve, reject) => {
[ this.goals.options, this.goals.value ] = this.removeElementInData('goals', response.results);
resolve();
})).catch;
getResultByAction(value.id).then(response => new Promise((resolve, reject) => {
[ this.results.options, this.results.value ] = this.removeElementInData('results', response.results);
resolve();
})).catch;
},
/**
* Select/unselect in Goal Multiselect
* @param value
*/
selectGoal(value) {
//console.log('----'); console.log('select goal', value.id);
getResultByGoal(value.id).then(response => new Promise((resolve, reject) => {
this.addElementInData('results', response.results);
resolve();
})).catch;
},
unselectGoal(value) {
//console.log('----'); console.log('unselect goal', value.id);
getResultByGoal(value.id).then(response => new Promise((resolve, reject) => {
[ this.results.options, this.results.value ] = this.removeElementInData('results', response.results);
resolve();
})).catch;
},
/**
* Select/unselect in Result Multiselect
* @param value
*/
selectResult(value) {
//console.log('----'); console.log('select result', value.id);
},
unselectResult(value) {
//console.log('----'); console.log('unselect result', value.id);
},
/**
* Choose parent action will involve retaining the "children" actions.
* @param value
* @return array
*/
getChildrensFromParent(value) {
if (null === value.parent) {
let excludeParent = this.actions.options.filter(o => o.parent !== null);
let children = excludeParent.filter(o => o.parent.id === value.id);
//console.log("get childrens", children.map(e => e.id));
return children;
}
return [];
},
/**
* Add response elements in data target
* @param target string -> 'actions', 'goals' or 'results'
* @param response array of objects with fetch results
*/
addElementInData(target, response) {
let data = this[target];
let dump = [];
response.forEach(elem => {
let found = data.options.some(e => e.id === elem.id);
if (!found) {
data.options.push(elem);
dump.push(elem.id);
}
})
if (dump.length > 0) {
//console.log('push ' + dump.length + ' elems in', target, dump);
}
},
/**
* Remove response elements from data target
* @param target string -> 'actions', 'goals' or 'results'
* @param response array of objects with fetch results
* @returns data.<target>.options
*/
removeElementInData(target, response) {
let data = this[target];
let dump = [];
response.forEach(elem => {
let found = data.options.some(e => e.id === elem.id);
if (found) {
data.options = data.options.filter(e => e.id !== elem.id);
dump.push(elem.id);
this.removeSelectedElement(target, elem);
}
})
if (dump.length > 0) {
//console.log('remove ' + dump.length + ' elems from ' + target + ' options', dump);
}
return [ data.options, data.value ];
},
/**
*
* @param target
* @param elements
*/
addSelectedElement(target, elements) {
let data = this[target];
let dump = [];
elements.forEach(elem => {
let selected = data.value.some(e => e.id === elem.id);
if (!selected) {
data.value.push(elem);
dump.push(elem.id);
// add in hiddenField
this.rebuildHiddenFieldValues(target);
}
});
if (dump.length > 0) {
//console.log('add ' + dump.length + ' selected elems in', target, dump);
}
},
/**
* Remove element from selected and from hiddenField
* @param target
* @param elem
*/
removeSelectedElement(target, elem) {
let data = this[target];
let selected = data.value.some(e => e.id === elem.id);
if (selected) {
// remove from selected
data.value = data.value.filter(e => e.id !== elem.id);
//console.log('remove ' + elem.id + ' from selected ' + target);
// remove from hiddenField
this.rebuildHiddenFieldValues(target);
// in any cases, remove should be recursive
this.unselectToNextField(target, elem);
}
},
/**
* When unselect Action, it could remove elements in goals multiselect.
* In that case, we have to unselect Goal to remove elements in results too.
* @param target
* @param elem
*/
unselectToNextField(target, elem) {
if (target === 'goals') {
//console.log('!!!! target is goal: unselect goal', elem.id);
this.unselectGoal(elem);
//console.log('!!!! done');
}
},
/**
* Rebuild values serie (string) in target HiddenField
* @param target
*/
rebuildHiddenFieldValues(target) {
let data = this[target];
//console.log('rebuild hiddenFields ' + target + ' values :');
data.hiddenField.value = ''; // reset
data.value.forEach(elem => {
data.hiddenField.value = this.addIdToValue(data.hiddenField.value, elem.id);
})
//console.log(data.hiddenField);
},
addIdToValue(string, id) {
let array = string ? string.split(',') : [];
array.push(id.toString());
let str = array.join();
return str;
},
transTitle ({ title }) {
return title.fr //TODO multilang
},
},
}
</script>
<style scoped>
</style>

View File

@ -0,0 +1,41 @@
import { fetchResults } from 'ChillMainAssets/lib/api/apiMethods';
const getSocialActions = () => fetchResults(
'/api/1.0/person/social/social-action.json', {
item_per_page: 200
}
);
const getGoalByAction = (id) => {
let url = `/api/1.0/person/social-work/goal/by-social-action/${id}.json`;
return fetch(url)
.then(response => {
if (response.ok) { return response.json(); }
throw Error('Error with request resource response');
});
};
const getResultByAction = (id) => {
let url = `/api/1.0/person/social-work/result/by-social-action/${id}.json`;
return fetch(url)
.then(response => {
if (response.ok) { return response.json(); }
throw Error('Error with request resource response');
});
};
const getResultByGoal = (id) => {
let url = `/api/1.0/person/social-work/result/by-goal/${id}.json`;
return fetch(url)
.then(response => {
if (response.ok) { return response.json(); }
throw Error('Error with request resource response');
});
};
export {
getSocialActions,
getGoalByAction,
getResultByAction,
getResultByGoal,
}

View File

@ -0,0 +1,13 @@
import { createApp } from "vue";
import { _createI18n } from 'ChillMainAssets/vuejs/_js/i18n';
import App from './App.vue';
const i18n = _createI18n({});
const app = createApp({
template: `<app></app>`,
})
.use(i18n)
.component('app', App)
.mount('#export_export')
;

View File

@ -11,6 +11,7 @@ declare(strict_types=1);
namespace Chill\PersonBundle\Security\Authorization;
use Chill\MainBundle\Entity\Center;
use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Security\Authorization\AbstractChillVoter;
use Chill\MainBundle\Security\Authorization\VoterHelperFactoryInterface;
@ -119,6 +120,7 @@ class AccompanyingPeriodVoter extends AbstractChillVoter implements ProvideRoleH
->addCheckFor(null, [self::CREATE, self::REASSIGN_BULK])
->addCheckFor(AccompanyingPeriod::class, [self::TOGGLE_CONFIDENTIAL, ...self::ALL])
->addCheckFor(Person::class, [self::SEE, self::CREATE])
->addCheckFor(Center::class, [self::STATS])
->build();
}

View File

@ -11,6 +11,10 @@ declare(strict_types=1);
namespace Chill\PersonBundle\Security\Authorization;
use Chill\MainBundle\Entity\Center;
use Chill\MainBundle\Security\Authorization\VoterHelperFactoryInterface;
use Chill\MainBundle\Security\Authorization\VoterHelperInterface;
use Chill\MainBundle\Security\ProvideRoleHierarchyInterface;
use Chill\PersonBundle\Entity\Household\Household;
use Chill\PersonBundle\Entity\Household\HouseholdMember;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
@ -19,7 +23,7 @@ use Symfony\Component\Security\Core\Security;
use UnexpectedValueException;
use function in_array;
class HouseholdVoter extends Voter
class HouseholdVoter extends Voter implements ProvideRoleHierarchyInterface
{
public const EDIT = 'CHILL_PERSON_HOUSEHOLD_EDIT';
@ -36,17 +40,40 @@ class HouseholdVoter extends Voter
self::EDIT, self::SEE,
];
private VoterHelperInterface $helper;
private Security $security;
public function __construct(Security $security)
public function __construct(Security $security, VoterHelperFactoryInterface $voterHelperFactory)
{
$this->security = $security;
$this->helper = $voterHelperFactory
->generate(self::class)
->addCheckFor(Center::class, [self::STATS])
->build();
}
public function getRolesWithHierarchy(): array
{
return [ 'Person' => $this->getRoles() ];
}
public function getRoles(): array
{
return [self::STATS];
}
public function getRolesWithoutScope(): array
{
return $this->getRoles();
}
protected function supports($attribute, $subject)
{
return $subject instanceof Household
&& in_array($attribute, self::ALL, true);
return ($subject instanceof Household
&& in_array($attribute, self::ALL, true))
|| $this->helper->supports($attribute, $subject)
;
}
protected function voteOnAttribute($attribute, $subject, TokenInterface $token): bool
@ -58,6 +85,9 @@ class HouseholdVoter extends Voter
case self::EDIT:
return $this->checkAssociatedMembersRole($subject, PersonVoter::UPDATE);
case self::STATS:
return $this->voteOnAttribute($attribute, $subject, $token);
default:
throw new UnexpectedValueException('attribute not supported');
}

View File

@ -13,6 +13,7 @@ module.exports = function(encore, entries)
encore.addEntry('vue_accourse_work_create', __dirname + '/Resources/public/vuejs/AccompanyingCourseWorkCreate/index.js');
encore.addEntry('vue_accourse_work_edit', __dirname + '/Resources/public/vuejs/AccompanyingCourseWorkEdit/index.js');
encore.addEntry('vue_visgraph', __dirname + '/Resources/public/vuejs/VisGraph/index.js');
encore.addEntry('vue_export_action_goal_result', __dirname + '/Resources/public/vuejs/ExportFormActionGoalResult/index.js');
encore.addEntry('mod_set_referrer', __dirname + '/Resources/public/mod/AccompanyingPeriod/setReferrer.js');

View File

@ -65,13 +65,6 @@ services:
tags:
- { name: chill.export_filter, alias: accompanyingcourse_evaluation_filter }
chill.person.export.filter_activitytype:
class: Chill\PersonBundle\Export\Filter\AccompanyingCourseFilters\ActivityTypeFilter
autowire: true
autoconfigure: true
tags:
- { name: chill.export_filter, alias: accompanyingcourse_activitytype_filter }
chill.person.export.filter_origin:
class: Chill\PersonBundle\Export\Filter\AccompanyingCourseFilters\OriginFilter
autowire: true

View File

@ -79,3 +79,10 @@ services:
autoconfigure: true
tags:
- { name: chill.export_aggregator, alias: social_work_actions_result_aggregator }
chill.person.export.aggregator_goalresult:
class: Chill\PersonBundle\Export\Aggregator\SocialWorkAggregators\GoalResultAggregator
autowire: true
autoconfigure: true
tags:
- { name: chill.export_aggregator, alias: social_work_actions_goal_result_aggregator }

View File

@ -86,8 +86,8 @@ Civility: Civilité
choose civility: --
All genders: tous les genres
Any person selected: Aucune personne sélectionnée
Create a household and add an address: Ajouter une adresse pour un usager non suivi et seul dans un ménage
A new household will be created. The person will be member of this household.: Un nouveau ménage va être créé. L'usager sera membre de ce ménage.
Create a household and add an address: Ajouter une adresse pour une personne non suivie et seule dans un ménage
A new household will be created. The person will be member of this household.: Un nouveau ménage va être créé. La personne sera membre de ce ménage.
Comment on the gender: Commentaire sur le genre
# dédoublonnage
@ -127,8 +127,8 @@ address_country_code: Code pays
'Alreay existing person': 'Dossiers déjà encodés'
'Add the person': 'Ajouter la personne'
'Add the person and create an accompanying period': "Créer l'usager ET créer une période d'accompagnement"
'Add the person and create a household': "Créer l'usager ET créer un ménage"
'Add the person and create an accompanying period': "Créer la personne ET créer une période d'accompagnement"
'Add the person and create a household': "Créer la personne ET créer un ménage"
Show person: Voir le dossier de la personne
'Confirm the creation': 'Confirmer la création'
'You will create this person': 'Vous allez créer le dossier suivant'
@ -200,7 +200,7 @@ Participants: Personnes impliquées
Create an accompanying course: Créer un parcours
Accompanying courses of users: Parcours des utilisateurs
This accompanying course is still a draft: Ce parcours est encore à l'état brouillon.
Associated peoples: Usagers concernés
Associated peoples: Personnes concernées
Resources: Interlocuteurs privilégiés
Any requestor to this accompanying course: Aucun demandeur pour ce parcours
Social action: Action d'accompagnement
@ -217,7 +217,7 @@ See this period: Voir cette période
Requestor: Demandeur
No requestor: Pas de demandeur
No resources: "Pas d'interlocuteurs privilégiés"
Persons associated: Usagers concernés
Persons associated: Personnes concernés
Referrer: Référent
Referrers: Agents traitants
Some peoples does not belong to any household currently. Add them to an household soon: Certaines personnes n'appartiennent à aucun ménage actuellement. Renseignez leur ménage dès que possible.
@ -244,7 +244,7 @@ List of resources: "Liste des ressources"
There are no available resources: "Aucun ressource"
no comment found: "Aucun commentaire"
Select a type: "Choisissez un type"
Select a person: "Choisissez un usager"
Select a person: "Choisissez une personne"
Select a thirdparty: "Choisissez un tiers"
Contact person: "Personne de contact"
Kind: "Type"
@ -276,11 +276,11 @@ Which kind of residential address would you create ?: Quel type d'adresse de ré
The address of another person: L'adresse d'une autre personne
The address of a third party: L'adresse d'un tiers
A new address: Une nouvelle adresse
residential_address_person_explanation: L'adresse sera positionnée auprès d'un usager. Lorsque l'usager déménage, l'adresse de résidence suivra également cet usager
residential_address_person_explanation: L'adresse sera positionnée auprès d'une personne. Lorsque la personne déménage, l'adresse de résidence suivra également cette personne
residential_address_third_party_explanation: L'adresse sera associée à celle d'un tiers.
residential_address_new_address_explanation: Créer une nouvelle adresse. L'adresse sera fixe.
New residential address: Nouvelle adresse de résidence
Host person: Choisir l'adresse d'un usager
Host person: Choisir l'adresse d'une personne
Host third party: Choisir l'adresse d'un tiers
The new residential address was created successfully: La nouvelle adresse de résidence a été créée
Edit a residential address: Modifier l'addresse de résidence
@ -318,6 +318,7 @@ CHILL_PERSON_ACCOMPANYING_PERIOD_FULL: Voir les détails, créer, supprimer et m
CHILL_PERSON_ACCOMPANYING_COURSE_REASSIGN_BULK: Réassigner les parcours en lot
CHILL_PERSON_ACCOMPANYING_PERIOD_SEE_DETAILS: Voir les détails d'une période d'accompagnement
CHILL_PERSON_ACCOMPANYING_PERIOD_STATS: Statistiques sur les parcours d'accompagnement
CHILL_PERSON_HOUSEHOLD_STATS: Statistiques sur les ménages
#period
Period closed!: Période clôturée!
@ -330,16 +331,16 @@ Accompanyied people: Personnes accompagnées
## exports
Exports of persons: Exports des personnes
Count people by various parameters.: Compte le nombre d'usagers en fonction de différents filtres.
Count people: Nombre d'usagers
Count people by various parameters.: Compte le nombre de personnes en fonction de différents filtres.
Count people: Nombre de personnes
List peoples: Liste des personnes
Create a list of people according to various filters.: Crée une liste des personnes selon différents filtres.
Fields to include in export: Champs à inclure dans l'export
Address valid at this date: Addresse valide à cette date
List duplicates: Liste des doublons
Create a list of duplicate people: Créer la liste des personnes détectées comme doublons.
Count people participating in an accompanying course by various parameters.: Nombre d'usagers concernées par un parcours
Count people participating in an accompanying course: Nombre d'usagers concernés par un parcours
Count people participating in an accompanying course by various parameters.: Nombre de personnes concernées par un parcours
Count people participating in an accompanying course: Nombre de personnes concernés par un parcours
Exports of accompanying courses: Exports des parcours d'accompagnement
Count accompanying courses: Nombre de parcours
@ -362,72 +363,77 @@ 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 par genre de la personne
Filter by person gender: Filtrer les personnes par genre
Accepted genders: Genres acceptés
'Filtering by genders: only %genders%': 'Filtré par genre: seulement %genders%'
Filter by person's nationality: Filtrer par nationalité
Filter by person's nationality: Filtrer les personnes par nationalité
Nationalities: Nationalités
Choose countries: Choisir les nationalités
'Filtered by nationality : %nationalities%': 'Filtré par nationalité : seulement %nationalities%'
Filter by person's birthdate: Filtrer par date de naissance de la personne
Filter by person's birthdate: Filtrer les personnes par date de naissance
Born after this date: Nés après cette date
Born before this date: Nés avant cette date
This field should not be empty: Ce champ ne peut pas être vide
This date should be after the date given in "born after" field: Cette date doit être après la date donnée du le champ "nés après le"
"Filtered by person's birthdate: between %date_from% and %date_to%": "Filtré par date de naissance de la personne: uniquement nés entre le %date_from% et %date_to%"
Filter by person's deathdate: Filtrer par date de décès de la personne
Filter by person's deathdate: Filtrer les personnes par date de décès
"Filtered by person's deathdate: between %date_from% and %date_to%": "Filtré par date de naissance de la personne: uniquement nés entre le %date_from% et %date_to%"
Death after this date: Décédé après cette date
Deathdate before: Décédé avant cette date
Alive: Vivant
Deceased: Décédé
Filter in relation to this date: Filtrer par rapport à cette date
"Filtered by a state of %deadOrAlive% at this date %date_calc%": Filtré par personnes qui sont %deadOrAlive% à cette date %date_calc%
Filter by person's age: Filtrer par âge de la personne
Filter by person's age: Filtrer les personnes par age
"Filtered by person's age: between %min_age% and %max_age%": "Filtré par âge de la personne entre %min_age% et %max_age%"
Minimum age: Âge minimum
Maximum age: Âge maximum
The minimum age should be less than the maximum age.: L'âge minimum doit être plus bas que l'âge maximum.
Date during which residential address was valid: Date de validité
Filtered by person\'s who have a residential address located at a thirdparty of type %thirparty_type%: Uniquement les usagers qui ont une addresse de résidence chez un tiers de catégorie %thirdparty_type%
Filtered by person\'s who have a residential address located at a thirdparty of type %thirparty_type%: Uniquement les personnes qui ont une addresse de résidence chez un tiers de catégorie %thirdparty_type%
Family composition: Composition familiale
Family composition at this time: Composition familiale à cette date.
Filter by person's marital status: Filtrer les personnes par état matrimonial
Filtered by person's marital status: Filtré par état matrimonial
Filter by person's marital status: Filtrer par état matrimonial
Marital status at this time: État matrimonial par rapport à cette date
Filter by entrusted child status: Filtrer les usagers qui sont "enfant confié"
Filtered by entrusted child status: Uniquement les usagers qui sont "enfant confié"
Filter by entrusted child status: Filtrer les personnes "enfant confié"
Filtered by entrusted child status: Uniquement les personnes qui sont "enfant confié"
Filter by nomadic status: Filtrer les usagers qui sont "gens de voyage"
Filtered by nomadic status: Uniquement les usagers qui sont "gens de voyage"
Filter by nomadic status: Filtrer les personnes "gens du voyage"
Filtered by nomadic status: Uniquement les personnes qui sont "gens du voyage"
Filtered by person's who have a residential address located at another user: Uniquement les usagers qui ont une addresse de résidence chez un autre usager
"Filter by person's who have a residential address located at another user": Filtrer les personnes qui ont une addresse de résidence chez une autre personne
"Filtered by person's who have a residential address located at another user": Uniquement les personnes qui ont une addresse de résidence chez une autre personne
Filtered by person's that are alive or have deceased at a certain date: Filtrer par usagers qui sont décédé ou vivant à une certaine date
Filter by person's that are alive or have deceased at a certain date: Filtrer les personnes qui sont décédées ou vivantes à une certaine date
Filtered by person's that are alive or have deceased at a certain date: Uniquement les personnes qui sont décédées ou vivantes à une certaine date
"Filter by accompanying period: active period": "Filtrer par période d'accompagnement: en file active"
"Filter by accompanying period: active period": "Filtrer les personnes par période d'accompagnement: en file active"
Having an accompanying period opened after this date: Ayant une période d'accompagnement ouverte après cette date
Having an accompanying period ending before this date, or still opened at this date: Ayant une période d'accompagnement fermée après cette date, ou toujours ouverte à cette date
"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%)": "Filtré par période d'accompagnement: personnes ayant une période d'accompagnement ouverte après le %date_from%, et cloturée le %date_to% (ou toujours ouverte le %date_to%)"
"Filter by accompanying period: starting between two dates": "Filtrer par période d'accompagnement: début de la période entre deux dates"
"Filter by accompanying period: starting between two dates": "Filtrer les personnes par période d'accompagnement: début de la période entre deux dates"
"Having an accompanying period opened before this date": "Ayant une période d'accompagnement ouverte avant cette date"
"Filtered by accompanying period: persons having an accompanying period opened between the %date_from% and %date_to%": "Filtrer par période d'accompagnement: ayant une période ouverte entre le %date_from% et le %date_to%"
"Filter by accompanying period: closed between two dates": "Filtrer par période d'accompagnement: période fermée entre deux dates"
"Filter by accompanying period: closed between two dates": "Filtrer les personnes par période d'accompagnement: période fermée entre deux dates"
Having an accompanying period closed after this date: Ayant une période d'accompagnement fermée après cette date
"Having an accompanying period closed before this date": "Ayant une période d'accompagnement fermée avant cette date"
"Filtered by accompanying period: persons having an accompanying period closed between the %date_from% and %date_to%": "Filtrer par période d'accompagnement: ayant une période fermée entre le %date_from% et le %date_to%"
Filter by person having an activity in a period: Filtrer les personnes ayant eu une échange pendant la période donnée
Filtered by person having an activity between %date_from% and %date_to% with reasons %reasons_name%: Uniquement les personnes associées à une échange entre %date_from% et %date_to% avec les sujets %reasons_name%
## accompanying course filters/aggr
Filter by user scope: Filtrer les parcours par service du référent
"Filtered by user main scope: only %scope%": "Filtré par service du référent: uniquement %scope%"
@ -456,7 +462,9 @@ Filter by socialaction: Filtrer les parcours par action d'accompagnement
Accepted socialactions: Actions d'accompagnement
"Filtered by socialactions: only %socialactions%": "Filtré par action d'accompagnement: uniquement %socialactions%"
Group by social action: Grouper les parcours par action d'accompagnement
Filter by type of action, objectives and results: "Filtrer par type d'action, objectif et résultat"
Filter by type of action, goals and results: "Filtrer les actions par type, objectif et résultat"
'Filtered actions by type, goals and results: %selected%': "Actions filtrées par: %selected%"
Filter by evaluation: Filtrer les parcours par évaluation
Accepted evaluations: Évaluations
@ -484,13 +492,13 @@ Administrative location: Localisation administrative
"Filtered by administratives locations: only %locations%": "Filtré par localisation administrative: uniquement %locations%"
Group by administrative location: Grouper les parcours par localisation administrative
Filter by requestor: Filtrer les parcours selon la présence du demandeur au sein des usagers concernés
Filter by requestor: Filtrer les parcours selon la présence du demandeur au sein des personnes concernées
Accepted choices: ''
is person concerned: Le demandeur est un usager concerné
is other person: Le demandeur est un usager, mais n'est pas concerné
is person concerned: Le demandeur est une personne concernée
is other person: Le demandeur est une personne, mais n'est pas concernée
is thirdparty: Le demandeur est un tiers
no requestor: Le parcours ne comporte pas de demandeur
"Filtered by requestor: only %choice%": "Filtré par présence du demandeur au sein des usagers concernés: uniquement si %choice%"
"Filtered by requestor: only %choice%": "Filtré par présence du demandeur au sein des personnes concernées: uniquement si %choice%"
Group by requestor: Grouper les parcours selon la nature du demandeur
Filter by confidential: Filtrer les parcours par confidentialité
@ -553,6 +561,10 @@ Group by treating agent: Grouper les actions par agent traitant
Group social work actions by action type: Grouper les actions par type
Group social work actions by goal: Grouper les actions par objectif
Group social work actions by result: Grouper les actions par résultat
Group social work actions by goal and result: Grouper les actions par objectif et résultat
Goal Type: Objectif
Result Type: Résultat
Goal and result Type: Objectif et résultat
## evaluations filters/aggr
Filter by evaluation type: Filtrer les évaluations par type
@ -576,7 +588,7 @@ Group by composition: Grouper les ménages par composition familiale
Group by number of children: Grouper les ménages par nombre d'enfants
## persons aggregators
Group by duration: Grouper par durée du parcours
Group by duration: Grouper les parcours par durée
Rounded month duration: Durée en mois (arrondie)
current duration: en cours
duration 0 month: 0 mois (<15 jours)
@ -589,13 +601,15 @@ Group by country: Grouper par pays
Group people by gender: Grouper les personnes par genre
Group people by their professional situation: Grouper les personnes par situation professionelle
Group people by marital status: Grouper les personnes par état matrimonial
Aggregate by household position: Grouper par position dans le ménage
Aggregate by household position: Grouper les personnes par position dans le ménage
Household position in relation to this date: Position dans le ménage par rapport à cette date
Household position: Position dans le ménage
Filtered by person's who have a residential address located at a thirdparty of type: Uniquement les usagers qui ont une addresse de résidence chez un tiers de catégorie "xxx"
"Filtered by person's who have a residential address located at a thirdparty of type %thirdparty_type% and valid on %date_calc%": "Uniquement les usagers qui ont une addresse de résidence chez un tiers de catégorie %thirdparty_type% et valide sur la date %date_calc%"
Aggregate by age: Grouper par âge
Filter by person's who have a residential address located at a thirdparty of type: Filtrer les personnes qui ont une addresse de résidence chez un tiers de catégorie "xxx"
"Filtered by person's who have a residential address located at a thirdparty of type %thirdparty_type% and valid on %date_calc%": "Uniquement les personnes qui ont une addresse de résidence chez un tiers de catégorie %thirdparty_type% et valide sur la date %date_calc%"
Aggregate by age: Grouper les personnes par âge
Calculate age in relation to this date: Calculer l'âge par rapport à cette date
Group people by country of birth: Grouper les personnes par pays de naissance
@ -763,7 +777,7 @@ This course is located at a temporarily address. You should locate this course t
Accompanying course location: Localisation du parcours
This course is located by: Localisé auprès de
This course has a temporarily location: Localisation temporaire
Choose a person to locate by: Localiser auprès d'un usager concerné
Choose a person to locate by: Localiser auprès d'une personne concernée
Associate at least one member with an household, and set an address to this household: Associez au moins un membre du parcours à un ménage, et indiquez une adresse à ce ménage.
Locate by: Localiser auprès de
fix it: Compléter
@ -848,7 +862,7 @@ Person addresses: Adresses de résidence
Household addresses: Adresses de domicile
Insert an address: Insérer une adresse
see social issues: Voir les problématiques sociales
see persons associated: Voir les usagers concernés
see persons associated: Voir les personnes concernées
docgen:
Accompanying Period basic: "Parcours d'accompagnement (basique)"
@ -869,10 +883,10 @@ docgen:
period_notification:
period_designated_subject: Vous êtes référent d'un parcours d'accompagnement
You are designated to a new period: Vous avez été désigné référent d'un parcours d'accompagnement.
Persons are: Les usagers concernés sont les suivants
Persons are: Les personnes concernées sont les suivantes
Social issues are: Les problématiques sociales renseignées sont les suivantes
See it online: Visualisez le parcours en ligne
Person locating period has moved: L'usager qui localise un parcours a déménagé
Person locating period has moved: La personne qui localise un parcours a déménagé
You are getting a notification for a period which does not exists any more: Cette notification ne correspond pas à une période d'accompagnement valide.
You are getting a notification for a period you are not allowed to see: La notification fait référence à une période d'accompagnement à laquelle vous n'avez pas accès.