mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2025-06-14 06:14:23 +00:00
Merge branch 'master' into improvements_acitivite_annexe
This commit is contained in:
commit
d4116a4659
@ -62,7 +62,8 @@
|
|||||||
"symfony/web-profiler-bundle": "^5.0"
|
"symfony/web-profiler-bundle": "^5.0"
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"bin-dir": "bin"
|
"bin-dir": "bin",
|
||||||
|
"vendor-dir": "tests/app/vendor"
|
||||||
},
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
"psr-4": {
|
"psr-4": {
|
||||||
|
@ -22,6 +22,9 @@
|
|||||||
|
|
||||||
namespace Chill\ActivityBundle\Controller;
|
namespace Chill\ActivityBundle\Controller;
|
||||||
|
|
||||||
|
use Chill\ActivityBundle\Repository\ActivityACLAwareRepository;
|
||||||
|
use Chill\ActivityBundle\Repository\ActivityACLAwareRepositoryInterface;
|
||||||
|
use Chill\ActivityBundle\Security\Authorization\ActivityVoter;
|
||||||
use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
|
use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
|
||||||
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
||||||
use Chill\PersonBundle\Entity\Person;
|
use Chill\PersonBundle\Entity\Person;
|
||||||
@ -36,6 +39,7 @@ use Symfony\Component\HttpFoundation\Response;
|
|||||||
use Symfony\Component\Security\Core\Role\Role;
|
use Symfony\Component\Security\Core\Role\Role;
|
||||||
use Chill\ActivityBundle\Entity\Activity;
|
use Chill\ActivityBundle\Entity\Activity;
|
||||||
use Chill\ActivityBundle\Form\ActivityType;
|
use Chill\ActivityBundle\Form\ActivityType;
|
||||||
|
use Chill\MainBundle\Entity\Embeddable\CommentEmbeddable;
|
||||||
use Symfony\Component\Serializer\SerializerInterface;
|
use Symfony\Component\Serializer\SerializerInterface;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -53,12 +57,16 @@ class ActivityController extends AbstractController
|
|||||||
|
|
||||||
protected SerializerInterface $serializer;
|
protected SerializerInterface $serializer;
|
||||||
|
|
||||||
|
protected ActivityACLAwareRepositoryInterface $activityACLAwareRepository;
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
|
ActivityACLAwareRepositoryInterface $activityACLAwareRepository,
|
||||||
EventDispatcherInterface $eventDispatcher,
|
EventDispatcherInterface $eventDispatcher,
|
||||||
AuthorizationHelper $authorizationHelper,
|
AuthorizationHelper $authorizationHelper,
|
||||||
LoggerInterface $logger,
|
LoggerInterface $logger,
|
||||||
SerializerInterface $serializer
|
SerializerInterface $serializer
|
||||||
) {
|
) {
|
||||||
|
$this->activityACLAwareRepository = $activityACLAwareRepository;
|
||||||
$this->eventDispatcher = $eventDispatcher;
|
$this->eventDispatcher = $eventDispatcher;
|
||||||
$this->authorizationHelper = $authorizationHelper;
|
$this->authorizationHelper = $authorizationHelper;
|
||||||
$this->logger = $logger;
|
$this->logger = $logger;
|
||||||
@ -77,13 +85,9 @@ class ActivityController extends AbstractController
|
|||||||
[$person, $accompanyingPeriod] = $this->getEntity($request);
|
[$person, $accompanyingPeriod] = $this->getEntity($request);
|
||||||
|
|
||||||
if ($person instanceof Person) {
|
if ($person instanceof Person) {
|
||||||
$reachableScopes = $this->authorizationHelper
|
$this->denyAccessUnlessGranted(ActivityVoter::SEE, $person);
|
||||||
->getReachableCircles($this->getUser(), new Role('CHILL_ACTIVITY_SEE'),
|
$activities = $this->activityACLAwareRepository
|
||||||
$person->getCenter());
|
->findByPerson($person, ActivityVoter::SEE, 0, null);
|
||||||
|
|
||||||
$activities = $em->getRepository(Activity::class)
|
|
||||||
->findByPersonImplied($person, $reachableScopes)
|
|
||||||
;
|
|
||||||
|
|
||||||
$event = new PrivacyEvent($person, array(
|
$event = new PrivacyEvent($person, array(
|
||||||
'element_class' => Activity::class,
|
'element_class' => Activity::class,
|
||||||
@ -93,10 +97,10 @@ class ActivityController extends AbstractController
|
|||||||
|
|
||||||
$view = 'ChillActivityBundle:Activity:listPerson.html.twig';
|
$view = 'ChillActivityBundle:Activity:listPerson.html.twig';
|
||||||
} elseif ($accompanyingPeriod instanceof AccompanyingPeriod) {
|
} elseif ($accompanyingPeriod instanceof AccompanyingPeriod) {
|
||||||
$activities = $em->getRepository('ChillActivityBundle:Activity')->findBy(
|
$this->denyAccessUnlessGranted(ActivityVoter::SEE, $accompanyingPeriod);
|
||||||
['accompanyingPeriod' => $accompanyingPeriod],
|
|
||||||
['date' => 'DESC'],
|
$activities = $this->activityACLAwareRepository
|
||||||
);
|
->findByAccompanyingPeriod($accompanyingPeriod, ActivityVoter::SEE);
|
||||||
|
|
||||||
$view = 'ChillActivityBundle:Activity:listAccompanyingCourse.html.twig';
|
$view = 'ChillActivityBundle:Activity:listAccompanyingCourse.html.twig';
|
||||||
}
|
}
|
||||||
@ -136,6 +140,12 @@ class ActivityController extends AbstractController
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($request->query->has('activityData')) {
|
||||||
|
$activityData = $request->query->get('activityData');
|
||||||
|
} else {
|
||||||
|
$activityData = [];
|
||||||
|
}
|
||||||
|
|
||||||
if ($view === null) {
|
if ($view === null) {
|
||||||
throw $this->createNotFoundException('Template not found');
|
throw $this->createNotFoundException('Template not found');
|
||||||
}
|
}
|
||||||
@ -144,6 +154,7 @@ class ActivityController extends AbstractController
|
|||||||
'person' => $person,
|
'person' => $person,
|
||||||
'accompanyingCourse' => $accompanyingPeriod,
|
'accompanyingCourse' => $accompanyingPeriod,
|
||||||
'data' => $data,
|
'data' => $data,
|
||||||
|
'activityData' => $activityData
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -163,10 +174,19 @@ class ActivityController extends AbstractController
|
|||||||
$activityType = $em->getRepository(\Chill\ActivityBundle\Entity\ActivityType::class)
|
$activityType = $em->getRepository(\Chill\ActivityBundle\Entity\ActivityType::class)
|
||||||
->find($activityType_id);
|
->find($activityType_id);
|
||||||
|
|
||||||
|
$activityData = null;
|
||||||
|
if ($request->query->has('activityData')) {
|
||||||
|
$activityData = $request->query->get('activityData');
|
||||||
|
}
|
||||||
|
|
||||||
if (!$activityType instanceof \Chill\ActivityBundle\Entity\ActivityType ||
|
if (!$activityType instanceof \Chill\ActivityBundle\Entity\ActivityType ||
|
||||||
!$activityType->isActive()) {
|
!$activityType->isActive()) {
|
||||||
|
|
||||||
$params = $this->buildParamsToUrl($person, $accompanyingPeriod);
|
$params = $this->buildParamsToUrl($person, $accompanyingPeriod);
|
||||||
|
|
||||||
|
if (null !== $activityData) {
|
||||||
|
$params['activityData'] = $activityData;
|
||||||
|
}
|
||||||
return $this->redirectToRoute('chill_activity_activity_select_type', $params);
|
return $this->redirectToRoute('chill_activity_activity_select_type', $params);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -184,6 +204,50 @@ class ActivityController extends AbstractController
|
|||||||
$entity->setType($activityType);
|
$entity->setType($activityType);
|
||||||
$entity->setDate(new \DateTime('now'));
|
$entity->setDate(new \DateTime('now'));
|
||||||
|
|
||||||
|
if ($request->query->has('activityData')) {
|
||||||
|
$activityData = $request->query->get('activityData');
|
||||||
|
|
||||||
|
if (array_key_exists('durationTime', $activityData)) {
|
||||||
|
$durationTimeInMinutes = $activityData['durationTime'];
|
||||||
|
$hours = floor($durationTimeInMinutes / 60);
|
||||||
|
$minutes = $durationTimeInMinutes % 60;
|
||||||
|
$duration = \DateTime::createFromFormat("H:i", $hours.':'.$minutes);
|
||||||
|
if ($duration) {
|
||||||
|
$entity->setDurationTime($duration);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (array_key_exists('date', $activityData)) {
|
||||||
|
$date = \DateTime::createFromFormat('Y-m-d', $activityData['date']);
|
||||||
|
if ($date) {
|
||||||
|
$entity->setDate($date);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (array_key_exists('personsId', $activityData)) {
|
||||||
|
foreach($activityData['personsId'] as $personId){
|
||||||
|
$concernedPerson = $em->getRepository(\Chill\PersonBundle\Entity\Person::class)->find($personId);
|
||||||
|
$entity->addPerson($concernedPerson);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (array_key_exists('professionalsId', $activityData)) {
|
||||||
|
foreach($activityData['professionalsId'] as $professionalsId){
|
||||||
|
$professional = $em->getRepository(\Chill\ThirdPartyBundle\Entity\ThirdParty::class)->find($professionalsId);
|
||||||
|
$entity->addThirdParty($professional);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (array_key_exists('comment', $activityData)) {
|
||||||
|
$comment = new CommentEmbeddable();
|
||||||
|
$comment->setComment($activityData['comment']);
|
||||||
|
$comment->setUserId($this->getUser()->getid());
|
||||||
|
$comment->setDate(new \DateTime('now'));
|
||||||
|
$entity->setComment($comment);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
// TODO revoir le Voter de Activity pour tenir compte qu'une activité peut appartenir a une période
|
// TODO revoir le Voter de Activity pour tenir compte qu'une activité peut appartenir a une période
|
||||||
// $this->denyAccessUnlessGranted('CHILL_ACTIVITY_CREATE', $entity);
|
// $this->denyAccessUnlessGranted('CHILL_ACTIVITY_CREATE', $entity);
|
||||||
|
|
||||||
@ -201,6 +265,7 @@ class ActivityController extends AbstractController
|
|||||||
$this->addFlash('success', $this->get('translator')->trans('Success : activity created!'));
|
$this->addFlash('success', $this->get('translator')->trans('Success : activity created!'));
|
||||||
|
|
||||||
$params = $this->buildParamsToUrl($person, $accompanyingPeriod);
|
$params = $this->buildParamsToUrl($person, $accompanyingPeriod);
|
||||||
|
|
||||||
$params['id'] = $entity->getId();
|
$params['id'] = $entity->getId();
|
||||||
|
|
||||||
return $this->redirectToRoute('chill_activity_activity_show', $params);
|
return $this->redirectToRoute('chill_activity_activity_show', $params);
|
||||||
|
@ -23,6 +23,8 @@
|
|||||||
namespace Chill\ActivityBundle\Repository;
|
namespace Chill\ActivityBundle\Repository;
|
||||||
|
|
||||||
use Chill\ActivityBundle\Entity\Activity;
|
use Chill\ActivityBundle\Entity\Activity;
|
||||||
|
use Chill\MainBundle\Security\Resolver\CenterResolverDispatcher;
|
||||||
|
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
||||||
use Chill\PersonBundle\Entity\Person;
|
use Chill\PersonBundle\Entity\Person;
|
||||||
use Chill\ActivityBundle\Repository\ActivityRepository;
|
use Chill\ActivityBundle\Repository\ActivityRepository;
|
||||||
use Chill\ActivityBundle\Security\Authorization\ActivityVoter;
|
use Chill\ActivityBundle\Security\Authorization\ActivityVoter;
|
||||||
@ -33,9 +35,10 @@ use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
|
|||||||
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
|
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
|
||||||
use Symfony\Component\Security\Core\Role\Role;
|
use Symfony\Component\Security\Core\Role\Role;
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
use Symfony\Component\Security\Core\Security;
|
||||||
|
|
||||||
|
|
||||||
final class ActivityACLAwareRepository
|
final class ActivityACLAwareRepository implements ActivityACLAwareRepositoryInterface
|
||||||
{
|
{
|
||||||
private AuthorizationHelper $authorizationHelper;
|
private AuthorizationHelper $authorizationHelper;
|
||||||
|
|
||||||
@ -45,16 +48,63 @@ final class ActivityACLAwareRepository
|
|||||||
|
|
||||||
private EntityManagerInterface $em;
|
private EntityManagerInterface $em;
|
||||||
|
|
||||||
|
private Security $security;
|
||||||
|
|
||||||
|
private CenterResolverDispatcher $centerResolverDispatcher;
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
AuthorizationHelper $authorizationHelper,
|
AuthorizationHelper $authorizationHelper,
|
||||||
|
CenterResolverDispatcher $centerResolverDispatcher,
|
||||||
TokenStorageInterface $tokenStorage,
|
TokenStorageInterface $tokenStorage,
|
||||||
ActivityRepository $repository,
|
ActivityRepository $repository,
|
||||||
EntityManagerInterface $em
|
EntityManagerInterface $em,
|
||||||
|
Security $security
|
||||||
) {
|
) {
|
||||||
$this->authorizationHelper = $authorizationHelper;
|
$this->authorizationHelper = $authorizationHelper;
|
||||||
|
$this->centerResolverDispatcher = $centerResolverDispatcher;
|
||||||
$this->tokenStorage = $tokenStorage;
|
$this->tokenStorage = $tokenStorage;
|
||||||
$this->repository = $repository;
|
$this->repository = $repository;
|
||||||
$this->em = $em;
|
$this->em = $em;
|
||||||
|
$this->security = $security;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Person $person
|
||||||
|
* @param string $role
|
||||||
|
* @param int|null $start
|
||||||
|
* @param int|null $limit
|
||||||
|
* @param array $orderBy
|
||||||
|
* @return array|Activity[]
|
||||||
|
*/
|
||||||
|
public function findByPerson(Person $person, string $role, ?int $start = 0, ?int $limit = 1000, ?array $orderBy = []): array
|
||||||
|
{
|
||||||
|
$user = $this->security->getUser();
|
||||||
|
$center = $this->centerResolverDispatcher->resolveCenter($person);
|
||||||
|
if (0 === count($orderBy)) {
|
||||||
|
$orderBy = ['date' => 'DESC'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$reachableScopes = $this->authorizationHelper
|
||||||
|
->getReachableCircles($user, $role, $center);
|
||||||
|
|
||||||
|
return $this->em->getRepository(Activity::class)
|
||||||
|
->findByPersonImplied($person, $reachableScopes, $orderBy, $limit, $start);
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function findByAccompanyingPeriod(AccompanyingPeriod $period, string $role, ?int $start = 0, ?int $limit = 1000, ?array $orderBy = []): array
|
||||||
|
{
|
||||||
|
$user = $this->security->getUser();
|
||||||
|
$center = $this->centerResolverDispatcher->resolveCenter($period);
|
||||||
|
if (0 === count($orderBy)) {
|
||||||
|
$orderBy = ['date' => 'DESC'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$scopes = $this->authorizationHelper
|
||||||
|
->getReachableCircles($user, $role, $center);
|
||||||
|
|
||||||
|
return $this->em->getRepository(Activity::class)
|
||||||
|
->findByAccompanyingPeriod($period, $scopes, true, $limit, $start, $orderBy);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function queryTimelineIndexer(string $context, array $args = []): array
|
public function queryTimelineIndexer(string $context, array $args = []): array
|
||||||
|
@ -0,0 +1,19 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Chill\ActivityBundle\Repository;
|
||||||
|
|
||||||
|
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
||||||
|
use Chill\PersonBundle\Entity\Person;
|
||||||
|
|
||||||
|
interface ActivityACLAwareRepositoryInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @return array|Activity[]
|
||||||
|
*/
|
||||||
|
public function findByPerson(Person $person, string $role, ?int $start = 0, ?int $limit = 1000, ?array $orderBy = []): array;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array|Activity[]
|
||||||
|
*/
|
||||||
|
public function findByAccompanyingPeriod(AccompanyingPeriod $period, string $role, ?int $start = 0, ?int $limit = 1000, ?array $orderBy = []): array;
|
||||||
|
}
|
@ -23,6 +23,8 @@
|
|||||||
namespace Chill\ActivityBundle\Repository;
|
namespace Chill\ActivityBundle\Repository;
|
||||||
|
|
||||||
use Chill\ActivityBundle\Entity\Activity;
|
use Chill\ActivityBundle\Entity\Activity;
|
||||||
|
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
||||||
|
use Chill\PersonBundle\Entity\Person;
|
||||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||||
use Doctrine\Persistence\ManagerRegistry;
|
use Doctrine\Persistence\ManagerRegistry;
|
||||||
|
|
||||||
@ -39,15 +41,22 @@ class ActivityRepository extends ServiceEntityRepository
|
|||||||
parent::__construct($registry, Activity::class);
|
parent::__construct($registry, Activity::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function findByPersonImplied($person, array $scopes, $orderBy = [ 'date' => 'DESC'], $limit = 100, $offset = 0)
|
/**
|
||||||
|
* @param $person
|
||||||
|
* @param array $scopes
|
||||||
|
* @param string[] $orderBy
|
||||||
|
* @param int $limit
|
||||||
|
* @param int $offset
|
||||||
|
* @return array|Activity[]
|
||||||
|
*/
|
||||||
|
public function findByPersonImplied(Person $person, array $scopes, ?array $orderBy = [ 'date' => 'DESC'], ?int $limit = 100, ?int $offset = 0): array
|
||||||
{
|
{
|
||||||
$qb = $this->createQueryBuilder('a');
|
$qb = $this->createQueryBuilder('a');
|
||||||
$qb->select('a');
|
$qb->select('a');
|
||||||
|
|
||||||
$qb
|
$qb
|
||||||
// TODO add acl
|
->where($qb->expr()->in('a.scope', ':scopes'))
|
||||||
//->where($qb->expr()->in('a.scope', ':scopes'))
|
->setParameter('scopes', $scopes)
|
||||||
//->setParameter('scopes', $scopes)
|
|
||||||
->andWhere(
|
->andWhere(
|
||||||
$qb->expr()->orX(
|
$qb->expr()->orX(
|
||||||
$qb->expr()->eq('a.person', ':person'),
|
$qb->expr()->eq('a.person', ':person'),
|
||||||
@ -61,6 +70,55 @@ class ActivityRepository extends ServiceEntityRepository
|
|||||||
$qb->addOrderBy('a.'.$k, $dir);
|
$qb->addOrderBy('a.'.$k, $dir);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$qb->setMaxResults($limit)->setFirstResult($offset);
|
||||||
|
|
||||||
|
return $qb->getQuery()
|
||||||
|
->getResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param AccompanyingPeriod $period
|
||||||
|
* @param array $scopes
|
||||||
|
* @param int|null $limit
|
||||||
|
* @param int|null $offset
|
||||||
|
* @param array|string[] $orderBy
|
||||||
|
* @return array|Activity[]
|
||||||
|
*/
|
||||||
|
public function findByAccompanyingPeriod(AccompanyingPeriod $period, array $scopes, ?bool $allowNullScope = false, ?int $limit = 100, ?int $offset = 0, array $orderBy = ['date' => 'desc']): array
|
||||||
|
{
|
||||||
|
$qb = $this->createQueryBuilder('a');
|
||||||
|
$qb->select('a');
|
||||||
|
|
||||||
|
if (!$allowNullScope) {
|
||||||
|
$qb
|
||||||
|
->where($qb->expr()->in('a.scope', ':scopes'))
|
||||||
|
->setParameter('scopes', $scopes)
|
||||||
|
;
|
||||||
|
} else {
|
||||||
|
$qb
|
||||||
|
->where(
|
||||||
|
$qb->expr()->orX(
|
||||||
|
$qb->expr()->in('a.scope', ':scopes'),
|
||||||
|
$qb->expr()->isNull('a.scope')
|
||||||
|
)
|
||||||
|
)
|
||||||
|
->setParameter('scopes', $scopes)
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
$qb
|
||||||
|
->andWhere(
|
||||||
|
$qb->expr()->eq('a.accompanyingPeriod', ':period')
|
||||||
|
)
|
||||||
|
->setParameter('period', $period)
|
||||||
|
;
|
||||||
|
|
||||||
|
foreach ($orderBy as $k => $dir) {
|
||||||
|
$qb->addOrderBy('a.'.$k, $dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
$qb->setMaxResults($limit)->setFirstResult($offset);
|
||||||
|
|
||||||
return $qb->getQuery()
|
return $qb->getQuery()
|
||||||
->getResult();
|
->getResult();
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<div class="duration">
|
<div class="duration">
|
||||||
{% if t.durationTimeVisible > 0 %}
|
{% if activity.durationTime and t.durationTimeVisible %}
|
||||||
<p>
|
<p>
|
||||||
<i class="fa fa-fw fa-hourglass-end"></i>
|
<i class="fa fa-fw fa-hourglass-end"></i>
|
||||||
{{ activity.durationTime|date('H:i') }}
|
{{ activity.durationTime|date('H:i') }}
|
||||||
|
@ -18,7 +18,12 @@
|
|||||||
{% set accompanying_course_id = accompanyingCourse.id %}
|
{% set accompanying_course_id = accompanyingCourse.id %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<a href="{{ path('chill_activity_activity_new', {'person_id': person_id, 'activityType_id': activityType.id, 'accompanying_period_id': accompanying_course_id }) }}">
|
<a href="{{ path('chill_activity_activity_new', {
|
||||||
|
'person_id': person_id,
|
||||||
|
'activityType_id': activityType.id,
|
||||||
|
'accompanying_period_id': accompanying_course_id,
|
||||||
|
'activityData': activityData
|
||||||
|
}) }}">
|
||||||
|
|
||||||
<div class="bloc btn btn-primary btn-lg btn-block">
|
<div class="bloc btn btn-primary btn-lg btn-block">
|
||||||
{{ activityType.name|localize_translatable_string }}
|
{{ activityType.name|localize_translatable_string }}
|
||||||
|
@ -64,12 +64,22 @@
|
|||||||
|
|
||||||
{% if t.durationTimeVisible %}
|
{% if t.durationTimeVisible %}
|
||||||
<dt class="inline">{{ 'Duration Time'|trans }}</dt>
|
<dt class="inline">{{ 'Duration Time'|trans }}</dt>
|
||||||
<dd>{{ entity.durationTime|date('H:i') }}</dd>
|
<dd>{% if entity.durationTime is not null %}
|
||||||
|
{{ entity.durationTime|date('H:i') }}
|
||||||
|
{% else %}
|
||||||
|
{{ 'None'|trans|capitalize }}
|
||||||
|
{% endif %}
|
||||||
|
</dd>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if t.travelTimeVisible %}
|
{% if t.travelTimeVisible %}
|
||||||
<dt class="inline">{{ 'Travel Time'|trans }}</dt>
|
<dt class="inline">{{ 'Travel Time'|trans }}</dt>
|
||||||
<dd>{{ entity.travelTime|date('H:i') }}</dd>
|
<dd>{% if entity.travelTime is not null %}
|
||||||
|
{{ entity.travelTime|date('H:i') }}
|
||||||
|
{% else %}
|
||||||
|
{{ 'None'|trans|capitalize }}
|
||||||
|
{% endif %}
|
||||||
|
</dd>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if t.commentVisible %}
|
{% if t.commentVisible %}
|
||||||
|
@ -19,6 +19,11 @@
|
|||||||
|
|
||||||
namespace Chill\ActivityBundle\Security\Authorization;
|
namespace Chill\ActivityBundle\Security\Authorization;
|
||||||
|
|
||||||
|
use Chill\MainBundle\Security\Authorization\VoterHelperFactoryInterface;
|
||||||
|
use Chill\MainBundle\Security\Authorization\VoterHelperInterface;
|
||||||
|
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
||||||
|
use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter;
|
||||||
|
use Chill\PersonBundle\Security\Authorization\PersonVoter;
|
||||||
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
||||||
|
|
||||||
use Chill\MainBundle\Security\Authorization\AbstractChillVoter;
|
use Chill\MainBundle\Security\Authorization\AbstractChillVoter;
|
||||||
@ -28,11 +33,10 @@ use Chill\MainBundle\Entity\User;
|
|||||||
use Chill\ActivityBundle\Entity\Activity;
|
use Chill\ActivityBundle\Entity\Activity;
|
||||||
use Chill\PersonBundle\Entity\Person;
|
use Chill\PersonBundle\Entity\Person;
|
||||||
use Symfony\Component\Security\Core\Role\Role;
|
use Symfony\Component\Security\Core\Role\Role;
|
||||||
|
use Symfony\Component\Security\Core\Security;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
* Voter for Activity class
|
||||||
*
|
|
||||||
* @author Julien Fastré <julien.fastre@champs-libres.coop>
|
|
||||||
*/
|
*/
|
||||||
class ActivityVoter extends AbstractChillVoter implements ProvideRoleHierarchyInterface
|
class ActivityVoter extends AbstractChillVoter implements ProvideRoleHierarchyInterface
|
||||||
{
|
{
|
||||||
@ -41,30 +45,37 @@ class ActivityVoter extends AbstractChillVoter implements ProvideRoleHierarchyIn
|
|||||||
const SEE_DETAILS = 'CHILL_ACTIVITY_SEE_DETAILS';
|
const SEE_DETAILS = 'CHILL_ACTIVITY_SEE_DETAILS';
|
||||||
const UPDATE = 'CHILL_ACTIVITY_UPDATE';
|
const UPDATE = 'CHILL_ACTIVITY_UPDATE';
|
||||||
const DELETE = 'CHILL_ACTIVITY_DELETE';
|
const DELETE = 'CHILL_ACTIVITY_DELETE';
|
||||||
|
const FULL = 'CHILL_ACTIVITY_FULL';
|
||||||
|
|
||||||
/**
|
private const ALL = [
|
||||||
*
|
self::CREATE,
|
||||||
* @var AuthorizationHelper
|
self::SEE,
|
||||||
*/
|
self::UPDATE,
|
||||||
protected $helper;
|
self::DELETE,
|
||||||
|
self::SEE_DETAILS,
|
||||||
|
self::FULL
|
||||||
|
];
|
||||||
|
|
||||||
public function __construct(AuthorizationHelper $helper)
|
protected VoterHelperInterface $voterHelper;
|
||||||
{
|
|
||||||
$this->helper = $helper;
|
protected Security $security;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
Security $security,
|
||||||
|
VoterHelperFactoryInterface $voterHelperFactory
|
||||||
|
) {
|
||||||
|
$this->security = $security;
|
||||||
|
$this->voterHelper = $voterHelperFactory->generate(self::class)
|
||||||
|
->addCheckFor(Person::class, [self::SEE, self::CREATE])
|
||||||
|
->addCheckFor(AccompanyingPeriod::class, [self::SEE, self::CREATE])
|
||||||
|
->addCheckFor(Activity::class, self::ALL)
|
||||||
|
->build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
protected function supports($attribute, $subject)
|
protected function supports($attribute, $subject)
|
||||||
{
|
{
|
||||||
if ($subject instanceof Activity) {
|
return $this->voterHelper->supports($attribute, $subject);
|
||||||
return \in_array($attribute, $this->getAttributes());
|
|
||||||
} elseif ($subject instanceof Person) {
|
|
||||||
return $attribute === self::SEE
|
|
||||||
||
|
|
||||||
$attribute === self::CREATE;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function voteOnAttribute($attribute, $subject, TokenInterface $token)
|
protected function voteOnAttribute($attribute, $subject, TokenInterface $token)
|
||||||
@ -73,31 +84,33 @@ class ActivityVoter extends AbstractChillVoter implements ProvideRoleHierarchyIn
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($subject instanceof Person) {
|
if ($subject instanceof Activity) {
|
||||||
$centers = $this->helper->getReachableCenters($token->getUser(), new Role($attribute));
|
if ($subject->getPerson() instanceof Person) {
|
||||||
|
// the context is person: we must have the right to see the person
|
||||||
return \in_array($subject->getCenter(), $centers);
|
if (!$this->security->isGranted(PersonVoter::SEE, $subject->getPerson())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} elseif ($subject->getAccompanyingPeriod() instanceof AccompanyingPeriod) {
|
||||||
|
if (!$this->security->isGranted(AccompanyingPeriodVoter::SEE, $subject->getAccompanyingPeriod())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new \RuntimeException("could not determine context of activity");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* @var $subject Activity */
|
return $this->voterHelper->voteOnAttribute($attribute, $subject, $token);
|
||||||
return $this->helper->userHasAccess($token->getUser(), $subject, $attribute);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function getAttributes()
|
|
||||||
{
|
|
||||||
return [ self::CREATE, self::SEE, self::UPDATE, self::DELETE,
|
|
||||||
self::SEE_DETAILS ];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public function getRoles()
|
public function getRoles()
|
||||||
{
|
{
|
||||||
return $this->getAttributes();
|
return self::ALL;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getRolesWithoutScope()
|
public function getRolesWithoutScope()
|
||||||
{
|
{
|
||||||
return array();
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,20 +1,4 @@
|
|||||||
services:
|
services:
|
||||||
chill.activity.security.authorization.activity_voter:
|
|
||||||
class: Chill\ActivityBundle\Security\Authorization\ActivityVoter
|
|
||||||
arguments:
|
|
||||||
- "@chill.main.security.authorization.helper"
|
|
||||||
tags:
|
|
||||||
- { name: security.voter }
|
|
||||||
- { name: chill.role }
|
|
||||||
|
|
||||||
chill.activity.security.authorization.activity_stats_voter:
|
|
||||||
class: Chill\ActivityBundle\Security\Authorization\ActivityStatsVoter
|
|
||||||
arguments:
|
|
||||||
- "@chill.main.security.authorization.helper"
|
|
||||||
tags:
|
|
||||||
- { name: security.voter }
|
|
||||||
- { name: chill.role }
|
|
||||||
|
|
||||||
|
|
||||||
chill.activity.timeline:
|
chill.activity.timeline:
|
||||||
class: Chill\ActivityBundle\Timeline\TimelineActivityProvider
|
class: Chill\ActivityBundle\Timeline\TimelineActivityProvider
|
||||||
@ -38,3 +22,8 @@ services:
|
|||||||
autowire: true
|
autowire: true
|
||||||
autoconfigure: true
|
autoconfigure: true
|
||||||
resource: '../Notification'
|
resource: '../Notification'
|
||||||
|
|
||||||
|
Chill\ActivityBundle\Security\Authorization\:
|
||||||
|
resource: '../Security/Authorization/'
|
||||||
|
autowire: true
|
||||||
|
autoconfigure: true
|
||||||
|
@ -1,8 +1,4 @@
|
|||||||
services:
|
services:
|
||||||
Chill\ActivityBundle\Controller\ActivityController:
|
Chill\ActivityBundle\Controller\ActivityController:
|
||||||
arguments:
|
autowire: true
|
||||||
$eventDispatcher: '@Symfony\Component\EventDispatcher\EventDispatcherInterface'
|
|
||||||
$authorizationHelper: '@Chill\MainBundle\Security\Authorization\AuthorizationHelper'
|
|
||||||
$logger: '@chill.main.logger'
|
|
||||||
$serializer: '@Symfony\Component\Serializer\SerializerInterface'
|
|
||||||
tags: ['controller.service_arguments']
|
tags: ['controller.service_arguments']
|
||||||
|
@ -24,9 +24,7 @@ services:
|
|||||||
- '@Doctrine\Persistence\ManagerRegistry'
|
- '@Doctrine\Persistence\ManagerRegistry'
|
||||||
|
|
||||||
Chill\ActivityBundle\Repository\ActivityACLAwareRepository:
|
Chill\ActivityBundle\Repository\ActivityACLAwareRepository:
|
||||||
arguments:
|
autowire: true
|
||||||
$tokenStorage: '@Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface'
|
autoconfigure: true
|
||||||
$authorizationHelper: '@Chill\MainBundle\Security\Authorization\AuthorizationHelper'
|
Chill\ActivityBundle\Repository\ActivityACLAwareRepositoryInterface: '@Chill\ActivityBundle\Repository\ActivityACLAwareRepository'
|
||||||
$repository: '@Chill\ActivityBundle\Repository\ActivityRepository'
|
|
||||||
$em: '@Doctrine\ORM\EntityManagerInterface'
|
|
||||||
|
|
||||||
|
@ -36,7 +36,9 @@ use Symfony\Component\HttpFoundation\Response;
|
|||||||
use Symfony\Component\Security\Core\Role\Role;
|
use Symfony\Component\Security\Core\Role\Role;
|
||||||
use Chill\CalendarBundle\Entity\Calendar;
|
use Chill\CalendarBundle\Entity\Calendar;
|
||||||
use Chill\CalendarBundle\Form\CalendarType;
|
use Chill\CalendarBundle\Form\CalendarType;
|
||||||
|
use Chill\CalendarBundle\Repository\CalendarRepository;
|
||||||
use Chill\MainBundle\Entity\User;
|
use Chill\MainBundle\Entity\User;
|
||||||
|
use Chill\MainBundle\Pagination\PaginatorFactory;
|
||||||
use Symfony\Component\Serializer\SerializerInterface;
|
use Symfony\Component\Serializer\SerializerInterface;
|
||||||
use Symfony\Component\Routing\Annotation\Route;
|
use Symfony\Component\Routing\Annotation\Route;
|
||||||
|
|
||||||
@ -55,16 +57,24 @@ class CalendarController extends AbstractController
|
|||||||
|
|
||||||
protected SerializerInterface $serializer;
|
protected SerializerInterface $serializer;
|
||||||
|
|
||||||
|
protected PaginatorFactory $paginator;
|
||||||
|
|
||||||
|
private CalendarRepository $calendarRepository;
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
EventDispatcherInterface $eventDispatcher,
|
EventDispatcherInterface $eventDispatcher,
|
||||||
AuthorizationHelper $authorizationHelper,
|
AuthorizationHelper $authorizationHelper,
|
||||||
LoggerInterface $logger,
|
LoggerInterface $logger,
|
||||||
SerializerInterface $serializer
|
SerializerInterface $serializer,
|
||||||
|
PaginatorFactory $paginator,
|
||||||
|
CalendarRepository $calendarRepository
|
||||||
) {
|
) {
|
||||||
$this->eventDispatcher = $eventDispatcher;
|
$this->eventDispatcher = $eventDispatcher;
|
||||||
$this->authorizationHelper = $authorizationHelper;
|
$this->authorizationHelper = $authorizationHelper;
|
||||||
$this->logger = $logger;
|
$this->logger = $logger;
|
||||||
$this->serializer = $serializer;
|
$this->serializer = $serializer;
|
||||||
|
$this->paginator = $paginator;
|
||||||
|
$this->calendarRepository = $calendarRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -73,31 +83,40 @@ class CalendarController extends AbstractController
|
|||||||
*/
|
*/
|
||||||
public function listAction(Request $request): Response
|
public function listAction(Request $request): Response
|
||||||
{
|
{
|
||||||
$em = $this->getDoctrine()->getManager();
|
|
||||||
$view = null;
|
$view = null;
|
||||||
|
|
||||||
[$user, $accompanyingPeriod] = $this->getEntity($request);
|
[$user, $accompanyingPeriod] = $this->getEntity($request);
|
||||||
|
|
||||||
if ($user instanceof User) {
|
if ($user instanceof User) {
|
||||||
|
|
||||||
$calendarItems = $em->getRepository(Calendar::class)
|
$calendarItems = $this->calendarRepository->findByUser($user);
|
||||||
->findByUser($user)
|
|
||||||
;
|
|
||||||
$view = '@ChillCalendar/Calendar/listByUser.html.twig';
|
|
||||||
} elseif ($accompanyingPeriod instanceof AccompanyingPeriod) {
|
|
||||||
$calendarItems = $em->getRepository(Calendar::class)->findBy(
|
|
||||||
['accompanyingPeriod' => $accompanyingPeriod],
|
|
||||||
['startDate' => 'DESC']
|
|
||||||
);
|
|
||||||
|
|
||||||
$view = '@ChillCalendar/Calendar/listByAccompanyingCourse.html.twig';
|
$view = '@ChillCalendar/Calendar/listByUser.html.twig';
|
||||||
}
|
|
||||||
|
|
||||||
return $this->render($view, [
|
return $this->render($view, [
|
||||||
'calendarItems' => $calendarItems,
|
'calendarItems' => $calendarItems,
|
||||||
'user' => $user,
|
'user' => $user
|
||||||
'accompanyingCourse' => $accompanyingPeriod,
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
} elseif ($accompanyingPeriod instanceof AccompanyingPeriod) {
|
||||||
|
|
||||||
|
$total = $this->calendarRepository->countByAccompanyingPeriod($accompanyingPeriod);
|
||||||
|
$paginator = $this->paginator->create($total);
|
||||||
|
$calendarItems = $this->calendarRepository->findBy(
|
||||||
|
['accompanyingPeriod' => $accompanyingPeriod],
|
||||||
|
['startDate' => 'DESC'],
|
||||||
|
$paginator->getItemsPerPage(),
|
||||||
|
$paginator->getCurrentPageFirstItemNumber()
|
||||||
|
);
|
||||||
|
|
||||||
|
$view = '@ChillCalendar/Calendar/listByAccompanyingCourse.html.twig';
|
||||||
|
|
||||||
|
return $this->render($view, [
|
||||||
|
'calendarItems' => $calendarItems,
|
||||||
|
'accompanyingCourse' => $accompanyingPeriod,
|
||||||
|
'paginator' => $paginator
|
||||||
|
]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -111,7 +130,7 @@ class CalendarController extends AbstractController
|
|||||||
[$user, $accompanyingPeriod] = $this->getEntity($request);
|
[$user, $accompanyingPeriod] = $this->getEntity($request);
|
||||||
|
|
||||||
if ($accompanyingPeriod instanceof AccompanyingPeriod) {
|
if ($accompanyingPeriod instanceof AccompanyingPeriod) {
|
||||||
$view = '@ChillCalendar/Calendar/newAccompanyingCourse.html.twig';
|
$view = '@ChillCalendar/Calendar/newByAccompanyingCourse.html.twig';
|
||||||
}
|
}
|
||||||
// elseif ($user instanceof User) {
|
// elseif ($user instanceof User) {
|
||||||
// $view = '@ChillCalendar/Calendar/newUser.html.twig';
|
// $view = '@ChillCalendar/Calendar/newUser.html.twig';
|
||||||
@ -196,10 +215,33 @@ class CalendarController extends AbstractController
|
|||||||
throw $this->createNotFoundException('Template not found');
|
throw $this->createNotFoundException('Template not found');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$personsId = [];
|
||||||
|
foreach ($entity->getPersons() as $p) {
|
||||||
|
array_push($personsId, $p->getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
$professionalsId = [];
|
||||||
|
foreach ($entity->getProfessionals() as $p) {
|
||||||
|
array_push($professionalsId, $p->getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
$durationTime = $entity->getEndDate()->diff($entity->getStartDate());
|
||||||
|
$durationTimeInMinutes = $durationTime->days*1440 + $durationTime->h*60 + $durationTime->i;
|
||||||
|
|
||||||
|
$activityData = [
|
||||||
|
'calendarId' => $id,
|
||||||
|
'personsId' => $personsId,
|
||||||
|
'professionalsId' => $professionalsId,
|
||||||
|
'date' => $entity->getStartDate()->format('Y-m-d'),
|
||||||
|
'durationTime' => $durationTimeInMinutes,
|
||||||
|
'comment' => $entity->getComment()->getComment(),
|
||||||
|
];
|
||||||
|
|
||||||
return $this->render($view, [
|
return $this->render($view, [
|
||||||
'accompanyingCourse' => $accompanyingPeriod,
|
'accompanyingCourse' => $accompanyingPeriod,
|
||||||
'entity' => $entity,
|
'entity' => $entity,
|
||||||
'user' => $user
|
'user' => $user,
|
||||||
|
'activityData' => $activityData
|
||||||
//'delete_form' => $deleteForm->createView(),
|
//'delete_form' => $deleteForm->createView(),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
@ -29,6 +29,7 @@ class ChillCalendarExtension extends Extension implements PrependExtensionInterf
|
|||||||
$loader->load('services/controller.yml');
|
$loader->load('services/controller.yml');
|
||||||
$loader->load('services/fixtures.yml');
|
$loader->load('services/fixtures.yml');
|
||||||
$loader->load('services/form.yml');
|
$loader->load('services/form.yml');
|
||||||
|
$loader->load('services/event.yml');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function prepend(ContainerBuilder $container)
|
public function prepend(ContainerBuilder $container)
|
||||||
|
@ -0,0 +1,45 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Chill\CalendarBundle\Event;
|
||||||
|
|
||||||
|
use Chill\ActivityBundle\Entity\Activity;
|
||||||
|
use Doctrine\Persistence\Event\LifecycleEventArgs;
|
||||||
|
use Symfony\Component\HttpFoundation\RequestStack;
|
||||||
|
|
||||||
|
class ListenToActivityCreate
|
||||||
|
{
|
||||||
|
|
||||||
|
private RequestStack $requestStack;
|
||||||
|
|
||||||
|
public function __construct(RequestStack $requestStack)
|
||||||
|
{
|
||||||
|
$this->requestStack = $requestStack;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function postPersist(Activity $activity, LifecycleEventArgs $event): void
|
||||||
|
{
|
||||||
|
// Get the calendarId from the request
|
||||||
|
$request = $this->requestStack->getCurrentRequest();
|
||||||
|
|
||||||
|
if (null === $request) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($request->query->has('activityData')) {
|
||||||
|
$activityData = $request->query->get('activityData');
|
||||||
|
if (array_key_exists('calendarId', $activityData)) {
|
||||||
|
$calendarId = $activityData['calendarId'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attach the activity to the calendar
|
||||||
|
$em = $event->getObjectManager();
|
||||||
|
|
||||||
|
$calendar = $em->getRepository(\Chill\CalendarBundle\Entity\Calendar::class)->find($calendarId);
|
||||||
|
$calendar->setActivity($activity);
|
||||||
|
|
||||||
|
$em->persist($calendar);
|
||||||
|
$em->flush();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -3,7 +3,9 @@
|
|||||||
namespace Chill\CalendarBundle\Repository;
|
namespace Chill\CalendarBundle\Repository;
|
||||||
|
|
||||||
use Chill\CalendarBundle\Entity\Calendar;
|
use Chill\CalendarBundle\Entity\Calendar;
|
||||||
|
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
||||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||||
|
use Doctrine\ORM\EntityRepository;
|
||||||
use Doctrine\Persistence\ManagerRegistry;
|
use Doctrine\Persistence\ManagerRegistry;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -14,9 +16,13 @@ use Doctrine\Persistence\ManagerRegistry;
|
|||||||
*/
|
*/
|
||||||
class CalendarRepository extends ServiceEntityRepository
|
class CalendarRepository extends ServiceEntityRepository
|
||||||
{
|
{
|
||||||
|
|
||||||
|
// private EntityRepository $repository;
|
||||||
|
|
||||||
public function __construct(ManagerRegistry $registry)
|
public function __construct(ManagerRegistry $registry)
|
||||||
{
|
{
|
||||||
parent::__construct($registry, Calendar::class);
|
parent::__construct($registry, Calendar::class);
|
||||||
|
// $this->repository = $entityManager->getRepository(AccompanyingPeriodWork::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
// /**
|
// /**
|
||||||
|
@ -0,0 +1,10 @@
|
|||||||
|
services:
|
||||||
|
Chill\CalendarBundle\Event\ListenToActivityCreate:
|
||||||
|
autowire: true
|
||||||
|
autoconfigure: true
|
||||||
|
tags:
|
||||||
|
-
|
||||||
|
name: 'doctrine.orm.entity_listener'
|
||||||
|
event: 'postPersist'
|
||||||
|
entity: 'Chill\ActivityBundle\Entity\Activity'
|
||||||
|
|
@ -1,135 +0,0 @@
|
|||||||
{% set user_id = null %}
|
|
||||||
{% if user %}
|
|
||||||
{% set user_id = user.id %}
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% set accompanying_course_id = null %}
|
|
||||||
{% if accompanyingCourse %}
|
|
||||||
{% set accompanying_course_id = accompanyingCourse.id %}
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if context == 'user' %}
|
|
||||||
<h2>{{ 'My calendar list' |trans }}</h2>
|
|
||||||
{% else %}
|
|
||||||
<h2>{{ 'Calendar list' |trans }}</h2>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if context == 'user' %}
|
|
||||||
<div id="myCalendar"></div>
|
|
||||||
{% else %}
|
|
||||||
|
|
||||||
{% if calendarItems|length == 0 %}
|
|
||||||
<p class="chill-no-data-statement">
|
|
||||||
{{ "There is no calendar items."|trans }}
|
|
||||||
<a href="{{ path('chill_calendar_calendar_new', {'user_id': user_id, 'accompanying_period_id': accompanying_course_id}) }}" class="btn btn-create button-small"></a>
|
|
||||||
</p>
|
|
||||||
{% else %}
|
|
||||||
|
|
||||||
<div class="flex-table list-records context-{{ context }}">
|
|
||||||
|
|
||||||
{% for calendar in calendarItems %}
|
|
||||||
|
|
||||||
<div class="item-bloc">
|
|
||||||
<div class="item-row main">
|
|
||||||
<div class="item-col">
|
|
||||||
|
|
||||||
|
|
||||||
{% if calendar.startDate and calendar.endDate %}
|
|
||||||
{% if calendar.endDate.diff(calendar.startDate).days >= 1 %}
|
|
||||||
<h3>{{ "From the day"|trans }} {{ calendar.startDate|format_datetime('medium', 'short') }} </h3>
|
|
||||||
<h3>{{ "to the day"|trans }} {{ calendar.endDate|format_datetime('medium', 'short') }}</h3>
|
|
||||||
{% else %}
|
|
||||||
<h3>{{ calendar.startDate|format_date('full') }} </h3>
|
|
||||||
<h3>{{ calendar.startDate|format_datetime('none', 'short', locale='fr') }} - {{ calendar.endDate|format_datetime('none', 'short', locale='fr') }}</h3>
|
|
||||||
|
|
||||||
<div class="duration">
|
|
||||||
<p>
|
|
||||||
<i class="fa fa-fw fa-hourglass-end"></i>
|
|
||||||
{{ calendar.endDate.diff(calendar.startDate)|date("%H:%M")}}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<div class="item-col">
|
|
||||||
<ul class="list-content">
|
|
||||||
{% if calendar.user %}
|
|
||||||
<li>
|
|
||||||
<b>{{ 'by'|trans }}{{ calendar.user.usernameCanonical }}</b>
|
|
||||||
</li>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if calendar.mainUser is not empty %}
|
|
||||||
<li>
|
|
||||||
<b>{{ 'main user concerned'|trans }}: {{ calendar.mainUser.usernameCanonical }}</b>
|
|
||||||
</li>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
</ul>
|
|
||||||
<ul class="record_actions">
|
|
||||||
<li>
|
|
||||||
<a href="{{ path('chill_calendar_calendar_show', { 'id': calendar.id, 'user_id': user_id, 'accompanying_period_id': accompanying_course_id }) }}" class="btn btn-show "></a>
|
|
||||||
</li>
|
|
||||||
{# TOOD
|
|
||||||
{% if is_granted('CHILL_ACTIVITY_UPDATE', calendar) %}
|
|
||||||
#}
|
|
||||||
<li>
|
|
||||||
<a href="{{ path('chill_calendar_calendar_edit', { 'id': calendar.id, 'user_id': user_id, 'accompanying_period_id': accompanying_course_id }) }}" class="btn btn-update "></a>
|
|
||||||
</li>
|
|
||||||
{# TOOD
|
|
||||||
{% endif %}
|
|
||||||
{% if is_granted('CHILL_ACTIVITY_DELETE', calendar) %}
|
|
||||||
#}
|
|
||||||
<li>
|
|
||||||
<a href="{{ path('chill_calendar_calendar_delete', { 'id': calendar.id, 'user_id' : user_id, 'accompanying_period_id': accompanying_course_id } ) }}" class="btn btn-delete "></a>
|
|
||||||
</li>
|
|
||||||
{#
|
|
||||||
{% endif %}
|
|
||||||
#}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{%
|
|
||||||
if calendar.comment.comment is not empty
|
|
||||||
or calendar.users|length > 0
|
|
||||||
or calendar.thirdParties|length > 0
|
|
||||||
or calendar.users|length > 0
|
|
||||||
%}
|
|
||||||
<div class="item-row details">
|
|
||||||
<div class="item-col">
|
|
||||||
|
|
||||||
{% include 'ChillActivityBundle:Activity:concernedGroups.html.twig' with {'context': context, 'with_display': 'row', 'entity': calendar } %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{% if calendar.comment.comment is not empty %}
|
|
||||||
<div class="item-col comment">
|
|
||||||
{{ calendar.comment|chill_entity_render_box( { 'limit_lines': 3, 'metadata': false } ) }}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
</div>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if context != 'user' %}
|
|
||||||
{# TODO set this condition in configuration #}
|
|
||||||
<ul class="record_actions">
|
|
||||||
<li>
|
|
||||||
<a href="{{ path('chill_calendar_calendar_new', {'user_id': user_id, 'accompanying_period_id': accompanying_course_id}) }}" class="btn btn-create">
|
|
||||||
{{ 'Add a new calendar' | trans }}
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
{% endif %}
|
|
||||||
|
|
@ -4,6 +4,123 @@
|
|||||||
|
|
||||||
{% block title %}{{ 'Calendar list' |trans }}{% endblock title %}
|
{% block title %}{{ 'Calendar list' |trans }}{% endblock title %}
|
||||||
|
|
||||||
|
{% set user_id = null %}
|
||||||
|
{% set accompanying_course_id = accompanyingCourse.id %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
{% include 'ChillCalendarBundle:Calendar:list.html.twig' with {'context': 'accompanyingCourse'} %}
|
|
||||||
|
<h1>{{ 'Calendar list' |trans }}</h1>
|
||||||
|
|
||||||
|
{% if calendarItems|length == 0 %}
|
||||||
|
<p class="chill-no-data-statement">
|
||||||
|
{{ "There is no calendar items."|trans }}
|
||||||
|
<a href="{{ path('chill_calendar_calendar_new', {'user_id': user_id, 'accompanying_period_id': accompanying_course_id}) }}" class="btn btn-create button-small"></a>
|
||||||
|
</p>
|
||||||
|
{% else %}
|
||||||
|
|
||||||
|
<div class="flex-table list-records context-accompanyingCourse">
|
||||||
|
|
||||||
|
{% for calendar in calendarItems %}
|
||||||
|
|
||||||
|
<div class="item-bloc">
|
||||||
|
<div class="item-row main">
|
||||||
|
<div class="item-col">
|
||||||
|
|
||||||
|
{% if calendar.startDate and calendar.endDate %}
|
||||||
|
{% if calendar.endDate.diff(calendar.startDate).days >= 1 %}
|
||||||
|
<h3>{{ "From the day"|trans }} {{ calendar.startDate|format_datetime('medium', 'short') }} </h3>
|
||||||
|
<h3>{{ "to the day"|trans }} {{ calendar.endDate|format_datetime('medium', 'short') }}</h3>
|
||||||
|
{% else %}
|
||||||
|
<h3>{{ calendar.startDate|format_date('full') }} </h3>
|
||||||
|
<h3>{{ calendar.startDate|format_datetime('none', 'short', locale='fr') }} - {{ calendar.endDate|format_datetime('none', 'short', locale='fr') }}</h3>
|
||||||
|
|
||||||
|
<div class="duration">
|
||||||
|
<p>
|
||||||
|
<i class="fa fa-fw fa-hourglass-end"></i>
|
||||||
|
{{ calendar.endDate.diff(calendar.startDate)|date("%H:%M")}}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="item-col">
|
||||||
|
<ul class="list-content">
|
||||||
|
{% if calendar.user %}
|
||||||
|
<li>
|
||||||
|
<b>{{ 'by'|trans }}{{ calendar.user.usernameCanonical }}</b>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if calendar.mainUser is not empty %}
|
||||||
|
<li>
|
||||||
|
<b>{{ 'main user concerned'|trans }}: {{ calendar.mainUser.usernameCanonical }}</b>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
</ul>
|
||||||
|
<ul class="record_actions">
|
||||||
|
<li>
|
||||||
|
<a href="{{ path('chill_calendar_calendar_show', { 'id': calendar.id, 'user_id': user_id, 'accompanying_period_id': accompanying_course_id }) }}" class="btn btn-show "></a>
|
||||||
|
</li>
|
||||||
|
{# TOOD
|
||||||
|
{% if is_granted('CHILL_ACTIVITY_UPDATE', calendar) %}
|
||||||
|
#}
|
||||||
|
<li>
|
||||||
|
<a href="{{ path('chill_calendar_calendar_edit', { 'id': calendar.id, 'user_id': user_id, 'accompanying_period_id': accompanying_course_id }) }}" class="btn btn-update "></a>
|
||||||
|
</li>
|
||||||
|
{# TOOD
|
||||||
|
{% endif %}
|
||||||
|
{% if is_granted('CHILL_ACTIVITY_DELETE', calendar) %}
|
||||||
|
#}
|
||||||
|
<li>
|
||||||
|
<a href="{{ path('chill_calendar_calendar_delete', { 'id': calendar.id, 'user_id' : user_id, 'accompanying_period_id': accompanying_course_id } ) }}" class="btn btn-delete "></a>
|
||||||
|
</li>
|
||||||
|
{#
|
||||||
|
{% endif %}
|
||||||
|
#}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{%
|
||||||
|
if calendar.comment.comment is not empty
|
||||||
|
or calendar.users|length > 0
|
||||||
|
or calendar.thirdParties|length > 0
|
||||||
|
or calendar.users|length > 0
|
||||||
|
%}
|
||||||
|
<div class="item-row details">
|
||||||
|
<div class="item-col">
|
||||||
|
|
||||||
|
{% include 'ChillActivityBundle:Activity:concernedGroups.html.twig' with {'context': accompanyingCourse, 'with_display': 'row', 'entity': calendar } %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if calendar.comment.comment is not empty %}
|
||||||
|
<div class="item-col comment">
|
||||||
|
{{ calendar.comment|chill_entity_render_box( { 'limit_lines': 3, 'metadata': false } ) }}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
{% if calendarItems|length < paginator.getTotalItems %}
|
||||||
|
{{ chill_pagination(paginator) }}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
|
||||||
|
<ul class="record_actions">
|
||||||
|
<li>
|
||||||
|
<a href="{{ path('chill_calendar_calendar_new', {'user_id': user_id, 'accompanying_period_id': accompanying_course_id}) }}" class="btn btn-create">
|
||||||
|
{{ 'Add a new calendar' | trans }}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
@ -5,7 +5,10 @@
|
|||||||
{% block title %}{{ 'My calendar list' |trans }}{% endblock title %}
|
{% block title %}{{ 'My calendar list' |trans }}{% endblock title %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
{% include 'ChillCalendarBundle:Calendar:list.html.twig' with {'context': 'user'} %}
|
|
||||||
|
<h1>{{ 'My calendar list' |trans }}</h1>
|
||||||
|
<div id="myCalendar"></div>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block js %}
|
{% block js %}
|
||||||
|
@ -65,13 +65,21 @@
|
|||||||
|
|
||||||
<ul class="record_actions sticky-form-buttons">
|
<ul class="record_actions sticky-form-buttons">
|
||||||
<li class="cancel">
|
<li class="cancel">
|
||||||
<a class="btn btn-cancel" href="{{ path('chill_calendar_calendar_list', { 'accompanying_period_id': accompanying_course_id, 'user_id': user_id } ) }}">
|
<a class="btn btn-cancel" href="{{ path('chill_calendar_calendar_list',
|
||||||
|
{ 'accompanying_period_id': accompanying_course_id, 'user_id': user_id }) }}">
|
||||||
{{ 'Back to the list'|trans }}
|
{{ 'Back to the list'|trans }}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<a class="btn btn-create" href="{{ chill_path_add_return_path('chill_activity_activity_new',
|
||||||
|
{ 'accompanying_period_id': accompanying_course_id, 'activityData': activityData }) }}">
|
||||||
|
{{ 'Transform to activity'|trans }}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
{% if accompanyingCourse %}
|
{% if accompanyingCourse %}
|
||||||
<li>
|
<li>
|
||||||
<a class="btn btn-update" href="{{ path('chill_calendar_calendar_edit', { 'id': entity.id, 'accompanying_period_id': accompanying_course_id }) }}">
|
<a class="btn btn-update" href="{{ path('chill_calendar_calendar_edit',
|
||||||
|
{ 'id': entity.id, 'accompanying_period_id': accompanying_course_id }) }}">
|
||||||
{{ 'Edit'|trans }}
|
{{ 'Edit'|trans }}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
@ -24,3 +24,4 @@ Add a new calendar: Ajouter un nouveau rendez-vous
|
|||||||
The calendar item has been successfully removed.: Le rendez-vous a été supprimé
|
The calendar item has been successfully removed.: Le rendez-vous a été supprimé
|
||||||
From the day: Du
|
From the day: Du
|
||||||
to the day: au
|
to the day: au
|
||||||
|
Transform to activity: Transformer en échange
|
@ -23,6 +23,7 @@ use Chill\MainBundle\Security\Authorization\AbstractChillVoter;
|
|||||||
use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
|
use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
|
||||||
use Chill\MainBundle\Security\ProvideRoleHierarchyInterface;
|
use Chill\MainBundle\Security\ProvideRoleHierarchyInterface;
|
||||||
use Chill\DocStoreBundle\Entity\PersonDocument;
|
use Chill\DocStoreBundle\Entity\PersonDocument;
|
||||||
|
use Chill\MainBundle\Security\Resolver\CenterResolverDispatcher;
|
||||||
use Chill\PersonBundle\Entity\Person;
|
use Chill\PersonBundle\Entity\Person;
|
||||||
use Chill\MainBundle\Entity\User;
|
use Chill\MainBundle\Entity\User;
|
||||||
use Chill\PersonBundle\Security\Authorization\PersonVoter;
|
use Chill\PersonBundle\Security\Authorization\PersonVoter;
|
||||||
@ -42,30 +43,25 @@ class PersonDocumentVoter extends AbstractChillVoter implements ProvideRoleHiera
|
|||||||
const UPDATE = 'CHILL_PERSON_DOCUMENT_UPDATE';
|
const UPDATE = 'CHILL_PERSON_DOCUMENT_UPDATE';
|
||||||
const DELETE = 'CHILL_PERSON_DOCUMENT_DELETE';
|
const DELETE = 'CHILL_PERSON_DOCUMENT_DELETE';
|
||||||
|
|
||||||
/**
|
protected AuthorizationHelper $authorizationHelper;
|
||||||
* @var AuthorizationHelper
|
|
||||||
*/
|
|
||||||
protected $authorizationHelper;
|
|
||||||
|
|
||||||
/**
|
protected AccessDecisionManagerInterface $accessDecisionManager;
|
||||||
* @var AccessDecisionManagerInterface
|
|
||||||
*/
|
|
||||||
protected $accessDecisionManager;
|
|
||||||
|
|
||||||
/**
|
protected LoggerInterface $logger;
|
||||||
* @var LoggerInterface
|
|
||||||
*/
|
protected CenterResolverDispatcher $centerResolverDispatcher;
|
||||||
protected $logger;
|
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
AccessDecisionManagerInterface $accessDecisionManager,
|
AccessDecisionManagerInterface $accessDecisionManager,
|
||||||
AuthorizationHelper $authorizationHelper,
|
AuthorizationHelper $authorizationHelper,
|
||||||
LoggerInterface $logger
|
LoggerInterface $logger//,
|
||||||
|
//CenterResolverDispatcher $centerResolverDispatcher
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
$this->accessDecisionManager = $accessDecisionManager;
|
$this->accessDecisionManager = $accessDecisionManager;
|
||||||
$this->authorizationHelper = $authorizationHelper;
|
$this->authorizationHelper = $authorizationHelper;
|
||||||
$this->logger = $logger;
|
$this->logger = $logger;
|
||||||
|
//$this->centerResolverDispatcher = $centerResolverDispatcher;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getRoles()
|
public function getRoles()
|
||||||
@ -85,7 +81,8 @@ class PersonDocumentVoter extends AbstractChillVoter implements ProvideRoleHiera
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($subject instanceof Person && $attribute === self::CREATE) {
|
if ($subject instanceof Person
|
||||||
|
&& \in_array($attribute, [self::CREATE, self::SEE])) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -107,6 +104,8 @@ class PersonDocumentVoter extends AbstractChillVoter implements ProvideRoleHiera
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$center = $this->centerResolverDispatcher->resolveCenter($subject);
|
||||||
|
|
||||||
if ($subject instanceof PersonDocument) {
|
if ($subject instanceof PersonDocument) {
|
||||||
return $this->authorizationHelper->userHasAccess($token->getUser(), $subject, $attribute);
|
return $this->authorizationHelper->userHasAccess($token->getUser(), $subject, $attribute);
|
||||||
|
|
||||||
|
@ -3,6 +3,11 @@
|
|||||||
namespace Chill\MainBundle;
|
namespace Chill\MainBundle;
|
||||||
|
|
||||||
use Chill\MainBundle\Routing\LocalMenuBuilderInterface;
|
use Chill\MainBundle\Routing\LocalMenuBuilderInterface;
|
||||||
|
use Chill\MainBundle\Search\SearchInterface;
|
||||||
|
use Chill\MainBundle\Security\Authorization\ChillVoterInterface;
|
||||||
|
use Chill\MainBundle\Security\ProvideRoleInterface;
|
||||||
|
use Chill\MainBundle\Security\Resolver\CenterResolverInterface;
|
||||||
|
use Chill\MainBundle\Security\Resolver\ScopeResolverInterface;
|
||||||
use Symfony\Component\HttpKernel\Bundle\Bundle;
|
use Symfony\Component\HttpKernel\Bundle\Bundle;
|
||||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||||
use Chill\MainBundle\DependencyInjection\CompilerPass\SearchableServicesCompilerPass;
|
use Chill\MainBundle\DependencyInjection\CompilerPass\SearchableServicesCompilerPass;
|
||||||
@ -27,6 +32,12 @@ class ChillMainBundle extends Bundle
|
|||||||
|
|
||||||
$container->registerForAutoconfiguration(LocalMenuBuilderInterface::class)
|
$container->registerForAutoconfiguration(LocalMenuBuilderInterface::class)
|
||||||
->addTag('chill.menu_builder');
|
->addTag('chill.menu_builder');
|
||||||
|
$container->registerForAutoconfiguration(ProvideRoleInterface::class)
|
||||||
|
->addTag('chill.role');
|
||||||
|
$container->registerForAutoconfiguration(CenterResolverInterface::class)
|
||||||
|
->addTag('chill_main.center_resolver');
|
||||||
|
$container->registerForAutoconfiguration(ScopeResolverInterface::class)
|
||||||
|
->addTag('chill_main.scope_resolver');
|
||||||
|
|
||||||
$container->addCompilerPass(new SearchableServicesCompilerPass());
|
$container->addCompilerPass(new SearchableServicesCompilerPass());
|
||||||
$container->addCompilerPass(new ConfigConsistencyCompilerPass());
|
$container->addCompilerPass(new ConfigConsistencyCompilerPass());
|
||||||
|
@ -19,6 +19,10 @@
|
|||||||
|
|
||||||
namespace Chill\MainBundle\DependencyInjection;
|
namespace Chill\MainBundle\DependencyInjection;
|
||||||
|
|
||||||
|
use Chill\MainBundle\Doctrine\DQL\STContains;
|
||||||
|
use Chill\MainBundle\Doctrine\DQL\StrictWordSimilarityOPS;
|
||||||
|
use Chill\MainBundle\Entity\UserJob;
|
||||||
|
use Chill\MainBundle\Form\UserJobType;
|
||||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||||
use Symfony\Component\Config\FileLocator;
|
use Symfony\Component\Config\FileLocator;
|
||||||
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
|
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
|
||||||
@ -183,6 +187,8 @@ class ChillMainExtension extends Extension implements PrependExtensionInterface,
|
|||||||
'JSONB_EXISTS_IN_ARRAY' => JsonbExistsInArray::class,
|
'JSONB_EXISTS_IN_ARRAY' => JsonbExistsInArray::class,
|
||||||
'SIMILARITY' => Similarity::class,
|
'SIMILARITY' => Similarity::class,
|
||||||
'OVERLAPSI' => OverlapsI::class,
|
'OVERLAPSI' => OverlapsI::class,
|
||||||
|
'STRICT_WORD_SIMILARITY_OPS' => StrictWordSimilarityOPS::class,
|
||||||
|
'ST_CONTAINS' => STContains::class,
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
'hydrators' => [
|
'hydrators' => [
|
||||||
@ -264,6 +270,27 @@ class ChillMainExtension extends Extension implements PrependExtensionInterface,
|
|||||||
protected function prependCruds(ContainerBuilder $container)
|
protected function prependCruds(ContainerBuilder $container)
|
||||||
{
|
{
|
||||||
$container->prependExtensionConfig('chill_main', [
|
$container->prependExtensionConfig('chill_main', [
|
||||||
|
'cruds' => [
|
||||||
|
[
|
||||||
|
'class' => UserJob::class,
|
||||||
|
'name' => 'admin_user_job',
|
||||||
|
'base_path' => '/admin/main/user-job',
|
||||||
|
'base_role' => 'ROLE_ADMIN',
|
||||||
|
'form_class' => UserJobType::class,
|
||||||
|
'actions' => [
|
||||||
|
'index' => [
|
||||||
|
'role' => 'ROLE_ADMIN',
|
||||||
|
'template' => '@ChillMain/UserJob/index.html.twig',
|
||||||
|
],
|
||||||
|
'new' => [
|
||||||
|
'role' => 'ROLE_ADMIN'
|
||||||
|
],
|
||||||
|
'edit' => [
|
||||||
|
'role' => 'ROLE_ADMIN'
|
||||||
|
]
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
'apis' => [
|
'apis' => [
|
||||||
[
|
[
|
||||||
'class' => \Chill\MainBundle\Entity\Address::class,
|
'class' => \Chill\MainBundle\Entity\Address::class,
|
||||||
@ -371,6 +398,26 @@ class ChillMainExtension extends Extension implements PrependExtensionInterface,
|
|||||||
],
|
],
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
'class' => \Chill\MainBundle\Entity\Scope::class,
|
||||||
|
'name' => 'scope',
|
||||||
|
'base_path' => '/api/1.0/main/scope',
|
||||||
|
'base_role' => 'ROLE_USER',
|
||||||
|
'actions' => [
|
||||||
|
'_index' => [
|
||||||
|
'methods' => [
|
||||||
|
Request::METHOD_GET => true,
|
||||||
|
Request::METHOD_HEAD => true
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'_entity' => [
|
||||||
|
'methods' => [
|
||||||
|
Request::METHOD_GET => true,
|
||||||
|
Request::METHOD_HEAD => true,
|
||||||
|
]
|
||||||
|
],
|
||||||
|
]
|
||||||
|
],
|
||||||
]
|
]
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
52
src/Bundle/ChillMainBundle/Doctrine/DQL/STContains.php
Normal file
52
src/Bundle/ChillMainBundle/Doctrine/DQL/STContains.php
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
<?php
|
||||||
|
/*
|
||||||
|
* Chill is a software for social workers
|
||||||
|
* Copyright (C) 2018 Champs-Libres Coopérative <info@champs-libres.coop>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
namespace Chill\MainBundle\Doctrine\DQL;
|
||||||
|
|
||||||
|
use Doctrine\ORM\Query\AST\Functions\FunctionNode;
|
||||||
|
use Doctrine\ORM\Query\Lexer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Geometry function 'ST_CONTAINS', added by postgis
|
||||||
|
*/
|
||||||
|
class STContains extends FunctionNode
|
||||||
|
{
|
||||||
|
private $firstPart;
|
||||||
|
|
||||||
|
private $secondPart;
|
||||||
|
|
||||||
|
public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker)
|
||||||
|
{
|
||||||
|
return 'ST_CONTAINS('.$this->firstPart->dispatch($sqlWalker).
|
||||||
|
', ' . $this->secondPart->dispatch($sqlWalker) .")";
|
||||||
|
}
|
||||||
|
|
||||||
|
public function parse(\Doctrine\ORM\Query\Parser $parser)
|
||||||
|
{
|
||||||
|
$parser->match(Lexer::T_IDENTIFIER);
|
||||||
|
$parser->match(Lexer::T_OPEN_PARENTHESIS);
|
||||||
|
|
||||||
|
$this->firstPart = $parser->StringPrimary();
|
||||||
|
|
||||||
|
$parser->match(Lexer::T_COMMA);
|
||||||
|
|
||||||
|
$this->secondPart = $parser->StringPrimary();
|
||||||
|
|
||||||
|
$parser->match(Lexer::T_CLOSE_PARENTHESIS);
|
||||||
|
}
|
||||||
|
}
|
@ -21,11 +21,6 @@ namespace Chill\MainBundle\Doctrine\DQL;
|
|||||||
use Doctrine\ORM\Query\AST\Functions\FunctionNode;
|
use Doctrine\ORM\Query\AST\Functions\FunctionNode;
|
||||||
use Doctrine\ORM\Query\Lexer;
|
use Doctrine\ORM\Query\Lexer;
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
*
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
class Similarity extends FunctionNode
|
class Similarity extends FunctionNode
|
||||||
{
|
{
|
||||||
private $firstPart;
|
private $firstPart;
|
||||||
|
@ -0,0 +1,34 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Chill\MainBundle\Doctrine\DQL;
|
||||||
|
|
||||||
|
use Doctrine\ORM\Query\Lexer;
|
||||||
|
use Doctrine\ORM\Query\Parser;
|
||||||
|
use Doctrine\ORM\Query\SqlWalker;
|
||||||
|
|
||||||
|
class StrictWordSimilarityOPS extends \Doctrine\ORM\Query\AST\Functions\FunctionNode
|
||||||
|
{
|
||||||
|
private $firstPart;
|
||||||
|
|
||||||
|
private $secondPart;
|
||||||
|
|
||||||
|
public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker)
|
||||||
|
{
|
||||||
|
return $this->firstPart->dispatch($sqlWalker).
|
||||||
|
' <<% ' . $this->secondPart->dispatch($sqlWalker);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function parse(\Doctrine\ORM\Query\Parser $parser)
|
||||||
|
{
|
||||||
|
$parser->match(Lexer::T_IDENTIFIER);
|
||||||
|
$parser->match(Lexer::T_OPEN_PARENTHESIS);
|
||||||
|
|
||||||
|
$this->firstPart = $parser->StringPrimary();
|
||||||
|
|
||||||
|
$parser->match(Lexer::T_COMMA);
|
||||||
|
|
||||||
|
$this->secondPart = $parser->StringPrimary();
|
||||||
|
|
||||||
|
$parser->match(Lexer::T_CLOSE_PARENTHESIS);
|
||||||
|
}
|
||||||
|
}
|
@ -21,7 +21,7 @@ class PointType extends Type {
|
|||||||
*
|
*
|
||||||
* @param array $fieldDeclaration
|
* @param array $fieldDeclaration
|
||||||
* @param AbstractPlatform $platform
|
* @param AbstractPlatform $platform
|
||||||
* @return type
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function getSqlDeclaration(array $fieldDeclaration, AbstractPlatform $platform)
|
public function getSqlDeclaration(array $fieldDeclaration, AbstractPlatform $platform)
|
||||||
{
|
{
|
||||||
@ -32,7 +32,7 @@ class PointType extends Type {
|
|||||||
*
|
*
|
||||||
* @param type $value
|
* @param type $value
|
||||||
* @param AbstractPlatform $platform
|
* @param AbstractPlatform $platform
|
||||||
* @return Point
|
* @return ?Point
|
||||||
*/
|
*/
|
||||||
public function convertToPHPValue($value, AbstractPlatform $platform)
|
public function convertToPHPValue($value, AbstractPlatform $platform)
|
||||||
{
|
{
|
||||||
|
@ -383,6 +383,16 @@ class Address
|
|||||||
;
|
;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function createFromAddressReference(AddressReference $original): Address
|
||||||
|
{
|
||||||
|
return (new Address())
|
||||||
|
->setPoint($original->getPoint())
|
||||||
|
->setPostcode($original->getPostcode())
|
||||||
|
->setStreet($original->getStreet())
|
||||||
|
->setStreetNumber($original->getStreetNumber())
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
public function getStreet(): ?string
|
public function getStreet(): ?string
|
||||||
{
|
{
|
||||||
return $this->street;
|
return $this->street;
|
||||||
|
@ -0,0 +1,8 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Chill\MainBundle\Entity;
|
||||||
|
|
||||||
|
interface HasCentersInterface
|
||||||
|
{
|
||||||
|
public function getCenters(): ?iterable;
|
||||||
|
}
|
11
src/Bundle/ChillMainBundle/Entity/HasScopesInterface.php
Normal file
11
src/Bundle/ChillMainBundle/Entity/HasScopesInterface.php
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Chill\MainBundle\Entity;
|
||||||
|
|
||||||
|
interface HasScopesInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @return array|Scope[]
|
||||||
|
*/
|
||||||
|
public function getScopes(): iterable;
|
||||||
|
}
|
@ -5,6 +5,7 @@ namespace Chill\MainBundle\Entity;
|
|||||||
use Doctrine\ORM\Mapping as ORM;
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
use Doctrine\Common\Collections\Collection;
|
use Doctrine\Common\Collections\Collection;
|
||||||
use Doctrine\Common\Collections\ArrayCollection;
|
use Doctrine\Common\Collections\ArrayCollection;
|
||||||
|
use Chill\MainBundle\Entity\UserJob;
|
||||||
use Symfony\Component\Security\Core\User\AdvancedUserInterface;
|
use Symfony\Component\Security\Core\User\AdvancedUserInterface;
|
||||||
use Symfony\Component\Validator\Context\ExecutionContextInterface;
|
use Symfony\Component\Validator\Context\ExecutionContextInterface;
|
||||||
use Symfony\Component\Serializer\Annotation\DiscriminatorMap;
|
use Symfony\Component\Serializer\Annotation\DiscriminatorMap;
|
||||||
@ -43,10 +44,16 @@ class User implements AdvancedUserInterface {
|
|||||||
* @ORM\Column(
|
* @ORM\Column(
|
||||||
* type="string",
|
* type="string",
|
||||||
* length=80,
|
* length=80,
|
||||||
* unique=true)
|
* unique=true,
|
||||||
|
* nullable=true)
|
||||||
*/
|
*/
|
||||||
private $usernameCanonical;
|
private $usernameCanonical;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\Column(type="string", length=200)
|
||||||
|
*/
|
||||||
|
private string $label = '';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var string
|
* @var string
|
||||||
*
|
*
|
||||||
@ -113,6 +120,24 @@ class User implements AdvancedUserInterface {
|
|||||||
*/
|
*/
|
||||||
private $attributes;
|
private $attributes;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var Center|null
|
||||||
|
* @ORM\ManyToOne(targetEntity=Center::class)
|
||||||
|
*/
|
||||||
|
private ?Center $mainCenter = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var Scope|null
|
||||||
|
* @ORM\ManyToOne(targetEntity=Scope::class)
|
||||||
|
*/
|
||||||
|
private ?Scope $mainScope = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var UserJob|null
|
||||||
|
* @ORM\ManyToOne(targetEntity=UserJob::class)
|
||||||
|
*/
|
||||||
|
private ?UserJob $userJob = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* User constructor.
|
* User constructor.
|
||||||
*/
|
*/
|
||||||
@ -126,7 +151,7 @@ class User implements AdvancedUserInterface {
|
|||||||
*/
|
*/
|
||||||
public function __toString()
|
public function __toString()
|
||||||
{
|
{
|
||||||
return $this->getUsername();
|
return $this->getLabel();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -149,6 +174,10 @@ class User implements AdvancedUserInterface {
|
|||||||
{
|
{
|
||||||
$this->username = $name;
|
$this->username = $name;
|
||||||
|
|
||||||
|
if (empty($this->getLabel())) {
|
||||||
|
$this->setLabel($name);
|
||||||
|
}
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -384,4 +413,76 @@ class User implements AdvancedUserInterface {
|
|||||||
|
|
||||||
return $this->attributes;
|
return $this->attributes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getLabel(): string
|
||||||
|
{
|
||||||
|
return $this->label;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $label
|
||||||
|
* @return User
|
||||||
|
*/
|
||||||
|
public function setLabel(string $label): User
|
||||||
|
{
|
||||||
|
$this->label = $label;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Center|null
|
||||||
|
*/
|
||||||
|
public function getMainCenter(): ?Center
|
||||||
|
{
|
||||||
|
return $this->mainCenter;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Center|null $mainCenter
|
||||||
|
* @return User
|
||||||
|
*/
|
||||||
|
public function setMainCenter(?Center $mainCenter): User
|
||||||
|
{
|
||||||
|
$this->mainCenter = $mainCenter;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Scope|null
|
||||||
|
*/
|
||||||
|
public function getMainScope(): ?Scope
|
||||||
|
{
|
||||||
|
return $this->mainScope;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Scope|null $mainScope
|
||||||
|
* @return User
|
||||||
|
*/
|
||||||
|
public function setMainScope(?Scope $mainScope): User
|
||||||
|
{
|
||||||
|
$this->mainScope = $mainScope;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return UserJob|null
|
||||||
|
*/
|
||||||
|
public function getUserJob(): ?UserJob
|
||||||
|
{
|
||||||
|
return $this->userJob;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param UserJob|null $userJob
|
||||||
|
* @return User
|
||||||
|
*/
|
||||||
|
public function setUserJob(?UserJob $userJob): User
|
||||||
|
{
|
||||||
|
$this->userJob = $userJob;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
76
src/Bundle/ChillMainBundle/Entity/UserJob.php
Normal file
76
src/Bundle/ChillMainBundle/Entity/UserJob.php
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Chill\MainBundle\Entity;
|
||||||
|
|
||||||
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\Entity
|
||||||
|
* @ORM\Table("chill_main_user_job")
|
||||||
|
*/
|
||||||
|
class UserJob
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\Id
|
||||||
|
* @ORM\Column(name="id", type="integer")
|
||||||
|
* @ORM\GeneratedValue(strategy="AUTO")
|
||||||
|
*/
|
||||||
|
protected ?int $id = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array|string[]A
|
||||||
|
* @ORM\Column(name="label", type="json")
|
||||||
|
*/
|
||||||
|
protected array $label = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var bool
|
||||||
|
* @ORM\Column(name="active", type="boolean")
|
||||||
|
*/
|
||||||
|
protected bool $active = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return int|null
|
||||||
|
*/
|
||||||
|
public function getId(): ?int
|
||||||
|
{
|
||||||
|
return $this->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array|string[]
|
||||||
|
*/
|
||||||
|
public function getLabel(): array
|
||||||
|
{
|
||||||
|
return $this->label;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array|string[] $label
|
||||||
|
* @return UserJob
|
||||||
|
*/
|
||||||
|
public function setLabel(array $label): UserJob
|
||||||
|
{
|
||||||
|
$this->label = $label;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isActive(): bool
|
||||||
|
{
|
||||||
|
return $this->active;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param bool $active
|
||||||
|
* @return UserJob
|
||||||
|
*/
|
||||||
|
public function setActive(bool $active): UserJob
|
||||||
|
{
|
||||||
|
$this->active = $active;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
}
|
36
src/Bundle/ChillMainBundle/Form/Event/CustomizeFormEvent.php
Normal file
36
src/Bundle/ChillMainBundle/Form/Event/CustomizeFormEvent.php
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Chill\MainBundle\Form\Event;
|
||||||
|
|
||||||
|
use Symfony\Component\Form\FormBuilderInterface;
|
||||||
|
|
||||||
|
class CustomizeFormEvent extends \Symfony\Component\EventDispatcher\Event
|
||||||
|
{
|
||||||
|
const NAME = 'chill_main.customize_form';
|
||||||
|
|
||||||
|
protected string $type;
|
||||||
|
|
||||||
|
protected FormBuilderInterface $builder;
|
||||||
|
|
||||||
|
public function __construct(string $type, FormBuilderInterface $builder)
|
||||||
|
{
|
||||||
|
$this->type = $type;
|
||||||
|
$this->builder = $builder;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getType(): string
|
||||||
|
{
|
||||||
|
return $this->type;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return FormBuilderInterface
|
||||||
|
*/
|
||||||
|
public function getBuilder(): FormBuilderInterface
|
||||||
|
{
|
||||||
|
return $this->builder;
|
||||||
|
}
|
||||||
|
}
|
@ -95,9 +95,12 @@ class CenterType extends AbstractType
|
|||||||
public function configureOptions(OptionsResolver $resolver)
|
public function configureOptions(OptionsResolver $resolver)
|
||||||
{
|
{
|
||||||
if (count($this->reachableCenters) > 1) {
|
if (count($this->reachableCenters) > 1) {
|
||||||
$resolver->setDefault('class', Center::class);
|
$resolver->setDefault('class', Center::class)
|
||||||
$resolver->setDefault('choices', $this->reachableCenters);
|
->setDefault('choices', $this->reachableCenters)
|
||||||
|
->setDefault('placeholder', 'Pick a center')
|
||||||
|
;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -146,14 +146,7 @@ class ScopePickerType extends AbstractType
|
|||||||
->setParameter('center', $center->getId())
|
->setParameter('center', $center->getId())
|
||||||
// role constraints
|
// role constraints
|
||||||
->andWhere($qb->expr()->in('rs.role', ':roles'))
|
->andWhere($qb->expr()->in('rs.role', ':roles'))
|
||||||
->setParameter(
|
->setParameter('roles', $roles)
|
||||||
'roles', \array_map(
|
|
||||||
function (Role $role) {
|
|
||||||
return $role->getRole();
|
|
||||||
},
|
|
||||||
$roles
|
|
||||||
)
|
|
||||||
)
|
|
||||||
// user contraint
|
// user contraint
|
||||||
->andWhere(':user MEMBER OF gc.users')
|
->andWhere(':user MEMBER OF gc.users')
|
||||||
->setParameter('user', $this->tokenStorage->getToken()->getUser());
|
->setParameter('user', $this->tokenStorage->getToken()->getUser());
|
||||||
|
27
src/Bundle/ChillMainBundle/Form/UserJobType.php
Normal file
27
src/Bundle/ChillMainBundle/Form/UserJobType.php
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Chill\MainBundle\Form;
|
||||||
|
|
||||||
|
use Chill\MainBundle\Form\Type\TranslatableStringFormType;
|
||||||
|
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||||
|
use Symfony\Component\Form\FormBuilderInterface;
|
||||||
|
|
||||||
|
class UserJobType extends \Symfony\Component\Form\AbstractType
|
||||||
|
{
|
||||||
|
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||||
|
{
|
||||||
|
$builder
|
||||||
|
->add('label', TranslatableStringFormType::class, [
|
||||||
|
'label' => 'Label',
|
||||||
|
'required' => true
|
||||||
|
])
|
||||||
|
->add('active', ChoiceType::class, [
|
||||||
|
'choices' => [
|
||||||
|
'Active' => true,
|
||||||
|
'Inactive' => false
|
||||||
|
]
|
||||||
|
])
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -2,7 +2,15 @@
|
|||||||
|
|
||||||
namespace Chill\MainBundle\Form;
|
namespace Chill\MainBundle\Form;
|
||||||
|
|
||||||
|
use Chill\MainBundle\Entity\Center;
|
||||||
|
use Chill\MainBundle\Entity\Scope;
|
||||||
|
use Chill\MainBundle\Entity\UserJob;
|
||||||
|
use Chill\MainBundle\Templating\TranslatableStringHelper;
|
||||||
|
use Doctrine\ORM\EntityRepository;
|
||||||
|
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
|
||||||
use Symfony\Component\Form\AbstractType;
|
use Symfony\Component\Form\AbstractType;
|
||||||
|
use Symfony\Component\Form\Extension\Core\Type\EmailType;
|
||||||
|
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
||||||
use Symfony\Component\Form\FormBuilderInterface;
|
use Symfony\Component\Form\FormBuilderInterface;
|
||||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||||
@ -16,6 +24,16 @@ use Chill\MainBundle\Form\UserPasswordType;
|
|||||||
|
|
||||||
class UserType extends AbstractType
|
class UserType extends AbstractType
|
||||||
{
|
{
|
||||||
|
private TranslatableStringHelper $translatableStringHelper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param TranslatableStringHelper $translatableStringHelper
|
||||||
|
*/
|
||||||
|
public function __construct(TranslatableStringHelper $translatableStringHelper)
|
||||||
|
{
|
||||||
|
$this->translatableStringHelper = $translatableStringHelper;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param FormBuilderInterface $builder
|
* @param FormBuilderInterface $builder
|
||||||
* @param array $options
|
* @param array $options
|
||||||
@ -24,7 +42,40 @@ class UserType extends AbstractType
|
|||||||
{
|
{
|
||||||
$builder
|
$builder
|
||||||
->add('username')
|
->add('username')
|
||||||
->add('email')
|
->add('email', EmailType::class, [
|
||||||
|
'required' => true
|
||||||
|
])
|
||||||
|
->add('label', TextType::class)
|
||||||
|
->add('mainCenter', EntityType::class, [
|
||||||
|
'label' => 'main center',
|
||||||
|
'required' => false,
|
||||||
|
'placeholder' => 'choose a main center',
|
||||||
|
'class' => Center::class,
|
||||||
|
'query_builder' => function (EntityRepository $er) {
|
||||||
|
$qb = $er->createQueryBuilder('c');
|
||||||
|
$qb->addOrderBy('c.name');
|
||||||
|
|
||||||
|
return $qb;
|
||||||
|
}
|
||||||
|
])
|
||||||
|
->add('mainScope', EntityType::class, [
|
||||||
|
'label' => 'Choose a main scope',
|
||||||
|
'required' => false,
|
||||||
|
'placeholder' => 'choose a main scope',
|
||||||
|
'class' => Scope::class,
|
||||||
|
'choice_label' => function (Scope $c) {
|
||||||
|
return $this->translatableStringHelper->localize($c->getName());
|
||||||
|
},
|
||||||
|
])
|
||||||
|
->add('userJob', EntityType::class, [
|
||||||
|
'label' => 'Choose a job',
|
||||||
|
'required' => false,
|
||||||
|
'placeholder' => 'choose a job',
|
||||||
|
'class' => UserJob::class,
|
||||||
|
'choice_label' => function (UserJob $c) {
|
||||||
|
return $this->translatableStringHelper->localize($c->getLabel());
|
||||||
|
},
|
||||||
|
])
|
||||||
;
|
;
|
||||||
if ($options['is_creation']) {
|
if ($options['is_creation']) {
|
||||||
$builder->add('plainPassword', RepeatedType::class, array(
|
$builder->add('plainPassword', RepeatedType::class, array(
|
||||||
|
@ -36,6 +36,14 @@ final class AddressReferenceRepository implements ObjectRepository
|
|||||||
return $this->repository->findAll();
|
return $this->repository->findAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function countAll(): int
|
||||||
|
{
|
||||||
|
$qb = $this->repository->createQueryBuilder('ar');
|
||||||
|
$qb->select('count(ar.id)');
|
||||||
|
|
||||||
|
return $qb->getQuery()->getSingleScalarResult();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return AddressReference[]
|
* @return AddressReference[]
|
||||||
*/
|
*/
|
||||||
|
@ -98,6 +98,9 @@ section.chill-entity {
|
|||||||
&.date-since {}
|
&.date-since {}
|
||||||
&.date-until {}
|
&.date-until {}
|
||||||
}
|
}
|
||||||
|
.address-more {
|
||||||
|
margin-bottom: 1em;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// used for comment-embeddable
|
// used for comment-embeddable
|
||||||
|
17
src/Bundle/ChillMainBundle/Resources/public/lib/api/scope.js
Normal file
17
src/Bundle/ChillMainBundle/Resources/public/lib/api/scope.js
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
const fetchScopes = () => {
|
||||||
|
return window.fetch('/api/1.0/main/scope.json').then(response => {
|
||||||
|
if (response.ok) {
|
||||||
|
return response.json();
|
||||||
|
}
|
||||||
|
}).then(data => {
|
||||||
|
console.log(data);
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
console.log(data);
|
||||||
|
resolve(data.results);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export {
|
||||||
|
fetchScopes
|
||||||
|
};
|
@ -52,7 +52,7 @@ Fields.forEach(function(field) {
|
|||||||
ClassicEditor
|
ClassicEditor
|
||||||
.create( field )
|
.create( field )
|
||||||
.then( editor => {
|
.then( editor => {
|
||||||
console.log( 'CkEditor was initialized', editor );
|
//console.log( 'CkEditor was initialized', editor );
|
||||||
})
|
})
|
||||||
.catch( error => {
|
.catch( error => {
|
||||||
console.error( error.stack );
|
console.error( error.stack );
|
||||||
|
@ -1,24 +1,15 @@
|
|||||||
<template>
|
<template>
|
||||||
<add-address
|
<add-address
|
||||||
v-bind:key="context.entity.type"
|
v-bind:key="key"
|
||||||
v-bind:context="context"
|
v-bind:context="context"
|
||||||
v-bind:options="addAddress.options"
|
v-bind:options="options"
|
||||||
v-bind:result="addAddress.result"
|
v-bind:addressChangedCallback="submitAddress"
|
||||||
@submitAddress="submitAddress"
|
|
||||||
ref="addAddress">
|
ref="addAddress">
|
||||||
</add-address>
|
</add-address>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
/*
|
|
||||||
* Address component is a uniq component for many contexts.
|
|
||||||
* Allow to create/attach/edit an address to
|
|
||||||
* - a person (new or edit address),
|
|
||||||
* - a household (move or edit address)
|
|
||||||
*
|
|
||||||
* */
|
|
||||||
import AddAddress from './components/AddAddress.vue';
|
import AddAddress from './components/AddAddress.vue';
|
||||||
import { patchAddress } from "./api";
|
|
||||||
import { postAddressToHousehold, postAddressToPerson } from "ChillPersonAssets/vuejs/_api/AddAddress";
|
import { postAddressToHousehold, postAddressToPerson } from "ChillPersonAssets/vuejs/_api/AddAddress";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@ -26,60 +17,46 @@ export default {
|
|||||||
components: {
|
components: {
|
||||||
AddAddress
|
AddAddress
|
||||||
},
|
},
|
||||||
data() {
|
props: ['addAddress'],
|
||||||
return {
|
computed: {
|
||||||
context: {
|
context() {
|
||||||
edit: window.mode === 'edit',
|
return this.addAddress.context;
|
||||||
entity: {
|
|
||||||
type: window.entityType,
|
|
||||||
id: window.entityId
|
|
||||||
},
|
},
|
||||||
addressId: window.addressId | null,
|
options() {
|
||||||
backUrl: window.backUrl,
|
return this.addAddress.options;
|
||||||
valid: {
|
|
||||||
from: new Date()
|
|
||||||
},
|
},
|
||||||
},
|
key() {
|
||||||
addAddress: {
|
return (this.context.edit) ? 'address_' + this.context.addressId
|
||||||
options: {
|
: this.context.target.name + '_' + this.context.target.id ;
|
||||||
|
|
||||||
/// Options override default.
|
|
||||||
/// null value take default component value defined in AddAddress data()
|
|
||||||
button: {
|
|
||||||
text: {
|
|
||||||
create: window.buttonText || null,
|
|
||||||
edit: window.buttonText || null
|
|
||||||
},
|
|
||||||
size: window.buttonSize || null,
|
|
||||||
displayText: window.buttonDisplayText //boolean, default: true
|
|
||||||
},
|
|
||||||
|
|
||||||
/// Modal title text if create or edit address (trans chain, see i18n)
|
|
||||||
title: {
|
|
||||||
create: window.modalTitle || null,
|
|
||||||
edit: window.modalTitle || null
|
|
||||||
},
|
|
||||||
|
|
||||||
/// Display each step in page or Modal
|
|
||||||
bindModal: {
|
|
||||||
step1: window.binModalStep1, //boolean, default: true
|
|
||||||
step2: window.binModalStep2 //boolean, default: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
mounted() {
|
||||||
|
//console.log('AddAddress: data context', this.context);
|
||||||
|
//console.log('AddAddress: data options', this.options);
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
displayErrors() {
|
displayErrors() {
|
||||||
return this.$refs.addAddress.errorMsg;
|
return this.$refs.addAddress.errorMsg;
|
||||||
},
|
},
|
||||||
submitAddress() {
|
submitAddress(payload) {
|
||||||
console.log('@@@ click on Submit Address Button');
|
console.log('@@@ click on Submit Address Button', payload);
|
||||||
let payload = this.$refs.addAddress.submitNewAddress(); // Cast child method
|
|
||||||
this.addDateToAddressAndPostAddressTo(payload);
|
// Existing address
|
||||||
|
if (this.context.edit) {
|
||||||
|
|
||||||
|
// address is already linked, just finish !
|
||||||
|
this.$refs.addAddress.afterLastPaneAction({});
|
||||||
|
|
||||||
|
// New created address
|
||||||
|
} else {
|
||||||
|
this.postAddressTo(payload);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
addDateToAddressAndPostAddressTo(payload)
|
|
||||||
{
|
/*
|
||||||
|
* Patch date
|
||||||
|
* TO MOVE in DatePane
|
||||||
|
addDateToAddressAndPostAddressTo(payload) {
|
||||||
payload.body = {
|
payload.body = {
|
||||||
validFrom: {
|
validFrom: {
|
||||||
datetime: `${this.context.valid.from.toISOString().split('T')[0]}T00:00:00+0100`
|
datetime: `${this.context.valid.from.toISOString().split('T')[0]}T00:00:00+0100`
|
||||||
@ -100,19 +77,24 @@ export default {
|
|||||||
this.$refs.addAddress.flag.loading = false;
|
this.$refs.addAddress.flag.loading = false;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
*/
|
||||||
|
|
||||||
postAddressTo(payload)
|
/*
|
||||||
{
|
* Post new created address to targetEntity
|
||||||
console.log('postAddressTo', payload.entity);
|
*/
|
||||||
if (!this.context.edit) {
|
postAddressTo(payload) {
|
||||||
switch (payload.entity) {
|
console.log('postAddress', payload.addressId, 'To', payload.target, payload.targetId);
|
||||||
|
switch (payload.target) {
|
||||||
case 'household':
|
case 'household':
|
||||||
postAddressToHousehold(payload.entityId, payload.addressId)
|
postAddressToHousehold(payload.targetId, payload.addressId)
|
||||||
.then(household => new Promise((resolve, reject) => {
|
.then(address => new Promise((resolve, reject) => {
|
||||||
console.log('..toHousehold', household);
|
console.log('..household address', address);
|
||||||
this.$refs.addAddress.flag.loading = false;
|
this.$refs.addAddress.flag.loading = false;
|
||||||
this.$refs.addAddress.flag.success = true;
|
this.$refs.addAddress.flag.success = true;
|
||||||
window.location.assign(this.context.backUrl);
|
|
||||||
|
// finish
|
||||||
|
this.$refs.addAddress.afterLastPaneAction({ addressId: address.address_id });
|
||||||
|
|
||||||
resolve();
|
resolve();
|
||||||
}))
|
}))
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
@ -122,12 +104,15 @@ export default {
|
|||||||
;
|
;
|
||||||
break;
|
break;
|
||||||
case 'person':
|
case 'person':
|
||||||
postAddressToPerson(payload.entityId, payload.addressId)
|
postAddressToPerson(payload.targetId, payload.addressId)
|
||||||
.then(person => new Promise((resolve, reject) => {
|
.then(address => new Promise((resolve, reject) => {
|
||||||
console.log('..toPerson', person);
|
console.log('..person address', address);
|
||||||
this.$refs.addAddress.flag.loading = false;
|
this.$refs.addAddress.flag.loading = false;
|
||||||
this.$refs.addAddress.flag.success = true;
|
this.$refs.addAddress.flag.success = true;
|
||||||
window.location.assign(this.context.backUrl);
|
|
||||||
|
// finish
|
||||||
|
this.$refs.addAddress.afterLastPaneAction({ addressId: address.address_id });
|
||||||
|
|
||||||
resolve();
|
resolve();
|
||||||
}))
|
}))
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
@ -136,14 +121,13 @@ export default {
|
|||||||
})
|
})
|
||||||
;
|
;
|
||||||
break;
|
break;
|
||||||
|
case 'thirdparty':
|
||||||
|
console.log('TODO write postAddressToThirdparty');
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
this.$refs.addAddress.errorMsg.push('That entity is not managed by address !');
|
this.$refs.addAddress.errorMsg.push('That entity is not managed by address !');
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
// address is already linked, just finish !
|
|
||||||
window.location.assign(this.context.backUrl);
|
|
||||||
}
|
}
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
@ -109,7 +109,7 @@ const patchAddress = (id, body) => {
|
|||||||
* method POST, post Postal Code Object
|
* method POST, post Postal Code Object
|
||||||
* @returns {Promise}
|
* @returns {Promise}
|
||||||
*/
|
*/
|
||||||
const postPostalCode = (postalCode) => {
|
const postPostalCode = (postalCode) => { //<--
|
||||||
const url = `/api/1.0/main/postal-code.json?`;
|
const url = `/api/1.0/main/postal-code.json?`;
|
||||||
const body = postalCode;
|
const body = postalCode;
|
||||||
|
|
||||||
|
@ -0,0 +1,31 @@
|
|||||||
|
<template>
|
||||||
|
<ul class="record_actions"
|
||||||
|
:class="{ 'sticky-form-buttons': isStickyForm }">
|
||||||
|
|
||||||
|
<li v-if="isStickyForm" class="cancel">
|
||||||
|
<slot name="before"></slot>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<slot name="action"></slot>
|
||||||
|
|
||||||
|
<li v-if="isStickyForm">
|
||||||
|
<slot name="after"></slot>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "ActionButtons",
|
||||||
|
props: ['options', 'defaultz'],
|
||||||
|
computed: {
|
||||||
|
isStickyForm() {
|
||||||
|
return (typeof this.options.stickyActions !== 'undefined') ?
|
||||||
|
this.options.stickyActions : this.defaultz.stickyActions;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
@ -1,18 +1,22 @@
|
|||||||
<template>
|
<template>
|
||||||
|
|
||||||
<!-- start with a button -->
|
<!-- step0 -->
|
||||||
<button v-if="step1WithModal"
|
<show-pane v-if="flag.showPane"
|
||||||
@click="openShowPane"
|
v-bind:context="this.context"
|
||||||
class="btn" :class="getClassButton"
|
v-bind:options="this.options"
|
||||||
type="button" name="button" :title="$t(getTextButton)">
|
v-bind:defaultz="this.defaultz"
|
||||||
<span v-if="displayTextButton">{{ $t(getTextButton) }}</span>
|
v-bind:entity="this.entity"
|
||||||
</button>
|
v-bind:flag="this.flag"
|
||||||
|
v-bind:useDatePane="this.useDatePane"
|
||||||
|
@openEditPane="openEditPane"
|
||||||
|
ref="showAddress">
|
||||||
|
</show-pane>
|
||||||
|
|
||||||
<!-- step 1 -->
|
<!-- step 1 -->
|
||||||
<teleport to="body" v-if="step1WithModal">
|
<teleport to="body" v-if="inModal">
|
||||||
<modal v-if="flag.showPane"
|
<modal v-if="flag.suggestPane"
|
||||||
modalDialogClass="modal-dialog-scrollable modal-xl"
|
modalDialogClass="modal-dialog-scrollable modal-xl"
|
||||||
@close="flag.showPane = false">
|
@close="resetPane">
|
||||||
|
|
||||||
<template v-slot:header>
|
<template v-slot:header>
|
||||||
<h2 class="modal-title">{{ $t(getTextTitle) }}
|
<h2 class="modal-title">{{ $t(getTextTitle) }}
|
||||||
@ -24,24 +28,86 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template v-slot:body>
|
<template v-slot:body>
|
||||||
<show-address-pane
|
<suggest-pane
|
||||||
v-bind:context="this.context"
|
v-bind:context="this.context"
|
||||||
v-bind:options="this.options"
|
v-bind:options="this.options"
|
||||||
v-bind:default="this.default"
|
v-bind:defaultz="this.defaultz"
|
||||||
v-bind:entity="this.entity"
|
v-bind:entity="this.entity"
|
||||||
v-bind:valid="this.context.valid"
|
|
||||||
v-bind:flag="this.flag"
|
v-bind:flag="this.flag"
|
||||||
ref="showAddress">
|
ref="suggestAddress">
|
||||||
</show-address-pane>
|
</suggest-pane>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template v-slot:footer>
|
<template v-slot:footer>
|
||||||
<button @click="openEditPane"
|
<button @click="openEditPane"
|
||||||
class="btn btn-update">
|
class="btn btn-create">
|
||||||
{{ $t('action.edit')}}
|
{{ $t('create_a_new_address')}}
|
||||||
</button>
|
</button>
|
||||||
<button class="btn btn-save"
|
</template>
|
||||||
@click.prevent="$emit('submitAddress')">
|
|
||||||
|
</modal>
|
||||||
|
</teleport>
|
||||||
|
<div class="mt-4" v-else>
|
||||||
|
<suggest-pane v-if="flag.suggestPane"
|
||||||
|
v-bind:context="this.context"
|
||||||
|
v-bind:options="this.options"
|
||||||
|
v-bind:defaultz="this.defaultz"
|
||||||
|
v-bind:entity="this.entity"
|
||||||
|
v-bind:flag="this.flag"
|
||||||
|
v-bind:insideModal="false"
|
||||||
|
ref="suggestAddress">
|
||||||
|
|
||||||
|
<template v-slot:before v-if="!bypassFirstStep">
|
||||||
|
<a class="btn btn-cancel" @click="resetPane">
|
||||||
|
{{ $t('action.cancel') }}
|
||||||
|
</a>
|
||||||
|
</template>
|
||||||
|
<template v-slot:action>
|
||||||
|
<li>
|
||||||
|
<button @click="openEditPane"
|
||||||
|
class="btn btn-create">
|
||||||
|
{{ $t('create_a_new_address')}}
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
</suggest-pane>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- step 2 -->
|
||||||
|
<teleport to="body" v-if="inModal">
|
||||||
|
<modal v-if="flag.editPane"
|
||||||
|
modalDialogClass="modal-dialog-scrollable modal-xl"
|
||||||
|
@close="resetPane">
|
||||||
|
|
||||||
|
<template v-slot:header>
|
||||||
|
<h2 class="modal-title">{{ $t(getTextTitle) }}
|
||||||
|
<span v-if="flag.loading" class="loading">
|
||||||
|
<i class="fa fa-circle-o-notch fa-spin fa-fw"></i>
|
||||||
|
<span class="sr-only">{{ $t('loading') }}</span>
|
||||||
|
</span>
|
||||||
|
</h2>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template v-slot:body>
|
||||||
|
<edit-pane
|
||||||
|
v-bind:context="this.context"
|
||||||
|
v-bind:options="this.options"
|
||||||
|
v-bind:defaultz="this.defaultz"
|
||||||
|
v-bind:entity="this.entity"
|
||||||
|
v-bind:flag="this.flag"
|
||||||
|
@getCities="getCities"
|
||||||
|
@getReferenceAddresses="getReferenceAddresses">
|
||||||
|
</edit-pane>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template v-slot:footer>
|
||||||
|
<!--<button class="btn btn-cancel change-icon" @click="resetPane">{{ $t('action.cancel') }}</button>-->
|
||||||
|
<button v-if="!this.context.edit && this.useDatePane" class="btn btn-update change-icon" @click="closeEditPane">
|
||||||
|
{{ $t('nav.next')}}
|
||||||
|
<i class="fa fa-fw fa-arrow-right"></i>
|
||||||
|
</button>
|
||||||
|
<button v-else class="btn btn-save" @click="closeEditPane">
|
||||||
{{ $t('action.save')}}
|
{{ $t('action.save')}}
|
||||||
</button>
|
</button>
|
||||||
</template>
|
</template>
|
||||||
@ -49,25 +115,43 @@
|
|||||||
</modal>
|
</modal>
|
||||||
</teleport>
|
</teleport>
|
||||||
<div class="mt-4" v-else>
|
<div class="mt-4" v-else>
|
||||||
|
<edit-pane v-if="flag.editPane"
|
||||||
<show-address-pane v-if="flag.showPane"
|
|
||||||
v-bind:context="this.context"
|
v-bind:context="this.context"
|
||||||
v-bind:options="this.options"
|
v-bind:options="this.options"
|
||||||
v-bind:default="this.default"
|
v-bind:defaultz="this.defaultz"
|
||||||
v-bind:entity="this.entity"
|
v-bind:entity="this.entity"
|
||||||
v-bind:valid="this.context.valid"
|
|
||||||
v-bind:flag="this.flag"
|
v-bind:flag="this.flag"
|
||||||
ref="showAddress"
|
v-bind:insideModal="false"
|
||||||
v-bind:insideModal="false" @openEditPane="openEditPane"
|
@getCities="getCities"
|
||||||
@submitAddress="$emit('submitAddress')">
|
@getReferenceAddresses="getReferenceAddresses">
|
||||||
</show-address-pane>
|
|
||||||
|
<template v-slot:before>
|
||||||
|
<a class="btn btn-cancel" @click="resetPane">
|
||||||
|
{{ $t('action.cancel') }}
|
||||||
|
</a>
|
||||||
|
</template>
|
||||||
|
<template v-slot:action>
|
||||||
|
<li v-if="!this.context.edit && this.useDatePane">
|
||||||
|
<button class="btn btn-update change-icon" @click="closeEditPane">
|
||||||
|
{{ $t('nav.next')}}
|
||||||
|
<i class="fa fa-fw fa-arrow-right"></i>
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
<li v-else>
|
||||||
|
<button class="btn btn-save" @click="closeEditPane">
|
||||||
|
{{ $t('action.save')}}
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
</edit-pane>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- step 2 -->
|
<!-- step 3 -->
|
||||||
<teleport to="body" v-if="step2WithModal">
|
<teleport to="body" v-if="inModal">
|
||||||
<modal v-if="flag.editPane"
|
<modal v-if="flag.datePane"
|
||||||
modalDialogClass="modal-dialog-scrollable modal-xl"
|
modalDialogClass="modal-dialog-scrollable modal-xl"
|
||||||
@close="flag.editPane = false">
|
@close="resetPane">
|
||||||
|
|
||||||
<template v-slot:header>
|
<template v-slot:header>
|
||||||
<h2 class="modal-title">{{ $t(getTextTitle) }}
|
<h2 class="modal-title">{{ $t(getTextTitle) }}
|
||||||
@ -79,41 +163,54 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template v-slot:body>
|
<template v-slot:body>
|
||||||
<edit-address-pane
|
<date-pane
|
||||||
v-bind:context="this.context"
|
v-bind:context="this.context"
|
||||||
v-bind:options="this.options"
|
v-bind:options="this.options"
|
||||||
v-bind:default="this.default"
|
v-bind:defaultz="this.defaultz"
|
||||||
v-bind:entity="this.entity"
|
v-bind:entity="this.entity"
|
||||||
v-bind:flag="this.flag"
|
v-bind:flag="this.flag"
|
||||||
@getCities="getCities"
|
ref="dateAddress">
|
||||||
@getReferenceAddresses="getReferenceAddresses">
|
</date-pane>
|
||||||
</edit-address-pane>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template v-slot:footer>
|
<template v-slot:footer>
|
||||||
<button class="btn btn-cancel change-icon" @click="flag.showPane = true; flag.editPane = false;">
|
<button class="btn btn-misc" @click="openEditPane">
|
||||||
{{ $t('action.cancel') }}
|
<i class="fa fa-fw fa-arrow-left"></i>
|
||||||
|
{{ $t('nav.previous')}}
|
||||||
</button>
|
</button>
|
||||||
<button class="btn btn-update"
|
<button class="btn btn-save" @click="closeDatePane">
|
||||||
@click="closeEditPane">
|
{{ $t('action.save')}}
|
||||||
{{ $t('action.valid')}}
|
|
||||||
</button>
|
</button>
|
||||||
|
<!-- -->
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
</modal>
|
</modal>
|
||||||
</teleport>
|
</teleport>
|
||||||
<div class="mt-4" v-else>
|
<div class="mt-4" v-else>
|
||||||
|
<date-pane v-if="flag.datePane"
|
||||||
<edit-address-pane v-if="flag.editPane"
|
|
||||||
v-bind:context="this.context"
|
v-bind:context="this.context"
|
||||||
v-bind:options="this.options"
|
v-bind:options="this.options"
|
||||||
v-bind:default="this.default"
|
v-bind:defaultz="this.defaultz"
|
||||||
v-bind:entity="this.entity"
|
v-bind:entity="this.entity"
|
||||||
v-bind:flag="this.flag"
|
v-bind:flag="this.flag"
|
||||||
v-bind:insideModal="false" @closeEditPane="closeEditPane"
|
v-bind:insideModal="false"
|
||||||
@getCities="getCities"
|
ref="dateAddress">
|
||||||
@getReferenceAddresses="getReferenceAddresses">
|
|
||||||
</edit-address-pane>
|
<template v-slot:before>
|
||||||
|
<button class="btn btn-misc" @click="openEditPane">
|
||||||
|
<i class="fa fa-fw fa-arrow-left"></i>
|
||||||
|
{{ $t('nav.previous')}}
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
|
<template v-slot:action>
|
||||||
|
<li>
|
||||||
|
<button class="btn btn-save" @click="closeDatePane">
|
||||||
|
{{ $t('action.save')}}
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
</date-pane>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</template>
|
</template>
|
||||||
@ -122,37 +219,45 @@
|
|||||||
import Modal from 'ChillMainAssets/vuejs/_components/Modal';
|
import Modal from 'ChillMainAssets/vuejs/_components/Modal';
|
||||||
import { getAddress, fetchCountries, fetchCities, fetchReferenceAddresses, patchAddress, postAddress, postPostalCode } from '../api';
|
import { getAddress, fetchCountries, fetchCities, fetchReferenceAddresses, patchAddress, postAddress, postPostalCode } from '../api';
|
||||||
import { postAddressToPerson, postAddressToHousehold } from "ChillPersonAssets/vuejs/_api/AddAddress.js";
|
import { postAddressToPerson, postAddressToHousehold } from "ChillPersonAssets/vuejs/_api/AddAddress.js";
|
||||||
import ShowAddressPane from './ShowAddressPane.vue';
|
import ShowPane from './ShowPane.vue';
|
||||||
import EditAddressPane from './EditAddressPane.vue';
|
import SuggestPane from './SuggestPane.vue';
|
||||||
|
import EditPane from './EditPane.vue';
|
||||||
|
import DatePane from './DatePane.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "AddAddress",
|
name: "AddAddress",
|
||||||
props: ['context', 'options', 'result'],
|
props: ['context', 'options', 'addressChangedCallback'],
|
||||||
emits: ['submitAddress'],
|
|
||||||
components: {
|
components: {
|
||||||
Modal,
|
Modal,
|
||||||
ShowAddressPane,
|
ShowPane,
|
||||||
EditAddressPane,
|
SuggestPane,
|
||||||
|
EditPane,
|
||||||
|
DatePane
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
flag: {
|
flag: {
|
||||||
showPane: false,
|
showPane: true, // begin with showPane
|
||||||
|
suggestPane: false,
|
||||||
editPane: false,
|
editPane: false,
|
||||||
|
datePane: false,
|
||||||
loading: false,
|
loading: false,
|
||||||
success: false
|
success: false
|
||||||
},
|
},
|
||||||
default: {
|
defaultz: {
|
||||||
button: {
|
button: {
|
||||||
text: { create: 'add_an_address_title', edit: 'edit_address' },
|
text: { create: 'add_an_address_title', edit: 'edit_address' },
|
||||||
type: { create: 'btn-create', edit: 'btn-update'},
|
type: { create: 'btn-create', edit: 'btn-update'},
|
||||||
displayText: true
|
displayText: true
|
||||||
},
|
},
|
||||||
title: { create: 'add_an_address_title', edit: 'edit_address' },
|
title: { create: 'add_an_address_title', edit: 'edit_address' },
|
||||||
bindModal: {
|
openPanesInModal: true,
|
||||||
step1: true,
|
stickyActions: false,
|
||||||
step2: true,
|
useDate: {
|
||||||
|
validFrom: false,
|
||||||
|
validTo: false
|
||||||
},
|
},
|
||||||
|
hideAddress: false
|
||||||
},
|
},
|
||||||
entity: {
|
entity: {
|
||||||
address: {}, // <== loaded and returned
|
address: {}, // <== loaded and returned
|
||||||
@ -173,6 +278,10 @@ export default {
|
|||||||
writeNew: {
|
writeNew: {
|
||||||
address: false,
|
address: false,
|
||||||
postcode: false
|
postcode: false
|
||||||
|
},
|
||||||
|
valid: {
|
||||||
|
from: new Date(),
|
||||||
|
to: null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
addressMap: {
|
addressMap: {
|
||||||
@ -186,19 +295,26 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
step1WithModal() {
|
inModal() {
|
||||||
return (this.options.bindModal !== null && typeof this.options.bindModal.step1 !== 'undefined') ?
|
return (typeof this.options.openPanesInModal !== 'undefined') ?
|
||||||
this.options.bindModal.step1 : this.default.bindModal.step1;
|
this.options.openPanesInModal : this.defaultz.openPanesInModal;
|
||||||
},
|
},
|
||||||
step2WithModal() {
|
useDatePane() {
|
||||||
let step2 = (this.options.bindModal !== null && typeof this.options.bindModal.step2 !== 'undefined') ?
|
let vFrom = (typeof this.options.useDate !== 'undefined'
|
||||||
this.options.bindModal.step2 : this.default.bindModal.step2;
|
&& typeof this.options.useDate.validFrom !== 'undefined') ?
|
||||||
|
this.options.useDate.validFrom : this.defaultz.useDate.validFrom ;
|
||||||
if (step2 === false && this.step1WithModal === true) {
|
let vTo = (typeof this.options.useDate !== 'undefined'
|
||||||
console.log("step2 must open in a Modal");
|
&& typeof this.options.useDate.validTo !== 'undefined') ?
|
||||||
return true;
|
this.options.useDate.validTo : this.defaultz.useDate.validTo ;
|
||||||
}
|
return (vFrom || vTo) ? true : false;
|
||||||
return step2;
|
},
|
||||||
|
hasSuggestions() {
|
||||||
|
// TODO
|
||||||
|
//return addressSuggestions.length > 0
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
displaySuggestions() {
|
||||||
|
return !this.context.edit && this.hasSuggestions;
|
||||||
},
|
},
|
||||||
getTextTitle() {
|
getTextTitle() {
|
||||||
if ( typeof this.options.title !== 'undefined'
|
if ( typeof this.options.title !== 'undefined'
|
||||||
@ -207,36 +323,27 @@ export default {
|
|||||||
)) {
|
)) {
|
||||||
return (this.context.edit) ? this.options.title.edit : this.options.title.create;
|
return (this.context.edit) ? this.options.title.edit : this.options.title.create;
|
||||||
}
|
}
|
||||||
return (this.context.edit) ? this.default.title.edit : this.default.title.create;
|
return (this.context.edit) ? this.defaultz.title.edit : this.defaultz.title.create;
|
||||||
},
|
},
|
||||||
getTextButton() {
|
bypassFirstStep() {
|
||||||
if ( typeof this.options.button.text !== 'undefined'
|
// exception: passing step0 if new address and pane are not in modal
|
||||||
&& ( this.options.button.text.edit !== null
|
return !this.context.edit && !this.inModal;
|
||||||
|| this.options.button.text.create !== null
|
|
||||||
)) {
|
|
||||||
return (this.context.edit) ? this.options.button.text.edit : this.options.button.text.create;
|
|
||||||
}
|
|
||||||
return (this.context.edit) ? this.default.button.text.edit : this.default.button.text.create;
|
|
||||||
},
|
},
|
||||||
getClassButton() {
|
forceRedirect() {
|
||||||
let type = (this.context.edit) ? this.default.button.type.edit : this.default.button.type.create;
|
return (!(this.context.backUrl === null || typeof this.context.backUrl === 'undefined'));
|
||||||
let size = (typeof this.options.button !== 'undefined' && this.options.button.size !== null) ?
|
|
||||||
`${this.options.button.size} ` : '';
|
|
||||||
return `${size}${type}`;
|
|
||||||
},
|
|
||||||
displayTextButton() {
|
|
||||||
return (typeof this.options.button !== 'undefined' && typeof this.options.button.displayText !== 'undefined') ?
|
|
||||||
this.options.button.displayText : this.default.button.displayText;
|
|
||||||
},
|
|
||||||
context() {
|
|
||||||
return this.context;
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
if (!this.step1WithModal) {
|
|
||||||
//console.log('Mounted now !');
|
console.log('useDatePane', this.useDatePane);
|
||||||
this.openShowPane();
|
|
||||||
|
console.log('Mounted now !');
|
||||||
|
if (this.context.edit) {
|
||||||
|
console.log('getInitialAddress', this.context.addressId);
|
||||||
|
this.getInitialAddress(this.context.addressId);
|
||||||
}
|
}
|
||||||
|
this.openShowPane();
|
||||||
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
|
||||||
@ -244,35 +351,89 @@ export default {
|
|||||||
* Opening and closing Panes when interacting with buttons
|
* Opening and closing Panes when interacting with buttons
|
||||||
*/
|
*/
|
||||||
openShowPane() {
|
openShowPane() {
|
||||||
|
if (this.flag.editPane === false && this.bypassFirstStep) {
|
||||||
if (this.context.edit) {
|
console.log('bypassFirstStep');
|
||||||
this.getInitialAddress(this.context.addressId);
|
this.closeShowPane();
|
||||||
}
|
|
||||||
|
|
||||||
// when create new address, start first with editPane
|
|
||||||
if ( this.context.edit === false
|
|
||||||
&& this.flag.editPane === false
|
|
||||||
) {
|
|
||||||
this.openEditPane();
|
this.openEditPane();
|
||||||
this.flag.editPane = true;
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
this.flag.showPane = true;
|
this.flag.showPane = true;
|
||||||
console.log('step1: open the Show Panel');
|
console.log('step0: open the Show Panel');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
closeShowPane() {
|
||||||
|
// Show pane can be closed only when openPanesInModal is false
|
||||||
|
if (!this.inModal) {
|
||||||
|
this.flag.showPane = false;
|
||||||
|
console.log('step0: close the Show Panel');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
openSuggestPane() {
|
||||||
|
this.flag.suggestPane = true;
|
||||||
|
console.log('step1: open the Suggestion Panel');
|
||||||
|
},
|
||||||
|
closeSuggestPane() {
|
||||||
|
this.flag.suggestPane = false;
|
||||||
|
console.log('step1: close the Suggestion Panel');
|
||||||
|
},
|
||||||
openEditPane() {
|
openEditPane() {
|
||||||
console.log('step2: open the Edit panel');
|
if (this.flag.suggestPane === false && this.displaySuggestions) {
|
||||||
this.initForm();
|
console.log('displaySuggestions');
|
||||||
this.getCountries();
|
this.openSuggestPane();
|
||||||
|
} else {
|
||||||
|
if (this.flag.datePane === false) {
|
||||||
|
this.initForm(); // reset form except if we come back from datePane
|
||||||
|
}
|
||||||
|
this.getCountries(); // will open editPane when resolve promise
|
||||||
|
}
|
||||||
|
},
|
||||||
|
closeEditPane() {
|
||||||
|
this.flag.editPane = false;
|
||||||
|
console.log('step2: close the Edit Panel');
|
||||||
|
if (!this.context.edit && this.useDatePane) {
|
||||||
|
this.openDatePane();
|
||||||
|
} else {
|
||||||
|
this.applyChanges();
|
||||||
|
if (!this.forceRedirect) {
|
||||||
|
this.openShowPane();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
openDatePane() {
|
||||||
|
this.flag.datePane = true;
|
||||||
|
console.log('step3: open the Date Panel');
|
||||||
|
},
|
||||||
|
closeDatePane() {
|
||||||
|
this.applyChanges();
|
||||||
|
this.flag.datePane = false;
|
||||||
|
console.log('step3: close the Date Panel');
|
||||||
|
},
|
||||||
|
resetPane() {
|
||||||
|
console.log('resetPane');
|
||||||
|
this.flag.suggestPane = false;
|
||||||
|
this.flag.editPane = false;
|
||||||
|
this.flag.datePane = false;
|
||||||
|
this.openShowPane();
|
||||||
},
|
},
|
||||||
|
|
||||||
closeEditPane() {
|
/*
|
||||||
console.log('step2: close the Edit Panel');
|
* What happens when submitting last Pane:
|
||||||
this.applyChanges();
|
* - redirect or reset pane,
|
||||||
this.flag.showPane = true;
|
* - change context to editing
|
||||||
this.flag.editPane = false;
|
*/
|
||||||
|
afterLastPaneAction(params) {
|
||||||
|
this.initForm();
|
||||||
|
if (this.forceRedirect) {
|
||||||
|
console.log("redirect to backUrl");
|
||||||
|
window.location.assign(this.context.backUrl);
|
||||||
|
} else {
|
||||||
|
console.log("don't redirect");
|
||||||
|
this.resetPane();
|
||||||
|
if (!this.context.edit) {
|
||||||
|
this.context.edit = true;
|
||||||
|
this.context.addressId = params.addressId;
|
||||||
|
console.log("context is now edit, with address", params.addressId);
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -297,7 +458,16 @@ export default {
|
|||||||
fetchCountries().then(
|
fetchCountries().then(
|
||||||
countries => new Promise((resolve, reject) => {
|
countries => new Promise((resolve, reject) => {
|
||||||
this.entity.loaded.countries = countries.results;
|
this.entity.loaded.countries = countries.results;
|
||||||
this.flag.showPane = false;
|
if (this.flag.showPane === true) {
|
||||||
|
this.closeShowPane();
|
||||||
|
}
|
||||||
|
if (this.flag.suggestPane === true) {
|
||||||
|
this.closeSuggestPane();
|
||||||
|
}
|
||||||
|
if (this.flag.datePane === true) {
|
||||||
|
this.flag.datePane = false;
|
||||||
|
}
|
||||||
|
console.log('step2: open the Edit panel');
|
||||||
this.flag.editPane = true;
|
this.flag.editPane = true;
|
||||||
this.flag.loading = false;
|
this.flag.loading = false;
|
||||||
resolve()
|
resolve()
|
||||||
@ -365,6 +535,7 @@ export default {
|
|||||||
|
|
||||||
this.entity.selected.writeNew.address = this.context.edit;
|
this.entity.selected.writeNew.address = this.context.edit;
|
||||||
this.entity.selected.writeNew.postcode = this.context.edit;
|
this.entity.selected.writeNew.postcode = this.context.edit;
|
||||||
|
console.log('!! just set writeNew.postcode to', this.entity.selected.writeNew.postcode);
|
||||||
},
|
},
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -374,6 +545,8 @@ export default {
|
|||||||
*/
|
*/
|
||||||
applyChanges()
|
applyChanges()
|
||||||
{
|
{
|
||||||
|
console.log('apply changes');
|
||||||
|
|
||||||
let newAddress = {
|
let newAddress = {
|
||||||
'isNoAddress': this.entity.selected.isNoAddress,
|
'isNoAddress': this.entity.selected.isNoAddress,
|
||||||
'street': this.entity.selected.isNoAddress ? '' : this.entity.selected.address.street,
|
'street': this.entity.selected.isNoAddress ? '' : this.entity.selected.address.street,
|
||||||
@ -398,18 +571,22 @@ export default {
|
|||||||
newPostcode = Object.assign(newPostcode, {
|
newPostcode = Object.assign(newPostcode, {
|
||||||
'country': {'id': this.entity.selected.country.id },
|
'country': {'id': this.entity.selected.country.id },
|
||||||
});
|
});
|
||||||
|
console.log('writeNew postcode is true! newPostcode: ', newPostcode);
|
||||||
newAddress = Object.assign(newAddress, {
|
newAddress = Object.assign(newAddress, {
|
||||||
'newPostcode': newPostcode
|
'newPostcode': newPostcode
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.context.edit) {
|
if (!this.context.edit) {
|
||||||
|
this.addNewAddress(newAddress)
|
||||||
|
.then(payload => this.addressChangedCallback(payload));
|
||||||
|
|
||||||
|
} else {
|
||||||
this.updateAddress({
|
this.updateAddress({
|
||||||
addressId: this.context.addressId,
|
addressId: this.context.addressId,
|
||||||
newAddress: newAddress
|
newAddress: newAddress
|
||||||
});
|
})
|
||||||
} else {
|
.then(payload => this.addressChangedCallback(payload));
|
||||||
this.addNewAddress(newAddress);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -419,34 +596,42 @@ export default {
|
|||||||
*/
|
*/
|
||||||
addNewAddress(payload)
|
addNewAddress(payload)
|
||||||
{
|
{
|
||||||
//console.log('addNewAddress', payload);
|
console.log('addNewAddress', payload);
|
||||||
this.flag.loading = true;
|
this.flag.loading = true;
|
||||||
|
|
||||||
if ('newPostcode' in payload) {
|
if ('newPostcode' in payload) {
|
||||||
|
|
||||||
let postcodeBody = payload.newPostcode;
|
let postcodeBody = payload.newPostcode;
|
||||||
if (this.context.entity.type === 'person') {
|
if (this.context.target.name === 'person') { // !!! maintain here ?
|
||||||
postcodeBody = Object.assign(postcodeBody, {'origin': 3});
|
postcodeBody = Object.assign(postcodeBody, {'origin': 3});
|
||||||
}
|
}
|
||||||
postPostalCode(postcodeBody)
|
console.log('juste before post new postcode', postcodeBody);
|
||||||
|
return postPostalCode(postcodeBody)
|
||||||
.then(postalCode => {
|
.then(postalCode => {
|
||||||
|
console.log('new postcode created', postalCode.id);
|
||||||
payload.postcode = {'id': postalCode.id };
|
payload.postcode = {'id': postalCode.id };
|
||||||
this.postNewAddress(payload);
|
return this.postNewAddress(payload);
|
||||||
});
|
});
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
this.postNewAddress(payload);
|
return this.postNewAddress(payload);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
postNewAddress(payload) {
|
postNewAddress(payload)
|
||||||
//console.log('postNewAddress', payload);
|
{
|
||||||
postAddress(payload)
|
console.log('postNewAddress', payload);
|
||||||
|
return postAddress(payload)
|
||||||
.then(address => new Promise((resolve, reject) => {
|
.then(address => new Promise((resolve, reject) => {
|
||||||
this.entity.address = address;
|
this.entity.address = address;
|
||||||
this.flag.loading = false;
|
this.flag.loading = false;
|
||||||
this.flag.success = true;
|
this.flag.success = true;
|
||||||
resolve();
|
resolve({
|
||||||
|
target: this.context.target.name,
|
||||||
|
targetId: this.context.target.id,
|
||||||
|
addressId: this.entity.address.address_id
|
||||||
|
}
|
||||||
|
);
|
||||||
}))
|
}))
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
this.errorMsg.push(error);
|
this.errorMsg.push(error);
|
||||||
@ -463,31 +648,39 @@ export default {
|
|||||||
this.flag.loading = true;
|
this.flag.loading = true;
|
||||||
|
|
||||||
// TODO change the condition because it writes new postal code in edit mode now: !writeNewPostalCode
|
// TODO change the condition because it writes new postal code in edit mode now: !writeNewPostalCode
|
||||||
|
// BUG réécrit un postcode à chaque édition !
|
||||||
if ('newPostcode' in payload.newAddress) {
|
if ('newPostcode' in payload.newAddress) {
|
||||||
|
|
||||||
let postcodeBody = payload.newAddress.newPostcode;
|
let postcodeBody = payload.newAddress.newPostcode;
|
||||||
postcodeBody = Object.assign(postcodeBody, {'origin': 3});
|
postcodeBody = Object.assign(postcodeBody, {'origin': 3});
|
||||||
|
|
||||||
postPostalCode(postcodeBody)
|
console.log('juste before post new postcode', postcodeBody);
|
||||||
|
return postPostalCode(postcodeBody)
|
||||||
.then(postalCode => {
|
.then(postalCode => {
|
||||||
|
console.log('new postcode created', postalCode.id);
|
||||||
payload.newAddress.postcode = {'id': postalCode.id };
|
payload.newAddress.postcode = {'id': postalCode.id };
|
||||||
this.patchExistingAddress(payload);
|
return this.patchExistingAddress(payload);
|
||||||
});
|
});
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
this.patchExistingAddress(payload);
|
return this.patchExistingAddress(payload);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
patchExistingAddress(payload) {
|
patchExistingAddress(payload) {
|
||||||
console.log('patchExistingAddress', payload);
|
console.log('patchExistingAddress', payload);
|
||||||
patchAddress(payload.addressId, payload.newAddress)
|
return patchAddress(payload.addressId, payload.newAddress)
|
||||||
.then(address => new Promise((resolve, reject) => {
|
.then(address => new Promise((resolve, reject) => {
|
||||||
this.entity.address = address;
|
this.entity.address = address;
|
||||||
this.flag.loading = false;
|
this.flag.loading = false;
|
||||||
this.flag.success = true;
|
this.flag.success = true;
|
||||||
resolve();
|
return resolve({
|
||||||
}))
|
target: this.context.target.name,
|
||||||
|
targetId: this.context.target.id,
|
||||||
|
addressId: this.entity.address.address_id
|
||||||
|
});
|
||||||
|
})
|
||||||
|
)
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
this.errorMsg.push(error);
|
this.errorMsg.push(error);
|
||||||
this.flag.loading = false;
|
this.flag.loading = false;
|
||||||
@ -495,24 +688,17 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Method called by parent when submitting address
|
* Method just add closing pane to the callback method
|
||||||
* (get out step1 show pane, submit button)
|
* (get out step1 show pane, submit button)
|
||||||
*/
|
closePaneAndCallbackSubmit(payload)
|
||||||
submitNewAddress()
|
|
||||||
{
|
{
|
||||||
let payload = {
|
//this.initForm();
|
||||||
entity: this.context.entity.type,
|
//this.resetPane(); // because parent callback will cast afterLastPaneAction()
|
||||||
entityId: this.context.entity.id,
|
console.log('will call parent callback method', payload);
|
||||||
addressId: this.entity.address.address_id
|
// callback props method from parent
|
||||||
};
|
this.addressChangedCallback(payload);
|
||||||
|
|
||||||
console.log('submitNewAddress return', payload);
|
|
||||||
|
|
||||||
this.initForm();
|
|
||||||
this.flag.showPane = false;
|
|
||||||
|
|
||||||
return payload;
|
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@ -525,4 +711,7 @@ div.entity-address {
|
|||||||
right: 0; top: -55px;
|
right: 0; top: -55px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
h4.h3 {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="my-1">
|
<div class="my-1">
|
||||||
<label class="col-form-label" for="citySelector">{{ $t('city') }}</label>
|
<label class="col-form-label">{{ $t('city') }}</label>
|
||||||
<VueMultiselect
|
<VueMultiselect
|
||||||
id="citySelector"
|
id="citySelector"
|
||||||
v-model="value"
|
v-model="value"
|
||||||
@ -25,7 +25,7 @@
|
|||||||
<div class="form-floating">
|
<div class="form-floating">
|
||||||
<input class="form-control"
|
<input class="form-control"
|
||||||
type="text"
|
type="text"
|
||||||
name="code"
|
id="code"
|
||||||
:placeholder="$t('postalCode_code')"
|
:placeholder="$t('postalCode_code')"
|
||||||
v-model="code"/>
|
v-model="code"/>
|
||||||
<label for="code">{{ $t('postalCode_code') }}</label>
|
<label for="code">{{ $t('postalCode_code') }}</label>
|
||||||
@ -35,7 +35,7 @@
|
|||||||
<div class="form-floating">
|
<div class="form-floating">
|
||||||
<input class="form-control"
|
<input class="form-control"
|
||||||
type="text"
|
type="text"
|
||||||
name="name"
|
id="name"
|
||||||
:placeholder="$t('postalCode_name')"
|
:placeholder="$t('postalCode_name')"
|
||||||
v-model="name"/>
|
v-model="name"/>
|
||||||
<label for="name">{{ $t('postalCode_name') }}</label>
|
<label for="name">{{ $t('postalCode_name') }}</label>
|
||||||
@ -87,6 +87,9 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
mounted() {
|
||||||
|
console.log('writeNew.postcode', this.entity.selected.writeNew.postcode, 'in mounted');
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
transName(value) {
|
transName(value) {
|
||||||
return (value.code && value.name) ? `${value.code}-${value.name}` : '';
|
return (value.code && value.name) ? `${value.code}-${value.name}` : '';
|
||||||
@ -96,6 +99,7 @@ export default {
|
|||||||
this.entity.selected.postcode.name = value.name;
|
this.entity.selected.postcode.name = value.name;
|
||||||
this.entity.selected.postcode.code = value.code;
|
this.entity.selected.postcode.code = value.code;
|
||||||
this.entity.selected.writeNew.postcode = false;
|
this.entity.selected.writeNew.postcode = false;
|
||||||
|
console.log('writeNew.postcode false, in selectCity');
|
||||||
this.$emit('getReferenceAddresses', value);
|
this.$emit('getReferenceAddresses', value);
|
||||||
this.focusOnAddress();
|
this.focusOnAddress();
|
||||||
},
|
},
|
||||||
@ -110,6 +114,7 @@ export default {
|
|||||||
this.entity.selected.postcode.name = city.name;
|
this.entity.selected.postcode.name = city.name;
|
||||||
this.entity.selected.postcode.code = city.code;
|
this.entity.selected.postcode.code = city.code;
|
||||||
this.entity.selected.writeNew.postcode = true;
|
this.entity.selected.writeNew.postcode = true;
|
||||||
|
console.log('writeNew.postcode true, in listenInputSearch');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
splitCity(city) {
|
splitCity(city) {
|
||||||
@ -142,6 +147,7 @@ export default {
|
|||||||
addPostcode() {
|
addPostcode() {
|
||||||
console.log('addPostcode: pass here ?? never, it seems');
|
console.log('addPostcode: pass here ?? never, it seems');
|
||||||
this.entity.selected.writeNew.postcode = true;
|
this.entity.selected.writeNew.postcode = true;
|
||||||
|
console.log('writeNew.postcode true, in addPostcode');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -1,47 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="container">
|
|
||||||
<VueMultiselect
|
|
||||||
v-model="value"
|
|
||||||
@select="selectAddress"
|
|
||||||
name="field"
|
|
||||||
track-by="id"
|
|
||||||
label="value"
|
|
||||||
:custom-label="transName"
|
|
||||||
:multiple="false"
|
|
||||||
:placeholder="$t('select_address')"
|
|
||||||
:options="addresses">
|
|
||||||
</VueMultiselect>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import VueMultiselect from 'vue-multiselect';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'SelectHouseholdAddress',
|
|
||||||
components: { VueMultiselect },
|
|
||||||
props: ['address'],
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
value: null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
addresses() {
|
|
||||||
return this.address.loaded.addresses;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
transName(value) {
|
|
||||||
return `${value.text} ${value.postcode.name}`
|
|
||||||
},
|
|
||||||
selectAddress(value) {
|
|
||||||
this.address.selected.address = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style src="vue-multiselect/dist/vue-multiselect.css"></style>
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,139 @@
|
|||||||
|
<template>
|
||||||
|
|
||||||
|
<div v-if="insideModal === false" class="loading">
|
||||||
|
<i v-if="flag.loading" class="fa fa-circle-o-notch fa-spin fa-2x fa-fw"></i>
|
||||||
|
<span class="sr-only">{{ $t('loading') }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="errorMsg && errorMsg.length > 0" class="alert alert-danger">
|
||||||
|
{{ errorMsg }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<address-render-box :address="selectedAddress"></address-render-box>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div v-if="showDateFrom" class='col-lg-6 address-valid date-since'>
|
||||||
|
<h3>{{ $t(getValidFromDateText) }}</h3>
|
||||||
|
<div class="input-group mb-3">
|
||||||
|
<span class="input-group-text" id="validFrom"><i class="fa fa-fw fa-calendar"></i></span>
|
||||||
|
<input type="date" class="form-control form-control-lg" name="validFrom"
|
||||||
|
v-bind:placeholder="$t(getValidFromDateText)"
|
||||||
|
v-model="validFrom"
|
||||||
|
aria-describedby="validFrom"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="showDateTo" class='col-lg-6 address-valid date-until'>
|
||||||
|
<h3>{{ $t(getValidToDateText) }}</h3>
|
||||||
|
<div class="input-group mb-3">
|
||||||
|
<span class="input-group-text" id="validTo"><i class="fa fa-fw fa-calendar"></i></span>
|
||||||
|
<input type="date" class="form-control form-control-lg" name="validTo"
|
||||||
|
v-bind:placeholder="$t(getValidToDateText)"
|
||||||
|
v-model="validTo"
|
||||||
|
aria-describedby="validTo"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<action-buttons v-if="insideModal === false"
|
||||||
|
:options="this.options"
|
||||||
|
:defaultz="this.defaultz">
|
||||||
|
<template v-slot:before>
|
||||||
|
<slot name="before"></slot>
|
||||||
|
</template>
|
||||||
|
<template v-slot:action>
|
||||||
|
<slot name="action"></slot>
|
||||||
|
</template>
|
||||||
|
<template v-slot:after>
|
||||||
|
<slot name="after"></slot>
|
||||||
|
</template>
|
||||||
|
</action-buttons>
|
||||||
|
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { dateToISO, ISOToDate, ISOToDatetime } from 'ChillMainAssets/chill/js/date.js';
|
||||||
|
import AddressRenderBox from 'ChillMainAssets/vuejs/_components/Entity/AddressRenderBox.vue';
|
||||||
|
import ActionButtons from './ActionButtons.vue';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "DatePane",
|
||||||
|
components: {
|
||||||
|
AddressRenderBox,
|
||||||
|
ActionButtons
|
||||||
|
},
|
||||||
|
props: [
|
||||||
|
'context',
|
||||||
|
'options',
|
||||||
|
'defaultz',
|
||||||
|
'flag',
|
||||||
|
'entity',
|
||||||
|
'errorMsg',
|
||||||
|
'insideModal'
|
||||||
|
],
|
||||||
|
computed: {
|
||||||
|
address() {
|
||||||
|
return this.entity.address;
|
||||||
|
},
|
||||||
|
validFrom: {
|
||||||
|
set(value) {
|
||||||
|
this.entity.selected.valid.from = ISOToDate(value);
|
||||||
|
},
|
||||||
|
get() {
|
||||||
|
return dateToISO(this.entity.selected.valid.from);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
validTo: {
|
||||||
|
set(value) {
|
||||||
|
this.entity.selected.valid.to = ISOToDate(value);
|
||||||
|
},
|
||||||
|
get() {
|
||||||
|
return dateToISO(this.entity.selected.valid.to);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getValidFromDateText() {
|
||||||
|
return (this.context.target.name === 'household') ? 'move_date' : 'validFrom';
|
||||||
|
},
|
||||||
|
getValidToDateText() {
|
||||||
|
return 'validTo';
|
||||||
|
},
|
||||||
|
showDateFrom() {
|
||||||
|
return !this.context.edit && this.options.useDate.validFrom;
|
||||||
|
},
|
||||||
|
showDateTo() {
|
||||||
|
return !this.context.edit && this.options.useDate.validTo;
|
||||||
|
},
|
||||||
|
selectedAddress() {
|
||||||
|
let address = {};
|
||||||
|
|
||||||
|
address['country'] = (this.entity.selected.country) ? this.entity.selected.country : null;
|
||||||
|
address['postcode'] = (this.entity.selected.postcode) ? this.entity.selected.postcode : null;
|
||||||
|
|
||||||
|
if (this.entity.selected.address) {
|
||||||
|
let number = (this.entity.selected.address.streetNumber) ? this.entity.selected.address.streetNumber : null;
|
||||||
|
let street = (this.entity.selected.address.street) ? this.entity.selected.address.street : null;
|
||||||
|
address['text'] = number + ', ' + street;
|
||||||
|
|
||||||
|
address['street'] = (this.entity.selected.address.street) ? this.entity.selected.address.street : null;
|
||||||
|
address['streetNumber'] = (this.entity.selected.address.streetNumber) ? this.entity.selected.address.streetNumber : null;
|
||||||
|
address['floor'] = (this.entity.selected.address.floor) ? this.entity.selected.address.floor : null;
|
||||||
|
address['corridor'] = (this.entity.selected.address.corridor) ? this.entity.selected.address.corridor : null;
|
||||||
|
address['steps'] = (this.entity.selected.address.steps) ? this.entity.selected.address.steps : null;
|
||||||
|
address['flat'] = (this.entity.selected.address.flat) ? this.entity.selected.address.flat : null;
|
||||||
|
address['buildingName'] = (this.entity.selected.address.buildingName) ? this.entity.selected.address.buildingName : null;
|
||||||
|
address['distribution'] = (this.entity.selected.address.distribution) ? this.entity.selected.address.distribution : null;
|
||||||
|
address['extra'] = (this.entity.selected.address.extra) ? this.entity.selected.address.extra : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.entity.selected.valid) {
|
||||||
|
address['validFrom'] = (this.entity.selected.valid.from) ? dateToISO(this.entity.selected.valid.from) : null;
|
||||||
|
address['validTo'] = (this.entity.selected.valid.to) ? dateToISO(this.entity.selected.valid.to) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return address;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
@ -2,7 +2,7 @@
|
|||||||
<div class="address-form">
|
<div class="address-form">
|
||||||
|
|
||||||
<!-- Not display in modal -->
|
<!-- Not display in modal -->
|
||||||
<div v-if="insideModal == false" class="loading">
|
<div v-if="insideModal === false" class="loading">
|
||||||
<i v-if="flag.loading" class="fa fa-circle-o-notch fa-spin fa-2x fa-fw"></i>
|
<i v-if="flag.loading" class="fa fa-circle-o-notch fa-spin fa-2x fa-fw"></i>
|
||||||
<span class="sr-only">Loading...</span>
|
<span class="sr-only">Loading...</span>
|
||||||
</div>
|
</div>
|
||||||
@ -54,25 +54,19 @@
|
|||||||
v-bind:entity="entity">
|
v-bind:entity="entity">
|
||||||
</address-more>
|
</address-more>
|
||||||
|
|
||||||
<!-- Not display in modal -->
|
<action-buttons v-if="insideModal === false"
|
||||||
<ul v-if="insideModal == false"
|
:options="this.options"
|
||||||
class="record_actions sticky-form-buttons">
|
:defaultz="this.defaultz">
|
||||||
<li class="cancel">
|
<template v-slot:before>
|
||||||
<a class="btn btn-cancel" v-bind:href="context.backUrl">
|
<slot name="before"></slot>
|
||||||
{{ $t('back_to_the_list') }}
|
</template>
|
||||||
</a>
|
<template v-slot:action>
|
||||||
</li>
|
<slot name="action"></slot>
|
||||||
<li>
|
</template>
|
||||||
<a class="btn btn-cancel change-icon" @click="flag.showPane = true; flag.editPane = false;">
|
<template v-slot:after>
|
||||||
{{ $t('action.cancel') }}
|
<slot name="after"></slot>
|
||||||
</a>
|
</template>
|
||||||
</li>
|
</action-buttons>
|
||||||
<li>
|
|
||||||
<a class="btn btn-update" @click.prevent="$emit('closeEditPane')">
|
|
||||||
{{ $t('action.valid_and_see')}}
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -82,27 +76,29 @@ import CountrySelection from './AddAddress/CountrySelection';
|
|||||||
import CitySelection from './AddAddress/CitySelection';
|
import CitySelection from './AddAddress/CitySelection';
|
||||||
import AddressSelection from './AddAddress/AddressSelection';
|
import AddressSelection from './AddAddress/AddressSelection';
|
||||||
import AddressMap from './AddAddress/AddressMap';
|
import AddressMap from './AddAddress/AddressMap';
|
||||||
import AddressMore from './AddAddress/AddressMore'
|
import AddressMore from './AddAddress/AddressMore';
|
||||||
|
import ActionButtons from './ActionButtons.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "EditAddressPane",
|
name: "EditPane",
|
||||||
components: {
|
components: {
|
||||||
CountrySelection,
|
CountrySelection,
|
||||||
CitySelection,
|
CitySelection,
|
||||||
AddressSelection,
|
AddressSelection,
|
||||||
AddressMap,
|
AddressMap,
|
||||||
AddressMore
|
AddressMore,
|
||||||
|
ActionButtons
|
||||||
},
|
},
|
||||||
props: [
|
props: [
|
||||||
'context',
|
'context',
|
||||||
'options',
|
'options',
|
||||||
'default',
|
'defaultz',
|
||||||
'flag',
|
'flag',
|
||||||
'entity',
|
'entity',
|
||||||
'errorMsg',
|
'errorMsg',
|
||||||
'insideModal'
|
'insideModal'
|
||||||
],
|
],
|
||||||
emits: ['closeEditPane', 'getCities', 'getReferenceAddresses'],
|
emits: ['getCities', 'getReferenceAddresses'],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
value: false
|
value: false
|
||||||
@ -149,11 +145,7 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
|
||||||
div.address-form {
|
div.address-form {
|
||||||
h4.h3 {
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
div#address_map {
|
div#address_map {
|
||||||
height: 400px;
|
height: 400px;
|
||||||
width: 100%;
|
width: 100%;
|
@ -1,101 +0,0 @@
|
|||||||
<template>
|
|
||||||
|
|
||||||
<div v-if="insideModal == false" class="loading">
|
|
||||||
<i v-if="flag.loading" class="fa fa-circle-o-notch fa-spin fa-2x fa-fw"></i>
|
|
||||||
<span class="sr-only">{{ $t('loading') }}</span>
|
|
||||||
</div>
|
|
||||||
<div v-if="errorMsg && errorMsg.length > 0" class="alert alert-danger">
|
|
||||||
{{ errorMsg }}
|
|
||||||
</div>
|
|
||||||
<div v-if="flag.success" class="alert alert-success">
|
|
||||||
{{ $t(getSuccessText) }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<address-render-box :address="address"></address-render-box>
|
|
||||||
|
|
||||||
<div v-if="showDateFrom" class='address-valid date-since'>
|
|
||||||
<h3>{{ $t(getValidFromDateText) }}</h3>
|
|
||||||
<div class="input-group mb-3">
|
|
||||||
<span class="input-group-text" id="validFrom"><i class="fa fa-fw fa-calendar"></i></span>
|
|
||||||
<input type="date" class="form-control form-control-lg" name="validFrom"
|
|
||||||
v-bind:placeholder="$t(getValidFromDateText)"
|
|
||||||
v-model="validFrom"
|
|
||||||
aria-describedby="validFrom"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<ul v-if="insideModal == false"
|
|
||||||
class="record_actions sticky-form-buttons">
|
|
||||||
<li class="cancel">
|
|
||||||
<a class="btn btn-cancel" v-bind:href="context.backUrl">
|
|
||||||
{{ $t('back_to_the_list') }}</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a @click.prevent="$emit('openEditPane')"
|
|
||||||
class="btn btn-update">
|
|
||||||
{{ $t('action.edit')}}
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a class="btn btn-save"
|
|
||||||
@click.prevent="$emit('submitAddress')">
|
|
||||||
{{ $t('action.save')}}
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import { dateToISO, ISOToDate, ISOToDatetime } from 'ChillMainAssets/chill/js/date.js';
|
|
||||||
import AddressRenderBox from 'ChillMainAssets/vuejs/_components/Entity/AddressRenderBox.vue';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'ShowAddressPane',
|
|
||||||
components: {
|
|
||||||
AddressRenderBox
|
|
||||||
},
|
|
||||||
props: [
|
|
||||||
'context',
|
|
||||||
'options',
|
|
||||||
'default',
|
|
||||||
'flag',
|
|
||||||
'entity',
|
|
||||||
'valid',
|
|
||||||
'errorMsg',
|
|
||||||
'insideModal'
|
|
||||||
],
|
|
||||||
emits: ['openEditPane', 'submitAddress'], //?
|
|
||||||
computed: {
|
|
||||||
address() {
|
|
||||||
return this.entity.address;
|
|
||||||
},
|
|
||||||
validFrom: {
|
|
||||||
set(value) {
|
|
||||||
this.valid.from = ISOToDate(value);
|
|
||||||
},
|
|
||||||
get() {
|
|
||||||
return dateToISO(this.valid.from);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
getValidFromDateText() {
|
|
||||||
return (this.context.entity.type === 'household') ? 'move_date' : 'validFrom';
|
|
||||||
},
|
|
||||||
getSuccessText() {
|
|
||||||
switch (this.context.entity.type) {
|
|
||||||
/*
|
|
||||||
case 'household':
|
|
||||||
return (this.context.edit) ? 'household_address_edit_success' : 'household_address_move_success';
|
|
||||||
case 'person':
|
|
||||||
return (this.context.edit) ? 'person_address_edit_success' : 'person_address_creation_success';
|
|
||||||
*/
|
|
||||||
default:
|
|
||||||
return (this.context.edit) ? 'address_edit_success' : 'address_new_success';
|
|
||||||
}
|
|
||||||
},
|
|
||||||
showDateFrom() {
|
|
||||||
return !this.context.edit && !this.options.hideDateFrom;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
|
@ -0,0 +1,101 @@
|
|||||||
|
<template>
|
||||||
|
|
||||||
|
<div v-if="!hideAddress">
|
||||||
|
<div class="loading">
|
||||||
|
<i v-if="flag.loading" class="fa fa-circle-o-notch fa-spin fa-2x fa-fw"></i>
|
||||||
|
<span class="sr-only">{{ $t('loading') }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="errorMsg && errorMsg.length > 0" class="alert alert-danger">
|
||||||
|
{{ errorMsg }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="flag.success" class="alert alert-success">
|
||||||
|
{{ $t(getSuccessText) }}
|
||||||
|
<span v-if="forceRedirect">{{ $t('wait_redirection') }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="noAddressWithStickyActions" class="mt-5">
|
||||||
|
<p class="chill-no-data-statement">
|
||||||
|
{{ $t('not_yet_address') }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<address-render-box :address="address" :useDatePane="useDatePane"></address-render-box>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<action-buttons
|
||||||
|
:options="this.options"
|
||||||
|
:defaultz="this.defaultz">
|
||||||
|
<template v-slot:action>
|
||||||
|
<li>
|
||||||
|
<button @click.prevent="$emit('openEditPane')"
|
||||||
|
class="btn" :class="getClassButton"
|
||||||
|
type="button" name="button" :title="$t(getTextButton)">
|
||||||
|
<span v-if="displayTextButton">{{ $t(getTextButton) }}</span>
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
</template>
|
||||||
|
</action-buttons>
|
||||||
|
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import AddressRenderBox from 'ChillMainAssets/vuejs/_components/Entity/AddressRenderBox.vue';
|
||||||
|
import ActionButtons from './ActionButtons.vue';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'ShowPane',
|
||||||
|
components: {
|
||||||
|
AddressRenderBox,
|
||||||
|
ActionButtons
|
||||||
|
},
|
||||||
|
props: [
|
||||||
|
'context',
|
||||||
|
'options',
|
||||||
|
'defaultz',
|
||||||
|
'flag',
|
||||||
|
'entity',
|
||||||
|
'errorMsg',
|
||||||
|
'useDatePane'
|
||||||
|
],
|
||||||
|
emits: ['openEditPane'],
|
||||||
|
computed: {
|
||||||
|
address() {
|
||||||
|
return this.entity.address;
|
||||||
|
},
|
||||||
|
displayTextButton() {
|
||||||
|
return (typeof this.options.button !== 'undefined' && typeof this.options.button.displayText !== 'undefined') ?
|
||||||
|
this.options.button.displayText : this.defaultz.button.displayText;
|
||||||
|
},
|
||||||
|
getClassButton() {
|
||||||
|
let type = (this.context.edit) ? this.defaultz.button.type.edit : this.defaultz.button.type.create;
|
||||||
|
let size = (typeof this.options.button !== 'undefined' && this.options.button.size !== null) ?
|
||||||
|
`${this.options.button.size} ` : '';
|
||||||
|
return `${size}${type}`;
|
||||||
|
},
|
||||||
|
getTextButton() {
|
||||||
|
if ( typeof this.options.button.text !== 'undefined'
|
||||||
|
&& ( this.options.button.text.edit !== null
|
||||||
|
|| this.options.button.text.create !== null
|
||||||
|
)) {
|
||||||
|
return (this.context.edit) ? this.options.button.text.edit : this.options.button.text.create;
|
||||||
|
}
|
||||||
|
return (this.context.edit) ? this.defaultz.button.text.edit : this.defaultz.button.text.create;
|
||||||
|
},
|
||||||
|
getSuccessText() {
|
||||||
|
return (this.context.edit) ? 'address_edit_success' : 'address_new_success';
|
||||||
|
},
|
||||||
|
hideAddress() {
|
||||||
|
return (typeof this.options.hideAddress !== 'undefined') ?
|
||||||
|
this.options.hideAddress : this.defaultz.hideAddress;
|
||||||
|
},
|
||||||
|
forceRedirect() {
|
||||||
|
return (!(this.context.backUrl === null || typeof this.context.backUrl === 'undefined'));
|
||||||
|
},
|
||||||
|
noAddressWithStickyActions() {
|
||||||
|
return !this.context.edit && !this.address.id && this.options.stickyActions;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
@ -0,0 +1,76 @@
|
|||||||
|
<template>
|
||||||
|
|
||||||
|
<div v-if="insideModal === false" class="loading">
|
||||||
|
<i v-if="flag.loading" class="fa fa-circle-o-notch fa-spin fa-2x fa-fw"></i>
|
||||||
|
<span class="sr-only">{{ $t('loading') }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="errorMsg && errorMsg.length > 0" class="alert alert-danger">{{ errorMsg }}</div>
|
||||||
|
|
||||||
|
<h4 class="h3">{{ $t('address_suggestions') }}</h4>
|
||||||
|
|
||||||
|
<div class="flex-table AddressSuggestionList">
|
||||||
|
<div class="item-bloc">
|
||||||
|
<div class="float-button bottom">
|
||||||
|
<div class="box">
|
||||||
|
<div class="action">
|
||||||
|
<ul class="record_actions">
|
||||||
|
<li>
|
||||||
|
<button class="btn btn-sm btn-choose">
|
||||||
|
{{ $t('use_this_address') }}
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<ul class="list-content fa-ul">
|
||||||
|
<li>
|
||||||
|
<i class="fa fa-li fa-map-marker"></i>
|
||||||
|
<!--
|
||||||
|
<address-render-box></address-render-box>
|
||||||
|
-->
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<action-buttons v-if="insideModal === false"
|
||||||
|
:options="this.options"
|
||||||
|
:defaultz="this.defaultz">
|
||||||
|
<template v-slot:before>
|
||||||
|
<slot name="before"></slot>
|
||||||
|
</template>
|
||||||
|
<template v-slot:action>
|
||||||
|
<slot name="action"></slot>
|
||||||
|
</template>
|
||||||
|
<template v-slot:after>
|
||||||
|
<slot name="after"></slot>
|
||||||
|
</template>
|
||||||
|
</action-buttons>
|
||||||
|
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import AddressRenderBox from 'ChillMainAssets/vuejs/_components/Entity/AddressRenderBox.vue';
|
||||||
|
import ActionButtons from './ActionButtons.vue';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "SuggestPane",
|
||||||
|
components: {
|
||||||
|
AddressRenderBox,
|
||||||
|
ActionButtons
|
||||||
|
},
|
||||||
|
props: [
|
||||||
|
'context',
|
||||||
|
'options',
|
||||||
|
'defaultz',
|
||||||
|
'flag',
|
||||||
|
'entity',
|
||||||
|
'errorMsg',
|
||||||
|
'insideModal'
|
||||||
|
],
|
||||||
|
computed: {},
|
||||||
|
methods: {}
|
||||||
|
}
|
||||||
|
</script>
|
@ -22,7 +22,7 @@ const addressMessages = {
|
|||||||
corridor: 'Couloir',
|
corridor: 'Couloir',
|
||||||
steps: 'Escalier',
|
steps: 'Escalier',
|
||||||
flat: 'Appartement',
|
flat: 'Appartement',
|
||||||
buildingName: 'Nom du bâtiment',
|
buildingName: 'Résidence',
|
||||||
extra: 'Complément d\'adresse',
|
extra: 'Complément d\'adresse',
|
||||||
distribution: 'Cedex',
|
distribution: 'Cedex',
|
||||||
create_postal_code: 'Localité inconnue. Cliquez ici pour créer une nouvelle localité',
|
create_postal_code: 'Localité inconnue. Cliquez ici pour créer une nouvelle localité',
|
||||||
@ -30,22 +30,18 @@ const addressMessages = {
|
|||||||
postalCode_code: 'Code postal',
|
postalCode_code: 'Code postal',
|
||||||
date: "Date de la nouvelle adresse",
|
date: "Date de la nouvelle adresse",
|
||||||
validFrom: "L'adresse est valable à partir du",
|
validFrom: "L'adresse est valable à partir du",
|
||||||
|
validTo: "L'adresse est valable jusqu'au",
|
||||||
back_to_the_list: 'Retour à la liste',
|
back_to_the_list: 'Retour à la liste',
|
||||||
loading: 'chargement en cours...',
|
loading: 'chargement en cours...',
|
||||||
address_new_success: 'La nouvelle adresse est enregistrée',
|
address_suggestions: "Suggestion d'adresses",
|
||||||
address_edit_success: 'L\'adresse a été mise à jour',
|
address_new_success: 'La nouvelle adresse est enregistrée.',
|
||||||
|
address_edit_success: 'L\'adresse a été mise à jour.',
|
||||||
// person specific
|
wait_redirection: " La page est redirigée.",
|
||||||
add_an_address_to_person: 'Ajouter l\'adresse à la personne',
|
not_yet_address: "Il n'y a pas encore d'adresse. Cliquez sur '+ Créer une adresse'",
|
||||||
person_address_creation_success: 'La nouvelle adresse de la personne est enregistrée',
|
use_this_address: "Utiliser cette adresse",
|
||||||
person_address_edit_success: 'L\'adresse de la personne a été mise à jour',
|
|
||||||
|
|
||||||
// household specific
|
// household specific
|
||||||
move_date: 'Date du déménagement',
|
move_date: 'Date du déménagement',
|
||||||
select_a_existing_address: 'Sélectionner une adresse existante',
|
|
||||||
add_an_address_to_household: 'Enregistrer',
|
|
||||||
household_address_move_success: 'La nouvelle adresse du ménage est enregistrée',
|
|
||||||
household_address_edit_success: 'L\'adresse du ménage a été mise à jour',
|
|
||||||
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -1,13 +1,67 @@
|
|||||||
import { createApp } from 'vue';
|
import { createApp } from 'vue';
|
||||||
import App from './App.vue';
|
|
||||||
import { _createI18n } from 'ChillMainAssets/vuejs/_js/i18n';
|
import { _createI18n } from 'ChillMainAssets/vuejs/_js/i18n';
|
||||||
import { addressMessages } from './i18n';
|
import { addressMessages } from './i18n';
|
||||||
|
import App from './App.vue';
|
||||||
|
|
||||||
const i18n = _createI18n( addressMessages );
|
const i18n = _createI18n( addressMessages );
|
||||||
|
|
||||||
|
let containers = document.querySelectorAll('.address-container');
|
||||||
|
containers.forEach((container) => {
|
||||||
|
|
||||||
const app = createApp({
|
const app = createApp({
|
||||||
template: `<app></app>`,
|
template: `<app v-bind:addAddress="this.addAddress" ></app>`,
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
addAddress: {
|
||||||
|
context: {
|
||||||
|
target: {
|
||||||
|
name: container.dataset.targetName,
|
||||||
|
id: parseInt(container.dataset.targetId)
|
||||||
|
},
|
||||||
|
edit: container.dataset.mode === 'edit', //boolean
|
||||||
|
addressId: parseInt(container.dataset.addressId) || null,
|
||||||
|
backUrl: container.dataset.backUrl || null
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
/// Options override default.
|
||||||
|
/// null value take default component value defined in AddAddress data()
|
||||||
|
button: {
|
||||||
|
text: {
|
||||||
|
create: container.dataset.buttonText || null,
|
||||||
|
edit: container.dataset.buttonText || null
|
||||||
|
},
|
||||||
|
size: container.dataset.buttonSize || null,
|
||||||
|
displayText: container.dataset.buttonDisplayText !== 'false' //boolean, default: true
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Modal title text if create or edit address (trans chain, see i18n)
|
||||||
|
title: {
|
||||||
|
create: container.dataset.modalTitle || null,
|
||||||
|
edit: container.dataset.modalTitle || null
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Display panes in Modal for step123
|
||||||
|
openPanesInModal: container.dataset.openPanesInModal !== 'false', //boolean, default: true
|
||||||
|
|
||||||
|
/// Display actions buttons of panes in a sticky-form-button navbar
|
||||||
|
stickyActions: container.dataset.stickyActions === 'true', //boolean, default: false
|
||||||
|
|
||||||
|
/// Use Date fields
|
||||||
|
useDate: {
|
||||||
|
validFrom: container.dataset.useValidFrom === 'true', //boolean, default: false
|
||||||
|
validTo: container.dataset.useValidTo === 'true' //boolean, default: false
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Don't display show renderbox Address: showPane display only a button
|
||||||
|
hideAddress: container.dataset.hideAddress === 'true' //boolean, default: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.use(i18n)
|
.use(i18n)
|
||||||
.component('app', App)
|
.component('app', App)
|
||||||
.mount('#address');
|
.mount(container);
|
||||||
|
|
||||||
|
//console.log('container dataset', container.dataset);
|
||||||
|
});
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
</p>
|
</p>
|
||||||
</component>
|
</component>
|
||||||
|
|
||||||
<div v-if="isMultiline === true">
|
<div v-if="isMultiline === true" class="address-more">
|
||||||
<div v-if="address.floor">
|
<div v-if="address.floor">
|
||||||
<span class="floor">
|
<span class="floor">
|
||||||
<b>{{ $t('floor') }}</b>: {{ address.floor }}
|
<b>{{ $t('floor') }}</b>: {{ address.floor }}
|
||||||
@ -54,6 +54,19 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div v-if="useDatePane === true" class="address-more">
|
||||||
|
<div v-if="address.validFrom">
|
||||||
|
<span class="validFrom">
|
||||||
|
<b>{{ $t('validFrom') }}</b>: {{ $d(address.validFrom.date) }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div v-if="address.validTo">
|
||||||
|
<span class="validTo">
|
||||||
|
<b>{{ $t('validTo') }}</b>: {{ $d(address.validTo.date) }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
</component>
|
</component>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -67,6 +80,10 @@ export default {
|
|||||||
isMultiline: {
|
isMultiline: {
|
||||||
default: true,
|
default: true,
|
||||||
type: Boolean
|
type: Boolean
|
||||||
|
},
|
||||||
|
useDatePane: {
|
||||||
|
default: false,
|
||||||
|
type: Boolean
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
@ -77,6 +77,8 @@ const messages = {
|
|||||||
man: "Né le",
|
man: "Né le",
|
||||||
woman: "Née le"
|
woman: "Née le"
|
||||||
},
|
},
|
||||||
|
deathdate: "Date de décès",
|
||||||
|
years_old: "ans",
|
||||||
household_without_address: "Le ménage de l'usager est sans adresse",
|
household_without_address: "Le ménage de l'usager est sans adresse",
|
||||||
no_data: "Aucune information renseignée",
|
no_data: "Aucune information renseignée",
|
||||||
type: {
|
type: {
|
||||||
|
@ -0,0 +1,78 @@
|
|||||||
|
{#
|
||||||
|
This Twig template include load vue_address component.
|
||||||
|
It push all variables from context in Address/App.vue.
|
||||||
|
|
||||||
|
OPTIONS
|
||||||
|
* targetEntity {
|
||||||
|
name: string
|
||||||
|
id: integer
|
||||||
|
}
|
||||||
|
* mode string ['edit*'|'new']
|
||||||
|
* addressId integer
|
||||||
|
* backUrl twig route: path('route', {parameters})
|
||||||
|
* modalTitle twig translated chain
|
||||||
|
* buttonText twig translated chain
|
||||||
|
* buttonSize bootstrap class like 'btn-sm'
|
||||||
|
* buttonDisplayText bool (default: true)
|
||||||
|
* openPanesInModal bool (default: true)
|
||||||
|
* stickyActions bool (default: false)
|
||||||
|
* useValidFrom bool (default: false)
|
||||||
|
* useValidTo bool (default: false)
|
||||||
|
* hideAddress bool (default: false)
|
||||||
|
#}
|
||||||
|
<div class="address-container"
|
||||||
|
|
||||||
|
data-target-name="{{ targetEntity.name|e('html_attr') }}"
|
||||||
|
data-target-id="{{ targetEntity.id|e('html_attr') }}"
|
||||||
|
|
||||||
|
{% if 'edit' in app.request.get('_route') %}
|
||||||
|
data-mode="edit"
|
||||||
|
data-address-id="{{ app.request.get('address_id')|e('html_attr') }}"
|
||||||
|
{% elseif mode is defined and mode == 'edit' %}
|
||||||
|
data-mode="edit"
|
||||||
|
data-address-id="{{ addressId|e('html_attr') }}"
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if backUrl is defined %}
|
||||||
|
data-back-url="{{ backUrl|e('html_attr') }}"
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if modalTitle is defined %}
|
||||||
|
data-modal-title="{{ modalTitle|trans|e('html_attr') }}"
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if buttonText is defined %}
|
||||||
|
data-button-text="{{ buttonText|trans|e('html_attr') }}"
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if buttonSize is defined %}
|
||||||
|
data-button-size="{{ buttonSize|e('html_attr') }}"
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if buttonDisplayText is defined and buttonDisplayText != 1 %}
|
||||||
|
data-button-display-text="false"
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if openPanesInModal is defined and openPanesInModal != 1 %}
|
||||||
|
data-open-panes-in-modal="false"
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if stickyActions is defined and stickyActions == 1 %}
|
||||||
|
data-sticky-actions="true"
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if useValidFrom is defined and useValidFrom == 1 %}
|
||||||
|
data-use-valid-from="true"
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if useValidTo is defined and useValidTo == 1 %}
|
||||||
|
data-use-valid-to="true"
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if hideAddress is defined and hideAddress == 1 %}
|
||||||
|
data-hide-address="true"
|
||||||
|
{% endif %}
|
||||||
|
></div>
|
||||||
|
|
||||||
|
{{ encore_entry_script_tags('vue_address') }}
|
||||||
|
{{ encore_entry_link_tags('vue_address') }}
|
@ -0,0 +1,8 @@
|
|||||||
|
{% extends '@ChillMain/Admin/layout.html.twig' %}
|
||||||
|
|
||||||
|
{% block title %}{{ ('crud.' ~ crud_name ~ '.index.title')|trans({'%crud_name%': crud_name}) }}{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
{% embed '@ChillMain/CRUD/_index.html.twig' %}
|
||||||
|
{% endembed %}
|
||||||
|
{% endblock content %}
|
@ -34,25 +34,25 @@
|
|||||||
|
|
||||||
{% macro extended(address, options) %}
|
{% macro extended(address, options) %}
|
||||||
{% if address.floor is not empty %}
|
{% if address.floor is not empty %}
|
||||||
<span class="floor">{{ address.floor }}</span>
|
<span class="floor">{{ 'address more.floor'|trans }} {{ address.floor }}</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if address.corridor is not empty %}
|
{% if address.corridor is not empty %}
|
||||||
<span class="corridor">{{ address.corridor }}</span>
|
<span class="corridor">{{ 'address more.corridor'|trans }} {{ address.corridor }}</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if address.steps is not empty %}
|
{% if address.steps is not empty %}
|
||||||
<span class="steps">{{ address.steps }}</span>
|
<span class="steps">{{ 'address more.steps'|trans }} {{ address.steps }}</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if address.buildingName is not empty %}
|
{% if address.buildingName is not empty %}
|
||||||
<span class="buildingName">{{ address.buildingName }}</span>
|
<span class="buildingName">{{ 'address more.buildingName'|trans }} {{ address.buildingName }}</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if address.flat is not empty %}
|
{% if address.flat is not empty %}
|
||||||
<span class="flat">{{ address.flat }}</span>
|
<span class="flat">{{ 'address more.flat'|trans }} {{ address.flat }}</span>
|
||||||
{% endif %}
|
|
||||||
{% if address.distribution is not empty %}
|
|
||||||
<span class="distribution">{{ address.distribution }}</span>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if address.extra is not empty %}
|
{% if address.extra is not empty %}
|
||||||
<span class="extra">{{ address.extra }}</span>
|
<span class="extra">{{ 'address more.extra'|trans }} {{ address.extra }}</span>
|
||||||
|
{% endif %}
|
||||||
|
{% if address.distribution is not empty %}
|
||||||
|
<span class="distribution">{{ 'address more.distribution'|trans }} {{ address.distribution }}</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endmacro %}
|
{% endmacro %}
|
||||||
|
|
||||||
|
@ -0,0 +1,26 @@
|
|||||||
|
{% extends '@ChillMain/CRUD/Admin/index.html.twig' %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
{% embed '@ChillMain/CRUD/_index.html.twig' %}
|
||||||
|
{% block table_entities_thead_tr %}
|
||||||
|
<th>id</th>
|
||||||
|
<th>label</th>
|
||||||
|
<th> </th>
|
||||||
|
{% endblock %}
|
||||||
|
{% block table_entities_tbody %}
|
||||||
|
{% for entity in entities %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ entity.id }}</td>
|
||||||
|
<td>{{ entity.label|localize_translatable_string }}</td>
|
||||||
|
<td>
|
||||||
|
<ul class="record_actions">
|
||||||
|
<li>
|
||||||
|
<a href="{{ chill_path_add_return_path('chill_crud_admin_user_job_edit', { 'id': entity.id}) }}" class="btn btn-sm btn-edit btn-mini"></a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
{% endblock %}
|
||||||
|
{% endembed %}
|
||||||
|
{% endblock content %}
|
@ -31,7 +31,6 @@ namespace Chill\MainBundle\Search;
|
|||||||
*/
|
*/
|
||||||
interface SearchInterface
|
interface SearchInterface
|
||||||
{
|
{
|
||||||
|
|
||||||
const SEARCH_PREVIEW_OPTION = '_search_preview';
|
const SEARCH_PREVIEW_OPTION = '_search_preview';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -23,6 +23,11 @@ use Chill\MainBundle\Entity\User;
|
|||||||
use Chill\MainBundle\Entity\Center;
|
use Chill\MainBundle\Entity\Center;
|
||||||
use Chill\MainBundle\Entity\HasCenterInterface;
|
use Chill\MainBundle\Entity\HasCenterInterface;
|
||||||
use Chill\MainBundle\Entity\HasScopeInterface;
|
use Chill\MainBundle\Entity\HasScopeInterface;
|
||||||
|
use Chill\MainBundle\Security\Resolver\CenterResolverDispatcher;
|
||||||
|
use Chill\MainBundle\Security\Resolver\ScopeResolverDispatcher;
|
||||||
|
use Chill\MainBundle\Security\Resolver\ScopeResolverInterface;
|
||||||
|
use Psr\Log\LoggerInterface;
|
||||||
|
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
|
||||||
use Symfony\Component\Security\Core\Role\RoleHierarchyInterface;
|
use Symfony\Component\Security\Core\Role\RoleHierarchyInterface;
|
||||||
use Symfony\Component\Security\Core\Role\Role;
|
use Symfony\Component\Security\Core\Role\Role;
|
||||||
use Chill\MainBundle\Entity\Scope;
|
use Chill\MainBundle\Entity\Scope;
|
||||||
@ -40,11 +45,7 @@ use Chill\MainBundle\Entity\RoleScope;
|
|||||||
*/
|
*/
|
||||||
class AuthorizationHelper
|
class AuthorizationHelper
|
||||||
{
|
{
|
||||||
/**
|
protected RoleHierarchyInterface $roleHierarchy;
|
||||||
*
|
|
||||||
* @var RoleHierarchyInterface
|
|
||||||
*/
|
|
||||||
protected $roleHierarchy;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The role in a hierarchy, given by the parameter
|
* The role in a hierarchy, given by the parameter
|
||||||
@ -52,36 +53,53 @@ class AuthorizationHelper
|
|||||||
*
|
*
|
||||||
* @var string[]
|
* @var string[]
|
||||||
*/
|
*/
|
||||||
protected $hierarchy;
|
protected array $hierarchy;
|
||||||
|
|
||||||
/**
|
protected EntityManagerInterface $em;
|
||||||
*
|
|
||||||
* @var EntityManagerInterface
|
protected CenterResolverDispatcher $centerResolverDispatcher;
|
||||||
*/
|
|
||||||
protected $em;
|
protected ScopeResolverDispatcher $scopeResolverDispatcher;
|
||||||
|
|
||||||
|
protected LoggerInterface $logger;
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
RoleHierarchyInterface $roleHierarchy,
|
RoleHierarchyInterface $roleHierarchy,
|
||||||
$hierarchy,
|
ParameterBagInterface $parameterBag,
|
||||||
EntityManagerInterface $em
|
EntityManagerInterface $em,
|
||||||
|
CenterResolverDispatcher $centerResolverDispatcher,
|
||||||
|
LoggerInterface $logger,
|
||||||
|
ScopeResolverDispatcher $scopeResolverDispatcher
|
||||||
) {
|
) {
|
||||||
$this->roleHierarchy = $roleHierarchy;
|
$this->roleHierarchy = $roleHierarchy;
|
||||||
$this->hierarchy = $hierarchy;
|
$this->hierarchy = $parameterBag->get('security.role_hierarchy.roles');
|
||||||
$this->em = $em;
|
$this->em = $em;
|
||||||
|
$this->centerResolverDispatcher = $centerResolverDispatcher;
|
||||||
|
$this->logger = $logger;
|
||||||
|
$this->scopeResolverDispatcher = $scopeResolverDispatcher;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determines if a user is active on this center
|
* Determines if a user is active on this center
|
||||||
*
|
*
|
||||||
|
* If
|
||||||
|
*
|
||||||
* @param User $user
|
* @param User $user
|
||||||
* @param Center $center
|
* @param Center|Center[] $center May be an array of center
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
public function userCanReachCenter(User $user, Center $center)
|
public function userCanReachCenter(User $user, $center)
|
||||||
{
|
{
|
||||||
|
if ($center instanceof \Traversable) {
|
||||||
|
foreach ($center as $c) {
|
||||||
|
if ($c->userCanReachCenter($user, $c)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
} elseif ($center instanceof Center) {
|
||||||
foreach ($user->getGroupCenters() as $groupCenter) {
|
foreach ($user->getGroupCenters() as $groupCenter) {
|
||||||
if ($center->getId() === $groupCenter->getCenter()->getId()) {
|
if ($center->getId() === $groupCenter->getCenter()->getId()) {
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -89,6 +107,10 @@ class AuthorizationHelper
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
throw new \UnexpectedValueException(sprintf("The entity given is not an ".
|
||||||
|
"instance of %s, %s given", Center::class, get_class($center)));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* Determines if the user has access to the given entity.
|
* Determines if the user has access to the given entity.
|
||||||
@ -97,22 +119,44 @@ class AuthorizationHelper
|
|||||||
* the scope is taken into account.
|
* the scope is taken into account.
|
||||||
*
|
*
|
||||||
* @param User $user
|
* @param User $user
|
||||||
* @param HasCenterInterface $entity the entity may also implement HasScopeInterface
|
* @param mixed $entity the entity may also implement HasScopeInterface
|
||||||
* @param string|Role $attribute
|
* @param string|Role $attribute
|
||||||
* @return boolean true if the user has access
|
* @return boolean true if the user has access
|
||||||
*/
|
*/
|
||||||
public function userHasAccess(User $user, HasCenterInterface $entity, $attribute)
|
public function userHasAccess(User $user, $entity, $attribute)
|
||||||
{
|
{
|
||||||
|
$center = $this->centerResolverDispatcher->resolveCenter($entity);
|
||||||
|
|
||||||
$center = $entity->getCenter();
|
if (is_iterable($center)) {
|
||||||
|
foreach ($center as $c) {
|
||||||
|
if ($this->userHasAccessForCenter($user, $c, $entity, $attribute)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
} elseif ($center instanceof Center) {
|
||||||
|
return $this->userHasAccessForCenter($user, $center, $entity, $attribute);
|
||||||
|
} elseif (NULL === $center) {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
throw new \UnexpectedValueException("could not resolver a center");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function userHasAccessForCenter(User $user, Center $center, $entity, $attribute): bool
|
||||||
|
{
|
||||||
if (!$this->userCanReachCenter($user, $center)) {
|
if (!$this->userCanReachCenter($user, $center)) {
|
||||||
|
$this->logger->debug("user cannot reach center of entity", [
|
||||||
|
'center_name' => $center->getName(),
|
||||||
|
'user' => $user->getUsername()
|
||||||
|
]);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ($user->getGroupCenters() as $groupCenter){
|
foreach ($user->getGroupCenters() as $groupCenter){
|
||||||
//filter on center
|
//filter on center
|
||||||
if ($groupCenter->getCenter()->getId() === $entity->getCenter()->getId()) {
|
if ($groupCenter->getCenter() === $center) {
|
||||||
$permissionGroup = $groupCenter->getPermissionsGroup();
|
$permissionGroup = $groupCenter->getPermissionsGroup();
|
||||||
//iterate on roleScopes
|
//iterate on roleScopes
|
||||||
foreach($permissionGroup->getRoleScopes() as $roleScope) {
|
foreach($permissionGroup->getRoleScopes() as $roleScope) {
|
||||||
@ -120,23 +164,34 @@ class AuthorizationHelper
|
|||||||
if ($this->isRoleReached($attribute, $roleScope->getRole())) {
|
if ($this->isRoleReached($attribute, $roleScope->getRole())) {
|
||||||
//if yes, we have a right on something...
|
//if yes, we have a right on something...
|
||||||
// perform check on scope if necessary
|
// perform check on scope if necessary
|
||||||
if ($entity instanceof HasScopeInterface) {
|
if ($this->scopeResolverDispatcher->isConcerned($entity)) {
|
||||||
$scope = $entity->getScope();
|
$scope = $this->scopeResolverDispatcher->resolveScope($entity);
|
||||||
if ($scope === NULL) {
|
|
||||||
|
if (NULL === $scope) {
|
||||||
|
return true;
|
||||||
|
} elseif (is_iterable($scope)) {
|
||||||
|
foreach ($scope as $s) {
|
||||||
|
if ($s === $roleScope->getScope()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if ($scope->getId() === $roleScope
|
}
|
||||||
->getScope()->getId()) {
|
} else {
|
||||||
|
if ($scope === $roleScope->getScope()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
$this->logger->debug("user can reach center entity, but not role", [
|
||||||
}
|
'username' => $user->getUsername(),
|
||||||
|
'center' => $center->getName()
|
||||||
|
]);
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -210,11 +265,11 @@ class AuthorizationHelper
|
|||||||
* @deprecated Use getReachableCircles
|
* @deprecated Use getReachableCircles
|
||||||
*
|
*
|
||||||
* @param User $user
|
* @param User $user
|
||||||
* @param Role $role
|
* @param string role
|
||||||
* @param Center $center
|
* @param Center|Center[] $center
|
||||||
* @return Scope[]
|
* @return Scope[]
|
||||||
*/
|
*/
|
||||||
public function getReachableScopes(User $user, $role, Center $center)
|
public function getReachableScopes(User $user, $role, $center)
|
||||||
{
|
{
|
||||||
if ($role instanceof Role) {
|
if ($role instanceof Role) {
|
||||||
$role = $role->getRole();
|
$role = $role->getRole();
|
||||||
@ -228,15 +283,24 @@ class AuthorizationHelper
|
|||||||
*
|
*
|
||||||
* @param User $user
|
* @param User $user
|
||||||
* @param string|Role $role
|
* @param string|Role $role
|
||||||
* @param Center $center
|
* @param Center|Center[] $center
|
||||||
* @return Scope[]
|
* @return Scope[]
|
||||||
*/
|
*/
|
||||||
public function getReachableCircles(User $user, $role, Center $center)
|
public function getReachableCircles(User $user, $role, $center)
|
||||||
{
|
{
|
||||||
|
$scopes = [];
|
||||||
|
|
||||||
|
if (is_iterable($center)) {
|
||||||
|
foreach ($center as $c) {
|
||||||
|
$scopes = \array_merge($scopes, $this->getReachableCircles($user, $role, $c));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $scopes;
|
||||||
|
}
|
||||||
|
|
||||||
if ($role instanceof Role) {
|
if ($role instanceof Role) {
|
||||||
$role = $role->getRole();
|
$role = $role->getRole();
|
||||||
}
|
}
|
||||||
$scopes = array();
|
|
||||||
|
|
||||||
foreach ($user->getGroupCenters() as $groupCenter){
|
foreach ($user->getGroupCenters() as $groupCenter){
|
||||||
if ($center->getId() === $groupCenter->getCenter()->getId()) {
|
if ($center->getId() === $groupCenter->getCenter()->getId()) {
|
||||||
@ -257,16 +321,12 @@ class AuthorizationHelper
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param Role $role
|
* @return User[]
|
||||||
* @param Center $center
|
|
||||||
* @param Scope $circle
|
|
||||||
* @return Users
|
|
||||||
*/
|
*/
|
||||||
public function findUsersReaching(Role $role, Center $center, Scope $circle = null)
|
public function findUsersReaching(string $role, Center $center, Scope $circle = null): array
|
||||||
{
|
{
|
||||||
$parents = $this->getParentRoles($role);
|
$parents = $this->getParentRoles($role);
|
||||||
$parents[] = $role;
|
$parents[] = $role;
|
||||||
$parentRolesString = \array_map(function(Role $r) { return $r->getRole(); }, $parents);
|
|
||||||
|
|
||||||
$qb = $this->em->createQueryBuilder();
|
$qb = $this->em->createQueryBuilder();
|
||||||
$qb
|
$qb
|
||||||
@ -276,7 +336,7 @@ class AuthorizationHelper
|
|||||||
->join('gc.permissionsGroup', 'pg')
|
->join('gc.permissionsGroup', 'pg')
|
||||||
->join('pg.roleScopes', 'rs')
|
->join('pg.roleScopes', 'rs')
|
||||||
->where('gc.center = :center')
|
->where('gc.center = :center')
|
||||||
->andWhere($qb->expr()->in('rs.role', $parentRolesString))
|
->andWhere($qb->expr()->in('rs.role', $parents))
|
||||||
;
|
;
|
||||||
|
|
||||||
$qb->setParameter('center', $center);
|
$qb->setParameter('center', $center);
|
||||||
@ -310,21 +370,16 @@ class AuthorizationHelper
|
|||||||
* which are registered into Chill are taken into account.
|
* which are registered into Chill are taken into account.
|
||||||
*
|
*
|
||||||
* @param Role $role
|
* @param Role $role
|
||||||
* @return Role[] the role which give access to the given $role
|
* @return string[] the role which give access to the given $role
|
||||||
*/
|
*/
|
||||||
public function getParentRoles(Role $role)
|
public function getParentRoles($role): array
|
||||||
{
|
{
|
||||||
$parentRoles = [];
|
$parentRoles = [];
|
||||||
// transform the roles from role hierarchy from string to Role
|
// transform the roles from role hierarchy from string to Role
|
||||||
$roles = \array_map(
|
$roles = \array_keys($this->hierarchy);
|
||||||
function($string) {
|
|
||||||
return new Role($string);
|
|
||||||
},
|
|
||||||
\array_keys($this->hierarchy)
|
|
||||||
);
|
|
||||||
|
|
||||||
foreach ($roles as $r) {
|
foreach ($roles as $r) {
|
||||||
$childRoles = $this->roleHierarchy->getReachableRoleNames([$r->getRole()]);
|
$childRoles = $this->roleHierarchy->getReachableRoleNames([$r]);
|
||||||
|
|
||||||
if (\in_array($role, $childRoles)) {
|
if (\in_array($role, $childRoles)) {
|
||||||
$parentRoles[] = $r;
|
$parentRoles[] = $r;
|
||||||
|
@ -0,0 +1,62 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Chill\MainBundle\Security\Authorization;
|
||||||
|
|
||||||
|
use Chill\MainBundle\Entity\User;
|
||||||
|
use Chill\MainBundle\Security\Resolver\CenterResolverDispatcher;
|
||||||
|
|
||||||
|
final class DefaultVoterHelper implements VoterHelperInterface
|
||||||
|
{
|
||||||
|
protected AuthorizationHelper $authorizationHelper;
|
||||||
|
|
||||||
|
protected CenterResolverDispatcher $centerResolverDispatcher;
|
||||||
|
|
||||||
|
protected array $configuration = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param AuthorizationHelper $authorizationHelper
|
||||||
|
* @param CenterResolverDispatcher $centerResolverDispatcher
|
||||||
|
* @param array $configuration
|
||||||
|
*/
|
||||||
|
public function __construct(
|
||||||
|
AuthorizationHelper $authorizationHelper,
|
||||||
|
CenterResolverDispatcher $centerResolverDispatcher,
|
||||||
|
array $configuration
|
||||||
|
) {
|
||||||
|
$this->authorizationHelper = $authorizationHelper;
|
||||||
|
$this->centerResolverDispatcher = $centerResolverDispatcher;
|
||||||
|
$this->configuration = $configuration;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function supports($attribute, $subject): bool
|
||||||
|
{
|
||||||
|
foreach ($this->configuration as list($attributes, $subj)) {
|
||||||
|
if ($subj === null) {
|
||||||
|
if ($subject === null && \in_array($attribute, $attributes)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} elseif ($subject instanceof $subj) {
|
||||||
|
return \in_array($attribute, $attributes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function voteOnAttribute($attribute, $subject, $token): bool
|
||||||
|
{
|
||||||
|
if (!$token->getUser() instanceof User) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (NULL === $subject) {
|
||||||
|
return 0 < count($this->authorizationHelper->getReachableCenters($token->getUser(), $attribute, null));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->authorizationHelper->userHasAccess(
|
||||||
|
$token->getUser(),
|
||||||
|
$subject,
|
||||||
|
$attribute
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Chill\MainBundle\Security\Authorization;
|
||||||
|
|
||||||
|
use Chill\MainBundle\Security\Resolver\CenterResolverDispatcher;
|
||||||
|
|
||||||
|
class DefaultVoterHelperFactory implements VoterHelperFactoryInterface
|
||||||
|
{
|
||||||
|
protected AuthorizationHelper $authorizationHelper;
|
||||||
|
protected CenterResolverDispatcher $centerResolverDispatcher;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
AuthorizationHelper $authorizationHelper,
|
||||||
|
CenterResolverDispatcher $centerResolverDispatcher
|
||||||
|
) {
|
||||||
|
$this->authorizationHelper = $authorizationHelper;
|
||||||
|
$this->centerResolverDispatcher = $centerResolverDispatcher;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function generate($context): VoterGeneratorInterface
|
||||||
|
{
|
||||||
|
return new DefaultVoterHelperGenerator(
|
||||||
|
$this->authorizationHelper,
|
||||||
|
$this->centerResolverDispatcher
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,36 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Chill\MainBundle\Security\Authorization;
|
||||||
|
|
||||||
|
use Chill\MainBundle\Security\Resolver\CenterResolverDispatcher;
|
||||||
|
|
||||||
|
final class DefaultVoterHelperGenerator implements VoterGeneratorInterface
|
||||||
|
{
|
||||||
|
protected AuthorizationHelper $authorizationHelper;
|
||||||
|
protected CenterResolverDispatcher $centerResolverDispatcher;
|
||||||
|
protected array $configuration = [];
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
AuthorizationHelper $authorizationHelper,
|
||||||
|
CenterResolverDispatcher $centerResolverDispatcher
|
||||||
|
) {
|
||||||
|
$this->authorizationHelper = $authorizationHelper;
|
||||||
|
$this->centerResolverDispatcher = $centerResolverDispatcher;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addCheckFor(?string $subject, array $attributes): self
|
||||||
|
{
|
||||||
|
$this->configuration[] = [$attributes, $subject];
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function build(): VoterHelperInterface
|
||||||
|
{
|
||||||
|
return new DefaultVoterHelper(
|
||||||
|
$this->authorizationHelper,
|
||||||
|
$this->centerResolverDispatcher,
|
||||||
|
$this->configuration
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,15 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Chill\MainBundle\Security\Authorization;
|
||||||
|
|
||||||
|
interface VoterGeneratorInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param string $class The FQDN of a class
|
||||||
|
* @param array $attributes an array of attributes
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function addCheckFor(?string $class, array $attributes): self;
|
||||||
|
|
||||||
|
public function build(): VoterHelperInterface;
|
||||||
|
}
|
@ -0,0 +1,8 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Chill\MainBundle\Security\Authorization;
|
||||||
|
|
||||||
|
interface VoterHelperFactoryInterface
|
||||||
|
{
|
||||||
|
public function generate($context): VoterGeneratorInterface;
|
||||||
|
}
|
@ -0,0 +1,12 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Chill\MainBundle\Security\Authorization;
|
||||||
|
|
||||||
|
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
||||||
|
|
||||||
|
interface VoterHelperInterface
|
||||||
|
{
|
||||||
|
public function supports($attribute, $subject): bool;
|
||||||
|
|
||||||
|
public function voteOnAttribute($attribute, $subject, TokenInterface $token);
|
||||||
|
}
|
@ -0,0 +1,32 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Chill\MainBundle\Security\Resolver;
|
||||||
|
|
||||||
|
class CenterResolverDispatcher
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var iterabble|CenterResolverInterface[]
|
||||||
|
*/
|
||||||
|
private iterable $resolvers = [];
|
||||||
|
|
||||||
|
public function __construct(iterable $resolvers)
|
||||||
|
{
|
||||||
|
$this->resolvers = $resolvers;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param mixed $entity
|
||||||
|
* @param array|null $options
|
||||||
|
* @return null|Center|Center[]
|
||||||
|
*/
|
||||||
|
public function resolveCenter($entity, ?array $options = [])
|
||||||
|
{
|
||||||
|
foreach($this->resolvers as $priority => $resolver) {
|
||||||
|
if ($resolver->supports($entity, $options)) {
|
||||||
|
return $resolver->resolveCenter($entity, $options);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Chill\MainBundle\Security\Resolver;
|
||||||
|
|
||||||
|
use Chill\MainBundle\Entity\Center;
|
||||||
|
|
||||||
|
interface CenterResolverInterface
|
||||||
|
{
|
||||||
|
public function supports($entity, ?array $options = []): bool;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param $entity
|
||||||
|
* @param array|null $options
|
||||||
|
* @return Center|array|Center[]
|
||||||
|
*/
|
||||||
|
public function resolveCenter($entity, ?array $options = []);
|
||||||
|
|
||||||
|
public static function getDefaultPriority(): int;
|
||||||
|
}
|
@ -0,0 +1,37 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Chill\MainBundle\Security\Resolver;
|
||||||
|
|
||||||
|
use Chill\MainBundle\Entity\Center;
|
||||||
|
use Chill\MainBundle\Entity\HasCenterInterface;
|
||||||
|
use Chill\MainBundle\Entity\HasCentersInterface;
|
||||||
|
|
||||||
|
class DefaultCenterResolver implements CenterResolverInterface
|
||||||
|
{
|
||||||
|
public function supports($entity, ?array $options = []): bool
|
||||||
|
{
|
||||||
|
return $entity instanceof HasCenterInterface || $entity instanceof HasCentersInterface;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*
|
||||||
|
* @param HasCenterInterface $entity
|
||||||
|
* @param array $options
|
||||||
|
*/
|
||||||
|
public function resolveCenter($entity, ?array $options = [])
|
||||||
|
{
|
||||||
|
if ($entity instanceof HasCenterInterface) {
|
||||||
|
return $entity->getCenter();
|
||||||
|
} elseif ($entity instanceof HasCentersInterface) {
|
||||||
|
return $entity->getCenters();
|
||||||
|
} else {
|
||||||
|
throw new \UnexpectedValueException("should be an instanceof");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getDefaultPriority(): int
|
||||||
|
{
|
||||||
|
return -256;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,42 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Chill\MainBundle\Security\Resolver;
|
||||||
|
|
||||||
|
use Chill\MainBundle\Entity\HasScopeInterface;
|
||||||
|
use Chill\MainBundle\Entity\HasScopesInterface;
|
||||||
|
|
||||||
|
class DefaultScopeResolver implements ScopeResolverInterface
|
||||||
|
{
|
||||||
|
|
||||||
|
public function supports($entity, ?array $options = []): bool
|
||||||
|
{
|
||||||
|
return $entity instanceof HasScopeInterface || $entity instanceof HasScopesInterface;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*
|
||||||
|
* @param HasScopeInterface|HasScopesInterface $entity
|
||||||
|
*/
|
||||||
|
public function resolveScope($entity, ?array $options = [])
|
||||||
|
{
|
||||||
|
if ($entity instanceof HasScopeInterface) {
|
||||||
|
return $entity->getScope();
|
||||||
|
} elseif ($entity instanceof HasScopesInterface) {
|
||||||
|
return $entity->getScopes();
|
||||||
|
} else {
|
||||||
|
throw new \UnexpectedValueException("should be an instanceof %s or %s",
|
||||||
|
HasScopesInterface::class, HasScopeInterface::class);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isConcerned($entity, ?array $options = []): bool
|
||||||
|
{
|
||||||
|
return $entity instanceof HasScopeInterface || $entity instanceof HasScopesInterface;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getDefaultPriority(): int
|
||||||
|
{
|
||||||
|
return -256;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,36 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Chill\MainBundle\Security\Resolver;
|
||||||
|
|
||||||
|
use Twig\TwigFilter;
|
||||||
|
|
||||||
|
final class ResolverTwigExtension extends \Twig\Extension\AbstractExtension
|
||||||
|
{
|
||||||
|
private CenterResolverDispatcher $centerResolverDispatcher;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param CenterResolverDispatcher $centerResolverDispatcher
|
||||||
|
*/
|
||||||
|
public function __construct(CenterResolverDispatcher $centerResolverDispatcher)
|
||||||
|
{
|
||||||
|
$this->centerResolverDispatcher = $centerResolverDispatcher;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getFilters()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
new TwigFilter('chill_resolve_center', [$this, 'resolveCenter'])
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param mixed $entity
|
||||||
|
* @param array|null $options
|
||||||
|
* @return Center|Center[]|null
|
||||||
|
*/
|
||||||
|
public function resolveCenter($entity, ?array $options = [])
|
||||||
|
{
|
||||||
|
return $this->centerResolverDispatcher->resolveCenter($entity, $options);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,44 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Chill\MainBundle\Security\Resolver;
|
||||||
|
|
||||||
|
use Chill\MainBundle\Entity\Scope;
|
||||||
|
|
||||||
|
final class ScopeResolverDispatcher
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var iterable|ScopeResolverInterface[]
|
||||||
|
*/
|
||||||
|
private iterable $resolvers;
|
||||||
|
|
||||||
|
public function __construct(iterable $resolvers)
|
||||||
|
{
|
||||||
|
$this->resolvers = $resolvers;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param $entity
|
||||||
|
* @return Scope|Scope[]|iterable
|
||||||
|
*/
|
||||||
|
public function resolveScope($entity, ?array $options = [])
|
||||||
|
{
|
||||||
|
foreach ($this->resolvers as $resolver) {
|
||||||
|
if ($resolver->supports($entity, $options)) {
|
||||||
|
return $resolver->resolveScope($entity, $options);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isConcerned($entity, ?array $options = []): bool
|
||||||
|
{
|
||||||
|
foreach ($this->resolvers as $resolver) {
|
||||||
|
if ($resolver->supports($entity, $options)) {
|
||||||
|
return $resolver->isConcerned($entity, $options);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,35 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Chill\MainBundle\Security\Resolver;
|
||||||
|
|
||||||
|
use Chill\MainBundle\Entity\Scope;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface to implement to define a ScopeResolver.
|
||||||
|
*/
|
||||||
|
interface ScopeResolverInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Return true if this resolve is able to decide "something" on this entity.
|
||||||
|
*/
|
||||||
|
public function supports($entity, ?array $options = []): bool;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Will return the scope for the entity
|
||||||
|
*
|
||||||
|
* @return Scope|array|Scope[]
|
||||||
|
*/
|
||||||
|
public function resolveScope($entity, ?array $options = []);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return true if the entity is concerned by scope, false otherwise.
|
||||||
|
*/
|
||||||
|
public function isConcerned($entity, ?array $options = []): bool;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* get the default priority for this resolver. Resolver with an higher priority will be
|
||||||
|
* queried first.
|
||||||
|
*/
|
||||||
|
public static function getDefaultPriority(): int;
|
||||||
|
}
|
@ -13,12 +13,15 @@ class AddressNormalizer implements NormalizerAwareInterface, NormalizerInterface
|
|||||||
|
|
||||||
public function normalize($address, string $format = null, array $context = [])
|
public function normalize($address, string $format = null, array $context = [])
|
||||||
{
|
{
|
||||||
|
/** @var Address $address */
|
||||||
$data['address_id'] = $address->getId();
|
$data['address_id'] = $address->getId();
|
||||||
$data['text'] = $address->isNoAddress() ? '' : $address->getStreetNumber().', '.$address->getStreet();
|
$data['text'] = $address->isNoAddress() ? '' : $address->getStreetNumber().', '.$address->getStreet();
|
||||||
$data['street'] = $address->getStreet();
|
$data['street'] = $address->getStreet();
|
||||||
$data['streetNumber'] = $address->getStreetNumber();
|
$data['streetNumber'] = $address->getStreetNumber();
|
||||||
|
$data['postcode']['id'] = $address->getPostCode()->getId();
|
||||||
$data['postcode']['name'] = $address->getPostCode()->getName();
|
$data['postcode']['name'] = $address->getPostCode()->getName();
|
||||||
$data['postcode']['code'] = $address->getPostCode()->getCode();
|
$data['postcode']['code'] = $address->getPostCode()->getCode();
|
||||||
|
$data['country']['id'] = $address->getPostCode()->getCountry()->getId();
|
||||||
$data['country']['name'] = $address->getPostCode()->getCountry()->getName();
|
$data['country']['name'] = $address->getPostCode()->getCountry()->getName();
|
||||||
$data['country']['code'] = $address->getPostCode()->getCountry()->getCountryCode();
|
$data['country']['code'] = $address->getPostCode()->getCountry()->getCountryCode();
|
||||||
$data['floor'] = $address->getFloor();
|
$data['floor'] = $address->getFloor();
|
||||||
@ -28,6 +31,8 @@ class AddressNormalizer implements NormalizerAwareInterface, NormalizerInterface
|
|||||||
$data['buildingName'] = $address->getBuildingName();
|
$data['buildingName'] = $address->getBuildingName();
|
||||||
$data['distribution'] = $address->getDistribution();
|
$data['distribution'] = $address->getDistribution();
|
||||||
$data['extra'] = $address->getExtra();
|
$data['extra'] = $address->getExtra();
|
||||||
|
$data['validFrom'] = $address->getValidFrom();
|
||||||
|
$data['validTo'] = $address->getValidTo();
|
||||||
|
|
||||||
return $data;
|
return $data;
|
||||||
}
|
}
|
||||||
|
@ -37,12 +37,14 @@ class UserControllerTest extends WebTestCase
|
|||||||
|
|
||||||
$username = 'Test_user'. uniqid();
|
$username = 'Test_user'. uniqid();
|
||||||
$password = 'Password1234!';
|
$password = 'Password1234!';
|
||||||
dump($crawler->text());
|
|
||||||
// Fill in the form and submit it
|
// Fill in the form and submit it
|
||||||
$form = $crawler->selectButton('Créer')->form(array(
|
$form = $crawler->selectButton('Créer')->form(array(
|
||||||
'chill_mainbundle_user[username]' => $username,
|
'chill_mainbundle_user[username]' => $username,
|
||||||
'chill_mainbundle_user[plainPassword][first]' => $password,
|
'chill_mainbundle_user[plainPassword][first]' => $password,
|
||||||
'chill_mainbundle_user[plainPassword][second]' => $password
|
'chill_mainbundle_user[plainPassword][second]' => $password,
|
||||||
|
'chill_mainbundle_user[email]' => $username.'@gmail.com',
|
||||||
|
'chill_mainbundle_user[label]' => $username,
|
||||||
|
|
||||||
));
|
));
|
||||||
|
|
||||||
$this->client->submit($form);
|
$this->client->submit($form);
|
||||||
|
@ -19,6 +19,10 @@
|
|||||||
|
|
||||||
namespace Chill\MainBundle\Tests\Security\Authorization;
|
namespace Chill\MainBundle\Tests\Security\Authorization;
|
||||||
|
|
||||||
|
use Chill\MainBundle\Entity\HasCenterInterface;
|
||||||
|
use Chill\MainBundle\Entity\HasCentersInterface;
|
||||||
|
use Chill\MainBundle\Entity\HasScopeInterface;
|
||||||
|
use Chill\MainBundle\Entity\HasScopesInterface;
|
||||||
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
|
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
|
||||||
use Chill\MainBundle\Test\PrepareUserTrait;
|
use Chill\MainBundle\Test\PrepareUserTrait;
|
||||||
use Chill\MainBundle\Test\PrepareCenterTrait;
|
use Chill\MainBundle\Test\PrepareCenterTrait;
|
||||||
@ -268,14 +272,104 @@ class AuthorizationHelperTest extends KernelTestCase
|
|||||||
));
|
));
|
||||||
$helper = $this->getAuthorizationHelper();
|
$helper = $this->getAuthorizationHelper();
|
||||||
$entity = $this->getProphet()->prophesize();
|
$entity = $this->getProphet()->prophesize();
|
||||||
$entity->willImplement('\Chill\MainBundle\Entity\HasCenterInterface');
|
$entity->willImplement(HasCenterInterface::class);
|
||||||
$entity->willImplement('\Chill\MainBundle\Entity\HasScopeInterface');
|
$entity->willImplement(HasScopeInterface::class);
|
||||||
$entity->getCenter()->willReturn($center);
|
$entity->getCenter()->willReturn($center);
|
||||||
$entity->getScope()->willReturn($scopeA);
|
$entity->getScope()->willReturn($scopeA);
|
||||||
|
|
||||||
$this->assertFalse($helper->userHasAccess($user, $entity->reveal(), 'CHILL_ROLE'));
|
$this->assertFalse($helper->userHasAccess($user, $entity->reveal(), 'CHILL_ROLE'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testUserHasAccess_MultiCenter_EntityWithoutScope()
|
||||||
|
{
|
||||||
|
$center = $this->prepareCenter(1, 'center');
|
||||||
|
$centerB = $this->prepareCenter(1, 'centerB');
|
||||||
|
$scopeB = $this->prepareScope(2, 'other'); //the user will be granted this scope
|
||||||
|
$user = $this->prepareUser(array(
|
||||||
|
array(
|
||||||
|
'center' => $center, 'permissionsGroup' => array(
|
||||||
|
['scope' => $scopeB, 'role' => 'CHILL_ROLE']
|
||||||
|
)
|
||||||
|
)
|
||||||
|
));
|
||||||
|
$helper = $this->getAuthorizationHelper();
|
||||||
|
$entity = $this->getProphet()->prophesize();
|
||||||
|
$entity->willImplement(HasCentersInterface::class);
|
||||||
|
$entity->getCenters()->willReturn([$center, $centerB]);
|
||||||
|
|
||||||
|
$this->assertTrue($helper->userHasAccess($user, $entity->reveal(), 'CHILL_ROLE'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testUserHasNoAccess_MultiCenter_EntityWithoutScope()
|
||||||
|
{
|
||||||
|
$center = $this->prepareCenter(1, 'center');
|
||||||
|
$centerB = $this->prepareCenter(1, 'centerB');
|
||||||
|
$centerC = $this->prepareCenter(1, 'centerC');
|
||||||
|
$scopeB = $this->prepareScope(2, 'other'); //the user will be granted this scope
|
||||||
|
$user = $this->prepareUser(array(
|
||||||
|
array(
|
||||||
|
'center' => $center, 'permissionsGroup' => array(
|
||||||
|
['scope' => $scopeB, 'role' => 'CHILL_ROLE']
|
||||||
|
)
|
||||||
|
)
|
||||||
|
));
|
||||||
|
$helper = $this->getAuthorizationHelper();
|
||||||
|
$entity = $this->getProphet()->prophesize();
|
||||||
|
$entity->willImplement(HasCentersInterface::class);
|
||||||
|
$entity->getCenters()->willReturn([$centerB, $centerC]);
|
||||||
|
|
||||||
|
$this->assertFalse($helper->userHasAccess($user, $entity->reveal(), 'CHILL_ROLE'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testUserHasNoAccess_EntityMultiScope()
|
||||||
|
{
|
||||||
|
$centerA = $this->prepareCenter(1, 'center');
|
||||||
|
$centerB = $this->prepareCenter(1, 'centerB');
|
||||||
|
$scopeA = $this->prepareScope(2, 'other'); //the user will be granted this scope
|
||||||
|
$scopeB = $this->prepareScope(2, 'other'); //the user will be granted this scope
|
||||||
|
$scopeC = $this->prepareScope(2, 'other'); //the user will be granted this scope
|
||||||
|
$user = $this->prepareUser(array(
|
||||||
|
array(
|
||||||
|
'center' => $centerA, 'permissionsGroup' => array(
|
||||||
|
['scope' => $scopeA, 'role' => 'CHILL_ROLE']
|
||||||
|
)
|
||||||
|
)
|
||||||
|
));
|
||||||
|
$helper = $this->getAuthorizationHelper();
|
||||||
|
$entity = $this->getProphet()->prophesize();
|
||||||
|
$entity->willImplement(HasCentersInterface::class);
|
||||||
|
$entity->willImplement(HasScopesInterface::class);
|
||||||
|
$entity->getCenters()->willReturn([$centerA, $centerB]);
|
||||||
|
$entity->getScopes()->willReturn([$scopeB, $scopeC]);
|
||||||
|
|
||||||
|
$this->assertFalse($helper->userHasAccess($user, $entity->reveal(), 'CHILL_ROLE'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testUserHasAccess_EntityMultiScope()
|
||||||
|
{
|
||||||
|
$centerA = $this->prepareCenter(1, 'center');
|
||||||
|
$centerB = $this->prepareCenter(1, 'centerB');
|
||||||
|
$scopeA = $this->prepareScope(2, 'other'); //the user will be granted this scope
|
||||||
|
$scopeB = $this->prepareScope(2, 'other'); //the user will be granted this scope
|
||||||
|
$user = $this->prepareUser(array(
|
||||||
|
array(
|
||||||
|
'center' => $centerA, 'permissionsGroup' => array(
|
||||||
|
['scope' => $scopeA, 'role' => 'CHILL_ROLE']
|
||||||
|
)
|
||||||
|
)
|
||||||
|
));
|
||||||
|
$helper = $this->getAuthorizationHelper();
|
||||||
|
$entity = $this->getProphet()->prophesize();
|
||||||
|
$entity->willImplement(HasCentersInterface::class);
|
||||||
|
$entity->willImplement(HasScopesInterface::class);
|
||||||
|
$entity->getCenters()->willReturn([$centerA, $centerB]);
|
||||||
|
$entity->getScopes()->willReturn([$scopeA, $scopeB]);
|
||||||
|
|
||||||
|
$this->assertTrue($helper->userHasAccess($user, $entity->reveal(), 'CHILL_ROLE'));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @dataProvider dataProvider_getReachableCenters
|
* @dataProvider dataProvider_getReachableCenters
|
||||||
@ -446,16 +540,9 @@ class AuthorizationHelperTest extends KernelTestCase
|
|||||||
public function testGetParentRoles()
|
public function testGetParentRoles()
|
||||||
{
|
{
|
||||||
$parentRoles = $this->getAuthorizationHelper()
|
$parentRoles = $this->getAuthorizationHelper()
|
||||||
->getParentRoles(new Role('CHILL_INHERITED_ROLE_1'));
|
->getParentRoles('CHILL_INHERITED_ROLE_1');
|
||||||
|
|
||||||
$this->assertContains(
|
$this->assertContains('CHILL_MASTER_ROLE', $parentRoles,
|
||||||
'CHILL_MASTER_ROLE',
|
|
||||||
\array_map(
|
|
||||||
function(Role $role) {
|
|
||||||
return $role->getRole();
|
|
||||||
},
|
|
||||||
$parentRoles
|
|
||||||
),
|
|
||||||
"Assert that `CHILL_MASTER_ROLE` is a parent of `CHILL_INHERITED_ROLE_1`");
|
"Assert that `CHILL_MASTER_ROLE` is a parent of `CHILL_INHERITED_ROLE_1`");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,27 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Chill\MainBundle\Tests\Security\Resolver;
|
||||||
|
|
||||||
|
use Chill\MainBundle\Entity\Center;
|
||||||
|
use Chill\MainBundle\Security\Resolver\CenterResolverDispatcher;
|
||||||
|
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
|
||||||
|
|
||||||
|
class CenterResolverDispatcherTest extends KernelTestCase
|
||||||
|
{
|
||||||
|
private CenterResolverDispatcher $dispatcher;
|
||||||
|
|
||||||
|
protected function setUp()
|
||||||
|
{
|
||||||
|
self::bootKernel();
|
||||||
|
$this->dispatcher = self::$container->get(CenterResolverDispatcher::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testResolveCenter()
|
||||||
|
{
|
||||||
|
$center = new Center();
|
||||||
|
|
||||||
|
$resolved = $this->dispatcher->resolveCenter($center);
|
||||||
|
|
||||||
|
$this->assertSame($center, $resolved);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,62 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Chill\MainBundle\Tests\Security\Resolver;
|
||||||
|
|
||||||
|
use Chill\MainBundle\Entity\HasScopeInterface;
|
||||||
|
use Chill\MainBundle\Entity\HasScopesInterface;
|
||||||
|
use Chill\MainBundle\Entity\Scope;
|
||||||
|
use Chill\MainBundle\Security\Resolver\DefaultScopeResolver;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
class DefaultScopeResolverTest extends TestCase
|
||||||
|
{
|
||||||
|
private DefaultScopeResolver $scopeResolver;
|
||||||
|
|
||||||
|
public function setUp()
|
||||||
|
{
|
||||||
|
$this->scopeResolver = new DefaultScopeResolver();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testHasScopeInterface()
|
||||||
|
{
|
||||||
|
$scope = new Scope();
|
||||||
|
$entity = new class($scope) implements HasScopeInterface {
|
||||||
|
|
||||||
|
public function __construct(Scope $scope) {
|
||||||
|
$this->scope = $scope;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getScope()
|
||||||
|
{
|
||||||
|
return $this->scope;
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
$this->assertTrue($this->scopeResolver->supports($entity));
|
||||||
|
$this->assertTrue($this->scopeResolver->isConcerned($entity));
|
||||||
|
$this->assertSame($scope, $this->scopeResolver->resolveScope($entity));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testHasScopesInterface()
|
||||||
|
{
|
||||||
|
$entity = new class($scopeA = new Scope(), $scopeB = new Scope()) implements HasScopesInterface {
|
||||||
|
|
||||||
|
public function __construct(Scope $scopeA, Scope $scopeB) {
|
||||||
|
$this->scopes = [$scopeA, $scopeB];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getScopes(): iterable
|
||||||
|
{
|
||||||
|
return $this->scopes;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
$this->assertTrue($this->scopeResolver->supports($entity));
|
||||||
|
$this->assertTrue($this->scopeResolver->isConcerned($entity));
|
||||||
|
$this->assertIsArray($this->scopeResolver->resolveScope($entity));
|
||||||
|
$this->assertSame($scopeA, $this->scopeResolver->resolveScope($entity)[0]);
|
||||||
|
$this->assertSame($scopeB, $this->scopeResolver->resolveScope($entity)[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,60 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Chill\MainBundle\Tests\Security\Resolver;
|
||||||
|
|
||||||
|
use Chill\MainBundle\Entity\HasScopeInterface;
|
||||||
|
use Chill\MainBundle\Entity\HasScopesInterface;
|
||||||
|
use Chill\MainBundle\Entity\Scope;
|
||||||
|
use Chill\MainBundle\Security\Resolver\DefaultScopeResolver;
|
||||||
|
use Chill\MainBundle\Security\Resolver\ScopeResolverDispatcher;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
class DefaultScopeResolverDispatcherTest extends TestCase
|
||||||
|
{
|
||||||
|
private ScopeResolverDispatcher $scopeResolverDispatcher;
|
||||||
|
|
||||||
|
public function setUp()
|
||||||
|
{
|
||||||
|
$this->scopeResolverDispatcher = new ScopeResolverDispatcher([new DefaultScopeResolver()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testHasScopeInterface()
|
||||||
|
{
|
||||||
|
$scope = new Scope();
|
||||||
|
$entity = new class($scope) implements HasScopeInterface {
|
||||||
|
|
||||||
|
public function __construct(Scope $scope) {
|
||||||
|
$this->scope = $scope;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getScope()
|
||||||
|
{
|
||||||
|
return $this->scope;
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
$this->assertTrue($this->scopeResolverDispatcher->isConcerned($entity));
|
||||||
|
$this->assertSame($scope, $this->scopeResolverDispatcher->resolveScope($entity));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testHasScopesInterface()
|
||||||
|
{
|
||||||
|
$entity = new class($scopeA = new Scope(), $scopeB = new Scope()) implements HasScopesInterface {
|
||||||
|
|
||||||
|
public function __construct(Scope $scopeA, Scope $scopeB) {
|
||||||
|
$this->scopes = [$scopeA, $scopeB];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getScopes(): iterable
|
||||||
|
{
|
||||||
|
return $this->scopes;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
$this->assertTrue($this->scopeResolverDispatcher->isConcerned($entity));
|
||||||
|
$this->assertIsArray($this->scopeResolverDispatcher->resolveScope($entity));
|
||||||
|
$this->assertSame($scopeA, $this->scopeResolverDispatcher->resolveScope($entity)[0]);
|
||||||
|
$this->assertSame($scopeB, $this->scopeResolverDispatcher->resolveScope($entity)[1]);
|
||||||
|
}
|
||||||
|
}
|
@ -480,3 +480,34 @@ paths:
|
|||||||
description: "not found"
|
description: "not found"
|
||||||
401:
|
401:
|
||||||
description: "Unauthorized"
|
description: "Unauthorized"
|
||||||
|
|
||||||
|
/1.0/main/scope.json:
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- scope
|
||||||
|
summary: return a list of scopes
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: "ok"
|
||||||
|
401:
|
||||||
|
description: "Unauthorized"
|
||||||
|
|
||||||
|
/1.0/main/scope/{id}.json:
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- scope
|
||||||
|
summary: return a list of scopes
|
||||||
|
parameters:
|
||||||
|
- name: id
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
description: The scope id
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
format: integer
|
||||||
|
minimum: 1
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: "ok"
|
||||||
|
401:
|
||||||
|
description: "Unauthorized"
|
||||||
|
@ -113,6 +113,10 @@ services:
|
|||||||
tags:
|
tags:
|
||||||
- { name: form.type }
|
- { name: form.type }
|
||||||
|
|
||||||
|
Chill\MainBundle\Form\UserType:
|
||||||
|
autowire: true
|
||||||
|
autoconfigure: true
|
||||||
|
|
||||||
Chill\MainBundle\Form\PermissionsGroupType:
|
Chill\MainBundle\Form\PermissionsGroupType:
|
||||||
tags:
|
tags:
|
||||||
- { name: form.type }
|
- { name: form.type }
|
||||||
@ -123,3 +127,4 @@ services:
|
|||||||
- "@security.token_storage"
|
- "@security.token_storage"
|
||||||
tags:
|
tags:
|
||||||
- { name: form.type }
|
- { name: form.type }
|
||||||
|
|
||||||
|
@ -3,16 +3,45 @@ services:
|
|||||||
autowire: true
|
autowire: true
|
||||||
autoconfigure: true
|
autoconfigure: true
|
||||||
|
|
||||||
|
# do not autowire the directory Security/Resolver
|
||||||
|
Chill\MainBundle\Security\Resolver\CenterResolverDispatcher:
|
||||||
|
arguments:
|
||||||
|
- !tagged_iterator chill_main.center_resolver
|
||||||
|
|
||||||
|
Chill\MainBundle\Security\Resolver\ScopeResolverDispatcher:
|
||||||
|
arguments:
|
||||||
|
- !tagged_iterator chill_main.scope_resolver
|
||||||
|
|
||||||
|
# do not autowire the directory Security/Resolver
|
||||||
|
Chill\MainBundle\Security\Resolver\DefaultCenterResolver:
|
||||||
|
autoconfigure: true
|
||||||
|
autowire: true
|
||||||
|
|
||||||
|
Chill\MainBundle\Security\Resolver\DefaultScopeResolver:
|
||||||
|
autoconfigure: true
|
||||||
|
autowire: true
|
||||||
|
|
||||||
|
# do not autowire the directory Security/Resolver
|
||||||
|
Chill\MainBundle\Security\Resolver\ResolverTwigExtension:
|
||||||
|
autoconfigure: true
|
||||||
|
autowire: true
|
||||||
|
|
||||||
|
# do not autowire the directory Security/Resolver
|
||||||
|
Chill\MainBundle\Security\Authorization\DefaultVoterHelperFactory:
|
||||||
|
autowire: true
|
||||||
|
|
||||||
|
# do not autowire the directory Security/Resolver
|
||||||
|
Chill\MainBundle\Security\Authorization\VoterHelperFactoryInterface: '@Chill\MainBundle\Security\Authorization\DefaultVoterHelperFactory'
|
||||||
|
|
||||||
chill.main.security.authorization.helper:
|
chill.main.security.authorization.helper:
|
||||||
class: Chill\MainBundle\Security\Authorization\AuthorizationHelper
|
class: Chill\MainBundle\Security\Authorization\AuthorizationHelper
|
||||||
arguments:
|
autowire: true
|
||||||
$roleHierarchy: "@security.role_hierarchy"
|
autoconfigure: true
|
||||||
$hierarchy: "%security.role_hierarchy.roles%"
|
|
||||||
$em: '@Doctrine\ORM\EntityManagerInterface'
|
|
||||||
Chill\MainBundle\Security\Authorization\AuthorizationHelper: '@chill.main.security.authorization.helper'
|
Chill\MainBundle\Security\Authorization\AuthorizationHelper: '@chill.main.security.authorization.helper'
|
||||||
|
|
||||||
chill.main.role_provider:
|
chill.main.role_provider:
|
||||||
class: Chill\MainBundle\Security\RoleProvider
|
class: Chill\MainBundle\Security\RoleProvider
|
||||||
|
Chill\MainBundle\Security\RoleProvider: '@chill.main.role_provider'
|
||||||
|
|
||||||
chill.main.user_provider:
|
chill.main.user_provider:
|
||||||
class: Chill\MainBundle\Security\UserProvider\UserProvider
|
class: Chill\MainBundle\Security\UserProvider\UserProvider
|
||||||
|
@ -18,6 +18,8 @@ Chill\MainBundle\Entity\User:
|
|||||||
min: 3
|
min: 3
|
||||||
email:
|
email:
|
||||||
- Email: ~
|
- Email: ~
|
||||||
|
label:
|
||||||
|
- NotBlank: ~
|
||||||
constraints:
|
constraints:
|
||||||
- Callback:
|
- Callback:
|
||||||
callback: isGroupCenterPresentOnce
|
callback: isGroupCenterPresentOnce
|
||||||
|
@ -0,0 +1,52 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Chill\Migrations\Main;
|
||||||
|
|
||||||
|
use Doctrine\DBAL\Schema\Schema;
|
||||||
|
use Doctrine\Migrations\AbstractMigration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add metadata on users
|
||||||
|
*/
|
||||||
|
final class Version20210903144853 extends AbstractMigration
|
||||||
|
{
|
||||||
|
public function getDescription(): string
|
||||||
|
{
|
||||||
|
return 'Add metadata on users';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function up(Schema $schema): void
|
||||||
|
{
|
||||||
|
$this->addSql('CREATE SEQUENCE chill_main_user_job_id_seq INCREMENT BY 1 MINVALUE 1 START 1');
|
||||||
|
$this->addSql('CREATE TABLE chill_main_user_job (id INT NOT NULL, label JSON NOT NULL, active BOOLEAN NOT NULL, PRIMARY KEY(id))');
|
||||||
|
$this->addSql('ALTER TABLE users ADD label VARCHAR(200) NULL DEFAULT NULL');
|
||||||
|
$this->addSql('UPDATE users SET label=username');
|
||||||
|
$this->addSql('ALTER TABLE users ALTER label DROP DEFAULT');
|
||||||
|
$this->addSql('ALTER TABLE users ALTER label SET NOT NULL');
|
||||||
|
$this->addSql('ALTER TABLE users ADD mainCenter_id INT DEFAULT NULL');
|
||||||
|
$this->addSql('ALTER TABLE users ADD mainScope_id INT DEFAULT NULL');
|
||||||
|
$this->addSql('ALTER TABLE users ADD userJob_id INT DEFAULT NULL');
|
||||||
|
$this->addSql('ALTER TABLE users ADD CONSTRAINT FK_1483A5E92C2125C1 FOREIGN KEY (mainCenter_id) REFERENCES centers (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||||
|
$this->addSql('ALTER TABLE users ADD CONSTRAINT FK_1483A5E9115E73F3 FOREIGN KEY (mainScope_id) REFERENCES scopes (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||||
|
$this->addSql('ALTER TABLE users ADD CONSTRAINT FK_1483A5E964B65C5B FOREIGN KEY (userJob_id) REFERENCES chill_main_user_job (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||||
|
$this->addSql('CREATE INDEX IDX_1483A5E92C2125C1 ON users (mainCenter_id)');
|
||||||
|
$this->addSql('CREATE INDEX IDX_1483A5E9115E73F3 ON users (mainScope_id)');
|
||||||
|
$this->addSql('CREATE INDEX IDX_1483A5E964B65C5B ON users (userJob_id)');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(Schema $schema): void
|
||||||
|
{
|
||||||
|
$this->addSql('ALTER TABLE users DROP CONSTRAINT FK_1483A5E964B65C5B');
|
||||||
|
$this->addSql('DROP SEQUENCE chill_main_user_job_id_seq CASCADE');
|
||||||
|
$this->addSql('DROP TABLE chill_main_user_job');
|
||||||
|
$this->addSql('ALTER TABLE users DROP CONSTRAINT FK_1483A5E92C2125C1');
|
||||||
|
$this->addSql('ALTER TABLE users DROP CONSTRAINT FK_1483A5E9115E73F3');
|
||||||
|
$this->addSql('ALTER TABLE users DROP label');
|
||||||
|
$this->addSql('ALTER TABLE users DROP mainCenter_id');
|
||||||
|
$this->addSql('ALTER TABLE users DROP mainScope_id');
|
||||||
|
$this->addSql('ALTER TABLE users DROP userJob_id');
|
||||||
|
$this->addSql('ALTER TABLE users ALTER usernameCanonical DROP NOT NULL');
|
||||||
|
}
|
||||||
|
}
|
@ -74,6 +74,15 @@ address:
|
|||||||
address_homeless: L'adresse est-elle celle d'un domicile fixe ?
|
address_homeless: L'adresse est-elle celle d'un domicile fixe ?
|
||||||
real address: Adresse d'un domicile
|
real address: Adresse d'un domicile
|
||||||
consider homeless: N'est pas l'adresse d'un domicile (SDF)
|
consider homeless: N'est pas l'adresse d'un domicile (SDF)
|
||||||
|
address more:
|
||||||
|
floor: ét
|
||||||
|
corridor: coul
|
||||||
|
steps: esc
|
||||||
|
flat: appart
|
||||||
|
buildingName: résidence
|
||||||
|
extra: ''
|
||||||
|
distribution: cedex
|
||||||
|
Create a new address: Créer une nouvelle adresse
|
||||||
|
|
||||||
#serach
|
#serach
|
||||||
Your search is empty. Please provide search terms.: La recherche est vide. Merci de fournir des termes de recherche.
|
Your search is empty. Please provide search terms.: La recherche est vide. Merci de fournir des termes de recherche.
|
||||||
@ -177,6 +186,7 @@ Exports list: Liste des exports
|
|||||||
Create an export: Créer un export
|
Create an export: Créer un export
|
||||||
#export creation step 'center' : pick a center
|
#export creation step 'center' : pick a center
|
||||||
Pick centers: Choisir les centres
|
Pick centers: Choisir les centres
|
||||||
|
Pick a center: Choisir un centre
|
||||||
The export will contains only data from the picked centers.: L'export ne contiendra que les données des centres choisis.
|
The export will contains only data from the picked centers.: L'export ne contiendra que les données des centres choisis.
|
||||||
This will eventually restrict your possibilities in filtering the data.: Les possibilités de filtrages seront adaptées aux droits de consultation pour les centres choisis.
|
This will eventually restrict your possibilities in filtering the data.: Les possibilités de filtrages seront adaptées aux droits de consultation pour les centres choisis.
|
||||||
Go to export options: Vers la préparation de l'export
|
Go to export options: Vers la préparation de l'export
|
||||||
|
@ -73,7 +73,7 @@ class AccompanyingCourseController extends Controller
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->denyAccessUnlessGranted(AccompanyingPeriodVoter::SEE, $period);
|
$this->denyAccessUnlessGranted(AccompanyingPeriodVoter::CREATE, $period);
|
||||||
|
|
||||||
$em->persist($period);
|
$em->persist($period);
|
||||||
$em->flush();
|
$em->flush();
|
||||||
@ -92,6 +92,8 @@ class AccompanyingCourseController extends Controller
|
|||||||
*/
|
*/
|
||||||
public function indexAction(AccompanyingPeriod $accompanyingCourse): Response
|
public function indexAction(AccompanyingPeriod $accompanyingCourse): Response
|
||||||
{
|
{
|
||||||
|
$this->denyAccessUnlessGranted(AccompanyingPeriodVoter::SEE, $accompanyingCourse);
|
||||||
|
|
||||||
// compute some warnings
|
// compute some warnings
|
||||||
// get persons without household
|
// get persons without household
|
||||||
$withoutHousehold = [];
|
$withoutHousehold = [];
|
||||||
@ -131,6 +133,8 @@ class AccompanyingCourseController extends Controller
|
|||||||
*/
|
*/
|
||||||
public function editAction(AccompanyingPeriod $accompanyingCourse): Response
|
public function editAction(AccompanyingPeriod $accompanyingCourse): Response
|
||||||
{
|
{
|
||||||
|
$this->denyAccessUnlessGranted(AccompanyingPeriodVoter::SEE, $accompanyingCourse);
|
||||||
|
|
||||||
return $this->render('@ChillPerson/AccompanyingCourse/edit.html.twig', [
|
return $this->render('@ChillPerson/AccompanyingCourse/edit.html.twig', [
|
||||||
'accompanyingCourse' => $accompanyingCourse
|
'accompanyingCourse' => $accompanyingCourse
|
||||||
]);
|
]);
|
||||||
@ -146,6 +150,8 @@ class AccompanyingCourseController extends Controller
|
|||||||
*/
|
*/
|
||||||
public function historyAction(AccompanyingPeriod $accompanyingCourse): Response
|
public function historyAction(AccompanyingPeriod $accompanyingCourse): Response
|
||||||
{
|
{
|
||||||
|
$this->denyAccessUnlessGranted(AccompanyingPeriodVoter::SEE, $accompanyingCourse);
|
||||||
|
|
||||||
return $this->render('@ChillPerson/AccompanyingCourse/history.html.twig', [
|
return $this->render('@ChillPerson/AccompanyingCourse/history.html.twig', [
|
||||||
'accompanyingCourse' => $accompanyingCourse
|
'accompanyingCourse' => $accompanyingCourse
|
||||||
]);
|
]);
|
||||||
|
@ -23,7 +23,10 @@
|
|||||||
namespace Chill\PersonBundle\Controller;
|
namespace Chill\PersonBundle\Controller;
|
||||||
|
|
||||||
use Chill\PersonBundle\Privacy\PrivacyEvent;
|
use Chill\PersonBundle\Privacy\PrivacyEvent;
|
||||||
|
use Chill\PersonBundle\Repository\AccompanyingPeriodACLAwareRepositoryInterface;
|
||||||
|
use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter;
|
||||||
use Doctrine\DBAL\Exception;
|
use Doctrine\DBAL\Exception;
|
||||||
|
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
|
||||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
use Chill\PersonBundle\Entity\Person;
|
use Chill\PersonBundle\Entity\Person;
|
||||||
use Chill\PersonBundle\Form\AccompanyingPeriodType;
|
use Chill\PersonBundle\Form\AccompanyingPeriodType;
|
||||||
@ -53,21 +56,24 @@ class AccompanyingPeriodController extends AbstractController
|
|||||||
*/
|
*/
|
||||||
protected $validator;
|
protected $validator;
|
||||||
|
|
||||||
/**
|
protected AccompanyingPeriodACLAwareRepositoryInterface $accompanyingPeriodACLAwareRepository;
|
||||||
* AccompanyingPeriodController constructor.
|
|
||||||
*
|
public function __construct(
|
||||||
* @param EventDispatcherInterface $eventDispatcher
|
AccompanyingPeriodACLAwareRepositoryInterface $accompanyingPeriodACLAwareRepository,
|
||||||
* @param ValidatorInterface $validator
|
EventDispatcherInterface $eventDispatcher,
|
||||||
*/
|
ValidatorInterface $validator
|
||||||
public function __construct(EventDispatcherInterface $eventDispatcher, ValidatorInterface $validator)
|
) {
|
||||||
{
|
$this->accompanyingPeriodACLAwareRepository = $accompanyingPeriodACLAwareRepository;
|
||||||
$this->eventDispatcher = $eventDispatcher;
|
$this->eventDispatcher = $eventDispatcher;
|
||||||
$this->validator = $validator;
|
$this->validator = $validator;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function listAction(int $person_id): Response
|
/**
|
||||||
|
* @ParamConverter("person", options={"id"="person_id"})
|
||||||
|
*/
|
||||||
|
public function listAction(Person $person): Response
|
||||||
{
|
{
|
||||||
$person = $this->_getPerson($person_id);
|
$this->denyAccessUnlessGranted(AccompanyingPeriodVoter::SEE, $person);
|
||||||
|
|
||||||
$event = new PrivacyEvent($person, [
|
$event = new PrivacyEvent($person, [
|
||||||
'element_class' => AccompanyingPeriod::class,
|
'element_class' => AccompanyingPeriod::class,
|
||||||
@ -75,9 +81,10 @@ class AccompanyingPeriodController extends AbstractController
|
|||||||
]);
|
]);
|
||||||
$this->eventDispatcher->dispatch(PrivacyEvent::PERSON_PRIVACY_EVENT, $event);
|
$this->eventDispatcher->dispatch(PrivacyEvent::PERSON_PRIVACY_EVENT, $event);
|
||||||
|
|
||||||
$accompanyingPeriods = $person->getAccompanyingPeriodsOrdered();
|
$accompanyingPeriods = $this->accompanyingPeriodACLAwareRepository
|
||||||
|
->findByPerson($person, AccompanyingPeriodVoter::SEE);
|
||||||
|
|
||||||
return $this->render('ChillPersonBundle:AccompanyingPeriod:list.html.twig', [
|
return $this->render('@ChillPerson/AccompanyingPeriod/list.html.twig', [
|
||||||
'accompanying_periods' => $accompanyingPeriods,
|
'accompanying_periods' => $accompanyingPeriods,
|
||||||
'person' => $person
|
'person' => $person
|
||||||
]);
|
]);
|
||||||
|
@ -230,13 +230,16 @@ final class PersonController extends AbstractController
|
|||||||
*/
|
*/
|
||||||
public function newAction(Request $request)
|
public function newAction(Request $request)
|
||||||
{
|
{
|
||||||
$defaultCenter = $this->security
|
$person = new Person();
|
||||||
->getUser()
|
|
||||||
->getGroupCenters()[0]
|
|
||||||
->getCenter();
|
|
||||||
|
|
||||||
$person = (new Person(new \DateTime('now')))
|
if (1 === count($this->security->getUser()
|
||||||
->setCenter($defaultCenter);
|
->getGroupCenters())) {
|
||||||
|
$person->setCenter(
|
||||||
|
$this->security->getUser()
|
||||||
|
->getGroupCenters()[0]
|
||||||
|
->getCenter()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
$form = $this->createForm(CreationPersonType::class, $person, [
|
$form = $this->createForm(CreationPersonType::class, $person, [
|
||||||
'validation_groups' => ['create']
|
'validation_groups' => ['create']
|
||||||
|
@ -24,8 +24,12 @@ namespace Chill\PersonBundle\DataFixtures\ORM;
|
|||||||
use Chill\MainBundle\Entity\Center;
|
use Chill\MainBundle\Entity\Center;
|
||||||
use Chill\MainBundle\Entity\Country;
|
use Chill\MainBundle\Entity\Country;
|
||||||
use Chill\MainBundle\Entity\PostalCode;
|
use Chill\MainBundle\Entity\PostalCode;
|
||||||
|
use Chill\MainBundle\Entity\Scope;
|
||||||
|
use Chill\MainBundle\Entity\User;
|
||||||
use Chill\MainBundle\Repository\CenterRepository;
|
use Chill\MainBundle\Repository\CenterRepository;
|
||||||
use Chill\MainBundle\Repository\CountryRepository;
|
use Chill\MainBundle\Repository\CountryRepository;
|
||||||
|
use Chill\MainBundle\Repository\ScopeRepository;
|
||||||
|
use Chill\MainBundle\Repository\UserRepository;
|
||||||
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
||||||
use Chill\PersonBundle\Entity\MaritalStatus;
|
use Chill\PersonBundle\Entity\MaritalStatus;
|
||||||
use Chill\PersonBundle\Entity\SocialWork\SocialIssue;
|
use Chill\PersonBundle\Entity\SocialWork\SocialIssue;
|
||||||
@ -90,12 +94,26 @@ class LoadPeople extends AbstractFixture implements OrderedFixtureInterface, Con
|
|||||||
|
|
||||||
protected MaritalStatusRepository $maritalStatusRepository;
|
protected MaritalStatusRepository $maritalStatusRepository;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array|Scope[]
|
||||||
|
*/
|
||||||
|
protected array $cacheScopes = [];
|
||||||
|
|
||||||
|
protected ScopeRepository $scopeRepository;
|
||||||
|
|
||||||
|
/** @var array|User[] */
|
||||||
|
protected array $cacheUsers = [];
|
||||||
|
|
||||||
|
protected UserRepository $userRepository;
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
Registry $workflowRegistry,
|
Registry $workflowRegistry,
|
||||||
SocialIssueRepository $socialIssueRepository,
|
SocialIssueRepository $socialIssueRepository,
|
||||||
CenterRepository $centerRepository,
|
CenterRepository $centerRepository,
|
||||||
CountryRepository $countryRepository,
|
CountryRepository $countryRepository,
|
||||||
MaritalStatusRepository $maritalStatusRepository
|
MaritalStatusRepository $maritalStatusRepository,
|
||||||
|
ScopeRepository $scopeRepository,
|
||||||
|
UserRepository $userRepository
|
||||||
) {
|
) {
|
||||||
$this->faker = Factory::create('fr_FR');
|
$this->faker = Factory::create('fr_FR');
|
||||||
$this->faker->addProvider($this);
|
$this->faker->addProvider($this);
|
||||||
@ -105,7 +123,8 @@ class LoadPeople extends AbstractFixture implements OrderedFixtureInterface, Con
|
|||||||
$this->countryRepository = $countryRepository;
|
$this->countryRepository = $countryRepository;
|
||||||
$this->maritalStatusRepository = $maritalStatusRepository;
|
$this->maritalStatusRepository = $maritalStatusRepository;
|
||||||
$this->loader = new NativeLoader($this->faker);
|
$this->loader = new NativeLoader($this->faker);
|
||||||
|
$this->scopeRepository = $scopeRepository;
|
||||||
|
$this->userRepository = $userRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getOrder()
|
public function getOrder()
|
||||||
@ -220,10 +239,16 @@ class LoadPeople extends AbstractFixture implements OrderedFixtureInterface, Con
|
|||||||
new \DateInterval('P' . \random_int(0, 180) . 'D')
|
new \DateInterval('P' . \random_int(0, 180) . 'D')
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
$accompanyingPeriod->setCreatedBy($this->getRandomUser())
|
||||||
|
->setCreatedAt(new \DateTimeImmutable('now'));
|
||||||
$person->addAccompanyingPeriod($accompanyingPeriod);
|
$person->addAccompanyingPeriod($accompanyingPeriod);
|
||||||
$accompanyingPeriod->addSocialIssue($this->getRandomSocialIssue());
|
$accompanyingPeriod->addSocialIssue($this->getRandomSocialIssue());
|
||||||
|
|
||||||
if (\random_int(0, 10) > 3) {
|
if (\random_int(0, 10) > 3) {
|
||||||
|
// always add social scope:
|
||||||
|
$accompanyingPeriod->addScope($this->getReference('scope_social'));
|
||||||
|
var_dump(count($accompanyingPeriod->getScopes()));
|
||||||
|
|
||||||
$accompanyingPeriod->setAddressLocation($this->createAddress());
|
$accompanyingPeriod->setAddressLocation($this->createAddress());
|
||||||
$manager->persist($accompanyingPeriod->getAddressLocation());
|
$manager->persist($accompanyingPeriod->getAddressLocation());
|
||||||
$workflow = $this->workflowRegistry->get($accompanyingPeriod);
|
$workflow = $this->workflowRegistry->get($accompanyingPeriod);
|
||||||
@ -231,9 +256,19 @@ class LoadPeople extends AbstractFixture implements OrderedFixtureInterface, Con
|
|||||||
}
|
}
|
||||||
|
|
||||||
$manager->persist($person);
|
$manager->persist($person);
|
||||||
|
$manager->persist($accompanyingPeriod);
|
||||||
echo "add person'".$person->__toString()."'\n";
|
echo "add person'".$person->__toString()."'\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function getRandomUser(): User
|
||||||
|
{
|
||||||
|
if (0 === count($this->cacheUsers)) {
|
||||||
|
$this->cacheUsers = $this->userRepository->findAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->cacheUsers[\array_rand($this->cacheUsers)];
|
||||||
|
}
|
||||||
|
|
||||||
private function createAddress(): Address
|
private function createAddress(): Address
|
||||||
{
|
{
|
||||||
$objectSet = $this->loader->loadData([
|
$objectSet = $this->loader->loadData([
|
||||||
|
@ -55,7 +55,7 @@ class LoadPersonACL extends AbstractFixture implements OrderedFixtureInterface
|
|||||||
|
|
||||||
$permissionsGroup->addRoleScope(
|
$permissionsGroup->addRoleScope(
|
||||||
(new RoleScope())
|
(new RoleScope())
|
||||||
->setRole(AccompanyingPeriodVoter::SEE)
|
->setRole(AccompanyingPeriodVoter::FULL)
|
||||||
->setScope($scopeSocial)
|
->setScope($scopeSocial)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
|
|
||||||
namespace Chill\PersonBundle\DependencyInjection;
|
namespace Chill\PersonBundle\DependencyInjection;
|
||||||
|
|
||||||
|
use Chill\PersonBundle\Security\Authorization\AccompanyingPeriodVoter;
|
||||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||||
use Symfony\Component\Config\FileLocator;
|
use Symfony\Component\Config\FileLocator;
|
||||||
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
|
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
|
||||||
@ -60,6 +61,9 @@ class ChillPersonExtension extends Extension implements PrependExtensionInterfac
|
|||||||
$container->setParameter('chill_person.allow_multiple_simultaneous_accompanying_periods',
|
$container->setParameter('chill_person.allow_multiple_simultaneous_accompanying_periods',
|
||||||
$config['allow_multiple_simultaneous_accompanying_periods']);
|
$config['allow_multiple_simultaneous_accompanying_periods']);
|
||||||
|
|
||||||
|
// register all configuration in a unique parameter
|
||||||
|
$container->setParameter('chill_person', $config);
|
||||||
|
|
||||||
$loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../config'));
|
$loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../config'));
|
||||||
$loader->load('services.yaml');
|
$loader->load('services.yaml');
|
||||||
$loader->load('services/widgets.yaml');
|
$loader->load('services/widgets.yaml');
|
||||||
@ -255,14 +259,26 @@ class ChillPersonExtension extends Extension implements PrependExtensionInterfac
|
|||||||
*/
|
*/
|
||||||
protected function prependRoleHierarchy(ContainerBuilder $container)
|
protected function prependRoleHierarchy(ContainerBuilder $container)
|
||||||
{
|
{
|
||||||
$container->prependExtensionConfig('security', array(
|
$container->prependExtensionConfig('security', [
|
||||||
'role_hierarchy' => array(
|
'role_hierarchy' => [
|
||||||
'CHILL_PERSON_UPDATE' => array('CHILL_PERSON_SEE'),
|
PersonVoter::UPDATE => [PersonVoter::SEE],
|
||||||
'CHILL_PERSON_CREATE' => array('CHILL_PERSON_SEE'),
|
PersonVoter::CREATE => [PersonVoter::SEE],
|
||||||
PersonVoter::LISTS => [ChillExportVoter::EXPORT],
|
PersonVoter::LISTS => [ChillExportVoter::EXPORT],
|
||||||
PersonVoter::STATS => [ ChillExportVoter::EXPORT ]
|
PersonVoter::STATS => [ChillExportVoter::EXPORT],
|
||||||
)
|
// accompanying period
|
||||||
));
|
AccompanyingPeriodVoter::SEE_DETAILS => [AccompanyingPeriodVoter::SEE],
|
||||||
|
AccompanyingPeriodVoter::CREATE => [AccompanyingPeriodVoter::SEE_DETAILS],
|
||||||
|
AccompanyingPeriodVoter::DELETE => [AccompanyingPeriodVoter::SEE_DETAILS],
|
||||||
|
AccompanyingPeriodVoter::EDIT => [AccompanyingPeriodVoter::SEE_DETAILS],
|
||||||
|
// give all ACL for FULL
|
||||||
|
AccompanyingPeriodVoter::FULL => [
|
||||||
|
AccompanyingPeriodVoter::SEE_DETAILS,
|
||||||
|
AccompanyingPeriodVoter::CREATE,
|
||||||
|
AccompanyingPeriodVoter::EDIT,
|
||||||
|
AccompanyingPeriodVoter::DELETE
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -43,6 +43,10 @@ class Configuration implements ConfigurationInterface
|
|||||||
->arrayNode('validation')
|
->arrayNode('validation')
|
||||||
->canBeDisabled()
|
->canBeDisabled()
|
||||||
->children()
|
->children()
|
||||||
|
->booleanNode('center_required')
|
||||||
|
->info('Enable a center for each person entity. If disabled, you must provide your own center provider')
|
||||||
|
->defaultValue(true)
|
||||||
|
->end()
|
||||||
->scalarNode('birthdate_not_after')
|
->scalarNode('birthdate_not_after')
|
||||||
->info($this->validationBirthdateNotAfterInfos)
|
->info($this->validationBirthdateNotAfterInfos)
|
||||||
->defaultValue('P1D')
|
->defaultValue('P1D')
|
||||||
@ -59,7 +63,6 @@ class Configuration implements ConfigurationInterface
|
|||||||
. 'The parameter should match duration as defined by ISO8601 : '
|
. 'The parameter should match duration as defined by ISO8601 : '
|
||||||
. 'https://en.wikipedia.org/wiki/ISO_8601#Durations')
|
. 'https://en.wikipedia.org/wiki/ISO_8601#Durations')
|
||||||
->end() // birthdate_not_after, parent = children of validation
|
->end() // birthdate_not_after, parent = children of validation
|
||||||
|
|
||||||
->end() // children for 'validation', parent = validation
|
->end() // children for 'validation', parent = validation
|
||||||
->end() //validation, parent = children of root
|
->end() //validation, parent = children of root
|
||||||
->end() // children of root, parent = root
|
->end() // children of root, parent = root
|
||||||
|
@ -24,6 +24,8 @@ namespace Chill\PersonBundle\Entity;
|
|||||||
|
|
||||||
use Chill\MainBundle\Doctrine\Model\TrackUpdateInterface;
|
use Chill\MainBundle\Doctrine\Model\TrackUpdateInterface;
|
||||||
use Chill\MainBundle\Doctrine\Model\TrackCreationInterface;
|
use Chill\MainBundle\Doctrine\Model\TrackCreationInterface;
|
||||||
|
use Chill\MainBundle\Entity\HasCentersInterface;
|
||||||
|
use Chill\MainBundle\Entity\HasScopesInterface;
|
||||||
use Chill\MainBundle\Entity\Scope;
|
use Chill\MainBundle\Entity\Scope;
|
||||||
use Chill\MainBundle\Entity\Address;
|
use Chill\MainBundle\Entity\Address;
|
||||||
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWork;
|
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWork;
|
||||||
@ -52,7 +54,8 @@ use Symfony\Component\Validator\Constraints as Assert;
|
|||||||
* "accompanying_period"=AccompanyingPeriod::class
|
* "accompanying_period"=AccompanyingPeriod::class
|
||||||
* })
|
* })
|
||||||
*/
|
*/
|
||||||
class AccompanyingPeriod implements TrackCreationInterface, TrackUpdateInterface
|
class AccompanyingPeriod implements TrackCreationInterface, TrackUpdateInterface,
|
||||||
|
HasScopesInterface, HasCentersInterface
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Mark an accompanying period as "occasional"
|
* Mark an accompanying period as "occasional"
|
||||||
@ -809,14 +812,21 @@ class AccompanyingPeriod implements TrackCreationInterface, TrackUpdateInterface
|
|||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return iterable|Collection
|
||||||
|
*/
|
||||||
public function getScopes(): Collection
|
public function getScopes(): Collection
|
||||||
{
|
{
|
||||||
return $this->scopes;
|
return $this->scopes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public function addScope(Scope $scope): self
|
public function addScope(Scope $scope): self
|
||||||
{
|
{
|
||||||
|
if (!$this->scopes->contains($scope)) {
|
||||||
$this->scopes[] = $scope;
|
$this->scopes[] = $scope;
|
||||||
|
}
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
@ -1040,4 +1050,16 @@ class AccompanyingPeriod implements TrackCreationInterface, TrackUpdateInterface
|
|||||||
return 'none';
|
return 'none';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getCenters(): ?iterable
|
||||||
|
{
|
||||||
|
foreach ($this->getPersons() as $person) {
|
||||||
|
if (!in_array($person->getCenter(), $centers ?? [])
|
||||||
|
&& NULL !== $person->getCenter()) {
|
||||||
|
$centers[] = $person->getCenter();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $centers ?? null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,29 @@ use Chill\PersonBundle\Entity\Person;
|
|||||||
use Doctrine\ORM\Mapping as ORM;
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* This class links a person to the history of his addresses, through
|
||||||
|
* household membership.
|
||||||
|
*
|
||||||
|
* It is optimized on DB side, and compute the start date and end date
|
||||||
|
* of each address by the belonging of household.
|
||||||
|
*
|
||||||
|
* **note**: the start date and end date are the date of belonging to the address,
|
||||||
|
* not the belonging of the household.
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
*
|
||||||
|
* * person A is member of household W from 2021-01-01 to 2021-12-01
|
||||||
|
* * person A is member of household V from 2021-12-01, still present after
|
||||||
|
* * household W lives in address Q from 2020-06-01 to 2021-06-01
|
||||||
|
* * household W lives in address R from 2021-06-01 to 2022-06-01
|
||||||
|
* * household V lives in address T from 2021-12-01 to still living there after
|
||||||
|
*
|
||||||
|
* The person A will have those 3 entities:
|
||||||
|
*
|
||||||
|
* 1. 1st entity: from 2021-01-01 to 2021-06-01, household W, address Q;
|
||||||
|
* 2. 2st entity: from 2021-06-01 to 2021-12-01, household W, address R;
|
||||||
|
* 3. 3st entity: from 2021-12-01 to NULL, household V, address T;
|
||||||
|
*
|
||||||
* @ORM\Entity(readOnly=true)
|
* @ORM\Entity(readOnly=true)
|
||||||
* @ORM\Table(name="view_chill_person_household_address")
|
* @ORM\Table(name="view_chill_person_household_address")
|
||||||
*/
|
*/
|
||||||
@ -45,11 +68,23 @@ class PersonHouseholdAddress
|
|||||||
*/
|
*/
|
||||||
private $address;
|
private $address;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The start date of the intersection address/household
|
||||||
|
*
|
||||||
|
* (this is not the startdate of the household, not
|
||||||
|
* the startdate of the address)
|
||||||
|
*/
|
||||||
public function getValidFrom(): ?\DateTimeInterface
|
public function getValidFrom(): ?\DateTimeInterface
|
||||||
{
|
{
|
||||||
return $this->validFrom;
|
return $this->validFrom;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The end date of the intersection address/household
|
||||||
|
*
|
||||||
|
* (this is not the enddate of the household, not
|
||||||
|
* the enddate of the address)
|
||||||
|
*/
|
||||||
public function getValidTo(): ?\DateTimeImmutable
|
public function getValidTo(): ?\DateTimeImmutable
|
||||||
{
|
{
|
||||||
return $this->validTo;
|
return $this->validTo;
|
||||||
|
@ -35,6 +35,7 @@ use Chill\PersonBundle\Entity\Household\HouseholdMember;
|
|||||||
use Chill\MainBundle\Entity\HasCenterInterface;
|
use Chill\MainBundle\Entity\HasCenterInterface;
|
||||||
use Chill\MainBundle\Entity\Address;
|
use Chill\MainBundle\Entity\Address;
|
||||||
use Chill\MainBundle\Entity\Embeddable\CommentEmbeddable;
|
use Chill\MainBundle\Entity\Embeddable\CommentEmbeddable;
|
||||||
|
use Chill\PersonBundle\Entity\Person\PersonCurrentAddress;
|
||||||
use DateTime;
|
use DateTime;
|
||||||
use Doctrine\ORM\Mapping as ORM;
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
use Doctrine\Common\Collections\Collection;
|
use Doctrine\Common\Collections\Collection;
|
||||||
@ -43,6 +44,11 @@ use Doctrine\Common\Collections\Criteria;
|
|||||||
use Symfony\Component\Validator\Context\ExecutionContextInterface;
|
use Symfony\Component\Validator\Context\ExecutionContextInterface;
|
||||||
use Symfony\Component\Serializer\Annotation\DiscriminatorMap;
|
use Symfony\Component\Serializer\Annotation\DiscriminatorMap;
|
||||||
use Chill\PersonBundle\Entity\Household\PersonHouseholdAddress;
|
use Chill\PersonBundle\Entity\Household\PersonHouseholdAddress;
|
||||||
|
use Symfony\Component\Validator\Constraints as Assert;
|
||||||
|
use Chill\PersonBundle\Validator\Constraints\Person\Birthdate;
|
||||||
|
use Chill\MainBundle\Validation\Constraint\PhonenumberConstraint;
|
||||||
|
use Chill\PersonBundle\Validator\Constraints\Person\PersonHasCenter;
|
||||||
|
use Chill\PersonBundle\Validator\Constraints\Household\HouseholdMembershipSequential;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Person Class
|
* Person Class
|
||||||
@ -57,6 +63,12 @@ use Chill\PersonBundle\Entity\Household\PersonHouseholdAddress;
|
|||||||
* @DiscriminatorMap(typeProperty="type", mapping={
|
* @DiscriminatorMap(typeProperty="type", mapping={
|
||||||
* "person"=Person::class
|
* "person"=Person::class
|
||||||
* })
|
* })
|
||||||
|
* @PersonHasCenter(
|
||||||
|
* groups={"general", "creation"}
|
||||||
|
* )
|
||||||
|
* @HouseholdMembershipSequential(
|
||||||
|
* groups={"household_memberships"}
|
||||||
|
* )
|
||||||
*/
|
*/
|
||||||
class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateInterface
|
class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateInterface
|
||||||
{
|
{
|
||||||
@ -75,6 +87,13 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI
|
|||||||
* @var string
|
* @var string
|
||||||
*
|
*
|
||||||
* @ORM\Column(type="string", length=255)
|
* @ORM\Column(type="string", length=255)
|
||||||
|
* @Assert\NotBlank(
|
||||||
|
* groups={"general", "creation"}
|
||||||
|
* )
|
||||||
|
* @Assert\Length(
|
||||||
|
* max=255,
|
||||||
|
* groups={"general", "creation"}
|
||||||
|
* )
|
||||||
*/
|
*/
|
||||||
private $firstName;
|
private $firstName;
|
||||||
|
|
||||||
@ -83,6 +102,13 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI
|
|||||||
* @var string
|
* @var string
|
||||||
*
|
*
|
||||||
* @ORM\Column(type="string", length=255)
|
* @ORM\Column(type="string", length=255)
|
||||||
|
* @Assert\NotBlank(
|
||||||
|
* groups={"general", "creation"}
|
||||||
|
* )
|
||||||
|
* @Assert\Length(
|
||||||
|
* max=255,
|
||||||
|
* groups={"general", "creation"}
|
||||||
|
* )
|
||||||
*/
|
*/
|
||||||
private $lastName;
|
private $lastName;
|
||||||
|
|
||||||
@ -102,6 +128,12 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI
|
|||||||
* @var \DateTime
|
* @var \DateTime
|
||||||
*
|
*
|
||||||
* @ORM\Column(type="date", nullable=true)
|
* @ORM\Column(type="date", nullable=true)
|
||||||
|
* @Assert\Date(
|
||||||
|
* groups={"general", "creation"}
|
||||||
|
* )
|
||||||
|
* @Birthdate(
|
||||||
|
* groups={"general", "creation"}
|
||||||
|
* )
|
||||||
*/
|
*/
|
||||||
private $birthdate;
|
private $birthdate;
|
||||||
|
|
||||||
@ -110,6 +142,9 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI
|
|||||||
* @var \DateTimeImmutable
|
* @var \DateTimeImmutable
|
||||||
*
|
*
|
||||||
* @ORM\Column(type="date_immutable", nullable=true)
|
* @ORM\Column(type="date_immutable", nullable=true)
|
||||||
|
* @Assert\Date(
|
||||||
|
* groups={"general", "creation"}
|
||||||
|
* )
|
||||||
*/
|
*/
|
||||||
private ?\DateTimeImmutable $deathdate;
|
private ?\DateTimeImmutable $deathdate;
|
||||||
|
|
||||||
@ -150,6 +185,9 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI
|
|||||||
* @var string
|
* @var string
|
||||||
*
|
*
|
||||||
* @ORM\Column(type="string", length=9, nullable=true)
|
* @ORM\Column(type="string", length=9, nullable=true)
|
||||||
|
* @Assert\NotNull(
|
||||||
|
* groups={"general", "creation"}
|
||||||
|
* )
|
||||||
*/
|
*/
|
||||||
private $gender;
|
private $gender;
|
||||||
|
|
||||||
@ -179,8 +217,11 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI
|
|||||||
* @var \DateTime
|
* @var \DateTime
|
||||||
*
|
*
|
||||||
* @ORM\Column(type="date", nullable=true)
|
* @ORM\Column(type="date", nullable=true)
|
||||||
|
* @Assert\Date(
|
||||||
|
* groups={"general", "creation"}
|
||||||
|
* )
|
||||||
*/
|
*/
|
||||||
private $maritalStatusDate;
|
private ?\DateTime $maritalStatusDate;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Comment on marital status
|
* Comment on marital status
|
||||||
@ -202,6 +243,10 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI
|
|||||||
* @var string
|
* @var string
|
||||||
*
|
*
|
||||||
* @ORM\Column(type="text", nullable=true)
|
* @ORM\Column(type="text", nullable=true)
|
||||||
|
* @Assert\Email(
|
||||||
|
* checkMX=true,
|
||||||
|
* groups={"general", "creation"}
|
||||||
|
* )
|
||||||
*/
|
*/
|
||||||
private $email = '';
|
private $email = '';
|
||||||
|
|
||||||
@ -210,6 +255,14 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI
|
|||||||
* @var string
|
* @var string
|
||||||
*
|
*
|
||||||
* @ORM\Column(type="text", length=40, nullable=true)
|
* @ORM\Column(type="text", length=40, nullable=true)
|
||||||
|
* @Assert\Regex(
|
||||||
|
* pattern="/^([\+{1}])([0-9\s*]{4,20})$/",
|
||||||
|
* groups={"general", "creation"}
|
||||||
|
* )
|
||||||
|
* @PhonenumberConstraint(
|
||||||
|
* type="landline",
|
||||||
|
* groups={"general", "creation"}
|
||||||
|
* )
|
||||||
*/
|
*/
|
||||||
private $phonenumber = '';
|
private $phonenumber = '';
|
||||||
|
|
||||||
@ -218,6 +271,14 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI
|
|||||||
* @var string
|
* @var string
|
||||||
*
|
*
|
||||||
* @ORM\Column(type="text", length=40, nullable=true)
|
* @ORM\Column(type="text", length=40, nullable=true)
|
||||||
|
* @Assert\Regex(
|
||||||
|
* pattern="/^([\+{1}])([0-9\s*]{4,20})$/",
|
||||||
|
* groups={"general", "creation"}
|
||||||
|
* )
|
||||||
|
* @PhonenumberConstraint(
|
||||||
|
* type="mobile",
|
||||||
|
* groups={"general", "creation"}
|
||||||
|
* )
|
||||||
*/
|
*/
|
||||||
private $mobilenumber = '';
|
private $mobilenumber = '';
|
||||||
|
|
||||||
@ -230,12 +291,13 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI
|
|||||||
* cascade={"persist", "remove", "merge", "detach"},
|
* cascade={"persist", "remove", "merge", "detach"},
|
||||||
* orphanRemoval=true
|
* orphanRemoval=true
|
||||||
* )
|
* )
|
||||||
|
* @Assert\Valid(
|
||||||
|
* traverse=true,
|
||||||
|
* groups={"general", "creation"}
|
||||||
|
* )
|
||||||
*/
|
*/
|
||||||
private $otherPhoneNumbers;
|
private $otherPhoneNumbers;
|
||||||
|
|
||||||
//TO-ADD caseOpeningDate
|
|
||||||
//TO-ADD nativeLanguag
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The person's spoken languages
|
* The person's spoken languages
|
||||||
* @var ArrayCollection
|
* @var ArrayCollection
|
||||||
@ -254,7 +316,6 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI
|
|||||||
* @var Center
|
* @var Center
|
||||||
*
|
*
|
||||||
* @ORM\ManyToOne(targetEntity="Chill\MainBundle\Entity\Center")
|
* @ORM\ManyToOne(targetEntity="Chill\MainBundle\Entity\Center")
|
||||||
* @ORM\JoinColumn(nullable=false)
|
|
||||||
*/
|
*/
|
||||||
private $center;
|
private $center;
|
||||||
|
|
||||||
@ -353,6 +414,18 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI
|
|||||||
private $addresses;
|
private $addresses;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* The current person address.
|
||||||
|
*
|
||||||
|
* This is computed through database and is optimized on database side.
|
||||||
|
*
|
||||||
|
* @var PersonCurrentAddress|null
|
||||||
|
* @ORM\OneToOne(targetEntity=PersonCurrentAddress::class, mappedBy="person")
|
||||||
|
*/
|
||||||
|
private ?PersonCurrentAddress $currentPersonAddress = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* fullname canonical. Read-only field, which is calculated by
|
||||||
|
* the database.
|
||||||
* @var string
|
* @var string
|
||||||
*
|
*
|
||||||
* @ORM\Column(type="text", nullable=true)
|
* @ORM\Column(type="text", nullable=true)
|
||||||
@ -373,6 +446,8 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI
|
|||||||
private array $currentHouseholdAt = [];
|
private array $currentHouseholdAt = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Read-only field, computed by the database
|
||||||
|
*
|
||||||
* @ORM\OneToMany(
|
* @ORM\OneToMany(
|
||||||
* targetEntity=PersonHouseholdAddress::class,
|
* targetEntity=PersonHouseholdAddress::class,
|
||||||
* mappedBy="person"
|
* mappedBy="person"
|
||||||
@ -390,8 +465,6 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Person constructor.
|
* Person constructor.
|
||||||
*
|
|
||||||
* @param \DateTime|null $opening
|
|
||||||
*/
|
*/
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
@ -404,6 +477,7 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI
|
|||||||
$this->householdAddresses = new ArrayCollection();
|
$this->householdAddresses = new ArrayCollection();
|
||||||
$this->genderComment = new CommentEmbeddable();
|
$this->genderComment = new CommentEmbeddable();
|
||||||
$this->maritalStatusComment = new CommentEmbeddable();
|
$this->maritalStatusComment = new CommentEmbeddable();
|
||||||
|
$this->periodLocatedOn = new ArrayCollection();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -501,6 +575,8 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI
|
|||||||
return $participation->getAccompanyingPeriod();
|
return $participation->getAccompanyingPeriod();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1179,13 +1255,31 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI
|
|||||||
return $this->addresses;
|
return $this->addresses;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use `getCurrentPersonAddress` instead
|
||||||
|
* @param DateTime|null $from
|
||||||
|
* @return false|mixed|null
|
||||||
|
* @throws \Exception
|
||||||
|
*/
|
||||||
public function getLastAddress(DateTime $from = null)
|
public function getLastAddress(DateTime $from = null)
|
||||||
{
|
{
|
||||||
$from ??= new DateTime('now');
|
return $this->getCurrentPersonAddress($from);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* get the address associated with the person at the given date
|
||||||
|
*
|
||||||
|
* @param DateTime|null $at
|
||||||
|
* @return Address|null
|
||||||
|
* @throws \Exception
|
||||||
|
*/
|
||||||
|
public function getCurrentPersonAddress(?\DateTime $at = null): ?Address
|
||||||
|
{
|
||||||
|
$at ??= new DateTime('now');
|
||||||
|
|
||||||
/** @var ArrayIterator $addressesIterator */
|
/** @var ArrayIterator $addressesIterator */
|
||||||
$addressesIterator = $this->getAddresses()
|
$addressesIterator = $this->getAddresses()
|
||||||
->filter(static fn (Address $address): bool => $address->getValidFrom() <= $from)
|
->filter(static fn (Address $address): bool => $address->getValidFrom() <= $at)
|
||||||
->getIterator();
|
->getIterator();
|
||||||
|
|
||||||
$addressesIterator->uasort(
|
$addressesIterator->uasort(
|
||||||
@ -1201,6 +1295,10 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI
|
|||||||
* Validation callback that checks if the accompanying periods are valid
|
* Validation callback that checks if the accompanying periods are valid
|
||||||
*
|
*
|
||||||
* This method add violation errors.
|
* This method add violation errors.
|
||||||
|
*
|
||||||
|
* @Assert\Callback(
|
||||||
|
* groups={"accompanying_period_consistent"}
|
||||||
|
* )
|
||||||
*/
|
*/
|
||||||
public function isAccompanyingPeriodValid(ExecutionContextInterface $context)
|
public function isAccompanyingPeriodValid(ExecutionContextInterface $context)
|
||||||
{
|
{
|
||||||
@ -1246,6 +1344,10 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI
|
|||||||
* two addresses with the same validFrom date)
|
* two addresses with the same validFrom date)
|
||||||
*
|
*
|
||||||
* This method add violation errors.
|
* This method add violation errors.
|
||||||
|
*
|
||||||
|
* @Assert\Callback(
|
||||||
|
* groups={"addresses_consistent"}
|
||||||
|
* )
|
||||||
*/
|
*/
|
||||||
public function isAddressesValid(ExecutionContextInterface $context)
|
public function isAddressesValid(ExecutionContextInterface $context)
|
||||||
{
|
{
|
||||||
@ -1425,7 +1527,16 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI
|
|||||||
|
|
||||||
public function getCurrentHouseholdAddress(?\DateTimeImmutable $at = null): ?Address
|
public function getCurrentHouseholdAddress(?\DateTimeImmutable $at = null): ?Address
|
||||||
{
|
{
|
||||||
$at = $at === null ? new \DateTimeImmutable('today') : $at;
|
if (
|
||||||
|
NULL === $at
|
||||||
|
||
|
||||||
|
$at->format('Ymd') === (new \DateTime('today'))->format('Ymd')
|
||||||
|
) {
|
||||||
|
return $this->currentPersonAddress instanceof PersonCurrentAddress
|
||||||
|
? $this->currentPersonAddress->getAddress() : NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if not now, compute the date from history
|
||||||
$criteria = new Criteria();
|
$criteria = new Criteria();
|
||||||
$expr = Criteria::expr();
|
$expr = Criteria::expr();
|
||||||
|
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user